pkgtools/pkglint: Update to 22.4.1 Changes since 22.4.0: Allow pkglint to be run outside the pkgsrc top directory, in order to allow individual makefiles to be formatted and checked as well. The many pkgsrc-specific checks don't apply in this case. This mode is experimental, there are still crashes to be expected. Fix a crash for syntactically invalid conditions in '.if'. Fix parsing of dependency lines that contain an escaped '#', which is used for switching to another shell using a '.SHELL: ...' line.diff -r1.739 -r1.740 pkgsrc/pkgtools/pkglint/Makefile
(rillig)
@@ -1,17 +1,16 @@ | @@ -1,17 +1,16 @@ | |||
1 | # $NetBSD: Makefile,v 1.739 2023/02/16 15:02:04 bsiegert Exp $ | 1 | # $NetBSD: Makefile,v 1.740 2023/03/02 08:58:28 rillig Exp $ | |
2 | 2 | |||
3 | PKGNAME= pkglint-22.4.0 | 3 | PKGNAME= pkglint-22.4.1 | |
4 | PKGREVISION= 1 | |||
5 | CATEGORIES= pkgtools | 4 | CATEGORIES= pkgtools | |
6 | 5 | |||
7 | MAINTAINER= rillig@NetBSD.org | 6 | MAINTAINER= rillig@NetBSD.org | |
8 | HOMEPAGE= https://github.com/rillig/pkglint | 7 | HOMEPAGE= https://github.com/rillig/pkglint | |
9 | COMMENT= Verifier for NetBSD packages | 8 | COMMENT= Verifier for NetBSD packages | |
10 | LICENSE= 2-clause-bsd | 9 | LICENSE= 2-clause-bsd | |
11 | CONFLICTS+= pkglint4-[0-9]* | 10 | CONFLICTS+= pkglint4-[0-9]* | |
12 | 11 | |||
13 | USE_TOOLS+= pax | 12 | USE_TOOLS+= pax | |
14 | CHECK_RELRO_SKIP+= bin/pkglint | 13 | CHECK_RELRO_SKIP+= bin/pkglint | |
15 | 14 | |||
16 | 15 | |||
17 | SUBST_CLASSES+= pkglint | 16 | SUBST_CLASSES+= pkglint |
@@ -147,30 +147,33 @@ func Test__qa(t *testing.T) { | @@ -147,30 +147,33 @@ func Test__qa(t *testing.T) { | |||
147 | // They are tested indirectly by the tests of their corresponding checks. | 147 | // They are tested indirectly by the tests of their corresponding checks. | |
148 | ck.Configure("*", "*", "warn[A-Z]*", -intqa.EMissingTest) | 148 | ck.Configure("*", "*", "warn[A-Z]*", -intqa.EMissingTest) | |
149 | 149 | |||
150 | // Generated code doesn't need a unit test. | 150 | // Generated code doesn't need a unit test. | |
151 | // If any, every grammar production in the corresponding yacc file | 151 | // If any, every grammar production in the corresponding yacc file | |
152 | // should have a unit test. | 152 | // should have a unit test. | |
153 | ck.Configure("*yacc.go", "*", "*", intqa.ENone) | 153 | ck.Configure("*yacc.go", "*", "*", intqa.ENone) | |
154 | 154 | |||
155 | // Type definitions don't need a unit test. | 155 | // Type definitions don't need a unit test. | |
156 | // Only functions and methods do. | 156 | // Only functions and methods do. | |
157 | ck.Configure("*", "*", "", -intqa.EMissingTest) | 157 | ck.Configure("*", "*", "", -intqa.EMissingTest) | |
158 | 158 | |||
159 | // The Suite type is used for testing all parts of pkglint. | 159 | // The Suite type is used for testing all parts of pkglint. | |
160 | // Therefore its test methods may be everywhere. | 160 | // Therefore, its test methods may be everywhere. | |
161 | ck.Configure("*.go", "Suite", "*", -intqa.EMethodsSameFile) | 161 | ck.Configure("*.go", "Suite", "*", -intqa.EMethodsSameFile) | |
162 | ck.Configure("*.go", "Tester", "*", -intqa.EMethodsSameFile) | 162 | ck.Configure("*.go", "Tester", "*", -intqa.EMethodsSameFile) | |
163 | 163 | |||
164 | // When running gobco, it inserts the function 'GobcoCover'. | |||
165 | ck.Configure("*", "", "GobcoCover", -intqa.EMissingTest) | |||
166 | ||||
164 | ck.Check() | 167 | ck.Check() | |
165 | } | 168 | } | |
166 | 169 | |||
167 | var _ = check.Suite(new(Suite)) | 170 | var _ = check.Suite(new(Suite)) | |
168 | 171 | |||
169 | func Test(t *testing.T) { check.TestingT(t) } | 172 | func Test(t *testing.T) { check.TestingT(t) } | |
170 | 173 | |||
171 | // Tester provides utility methods for testing pkglint. | 174 | // Tester provides utility methods for testing pkglint. | |
172 | // It is separated from the Suite since the latter contains | 175 | // It is separated from the Suite since the latter contains | |
173 | // all the test methods, which makes it difficult to find | 176 | // all the test methods, which makes it difficult to find | |
174 | // a method by auto-completion. | 177 | // a method by auto-completion. | |
175 | type Tester struct { | 178 | type Tester struct { | |
176 | c *check.C // Only usable during the test method itself | 179 | c *check.C // Only usable during the test method itself | |
@@ -218,27 +221,27 @@ func (t *Tester) SetUpCommandLine(args . | @@ -218,27 +221,27 @@ func (t *Tester) SetUpCommandLine(args . | |||
218 | G.Logger.verbose = true | 221 | G.Logger.verbose = true | |
219 | 222 | |||
220 | t.seenSetUpCommandLine = true | 223 | t.seenSetUpCommandLine = true | |
221 | } | 224 | } | |
222 | 225 | |||
223 | // SetUpVartypes registers a few hundred variables like MASTER_SITES, | 226 | // SetUpVartypes registers a few hundred variables like MASTER_SITES, | |
224 | // WRKSRC, SUBST_SED.*, so that their data types are known to pkglint. | 227 | // WRKSRC, SUBST_SED.*, so that their data types are known to pkglint. | |
225 | // | 228 | // | |
226 | // Without calling this, there will be many warnings about undefined | 229 | // Without calling this, there will be many warnings about undefined | |
227 | // or unused variables, or unknown shell commands. | 230 | // or unused variables, or unknown shell commands. | |
228 | // | 231 | // | |
229 | // See SetUpTool for registering tools like echo, awk, perl. | 232 | // See SetUpTool for registering tools like echo, awk, perl. | |
230 | func (t *Tester) SetUpVartypes() { | 233 | func (t *Tester) SetUpVartypes() { | |
231 | G.Pkgsrc.vartypes.Init(&G.Pkgsrc) | 234 | G.Pkgsrc.vartypes.Init(G.Pkgsrc) | |
232 | } | 235 | } | |
233 | 236 | |||
234 | func (t *Tester) SetUpMasterSite(varname string, urls ...string) { | 237 | func (t *Tester) SetUpMasterSite(varname string, urls ...string) { | |
235 | if !G.Pkgsrc.vartypes.IsDefinedExact(varname) { | 238 | if !G.Pkgsrc.vartypes.IsDefinedExact(varname) { | |
236 | t.SetUpVarType(varname, BtFetchURL, | 239 | t.SetUpVarType(varname, BtFetchURL, | |
237 | List|SystemProvided, | 240 | List|SystemProvided, | |
238 | "buildlink3.mk: none", | 241 | "buildlink3.mk: none", | |
239 | "*: use") | 242 | "*: use") | |
240 | } | 243 | } | |
241 | 244 | |||
242 | for _, url := range urls { | 245 | for _, url := range urls { | |
243 | G.Pkgsrc.registerMasterSite(varname, url) | 246 | G.Pkgsrc.registerMasterSite(varname, url) | |
244 | } | 247 | } | |
@@ -1088,32 +1091,34 @@ func (t *Tester) NewLinesAt(filename Cur | @@ -1088,32 +1091,34 @@ func (t *Tester) NewLinesAt(filename Cur | |||
1088 | // not exist." because an intermediate directory in the path does not exist. | 1091 | // not exist." because an intermediate directory in the path does not exist. | |
1089 | // | 1092 | // | |
1090 | // If the filename is irrelevant for the particular test, just use filename.mk. | 1093 | // If the filename is irrelevant for the particular test, just use filename.mk. | |
1091 | func (t *Tester) NewMkLines(filename CurrPath, lines ...string) *MkLines { | 1094 | func (t *Tester) NewMkLines(filename CurrPath, lines ...string) *MkLines { | |
1092 | return t.NewMkLinesPkg(filename, nil, lines...) | 1095 | return t.NewMkLinesPkg(filename, nil, lines...) | |
1093 | } | 1096 | } | |
1094 | 1097 | |||
1095 | func (t *Tester) NewMkLinesPkg(filename CurrPath, pkg *Package, lines ...string) *MkLines { | 1098 | func (t *Tester) NewMkLinesPkg(filename CurrPath, pkg *Package, lines ...string) *MkLines { | |
1096 | basename := filename.Base() | 1099 | basename := filename.Base() | |
1097 | assertf( | 1100 | assertf( | |
1098 | basename.HasSuffixText(".mk") || basename == "Makefile" || basename.HasPrefixText("Makefile."), | 1101 | basename.HasSuffixText(".mk") || basename == "Makefile" || basename.HasPrefixText("Makefile."), | |
1099 | "filename %q must be realistic, otherwise the variable permissions are wrong", filename) | 1102 | "filename %q must be realistic, otherwise the variable permissions are wrong", filename) | |
1100 | 1103 | |||
1101 | var rawText strings.Builder | 1104 | var sb strings.Builder | |
1102 | for _, line := range lines { | 1105 | for _, line := range lines { | |
1103 | rawText.WriteString(line) | 1106 | sb.WriteString(line) | |
1104 | rawText.WriteString("\n") | 1107 | sb.WriteString("\n") | |
1105 | } | 1108 | } | |
1106 | return NewMkLines(convertToLogicalLines(filename, rawText.String(), true), pkg, nil) | 1109 | rawText := sb.String() | |
1110 | logicalLines := convertToLogicalLines(filename, rawText, true) | |||
1111 | return NewMkLines(logicalLines, pkg, nil) | |||
1107 | } | 1112 | } | |
1108 | 1113 | |||
1109 | // Returns and consumes the output from both stdout and stderr. | 1114 | // Returns and consumes the output from both stdout and stderr. | |
1110 | // In the output, the temporary directory is replaced with a tilde (~). | 1115 | // In the output, the temporary directory is replaced with a tilde (~). | |
1111 | func (t *Tester) Output() string { | 1116 | func (t *Tester) Output() string { | |
1112 | stdout := t.stdout.String() | 1117 | stdout := t.stdout.String() | |
1113 | stderr := t.stderr.String() | 1118 | stderr := t.stderr.String() | |
1114 | 1119 | |||
1115 | t.stdout.Reset() | 1120 | t.stdout.Reset() | |
1116 | t.stderr.Reset() | 1121 | t.stderr.Reset() | |
1117 | if G.isUsable() { | 1122 | if G.isUsable() { | |
1118 | G.Logger.logged = Once{} | 1123 | G.Logger.logged = Once{} | |
1119 | if G.Logger.out != nil { // Necessary because Main resets the G variable. | 1124 | if G.Logger.out != nil { // Necessary because Main resets the G variable. |
@@ -49,26 +49,29 @@ func Load(filename CurrPath, options Loa | @@ -49,26 +49,29 @@ func Load(filename CurrPath, options Loa | |||
49 | } | 49 | } | |
50 | 50 | |||
51 | if G.Profiling { | 51 | if G.Profiling { | |
52 | G.loaded.Add(filename.Clean().String(), 1) | 52 | G.loaded.Add(filename.Clean().String(), 1) | |
53 | } | 53 | } | |
54 | 54 | |||
55 | result := convertToLogicalLines(filename, rawText, options&Makefile != 0) | 55 | result := convertToLogicalLines(filename, rawText, options&Makefile != 0) | |
56 | if filename.HasSuffixText(".mk") { | 56 | if filename.HasSuffixText(".mk") { | |
57 | G.fileCache.Put(filename, options, result) | 57 | G.fileCache.Put(filename, options, result) | |
58 | } | 58 | } | |
59 | return result | 59 | return result | |
60 | } | 60 | } | |
61 | 61 | |||
62 | // convertToLogicalLines splits the raw text into lines. | |||
63 | // If joinBackslashes is true, lines that end with an odd number of backslashes | |||
64 | // are joined with the following line. | |||
62 | func convertToLogicalLines(filename CurrPath, rawText string, joinBackslashLines bool) *Lines { | 65 | func convertToLogicalLines(filename CurrPath, rawText string, joinBackslashLines bool) *Lines { | |
63 | var rawLines []*RawLine | 66 | var rawLines []*RawLine | |
64 | for _, rawLine := range strings.SplitAfter(rawText, "\n") { | 67 | for _, rawLine := range strings.SplitAfter(rawText, "\n") { | |
65 | if rawLine != "" { | 68 | if rawLine != "" { | |
66 | rawLines = append(rawLines, &RawLine{rawLine}) | 69 | rawLines = append(rawLines, &RawLine{rawLine}) | |
67 | } | 70 | } | |
68 | } | 71 | } | |
69 | 72 | |||
70 | var loglines []*Line | 73 | var loglines []*Line | |
71 | if joinBackslashLines { | 74 | if joinBackslashLines { | |
72 | for lineno := 0; lineno < len(rawLines); { | 75 | for lineno := 0; lineno < len(rawLines); { | |
73 | line, nextLineno := nextLogicalLine(filename, rawLines, lineno) | 76 | line, nextLineno := nextLogicalLine(filename, rawLines, lineno) | |
74 | loglines = append(loglines, line) | 77 | loglines = append(loglines, line) |
@@ -19,36 +19,40 @@ func NewMkAssignChecker(mkLine *MkLine, | @@ -19,36 +19,40 @@ func NewMkAssignChecker(mkLine *MkLine, | |||
19 | func (ck *MkAssignChecker) check() { | 19 | func (ck *MkAssignChecker) check() { | |
20 | ck.checkLeft() | 20 | ck.checkLeft() | |
21 | ck.checkOp() | 21 | ck.checkOp() | |
22 | ck.checkRight() | 22 | ck.checkRight() | |
23 | } | 23 | } | |
24 | 24 | |||
25 | // checkLeft checks everything to the left of the assignment operator. | 25 | // checkLeft checks everything to the left of the assignment operator. | |
26 | func (ck *MkAssignChecker) checkLeft() { | 26 | func (ck *MkAssignChecker) checkLeft() { | |
27 | varname := ck.MkLine.Varname() | 27 | varname := ck.MkLine.Varname() | |
28 | if !ck.mayBeDefined(varname) { | 28 | if !ck.mayBeDefined(varname) { | |
29 | ck.MkLine.Warnf("Variable names starting with an underscore (%s) are reserved for internal pkgsrc use.", varname) | 29 | ck.MkLine.Warnf("Variable names starting with an underscore (%s) are reserved for internal pkgsrc use.", varname) | |
30 | } | 30 | } | |
31 | 31 | |||
32 | if G.Pkgsrc == nil { | |||
33 | goto checkVarUse | |||
34 | } | |||
32 | ck.checkLeftNotUsed() | 35 | ck.checkLeftNotUsed() | |
33 | ck.checkLeftOpsys() | 36 | ck.checkLeftOpsys() | |
34 | ck.checkLeftDeprecated() | 37 | ck.checkLeftDeprecated() | |
35 | ck.checkLeftBsdPrefs() | 38 | ck.checkLeftBsdPrefs() | |
36 | if !ck.checkLeftUserSettable() { | 39 | if !ck.checkLeftUserSettable() { | |
37 | ck.checkLeftPermissions() | 40 | ck.checkLeftPermissions() | |
38 | ck.checkLeftAbiDepends() | 41 | ck.checkLeftAbiDepends() | |
39 | } | 42 | } | |
40 | ck.checkLeftRationale() | 43 | ck.checkLeftRationale() | |
41 | 44 | |||
45 | checkVarUse: | |||
42 | NewMkLineChecker(ck.MkLines, ck.MkLine).checkTextVarUse( | 46 | NewMkLineChecker(ck.MkLines, ck.MkLine).checkTextVarUse( | |
43 | varname, | 47 | varname, | |
44 | NewVartype(BtVariableName, NoVartypeOptions, NewACLEntry("*", aclpAll)), | 48 | NewVartype(BtVariableName, NoVartypeOptions, NewACLEntry("*", aclpAll)), | |
45 | VucLoadTime) | 49 | VucLoadTime) | |
46 | } | 50 | } | |
47 | 51 | |||
48 | // checkLeftNotUsed checks whether the left-hand side of a variable | 52 | // checkLeftNotUsed checks whether the left-hand side of a variable | |
49 | // assignment is not used. If it is unused and also doesn't have a predefined | 53 | // assignment is not used. If it is unused and also doesn't have a predefined | |
50 | // data type, it may be a spelling mistake. | 54 | // data type, it may be a spelling mistake. | |
51 | func (ck *MkAssignChecker) checkLeftNotUsed() { | 55 | func (ck *MkAssignChecker) checkLeftNotUsed() { | |
52 | varname := ck.MkLine.Varname() | 56 | varname := ck.MkLine.Varname() | |
53 | varcanon := varnameCanon(varname) | 57 | varcanon := varnameCanon(varname) | |
54 | 58 | |||
@@ -689,27 +693,27 @@ func (ck *MkAssignChecker) checkVaruseSh | @@ -689,27 +693,27 @@ func (ck *MkAssignChecker) checkVaruseSh | |||
689 | vuc := VarUseContext{vartype, time, atom.Quoting.ToVarUseContext(), wordPart} | 693 | vuc := VarUseContext{vartype, time, atom.Quoting.ToVarUseContext(), wordPart} | |
690 | NewMkVarUseChecker(varuse, ck.MkLines, mkline).Check(&vuc) | 694 | NewMkVarUseChecker(varuse, ck.MkLines, mkline).Check(&vuc) | |
691 | } | 695 | } | |
692 | } | 696 | } | |
693 | } | 697 | } | |
694 | 698 | |||
695 | func (ck *MkAssignChecker) mayBeDefined(varname string) bool { | 699 | func (ck *MkAssignChecker) mayBeDefined(varname string) bool { | |
696 | if !hasPrefix(varname, "_") { | 700 | if !hasPrefix(varname, "_") { | |
697 | return true | 701 | return true | |
698 | } | 702 | } | |
699 | if G.Infrastructure { | 703 | if G.Infrastructure { | |
700 | return true | 704 | return true | |
701 | } | 705 | } | |
702 | if G.Pkgsrc.vartypes.Canon(varname) != nil { | 706 | if G.Pkgsrc == nil || G.Pkgsrc.vartypes.Canon(varname) != nil { | |
703 | return true | 707 | return true | |
704 | } | 708 | } | |
705 | 709 | |||
706 | // Defining the group 'cmake' allows the variable names '_CMAKE_*', | 710 | // Defining the group 'cmake' allows the variable names '_CMAKE_*', | |
707 | // it's kind of a namespace declaration. | 711 | // it's kind of a namespace declaration. | |
708 | vargroups := ck.MkLines.allVars.FirstDefinition("_VARGROUPS") | 712 | vargroups := ck.MkLines.allVars.FirstDefinition("_VARGROUPS") | |
709 | if vargroups != nil { | 713 | if vargroups != nil { | |
710 | prefix := "_" + strings.ToUpper(vargroups.Value()) + "_" | 714 | prefix := "_" + strings.ToUpper(vargroups.Value()) + "_" | |
711 | if hasPrefix(varname, prefix) { | 715 | if hasPrefix(varname, prefix) { | |
712 | return true | 716 | return true | |
713 | } | 717 | } | |
714 | } | 718 | } | |
715 | 719 |
@@ -15,27 +15,27 @@ type MkCondChecker struct { | @@ -15,27 +15,27 @@ type MkCondChecker struct { | |||
15 | 15 | |||
16 | func NewMkCondChecker(mkLine *MkLine, mkLines *MkLines) *MkCondChecker { | 16 | func NewMkCondChecker(mkLine *MkLine, mkLines *MkLines) *MkCondChecker { | |
17 | return &MkCondChecker{MkLine: mkLine, MkLines: mkLines} | 17 | return &MkCondChecker{MkLine: mkLine, MkLines: mkLines} | |
18 | } | 18 | } | |
19 | 19 | |||
20 | func (ck *MkCondChecker) Check() { | 20 | func (ck *MkCondChecker) Check() { | |
21 | mkline := ck.MkLine | 21 | mkline := ck.MkLine | |
22 | if trace.Tracing { | 22 | if trace.Tracing { | |
23 | defer trace.Call1(mkline.Args())() | 23 | defer trace.Call1(mkline.Args())() | |
24 | } | 24 | } | |
25 | 25 | |||
26 | p := NewMkParser(nil, mkline.Args()) // No emitWarnings here, see the code below. | 26 | p := NewMkParser(nil, mkline.Args()) // No emitWarnings here, see the code below. | |
27 | cond := p.MkCond() | 27 | cond := p.MkCond() | |
28 | if !p.EOF() { | 28 | if !p.EOF() || cond == nil { | |
29 | mkline.Warnf("Invalid condition, unrecognized part: %q.", p.Rest()) | 29 | mkline.Warnf("Invalid condition, unrecognized part: %q.", p.Rest()) | |
30 | return | 30 | return | |
31 | } | 31 | } | |
32 | 32 | |||
33 | checkVarUse := func(varuse *MkVarUse) { | 33 | checkVarUse := func(varuse *MkVarUse) { | |
34 | var vartype *Vartype // TODO: Insert a better type guess here. | 34 | var vartype *Vartype // TODO: Insert a better type guess here. | |
35 | // See Test_MkVarUseChecker_checkAssignable__shell_command_in_exists. | 35 | // See Test_MkVarUseChecker_checkAssignable__shell_command_in_exists. | |
36 | vuc := VarUseContext{vartype, VucLoadTime, VucQuotPlain, false} | 36 | vuc := VarUseContext{vartype, VucLoadTime, VucQuotPlain, false} | |
37 | NewMkVarUseChecker(varuse, ck.MkLines, mkline).Check(&vuc) | 37 | NewMkVarUseChecker(varuse, ck.MkLines, mkline).Check(&vuc) | |
38 | } | 38 | } | |
39 | 39 | |||
40 | // Skip subconditions that have already been handled as part of the !(...). | 40 | // Skip subconditions that have already been handled as part of the !(...). | |
41 | done := make(map[interface{}]bool) | 41 | done := make(map[interface{}]bool) | |
@@ -155,27 +155,27 @@ func (ck *MkCondChecker) checkEmptyType( | @@ -155,27 +155,27 @@ func (ck *MkCondChecker) checkEmptyType( | |||
155 | // that may be used without surrounding quotes in a comparison such as | 155 | // that may be used without surrounding quotes in a comparison such as | |
156 | // ${PKGPATH} == category/package. | 156 | // ${PKGPATH} == category/package. | |
157 | // TODO: Check whether the ',' really needs to be here. | 157 | // TODO: Check whether the ',' really needs to be here. | |
158 | var mkCondStringLiteralUnquoted = textproc.NewByteSet("-+,./0-9@A-Z_a-z") | 158 | var mkCondStringLiteralUnquoted = textproc.NewByteSet("-+,./0-9@A-Z_a-z") | |
159 | 159 | |||
160 | // mkCondModifierPatternLiteral contains a safe subset of the characters | 160 | // mkCondModifierPatternLiteral contains a safe subset of the characters | |
161 | // that are interpreted literally in the :M and :N modifiers. | 161 | // that are interpreted literally in the :M and :N modifiers. | |
162 | // TODO: Check whether the ',' really needs to be here. | 162 | // TODO: Check whether the ',' really needs to be here. | |
163 | var mkCondModifierPatternLiteral = textproc.NewByteSet("-+,./0-9<=>@A-Z_a-z") | 163 | var mkCondModifierPatternLiteral = textproc.NewByteSet("-+,./0-9<=>@A-Z_a-z") | |
164 | 164 | |||
165 | func (ck *MkCondChecker) checkCompare(left *MkCondTerm, op string, right *MkCondTerm) { | 165 | func (ck *MkCondChecker) checkCompare(left *MkCondTerm, op string, right *MkCondTerm) { | |
166 | switch { | 166 | switch { | |
167 | case right.Num != "": | 167 | case right.Num != "": | |
168 | ck.checkCompareVarNum(left, op, right.Num) | 168 | ck.checkCompareWithNum(left, op, right.Num) | |
169 | case left.Var != nil && right.Var == nil: | 169 | case left.Var != nil && right.Var == nil: | |
170 | ck.checkCompareVarStr(left.Var, op, right.Str) | 170 | ck.checkCompareVarStr(left.Var, op, right.Str) | |
171 | } | 171 | } | |
172 | } | 172 | } | |
173 | 173 | |||
174 | func (ck *MkCondChecker) checkCompareVarStr(varuse *MkVarUse, op string, str string) { | 174 | func (ck *MkCondChecker) checkCompareVarStr(varuse *MkVarUse, op string, str string) { | |
175 | varname := varuse.varname | 175 | varname := varuse.varname | |
176 | varmods := varuse.modifiers | 176 | varmods := varuse.modifiers | |
177 | switch len(varmods) { | 177 | switch len(varmods) { | |
178 | case 0: | 178 | case 0: | |
179 | mkLineChecker := NewMkLineChecker(ck.MkLines, ck.MkLine) | 179 | mkLineChecker := NewMkLineChecker(ck.MkLines, ck.MkLine) | |
180 | mkLineChecker.checkVartype(varname, opUseCompare, str, "") | 180 | mkLineChecker.checkVartype(varname, opUseCompare, str, "") | |
181 | 181 | |||
@@ -199,54 +199,54 @@ func (ck *MkCondChecker) checkCompareVar | @@ -199,54 +199,54 @@ func (ck *MkCondChecker) checkCompareVar | |||
199 | // This case covers ${VAR:Mfilter:O:u} or similar uses in conditions. | 199 | // This case covers ${VAR:Mfilter:O:u} or similar uses in conditions. | |
200 | // To check these properly, pkglint first needs to know the most common | 200 | // To check these properly, pkglint first needs to know the most common | |
201 | // modifiers and how they interact. | 201 | // modifiers and how they interact. | |
202 | // As of March 2019, the modifiers are not modeled. | 202 | // As of March 2019, the modifiers are not modeled. | |
203 | // The following tracing statement makes it easy to discover these cases, | 203 | // The following tracing statement makes it easy to discover these cases, | |
204 | // in order to decide whether checking them is worthwhile. | 204 | // in order to decide whether checking them is worthwhile. | |
205 | if trace.Tracing { | 205 | if trace.Tracing { | |
206 | trace.Stepf("checkCompareVarStr ${%s%s} %s %s", | 206 | trace.Stepf("checkCompareVarStr ${%s%s} %s %s", | |
207 | varuse.varname, varuse.Mod(), op, str) | 207 | varuse.varname, varuse.Mod(), op, str) | |
208 | } | 208 | } | |
209 | } | 209 | } | |
210 | } | 210 | } | |
211 | 211 | |||
212 | func (ck *MkCondChecker) checkCompareVarNum(left *MkCondTerm, op string, num string) { | 212 | func (ck *MkCondChecker) checkCompareWithNum(left *MkCondTerm, op string, num string) { | |
213 | ck.checkCompareVarNumVersion(op, num) | 213 | ck.checkCompareWithNumVersion(op, num) | |
214 | ck.checkCompareVarNumPython(left, op, num) | 214 | ck.checkCompareWithNumPython(left, op, num) | |
215 | } | 215 | } | |
216 | 216 | |||
217 | func (ck *MkCondChecker) checkCompareVarNumVersion(op string, num string) { | 217 | func (ck *MkCondChecker) checkCompareWithNumVersion(op string, num string) { | |
218 | if !contains(num, ".") { | 218 | if !contains(num, ".") { | |
219 | return | 219 | return | |
220 | } | 220 | } | |
221 | 221 | |||
222 | mkline := ck.MkLine | 222 | mkline := ck.MkLine | |
223 | mkline.Warnf("Numeric comparison %s %s.", op, num) | 223 | mkline.Warnf("Numeric comparison %s %s.", op, num) | |
224 | mkline.Explain( | 224 | mkline.Explain( | |
225 | "The numeric comparison of bmake is not suitable for version numbers", | 225 | "The numeric comparison of bmake is not suitable for version numbers", | |
226 | "since 5.1 == 5.10 == 5.1000000.", | 226 | "since 5.1 == 5.10 == 5.1000000.", | |
227 | "", | 227 | "", | |
228 | "To fix this, either enclose the number in double quotes,", | 228 | "To fix this, either enclose the number in double quotes,", | |
229 | "or use pattern matching:", | 229 | "or use pattern matching:", | |
230 | "", | 230 | "", | |
231 | "\t${OS_VERSION} == \"6.5\"", | 231 | "\t${OS_VERSION} == \"6.5\"", | |
232 | "\t${OS_VERSION:M1.[1-9]} || ${OS_VERSION:M1.[1-9].*}", | 232 | "\t${OS_VERSION:M1.[1-9]} || ${OS_VERSION:M1.[1-9].*}", | |
233 | "", | 233 | "", | |
234 | "The second example needs to be split into two parts", | 234 | "The second example needs to be split into two parts", | |
235 | "since with a single comparison of the form ${OS_VERSION:M1.[1-9]*},", | 235 | "since with a single comparison of the form ${OS_VERSION:M1.[1-9]*},", | |
236 | "the version number 1.11 would also match, which is not intended.") | 236 | "the version number 1.11 would also match, which is not intended.") | |
237 | } | 237 | } | |
238 | 238 | |||
239 | func (ck *MkCondChecker) checkCompareVarNumPython(left *MkCondTerm, op string, num string) { | 239 | func (ck *MkCondChecker) checkCompareWithNumPython(left *MkCondTerm, op string, num string) { | |
240 | if left.Var != nil && left.Var.varname == "_PYTHON_VERSION" && | 240 | if left.Var != nil && left.Var.varname == "_PYTHON_VERSION" && | |
241 | op != "==" && op != "!=" && | 241 | op != "==" && op != "!=" && | |
242 | matches(num, `^\d+$`) { | 242 | matches(num, `^\d+$`) { | |
243 | 243 | |||
244 | ck.MkLine.Errorf("The Python version must not be compared numerically.") | 244 | ck.MkLine.Errorf("The Python version must not be compared numerically.") | |
245 | ck.MkLine.Explain( | 245 | ck.MkLine.Explain( | |
246 | "The variable _PYTHON_VERSION must not be compared", | 246 | "The variable _PYTHON_VERSION must not be compared", | |
247 | "against an integer number, as these comparisons are", | 247 | "against an integer number, as these comparisons are", | |
248 | "not meaningful.", | 248 | "not meaningful.", | |
249 | "For example, 27 < 39 < 40 < 41 < 310, which means that", | 249 | "For example, 27 < 39 < 40 < 41 < 310, which means that", | |
250 | "Python 3.10 would be considered newer than a", | 250 | "Python 3.10 would be considered newer than a", | |
251 | "possible future Python 4.0.", | 251 | "possible future Python 4.0.", | |
252 | "", | 252 | "", | |
@@ -279,37 +279,41 @@ func (ck *MkCondChecker) checkCompareVar | @@ -279,37 +279,41 @@ func (ck *MkCondChecker) checkCompareVar | |||
279 | func (ck *MkCondChecker) checkNotCompare(not *MkCond) { | 279 | func (ck *MkCondChecker) checkNotCompare(not *MkCond) { | |
280 | if not.Compare == nil { | 280 | if not.Compare == nil { | |
281 | return | 281 | return | |
282 | } | 282 | } | |
283 | 283 | |||
284 | ck.MkLine.Warnf("The ! should use parentheses or be merged into the comparison operator.") | 284 | ck.MkLine.Warnf("The ! should use parentheses or be merged into the comparison operator.") | |
285 | } | 285 | } | |
286 | 286 | |||
287 | func (ck *MkCondChecker) checkContradictions() { | 287 | func (ck *MkCondChecker) checkContradictions() { | |
288 | mkline := ck.MkLine | 288 | mkline := ck.MkLine | |
289 | 289 | |||
290 | byVarname := make(map[string][]VarFact) | 290 | byVarname := make(map[string][]VarFact) | |
291 | levels := ck.MkLines.indentation.levels | 291 | levels := ck.MkLines.indentation.levels | |
292 | if len(levels) == 0 { | |||
293 | goto skip // For .elif outside .if | |||
294 | } | |||
292 | for _, level := range levels[:len(levels)-1] { | 295 | for _, level := range levels[:len(levels)-1] { | |
293 | if !level.mkline.NeedsCond() { | 296 | if !level.mkline.NeedsCond() { | |
294 | continue | 297 | continue | |
295 | } | 298 | } | |
296 | prevFacts := ck.collectFacts(level.mkline) | 299 | prevFacts := ck.collectFacts(level.mkline) | |
297 | for _, prevFact := range prevFacts { | 300 | for _, prevFact := range prevFacts { | |
298 | varname := prevFact.Varname | 301 | varname := prevFact.Varname | |
299 | byVarname[varname] = append(byVarname[varname], prevFact) | 302 | byVarname[varname] = append(byVarname[varname], prevFact) | |
300 | } | 303 | } | |
301 | } | 304 | } | |
302 | 305 | |||
306 | skip: | |||
303 | facts := ck.collectFacts(mkline) | 307 | facts := ck.collectFacts(mkline) | |
304 | for _, curr := range facts { | 308 | for _, curr := range facts { | |
305 | varname := curr.Varname | 309 | varname := curr.Varname | |
306 | for _, prev := range byVarname[varname] { | 310 | for _, prev := range byVarname[varname] { | |
307 | both := makepat.Intersect(prev.Matches, curr.Matches) | 311 | both := makepat.Intersect(prev.Matches, curr.Matches) | |
308 | if !both.CanMatch() { | 312 | if !both.CanMatch() { | |
309 | if prev.MkLine != mkline { | 313 | if prev.MkLine != mkline { | |
310 | mkline.Errorf("The patterns %q from %s and %q cannot match at the same time.", | 314 | mkline.Errorf("The patterns %q from %s and %q cannot match at the same time.", | |
311 | prev.Text, mkline.RelMkLine(prev.MkLine), curr.Text) | 315 | prev.Text, mkline.RelMkLine(prev.MkLine), curr.Text) | |
312 | } else { | 316 | } else { | |
313 | mkline.Errorf("The patterns %q and %q cannot match at the same time.", | 317 | mkline.Errorf("The patterns %q and %q cannot match at the same time.", | |
314 | prev.Text, curr.Text) | 318 | prev.Text, curr.Text) | |
315 | } | 319 | } |
@@ -119,26 +119,33 @@ func (s *Suite) Test_MkCondChecker_Check | @@ -119,26 +119,33 @@ func (s *Suite) Test_MkCondChecker_Check | |||
119 | 119 | |||
120 | test(".if ${VAR} == 3", | 120 | test(".if ${VAR} == 3", | |
121 | "WARN: filename.mk:4: VAR is used but not defined.") | 121 | "WARN: filename.mk:4: VAR is used but not defined.") | |
122 | 122 | |||
123 | test(".if \"value\" == ${VAR}", | 123 | test(".if \"value\" == ${VAR}", | |
124 | "WARN: filename.mk:4: VAR is used but not defined.") | 124 | "WARN: filename.mk:4: VAR is used but not defined.") | |
125 | 125 | |||
126 | test(".if ${MASTER_SITES:Mftp://*} == \"ftp://netbsd.org/\"", | 126 | test(".if ${MASTER_SITES:Mftp://*} == \"ftp://netbsd.org/\"", | |
127 | // XXX: duplicate diagnostic, see MkParser.MkCond. | 127 | // XXX: duplicate diagnostic, see MkParser.MkCond. | |
128 | "WARN: filename.mk:4: Invalid variable modifier \"//*\" for \"MASTER_SITES\".", | 128 | "WARN: filename.mk:4: Invalid variable modifier \"//*\" for \"MASTER_SITES\".", | |
129 | "WARN: filename.mk:4: Invalid variable modifier \"//*\" for \"MASTER_SITES\".", | 129 | "WARN: filename.mk:4: Invalid variable modifier \"//*\" for \"MASTER_SITES\".", | |
130 | "WARN: filename.mk:4: \"ftp\" is not a valid URL.", | 130 | "WARN: filename.mk:4: \"ftp\" is not a valid URL.", | |
131 | "WARN: filename.mk:4: MASTER_SITES should not be used at load time in any file.") | 131 | "WARN: filename.mk:4: MASTER_SITES should not be used at load time in any file.") | |
132 | ||||
133 | test(".if !", | |||
134 | "WARN: filename.mk:4: Invalid condition, unrecognized part: \"\".") | |||
135 | ||||
136 | // TODO: There should be another error for the '.elif' outside '.if'. | |||
137 | test(".elif 0", | |||
138 | "ERROR: filename.mk:5: Unmatched .endif.") | |||
132 | } | 139 | } | |
133 | 140 | |||
134 | func (s *Suite) Test_MkCondChecker_Check__tracing(c *check.C) { | 141 | func (s *Suite) Test_MkCondChecker_Check__tracing(c *check.C) { | |
135 | t := s.Init(c) | 142 | t := s.Init(c) | |
136 | 143 | |||
137 | t.EnableTracingToLog() | 144 | t.EnableTracingToLog() | |
138 | mklines := t.NewMkLines("filename.mk", | 145 | mklines := t.NewMkLines("filename.mk", | |
139 | MkCvsID, | 146 | MkCvsID, | |
140 | ".if ${VAR:Mpattern1:Mpattern2} == comparison", | 147 | ".if ${VAR:Mpattern1:Mpattern2} == comparison", | |
141 | ".endif") | 148 | ".endif") | |
142 | 149 | |||
143 | mklines.Check() | 150 | mklines.Check() | |
144 | 151 | |||
@@ -589,89 +596,88 @@ func (s *Suite) Test_MkCondChecker_check | @@ -589,89 +596,88 @@ func (s *Suite) Test_MkCondChecker_check | |||
589 | t.SetUpVartypes() | 596 | t.SetUpVartypes() | |
590 | mklines := t.NewMkLines("filename.mk", | 597 | mklines := t.NewMkLines("filename.mk", | |
591 | ".if ${DISTFILES:Mpattern:O:u} == NetBSD") | 598 | ".if ${DISTFILES:Mpattern:O:u} == NetBSD") | |
592 | t.DisableTracing() | 599 | t.DisableTracing() | |
593 | 600 | |||
594 | ck := NewMkCondChecker(mklines.mklines[0], mklines) | 601 | ck := NewMkCondChecker(mklines.mklines[0], mklines) | |
595 | varUse := b.VarUse("DISTFILES", "Mpattern", "O", "u") | 602 | varUse := b.VarUse("DISTFILES", "Mpattern", "O", "u") | |
596 | // TODO: mklines.ForEach | 603 | // TODO: mklines.ForEach | |
597 | ck.checkCompareVarStr(varUse, "==", "distfile-1.0.tar.gz") | 604 | ck.checkCompareVarStr(varUse, "==", "distfile-1.0.tar.gz") | |
598 | 605 | |||
599 | t.CheckOutputEmpty() | 606 | t.CheckOutputEmpty() | |
600 | } | 607 | } | |
601 | 608 | |||
602 | func (s *Suite) Test_MkCondChecker_checkCompareVarNum(c *check.C) { | 609 | func (s *Suite) Test_MkCondChecker_checkCompareWithNum(c *check.C) { | |
603 | t := s.Init(c) | 610 | t := s.Init(c) | |
604 | 611 | |||
605 | mklines := t.NewMkLines("filename.mk", | 612 | mklines := t.NewMkLines("filename.mk", | |
606 | MkCvsID, | 613 | MkCvsID, | |
607 | "", | 614 | "", | |
608 | "OS_VERSION=\t6.0", | 615 | "OS_VERSION=\t6.0", | |
609 | ".if ${OS_VERSION} > 6.5 || ${_PYTHON_VERSION} < 38", | 616 | ".if ${OS_VERSION} > 6.5 || ${_PYTHON_VERSION} < 38", | |
610 | ".endif") | 617 | ".endif") | |
611 | 618 | |||
612 | mklines.Check() | 619 | mklines.Check() | |
613 | 620 | |||
614 | t.CheckOutputLines( | 621 | t.CheckOutputLines( | |
615 | "WARN: filename.mk:4: Numeric comparison > 6.5.", | 622 | "WARN: filename.mk:4: Numeric comparison > 6.5.", | |
616 | "ERROR: filename.mk:4: The Python version must not be compared numerically.", | 623 | "ERROR: filename.mk:4: The Python version must not be compared numerically.", | |
617 | "WARN: filename.mk:4: _PYTHON_VERSION is used but not defined.") | 624 | "WARN: filename.mk:4: _PYTHON_VERSION is used but not defined.") | |
618 | } | 625 | } | |
619 | 626 | |||
620 | func (s *Suite) Test_MkCondChecker_checkCompareVarNumVersion(c *check.C) { | 627 | func (s *Suite) Test_MkCondChecker_checkCompareWithNumVersion(c *check.C) { | |
621 | t := s.Init(c) | 628 | t := s.Init(c) | |
622 | 629 | |||
623 | mklines := t.NewMkLines("filename.mk", | 630 | mklines := t.NewMkLines("filename.mk", | |
624 | MkCvsID, | 631 | MkCvsID, | |
625 | "", | 632 | "", | |
626 | "OS_VERSION=\t9.0", | 633 | "OS_VERSION=\t9.0", | |
627 | "", | 634 | "", | |
628 | ".if ${OS_VERSION} > 6.5", | 635 | ".if ${OS_VERSION} > 6.5", | |
629 | ".endif", | 636 | ".endif", | |
630 | "", | 637 | "", | |
631 | ".if ${OS_VERSION} == 6.5", | 638 | ".if ${OS_VERSION} == 6.5", | |
632 | ".endif") | 639 | ".endif") | |
633 | 640 | |||
634 | mklines.Check() | 641 | mklines.Check() | |
635 | 642 | |||
636 | t.CheckOutputLines( | 643 | t.CheckOutputLines( | |
637 | "WARN: filename.mk:5: Numeric comparison > 6.5.", | 644 | "WARN: filename.mk:5: Numeric comparison > 6.5.", | |
638 | "WARN: filename.mk:8: Numeric comparison == 6.5.") | 645 | "WARN: filename.mk:8: Numeric comparison == 6.5.") | |
639 | } | 646 | } | |
640 | 647 | |||
641 | func (s *Suite) Test_MkCondChecker_checkCompareVarNumPython(c *check.C) { | 648 | func (s *Suite) Test_MkCondChecker_checkCompareWithNumPython(c *check.C) { | |
642 | t := s.Init(c) | 649 | t := s.Init(c) | |
643 | 650 | |||
644 | mklines := t.NewMkLines("filename.mk", | 651 | mklines := t.NewMkLines("filename.mk", | |
645 | MkCvsID, | 652 | MkCvsID, | |
646 | "", | 653 | "", | |
647 | "_PYTHON_VERSION=\tnone", | 654 | "_PYTHON_VERSION=\tnone", | |
648 | "", | 655 | "", | |
649 | ".if ${_PYTHON_VERSION} < 38", | 656 | ".if ${_PYTHON_VERSION} < 38", | |
650 | ".endif", | 657 | ".elif ${_PYTHON_VERSION} < 310", | |
651 | "", | 658 | ".elif \"\" < 310", | |
652 | ".if ${_PYTHON_VERSION} < 310", | |||
653 | ".endif") | 659 | ".endif") | |
654 | 660 | |||
655 | mklines.Check() | 661 | mklines.Check() | |
656 | 662 | |||
657 | t.CheckOutputLines( | 663 | t.CheckOutputLines( | |
658 | "WARN: filename.mk:3: "+ | 664 | "WARN: filename.mk:3: "+ | |
659 | "Variable names starting with an underscore "+ | 665 | "Variable names starting with an underscore "+ | |
660 | "(_PYTHON_VERSION) are reserved "+ | 666 | "(_PYTHON_VERSION) are reserved "+ | |
661 | "for internal pkgsrc use.", | 667 | "for internal pkgsrc use.", | |
662 | "ERROR: filename.mk:5: "+ | 668 | "ERROR: filename.mk:5: "+ | |
663 | "The Python version must not be compared numerically.", | 669 | "The Python version must not be compared numerically.", | |
664 | "ERROR: filename.mk:8: "+ | 670 | "ERROR: filename.mk:6: "+ | |
665 | "The Python version must not be compared numerically.") | 671 | "The Python version must not be compared numerically.") | |
666 | } | 672 | } | |
667 | 673 | |||
668 | func (s *Suite) Test_MkCondChecker_checkCompareVarStrCompiler(c *check.C) { | 674 | func (s *Suite) Test_MkCondChecker_checkCompareVarStrCompiler(c *check.C) { | |
669 | t := s.Init(c) | 675 | t := s.Init(c) | |
670 | 676 | |||
671 | t.SetUpPkgsrc() | 677 | t.SetUpPkgsrc() | |
672 | t.Chdir("category/package") | 678 | t.Chdir("category/package") | |
673 | t.FinishSetUp() | 679 | t.FinishSetUp() | |
674 | 680 | |||
675 | doTest := func(cond string) { | 681 | doTest := func(cond string) { | |
676 | mklines := t.SetUpFileMkLines("filename.mk", | 682 | mklines := t.SetUpFileMkLines("filename.mk", | |
677 | MkCvsID, | 683 | MkCvsID, |
@@ -250,27 +250,27 @@ func (p MkLineParser) parseInclude(line | @@ -250,27 +250,27 @@ func (p MkLineParser) parseInclude(line | |||
250 | 250 | |||
251 | func (p MkLineParser) parseSysinclude(line *Line, splitResult mkLineSplitResult) *MkLine { | 251 | func (p MkLineParser) parseSysinclude(line *Line, splitResult mkLineSplitResult) *MkLine { | |
252 | m, indent, directive, includedFile := match3(splitResult.main, `^\.([\t ]*)(s?include)[\t ]+<([^>]+)>$`) | 252 | m, indent, directive, includedFile := match3(splitResult.main, `^\.([\t ]*)(s?include)[\t ]+<([^>]+)>$`) | |
253 | if !m { | 253 | if !m { | |
254 | return nil | 254 | return nil | |
255 | } | 255 | } | |
256 | 256 | |||
257 | return &MkLine{line, splitResult, &mkLineInclude{directive == "include", true, indent, NewRelPathString(includedFile), nil}} | 257 | return &MkLine{line, splitResult, &mkLineInclude{directive == "include", true, indent, NewRelPathString(includedFile), nil}} | |
258 | } | 258 | } | |
259 | 259 | |||
260 | func (p MkLineParser) parseDependency(line *Line, splitResult mkLineSplitResult) *MkLine { | 260 | func (p MkLineParser) parseDependency(line *Line, splitResult mkLineSplitResult) *MkLine { | |
261 | // XXX: Replace this regular expression with proper parsing. | 261 | // XXX: Replace this regular expression with proper parsing. | |
262 | // There might be a ${VAR:M*.c} in these variables, which the below regular expression cannot handle. | 262 | // There might be a ${VAR:M*.c} in these variables, which the below regular expression cannot handle. | |
263 | m, targets, whitespace, sources := match3(line.Text, `^([^\t :]+(?:[\t ]*[^\t :]+)*)([\t ]*):[\t ]*([^#]*?)(?:[\t ]*#.*)?$`) | 263 | m, targets, whitespace, sources := match3(splitResult.main, `^([^\t :]+(?:[\t ]*[^\t :]+)*)([\t ]*):[\t ]*(.*)$`) | |
264 | if !m { | 264 | if !m { | |
265 | return nil | 265 | return nil | |
266 | } | 266 | } | |
267 | 267 | |||
268 | if whitespace != "" { | 268 | if whitespace != "" { | |
269 | line.Notef("Space before colon in dependency line.") | 269 | line.Notef("Space before colon in dependency line.") | |
270 | } | 270 | } | |
271 | return &MkLine{line, splitResult, mkLineDependency{targets, sources}} | 271 | return &MkLine{line, splitResult, mkLineDependency{targets, sources}} | |
272 | } | 272 | } | |
273 | 273 | |||
274 | func (p MkLineParser) parseMergeConflict(line *Line, splitResult mkLineSplitResult) *MkLine { | 274 | func (p MkLineParser) parseMergeConflict(line *Line, splitResult mkLineSplitResult) *MkLine { | |
275 | if !matches(line.Text, `^(<<<<<<<|=======|>>>>>>>)`) { | 275 | if !matches(line.Text, `^(<<<<<<<|=======|>>>>>>>)`) { | |
276 | return nil | 276 | return nil |
@@ -23,31 +23,35 @@ func NewMkVarUseChecker(use *MkVarUse, m | @@ -23,31 +23,35 @@ func NewMkVarUseChecker(use *MkVarUse, m | |||
23 | func (ck *MkVarUseChecker) Check(vuc *VarUseContext) { | 23 | func (ck *MkVarUseChecker) Check(vuc *VarUseContext) { | |
24 | if ck.use.IsExpression() { | 24 | if ck.use.IsExpression() { | |
25 | return | 25 | return | |
26 | } | 26 | } | |
27 | 27 | |||
28 | ck.checkUndefined() | 28 | ck.checkUndefined() | |
29 | ck.checkPermissions(vuc) | 29 | ck.checkPermissions(vuc) | |
30 | 30 | |||
31 | ck.checkVarname(vuc.time) | 31 | ck.checkVarname(vuc.time) | |
32 | ck.checkModifiers() | 32 | ck.checkModifiers() | |
33 | ck.checkAssignable(vuc) | 33 | ck.checkAssignable(vuc) | |
34 | ck.checkQuoting(vuc) | 34 | ck.checkQuoting(vuc) | |
35 | 35 | |||
36 | if G.Pkgsrc == nil { | |||
37 | goto checkVarUse | |||
38 | } | |||
36 | ck.checkToolsPlatform() | 39 | ck.checkToolsPlatform() | |
37 | ck.checkBuildDefs() | 40 | ck.checkBuildDefs() | |
38 | ck.checkDeprecated() | 41 | ck.checkDeprecated() | |
39 | ck.checkPkgBuildOptions() | 42 | ck.checkPkgBuildOptions() | |
40 | 43 | |||
44 | checkVarUse: | |||
41 | NewMkLineChecker(ck.MkLines, ck.MkLine). | 45 | NewMkLineChecker(ck.MkLines, ck.MkLine). | |
42 | checkTextVarUse(ck.use.varname, ck.vartype, vuc.time) | 46 | checkTextVarUse(ck.use.varname, ck.vartype, vuc.time) | |
43 | } | 47 | } | |
44 | 48 | |||
45 | func (ck *MkVarUseChecker) checkUndefined() { | 49 | func (ck *MkVarUseChecker) checkUndefined() { | |
46 | varuse := ck.use | 50 | varuse := ck.use | |
47 | vartype := ck.vartype | 51 | vartype := ck.vartype | |
48 | varname := varuse.varname | 52 | varname := varuse.varname | |
49 | 53 | |||
50 | switch { | 54 | switch { | |
51 | case !G.WarnExtra, | 55 | case !G.WarnExtra, | |
52 | // Well-known variables are probably defined by the infrastructure. | 56 | // Well-known variables are probably defined by the infrastructure. | |
53 | vartype != nil && !vartype.IsGuessed(), | 57 | vartype != nil && !vartype.IsGuessed(), |
@@ -1295,26 +1295,29 @@ func (ind *Indentation) TrackAfter(mklin | @@ -1295,26 +1295,29 @@ func (ind *Indentation) TrackAfter(mklin | |||
1295 | ind.Pop() | 1295 | ind.Pop() | |
1296 | } | 1296 | } | |
1297 | } | 1297 | } | |
1298 | 1298 | |||
1299 | switch directive { | 1299 | switch directive { | |
1300 | case "if", "elif": | 1300 | case "if", "elif": | |
1301 | cond := mkline.Cond() | 1301 | cond := mkline.Cond() | |
1302 | if cond == nil { | 1302 | if cond == nil { | |
1303 | break | 1303 | break | |
1304 | } | 1304 | } | |
1305 | 1305 | |||
1306 | ind.RememberUsedVariables(cond) | 1306 | ind.RememberUsedVariables(cond) | |
1307 | 1307 | |||
1308 | if G.Pkgsrc == nil { | |||
1309 | break | |||
1310 | } | |||
1308 | cond.Walk(&MkCondCallback{ | 1311 | cond.Walk(&MkCondCallback{ | |
1309 | Call: func(name string, arg string) { | 1312 | Call: func(name string, arg string) { | |
1310 | if name == "exists" && !NewPath(arg).IsAbs() { | 1313 | if name == "exists" && !NewPath(arg).IsAbs() { | |
1311 | rel := G.Pkgsrc.Rel(mkline.File(NewRelPathString(arg))) | 1314 | rel := G.Pkgsrc.Rel(mkline.File(NewRelPathString(arg))) | |
1312 | ind.AddCheckedFile(rel) | 1315 | ind.AddCheckedFile(rel) | |
1313 | } | 1316 | } | |
1314 | }}) | 1317 | }}) | |
1315 | } | 1318 | } | |
1316 | } | 1319 | } | |
1317 | 1320 | |||
1318 | func (ind *Indentation) CheckFinish(filename CurrPath) { | 1321 | func (ind *Indentation) CheckFinish(filename CurrPath) { | |
1319 | if ind.IsEmpty() { | 1322 | if ind.IsEmpty() { | |
1320 | return | 1323 | return |
@@ -323,26 +323,40 @@ func (s *Suite) Test_MkLine_ValueFields_ | @@ -323,26 +323,40 @@ func (s *Suite) Test_MkLine_ValueFields_ | |||
323 | t.CheckEquals(rest, "") | 323 | t.CheckEquals(rest, "") | |
324 | 324 | |||
325 | words = mkline.ValueFields(url) | 325 | words = mkline.ValueFields(url) | |
326 | 326 | |||
327 | t.CheckDeepEquals(words, []string{url}) | 327 | t.CheckDeepEquals(words, []string{url}) | |
328 | 328 | |||
329 | words = mkline.ValueFields("a b \"c c c\" d;;d;; \"e\"''`` 'rest") | 329 | words = mkline.ValueFields("a b \"c c c\" d;;d;; \"e\"''`` 'rest") | |
330 | 330 | |||
331 | t.CheckDeepEquals(words, []string{"a", "b", "\"c c c\"", "d;;d;;", "\"e\"''``"}) | 331 | t.CheckDeepEquals(words, []string{"a", "b", "\"c c c\"", "d;;d;;", "\"e\"''``"}) | |
332 | // The rest "'rest" is silently discarded. | 332 | // The rest "'rest" is silently discarded. | |
333 | // Most probably, the shell will complain about it when it is executed. | 333 | // Most probably, the shell will complain about it when it is executed. | |
334 | } | 334 | } | |
335 | 335 | |||
336 | func (s *Suite) Test_MkLine_ValueFields__escaped_number_sign(c *check.C) { | |||
337 | t := s.Init(c) | |||
338 | mklines := t.NewMkLines("filename.mk", | |||
339 | ".SHELL: \\", | |||
340 | "\tname=sh \\", | |||
341 | "\tpath=${.SHELL} \\", | |||
342 | "\tquiet=\"\\# .echoOff\"") | |||
343 | mkline := mklines.mklines[0] | |||
344 | ||||
345 | words := mkline.ValueFields(mkline.Sources()) | |||
346 | ||||
347 | t.CheckDeepEquals(words, []string{"name=sh", "path=${.SHELL}", "quiet=\"# .echoOff\""}) | |||
348 | } | |||
349 | ||||
336 | func (s *Suite) Test_MkLine_ValueTokens(c *check.C) { | 350 | func (s *Suite) Test_MkLine_ValueTokens(c *check.C) { | |
337 | t := s.Init(c) | 351 | t := s.Init(c) | |
338 | b := NewMkTokenBuilder() | 352 | b := NewMkTokenBuilder() | |
339 | text := b.TextToken | 353 | text := b.TextToken | |
340 | varUseText := b.VaruseTextToken | 354 | varUseText := b.VaruseTextToken | |
341 | tokens := b.Tokens | 355 | tokens := b.Tokens | |
342 | 356 | |||
343 | test := func(value string, expected []*MkToken, diagnostics ...string) { | 357 | test := func(value string, expected []*MkToken, diagnostics ...string) { | |
344 | mkline := t.NewMkLine("Makefile", 1, "PATH=\t"+value) | 358 | mkline := t.NewMkLine("Makefile", 1, "PATH=\t"+value) | |
345 | actualTokens, _ := mkline.ValueTokens() | 359 | actualTokens, _ := mkline.ValueTokens() | |
346 | t.CheckDeepEquals(actualTokens, expected) | 360 | t.CheckDeepEquals(actualTokens, expected) | |
347 | t.CheckOutput(diagnostics) | 361 | t.CheckOutput(diagnostics) | |
348 | } | 362 | } | |
@@ -629,27 +643,27 @@ func (s *Suite) Test_MkLine_VariableNeed | @@ -629,27 +643,27 @@ func (s *Suite) Test_MkLine_VariableNeed | |||
629 | "MASTER_SITES=\t${HOMEPAGE}") | 643 | "MASTER_SITES=\t${HOMEPAGE}") | |
630 | mkline := mklines.mklines[1] | 644 | mkline := mklines.mklines[1] | |
631 | 645 | |||
632 | vuc := VarUseContext{G.Pkgsrc.vartypes.Canon("MASTER_SITES"), VucRunTime, VucQuotPlain, false} | 646 | vuc := VarUseContext{G.Pkgsrc.vartypes.Canon("MASTER_SITES"), VucRunTime, VucQuotPlain, false} | |
633 | nq := mkline.VariableNeedsQuoting(nil, NewMkVarUse("HOMEPAGE"), G.Pkgsrc.vartypes.Canon("HOMEPAGE"), &vuc) | 647 | nq := mkline.VariableNeedsQuoting(nil, NewMkVarUse("HOMEPAGE"), G.Pkgsrc.vartypes.Canon("HOMEPAGE"), &vuc) | |
634 | 648 | |||
635 | t.CheckEquals(nq, no) | 649 | t.CheckEquals(nq, no) | |
636 | 650 | |||
637 | NewMkAssignChecker(mkline, mklines).check() | 651 | NewMkAssignChecker(mkline, mklines).check() | |
638 | 652 | |||
639 | t.CheckOutputEmpty() // Up to version 5.3.6, pkglint warned about a missing :Q here, which was wrong. | 653 | t.CheckOutputEmpty() // Up to version 5.3.6, pkglint warned about a missing :Q here, which was wrong. | |
640 | } | 654 | } | |
641 | 655 | |||
642 | func (s *Suite) Test_MkLine_VariableNeedsQuoting__append_list_to_list(c *check.C) { | 656 | func (s *Suite) Test_MkLine_VariableNeedsQuoting__assign_list_to_list(c *check.C) { | |
643 | t := s.Init(c) | 657 | t := s.Init(c) | |
644 | 658 | |||
645 | t.SetUpVartypes() | 659 | t.SetUpVartypes() | |
646 | t.SetUpMasterSite("MASTER_SITE_SOURCEFORGE", "http://downloads.sourceforge.net/sourceforge/") | 660 | t.SetUpMasterSite("MASTER_SITE_SOURCEFORGE", "http://downloads.sourceforge.net/sourceforge/") | |
647 | mklines := t.NewMkLines("Makefile", | 661 | mklines := t.NewMkLines("Makefile", | |
648 | MkCvsID, | 662 | MkCvsID, | |
649 | "MASTER_SITES=\t${MASTER_SITE_SOURCEFORGE:=squirrel-sql/}") | 663 | "MASTER_SITES=\t${MASTER_SITE_SOURCEFORGE:=squirrel-sql/}") | |
650 | 664 | |||
651 | NewMkAssignChecker(mklines.mklines[1], mklines).check() | 665 | NewMkAssignChecker(mklines.mklines[1], mklines).check() | |
652 | 666 | |||
653 | // Assigning lists to lists is ok. | 667 | // Assigning lists to lists is ok. | |
654 | t.CheckOutputEmpty() | 668 | t.CheckOutputEmpty() | |
655 | } | 669 | } |
@@ -237,26 +237,29 @@ func (ck MkLineChecker) checkInclude() { | @@ -237,26 +237,29 @@ func (ck MkLineChecker) checkInclude() { | |||
237 | defer trace.Call0()() | 237 | defer trace.Call0()() | |
238 | } | 238 | } | |
239 | 239 | |||
240 | mkline := ck.MkLine | 240 | mkline := ck.MkLine | |
241 | if mkline.Indent() != "" { | 241 | if mkline.Indent() != "" { | |
242 | ck.checkDirectiveIndentation(ck.MkLines.indentation.Depth("include")) | 242 | ck.checkDirectiveIndentation(ck.MkLines.indentation.Depth("include")) | |
243 | } | 243 | } | |
244 | 244 | |||
245 | includedFile := mkline.IncludedFile() | 245 | includedFile := mkline.IncludedFile() | |
246 | mustExist := mkline.MustExist() | 246 | mustExist := mkline.MustExist() | |
247 | if trace.Tracing { | 247 | if trace.Tracing { | |
248 | trace.Stepf("includingFile=%s includedFile=%s", mkline.Filename(), includedFile) | 248 | trace.Stepf("includingFile=%s includedFile=%s", mkline.Filename(), includedFile) | |
249 | } | 249 | } | |
250 | if G.Pkgsrc == nil { | |||
251 | return | |||
252 | } | |||
250 | ck.CheckRelativePath(NewPackagePath(includedFile), includedFile, mustExist) | 253 | ck.CheckRelativePath(NewPackagePath(includedFile), includedFile, mustExist) | |
251 | 254 | |||
252 | switch { | 255 | switch { | |
253 | case includedFile.HasBase("Makefile"): | 256 | case includedFile.HasBase("Makefile"): | |
254 | mkline.Errorf("Other Makefiles must not be included directly.") | 257 | mkline.Errorf("Other Makefiles must not be included directly.") | |
255 | mkline.Explain( | 258 | mkline.Explain( | |
256 | "To include portions of another Makefile, extract the common parts", | 259 | "To include portions of another Makefile, extract the common parts", | |
257 | "and put them into a Makefile.common or a Makefile fragment called", | 260 | "and put them into a Makefile.common or a Makefile fragment called", | |
258 | "module.mk or similar.", | 261 | "module.mk or similar.", | |
259 | "After that, both this one and the other package should include the newly created file.") | 262 | "After that, both this one and the other package should include the newly created file.") | |
260 | 263 | |||
261 | case mkline.Basename != "Makefile" && includedFile.HasBase("bsd.pkg.mk"): | 264 | case mkline.Basename != "Makefile" && includedFile.HasBase("bsd.pkg.mk"): | |
262 | mkline.Errorf("The file bsd.pkg.mk must only be included by package Makefiles, not by other Makefile fragments.") | 265 | mkline.Errorf("The file bsd.pkg.mk must only be included by package Makefiles, not by other Makefile fragments.") |
@@ -52,27 +52,29 @@ type mklinesCheckAll struct { | @@ -52,27 +52,29 @@ type mklinesCheckAll struct { | |||
52 | forVars map[string]bool | 52 | forVars map[string]bool | |
53 | 53 | |||
54 | // Custom action that is run after checking each line | 54 | // Custom action that is run after checking each line | |
55 | postLine func(mkline *MkLine) | 55 | postLine func(mkline *MkLine) | |
56 | } | 56 | } | |
57 | 57 | |||
58 | func NewMkLines(lines *Lines, pkg *Package, extraScope *Scope) *MkLines { | 58 | func NewMkLines(lines *Lines, pkg *Package, extraScope *Scope) *MkLines { | |
59 | mklines := make([]*MkLine, lines.Len()) | 59 | mklines := make([]*MkLine, lines.Len()) | |
60 | for i, line := range lines.Lines { | 60 | for i, line := range lines.Lines { | |
61 | mklines[i] = NewMkLineParser().Parse(line) | 61 | mklines[i] = NewMkLineParser().Parse(line) | |
62 | } | 62 | } | |
63 | 63 | |||
64 | tools := NewTools() | 64 | tools := NewTools() | |
65 | tools.Fallback(G.Pkgsrc.Tools) | 65 | if G.Pkgsrc != nil { | |
66 | tools.Fallback(G.Pkgsrc.Tools) | |||
67 | } | |||
66 | 68 | |||
67 | return &MkLines{ | 69 | return &MkLines{ | |
68 | mklines, | 70 | mklines, | |
69 | lines, | 71 | lines, | |
70 | pkg, | 72 | pkg, | |
71 | extraScope, | 73 | extraScope, | |
72 | NewScope(), | 74 | NewScope(), | |
73 | make(map[string]bool), | 75 | make(map[string]bool), | |
74 | make(map[string]*MkLine), | 76 | make(map[string]*MkLine), | |
75 | make(map[string]*MkLine), | 77 | make(map[string]*MkLine), | |
76 | false, | 78 | false, | |
77 | tools, | 79 | tools, | |
78 | nil, | 80 | nil, |
@@ -292,26 +292,31 @@ func (s *Suite) Test_MkParser_MkCond(c * | @@ -292,26 +292,31 @@ func (s *Suite) Test_MkParser_MkCond(c * | |||
292 | 292 | |||
293 | // Function calls need round parentheses instead of curly braces. | 293 | // Function calls need round parentheses instead of curly braces. | |
294 | // As of July 2019, bmake silently accepts this wrong expression | 294 | // As of July 2019, bmake silently accepts this wrong expression | |
295 | // and interprets it as !defined(empty{USE_CROSS_COMPILE:M[yY][eE][sS]}), | 295 | // and interprets it as !defined(empty{USE_CROSS_COMPILE:M[yY][eE][sS]}), | |
296 | // which is always true, except if a variable of this strange name | 296 | // which is always true, except if a variable of this strange name | |
297 | // were actually defined. | 297 | // were actually defined. | |
298 | testRest("!empty{USE_CROSS_COMPILE:M[yY][eE][sS]}", | 298 | testRest("!empty{USE_CROSS_COMPILE:M[yY][eE][sS]}", | |
299 | nil, | 299 | nil, | |
300 | "empty{USE_CROSS_COMPILE:M[yY][eE][sS]}") | 300 | "empty{USE_CROSS_COMPILE:M[yY][eE][sS]}") | |
301 | 301 | |||
302 | testRest("unknown(arg)", | 302 | testRest("unknown(arg)", | |
303 | nil, | 303 | nil, | |
304 | "unknown(arg)") | 304 | "unknown(arg)") | |
305 | ||||
306 | // The '!' is consumed by the parser. | |||
307 | testRest("!", | |||
308 | nil, | |||
309 | "") | |||
305 | } | 310 | } | |
306 | 311 | |||
307 | func (s *Suite) Test_MkParser_mkCondCompare(c *check.C) { | 312 | func (s *Suite) Test_MkParser_mkCondCompare(c *check.C) { | |
308 | t := s.Init(c) | 313 | t := s.Init(c) | |
309 | b := NewMkTokenBuilder() | 314 | b := NewMkTokenBuilder() | |
310 | 315 | |||
311 | mkline := t.NewMkLine("Makefile", 123, ".if ${PKGPATH} == category/pack.age-3+") | 316 | mkline := t.NewMkLine("Makefile", 123, ".if ${PKGPATH} == category/pack.age-3+") | |
312 | p := NewMkParser(mkline.Line, mkline.Args()) | 317 | p := NewMkParser(mkline.Line, mkline.Args()) | |
313 | cond := p.MkCond() | 318 | cond := p.MkCond() | |
314 | 319 | |||
315 | t.CheckEquals(p.Rest(), "") | 320 | t.CheckEquals(p.Rest(), "") | |
316 | t.CheckDeepEquals( | 321 | t.CheckDeepEquals( | |
317 | cond, | 322 | cond, |
@@ -3143,27 +3143,27 @@ func (s *Suite) Test_Package_pkgnameFrom | @@ -3143,27 +3143,27 @@ func (s *Suite) Test_Package_pkgnameFrom | |||
3143 | "WARN: ~/category/package/Makefile:4: "+ | 3143 | "WARN: ~/category/package/Makefile:4: "+ | |
3144 | "Missing closing \"}\" for \"DISTNAME\".") | 3144 | "Missing closing \"}\" for \"DISTNAME\".") | |
3145 | 3145 | |||
3146 | // Parse error with an unparseable rest. | 3146 | // Parse error with an unparseable rest. | |
3147 | test("$", "package-1.0", "", | 3147 | test("$", "package-1.0", "", | |
3148 | nil...) | 3148 | nil...) | |
3149 | } | 3149 | } | |
3150 | 3150 | |||
3151 | func (s *Suite) Test_Package_checkPossibleDowngrade(c *check.C) { | 3151 | func (s *Suite) Test_Package_checkPossibleDowngrade(c *check.C) { | |
3152 | t := s.Init(c) | 3152 | t := s.Init(c) | |
3153 | 3153 | |||
3154 | t.CreateFileLines("doc/CHANGES-2018", | 3154 | t.CreateFileLines("doc/CHANGES-2018", | |
3155 | "\tUpdated category/pkgbase to 1.8 [committer 2018-01-05]") | 3155 | "\tUpdated category/pkgbase to 1.8 [committer 2018-01-05]") | |
3156 | G.Pkgsrc.changes.load(&G.Pkgsrc) | 3156 | G.Pkgsrc.changes.load(G.Pkgsrc) | |
3157 | 3157 | |||
3158 | t.Chdir("category/pkgbase") | 3158 | t.Chdir("category/pkgbase") | |
3159 | pkg := NewPackage(".") | 3159 | pkg := NewPackage(".") | |
3160 | pkg.EffectivePkgname = "package-1.0nb15" | 3160 | pkg.EffectivePkgname = "package-1.0nb15" | |
3161 | pkg.EffectivePkgnameLine = t.NewMkLine("Makefile", 5, "PKGNAME=dummy") | 3161 | pkg.EffectivePkgnameLine = t.NewMkLine("Makefile", 5, "PKGNAME=dummy") | |
3162 | 3162 | |||
3163 | pkg.checkPossibleDowngrade() | 3163 | pkg.checkPossibleDowngrade() | |
3164 | 3164 | |||
3165 | t.CheckOutputLines( | 3165 | t.CheckOutputLines( | |
3166 | "WARN: Makefile:5: The package is being downgraded from 1.8 (see ../../doc/CHANGES-2018:1) to 1.0nb15.") | 3166 | "WARN: Makefile:5: The package is being downgraded from 1.8 (see ../../doc/CHANGES-2018:1) to 1.0nb15.") | |
3167 | 3167 | |||
3168 | G.Pkgsrc.changes.LastChange["category/pkgbase"].target = "1.0nb22" | 3168 | G.Pkgsrc.changes.LastChange["category/pkgbase"].target = "1.0nb22" | |
3169 | 3169 |
@@ -173,28 +173,29 @@ func (ck *PatchChecker) checkUnifiedDiff | @@ -173,28 +173,29 @@ func (ck *PatchChecker) checkUnifiedDiff | |||
173 | 173 | |||
174 | default: | 174 | default: | |
175 | line.Errorf("Invalid line in unified patch hunk: %s", text) | 175 | line.Errorf("Invalid line in unified patch hunk: %s", text) | |
176 | return | 176 | return | |
177 | } | 177 | } | |
178 | } | 178 | } | |
179 | 179 | |||
180 | // When these two counts are equal, they may refer to context | 180 | // When these two counts are equal, they may refer to context | |
181 | // lines that consist only of whitespace and have therefore | 181 | // lines that consist only of whitespace and have therefore | |
182 | // been lost during transmission. There is no way to detect | 182 | // been lost during transmission. There is no way to detect | |
183 | // this by looking only at the patch file. | 183 | // this by looking only at the patch file. | |
184 | if linesToAdd != linesToDel { | 184 | if linesToAdd != linesToDel { | |
185 | line := ck.llex.PreviousLine() | 185 | line := ck.llex.PreviousLine() | |
186 | line.Warnf("Premature end of patch hunk (expected %d lines to be deleted and %d lines to be added).", | 186 | line.Warnf("Premature end of patch hunk (expected %d %s to be deleted and %d %s to be added).", | |
187 | linesToDel, linesToAdd) | 187 | linesToDel, condStr(linesToDel != 1, "lines", "line"), | |
188 | linesToAdd, condStr(linesToAdd != 1, "lines", "line")) | |||
188 | } | 189 | } | |
189 | } | 190 | } | |
190 | 191 | |||
191 | if !hasHunks { | 192 | if !hasHunks { | |
192 | ck.llex.CurrentLine().Errorf("No patch hunks for %q.", patchedFile.String()) | 193 | ck.llex.CurrentLine().Errorf("No patch hunks for %q.", patchedFile.String()) | |
193 | } | 194 | } | |
194 | 195 | |||
195 | if !ck.llex.EOF() { | 196 | if !ck.llex.EOF() { | |
196 | line := ck.llex.CurrentLine() | 197 | line := ck.llex.CurrentLine() | |
197 | if !ck.isEmptyLine(line.Text) && !matches(line.Text, rePatchUniFileDel) { | 198 | if !ck.isEmptyLine(line.Text) && !matches(line.Text, rePatchUniFileDel) { | |
198 | line.Warnf("Empty line or end of file expected.") | 199 | line.Warnf("Empty line or end of file expected.") | |
199 | line.Explain( | 200 | line.Explain( | |
200 | "This line is not part of the patch anymore, although it may look so.", | 201 | "This line is not part of the patch anymore, although it may look so.", |
@@ -22,27 +22,27 @@ const confVersion = "@VERSION@" | @@ -22,27 +22,27 @@ const confVersion = "@VERSION@" | |||
22 | type Pkglint struct { | 22 | type Pkglint struct { | |
23 | CheckGlobal bool | 23 | CheckGlobal bool | |
24 | 24 | |||
25 | WarnExtra, | 25 | WarnExtra, | |
26 | WarnPerm, | 26 | WarnPerm, | |
27 | WarnQuoting bool | 27 | WarnQuoting bool | |
28 | 28 | |||
29 | Profiling, | 29 | Profiling, | |
30 | DumpMakefile, | 30 | DumpMakefile, | |
31 | Import, | 31 | Import, | |
32 | Network, | 32 | Network, | |
33 | Recursive bool | 33 | Recursive bool | |
34 | 34 | |||
35 | Pkgsrc Pkgsrc // Global data, mostly extracted from mk/*. | 35 | Pkgsrc *Pkgsrc // Global data, mostly extracted from mk/*. | |
36 | 36 | |||
37 | Todo CurrPathQueue // The files or directories that still need to be checked. | 37 | Todo CurrPathQueue // The files or directories that still need to be checked. | |
38 | 38 | |||
39 | Wip bool // Is the currently checked file or package from pkgsrc-wip? | 39 | Wip bool // Is the currently checked file or package from pkgsrc-wip? | |
40 | Infrastructure bool // Is the currently checked file from the pkgsrc infrastructure? | 40 | Infrastructure bool // Is the currently checked file from the pkgsrc infrastructure? | |
41 | Testing bool // Is pkglint in self-testing mode (only during development)? | 41 | Testing bool // Is pkglint in self-testing mode (only during development)? | |
42 | Experimental bool // For experimental features, only enabled individually in tests | 42 | Experimental bool // For experimental features, only enabled individually in tests | |
43 | Username string // For checking against OWNER and MAINTAINER | 43 | Username string // For checking against OWNER and MAINTAINER | |
44 | 44 | |||
45 | cvsEntriesDir CurrPath // Cached to avoid I/O | 45 | cvsEntriesDir CurrPath // Cached to avoid I/O | |
46 | cvsEntries map[RelPath]CvsEntry | 46 | cvsEntries map[RelPath]CvsEntry | |
47 | 47 | |||
48 | Logger Logger | 48 | Logger Logger | |
@@ -180,43 +180,47 @@ func (p *Pkglint) setUpProfiling() func( | @@ -180,43 +180,47 @@ func (p *Pkglint) setUpProfiling() func( | |||
180 | p.loaded.PrintStats(p.Logger.out.out, "loaded", 10) | 180 | p.loaded.PrintStats(p.Logger.out.out, "loaded", 10) | |
181 | p.Logger.out.WriteLine(sprintf("fileCache: %d hits, %d misses", p.fileCache.hits, p.fileCache.misses)) | 181 | p.Logger.out.WriteLine(sprintf("fileCache: %d hits, %d misses", p.fileCache.hits, p.fileCache.misses)) | |
182 | }) | 182 | }) | |
183 | 183 | |||
184 | return func() { | 184 | return func() { | |
185 | for i := range cleanups { | 185 | for i := range cleanups { | |
186 | cleanups[len(cleanups)-1-i]() | 186 | cleanups[len(cleanups)-1-i]() | |
187 | } | 187 | } | |
188 | } | 188 | } | |
189 | } | 189 | } | |
190 | 190 | |||
191 | func (p *Pkglint) prepareMainLoop() { | 191 | func (p *Pkglint) prepareMainLoop() { | |
192 | firstDir := p.Todo.Front() | 192 | firstDir := p.Todo.Front() | |
193 | if firstDir.IsFile() { | 193 | isFile := firstDir.IsFile() | |
194 | if isFile { | |||
194 | firstDir = firstDir.Dir() | 195 | firstDir = firstDir.Dir() | |
195 | } | 196 | } | |
196 | 197 | |||
197 | relTopdir := p.findPkgsrcTopdir(firstDir) | 198 | relTopdir := p.findPkgsrcTopdir(firstDir) | |
198 | if relTopdir.IsEmpty() { | 199 | if relTopdir.IsEmpty() { | |
199 | // If the first argument to pkglint is not inside a pkgsrc tree, | 200 | // If the first argument to pkglint is not inside a pkgsrc tree, | |
200 | // pkglint doesn't know where to load the infrastructure files from, | 201 | // pkglint doesn't know where to load the infrastructure files from. | |
201 | // Since virtually every single check needs these files, | 202 | if isFile { | |
202 | // the only sensible thing to do is to quit immediately. | 203 | // Allow this mode nevertheless, for checking the basic syntax | |
203 | G.Logger.TechFatalf(firstDir, "Must be inside a pkgsrc tree.") | 204 | // and for formatting individual makefiles outside pkgsrc. | |
205 | } else { | |||
206 | G.Logger.TechFatalf(firstDir, "Must be inside a pkgsrc tree.") | |||
207 | } | |||
208 | } else { | |||
209 | p.Pkgsrc = NewPkgsrc(firstDir.JoinNoClean(relTopdir)) | |||
210 | p.Wip = p.Pkgsrc.IsWip(firstDir) // See Pkglint.checkMode. | |||
211 | p.Pkgsrc.LoadInfrastructure() | |||
204 | } | 212 | } | |
205 | 213 | |||
206 | p.Pkgsrc = NewPkgsrc(firstDir.JoinNoClean(relTopdir)) | |||
207 | p.Wip = p.Pkgsrc.IsWip(firstDir) // See Pkglint.checkMode. | |||
208 | p.Pkgsrc.LoadInfrastructure() | |||
209 | ||||
210 | currentUser, err := user.Current() | 214 | currentUser, err := user.Current() | |
211 | assertNil(err, "user.Current") | 215 | assertNil(err, "user.Current") | |
212 | // On Windows, this is `Computername\Username`. | 216 | // On Windows, this is `Computername\Username`. | |
213 | p.Username = replaceAll(currentUser.Username, `^.*\\`, "") | 217 | p.Username = replaceAll(currentUser.Username, `^.*\\`, "") | |
214 | } | 218 | } | |
215 | 219 | |||
216 | func (p *Pkglint) ParseCommandLine(args []string) int { | 220 | func (p *Pkglint) ParseCommandLine(args []string) int { | |
217 | lopts := &p.Logger.Opts | 221 | lopts := &p.Logger.Opts | |
218 | opts := getopt.NewOptions() | 222 | opts := getopt.NewOptions() | |
219 | 223 | |||
220 | var showHelp bool | 224 | var showHelp bool | |
221 | var showVersion bool | 225 | var showVersion bool | |
222 | 226 | |||
@@ -298,26 +302,31 @@ func (p *Pkglint) checkMode(dirent CurrP | @@ -298,26 +302,31 @@ func (p *Pkglint) checkMode(dirent CurrP | |||
298 | // TODO: merge duplicate code in Package.checkDirent | 302 | // TODO: merge duplicate code in Package.checkDirent | |
299 | isDir := mode.IsDir() | 303 | isDir := mode.IsDir() | |
300 | isReg := mode.IsRegular() | 304 | isReg := mode.IsRegular() | |
301 | if !isDir && !isReg { | 305 | if !isDir && !isReg { | |
302 | NewLineWhole(dirent).Errorf("No such file or directory.") | 306 | NewLineWhole(dirent).Errorf("No such file or directory.") | |
303 | return | 307 | return | |
304 | } | 308 | } | |
305 | 309 | |||
306 | dir := dirent | 310 | dir := dirent | |
307 | if !isDir { | 311 | if !isDir { | |
308 | dir = dirent.Dir() | 312 | dir = dirent.Dir() | |
309 | } | 313 | } | |
310 | 314 | |||
315 | if isReg && p.Pkgsrc == nil { | |||
316 | CheckFileMk(dirent, nil) | |||
317 | return | |||
318 | } | |||
319 | ||||
311 | pkgsrcRel := p.Pkgsrc.Rel(dirent) | 320 | pkgsrcRel := p.Pkgsrc.Rel(dirent) | |
312 | 321 | |||
313 | p.Wip = pkgsrcRel.HasPrefixPath("wip") | 322 | p.Wip = pkgsrcRel.HasPrefixPath("wip") | |
314 | p.Infrastructure = pkgsrcRel.HasPrefixPath("mk") || | 323 | p.Infrastructure = pkgsrcRel.HasPrefixPath("mk") || | |
315 | pkgsrcRel.HasPrefixPath("wip/mk") | 324 | pkgsrcRel.HasPrefixPath("wip/mk") | |
316 | pkgsrcdir := p.findPkgsrcTopdir(dir) | 325 | pkgsrcdir := p.findPkgsrcTopdir(dir) | |
317 | if pkgsrcdir.IsEmpty() { | 326 | if pkgsrcdir.IsEmpty() { | |
318 | G.Logger.TechErrorf("", | 327 | G.Logger.TechErrorf("", | |
319 | "Cannot determine the pkgsrc root directory for %q.", | 328 | "Cannot determine the pkgsrc root directory for %q.", | |
320 | dirent) | 329 | dirent) | |
321 | return | 330 | return | |
322 | } | 331 | } | |
323 | 332 |
@@ -234,27 +234,27 @@ func (s *Suite) Test_Pkglint_Main__compl | @@ -234,27 +234,27 @@ func (s *Suite) Test_Pkglint_Main__compl | |||
234 | t.Main("-Wall", "-Call", "sysutils/checkperms") | 234 | t.Main("-Wall", "-Call", "sysutils/checkperms") | |
235 | 235 | |||
236 | t.CheckOutputLines( | 236 | t.CheckOutputLines( | |
237 | "NOTE: ~/sysutils/checkperms/Makefile:3: "+ | 237 | "NOTE: ~/sysutils/checkperms/Makefile:3: "+ | |
238 | "Package version \"1.11\" is greater than the latest \"1.10\" "+ | 238 | "Package version \"1.11\" is greater than the latest \"1.10\" "+ | |
239 | "from ../../doc/CHANGES-2018:5.", | 239 | "from ../../doc/CHANGES-2018:5.", | |
240 | "WARN: ~/sysutils/checkperms/Makefile:3: "+ | 240 | "WARN: ~/sysutils/checkperms/Makefile:3: "+ | |
241 | "This package should be updated to 1.13 (supports more file formats; see ../../doc/TODO:5).", | 241 | "This package should be updated to 1.13 (supports more file formats; see ../../doc/TODO:5).", | |
242 | "ERROR: ~/sysutils/checkperms/Makefile:4: Invalid category \"tools\".", | 242 | "ERROR: ~/sysutils/checkperms/Makefile:4: Invalid category \"tools\".", | |
243 | "ERROR: ~/sysutils/checkperms/TODO: Packages in main pkgsrc must not have a TODO file.", | 243 | "ERROR: ~/sysutils/checkperms/TODO: Packages in main pkgsrc must not have a TODO file.", | |
244 | "ERROR: ~/sysutils/checkperms/distinfo:6: SHA1 hash of patches/patch-checkperms.c differs "+ | 244 | "ERROR: ~/sysutils/checkperms/distinfo:6: SHA1 hash of patches/patch-checkperms.c differs "+ | |
245 | "(distinfo has asdfasdf, patch file has bcfb79696cb6bf4d2222a6d78a530e11bf1c0cea).", | 245 | "(distinfo has asdfasdf, patch file has bcfb79696cb6bf4d2222a6d78a530e11bf1c0cea).", | |
246 | "WARN: ~/sysutils/checkperms/patches/patch-checkperms.c:12: Premature end of patch hunk "+ | 246 | "WARN: ~/sysutils/checkperms/patches/patch-checkperms.c:12: Premature end of patch hunk "+ | |
247 | "(expected 1 lines to be deleted and 0 lines to be added).", | 247 | "(expected 1 line to be deleted and 0 lines to be added).", | |
248 | "3 errors, 2 warnings and 1 note found.", | 248 | "3 errors, 2 warnings and 1 note found.", | |
249 | t.Shquote("(Run \"pkglint -e -Wall -Call %s\" to show explanations.)", "sysutils/checkperms"), | 249 | t.Shquote("(Run \"pkglint -e -Wall -Call %s\" to show explanations.)", "sysutils/checkperms"), | |
250 | t.Shquote("(Run \"pkglint -fs -Wall -Call %s\" to show what can be fixed automatically.)", "sysutils/checkperms"), | 250 | t.Shquote("(Run \"pkglint -fs -Wall -Call %s\" to show what can be fixed automatically.)", "sysutils/checkperms"), | |
251 | t.Shquote("(Run \"pkglint -F -Wall -Call %s\" to automatically fix some issues.)", "sysutils/checkperms")) | 251 | t.Shquote("(Run \"pkglint -F -Wall -Call %s\" to automatically fix some issues.)", "sysutils/checkperms")) | |
252 | } | 252 | } | |
253 | 253 | |||
254 | func (s *Suite) Test_Pkglint_Main__autofix_exitcode(c *check.C) { | 254 | func (s *Suite) Test_Pkglint_Main__autofix_exitcode(c *check.C) { | |
255 | t := s.Init(c) | 255 | t := s.Init(c) | |
256 | 256 | |||
257 | t.SetUpPkgsrc() | 257 | t.SetUpPkgsrc() | |
258 | t.CreateFileLines("filename.mk", | 258 | t.CreateFileLines("filename.mk", | |
259 | "") | 259 | "") | |
260 | 260 |
@@ -37,28 +37,28 @@ type Pkgsrc struct { | @@ -37,28 +37,28 @@ type Pkgsrc struct { | |||
37 | listVersions map[string][]string // See Pkgsrc.ListVersions | 37 | listVersions map[string][]string // See Pkgsrc.ListVersions | |
38 | 38 | |||
39 | // Variables that may be overridden by the pkgsrc user. | 39 | // Variables that may be overridden by the pkgsrc user. | |
40 | // They are typically defined in mk/defaults/mk.conf. | 40 | // They are typically defined in mk/defaults/mk.conf. | |
41 | // | 41 | // | |
42 | // Whenever a package uses such a variable, it must add the variable name | 42 | // Whenever a package uses such a variable, it must add the variable name | |
43 | // to BUILD_DEFS. | 43 | // to BUILD_DEFS. | |
44 | UserDefinedVars Scope | 44 | UserDefinedVars Scope | |
45 | 45 | |||
46 | Deprecated map[string]string | 46 | Deprecated map[string]string | |
47 | vartypes VarTypeRegistry | 47 | vartypes VarTypeRegistry | |
48 | } | 48 | } | |
49 | 49 | |||
50 | func NewPkgsrc(dir CurrPath) Pkgsrc { | 50 | func NewPkgsrc(dir CurrPath) *Pkgsrc { | |
51 | return Pkgsrc{ | 51 | return &Pkgsrc{ | |
52 | dir, | 52 | dir, | |
53 | make(map[string]bool), | 53 | make(map[string]bool), | |
54 | NewTools(), | 54 | NewTools(), | |
55 | make(map[string]string), | 55 | make(map[string]string), | |
56 | make(map[string]string), | 56 | make(map[string]string), | |
57 | make(map[string]string), | 57 | make(map[string]string), | |
58 | nil, | 58 | nil, | |
59 | nil, | 59 | nil, | |
60 | Changes{}, | 60 | Changes{}, | |
61 | make(map[string][]string), | 61 | make(map[string][]string), | |
62 | NewScope(), | 62 | NewScope(), | |
63 | make(map[string]string), | 63 | make(map[string]string), | |
64 | NewVarTypeRegistry()} | 64 | NewVarTypeRegistry()} | |
@@ -726,26 +726,29 @@ func (src *Pkgsrc) ListVersions(category | @@ -726,26 +726,29 @@ func (src *Pkgsrc) ListVersions(category | |||
726 | var repls = make([]string, len(names)) | 726 | var repls = make([]string, len(names)) | |
727 | for i, name := range names { | 727 | for i, name := range names { | |
728 | repls[i] = replaceAll(name, re, repl) | 728 | repls[i] = replaceAll(name, re, repl) | |
729 | } | 729 | } | |
730 | 730 | |||
731 | src.listVersions[cacheKey] = repls | 731 | src.listVersions[cacheKey] = repls | |
732 | return repls | 732 | return repls | |
733 | } | 733 | } | |
734 | 734 | |||
735 | // VariableType returns the type of the variable | 735 | // VariableType returns the type of the variable | |
736 | // (possibly guessed based on the variable name), | 736 | // (possibly guessed based on the variable name), | |
737 | // or nil if the type cannot even be guessed. | 737 | // or nil if the type cannot even be guessed. | |
738 | func (src *Pkgsrc) VariableType(mklines *MkLines, varname string) (vartype *Vartype) { | 738 | func (src *Pkgsrc) VariableType(mklines *MkLines, varname string) (vartype *Vartype) { | |
739 | if src == nil { | |||
740 | return nil | |||
741 | } | |||
739 | if trace.Tracing { | 742 | if trace.Tracing { | |
740 | defer trace.Call(varname, trace.Result(&vartype))() | 743 | defer trace.Call(varname, trace.Result(&vartype))() | |
741 | } | 744 | } | |
742 | 745 | |||
743 | // When scanning mk/** for otherwise unknown variables, their type | 746 | // When scanning mk/** for otherwise unknown variables, their type | |
744 | // is set to BtUnknown. These variables must not override the guess | 747 | // is set to BtUnknown. These variables must not override the guess | |
745 | // based on the variable name. | 748 | // based on the variable name. | |
746 | vartype = src.vartypes.Canon(varname) | 749 | vartype = src.vartypes.Canon(varname) | |
747 | if vartype != nil && vartype.basicType != BtUnknown { | 750 | if vartype != nil && vartype.basicType != BtUnknown { | |
748 | return vartype | 751 | return vartype | |
749 | } | 752 | } | |
750 | 753 | |||
751 | if tool := G.ToolByVarname(mklines, varname); tool != nil { | 754 | if tool := G.ToolByVarname(mklines, varname); tool != nil { |
@@ -177,27 +177,27 @@ func (v *Var) Write(mkline *MkLine, cond | @@ -177,27 +177,27 @@ func (v *Var) Write(mkline *MkLine, cond | |||
177 | v.writeLocations = append(v.writeLocations, mkline) | 177 | v.writeLocations = append(v.writeLocations, mkline) | |
178 | 178 | |||
179 | if conditional { | 179 | if conditional { | |
180 | v.conditional = true | 180 | v.conditional = true | |
181 | } | 181 | } | |
182 | v.conditionalVars.AddAll(conditionVarnames) | 182 | v.conditionalVars.AddAll(conditionVarnames) | |
183 | 183 | |||
184 | mkline.ForEachUsed(func(varUse *MkVarUse, time VucTime) { | 184 | mkline.ForEachUsed(func(varUse *MkVarUse, time VucTime) { | |
185 | v.refs.Add(varUse.varname) | 185 | v.refs.Add(varUse.varname) | |
186 | }) | 186 | }) | |
187 | v.refs.AddAll(conditionVarnames) | 187 | v.refs.AddAll(conditionVarnames) | |
188 | 188 | |||
189 | v.update(mkline, &v.valueInfra) | 189 | v.update(mkline, &v.valueInfra) | |
190 | if !G.Pkgsrc.IsInfra(mkline.Line.Filename()) { | 190 | if G.Pkgsrc != nil && !G.Pkgsrc.IsInfra(mkline.Line.Filename()) { | |
191 | v.update(mkline, &v.value) | 191 | v.update(mkline, &v.value) | |
192 | } | 192 | } | |
193 | 193 | |||
194 | v.updateConstantValue(mkline) | 194 | v.updateConstantValue(mkline) | |
195 | } | 195 | } | |
196 | 196 | |||
197 | func (v *Var) update(mkline *MkLine, update *string) { | 197 | func (v *Var) update(mkline *MkLine, update *string) { | |
198 | firstWrite := len(v.writeLocations) == 1 | 198 | firstWrite := len(v.writeLocations) == 1 | |
199 | if v.IsConditional() && !firstWrite { | 199 | if v.IsConditional() && !firstWrite { | |
200 | return | 200 | return | |
201 | } | 201 | } | |
202 | 202 | |||
203 | value := mkline.Value() | 203 | value := mkline.Value() |
@@ -31,27 +31,27 @@ func (s *Suite) Test_VarTypeRegistry_com | @@ -31,27 +31,27 @@ func (s *Suite) Test_VarTypeRegistry_com | |||
31 | ".if ${USE_LANGUAGES:Mexpr-lang} || !empty(USE_LANGUAGES:Mempty-lang)", | 31 | ".if ${USE_LANGUAGES:Mexpr-lang} || !empty(USE_LANGUAGES:Mempty-lang)", | |
32 | ".endif", | 32 | ".endif", | |
33 | "", | 33 | "", | |
34 | // Just for code coverage | 34 | // Just for code coverage | |
35 | ".if ${OTHER} || ${USE_LANGUAGES} \\", | 35 | ".if ${OTHER} || ${USE_LANGUAGES} \\", | |
36 | " || ${USE_LANGUAGES:O} || ${USE_LANGUAGES:Mc++-*}", | 36 | " || ${USE_LANGUAGES:O} || ${USE_LANGUAGES:Mc++-*}", | |
37 | ".endif", | 37 | ".endif", | |
38 | "", | 38 | "", | |
39 | // Just for code coverage | 39 | // Just for code coverage | |
40 | ".if", // missing condition | 40 | ".if", // missing condition | |
41 | ".endif") | 41 | ".endif") | |
42 | reg := NewVarTypeRegistry() | 42 | reg := NewVarTypeRegistry() | |
43 | 43 | |||
44 | compilerLanguages := reg.compilerLanguages(&G.Pkgsrc) | 44 | compilerLanguages := reg.compilerLanguages(G.Pkgsrc) | |
45 | 45 | |||
46 | enumValues := compilerLanguages.AllowedEnums() | 46 | enumValues := compilerLanguages.AllowedEnums() | |
47 | t.CheckEquals(enumValues, "empty-lang expr-lang gnu++14") | 47 | t.CheckEquals(enumValues, "empty-lang expr-lang gnu++14") | |
48 | } | 48 | } | |
49 | 49 | |||
50 | func (s *Suite) Test_VarTypeRegistry_enumFrom(c *check.C) { | 50 | func (s *Suite) Test_VarTypeRegistry_enumFrom(c *check.C) { | |
51 | t := s.Init(c) | 51 | t := s.Init(c) | |
52 | 52 | |||
53 | t.CreateFileLines("editors/emacs/modules.mk", | 53 | t.CreateFileLines("editors/emacs/modules.mk", | |
54 | MkCvsID, | 54 | MkCvsID, | |
55 | "", | 55 | "", | |
56 | "_EMACS_VERSIONS_ALL= emacs31", | 56 | "_EMACS_VERSIONS_ALL= emacs31", | |
57 | "_EMACS_VERSIONS_ALL+= emacs29") | 57 | "_EMACS_VERSIONS_ALL+= emacs29") | |
@@ -91,29 +91,29 @@ func (s *Suite) Test_VarTypeRegistry_enu | @@ -91,29 +91,29 @@ func (s *Suite) Test_VarTypeRegistry_enu | |||
91 | "gnu++03 gnu++0x gnu++11 gnu++14 (list, package-settable)") | 91 | "gnu++03 gnu++0x gnu++11 gnu++14 (list, package-settable)") | |
92 | test("PKGSRC_COMPILER", "enum: ccache distcc f2c g95 gcc ido mipspro-ucode sunpro (list, user-settable)") | 92 | test("PKGSRC_COMPILER", "enum: ccache distcc f2c g95 gcc ido mipspro-ucode sunpro (list, user-settable)") | |
93 | } | 93 | } | |
94 | 94 | |||
95 | func (s *Suite) Test_VarTypeRegistry_enumFrom__no_tracing(c *check.C) { | 95 | func (s *Suite) Test_VarTypeRegistry_enumFrom__no_tracing(c *check.C) { | |
96 | t := s.Init(c) | 96 | t := s.Init(c) | |
97 | 97 | |||
98 | t.CreateFileLines("mk/existing.mk", | 98 | t.CreateFileLines("mk/existing.mk", | |
99 | MkCvsID, | 99 | MkCvsID, | |
100 | "VAR=\tfirst second") | 100 | "VAR=\tfirst second") | |
101 | reg := NewVarTypeRegistry() | 101 | reg := NewVarTypeRegistry() | |
102 | t.DisableTracing() | 102 | t.DisableTracing() | |
103 | 103 | |||
104 | existingType := reg.enumFrom(&G.Pkgsrc, "mk/existing.mk", "defval", "VAR") | 104 | existingType := reg.enumFrom(G.Pkgsrc, "mk/existing.mk", "defval", "VAR") | |
105 | noAssignmentsType := reg.enumFrom(&G.Pkgsrc, "mk/existing.mk", "defval", "OTHER_VAR") | 105 | noAssignmentsType := reg.enumFrom(G.Pkgsrc, "mk/existing.mk", "defval", "OTHER_VAR") | |
106 | nonexistentType := reg.enumFrom(&G.Pkgsrc, "mk/nonexistent.mk", "defval", "VAR") | 106 | nonexistentType := reg.enumFrom(G.Pkgsrc, "mk/nonexistent.mk", "defval", "VAR") | |
107 | 107 | |||
108 | t.CheckEquals(existingType.AllowedEnums(), "first second") | 108 | t.CheckEquals(existingType.AllowedEnums(), "first second") | |
109 | t.CheckEquals(noAssignmentsType.AllowedEnums(), "defval") | 109 | t.CheckEquals(noAssignmentsType.AllowedEnums(), "defval") | |
110 | t.CheckEquals(nonexistentType.AllowedEnums(), "defval") | 110 | t.CheckEquals(nonexistentType.AllowedEnums(), "defval") | |
111 | } | 111 | } | |
112 | 112 | |||
113 | func (s *Suite) Test_VarTypeRegistry_enumFrom__no_testing(c *check.C) { | 113 | func (s *Suite) Test_VarTypeRegistry_enumFrom__no_testing(c *check.C) { | |
114 | t := s.Init(c) | 114 | t := s.Init(c) | |
115 | 115 | |||
116 | G.Testing = false | 116 | G.Testing = false | |
117 | 117 | |||
118 | t.ExpectFatal( | 118 | t.ExpectFatal( | |
119 | t.SetUpVartypes, | 119 | t.SetUpVartypes, | |
@@ -152,27 +152,27 @@ func (s *Suite) Test_VarTypeRegistry_enu | @@ -152,27 +152,27 @@ func (s *Suite) Test_VarTypeRegistry_enu | |||
152 | } | 152 | } | |
153 | 153 | |||
154 | test("PYPKGPREFIX", "enum: py28 py33 (system-provided)") | 154 | test("PYPKGPREFIX", "enum: py28 py33 (system-provided)") | |
155 | } | 155 | } | |
156 | 156 | |||
157 | func (s *Suite) Test_VarTypeRegistry_enumFromDirs__no_testing(c *check.C) { | 157 | func (s *Suite) Test_VarTypeRegistry_enumFromDirs__no_testing(c *check.C) { | |
158 | t := s.Init(c) | 158 | t := s.Init(c) | |
159 | 159 | |||
160 | G.Testing = false | 160 | G.Testing = false | |
161 | 161 | |||
162 | t.ExpectFatal( | 162 | t.ExpectFatal( | |
163 | func() { | 163 | func() { | |
164 | G.Pkgsrc.vartypes.enumFromDirs( | 164 | G.Pkgsrc.vartypes.enumFromDirs( | |
165 | &G.Pkgsrc, "category", `^pack.*`, "$0", "default") | 165 | G.Pkgsrc, "category", `^pack.*`, "$0", "default") | |
166 | }, | 166 | }, | |
167 | "FATAL: ~/category: Must contain at least 1 "+ | 167 | "FATAL: ~/category: Must contain at least 1 "+ | |
168 | "subdirectory matching \"^pack.*\".") | 168 | "subdirectory matching \"^pack.*\".") | |
169 | } | 169 | } | |
170 | 170 | |||
171 | func (s *Suite) Test_VarTypeRegistry_enumFromFiles(c *check.C) { | 171 | func (s *Suite) Test_VarTypeRegistry_enumFromFiles(c *check.C) { | |
172 | t := s.Init(c) | 172 | t := s.Init(c) | |
173 | 173 | |||
174 | t.CreateFileLines("mk/platform/NetBSD.mk") | 174 | t.CreateFileLines("mk/platform/NetBSD.mk") | |
175 | t.CreateFileLines("mk/platform/README") | 175 | t.CreateFileLines("mk/platform/README") | |
176 | t.CreateFileLines("mk/platform/SunOS.mk") | 176 | t.CreateFileLines("mk/platform/SunOS.mk") | |
177 | t.CreateFileLines("mk/platform/SunOS.mk~") | 177 | t.CreateFileLines("mk/platform/SunOS.mk~") | |
178 | 178 | |||
@@ -183,50 +183,50 @@ func (s *Suite) Test_VarTypeRegistry_enu | @@ -183,50 +183,50 @@ func (s *Suite) Test_VarTypeRegistry_enu | |||
183 | t.CheckEquals(vartype, values) | 183 | t.CheckEquals(vartype, values) | |
184 | } | 184 | } | |
185 | 185 | |||
186 | test("OPSYS", "enum: NetBSD SunOS (system-provided)") | 186 | test("OPSYS", "enum: NetBSD SunOS (system-provided)") | |
187 | } | 187 | } | |
188 | 188 | |||
189 | func (s *Suite) Test_VarTypeRegistry_enumFromFiles__no_testing(c *check.C) { | 189 | func (s *Suite) Test_VarTypeRegistry_enumFromFiles__no_testing(c *check.C) { | |
190 | t := s.Init(c) | 190 | t := s.Init(c) | |
191 | 191 | |||
192 | G.Testing = false | 192 | G.Testing = false | |
193 | 193 | |||
194 | t.ExpectFatal( | 194 | t.ExpectFatal( | |
195 | func() { | 195 | func() { | |
196 | G.Pkgsrc.vartypes.enumFromFiles(&G.Pkgsrc, | 196 | G.Pkgsrc.vartypes.enumFromFiles(G.Pkgsrc, | |
197 | "mk/platform", `^(\w+)\.mk$`, "$1", "default") | 197 | "mk/platform", `^(\w+)\.mk$`, "$1", "default") | |
198 | }, | 198 | }, | |
199 | "FATAL: ~/mk/platform: Must contain at least 1 "+ | 199 | "FATAL: ~/mk/platform: Must contain at least 1 "+ | |
200 | "file matching \"^(\\\\w+)\\\\.mk$\".") | 200 | "file matching \"^(\\\\w+)\\\\.mk$\".") | |
201 | } | 201 | } | |
202 | 202 | |||
203 | func (s *Suite) Test_VarTypeRegistry_options__assertion(c *check.C) { | 203 | func (s *Suite) Test_VarTypeRegistry_options__assertion(c *check.C) { | |
204 | t := s.Init(c) | 204 | t := s.Init(c) | |
205 | 205 | |||
206 | reg := NewVarTypeRegistry() | 206 | reg := NewVarTypeRegistry() | |
207 | 207 | |||
208 | t.ExpectAssert(func() { | 208 | t.ExpectAssert(func() { | |
209 | reg.options( | 209 | reg.options( | |
210 | SystemProvided, | 210 | SystemProvided, | |
211 | []vartypeOptions{DefinedIfInScope, NonemptyIfDefined}) | 211 | []vartypeOptions{DefinedIfInScope, NonemptyIfDefined}) | |
212 | }) | 212 | }) | |
213 | } | 213 | } | |
214 | 214 | |||
215 | func (s *Suite) Test_VarTypeRegistry_Init(c *check.C) { | 215 | func (s *Suite) Test_VarTypeRegistry_Init(c *check.C) { | |
216 | t := s.Init(c) | 216 | t := s.Init(c) | |
217 | 217 | |||
218 | src := NewPkgsrc(t.File(".")) | 218 | src := NewPkgsrc(t.File(".")) | |
219 | src.vartypes.Init(&src) | 219 | src.vartypes.Init(src) | |
220 | 220 | |||
221 | t.CheckEquals(src.vartypes.Canon("BSD_MAKE_ENV").basicType.name, "ShellWord") | 221 | t.CheckEquals(src.vartypes.Canon("BSD_MAKE_ENV").basicType.name, "ShellWord") | |
222 | t.CheckEquals(src.vartypes.Canon("USE_BUILTIN.*").basicType.name, "YesNoIndirectly") | 222 | t.CheckEquals(src.vartypes.Canon("USE_BUILTIN.*").basicType.name, "YesNoIndirectly") | |
223 | } | 223 | } | |
224 | 224 | |||
225 | func (s *Suite) Test_VarTypeRegistry_Init__LP64PLATFORMS(c *check.C) { | 225 | func (s *Suite) Test_VarTypeRegistry_Init__LP64PLATFORMS(c *check.C) { | |
226 | t := s.Init(c) | 226 | t := s.Init(c) | |
227 | 227 | |||
228 | pkg := t.SetUpPackage("category/package", | 228 | pkg := t.SetUpPackage("category/package", | |
229 | "BROKEN_ON_PLATFORM=\t${LP64PLATFORMS}") | 229 | "BROKEN_ON_PLATFORM=\t${LP64PLATFORMS}") | |
230 | t.FinishSetUp() | 230 | t.FinishSetUp() | |
231 | 231 | |||
232 | G.Check(pkg) | 232 | G.Check(pkg) |