Updated pkglint to 5.4.3. Changes since 5.4.2: * Variables like ${VAR_${OTHER_VAR}} are no longer checked for use/define mismatch * The check for plural variable names has been removed * The type of variables called *DESTDIR is no longer guessed to be a directory name * The check for unknown shell commands is disabled in Makefile sections that depend on OPSYS * The experimental hand-written shell parser has been replaced with a Yacc-generated one * Meta packages don't need a LICENSE * When PKGNAME is defined in terms of ${DISTNAME:S/from/to/:tl}, more modifiers (like :tl) are handled properly * When the MAINTAINER or OWNER of a package is not the current user, a warning is printed for modified files * The check for share/applications/*.desktop has been disabled, since pkglint would need to inspect the file's actual contents to see whether desktopdb.mk must be included or not * SUBST_CLASSES may also be SUBST_CLASSES.NetBSD * Loosened the usage restrictions for several variables, e.g. many variables that may be appended in a Makefile may also be set unconditionally * PKG_OPTIONS_VAR must be of the form PKG_OPTIONS.*diff -r1.487 -r1.488 pkgsrc/pkgtools/pkglint/Makefile
(rillig)
@@ -1,41 +1,44 @@ | @@ -1,41 +1,44 @@ | |||
1 | # $NetBSD: Makefile,v 1.487 2016/06/19 18:03:29 wiz Exp $ | 1 | # $NetBSD: Makefile,v 1.488 2016/07/07 12:09:26 rillig Exp $ | |
2 | 2 | |||
3 | PKGNAME= pkglint-5.4.2 | 3 | PKGNAME= pkglint-5.4.3 | |
4 | DISTFILES= # none | 4 | DISTFILES= # none | |
5 | CATEGORIES= pkgtools | 5 | CATEGORIES= pkgtools | |
6 | 6 | |||
7 | OWNER= rillig@NetBSD.org | 7 | OWNER= rillig@NetBSD.org | |
8 | HOMEPAGE= http://www.NetBSD.org/docs/pkgsrc/ | 8 | HOMEPAGE= http://www.NetBSD.org/docs/pkgsrc/ | |
9 | COMMENT= Verifier for NetBSD packages | 9 | COMMENT= Verifier for NetBSD packages | |
10 | LICENSE= 2-clause-bsd | 10 | LICENSE= 2-clause-bsd | |
11 | CONFLICTS+= pkglint4-[0-9]* | 11 | CONFLICTS+= pkglint4-[0-9]* | |
12 | 12 | |||
13 | WRKSRC= ${WRKDIR}/netbsd.org/pkglint | 13 | WRKSRC= ${WRKDIR}/netbsd.org/pkglint | |
14 | NO_CHECKSUM= yes | 14 | NO_CHECKSUM= yes | |
15 | USE_LANGUAGES= # none | 15 | USE_LANGUAGES= # none | |
16 | USE_TOOLS+= pax | 16 | USE_TOOLS+= pax | |
17 | AUTO_MKDIRS= yes | 17 | AUTO_MKDIRS= yes | |
18 | GO_SRCPATH= netbsd.org/pkglint | 18 | GO_SRCPATH= netbsd.org/pkglint | |
19 | 19 | |||
20 | SUBST_CLASSES+= pkglint | 20 | SUBST_CLASSES+= pkglint | |
21 | SUBST_STAGE.pkglint= post-configure | 21 | SUBST_STAGE.pkglint= post-configure | |
22 | SUBST_FILES.pkglint+= main.go package_test.go | 22 | SUBST_FILES.pkglint+= main.go package_test.go | |
23 | SUBST_SED.pkglint+= -e s\|@VERSION@\|${PKGNAME:S/pkglint-//}\|g | 23 | SUBST_SED.pkglint+= -e s\|@VERSION@\|${PKGNAME:S/pkglint-//}\|g | |
24 | SUBST_SED.pkglint+= -e s\|@BMAKE@\|${MAKE:Q}\|g | 24 | SUBST_SED.pkglint+= -e s\|@BMAKE@\|${MAKE:Q}\|g | |
25 | 25 | |||
26 | do-extract: | 26 | do-extract: | |
27 | ${RUN} mkdir -p ${WRKDIR}/pkglint/plist-clash | 27 | ${RUN} mkdir -p ${WRKDIR}/pkglint/plist-clash | |
28 | ${RUN} cd ${FILESDIR} && ${PAX} -rw *.go */*.go pkglint.[01] ${WRKDIR}/pkglint | 28 | ${RUN} cd ${FILESDIR} && ${PAX} -rw *.go *.y */*.go pkglint.[01] ${WRKDIR}/pkglint | |
29 | ||||
30 | pre-build: | |||
31 | ${RUN} env GOPATH=${WRKDIR}:${BUILDLINK_DIR}/gopkg go generate ${GO_BUILD_PATTERN} | |||
29 | 32 | |||
30 | do-install: do-install-man | 33 | do-install: do-install-man | |
31 | 34 | |||
32 | .include "../../mk/bsd.prefs.mk" | 35 | .include "../../mk/bsd.prefs.mk" | |
33 | 36 | |||
34 | do-install-man: .PHONY | 37 | do-install-man: .PHONY | |
35 | .if !empty(MANINSTALL:Mcatinstall) | 38 | .if !empty(MANINSTALL:Mcatinstall) | |
36 | . if defined(CATMAN_SECTION_SUFFIX) && !empty(CATMAN_SECTION_SUFFIX:M[Yy][Ee][Ss]) | 39 | . if defined(CATMAN_SECTION_SUFFIX) && !empty(CATMAN_SECTION_SUFFIX:M[Yy][Ee][Ss]) | |
37 | ${INSTALL_MAN} ${WRKSRC}/pkglint.0 ${DESTDIR}${PREFIX}/${PKGMANDIR}/cat1/pkglint.1 | 40 | ${INSTALL_MAN} ${WRKSRC}/pkglint.0 ${DESTDIR}${PREFIX}/${PKGMANDIR}/cat1/pkglint.1 | |
38 | . else | 41 | . else | |
39 | ${INSTALL_MAN} ${WRKSRC}/pkglint.0 ${DESTDIR}${PREFIX}/${PKGMANDIR}/cat1 | 42 | ${INSTALL_MAN} ${WRKSRC}/pkglint.0 ${DESTDIR}${PREFIX}/${PKGMANDIR}/cat1 | |
40 | . endif | 43 | . endif | |
41 | .endif | 44 | .endif |
@@ -65,32 +65,38 @@ func (s *Suite) NewRawLines(args ...inte | @@ -65,32 +65,38 @@ func (s *Suite) NewRawLines(args ...inte | |||
65 | func (s *Suite) NewLines(fname string, texts ...string) []*Line { | 65 | func (s *Suite) NewLines(fname string, texts ...string) []*Line { | |
66 | result := make([]*Line, len(texts)) | 66 | result := make([]*Line, len(texts)) | |
67 | for i, text := range texts { | 67 | for i, text := range texts { | |
68 | textnl := text + "\n" | 68 | textnl := text + "\n" | |
69 | result[i] = NewLine(fname, i+1, text, s.NewRawLines(i+1, textnl)) | 69 | result[i] = NewLine(fname, i+1, text, s.NewRawLines(i+1, textnl)) | |
70 | } | 70 | } | |
71 | return result | 71 | return result | |
72 | } | 72 | } | |
73 | 73 | |||
74 | func (s *Suite) NewMkLines(fname string, lines ...string) *MkLines { | 74 | func (s *Suite) NewMkLines(fname string, lines ...string) *MkLines { | |
75 | return NewMkLines(s.NewLines(fname, lines...)) | 75 | return NewMkLines(s.NewLines(fname, lines...)) | |
76 | } | 76 | } | |
77 | 77 | |||
78 | func (s *Suite) DebugToStdout() { | 78 | func (s *Suite) BeginDebugToStdout() { | |
79 | G.debugOut = os.Stdout | 79 | G.debugOut = os.Stdout | |
80 | G.logOut = os.Stdout | 80 | G.logOut = os.Stdout | |
81 | G.opts.Debug = true | 81 | G.opts.Debug = true | |
82 | } | 82 | } | |
83 | 83 | |||
84 | func (s *Suite) EndDebugToStdout() { | |||
85 | G.debugOut = &s.stdout | |||
86 | G.logOut = &s.stdout | |||
87 | G.opts.Debug = false | |||
88 | } | |||
89 | ||||
84 | func (s *Suite) UseCommandLine(c *check.C, args ...string) { | 90 | func (s *Suite) UseCommandLine(c *check.C, args ...string) { | |
85 | exitcode := new(Pkglint).ParseCommandLine(append([]string{"pkglint"}, args...)) | 91 | exitcode := new(Pkglint).ParseCommandLine(append([]string{"pkglint"}, args...)) | |
86 | if exitcode != nil && *exitcode != 0 { | 92 | if exitcode != nil && *exitcode != 0 { | |
87 | c.Fatalf("Cannot parse command line: %#v", args) | 93 | c.Fatalf("Cannot parse command line: %#v", args) | |
88 | } | 94 | } | |
89 | G.opts.LogVerbose = true // See SetUpTest | 95 | G.opts.LogVerbose = true // See SetUpTest | |
90 | } | 96 | } | |
91 | 97 | |||
92 | func (s *Suite) RegisterMasterSite(varname string, urls ...string) { | 98 | func (s *Suite) RegisterMasterSite(varname string, urls ...string) { | |
93 | name2url := &G.globalData.MasterSiteVarToURL | 99 | name2url := &G.globalData.MasterSiteVarToURL | |
94 | url2name := &G.globalData.MasterSiteURLToVar | 100 | url2name := &G.globalData.MasterSiteURLToVar | |
95 | if *name2url == nil { | 101 | if *name2url == nil { | |
96 | *name2url = make(map[string]string) | 102 | *name2url = make(map[string]string) |
@@ -1,115 +1,30 @@ | @@ -1,115 +1,30 @@ | |||
1 | package main | 1 | package main | |
2 | 2 | |||
3 | // Parsing and checking shell commands embedded in Makefiles | 3 | // Parsing and checking shell commands embedded in Makefiles | |
4 | 4 | |||
5 | import ( | 5 | import ( | |
6 | "path" | 6 | "path" | |
7 | "strings" | 7 | "strings" | |
8 | ) | 8 | ) | |
9 | 9 | |||
10 | const ( | 10 | const ( | |
11 | reShellToken = `^\s*(` + | |||
12 | `#.*` + // shell comment | |||
13 | `|(?:` + | |||
14 | `'[^']*'` + // single quoted string | |||
15 | "|\"`[^`]+`\"" + // backticks command execution in double quotes | |||
16 | `|"(?:\\.|[^"])*"` + // double quoted string | |||
17 | "|`[^`]*`" + // backticks command execution (very simple case) | |||
18 | `|\\\$\$` + // a shell-escaped dollar sign | |||
19 | `|\\[^\$]` + // other escaped characters | |||
20 | `|\$[\w_]` + // one-character make(1) variable | |||
21 | `|\$\$[0-9A-Z_a-z]+` + // shell variable | |||
22 | `|\$\$[!#?@]` + // special shell variables | |||
23 | `|\$\$[./]` + // unescaped dollar in shell, followed by punctuation | |||
24 | `|\$\$\$\$` + // the special pid shell variable | |||
25 | `|\$\$\{[0-9A-Z_a-z]+[#%:]?[^}]*\}` + // shell variable in braces | |||
26 | `|[^\(\)'\"\\\s;&\|<>` + "`" + `\$]` + // non-special character | |||
27 | `|\$\{[^\s\"'` + "`" + `]+` + // HACK: nested make(1) variables | |||
28 | `)+` + // any of the above may be repeated | |||
29 | `|\$\$\(` + // POSIX-style backticks replacement | |||
30 | `|;;?` + | |||
31 | `|&&?` + | |||
32 | `|\|\|?` + | |||
33 | `|\(` + | |||
34 | `|\)` + | |||
35 | `|>&` + | |||
36 | `|<<?` + | |||
37 | `|>>?` + | |||
38 | `|#.*)` | |||
39 | reShVarassign = `^([A-Z_a-z]\w*)=` | 11 | reShVarassign = `^([A-Z_a-z]\w*)=` | |
40 | reShVarname = `(?:[!#*\-\d?@]|\$\$|[A-Za-z_]\w*)` | 12 | reShVarname = `(?:[!#*\-\d?@]|\$\$|[A-Za-z_]\w*)` | |
41 | reShVarexpansion = `(?:(?:#|##|%|%%|:-|:=|:\?|:\+|\+)[^$\\{}]*)` | 13 | reShVarexpansion = `(?:(?:#|##|%|%%|:-|:=|:\?|:\+|\+)[^$\\{}]*)` | |
42 | reShVaruse = `\$\$` + `(?:` + reShVarname + `|` + `\{` + reShVarname + `(?:` + reShVarexpansion + `)?` + `\})` | 14 | reShVaruse = `\$\$` + `(?:` + reShVarname + `|` + `\{` + reShVarname + `(?:` + reShVarexpansion + `)?` + `\})` | |
43 | reShDollar = `\\\$\$|` + reShVaruse + `|\$\$[,\-/|]` | 15 | reShDollar = `\\\$\$|` + reShVaruse + `|\$\$[,\-/|]` | |
44 | ) | 16 | ) | |
45 | 17 | |||
46 | // ShellCommandState | |||
47 | type scState uint8 | |||
48 | ||||
49 | const ( | |||
50 | scstStart scState = iota | |||
51 | scstCont | |||
52 | scstInstall | |||
53 | scstInstallD | |||
54 | scstMkdir | |||
55 | scstPax | |||
56 | scstPaxS | |||
57 | scstSed | |||
58 | scstSedE | |||
59 | scstSet | |||
60 | scstSetCont | |||
61 | scstCond | |||
62 | scstCondCont | |||
63 | scstCase | |||
64 | scstCaseIn | |||
65 | scstCaseLabel | |||
66 | scstCaseLabelCont | |||
67 | scstFor | |||
68 | scstForIn | |||
69 | scstForCont | |||
70 | scstEcho | |||
71 | scstInstallDir | |||
72 | scstInstallDir2 | |||
73 | ) | |||
74 | ||||
75 | func (st scState) String() string { | |||
76 | return [...]string{ | |||
77 | "start", | |||
78 | "continuation", | |||
79 | "install", | |||
80 | "install -d", | |||
81 | "mkdir", | |||
82 | "pax", | |||
83 | "pax -s", | |||
84 | "sed", | |||
85 | "sed -e", | |||
86 | "set", | |||
87 | "set-continuation", | |||
88 | "cond", | |||
89 | "cond-continuation", | |||
90 | "case", | |||
91 | "case in", | |||
92 | "case label", | |||
93 | "case-label-continuation", | |||
94 | "for", | |||
95 | "for-in", | |||
96 | "for-continuation", | |||
97 | "echo", | |||
98 | "install-dir", | |||
99 | "install-dir2", | |||
100 | }[st] | |||
101 | } | |||
102 | ||||
103 | type ShellLine struct { | 18 | type ShellLine struct { | |
104 | line *Line | 19 | line *Line | |
105 | mkline *MkLine | 20 | mkline *MkLine | |
106 | } | 21 | } | |
107 | 22 | |||
108 | func NewShellLine(mkline *MkLine) *ShellLine { | 23 | func NewShellLine(mkline *MkLine) *ShellLine { | |
109 | return &ShellLine{mkline.Line, mkline} | 24 | return &ShellLine{mkline.Line, mkline} | |
110 | } | 25 | } | |
111 | 26 | |||
112 | var shellcommandsContextType = &Vartype{lkNone, CheckvarShellCommands, []AclEntry{{"*", aclpAllRuntime}}, false} | 27 | var shellcommandsContextType = &Vartype{lkNone, CheckvarShellCommands, []AclEntry{{"*", aclpAllRuntime}}, false} | |
113 | var shellwordVuc = &VarUseContext{shellcommandsContextType, vucTimeUnknown, vucQuotPlain, vucExtentWord} | 28 | var shellwordVuc = &VarUseContext{shellcommandsContextType, vucTimeUnknown, vucQuotPlain, vucExtentWord} | |
114 | 29 | |||
115 | func (shline *ShellLine) CheckWord(token string, checkQuoting bool) { | 30 | func (shline *ShellLine) CheckWord(token string, checkQuoting bool) { | |
@@ -345,32 +260,26 @@ func (shline *ShellLine) unescapeBacktic | @@ -345,32 +260,26 @@ func (shline *ShellLine) unescapeBacktic | |||
345 | return unescaped, quoting | 260 | return unescaped, quoting | |
346 | } | 261 | } | |
347 | 262 | |||
348 | func (shline *ShellLine) variableNeedsQuoting(shvarname string) bool { | 263 | func (shline *ShellLine) variableNeedsQuoting(shvarname string) bool { | |
349 | switch shvarname { | 264 | switch shvarname { | |
350 | case "#", "?": | 265 | case "#", "?": | |
351 | return false // Definitely ok | 266 | return false // Definitely ok | |
352 | case "d", "f", "i", "dir", "file", "src", "dst": | 267 | case "d", "f", "i", "dir", "file", "src", "dst": | |
353 | return false // Probably ok | 268 | return false // Probably ok | |
354 | } | 269 | } | |
355 | return true | 270 | return true | |
356 | } | 271 | } | |
357 | 272 | |||
358 | type ShelltextContext struct { | |||
359 | shline *ShellLine | |||
360 | state scState | |||
361 | shellword string | |||
362 | } | |||
363 | ||||
364 | func (shline *ShellLine) CheckShellCommandLine(shelltext string) { | 273 | func (shline *ShellLine) CheckShellCommandLine(shelltext string) { | |
365 | if G.opts.Debug { | 274 | if G.opts.Debug { | |
366 | defer tracecall1(shelltext)() | 275 | defer tracecall1(shelltext)() | |
367 | } | 276 | } | |
368 | 277 | |||
369 | line := shline.line | 278 | line := shline.line | |
370 | 279 | |||
371 | if contains(shelltext, "${SED}") && contains(shelltext, "${MV}") { | 280 | if contains(shelltext, "${SED}") && contains(shelltext, "${MV}") { | |
372 | line.Note0("Please use the SUBST framework instead of ${SED} and ${MV}.") | 281 | line.Note0("Please use the SUBST framework instead of ${SED} and ${MV}.") | |
373 | Explain( | 282 | Explain( | |
374 | "Using the SUBST framework instead of explicit commands is easier", | 283 | "Using the SUBST framework instead of explicit commands is easier", | |
375 | "to understand, since all the complexity of using sed and mv is", | 284 | "to understand, since all the complexity of using sed and mv is", | |
376 | "hidden behind the scenes.", | 285 | "hidden behind the scenes.", | |
@@ -401,109 +310,93 @@ func (shline *ShellLine) CheckShellComma | @@ -401,109 +310,93 @@ func (shline *ShellLine) CheckShellComma | |||
401 | shline.checkHiddenAndSuppress(repl.m[0], repl.rest) | 310 | shline.checkHiddenAndSuppress(repl.m[0], repl.rest) | |
402 | } | 311 | } | |
403 | setE := false | 312 | setE := false | |
404 | if repl.AdvanceStr("${RUN}") { | 313 | if repl.AdvanceStr("${RUN}") { | |
405 | setE = true | 314 | setE = true | |
406 | } else { | 315 | } else { | |
407 | repl.AdvanceStr("${_PKG_SILENT}${_PKG_DEBUG}") | 316 | repl.AdvanceStr("${_PKG_SILENT}${_PKG_DEBUG}") | |
408 | } | 317 | } | |
409 | 318 | |||
410 | shline.CheckShellCommand(repl.rest, &setE) | 319 | shline.CheckShellCommand(repl.rest, &setE) | |
411 | } | 320 | } | |
412 | 321 | |||
413 | func (shline *ShellLine) CheckShellCommand(shellcmd string, pSetE *bool) { | 322 | func (shline *ShellLine) CheckShellCommand(shellcmd string, pSetE *bool) { | |
414 | if false { | 323 | if G.opts.Debug { | |
415 | p := NewMkShParser(shline.line, shellcmd, false) | 324 | defer tracecall()() | |
416 | cmds := p.Program() | |||
417 | rest := p.tok.parser.Rest() | |||
418 | if rest != "" { | |||
419 | traceStep("shellcmd=%q", shellcmd) | |||
420 | if cmds != nil { | |||
421 | for _, andor := range cmds.AndOrs { | |||
422 | traceStep("AndOr %v", andor) | |||
423 | } | |||
424 | } | |||
425 | shline.line.Warnf("Pkglint parse error in ShellLine.CheckShellCommand at %q", p.peekText()+rest) | |||
426 | } | |||
427 | } | 325 | } | |
428 | 326 | |||
429 | state := scstStart | 327 | program, err := parseShellProgram(shline.line, shellcmd) | |
430 | tokens, rest := splitIntoShellTokens(shline.line, shellcmd) | 328 | if err != nil && contains(shellcmd, "$$(") { // Hack until the shell parser can handle subshells. | |
431 | if rest != "" { | 329 | shline.line.Warn0("Invoking subshells via $(...) is not portable enough.") | |
432 | shline.line.Warnf("Pkglint parse error in ShellLine.CheckShellCommand at %q (state=%s)", rest, state) | 330 | return | |
331 | } | |||
332 | if err != nil { | |||
333 | shline.line.Warnf("Pkglint ShellLine.CheckShellCommand: %s", err) | |||
334 | return | |||
433 | } | 335 | } | |
434 | 336 | |||
435 | prevToken := "" | 337 | spc := &ShellProgramChecker{shline} | |
436 | for _, token := range tokens { | 338 | spc.checkConditionalCd(program) | |
437 | if G.opts.Debug { | |||
438 | traceStep("checkShellCommand state=%v token=%q", state, token) | |||
439 | } | |||
440 | 339 | |||
441 | { | 340 | (*MkShWalker).Walk(nil, program, func(node interface{}) { | |
442 | noQuotingNeeded := state == scstCase || | 341 | if cmd, ok := node.(*MkShSimpleCommand); ok { | |
443 | state == scstForCont || | 342 | scc := NewSimpleCommandChecker(shline, cmd) | |
444 | state == scstSetCont || | 343 | scc.Check() | |
445 | (state == scstStart && matches(token, reShVarassign)) | 344 | if scc.strcmd.Name == "set" && scc.strcmd.AnyArgMatches(`^-.*e`) { | |
446 | shline.CheckWord(token, !noQuotingNeeded) | 345 | *pSetE = true | |
346 | } | |||
447 | } | 347 | } | |
448 | 348 | |||
449 | st := &ShelltextContext{shline, state, token} | 349 | if cmd, ok := node.(*MkShList); ok { | |
450 | st.checkCommandStart() | 350 | spc.checkSetE(cmd, pSetE) | |
451 | st.checkConditionalCd() | |||
452 | if state != scstPaxS && state != scstSedE && state != scstCaseLabel { | |||
453 | shline.line.CheckAbsolutePathname(token) | |||
454 | } | 351 | } | |
455 | st.checkAutoMkdirs() | |||
456 | st.checkInstallMulti() | |||
457 | st.checkPaxPe() | |||
458 | st.checkQuoteSubstitution() | |||
459 | st.checkEchoN() | |||
460 | st.checkPipeExitcode() | |||
461 | st.checkSetE(pSetE, prevToken) | |||
462 | 352 | |||
463 | if state == scstSet && hasPrefix(token, "-") && contains(token, "e") || state == scstStart && token == "${RUN}" { | 353 | if cmd, ok := node.(*MkShPipeline); ok { | |
464 | *pSetE = true | 354 | spc.checkPipeExitcode(shline.line, cmd) | |
465 | } | 355 | } | |
466 | 356 | |||
467 | state = shline.nextState(state, token) | 357 | if word, ok := node.(*ShToken); ok { | |
468 | prevToken = token | 358 | spc.checkWord(word, false) | |
469 | } | 359 | } | |
360 | }) | |||
470 | } | 361 | } | |
471 | 362 | |||
472 | func (shline *ShellLine) CheckShellCommands(shellcmds string) { | 363 | func (shline *ShellLine) CheckShellCommands(shellcmds string) { | |
473 | setE := true | 364 | setE := true | |
474 | shline.CheckShellCommand(shellcmds, &setE) | 365 | shline.CheckShellCommand(shellcmds, &setE) | |
475 | if !hasSuffix(shellcmds, ";") { | 366 | if !hasSuffix(shellcmds, ";") { | |
476 | shline.line.Warn0("This shell command list should end with a semicolon.") | 367 | shline.line.Warn0("This shell command list should end with a semicolon.") | |
477 | } | 368 | } | |
478 | } | 369 | } | |
479 | 370 | |||
480 | func (shline *ShellLine) checkHiddenAndSuppress(hiddenAndSuppress, rest string) { | 371 | func (shline *ShellLine) checkHiddenAndSuppress(hiddenAndSuppress, rest string) { | |
481 | if G.opts.Debug { | 372 | if G.opts.Debug { | |
482 | defer tracecall(hiddenAndSuppress, rest)() | 373 | defer tracecall(hiddenAndSuppress, rest)() | |
483 | } | 374 | } | |
484 | 375 | |||
485 | switch { | 376 | switch { | |
486 | case !contains(hiddenAndSuppress, "@"): | 377 | case !contains(hiddenAndSuppress, "@"): | |
487 | // Nothing is hidden at all. | 378 | // Nothing is hidden at all. | |
488 | 379 | |||
489 | case hasPrefix(G.Mk.target, "show-") || hasSuffix(G.Mk.target, "-message"): | 380 | case hasPrefix(G.Mk.target, "show-") || hasSuffix(G.Mk.target, "-message"): | |
490 | // In these targets, all commands may be hidden. | 381 | // In these targets, all commands may be hidden. | |
491 | 382 | |||
492 | case hasPrefix(rest, "#"): | 383 | case hasPrefix(rest, "#"): | |
493 | // Shell comments may be hidden, since they cannot have side effects. | 384 | // Shell comments may be hidden, since they cannot have side effects. | |
494 | 385 | |||
495 | default: | 386 | default: | |
496 | if m, cmd := match1(rest, reShellToken); m { | 387 | tokens, _ := splitIntoShellTokens(shline.line, rest) | |
388 | if len(tokens) > 0 { | |||
389 | cmd := tokens[0] | |||
497 | switch cmd { | 390 | switch cmd { | |
498 | case "${DELAYED_ERROR_MSG}", "${DELAYED_WARNING_MSG}", | 391 | case "${DELAYED_ERROR_MSG}", "${DELAYED_WARNING_MSG}", | |
499 | "${DO_NADA}", | 392 | "${DO_NADA}", | |
500 | "${ECHO}", "${ECHO_MSG}", "${ECHO_N}", "${ERROR_CAT}", "${ERROR_MSG}", | 393 | "${ECHO}", "${ECHO_MSG}", "${ECHO_N}", "${ERROR_CAT}", "${ERROR_MSG}", | |
501 | "${FAIL_MSG}", | 394 | "${FAIL_MSG}", | |
502 | "${PHASE_MSG}", "${PRINTF}", | 395 | "${PHASE_MSG}", "${PRINTF}", | |
503 | "${SHCOMMENT}", "${STEP_MSG}", | 396 | "${SHCOMMENT}", "${STEP_MSG}", | |
504 | "${WARNING_CAT}", "${WARNING_MSG}": | 397 | "${WARNING_CAT}", "${WARNING_MSG}": | |
505 | break | 398 | break | |
506 | default: | 399 | default: | |
507 | shline.line.Warn1("The shell command %q should not be hidden.", cmd) | 400 | shline.line.Warn1("The shell command %q should not be hidden.", cmd) | |
508 | Explain( | 401 | Explain( | |
509 | "Hidden shell commands do not appear on the terminal or in the log", | 402 | "Hidden shell commands do not appear on the terminal or in the log", | |
@@ -515,129 +408,158 @@ func (shline *ShellLine) checkHiddenAndS | @@ -515,129 +408,158 @@ func (shline *ShellLine) checkHiddenAndS | |||
515 | "PKG_DEBUG_LEVEL is set.") | 408 | "PKG_DEBUG_LEVEL is set.") | |
516 | } | 409 | } | |
517 | } | 410 | } | |
518 | } | 411 | } | |
519 | 412 | |||
520 | if contains(hiddenAndSuppress, "-") { | 413 | if contains(hiddenAndSuppress, "-") { | |
521 | shline.line.Warn0("Using a leading \"-\" to suppress errors is deprecated.") | 414 | shline.line.Warn0("Using a leading \"-\" to suppress errors is deprecated.") | |
522 | Explain2( | 415 | Explain2( | |
523 | "If you really want to ignore any errors from this command, append", | 416 | "If you really want to ignore any errors from this command, append", | |
524 | "\"|| ${TRUE}\" to the command.") | 417 | "\"|| ${TRUE}\" to the command.") | |
525 | } | 418 | } | |
526 | } | 419 | } | |
527 | 420 | |||
528 | func (ctx *ShelltextContext) checkCommandStart() { | 421 | type SimpleCommandChecker struct { | |
422 | shline *ShellLine | |||
423 | cmd *MkShSimpleCommand | |||
424 | strcmd *StrCommand | |||
425 | } | |||
426 | ||||
427 | func NewSimpleCommandChecker(shline *ShellLine, cmd *MkShSimpleCommand) *SimpleCommandChecker { | |||
428 | strcmd := NewStrCommand(cmd) | |||
429 | return &SimpleCommandChecker{shline, cmd, strcmd} | |||
430 | ||||
431 | } | |||
432 | ||||
433 | func (c *SimpleCommandChecker) Check() { | |||
529 | if G.opts.Debug { | 434 | if G.opts.Debug { | |
530 | defer tracecall2(ctx.state.String(), ctx.shellword)() | 435 | defer tracecall(c.strcmd)() | |
531 | } | 436 | } | |
532 | 437 | |||
533 | state, shellword := ctx.state, ctx.shellword | 438 | c.checkCommandStart() | |
534 | if state != scstStart && state != scstCond { | 439 | c.checkAbsolutePathnames() | |
535 | return | 440 | c.checkAutoMkdirs() | |
441 | c.checkInstallMulti() | |||
442 | c.checkPaxPe() | |||
443 | c.checkEchoN() | |||
444 | } | |||
445 | ||||
446 | func (ctx *SimpleCommandChecker) checkCommandStart() { | |||
447 | if G.opts.Debug { | |||
448 | defer tracecall()() | |||
536 | } | 449 | } | |
537 | 450 | |||
451 | shellword := ctx.strcmd.Name | |||
538 | switch { | 452 | switch { | |
539 | case shellword == "${RUN}": | 453 | case shellword == "${RUN}" || shellword == "": | |
540 | case ctx.handleForbiddenCommand(): | 454 | case ctx.handleForbiddenCommand(): | |
541 | case ctx.handleTool(): | 455 | case ctx.handleTool(): | |
542 | case ctx.handleCommandVariable(): | 456 | case ctx.handleCommandVariable(): | |
543 | case matches(shellword, `^(?:\$\$\(|\(|\)|:|;|;;|&&|\|\||\{|\}|break|case|cd|continue|do|done|elif|else|esac|eval|exec|exit|export|fi|for|if|read|set|shift|then|umask|unset|while)$`): | 457 | case matches(shellword, `^(?::|break|cd|continue|eval|exec|exit|export|read|set|shift|umask|unset)$`): | |
544 | case matches(shellword, `^\w+=`): // Variable assignment | |||
545 | case hasPrefix(shellword, "./"): // All commands from the current directory are fine. | 458 | case hasPrefix(shellword, "./"): // All commands from the current directory are fine. | |
546 | case hasPrefix(shellword, "${PKGSRCDIR"): // With or without the :Q modifier | 459 | case hasPrefix(shellword, "${PKGSRCDIR"): // With or without the :Q modifier | |
547 | case ctx.handleComment(): | 460 | case ctx.handleComment(): | |
548 | default: | 461 | default: | |
549 | if G.opts.WarnExtra { | 462 | if G.opts.WarnExtra && !(G.Mk != nil && G.Mk.indentation.DependsOn("OPSYS")) { | |
550 | ctx.shline.line.Warn1("Unknown shell command %q.", shellword) | 463 | ctx.shline.line.Warn1("Unknown shell command %q.", shellword) | |
551 | Explain3( | 464 | Explain3( | |
552 | "If you want your package to be portable to all platforms that pkgsrc", | 465 | "If you want your package to be portable to all platforms that pkgsrc", | |
553 | "supports, you should only use shell commands that are covered by the", | 466 | "supports, you should only use shell commands that are covered by the", | |
554 | "tools framework.") | 467 | "tools framework.") | |
555 | } | 468 | } | |
556 | } | 469 | } | |
557 | } | 470 | } | |
558 | 471 | |||
559 | func (ctx *ShelltextContext) handleTool() bool { | 472 | func (ctx *SimpleCommandChecker) handleTool() bool { | |
560 | if G.opts.Debug { | 473 | if G.opts.Debug { | |
561 | defer tracecall1(ctx.shellword)() | 474 | defer tracecall()() | |
562 | } | 475 | } | |
563 | 476 | |||
564 | shellword := ctx.shellword | 477 | shellword := ctx.strcmd.Name | |
565 | tool := G.globalData.Tools.byName[shellword] | 478 | tool := G.globalData.Tools.byName[shellword] | |
566 | if tool == nil { | 479 | if tool == nil { | |
567 | return false | 480 | return false | |
568 | } | 481 | } | |
569 | 482 | |||
570 | if !G.Mk.tools[shellword] && !G.Mk.tools["g"+shellword] { | 483 | if !G.Mk.tools[shellword] && !G.Mk.tools["g"+shellword] { | |
571 | ctx.shline.line.Warn1("The %q tool is used but not added to USE_TOOLS.", shellword) | 484 | ctx.shline.line.Warn1("The %q tool is used but not added to USE_TOOLS.", shellword) | |
572 | } | 485 | } | |
573 | 486 | |||
574 | if tool.MustUseVarForm { | 487 | if tool.MustUseVarForm { | |
575 | ctx.shline.line.Warn2("Please use \"${%s}\" instead of %q.", tool.Varname, shellword) | 488 | ctx.shline.line.Warn2("Please use \"${%s}\" instead of %q.", tool.Varname, shellword) | |
576 | } | 489 | } | |
577 | 490 | |||
578 | ctx.shline.checkCommandUse(shellword) | 491 | ctx.shline.checkCommandUse(shellword) | |
579 | return true | 492 | return true | |
580 | } | 493 | } | |
581 | 494 | |||
582 | func (ctx *ShelltextContext) handleForbiddenCommand() bool { | 495 | func (ctx *SimpleCommandChecker) handleForbiddenCommand() bool { | |
583 | switch path.Base(ctx.shellword) { | 496 | if G.opts.Debug { | |
497 | defer tracecall()() | |||
498 | } | |||
499 | ||||
500 | shellword := ctx.strcmd.Name | |||
501 | switch path.Base(shellword) { | |||
584 | case "ktrace", "mktexlsr", "strace", "texconfig", "truss": | 502 | case "ktrace", "mktexlsr", "strace", "texconfig", "truss": | |
585 | ctx.shline.line.Error1("%q must not be used in Makefiles.", ctx.shellword) | 503 | ctx.shline.line.Error1("%q must not be used in Makefiles.", shellword) | |
586 | Explain3( | 504 | Explain3( | |
587 | "This command must appear in INSTALL scripts, not in the package", | 505 | "This command must appear in INSTALL scripts, not in the package", | |
588 | "Makefile, so that the package also works if it is installed as a binary", | 506 | "Makefile, so that the package also works if it is installed as a binary", | |
589 | "package via pkg_add.") | 507 | "package via pkg_add.") | |
590 | return true | 508 | return true | |
591 | } | 509 | } | |
592 | return false | 510 | return false | |
593 | } | 511 | } | |
594 | 512 | |||
595 | func (ctx *ShelltextContext) handleCommandVariable() bool { | 513 | func (ctx *SimpleCommandChecker) handleCommandVariable() bool { | |
596 | if G.opts.Debug { | 514 | if G.opts.Debug { | |
597 | defer tracecall1(ctx.shellword)() | 515 | defer tracecall()() | |
598 | } | 516 | } | |
599 | 517 | |||
600 | shellword := ctx.shellword | 518 | shellword := ctx.strcmd.Name | |
601 | if m, varname := match1(shellword, `^\$\{([\w_]+)\}$`); m { | 519 | if m, varname := match1(shellword, `^\$\{([\w_]+)\}$`); m { | |
602 | 520 | |||
603 | if tool := G.globalData.Tools.byVarname[varname]; tool != nil { | 521 | if tool := G.globalData.Tools.byVarname[varname]; tool != nil { | |
604 | if !G.Mk.tools[tool.Name] { | 522 | if !G.Mk.tools[tool.Name] { | |
605 | ctx.shline.line.Warn1("The %q tool is used but not added to USE_TOOLS.", tool.Name) | 523 | ctx.shline.line.Warn1("The %q tool is used but not added to USE_TOOLS.", tool.Name) | |
606 | } | 524 | } | |
607 | ctx.shline.checkCommandUse(shellword) | 525 | ctx.shline.checkCommandUse(shellword) | |
608 | return true | 526 | return true | |
609 | } | 527 | } | |
610 | 528 | |||
611 | if vartype := ctx.shline.mkline.getVariableType(varname); vartype != nil && vartype.checker.name == "ShellCommand" { | 529 | if vartype := ctx.shline.mkline.getVariableType(varname); vartype != nil && vartype.checker.name == "ShellCommand" { | |
612 | ctx.shline.checkCommandUse(shellword) | 530 | ctx.shline.checkCommandUse(shellword) | |
613 | return true | 531 | return true | |
614 | } | 532 | } | |
615 | 533 | |||
616 | // When the package author has explicitly defined a command | 534 | // When the package author has explicitly defined a command | |
617 | // variable, assume it to be valid. | 535 | // variable, assume it to be valid. | |
618 | if G.Pkg != nil && G.Pkg.vardef[varname] != nil { | 536 | if G.Pkg != nil && G.Pkg.vardef[varname] != nil { | |
619 | return true | 537 | return true | |
620 | } | 538 | } | |
621 | } | 539 | } | |
622 | return false | 540 | return false | |
623 | } | 541 | } | |
624 | 542 | |||
625 | func (ctx *ShelltextContext) handleComment() bool { | 543 | func (ctx *SimpleCommandChecker) handleComment() bool { | |
626 | if G.opts.Debug { | 544 | if G.opts.Debug { | |
627 | defer tracecall1(ctx.shellword)() | 545 | defer tracecall()() | |
546 | } | |||
547 | ||||
548 | shellword := ctx.strcmd.Name | |||
549 | if G.opts.Debug { | |||
550 | defer tracecall1(shellword)() | |||
628 | } | 551 | } | |
629 | 552 | |||
630 | shellword := ctx.shellword | |||
631 | if !hasPrefix(shellword, "#") { | 553 | if !hasPrefix(shellword, "#") { | |
632 | return false | 554 | return false | |
633 | } | 555 | } | |
634 | 556 | |||
635 | semicolon := contains(shellword, ";") | 557 | semicolon := contains(shellword, ";") | |
636 | multiline := ctx.shline.line.IsMultiline() | 558 | multiline := ctx.shline.line.IsMultiline() | |
637 | 559 | |||
638 | if semicolon { | 560 | if semicolon { | |
639 | ctx.shline.line.Warn0("A shell comment should not contain semicolons.") | 561 | ctx.shline.line.Warn0("A shell comment should not contain semicolons.") | |
640 | } | 562 | } | |
641 | if multiline { | 563 | if multiline { | |
642 | ctx.shline.line.Warn0("A shell comment does not stop at the end of line.") | 564 | ctx.shline.line.Warn0("A shell comment does not stop at the end of line.") | |
643 | } | 565 | } | |
@@ -650,143 +572,249 @@ func (ctx *ShelltextContext) handleComme | @@ -650,143 +572,249 @@ func (ctx *ShelltextContext) handleComme | |||
650 | "it _looks_ like that the comment only spans one line in the", | 572 | "it _looks_ like that the comment only spans one line in the", | |
651 | "Makefile, in fact it spans until the end of the whole shell command.", | 573 | "Makefile, in fact it spans until the end of the whole shell command.", | |
652 | "", | 574 | "", | |
653 | "To insert a comment into shell code, you can write it like this:", | 575 | "To insert a comment into shell code, you can write it like this:", | |
654 | "", | 576 | "", | |
655 | "\t"+"${SHCOMMENT} \"The following command might fail; this is ok.\"", | 577 | "\t"+"${SHCOMMENT} \"The following command might fail; this is ok.\"", | |
656 | "", | 578 | "", | |
657 | "Note that any special characters in the comment are still", | 579 | "Note that any special characters in the comment are still", | |
658 | "interpreted by the shell.") | 580 | "interpreted by the shell.") | |
659 | } | 581 | } | |
660 | return true | 582 | return true | |
661 | } | 583 | } | |
662 | 584 | |||
663 | func (ctx *ShelltextContext) checkConditionalCd() { | 585 | type ShellProgramChecker struct { | |
664 | if ctx.state == scstCond && ctx.shellword == "cd" { | 586 | shline *ShellLine | |
665 | ctx.shline.line.Error0("The Solaris /bin/sh cannot handle \"cd\" inside conditionals.") | |||
666 | Explain3( | |||
667 | "When the Solaris shell is in \"set -e\" mode and \"cd\" fails, the", | |||
668 | "shell will exit, no matter if it is protected by an \"if\" or the", | |||
669 | "\"||\" operator.") | |||
670 | } | |||
671 | } | 587 | } | |
672 | 588 | |||
673 | func (ctx *ShelltextContext) checkAutoMkdirs() { | 589 | func (c *ShellProgramChecker) checkConditionalCd(list *MkShList) { | |
674 | state, shellword := ctx.state, ctx.shellword | 590 | if G.opts.Debug { | |
591 | defer tracecall()() | |||
592 | } | |||
675 | 593 | |||
676 | line := ctx.shline.line | 594 | getSimple := func(list *MkShList) *MkShSimpleCommand { | |
677 | if (state == scstInstallD || state == scstMkdir) && matches(shellword, `^(?:\$\{DESTDIR\})?\$\{PREFIX(?:|:Q)\}/`) { | 595 | if len(list.AndOrs) == 1 { | |
678 | line.Warn1("Please use AUTO_MKDIRS instead of %q.", | 596 | if len(list.AndOrs[0].Pipes) == 1 { | |
679 | ifelseStr(state == scstMkdir, "${MKDIR}", "${INSTALL} -d")) | 597 | if len(list.AndOrs[0].Pipes[0].Cmds) == 1 { | |
680 | Explain4( | 598 | return list.AndOrs[0].Pipes[0].Cmds[0].Simple | |
681 | "Setting AUTO_MKDIRS=yes automatically creates all directories that", | 599 | } | |
682 | "are mentioned in the PLIST. If you need additional directories,", | 600 | } | |
683 | "specify them in INSTALLATION_DIRS, which is a list of directories", | 601 | } | |
684 | "relative to ${PREFIX}.") | 602 | return nil | |
685 | } | 603 | } | |
686 | 604 | |||
687 | if (state == scstInstallDir || state == scstInstallDir2) && !contains(shellword, "$$") { | 605 | checkConditionalCd := func(cmd *MkShSimpleCommand) { | |
688 | if m, dirname := match1(shellword, `^(?:\$\{DESTDIR\})?\$\{PREFIX(?:|:Q)\}/(.*)`); m { | 606 | if NewStrCommand(cmd).Name == "cd" { | |
689 | line.Note1("You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= %s\" instead of this command.", dirname) | 607 | c.shline.line.Error0("The Solaris /bin/sh cannot handle \"cd\" inside conditionals.") | |
690 | Explain( | 608 | Explain3( | |
691 | "Many packages include a list of all needed directories in their", | 609 | "When the Solaris shell is in \"set -e\" mode and \"cd\" fails, the", | |
692 | "PLIST file. In such a case, you can just set AUTO_MKDIRS=yes and", | 610 | "shell will exit, no matter if it is protected by an \"if\" or the", | |
693 | "be done. The pkgsrc infrastructure will then create all directories", | 611 | "\"||\" operator.") | |
694 | "in advance.", | |||
695 | "", | |||
696 | "To create directories that are not mentioned in the PLIST file, it", | |||
697 | "is easier to just list them in INSTALLATION_DIRS than to execute the", | |||
698 | "commands explicitly. That way, you don't have to think about which", | |||
699 | "of the many INSTALL_*_DIR variables is appropriate, since", | |||
700 | "INSTALLATION_DIRS takes care of that.") | |||
701 | } | 612 | } | |
702 | } | 613 | } | |
614 | ||||
615 | (*MkShWalker).Walk(nil, list, func(node interface{}) { | |||
616 | if cmd, ok := node.(*MkShIfClause); ok { | |||
617 | for _, cond := range cmd.Conds { | |||
618 | if simple := getSimple(cond); simple != nil { | |||
619 | checkConditionalCd(simple) | |||
620 | } | |||
621 | } | |||
622 | } | |||
623 | if cmd, ok := node.(*MkShLoopClause); ok { | |||
624 | if simple := getSimple(cmd.Cond); simple != nil { | |||
625 | checkConditionalCd(simple) | |||
626 | } | |||
627 | } | |||
628 | }) | |||
703 | } | 629 | } | |
704 | 630 | |||
705 | func (ctx *ShelltextContext) checkInstallMulti() { | 631 | func (c *ShellProgramChecker) checkWords(words []*ShToken, checkQuoting bool) { | |
706 | if ctx.state == scstInstallDir2 && hasPrefix(ctx.shellword, "$") { | 632 | if G.opts.Debug { | |
707 | line := ctx.shline.line | 633 | defer tracecall()() | |
708 | line.Warn0("The INSTALL_*_DIR commands can only handle one directory at a time.") | 634 | } | |
709 | Explain2( | 635 | ||
710 | "Many implementations of install(1) can handle more, but pkgsrc aims", | 636 | for _, word := range words { | |
711 | "at maximum portability.") | 637 | c.checkWord(word, checkQuoting) | |
712 | } | 638 | } | |
713 | } | 639 | } | |
714 | 640 | |||
715 | func (ctx *ShelltextContext) checkPaxPe() { | 641 | func (c *ShellProgramChecker) checkWord(word *ShToken, checkQuoting bool) { | |
716 | if ctx.state == scstPax && ctx.shellword == "-pe" { | 642 | if G.opts.Debug { | |
717 | line := ctx.shline.line | 643 | defer tracecall(word.MkText)() | |
718 | line.Warn0("Please use the -pp option to pax(1) instead of -pe.") | |||
719 | Explain3( | |||
720 | "The -pe option tells pax to preserve the ownership of the files, which", | |||
721 | "means that the installed files will belong to the user that has built", | |||
722 | "the package.") | |||
723 | } | 644 | } | |
645 | ||||
646 | c.shline.CheckWord(word.MkText, checkQuoting) | |||
724 | } | 647 | } | |
725 | 648 | |||
726 | func (ctx *ShelltextContext) checkQuoteSubstitution() { | 649 | func (c *SimpleCommandChecker) checkAbsolutePathnames() { | |
727 | if ctx.state == scstPaxS || ctx.state == scstSedE { | 650 | if G.opts.Debug { | |
728 | if false && !matches(ctx.shellword, `"^[\"\'].*[\"\']$`) { | 651 | defer tracecall()() | |
729 | line := ctx.shline.line | 652 | } | |
730 | line.Warn1("Substitution commands like %q should always be quoted.", ctx.shellword) | 653 | ||
654 | cmdname := c.strcmd.Name | |||
655 | isSubst := false | |||
656 | for _, arg := range c.strcmd.Args { | |||
657 | if !isSubst { | |||
658 | c.shline.line.CheckAbsolutePathname(arg) | |||
659 | } | |||
660 | if false && isSubst && !matches(arg, `"^[\"\'].*[\"\']$`) { | |||
661 | c.shline.line.Warn1("Substitution commands like %q should always be quoted.", arg) | |||
731 | Explain3( | 662 | Explain3( | |
732 | "Usually these substitution commands contain characters like '*' or", | 663 | "Usually these substitution commands contain characters like '*' or", | |
733 | "other shell metacharacters that might lead to lookup of matching", | 664 | "other shell metacharacters that might lead to lookup of matching", | |
734 | "filenames and then expand to more than one word.") | 665 | "filenames and then expand to more than one word.") | |
735 | } | 666 | } | |
667 | isSubst = cmdname == "${PAX}" && arg == "-s" || cmdname == "${SED}" && arg == "-e" | |||
668 | } | |||
669 | } | |||
670 | ||||
671 | func (ctx *SimpleCommandChecker) checkAutoMkdirs() { | |||
672 | if G.opts.Debug { | |||
673 | defer tracecall()() | |||
674 | } | |||
675 | ||||
676 | cmdname := ctx.strcmd.Name | |||
677 | switch { | |||
678 | case cmdname == "${MKDIR}": | |||
679 | break | |||
680 | case cmdname == "${INSTALL}" && ctx.strcmd.HasOption("-d"): | |||
681 | cmdname = "${INSTALL} -d" | |||
682 | case matches(cmdname, `^\$\{INSTALL_.*_DIR\}$`): | |||
683 | break | |||
684 | default: | |||
685 | return | |||
686 | } | |||
687 | ||||
688 | for _, arg := range ctx.strcmd.Args { | |||
689 | if !contains(arg, "$$") && !matches(arg, `\$\{[_.]*[a-z]`) { | |||
690 | if m, dirname := match1(arg, `^(?:\$\{DESTDIR\})?\$\{PREFIX(?:|:Q)\}/(.*)`); m { | |||
691 | ctx.shline.line.Note2("You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= %s\" instead of %q.", dirname, cmdname) | |||
692 | Explain( | |||
693 | "Many packages include a list of all needed directories in their", | |||
694 | "PLIST file. In such a case, you can just set AUTO_MKDIRS=yes and", | |||
695 | "be done. The pkgsrc infrastructure will then create all directories", | |||
696 | "in advance.", | |||
697 | "", | |||
698 | "To create directories that are not mentioned in the PLIST file, it", | |||
699 | "is easier to just list them in INSTALLATION_DIRS than to execute the", | |||
700 | "commands explicitly. That way, you don't have to think about which", | |||
701 | "of the many INSTALL_*_DIR variables is appropriate, since", | |||
702 | "INSTALLATION_DIRS takes care of that.") | |||
703 | } | |||
704 | } | |||
705 | } | |||
706 | } | |||
707 | ||||
708 | func (ctx *SimpleCommandChecker) checkInstallMulti() { | |||
709 | if G.opts.Debug { | |||
710 | defer tracecall()() | |||
711 | } | |||
712 | ||||
713 | cmd := ctx.strcmd | |||
714 | ||||
715 | if hasPrefix(cmd.Name, "${INSTALL_") && hasSuffix(cmd.Name, "_DIR}") { | |||
716 | prevdir := "" | |||
717 | for i, arg := range cmd.Args { | |||
718 | switch { | |||
719 | case hasPrefix(arg, "-"): | |||
720 | break | |||
721 | case i > 0 && (cmd.Args[i-1] == "-m" || cmd.Args[i-1] == "-o" || cmd.Args[i-1] == "-g"): | |||
722 | break | |||
723 | default: | |||
724 | if prevdir != "" { | |||
725 | ctx.shline.line.Warn0("The INSTALL_*_DIR commands can only handle one directory at a time.") | |||
726 | Explain2( | |||
727 | "Many implementations of install(1) can handle more, but pkgsrc aims", | |||
728 | "at maximum portability.") | |||
729 | return | |||
730 | } | |||
731 | prevdir = arg | |||
732 | } | |||
733 | } | |||
734 | } | |||
735 | } | |||
736 | ||||
737 | func (ctx *SimpleCommandChecker) checkPaxPe() { | |||
738 | if G.opts.Debug { | |||
739 | defer tracecall()() | |||
740 | } | |||
741 | ||||
742 | if ctx.strcmd.Name == "${PAX}" && ctx.strcmd.HasOption("-pe") { | |||
743 | ctx.shline.line.Warn0("Please use the -pp option to pax(1) instead of -pe.") | |||
744 | Explain3( | |||
745 | "The -pe option tells pax to preserve the ownership of the files, which", | |||
746 | "means that the installed files will belong to the user that has built", | |||
747 | "the package.") | |||
736 | } | 748 | } | |
737 | } | 749 | } | |
738 | 750 | |||
739 | func (ctx *ShelltextContext) checkEchoN() { | 751 | func (ctx *SimpleCommandChecker) checkEchoN() { | |
740 | if ctx.state == scstEcho && ctx.shellword == "-n" { | 752 | if G.opts.Debug { | |
753 | defer tracecall()() | |||
754 | } | |||
755 | ||||
756 | if ctx.strcmd.Name == "${ECHO}" && ctx.strcmd.HasOption("-n") { | |||
741 | ctx.shline.line.Warn0("Please use ${ECHO_N} instead of \"echo -n\".") | 757 | ctx.shline.line.Warn0("Please use ${ECHO_N} instead of \"echo -n\".") | |
742 | } | 758 | } | |
743 | } | 759 | } | |
744 | 760 | |||
745 | func (ctx *ShelltextContext) checkPipeExitcode() { | 761 | func (ctx *ShellProgramChecker) checkPipeExitcode(line *Line, pipeline *MkShPipeline) { | |
746 | if G.opts.WarnExtra && ctx.state != scstCaseLabelCont && ctx.shellword == "|" { | 762 | if G.opts.Debug { | |
747 | line := ctx.shline.line | 763 | defer tracecall()() | |
764 | } | |||
765 | ||||
766 | if G.opts.WarnExtra && len(pipeline.Cmds) > 1 { | |||
748 | line.Warn0("The exitcode of the left-hand-side command of the pipe operator is ignored.") | 767 | line.Warn0("The exitcode of the left-hand-side command of the pipe operator is ignored.") | |
749 | Explain( | 768 | Explain( | |
750 | "In a shell command like \"cat *.txt | grep keyword\", if the command", | 769 | "In a shell command like \"cat *.txt | grep keyword\", if the command", | |
751 | "on the left side of the \"|\" fails, this failure is ignored.", | 770 | "on the left side of the \"|\" fails, this failure is ignored.", | |
752 | "", | 771 | "", | |
753 | "If you need to detect the failure of the left-hand-side command, use", | 772 | "If you need to detect the failure of the left-hand-side command, use", | |
754 | "temporary files to save the output of the command.") | 773 | "temporary files to save the output of the command.") | |
755 | } | 774 | } | |
756 | } | 775 | } | |
757 | 776 | |||
758 | func (ctx *ShelltextContext) checkSetE(eflag *bool, prevToken string) { | 777 | func (ctx *ShellProgramChecker) checkSetE(list *MkShList, eflag *bool) { | |
759 | if G.opts.WarnExtra && ctx.shellword == ";" && ctx.state != scstCondCont && ctx.state != scstForCont && !*eflag { | 778 | if G.opts.Debug { | |
779 | defer tracecall()() | |||
780 | } | |||
781 | ||||
782 | // Disabled until the shell parser can recognize "command || exit 1" reliably. | |||
783 | if false && G.opts.WarnExtra && !*eflag && "the current token" == ";" { | |||
760 | *eflag = true | 784 | *eflag = true | |
761 | ctx.shline.line.Warn1("Please switch to \"set -e\" mode before using a semicolon (the one after %q) to separate commands.", prevToken) | 785 | ctx.shline.line.Warn1("Please switch to \"set -e\" mode before using a semicolon (the one after %q) to separate commands.", "previous token") | |
762 | Explain( | 786 | Explain( | |
763 | "Normally, when a shell command fails (returns non-zero), the", | 787 | "Normally, when a shell command fails (returns non-zero), the", | |
764 | "remaining commands are still executed. For example, the following", | 788 | "remaining commands are still executed. For example, the following", | |
765 | "commands would remove all files from the HOME directory:", | 789 | "commands would remove all files from the HOME directory:", | |
766 | "", | 790 | "", | |
767 | "\tcd \"$HOME\"; cd /nonexistent; rm -rf *", | 791 | "\tcd \"$HOME\"; cd /nonexistent; rm -rf *", | |
768 | "", | 792 | "", | |
769 | "To fix this warning, you can:", | 793 | "To fix this warning, you can:", | |
770 | "", | 794 | "", | |
771 | "* insert ${RUN} at the beginning of the line", | 795 | "* insert ${RUN} at the beginning of the line", | |
772 | " (which among other things does \"set -e\")", | 796 | " (which among other things does \"set -e\")", | |
773 | "* insert \"set -e\" explicitly at the beginning of the line", | 797 | "* insert \"set -e\" explicitly at the beginning of the line", | |
774 | "* use \"&&\" instead of \";\" to separate the commands") | 798 | "* use \"&&\" instead of \";\" to separate the commands") | |
775 | } | 799 | } | |
776 | } | 800 | } | |
777 | 801 | |||
778 | // Some shell commands should not be used in the install phase. | 802 | // Some shell commands should not be used in the install phase. | |
779 | func (shline *ShellLine) checkCommandUse(shellcmd string) { | 803 | func (shline *ShellLine) checkCommandUse(shellcmd string) { | |
804 | if G.opts.Debug { | |||
805 | defer tracecall()() | |||
806 | } | |||
807 | ||||
780 | if G.Mk == nil || !matches(G.Mk.target, `^(?:pre|do|post)-install$`) { | 808 | if G.Mk == nil || !matches(G.Mk.target, `^(?:pre|do|post)-install$`) { | |
781 | return | 809 | return | |
782 | } | 810 | } | |
783 | 811 | |||
784 | line := shline.line | 812 | line := shline.line | |
785 | switch shellcmd { | 813 | switch shellcmd { | |
786 | case "${INSTALL}", | 814 | case "${INSTALL}", | |
787 | "${INSTALL_DATA}", "${INSTALL_DATA_DIR}", | 815 | "${INSTALL_DATA}", "${INSTALL_DATA_DIR}", | |
788 | "${INSTALL_LIB}", "${INSTALL_LIB_DIR}", | 816 | "${INSTALL_LIB}", "${INSTALL_LIB_DIR}", | |
789 | "${INSTALL_MAN}", "${INSTALL_MAN_DIR}", | 817 | "${INSTALL_MAN}", "${INSTALL_MAN_DIR}", | |
790 | "${INSTALL_PROGRAM}", "${INSTALL_PROGRAM_DIR}", | 818 | "${INSTALL_PROGRAM}", "${INSTALL_PROGRAM_DIR}", | |
791 | "${INSTALL_SCRIPT}", | 819 | "${INSTALL_SCRIPT}", | |
792 | "${LIBTOOL}", | 820 | "${LIBTOOL}", | |
@@ -805,180 +833,73 @@ func (shline *ShellLine) checkCommandUse | @@ -805,180 +833,73 @@ func (shline *ShellLine) checkCommandUse | |||
805 | case "cp", "${CP}": | 833 | case "cp", "${CP}": | |
806 | line.Warn0("${CP} should not be used to install files.") | 834 | line.Warn0("${CP} should not be used to install files.") | |
807 | Explain( | 835 | Explain( | |
808 | "The ${CP} command is highly platform dependent and cannot overwrite", | 836 | "The ${CP} command is highly platform dependent and cannot overwrite", | |
809 | "read-only files. Please use ${PAX} instead.", | 837 | "read-only files. Please use ${PAX} instead.", | |
810 | "", | 838 | "", | |
811 | "For example, instead of", | 839 | "For example, instead of", | |
812 | "\t${CP} -R ${WRKSRC}/* ${PREFIX}/foodir", | 840 | "\t${CP} -R ${WRKSRC}/* ${PREFIX}/foodir", | |
813 | "you should use", | 841 | "you should use", | |
814 | "\tcd ${WRKSRC} && ${PAX} -wr * ${PREFIX}/foodir") | 842 | "\tcd ${WRKSRC} && ${PAX} -wr * ${PREFIX}/foodir") | |
815 | } | 843 | } | |
816 | } | 844 | } | |
817 | 845 | |||
818 | func (shline *ShellLine) nextState(state scState, shellword string) scState { | |||
819 | switch { | |||
820 | case shellword == ";;": | |||
821 | return scstCaseLabel | |||
822 | case state == scstCaseLabelCont && shellword == "|": | |||
823 | return scstCaseLabel | |||
824 | case matches(shellword, `^[;&\|]+$`): | |||
825 | return scstStart | |||
826 | case state == scstStart: | |||
827 | switch shellword { | |||
828 | case "${INSTALL}": | |||
829 | return scstInstall | |||
830 | case "${MKDIR}": | |||
831 | return scstMkdir | |||
832 | case "${PAX}": | |||
833 | return scstPax | |||
834 | case "${SED}": | |||
835 | return scstSed | |||
836 | case "${ECHO}", "echo": | |||
837 | return scstEcho | |||
838 | case "${RUN}", "then", "else", "do", "(": | |||
839 | return scstStart | |||
840 | case "set": | |||
841 | return scstSet | |||
842 | case "if", "elif", "while": | |||
843 | return scstCond | |||
844 | case "case": | |||
845 | return scstCase | |||
846 | case "for": | |||
847 | return scstFor | |||
848 | default: | |||
849 | switch { | |||
850 | case matches(shellword, `^\$\{INSTALL_[A-Z]+_DIR\}$`): | |||
851 | return scstInstallDir | |||
852 | case matches(shellword, reShVarassign): | |||
853 | return scstStart | |||
854 | default: | |||
855 | return scstCont | |||
856 | } | |||
857 | } | |||
858 | case state == scstMkdir: | |||
859 | return scstMkdir | |||
860 | case state == scstInstall && shellword == "-d": | |||
861 | return scstInstallD | |||
862 | case state == scstInstall, state == scstInstallD: | |||
863 | if matches(shellword, `^-[ogm]$`) { | |||
864 | return scstCont // XXX: why not keep the state? | |||
865 | } | |||
866 | return state | |||
867 | case state == scstInstallDir && hasPrefix(shellword, "-"): | |||
868 | return scstCont | |||
869 | case state == scstInstallDir && hasPrefix(shellword, "$"): | |||
870 | return scstInstallDir2 | |||
871 | case state == scstInstallDir || state == scstInstallDir2: | |||
872 | return state | |||
873 | case state == scstPax && shellword == "-s": | |||
874 | return scstPaxS | |||
875 | case state == scstPax && hasPrefix(shellword, "-"): | |||
876 | return scstPax | |||
877 | case state == scstPax: | |||
878 | return scstCont | |||
879 | case state == scstPaxS: | |||
880 | return scstPax | |||
881 | case state == scstSed && shellword == "-e": | |||
882 | return scstSedE | |||
883 | case state == scstSed && hasPrefix(shellword, "-"): | |||
884 | return scstSed | |||
885 | case state == scstSed: | |||
886 | return scstCont | |||
887 | case state == scstSedE: | |||
888 | return scstSed | |||
889 | case state == scstSet: | |||
890 | return scstSetCont | |||
891 | case state == scstSetCont: | |||
892 | return scstSetCont | |||
893 | case state == scstCase: | |||
894 | return scstCaseIn | |||
895 | case state == scstCaseIn && shellword == "in": | |||
896 | return scstCaseLabel | |||
897 | case state == scstCaseLabel && shellword == "esac": | |||
898 | return scstCont | |||
899 | case state == scstCaseLabel: | |||
900 | return scstCaseLabelCont | |||
901 | case state == scstCaseLabelCont && shellword == ")": | |||
902 | return scstStart | |||
903 | case state == scstCont: | |||
904 | return scstCont | |||
905 | case state == scstCond: | |||
906 | return scstCondCont | |||
907 | case state == scstCondCont: | |||
908 | return scstCondCont | |||
909 | case state == scstFor: | |||
910 | return scstForIn | |||
911 | case state == scstForIn && shellword == "in": | |||
912 | return scstForCont | |||
913 | case state == scstForCont: | |||
914 | return scstForCont | |||
915 | case state == scstEcho: | |||
916 | return scstCont | |||
917 | default: | |||
918 | if G.opts.Debug { | |||
919 | traceStep("Internal pkglint error: shellword.nextState state=%s shellword=%q", state, shellword) | |||
920 | } | |||
921 | return scstStart | |||
922 | } | |||
923 | } | |||
924 | ||||
925 | // Example: "word1 word2;;;" => "word1", "word2", ";;", ";" | 846 | // Example: "word1 word2;;;" => "word1", "word2", ";;", ";" | |
926 | func splitIntoShellTokens(line *Line, text string) (tokens []string, rest string) { | 847 | func splitIntoShellTokens(line *Line, text string) (tokens []string, rest string) { | |
927 | if G.opts.Debug { | 848 | if G.opts.Debug { | |
928 | defer tracecall(line, text)() | 849 | defer tracecall(line, text)() | |
929 | } | 850 | } | |
930 | 851 | |||
931 | word := "" | 852 | word := "" | |
932 | emit := func() { | 853 | emit := func() { | |
933 | if word != "" { | 854 | if word != "" { | |
934 | tokens = append(tokens, word) | 855 | tokens = append(tokens, word) | |
935 | word = "" | 856 | word = "" | |
936 | } | 857 | } | |
937 | } | 858 | } | |
938 | p := NewShTokenizer(line, text, false) | 859 | p := NewShTokenizer(line, text, false) | |
939 | atoms := p.ShAtoms() | 860 | atoms := p.ShAtoms() | |
940 | q := shqPlain | 861 | q := shqPlain | |
941 | for _, atom := range atoms { | 862 | for _, atom := range atoms { | |
942 | q = atom.Quoting | 863 | q = atom.Quoting | |
943 | if atom.Type == shtSpace && q == shqPlain { | 864 | if atom.Type == shtSpace && q == shqPlain { | |
944 | emit() | 865 | emit() | |
945 | } else if atom.Type == shtWord || atom.Type == shtVaruse || atom.Quoting != shqPlain { | 866 | } else if atom.Type == shtWord || atom.Type == shtVaruse || atom.Quoting != shqPlain { | |
946 | word += atom.Text | 867 | word += atom.MkText | |
947 | } else { | 868 | } else { | |
948 | emit() | 869 | emit() | |
949 | tokens = append(tokens, atom.Text) | 870 | tokens = append(tokens, atom.MkText) | |
950 | } | 871 | } | |
951 | } | 872 | } | |
952 | emit() | 873 | emit() | |
953 | return tokens, word + p.mkp.Rest() | 874 | return tokens, word + p.mkp.Rest() | |
954 | } | 875 | } | |
955 | 876 | |||
956 | // Example: "word1 word2;;;" => "word1", "word2;;;" | 877 | // Example: "word1 word2;;;" => "word1", "word2;;;" | |
957 | // Compare devel/bmake/files/str.c, function brk_string. | 878 | // Compare devel/bmake/files/str.c, function brk_string. | |
958 | func splitIntoMkWords(line *Line, text string) (words []string, rest string) { | 879 | func splitIntoMkWords(line *Line, text string) (words []string, rest string) { | |
959 | if G.opts.Debug { | 880 | if G.opts.Debug { | |
960 | defer tracecall(line, text)() | 881 | defer tracecall(line, text)() | |
961 | } | 882 | } | |
962 | 883 | |||
963 | p := NewShTokenizer(line, text, false) | 884 | p := NewShTokenizer(line, text, false) | |
964 | atoms := p.ShAtoms() | 885 | atoms := p.ShAtoms() | |
965 | word := "" | 886 | word := "" | |
966 | for _, atom := range atoms { | 887 | for _, atom := range atoms { | |
967 | if atom.Type == shtSpace && atom.Quoting == shqPlain { | 888 | if atom.Type == shtSpace && atom.Quoting == shqPlain { | |
968 | words = append(words, word) | 889 | words = append(words, word) | |
969 | word = "" | 890 | word = "" | |
970 | } else { | 891 | } else { | |
971 | word += atom.Text | 892 | word += atom.MkText | |
972 | } | 893 | } | |
973 | } | 894 | } | |
974 | if word != "" && atoms[len(atoms)-1].Quoting == shqPlain { | 895 | if word != "" && atoms[len(atoms)-1].Quoting == shqPlain { | |
975 | words = append(words, word) | 896 | words = append(words, word) | |
976 | word = "" | 897 | word = "" | |
977 | } | 898 | } | |
978 | return words, word + p.mkp.Rest() | 899 | return words, word + p.mkp.Rest() | |
979 | } | 900 | } | |
980 | 901 | |||
981 | type ShQuote struct { | 902 | type ShQuote struct { | |
982 | repl *PrefixReplacer | 903 | repl *PrefixReplacer | |
983 | q ShQuoting | 904 | q ShQuoting | |
984 | } | 905 | } |
@@ -1,38 +1,21 @@ | @@ -1,38 +1,21 @@ | |||
1 | package main | 1 | package main | |
2 | 2 | |||
3 | import ( | 3 | import ( | |
4 | "strings" | 4 | "strings" | |
5 | 5 | |||
6 | check "gopkg.in/check.v1" | 6 | check "gopkg.in/check.v1" | |
7 | ) | 7 | ) | |
8 | 8 | |||
9 | func (s *Suite) TestReShellToken(c *check.C) { | |||
10 | re := `^(?:` + reShellToken + `)$` | |||
11 | matches := check.NotNil | |||
12 | doesntMatch := check.IsNil | |||
13 | ||||
14 | c.Check(match("", re), doesntMatch) | |||
15 | c.Check(match("$var", re), matches) | |||
16 | c.Check(match("$var$var", re), matches) | |||
17 | c.Check(match("$var;;", re), doesntMatch) // More than one token | |||
18 | c.Check(match("'single-quoted'", re), matches) | |||
19 | c.Check(match("\"", re), doesntMatch) // Incomplete string | |||
20 | c.Check(match("'...'\"...\"", re), matches) // Mixed strings | |||
21 | c.Check(match("\"...\"", re), matches) | |||
22 | c.Check(match("`cat file`", re), matches) | |||
23 | c.Check(match("${file%.c}.o", re), matches) | |||
24 | } | |||
25 | ||||
26 | func (s *Suite) Test_SplitIntoShellTokens_LineContinuation(c *check.C) { | 9 | func (s *Suite) Test_SplitIntoShellTokens_LineContinuation(c *check.C) { | |
27 | words, rest := splitIntoShellTokens(dummyLine, "if true; then \\") | 10 | words, rest := splitIntoShellTokens(dummyLine, "if true; then \\") | |
28 | 11 | |||
29 | c.Check(words, check.DeepEquals, []string{"if", "true", ";", "then"}) | 12 | c.Check(words, check.DeepEquals, []string{"if", "true", ";", "then"}) | |
30 | c.Check(rest, equals, "\\") | 13 | c.Check(rest, equals, "\\") | |
31 | 14 | |||
32 | c.Check(s.Output(), equals, "WARN: Pkglint parse error in ShTokenizer.ShAtom at \"\\\\\" (quoting=plain)\n") | 15 | c.Check(s.Output(), equals, "WARN: Pkglint parse error in ShTokenizer.ShAtom at \"\\\\\" (quoting=plain)\n") | |
33 | } | 16 | } | |
34 | 17 | |||
35 | func (s *Suite) Test_SplitIntoShellTokens_DollarSlash(c *check.C) { | 18 | func (s *Suite) Test_SplitIntoShellTokens_DollarSlash(c *check.C) { | |
36 | words, rest := splitIntoShellTokens(dummyLine, "pax -s /.*~$$//g") | 19 | words, rest := splitIntoShellTokens(dummyLine, "pax -s /.*~$$//g") | |
37 | 20 | |||
38 | c.Check(words, check.DeepEquals, []string{"pax", "-s", "/.*~$$//g"}) | 21 | c.Check(words, check.DeepEquals, []string{"pax", "-s", "/.*~$$//g"}) | |
@@ -84,140 +67,176 @@ func (s *Suite) Test_SplitIntoShellToken | @@ -84,140 +67,176 @@ func (s *Suite) Test_SplitIntoShellToken | |||
84 | words, rest := splitIntoShellTokens(dummyLine, "${VAR:S/ /_/g}") | 67 | words, rest := splitIntoShellTokens(dummyLine, "${VAR:S/ /_/g}") | |
85 | 68 | |||
86 | c.Check(words, deepEquals, []string{"${VAR:S/ /_/g}"}) | 69 | c.Check(words, deepEquals, []string{"${VAR:S/ /_/g}"}) | |
87 | c.Check(rest, equals, "") | 70 | c.Check(rest, equals, "") | |
88 | } | 71 | } | |
89 | 72 | |||
90 | func (s *Suite) Test_SplitIntoMkWords_VaruseSpace(c *check.C) { | 73 | func (s *Suite) Test_SplitIntoMkWords_VaruseSpace(c *check.C) { | |
91 | words, rest := splitIntoMkWords(dummyLine, "${VAR:S/ /_/g}") | 74 | words, rest := splitIntoMkWords(dummyLine, "${VAR:S/ /_/g}") | |
92 | 75 | |||
93 | c.Check(words, deepEquals, []string{"${VAR:S/ /_/g}"}) | 76 | c.Check(words, deepEquals, []string{"${VAR:S/ /_/g}"}) | |
94 | c.Check(rest, equals, "") | 77 | c.Check(rest, equals, "") | |
95 | } | 78 | } | |
96 | 79 | |||
80 | func (s *Suite) Test_splitIntoShellTokens_Redirect(c *check.C) { | |||
81 | words, rest := splitIntoShellTokens(dummyLine, "echo 1>output 2>>append 3>|clobber 4>&5 6<input >>append") | |||
82 | ||||
83 | c.Check(words, deepEquals, []string{ | |||
84 | "echo", | |||
85 | "1>", "output", | |||
86 | "2>>", "append", | |||
87 | "3>|", "clobber", | |||
88 | "4>&", "5", | |||
89 | "6<", "input", | |||
90 | ">>", "append"}) | |||
91 | c.Check(rest, equals, "") | |||
92 | ||||
93 | words, rest = splitIntoShellTokens(dummyLine, "echo 1> output 2>> append 3>| clobber 4>& 5 6< input >> append") | |||
94 | ||||
95 | c.Check(words, deepEquals, []string{ | |||
96 | "echo", | |||
97 | "1>", "output", | |||
98 | "2>>", "append", | |||
99 | "3>|", "clobber", | |||
100 | "4>&", "5", | |||
101 | "6<", "input", | |||
102 | ">>", "append"}) | |||
103 | c.Check(rest, equals, "") | |||
104 | } | |||
105 | ||||
97 | func (s *Suite) TestChecklineMkShellCommandLine(c *check.C) { | 106 | func (s *Suite) TestChecklineMkShellCommandLine(c *check.C) { | |
98 | s.UseCommandLine(c, "-Wall") | 107 | s.UseCommandLine(c, "-Wall") | |
99 | G.Mk = s.NewMkLines("fname", | 108 | G.Mk = s.NewMkLines("fname", | |
100 | "# dummy") | 109 | "# dummy") | |
101 | shline := NewShellLine(G.Mk.mklines[0]) | 110 | shline := NewShellLine(G.Mk.mklines[0]) | |
102 | 111 | |||
103 | shline.CheckShellCommandLine("@# Comment") | 112 | shline.CheckShellCommandLine("@# Comment") | |
104 | 113 | |||
105 | c.Check(s.Output(), equals, "") | 114 | c.Check(s.Output(), equals, "") | |
106 | 115 | |||
107 | shline.CheckShellCommandLine("uname=`uname`; echo $$uname; echo") | 116 | shline.CheckShellCommandLine("uname=`uname`; echo $$uname; echo") | |
108 | 117 | |||
109 | c.Check(s.Output(), equals, ""+ | 118 | c.Check(s.Output(), equals, ""+ | |
110 | "WARN: fname:1: Unknown shell command \"uname\".\n"+ | 119 | "WARN: fname:1: Unknown shell command \"uname\".\n"+ | |
111 | "WARN: fname:1: Please switch to \"set -e\" mode before using a semicolon (the one after \"uname=`uname`\") to separate commands.\n"+ | |||
112 | "WARN: fname:1: Unknown shell command \"echo\".\n"+ | 120 | "WARN: fname:1: Unknown shell command \"echo\".\n"+ | |
113 | "WARN: fname:1: Unquoted shell variable \"uname\".\n"+ | |||
114 | "WARN: fname:1: Unknown shell command \"echo\".\n") | 121 | "WARN: fname:1: Unknown shell command \"echo\".\n") | |
115 | 122 | |||
116 | s.RegisterTool(&Tool{Name: "echo", Predefined: true}) | 123 | s.RegisterTool(&Tool{Name: "echo", Predefined: true}) | |
117 | G.Mk = s.NewMkLines("fname", | 124 | G.Mk = s.NewMkLines("fname", | |
118 | "# dummy") | 125 | "# dummy") | |
119 | G.globalData.InitVartypes() | 126 | G.globalData.InitVartypes() | |
120 | 127 | |||
121 | shline.CheckShellCommandLine("echo ${PKGNAME:Q}") // vucQuotPlain | 128 | shline.CheckShellCommandLine("echo ${PKGNAME:Q}") // vucQuotPlain | |
122 | 129 | |||
123 | c.Check(s.Output(), equals, ""+ | 130 | c.Check(s.Output(), equals, ""+ | |
124 | "WARN: fname:1: PKGNAME may not be used in this file; it would be ok in Makefile, Makefile.*, *.mk.\n"+ | 131 | "WARN: fname:1: PKGNAME may not be used in this file; it would be ok in Makefile, Makefile.*, *.mk.\n"+ | |
125 | "NOTE: fname:1: The :Q operator isn't necessary for ${PKGNAME} here.\n") | 132 | "NOTE: fname:1: The :Q operator isn't necessary for ${PKGNAME} here.\n") | |
126 | 133 | |||
127 | shline.CheckShellCommandLine("echo \"${CFLAGS:Q}\"") // vucQuotDquot | 134 | shline.CheckShellCommandLine("echo \"${CFLAGS:Q}\"") // vucQuotDquot | |
128 | 135 | |||
129 | c.Check(s.Output(), equals, ""+ | 136 | c.Check(s.Output(), equals, ""+ | |
130 | "WARN: fname:1: Please don't use the :Q operator in double quotes.\n"+ | 137 | "WARN: fname:1: Please don't use the :Q operator in double quotes.\n"+ | |
131 | "WARN: fname:1: CFLAGS may not be used in this file; it would be ok in Makefile, Makefile.common, options.mk, *.mk.\n"+ | 138 | "WARN: fname:1: CFLAGS may not be used in this file; it would be ok in Makefile, Makefile.common, options.mk, *.mk.\n"+ | |
132 | "WARN: fname:1: Please use ${CFLAGS:M*:Q} instead of ${CFLAGS:Q} and make sure the variable appears outside of any quoting characters.\n") | 139 | "WARN: fname:1: Please use ${CFLAGS:M*:Q} instead of ${CFLAGS:Q} and make sure the variable appears outside of any quoting characters.\n") | |
133 | 140 | |||
134 | shline.CheckShellCommandLine("echo '${COMMENT:Q}'") // vucQuotSquot | 141 | shline.CheckShellCommandLine("echo '${COMMENT:Q}'") // vucQuotSquot | |
135 | 142 | |||
136 | c.Check(s.Output(), equals, ""+ | 143 | c.Check(s.Output(), equals, ""+ | |
137 | "WARN: fname:1: COMMENT may not be used in any file; it is a write-only variable.\n"+ | 144 | "WARN: fname:1: COMMENT may not be used in any file; it is a write-only variable.\n"+ | |
138 | "WARN: fname:1: Please move ${COMMENT:Q} outside of any quoting characters.\n") | 145 | "WARN: fname:1: Please move ${COMMENT:Q} outside of any quoting characters.\n") | |
139 | 146 | |||
147 | shline.CheckShellCommandLine("echo target=$@ exitcode=$$? '$$' \"\\$$\"") | |||
148 | ||||
149 | c.Check(s.Output(), equals, ""+ | |||
150 | "WARN: fname:1: Please use \"${.TARGET}\" instead of \"$@\".\n"+ | |||
151 | "WARN: fname:1: The $? shell variable is often not available in \"set -e\" mode.\n") | |||
152 | ||||
140 | shline.CheckShellCommandLine("echo $$@") | 153 | shline.CheckShellCommandLine("echo $$@") | |
141 | 154 | |||
142 | c.Check(s.Output(), equals, "WARN: fname:1: The $@ shell variable should only be used in double quotes.\n") | 155 | c.Check(s.Output(), equals, "WARN: fname:1: The $@ shell variable should only be used in double quotes.\n") | |
143 | 156 | |||
144 | shline.CheckShellCommandLine("echo \"$$\"") // As seen by make(1); the shell sees: echo "$" | 157 | shline.CheckShellCommandLine("echo \"$$\"") // As seen by make(1); the shell sees: echo "$" | |
145 | 158 | |||
146 | c.Check(s.Output(), equals, ""+ | 159 | c.Check(s.Output(), equals, ""+ | |
147 | "WARN: fname:1: Pkglint parse error in ShTokenizer.ShAtom at \"$$\\\"\" (quoting=d)\n"+ | 160 | "WARN: fname:1: Pkglint parse error in ShTokenizer.ShAtom at \"$$\\\"\" (quoting=d)\n"+ | |
148 | "WARN: fname:1: Pkglint parse error in ShellLine.CheckShellCommand at \"$$\\\"\" (state=start)\n") | 161 | "WARN: fname:1: Pkglint ShellLine.CheckShellCommand: parse error at [\"]\n") | |
149 | 162 | |||
150 | shline.CheckShellCommandLine("echo \"\\n\"") // As seen by make(1); the shell sees: echo "\n" | 163 | shline.CheckShellCommandLine("echo \"\\n\"") | |
151 | 164 | |||
152 | c.Check(s.Output(), equals, "") | 165 | c.Check(s.Output(), equals, "") | |
153 | 166 | |||
154 | shline.CheckShellCommandLine("${RUN} for f in *.c; do echo $${f%.c}; done") | 167 | shline.CheckShellCommandLine("${RUN} for f in *.c; do echo $${f%.c}; done") | |
155 | 168 | |||
156 | c.Check(s.Output(), equals, "") | 169 | c.Check(s.Output(), equals, "") | |
157 | 170 | |||
158 | shline.CheckShellCommandLine("${RUN} echo $${variable+set}") | 171 | shline.CheckShellCommandLine("${RUN} echo $${variable+set}") | |
159 | 172 | |||
160 | c.Check(s.Output(), equals, "") | 173 | c.Check(s.Output(), equals, "") | |
161 | 174 | |||
162 | // Based on mail/thunderbird/Makefile, rev. 1.159 | 175 | // Based on mail/thunderbird/Makefile, rev. 1.159 | |
163 | shline.CheckShellCommandLine("${RUN} subdir=\"`unzip -c \"$$e\" install.rdf | awk '/re/ { print \"hello\" }'`\"") | 176 | shline.CheckShellCommandLine("${RUN} subdir=\"`unzip -c \"$$e\" install.rdf | awk '/re/ { print \"hello\" }'`\"") | |
164 | 177 | |||
165 | c.Check(s.Output(), equals, ""+ | 178 | c.Check(s.Output(), equals, ""+ | |
166 | "WARN: fname:1: Unknown shell command \"unzip\".\n"+ | |||
167 | "WARN: fname:1: The exitcode of the left-hand-side command of the pipe operator is ignored.\n"+ | 179 | "WARN: fname:1: The exitcode of the left-hand-side command of the pipe operator is ignored.\n"+ | |
180 | "WARN: fname:1: Unknown shell command \"unzip\".\n"+ | |||
168 | "WARN: fname:1: Unknown shell command \"awk\".\n") | 181 | "WARN: fname:1: Unknown shell command \"awk\".\n") | |
169 | 182 | |||
170 | // From mail/thunderbird/Makefile, rev. 1.159 | 183 | // From mail/thunderbird/Makefile, rev. 1.159 | |
171 | shline.CheckShellCommandLine("" + | 184 | shline.CheckShellCommandLine("" + | |
172 | "${RUN} for e in ${XPI_FILES}; do " + | 185 | "${RUN} for e in ${XPI_FILES}; do " + | |
173 | " subdir=\"`${UNZIP_CMD} -c \"$$e\" install.rdf | awk '/^ <em:id>/ {sub(\".*<em:id>\",\"\");sub(\"</em:id>.*\",\"\");print;exit;}'`\" && " + | 186 | " subdir=\"`${UNZIP_CMD} -c \"$$e\" install.rdf | awk '/^ <em:id>/ {sub(\".*<em:id>\",\"\");sub(\"</em:id>.*\",\"\");print;exit;}'`\" && " + | |
174 | " ${MKDIR} \"${WRKDIR}/extensions/$$subdir\" && " + | 187 | " ${MKDIR} \"${WRKDIR}/extensions/$$subdir\" && " + | |
175 | " cd \"${WRKDIR}/extensions/$$subdir\" && " + | 188 | " cd \"${WRKDIR}/extensions/$$subdir\" && " + | |
176 | " ${UNZIP_CMD} -aqo $$e; " + | 189 | " ${UNZIP_CMD} -aqo $$e; " + | |
177 | "done") | 190 | "done") | |
178 | 191 | |||
179 | c.Check(s.Output(), equals, ""+ | 192 | c.Check(s.Output(), equals, ""+ | |
180 | "WARN: fname:1: XPI_FILES is used but not defined. Spelling mistake?\n"+ | 193 | "WARN: fname:1: XPI_FILES is used but not defined. Spelling mistake?\n"+ | |
181 | "WARN: fname:1: UNZIP_CMD is used but not defined. Spelling mistake?\n"+ | |||
182 | "WARN: fname:1: The exitcode of the left-hand-side command of the pipe operator is ignored.\n"+ | 194 | "WARN: fname:1: The exitcode of the left-hand-side command of the pipe operator is ignored.\n"+ | |
195 | "WARN: fname:1: UNZIP_CMD is used but not defined. Spelling mistake?\n"+ | |||
183 | "WARN: fname:1: Unknown shell command \"awk\".\n"+ | 196 | "WARN: fname:1: Unknown shell command \"awk\".\n"+ | |
184 | "WARN: fname:1: MKDIR is used but not defined. Spelling mistake?\n"+ | |||
185 | "WARN: fname:1: Unknown shell command \"${MKDIR}\".\n"+ | 197 | "WARN: fname:1: Unknown shell command \"${MKDIR}\".\n"+ | |
186 | "WARN: fname:1: UNZIP_CMD is used but not defined. Spelling mistake?\n"+ | 198 | "WARN: fname:1: MKDIR is used but not defined. Spelling mistake?\n"+ | |
187 | "WARN: fname:1: Unquoted shell variable \"e\".\n") | 199 | "WARN: fname:1: UNZIP_CMD is used but not defined. Spelling mistake?\n") | |
188 | 200 | |||
189 | // From x11/wxGTK28/Makefile | 201 | // From x11/wxGTK28/Makefile | |
190 | shline.CheckShellCommandLine("" + | 202 | shline.CheckShellCommandLine("" + | |
191 | "set -e; cd ${WRKSRC}/locale; " + | 203 | "set -e; cd ${WRKSRC}/locale; " + | |
192 | "for lang in *.po; do " + | 204 | "for lang in *.po; do " + | |
193 | " [ \"$${lang}\" = \"wxstd.po\" ] && continue; " + | 205 | " [ \"$${lang}\" = \"wxstd.po\" ] && continue; " + | |
194 | " ${TOOLS_PATH.msgfmt} -c -o \"$${lang%.po}.mo\" \"$${lang}\"; " + | 206 | " ${TOOLS_PATH.msgfmt} -c -o \"$${lang%.po}.mo\" \"$${lang}\"; " + | |
195 | "done") | 207 | "done") | |
196 | 208 | |||
197 | c.Check(s.Output(), equals, ""+ | 209 | c.Check(s.Output(), equals, ""+ | |
198 | "WARN: fname:1: WRKSRC may not be used in this file; it would be ok in Makefile, Makefile.*, *.mk.\n"+ | 210 | "WARN: fname:1: WRKSRC may not be used in this file; it would be ok in Makefile, Makefile.*, *.mk.\n"+ | |
199 | "WARN: fname:1: Unknown shell command \"[\".\n"+ | 211 | "WARN: fname:1: Unknown shell command \"[\".\n"+ | |
200 | "WARN: fname:1: Unknown shell command \"${TOOLS_PATH.msgfmt}\".\n") | 212 | "WARN: fname:1: Unknown shell command \"${TOOLS_PATH.msgfmt}\".\n") | |
201 | 213 | |||
202 | shline.CheckShellCommandLine("@cp from to") | 214 | shline.CheckShellCommandLine("@cp from to") | |
203 | 215 | |||
204 | c.Check(s.Output(), equals, ""+ | 216 | c.Check(s.Output(), equals, ""+ | |
205 | "WARN: fname:1: The shell command \"cp\" should not be hidden.\n"+ | 217 | "WARN: fname:1: The shell command \"cp\" should not be hidden.\n"+ | |
206 | "WARN: fname:1: Unknown shell command \"cp\".\n") | 218 | "WARN: fname:1: Unknown shell command \"cp\".\n") | |
207 | 219 | |||
208 | shline.CheckShellCommandLine("${RUN} ${INSTALL_DATA_DIR} share/pkgbase ${PREFIX}/share/pkgbase") | 220 | shline.CheckShellCommandLine("${RUN} ${INSTALL_DATA_DIR} share/pkgbase ${PREFIX}/share/pkgbase") | |
209 | 221 | |||
210 | c.Check(s.Output(), equals, "NOTE: fname:1: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= share/pkgbase\" instead of this command.\n") | 222 | c.Check(s.Output(), equals, ""+ | |
223 | "NOTE: fname:1: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= share/pkgbase\" instead of \"${INSTALL_DATA_DIR}\".\n"+ | |||
224 | "WARN: fname:1: The INSTALL_*_DIR commands can only handle one directory at a time.\n") | |||
225 | ||||
226 | // See PR 46570, item "1. It does not" | |||
227 | shline.CheckShellCommandLine("for x in 1 2 3; do echo \"$$x\" || exit 1; done") | |||
228 | ||||
229 | c.Check(s.Output(), equals, "") // No warning about missing error checking. | |||
211 | } | 230 | } | |
212 | 231 | |||
213 | func (s *Suite) TestShellLine_CheckShelltext_nofix(c *check.C) { | 232 | func (s *Suite) TestShellLine_CheckShelltext_nofix(c *check.C) { | |
214 | s.UseCommandLine(c, "-Wall") | 233 | s.UseCommandLine(c, "-Wall") | |
215 | G.globalData.InitVartypes() | 234 | G.globalData.InitVartypes() | |
216 | s.RegisterTool(&Tool{Name: "echo", Predefined: true}) | 235 | s.RegisterTool(&Tool{Name: "echo", Predefined: true}) | |
217 | G.Mk = s.NewMkLines("Makefile", | 236 | G.Mk = s.NewMkLines("Makefile", | |
218 | "\techo ${PKGNAME:Q}") | 237 | "\techo ${PKGNAME:Q}") | |
219 | shline := NewShellLine(G.Mk.mklines[0]) | 238 | shline := NewShellLine(G.Mk.mklines[0]) | |
220 | 239 | |||
221 | c.Check(shline.line.raw[0].textnl, equals, "\techo ${PKGNAME:Q}\n") | 240 | c.Check(shline.line.raw[0].textnl, equals, "\techo ${PKGNAME:Q}\n") | |
222 | c.Check(shline.line.raw[0].Lineno, equals, 1) | 241 | c.Check(shline.line.raw[0].Lineno, equals, 1) | |
223 | 242 | |||
@@ -261,26 +280,30 @@ func (s *Suite) TestShellLine_CheckShell | @@ -261,26 +280,30 @@ func (s *Suite) TestShellLine_CheckShell | |||
261 | G.globalData.InitVartypes() | 280 | G.globalData.InitVartypes() | |
262 | G.Mk = s.NewMkLines("fname", | 281 | G.Mk = s.NewMkLines("fname", | |
263 | "# dummy") | 282 | "# dummy") | |
264 | shline := NewShellLine(G.Mk.mklines[0]) | 283 | shline := NewShellLine(G.Mk.mklines[0]) | |
265 | 284 | |||
266 | // foobar="`echo \"foo bar\"`" | 285 | // foobar="`echo \"foo bar\"`" | |
267 | text := "foobar=\"`echo \\\"foo bar\\\"`\"" | 286 | text := "foobar=\"`echo \\\"foo bar\\\"`\"" | |
268 | 287 | |||
269 | tokens, rest := splitIntoShellTokens(dummyLine, text) | 288 | tokens, rest := splitIntoShellTokens(dummyLine, text) | |
270 | 289 | |||
271 | c.Check(tokens, deepEquals, []string{text}) | 290 | c.Check(tokens, deepEquals, []string{text}) | |
272 | c.Check(rest, equals, "") | 291 | c.Check(rest, equals, "") | |
273 | 292 | |||
293 | shline.CheckWord(text, false) | |||
294 | ||||
295 | c.Check(s.Output(), equals, "WARN: fname:1: Unknown shell command \"echo\".\n") | |||
296 | ||||
274 | shline.CheckShellCommandLine(text) | 297 | shline.CheckShellCommandLine(text) | |
275 | 298 | |||
276 | c.Check(s.Output(), equals, ""+ // No parse errors | 299 | c.Check(s.Output(), equals, ""+ // No parse errors | |
277 | "WARN: fname:1: Unknown shell command \"echo\".\n") | 300 | "WARN: fname:1: Unknown shell command \"echo\".\n") | |
278 | } | 301 | } | |
279 | 302 | |||
280 | func (s *Suite) TestShellLine_CheckShelltext_DollarWithoutVariable(c *check.C) { | 303 | func (s *Suite) TestShellLine_CheckShelltext_DollarWithoutVariable(c *check.C) { | |
281 | G.globalData.InitVartypes() | 304 | G.globalData.InitVartypes() | |
282 | G.Mk = s.NewMkLines("fname", | 305 | G.Mk = s.NewMkLines("fname", | |
283 | "# dummy") | 306 | "# dummy") | |
284 | shline := NewShellLine(G.Mk.mklines[0]) | 307 | shline := NewShellLine(G.Mk.mklines[0]) | |
285 | s.RegisterTool(&Tool{Name: "pax", Varname: "PAX"}) | 308 | s.RegisterTool(&Tool{Name: "pax", Varname: "PAX"}) | |
286 | G.Mk.tools["pax"] = true | 309 | G.Mk.tools["pax"] = true | |
@@ -291,27 +314,27 @@ func (s *Suite) TestShellLine_CheckShell | @@ -291,27 +314,27 @@ func (s *Suite) TestShellLine_CheckShell | |||
291 | } | 314 | } | |
292 | 315 | |||
293 | func (s *Suite) Test_ShellLine_CheckWord(c *check.C) { | 316 | func (s *Suite) Test_ShellLine_CheckWord(c *check.C) { | |
294 | s.UseCommandLine(c, "-Wall") | 317 | s.UseCommandLine(c, "-Wall") | |
295 | G.globalData.InitVartypes() | 318 | G.globalData.InitVartypes() | |
296 | shline := NewShellLine(NewMkLine(NewLine("fname", 1, "# dummy", nil))) | 319 | shline := NewShellLine(NewMkLine(NewLine("fname", 1, "# dummy", nil))) | |
297 | 320 | |||
298 | shline.CheckWord("${${list}}", false) | 321 | shline.CheckWord("${${list}}", false) | |
299 | 322 | |||
300 | c.Check(s.Output(), equals, "") // No warning for variables that are completely indirect. | 323 | c.Check(s.Output(), equals, "") // No warning for variables that are completely indirect. | |
301 | 324 | |||
302 | shline.CheckWord("${SED_FILE.${id}}", false) | 325 | shline.CheckWord("${SED_FILE.${id}}", false) | |
303 | 326 | |||
304 | c.Check(s.Output(), equals, "WARN: fname:1: SED_FILE.${id} is used but not defined. Spelling mistake?\n") | 327 | c.Check(s.Output(), equals, "") // No warning for variables that are partly indirect. | |
305 | 328 | |||
306 | shline.CheckWord("\"$@\"", false) | 329 | shline.CheckWord("\"$@\"", false) | |
307 | 330 | |||
308 | c.Check(s.Output(), equals, "WARN: fname:1: Please use \"${.TARGET}\" instead of \"$@\".\n") | 331 | c.Check(s.Output(), equals, "WARN: fname:1: Please use \"${.TARGET}\" instead of \"$@\".\n") | |
309 | 332 | |||
310 | shline.CheckWord("${COMMENT:Q}", true) | 333 | shline.CheckWord("${COMMENT:Q}", true) | |
311 | 334 | |||
312 | c.Check(s.Output(), equals, "WARN: fname:1: COMMENT may not be used in any file; it is a write-only variable.\n") | 335 | c.Check(s.Output(), equals, "WARN: fname:1: COMMENT may not be used in any file; it is a write-only variable.\n") | |
313 | 336 | |||
314 | shline.CheckWord("\"${DISTINFO_FILE:Q}\"", true) | 337 | shline.CheckWord("\"${DISTINFO_FILE:Q}\"", true) | |
315 | 338 | |||
316 | c.Check(s.Output(), equals, ""+ | 339 | c.Check(s.Output(), equals, ""+ | |
317 | "WARN: fname:1: DISTINFO_FILE may not be used in this file; it would be ok in Makefile, Makefile.*, *.mk.\n"+ | 340 | "WARN: fname:1: DISTINFO_FILE may not be used in this file; it would be ok in Makefile, Makefile.*, *.mk.\n"+ | |
@@ -426,39 +449,52 @@ func (s *Suite) TestShellLine_CheckShell | @@ -426,39 +449,52 @@ func (s *Suite) TestShellLine_CheckShell | |||
426 | shline := NewShellLine(NewMkLine(NewLine("Makefile", 85, "\t${RUN} uname=$$(uname)", nil))) | 449 | shline := NewShellLine(NewMkLine(NewLine("Makefile", 85, "\t${RUN} uname=$$(uname)", nil))) | |
427 | 450 | |||
428 | shline.CheckShellCommandLine(shline.mkline.Shellcmd()) | 451 | shline.CheckShellCommandLine(shline.mkline.Shellcmd()) | |
429 | 452 | |||
430 | c.Check(s.Output(), equals, "WARN: Makefile:85: Invoking subshells via $(...) is not portable enough.\n") | 453 | c.Check(s.Output(), equals, "WARN: Makefile:85: Invoking subshells via $(...) is not portable enough.\n") | |
431 | } | 454 | } | |
432 | 455 | |||
433 | func (s *Suite) TestShellLine_CheckShellCommandLine_InstallDirs(c *check.C) { | 456 | func (s *Suite) TestShellLine_CheckShellCommandLine_InstallDirs(c *check.C) { | |
434 | shline := NewShellLine(NewMkLine(NewLine("Makefile", 85, "\t${RUN} ${INSTALL_DATA_DIR} ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2", nil))) | 457 | shline := NewShellLine(NewMkLine(NewLine("Makefile", 85, "\t${RUN} ${INSTALL_DATA_DIR} ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2", nil))) | |
435 | 458 | |||
436 | shline.CheckShellCommandLine(shline.mkline.Shellcmd()) | 459 | shline.CheckShellCommandLine(shline.mkline.Shellcmd()) | |
437 | 460 | |||
438 | c.Check(s.Output(), equals, ""+ | 461 | c.Check(s.Output(), equals, ""+ | |
439 | "NOTE: Makefile:85: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= dir1\" instead of this command.\n"+ | 462 | "NOTE: Makefile:85: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= dir1\" instead of \"${INSTALL_DATA_DIR}\".\n"+ | |
440 | "NOTE: Makefile:85: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= dir2\" instead of this command.\n"+ | 463 | "NOTE: Makefile:85: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= dir2\" instead of \"${INSTALL_DATA_DIR}\".\n"+ | |
464 | "WARN: Makefile:85: The INSTALL_*_DIR commands can only handle one directory at a time.\n") | |||
465 | ||||
466 | shline.CheckShellCommandLine("${INSTALL_DATA_DIR} -d -m 0755 ${DESTDIR}${PREFIX}/share/examples/gdchart") | |||
467 | ||||
468 | // No warning about multiple directories, since 0755 is an option, not an argument. | |||
469 | c.Check(s.Output(), equals, ""+ | |||
470 | "NOTE: Makefile:85: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= share/examples/gdchart\" instead of \"${INSTALL_DATA_DIR}\".\n") | |||
471 | ||||
472 | shline.CheckShellCommandLine("${INSTALL_DATA_DIR} -d -m 0755 ${DESTDIR}${PREFIX}/dir1 ${PREFIX}/dir2") | |||
473 | ||||
474 | c.Check(s.Output(), equals, ""+ | |||
475 | "NOTE: Makefile:85: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= dir1\" instead of \"${INSTALL_DATA_DIR}\".\n"+ | |||
476 | "NOTE: Makefile:85: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= dir2\" instead of \"${INSTALL_DATA_DIR}\".\n"+ | |||
441 | "WARN: Makefile:85: The INSTALL_*_DIR commands can only handle one directory at a time.\n") | 477 | "WARN: Makefile:85: The INSTALL_*_DIR commands can only handle one directory at a time.\n") | |
442 | } | 478 | } | |
443 | 479 | |||
444 | func (s *Suite) TestShellLine_CheckShellCommandLine_InstallD(c *check.C) { | 480 | func (s *Suite) TestShellLine_CheckShellCommandLine_InstallD(c *check.C) { | |
445 | shline := NewShellLine(NewMkLine(NewLine("Makefile", 85, "\t${RUN} ${INSTALL} -d ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2", nil))) | 481 | shline := NewShellLine(NewMkLine(NewLine("Makefile", 85, "\t${RUN} ${INSTALL} -d ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2", nil))) | |
446 | 482 | |||
447 | shline.CheckShellCommandLine(shline.mkline.Shellcmd()) | 483 | shline.CheckShellCommandLine(shline.mkline.Shellcmd()) | |
448 | 484 | |||
449 | c.Check(s.Output(), equals, ""+ | 485 | c.Check(s.Output(), equals, ""+ | |
450 | "WARN: Makefile:85: Please use AUTO_MKDIRS instead of \"${INSTALL} -d\".\n"+ | 486 | "NOTE: Makefile:85: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= dir1\" instead of \"${INSTALL} -d\".\n"+ | |
451 | "WARN: Makefile:85: Please use AUTO_MKDIRS instead of \"${INSTALL} -d\".\n") | 487 | "NOTE: Makefile:85: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= dir2\" instead of \"${INSTALL} -d\".\n") | |
452 | } | 488 | } | |
453 | 489 | |||
454 | func (s *Suite) TestShellLine_(c *check.C) { | 490 | func (s *Suite) TestShellLine_(c *check.C) { | |
455 | tmpfile := s.CreateTmpFile(c, "Makefile", ""+ | 491 | tmpfile := s.CreateTmpFile(c, "Makefile", ""+ | |
456 | "# $"+"NetBSD$\n"+ | 492 | "# $"+"NetBSD$\n"+ | |
457 | "pre-install:\n"+ | 493 | "pre-install:\n"+ | |
458 | "\t"+"# comment\\\n"+ | 494 | "\t"+"# comment\\\n"+ | |
459 | "\t"+"echo \"hello\"\n") | 495 | "\t"+"echo \"hello\"\n") | |
460 | lines := LoadNonemptyLines(tmpfile, true) | 496 | lines := LoadNonemptyLines(tmpfile, true) | |
461 | 497 | |||
462 | NewMkLines(lines).Check() | 498 | NewMkLines(lines).Check() | |
463 | 499 | |||
464 | c.Check(s.Output(), equals, "WARN: ~/Makefile:3--4: A shell comment does not stop at the end of line.\n") | 500 | c.Check(s.Output(), equals, "WARN: ~/Makefile:3--4: A shell comment does not stop at the end of line.\n") |
@@ -506,26 +506,29 @@ func (gd *GlobalData) loadDeprecatedVars | @@ -506,26 +506,29 @@ func (gd *GlobalData) loadDeprecatedVars | |||
506 | "SETGIDGAME": "Use USE_GAMESGROUP instead.", | 506 | "SETGIDGAME": "Use USE_GAMESGROUP instead.", | |
507 | "GAMEGRP": "Use GAMES_GROUP instead.", | 507 | "GAMEGRP": "Use GAMES_GROUP instead.", | |
508 | "GAMEOWN": "Use GAMES_USER instead.", | 508 | "GAMEOWN": "Use GAMES_USER instead.", | |
509 | 509 | |||
510 | // July 2013 | 510 | // July 2013 | |
511 | "USE_GNU_READLINE": "Include \"../../devel/readline/buildlink3.mk\" instead.", | 511 | "USE_GNU_READLINE": "Include \"../../devel/readline/buildlink3.mk\" instead.", | |
512 | 512 | |||
513 | // October 2014 | 513 | // October 2014 | |
514 | "SVR4_PKGNAME": "Just remove it.", | 514 | "SVR4_PKGNAME": "Just remove it.", | |
515 | "PKG_INSTALLATION_TYPES": "Just remove it.", | 515 | "PKG_INSTALLATION_TYPES": "Just remove it.", | |
516 | 516 | |||
517 | // January 2016 | 517 | // January 2016 | |
518 | "SUBST_POSTCMD.*": "Has been removed, as it seemed unused.", | 518 | "SUBST_POSTCMD.*": "Has been removed, as it seemed unused.", | |
519 | ||||
520 | // June 2016 | |||
521 | "USE_CROSSBASE": "Has been removed.", | |||
519 | } | 522 | } | |
520 | } | 523 | } | |
521 | 524 | |||
522 | // See `mk/tools/`. | 525 | // See `mk/tools/`. | |
523 | type Tool struct { | 526 | type Tool struct { | |
524 | Name string // e.g. "sed", "gzip" | 527 | Name string // e.g. "sed", "gzip" | |
525 | Varname string // e.g. "SED", "GZIP_CMD" | 528 | Varname string // e.g. "SED", "GZIP_CMD" | |
526 | MustUseVarForm bool // True for `echo`, because of many differing implementations. | 529 | MustUseVarForm bool // True for `echo`, because of many differing implementations. | |
527 | Predefined bool // This tool is used by the pkgsrc infrastructure, therefore the package does not need to add it to `USE_TOOLS` explicitly. | 530 | Predefined bool // This tool is used by the pkgsrc infrastructure, therefore the package does not need to add it to `USE_TOOLS` explicitly. | |
528 | UsableAtLoadtime bool // May be used after including `bsd.prefs.mk`. | 531 | UsableAtLoadtime bool // May be used after including `bsd.prefs.mk`. | |
529 | } | 532 | } | |
530 | 533 | |||
531 | type ToolRegistry struct { | 534 | type ToolRegistry struct { |
@@ -134,103 +134,113 @@ func (gd *GlobalData) InitVartypes() { | @@ -134,103 +134,113 @@ func (gd *GlobalData) InitVartypes() { | |||
134 | acl("BUILDLINK_DIR", lkNone, CheckvarPathname, "*: use") | 134 | acl("BUILDLINK_DIR", lkNone, CheckvarPathname, "*: use") | |
135 | bl3list("BUILDLINK_FILES.*", lkShell, CheckvarPathmask) | 135 | bl3list("BUILDLINK_FILES.*", lkShell, CheckvarPathmask) | |
136 | acl("BUILDLINK_FILES_CMD.*", lkNone, CheckvarShellCommand, "") | 136 | acl("BUILDLINK_FILES_CMD.*", lkNone, CheckvarShellCommand, "") | |
137 | acl("BUILDLINK_INCDIRS.*", lkShell, CheckvarPathname, "buildlink3.mk: default, append; Makefile, Makefile.common, *.mk: use") | 137 | acl("BUILDLINK_INCDIRS.*", lkShell, CheckvarPathname, "buildlink3.mk: default, append; Makefile, Makefile.common, *.mk: use") | |
138 | acl("BUILDLINK_JAVA_PREFIX.*", lkNone, CheckvarPathname, "buildlink3.mk: set, use") | 138 | acl("BUILDLINK_JAVA_PREFIX.*", lkNone, CheckvarPathname, "buildlink3.mk: set, use") | |
139 | acl("BUILDLINK_LDADD.*", lkShell, CheckvarLdFlag, "builtin.mk: set, default, append, use; buildlink3.mk: append, use; Makefile, Makefile.common, *.mk: use") | 139 | acl("BUILDLINK_LDADD.*", lkShell, CheckvarLdFlag, "builtin.mk: set, default, append, use; buildlink3.mk: append, use; Makefile, Makefile.common, *.mk: use") | |
140 | acl("BUILDLINK_LDFLAGS", lkShell, CheckvarLdFlag, "*: use") | 140 | acl("BUILDLINK_LDFLAGS", lkShell, CheckvarLdFlag, "*: use") | |
141 | bl3list("BUILDLINK_LDFLAGS.*", lkShell, CheckvarLdFlag) | 141 | bl3list("BUILDLINK_LDFLAGS.*", lkShell, CheckvarLdFlag) | |
142 | acl("BUILDLINK_LIBDIRS.*", lkShell, CheckvarPathname, "buildlink3.mk, builtin.mk: append; Makefile, Makefile.common, *.mk: use") | 142 | acl("BUILDLINK_LIBDIRS.*", lkShell, CheckvarPathname, "buildlink3.mk, builtin.mk: append; Makefile, Makefile.common, *.mk: use") | |
143 | acl("BUILDLINK_LIBS.*", lkShell, CheckvarLdFlag, "buildlink3.mk: append") | 143 | acl("BUILDLINK_LIBS.*", lkShell, CheckvarLdFlag, "buildlink3.mk: append") | |
144 | acl("BUILDLINK_PASSTHRU_DIRS", lkShell, CheckvarPathname, "Makefile, Makefile.common, buildlink3.mk, hacks.mk: append") | 144 | acl("BUILDLINK_PASSTHRU_DIRS", lkShell, CheckvarPathname, "Makefile, Makefile.common, buildlink3.mk, hacks.mk: append") | |
145 | acl("BUILDLINK_PASSTHRU_RPATHDIRS", lkShell, CheckvarPathname, "Makefile, Makefile.common, buildlink3.mk, hacks.mk: append") | 145 | acl("BUILDLINK_PASSTHRU_RPATHDIRS", lkShell, CheckvarPathname, "Makefile, Makefile.common, buildlink3.mk, hacks.mk: append") | |
146 | acl("BUILDLINK_PKGSRCDIR.*", lkNone, CheckvarRelativePkgDir, "buildlink3.mk: default, use-loadtime") | 146 | acl("BUILDLINK_PKGSRCDIR.*", lkNone, CheckvarRelativePkgDir, "buildlink3.mk: default, use-loadtime") | |
147 | acl("BUILDLINK_PREFIX.*", lkNone, CheckvarPathname, "builtin.mk: set, use; buildlink3.mk: use; Makefile, Makefile.common, *.mk: use") | 147 | acl("BUILDLINK_PREFIX.*", lkNone, CheckvarPathname, "builtin.mk: set, use; Makefile, Makefile.common, *.mk: use") | |
148 | acl("BUILDLINK_RPATHDIRS.*", lkShell, CheckvarPathname, "buildlink3.mk: append") | 148 | acl("BUILDLINK_RPATHDIRS.*", lkShell, CheckvarPathname, "buildlink3.mk: append") | |
149 | acl("BUILDLINK_TARGETS", lkShell, CheckvarIdentifier, "") | 149 | acl("BUILDLINK_TARGETS", lkShell, CheckvarIdentifier, "") | |
150 | acl("BUILDLINK_FNAME_TRANSFORM.*", lkNone, CheckvarSedCommands, "Makefile, buildlink3.mk, builtin.mk, hacks.mk: append") | 150 | acl("BUILDLINK_FNAME_TRANSFORM.*", lkNone, CheckvarSedCommands, "Makefile, buildlink3.mk, builtin.mk, hacks.mk: append") | |
151 | acl("BUILDLINK_TRANSFORM", lkShell, CheckvarWrapperTransform, "*: append") | 151 | acl("BUILDLINK_TRANSFORM", lkShell, CheckvarWrapperTransform, "*: append") | |
152 | acl("BUILDLINK_TRANSFORM.*", lkShell, CheckvarWrapperTransform, "*: append") | |||
152 | acl("BUILDLINK_TREE", lkShell, CheckvarIdentifier, "buildlink3.mk: append") | 153 | acl("BUILDLINK_TREE", lkShell, CheckvarIdentifier, "buildlink3.mk: append") | |
153 | acl("BUILD_DEFS", lkShell, CheckvarVarname, "Makefile, Makefile.common, options.mk: append") | 154 | acl("BUILD_DEFS", lkShell, CheckvarVarname, "Makefile, Makefile.common, options.mk: append") | |
154 | acl("BUILD_DEPENDS", lkSpace, CheckvarDependencyWithPath, "Makefile, Makefile.common, *.mk: append") | 155 | acl("BUILD_DEPENDS", lkSpace, CheckvarDependencyWithPath, "Makefile, Makefile.common, *.mk: append") | |
155 | pkglist("BUILD_DIRS", lkShell, CheckvarWrksrcSubdirectory) | 156 | pkglist("BUILD_DIRS", lkShell, CheckvarWrksrcSubdirectory) | |
156 | pkglist("BUILD_ENV", lkShell, CheckvarShellWord) | 157 | pkglist("BUILD_ENV", lkShell, CheckvarShellWord) | |
157 | sys("BUILD_MAKE_CMD", lkNone, CheckvarShellCommand) | 158 | sys("BUILD_MAKE_CMD", lkNone, CheckvarShellCommand) | |
158 | pkglist("BUILD_MAKE_FLAGS", lkShell, CheckvarShellWord) | 159 | pkglist("BUILD_MAKE_FLAGS", lkShell, CheckvarShellWord) | |
159 | pkglist("BUILD_TARGET", lkShell, CheckvarIdentifier) | 160 | pkglist("BUILD_TARGET", lkShell, CheckvarIdentifier) | |
161 | pkglist("BUILD_TARGET.*", lkShell, CheckvarIdentifier) | |||
160 | pkg("BUILD_USES_MSGFMT", lkNone, CheckvarYes) | 162 | pkg("BUILD_USES_MSGFMT", lkNone, CheckvarYes) | |
161 | acl("BUILTIN_PKG", lkNone, CheckvarIdentifier, "builtin.mk: set, use-loadtime, use") | 163 | acl("BUILTIN_PKG", lkNone, CheckvarIdentifier, "builtin.mk: set, use-loadtime, use") | |
162 | acl("BUILTIN_PKG.*", lkNone, CheckvarPkgName, "builtin.mk: set, use-loadtime, use") | 164 | acl("BUILTIN_PKG.*", lkNone, CheckvarPkgName, "builtin.mk: set, use-loadtime, use") | |
163 | acl("BUILTIN_FIND_FILES_VAR", lkShell, CheckvarVarname, "builtin.mk: set") | 165 | acl("BUILTIN_FIND_FILES_VAR", lkShell, CheckvarVarname, "builtin.mk: set") | |
164 | acl("BUILTIN_FIND_FILES.*", lkShell, CheckvarPathname, "builtin.mk: set") | 166 | acl("BUILTIN_FIND_FILES.*", lkShell, CheckvarPathname, "builtin.mk: set") | |
165 | acl("BUILTIN_FIND_GREP.*", lkNone, CheckvarString, "builtin.mk: set") | 167 | acl("BUILTIN_FIND_GREP.*", lkNone, CheckvarString, "builtin.mk: set") | |
168 | acl("BUILTIN_FIND_HEADERS_VAR", lkShell, CheckvarVarname, "builtin.mk: set") | |||
169 | acl("BUILTIN_FIND_HEADERS.*", lkShell, CheckvarPathname, "builtin.mk: set") | |||
166 | acl("BUILTIN_FIND_LIBS", lkShell, CheckvarPathname, "builtin.mk: set") | 170 | acl("BUILTIN_FIND_LIBS", lkShell, CheckvarPathname, "builtin.mk: set") | |
167 | acl("BUILTIN_IMAKE_CHECK", lkShell, CheckvarUnchecked, "builtin.mk: set") | 171 | acl("BUILTIN_IMAKE_CHECK", lkShell, CheckvarUnchecked, "builtin.mk: set") | |
168 | acl("BUILTIN_IMAKE_CHECK.*", lkNone, CheckvarYesNo, "") | 172 | acl("BUILTIN_IMAKE_CHECK.*", lkNone, CheckvarYesNo, "") | |
169 | sys("BUILTIN_X11_TYPE", lkNone, CheckvarUnchecked) | 173 | sys("BUILTIN_X11_TYPE", lkNone, CheckvarUnchecked) | |
170 | sys("BUILTIN_X11_VERSION", lkNone, CheckvarUnchecked) | 174 | sys("BUILTIN_X11_VERSION", lkNone, CheckvarUnchecked) | |
171 | acl("CATEGORIES", lkShell, CheckvarCategory, "Makefile: set, append; Makefile.common: set, default, append") | 175 | acl("CATEGORIES", lkShell, CheckvarCategory, "Makefile: set, append; Makefile.common: set, default, append") | |
172 | sys("CC_VERSION", lkNone, CheckvarMessage) | 176 | sys("CC_VERSION", lkNone, CheckvarMessage) | |
173 | sys("CC", lkNone, CheckvarShellCommand) | 177 | sys("CC", lkNone, CheckvarShellCommand) | |
174 | pkglist("CFLAGS*", lkShell, CheckvarCFlag) // may also be changed by the user | 178 | pkglist("CFLAGS", lkShell, CheckvarCFlag) // may also be changed by the user | |
179 | pkglist("CFLAGS.*", lkShell, CheckvarCFlag) // may also be changed by the user | |||
175 | acl("CHECK_BUILTIN", lkNone, CheckvarYesNo, "builtin.mk: default; Makefile: set") | 180 | acl("CHECK_BUILTIN", lkNone, CheckvarYesNo, "builtin.mk: default; Makefile: set") | |
176 | acl("CHECK_BUILTIN.*", lkNone, CheckvarYesNo, "buildlink3.mk: set; builtin.mk: default; *: use-loadtime") | 181 | acl("CHECK_BUILTIN.*", lkNone, CheckvarYesNo, "Makefile, options.mk, buildlink3.mk: set; builtin.mk: default; *: use-loadtime") | |
177 | acl("CHECK_FILES_SKIP", lkShell, CheckvarBasicRegularExpression, "Makefile, Makefile.common: append") | 182 | acl("CHECK_FILES_SKIP", lkShell, CheckvarBasicRegularExpression, "Makefile, Makefile.common: append") | |
178 | pkg("CHECK_FILES_SUPPORTED", lkNone, CheckvarYesNo) | 183 | pkg("CHECK_FILES_SUPPORTED", lkNone, CheckvarYesNo) | |
179 | usr("CHECK_HEADERS", lkNone, CheckvarYesNo) | 184 | usr("CHECK_HEADERS", lkNone, CheckvarYesNo) | |
180 | pkglist("CHECK_HEADERS_SKIP", lkShell, CheckvarPathmask) | 185 | pkglist("CHECK_HEADERS_SKIP", lkShell, CheckvarPathmask) | |
181 | usr("CHECK_INTERPRETER", lkNone, CheckvarYesNo) | 186 | usr("CHECK_INTERPRETER", lkNone, CheckvarYesNo) | |
182 | pkglist("CHECK_INTERPRETER_SKIP", lkShell, CheckvarPathmask) | 187 | pkglist("CHECK_INTERPRETER_SKIP", lkShell, CheckvarPathmask) | |
183 | usr("CHECK_PERMS", lkNone, CheckvarYesNo) | 188 | usr("CHECK_PERMS", lkNone, CheckvarYesNo) | |
184 | pkglist("CHECK_PERMS_SKIP", lkShell, CheckvarPathmask) | 189 | pkglist("CHECK_PERMS_SKIP", lkShell, CheckvarPathmask) | |
185 | usr("CHECK_PORTABILITY", lkNone, CheckvarYesNo) | 190 | usr("CHECK_PORTABILITY", lkNone, CheckvarYesNo) | |
186 | pkglist("CHECK_PORTABILITY_SKIP", lkShell, CheckvarPathmask) | 191 | pkglist("CHECK_PORTABILITY_SKIP", lkShell, CheckvarPathmask) | |
187 | acl("CHECK_SHLIBS", lkNone, CheckvarYesNo, "Makefile: set") | 192 | acl("CHECK_SHLIBS", lkNone, CheckvarYesNo, "Makefile: set") | |
188 | pkglist("CHECK_SHLIBS_SKIP", lkShell, CheckvarPathmask) | 193 | pkglist("CHECK_SHLIBS_SKIP", lkShell, CheckvarPathmask) | |
189 | acl("CHECK_SHLIBS_SUPPORTED", lkNone, CheckvarYesNo, "Makefile: set") | 194 | acl("CHECK_SHLIBS_SUPPORTED", lkNone, CheckvarYesNo, "Makefile: set") | |
190 | pkglist("CHECK_WRKREF_SKIP", lkShell, CheckvarPathmask) | 195 | pkglist("CHECK_WRKREF_SKIP", lkShell, CheckvarPathmask) | |
191 | pkg("CMAKE_ARG_PATH", lkNone, CheckvarPathname) | 196 | pkg("CMAKE_ARG_PATH", lkNone, CheckvarPathname) | |
192 | pkglist("CMAKE_ARGS", lkShell, CheckvarShellWord) | 197 | pkglist("CMAKE_ARGS", lkShell, CheckvarShellWord) | |
198 | pkglist("CMAKE_ARGS.*", lkShell, CheckvarShellWord) | |||
193 | acl("COMMENT", lkNone, CheckvarComment, "Makefile, Makefile.common: set, append") | 199 | acl("COMMENT", lkNone, CheckvarComment, "Makefile, Makefile.common: set, append") | |
194 | acl("COMPILER_RPATH_FLAG", lkNone, enum("-Wl,-rpath"), "*: use") | 200 | acl("COMPILER_RPATH_FLAG", lkNone, enum("-Wl,-rpath"), "*: use") | |
195 | pkglist("CONFIGURE_ARGS", lkShell, CheckvarShellWord) | 201 | pkglist("CONFIGURE_ARGS", lkShell, CheckvarShellWord) | |
202 | pkglist("CONFIGURE_ARGS.*", lkShell, CheckvarShellWord) | |||
196 | pkglist("CONFIGURE_DIRS", lkShell, CheckvarWrksrcSubdirectory) | 203 | pkglist("CONFIGURE_DIRS", lkShell, CheckvarWrksrcSubdirectory) | |
197 | acl("CONFIGURE_ENV", lkShell, CheckvarShellWord, "Makefile, Makefile.common: append, set, use; buildlink3.mk, builtin.mk: append; *.mk: append, use") | 204 | acl("CONFIGURE_ENV", lkShell, CheckvarShellWord, "Makefile, Makefile.common: append, set, use; buildlink3.mk, builtin.mk: append; *.mk: append, use") | |
205 | acl("CONFIGURE_ENV.*", lkShell, CheckvarShellWord, "Makefile, Makefile.common: append, set, use; buildlink3.mk, builtin.mk: append; *.mk: append, use") | |||
198 | pkg("CONFIGURE_HAS_INFODIR", lkNone, CheckvarYesNo) | 206 | pkg("CONFIGURE_HAS_INFODIR", lkNone, CheckvarYesNo) | |
199 | pkg("CONFIGURE_HAS_LIBDIR", lkNone, CheckvarYesNo) | 207 | pkg("CONFIGURE_HAS_LIBDIR", lkNone, CheckvarYesNo) | |
200 | pkg("CONFIGURE_HAS_MANDIR", lkNone, CheckvarYesNo) | 208 | pkg("CONFIGURE_HAS_MANDIR", lkNone, CheckvarYesNo) | |
201 | pkg("CONFIGURE_SCRIPT", lkNone, CheckvarPathname) | 209 | pkg("CONFIGURE_SCRIPT", lkNone, CheckvarPathname) | |
202 | acl("CONFIG_GUESS_OVERRIDE", lkShell, CheckvarPathmask, "Makefile, Makefile.common: set, append") | 210 | acl("CONFIG_GUESS_OVERRIDE", lkShell, CheckvarPathmask, "Makefile, Makefile.common: set, append") | |
203 | acl("CONFIG_STATUS_OVERRIDE", lkShell, CheckvarPathmask, "Makefile, Makefile.common: set, append") | 211 | acl("CONFIG_STATUS_OVERRIDE", lkShell, CheckvarPathmask, "Makefile, Makefile.common: set, append") | |
204 | acl("CONFIG_SHELL", lkNone, CheckvarPathname, "Makefile, Makefile.common: set") | 212 | acl("CONFIG_SHELL", lkNone, CheckvarPathname, "Makefile, Makefile.common: set") | |
205 | acl("CONFIG_SUB_OVERRIDE", lkShell, CheckvarPathmask, "Makefile, Makefile.common: set, append") | 213 | acl("CONFIG_SUB_OVERRIDE", lkShell, CheckvarPathmask, "Makefile, Makefile.common: set, append") | |
206 | pkglist("CONFLICTS", lkSpace, CheckvarDependency) | 214 | pkglist("CONFLICTS", lkSpace, CheckvarDependency) | |
207 | pkglist("CONF_FILES", lkShell, CheckvarShellWord) | 215 | pkglist("CONF_FILES", lkShell, CheckvarShellWord) | |
208 | pkg("CONF_FILES_MODE", lkNone, enum("0644 0640 0600 0400")) | 216 | pkg("CONF_FILES_MODE", lkNone, enum("0644 0640 0600 0400")) | |
209 | pkglist("CONF_FILES_PERMS", lkShell, CheckvarPerms) | 217 | pkglist("CONF_FILES_PERMS", lkShell, CheckvarPerms) | |
210 | sys("COPY", lkNone, enum("-c")) // The flag that tells ${INSTALL} to copy a file | 218 | sys("COPY", lkNone, enum("-c")) // The flag that tells ${INSTALL} to copy a file | |
211 | sys("CPP", lkNone, CheckvarShellCommand) | 219 | sys("CPP", lkNone, CheckvarShellCommand) | |
212 | pkglist("CPPFLAGS*", lkShell, CheckvarCFlag) | 220 | pkglist("CPPFLAGS", lkShell, CheckvarCFlag) | |
221 | pkglist("CPPFLAGS.*", lkShell, CheckvarCFlag) | |||
213 | acl("CRYPTO", lkNone, CheckvarYes, "Makefile: set") | 222 | acl("CRYPTO", lkNone, CheckvarYes, "Makefile: set") | |
214 | sys("CXX", lkNone, CheckvarShellCommand) | 223 | sys("CXX", lkNone, CheckvarShellCommand) | |
215 | pkglist("CXXFLAGS*", lkShell, CheckvarCFlag) | 224 | pkglist("CXXFLAGS", lkShell, CheckvarCFlag) | |
225 | pkglist("CXXFLAGS.*", lkShell, CheckvarCFlag) | |||
216 | acl("DEINSTALL_FILE", lkNone, CheckvarPathname, "Makefile: set") | 226 | acl("DEINSTALL_FILE", lkNone, CheckvarPathname, "Makefile: set") | |
217 | acl("DEINSTALL_SRC", lkShell, CheckvarPathname, "Makefile: set; Makefile.common: default, set") | 227 | acl("DEINSTALL_SRC", lkShell, CheckvarPathname, "Makefile: set; Makefile.common: default, set") | |
218 | acl("DEINSTALL_TEMPLATES", lkShell, CheckvarPathname, "Makefile: set, append; Makefile.common: set, default, append") | 228 | acl("DEINSTALL_TEMPLATES", lkShell, CheckvarPathname, "Makefile: set, append; Makefile.common: set, default, append") | |
219 | sys("DELAYED_ERROR_MSG", lkNone, CheckvarShellCommand) | 229 | sys("DELAYED_ERROR_MSG", lkNone, CheckvarShellCommand) | |
220 | sys("DELAYED_WARNING_MSG", lkNone, CheckvarShellCommand) | 230 | sys("DELAYED_WARNING_MSG", lkNone, CheckvarShellCommand) | |
221 | pkglist("DEPENDS", lkSpace, CheckvarDependencyWithPath) | 231 | pkglist("DEPENDS", lkSpace, CheckvarDependencyWithPath) | |
222 | usr("DEPENDS_TARGET", lkShell, CheckvarIdentifier) | 232 | usr("DEPENDS_TARGET", lkShell, CheckvarIdentifier) | |
223 | acl("DESCR_SRC", lkShell, CheckvarPathname, "Makefile: set; Makefile.common: default, set") | 233 | acl("DESCR_SRC", lkShell, CheckvarPathname, "Makefile: set, append; Makefile.common: default, set") | |
224 | sys("DESTDIR", lkNone, CheckvarPathname) | 234 | sys("DESTDIR", lkNone, CheckvarPathname) | |
225 | acl("DESTDIR_VARNAME", lkNone, CheckvarVarname, "Makefile, Makefile.common: set") | 235 | acl("DESTDIR_VARNAME", lkNone, CheckvarVarname, "Makefile, Makefile.common: set") | |
226 | sys("DEVOSSAUDIO", lkNone, CheckvarPathname) | 236 | sys("DEVOSSAUDIO", lkNone, CheckvarPathname) | |
227 | sys("DEVOSSSOUND", lkNone, CheckvarPathname) | 237 | sys("DEVOSSSOUND", lkNone, CheckvarPathname) | |
228 | pkglist("DISTFILES", lkShell, CheckvarFilename) | 238 | pkglist("DISTFILES", lkShell, CheckvarFilename) | |
229 | pkg("DISTINFO_FILE", lkNone, CheckvarRelativePkgPath) | 239 | pkg("DISTINFO_FILE", lkNone, CheckvarRelativePkgPath) | |
230 | pkg("DISTNAME", lkNone, CheckvarFilename) | 240 | pkg("DISTNAME", lkNone, CheckvarFilename) | |
231 | pkg("DIST_SUBDIR", lkNone, CheckvarPathname) | 241 | pkg("DIST_SUBDIR", lkNone, CheckvarPathname) | |
232 | acl("DJB_BUILD_ARGS", lkShell, CheckvarShellWord, "") | 242 | acl("DJB_BUILD_ARGS", lkShell, CheckvarShellWord, "") | |
233 | acl("DJB_BUILD_TARGETS", lkShell, CheckvarIdentifier, "") | 243 | acl("DJB_BUILD_TARGETS", lkShell, CheckvarIdentifier, "") | |
234 | acl("DJB_CONFIG_CMDS", lkNone, CheckvarShellCommands, "options.mk: set") | 244 | acl("DJB_CONFIG_CMDS", lkNone, CheckvarShellCommands, "options.mk: set") | |
235 | acl("DJB_CONFIG_DIRS", lkShell, CheckvarWrksrcSubdirectory, "") | 245 | acl("DJB_CONFIG_DIRS", lkShell, CheckvarWrksrcSubdirectory, "") | |
236 | acl("DJB_CONFIG_HOME", lkNone, CheckvarFilename, "") | 246 | acl("DJB_CONFIG_HOME", lkNone, CheckvarFilename, "") | |
@@ -366,54 +376,58 @@ func (gd *GlobalData) InitVartypes() { | @@ -366,54 +376,58 @@ func (gd *GlobalData) InitVartypes() { | |||
366 | acl("IS_BUILTIN.*", lkNone, CheckvarYesNoIndirectly, "builtin.mk: set, use-loadtime, use") | 376 | acl("IS_BUILTIN.*", lkNone, CheckvarYesNoIndirectly, "builtin.mk: set, use-loadtime, use") | |
367 | sys("JAVA_BINPREFIX", lkNone, CheckvarPathname) | 377 | sys("JAVA_BINPREFIX", lkNone, CheckvarPathname) | |
368 | pkg("JAVA_CLASSPATH", lkNone, CheckvarShellWord) | 378 | pkg("JAVA_CLASSPATH", lkNone, CheckvarShellWord) | |
369 | pkg("JAVA_HOME", lkNone, CheckvarPathname) | 379 | pkg("JAVA_HOME", lkNone, CheckvarPathname) | |
370 | pkg("JAVA_NAME", lkNone, CheckvarFilename) | 380 | pkg("JAVA_NAME", lkNone, CheckvarFilename) | |
371 | pkglist("JAVA_UNLIMIT", lkShell, enum("cmdsize datasize stacksize")) | 381 | pkglist("JAVA_UNLIMIT", lkShell, enum("cmdsize datasize stacksize")) | |
372 | pkglist("JAVA_WRAPPERS", lkSpace, CheckvarFilename) | 382 | pkglist("JAVA_WRAPPERS", lkSpace, CheckvarFilename) | |
373 | pkg("JAVA_WRAPPER_BIN.*", lkNone, CheckvarPathname) | 383 | pkg("JAVA_WRAPPER_BIN.*", lkNone, CheckvarPathname) | |
374 | sys("KRB5BASE", lkNone, CheckvarPathname) | 384 | sys("KRB5BASE", lkNone, CheckvarPathname) | |
375 | acl("KRB5_ACCEPTED", lkShell, enum("heimdal mit-krb5"), "") | 385 | acl("KRB5_ACCEPTED", lkShell, enum("heimdal mit-krb5"), "") | |
376 | usr("KRB5_DEFAULT", lkNone, enum("heimdal mit-krb5")) | 386 | usr("KRB5_DEFAULT", lkNone, enum("heimdal mit-krb5")) | |
377 | sys("KRB5_TYPE", lkNone, CheckvarIdentifier) | 387 | sys("KRB5_TYPE", lkNone, CheckvarIdentifier) | |
378 | sys("LD", lkNone, CheckvarShellCommand) | 388 | sys("LD", lkNone, CheckvarShellCommand) | |
379 | pkglist("LDFLAGS*", lkShell, CheckvarLdFlag) | 389 | pkglist("LDFLAGS", lkShell, CheckvarLdFlag) | |
390 | pkglist("LDFLAGS.*", lkShell, CheckvarLdFlag) | |||
380 | sys("LIBGRP", lkNone, CheckvarUserGroupName) | 391 | sys("LIBGRP", lkNone, CheckvarUserGroupName) | |
381 | sys("LIBMODE", lkNone, CheckvarFileMode) | 392 | sys("LIBMODE", lkNone, CheckvarFileMode) | |
382 | sys("LIBOWN", lkNone, CheckvarUserGroupName) | 393 | sys("LIBOWN", lkNone, CheckvarUserGroupName) | |
383 | sys("LIBOSSAUDIO", lkNone, CheckvarPathname) | 394 | sys("LIBOSSAUDIO", lkNone, CheckvarPathname) | |
384 | pkglist("LIBS*", lkShell, CheckvarLdFlag) | 395 | pkglist("LIBS", lkShell, CheckvarLdFlag) | |
396 | pkglist("LIBS.*", lkShell, CheckvarLdFlag) | |||
385 | sys("LIBTOOL", lkNone, CheckvarShellCommand) | 397 | sys("LIBTOOL", lkNone, CheckvarShellCommand) | |
386 | acl("LIBTOOL_OVERRIDE", lkShell, CheckvarPathmask, "Makefile: set, append") | 398 | acl("LIBTOOL_OVERRIDE", lkShell, CheckvarPathmask, "Makefile: set, append") | |
387 | pkglist("LIBTOOL_REQD", lkShell, CheckvarVersion) | 399 | pkglist("LIBTOOL_REQD", lkShell, CheckvarVersion) | |
388 | acl("LICENCE", lkNone, CheckvarLicense, "Makefile, Makefile.common: set; options.mk: set") | 400 | acl("LICENCE", lkNone, CheckvarLicense, "Makefile, Makefile.common, options.mk: set") | |
389 | acl("LICENSE", lkNone, CheckvarLicense, "Makefile, Makefile.common: set; options.mk: set") | 401 | acl("LICENSE", lkNone, CheckvarLicense, "Makefile, Makefile.common, options.mk: set") | |
390 | pkg("LICENSE_FILE", lkNone, CheckvarPathname) | 402 | pkg("LICENSE_FILE", lkNone, CheckvarPathname) | |
391 | sys("LINKER_RPATH_FLAG", lkNone, CheckvarShellWord) | 403 | sys("LINKER_RPATH_FLAG", lkNone, CheckvarShellWord) | |
392 | sys("LOWER_OPSYS", lkNone, CheckvarIdentifier) | 404 | sys("LOWER_OPSYS", lkNone, CheckvarIdentifier) | |
393 | acl("LTCONFIG_OVERRIDE", lkShell, CheckvarPathmask, "Makefile: set, append; Makefile.common: append") | 405 | acl("LTCONFIG_OVERRIDE", lkShell, CheckvarPathmask, "Makefile: set, append; Makefile.common: append") | |
394 | sys("MACHINE_ARCH", lkNone, enumMachineArch) | 406 | sys("MACHINE_ARCH", lkNone, enumMachineArch) | |
395 | sys("MACHINE_GNU_ARCH", lkNone, enumMachineGnuArch) | 407 | sys("MACHINE_GNU_ARCH", lkNone, enumMachineGnuArch) | |
396 | sys("MACHINE_GNU_PLATFORM", lkNone, CheckvarMachineGnuPlatform) | 408 | sys("MACHINE_GNU_PLATFORM", lkNone, CheckvarMachineGnuPlatform) | |
397 | sys("MACHINE_PLATFORM", lkNone, CheckvarMachinePlatform) | 409 | sys("MACHINE_PLATFORM", lkNone, CheckvarMachinePlatform) | |
398 | acl("MAINTAINER", lkNone, CheckvarMailAddress, "Makefile: set; Makefile.common: default") | 410 | acl("MAINTAINER", lkNone, CheckvarMailAddress, "Makefile: set; Makefile.common: default") | |
399 | sys("MAKE", lkNone, CheckvarShellCommand) | 411 | sys("MAKE", lkNone, CheckvarShellCommand) | |
400 | pkglist("MAKEFLAGS", lkShell, CheckvarShellWord) | 412 | pkglist("MAKEFLAGS", lkShell, CheckvarShellWord) | |
401 | acl("MAKEVARS", lkShell, CheckvarVarname, "builtin.mk: append; buildlink3.mk: append; hacks.mk: append") | 413 | acl("MAKEVARS", lkShell, CheckvarVarname, "buildlink3.mk, builtin.mk, hacks.mk: append") | |
402 | pkglist("MAKE_DIRS", lkShell, CheckvarPathname) | 414 | pkglist("MAKE_DIRS", lkShell, CheckvarPathname) | |
403 | pkglist("MAKE_DIRS_PERMS", lkShell, CheckvarPerms) | 415 | pkglist("MAKE_DIRS_PERMS", lkShell, CheckvarPerms) | |
404 | acl("MAKE_ENV", lkShell, CheckvarShellWord, "Makefile: append, set, use; Makefile.common: append, set, use; buildlink3.mk: append; builtin.mk: append; *.mk: append, use") | 416 | acl("MAKE_ENV", lkShell, CheckvarShellWord, "Makefile, Makefile.common: append, set, use; buildlink3.mk, builtin.mk: append; *.mk: append, use") | |
417 | acl("MAKE_ENV.*", lkShell, CheckvarShellWord, "Makefile, Makefile.common: append, set, use; buildlink3.mk, builtin.mk: append; *.mk: append, use") | |||
405 | pkg("MAKE_FILE", lkNone, CheckvarPathname) | 418 | pkg("MAKE_FILE", lkNone, CheckvarPathname) | |
406 | pkglist("MAKE_FLAGS", lkShell, CheckvarShellWord) | 419 | pkglist("MAKE_FLAGS", lkShell, CheckvarShellWord) | |
420 | pkglist("MAKE_FLAGS.*", lkShell, CheckvarShellWord) | |||
407 | usr("MAKE_JOBS", lkNone, CheckvarInteger) | 421 | usr("MAKE_JOBS", lkNone, CheckvarInteger) | |
408 | pkg("MAKE_JOBS_SAFE", lkNone, CheckvarYesNo) | 422 | pkg("MAKE_JOBS_SAFE", lkNone, CheckvarYesNo) | |
409 | pkg("MAKE_PROGRAM", lkNone, CheckvarShellCommand) | 423 | pkg("MAKE_PROGRAM", lkNone, CheckvarShellCommand) | |
410 | acl("MANCOMPRESSED", lkNone, CheckvarYesNo, "Makefile: set; Makefile.common: default, set") | 424 | acl("MANCOMPRESSED", lkNone, CheckvarYesNo, "Makefile: set; Makefile.common: default, set") | |
411 | acl("MANCOMPRESSED_IF_MANZ", lkNone, CheckvarYes, "Makefile: set; Makefile.common: default, set") | 425 | acl("MANCOMPRESSED_IF_MANZ", lkNone, CheckvarYes, "Makefile: set; Makefile.common: default, set") | |
412 | sys("MANGRP", lkNone, CheckvarUserGroupName) | 426 | sys("MANGRP", lkNone, CheckvarUserGroupName) | |
413 | sys("MANMODE", lkNone, CheckvarFileMode) | 427 | sys("MANMODE", lkNone, CheckvarFileMode) | |
414 | sys("MANOWN", lkNone, CheckvarUserGroupName) | 428 | sys("MANOWN", lkNone, CheckvarUserGroupName) | |
415 | pkglist("MASTER_SITES", lkShell, CheckvarFetchURL) | 429 | pkglist("MASTER_SITES", lkShell, CheckvarFetchURL) | |
416 | sys("MASTER_SITE_APACHE", lkShell, CheckvarFetchURL) | 430 | sys("MASTER_SITE_APACHE", lkShell, CheckvarFetchURL) | |
417 | sys("MASTER_SITE_BACKUP", lkShell, CheckvarFetchURL) | 431 | sys("MASTER_SITE_BACKUP", lkShell, CheckvarFetchURL) | |
418 | sys("MASTER_SITE_CYGWIN", lkShell, CheckvarFetchURL) | 432 | sys("MASTER_SITE_CYGWIN", lkShell, CheckvarFetchURL) | |
419 | sys("MASTER_SITE_DEBIAN", lkShell, CheckvarFetchURL) | 433 | sys("MASTER_SITE_DEBIAN", lkShell, CheckvarFetchURL) | |
@@ -435,27 +449,27 @@ func (gd *GlobalData) InitVartypes() { | @@ -435,27 +449,27 @@ func (gd *GlobalData) InitVartypes() { | |||
435 | sys("MASTER_SITE_NETLIB", lkShell, CheckvarFetchURL) | 449 | sys("MASTER_SITE_NETLIB", lkShell, CheckvarFetchURL) | |
436 | sys("MASTER_SITE_OPENOFFICE", lkShell, CheckvarFetchURL) | 450 | sys("MASTER_SITE_OPENOFFICE", lkShell, CheckvarFetchURL) | |
437 | sys("MASTER_SITE_OSDN", lkShell, CheckvarFetchURL) | 451 | sys("MASTER_SITE_OSDN", lkShell, CheckvarFetchURL) | |
438 | sys("MASTER_SITE_PERL_CPAN", lkShell, CheckvarFetchURL) | 452 | sys("MASTER_SITE_PERL_CPAN", lkShell, CheckvarFetchURL) | |
439 | sys("MASTER_SITE_R_CRAN", lkShell, CheckvarFetchURL) | 453 | sys("MASTER_SITE_R_CRAN", lkShell, CheckvarFetchURL) | |
440 | sys("MASTER_SITE_RUBYGEMS", lkShell, CheckvarFetchURL) | 454 | sys("MASTER_SITE_RUBYGEMS", lkShell, CheckvarFetchURL) | |
441 | sys("MASTER_SITE_SOURCEFORGE", lkShell, CheckvarFetchURL) | 455 | sys("MASTER_SITE_SOURCEFORGE", lkShell, CheckvarFetchURL) | |
442 | sys("MASTER_SITE_SUNSITE", lkShell, CheckvarFetchURL) | 456 | sys("MASTER_SITE_SUNSITE", lkShell, CheckvarFetchURL) | |
443 | sys("MASTER_SITE_SUSE", lkShell, CheckvarFetchURL) | 457 | sys("MASTER_SITE_SUSE", lkShell, CheckvarFetchURL) | |
444 | sys("MASTER_SITE_TEX_CTAN", lkShell, CheckvarFetchURL) | 458 | sys("MASTER_SITE_TEX_CTAN", lkShell, CheckvarFetchURL) | |
445 | sys("MASTER_SITE_XCONTRIB", lkShell, CheckvarFetchURL) | 459 | sys("MASTER_SITE_XCONTRIB", lkShell, CheckvarFetchURL) | |
446 | sys("MASTER_SITE_XEMACS", lkShell, CheckvarFetchURL) | 460 | sys("MASTER_SITE_XEMACS", lkShell, CheckvarFetchURL) | |
447 | pkglist("MESSAGE_SRC", lkShell, CheckvarPathname) | 461 | pkglist("MESSAGE_SRC", lkShell, CheckvarPathname) | |
448 | acl("MESSAGE_SUBST", lkShell, CheckvarShellWord, "Makefile.common: append; Makefile: append; options.mk: append") | 462 | acl("MESSAGE_SUBST", lkShell, CheckvarShellWord, "Makefile, Makefile.common, options.mk: append") | |
449 | pkg("META_PACKAGE", lkNone, CheckvarYes) | 463 | pkg("META_PACKAGE", lkNone, CheckvarYes) | |
450 | sys("MISSING_FEATURES", lkShell, CheckvarIdentifier) | 464 | sys("MISSING_FEATURES", lkShell, CheckvarIdentifier) | |
451 | acl("MYSQL_VERSIONS_ACCEPTED", lkShell, enum("51 55 56"), "Makefile: set") | 465 | acl("MYSQL_VERSIONS_ACCEPTED", lkShell, enum("51 55 56"), "Makefile: set") | |
452 | usr("MYSQL_VERSION_DEFAULT", lkNone, CheckvarVersion) | 466 | usr("MYSQL_VERSION_DEFAULT", lkNone, CheckvarVersion) | |
453 | sys("NM", lkNone, CheckvarShellCommand) | 467 | sys("NM", lkNone, CheckvarShellCommand) | |
454 | sys("NONBINMODE", lkNone, CheckvarFileMode) | 468 | sys("NONBINMODE", lkNone, CheckvarFileMode) | |
455 | pkg("NOT_FOR_COMPILER", lkShell, enum("ccache ccc clang distcc f2c gcc hp icc ido mipspro mipspro-ucode pcc sunpro xlc")) | 469 | pkg("NOT_FOR_COMPILER", lkShell, enum("ccache ccc clang distcc f2c gcc hp icc ido mipspro mipspro-ucode pcc sunpro xlc")) | |
456 | pkglist("NOT_FOR_PLATFORM", lkSpace, CheckvarMachinePlatformPattern) | 470 | pkglist("NOT_FOR_PLATFORM", lkSpace, CheckvarMachinePlatformPattern) | |
457 | pkg("NOT_FOR_UNPRIVILEGED", lkNone, CheckvarYesNo) | 471 | pkg("NOT_FOR_UNPRIVILEGED", lkNone, CheckvarYesNo) | |
458 | acl("NO_BIN_ON_CDROM", lkNone, CheckvarRestricted, "Makefile, Makefile.common: set") | 472 | acl("NO_BIN_ON_CDROM", lkNone, CheckvarRestricted, "Makefile, Makefile.common: set") | |
459 | acl("NO_BIN_ON_FTP", lkNone, CheckvarRestricted, "Makefile, Makefile.common: set") | 473 | acl("NO_BIN_ON_FTP", lkNone, CheckvarRestricted, "Makefile, Makefile.common: set") | |
460 | acl("NO_BUILD", lkNone, CheckvarYes, "Makefile, Makefile.common: set; Makefile.*: default, set") | 474 | acl("NO_BUILD", lkNone, CheckvarYes, "Makefile, Makefile.common: set; Makefile.*: default, set") | |
461 | pkg("NO_CHECKSUM", lkNone, CheckvarYes) | 475 | pkg("NO_CHECKSUM", lkNone, CheckvarYes) | |
@@ -475,28 +489,28 @@ func (gd *GlobalData) InitVartypes() { | @@ -475,28 +489,28 @@ func (gd *GlobalData) InitVartypes() { | |||
475 | sys("OS_VERSION", lkNone, CheckvarVersion) | 489 | sys("OS_VERSION", lkNone, CheckvarVersion) | |
476 | pkg("OVERRIDE_DIRDEPTH*", lkNone, CheckvarInteger) | 490 | pkg("OVERRIDE_DIRDEPTH*", lkNone, CheckvarInteger) | |
477 | pkg("OVERRIDE_GNU_CONFIG_SCRIPTS", lkNone, CheckvarYes) | 491 | pkg("OVERRIDE_GNU_CONFIG_SCRIPTS", lkNone, CheckvarYes) | |
478 | acl("OWNER", lkNone, CheckvarMailAddress, "Makefile: set; Makefile.common: default") | 492 | acl("OWNER", lkNone, CheckvarMailAddress, "Makefile: set; Makefile.common: default") | |
479 | pkglist("OWN_DIRS", lkShell, CheckvarPathname) | 493 | pkglist("OWN_DIRS", lkShell, CheckvarPathname) | |
480 | pkglist("OWN_DIRS_PERMS", lkShell, CheckvarPerms) | 494 | pkglist("OWN_DIRS_PERMS", lkShell, CheckvarPerms) | |
481 | sys("PAMBASE", lkNone, CheckvarPathname) | 495 | sys("PAMBASE", lkNone, CheckvarPathname) | |
482 | usr("PAM_DEFAULT", lkNone, enum("linux-pam openpam solaris-pam")) | 496 | usr("PAM_DEFAULT", lkNone, enum("linux-pam openpam solaris-pam")) | |
483 | acl("PATCHDIR", lkNone, CheckvarRelativePkgPath, "Makefile: set; Makefile.common: default, set") | 497 | acl("PATCHDIR", lkNone, CheckvarRelativePkgPath, "Makefile: set; Makefile.common: default, set") | |
484 | pkglist("PATCHFILES", lkShell, CheckvarFilename) | 498 | pkglist("PATCHFILES", lkShell, CheckvarFilename) | |
485 | acl("PATCH_ARGS", lkShell, CheckvarShellWord, "") | 499 | acl("PATCH_ARGS", lkShell, CheckvarShellWord, "") | |
486 | acl("PATCH_DIST_ARGS", lkShell, CheckvarShellWord, "Makefile: set, append") | 500 | acl("PATCH_DIST_ARGS", lkShell, CheckvarShellWord, "Makefile: set, append") | |
487 | acl("PATCH_DIST_CAT", lkNone, CheckvarShellCommand, "") | 501 | acl("PATCH_DIST_CAT", lkNone, CheckvarShellCommand, "") | |
488 | acl("PATCH_DIST_STRIP*", lkNone, CheckvarShellWord, "Makefile, Makefile.common: set; buildlink3.mk:; builtin.mk:; *.mk: set") | 502 | acl("PATCH_DIST_STRIP*", lkNone, CheckvarShellWord, "buildlink3.mk, builtin.mk:; Makefile, Makefile.common, *.mk: set") | |
489 | acl("PATCH_SITES", lkShell, CheckvarFetchURL, "Makefile: set; options.mk: set; Makefile.common: set") | 503 | acl("PATCH_SITES", lkShell, CheckvarFetchURL, "Makefile, Makefile.common, options.mk: set") | |
490 | acl("PATCH_STRIP", lkNone, CheckvarShellWord, "") | 504 | acl("PATCH_STRIP", lkNone, CheckvarShellWord, "") | |
491 | pkg("PERL5_USE_PACKLIST", lkNone, CheckvarYesNo) | 505 | pkg("PERL5_USE_PACKLIST", lkNone, CheckvarYesNo) | |
492 | acl("PERL5_PACKLIST", lkShell, CheckvarPerl5Packlist, "Makefile: set; options.mk: set, append") | 506 | acl("PERL5_PACKLIST", lkShell, CheckvarPerl5Packlist, "Makefile: set; options.mk: set, append") | |
493 | acl("PERL5_PACKLIST_DIR", lkNone, CheckvarPathname, "") | 507 | acl("PERL5_PACKLIST_DIR", lkNone, CheckvarPathname, "") | |
494 | sys("PGSQL_PREFIX", lkNone, CheckvarPathname) | 508 | sys("PGSQL_PREFIX", lkNone, CheckvarPathname) | |
495 | acl("PGSQL_VERSIONS_ACCEPTED", lkShell, enum("91 92 93 94"), "") | 509 | acl("PGSQL_VERSIONS_ACCEPTED", lkShell, enum("91 92 93 94"), "") | |
496 | usr("PGSQL_VERSION_DEFAULT", lkNone, CheckvarVersion) | 510 | usr("PGSQL_VERSION_DEFAULT", lkNone, CheckvarVersion) | |
497 | sys("PG_LIB_EXT", lkNone, enum("dylib so")) | 511 | sys("PG_LIB_EXT", lkNone, enum("dylib so")) | |
498 | sys("PGSQL_TYPE", lkNone, enum("postgresql81-client postgresql80-client")) | 512 | sys("PGSQL_TYPE", lkNone, enum("postgresql81-client postgresql80-client")) | |
499 | sys("PGPKGSRCDIR", lkNone, CheckvarPathname) | 513 | sys("PGPKGSRCDIR", lkNone, CheckvarPathname) | |
500 | sys("PHASE_MSG", lkNone, CheckvarShellCommand) | 514 | sys("PHASE_MSG", lkNone, CheckvarShellCommand) | |
501 | usr("PHP_VERSION_REQD", lkNone, CheckvarVersion) | 515 | usr("PHP_VERSION_REQD", lkNone, CheckvarVersion) | |
502 | sys("PKGBASE", lkNone, CheckvarIdentifier) | 516 | sys("PKGBASE", lkNone, CheckvarIdentifier) | |
@@ -538,65 +552,65 @@ func (gd *GlobalData) InitVartypes() { | @@ -538,65 +552,65 @@ func (gd *GlobalData) InitVartypes() { | |||
538 | acl("PKG_HOME.*", lkNone, CheckvarPathname, "Makefile: set") | 552 | acl("PKG_HOME.*", lkNone, CheckvarPathname, "Makefile: set") | |
539 | acl("PKG_HACKS", lkShell, CheckvarIdentifier, "hacks.mk: append") | 553 | acl("PKG_HACKS", lkShell, CheckvarIdentifier, "hacks.mk: append") | |
540 | sys("PKG_INFO", lkNone, CheckvarShellCommand) | 554 | sys("PKG_INFO", lkNone, CheckvarShellCommand) | |
541 | sys("PKG_JAVA_HOME", lkNone, CheckvarPathname) | 555 | sys("PKG_JAVA_HOME", lkNone, CheckvarPathname) | |
542 | jvms := enum("openjdk8 oracle-jdk8 openjdk7 sun-jdk7 sun-jdk6 jdk16 jdk15 kaffe") // See mk/java-vm.mk:/_PKG_JVMS/ | 556 | jvms := enum("openjdk8 oracle-jdk8 openjdk7 sun-jdk7 sun-jdk6 jdk16 jdk15 kaffe") // See mk/java-vm.mk:/_PKG_JVMS/ | |
543 | sys("PKG_JVM", lkNone, jvms) | 557 | sys("PKG_JVM", lkNone, jvms) | |
544 | acl("PKG_JVMS_ACCEPTED", lkShell, jvms, "Makefile: set; Makefile.common: default, set") | 558 | acl("PKG_JVMS_ACCEPTED", lkShell, jvms, "Makefile: set; Makefile.common: default, set") | |
545 | usr("PKG_JVM_DEFAULT", lkNone, jvms) | 559 | usr("PKG_JVM_DEFAULT", lkNone, jvms) | |
546 | acl("PKG_LEGACY_OPTIONS", lkShell, CheckvarOption, "") | 560 | acl("PKG_LEGACY_OPTIONS", lkShell, CheckvarOption, "") | |
547 | acl("PKG_LIBTOOL", lkNone, CheckvarPathname, "Makefile: set") | 561 | acl("PKG_LIBTOOL", lkNone, CheckvarPathname, "Makefile: set") | |
548 | acl("PKG_OPTIONS", lkSpace, CheckvarOption, "bsd.options.mk: set; *: use-loadtime, use") | 562 | acl("PKG_OPTIONS", lkSpace, CheckvarOption, "bsd.options.mk: set; *: use-loadtime, use") | |
549 | usr("PKG_OPTIONS.*", lkSpace, CheckvarOption) | 563 | usr("PKG_OPTIONS.*", lkSpace, CheckvarOption) | |
550 | acl("PKG_OPTIONS_DEPRECATED_WARNINGS", lkShell, CheckvarShellWord, "") | 564 | acl("PKG_OPTIONS_DEPRECATED_WARNINGS", lkShell, CheckvarShellWord, "") | |
551 | acl("PKG_OPTIONS_GROUP.*", lkSpace, CheckvarOption, "options.mk: set; Makefile: set") | 565 | acl("PKG_OPTIONS_GROUP.*", lkSpace, CheckvarOption, "Makefile, options.mk: set, append") | |
552 | acl("PKG_OPTIONS_LEGACY_OPTS", lkSpace, CheckvarUnchecked, "Makefile, Makefile.common: append; options.mk: append") | 566 | acl("PKG_OPTIONS_LEGACY_OPTS", lkSpace, CheckvarUnchecked, "Makefile, Makefile.common, options.mk: append") | |
553 | acl("PKG_OPTIONS_LEGACY_VARS", lkSpace, CheckvarUnchecked, "Makefile, Makefile.common: append; options.mk: append") | 567 | acl("PKG_OPTIONS_LEGACY_VARS", lkSpace, CheckvarUnchecked, "Makefile, Makefile.common, options.mk: append") | |
554 | acl("PKG_OPTIONS_NONEMPTY_SETS", lkSpace, CheckvarIdentifier, "") | 568 | acl("PKG_OPTIONS_NONEMPTY_SETS", lkSpace, CheckvarIdentifier, "") | |
555 | acl("PKG_OPTIONS_OPTIONAL_GROUPS", lkSpace, CheckvarIdentifier, "options.mk: set, append") | 569 | acl("PKG_OPTIONS_OPTIONAL_GROUPS", lkSpace, CheckvarIdentifier, "options.mk: set, append") | |
556 | acl("PKG_OPTIONS_REQUIRED_GROUPS", lkSpace, CheckvarIdentifier, "options.mk: set; Makefile: set") | 570 | acl("PKG_OPTIONS_REQUIRED_GROUPS", lkSpace, CheckvarIdentifier, "Makefile, options.mk: set") | |
557 | acl("PKG_OPTIONS_SET.*", lkSpace, CheckvarOption, "") | 571 | acl("PKG_OPTIONS_SET.*", lkSpace, CheckvarOption, "") | |
558 | acl("PKG_OPTIONS_VAR", lkNone, CheckvarPkgOptionsVar, "options.mk: set; Makefile, Makefile.common: set; bsd.options.mk: use-loadtime") | 572 | acl("PKG_OPTIONS_VAR", lkNone, CheckvarPkgOptionsVar, "Makefile, Makefile.common, options.mk: set; bsd.options.mk: use-loadtime") | |
559 | acl("PKG_PRESERVE", lkNone, CheckvarYes, "Makefile: set") | 573 | acl("PKG_PRESERVE", lkNone, CheckvarYes, "Makefile: set") | |
560 | acl("PKG_SHELL", lkNone, CheckvarPathname, "Makefile, Makefile.common: set") | 574 | acl("PKG_SHELL", lkNone, CheckvarPathname, "Makefile, Makefile.common: set") | |
561 | acl("PKG_SHELL.*", lkNone, CheckvarPathname, "Makefile, Makefile.common: set") | 575 | acl("PKG_SHELL.*", lkNone, CheckvarPathname, "Makefile, Makefile.common: set") | |
562 | acl("PKG_SHLIBTOOL", lkNone, CheckvarPathname, "") | 576 | acl("PKG_SHLIBTOOL", lkNone, CheckvarPathname, "") | |
563 | pkglist("PKG_SKIP_REASON", lkShell, CheckvarShellWord) | 577 | pkglist("PKG_SKIP_REASON", lkShell, CheckvarShellWord) | |
564 | acl("PKG_SUGGESTED_OPTIONS", lkShell, CheckvarOption, "options.mk: set, append; Makefile: set, append; Makefile.common: set") | 578 | acl("PKG_SUGGESTED_OPTIONS", lkShell, CheckvarOption, "Makefile, Makefile.common, options.mk: set, append") | |
565 | acl("PKG_SUPPORTED_OPTIONS", lkShell, CheckvarOption, "options.mk: set, append, use; Makefile: set, append; Makefile.common: set") | 579 | acl("PKG_SUPPORTED_OPTIONS", lkShell, CheckvarOption, "Makefile: set, append; Makefile.common: set; options.mk: set, append, use") | |
566 | pkg("PKG_SYSCONFDIR*", lkNone, CheckvarPathname) | 580 | pkg("PKG_SYSCONFDIR*", lkNone, CheckvarPathname) | |
567 | pkglist("PKG_SYSCONFDIR_PERMS", lkShell, CheckvarPerms) | 581 | pkglist("PKG_SYSCONFDIR_PERMS", lkShell, CheckvarPerms) | |
568 | sys("PKG_SYSCONFBASEDIR", lkNone, CheckvarPathname) | 582 | sys("PKG_SYSCONFBASEDIR", lkNone, CheckvarPathname) | |
569 | pkg("PKG_SYSCONFSUBDIR", lkNone, CheckvarPathname) | 583 | pkg("PKG_SYSCONFSUBDIR", lkNone, CheckvarPathname) | |
570 | acl("PKG_SYSCONFVAR", lkNone, CheckvarIdentifier, "") // FIXME: name/type mismatch. | 584 | acl("PKG_SYSCONFVAR", lkNone, CheckvarIdentifier, "") // FIXME: name/type mismatch. | |
571 | acl("PKG_UID", lkNone, CheckvarInteger, "Makefile: set") | 585 | acl("PKG_UID", lkNone, CheckvarInteger, "Makefile: set") | |
572 | acl("PKG_USERS", lkShell, CheckvarShellWord, "Makefile: set, append") | 586 | acl("PKG_USERS", lkShell, CheckvarShellWord, "Makefile: set, append") | |
573 | pkg("PKG_USERS_VARS", lkShell, CheckvarVarname) | 587 | pkg("PKG_USERS_VARS", lkShell, CheckvarVarname) | |
574 | acl("PKG_USE_KERBEROS", lkNone, CheckvarYes, "Makefile, Makefile.common: set") | 588 | acl("PKG_USE_KERBEROS", lkNone, CheckvarYes, "Makefile, Makefile.common: set") | |
575 | // PLIST.* has special handling code | 589 | // PLIST.* has special handling code | |
576 | pkglist("PLIST_VARS", lkShell, CheckvarIdentifier) | 590 | pkglist("PLIST_VARS", lkShell, CheckvarIdentifier) | |
577 | pkglist("PLIST_SRC", lkShell, CheckvarRelativePkgPath) | 591 | pkglist("PLIST_SRC", lkShell, CheckvarRelativePkgPath) | |
578 | pkglist("PLIST_SUBST", lkShell, CheckvarShellWord) | 592 | pkglist("PLIST_SUBST", lkShell, CheckvarShellWord) | |
579 | acl("PLIST_TYPE", lkNone, enum("dynamic static"), "") | 593 | acl("PLIST_TYPE", lkNone, enum("dynamic static"), "") | |
580 | acl("PREPEND_PATH", lkShell, CheckvarPathname, "") | 594 | acl("PREPEND_PATH", lkShell, CheckvarPathname, "") | |
581 | acl("PREFIX", lkNone, CheckvarPathname, "*: use") | 595 | acl("PREFIX", lkNone, CheckvarPathname, "*: use") | |
582 | acl("PREV_PKGPATH", lkNone, CheckvarPathname, "*: use") // doesn't exist any longer | 596 | acl("PREV_PKGPATH", lkNone, CheckvarPathname, "*: use") // doesn't exist any longer | |
583 | acl("PRINT_PLIST_AWK", lkNone, CheckvarAwkCommand, "*: append") | 597 | acl("PRINT_PLIST_AWK", lkNone, CheckvarAwkCommand, "*: append") | |
584 | acl("PRIVILEGED_STAGES", lkShell, enum("install package clean"), "") | 598 | acl("PRIVILEGED_STAGES", lkShell, enum("install package clean"), "") | |
585 | acl("PTHREAD_AUTO_VARS", lkNone, CheckvarYesNo, "Makefile: set") | 599 | acl("PTHREAD_AUTO_VARS", lkNone, CheckvarYesNo, "Makefile: set") | |
586 | sys("PTHREAD_CFLAGS", lkShell, CheckvarCFlag) | 600 | sys("PTHREAD_CFLAGS", lkShell, CheckvarCFlag) | |
587 | sys("PTHREAD_LDFLAGS", lkShell, CheckvarLdFlag) | 601 | sys("PTHREAD_LDFLAGS", lkShell, CheckvarLdFlag) | |
588 | sys("PTHREAD_LIBS", lkShell, CheckvarLdFlag) | 602 | sys("PTHREAD_LIBS", lkShell, CheckvarLdFlag) | |
589 | acl("PTHREAD_OPTS", lkShell, enum("native optional require"), "Makefile: set, append; Makefile.common: append; buildlink3.mk: append") | 603 | acl("PTHREAD_OPTS", lkShell, enum("native optional require"), "Makefile: set, append; Makefile.common, buildlink3.mk: append") | |
590 | sys("PTHREAD_TYPE", lkNone, CheckvarIdentifier) // Or "native" or "none". | 604 | sys("PTHREAD_TYPE", lkNone, CheckvarIdentifier) // Or "native" or "none". | |
591 | pkg("PY_PATCHPLIST", lkNone, CheckvarYes) | 605 | pkg("PY_PATCHPLIST", lkNone, CheckvarYes) | |
592 | acl("PYPKGPREFIX", lkNone, enum("py27 py33 py34 py35"), "pyversion.mk: set; *: use-loadtime, use") | 606 | acl("PYPKGPREFIX", lkNone, enum("py27 py33 py34 py35"), "pyversion.mk: set; *: use-loadtime, use") | |
593 | pkg("PYTHON_FOR_BUILD_ONLY", lkNone, CheckvarYes) | 607 | pkg("PYTHON_FOR_BUILD_ONLY", lkNone, CheckvarYes) | |
594 | pkglist("REPLACE_PYTHON", lkShell, CheckvarPathmask) | 608 | pkglist("REPLACE_PYTHON", lkShell, CheckvarPathmask) | |
595 | pkg("PYTHON_VERSIONS_ACCEPTED", lkShell, CheckvarVersion) | 609 | pkg("PYTHON_VERSIONS_ACCEPTED", lkShell, CheckvarVersion) | |
596 | pkg("PYTHON_VERSIONS_INCOMPATIBLE", lkShell, CheckvarVersion) | 610 | pkg("PYTHON_VERSIONS_INCOMPATIBLE", lkShell, CheckvarVersion) | |
597 | usr("PYTHON_VERSION_DEFAULT", lkNone, CheckvarVersion) | 611 | usr("PYTHON_VERSION_DEFAULT", lkNone, CheckvarVersion) | |
598 | usr("PYTHON_VERSION_REQD", lkNone, CheckvarVersion) | 612 | usr("PYTHON_VERSION_REQD", lkNone, CheckvarVersion) | |
599 | pkglist("PYTHON_VERSIONED_DEPENDENCIES", lkShell, CheckvarPythonDependency) | 613 | pkglist("PYTHON_VERSIONED_DEPENDENCIES", lkShell, CheckvarPythonDependency) | |
600 | sys("RANLIB", lkNone, CheckvarShellCommand) | 614 | sys("RANLIB", lkNone, CheckvarShellCommand) | |
601 | pkglist("RCD_SCRIPTS", lkShell, CheckvarFilename) | 615 | pkglist("RCD_SCRIPTS", lkShell, CheckvarFilename) | |
602 | acl("RCD_SCRIPT_SRC.*", lkShell, CheckvarPathname, "Makefile: set") | 616 | acl("RCD_SCRIPT_SRC.*", lkShell, CheckvarPathname, "Makefile: set") | |
@@ -628,27 +642,28 @@ func (gd *GlobalData) InitVartypes() { | @@ -628,27 +642,28 @@ func (gd *GlobalData) InitVartypes() { | |||
628 | usr("SETUID_ROOT_PERMS", lkShell, CheckvarShellWord) | 642 | usr("SETUID_ROOT_PERMS", lkShell, CheckvarShellWord) | |
629 | sys("SHAREGRP", lkNone, CheckvarUserGroupName) | 643 | sys("SHAREGRP", lkNone, CheckvarUserGroupName) | |
630 | sys("SHAREMODE", lkNone, CheckvarFileMode) | 644 | sys("SHAREMODE", lkNone, CheckvarFileMode) | |
631 | sys("SHAREOWN", lkNone, CheckvarUserGroupName) | 645 | sys("SHAREOWN", lkNone, CheckvarUserGroupName) | |
632 | sys("SHCOMMENT", lkNone, CheckvarShellCommand) | 646 | sys("SHCOMMENT", lkNone, CheckvarShellCommand) | |
633 | acl("SHLIB_HANDLING", lkNone, enum("YES NO no"), "") | 647 | acl("SHLIB_HANDLING", lkNone, enum("YES NO no"), "") | |
634 | acl("SHLIBTOOL", lkNone, CheckvarShellCommand, "Makefile: use") | 648 | acl("SHLIBTOOL", lkNone, CheckvarShellCommand, "Makefile: use") | |
635 | acl("SHLIBTOOL_OVERRIDE", lkShell, CheckvarPathmask, "Makefile: set, append; Makefile.common: append") | 649 | acl("SHLIBTOOL_OVERRIDE", lkShell, CheckvarPathmask, "Makefile: set, append; Makefile.common: append") | |
636 | acl("SITES.*", lkShell, CheckvarFetchURL, "Makefile, Makefile.common, options.mk: set, append, use") | 650 | acl("SITES.*", lkShell, CheckvarFetchURL, "Makefile, Makefile.common, options.mk: set, append, use") | |
637 | pkglist("SPECIAL_PERMS", lkShell, CheckvarPerms) | 651 | pkglist("SPECIAL_PERMS", lkShell, CheckvarPerms) | |
638 | sys("STEP_MSG", lkNone, CheckvarShellCommand) | 652 | sys("STEP_MSG", lkNone, CheckvarShellCommand) | |
639 | acl("SUBDIR", lkShell, CheckvarFilename, "Makefile: append; *:") | 653 | acl("SUBDIR", lkShell, CheckvarFilename, "Makefile: append; *:") | |
640 | acl("SUBST_CLASSES", lkShell, CheckvarIdentifier, "Makefile: set, append; *: append") | 654 | acl("SUBST_CLASSES", lkShell, CheckvarIdentifier, "Makefile: set, append; *: append") | |
641 | acl("SUBST_FILES.*", lkShell, CheckvarPathmask, "Makefile: set, append; Makefile.*, *.mk: set, append") | 655 | acl("SUBST_CLASSES.*", lkShell, CheckvarIdentifier, "Makefile: set, append; *: append") | |
656 | acl("SUBST_FILES.*", lkShell, CheckvarPathmask, "Makefile, Makefile.*, *.mk: set, append") | |||
642 | acl("SUBST_FILTER_CMD.*", lkNone, CheckvarShellCommand, "Makefile, Makefile.*, *.mk: set") | 657 | acl("SUBST_FILTER_CMD.*", lkNone, CheckvarShellCommand, "Makefile, Makefile.*, *.mk: set") | |
643 | acl("SUBST_MESSAGE.*", lkNone, CheckvarMessage, "Makefile, Makefile.*, *.mk: set") | 658 | acl("SUBST_MESSAGE.*", lkNone, CheckvarMessage, "Makefile, Makefile.*, *.mk: set") | |
644 | acl("SUBST_SED.*", lkNone, CheckvarSedCommands, "Makefile, Makefile.*, *.mk: set, append") | 659 | acl("SUBST_SED.*", lkNone, CheckvarSedCommands, "Makefile, Makefile.*, *.mk: set, append") | |
645 | pkg("SUBST_STAGE.*", lkNone, CheckvarStage) | 660 | pkg("SUBST_STAGE.*", lkNone, CheckvarStage) | |
646 | pkglist("SUBST_VARS.*", lkShell, CheckvarVarname) | 661 | pkglist("SUBST_VARS.*", lkShell, CheckvarVarname) | |
647 | pkglist("SUPERSEDES", lkSpace, CheckvarDependency) | 662 | pkglist("SUPERSEDES", lkSpace, CheckvarDependency) | |
648 | pkglist("TEST_DIRS", lkShell, CheckvarWrksrcSubdirectory) | 663 | pkglist("TEST_DIRS", lkShell, CheckvarWrksrcSubdirectory) | |
649 | pkglist("TEST_ENV", lkShell, CheckvarShellWord) | 664 | pkglist("TEST_ENV", lkShell, CheckvarShellWord) | |
650 | acl("TEST_TARGET", lkShell, CheckvarIdentifier, "Makefile: set; Makefile.common: default, set; options.mk: set, append") | 665 | acl("TEST_TARGET", lkShell, CheckvarIdentifier, "Makefile: set; Makefile.common: default, set; options.mk: set, append") | |
651 | acl("TEX_ACCEPTED", lkShell, enum("teTeX1 teTeX2 teTeX3"), "Makefile, Makefile.common: set") | 666 | acl("TEX_ACCEPTED", lkShell, enum("teTeX1 teTeX2 teTeX3"), "Makefile, Makefile.common: set") | |
652 | acl("TEX_DEPMETHOD", lkNone, enum("build run"), "Makefile, Makefile.common: set") | 667 | acl("TEX_DEPMETHOD", lkNone, enum("build run"), "Makefile, Makefile.common: set") | |
653 | pkglist("TEXINFO_REQD", lkShell, CheckvarVersion) | 668 | pkglist("TEXINFO_REQD", lkShell, CheckvarVersion) | |
654 | acl("TOOL_DEPENDS", lkSpace, CheckvarDependencyWithPath, "Makefile, Makefile.common, *.mk: append") | 669 | acl("TOOL_DEPENDS", lkSpace, CheckvarDependencyWithPath, "Makefile, Makefile.common, *.mk: append") | |
@@ -662,43 +677,44 @@ func (gd *GlobalData) InitVartypes() { | @@ -662,43 +677,44 @@ func (gd *GlobalData) InitVartypes() { | |||
662 | sys("TOOLS_PATH.*", lkNone, CheckvarPathname) | 677 | sys("TOOLS_PATH.*", lkNone, CheckvarPathname) | |
663 | sys("TOOLS_PLATFORM.*", lkNone, CheckvarShellCommand) | 678 | sys("TOOLS_PLATFORM.*", lkNone, CheckvarShellCommand) | |
664 | sys("TOUCH_FLAGS", lkShell, CheckvarShellWord) | 679 | sys("TOUCH_FLAGS", lkShell, CheckvarShellWord) | |
665 | pkglist("UAC_REQD_EXECS", lkShell, CheckvarPrefixPathname) | 680 | pkglist("UAC_REQD_EXECS", lkShell, CheckvarPrefixPathname) | |
666 | acl("UNLIMIT_RESOURCES", lkShell, enum("datasize stacksize memorysize"), "Makefile: set, append; Makefile.common: append") | 681 | acl("UNLIMIT_RESOURCES", lkShell, enum("datasize stacksize memorysize"), "Makefile: set, append; Makefile.common: append") | |
667 | usr("UNPRIVILEGED_USER", lkNone, CheckvarUserGroupName) | 682 | usr("UNPRIVILEGED_USER", lkNone, CheckvarUserGroupName) | |
668 | usr("UNPRIVILEGED_GROUP", lkNone, CheckvarUserGroupName) | 683 | usr("UNPRIVILEGED_GROUP", lkNone, CheckvarUserGroupName) | |
669 | pkglist("UNWRAP_FILES", lkShell, CheckvarPathmask) | 684 | pkglist("UNWRAP_FILES", lkShell, CheckvarPathmask) | |
670 | usr("UPDATE_TARGET", lkShell, CheckvarIdentifier) | 685 | usr("UPDATE_TARGET", lkShell, CheckvarIdentifier) | |
671 | pkg("USE_BSD_MAKEFILE", lkNone, CheckvarYes) | 686 | pkg("USE_BSD_MAKEFILE", lkNone, CheckvarYes) | |
672 | acl("USE_BUILTIN.*", lkNone, CheckvarYesNoIndirectly, "builtin.mk: set") | 687 | acl("USE_BUILTIN.*", lkNone, CheckvarYesNoIndirectly, "builtin.mk: set") | |
673 | pkg("USE_CMAKE", lkNone, CheckvarYes) | 688 | pkg("USE_CMAKE", lkNone, CheckvarYes) | |
674 | usr("USE_DESTDIR", lkNone, CheckvarYes) | 689 | usr("USE_DESTDIR", lkNone, CheckvarYes) | |
675 | pkg("USE_FEATURES", lkShell, CheckvarIdentifier) | 690 | pkglist("USE_FEATURES", lkShell, CheckvarIdentifier) | |
676 | pkg("USE_GCC_RUNTIME", lkNone, CheckvarYesNo) | 691 | pkg("USE_GCC_RUNTIME", lkNone, CheckvarYesNo) | |
677 | pkg("USE_GNU_CONFIGURE_HOST", lkNone, CheckvarYesNo) | 692 | pkg("USE_GNU_CONFIGURE_HOST", lkNone, CheckvarYesNo) | |
678 | acl("USE_GNU_ICONV", lkNone, CheckvarYes, "Makefile, Makefile.common: set; options.mk: set") | 693 | acl("USE_GNU_ICONV", lkNone, CheckvarYes, "Makefile, Makefile.common, options.mk: set") | |
679 | acl("USE_IMAKE", lkNone, CheckvarYes, "Makefile: set") | 694 | acl("USE_IMAKE", lkNone, CheckvarYes, "Makefile: set") | |
680 | pkg("USE_JAVA", lkNone, enum("run yes build")) | 695 | pkg("USE_JAVA", lkNone, enum("run yes build")) | |
681 | pkg("USE_JAVA2", lkNone, enum("YES yes no 1.4 1.5 6 7 8")) | 696 | pkg("USE_JAVA2", lkNone, enum("YES yes no 1.4 1.5 6 7 8")) | |
682 | acl("USE_LANGUAGES", lkShell, enum("ada c c99 c++ fortran fortran77 java objc"), "Makefile, Makefile.common, options.mk: set, append") | 697 | acl("USE_LANGUAGES", lkShell, enum("ada c c99 c++ fortran fortran77 java objc"), "Makefile, Makefile.common, options.mk: set, append") | |
683 | pkg("USE_LIBTOOL", lkNone, CheckvarYes) | 698 | pkg("USE_LIBTOOL", lkNone, CheckvarYes) | |
684 | pkg("USE_MAKEINFO", lkNone, CheckvarYes) | 699 | pkg("USE_MAKEINFO", lkNone, CheckvarYes) | |
685 | pkg("USE_MSGFMT_PLURALS", lkNone, CheckvarYes) | 700 | pkg("USE_MSGFMT_PLURALS", lkNone, CheckvarYes) | |
686 | pkg("USE_NCURSES", lkNone, CheckvarYes) | 701 | pkg("USE_NCURSES", lkNone, CheckvarYes) | |
687 | pkg("USE_OLD_DES_API", lkNone, CheckvarYesNo) | 702 | pkg("USE_OLD_DES_API", lkNone, CheckvarYesNo) | |
688 | pkg("USE_PKGINSTALL", lkNone, CheckvarYes) | 703 | pkg("USE_PKGINSTALL", lkNone, CheckvarYes) | |
689 | pkg("USE_PKGLOCALEDIR", lkNone, CheckvarYesNo) | 704 | pkg("USE_PKGLOCALEDIR", lkNone, CheckvarYesNo) | |
690 | usr("USE_PKGSRC_GCC", lkNone, CheckvarYes) | 705 | usr("USE_PKGSRC_GCC", lkNone, CheckvarYes) | |
691 | acl("USE_TOOLS", lkShell, CheckvarTool, "*: append") | 706 | acl("USE_TOOLS", lkShell, CheckvarTool, "*: append") | |
707 | acl("USE_TOOLS.*", lkShell, CheckvarTool, "*: append") | |||
692 | pkg("USE_X11", lkNone, CheckvarYes) | 708 | pkg("USE_X11", lkNone, CheckvarYes) | |
693 | sys("WARNING_MSG", lkNone, CheckvarShellCommand) | 709 | sys("WARNING_MSG", lkNone, CheckvarShellCommand) | |
694 | sys("WARNING_CAT", lkNone, CheckvarShellCommand) | 710 | sys("WARNING_CAT", lkNone, CheckvarShellCommand) | |
695 | acl("WRAPPER_REORDER_CMDS", lkShell, CheckvarWrapperReorder, "Makefile, Makefile.common, buildlink3.mk: append") | 711 | acl("WRAPPER_REORDER_CMDS", lkShell, CheckvarWrapperReorder, "Makefile, Makefile.common, buildlink3.mk: append") | |
696 | acl("WRAPPER_TRANSFORM_CMDS", lkShell, CheckvarWrapperTransform, "Makefile, Makefile.common, buildlink3.mk: append") | 712 | acl("WRAPPER_TRANSFORM_CMDS", lkShell, CheckvarWrapperTransform, "Makefile, Makefile.common, buildlink3.mk: append") | |
697 | sys("WRKDIR", lkNone, CheckvarPathname) | 713 | sys("WRKDIR", lkNone, CheckvarPathname) | |
698 | pkg("WRKSRC", lkNone, CheckvarWrkdirSubdirectory) | 714 | pkg("WRKSRC", lkNone, CheckvarWrkdirSubdirectory) | |
699 | sys("X11_PKGSRCDIR.*", lkNone, CheckvarPathname) | 715 | sys("X11_PKGSRCDIR.*", lkNone, CheckvarPathname) | |
700 | usr("XAW_TYPE", lkNone, enum("3d neXtaw standard xpm")) | 716 | usr("XAW_TYPE", lkNone, enum("3d neXtaw standard xpm")) | |
701 | acl("XMKMF_FLAGS", lkShell, CheckvarShellWord, "") | 717 | acl("XMKMF_FLAGS", lkShell, CheckvarShellWord, "") | |
702 | } | 718 | } | |
703 | 719 | |||
704 | func enum(values string) *VarChecker { | 720 | func enum(values string) *VarChecker { | |
@@ -743,33 +759,38 @@ func acl(varname string, kindOfList Kind | @@ -743,33 +759,38 @@ func acl(varname string, kindOfList Kind | |||
743 | if varparam == "" || varparam == "*" { | 759 | if varparam == "" || varparam == "*" { | |
744 | G.globalData.vartypes[varbase] = vtype | 760 | G.globalData.vartypes[varbase] = vtype | |
745 | } | 761 | } | |
746 | if varparam == "*" || varparam == ".*" { | 762 | if varparam == "*" || varparam == ".*" { | |
747 | G.globalData.vartypes[varbase+".*"] = vtype | 763 | G.globalData.vartypes[varbase+".*"] = vtype | |
748 | } | 764 | } | |
749 | } | 765 | } | |
750 | 766 | |||
751 | func parseAclEntries(varname string, aclentries string) []AclEntry { | 767 | func parseAclEntries(varname string, aclentries string) []AclEntry { | |
752 | if aclentries == "" { | 768 | if aclentries == "" { | |
753 | return nil | 769 | return nil | |
754 | } | 770 | } | |
755 | var result []AclEntry | 771 | var result []AclEntry | |
772 | prevperms := "(first)" | |||
756 | for _, arg := range strings.Split(aclentries, "; ") { | 773 | for _, arg := range strings.Split(aclentries, "; ") { | |
757 | var globs, perms string | 774 | var globs, perms string | |
758 | if fields := strings.SplitN(arg, ": ", 2); len(fields) == 2 { | 775 | if fields := strings.SplitN(arg, ": ", 2); len(fields) == 2 { | |
759 | globs, perms = fields[0], fields[1] | 776 | globs, perms = fields[0], fields[1] | |
760 | } else { | 777 | } else { | |
761 | globs = strings.TrimSuffix(arg, ":") | 778 | globs = strings.TrimSuffix(arg, ":") | |
762 | } | 779 | } | |
780 | if perms == prevperms { | |||
781 | fmt.Printf("Repeated permissions for %s: %s\n", varname, perms) | |||
782 | } | |||
783 | prevperms = perms | |||
763 | var permissions AclPermissions | 784 | var permissions AclPermissions | |
764 | for _, perm := range strings.Split(perms, ", ") { | 785 | for _, perm := range strings.Split(perms, ", ") { | |
765 | switch perm { | 786 | switch perm { | |
766 | case "append": | 787 | case "append": | |
767 | permissions |= aclpAppend | 788 | permissions |= aclpAppend | |
768 | case "default": | 789 | case "default": | |
769 | permissions |= aclpSetDefault | 790 | permissions |= aclpSetDefault | |
770 | case "set": | 791 | case "set": | |
771 | permissions |= aclpSet | 792 | permissions |= aclpSet | |
772 | case "use": | 793 | case "use": | |
773 | permissions |= aclpUse | 794 | permissions |= aclpUse | |
774 | case "use-loadtime": | 795 | case "use-loadtime": | |
775 | permissions |= aclpUseLoadtime | 796 | permissions |= aclpUseLoadtime |
@@ -279,29 +279,31 @@ func (s *Suite) TestVartypeCheck_Pathlis | @@ -279,29 +279,31 @@ func (s *Suite) TestVartypeCheck_Pathlis | |||
279 | 279 | |||
280 | func (s *Suite) Test_VartypeCheck_Perms(c *check.C) { | 280 | func (s *Suite) Test_VartypeCheck_Perms(c *check.C) { | |
281 | runVartypeChecks("CONF_FILES_PERMS", opAssignAppend, (*VartypeCheck).Perms, | 281 | runVartypeChecks("CONF_FILES_PERMS", opAssignAppend, (*VartypeCheck).Perms, | |
282 | "root", | 282 | "root", | |
283 | "${ROOT_USER}", | 283 | "${ROOT_USER}", | |
284 | "ROOT_USER", | 284 | "ROOT_USER", | |
285 | "${REAL_ROOT_USER}") | 285 | "${REAL_ROOT_USER}") | |
286 | 286 | |||
287 | c.Check(s.Output(), equals, "ERROR: fname:2: ROOT_USER must not be used in permission definitions. Use REAL_ROOT_USER instead.\n") | 287 | c.Check(s.Output(), equals, "ERROR: fname:2: ROOT_USER must not be used in permission definitions. Use REAL_ROOT_USER instead.\n") | |
288 | } | 288 | } | |
289 | 289 | |||
290 | func (s *Suite) TestVartypeCheck_PkgOptionsVar(c *check.C) { | 290 | func (s *Suite) TestVartypeCheck_PkgOptionsVar(c *check.C) { | |
291 | runVartypeChecks("PKG_OPTIONS_VAR.screen", opAssign, (*VartypeCheck).PkgOptionsVar, | 291 | runVartypeChecks("PKG_OPTIONS_VAR.screen", opAssign, (*VartypeCheck).PkgOptionsVar, | |
292 | "PKG_OPTIONS.${PKGBASE}") | 292 | "PKG_OPTIONS.${PKGBASE}", | |
293 | "PKG_OPTIONS.anypkgbase") | |||
293 | 294 | |||
294 | c.Check(s.Output(), equals, "ERROR: fname:1: PKGBASE must not be used in PKG_OPTIONS_VAR.\n") | 295 | c.Check(s.Output(), equals, ""+ | |
296 | "ERROR: fname:1: PKGBASE must not be used in PKG_OPTIONS_VAR.\n") | |||
295 | } | 297 | } | |
296 | 298 | |||
297 | func (s *Suite) TestVartypeCheck_PkgRevision(c *check.C) { | 299 | func (s *Suite) TestVartypeCheck_PkgRevision(c *check.C) { | |
298 | runVartypeChecks("PKGREVISION", opAssign, (*VartypeCheck).PkgRevision, | 300 | runVartypeChecks("PKGREVISION", opAssign, (*VartypeCheck).PkgRevision, | |
299 | "3a") | 301 | "3a") | |
300 | 302 | |||
301 | c.Check(s.Output(), equals, ""+ | 303 | c.Check(s.Output(), equals, ""+ | |
302 | "WARN: fname:1: PKGREVISION must be a positive integer number.\n"+ | 304 | "WARN: fname:1: PKGREVISION must be a positive integer number.\n"+ | |
303 | "ERROR: fname:1: PKGREVISION only makes sense directly in the package Makefile.\n") | 305 | "ERROR: fname:1: PKGREVISION only makes sense directly in the package Makefile.\n") | |
304 | 306 | |||
305 | runVartypeChecksFname("Makefile", "PKGREVISION", opAssign, (*VartypeCheck).PkgRevision, | 307 | runVartypeChecksFname("Makefile", "PKGREVISION", opAssign, (*VartypeCheck).PkgRevision, | |
306 | "3") | 308 | "3") | |
307 | 309 |
@@ -46,16 +46,21 @@ func checklineLicense(line *MkLine, valu | @@ -46,16 +46,21 @@ func checklineLicense(line *MkLine, valu | |||
46 | } | 46 | } | |
47 | 47 | |||
48 | if !fileExists(licenseFile) { | 48 | if !fileExists(licenseFile) { | |
49 | line.Warn1("License file %s does not exist.", cleanpath(licenseFile)) | 49 | line.Warn1("License file %s does not exist.", cleanpath(licenseFile)) | |
50 | } | 50 | } | |
51 | 51 | |||
52 | switch license { | 52 | switch license { | |
53 | case "fee-based-commercial-use", | 53 | case "fee-based-commercial-use", | |
54 | "no-commercial-use", | 54 | "no-commercial-use", | |
55 | "no-profit", | 55 | "no-profit", | |
56 | "no-redistribution", | 56 | "no-redistribution", | |
57 | "shareware": | 57 | "shareware": | |
58 | line.Warn1("License %q is deprecated.", license) | 58 | line.Warn1("License %q is deprecated.", license) | |
59 | Explain( | |||
60 | "Instead of using these deprecated licenses, extract the actual", | |||
61 | "license from the package into the pkgsrc/licenses/ directory", | |||
62 | "and define LICENSE to that file name. See the pkgsrc guide,", | |||
63 | "keyword LICENSE, for more information.") | |||
59 | } | 64 | } | |
60 | } | 65 | } | |
61 | } | 66 | } |
@@ -39,26 +39,43 @@ func (s *Suite) TestSubstContext_Complet | @@ -39,26 +39,43 @@ func (s *Suite) TestSubstContext_Complet | |||
39 | ctx.Varassign(newSubstLine(13, "SUBST_SED.p=s,@PREFIX@,${PREFIX},g")) | 39 | ctx.Varassign(newSubstLine(13, "SUBST_SED.p=s,@PREFIX@,${PREFIX},g")) | |
40 | 40 | |||
41 | c.Check(ctx.IsComplete(), equals, false) | 41 | c.Check(ctx.IsComplete(), equals, false) | |
42 | 42 | |||
43 | ctx.Varassign(newSubstLine(14, "SUBST_STAGE.p=post-configure")) | 43 | ctx.Varassign(newSubstLine(14, "SUBST_STAGE.p=post-configure")) | |
44 | 44 | |||
45 | c.Check(ctx.IsComplete(), equals, true) | 45 | c.Check(ctx.IsComplete(), equals, true) | |
46 | 46 | |||
47 | ctx.Finish(newSubstLine(15, "")) | 47 | ctx.Finish(newSubstLine(15, "")) | |
48 | 48 | |||
49 | c.Check(s.Output(), equals, "") | 49 | c.Check(s.Output(), equals, "") | |
50 | } | 50 | } | |
51 | 51 | |||
52 | func (s *Suite) Test_SubstContext_OPSYSVARS(c *check.C) { | |||
53 | G.opts.WarnExtra = true | |||
54 | ctx := new(SubstContext) | |||
55 | ||||
56 | ctx.Varassign(newSubstLine(11, "SUBST_CLASSES.SunOS+=prefix")) | |||
57 | ctx.Varassign(newSubstLine(12, "SUBST_CLASSES.NetBSD+=prefix")) | |||
58 | ctx.Varassign(newSubstLine(13, "SUBST_FILES.prefix=Makefile")) | |||
59 | ctx.Varassign(newSubstLine(14, "SUBST_SED.prefix=s,@PREFIX@,${PREFIX},g")) | |||
60 | ctx.Varassign(newSubstLine(15, "SUBST_STAGE.prefix=post-configure")) | |||
61 | ||||
62 | c.Check(ctx.IsComplete(), equals, true) | |||
63 | ||||
64 | ctx.Finish(newSubstLine(15, "")) | |||
65 | ||||
66 | c.Check(s.Output(), equals, "") | |||
67 | } | |||
68 | ||||
52 | func (s *Suite) TestSubstContext_NoClass(c *check.C) { | 69 | func (s *Suite) TestSubstContext_NoClass(c *check.C) { | |
53 | s.UseCommandLine(c, "-Wextra") | 70 | s.UseCommandLine(c, "-Wextra") | |
54 | ctx := new(SubstContext) | 71 | ctx := new(SubstContext) | |
55 | 72 | |||
56 | ctx.Varassign(newSubstLine(10, "UNRELATED=anything")) | 73 | ctx.Varassign(newSubstLine(10, "UNRELATED=anything")) | |
57 | ctx.Varassign(newSubstLine(11, "SUBST_FILES.repl+=Makefile.in")) | 74 | ctx.Varassign(newSubstLine(11, "SUBST_FILES.repl+=Makefile.in")) | |
58 | ctx.Varassign(newSubstLine(12, "SUBST_SED.repl+=-e s,from,to,g")) | 75 | ctx.Varassign(newSubstLine(12, "SUBST_SED.repl+=-e s,from,to,g")) | |
59 | ctx.Finish(newSubstLine(13, "")) | 76 | ctx.Finish(newSubstLine(13, "")) | |
60 | 77 | |||
61 | c.Check(s.Output(), equals, ""+ | 78 | c.Check(s.Output(), equals, ""+ | |
62 | "WARN: Makefile:11: SUBST_CLASSES should come before the definition of \"SUBST_FILES.repl\".\n"+ | 79 | "WARN: Makefile:11: SUBST_CLASSES should come before the definition of \"SUBST_FILES.repl\".\n"+ | |
63 | "WARN: Makefile:13: Incomplete SUBST block: SUBST_STAGE.repl missing.\n") | 80 | "WARN: Makefile:13: Incomplete SUBST block: SUBST_STAGE.repl missing.\n") | |
64 | } | 81 | } |
@@ -420,27 +420,27 @@ func (mkline *MkLine) CheckVaruse(varuse | @@ -420,27 +420,27 @@ func (mkline *MkLine) CheckVaruse(varuse | |||
420 | defer tracecall(mkline, varuse, vuc)() | 420 | defer tracecall(mkline, varuse, vuc)() | |
421 | } | 421 | } | |
422 | 422 | |||
423 | if varuse.IsExpression() { | 423 | if varuse.IsExpression() { | |
424 | return | 424 | return | |
425 | } | 425 | } | |
426 | 426 | |||
427 | varname := varuse.varname | 427 | varname := varuse.varname | |
428 | vartype := mkline.getVariableType(varname) | 428 | vartype := mkline.getVariableType(varname) | |
429 | if G.opts.WarnExtra && | 429 | if G.opts.WarnExtra && | |
430 | (vartype == nil || vartype.guessed) && | 430 | (vartype == nil || vartype.guessed) && | |
431 | !varIsUsed(varname) && | 431 | !varIsUsed(varname) && | |
432 | !(G.Mk != nil && G.Mk.forVars[varname]) && | 432 | !(G.Mk != nil && G.Mk.forVars[varname]) && | |
433 | !hasPrefix(varname, "${") { | 433 | !containsVarRef(varname) { | |
434 | mkline.Warn1("%s is used but not defined. Spelling mistake?", varname) | 434 | mkline.Warn1("%s is used but not defined. Spelling mistake?", varname) | |
435 | } | 435 | } | |
436 | 436 | |||
437 | mkline.CheckVarusePermissions(varname, vartype, vuc) | 437 | mkline.CheckVarusePermissions(varname, vartype, vuc) | |
438 | 438 | |||
439 | if varname == "LOCALBASE" && !G.Infrastructure { | 439 | if varname == "LOCALBASE" && !G.Infrastructure { | |
440 | mkline.WarnVaruseLocalbase() | 440 | mkline.WarnVaruseLocalbase() | |
441 | } | 441 | } | |
442 | 442 | |||
443 | needsQuoting := mkline.variableNeedsQuoting(varname, vartype, vuc) | 443 | needsQuoting := mkline.variableNeedsQuoting(varname, vartype, vuc) | |
444 | 444 | |||
445 | if vuc.quoting == vucQuotFor { | 445 | if vuc.quoting == vucQuotFor { | |
446 | mkline.checkVaruseFor(varname, vartype, needsQuoting) | 446 | mkline.checkVaruseFor(varname, vartype, needsQuoting) | |
@@ -989,106 +989,45 @@ func (mkline *MkLine) checkVarassignPlis | @@ -989,106 +989,45 @@ func (mkline *MkLine) checkVarassignPlis | |||
989 | "\t.if ...", | 989 | "\t.if ...", | |
990 | "\tMY_VAR?=\tyes", | 990 | "\tMY_VAR?=\tyes", | |
991 | "\t.endif") | 991 | "\t.endif") | |
992 | } | 992 | } | |
993 | 993 | |||
994 | // Mark the variable as PLIST condition. This is later used in checkfile_PLIST. | 994 | // Mark the variable as PLIST condition. This is later used in checkfile_PLIST. | |
995 | if G.Pkg != nil { | 995 | if G.Pkg != nil { | |
996 | if m, plistVarname := match1(value, `(.+)=.*@comment.*`); m { | 996 | if m, plistVarname := match1(value, `(.+)=.*@comment.*`); m { | |
997 | G.Pkg.plistSubstCond[plistVarname] = true | 997 | G.Pkg.plistSubstCond[plistVarname] = true | |
998 | } | 998 | } | |
999 | } | 999 | } | |
1000 | } | 1000 | } | |
1001 | 1001 | |||
1002 | const reVarnamePlural = `^(?:` + | |||
1003 | `.*[Ss]` + | |||
1004 | `|.*LIST` + | |||
1005 | `|.*_AWK` + | |||
1006 | `|.*_ENV` + | |||
1007 | `|.*_OVERRIDE` + | |||
1008 | `|.*_PREREQ` + | |||
1009 | `|.*_REQD` + | |||
1010 | `|.*_SED` + | |||
1011 | `|.*_SKIP` + | |||
1012 | `|.*_SRC` + | |||
1013 | `|.*_SUBST` + | |||
1014 | `|.*_TARGET` + | |||
1015 | `|.*_TMPL` + | |||
1016 | `|BROKEN_EXCEPT_ON_PLATFORM` + | |||
1017 | `|BROKEN_ON_PLATFORM` + | |||
1018 | `|BUILDLINK_DEPMETHOD` + | |||
1019 | `|BUILDLINK_LDADD` + | |||
1020 | `|BUILDLINK_TRANSFORM` + | |||
1021 | `|COMMENT` + | |||
1022 | `|CRYPTO` + | |||
1023 | `|DEINSTALL_TEMPLATE` + | |||
1024 | `|EVAL_PREFIX` + | |||
1025 | `|EXTRACT_ONLY` + | |||
1026 | `|FETCH_MESSAGE` + | |||
1027 | `|FIX_RPATH` + | |||
1028 | `|GENERATE_PLIST` + | |||
1029 | `|INSTALL_TEMPLATE` + | |||
1030 | `|INTERACTIVE_STAGE` + | |||
1031 | `|LICENSE` + | |||
1032 | `|MASTER_SITE_.*` + | |||
1033 | `|MASTER_SORT_REGEX` + | |||
1034 | `|NOT_FOR_COMPILER` + | |||
1035 | `|NOT_FOR_PLATFORM` + | |||
1036 | `|ONLY_FOR_COMPILER` + | |||
1037 | `|ONLY_FOR_PLATFORM` + | |||
1038 | `|PERL5_PACKLIST` + | |||
1039 | `|PLIST_CAT` + | |||
1040 | `|PLIST_PRE` + | |||
1041 | `|PKG_FAIL_REASON` + | |||
1042 | `|PKG_SKIP_REASON` + | |||
1043 | `|PREPEND_PATH` + | |||
1044 | `|PYTHON_VERSIONS_INCOMPATIBLE` + | |||
1045 | `|REPLACE_INTERPRETER` + | |||
1046 | `|REPLACE_PERL` + | |||
1047 | `|REPLACE_RUBY` + | |||
1048 | `|RESTRICTED` + | |||
1049 | `|SITES_.+` + | |||
1050 | `|TOOLS_ALIASES\..+` + | |||
1051 | `|TOOLS_BROKEN` + | |||
1052 | `|TOOLS_CREATE` + | |||
1053 | `|TOOLS_GNU_MISSING` + | |||
1054 | `|TOOLS_NOOP` + | |||
1055 | `)$` | |||
1056 | ||||
1057 | func (mkline *MkLine) CheckVartype(varname string, op MkOperator, value, comment string) { | 1002 | func (mkline *MkLine) CheckVartype(varname string, op MkOperator, value, comment string) { | |
1058 | if G.opts.Debug { | 1003 | if G.opts.Debug { | |
1059 | defer tracecall(varname, op, value, comment)() | 1004 | defer tracecall(varname, op, value, comment)() | |
1060 | } | 1005 | } | |
1061 | 1006 | |||
1062 | if !G.opts.WarnTypes { | 1007 | if !G.opts.WarnTypes { | |
1063 | return | 1008 | return | |
1064 | } | 1009 | } | |
1065 | 1010 | |||
1066 | varbase := varnameBase(varname) | |||
1067 | vartype := mkline.getVariableType(varname) | 1011 | vartype := mkline.getVariableType(varname) | |
1068 | 1012 | |||
1069 | if op == opAssignAppend { | 1013 | if op == opAssignAppend { | |
1070 | if vartype != nil { | 1014 | if vartype != nil && !vartype.MayBeAppendedTo() { | |
1071 | if !vartype.MayBeAppendedTo() { | 1015 | mkline.Warn0("The \"+=\" operator should only be used with lists.") | |
1072 | mkline.Warn0("The \"+=\" operator should only be used with lists.") | |||
1073 | } | |||
1074 | } else if !hasPrefix(varbase, "_") && !matches(varbase, reVarnamePlural) { | |||
1075 | mkline.Warn1("As %s is modified using \"+=\", its name should indicate plural.", varname) | |||
1076 | } | 1016 | } | |
1077 | } | 1017 | } | |
1078 | 1018 | |||
1079 | switch { | 1019 | switch { | |
1080 | case vartype == nil: | 1020 | case vartype == nil: | |
1081 | // Cannot check anything if the type is not known. | |||
1082 | if G.opts.Debug { | 1021 | if G.opts.Debug { | |
1083 | traceStep1("Unchecked variable assignment for %s.", varname) | 1022 | traceStep1("Unchecked variable assignment for %s.", varname) | |
1084 | } | 1023 | } | |
1085 | 1024 | |||
1086 | case op == opAssignShell: | 1025 | case op == opAssignShell: | |
1087 | if G.opts.Debug { | 1026 | if G.opts.Debug { | |
1088 | traceStep1("Unchecked use of !=: %q", value) | 1027 | traceStep1("Unchecked use of !=: %q", value) | |
1089 | } | 1028 | } | |
1090 | 1029 | |||
1091 | case vartype.kindOfList == lkNone: | 1030 | case vartype.kindOfList == lkNone: | |
1092 | mkline.CheckVartypePrimitive(varname, vartype.checker, op, value, comment, vartype.IsConsideredList(), vartype.guessed) | 1031 | mkline.CheckVartypePrimitive(varname, vartype.checker, op, value, comment, vartype.IsConsideredList(), vartype.guessed) | |
1093 | 1032 | |||
1094 | case value == "": | 1033 | case value == "": | |
@@ -1370,27 +1309,27 @@ func matchMkCond(text string) (m bool, i | @@ -1370,27 +1309,27 @@ func matchMkCond(text string) (m bool, i | |||
1370 | return | 1309 | return | |
1371 | } | 1310 | } | |
1372 | 1311 | |||
1373 | type NeedsQuoting uint8 | 1312 | type NeedsQuoting uint8 | |
1374 | 1313 | |||
1375 | const ( | 1314 | const ( | |
1376 | nqNo NeedsQuoting = iota | 1315 | nqNo NeedsQuoting = iota | |
1377 | nqYes | 1316 | nqYes | |
1378 | nqDoesntMatter | 1317 | nqDoesntMatter | |
1379 | nqDontKnow | 1318 | nqDontKnow | |
1380 | ) | 1319 | ) | |
1381 | 1320 | |||
1382 | func (nq NeedsQuoting) String() string { | 1321 | func (nq NeedsQuoting) String() string { | |
1383 | return [...]string{"no", "yes", "doesn't matter", "don't known"}[nq] | 1322 | return [...]string{"no", "yes", "doesn't matter", "don't know"}[nq] | |
1384 | } | 1323 | } | |
1385 | 1324 | |||
1386 | func (mkline *MkLine) variableNeedsQuoting(varname string, vartype *Vartype, vuc *VarUseContext) (needsQuoting NeedsQuoting) { | 1325 | func (mkline *MkLine) variableNeedsQuoting(varname string, vartype *Vartype, vuc *VarUseContext) (needsQuoting NeedsQuoting) { | |
1387 | if G.opts.Debug { | 1326 | if G.opts.Debug { | |
1388 | defer tracecall(varname, vartype, vuc, "=>", &needsQuoting)() | 1327 | defer tracecall(varname, vartype, vuc, "=>", &needsQuoting)() | |
1389 | } | 1328 | } | |
1390 | 1329 | |||
1391 | if vartype == nil || vuc.vartype == nil { | 1330 | if vartype == nil || vuc.vartype == nil { | |
1392 | return nqDontKnow | 1331 | return nqDontKnow | |
1393 | } | 1332 | } | |
1394 | 1333 | |||
1395 | if vartype.checker.IsEnum() || vartype.IsBasicSafe() { | 1334 | if vartype.checker.IsEnum() || vartype.IsBasicSafe() { | |
1396 | if vartype.kindOfList == lkNone { | 1335 | if vartype.kindOfList == lkNone { | |
@@ -1520,27 +1459,27 @@ func (mkline *MkLine) getVariableType(va | @@ -1520,27 +1459,27 @@ func (mkline *MkLine) getVariableType(va | |||
1520 | if m, toolvarname := match1(varname, `^TOOLS_(.*)`); m && G.globalData.Tools.byVarname[toolvarname] != nil { | 1459 | if m, toolvarname := match1(varname, `^TOOLS_(.*)`); m && G.globalData.Tools.byVarname[toolvarname] != nil { | |
1521 | return &Vartype{lkNone, CheckvarPathname, []AclEntry{{"*", aclpUse}}, false} | 1460 | return &Vartype{lkNone, CheckvarPathname, []AclEntry{{"*", aclpUse}}, false} | |
1522 | } | 1461 | } | |
1523 | 1462 | |||
1524 | allowAll := []AclEntry{{"*", aclpAll}} | 1463 | allowAll := []AclEntry{{"*", aclpAll}} | |
1525 | allowRuntime := []AclEntry{{"*", aclpAllRuntime}} | 1464 | allowRuntime := []AclEntry{{"*", aclpAllRuntime}} | |
1526 | 1465 | |||
1527 | // Guess the datatype of the variable based on naming conventions. | 1466 | // Guess the datatype of the variable based on naming conventions. | |
1528 | varbase := varnameBase(varname) | 1467 | varbase := varnameBase(varname) | |
1529 | var gtype *Vartype | 1468 | var gtype *Vartype | |
1530 | switch { | 1469 | switch { | |
1531 | case hasSuffix(varbase, "DIRS"): | 1470 | case hasSuffix(varbase, "DIRS"): | |
1532 | gtype = &Vartype{lkShell, CheckvarPathmask, allowRuntime, true} | 1471 | gtype = &Vartype{lkShell, CheckvarPathmask, allowRuntime, true} | |
1533 | case hasSuffix(varbase, "DIR"), hasSuffix(varname, "_HOME"): | 1472 | case hasSuffix(varbase, "DIR") && !hasSuffix(varbase, "DESTDIR"), hasSuffix(varname, "_HOME"): | |
1534 | gtype = &Vartype{lkNone, CheckvarPathname, allowRuntime, true} | 1473 | gtype = &Vartype{lkNone, CheckvarPathname, allowRuntime, true} | |
1535 | case hasSuffix(varbase, "FILES"): | 1474 | case hasSuffix(varbase, "FILES"): | |
1536 | gtype = &Vartype{lkShell, CheckvarPathmask, allowRuntime, true} | 1475 | gtype = &Vartype{lkShell, CheckvarPathmask, allowRuntime, true} | |
1537 | case hasSuffix(varbase, "FILE"): | 1476 | case hasSuffix(varbase, "FILE"): | |
1538 | gtype = &Vartype{lkNone, CheckvarPathname, allowRuntime, true} | 1477 | gtype = &Vartype{lkNone, CheckvarPathname, allowRuntime, true} | |
1539 | case hasSuffix(varbase, "PATH"): | 1478 | case hasSuffix(varbase, "PATH"): | |
1540 | gtype = &Vartype{lkNone, CheckvarPathlist, allowRuntime, true} | 1479 | gtype = &Vartype{lkNone, CheckvarPathlist, allowRuntime, true} | |
1541 | case hasSuffix(varbase, "PATHS"): | 1480 | case hasSuffix(varbase, "PATHS"): | |
1542 | gtype = &Vartype{lkShell, CheckvarPathname, allowRuntime, true} | 1481 | gtype = &Vartype{lkShell, CheckvarPathname, allowRuntime, true} | |
1543 | case hasSuffix(varbase, "_USER"): | 1482 | case hasSuffix(varbase, "_USER"): | |
1544 | gtype = &Vartype{lkNone, CheckvarUserGroupName, allowAll, true} | 1483 | gtype = &Vartype{lkNone, CheckvarUserGroupName, allowAll, true} | |
1545 | case hasSuffix(varbase, "_GROUP"): | 1484 | case hasSuffix(varbase, "_GROUP"): | |
1546 | gtype = &Vartype{lkNone, CheckvarUserGroupName, allowAll, true} | 1485 | gtype = &Vartype{lkNone, CheckvarUserGroupName, allowAll, true} |
@@ -282,36 +282,31 @@ func (s *Suite) TestVarUseContext_ToStri | @@ -282,36 +282,31 @@ func (s *Suite) TestVarUseContext_ToStri | |||
282 | G.globalData.InitVartypes() | 282 | G.globalData.InitVartypes() | |
283 | mkline := NewMkLine(NewLine("fname", 1, "# dummy", nil)) | 283 | mkline := NewMkLine(NewLine("fname", 1, "# dummy", nil)) | |
284 | vartype := mkline.getVariableType("PKGNAME") | 284 | vartype := mkline.getVariableType("PKGNAME") | |
285 | vuc := &VarUseContext{vartype, vucTimeUnknown, vucQuotBackt, vucExtentWord} | 285 | vuc := &VarUseContext{vartype, vucTimeUnknown, vucQuotBackt, vucExtentWord} | |
286 | 286 | |||
287 | c.Check(vuc.String(), equals, "(PkgName time:unknown quoting:backt extent:word)") | 287 | c.Check(vuc.String(), equals, "(PkgName time:unknown quoting:backt extent:word)") | |
288 | } | 288 | } | |
289 | 289 | |||
290 | func (s *Suite) TestMkLine_(c *check.C) { | 290 | func (s *Suite) TestMkLine_(c *check.C) { | |
291 | G.globalData.InitVartypes() | 291 | G.globalData.InitVartypes() | |
292 | 292 | |||
293 | G.Mk = s.NewMkLines("Makefile", | 293 | G.Mk = s.NewMkLines("Makefile", | |
294 | "# $"+"NetBSD$", | 294 | "# $"+"NetBSD$", | |
295 | "ac_cv_libpari_libs+=\t-L${BUILDLINK_PREFIX.pari}/lib", // From math/clisp-pari/Makefile, rev. 1.8 | 295 | "ac_cv_libpari_libs+=\t-L${BUILDLINK_PREFIX.pari}/lib") // From math/clisp-pari/Makefile, rev. 1.8 | |
296 | "var+=value") | |||
297 | 296 | |||
298 | G.Mk.mklines[1].checkVarassign() | 297 | G.Mk.mklines[1].checkVarassign() | |
299 | G.Mk.mklines[2].checkVarassign() | |||
300 | 298 | |||
301 | c.Check(s.Output(), equals, ""+ | 299 | c.Check(s.Output(), equals, "WARN: Makefile:2: ac_cv_libpari_libs is defined but not used. Spelling mistake?\n") | |
302 | "WARN: Makefile:2: ac_cv_libpari_libs is defined but not used. Spelling mistake?\n"+ | |||
303 | "WARN: Makefile:3: As var is modified using \"+=\", its name should indicate plural.\n"+ | |||
304 | "WARN: Makefile:3: var is defined but not used. Spelling mistake?\n") | |||
305 | } | 300 | } | |
306 | 301 | |||
307 | // In variable assignments, a plain '#' introduces a line comment, unless | 302 | // In variable assignments, a plain '#' introduces a line comment, unless | |
308 | // it is escaped by a backslash. In shell commands, on the other hand, it | 303 | // it is escaped by a backslash. In shell commands, on the other hand, it | |
309 | // is interpreted literally. | 304 | // is interpreted literally. | |
310 | func (s *Suite) TestParselineMk(c *check.C) { | 305 | func (s *Suite) TestParselineMk(c *check.C) { | |
311 | line1 := NewMkLine(NewLine("fname", 1, "SED_CMD=\t's,\\#,hash,g'", nil)) | 306 | line1 := NewMkLine(NewLine("fname", 1, "SED_CMD=\t's,\\#,hash,g'", nil)) | |
312 | 307 | |||
313 | c.Check(line1.Varname(), equals, "SED_CMD") | 308 | c.Check(line1.Varname(), equals, "SED_CMD") | |
314 | c.Check(line1.Value(), equals, "'s,#,hash,g'") | 309 | c.Check(line1.Value(), equals, "'s,#,hash,g'") | |
315 | 310 | |||
316 | line2 := NewMkLine(NewLine("fname", 1, "\tsed -e 's,\\#,hash,g'", nil)) | 311 | line2 := NewMkLine(NewLine("fname", 1, "\tsed -e 's,\\#,hash,g'", nil)) | |
317 | 312 | |||
@@ -669,26 +664,44 @@ func (s *Suite) TestMkLine_variableNeeds | @@ -669,26 +664,44 @@ func (s *Suite) TestMkLine_variableNeeds | |||
669 | func (s *Suite) TestMkLine_variableNeedsQuoting_18(c *check.C) { | 664 | func (s *Suite) TestMkLine_variableNeedsQuoting_18(c *check.C) { | |
670 | s.UseCommandLine(c, "-Wall") | 665 | s.UseCommandLine(c, "-Wall") | |
671 | s.RegisterMasterSite("MASTER_SITE_GNOME", "http://ftp.gnome.org/") | 666 | s.RegisterMasterSite("MASTER_SITE_GNOME", "http://ftp.gnome.org/") | |
672 | G.globalData.InitVartypes() | 667 | G.globalData.InitVartypes() | |
673 | G.Mk = s.NewMkLines("x11/gtk3/Makefile", | 668 | G.Mk = s.NewMkLines("x11/gtk3/Makefile", | |
674 | "# $"+"NetBSD$", | 669 | "# $"+"NetBSD$", | |
675 | "MASTER_SITES=\tftp://ftp.gtk.org/${PKGNAME}/ ${MASTER_SITE_GNOME:=subdir/}") | 670 | "MASTER_SITES=\tftp://ftp.gtk.org/${PKGNAME}/ ${MASTER_SITE_GNOME:=subdir/}") | |
676 | 671 | |||
677 | G.Mk.mklines[1].checkVarassignVaruse() | 672 | G.Mk.mklines[1].checkVarassignVaruse() | |
678 | 673 | |||
679 | c.Check(s.Output(), equals, "") // Don’t warn about missing :Q operators. | 674 | c.Check(s.Output(), equals, "") // Don’t warn about missing :Q operators. | |
680 | } | 675 | } | |
681 | 676 | |||
677 | func (s *Suite) Test_MkLine_variableNeedsQuoting_tool_in_CONFIGURE_ENV(c *check.C) { | |||
678 | s.UseCommandLine(c, "-Wall") | |||
679 | G.globalData.InitVartypes() | |||
680 | G.globalData.Tools = NewToolRegistry() | |||
681 | G.globalData.Tools.RegisterVarname("tar", "TAR") | |||
682 | ||||
683 | mklines := s.NewMkLines("Makefile", | |||
684 | "# $"+"NetBSD$", | |||
685 | "", | |||
686 | "CONFIGURE_ENV+=\tSYS_TAR_COMMAND_PATH=${TOOLS_TAR:Q}") | |||
687 | ||||
688 | mklines.mklines[2].checkVarassignVaruse() | |||
689 | ||||
690 | // The TOOLS_* variables only contain the path to the tool, | |||
691 | // without any additional arguments that might be necessary. | |||
692 | c.Check(s.Output(), equals, "NOTE: Makefile:3: The :Q operator isn't necessary for ${TOOLS_TAR} here.\n") | |||
693 | } | |||
694 | ||||
682 | func (s *Suite) Test_MkLine_Varuse_Modifier_L(c *check.C) { | 695 | func (s *Suite) Test_MkLine_Varuse_Modifier_L(c *check.C) { | |
683 | s.UseCommandLine(c, "-Wall") | 696 | s.UseCommandLine(c, "-Wall") | |
684 | G.globalData.InitVartypes() | 697 | G.globalData.InitVartypes() | |
685 | G.Mk = s.NewMkLines("x11/xkeyboard-config/Makefile", | 698 | G.Mk = s.NewMkLines("x11/xkeyboard-config/Makefile", | |
686 | "FILES_SUBST+=XKBCOMP_SYMLINK=${${XKBBASE}/xkbcomp:L:Q}") | 699 | "FILES_SUBST+=XKBCOMP_SYMLINK=${${XKBBASE}/xkbcomp:L:Q}") | |
687 | 700 | |||
688 | G.Mk.mklines[0].Check() | 701 | G.Mk.mklines[0].Check() | |
689 | 702 | |||
690 | c.Check(s.Output(), equals, "") // Don’t warn that ${XKBBASE}/xkbcomp is used but not defined. | 703 | c.Check(s.Output(), equals, "") // Don’t warn that ${XKBBASE}/xkbcomp is used but not defined. | |
691 | } | 704 | } | |
692 | 705 | |||
693 | func (s *Suite) Test_MkLine_Cond_ShellCommand(c *check.C) { | 706 | func (s *Suite) Test_MkLine_Cond_ShellCommand(c *check.C) { | |
694 | s.UseCommandLine(c, "-Wall") | 707 | s.UseCommandLine(c, "-Wall") | |
@@ -761,13 +774,38 @@ func (s *Suite) Test_MkLine_ShellCommand | @@ -761,13 +774,38 @@ func (s *Suite) Test_MkLine_ShellCommand | |||
761 | 774 | |||
762 | func (s *Suite) Test_MkLine_shell_varuse_in_backt_dquot(c *check.C) { | 775 | func (s *Suite) Test_MkLine_shell_varuse_in_backt_dquot(c *check.C) { | |
763 | s.UseCommandLine(c, "-Wall") | 776 | s.UseCommandLine(c, "-Wall") | |
764 | G.globalData.InitVartypes() | 777 | G.globalData.InitVartypes() | |
765 | mklines := s.NewMkLines("x11/motif/Makefile", | 778 | mklines := s.NewMkLines("x11/motif/Makefile", | |
766 | "# $"+"NetBSD$", | 779 | "# $"+"NetBSD$", | |
767 | "post-patch:", | 780 | "post-patch:", | |
768 | "\tfiles=`${GREP} -l \".fB$${name}.fP(3)\" *.3`") | 781 | "\tfiles=`${GREP} -l \".fB$${name}.fP(3)\" *.3`") | |
769 | 782 | |||
770 | mklines.Check() | 783 | mklines.Check() | |
771 | 784 | |||
772 | c.Check(s.Output(), equals, "WARN: x11/motif/Makefile:3: Unknown shell command \"${GREP}\".\n") // No parse errors. | 785 | c.Check(s.Output(), equals, "WARN: x11/motif/Makefile:3: Unknown shell command \"${GREP}\".\n") // No parse errors. | |
773 | } | 786 | } | |
787 | ||||
788 | // See PR 46570, Ctrl+F "3. In lang/perl5". | |||
789 | func (s *Suite) Test_MkLine_getVariableType(c *check.C) { | |||
790 | mkline := NewMkLine(dummyLine) | |||
791 | ||||
792 | c.Check(mkline.getVariableType("_PERL5_PACKLIST_AWK_STRIP_DESTDIR"), check.IsNil) | |||
793 | c.Check(mkline.getVariableType("SOME_DIR").guessed, equals, true) | |||
794 | c.Check(mkline.getVariableType("SOMEDIR").guessed, equals, true) | |||
795 | } | |||
796 | ||||
797 | // See PR 46570, Ctrl+F "4. Shell quoting". | |||
798 | // Pkglint is correct, since this definition for CPPFLAGS should be | |||
799 | // seen by the shell as three words, not one word. | |||
800 | func (s *Suite) Test_MkLine_Cflags(c *check.C) { | |||
801 | G.globalData.InitVartypes() | |||
802 | mklines := s.NewMkLines("Makefile", | |||
803 | "# $"+"NetBSD$", | |||
804 | "CPPFLAGS.SunOS+=\t-DPIPECOMMAND=\\\"/usr/sbin/sendmail -bs %s\\\"") | |||
805 | ||||
806 | mklines.Check() | |||
807 | ||||
808 | c.Check(s.Output(), equals, ""+ | |||
809 | "WARN: Makefile:2: Unknown compiler flag \"-bs\".\n"+ | |||
810 | "WARN: Makefile:2: Compiler flag \"%s\\\\\\\"\" should start with a hyphen.\n") | |||
811 | } |
@@ -1,19 +1,21 @@ | @@ -1,19 +1,21 @@ | |||
1 | package main | 1 | package main | |
2 | 2 | |||
3 | import ( | 3 | import ( | |
4 | check "gopkg.in/check.v1" | 4 | check "gopkg.in/check.v1" | |
5 | ) | 5 | ) | |
6 | 6 | |||
7 | const mkrcsid = "# $" + "NetBSD$" | |||
8 | ||||
7 | func (s *Suite) TestMkLines_AutofixConditionalIndentation(c *check.C) { | 9 | func (s *Suite) TestMkLines_AutofixConditionalIndentation(c *check.C) { | |
8 | s.UseCommandLine(c, "--autofix", "-Wspace") | 10 | s.UseCommandLine(c, "--autofix", "-Wspace") | |
9 | tmpfile := s.CreateTmpFile(c, "fname.mk", "") | 11 | tmpfile := s.CreateTmpFile(c, "fname.mk", "") | |
10 | mklines := s.NewMkLines(tmpfile, | 12 | mklines := s.NewMkLines(tmpfile, | |
11 | "# $"+"NetBSD$", | 13 | "# $"+"NetBSD$", | |
12 | ".if defined(A)", | 14 | ".if defined(A)", | |
13 | ".for a in ${A}", | 15 | ".for a in ${A}", | |
14 | ".if defined(C)", | 16 | ".if defined(C)", | |
15 | ".endif", | 17 | ".endif", | |
16 | ".endfor", | 18 | ".endfor", | |
17 | ".endif") | 19 | ".endif") | |
18 | 20 | |||
19 | mklines.Check() | 21 | mklines.Check() | |
@@ -248,13 +250,64 @@ func (s *Suite) Test_MkLines_LoopModifie | @@ -248,13 +250,64 @@ func (s *Suite) Test_MkLines_LoopModifie | |||
248 | func (s *Suite) Test_MkLines_Indentation_DependsOn(c *check.C) { | 250 | func (s *Suite) Test_MkLines_Indentation_DependsOn(c *check.C) { | |
249 | G.globalData.InitVartypes() | 251 | G.globalData.InitVartypes() | |
250 | mklines := s.NewMkLines("Makefile", | 252 | mklines := s.NewMkLines("Makefile", | |
251 | "# $"+"NetBSD$", | 253 | "# $"+"NetBSD$", | |
252 | "PKG_SKIP_REASON+=\t\"Fails everywhere\"", | 254 | "PKG_SKIP_REASON+=\t\"Fails everywhere\"", | |
253 | ".if ${OPSYS} == \"Cygwin\"", | 255 | ".if ${OPSYS} == \"Cygwin\"", | |
254 | "PKG_SKIP_REASON+=\t\"Fails on Cygwin\"", | 256 | "PKG_SKIP_REASON+=\t\"Fails on Cygwin\"", | |
255 | ".endif") | 257 | ".endif") | |
256 | 258 | |||
257 | mklines.Check() | 259 | mklines.Check() | |
258 | 260 | |||
259 | c.Check(s.Output(), equals, "NOTE: Makefile:4: Consider defining NOT_FOR_PLATFORM instead of setting PKG_SKIP_REASON depending on ${OPSYS}.\n") | 261 | c.Check(s.Output(), equals, "NOTE: Makefile:4: Consider defining NOT_FOR_PLATFORM instead of setting PKG_SKIP_REASON depending on ${OPSYS}.\n") | |
260 | } | 262 | } | |
263 | ||||
264 | // PR 46570, item "15. net/uucp/Makefile has a make loop" | |||
265 | func (s *Suite) Test_MkLines_indirect_variables(c *check.C) { | |||
266 | s.UseCommandLine(c, "-Wall") | |||
267 | mklines := s.NewMkLines("net/uucp/Makefile", | |||
268 | "# $"+"NetBSD$", | |||
269 | "", | |||
270 | "post-configure:", | |||
271 | ".for var in MAIL_PROGRAM CMDPATH", | |||
272 | "\t"+`${RUN} ${ECHO} "#define ${var} \""${UUCP_${var}}"\"`, | |||
273 | ".endfor") | |||
274 | ||||
275 | mklines.Check() | |||
276 | ||||
277 | // No warning about UUCP_${var} being used but not defined. | |||
278 | c.Check(s.Output(), equals, ""+ | |||
279 | "WARN: net/uucp/Makefile:5: Unknown shell command \"${ECHO}\".\n") | |||
280 | } | |||
281 | ||||
282 | func (s *Suite) Test_MkLines_Check_list_variable_as_part_of_word(c *check.C) { | |||
283 | s.UseCommandLine(c, "-Wall") | |||
284 | mklines := s.NewMkLines("converters/chef/Makefile", | |||
285 | mkrcsid, | |||
286 | "\tcd ${WRKSRC} && tr '\\r' '\\n' < ${DISTDIR}/${DIST_SUBDIR}/${DISTFILES} > chef.l") | |||
287 | ||||
288 | mklines.Check() | |||
289 | ||||
290 | c.Check(s.Output(), equals, ""+ | |||
291 | "WARN: converters/chef/Makefile:2: Unknown shell command \"tr\".\n"+ | |||
292 | "WARN: converters/chef/Makefile:2: The list variable DISTFILES should not be embedded in a word.\n") | |||
293 | } | |||
294 | ||||
295 | func (s *Suite) Test_MkLines_Check_absolute_pathname_depending_on_OPSYS(c *check.C) { | |||
296 | s.UseCommandLine(c, "-Wall") | |||
297 | G.globalData.InitVartypes() | |||
298 | mklines := s.NewMkLines("games/heretic2-demo/Makefile", | |||
299 | mkrcsid, | |||
300 | ".if ${OPSYS} == \"DragonFly\"", | |||
301 | "TOOLS_PLATFORM.gtar=\t/usr/bin/bsdtar", | |||
302 | ".endif", | |||
303 | "TOOLS_PLATFORM.gtar=\t/usr/bin/bsdtar") | |||
304 | ||||
305 | mklines.Check() | |||
306 | ||||
307 | // No warning about an unknown shell command in line 3, | |||
308 | // since that line depends on OPSYS. | |||
309 | c.Check(s.Output(), equals, ""+ | |||
310 | "WARN: games/heretic2-demo/Makefile:3: The variable TOOLS_PLATFORM.gtar may not be set by any package.\n"+ | |||
311 | "WARN: games/heretic2-demo/Makefile:5: The variable TOOLS_PLATFORM.gtar may not be set by any package.\n"+ | |||
312 | "WARN: games/heretic2-demo/Makefile:5: Unknown shell command \"/usr/bin/bsdtar\".\n") | |||
313 | } |
@@ -316,27 +316,28 @@ func (ck *PlistChecker) checkpathSbin(pl | @@ -316,27 +316,28 @@ func (ck *PlistChecker) checkpathSbin(pl | |||
316 | pline.line.Warn1("Manual page missing for sbin/%s.", binname) | 316 | pline.line.Warn1("Manual page missing for sbin/%s.", binname) | |
317 | Explain( | 317 | Explain( | |
318 | "All programs that can be run directly by the user should have a", | 318 | "All programs that can be run directly by the user should have a", | |
319 | "manual page for quick reference. The programs in the sbin/", | 319 | "manual page for quick reference. The programs in the sbin/", | |
320 | "directory should have corresponding manual pages in section 8", | 320 | "directory should have corresponding manual pages in section 8", | |
321 | "(filename program.8), while the programs in the bin/ directory", | 321 | "(filename program.8), while the programs in the bin/ directory", | |
322 | "have their manual pages in section 1.") | 322 | "have their manual pages in section 1.") | |
323 | } | 323 | } | |
324 | } | 324 | } | |
325 | 325 | |||
326 | func (ck *PlistChecker) checkpathShare(pline *PlistLine) { | 326 | func (ck *PlistChecker) checkpathShare(pline *PlistLine) { | |
327 | line, text := pline.line, pline.text | 327 | line, text := pline.line, pline.text | |
328 | switch { | 328 | switch { | |
329 | case hasPrefix(text, "share/applications/") && hasSuffix(text, ".desktop"): | 329 | // Disabled due to PR 46570, item "10. It should stop". | |
330 | case false && hasPrefix(text, "share/applications/") && hasSuffix(text, ".desktop"): | |||
330 | f := "../../sysutils/desktop-file-utils/desktopdb.mk" | 331 | f := "../../sysutils/desktop-file-utils/desktopdb.mk" | |
331 | if G.opts.WarnExtra && G.Pkg != nil && G.Pkg.included[f] == nil { | 332 | if G.opts.WarnExtra && G.Pkg != nil && G.Pkg.included[f] == nil { | |
332 | line.Warn1("Packages that install a .desktop entry should .include %q.", f) | 333 | line.Warn1("Packages that install a .desktop entry should .include %q.", f) | |
333 | Explain3( | 334 | Explain3( | |
334 | "If *.desktop files contain MimeType keys, the global MIME type", | 335 | "If *.desktop files contain MimeType keys, the global MIME type", | |
335 | "registry must be updated by desktop-file-utils. Otherwise, this", | 336 | "registry must be updated by desktop-file-utils. Otherwise, this", | |
336 | "warning is harmless.") | 337 | "warning is harmless.") | |
337 | } | 338 | } | |
338 | 339 | |||
339 | case hasPrefix(text, "share/icons/hicolor/") && G.Pkg != nil && G.Pkg.Pkgpath != "graphics/hicolor-icon-theme": | 340 | case hasPrefix(text, "share/icons/hicolor/") && G.Pkg != nil && G.Pkg.Pkgpath != "graphics/hicolor-icon-theme": | |
340 | f := "../../graphics/hicolor-icon-theme/buildlink3.mk" | 341 | f := "../../graphics/hicolor-icon-theme/buildlink3.mk" | |
341 | if G.Pkg.included[f] == nil { | 342 | if G.Pkg.included[f] == nil { | |
342 | line.Error1("Packages that install hicolor icons must include %q in the Makefile.", f) | 343 | line.Error1("Packages that install hicolor icons must include %q in the Makefile.", f) |
@@ -135,26 +135,29 @@ func (s *Suite) TestPlistChecker_sort(c | @@ -135,26 +135,29 @@ func (s *Suite) TestPlistChecker_sort(c | |||
135 | "b\n"+ | 135 | "b\n"+ | |
136 | "${PLIST.one}bin/program\n"+ // Conditionals are ignored while sorting | 136 | "${PLIST.one}bin/program\n"+ // Conditionals are ignored while sorting | |
137 | "${PKGMANDIR}/man1/program.1\n"+ // Stays below the previous line | 137 | "${PKGMANDIR}/man1/program.1\n"+ // Stays below the previous line | |
138 | "${PLIST.two}bin/program2\n"+ | 138 | "${PLIST.two}bin/program2\n"+ | |
139 | "ddd\n"+ | 139 | "ddd\n"+ | |
140 | "@exec echo \"after ddd\"\n"+ // Stays below the previous line | 140 | "@exec echo \"after ddd\"\n"+ // Stays below the previous line | |
141 | "lib/after.la\n"+ | 141 | "lib/after.la\n"+ | |
142 | "@exec echo \"after lib/after.la\"\n"+ | 142 | "@exec echo \"after lib/after.la\"\n"+ | |
143 | "lib/before.la\n"+ | 143 | "lib/before.la\n"+ | |
144 | "sbin/program\n") | 144 | "sbin/program\n") | |
145 | } | 145 | } | |
146 | 146 | |||
147 | func (s *Suite) TestPlistChecker_checkpathShare_Desktop(c *check.C) { | 147 | func (s *Suite) TestPlistChecker_checkpathShare_Desktop(c *check.C) { | |
148 | // Disabled due to PR 46570, item "10. It should stop". | |||
149 | return | |||
150 | ||||
148 | s.UseCommandLine(c, "-Wextra") | 151 | s.UseCommandLine(c, "-Wextra") | |
149 | G.Pkg = NewPackage("category/pkgpath") | 152 | G.Pkg = NewPackage("category/pkgpath") | |
150 | 153 | |||
151 | ChecklinesPlist(s.NewLines("PLIST", | 154 | ChecklinesPlist(s.NewLines("PLIST", | |
152 | "@comment $"+"NetBSD$", | 155 | "@comment $"+"NetBSD$", | |
153 | "share/applications/pkgbase.desktop")) | 156 | "share/applications/pkgbase.desktop")) | |
154 | 157 | |||
155 | c.Check(s.Output(), equals, "WARN: PLIST:2: Packages that install a .desktop entry should .include \"../../sysutils/desktop-file-utils/desktopdb.mk\".\n") | 158 | c.Check(s.Output(), equals, "WARN: PLIST:2: Packages that install a .desktop entry should .include \"../../sysutils/desktop-file-utils/desktopdb.mk\".\n") | |
156 | } | 159 | } | |
157 | 160 | |||
158 | func (s *Suite) TestPlistChecker_checkpathMan_gz(c *check.C) { | 161 | func (s *Suite) TestPlistChecker_checkpathMan_gz(c *check.C) { | |
159 | G.Pkg = NewPackage("category/pkgbase") | 162 | G.Pkg = NewPackage("category/pkgbase") | |
160 | 163 |
@@ -10,32 +10,32 @@ type SubstContext struct { | @@ -10,32 +10,32 @@ type SubstContext struct { | |||
10 | sed []string | 10 | sed []string | |
11 | vars []string | 11 | vars []string | |
12 | filterCmd string | 12 | filterCmd string | |
13 | } | 13 | } | |
14 | 14 | |||
15 | func (ctx *SubstContext) Varassign(mkline *MkLine) { | 15 | func (ctx *SubstContext) Varassign(mkline *MkLine) { | |
16 | if !G.opts.WarnExtra { | 16 | if !G.opts.WarnExtra { | |
17 | return | 17 | return | |
18 | } | 18 | } | |
19 | 19 | |||
20 | varname := mkline.Varname() | 20 | varname := mkline.Varname() | |
21 | op := mkline.Op() | 21 | op := mkline.Op() | |
22 | value := mkline.Value() | 22 | value := mkline.Value() | |
23 | if varname == "SUBST_CLASSES" { | 23 | if varname == "SUBST_CLASSES" || hasPrefix(varname, "SUBST_CLASSES.") { | |
24 | classes := splitOnSpace(value) | 24 | classes := splitOnSpace(value) | |
25 | if len(classes) > 1 { | 25 | if len(classes) > 1 { | |
26 | mkline.Warn0("Please add only one class at a time to SUBST_CLASSES.") | 26 | mkline.Warn0("Please add only one class at a time to SUBST_CLASSES.") | |
27 | } | 27 | } | |
28 | if ctx.id != "" { | 28 | if ctx.id != "" && ctx.id != classes[0] { | |
29 | mkline.Warn0("SUBST_CLASSES should only appear once in a SUBST block.") | 29 | mkline.Warn0("SUBST_CLASSES should only appear once in a SUBST block.") | |
30 | } | 30 | } | |
31 | ctx.id = classes[0] | 31 | ctx.id = classes[0] | |
32 | return | 32 | return | |
33 | } | 33 | } | |
34 | 34 | |||
35 | m, varbase, varparam := match2(varname, `^(SUBST_(?:STAGE|MESSAGE|FILES|SED|VARS|FILTER_CMD))\.([\-\w_]+)$`) | 35 | m, varbase, varparam := match2(varname, `^(SUBST_(?:STAGE|MESSAGE|FILES|SED|VARS|FILTER_CMD))\.([\-\w_]+)$`) | |
36 | if !m { | 36 | if !m { | |
37 | if ctx.id != "" { | 37 | if ctx.id != "" { | |
38 | mkline.Warn1("Foreign variable %q in SUBST block.", varname) | 38 | mkline.Warn1("Foreign variable %q in SUBST block.", varname) | |
39 | } | 39 | } | |
40 | return | 40 | return | |
41 | } | 41 | } |
@@ -17,41 +17,36 @@ func (p *MkParser) MkTokens() []*MkToken | @@ -17,41 +17,36 @@ func (p *MkParser) MkTokens() []*MkToken | |||
17 | 17 | |||
18 | var tokens []*MkToken | 18 | var tokens []*MkToken | |
19 | for !p.EOF() { | 19 | for !p.EOF() { | |
20 | if repl.AdvanceStr("#") { | 20 | if repl.AdvanceStr("#") { | |
21 | repl.AdvanceRest() | 21 | repl.AdvanceRest() | |
22 | } | 22 | } | |
23 | 23 | |||
24 | mark := repl.Mark() | 24 | mark := repl.Mark() | |
25 | if varuse := p.VarUse(); varuse != nil { | 25 | if varuse := p.VarUse(); varuse != nil { | |
26 | tokens = append(tokens, &MkToken{Text: repl.Since(mark), Varuse: varuse}) | 26 | tokens = append(tokens, &MkToken{Text: repl.Since(mark), Varuse: varuse}) | |
27 | continue | 27 | continue | |
28 | } | 28 | } | |
29 | 29 | |||
30 | needsReplace := false | |||
31 | again: | 30 | again: | |
32 | dollar := strings.IndexByte(repl.rest, '$') | 31 | dollar := strings.IndexByte(repl.rest, '$') | |
33 | if dollar == -1 { | 32 | if dollar == -1 { | |
34 | dollar = len(repl.rest) | 33 | dollar = len(repl.rest) | |
35 | } | 34 | } | |
36 | repl.Skip(dollar) | 35 | repl.Skip(dollar) | |
37 | if repl.AdvanceStr("$$") { | 36 | if repl.AdvanceStr("$$") { | |
38 | needsReplace = true | |||
39 | goto again | 37 | goto again | |
40 | } | 38 | } | |
41 | text := repl.Since(mark) | 39 | text := repl.Since(mark) | |
42 | if needsReplace { | |||
43 | text = strings.Replace(text, "$$", "$", -1) | |||
44 | } | |||
45 | if text != "" { | 40 | if text != "" { | |
46 | tokens = append(tokens, &MkToken{Text: text}) | 41 | tokens = append(tokens, &MkToken{Text: text}) | |
47 | continue | 42 | continue | |
48 | } | 43 | } | |
49 | 44 | |||
50 | break | 45 | break | |
51 | } | 46 | } | |
52 | return tokens | 47 | return tokens | |
53 | } | 48 | } | |
54 | 49 | |||
55 | func (p *MkParser) VarUse() *MkVarUse { | 50 | func (p *MkParser) VarUse() *MkVarUse { | |
56 | repl := p.repl | 51 | repl := p.repl | |
57 | 52 | |||
@@ -81,27 +76,27 @@ func (p *MkParser) VarUse() *MkVarUse { | @@ -81,27 +76,27 @@ func (p *MkParser) VarUse() *MkVarUse { | |||
81 | if repl.AdvanceStr(closing) { | 76 | if repl.AdvanceStr(closing) { | |
82 | return &MkVarUse{varexpr, modifiers} | 77 | return &MkVarUse{varexpr, modifiers} | |
83 | } | 78 | } | |
84 | } | 79 | } | |
85 | repl.Reset(mark) | 80 | repl.Reset(mark) | |
86 | } | 81 | } | |
87 | 82 | |||
88 | if repl.AdvanceStr("$@") { | 83 | if repl.AdvanceStr("$@") { | |
89 | return &MkVarUse{"@", nil} | 84 | return &MkVarUse{"@", nil} | |
90 | } | 85 | } | |
91 | if repl.AdvanceStr("$<") { | 86 | if repl.AdvanceStr("$<") { | |
92 | return &MkVarUse{"<", nil} | 87 | return &MkVarUse{"<", nil} | |
93 | } | 88 | } | |
94 | if repl.AdvanceRegexp(`^\$(\w)`) { | 89 | if repl.PeekByte() == '$' && repl.AdvanceRegexp(`^\$(\w)`) { | |
95 | varname := repl.m[1] | 90 | varname := repl.m[1] | |
96 | if p.EmitWarnings { | 91 | if p.EmitWarnings { | |
97 | p.Line.Warn1("$%[1]s is ambiguous. Use ${%[1]s} if you mean a Makefile variable or $$%[1]s if you mean a shell variable.", varname) | 92 | p.Line.Warn1("$%[1]s is ambiguous. Use ${%[1]s} if you mean a Makefile variable or $$%[1]s if you mean a shell variable.", varname) | |
98 | } | 93 | } | |
99 | return &MkVarUse{varname, nil} | 94 | return &MkVarUse{varname, nil} | |
100 | } | 95 | } | |
101 | return nil | 96 | return nil | |
102 | } | 97 | } | |
103 | 98 | |||
104 | func (p *MkParser) VarUseModifiers(varname, closing string) []string { | 99 | func (p *MkParser) VarUseModifiers(varname, closing string) []string { | |
105 | repl := p.repl | 100 | repl := p.repl | |
106 | 101 | |||
107 | var modifiers []string | 102 | var modifiers []string |
@@ -27,27 +27,27 @@ func (s *Suite) Test_MkParser_MkTokens(c | @@ -27,27 +27,27 @@ func (s *Suite) Test_MkParser_MkTokens(c | |||
27 | for _, modifier := range modifiers { | 27 | for _, modifier := range modifiers { | |
28 | text += ":" + modifier | 28 | text += ":" + modifier | |
29 | } | 29 | } | |
30 | text += "}" | 30 | text += "}" | |
31 | return &MkToken{Text: text, Varuse: &MkVarUse{varname: varname, modifiers: modifiers}} | 31 | return &MkToken{Text: text, Varuse: &MkVarUse{varname: varname, modifiers: modifiers}} | |
32 | } | 32 | } | |
33 | varuseText := func(text, varname string, modifiers ...string) *MkToken { | 33 | varuseText := func(text, varname string, modifiers ...string) *MkToken { | |
34 | return &MkToken{Text: text, Varuse: &MkVarUse{varname: varname, modifiers: modifiers}} | 34 | return &MkToken{Text: text, Varuse: &MkVarUse{varname: varname, modifiers: modifiers}} | |
35 | } | 35 | } | |
36 | 36 | |||
37 | check("literal", literal("literal")) | 37 | check("literal", literal("literal")) | |
38 | check("\\/share\\/ { print \"share directory\" }", literal("\\/share\\/ { print \"share directory\" }")) | 38 | check("\\/share\\/ { print \"share directory\" }", literal("\\/share\\/ { print \"share directory\" }")) | |
39 | check("find . -name \\*.orig -o -name \\*.pre", literal("find . -name \\*.orig -o -name \\*.pre")) | 39 | check("find . -name \\*.orig -o -name \\*.pre", literal("find . -name \\*.orig -o -name \\*.pre")) | |
40 | check("-e 's|\\$${EC2_HOME.*}|EC2_HOME}|g'", literal("-e 's|\\${EC2_HOME.*}|EC2_HOME}|g'")) | 40 | check("-e 's|\\$${EC2_HOME.*}|EC2_HOME}|g'", literal("-e 's|\\$${EC2_HOME.*}|EC2_HOME}|g'")) | |
41 | 41 | |||
42 | check("${VARIABLE}", varuse("VARIABLE")) | 42 | check("${VARIABLE}", varuse("VARIABLE")) | |
43 | check("${VARIABLE.param}", varuse("VARIABLE.param")) | 43 | check("${VARIABLE.param}", varuse("VARIABLE.param")) | |
44 | check("${VARIABLE.${param}}", varuse("VARIABLE.${param}")) | 44 | check("${VARIABLE.${param}}", varuse("VARIABLE.${param}")) | |
45 | check("${VARIABLE.hicolor-icon-theme}", varuse("VARIABLE.hicolor-icon-theme")) | 45 | check("${VARIABLE.hicolor-icon-theme}", varuse("VARIABLE.hicolor-icon-theme")) | |
46 | check("${VARIABLE.gtk+extra}", varuse("VARIABLE.gtk+extra")) | 46 | check("${VARIABLE.gtk+extra}", varuse("VARIABLE.gtk+extra")) | |
47 | check("${VARIABLE:S/old/new/}", varuse("VARIABLE", "S/old/new/")) | 47 | check("${VARIABLE:S/old/new/}", varuse("VARIABLE", "S/old/new/")) | |
48 | check("${GNUSTEP_LFLAGS:S/-L//g}", varuse("GNUSTEP_LFLAGS", "S/-L//g")) | 48 | check("${GNUSTEP_LFLAGS:S/-L//g}", varuse("GNUSTEP_LFLAGS", "S/-L//g")) | |
49 | check("${SUSE_VERSION:S/.//}", varuse("SUSE_VERSION", "S/.//")) | 49 | check("${SUSE_VERSION:S/.//}", varuse("SUSE_VERSION", "S/.//")) | |
50 | check("${MASTER_SITE_GNOME:=sources/alacarte/0.13/}", varuse("MASTER_SITE_GNOME", "=sources/alacarte/0.13/")) | 50 | check("${MASTER_SITE_GNOME:=sources/alacarte/0.13/}", varuse("MASTER_SITE_GNOME", "=sources/alacarte/0.13/")) | |
51 | check("${INCLUDE_DIRS:H:T}", varuse("INCLUDE_DIRS", "H", "T")) | 51 | check("${INCLUDE_DIRS:H:T}", varuse("INCLUDE_DIRS", "H", "T")) | |
52 | check("${A.${B.${C.${D}}}}", varuse("A.${B.${C.${D}}}")) | 52 | check("${A.${B.${C.${D}}}}", varuse("A.${B.${C.${D}}}")) | |
53 | check("${RUBY_VERSION:C/([0-9]+)\\.([0-9]+)\\.([0-9]+)/\\1/}", varuse("RUBY_VERSION", "C/([0-9]+)\\.([0-9]+)\\.([0-9]+)/\\1/")) | 53 | check("${RUBY_VERSION:C/([0-9]+)\\.([0-9]+)\\.([0-9]+)/\\1/}", varuse("RUBY_VERSION", "C/([0-9]+)\\.([0-9]+)\\.([0-9]+)/\\1/")) |
@@ -1,655 +1,224 @@ | @@ -1,655 +1,224 @@ | |||
1 | package main | 1 | package main | |
2 | 2 | |||
3 | import ( | 3 | import ( | |
4 | "fmt" | 4 | "fmt" | |
5 | "strconv" | 5 | "strconv" | |
6 | ) | 6 | ) | |
7 | 7 | |||
8 | type MkShParser struct { | 8 | func parseShellProgram(line *Line, program string) (list *MkShList, err error) { | |
9 | tok *ShTokenizer | 9 | if G.opts.Debug { | |
10 | curr *ShToken | 10 | defer tracecall(program)() | |
11 | } | |||
12 | ||||
13 | func NewMkShParser(line *Line, text string, emitWarnings bool) *MkShParser { | |||
14 | shp := NewShTokenizer(line, text, emitWarnings) | |||
15 | return &MkShParser{shp, nil} | |||
16 | } | |||
17 | ||||
18 | func (p *MkShParser) Program() (retval *MkShList) { | |||
19 | defer p.trace(&retval)() | |||
20 | ||||
21 | list := p.List() | |||
22 | if list == nil { | |||
23 | return nil | |||
24 | } | |||
25 | separator := p.Separator() | |||
26 | if separator == nil { | |||
27 | return list | |||
28 | } | |||
29 | return &MkShList{list.AndOrs, append(list.Separators, *separator)} | |||
30 | } | |||
31 | ||||
32 | // ::= AndOr (SeparatorOp AndOr)* | |||
33 | func (p *MkShParser) List() (retval *MkShList) { | |||
34 | defer p.trace(&retval)() | |||
35 | ok := false | |||
36 | defer p.rollback(&ok)() | |||
37 | ||||
38 | var andors []*MkShAndOr | |||
39 | var seps []MkShSeparator | |||
40 | ||||
41 | if andor := p.AndOr(); andor != nil { | |||
42 | andors = append(andors, andor) | |||
43 | } else { | |||
44 | return nil | |||
45 | } | |||
46 | ||||
47 | next: | |||
48 | mark := p.mark() | |||
49 | if sep := p.SeparatorOp(); sep != nil { | |||
50 | if andor := p.AndOr(); andor != nil { | |||
51 | andors = append(andors, andor) | |||
52 | seps = append(seps, *sep) | |||
53 | goto next | |||
54 | } | |||
55 | } | |||
56 | p.reset(mark) | |||
57 | ||||
58 | ok = true | |||
59 | return &MkShList{andors, seps} | |||
60 | } | |||
61 | ||||
62 | func (p *MkShParser) AndOr() (retval *MkShAndOr) { | |||
63 | defer p.trace(&retval)() | |||
64 | ok := false | |||
65 | defer p.rollback(&ok) | |||
66 | ||||
67 | var pipes []*MkShPipeline | |||
68 | var ops []string | |||
69 | nextpipe: | |||
70 | if pipe := p.Pipeline(); pipe != nil { | |||
71 | pipes = append(pipes, pipe) | |||
72 | switch op := p.peekText(); op { | |||
73 | case "&&", "||": | |||
74 | p.skip() | |||
75 | p.Linebreak() | |||
76 | ops = append(ops, op) | |||
77 | goto nextpipe | |||
78 | } | |||
79 | } | |||
80 | ||||
81 | if len(pipes) == len(ops) { | |||
82 | return nil | |||
83 | } | |||
84 | ok = true | |||
85 | return &MkShAndOr{pipes, ops} | |||
86 | } | |||
87 | ||||
88 | // ::= Command (msttPipe Linebreak Command)* | |||
89 | func (p *MkShParser) Pipeline() (retval *MkShPipeline) { | |||
90 | defer p.trace(&retval)() | |||
91 | ok := false | |||
92 | defer p.rollback(&ok)() | |||
93 | ||||
94 | bang := p.eat("!") | |||
95 | var cmds []*MkShCommand | |||
96 | nextcmd: | |||
97 | cmd := p.Command() | |||
98 | if cmd == nil { | |||
99 | return nil | |||
100 | } | |||
101 | cmds = append(cmds, cmd) | |||
102 | if p.eat("|") { | |||
103 | p.Linebreak() | |||
104 | goto nextcmd | |||
105 | } | |||
106 | ok = true | |||
107 | return &MkShPipeline{bang, cmds} | |||
108 | } | |||
109 | ||||
110 | func (p *MkShParser) Command() (retval *MkShCommand) { | |||
111 | defer p.trace(&retval)() | |||
112 | ||||
113 | if simple := p.SimpleCommand(); simple != nil { | |||
114 | return &MkShCommand{Simple: simple} | |||
115 | } | |||
116 | if compound := p.CompoundCommand(); compound != nil { | |||
117 | redirects := p.RedirectList() | |||
118 | return &MkShCommand{Compound: compound, Redirects: redirects} | |||
119 | } | |||
120 | if funcdef := p.FunctionDefinition(); funcdef != nil { | |||
121 | return &MkShCommand{FuncDef: funcdef} | |||
122 | } | |||
123 | return nil | |||
124 | } | |||
125 | ||||
126 | func (p *MkShParser) CompoundCommand() (retval *MkShCompoundCommand) { | |||
127 | defer p.trace(&retval)() | |||
128 | ||||
129 | if brace := p.BraceGroup(); brace != nil { | |||
130 | return &MkShCompoundCommand{Brace: brace} | |||
131 | } | |||
132 | if subshell := p.Subshell(); subshell != nil { | |||
133 | return &MkShCompoundCommand{Subshell: subshell} | |||
134 | } | |||
135 | if forclause := p.ForClause(); forclause != nil { | |||
136 | return &MkShCompoundCommand{For: forclause} | |||
137 | } | |||
138 | if caseclause := p.CaseClause(); caseclause != nil { | |||
139 | return &MkShCompoundCommand{Case: caseclause} | |||
140 | } | |||
141 | if ifclause := p.IfClause(); ifclause != nil { | |||
142 | return &MkShCompoundCommand{If: ifclause} | |||
143 | } | |||
144 | if whileclause := p.WhileClause(); whileclause != nil { | |||
145 | return &MkShCompoundCommand{While: whileclause} | |||
146 | } | |||
147 | if untilclause := p.UntilClause(); untilclause != nil { | |||
148 | return &MkShCompoundCommand{Until: untilclause} | |||
149 | } | |||
150 | return nil | |||
151 | } | |||
152 | ||||
153 | func (p *MkShParser) Subshell() (retval *MkShList) { | |||
154 | defer p.trace(&retval)() | |||
155 | ok := false | |||
156 | defer p.rollback(&ok)() | |||
157 | ||||
158 | if !p.eat("(") { | |||
159 | return nil | |||
160 | } | |||
161 | list := p.CompoundList() | |||
162 | if list == nil { | |||
163 | return nil | |||
164 | } | |||
165 | if !p.eat(")") { | |||
166 | return nil | |||
167 | } | |||
168 | ok = true | |||
169 | return list | |||
170 | } | |||
171 | ||||
172 | // ::= Newline* AndOr (Separator AndOr)* Separator? | |||
173 | func (p *MkShParser) CompoundList() (retval *MkShList) { | |||
174 | defer p.trace(&retval)() | |||
175 | ok := false | |||
176 | defer p.rollback(&ok)() | |||
177 | ||||
178 | p.Linebreak() | |||
179 | var andors []*MkShAndOr | |||
180 | var separators []MkShSeparator | |||
181 | nextandor: | |||
182 | if andor := p.AndOr(); andor != nil { | |||
183 | andors = append(andors, andor) | |||
184 | if sep := p.Separator(); sep != nil { | |||
185 | separators = append(separators, *sep) | |||
186 | goto nextandor | |||
187 | } | |||
188 | } | |||
189 | if len(andors) == 0 { | |||
190 | return nil | |||
191 | } | |||
192 | ok = true | |||
193 | return &MkShList{andors, separators} | |||
194 | } | |||
195 | ||||
196 | // ::= "for" msttWORD Linebreak DoGroup | |||
197 | // ::= "for" msttWORD Linebreak "in" SequentialSep DoGroup | |||
198 | // ::= "for" msttWORD Linebreak "in" Wordlist SequentialSep DoGroup | |||
199 | func (p *MkShParser) ForClause() (retval *MkShForClause) { | |||
200 | defer p.trace(&retval)() | |||
201 | ok := false | |||
202 | defer p.rollback(&ok)() | |||
203 | ||||
204 | if !p.eat("for") { | |||
205 | return nil | |||
206 | } | |||
207 | varword := p.Word(false) | |||
208 | if varword == nil || !matches(varword.MkText, `^[A-Z_a-z][0-9A-Za-z]*`) { | |||
209 | return nil | |||
210 | } | |||
211 | varname := varword.MkText | |||
212 | ||||
213 | var values []*ShToken | |||
214 | if p.eat("in") { | |||
215 | values = p.Wordlist() | |||
216 | } else { | |||
217 | values = []*ShToken{NewShToken("\"$$@\"", | |||
218 | NewShAtom(shtWord, "\"", shqDquot), | |||
219 | NewShAtom(shtWord, "$$@", shqDquot), | |||
220 | NewShAtom(shtWord, "\"", shqPlain))} | |||
221 | } | |||
222 | if values == nil || !p.SequentialSep() { | |||
223 | return nil | |||
224 | } | |||
225 | ||||
226 | p.Linebreak() | |||
227 | body := p.DoGroup() | |||
228 | if body == nil { | |||
229 | return nil | |||
230 | } | |||
231 | ||||
232 | ok = true | |||
233 | return &MkShForClause{varname, values, body} | |||
234 | } | |||
235 | ||||
236 | func (p *MkShParser) Wordlist() (retval []*ShToken) { | |||
237 | defer p.trace(&retval)() | |||
238 | ||||
239 | var words []*ShToken | |||
240 | nextword: | |||
241 | word := p.Word(false) | |||
242 | if word != nil { | |||
243 | words = append(words, word) | |||
244 | goto nextword | |||
245 | } | |||
246 | return words | |||
247 | } | |||
248 | ||||
249 | // ::= "case" msttWORD Linebreak "in" Linebreak CaseItem* "esac" | |||
250 | func (p *MkShParser) CaseClause() (retval *MkShCaseClause) { | |||
251 | defer p.trace(&retval)() | |||
252 | ok := false | |||
253 | defer p.rollback(&ok)() | |||
254 | ||||
255 | if !p.eat("case") { | |||
256 | return nil | |||
257 | } | |||
258 | ||||
259 | panic("CaseClause") | |||
260 | p.Linebreak() | |||
261 | p.CaseItem() | |||
262 | return nil | |||
263 | } | |||
264 | ||||
265 | // ::= "("? Pattern ")" (CompoundList | Linebreak) msttDSEMI? Linebreak | |||
266 | func (p *MkShParser) CaseItem() (retval *MkShCaseItem) { | |||
267 | defer p.trace(&retval)() | |||
268 | ||||
269 | panic("CaseItem") | |||
270 | p.Pattern() | |||
271 | p.Linebreak() | |||
272 | p.CompoundList() | |||
273 | return nil | |||
274 | } | |||
275 | ||||
276 | // ::= msttWORD | |||
277 | // ::= Pattern "|" msttWORD | |||
278 | func (p *MkShParser) Pattern() (retval []*ShToken) { | |||
279 | defer p.trace(&retval)() | |||
280 | ok := false | |||
281 | defer p.rollback(&ok)() | |||
282 | ||||
283 | var words []*ShToken | |||
284 | nextword: | |||
285 | word := p.Word(false) | |||
286 | if word == nil { | |||
287 | return nil | |||
288 | } | |||
289 | words = append(words, word) | |||
290 | if p.eat("|") { | |||
291 | goto nextword | |||
292 | ||||
293 | } | |||
294 | ok = true | |||
295 | return words | |||
296 | } | |||
297 | ||||
298 | func (p *MkShParser) IfClause() (retval *MkShIfClause) { | |||
299 | defer p.trace(&retval)() | |||
300 | ok := false | |||
301 | defer p.rollback(&ok)() | |||
302 | ||||
303 | var conds []*MkShList | |||
304 | var actions []*MkShList | |||
305 | var elseaction *MkShList | |||
306 | if !p.eat("if") { | |||
307 | return nil | |||
308 | } | |||
309 | ||||
310 | nextcond: | |||
311 | cond := p.CompoundList() | |||
312 | if cond == nil || !p.eat("then") { | |||
313 | return nil | |||
314 | } | |||
315 | action := p.CompoundList() | |||
316 | if action == nil { | |||
317 | return nil | |||
318 | } | |||
319 | conds = append(conds, cond) | |||
320 | actions = append(actions, action) | |||
321 | if p.eat("elif") { | |||
322 | goto nextcond | |||
323 | } | |||
324 | if p.eat("else") { | |||
325 | elseaction = p.CompoundList() | |||
326 | if elseaction == nil { | |||
327 | return nil | |||
328 | } | |||
329 | } | |||
330 | if !p.eat("fi") { | |||
331 | return nil | |||
332 | } | |||
333 | ok = true | |||
334 | return &MkShIfClause{conds, actions, elseaction} | |||
335 | } | |||
336 | ||||
337 | // ::= "while" CompoundList DoGroup | |||
338 | func (p *MkShParser) WhileClause() (retval *MkShLoopClause) { | |||
339 | defer p.trace(&retval)() | |||
340 | ok := false | |||
341 | defer p.rollback(&ok)() | |||
342 | ||||
343 | if !p.eat("while") { | |||
344 | return nil | |||
345 | } | |||
346 | ||||
347 | panic("WhileClause") | |||
348 | p.CompoundList() | |||
349 | p.DoGroup() | |||
350 | return nil | |||
351 | } | |||
352 | ||||
353 | // ::= "until" CompoundList DoGroup | |||
354 | func (p *MkShParser) UntilClause() (retval *MkShLoopClause) { | |||
355 | defer p.trace(&retval)() | |||
356 | ok := false | |||
357 | defer p.rollback(&ok)() | |||
358 | ||||
359 | if !p.eat("until") { | |||
360 | return nil | |||
361 | } | |||
362 | ||||
363 | panic("UntilClause") | |||
364 | p.CompoundList() | |||
365 | p.DoGroup() | |||
366 | return nil | |||
367 | } | |||
368 | ||||
369 | // ::= msttNAME "(" ")" Linebreak CompoundCommand Redirect* | |||
370 | func (p *MkShParser) FunctionDefinition() (retval *MkShFunctionDefinition) { | |||
371 | defer p.trace(&retval)() | |||
372 | ok := false | |||
373 | defer p.rollback(&ok)() | |||
374 | ||||
375 | funcname := p.Word(true) | |||
376 | if funcname == nil || !matches(funcname.MkText, `^[A-Z_a-z][0-9A-Z_a-z]*$`) { | |||
377 | return nil | |||
378 | } | |||
379 | ||||
380 | if !p.eat("(") || !p.eat(")") { | |||
381 | return nil | |||
382 | } | |||
383 | ||||
384 | p.Linebreak() | |||
385 | ||||
386 | body := p.CompoundCommand() | |||
387 | if body == nil { | |||
388 | return nil | |||
389 | } | |||
390 | ||||
391 | redirects := p.RedirectList() | |||
392 | ok = true | |||
393 | return &MkShFunctionDefinition{funcname.MkText, body, redirects} | |||
394 | } | |||
395 | ||||
396 | func (p *MkShParser) BraceGroup() (retval *MkShList) { | |||
397 | defer p.trace(&retval)() | |||
398 | ok := false | |||
399 | defer p.rollback(&ok)() | |||
400 | ||||
401 | if !p.eat("{") { | |||
402 | return nil | |||
403 | } | |||
404 | list := p.CompoundList() | |||
405 | if list == nil { | |||
406 | return nil | |||
407 | } | |||
408 | if !p.eat("}") { | |||
409 | return nil | |||
410 | } | |||
411 | ok = true | |||
412 | return list | |||
413 | } | |||
414 | ||||
415 | func (p *MkShParser) DoGroup() (retval *MkShList) { | |||
416 | defer p.trace(&retval)() | |||
417 | ok := false | |||
418 | defer p.rollback(&ok)() | |||
419 | ||||
420 | if !p.eat("do") { | |||
421 | return nil | |||
422 | } | |||
423 | list := p.CompoundList() | |||
424 | if list == nil { | |||
425 | return nil | |||
426 | } | |||
427 | if !p.eat("done") { | |||
428 | return nil | |||
429 | } | 11 | } | |
430 | ok = true | |||
431 | return list | |||
432 | } | |||
433 | 12 | |||
434 | func (p *MkShParser) SimpleCommand() (retval *MkShSimpleCommand) { | 13 | tokens, rest := splitIntoShellTokens(line, program) | |
435 | defer p.trace(&retval)() | 14 | lexer := NewShellLexer(tokens, rest) | |
436 | ok := false | 15 | parser := ­yParserImpl{} | |
437 | defer p.rollback(&ok)() | |||
438 | ||||
439 | var assignments []*ShToken | |||
440 | var name *ShToken | |||
441 | var args []*ShToken | |||
442 | var redirections []*MkShRedirection | |||
443 | first := true | |||
444 | seenName := false | |||
445 | nextword: | |||
446 | if word := p.Word(first); word != nil { | |||
447 | first = false | |||
448 | if !seenName && word.IsAssignment() { | |||
449 | assignments = append(assignments, word) | |||
450 | } else if !seenName { | |||
451 | name = word | |||
452 | seenName = true | |||
453 | } else { | |||
454 | args = append(args, word) | |||
455 | } | |||
456 | goto nextword | |||
457 | } | |||
458 | if len(assignments) == 0 && name == nil && len(args) == 0 && len(redirections) == 0 { | |||
459 | return nil | |||
460 | } | |||
461 | ok = true | |||
462 | return &MkShSimpleCommand{assignments, name, args, redirections} | |||
463 | } | |||
464 | 16 | |||
465 | func (p *MkShParser) RedirectList() (retval []*MkShRedirection) { | 17 | succeeded := parser.Parse(lexer) | |
466 | defer p.trace(&retval)() | |||
467 | 18 | |||
468 | nextredirect: | 19 | if succeeded == 0 && lexer.error == "" { | |
469 | if redirect := p.IoRedirect(); redirect != nil { | 20 | return lexer.result, nil | |
470 | retval = append(retval, redirect) | |||
471 | goto nextredirect | |||
472 | } | 21 | } | |
473 | return nil | 22 | return nil, &ParseError{append([]string{lexer.current}, lexer.remaining...)} | |
474 | } | 23 | } | |
475 | 24 | |||
476 | func (p *MkShParser) IoRedirect() (retval *MkShRedirection) { | 25 | type ParseError struct { | |
477 | defer p.trace(&retval)() | 26 | RemainingTokens []string | |
478 | ||||
479 | if m, redirect, fdstr, op := match3(p.peekText(), `^((\d*)\s*(<|<&|>|>&|>>|<>|>\||<<|<<-))`); m { | |||
480 | target := p.peekText()[len(redirect):] | |||
481 | _, _, _ = fdstr, op, target | |||
482 | ||||
483 | fd, err := strconv.ParseInt(fdstr, 10, 32) | |||
484 | if err != nil { | |||
485 | fd = -1 | |||
486 | } | |||
487 | p.skip() | |||
488 | targetToken := NewShTokenizer(p.tok.mkp.Line, target, false).ShToken() | |||
489 | return &MkShRedirection{int(fd), op, targetToken} | |||
490 | } | |||
491 | return nil | |||
492 | } | 27 | } | |
493 | 28 | |||
494 | func (p *MkShParser) NewlineList() (retval bool) { | 29 | func (e *ParseError) Error() string { | |
495 | defer p.trace(&retval)() | 30 | return fmt.Sprintf("parse error at %v", e.RemainingTokens) | |
496 | ||||
497 | ok := false | |||
498 | for p.eat("\n") { | |||
499 | ok = true | |||
500 | } | |||
501 | return ok | |||
502 | } | 31 | } | |
503 | 32 | |||
504 | func (p *MkShParser) Linebreak() { | 33 | type ShellLexer struct { | |
505 | for p.eat("\n") { | 34 | current string | |
506 | } | 35 | ioredirect string | |
36 | remaining []string | |||
37 | atCommandStart bool | |||
38 | sinceFor int | |||
39 | sinceCase int | |||
40 | error string | |||
41 | result *MkShList | |||
507 | } | 42 | } | |
508 | 43 | |||
509 | func (p *MkShParser) SeparatorOp() (retval *MkShSeparator) { | 44 | func NewShellLexer(tokens []string, rest string) *ShellLexer { | |
510 | defer p.trace(&retval)() | 45 | return &ShellLexer{ | |
511 | 46 | current: "", | ||
512 | if p.eat(";") { | 47 | ioredirect: "", | |
513 | op := MkShSeparator(";") | 48 | remaining: tokens, | |
514 | return &op | 49 | atCommandStart: true, | |
515 | } | 50 | error: rest} | |
516 | if p.eat("&") { | |||
517 | op := MkShSeparator("&") | |||
518 | return &op | |||
519 | } | |||
520 | return nil | |||
521 | } | 51 | } | |
522 | 52 | func (lex *ShellLexer) Lex(lval *shyySymType) (ttype int) { | ||
523 | func (p *MkShParser) Separator() (retval *MkShSeparator) { | 53 | if len(lex.remaining) == 0 { | |
524 | defer p.trace(&retval)() | 54 | return 0 | |
525 | ||||
526 | op := p.SeparatorOp() | |||
527 | if op == nil && p.eat("\n") { | |||
528 | sep := MkShSeparator('\n') | |||
529 | op = &sep | |||
530 | } | 55 | } | |
531 | if op != nil { | |||
532 | p.Linebreak() | |||
533 | } | |||
534 | return op | |||
535 | } | |||
536 | 56 | |||
537 | func (p *MkShParser) SequentialSep() (retval bool) { | 57 | if G.opts.Debug { | |
538 | defer p.trace(&retval)() | 58 | defer func() { | |
539 | 59 | tname := shyyTokname(shyyTok2[ttype-shyyPrivate]) | ||
540 | if p.peekText() == ";" { | 60 | switch ttype { | |
541 | p.skip() | 61 | case tkWORD, tkASSIGNMENT_WORD: | |
542 | p.Linebreak() | 62 | traceStep("lex %v %q", tname, lval.Word.MkText) | |
543 | return true | 63 | case tkIO_NUMBER: | |
544 | } else { | 64 | traceStep("lex %v %v", tname, lval.IONum) | |
545 | return p.NewlineList() | 65 | default: | |
546 | } | 66 | traceStep("lex %v", tname) | |
547 | } | |||
548 | ||||
549 | func (p *MkShParser) Word(cmdstart bool) (retval *ShToken) { | |||
550 | defer p.trace(&retval)() | |||
551 | ||||
552 | if token := p.peek(); token != nil && token.IsWord() { | |||
553 | if cmdstart { | |||
554 | switch token.MkText { | |||
555 | case "while", "until", "for", "do", "done", | |||
556 | "if", "then", "else", "elif", "fi", | |||
557 | "{", "}": | |||
558 | return nil | |||
559 | } | 67 | } | |
560 | } | 68 | }() | |
561 | p.skip() | |||
562 | return token | |||
563 | } | 69 | } | |
564 | return nil | |||
565 | } | |||
566 | ||||
567 | func (p *MkShParser) EOF() bool { | |||
568 | return p.peek() == nil | |||
569 | } | |||
570 | 70 | |||
571 | func (p *MkShParser) peek() *ShToken { | 71 | token := lex.ioredirect | |
572 | if p.curr == nil { | 72 | lex.ioredirect = "" | |
573 | nexttoken: | 73 | if token == "" { | |
574 | p.curr = p.tok.ShToken() | 74 | token = lex.remaining[0] | |
575 | if p.curr == nil && !p.tok.parser.EOF() { | 75 | lex.current = token | |
576 | p.tok.mkp.Line.Warnf("Pkglint tokenize error at " + p.tok.parser.Rest()) | 76 | lex.remaining = lex.remaining[1:] | |
577 | p.tok.mkp.Parser.repl.AdvanceRest() | 77 | } | |
578 | return nil | 78 | ||
579 | } | 79 | switch token { | |
580 | if p.curr != nil && hasPrefix(p.curr.MkText, "#") { | 80 | case ";": | |
581 | goto nexttoken | 81 | lex.atCommandStart = true | |
582 | } | 82 | return tkSEMI | |
83 | case ";;": | |||
84 | lex.atCommandStart = true | |||
85 | return tkSEMISEMI | |||
86 | case "\n": | |||
87 | lex.atCommandStart = true | |||
88 | return tkNEWLINE | |||
89 | case "&": | |||
90 | lex.atCommandStart = true | |||
91 | return tkBACKGROUND | |||
92 | case "|": | |||
93 | lex.atCommandStart = true | |||
94 | return tkPIPE | |||
95 | case "(": | |||
96 | lex.atCommandStart = true | |||
97 | return tkLPAREN | |||
98 | case ")": | |||
99 | lex.atCommandStart = true | |||
100 | return tkRPAREN | |||
101 | case "&&": | |||
102 | lex.atCommandStart = true | |||
103 | return tkAND | |||
104 | case "||": | |||
105 | lex.atCommandStart = true | |||
106 | return tkOR | |||
107 | case ">": | |||
108 | lex.atCommandStart = false | |||
109 | return tkGT | |||
110 | case ">&": | |||
111 | lex.atCommandStart = false | |||
112 | return tkGTAND | |||
113 | case "<": | |||
114 | lex.atCommandStart = false | |||
115 | return tkLT | |||
116 | case "<&": | |||
117 | lex.atCommandStart = false | |||
118 | return tkLTAND | |||
119 | case "<>": | |||
120 | lex.atCommandStart = false | |||
121 | return tkLTGT | |||
122 | case ">>": | |||
123 | lex.atCommandStart = false | |||
124 | return tkGTGT | |||
125 | case "<<": | |||
126 | lex.atCommandStart = false | |||
127 | return tkLTLT | |||
128 | case "<<-": | |||
129 | lex.atCommandStart = false | |||
130 | return tkLTLTDASH | |||
131 | case ">|": | |||
132 | lex.atCommandStart = false | |||
133 | return tkGTPIPE | |||
134 | } | |||
135 | ||||
136 | if m, fdstr, op := match2(token, `^(\d+)(<<-|<<|<>|<&|>>|>&|>\||<|>)$`); m { | |||
137 | fd, _ := strconv.Atoi(fdstr) | |||
138 | lval.IONum = fd | |||
139 | lex.ioredirect = op | |||
140 | return tkIO_NUMBER | |||
141 | } | |||
142 | ||||
143 | if lex.atCommandStart { | |||
144 | lex.sinceCase = -1 | |||
145 | lex.sinceFor = -1 | |||
146 | switch token { | |||
147 | case "if": | |||
148 | return tkIF | |||
149 | case "then": | |||
150 | return tkTHEN | |||
151 | case "elif": | |||
152 | return tkELIF | |||
153 | case "else": | |||
154 | return tkELSE | |||
155 | case "fi": | |||
156 | return tkFI | |||
157 | case "for": | |||
158 | lex.atCommandStart = false | |||
159 | lex.sinceFor = 0 | |||
160 | return tkFOR | |||
161 | case "while": | |||
162 | return tkWHILE | |||
163 | case "until": | |||
164 | return tkUNTIL | |||
165 | case "do": | |||
166 | return tkDO | |||
167 | case "done": | |||
168 | return tkDONE | |||
169 | case "in": | |||
170 | lex.atCommandStart = false | |||
171 | return tkIN | |||
172 | case "case": | |||
173 | lex.atCommandStart = false | |||
174 | lex.sinceCase = 0 | |||
175 | return tkCASE | |||
176 | case "{": | |||
177 | return tkLBRACE | |||
178 | case "}": | |||
179 | return tkRBRACE | |||
180 | case "!": | |||
181 | return tkEXCLAM | |||
182 | } | |||
183 | } | |||
184 | ||||
185 | if lex.sinceFor >= 0 { | |||
186 | lex.sinceFor++ | |||
187 | } | |||
188 | if lex.sinceCase >= 0 { | |||
189 | lex.sinceCase++ | |||
190 | } | |||
191 | ||||
192 | switch { | |||
193 | case matches(token, `^\d+$`) && len(lex.remaining) != 0 && matches(lex.remaining[0], `^[<>]`): | |||
194 | ttype = tkIO_NUMBER | |||
195 | lval.IONum, _ = strconv.Atoi(token) | |||
196 | case lex.sinceFor == 2 && token == "in": | |||
197 | ttype = tkIN | |||
198 | lex.atCommandStart = false | |||
199 | case lex.sinceFor == 2 && token == "do": | |||
200 | ttype = tkDO | |||
201 | lex.atCommandStart = true | |||
202 | case lex.sinceCase == 2 && token == "in": | |||
203 | ttype = tkIN | |||
204 | lex.atCommandStart = false | |||
205 | case (lex.atCommandStart || lex.sinceCase == 3) && token == "esac": | |||
206 | ttype = tkESAC | |||
207 | lex.atCommandStart = false | |||
208 | case lex.atCommandStart && matches(token, `^[A-Za-z_]\w*=`): | |||
209 | ttype = tkASSIGNMENT_WORD | |||
210 | p := NewShTokenizer(dummyLine, token, false) | |||
211 | lval.Word = p.ShToken() | |||
212 | default: | |||
213 | ttype = tkWORD | |||
214 | p := NewShTokenizer(dummyLine, token, false) | |||
215 | lval.Word = p.ShToken() | |||
216 | lex.atCommandStart = false | |||
583 | } | 217 | } | |
584 | //traceStep("MkShParser.peek %v rest=%q", p.curr, p.tok.mkp.repl.rest) | |||
585 | return p.curr | |||
586 | } | |||
587 | ||||
588 | func (p *MkShParser) peekText() string { | |||
589 | if next := p.peek(); next != nil { | |||
590 | return next.MkText | |||
591 | } | |||
592 | return "" | |||
593 | } | |||
594 | ||||
595 | func (p *MkShParser) skip() { | |||
596 | p.curr = nil | |||
597 | } | |||
598 | ||||
599 | func (p *MkShParser) eat(s string) bool { | |||
600 | if p.peek() == nil { | |||
601 | return false | |||
602 | } | |||
603 | if p.peek().MkText == s { | |||
604 | p.skip() | |||
605 | return true | |||
606 | } | |||
607 | return false | |||
608 | } | |||
609 | ||||
610 | func (p *MkShParser) rollback(pok *bool) func() { | |||
611 | mark := p.mark() | |||
612 | return func() { | |||
613 | if !*pok { | |||
614 | p.reset(mark) | |||
615 | } | |||
616 | } | |||
617 | } | |||
618 | ||||
619 | func (p *MkShParser) trace(retval interface{}) func() { | |||
620 | if G.opts.Debug { | |||
621 | return tracecallInternal(p.peek(), p.restref(), "=>", ref(retval)) | |||
622 | } else { | |||
623 | return func() {} | |||
624 | } | |||
625 | } | |||
626 | ||||
627 | func (p *MkShParser) mark() MkShParserMark { | |||
628 | return MkShParserMark{p.tok.parser.repl.Mark(), p.curr} | |||
629 | } | |||
630 | ||||
631 | func (p *MkShParser) reset(mark MkShParserMark) { | |||
632 | p.tok.parser.repl.Reset(mark.rest) | |||
633 | p.curr = mark.curr | |||
634 | } | |||
635 | ||||
636 | func (p *MkShParser) restref() MkShParserRest { | |||
637 | return MkShParserRest{&p.tok.mkp.repl.rest} | |||
638 | } | |||
639 | ||||
640 | func (p *MkShParser) Rest() string { | |||
641 | return p.peekText() + p.tok.mkp.repl.AdvanceRest() | |||
642 | } | |||
643 | ||||
644 | type MkShParserMark struct { | |||
645 | rest PrefixReplacerMark | |||
646 | curr *ShToken | |||
647 | } | |||
648 | 218 | |||
649 | type MkShParserRest struct { | 219 | return ttype | |
650 | restref *string | |||
651 | } | 220 | } | |
652 | 221 | |||
653 | func (rest MkShParserRest) String() string { | 222 | func (lex *ShellLexer) Error(s string) { | |
654 | return fmt.Sprintf("rest=%q", *rest.restref) | 223 | lex.error = s | |
655 | } | 224 | } |
@@ -1,352 +1,595 @@ | @@ -1,352 +1,595 @@ | |||
1 | package main | 1 | package main | |
2 | 2 | |||
3 | import ( | 3 | import ( | |
4 | check "gopkg.in/check.v1" | 4 | "encoding/json" | |
5 | "gopkg.in/check.v1" | |||
6 | "strconv" | |||
5 | ) | 7 | ) | |
6 | 8 | |||
7 | func (s *Suite) Test_MkShParser_Program(c *check.C) { | 9 | type ShSuite struct { | |
8 | parse := func(cmd string, expected *MkShList) { | 10 | c *check.C | |
9 | p := NewMkShParser(dummyLine, cmd, false) | |||
10 | program := p.Program() | |||
11 | c.Check(program, deepEquals, expected) | |||
12 | c.Check(p.tok.parser.Rest(), equals, "") | |||
13 | c.Check(s.Output(), equals, "") | |||
14 | } | |||
15 | ||||
16 | if false { | |||
17 | parse(""+ | |||
18 | "\tcd ${WRKSRC} && ${FIND} ${${_list_}} -type f ! -name '*.orig' 2>/dev/null "+ | |||
19 | "| pax -rw -pm ${DESTDIR}${PREFIX}/${${_dir_}}", | |||
20 | NewMkShList()) | |||
21 | } | |||
22 | } | |||
23 | ||||
24 | func (s *Suite) Test_MkShParser_List(c *check.C) { | |||
25 | ||||
26 | } | |||
27 | ||||
28 | func (s *Suite) Test_MkShParser_AndOr(c *check.C) { | |||
29 | parse := func(cmd string, expected *MkShAndOr) { | |||
30 | p := NewMkShParser(dummyLine, cmd, false) | |||
31 | andor := p.AndOr() | |||
32 | c.Check(andor, deepEquals, expected) | |||
33 | c.Check(p.tok.parser.Rest(), equals, "") | |||
34 | c.Check(s.Output(), equals, "") | |||
35 | } | |||
36 | tester := &MkShTester{c} | |||
37 | ||||
38 | parse("simplecmd", | |||
39 | NewMkShAndOr(NewMkShPipeline(false, tester.ParseCommand("simplecmd")))) | |||
40 | ||||
41 | expected := NewMkShAndOr(NewMkShPipeline(false, tester.ParseCommand("simplecmd1"))) | |||
42 | expected.Add("&&", NewMkShPipeline(false, tester.ParseCommand("simplecmd2"))) | |||
43 | parse("simplecmd1 && simplecmd2", expected) | |||
44 | } | |||
45 | ||||
46 | func (s *Suite) Test_MkShParser_Pipeline(c *check.C) { | |||
47 | ||||
48 | } | |||
49 | ||||
50 | func (s *Suite) Test_MkShParser_Command(c *check.C) { | |||
51 | parse := func(cmd string, expected *MkShCommand) { | |||
52 | p := NewMkShParser(dummyLine, cmd, false) | |||
53 | command := p.Command() | |||
54 | c.Check(command, deepEquals, expected) | |||
55 | c.Check(p.tok.parser.Rest(), equals, "") | |||
56 | c.Check(s.Output(), equals, "") | |||
57 | } | |||
58 | tester := &MkShTester{c} | |||
59 | ||||
60 | parse("simple", | |||
61 | &MkShCommand{Simple: tester.ParseSimpleCommand("simple")}) | |||
62 | } | |||
63 | ||||
64 | func (s *Suite) Test_MkShParser_CompoundCommand(c *check.C) { | |||
65 | ||||
66 | } | 11 | } | |
67 | 12 | |||
68 | func (s *Suite) Test_MkShParser_Subshell(c *check.C) { | 13 | var _ = check.Suite(&ShSuite{}) | |
69 | ||||
70 | } | |||
71 | 14 | |||
72 | func (s *Suite) Test_MkShParser_CompoundList(c *check.C) { | 15 | func (s *ShSuite) Test_ShellParser_program(c *check.C) { | |
73 | parse := func(cmd string, expected *MkShList) { | 16 | b := s.init(c) | |
74 | p := NewMkShParser(dummyLine, cmd, false) | |||
75 | compoundList := p.CompoundList() | |||
76 | c.Check(compoundList, deepEquals, expected) | |||
77 | c.Check(p.tok.parser.Rest(), equals, "") | |||
78 | c.Check(s.Output(), equals, "") | |||
79 | } | |||
80 | tester := &MkShTester{c} | |||
81 | 17 | |||
82 | parse("simplecmd", | 18 | s.test("", | |
83 | NewMkShList().AddAndOr(NewMkShAndOr(NewMkShPipeline(false, tester.ParseCommand("simplecmd"))))) | 19 | b.List()) | |
84 | 20 | |||
85 | simplecmd1 := NewMkShPipeline(false, tester.ParseCommand("simplecmd1")) | 21 | s.test("echo ;", | |
86 | simplecmd2 := NewMkShPipeline(false, tester.ParseCommand("simplecmd2")) | 22 | b.List().AddCommand(b.SimpleCommand("echo")).AddSeparator(";")) | |
87 | expected := NewMkShList().AddAndOr(NewMkShAndOr(simplecmd1).Add("&&", simplecmd2)) | 23 | ||
88 | parse("simplecmd1 && simplecmd2", expected) | 24 | s.test("echo", | |
89 | } | 25 | b.List().AddCommand(b.SimpleCommand("echo"))) | |
90 | 26 | |||
91 | func (s *Suite) Test_MkShParser_ForClause(c *check.C) { | 27 | s.test(""+ | |
92 | parse := func(cmd string, expected *MkShForClause) { | 28 | "cd ${WRKSRC} && ${FIND} ${${_list_}} -type f ! -name '*.orig' 2 > /dev/null "+ | |
93 | p := NewMkShParser(dummyLine, cmd, false) | 29 | "| pax -rw -pm ${DESTDIR}${PREFIX}/${${_dir_}}", | |
94 | forclause := p.ForClause() | 30 | b.List().AddAndOr(b.AndOr( | |
95 | c.Check(forclause, deepEquals, expected) | 31 | b.Pipeline(false, b.SimpleCommand("cd", "${WRKSRC}"))).Add("&&", | |
96 | c.Check(p.tok.parser.Rest(), equals, "") | 32 | b.Pipeline(false, | |
97 | c.Check(s.Output(), equals, "") | 33 | b.SimpleCommand("${FIND}", "${${_list_}}", "-type", "f", "!", "-name", "'*.orig'", "2>/dev/null"), | |
34 | b.SimpleCommand("pax", "-rw", "-pm", "${DESTDIR}${PREFIX}/${${_dir_}}"))))) | |||
35 | ||||
36 | s.test("${CAT} ${PKGDIR}/PLIST | while read entry ; do : ; done", | |||
37 | b.List().AddAndOr(b.AndOr(b.Pipeline(false, | |||
38 | b.SimpleCommand("${CAT}", "${PKGDIR}/PLIST"), | |||
39 | b.While( | |||
40 | b.List().AddCommand(b.SimpleCommand("read", "entry")).AddSeparator(";"), | |||
41 | b.List().AddCommand(b.SimpleCommand(":")).AddSeparator(";")))))) | |||
42 | ||||
43 | s.test("while read entry ; do case \"$$entry\" in include/c-client/* ) ${INSTALL_DATA} $$src $$dest ; esac ; done", | |||
44 | b.List().AddCommand(b.While( | |||
45 | b.List().AddCommand(b.SimpleCommand("read", "entry")).AddSeparator(";"), | |||
46 | b.List().AddCommand(b.Case( | |||
47 | b.Token("\"$$entry\""), | |||
48 | b.CaseItem( | |||
49 | b.Words("include/c-client/*"), | |||
50 | b.List().AddCommand(b.SimpleCommand("${INSTALL_DATA}", "$$src", "$$dest")), | |||
51 | &SEP_SEMI))).AddSeparator(";")))) | |||
52 | ||||
53 | s.test("command | while condition ; do case selector in pattern ) : ; esac ; done", | |||
54 | b.List().AddAndOr(b.AndOr(b.Pipeline(false, | |||
55 | b.SimpleCommand("command"), | |||
56 | b.While( | |||
57 | b.List().AddCommand(b.SimpleCommand("condition")).AddSeparator(";"), | |||
58 | b.List().AddCommand(b.Case( | |||
59 | b.Token("selector"), | |||
60 | b.CaseItem( | |||
61 | b.Words("pattern"), | |||
62 | b.List().AddCommand(b.SimpleCommand(":")), | |||
63 | &SEP_SEMI))).AddSeparator(";")))))) | |||
64 | ||||
65 | s.test("command1 \n command2 \n command3", | |||
66 | b.List(). | |||
67 | AddCommand(b.SimpleCommand("command1")). | |||
68 | AddSeparator(SEP_NEWLINE). | |||
69 | AddCommand(b.SimpleCommand("command2")). | |||
70 | AddSeparator(SEP_NEWLINE). | |||
71 | AddCommand(b.SimpleCommand("command3"))) | |||
72 | ||||
73 | s.test("if condition; then action; else case selector in pattern) case-item-action ;; esac; fi", | |||
74 | b.List().AddCommand(b.If( | |||
75 | b.List().AddCommand(b.SimpleCommand("condition")).AddSeparator(";"), | |||
76 | b.List().AddCommand(b.SimpleCommand("action")).AddSeparator(";"), | |||
77 | b.List().AddCommand(b.Case( | |||
78 | b.Token("selector"), | |||
79 | b.CaseItem( | |||
80 | b.Words("pattern"), | |||
81 | b.List().AddCommand(b.SimpleCommand("case-item-action")), nil))).AddSeparator(";")))) | |||
82 | } | |||
83 | ||||
84 | func (s *ShSuite) Test_ShellParser_list(c *check.C) { | |||
85 | b := s.init(c) | |||
86 | ||||
87 | s.test("echo1 && echo2", | |||
88 | b.List().AddAndOr( | |||
89 | b.AndOr(b.Pipeline(false, b.SimpleCommand("echo1"))). | |||
90 | Add("&&", b.Pipeline(false, b.SimpleCommand("echo2"))))) | |||
91 | ||||
92 | s.test("echo1 ; echo2", | |||
93 | b.List(). | |||
94 | AddCommand(b.SimpleCommand("echo1")). | |||
95 | AddSeparator(";"). | |||
96 | AddCommand(b.SimpleCommand("echo2"))) | |||
97 | ||||
98 | s.test("echo1 ; echo2 &", | |||
99 | b.List(). | |||
100 | AddCommand(b.SimpleCommand("echo1")). | |||
101 | AddSeparator(";"). | |||
102 | AddCommand(b.SimpleCommand("echo2")). | |||
103 | AddSeparator("&")) | |||
104 | } | |||
105 | ||||
106 | func (s *ShSuite) Test_ShellParser_and_or(c *check.C) { | |||
107 | b := s.init(c) | |||
108 | ||||
109 | s.test("echo1 | echo2", | |||
110 | b.List().AddAndOr(b.AndOr(b.Pipeline(false, | |||
111 | b.SimpleCommand("echo1"), | |||
112 | b.SimpleCommand("echo2"))))) | |||
113 | ||||
114 | s.test("echo1 | echo2 && echo3 | echo4", | |||
115 | b.List().AddAndOr(b.AndOr( | |||
116 | b.Pipeline(false, | |||
117 | b.SimpleCommand("echo1"), | |||
118 | b.SimpleCommand("echo2")), | |||
119 | ).Add("&&", | |||
120 | b.Pipeline(false, | |||
121 | b.SimpleCommand("echo3"), | |||
122 | b.SimpleCommand("echo4"))))) | |||
123 | ||||
124 | s.test("echo1 | echo2 || ! echo3 | echo4", | |||
125 | b.List().AddAndOr(b.AndOr( | |||
126 | b.Pipeline(false, | |||
127 | b.SimpleCommand("echo1"), | |||
128 | b.SimpleCommand("echo2")), | |||
129 | ).Add("||", | |||
130 | b.Pipeline(true, | |||
131 | b.SimpleCommand("echo3"), | |||
132 | b.SimpleCommand("echo4"))))) | |||
133 | } | |||
134 | ||||
135 | func (s *ShSuite) Test_ShellParser_pipeline(c *check.C) { | |||
136 | b := s.init(c) | |||
137 | ||||
138 | s.test("command1 | command2", | |||
139 | b.List().AddAndOr(b.AndOr(b.Pipeline(false, | |||
140 | b.SimpleCommand("command1"), | |||
141 | b.SimpleCommand("command2"))))) | |||
142 | ||||
143 | s.test("! command1 | command2", | |||
144 | b.List().AddAndOr(b.AndOr(b.Pipeline(true, | |||
145 | b.SimpleCommand("command1"), | |||
146 | b.SimpleCommand("command2"))))) | |||
147 | } | |||
148 | ||||
149 | func (s *ShSuite) Test_ShellParser_pipe_sequence(c *check.C) { | |||
150 | b := s.init(c) | |||
151 | ||||
152 | s.test("command1 | if true ; then : ; fi", | |||
153 | b.List().AddAndOr(b.AndOr(b.Pipeline(false, | |||
154 | b.SimpleCommand("command1"), | |||
155 | b.If( | |||
156 | b.List().AddCommand(b.SimpleCommand("true")).AddSeparator(";"), | |||
157 | b.List().AddCommand(b.SimpleCommand(":")).AddSeparator(";")))))) | |||
158 | } | |||
159 | ||||
160 | func (s *ShSuite) Test_ShellParser_command(c *check.C) { | |||
161 | b := s.init(c) | |||
162 | ||||
163 | s.test("simple_command", | |||
164 | b.List().AddAndOr(b.AndOr(b.Pipeline(false, b.SimpleCommand("simple_command"))))) | |||
165 | ||||
166 | s.test("while 1 ; do 2 ; done", | |||
167 | b.List().AddAndOr(b.AndOr(b.Pipeline(false, | |||
168 | b.While( | |||
169 | b.List().AddCommand(b.SimpleCommand("1")).AddSeparator(";"), | |||
170 | b.List().AddCommand(b.SimpleCommand("2")).AddSeparator(";")))))) | |||
171 | ||||
172 | s.test("while 1 ; do 2 ; done 1 >& 2", | |||
173 | b.List().AddAndOr(b.AndOr(b.Pipeline(false, | |||
174 | b.While( | |||
175 | b.List().AddCommand(b.SimpleCommand("1")).AddSeparator(";"), | |||
176 | b.List().AddCommand(b.SimpleCommand("2")).AddSeparator(";"), | |||
177 | b.Redirection(1, ">&", "2")))))) | |||
178 | ||||
179 | s.test("func ( ) { echo hello ; } 2 >& 1", | |||
180 | b.List().AddCommand(b.Function( | |||
181 | "func", | |||
182 | b.Brace(b.List().AddCommand(b.SimpleCommand("echo", "hello")).AddSeparator(";")).Compound, | |||
183 | b.Redirection(2, ">&", "1")))) | |||
184 | } | |||
185 | ||||
186 | func (s *ShSuite) Test_ShellParser_compound_command(c *check.C) { | |||
187 | b := s.init(c) | |||
188 | ||||
189 | s.test("{ brace ; }", | |||
190 | b.List().AddCommand(b.Brace( | |||
191 | b.List().AddCommand(b.SimpleCommand("brace")).AddSeparator(";")))) | |||
192 | ||||
193 | s.test("( subshell )", | |||
194 | b.List().AddCommand(b.Subshell( | |||
195 | b.List().AddCommand(b.SimpleCommand("subshell"))))) | |||
196 | ||||
197 | s.test("for i in * ; do echo $i ; done", | |||
198 | b.List().AddCommand(b.For( | |||
199 | "i", | |||
200 | b.Words("*"), | |||
201 | b.List().AddCommand(b.SimpleCommand("echo", "$i")).AddSeparator(";")))) | |||
202 | ||||
203 | s.test("case $i in esac", | |||
204 | b.List().AddCommand(b.Case( | |||
205 | b.Token("$i")))) | |||
206 | ||||
207 | } | |||
208 | ||||
209 | func (s *ShSuite) Test_ShellParser_subshell(c *check.C) { | |||
210 | b := s.init(c) | |||
211 | ||||
212 | sub3 := b.Subshell(b.List().AddCommand(b.SimpleCommand("sub3"))) | |||
213 | sub2 := b.Subshell(b.List().AddCommand(sub3).AddSeparator(";").AddCommand(b.SimpleCommand("sub2"))) | |||
214 | sub1 := b.Subshell(b.List().AddCommand(sub2).AddSeparator(";").AddCommand(b.SimpleCommand("sub1"))) | |||
215 | s.test("( ( ( sub3 ) ; sub2 ) ; sub1 )", b.List().AddCommand(sub1)) | |||
216 | } | |||
217 | ||||
218 | func (s *ShSuite) Test_ShellParser_compound_list(c *check.C) { | |||
219 | b := s.init(c) | |||
220 | ||||
221 | s.test("( \n echo )", | |||
222 | b.List().AddCommand(b.Subshell( | |||
223 | b.List().AddCommand(b.SimpleCommand("echo"))))) | |||
224 | } | |||
225 | ||||
226 | func (s *ShSuite) Test_ShellParser_term(c *check.C) { | |||
227 | b := s.init(c) | |||
228 | ||||
229 | _ = b | |||
230 | } | |||
231 | ||||
232 | func (s *ShSuite) Test_ShellParser_for_clause(c *check.C) { | |||
233 | b := s.init(c) | |||
234 | ||||
235 | s.test("for var do echo $var ; done", | |||
236 | b.List().AddCommand(b.For( | |||
237 | "var", | |||
238 | b.Words("\"$$@\""), | |||
239 | b.List().AddCommand(b.SimpleCommand("echo", "$var")).AddSeparator(";")))) | |||
240 | ||||
241 | // Only linebreak is allowed, but not semicolon. | |||
242 | s.test("for var \n do echo $var ; done", | |||
243 | b.List().AddCommand(b.For( | |||
244 | "var", | |||
245 | b.Words("\"$$@\""), | |||
246 | b.List().AddCommand(b.SimpleCommand("echo", "$var")).AddSeparator(";")))) | |||
247 | ||||
248 | s.test("for var in a b c ; do echo $var ; done", | |||
249 | b.List().AddCommand(b.For( | |||
250 | "var", | |||
251 | b.Words("a", "b", "c"), | |||
252 | b.List().AddCommand(b.SimpleCommand("echo", "$var")).AddSeparator(";")))) | |||
253 | ||||
254 | s.test("for var \n \n \n in a b c ; do echo $var ; done", | |||
255 | b.List().AddCommand(b.For( | |||
256 | "var", | |||
257 | b.Words("a", "b", "c"), | |||
258 | b.List().AddCommand(b.SimpleCommand("echo", "$var")).AddSeparator(";")))) | |||
259 | ||||
260 | s.test("for var in in esac ; do echo $var ; done", | |||
261 | b.List().AddCommand(b.For( | |||
262 | "var", | |||
263 | b.Words("in", "esac"), | |||
264 | b.List().AddCommand(b.SimpleCommand("echo", "$var")).AddSeparator(";")))) | |||
265 | ||||
266 | // No semicolon necessary between the two “done”. | |||
267 | s.test("for i in 1; do for j in 1; do echo $$i$$j; done done", | |||
268 | b.List().AddCommand(b.For( | |||
269 | "i", | |||
270 | b.Words("1"), | |||
271 | b.List().AddCommand(b.For( | |||
272 | "j", | |||
273 | b.Words("1"), | |||
274 | b.List().AddCommand(b.SimpleCommand("echo", "$$i$$j")).AddSeparator(";")))))) | |||
275 | } | |||
276 | ||||
277 | func (s *ShSuite) Test_ShellParser_case_clause(c *check.C) { | |||
278 | b := s.init(c) | |||
279 | ||||
280 | s.test("case $var in esac", | |||
281 | b.List().AddCommand(b.Case(b.Token("$var")))) | |||
282 | ||||
283 | s.test("case selector in pattern) ;; pattern) esac", | |||
284 | b.List().AddCommand(b.Case( | |||
285 | b.Token("selector"), | |||
286 | b.CaseItem(b.Words("pattern"), b.List(), nil), | |||
287 | b.CaseItem(b.Words("pattern"), b.List(), nil)))) | |||
288 | ||||
289 | s.test("case $$i in *.c | *.h ) echo C ;; * ) echo Other ; esac", | |||
290 | b.List().AddCommand(b.Case( | |||
291 | b.Token("$$i"), | |||
292 | b.CaseItem(b.Words("*.c", "*.h"), b.List().AddCommand(b.SimpleCommand("echo", "C")), nil), | |||
293 | b.CaseItem(b.Words("*"), b.List().AddCommand(b.SimpleCommand("echo", "Other")), &SEP_SEMI)))) | |||
294 | ||||
295 | s.test("case $$i in *.c ) echo ; esac", | |||
296 | b.List().AddCommand(b.Case( | |||
297 | b.Token("$$i"), | |||
298 | b.CaseItem(b.Words("*.c"), b.List().AddCommand(b.SimpleCommand("echo")), &SEP_SEMI)))) | |||
299 | ||||
300 | s.test("case selector in pattern) case-item-action ; esac", | |||
301 | b.List().AddCommand(b.Case( | |||
302 | b.Token("selector"), | |||
303 | b.CaseItem( | |||
304 | b.Words("pattern"), | |||
305 | b.List().AddCommand(b.SimpleCommand("case-item-action")), &SEP_SEMI)))) | |||
306 | ||||
307 | s.test("case selector in pattern) case-item-action ;; esac", | |||
308 | b.List().AddCommand(b.Case( | |||
309 | b.Token("selector"), | |||
310 | b.CaseItem( | |||
311 | b.Words("pattern"), | |||
312 | b.List().AddCommand(b.SimpleCommand("case-item-action")), nil)))) | |||
313 | ||||
314 | } | |||
315 | ||||
316 | func (s *ShSuite) Test_ShellParser_if_clause(c *check.C) { | |||
317 | b := s.init(c) | |||
318 | ||||
319 | s.test( | |||
320 | "if true ; then echo yes ; else echo no ; fi", | |||
321 | b.List().AddCommand(b.If( | |||
322 | b.List().AddCommand(b.SimpleCommand("true")).AddSeparator(";"), | |||
323 | b.List().AddCommand(b.SimpleCommand("echo", "yes")).AddSeparator(";"), | |||
324 | b.List().AddCommand(b.SimpleCommand("echo", "no")).AddSeparator(";")))) | |||
325 | ||||
326 | // No semicolon necessary between the two “fi”. | |||
327 | s.test("if cond1; then if cond2; then action; fi fi", | |||
328 | b.List().AddCommand(b.If( | |||
329 | b.List().AddCommand(b.SimpleCommand("cond1")).AddSeparator(";"), | |||
330 | b.List().AddCommand(b.If( | |||
331 | b.List().AddCommand(b.SimpleCommand("cond2")).AddSeparator(";"), | |||
332 | b.List().AddCommand(b.SimpleCommand("action")).AddSeparator(";")))))) | |||
333 | } | |||
334 | ||||
335 | func (s *ShSuite) Test_ShellParser_while_clause(c *check.C) { | |||
336 | b := s.init(c) | |||
337 | ||||
338 | s.test("while condition ; do action ; done", | |||
339 | b.List().AddCommand(b.While( | |||
340 | b.List().AddCommand(b.SimpleCommand("condition")).AddSeparator(";"), | |||
341 | b.List().AddCommand(b.SimpleCommand("action")).AddSeparator(";")))) | |||
342 | } | |||
343 | ||||
344 | func (s *ShSuite) Test_ShellParser_until_clause(c *check.C) { | |||
345 | b := s.init(c) | |||
346 | ||||
347 | s.test("until condition ; do action ; done", | |||
348 | b.List().AddCommand(b.Until( | |||
349 | b.List().AddCommand(b.SimpleCommand("condition")).AddSeparator(";"), | |||
350 | b.List().AddCommand(b.SimpleCommand("action")).AddSeparator(";")))) | |||
351 | } | |||
352 | ||||
353 | func (s *ShSuite) Test_ShellParser_function_definition(c *check.C) { | |||
354 | b := s.init(c) | |||
355 | ||||
356 | _ = b | |||
357 | } | |||
358 | ||||
359 | func (s *ShSuite) Test_ShellParser_brace_group(c *check.C) { | |||
360 | b := s.init(c) | |||
361 | ||||
362 | // No semicolon necessary after the closing brace. | |||
363 | s.test("if true; then { echo yes; } fi", | |||
364 | b.List().AddCommand(b.If( | |||
365 | b.List().AddCommand(b.SimpleCommand("true")).AddSeparator(";"), | |||
366 | b.List().AddCommand(b.Brace( | |||
367 | b.List().AddCommand(b.SimpleCommand("echo", "yes")).AddSeparator(";")))))) | |||
368 | } | |||
369 | ||||
370 | func (s *ShSuite) Test_ShellParser_simple_command(c *check.C) { | |||
371 | b := s.init(c) | |||
372 | ||||
373 | s.test( | |||
374 | "echo hello, world", | |||
375 | b.List().AddCommand(b.SimpleCommand("echo", "hello,", "world"))) | |||
376 | ||||
377 | s.test("echo ${PKGNAME:Q}", | |||
378 | b.List().AddCommand(b.SimpleCommand("echo", "${PKGNAME:Q}"))) | |||
379 | ||||
380 | s.test("${ECHO} \"Double-quoted\" 'Single-quoted'", | |||
381 | b.List().AddCommand(b.SimpleCommand("${ECHO}", "\"Double-quoted\"", "'Single-quoted'"))) | |||
382 | ||||
383 | s.test("`cat plain` \"`cat double`\" '`cat single`'", | |||
384 | b.List().AddCommand(b.SimpleCommand("`cat plain`", "\"`cat double`\"", "'`cat single`'"))) | |||
385 | ||||
386 | s.test("`\"one word\"`", | |||
387 | b.List().AddCommand(b.SimpleCommand("`\"one word\"`"))) | |||
388 | ||||
389 | s.test("PAGES=\"`ls -1 | ${SED} -e 's,3qt$$,3,'`\"", | |||
390 | b.List().AddCommand(b.SimpleCommand("PAGES=\"`ls -1 | ${SED} -e 's,3qt$$,3,'`\""))) | |||
391 | ||||
392 | s.test("var=Plain var=\"Dquot\" var='Squot' var=Plain\"Dquot\"'Squot'", | |||
393 | b.List().AddCommand(b.SimpleCommand("var=Plain", "var=\"Dquot\"", "var='Squot'", "var=Plain\"Dquot\"'Squot'"))) | |||
394 | ||||
395 | // RUN is a special Make variable since it ends with a semicolon; | |||
396 | // therefore it needs to be split off before passing the rest of | |||
397 | // the command to the shell command parser. | |||
398 | s.test("${RUN} subdir=\"`unzip -c \"$$e\" install.rdf | awk '/re/ { print \"hello\" }'`\"", | |||
399 | b.List().AddCommand(b.SimpleCommand("${RUN}", "subdir=\"`unzip -c \"$$e\" install.rdf | awk '/re/ { print \"hello\" }'`\""))) | |||
400 | ||||
401 | s.test("PATH=/nonexistent env PATH=${PATH:Q} true", | |||
402 | b.List().AddCommand(b.SimpleCommand("PATH=/nonexistent", "env", "PATH=${PATH:Q}", "true"))) | |||
403 | ||||
404 | s.test("{OpenGrok args", | |||
405 | b.List().AddCommand(b.SimpleCommand("{OpenGrok", "args"))) | |||
406 | } | |||
407 | ||||
408 | func (s *ShSuite) Test_ShellParser_io_redirect(c *check.C) { | |||
409 | b := s.init(c) | |||
410 | ||||
411 | s.test("echo >> ${PLIST_SRC}", | |||
412 | b.List().AddCommand(b.SimpleCommand("echo", ">>${PLIST_SRC}"))) | |||
413 | ||||
414 | s.test("echo >> ${PLIST_SRC}", | |||
415 | b.List().AddCommand(b.SimpleCommand("echo", ">>${PLIST_SRC}"))) | |||
416 | ||||
417 | s.test("echo 1>output 2>>append 3>|clobber 4>&5 6<input >>append", | |||
418 | b.List().AddCommand(&MkShCommand{Simple: &MkShSimpleCommand{ | |||
419 | Assignments: nil, | |||
420 | Name: b.Token("echo"), | |||
421 | Args: nil, | |||
422 | Redirections: []*MkShRedirection{ | |||
423 | {1, ">", b.Token("output")}, | |||
424 | {2, ">>", b.Token("append")}, | |||
425 | {3, ">|", b.Token("clobber")}, | |||
426 | {4, ">&", b.Token("5")}, | |||
427 | {6, "<", b.Token("input")}, | |||
428 | {-1, ">>", b.Token("append")}}}})) | |||
429 | ||||
430 | s.test("echo 1> output 2>> append 3>| clobber 4>& 5 6< input >> append", | |||
431 | b.List().AddCommand(&MkShCommand{Simple: &MkShSimpleCommand{ | |||
432 | Assignments: nil, | |||
433 | Name: b.Token("echo"), | |||
434 | Args: nil, | |||
435 | Redirections: []*MkShRedirection{ | |||
436 | {1, ">", b.Token("output")}, | |||
437 | {2, ">>", b.Token("append")}, | |||
438 | {3, ">|", b.Token("clobber")}, | |||
439 | {4, ">&", b.Token("5")}, | |||
440 | {6, "<", b.Token("input")}, | |||
441 | {-1, ">>", b.Token("append")}}}})) | |||
442 | } | |||
443 | ||||
444 | func (s *ShSuite) Test_ShellParser_io_here(c *check.C) { | |||
445 | b := s.init(c) | |||
446 | ||||
447 | _ = b | |||
448 | } | |||
449 | ||||
450 | func (s *ShSuite) init(c *check.C) *MkShBuilder { | |||
451 | s.c = c | |||
452 | return NewMkShBuilder() | |||
453 | } | |||
454 | ||||
455 | func (s *ShSuite) test(program string, expected *MkShList) { | |||
456 | tokens, rest := splitIntoShellTokens(dummyLine, program) | |||
457 | s.c.Check(rest, equals, "") | |||
458 | lexer := &ShellLexer{ | |||
459 | current: "", | |||
460 | remaining: tokens, | |||
461 | atCommandStart: true, | |||
462 | error: ""} | |||
463 | parser := ­yParserImpl{} | |||
464 | ||||
465 | succeeded := parser.Parse(lexer) | |||
466 | ||||
467 | c := s.c | |||
468 | ||||
469 | if ok1, ok2 := c.Check(succeeded, equals, 0), c.Check(lexer.error, equals, ""); ok1 && ok2 { | |||
470 | if !c.Check(lexer.result, deepEquals, expected) { | |||
471 | actualJson, actualErr := json.MarshalIndent(lexer.result, "", " ") | |||
472 | expectedJson, expectedErr := json.MarshalIndent(expected, "", " ") | |||
473 | if c.Check(actualErr, check.IsNil) && c.Check(expectedErr, check.IsNil) { | |||
474 | c.Check(string(actualJson), deepEquals, string(expectedJson)) | |||
475 | } | |||
476 | } | |||
477 | } else { | |||
478 | c.Check(lexer.remaining, deepEquals, []string{}) | |||
98 | } | 479 | } | |
99 | tester := &MkShTester{c} | |||
100 | ||||
101 | params := []*ShToken{tester.Token("\"$$@\"")} | |||
102 | action := tester.ParseCompoundList("action;") | |||
103 | parse("for var; do action; done", | |||
104 | &MkShForClause{"var", params, action}) | |||
105 | ||||
106 | abc := []*ShToken{tester.Token("a"), tester.Token("b"), tester.Token("c")} | |||
107 | parse("for var in a b c; do action; done", | |||
108 | &MkShForClause{"var", abc, action}) | |||
109 | ||||
110 | actions := tester.ParseCompoundList("action1 && action2;") | |||
111 | parse("for var in a b c; do action1 && action2; done", | |||
112 | &MkShForClause{"var", abc, actions}) | |||
113 | } | |||
114 | ||||
115 | func (s *Suite) Test_MkShParser_Wordlist(c *check.C) { | |||
116 | ||||
117 | } | |||
118 | ||||
119 | func (s *Suite) Test_MkShParser_CaseClause(c *check.C) { | |||
120 | ||||
121 | } | |||
122 | ||||
123 | func (s *Suite) Test_MkShParser_CaseItem(c *check.C) { | |||
124 | ||||
125 | } | |||
126 | ||||
127 | func (s *Suite) Test_MkShParser_Pattern(c *check.C) { | |||
128 | ||||
129 | } | 480 | } | |
130 | 481 | |||
131 | func (s *Suite) Test_MkShParser_IfClause(c *check.C) { | 482 | type MkShBuilder struct { | |
132 | ||||
133 | } | 483 | } | |
134 | 484 | |||
135 | func (s *Suite) Test_MkShParser_WhileClause(c *check.C) { | 485 | func NewMkShBuilder() *MkShBuilder { | |
136 | 486 | return &MkShBuilder{} | ||
137 | } | 487 | } | |
138 | 488 | |||
139 | func (s *Suite) Test_MkShParser_UntilClause(c *check.C) { | 489 | func (b *MkShBuilder) List() *MkShList { | |
140 | 490 | return NewMkShList() | ||
141 | } | 491 | } | |
142 | 492 | |||
143 | func (s *Suite) Test_MkShParser_FunctionDefinition(c *check.C) { | 493 | func (b *MkShBuilder) AndOr(pipeline *MkShPipeline) *MkShAndOr { | |
144 | 494 | return NewMkShAndOr(pipeline) | ||
145 | } | 495 | } | |
146 | 496 | |||
147 | func (s *Suite) Test_MkShParser_BraceGroup(c *check.C) { | 497 | func (b *MkShBuilder) Pipeline(negated bool, cmds ...*MkShCommand) *MkShPipeline { | |
148 | 498 | return NewMkShPipeline(negated, cmds...) | ||
149 | } | 499 | } | |
150 | 500 | |||
151 | func (s *Suite) Test_MkShParser_DoGroup(c *check.C) { | 501 | func (b *MkShBuilder) SimpleCommand(words ...string) *MkShCommand { | |
152 | tester := &MkShTester{c} | 502 | cmd := &MkShSimpleCommand{} | |
153 | check := func(str string, expected *MkShList) { | 503 | assignments := true | |
154 | p := NewMkShParser(dummyLine, str, false) | 504 | for _, word := range words { | |
155 | dogroup := p.DoGroup() | 505 | if assignments && matches(word, `^\w+=`) { | |
156 | if c.Check(dogroup, check.NotNil) { | 506 | cmd.Assignments = append(cmd.Assignments, b.Token(word)) | |
157 | if !c.Check(dogroup, deepEquals, expected) { | 507 | } else if m, fdstr, op, rest := match3(word, `^(\d*)(<<-|<<|<&|>>|>&|>\||<|>)(.*)$`); m { | |
158 | for i, andor := range dogroup.AndOrs { | 508 | fd, err := strconv.Atoi(fdstr) | |
159 | c.Check(andor, deepEquals, expected.AndOrs[i]) | 509 | if err != nil { | |
160 | } | 510 | fd = -1 | |
161 | } | 511 | } | |
162 | } | 512 | cmd.Redirections = append(cmd.Redirections, b.Redirection(fd, op, rest)) | |
163 | c.Check(p.tok.parser.Rest(), equals, "") | 513 | } else { | |
164 | c.Check(s.Output(), equals, "") | 514 | assignments = false | |
165 | } | 515 | if cmd.Name == nil { | |
166 | 516 | cmd.Name = b.Token(word) | ||
167 | andor := NewMkShAndOr(NewMkShPipeline(false, tester.ParseCommand("action"))) | 517 | } else { | |
168 | check("do action; done", | 518 | cmd.Args = append(cmd.Args, b.Token(word)) | |
169 | &MkShList{[]*MkShAndOr{andor}, []MkShSeparator{";"}}) | |||
170 | } | |||
171 | ||||
172 | func (s *Suite) Test_MkShParser_SimpleCommand(c *check.C) { | |||
173 | parse := func(cmd string, builder *SimpleCommandBuilder) { | |||
174 | expected := builder.Cmd | |||
175 | p := NewMkShParser(dummyLine, cmd, false) | |||
176 | shcmd := p.SimpleCommand() | |||
177 | if c.Check(shcmd, check.NotNil) { | |||
178 | if !c.Check(shcmd, deepEquals, expected) { | |||
179 | for i, assignment := range shcmd.Assignments { | |||
180 | c.Check(assignment, deepEquals, expected.Assignments[i]) | |||
181 | } | |||
182 | c.Check(shcmd.Name, deepEquals, expected.Name) | |||
183 | for i, word := range shcmd.Args { | |||
184 | c.Check(word, deepEquals, expected.Args[i]) | |||
185 | } | |||
186 | for i, redirection := range shcmd.Redirections { | |||
187 | c.Check(redirection, deepEquals, expected.Redirections[i]) | |||
188 | } | |||
189 | } | 519 | } | |
190 | } | 520 | } | |
191 | c.Check(p.tok.parser.Rest(), equals, "") | |||
192 | c.Check(s.Output(), equals, "") | |||
193 | } | |||
194 | ||||
195 | fail := func(noncmd string, expectedRest string) { | |||
196 | p := NewMkShParser(dummyLine, noncmd, false) | |||
197 | shcmd := p.SimpleCommand() | |||
198 | c.Check(shcmd, check.IsNil) | |||
199 | c.Check(p.tok.parser.Rest(), equals, expectedRest) | |||
200 | c.Check(s.Output(), equals, "") | |||
201 | } | 521 | } | |
202 | tester := &MkShTester{c} | 522 | return &MkShCommand{Simple: cmd} | |
203 | ||||
204 | parse("echo ${PKGNAME:Q}", | |||
205 | NewSimpleCommandBuilder(). | |||
206 | Name(tester.Token("echo")). | |||
207 | Arg(tester.Token("${PKGNAME:Q}"))) | |||
208 | ||||
209 | parse("${ECHO} \"Double-quoted\" 'Single-quoted'", | |||
210 | NewSimpleCommandBuilder(). | |||
211 | Name(tester.Token("${ECHO}")). | |||
212 | Arg(tester.Token("\"Double-quoted\"")). | |||
213 | Arg(tester.Token("'Single-quoted'"))) | |||
214 | ||||
215 | parse("`cat plain` \"`cat double`\" '`cat single`'", | |||
216 | NewSimpleCommandBuilder(). | |||
217 | Name(tester.Token("`cat plain`")). | |||
218 | Arg(tester.Token("\"`cat double`\"")). | |||
219 | Arg(tester.Token("'`cat single`'"))) | |||
220 | ||||
221 | parse("`\"one word\"`", | |||
222 | NewSimpleCommandBuilder(). | |||
223 | Name(tester.Token("`\"one word\"`"))) | |||
224 | ||||
225 | parse("PAGES=\"`ls -1 | ${SED} -e 's,3qt$$,3,'`\"", | |||
226 | NewSimpleCommandBuilder(). | |||
227 | Assignment(tester.Token("PAGES=\"`ls -1 | ${SED} -e 's,3qt$$,3,'`\""))) | |||
228 | ||||
229 | parse("var=Plain var=\"Dquot\" var='Squot' var=Plain\"Dquot\"'Squot'", | |||
230 | NewSimpleCommandBuilder(). | |||
231 | Assignment(tester.Token("var=Plain")). | |||
232 | Assignment(tester.Token("var=\"Dquot\"")). | |||
233 | Assignment(tester.Token("var='Squot'")). | |||
234 | Assignment(tester.Token("var=Plain\"Dquot\"'Squot'"))) | |||
235 | ||||
236 | parse("${RUN} subdir=\"`unzip -c \"$$e\" install.rdf | awk '/re/ { print \"hello\" }'`\"", | |||
237 | NewSimpleCommandBuilder(). | |||
238 | Name(tester.Token("${RUN}")). | |||
239 | Arg(tester.Token("subdir=\"`unzip -c \"$$e\" install.rdf | awk '/re/ { print \"hello\" }'`\""))) | |||
240 | ||||
241 | parse("PATH=/nonexistent env PATH=${PATH:Q} true", | |||
242 | NewSimpleCommandBuilder(). | |||
243 | Assignment(tester.Token("PATH=/nonexistent")). | |||
244 | Name(tester.Token("env")). | |||
245 | Arg(tester.Token("PATH=${PATH:Q}")). | |||
246 | Arg(tester.Token("true"))) | |||
247 | ||||
248 | parse("{OpenGrok args", | |||
249 | NewSimpleCommandBuilder(). | |||
250 | Name(tester.Token("{OpenGrok")). | |||
251 | Arg(tester.Token("args"))) | |||
252 | ||||
253 | fail("if clause", "if clause") | |||
254 | fail("{ group; }", "{ group; }") | |||
255 | ||||
256 | } | |||
257 | ||||
258 | func (s *Suite) Test_MkShParser_RedirectList(c *check.C) { | |||
259 | } | |||
260 | ||||
261 | func (s *Suite) Test_MkShParser_IoRedirect(c *check.C) { | |||
262 | } | |||
263 | ||||
264 | func (s *Suite) Test_MkShParser_IoFile(c *check.C) { | |||
265 | } | |||
266 | ||||
267 | func (s *Suite) Test_MkShParser_IoHere(c *check.C) { | |||
268 | } | |||
269 | ||||
270 | func (s *Suite) Test_MkShParser_NewlineList(c *check.C) { | |||
271 | } | 523 | } | |
272 | 524 | |||
273 | func (s *Suite) Test_MkShParser_Linebreak(c *check.C) { | 525 | func (b *MkShBuilder) If(condActionElse ...*MkShList) *MkShCommand { | |
526 | ifclause := &MkShIfClause{} | |||
527 | for i, part := range condActionElse { | |||
528 | if i%2 == 0 && i != len(condActionElse)-1 { | |||
529 | ifclause.Conds = append(ifclause.Conds, part) | |||
530 | } else if i%2 == 1 { | |||
531 | ifclause.Actions = append(ifclause.Actions, part) | |||
532 | } else { | |||
533 | ifclause.Else = part | |||
534 | } | |||
535 | } | |||
536 | return &MkShCommand{Compound: &MkShCompoundCommand{If: ifclause}} | |||
274 | } | 537 | } | |
275 | 538 | |||
276 | func (s *Suite) Test_MkShParser_SeparatorOp(c *check.C) { | 539 | func (b *MkShBuilder) For(varname string, items []*ShToken, action *MkShList) *MkShCommand { | |
277 | 540 | return &MkShCommand{Compound: &MkShCompoundCommand{For: &MkShForClause{varname, items, action}}} | ||
278 | } | 541 | } | |
279 | 542 | |||
280 | func (s *Suite) Test_MkShParser_Separator(c *check.C) { | 543 | func (b *MkShBuilder) Case(selector *ShToken, items ...*MkShCaseItem) *MkShCommand { | |
281 | 544 | return &MkShCommand{Compound: &MkShCompoundCommand{Case: &MkShCaseClause{selector, items}}} | ||
282 | } | 545 | } | |
283 | 546 | |||
284 | func (s *Suite) Test_MkShParser_SequentialSep(c *check.C) { | 547 | func (b *MkShBuilder) CaseItem(patterns []*ShToken, action *MkShList, separator *MkShSeparator) *MkShCaseItem { | |
285 | 548 | return &MkShCaseItem{patterns, action, separator} | ||
286 | } | 549 | } | |
287 | 550 | |||
288 | func (s *Suite) Test_MkShParser_Word(c *check.C) { | 551 | func (b *MkShBuilder) While(cond, action *MkShList, redirects ...*MkShRedirection) *MkShCommand { | |
289 | 552 | return &MkShCommand{ | ||
553 | Compound: &MkShCompoundCommand{ | |||
554 | Loop: &MkShLoopClause{cond, action, false}}, | |||
555 | Redirects: redirects} | |||
290 | } | 556 | } | |
291 | 557 | |||
292 | type MkShTester struct { | 558 | func (b *MkShBuilder) Until(cond, action *MkShList, redirects ...*MkShRedirection) *MkShCommand { | |
293 | c *check.C | 559 | return &MkShCommand{ | |
560 | Compound: &MkShCompoundCommand{ | |||
561 | Loop: &MkShLoopClause{cond, action, true}}, | |||
562 | Redirects: redirects} | |||
294 | } | 563 | } | |
295 | 564 | |||
296 | func (t *MkShTester) ParseCommand(str string) *MkShCommand { | 565 | func (b *MkShBuilder) Function(name string, body *MkShCompoundCommand, redirects ...*MkShRedirection) *MkShCommand { | |
297 | p := NewMkShParser(dummyLine, str, false) | 566 | return &MkShCommand{ | |
298 | cmd := p.Command() | 567 | FuncDef: &MkShFunctionDefinition{name, body}, | |
299 | t.c.Check(cmd, check.NotNil) | 568 | Redirects: redirects} | |
300 | t.c.Check(p.Rest(), equals, "") | |||
301 | return cmd | |||
302 | } | 569 | } | |
303 | 570 | |||
304 | func (t *MkShTester) ParseSimpleCommand(str string) *MkShSimpleCommand { | 571 | func (b *MkShBuilder) Brace(list *MkShList) *MkShCommand { | |
305 | p := NewMkShParser(dummyLine, str, false) | 572 | return &MkShCommand{Compound: &MkShCompoundCommand{Brace: list}} | |
306 | parsed := p.SimpleCommand() | |||
307 | t.c.Check(parsed, check.NotNil) | |||
308 | t.c.Check(p.Rest(), equals, "") | |||
309 | return parsed | |||
310 | } | 573 | } | |
311 | 574 | |||
312 | func (t *MkShTester) ParseCompoundList(str string) *MkShList { | 575 | func (b *MkShBuilder) Subshell(list *MkShList) *MkShCommand { | |
313 | p := NewMkShParser(dummyLine, str, false) | 576 | return &MkShCommand{Compound: &MkShCompoundCommand{Subshell: list}} | |
314 | parsed := p.CompoundList() | |||
315 | t.c.Check(parsed, check.NotNil) | |||
316 | t.c.Check(p.Rest(), equals, "") | |||
317 | return parsed | |||
318 | } | 577 | } | |
319 | 578 | |||
320 | func (t *MkShTester) Token(str string) *ShToken { | 579 | func (b *MkShBuilder) Token(mktext string) *ShToken { | |
321 | p := NewMkShParser(dummyLine, str, false) | 580 | tokenizer := NewShTokenizer(dummyLine, mktext, false) | |
322 | parsed := p.peek() | 581 | token := tokenizer.ShToken() | |
323 | p.skip() | 582 | return token | |
324 | t.c.Check(parsed, check.NotNil) | |||
325 | t.c.Check(p.Rest(), equals, "") | |||
326 | return parsed | |||
327 | } | 583 | } | |
328 | 584 | |||
329 | type SimpleCommandBuilder struct { | 585 | func (b *MkShBuilder) Words(words ...string) []*ShToken { | |
330 | Cmd *MkShSimpleCommand | 586 | tokens := make([]*ShToken, len(words)) | |
587 | for i, word := range words { | |||
588 | tokens[i] = b.Token(word) | |||
589 | } | |||
590 | return tokens | |||
331 | } | 591 | } | |
332 | 592 | |||
333 | func NewSimpleCommandBuilder() *SimpleCommandBuilder { | 593 | func (b *MkShBuilder) Redirection(fd int, op string, target string) *MkShRedirection { | |
334 | cmd := &MkShSimpleCommand{} | 594 | return &MkShRedirection{fd, op, b.Token(target)} | |
335 | return &SimpleCommandBuilder{cmd} | |||
336 | } | |||
337 | func (b *SimpleCommandBuilder) Name(name *ShToken) *SimpleCommandBuilder { | |||
338 | b.Cmd.Name = name | |||
339 | return b | |||
340 | } | |||
341 | func (b *SimpleCommandBuilder) Assignment(assignment *ShToken) *SimpleCommandBuilder { | |||
342 | b.Cmd.Assignments = append(b.Cmd.Assignments, assignment) | |||
343 | return b | |||
344 | } | |||
345 | func (b *SimpleCommandBuilder) Arg(arg *ShToken) *SimpleCommandBuilder { | |||
346 | b.Cmd.Args = append(b.Cmd.Args, arg) | |||
347 | return b | |||
348 | } | |||
349 | func (b *SimpleCommandBuilder) Redirection(redirection *MkShRedirection) *SimpleCommandBuilder { | |||
350 | b.Cmd.Redirections = append(b.Cmd.Redirections, redirection) | |||
351 | return b | |||
352 | } | 595 | } |
@@ -1,231 +1,173 @@ | @@ -1,231 +1,173 @@ | |||
1 | package main | 1 | package main | |
2 | 2 | |||
3 | import ( | 3 | import "fmt" | |
4 | "fmt" | |||
5 | ) | |||
6 | 4 | |||
7 | type MkShList struct { | 5 | type MkShList struct { | |
8 | AndOrs []*MkShAndOr | 6 | AndOrs []*MkShAndOr | |
9 | Separators []MkShSeparator | 7 | Separators []MkShSeparator | |
10 | } | 8 | } | |
11 | 9 | |||
12 | func NewMkShList() *MkShList { | 10 | func NewMkShList() *MkShList { | |
13 | return &MkShList{nil, nil} | 11 | return &MkShList{nil, nil} | |
14 | } | 12 | } | |
15 | 13 | |||
16 | func (list *MkShList) String() string { | |||
17 | return fmt.Sprintf("MkShList(%v)", list.AndOrs) | |||
18 | } | |||
19 | ||||
20 | func (list *MkShList) AddAndOr(andor *MkShAndOr) *MkShList { | 14 | func (list *MkShList) AddAndOr(andor *MkShAndOr) *MkShList { | |
21 | list.AndOrs = append(list.AndOrs, andor) | 15 | list.AndOrs = append(list.AndOrs, andor) | |
22 | return list | 16 | return list | |
23 | } | 17 | } | |
24 | 18 | |||
25 | func (list *MkShList) AddSeparator(separator MkShSeparator) *MkShList { | 19 | func (list *MkShList) AddSeparator(separator MkShSeparator) *MkShList { | |
26 | list.Separators = append(list.Separators, separator) | 20 | list.Separators = append(list.Separators, separator) | |
27 | return list | 21 | return list | |
28 | } | 22 | } | |
29 | 23 | |||
30 | type MkShAndOr struct { | 24 | type MkShAndOr struct { | |
31 | Pipes []*MkShPipeline | 25 | Pipes []*MkShPipeline | |
32 | Ops []string // Either "&&" or "||" | 26 | Ops []string // Either "&&" or "||" | |
33 | } | 27 | } | |
34 | 28 | |||
35 | func NewMkShAndOr(pipeline *MkShPipeline) *MkShAndOr { | 29 | func NewMkShAndOr(pipeline *MkShPipeline) *MkShAndOr { | |
36 | return &MkShAndOr{[]*MkShPipeline{pipeline}, nil} | 30 | return &MkShAndOr{[]*MkShPipeline{pipeline}, nil} | |
37 | } | 31 | } | |
38 | 32 | |||
39 | func (andor *MkShAndOr) String() string { | |||
40 | return fmt.Sprintf("MkShAndOr(%v)", andor.Pipes) | |||
41 | } | |||
42 | ||||
43 | func (andor *MkShAndOr) Add(op string, pipeline *MkShPipeline) *MkShAndOr { | 33 | func (andor *MkShAndOr) Add(op string, pipeline *MkShPipeline) *MkShAndOr { | |
44 | andor.Pipes = append(andor.Pipes, pipeline) | 34 | andor.Pipes = append(andor.Pipes, pipeline) | |
45 | andor.Ops = append(andor.Ops, op) | 35 | andor.Ops = append(andor.Ops, op) | |
46 | return andor | 36 | return andor | |
47 | } | 37 | } | |
48 | 38 | |||
49 | type MkShPipeline struct { | 39 | type MkShPipeline struct { | |
50 | Negated bool | 40 | Negated bool | |
51 | Cmds []*MkShCommand | 41 | Cmds []*MkShCommand | |
52 | } | 42 | } | |
53 | 43 | |||
54 | func NewMkShPipeline(negated bool, cmds ...*MkShCommand) *MkShPipeline { | 44 | func NewMkShPipeline(negated bool, cmds ...*MkShCommand) *MkShPipeline { | |
55 | return &MkShPipeline{negated, cmds} | 45 | return &MkShPipeline{negated, cmds} | |
56 | } | 46 | } | |
57 | 47 | |||
58 | func (pipe *MkShPipeline) String() string { | |||
59 | return fmt.Sprintf("MkShPipeline(%v)", pipe.Cmds) | |||
60 | } | |||
61 | ||||
62 | func (pipe *MkShPipeline) Add(cmd *MkShCommand) *MkShPipeline { | 48 | func (pipe *MkShPipeline) Add(cmd *MkShCommand) *MkShPipeline { | |
63 | pipe.Cmds = append(pipe.Cmds, cmd) | 49 | pipe.Cmds = append(pipe.Cmds, cmd) | |
64 | return pipe | 50 | return pipe | |
65 | } | 51 | } | |
66 | 52 | |||
67 | type MkShCommand struct { | 53 | type MkShCommand struct { | |
68 | Simple *MkShSimpleCommand | 54 | Simple *MkShSimpleCommand | |
69 | Compound *MkShCompoundCommand | 55 | Compound *MkShCompoundCommand | |
70 | FuncDef *MkShFunctionDefinition | 56 | FuncDef *MkShFunctionDefinition | |
71 | Redirects []*MkShRedirection // For Compound and FuncDef | 57 | Redirects []*MkShRedirection // For Compound and FuncDef | |
72 | } | 58 | } | |
73 | 59 | |||
74 | func (cmd *MkShCommand) String() string { | |||
75 | switch { | |||
76 | case cmd.Simple != nil: | |||
77 | return cmd.Simple.String() | |||
78 | case cmd.Compound != nil: | |||
79 | return cmd.Compound.String() | |||
80 | case cmd.FuncDef != nil: | |||
81 | return cmd.FuncDef.String() | |||
82 | } | |||
83 | return "MkShCommand(?)" | |||
84 | } | |||
85 | ||||
86 | type MkShCompoundCommand struct { | 60 | type MkShCompoundCommand struct { | |
87 | Brace *MkShList | 61 | Brace *MkShList | |
88 | Subshell *MkShList | 62 | Subshell *MkShList | |
89 | For *MkShForClause | 63 | For *MkShForClause | |
90 | Case *MkShCaseClause | 64 | Case *MkShCaseClause | |
91 | If *MkShIfClause | 65 | If *MkShIfClause | |
92 | While *MkShLoopClause | 66 | Loop *MkShLoopClause | |
93 | Until *MkShLoopClause | |||
94 | } | |||
95 | ||||
96 | func (cmd *MkShCompoundCommand) String() string { | |||
97 | switch { | |||
98 | case cmd.Brace != nil: | |||
99 | return cmd.Brace.String() | |||
100 | case cmd.Subshell != nil: | |||
101 | return cmd.Subshell.String() | |||
102 | case cmd.For != nil: | |||
103 | return cmd.For.String() | |||
104 | case cmd.Case != nil: | |||
105 | return cmd.Case.String() | |||
106 | case cmd.If != nil: | |||
107 | return cmd.If.String() | |||
108 | case cmd.While != nil: | |||
109 | return cmd.While.String() | |||
110 | case cmd.Until != nil: | |||
111 | return cmd.Until.String() | |||
112 | } | |||
113 | return "MkShCompoundCommand(?)" | |||
114 | } | 67 | } | |
115 | 68 | |||
116 | type MkShForClause struct { | 69 | type MkShForClause struct { | |
117 | Varname string | 70 | Varname string | |
118 | Values []*ShToken | 71 | Values []*ShToken | |
119 | Body *MkShList | 72 | Body *MkShList | |
120 | } | 73 | } | |
121 | 74 | |||
122 | func (cl *MkShForClause) String() string { | |||
123 | return fmt.Sprintf("MkShForClause(%v, %v, %v)", cl.Varname, cl.Values, cl.Body) | |||
124 | } | |||
125 | ||||
126 | type MkShCaseClause struct { | 75 | type MkShCaseClause struct { | |
127 | Word *ShToken | 76 | Word *ShToken | |
128 | Cases []*MkShCaseItem | 77 | Cases []*MkShCaseItem | |
129 | } | 78 | } | |
130 | 79 | |||
131 | func (cl *MkShCaseClause) String() string { | |||
132 | return fmt.Sprintf("MkShCaseClause(...)") | |||
133 | } | |||
134 | ||||
135 | type MkShCaseItem struct { | 80 | type MkShCaseItem struct { | |
136 | Patterns []*ShToken | 81 | Patterns []*ShToken | |
137 | Action *MkShList | 82 | Action *MkShList | |
83 | Separator *MkShSeparator | |||
138 | } | 84 | } | |
139 | 85 | |||
140 | type MkShIfClause struct { | 86 | type MkShIfClause struct { | |
141 | Conds []*MkShList | 87 | Conds []*MkShList | |
142 | Actions []*MkShList | 88 | Actions []*MkShList | |
143 | Else *MkShList | 89 | Else *MkShList | |
144 | } | 90 | } | |
145 | 91 | |||
146 | func (cl *MkShIfClause) String() string { | |||
147 | return "MkShIf(...)" | |||
148 | } | |||
149 | ||||
150 | func (cl *MkShIfClause) Prepend(cond *MkShList, action *MkShList) { | 92 | func (cl *MkShIfClause) Prepend(cond *MkShList, action *MkShList) { | |
151 | cl.Conds = append([]*MkShList{cond}, cl.Conds...) | 93 | cl.Conds = append([]*MkShList{cond}, cl.Conds...) | |
152 | cl.Actions = append([]*MkShList{action}, cl.Actions...) | 94 | cl.Actions = append([]*MkShList{action}, cl.Actions...) | |
153 | } | 95 | } | |
154 | 96 | |||
155 | type MkShLoopClause struct { | 97 | type MkShLoopClause struct { | |
156 | Cond *MkShList | 98 | Cond *MkShList | |
157 | Action *MkShList | 99 | Action *MkShList | |
158 | Until bool | 100 | Until bool | |
159 | } | 101 | } | |
160 | 102 | |||
161 | func (cl *MkShLoopClause) String() string { | |||
162 | return "MkShLoop(...)" | |||
163 | } | |||
164 | ||||
165 | type MkShFunctionDefinition struct { | 103 | type MkShFunctionDefinition struct { | |
166 | Name string | 104 | Name string | |
167 | Body *MkShCompoundCommand | 105 | Body *MkShCompoundCommand | |
168 | Redirects []*MkShRedirection | |||
169 | } | |||
170 | ||||
171 | func (def *MkShFunctionDefinition) String() string { | |||
172 | return "MkShFunctionDef(...)" | |||
173 | } | 106 | } | |
174 | 107 | |||
175 | type MkShSimpleCommand struct { | 108 | type MkShSimpleCommand struct { | |
176 | Assignments []*ShToken | 109 | Assignments []*ShToken | |
177 | Name *ShToken | 110 | Name *ShToken | |
178 | Args []*ShToken | 111 | Args []*ShToken | |
179 | Redirections []*MkShRedirection | 112 | Redirections []*MkShRedirection | |
180 | } | 113 | } | |
181 | 114 | |||
182 | func (scmd *MkShSimpleCommand) String() string { | 115 | func NewStrCommand(cmd *MkShSimpleCommand) *StrCommand { | |
183 | str := "SimpleCommand(" | 116 | strcmd := &StrCommand{ | |
184 | first := true | 117 | make([]string, len(cmd.Assignments)), | |
185 | sep := func() { | 118 | "", | |
186 | if first { | 119 | make([]string, len(cmd.Args))} | |
187 | first = false | 120 | for i, assignment := range cmd.Assignments { | |
188 | } else { | 121 | strcmd.Assignments[i] = assignment.MkText | |
189 | str += ", " | |||
190 | } | |||
191 | } | 122 | } | |
192 | for _, word := range scmd.Assignments { | 123 | if cmd.Name != nil { | |
193 | sep() | 124 | strcmd.Name = cmd.Name.MkText | |
194 | str += word.MkText | |||
195 | } | 125 | } | |
196 | if word := scmd.Name; word != nil { | 126 | for i, arg := range cmd.Args { | |
197 | sep() | 127 | strcmd.Args[i] = arg.MkText | |
198 | str += word.MkText | |||
199 | } | 128 | } | |
200 | for _, word := range scmd.Args { | 129 | return strcmd | |
201 | sep() | 130 | } | |
202 | str += word.MkText | 131 | ||
132 | type StrCommand struct { | |||
133 | Assignments []string | |||
134 | Name string | |||
135 | Args []string | |||
136 | } | |||
137 | ||||
138 | func (c *StrCommand) HasOption(opt string) bool { | |||
139 | for _, arg := range c.Args { | |||
140 | if arg == opt { | |||
141 | return true | |||
142 | } | |||
203 | } | 143 | } | |
204 | for _, redirection := range scmd.Redirections { | 144 | return false | |
205 | sep() | 145 | } | |
206 | str += redirection.String() | 146 | ||
147 | func (c *StrCommand) AnyArgMatches(pattern string) bool { | |||
148 | for _, arg := range c.Args { | |||
149 | if matches(arg, pattern) { | |||
150 | return true | |||
151 | } | |||
207 | } | 152 | } | |
208 | return str + ")" | 153 | return false | |
154 | } | |||
155 | ||||
156 | func (c *StrCommand) String() string { | |||
157 | return fmt.Sprintf("%v %v %v", c.Assignments, c.Name, c.Args) | |||
209 | } | 158 | } | |
210 | 159 | |||
211 | type MkShRedirection struct { | 160 | type MkShRedirection struct { | |
212 | Fd int // Or -1 | 161 | Fd int // Or -1 | |
213 | Op string | 162 | Op string | |
214 | Target *ShToken | 163 | Target *ShToken | |
215 | } | 164 | } | |
216 | 165 | |||
217 | func (r *MkShRedirection) String() string { | |||
218 | if r.Fd != -1 { | |||
219 | return fmt.Sprintf("%d%s%s", r.Fd, r.Op, r.Target.MkText) | |||
220 | } else { | |||
221 | return r.Op + r.Target.MkText | |||
222 | } | |||
223 | } | |||
224 | ||||
225 | // One of ";", "&", "\n" | 166 | // One of ";", "&", "\n" | |
226 | type MkShSeparator string | 167 | type MkShSeparator string | |
227 | 168 | |||
228 | func (sep *MkShSeparator) String() string { | 169 | var ( | |
229 | return fmt.Sprintf("%q", sep) | 170 | SEP_SEMI MkShSeparator = ";" | |
230 | 171 | SEP_BACKGROUND MkShSeparator = "&" | ||
231 | } | 172 | SEP_NEWLINE MkShSeparator = "\n" | |
173 | ) |
@@ -1,11 +1,17 @@ | @@ -1,11 +1,17 @@ | |||
1 | package main | 1 | package main | |
2 | 2 | |||
3 | import ( | 3 | import ( | |
4 | check "gopkg.in/check.v1" | 4 | check "gopkg.in/check.v1" | |
5 | ) | 5 | ) | |
6 | 6 | |||
7 | func (s *Suite) Test_MkVarUse_Mod(c *check.C) { | 7 | func (s *Suite) Test_MkVarUse_Mod(c *check.C) { | |
8 | varuse := &MkVarUse{"varname", []string{"Q"}} | 8 | varuse := &MkVarUse{"varname", []string{"Q"}} | |
9 | 9 | |||
10 | c.Check(varuse.Mod(), equals, ":Q") | 10 | c.Check(varuse.Mod(), equals, ":Q") | |
11 | } | 11 | } | |
12 | ||||
13 | func (list *MkShList) AddCommand(command *MkShCommand) *MkShList { | |||
14 | pipeline := NewMkShPipeline(false, command) | |||
15 | andOr := NewMkShAndOr(pipeline) | |||
16 | return list.AddAndOr(andOr) | |||
17 | } |
@@ -24,74 +24,80 @@ func (p *ShTokenizer) ShAtom(quoting ShQ | @@ -24,74 +24,80 @@ func (p *ShTokenizer) ShAtom(quoting ShQ | |||
24 | return &ShAtom{shtVaruse, repl.Since(mark), quoting, varuse} | 24 | return &ShAtom{shtVaruse, repl.Since(mark), quoting, varuse} | |
25 | } | 25 | } | |
26 | 26 | |||
27 | var atom *ShAtom | 27 | var atom *ShAtom | |
28 | switch quoting { | 28 | switch quoting { | |
29 | case shqPlain: | 29 | case shqPlain: | |
30 | atom = p.shAtomPlain() | 30 | atom = p.shAtomPlain() | |
31 | case shqDquot: | 31 | case shqDquot: | |
32 | atom = p.shAtomDquot() | 32 | atom = p.shAtomDquot() | |
33 | case shqSquot: | 33 | case shqSquot: | |
34 | atom = p.shAtomSquot() | 34 | atom = p.shAtomSquot() | |
35 | case shqBackt: | 35 | case shqBackt: | |
36 | atom = p.shAtomBackt() | 36 | atom = p.shAtomBackt() | |
37 | case shqSubsh: | |||
38 | atom = p.shAtomSub() | |||
37 | case shqDquotBackt: | 39 | case shqDquotBackt: | |
38 | atom = p.shAtomDquotBackt() | 40 | atom = p.shAtomDquotBackt() | |
39 | case shqBacktDquot: | 41 | case shqBacktDquot: | |
40 | atom = p.shAtomBacktDquot() | 42 | atom = p.shAtomBacktDquot() | |
41 | case shqBacktSquot: | 43 | case shqBacktSquot: | |
42 | atom = p.shAtomBacktSquot() | 44 | atom = p.shAtomBacktSquot() | |
45 | case shqSubshSquot: | |||
46 | atom = p.shAtomSubshSquot() | |||
43 | case shqDquotBacktDquot: | 47 | case shqDquotBacktDquot: | |
44 | atom = p.shAtomDquotBacktDquot() | 48 | atom = p.shAtomDquotBacktDquot() | |
45 | case shqDquotBacktSquot: | 49 | case shqDquotBacktSquot: | |
46 | atom = p.shAtomDquotBacktSquot() | 50 | atom = p.shAtomDquotBacktSquot() | |
47 | } | 51 | } | |
48 | 52 | |||
49 | if atom == nil { | 53 | if atom == nil { | |
50 | repl.Reset(mark) | 54 | repl.Reset(mark) | |
51 | p.parser.Line.Warnf("Pkglint parse error in ShTokenizer.ShAtom at %q (quoting=%s)", repl.rest, quoting) | 55 | p.parser.Line.Warnf("Pkglint parse error in ShTokenizer.ShAtom at %q (quoting=%s)", repl.rest, quoting) | |
52 | } | 56 | } | |
53 | return atom | 57 | return atom | |
54 | } | 58 | } | |
55 | 59 | |||
56 | func (p *ShTokenizer) shAtomPlain() *ShAtom { | 60 | func (p *ShTokenizer) shAtomPlain() *ShAtom { | |
57 | q := shqPlain | 61 | q := shqPlain | |
58 | repl := p.parser.repl | 62 | repl := p.parser.repl | |
59 | switch { | 63 | switch { | |
60 | case repl.AdvanceHspace(): | 64 | case repl.AdvanceHspace(): | |
61 | return &ShAtom{shtSpace, repl.s, q, nil} | 65 | return &ShAtom{shtSpace, repl.s, q, nil} | |
66 | case repl.AdvanceStr("\n"): | |||
67 | return &ShAtom{shtNewline, repl.s, q, nil} | |||
62 | case repl.AdvanceStr(";;"): | 68 | case repl.AdvanceStr(";;"): | |
63 | return &ShAtom{shtCaseSeparator, repl.s, q, nil} | 69 | return &ShAtom{shtCaseSeparator, repl.s, q, nil} | |
64 | case repl.AdvanceStr(";"): | 70 | case repl.AdvanceStr(";"): | |
65 | return &ShAtom{shtSemicolon, repl.s, q, nil} | 71 | return &ShAtom{shtSemicolon, repl.s, q, nil} | |
66 | case repl.AdvanceStr("("): | 72 | case repl.AdvanceStr("("): | |
67 | return &ShAtom{shtParenOpen, repl.s, q, nil} | 73 | return &ShAtom{shtParenOpen, repl.s, q, nil} | |
68 | case repl.AdvanceStr(")"): | 74 | case repl.AdvanceStr(")"): | |
69 | return &ShAtom{shtParenClose, repl.s, q, nil} | 75 | return &ShAtom{shtParenClose, repl.s, q, nil} | |
70 | case repl.AdvanceStr("||"): | 76 | case repl.AdvanceStr("||"): | |
71 | return &ShAtom{shtOr, repl.s, q, nil} | 77 | return &ShAtom{shtOr, repl.s, q, nil} | |
72 | case repl.AdvanceStr("&&"): | 78 | case repl.AdvanceStr("&&"): | |
73 | return &ShAtom{shtAnd, repl.s, q, nil} | 79 | return &ShAtom{shtAnd, repl.s, q, nil} | |
74 | case repl.AdvanceStr("|"): | 80 | case repl.AdvanceStr("|"): | |
75 | return &ShAtom{shtPipe, repl.s, q, nil} | 81 | return &ShAtom{shtPipe, repl.s, q, nil} | |
76 | case repl.AdvanceStr("&"): | 82 | case repl.AdvanceStr("&"): | |
77 | return &ShAtom{shtBackground, repl.s, q, nil} | 83 | return &ShAtom{shtBackground, repl.s, q, nil} | |
78 | case repl.AdvanceStr("\""): | 84 | case repl.AdvanceStr("\""): | |
79 | return &ShAtom{shtWord, repl.s, shqDquot, nil} | 85 | return &ShAtom{shtWord, repl.s, shqDquot, nil} | |
80 | case repl.AdvanceStr("'"): | 86 | case repl.AdvanceStr("'"): | |
81 | return &ShAtom{shtWord, repl.s, shqSquot, nil} | 87 | return &ShAtom{shtWord, repl.s, shqSquot, nil} | |
82 | case repl.AdvanceStr("`"): | 88 | case repl.AdvanceStr("`"): | |
83 | return &ShAtom{shtWord, repl.s, shqBackt, nil} | 89 | return &ShAtom{shtWord, repl.s, shqBackt, nil} | |
84 | case repl.AdvanceRegexp(`^(?:<|<<|>|>>|>&)`): | 90 | case repl.AdvanceRegexp(`^\d*(?:<<-|<<|<&|<>|>>|>&|>\||<|>)`): | |
85 | return &ShAtom{shtRedirect, repl.m[0], q, nil} | 91 | return &ShAtom{shtRedirect, repl.m[0], q, nil} | |
86 | case repl.AdvanceRegexp(`^#.*`): | 92 | case repl.AdvanceRegexp(`^#.*`): | |
87 | return &ShAtom{shtComment, repl.m[0], q, nil} | 93 | return &ShAtom{shtComment, repl.m[0], q, nil} | |
88 | case repl.AdvanceStr("$$("): | 94 | case repl.AdvanceStr("$$("): | |
89 | return &ShAtom{shtSubshell, repl.s, q, nil} | 95 | return &ShAtom{shtSubshell, repl.s, q, nil} | |
90 | case repl.AdvanceRegexp(`^(?:[!#%*+,\-./0-9:=?@A-Z\[\]^_a-z{}~]+|\\[^$]|` + reShDollar + `)+`): | 96 | case repl.AdvanceRegexp(`^(?:[!#%*+,\-./0-9:=?@A-Z\[\]^_a-z{}~]+|\\[^$]|` + reShDollar + `)+`): | |
91 | return &ShAtom{shtWord, repl.m[0], q, nil} | 97 | return &ShAtom{shtWord, repl.m[0], q, nil} | |
92 | } | 98 | } | |
93 | return nil | 99 | return nil | |
94 | } | 100 | } | |
95 | 101 | |||
96 | func (p *ShTokenizer) shAtomDquot() *ShAtom { | 102 | func (p *ShTokenizer) shAtomDquot() *ShAtom { | |
97 | repl := p.parser.repl | 103 | repl := p.parser.repl | |
@@ -145,26 +151,66 @@ func (p *ShTokenizer) shAtomBackt() *ShA | @@ -145,26 +151,66 @@ func (p *ShTokenizer) shAtomBackt() *ShA | |||
145 | return &ShAtom{shtPipe, repl.s, q, nil} | 151 | return &ShAtom{shtPipe, repl.s, q, nil} | |
146 | case repl.AdvanceStr("&"): | 152 | case repl.AdvanceStr("&"): | |
147 | return &ShAtom{shtBackground, repl.s, q, nil} | 153 | return &ShAtom{shtBackground, repl.s, q, nil} | |
148 | case repl.AdvanceRegexp(`^(?:<|<<|>|>>|>&)`): | 154 | case repl.AdvanceRegexp(`^(?:<|<<|>|>>|>&)`): | |
149 | return &ShAtom{shtRedirect, repl.s, q, nil} | 155 | return &ShAtom{shtRedirect, repl.s, q, nil} | |
150 | case repl.AdvanceRegexp("^#[^`]*"): | 156 | case repl.AdvanceRegexp("^#[^`]*"): | |
151 | return &ShAtom{shtComment, repl.s, q, nil} | 157 | return &ShAtom{shtComment, repl.s, q, nil} | |
152 | case repl.AdvanceRegexp(`^(?:[!#%*+,\-./0-9:=?@A-Z\[\]_a-z~]+|\\[^$]|` + reShDollar + `)+`): | 158 | case repl.AdvanceRegexp(`^(?:[!#%*+,\-./0-9:=?@A-Z\[\]_a-z~]+|\\[^$]|` + reShDollar + `)+`): | |
153 | return &ShAtom{shtWord, repl.s, q, nil} | 159 | return &ShAtom{shtWord, repl.s, q, nil} | |
154 | } | 160 | } | |
155 | return nil | 161 | return nil | |
156 | } | 162 | } | |
157 | 163 | |||
164 | func (p *ShTokenizer) shAtomSub() *ShAtom { | |||
165 | const q = shqSubsh | |||
166 | repl := p.parser.repl | |||
167 | mark := repl.Mark() | |||
168 | atom := func(typ ShAtomType) *ShAtom { | |||
169 | return NewShAtom(typ, repl.Since(mark), shqSubsh) | |||
170 | } | |||
171 | switch { | |||
172 | case repl.AdvanceHspace(): | |||
173 | return atom(shtSpace) | |||
174 | case repl.AdvanceStr(";;"): | |||
175 | return atom(shtCaseSeparator) | |||
176 | case repl.AdvanceStr(";"): | |||
177 | return atom(shtSemicolon) | |||
178 | case repl.AdvanceStr("||"): | |||
179 | return atom(shtOr) | |||
180 | case repl.AdvanceStr("&&"): | |||
181 | return atom(shtAnd) | |||
182 | case repl.AdvanceStr("|"): | |||
183 | return atom(shtPipe) | |||
184 | case repl.AdvanceStr("&"): | |||
185 | return atom(shtBackground) | |||
186 | case repl.AdvanceStr("\""): | |||
187 | //return &ShAtom{shtWord, repl.s, shqDquot, nil} | |||
188 | case repl.AdvanceStr("'"): | |||
189 | return &ShAtom{shtWord, repl.s, shqSubshSquot, nil} | |||
190 | case repl.AdvanceStr("`"): | |||
191 | //return &ShAtom{shtWord, repl.s, shqBackt, nil} | |||
192 | case repl.AdvanceRegexp(`^\d*(?:<<-|<<|<&|<>|>>|>&|>\||<|>)`): | |||
193 | return &ShAtom{shtRedirect, repl.m[0], q, nil} | |||
194 | case repl.AdvanceRegexp(`^#.*`): | |||
195 | return &ShAtom{shtComment, repl.m[0], q, nil} | |||
196 | case repl.AdvanceStr(")"): | |||
197 | return NewShAtom(shtWord, repl.s, shqPlain) | |||
198 | case repl.AdvanceRegexp(`^(?:[!#%*+,\-./0-9:=?@A-Z\[\]^_a-z{}~]+|\\[^$]|` + reShDollar + `)+`): | |||
199 | return &ShAtom{shtWord, repl.m[0], q, nil} | |||
200 | } | |||
201 | return nil | |||
202 | } | |||
203 | ||||
158 | func (p *ShTokenizer) shAtomDquotBackt() *ShAtom { | 204 | func (p *ShTokenizer) shAtomDquotBackt() *ShAtom { | |
159 | const q = shqDquotBackt | 205 | const q = shqDquotBackt | |
160 | repl := p.parser.repl | 206 | repl := p.parser.repl | |
161 | switch { | 207 | switch { | |
162 | case repl.AdvanceStr("`"): | 208 | case repl.AdvanceStr("`"): | |
163 | return &ShAtom{shtWord, repl.s, shqDquot, nil} | 209 | return &ShAtom{shtWord, repl.s, shqDquot, nil} | |
164 | case repl.AdvanceStr("\""): | 210 | case repl.AdvanceStr("\""): | |
165 | return &ShAtom{shtWord, repl.s, shqDquotBacktDquot, nil} | 211 | return &ShAtom{shtWord, repl.s, shqDquotBacktDquot, nil} | |
166 | case repl.AdvanceStr("'"): | 212 | case repl.AdvanceStr("'"): | |
167 | return &ShAtom{shtWord, repl.s, shqDquotBacktSquot, nil} | 213 | return &ShAtom{shtWord, repl.s, shqDquotBacktSquot, nil} | |
168 | case repl.AdvanceRegexp("^#[^`]*"): | 214 | case repl.AdvanceRegexp("^#[^`]*"): | |
169 | return &ShAtom{shtComment, repl.s, q, nil} | 215 | return &ShAtom{shtComment, repl.s, q, nil} | |
170 | case repl.AdvanceStr(";;"): | 216 | case repl.AdvanceStr(";;"): | |
@@ -206,26 +252,38 @@ func (p *ShTokenizer) shAtomBacktDquot() | @@ -206,26 +252,38 @@ func (p *ShTokenizer) shAtomBacktDquot() | |||
206 | 252 | |||
207 | func (p *ShTokenizer) shAtomBacktSquot() *ShAtom { | 253 | func (p *ShTokenizer) shAtomBacktSquot() *ShAtom { | |
208 | const q = shqBacktSquot | 254 | const q = shqBacktSquot | |
209 | repl := p.parser.repl | 255 | repl := p.parser.repl | |
210 | switch { | 256 | switch { | |
211 | case repl.AdvanceStr("'"): | 257 | case repl.AdvanceStr("'"): | |
212 | return &ShAtom{shtWord, repl.s, shqBackt, nil} | 258 | return &ShAtom{shtWord, repl.s, shqBackt, nil} | |
213 | case repl.AdvanceRegexp(`^([\t !"#%&()*+,\-./0-9:;<=>?@A-Z\[\\\]^_` + "`" + `a-z{|}~]+|\$\$)+`): | 259 | case repl.AdvanceRegexp(`^([\t !"#%&()*+,\-./0-9:;<=>?@A-Z\[\\\]^_` + "`" + `a-z{|}~]+|\$\$)+`): | |
214 | return &ShAtom{shtWord, repl.m[0], q, nil} | 260 | return &ShAtom{shtWord, repl.m[0], q, nil} | |
215 | } | 261 | } | |
216 | return nil | 262 | return nil | |
217 | } | 263 | } | |
218 | 264 | |||
265 | func (p *ShTokenizer) shAtomSubshSquot() *ShAtom { | |||
266 | const q = shqSubshSquot | |||
267 | repl := p.parser.repl | |||
268 | switch { | |||
269 | case repl.AdvanceStr("'"): | |||
270 | return &ShAtom{shtWord, repl.s, shqSubsh, nil} | |||
271 | case repl.AdvanceRegexp(`^([\t !"#%&()*+,\-./0-9:;<=>?@A-Z\[\\\]^_` + "`" + `a-z{|}~]+|\$\$)+`): | |||
272 | return &ShAtom{shtWord, repl.m[0], q, nil} | |||
273 | } | |||
274 | return nil | |||
275 | } | |||
276 | ||||
219 | func (p *ShTokenizer) shAtomDquotBacktDquot() *ShAtom { | 277 | func (p *ShTokenizer) shAtomDquotBacktDquot() *ShAtom { | |
220 | const q = shqDquotBacktDquot | 278 | const q = shqDquotBacktDquot | |
221 | repl := p.parser.repl | 279 | repl := p.parser.repl | |
222 | switch { | 280 | switch { | |
223 | case repl.AdvanceStr("\""): | 281 | case repl.AdvanceStr("\""): | |
224 | return &ShAtom{shtWord, repl.s, shqDquotBackt, nil} | 282 | return &ShAtom{shtWord, repl.s, shqDquotBackt, nil} | |
225 | case repl.AdvanceRegexp(`^(?:[\t !%&()*+,\-./0-9:;<=>?@A-Z\[\]^_a-z{|}~]+|\\[^$]|` + reShDollar + `)+`): | 283 | case repl.AdvanceRegexp(`^(?:[\t !%&()*+,\-./0-9:;<=>?@A-Z\[\]^_a-z{|}~]+|\\[^$]|` + reShDollar + `)+`): | |
226 | return &ShAtom{shtWord, repl.m[0], q, nil} | 284 | return &ShAtom{shtWord, repl.m[0], q, nil} | |
227 | } | 285 | } | |
228 | return nil | 286 | return nil | |
229 | } | 287 | } | |
230 | 288 | |||
231 | func (p *ShTokenizer) shAtomDquotBacktSquot() *ShAtom { | 289 | func (p *ShTokenizer) shAtomDquotBacktSquot() *ShAtom { | |
@@ -271,27 +329,27 @@ func (p *ShTokenizer) ShToken() *ShToken | @@ -271,27 +329,27 @@ func (p *ShTokenizer) ShToken() *ShToken | |||
271 | repl := p.parser.repl | 329 | repl := p.parser.repl | |
272 | inimark := repl.Mark() | 330 | inimark := repl.Mark() | |
273 | var atoms []*ShAtom | 331 | var atoms []*ShAtom | |
274 | 332 | |||
275 | for peek() != nil && peek().Type == shtSpace { | 333 | for peek() != nil && peek().Type == shtSpace { | |
276 | skip() | 334 | skip() | |
277 | inimark = repl.Mark() | 335 | inimark = repl.Mark() | |
278 | } | 336 | } | |
279 | 337 | |||
280 | if peek() == nil { | 338 | if peek() == nil { | |
281 | return nil | 339 | return nil | |
282 | } | 340 | } | |
283 | if atom := peek(); !atom.Type.IsWord() { | 341 | if atom := peek(); !atom.Type.IsWord() { | |
284 | return NewShToken(atom.Text, atom) | 342 | return NewShToken(atom.MkText, atom) | |
285 | } | 343 | } | |
286 | 344 | |||
287 | nextatom: | 345 | nextatom: | |
288 | mark := repl.Mark() | 346 | mark := repl.Mark() | |
289 | atom := peek() | 347 | atom := peek() | |
290 | if atom != nil && (atom.Type.IsWord() || atom.Quoting != shqPlain) { | 348 | if atom != nil && (atom.Type.IsWord() || atom.Quoting != shqPlain) { | |
291 | skip() | 349 | skip() | |
292 | atoms = append(atoms, atom) | 350 | atoms = append(atoms, atom) | |
293 | goto nextatom | 351 | goto nextatom | |
294 | } | 352 | } | |
295 | repl.Reset(mark) | 353 | repl.Reset(mark) | |
296 | 354 | |||
297 | if len(atoms) == 0 { | 355 | if len(atoms) == 0 { |
@@ -5,49 +5,50 @@ import ( | @@ -5,49 +5,50 @@ import ( | |||
5 | ) | 5 | ) | |
6 | 6 | |||
7 | // @Beta | 7 | // @Beta | |
8 | func (s *Suite) Test_ShTokenizer_ShAtom(c *check.C) { | 8 | func (s *Suite) Test_ShTokenizer_ShAtom(c *check.C) { | |
9 | checkRest := func(s string, expected ...*ShAtom) string { | 9 | checkRest := func(s string, expected ...*ShAtom) string { | |
10 | p := NewShTokenizer(dummyLine, s, false) | 10 | p := NewShTokenizer(dummyLine, s, false) | |
11 | q := shqPlain | 11 | q := shqPlain | |
12 | for _, exp := range expected { | 12 | for _, exp := range expected { | |
13 | c.Check(p.ShAtom(q), deepEquals, exp) | 13 | c.Check(p.ShAtom(q), deepEquals, exp) | |
14 | q = exp.Quoting | 14 | q = exp.Quoting | |
15 | } | 15 | } | |
16 | return p.Rest() | 16 | return p.Rest() | |
17 | } | 17 | } | |
18 | check := func(s string, expected ...*ShAtom) { | 18 | check := func(str string, expected ...*ShAtom) { | |
19 | rest := checkRest(s, expected...) | 19 | rest := checkRest(str, expected...) | |
20 | c.Check(rest, equals, "") | 20 | c.Check(rest, equals, "") | |
21 | c.Check(s.Output(), equals, "") | |||
21 | } | 22 | } | |
22 | 23 | |||
23 | token := func(typ ShAtomType, text string, quoting ShQuoting) *ShAtom { | 24 | token := func(typ ShAtomType, text string, quoting ShQuoting) *ShAtom { | |
24 | return &ShAtom{typ, text, quoting, nil} | 25 | return &ShAtom{typ, text, quoting, nil} | |
25 | } | 26 | } | |
26 | word := func(s string) *ShAtom { return token(shtWord, s, shqPlain) } | 27 | word := func(s string) *ShAtom { return token(shtWord, s, shqPlain) } | |
27 | dquot := func(s string) *ShAtom { return token(shtWord, s, shqDquot) } | 28 | dquot := func(s string) *ShAtom { return token(shtWord, s, shqDquot) } | |
28 | squot := func(s string) *ShAtom { return token(shtWord, s, shqSquot) } | 29 | squot := func(s string) *ShAtom { return token(shtWord, s, shqSquot) } | |
29 | backt := func(s string) *ShAtom { return token(shtWord, s, shqBackt) } | 30 | backt := func(s string) *ShAtom { return token(shtWord, s, shqBackt) } | |
30 | varuse := func(varname string, modifiers ...string) *ShAtom { | 31 | varuse := func(varname string, modifiers ...string) *ShAtom { | |
31 | text := "${" + varname | 32 | text := "${" + varname | |
32 | for _, modifier := range modifiers { | 33 | for _, modifier := range modifiers { | |
33 | text += ":" + modifier | 34 | text += ":" + modifier | |
34 | } | 35 | } | |
35 | text += "}" | 36 | text += "}" | |
36 | varuse := &MkVarUse{varname: varname, modifiers: modifiers} | 37 | varuse := &MkVarUse{varname: varname, modifiers: modifiers} | |
37 | return &ShAtom{shtVaruse, text, shqPlain, varuse} | 38 | return &ShAtom{shtVaruse, text, shqPlain, varuse} | |
38 | } | 39 | } | |
39 | q := func(q ShQuoting, token *ShAtom) *ShAtom { | 40 | q := func(q ShQuoting, token *ShAtom) *ShAtom { | |
40 | return &ShAtom{token.Type, token.Text, q, token.Data} | 41 | return &ShAtom{token.Type, token.MkText, q, token.Data} | |
41 | } | 42 | } | |
42 | whitespace := func(s string) *ShAtom { return token(shtSpace, s, shqPlain) } | 43 | whitespace := func(s string) *ShAtom { return token(shtSpace, s, shqPlain) } | |
43 | space := token(shtSpace, " ", shqPlain) | 44 | space := token(shtSpace, " ", shqPlain) | |
44 | semicolon := token(shtSemicolon, ";", shqPlain) | 45 | semicolon := token(shtSemicolon, ";", shqPlain) | |
45 | pipe := token(shtPipe, "|", shqPlain) | 46 | pipe := token(shtPipe, "|", shqPlain) | |
46 | 47 | |||
47 | check("" /* none */) | 48 | check("" /* none */) | |
48 | 49 | |||
49 | check("$$var", | 50 | check("$$var", | |
50 | word("$$var")) | 51 | word("$$var")) | |
51 | 52 | |||
52 | check("$$var$$var", | 53 | check("$$var$$var", | |
53 | word("$$var$$var")) | 54 | word("$$var$$var")) | |
@@ -292,39 +293,55 @@ func (s *Suite) Test_ShTokenizer_ShAtom( | @@ -292,39 +293,55 @@ func (s *Suite) Test_ShTokenizer_ShAtom( | |||
292 | token(shtWord, "\"", shqDquotBacktDquot), | 293 | token(shtWord, "\"", shqDquotBacktDquot), | |
293 | token(shtWord, "\\`echo foo\\`", shqDquotBacktDquot), // One token, since it doesn’t influence parsing. | 294 | token(shtWord, "\\`echo foo\\`", shqDquotBacktDquot), // One token, since it doesn’t influence parsing. | |
294 | token(shtWord, "\"", shqDquotBackt), | 295 | token(shtWord, "\"", shqDquotBackt), | |
295 | token(shtWord, "`", shqDquot), | 296 | token(shtWord, "`", shqDquot), | |
296 | token(shtWord, "\"", shqPlain)) | 297 | token(shtWord, "\"", shqPlain)) | |
297 | 298 | |||
298 | check("if cond1; then action1; elif cond2; then action2; else action3; fi", | 299 | check("if cond1; then action1; elif cond2; then action2; else action3; fi", | |
299 | word("if"), space, word("cond1"), semicolon, space, | 300 | word("if"), space, word("cond1"), semicolon, space, | |
300 | word("then"), space, word("action1"), semicolon, space, | 301 | word("then"), space, word("action1"), semicolon, space, | |
301 | word("elif"), space, word("cond2"), semicolon, space, | 302 | word("elif"), space, word("cond2"), semicolon, space, | |
302 | word("then"), space, word("action2"), semicolon, space, | 303 | word("then"), space, word("action2"), semicolon, space, | |
303 | word("else"), space, word("action3"), semicolon, space, | 304 | word("else"), space, word("action3"), semicolon, space, | |
304 | word("fi")) | 305 | word("fi")) | |
306 | ||||
307 | if false { | |||
308 | check("$$(cat)", | |||
309 | token(shtWord, "$$(", shqSubsh), | |||
310 | token(shtWord, "cat", shqSubsh), | |||
311 | token(shtWord, ")", shqPlain)) | |||
312 | ||||
313 | check("$$(cat 'file')", | |||
314 | token(shtWord, "$$(", shqSubsh), | |||
315 | token(shtWord, "cat", shqSubsh), | |||
316 | token(shtSpace, " ", shqSubsh), | |||
317 | token(shtWord, "'", shqSubshSquot), | |||
318 | token(shtWord, "file", shqSubshSquot), | |||
319 | token(shtWord, "'", shqSubsh), | |||
320 | token(shtWord, ")", shqPlain)) | |||
321 | } | |||
305 | } | 322 | } | |
306 | 323 | |||
307 | func (s *Suite) Test_Shtokenizer_ShAtom_Quoting(c *check.C) { | 324 | func (s *Suite) Test_Shtokenizer_ShAtom_Quoting(c *check.C) { | |
308 | checkQuotingChange := func(input, expectedOutput string) { | 325 | checkQuotingChange := func(input, expectedOutput string) { | |
309 | p := NewShTokenizer(dummyLine, input, false) | 326 | p := NewShTokenizer(dummyLine, input, false) | |
310 | q := shqPlain | 327 | q := shqPlain | |
311 | result := "" | 328 | result := "" | |
312 | for { | 329 | for { | |
313 | token := p.ShAtom(q) | 330 | token := p.ShAtom(q) | |
314 | if token == nil { | 331 | if token == nil { | |
315 | break | 332 | break | |
316 | } | 333 | } | |
317 | result += token.Text | 334 | result += token.MkText | |
318 | if token.Quoting != q { | 335 | if token.Quoting != q { | |
319 | q = token.Quoting | 336 | q = token.Quoting | |
320 | result += "[" + q.String() + "]" | 337 | result += "[" + q.String() + "]" | |
321 | } | 338 | } | |
322 | } | 339 | } | |
323 | c.Check(result, equals, expectedOutput) | 340 | c.Check(result, equals, expectedOutput) | |
324 | c.Check(p.Rest(), equals, "") | 341 | c.Check(p.Rest(), equals, "") | |
325 | } | 342 | } | |
326 | 343 | |||
327 | checkQuotingChange("hello, world", "hello, world") | 344 | checkQuotingChange("hello, world", "hello, world") | |
328 | checkQuotingChange("hello, \"world\"", "hello, \"[d]world\"[plain]") | 345 | checkQuotingChange("hello, \"world\"", "hello, \"[d]world\"[plain]") | |
329 | checkQuotingChange("1 \"\" 2 '' 3 `` 4", "1 \"[d]\"[plain] 2 '[s]'[plain] 3 `[b]`[plain] 4") | 346 | checkQuotingChange("1 \"\" 2 '' 3 `` 4", "1 \"[d]\"[plain] 2 '[s]'[plain] 3 `[b]`[plain] 4") | |
330 | checkQuotingChange("\"\"", "\"[d]\"[plain]") | 347 | checkQuotingChange("\"\"", "\"[d]\"[plain]") |
@@ -13,108 +13,112 @@ const ( | @@ -13,108 +13,112 @@ const ( | |||
13 | shtVaruse // ${PREFIX} | 13 | shtVaruse // ${PREFIX} | |
14 | shtWord // | 14 | shtWord // | |
15 | shtSemicolon // ; | 15 | shtSemicolon // ; | |
16 | shtCaseSeparator // ;; | 16 | shtCaseSeparator // ;; | |
17 | shtParenOpen // ( | 17 | shtParenOpen // ( | |
18 | shtParenClose // ) | 18 | shtParenClose // ) | |
19 | shtPipe // | | 19 | shtPipe // | | |
20 | shtBackground // & | 20 | shtBackground // & | |
21 | shtOr // || | 21 | shtOr // || | |
22 | shtAnd // && | 22 | shtAnd // && | |
23 | shtRedirect // >, <, >> | 23 | shtRedirect // >, <, >> | |
24 | shtComment // # ... | 24 | shtComment // # ... | |
25 | shtSubshell // $$( | 25 | shtSubshell // $$( | |
26 | shtNewline // \n | |||
26 | ) | 27 | ) | |
27 | 28 | |||
28 | func (t ShAtomType) String() string { | 29 | func (t ShAtomType) String() string { | |
29 | return [...]string{ | 30 | return [...]string{ | |
30 | "space", | 31 | "space", | |
31 | "varuse", | 32 | "varuse", | |
32 | "word", | 33 | "word", | |
33 | "semicolon", | 34 | "semicolon", | |
34 | "caseSeparator", | 35 | "caseSeparator", | |
35 | "parenOpen", "parenClose", | 36 | "parenOpen", "parenClose", | |
36 | "pipe", "background", | 37 | "pipe", "background", | |
37 | "or", "and", | 38 | "or", "and", | |
38 | "redirect", | 39 | "redirect", | |
39 | "comment", | 40 | "comment", | |
41 | "newline", | |||
40 | }[t] | 42 | }[t] | |
41 | } | 43 | } | |
42 | 44 | |||
43 | func (t ShAtomType) IsWord() bool { | 45 | func (t ShAtomType) IsWord() bool { | |
44 | switch t { | 46 | switch t { | |
45 | case shtVaruse, shtWord, shtRedirect: | 47 | case shtVaruse, shtWord, shtRedirect: | |
46 | return true | 48 | return true | |
47 | } | 49 | } | |
48 | return false | 50 | return false | |
49 | } | 51 | } | |
50 | 52 | |||
51 | func (t ShAtomType) IsCommandDelimiter() bool { | 53 | func (t ShAtomType) IsCommandDelimiter() bool { | |
52 | switch t { | 54 | switch t { | |
53 | case shtSemicolon, shtPipe, shtBackground, shtAnd, shtOr, shtCaseSeparator: | 55 | case shtSemicolon, shtNewline, shtPipe, shtBackground, shtAnd, shtOr, shtCaseSeparator: | |
54 | return true | 56 | return true | |
55 | } | 57 | } | |
56 | return false | 58 | return false | |
57 | } | 59 | } | |
58 | 60 | |||
59 | // @Beta | 61 | // @Beta | |
60 | type ShAtom struct { | 62 | type ShAtom struct { | |
61 | Type ShAtomType | 63 | Type ShAtomType | |
62 | Text string | 64 | MkText string | |
63 | Quoting ShQuoting | 65 | Quoting ShQuoting | |
64 | Data interface{} | 66 | Data interface{} | |
65 | } | 67 | } | |
66 | 68 | |||
67 | func NewShAtom(typ ShAtomType, text string, quoting ShQuoting) *ShAtom { | 69 | func NewShAtom(typ ShAtomType, text string, quoting ShQuoting) *ShAtom { | |
68 | return &ShAtom{typ, text, quoting, nil} | 70 | return &ShAtom{typ, text, quoting, nil} | |
69 | } | 71 | } | |
70 | 72 | |||
71 | func NewShAtomVaruse(text string, quoting ShQuoting, varname string, modifiers ...string) *ShAtom { | 73 | func NewShAtomVaruse(text string, quoting ShQuoting, varname string, modifiers ...string) *ShAtom { | |
72 | return &ShAtom{shtVaruse, text, quoting, NewMkVarUse(varname, modifiers...)} | 74 | return &ShAtom{shtVaruse, text, quoting, NewMkVarUse(varname, modifiers...)} | |
73 | } | 75 | } | |
74 | 76 | |||
75 | func (token *ShAtom) String() string { | 77 | func (token *ShAtom) String() string { | |
76 | if token.Type == shtWord && token.Quoting == shqPlain && token.Data == nil { | 78 | if token.Type == shtWord && token.Quoting == shqPlain && token.Data == nil { | |
77 | return fmt.Sprintf("%q", token.Text) | 79 | return fmt.Sprintf("%q", token.MkText) | |
78 | } | 80 | } | |
79 | if token.Type == shtVaruse { | 81 | if token.Type == shtVaruse { | |
80 | varuse := token.Data.(*MkVarUse) | 82 | varuse := token.Data.(*MkVarUse) | |
81 | return fmt.Sprintf("varuse(%q)", varuse.varname+varuse.Mod()) | 83 | return fmt.Sprintf("varuse(%q)", varuse.varname+varuse.Mod()) | |
82 | } | 84 | } | |
83 | return fmt.Sprintf("ShAtom(%v, %q, %s)", token.Type, token.Text, token.Quoting) | 85 | return fmt.Sprintf("ShAtom(%v, %q, %s)", token.Type, token.MkText, token.Quoting) | |
84 | } | 86 | } | |
85 | 87 | |||
86 | // ShQuoting describes the context in which a string appears | 88 | // ShQuoting describes the context in which a string appears | |
87 | // and how it must be unescaped to get its literal value. | 89 | // and how it must be unescaped to get its literal value. | |
88 | type ShQuoting uint8 | 90 | type ShQuoting uint8 | |
89 | 91 | |||
90 | const ( | 92 | const ( | |
91 | shqPlain ShQuoting = iota | 93 | shqPlain ShQuoting = iota | |
92 | shqDquot | 94 | shqDquot | |
93 | shqSquot | 95 | shqSquot | |
94 | shqBackt | 96 | shqBackt | |
97 | shqSubsh | |||
95 | shqDquotBackt | 98 | shqDquotBackt | |
96 | shqBacktDquot | 99 | shqBacktDquot | |
97 | shqBacktSquot | 100 | shqBacktSquot | |
101 | shqSubshSquot | |||
98 | shqDquotBacktDquot | 102 | shqDquotBacktDquot | |
99 | shqDquotBacktSquot | 103 | shqDquotBacktSquot | |
100 | shqUnknown | 104 | shqUnknown | |
101 | ) | 105 | ) | |
102 | 106 | |||
103 | func (q ShQuoting) String() string { | 107 | func (q ShQuoting) String() string { | |
104 | return [...]string{ | 108 | return [...]string{ | |
105 | "plain", | 109 | "plain", | |
106 | "d", "s", "b", | 110 | "d", "s", "b", "S", | |
107 | "db", "bd", "bs", | 111 | "db", "bd", "bs", "Ss", | |
108 | "dbd", "dbs", | 112 | "dbd", "dbs", | |
109 | "unknown", | 113 | "unknown", | |
110 | }[q] | 114 | }[q] | |
111 | } | 115 | } | |
112 | 116 | |||
113 | func (q ShQuoting) ToVarUseContext() vucQuoting { | 117 | func (q ShQuoting) ToVarUseContext() vucQuoting { | |
114 | switch q { | 118 | switch q { | |
115 | case shqPlain: | 119 | case shqPlain: | |
116 | return vucQuotPlain | 120 | return vucQuotPlain | |
117 | case shqDquot: | 121 | case shqDquot: | |
118 | return vucQuotDquot | 122 | return vucQuotDquot | |
119 | case shqSquot: | 123 | case shqSquot: | |
120 | return vucQuotSquot | 124 | return vucQuotSquot |
package main
type MkShWalker struct {
}
func (w *MkShWalker) Walk(list *MkShList, callback func(node interface{})) {
for element := range w.iterate(list) {
callback(element)
}
}
func (w *MkShWalker) iterate(list *MkShList) chan interface{} {
elements := make(chan interface{})
go func() {
w.walkList(list, elements)
close(elements)
}()
return elements
}
func (w *MkShWalker) walkList(list *MkShList, collector chan interface{}) {
collector <- list
for _, andor := range list.AndOrs {
w.walkAndOr(andor, collector)
}
}
func (w *MkShWalker) walkAndOr(andor *MkShAndOr, collector chan interface{}) {
collector <- andor
for _, pipeline := range andor.Pipes {
w.walkPipeline(pipeline, collector)
}
}
func (w *MkShWalker) walkPipeline(pipeline *MkShPipeline, collector chan interface{}) {
collector <- pipeline
for _, command := range pipeline.Cmds {
w.walkCommand(command, collector)
}
}
func (w *MkShWalker) walkCommand(command *MkShCommand, collector chan interface{}) {
collector <- command
switch {
case command.Simple != nil:
w.walkSimpleCommand(command.Simple, collector)
case command.Compound != nil:
w.walkCompoundCommand(command.Compound, collector)
w.walkRedirects(command.Redirects, collector)
case command.FuncDef != nil:
w.walkFunctionDefinition(command.FuncDef, collector)
w.walkRedirects(command.Redirects, collector)
}
}
func (w *MkShWalker) walkSimpleCommand(command *MkShSimpleCommand, collector chan interface{}) {
collector <- command
w.walkWords(command.Assignments, collector)
if command.Name != nil {
w.walkWord(command.Name, collector)
}
w.walkWords(command.Args, collector)
w.walkRedirects(command.Redirections, collector)
}
func (w *MkShWalker) walkCompoundCommand(command *MkShCompoundCommand, collector chan interface{}) {
collector <- command
switch {
case command.Brace != nil:
w.walkList(command.Brace, collector)
case command.Case != nil:
w.walkCase(command.Case, collector)
case command.For != nil:
w.walkFor(command.For, collector)
case command.If != nil:
w.walkIf(command.If, collector)
case command.Loop != nil:
w.walkLoop(command.Loop, collector)
case command.Subshell != nil:
w.walkList(command.Subshell, collector)
}
}
func (w *MkShWalker) walkCase(caseClause *MkShCaseClause, collector chan interface{}) {
collector <- caseClause
w.walkWord(caseClause.Word, collector)
for _, caseItem := range caseClause.Cases {
collector <- caseItem
w.walkWords(caseItem.Patterns, collector)
w.walkList(caseItem.Action, collector)
}
}
func (w *MkShWalker) walkFunctionDefinition(funcdef *MkShFunctionDefinition, collector chan interface{}) {
collector <- funcdef
w.walkCompoundCommand(funcdef.Body, collector)
}
func (w *MkShWalker) walkIf(ifClause *MkShIfClause, collector chan interface{}) {
collector <- ifClause
for i, cond := range ifClause.Conds {
w.walkList(cond, collector)
w.walkList(ifClause.Actions[i], collector)
}
if ifClause.Else != nil {
w.walkList(ifClause.Else, collector)
}
}
func (w *MkShWalker) walkLoop(loop *MkShLoopClause, collector chan interface{}) {
collector <- loop
w.walkList(loop.Cond, collector)
w.walkList(loop.Action, collector)
}
func (w *MkShWalker) walkWords(words []*ShToken, collector chan interface{}) {
collector <- words
for _, word := range words {
w.walkWord(word, collector)
}
}
func (w *MkShWalker) walkWord(word *ShToken, collector chan interface{}) {
collector <- word
}
func (w *MkShWalker) walkRedirects(redirects []*MkShRedirection, collector chan interface{}) {
collector <- redirects
for _, redirect := range redirects {
collector <- redirect
w.walkWord(redirect.Target, collector)
}
}
func (w *MkShWalker) walkFor(forClause *MkShForClause, collector chan interface{}) {
collector <- forClause
collector <- forClause.Varname
w.walkWords(forClause.Values, collector)
w.walkList(forClause.Body, collector)
}
package main
import (
"gopkg.in/check.v1"
)
func (s *Suite) Test_MkShWalker_Walk(c *check.C) {
list, err := parseShellProgram(dummyLine, ""+
"if condition; then action; else case selector in pattern) case-item-action ;; esac; fi; "+
"set -e; cd ${WRKSRC}/locale; "+
"for lang in *.po; do "+
" [ \"$${lang}\" = \"wxstd.po\" ] && continue; "+
" ${TOOLS_PATH.msgfmt} -c -o \"$${lang%.po}.mo\" \"$${lang}\"; "+
"done")
if c.Check(err, check.IsNil) && c.Check(list, check.NotNil) {
var commands []string
(*MkShWalker).Walk(nil, list, func(node interface{}) {
if cmd, ok := node.(*MkShSimpleCommand); ok {
commands = append(commands, NewStrCommand(cmd).String())
}
})
c.Check(commands, deepEquals, []string{
"[] condition []",
"[] action []",
"[] case-item-action []",
"[] set [-e]",
"[] cd [${WRKSRC}/locale]",
"[] [ [\"$${lang}\" = \"wxstd.po\" ]]",
"[] continue []",
"[] ${TOOLS_PATH.msgfmt} [-c -o \"$${lang%.po}.mo\" \"$${lang}\"]"})
}
}
%{
package main
%}
%token <Word> tkWORD
%token <Word> tkASSIGNMENT_WORD
%token tkNEWLINE
%token <IONum> tkIO_NUMBER
%token tkBACKGROUND
%token tkPIPE tkSEMI
%token tkAND tkOR tkSEMISEMI
%token tkLT tkGT tkLTLT tkGTGT tkLTAND tkGTAND tkLTGT tkLTLTDASH tkGTPIPE
%token tkIF tkTHEN tkELSE tkELIF tkFI tkDO tkDONE
%token tkCASE tkESAC tkWHILE tkUNTIL tkFOR
%token tkLPAREN tkRPAREN tkLBRACE tkRBRACE tkEXCLAM
%token tkIN
%union {
IONum int
List *MkShList
AndOr *MkShAndOr
Pipeline *MkShPipeline
Command *MkShCommand
CompoundCommand *MkShCompoundCommand
Separator MkShSeparator
Simple *MkShSimpleCommand
FuncDef *MkShFunctionDefinition
For *MkShForClause
If *MkShIfClause
Case *MkShCaseClause
CaseItem *MkShCaseItem
Loop *MkShLoopClause
Words []*ShToken
Word *ShToken
Redirections []*MkShRedirection
Redirection *MkShRedirection
}
%type <List> start program compound_list brace_group subshell term do_group
%type <AndOr> and_or
%type <Pipeline> pipeline pipe_sequence
%type <Command> command
%type <CompoundCommand> compound_command
%type <Separator> separator separator_op sequential_sep
%type <Simple> simple_command cmd_prefix cmd_suffix
%type <FuncDef> function_definition
%type <For> for_clause
%type <If> if_clause else_part
%type <Case> case_clause case_list case_list_ns
%type <CaseItem> case_item case_item_ns
%type <Loop> while_clause until_clause
%type <Words> wordlist case_selector pattern
%type <Word> filename cmd_word here_end
%type <Redirections> redirect_list
%type <Redirection> io_redirect io_file io_here
%%
start : program {
shyylex.(*ShellLexer).result = $$
}
program : compound_list {
$$ = $1
}
program : /* empty */ {
$$ = &MkShList{}
}
and_or : pipeline {
$$ = NewMkShAndOr($1)
}
and_or : and_or tkAND linebreak pipeline {
$$.Add("&&", $4)
}
and_or : and_or tkOR linebreak pipeline {
$$.Add("||", $4)
}
pipeline : pipe_sequence {
/* empty */
}
pipeline : tkEXCLAM pipe_sequence {
$$ = $2
$$.Negated = true
}
pipe_sequence : command {
$$ = NewMkShPipeline(false, $1)
}
pipe_sequence : pipe_sequence tkPIPE linebreak command {
$$.Add($4)
}
command : simple_command {
$$ = &MkShCommand{Simple: $1}
}
command : compound_command {
$$ = &MkShCommand{Compound: $1}
}
command : compound_command redirect_list {
$$ = &MkShCommand{Compound: $1, Redirects: $2}
}
command : function_definition {
$$ = &MkShCommand{FuncDef: $1}
}
command : function_definition redirect_list {
$$ = &MkShCommand{FuncDef: $1, Redirects: $2}
}
compound_command : brace_group {
$$ = &MkShCompoundCommand{Brace: $1}
}
compound_command : subshell {
$$ = &MkShCompoundCommand{Subshell: $1}
}
compound_command : for_clause {
$$ = &MkShCompoundCommand{For: $1}
}
compound_command : case_clause {
$$ = &MkShCompoundCommand{Case: $1}
}
compound_command : if_clause {
$$ = &MkShCompoundCommand{If: $1}
}
compound_command : while_clause {
$$ = &MkShCompoundCommand{Loop: $1}
}
compound_command : until_clause {
$$ = &MkShCompoundCommand{Loop: $1}
}
subshell : tkLPAREN compound_list tkRPAREN {
$$ = $2
}
compound_list : linebreak term {
$$ = $2
}
compound_list : linebreak term separator {
$$ = $2
$$.AddSeparator($3)
}
term : and_or {
$$ = NewMkShList()
$$.AddAndOr($1)
}
term : term separator and_or {
$$.AddSeparator($2)
$$.AddAndOr($3)
}
for_clause : tkFOR tkWORD linebreak do_group {
args := NewShToken("\"$$@\"",
NewShAtom(shtWord, "\"",shqDquot),
NewShAtom(shtWord, "$$@",shqDquot),
NewShAtom(shtWord,"\"",shqPlain))
$$ = &MkShForClause{$2.MkText, []*ShToken{args}, $4}
}
for_clause : tkFOR tkWORD linebreak tkIN sequential_sep do_group {
$$ = &MkShForClause{$2.MkText, nil, $6}
}
for_clause : tkFOR tkWORD linebreak tkIN wordlist sequential_sep do_group {
$$ = &MkShForClause{$2.MkText, $5, $7}
}
wordlist : tkWORD {
$$ = append($$, $1)
}
wordlist : wordlist tkWORD {
$$ = append($$, $2)
}
case_clause : tkCASE tkWORD linebreak tkIN linebreak case_list tkESAC {
$$ = $6
$$.Word = $2
}
case_clause : tkCASE tkWORD linebreak tkIN linebreak case_list_ns tkESAC {
$$ = $6
$$.Word = $2
}
case_clause : tkCASE tkWORD linebreak tkIN linebreak tkESAC {
$$ = &MkShCaseClause{$2, nil}
}
case_list_ns : case_item_ns {
$$ = &MkShCaseClause{nil, nil}
$$.Cases = append($$.Cases, $1)
}
case_list_ns : case_list case_item_ns {
$$.Cases = append($$.Cases, $2)
}
case_list : case_item {
$$ = &MkShCaseClause{nil, nil}
$$.Cases = append($$.Cases, $1)
}
case_list : case_list case_item {
$$.Cases = append($$.Cases, $2)
}
case_selector : tkLPAREN pattern tkRPAREN {
$$ = $2
}
case_selector : pattern tkRPAREN {
/* empty */
}
case_item_ns : case_selector linebreak {
$$ = &MkShCaseItem{$1, &MkShList{}, nil}
}
case_item_ns : case_selector linebreak term linebreak {
$$ = &MkShCaseItem{$1, $3, nil}
}
case_item_ns : case_selector linebreak term separator_op linebreak {
$$ = &MkShCaseItem{$1, $3, &$4}
}
case_item : case_selector linebreak tkSEMISEMI linebreak {
$$ = &MkShCaseItem{$1, &MkShList{}, nil}
}
case_item : case_selector compound_list tkSEMISEMI linebreak {
$$ = &MkShCaseItem{$1, $2, nil}
}
pattern : tkWORD {
$$ = nil
$$ = append($$, $1)
}
pattern : pattern tkPIPE tkWORD {
$$ = append($$, $3)
}
if_clause : tkIF compound_list tkTHEN compound_list else_part tkFI {
$$ = $5
$$.Prepend($2, $4)
}
if_clause : tkIF compound_list tkTHEN compound_list tkFI {
$$ = &MkShIfClause{}
$$.Prepend($2, $4)
}
else_part : tkELIF compound_list tkTHEN compound_list {
$$ = &MkShIfClause{}
$$.Prepend($2, $4)
}
else_part : tkELIF compound_list tkTHEN compound_list else_part {
$$ = $5
$$.Prepend($2, $4)
}
else_part : tkELSE compound_list {
$$ = &MkShIfClause{nil, nil, $2}
}
while_clause : tkWHILE compound_list do_group {
$$ = &MkShLoopClause{$2, $3, false}
}
until_clause : tkUNTIL compound_list do_group {
$$ = &MkShLoopClause{$2, $3, true}
}
function_definition : tkWORD tkLPAREN tkRPAREN linebreak compound_command { /* Apply rule 9 */
$$ = &MkShFunctionDefinition{$1.MkText, $5}
}
brace_group : tkLBRACE compound_list tkRBRACE {
$$ = $2
}
do_group : tkDO compound_list tkDONE {
$$ = $2
}
simple_command : cmd_prefix cmd_word cmd_suffix {
$$.Name = $2
$$.Args = append($$.Args, $3.Args...)
$$.Redirections = append($$.Redirections, $3.Redirections...)
}
simple_command : cmd_prefix cmd_word {
$$.Name = $2
}
simple_command : cmd_prefix {
/* empty */
}
simple_command : tkWORD cmd_suffix {
$$ = $2
$$.Name = $1
}
simple_command : tkWORD {
$$ = &MkShSimpleCommand{Name: $1}
}
cmd_word : tkWORD { /* Apply rule 7b */
/* empty */
}
cmd_prefix : io_redirect {
$$ = &MkShSimpleCommand{}
$$.Redirections = append($$.Redirections, $1)
}
cmd_prefix : tkASSIGNMENT_WORD {
$$ = &MkShSimpleCommand{}
$$.Assignments = append($$.Assignments, $1)
}
cmd_prefix : cmd_prefix io_redirect {
$$.Redirections = append($$.Redirections, $2)
}
cmd_prefix : cmd_prefix tkASSIGNMENT_WORD {
$$.Assignments = append($$.Assignments, $2)
}
cmd_suffix : io_redirect {
$$ = &MkShSimpleCommand{}
$$.Redirections = append($$.Redirections, $1)
}
cmd_suffix : tkWORD {
$$ = &MkShSimpleCommand{}
$$.Args = append($$.Args, $1)
}
cmd_suffix : cmd_suffix io_redirect {
$$.Redirections = append($$.Redirections, $2)
}
cmd_suffix : cmd_suffix tkWORD {
$$.Args = append($$.Args, $2)
}
redirect_list : io_redirect {
$$ = nil
$$ = append($$, $1)
}
redirect_list : redirect_list io_redirect {
$$ = append($$, $2)
}
io_redirect : io_file {
/* empty */
}
io_redirect : tkIO_NUMBER io_file {
$$ = $2
$$.Fd = $1
}
io_redirect : io_here {
/* empty */
}
io_redirect : tkIO_NUMBER io_here {
$$ = $2
$$.Fd = $1
}
io_file : tkLT filename {
$$ = &MkShRedirection{-1, "<", $2}
}
io_file : tkLTAND filename {
$$ = &MkShRedirection{-1, "<&", $2}
}
io_file : tkGT filename {
$$ = &MkShRedirection{-1, ">", $2}
}
io_file : tkGTAND filename {
$$ = &MkShRedirection{-1, ">&", $2}
}
io_file : tkGTGT filename {
$$ = &MkShRedirection{-1, ">>", $2}
}
io_file : tkLTGT filename {
$$ = &MkShRedirection{-1, "<>", $2}
}
io_file : tkGTPIPE filename {
$$ = &MkShRedirection{-1, ">|", $2}
}
filename : tkWORD { /* Apply rule 2 */
/* empty */
}
io_here : tkLTLT here_end {
$$ = &MkShRedirection{-1, "<<", $2}
}
io_here : tkLTLTDASH here_end {
$$ = &MkShRedirection{-1, "<<-", $2}
}
here_end : tkWORD { /* Apply rule 3 */
/* empty */
}
newline_list : tkNEWLINE {
/* empty */
}
newline_list : newline_list tkNEWLINE {
/* empty */
}
linebreak : newline_list {
/* empty */
}
linebreak : /* empty */ {
/* empty */
}
separator_op : tkBACKGROUND {
$$ = "&"
}
separator_op : tkSEMI {
$$ = ";"
}
separator : separator_op linebreak {
/* empty */
}
separator : newline_list {
$$ = "\n"
}
sequential_sep : tkSEMI linebreak {
$$ = ";"
}
sequential_sep : tkNEWLINE linebreak {
$$ = "\n"
}
@@ -1,17 +1,18 @@ | @@ -1,17 +1,18 @@ | |||
1 | package main | 1 | package main | |
2 | 2 | |||
3 | import ( | 3 | import ( | |
4 | "fmt" | 4 | "fmt" | |
5 | "os/user" | |||
5 | "path" | 6 | "path" | |
6 | "regexp" | 7 | "regexp" | |
7 | "strconv" | 8 | "strconv" | |
8 | "strings" | 9 | "strings" | |
9 | ) | 10 | ) | |
10 | 11 | |||
11 | // Package contains data for the pkgsrc package that is currently checked. | 12 | // Package contains data for the pkgsrc package that is currently checked. | |
12 | type Package struct { | 13 | type Package struct { | |
13 | Pkgpath string // e.g. "category/pkgdir" | 14 | Pkgpath string // e.g. "category/pkgdir" | |
14 | Pkgdir string // PKGDIR from the package Makefile | 15 | Pkgdir string // PKGDIR from the package Makefile | |
15 | Filesdir string // FILESDIR from the package Makefile | 16 | Filesdir string // FILESDIR from the package Makefile | |
16 | Patchdir string // PATCHDIR from the package Makefile | 17 | Patchdir string // PATCHDIR from the package Makefile | |
17 | DistinfoFile string // DISTINFO_FILE from the package Makefile | 18 | DistinfoFile string // DISTINFO_FILE from the package Makefile | |
@@ -186,26 +187,27 @@ func checkdirPackage(pkgpath string) { | @@ -186,26 +187,27 @@ func checkdirPackage(pkgpath string) { | |||
186 | } | 187 | } | |
187 | if fname == G.CurrentDir+"/Makefile" { | 188 | if fname == G.CurrentDir+"/Makefile" { | |
188 | if G.opts.CheckMakefile { | 189 | if G.opts.CheckMakefile { | |
189 | pkg.checkfilePackageMakefile(fname, lines) | 190 | pkg.checkfilePackageMakefile(fname, lines) | |
190 | } | 191 | } | |
191 | } else { | 192 | } else { | |
192 | Checkfile(fname) | 193 | Checkfile(fname) | |
193 | } | 194 | } | |
194 | if contains(fname, "/patches/patch-") { | 195 | if contains(fname, "/patches/patch-") { | |
195 | havePatches = true | 196 | havePatches = true | |
196 | } else if hasSuffix(fname, "/distinfo") { | 197 | } else if hasSuffix(fname, "/distinfo") { | |
197 | haveDistinfo = true | 198 | haveDistinfo = true | |
198 | } | 199 | } | |
200 | pkg.checkLocallyModified(fname) | |||
199 | } | 201 | } | |
200 | 202 | |||
201 | if G.opts.CheckDistinfo && G.opts.CheckPatches { | 203 | if G.opts.CheckDistinfo && G.opts.CheckPatches { | |
202 | if havePatches && !haveDistinfo { | 204 | if havePatches && !haveDistinfo { | |
203 | NewLineWhole(G.CurrentDir+"/"+pkg.DistinfoFile).Warn1("File not found. Please run \"%s makepatchsum\".", confMake) | 205 | NewLineWhole(G.CurrentDir+"/"+pkg.DistinfoFile).Warn1("File not found. Please run \"%s makepatchsum\".", confMake) | |
204 | } | 206 | } | |
205 | } | 207 | } | |
206 | 208 | |||
207 | if !isEmptyDir(G.CurrentDir + "/scripts") { | 209 | if !isEmptyDir(G.CurrentDir + "/scripts") { | |
208 | NewLineWhole(G.CurrentDir + "/scripts").Warn0("This directory and its contents are deprecated! Please call the script(s) explicitly from the corresponding target(s) in the pkg's Makefile.") | 210 | NewLineWhole(G.CurrentDir + "/scripts").Warn0("This directory and its contents are deprecated! Please call the script(s) explicitly from the corresponding target(s) in the pkg's Makefile.") | |
209 | } | 211 | } | |
210 | } | 212 | } | |
211 | 213 | |||
@@ -377,27 +379,27 @@ func (pkg *Package) checkfilePackageMake | @@ -377,27 +379,27 @@ func (pkg *Package) checkfilePackageMake | |||
377 | if distinfoFile := G.CurrentDir + "/" + pkg.DistinfoFile; fileExists(distinfoFile) { | 379 | if distinfoFile := G.CurrentDir + "/" + pkg.DistinfoFile; fileExists(distinfoFile) { | |
378 | NewLineWhole(distinfoFile).Warn0("This file should not exist if NO_CHECKSUM or META_PACKAGE is set.") | 380 | NewLineWhole(distinfoFile).Warn0("This file should not exist if NO_CHECKSUM or META_PACKAGE is set.") | |
379 | } | 381 | } | |
380 | } else { | 382 | } else { | |
381 | if distinfoFile := G.CurrentDir + "/" + pkg.DistinfoFile; !containsVarRef(distinfoFile) && !fileExists(distinfoFile) { | 383 | if distinfoFile := G.CurrentDir + "/" + pkg.DistinfoFile; !containsVarRef(distinfoFile) && !fileExists(distinfoFile) { | |
382 | NewLineWhole(distinfoFile).Warn1("File not found. Please run \"%s makesum\".", confMake) | 384 | NewLineWhole(distinfoFile).Warn1("File not found. Please run \"%s makesum\".", confMake) | |
383 | } | 385 | } | |
384 | } | 386 | } | |
385 | 387 | |||
386 | if perlLine, noconfLine := vardef["REPLACE_PERL"], vardef["NO_CONFIGURE"]; perlLine != nil && noconfLine != nil { | 388 | if perlLine, noconfLine := vardef["REPLACE_PERL"], vardef["NO_CONFIGURE"]; perlLine != nil && noconfLine != nil { | |
387 | perlLine.Warn1("REPLACE_PERL is ignored when NO_CONFIGURE is set (in %s)", noconfLine.Line.ReferenceFrom(perlLine.Line)) | 389 | perlLine.Warn1("REPLACE_PERL is ignored when NO_CONFIGURE is set (in %s)", noconfLine.Line.ReferenceFrom(perlLine.Line)) | |
388 | } | 390 | } | |
389 | 391 | |||
390 | if vardef["LICENSE"] == nil { | 392 | if vardef["LICENSE"] == nil && vardef["META_PACKAGE"] == nil { | |
391 | NewLineWhole(fname).Error0("Each package must define its LICENSE.") | 393 | NewLineWhole(fname).Error0("Each package must define its LICENSE.") | |
392 | } | 394 | } | |
393 | 395 | |||
394 | if gnuLine, useLine := vardef["GNU_CONFIGURE"], vardef["USE_LANGUAGES"]; gnuLine != nil && useLine != nil { | 396 | if gnuLine, useLine := vardef["GNU_CONFIGURE"], vardef["USE_LANGUAGES"]; gnuLine != nil && useLine != nil { | |
395 | if matches(useLine.Comment(), `(?-i)\b(?:c|empty|none)\b`) { | 397 | if matches(useLine.Comment(), `(?-i)\b(?:c|empty|none)\b`) { | |
396 | // Don't emit a warning, since the comment | 398 | // Don't emit a warning, since the comment | |
397 | // probably contains a statement that C is | 399 | // probably contains a statement that C is | |
398 | // really not needed. | 400 | // really not needed. | |
399 | 401 | |||
400 | } else if !matches(useLine.Value(), `(?:^|\s+)(?:c|c99|objc)(?:\s+|$)`) { | 402 | } else if !matches(useLine.Value(), `(?:^|\s+)(?:c|c99|objc)(?:\s+|$)`) { | |
401 | gnuLine.Warn1("GNU_CONFIGURE almost always needs a C compiler, but \"c\" is not added to USE_LANGUAGES in %s.", | 403 | gnuLine.Warn1("GNU_CONFIGURE almost always needs a C compiler, but \"c\" is not added to USE_LANGUAGES in %s.", | |
402 | useLine.Line.ReferenceFrom(gnuLine.Line)) | 404 | useLine.Line.ReferenceFrom(gnuLine.Line)) | |
403 | } | 405 | } | |
@@ -474,39 +476,63 @@ func (pkg *Package) determineEffectivePk | @@ -474,39 +476,63 @@ func (pkg *Package) determineEffectivePk | |||
474 | pkg.EffectivePkgbase = m1 | 476 | pkg.EffectivePkgbase = m1 | |
475 | pkg.EffectivePkgversion = m2 | 477 | pkg.EffectivePkgversion = m2 | |
476 | } | 478 | } | |
477 | } | 479 | } | |
478 | if pkg.EffectivePkgnameLine != nil { | 480 | if pkg.EffectivePkgnameLine != nil { | |
479 | if G.opts.Debug { | 481 | if G.opts.Debug { | |
480 | traceStep("Effective name=%q base=%q version=%q", | 482 | traceStep("Effective name=%q base=%q version=%q", | |
481 | pkg.EffectivePkgname, pkg.EffectivePkgbase, pkg.EffectivePkgversion) | 483 | pkg.EffectivePkgname, pkg.EffectivePkgbase, pkg.EffectivePkgversion) | |
482 | } | 484 | } | |
483 | } | 485 | } | |
484 | } | 486 | } | |
485 | 487 | |||
486 | func (pkg *Package) pkgnameFromDistname(pkgname, distname string) string { | 488 | func (pkg *Package) pkgnameFromDistname(pkgname, distname string) string { | |
487 | pkgname = strings.Replace(pkgname, "${DISTNAME}", distname, -1) | 489 | tokens := NewMkParser(dummyLine, pkgname, false).MkTokens() | |
488 | 490 | |||
489 | if m, before, sep, subst, after := match4(pkgname, `^(.*)\$\{DISTNAME:S(.)([^\\}:]+)\}(.*)$`); m { | 491 | subst := func(str, smod string) (result string) { | |
490 | qsep := regexp.QuoteMeta(sep) | 492 | if G.opts.Debug { | |
491 | if m, left, from, right, to, mod := match5(subst, `^(\^?)([^:]*)(\$?)`+qsep+`([^:]*)`+qsep+`(g?)$`); m { | 493 | defer tracecall(str, smod, ref(result))() | |
492 | newPkgname := before + mkopSubst(distname, left != "", from, right != "", to, mod != "") + after | 494 | } | |
495 | qsep := regexp.QuoteMeta(smod[1:2]) | |||
496 | if m, left, from, right, to, flags := match5(smod, `^S`+qsep+`(\^?)([^:]*?)(\$?)`+qsep+`([^:]*)`+qsep+`([1g]*)$`); m { | |||
497 | result := mkopSubst(str, left != "", from, right != "", to, flags) | |||
493 | if G.opts.Debug { | 498 | if G.opts.Debug { | |
494 | traceStep("%s: pkgnameFromDistname %q => %q", pkg.vardef["PKGNAME"], pkgname, newPkgname) | 499 | traceStep("subst %q %q => %q", str, smod, result) | |
495 | } | 500 | } | |
496 | pkgname = newPkgname | 501 | return result | |
497 | } | 502 | } | |
503 | return str | |||
498 | } | 504 | } | |
499 | return pkgname | 505 | ||
506 | result := "" | |||
507 | for _, token := range tokens { | |||
508 | if token.Varuse != nil && token.Varuse.varname == "DISTNAME" { | |||
509 | newDistname := distname | |||
510 | for _, mod := range token.Varuse.modifiers { | |||
511 | if mod == "tl" { | |||
512 | newDistname = strings.ToLower(newDistname) | |||
513 | } else if hasPrefix(mod, "S") { | |||
514 | newDistname = subst(newDistname, mod) | |||
515 | } else { | |||
516 | newDistname = token.Text | |||
517 | break | |||
518 | } | |||
519 | } | |||
520 | result += newDistname | |||
521 | } else { | |||
522 | result += token.Text | |||
523 | } | |||
524 | } | |||
525 | return result | |||
500 | } | 526 | } | |
501 | 527 | |||
502 | func (pkg *Package) checkUpdate() { | 528 | func (pkg *Package) checkUpdate() { | |
503 | if pkg.EffectivePkgbase != "" { | 529 | if pkg.EffectivePkgbase != "" { | |
504 | for _, sugg := range G.globalData.GetSuggestedPackageUpdates() { | 530 | for _, sugg := range G.globalData.GetSuggestedPackageUpdates() { | |
505 | if pkg.EffectivePkgbase != sugg.Pkgname { | 531 | if pkg.EffectivePkgbase != sugg.Pkgname { | |
506 | continue | 532 | continue | |
507 | } | 533 | } | |
508 | 534 | |||
509 | suggver, comment := sugg.Version, sugg.Comment | 535 | suggver, comment := sugg.Version, sugg.Comment | |
510 | if comment != "" { | 536 | if comment != "" { | |
511 | comment = " (" + comment + ")" | 537 | comment = " (" + comment + ")" | |
512 | } | 538 | } | |
@@ -748,13 +774,63 @@ func (mklines *MkLines) checkForUsedComm | @@ -748,13 +774,63 @@ func (mklines *MkLines) checkForUsedComm | |||
748 | insertLine.Warn1("Please add a line %q here.", expected) | 774 | insertLine.Warn1("Please add a line %q here.", expected) | |
749 | Explain( | 775 | Explain( | |
750 | "Since Makefile.common files usually don't have any comments and", | 776 | "Since Makefile.common files usually don't have any comments and", | |
751 | "therefore not a clearly defined interface, they should at least", | 777 | "therefore not a clearly defined interface, they should at least", | |
752 | "contain references to all files that include them, so that it is", | 778 | "contain references to all files that include them, so that it is", | |
753 | "easier to see what effects future changes may have.", | 779 | "easier to see what effects future changes may have.", | |
754 | "", | 780 | "", | |
755 | "If there are more than five packages that use a Makefile.common,", | 781 | "If there are more than five packages that use a Makefile.common,", | |
756 | "you should think about giving it a proper name (maybe plugin.mk) and", | 782 | "you should think about giving it a proper name (maybe plugin.mk) and", | |
757 | "documenting its interface.") | 783 | "documenting its interface.") | |
758 | } | 784 | } | |
759 | SaveAutofixChanges(lines) | 785 | SaveAutofixChanges(lines) | |
760 | } | 786 | } | |
787 | ||||
788 | func (pkg *Package) checkLocallyModified(fname string) { | |||
789 | if G.opts.Debug { | |||
790 | defer tracecall(fname)() | |||
791 | } | |||
792 | ||||
793 | ownerLine := pkg.vardef["OWNER"] | |||
794 | maintainerLine := pkg.vardef["MAINTAINER"] | |||
795 | owner := "" | |||
796 | maintainer := "" | |||
797 | if ownerLine != nil && !containsVarRef(ownerLine.Value()) { | |||
798 | owner = ownerLine.Value() | |||
799 | } | |||
800 | if maintainerLine != nil && !containsVarRef(maintainerLine.Value()) && maintainerLine.Value() != "pkgsrc-users@NetBSD.org" { | |||
801 | maintainer = maintainerLine.Value() | |||
802 | } | |||
803 | if owner == "" && maintainer == "" { | |||
804 | return | |||
805 | } | |||
806 | ||||
807 | user, err := user.Current() | |||
808 | if err != nil || user.Username == "" { | |||
809 | return | |||
810 | } | |||
811 | // On Windows, this is `Computername\Username`. | |||
812 | username := regcomp(`^.*\\`).ReplaceAllString(user.Username, "") | |||
813 | ||||
814 | if G.opts.Debug { | |||
815 | traceStep("user=%q owner=%q maintainer=%q", username, owner, maintainer) | |||
816 | } | |||
817 | ||||
818 | if username == strings.Split(owner, "@")[0] || username == strings.Split(maintainer, "@")[0] { | |||
819 | return | |||
820 | } | |||
821 | ||||
822 | if isLocallyModified(fname) { | |||
823 | if owner != "" { | |||
824 | NewLineWhole(fname).Warn1("Don't commit changes to this file without asking the OWNER, %s.", owner) | |||
825 | Explain2( | |||
826 | "See the pkgsrc guide, section \"Package components\",", | |||
827 | "keyword \"owner\", for more information.") | |||
828 | } | |||
829 | if maintainer != "" { | |||
830 | NewLineWhole(fname).Note1("Please only commit changes that %s would approve.", maintainer) | |||
831 | Explain2( | |||
832 | "See the pkgsrc guide, section \"Package components\",", | |||
833 | "keyword \"maintainer\", for more information.") | |||
834 | } | |||
835 | } | |||
836 | } |
@@ -192,27 +192,27 @@ func Checkfile(fname string) { | @@ -192,27 +192,27 @@ func Checkfile(fname string) { | |||
192 | 192 | |||
193 | switch { | 193 | switch { | |
194 | case st.Mode().IsDir(): | 194 | case st.Mode().IsDir(): | |
195 | switch { | 195 | switch { | |
196 | case basename == "files" || basename == "patches" || basename == "CVS": | 196 | case basename == "files" || basename == "patches" || basename == "CVS": | |
197 | // Ok | 197 | // Ok | |
198 | case matches(fname, `(?:^|/)files/[^/]*$`): | 198 | case matches(fname, `(?:^|/)files/[^/]*$`): | |
199 | // Ok | 199 | // Ok | |
200 | case !isEmptyDir(fname): | 200 | case !isEmptyDir(fname): | |
201 | NewLineWhole(fname).Warn0("Unknown directory name.") | 201 | NewLineWhole(fname).Warn0("Unknown directory name.") | |
202 | } | 202 | } | |
203 | 203 | |||
204 | case st.Mode()&os.ModeSymlink != 0: | 204 | case st.Mode()&os.ModeSymlink != 0: | |
205 | if !matches(basename, `^work`) { | 205 | if !hasPrefix(basename, "work") { | |
206 | NewLineWhole(fname).Warn0("Unknown symlink name.") | 206 | NewLineWhole(fname).Warn0("Unknown symlink name.") | |
207 | } | 207 | } | |
208 | 208 | |||
209 | case !st.Mode().IsRegular(): | 209 | case !st.Mode().IsRegular(): | |
210 | NewLineWhole(fname).Error0("Only files and directories are allowed in pkgsrc.") | 210 | NewLineWhole(fname).Error0("Only files and directories are allowed in pkgsrc.") | |
211 | 211 | |||
212 | case basename == "ALTERNATIVES": | 212 | case basename == "ALTERNATIVES": | |
213 | if G.opts.CheckAlternatives { | 213 | if G.opts.CheckAlternatives { | |
214 | CheckfileExtra(fname) | 214 | CheckfileExtra(fname) | |
215 | } | 215 | } | |
216 | 216 | |||
217 | case basename == "buildlink3.mk": | 217 | case basename == "buildlink3.mk": | |
218 | if G.opts.CheckBuildlink3 { | 218 | if G.opts.CheckBuildlink3 { | |
@@ -274,26 +274,29 @@ func Checkfile(fname string) { | @@ -274,26 +274,29 @@ func Checkfile(fname string) { | |||
274 | } | 274 | } | |
275 | } | 275 | } | |
276 | 276 | |||
277 | case basename == "TODO" || basename == "README": | 277 | case basename == "TODO" || basename == "README": | |
278 | // Ok | 278 | // Ok | |
279 | 279 | |||
280 | case hasPrefix(basename, "CHANGES-"): | 280 | case hasPrefix(basename, "CHANGES-"): | |
281 | // This only checks the file, but doesn’t register the changes globally. | 281 | // This only checks the file, but doesn’t register the changes globally. | |
282 | G.globalData.loadDocChangesFromFile(fname) | 282 | G.globalData.loadDocChangesFromFile(fname) | |
283 | 283 | |||
284 | case matches(fname, `(?:^|/)files/[^/]*$`): | 284 | case matches(fname, `(?:^|/)files/[^/]*$`): | |
285 | // Skip | 285 | // Skip | |
286 | 286 | |||
287 | case basename == "spec": | |||
288 | // Ok in regression tests | |||
289 | ||||
287 | default: | 290 | default: | |
288 | NewLineWhole(fname).Warn0("Unexpected file found.") | 291 | NewLineWhole(fname).Warn0("Unexpected file found.") | |
289 | if G.opts.CheckExtra { | 292 | if G.opts.CheckExtra { | |
290 | CheckfileExtra(fname) | 293 | CheckfileExtra(fname) | |
291 | } | 294 | } | |
292 | } | 295 | } | |
293 | } | 296 | } | |
294 | 297 | |||
295 | func ChecklinesTrailingEmptyLines(lines []*Line) { | 298 | func ChecklinesTrailingEmptyLines(lines []*Line) { | |
296 | max := len(lines) | 299 | max := len(lines) | |
297 | last := max | 300 | last := max | |
298 | for last > 1 && lines[last-1].Text == "" { | 301 | for last > 1 && lines[last-1].Text == "" { | |
299 | last-- | 302 | last-- |
@@ -4,26 +4,29 @@ import ( | @@ -4,26 +4,29 @@ import ( | |||
4 | check "gopkg.in/check.v1" | 4 | check "gopkg.in/check.v1" | |
5 | ) | 5 | ) | |
6 | 6 | |||
7 | func (s *Suite) TestPkgnameFromDistname(c *check.C) { | 7 | func (s *Suite) TestPkgnameFromDistname(c *check.C) { | |
8 | pkg := NewPackage("dummy") | 8 | pkg := NewPackage("dummy") | |
9 | pkg.vardef["PKGNAME"] = NewMkLine(NewLine("Makefile", 5, "PKGNAME=dummy", nil)) | 9 | pkg.vardef["PKGNAME"] = NewMkLine(NewLine("Makefile", 5, "PKGNAME=dummy", nil)) | |
10 | 10 | |||
11 | c.Check(pkg.pkgnameFromDistname("pkgname-1.0", "whatever"), equals, "pkgname-1.0") | 11 | c.Check(pkg.pkgnameFromDistname("pkgname-1.0", "whatever"), equals, "pkgname-1.0") | |
12 | c.Check(pkg.pkgnameFromDistname("${DISTNAME}", "distname-1.0"), equals, "distname-1.0") | 12 | c.Check(pkg.pkgnameFromDistname("${DISTNAME}", "distname-1.0"), equals, "distname-1.0") | |
13 | c.Check(pkg.pkgnameFromDistname("${DISTNAME:S/dist/pkg/}", "distname-1.0"), equals, "pkgname-1.0") | 13 | c.Check(pkg.pkgnameFromDistname("${DISTNAME:S/dist/pkg/}", "distname-1.0"), equals, "pkgname-1.0") | |
14 | c.Check(pkg.pkgnameFromDistname("${DISTNAME:S|a|b|g}", "panama-0.13"), equals, "pbnbmb-0.13") | 14 | c.Check(pkg.pkgnameFromDistname("${DISTNAME:S|a|b|g}", "panama-0.13"), equals, "pbnbmb-0.13") | |
15 | c.Check(pkg.pkgnameFromDistname("${DISTNAME:S|^lib||}", "libncurses"), equals, "ncurses") | 15 | c.Check(pkg.pkgnameFromDistname("${DISTNAME:S|^lib||}", "libncurses"), equals, "ncurses") | |
16 | c.Check(pkg.pkgnameFromDistname("${DISTNAME:S|^lib||}", "mylib"), equals, "mylib") | 16 | c.Check(pkg.pkgnameFromDistname("${DISTNAME:S|^lib||}", "mylib"), equals, "mylib") | |
17 | c.Check(pkg.pkgnameFromDistname("${DISTNAME:tl:S/-/./g:S/he/-/1}", "SaxonHE9-5-0-1J"), equals, "saxon-9.5.0.1j") | |||
18 | c.Check(pkg.pkgnameFromDistname("${DISTNAME:C/beta/.0./}", "fspanel-0.8beta1"), equals, "${DISTNAME:C/beta/.0./}") | |||
19 | c.Check(pkg.pkgnameFromDistname("${DISTNAME:S/-0$/.0/1}", "aspell-af-0.50-0"), equals, "aspell-af-0.50.0") | |||
17 | 20 | |||
18 | c.Check(s.Output(), equals, "") | 21 | c.Check(s.Output(), equals, "") | |
19 | } | 22 | } | |
20 | 23 | |||
21 | func (s *Suite) TestChecklinesPackageMakefileVarorder(c *check.C) { | 24 | func (s *Suite) TestChecklinesPackageMakefileVarorder(c *check.C) { | |
22 | s.UseCommandLine(c, "-Worder") | 25 | s.UseCommandLine(c, "-Worder") | |
23 | pkg := NewPackage("x11/9term") | 26 | pkg := NewPackage("x11/9term") | |
24 | 27 | |||
25 | pkg.ChecklinesPackageMakefileVarorder(s.NewMkLines("Makefile", | 28 | pkg.ChecklinesPackageMakefileVarorder(s.NewMkLines("Makefile", | |
26 | "# $"+"NetBSD$", | 29 | "# $"+"NetBSD$", | |
27 | "", | 30 | "", | |
28 | "DISTNAME=9term", | 31 | "DISTNAME=9term", | |
29 | "CATEGORIES=x11")) | 32 | "CATEGORIES=x11")) | |
@@ -147,26 +150,39 @@ func (s *Suite) TestCheckdirPackage(c *c | @@ -147,26 +150,39 @@ func (s *Suite) TestCheckdirPackage(c *c | |||
147 | s.CreateTmpFile(c, "Makefile", ""+ | 150 | s.CreateTmpFile(c, "Makefile", ""+ | |
148 | "# $"+"NetBSD$\n") | 151 | "# $"+"NetBSD$\n") | |
149 | G.CurrentDir = s.tmpdir | 152 | G.CurrentDir = s.tmpdir | |
150 | 153 | |||
151 | checkdirPackage(s.tmpdir) | 154 | checkdirPackage(s.tmpdir) | |
152 | 155 | |||
153 | c.Check(s.Output(), equals, ""+ | 156 | c.Check(s.Output(), equals, ""+ | |
154 | "WARN: ~/Makefile: Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset. Are you sure PLIST handling is ok?\n"+ | 157 | "WARN: ~/Makefile: Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset. Are you sure PLIST handling is ok?\n"+ | |
155 | "WARN: ~/distinfo: File not found. Please run \"@BMAKE@ makesum\".\n"+ | 158 | "WARN: ~/distinfo: File not found. Please run \"@BMAKE@ makesum\".\n"+ | |
156 | "ERROR: ~/Makefile: Each package must define its LICENSE.\n"+ | 159 | "ERROR: ~/Makefile: Each package must define its LICENSE.\n"+ | |
157 | "WARN: ~/Makefile: No COMMENT given.\n") | 160 | "WARN: ~/Makefile: No COMMENT given.\n") | |
158 | } | 161 | } | |
159 | 162 | |||
163 | func (s *Suite) Test_Package_Meta_package_License(c *check.C) { | |||
164 | s.CreateTmpFileLines(c, "Makefile", | |||
165 | "# $"+"NetBSD$", | |||
166 | "", | |||
167 | "META_PACKAGE=\tyes") | |||
168 | G.CurrentDir = s.tmpdir | |||
169 | G.globalData.InitVartypes() | |||
170 | ||||
171 | checkdirPackage(s.tmpdir) | |||
172 | ||||
173 | c.Check(s.Output(), equals, "WARN: ~/Makefile: No COMMENT given.\n") // No error about missing LICENSE. | |||
174 | } | |||
175 | ||||
160 | func (s *Suite) Test_Package_Varuse_LoadTime(c *check.C) { | 176 | func (s *Suite) Test_Package_Varuse_LoadTime(c *check.C) { | |
161 | s.CreateTmpFileLines(c, "doc/CHANGES-2016", | 177 | s.CreateTmpFileLines(c, "doc/CHANGES-2016", | |
162 | "# dummy") | 178 | "# dummy") | |
163 | s.CreateTmpFileLines(c, "doc/TODO", | 179 | s.CreateTmpFileLines(c, "doc/TODO", | |
164 | "# dummy") | 180 | "# dummy") | |
165 | s.CreateTmpFileLines(c, "licenses/bsd-2", | 181 | s.CreateTmpFileLines(c, "licenses/bsd-2", | |
166 | "# dummy") | 182 | "# dummy") | |
167 | s.CreateTmpFileLines(c, "mk/fetch/sites.mk", | 183 | s.CreateTmpFileLines(c, "mk/fetch/sites.mk", | |
168 | "# dummy") | 184 | "# dummy") | |
169 | s.CreateTmpFileLines(c, "mk/bsd.pkg.mk", | 185 | s.CreateTmpFileLines(c, "mk/bsd.pkg.mk", | |
170 | "# dummy") | 186 | "# dummy") | |
171 | s.CreateTmpFileLines(c, "mk/defaults/options.description", | 187 | s.CreateTmpFileLines(c, "mk/defaults/options.description", | |
172 | "option Description") | 188 | "option Description") |
@@ -74,26 +74,54 @@ func isCommitted(fname string) bool { | @@ -74,26 +74,54 @@ func isCommitted(fname string) bool { | |||
74 | basename := path.Base(fname) | 74 | basename := path.Base(fname) | |
75 | lines, err := readLines(path.Dir(fname)+"/CVS/Entries", false) | 75 | lines, err := readLines(path.Dir(fname)+"/CVS/Entries", false) | |
76 | if err != nil { | 76 | if err != nil { | |
77 | return false | 77 | return false | |
78 | } | 78 | } | |
79 | for _, line := range lines { | 79 | for _, line := range lines { | |
80 | if hasPrefix(line.Text, "/"+basename+"/") { | 80 | if hasPrefix(line.Text, "/"+basename+"/") { | |
81 | return true | 81 | return true | |
82 | } | 82 | } | |
83 | } | 83 | } | |
84 | return false | 84 | return false | |
85 | } | 85 | } | |
86 | 86 | |||
87 | func isLocallyModified(fname string) bool { | |||
88 | basename := path.Base(fname) | |||
89 | lines, err := readLines(path.Dir(fname)+"/CVS/Entries", false) | |||
90 | if err != nil { | |||
91 | return false | |||
92 | } | |||
93 | for _, line := range lines { | |||
94 | if hasPrefix(line.Text, "/"+basename+"/") { | |||
95 | cvsModTime, err := time.Parse(time.ANSIC, strings.Split(line.Text, "/")[3]) | |||
96 | if err != nil { | |||
97 | return false | |||
98 | } | |||
99 | st, err := os.Stat(fname) | |||
100 | if err != nil { | |||
101 | return false | |||
102 | } | |||
103 | ||||
104 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290(v=vs.85).aspx | |||
105 | delta := cvsModTime.Unix() - st.ModTime().Unix() | |||
106 | if G.opts.Debug { | |||
107 | traceStep("cvs.time=%v fs.time=%v delta=%v", cvsModTime, st.ModTime(), delta) | |||
108 | } | |||
109 | return !(-2 <= delta && delta <= 2) | |||
110 | } | |||
111 | } | |||
112 | return false | |||
113 | } | |||
114 | ||||
87 | // Returns the number of columns that a string occupies when printed with | 115 | // Returns the number of columns that a string occupies when printed with | |
88 | // a tabulator size of 8. | 116 | // a tabulator size of 8. | |
89 | func tabLength(s string) int { | 117 | func tabLength(s string) int { | |
90 | length := 0 | 118 | length := 0 | |
91 | for _, r := range s { | 119 | for _, r := range s { | |
92 | if r == '\t' { | 120 | if r == '\t' { | |
93 | length = length - length%8 + 8 | 121 | length = length - length%8 + 8 | |
94 | } else { | 122 | } else { | |
95 | length++ | 123 | length++ | |
96 | } | 124 | } | |
97 | } | 125 | } | |
98 | return length | 126 | return length | |
99 | } | 127 | } | |
@@ -488,32 +516,36 @@ type Ref struct { | @@ -488,32 +516,36 @@ type Ref struct { | |||
488 | } | 516 | } | |
489 | 517 | |||
490 | func ref(rv interface{}) Ref { | 518 | func ref(rv interface{}) Ref { | |
491 | return Ref{rv} | 519 | return Ref{rv} | |
492 | } | 520 | } | |
493 | 521 | |||
494 | func (r Ref) String() string { | 522 | func (r Ref) String() string { | |
495 | ptr := reflect.ValueOf(r.intf) | 523 | ptr := reflect.ValueOf(r.intf) | |
496 | ref := reflect.Indirect(ptr) | 524 | ref := reflect.Indirect(ptr) | |
497 | return fmt.Sprintf("%v", ref) | 525 | return fmt.Sprintf("%v", ref) | |
498 | } | 526 | } | |
499 | 527 | |||
500 | // Emulates make(1)’s :S substitution operator. | 528 | // Emulates make(1)’s :S substitution operator. | |
501 | func mkopSubst(s string, left bool, from string, right bool, to string, all bool) string { | 529 | func mkopSubst(s string, left bool, from string, right bool, to string, flags string) string { | |
530 | if G.opts.Debug { | |||
531 | defer tracecall(s, left, from, right, to, flags)() | |||
532 | } | |||
502 | re := ifelseStr(left, "^", "") + regexp.QuoteMeta(from) + ifelseStr(right, "$", "") | 533 | re := ifelseStr(left, "^", "") + regexp.QuoteMeta(from) + ifelseStr(right, "$", "") | |
503 | done := false | 534 | done := false | |
535 | gflag := contains(flags, "g") | |||
504 | return regcomp(re).ReplaceAllStringFunc(s, func(match string) string { | 536 | return regcomp(re).ReplaceAllStringFunc(s, func(match string) string { | |
505 | if all || !done { | 537 | if gflag || !done { | |
506 | done = !all | 538 | done = !gflag | |
507 | return to | 539 | return to | |
508 | } | 540 | } | |
509 | return match | 541 | return match | |
510 | }) | 542 | }) | |
511 | } | 543 | } | |
512 | 544 | |||
513 | func relpath(from, to string) string { | 545 | func relpath(from, to string) string { | |
514 | absFrom, err1 := filepath.Abs(from) | 546 | absFrom, err1 := filepath.Abs(from) | |
515 | absTo, err2 := filepath.Abs(to) | 547 | absTo, err2 := filepath.Abs(to) | |
516 | rel, err3 := filepath.Rel(absFrom, absTo) | 548 | rel, err3 := filepath.Rel(absFrom, absTo) | |
517 | if err1 != nil || err2 != nil || err3 != nil { | 549 | if err1 != nil || err2 != nil || err3 != nil { | |
518 | panic("relpath" + argsStr(from, to, err1, err2, err3)) | 550 | panic("relpath" + argsStr(from, to, err1, err2, err3)) | |
519 | } | 551 | } |
@@ -1,44 +1,44 @@ | @@ -1,44 +1,44 @@ | |||
1 | package main | 1 | package main | |
2 | 2 | |||
3 | import ( | 3 | import ( | |
4 | check "gopkg.in/check.v1" | 4 | check "gopkg.in/check.v1" | |
5 | ) | 5 | ) | |
6 | 6 | |||
7 | func (s *Suite) TestMkopSubst_middle(c *check.C) { | 7 | func (s *Suite) TestMkopSubst_middle(c *check.C) { | |
8 | c.Check(mkopSubst("pkgname", false, "kgna", false, "ri", false), equals, "prime") | 8 | c.Check(mkopSubst("pkgname", false, "kgna", false, "ri", ""), equals, "prime") | |
9 | c.Check(mkopSubst("pkgname", false, "pkgname", false, "replacement", false), equals, "replacement") | 9 | c.Check(mkopSubst("pkgname", false, "pkgname", false, "replacement", ""), equals, "replacement") | |
10 | } | 10 | } | |
11 | 11 | |||
12 | func (s *Suite) TestMkopSubst_left(c *check.C) { | 12 | func (s *Suite) TestMkopSubst_left(c *check.C) { | |
13 | c.Check(mkopSubst("pkgname", true, "kgna", false, "ri", false), equals, "pkgname") | 13 | c.Check(mkopSubst("pkgname", true, "kgna", false, "ri", ""), equals, "pkgname") | |
14 | c.Check(mkopSubst("pkgname", true, "pkgname", false, "replacement", false), equals, "replacement") | 14 | c.Check(mkopSubst("pkgname", true, "pkgname", false, "replacement", ""), equals, "replacement") | |
15 | } | 15 | } | |
16 | 16 | |||
17 | func (s *Suite) TestMkopSubst_right(c *check.C) { | 17 | func (s *Suite) TestMkopSubst_right(c *check.C) { | |
18 | c.Check(mkopSubst("pkgname", false, "kgna", true, "ri", false), equals, "pkgname") | 18 | c.Check(mkopSubst("pkgname", false, "kgna", true, "ri", ""), equals, "pkgname") | |
19 | c.Check(mkopSubst("pkgname", false, "pkgname", true, "replacement", false), equals, "replacement") | 19 | c.Check(mkopSubst("pkgname", false, "pkgname", true, "replacement", ""), equals, "replacement") | |
20 | } | 20 | } | |
21 | 21 | |||
22 | func (s *Suite) TestMkopSubst_leftRight(c *check.C) { | 22 | func (s *Suite) TestMkopSubst_leftRight(c *check.C) { | |
23 | c.Check(mkopSubst("pkgname", true, "kgna", true, "ri", false), equals, "pkgname") | 23 | c.Check(mkopSubst("pkgname", true, "kgna", true, "ri", ""), equals, "pkgname") | |
24 | c.Check(mkopSubst("pkgname", false, "pkgname", false, "replacement", false), equals, "replacement") | 24 | c.Check(mkopSubst("pkgname", false, "pkgname", false, "replacement", ""), equals, "replacement") | |
25 | } | 25 | } | |
26 | 26 | |||
27 | func (s *Suite) TestMkopSubst_all(c *check.C) { | 27 | func (s *Suite) TestMkopSubst_gflag(c *check.C) { | |
28 | c.Check(mkopSubst("aaaaa", false, "a", false, "b", true), equals, "bbbbb") | 28 | c.Check(mkopSubst("aaaaa", false, "a", false, "b", "g"), equals, "bbbbb") | |
29 | c.Check(mkopSubst("aaaaa", true, "a", false, "b", true), equals, "baaaa") | 29 | c.Check(mkopSubst("aaaaa", true, "a", false, "b", "g"), equals, "baaaa") | |
30 | c.Check(mkopSubst("aaaaa", false, "a", true, "b", true), equals, "aaaab") | 30 | c.Check(mkopSubst("aaaaa", false, "a", true, "b", "g"), equals, "aaaab") | |
31 | c.Check(mkopSubst("aaaaa", true, "a", true, "b", true), equals, "aaaaa") | 31 | c.Check(mkopSubst("aaaaa", true, "a", true, "b", "g"), equals, "aaaaa") | |
32 | } | 32 | } | |
33 | 33 | |||
34 | func (s *Suite) TestReplaceFirst(c *check.C) { | 34 | func (s *Suite) TestReplaceFirst(c *check.C) { | |
35 | m, rest := replaceFirst("a+b+c+d", `(\w)(.)(\w)`, "X") | 35 | m, rest := replaceFirst("a+b+c+d", `(\w)(.)(\w)`, "X") | |
36 | 36 | |||
37 | c.Assert(m, check.NotNil) | 37 | c.Assert(m, check.NotNil) | |
38 | c.Check(m, check.DeepEquals, []string{"a+b", "a", "+", "b"}) | 38 | c.Check(m, check.DeepEquals, []string{"a+b", "a", "+", "b"}) | |
39 | c.Check(rest, equals, "X+c+d") | 39 | c.Check(rest, equals, "X+c+d") | |
40 | } | 40 | } | |
41 | 41 | |||
42 | func (s *Suite) TestTabLength(c *check.C) { | 42 | func (s *Suite) TestTabLength(c *check.C) { | |
43 | c.Check(tabLength("12345"), equals, 5) | 43 | c.Check(tabLength("12345"), equals, 5) | |
44 | c.Check(tabLength("\t"), equals, 8) | 44 | c.Check(tabLength("\t"), equals, 8) |
@@ -674,26 +674,31 @@ func (cv *VartypeCheck) PkgName() { | @@ -674,26 +674,31 @@ func (cv *VartypeCheck) PkgName() { | |||
674 | cv.line.Warn1("%q is not a valid package name. A valid package name has the form packagename-version, where version consists only of digits, letters and dots.", cv.value) | 674 | cv.line.Warn1("%q is not a valid package name. A valid package name has the form packagename-version, where version consists only of digits, letters and dots.", cv.value) | |
675 | } | 675 | } | |
676 | } | 676 | } | |
677 | 677 | |||
678 | func (cv *VartypeCheck) PkgOptionsVar() { | 678 | func (cv *VartypeCheck) PkgOptionsVar() { | |
679 | cv.mkline.CheckVartypePrimitive(cv.varname, CheckvarVarname, cv.op, cv.value, cv.comment, false, cv.guessed) | 679 | cv.mkline.CheckVartypePrimitive(cv.varname, CheckvarVarname, cv.op, cv.value, cv.comment, false, cv.guessed) | |
680 | if matches(cv.value, `\$\{PKGBASE[:\}]`) { | 680 | if matches(cv.value, `\$\{PKGBASE[:\}]`) { | |
681 | cv.line.Error0("PKGBASE must not be used in PKG_OPTIONS_VAR.") | 681 | cv.line.Error0("PKGBASE must not be used in PKG_OPTIONS_VAR.") | |
682 | Explain3( | 682 | Explain3( | |
683 | "PKGBASE is defined in bsd.pkg.mk, which is included as the", | 683 | "PKGBASE is defined in bsd.pkg.mk, which is included as the", | |
684 | "very last file, but PKG_OPTIONS_VAR is evaluated earlier.", | 684 | "very last file, but PKG_OPTIONS_VAR is evaluated earlier.", | |
685 | "Use ${PKGNAME:C/-[0-9].*//} instead.") | 685 | "Use ${PKGNAME:C/-[0-9].*//} instead.") | |
686 | } | 686 | } | |
687 | ||||
688 | // PR 46570, item "6. It should complain in PKG_OPTIONS_VAR is wrong" | |||
689 | if !hasPrefix(cv.value, "PKG_OPTIONS.") { | |||
690 | cv.line.Error2("PKG_OPTIONS_VAR must be of the form %q, not %q.", "PKG_OPTIONS.*", cv.value) | |||
691 | } | |||
687 | } | 692 | } | |
688 | 693 | |||
689 | // A directory name relative to the top-level pkgsrc directory. | 694 | // A directory name relative to the top-level pkgsrc directory. | |
690 | // Despite its name, it is more similar to RelativePkgDir than to RelativePkgPath. | 695 | // Despite its name, it is more similar to RelativePkgDir than to RelativePkgPath. | |
691 | func (cv *VartypeCheck) PkgPath() { | 696 | func (cv *VartypeCheck) PkgPath() { | |
692 | cv.mkline.CheckRelativePkgdir(G.CurPkgsrcdir + "/" + cv.value) | 697 | cv.mkline.CheckRelativePkgdir(G.CurPkgsrcdir + "/" + cv.value) | |
693 | } | 698 | } | |
694 | 699 | |||
695 | func (cv *VartypeCheck) PkgRevision() { | 700 | func (cv *VartypeCheck) PkgRevision() { | |
696 | if !matches(cv.value, `^[1-9]\d*$`) { | 701 | if !matches(cv.value, `^[1-9]\d*$`) { | |
697 | cv.line.Warn1("%s must be a positive integer number.", cv.varname) | 702 | cv.line.Warn1("%s must be a positive integer number.", cv.varname) | |
698 | } | 703 | } | |
699 | if path.Base(cv.line.Fname) != "Makefile" { | 704 | if path.Base(cv.line.Fname) != "Makefile" { |
@@ -5,25 +5,60 @@ import ( | @@ -5,25 +5,60 @@ import ( | |||
5 | ) | 5 | ) | |
6 | 6 | |||
7 | func (s *Suite) TestMkversion(c *check.C) { | 7 | func (s *Suite) TestMkversion(c *check.C) { | |
8 | c.Check(newVersion("5.0"), check.DeepEquals, &version{[]int{5, 0, 0}, 0}) | 8 | c.Check(newVersion("5.0"), check.DeepEquals, &version{[]int{5, 0, 0}, 0}) | |
9 | c.Check(newVersion("5.0nb5"), check.DeepEquals, &version{[]int{5, 0, 0}, 5}) | 9 | c.Check(newVersion("5.0nb5"), check.DeepEquals, &version{[]int{5, 0, 0}, 5}) | |
10 | c.Check(newVersion("0.0.1-SNAPSHOT"), check.DeepEquals, &version{[]int{0, 0, 0, 0, 1, 19, 14, 1, 16, 19, 8, 15, 20}, 0}) | 10 | c.Check(newVersion("0.0.1-SNAPSHOT"), check.DeepEquals, &version{[]int{0, 0, 0, 0, 1, 19, 14, 1, 16, 19, 8, 15, 20}, 0}) | |
11 | c.Check(newVersion("1.0alpha3"), check.DeepEquals, &version{[]int{1, 0, 0, -3, 3}, 0}) | 11 | c.Check(newVersion("1.0alpha3"), check.DeepEquals, &version{[]int{1, 0, 0, -3, 3}, 0}) | |
12 | c.Check(newVersion("2.5beta"), check.DeepEquals, &version{[]int{2, 0, 5, -2}, 0}) | 12 | c.Check(newVersion("2.5beta"), check.DeepEquals, &version{[]int{2, 0, 5, -2}, 0}) | |
13 | c.Check(newVersion("20151110"), check.DeepEquals, &version{[]int{20151110}, 0}) | 13 | c.Check(newVersion("20151110"), check.DeepEquals, &version{[]int{20151110}, 0}) | |
14 | c.Check(newVersion("0"), check.DeepEquals, &version{[]int{0}, 0}) | 14 | c.Check(newVersion("0"), check.DeepEquals, &version{[]int{0}, 0}) | |
15 | c.Check(newVersion("nb1"), check.DeepEquals, &version{nil, 1}) | 15 | c.Check(newVersion("nb1"), check.DeepEquals, &version{nil, 1}) | |
16 | c.Check(newVersion("1.0.1a"), deepEquals, &version{[]int{1, 0, 0, 0, 1, 1}, 0}) | 16 | c.Check(newVersion("1.0.1a"), deepEquals, &version{[]int{1, 0, 0, 0, 1, 1}, 0}) | |
17 | c.Check(newVersion("1.0.1z"), deepEquals, &version{[]int{1, 0, 0, 0, 1, 26}, 0}) | 17 | c.Check(newVersion("1.0.1z"), deepEquals, &version{[]int{1, 0, 0, 0, 1, 26}, 0}) | |
18 | c.Check(newVersion("0pre20160620"), deepEquals, &version{[]int{0, -1, 20160620}, 0}) | |||
18 | } | 19 | } | |
19 | 20 | |||
20 | func (s *Suite) TestPkgverCmp(c *check.C) { | 21 | func (s *Suite) TestPkgverCmp(c *check.C) { | |
21 | c.Check(pkgverCmp("1.0", "1.0alpha"), equals, 1) | 22 | var versions = [][]string{ | |
22 | c.Check(pkgverCmp("1.0alpha", "1.0"), equals, -1) | 23 | {"0pre20160620"}, | |
23 | c.Check(pkgverCmp("1.0nb1", "1.0"), equals, 1) | 24 | {"0"}, | |
24 | c.Check(pkgverCmp("1.0nb2", "1.0nb1"), equals, 1) | 25 | {"nb1"}, | |
25 | c.Check(pkgverCmp("2.0.1nb17", "2.0.1nb4"), equals, 1) | 26 | {"0.0.1-SNAPSHOT"}, | |
26 | c.Check(pkgverCmp("2.0.1nb4", "2.0.1nb17"), equals, -1) | 27 | {"1.0alpha"}, | |
27 | c.Check(pkgverCmp("2.0pre", "2.0rc"), equals, 0) | 28 | {"1.0alpha3"}, | |
28 | c.Check(pkgverCmp("2.0pre", "2.0pl"), equals, -1) | 29 | {"1", "1.0", "1.0.0"}, | |
30 | {"1.0nb1"}, | |||
31 | {"1.0nb2"}, | |||
32 | {"1.0.1a"}, | |||
33 | {"1.0.1z"}, | |||
34 | {"2.0pre", "2.0rc"}, | |||
35 | {"2.0", "2.0pl"}, | |||
36 | {"2.0.1nb4"}, | |||
37 | {"2.0.1nb17"}, | |||
38 | {"2.5beta"}, | |||
39 | {"5.0"}, | |||
40 | {"5.0nb5"}, | |||
41 | {"5.5", "5.005"}, | |||
42 | {"20151110"}, | |||
43 | } | |||
44 | ||||
45 | for i, iversions := range versions { | |||
46 | for _, iversion := range iversions { | |||
47 | for j, jversions := range versions { | |||
48 | for _, jversion := range jversions { | |||
49 | actual := pkgverCmp(iversion, jversion) | |||
50 | if i < j && !(actual < 0) { | |||
51 | c.Check([]interface{}{i, iversion, j, jversion, "<0"}, deepEquals, []interface{}{i, iversion, j, jversion, actual}) | |||
52 | } | |||
53 | if i == j && !(actual == 0) { | |||
54 | c.Check([]interface{}{i, iversion, j, jversion, "==0"}, deepEquals, []interface{}{i, iversion, j, jversion, actual}) | |||
55 | } | |||
56 | if i > j && !(actual > 0) { | |||
57 | c.Check([]interface{}{i, iversion, j, jversion, ">0"}, deepEquals, []interface{}{i, iversion, j, jversion, actual}) | |||
58 | } | |||
59 | } | |||
60 | } | |||
61 | ||||
62 | } | |||
63 | } | |||
29 | } | 64 | } |