pkgtools/pkglint: update to 22.2.3 Changes since 22.2.2: CHECK_WRKREF is known to pkglint, which prevents conditions using this variable from being simplified in a wrong way. For variables that are guaranteed to be defined, suggest to simplify the condition '!empty(VAR:M[Yy][Ee][Ss])' to '${VAR:M[Yy][Ee][Ss]}', as that reduces the number of negations in the condition. Detect redundant WRKSRC definitions and suggest to remove them. Fix wrong "c99 is not valid for USE_LANGUAGES" warnings.diff -r1.722 -r1.723 pkgsrc/pkgtools/pkglint/Makefile
(rillig)
@@ -1,17 +1,16 @@ | @@ -1,17 +1,16 @@ | |||
1 | # $NetBSD: Makefile,v 1.722 2022/07/13 16:03:04 bsiegert Exp $ | 1 | # $NetBSD: Makefile,v 1.723 2022/07/24 20:07:20 rillig Exp $ | |
2 | 2 | |||
3 | PKGNAME= pkglint-22.2.2 | 3 | PKGNAME= pkglint-22.2.3 | |
4 | PKGREVISION= 1 | |||
5 | CATEGORIES= pkgtools | 4 | CATEGORIES= pkgtools | |
6 | DISTNAME= tools | 5 | DISTNAME= tools | |
7 | MASTER_SITES= ${MASTER_SITE_GITHUB:=golang/} | 6 | MASTER_SITES= ${MASTER_SITE_GITHUB:=golang/} | |
8 | GITHUB_PROJECT= tools | 7 | GITHUB_PROJECT= tools | |
9 | GITHUB_TAG= 92d8274bd7b8a4c65f24bafe401a029e58392704 | 8 | GITHUB_TAG= 92d8274bd7b8a4c65f24bafe401a029e58392704 | |
10 | 9 | |||
11 | MAINTAINER= rillig@NetBSD.org | 10 | MAINTAINER= rillig@NetBSD.org | |
12 | HOMEPAGE= https://github.com/rillig/pkglint | 11 | HOMEPAGE= https://github.com/rillig/pkglint | |
13 | COMMENT= Verifier for NetBSD packages | 12 | COMMENT= Verifier for NetBSD packages | |
14 | LICENSE= 2-clause-bsd | 13 | LICENSE= 2-clause-bsd | |
15 | CONFLICTS+= pkglint4-[0-9]* | 14 | CONFLICTS+= pkglint4-[0-9]* | |
16 | 15 | |||
17 | USE_TOOLS+= pax | 16 | USE_TOOLS+= pax |
@@ -484,26 +484,31 @@ func (ck *MkAssignChecker) checkRight() | @@ -484,26 +484,31 @@ func (ck *MkAssignChecker) checkRight() | |||
484 | mkline := ck.MkLine | 484 | mkline := ck.MkLine | |
485 | varname := mkline.Varname() | 485 | varname := mkline.Varname() | |
486 | op := mkline.Op() | 486 | op := mkline.Op() | |
487 | value := mkline.Value() | 487 | value := mkline.Value() | |
488 | comment := condStr(mkline.HasComment(), "#", "") + mkline.Comment() | 488 | comment := condStr(mkline.HasComment(), "#", "") + mkline.Comment() | |
489 | 489 | |||
490 | if trace.Tracing { | 490 | if trace.Tracing { | |
491 | defer trace.Call(varname, op, value)() | 491 | defer trace.Call(varname, op, value)() | |
492 | } | 492 | } | |
493 | 493 | |||
494 | mkLineChecker := NewMkLineChecker(ck.MkLines, ck.MkLine) | 494 | mkLineChecker := NewMkLineChecker(ck.MkLines, ck.MkLine) | |
495 | mkLineChecker.checkText(value) | 495 | mkLineChecker.checkText(value) | |
496 | mkLineChecker.checkVartype(varname, op, value, comment) | 496 | mkLineChecker.checkVartype(varname, op, value, comment) | |
497 | if mkline.IsEmpty() { | |||
498 | // The line type can change due to an Autofix, see for example | |||
499 | // VartypeCheck.WrkdirSubdirectory. | |||
500 | return | |||
501 | } | |||
497 | 502 | |||
498 | ck.checkMisc() | 503 | ck.checkMisc() | |
499 | 504 | |||
500 | ck.checkRightVaruse() | 505 | ck.checkRightVaruse() | |
501 | } | 506 | } | |
502 | 507 | |||
503 | func (ck *MkAssignChecker) checkRightCategory() { | 508 | func (ck *MkAssignChecker) checkRightCategory() { | |
504 | mkline := ck.MkLine | 509 | mkline := ck.MkLine | |
505 | if mkline.Op() != opAssign && mkline.Op() != opAssignDefault { | 510 | if mkline.Op() != opAssign && mkline.Op() != opAssignDefault { | |
506 | return | 511 | return | |
507 | } | 512 | } | |
508 | 513 | |||
509 | categories := mkline.ValueFields(mkline.Value()) | 514 | categories := mkline.ValueFields(mkline.Value()) |
@@ -231,27 +231,27 @@ func (s *RedundantScope) handleVarUse(mk | @@ -231,27 +231,27 @@ func (s *RedundantScope) handleVarUse(mk | |||
231 | case mkline.IsDirective(): | 231 | case mkline.IsDirective(): | |
232 | // TODO: Handle varuse for conditions and loops. | 232 | // TODO: Handle varuse for conditions and loops. | |
233 | break | 233 | break | |
234 | 234 | |||
235 | case mkline.IsInclude(), mkline.IsSysinclude(): | 235 | case mkline.IsInclude(), mkline.IsSysinclude(): | |
236 | // TODO: Handle VarUse for includes, which may reference variables. | 236 | // TODO: Handle VarUse for includes, which may reference variables. | |
237 | break | 237 | break | |
238 | 238 | |||
239 | case mkline.IsDependency(): | 239 | case mkline.IsDependency(): | |
240 | // TODO: Handle VarUse for this case. | 240 | // TODO: Handle VarUse for this case. | |
241 | } | 241 | } | |
242 | } | 242 | } | |
243 | 243 | |||
244 | // access returns the info for the given variable, creating it if necessary. | 244 | // get returns the info for the given variable, creating it if necessary. | |
245 | func (s *RedundantScope) get(varname string) *redundantScopeVarinfo { | 245 | func (s *RedundantScope) get(varname string) *redundantScopeVarinfo { | |
246 | info := s.vars[varname] | 246 | info := s.vars[varname] | |
247 | if info == nil { | 247 | if info == nil { | |
248 | v := NewVar(varname) | 248 | v := NewVar(varname) | |
249 | info = &redundantScopeVarinfo{v, nil, 0} | 249 | info = &redundantScopeVarinfo{v, nil, 0} | |
250 | s.vars[varname] = info | 250 | s.vars[varname] = info | |
251 | } | 251 | } | |
252 | return info | 252 | return info | |
253 | } | 253 | } | |
254 | 254 | |||
255 | // access records the current file location, to be used in later inclusion checks. | 255 | // access records the current file location, to be used in later inclusion checks. | |
256 | func (s *RedundantScope) access(varname string) { | 256 | func (s *RedundantScope) access(varname string) { | |
257 | info := s.vars[varname] | 257 | info := s.vars[varname] |
@@ -99,27 +99,29 @@ func (ck *MkCondChecker) checkNotEmpty(n | @@ -99,27 +99,29 @@ func (ck *MkCondChecker) checkNotEmpty(n | |||
99 | "Besides being simpler to read, the expression will also fail", | 99 | "Besides being simpler to read, the expression will also fail", | |
100 | "quickly with a \"Malformed conditional\" error from bmake", | 100 | "quickly with a \"Malformed conditional\" error from bmake", | |
101 | "if it should ever be undefined at this point.", | 101 | "if it should ever be undefined at this point.", | |
102 | "This catches typos and other programming mistakes.") | 102 | "This catches typos and other programming mistakes.") | |
103 | fix.Replace(from, to) | 103 | fix.Replace(from, to) | |
104 | fix.Apply() | 104 | fix.Apply() | |
105 | } | 105 | } | |
106 | 106 | |||
107 | // checkEmpty checks a condition of the form empty(VAR), | 107 | // checkEmpty checks a condition of the form empty(VAR), | |
108 | // empty(VAR:Mpattern) or ${VAR:Mpattern} in an .if directive. | 108 | // empty(VAR:Mpattern) or ${VAR:Mpattern} in an .if directive. | |
109 | func (ck *MkCondChecker) checkEmpty(varuse *MkVarUse, fromEmpty bool, neg bool) { | 109 | func (ck *MkCondChecker) checkEmpty(varuse *MkVarUse, fromEmpty bool, neg bool) { | |
110 | ck.checkEmptyExpr(varuse) | 110 | ck.checkEmptyExpr(varuse) | |
111 | ck.checkEmptyType(varuse) | 111 | ck.checkEmptyType(varuse) | |
112 | ck.simplify(varuse, fromEmpty, neg) | 112 | ||
113 | s := MkCondSimplifier{ck.MkLines, ck.MkLine} | |||
114 | s.SimplifyVarUse(varuse, fromEmpty, neg) | |||
113 | } | 115 | } | |
114 | 116 | |||
115 | func (ck *MkCondChecker) checkEmptyExpr(varuse *MkVarUse) { | 117 | func (ck *MkCondChecker) checkEmptyExpr(varuse *MkVarUse) { | |
116 | if !matches(varuse.varname, `^\$.*:[MN]`) { | 118 | if !matches(varuse.varname, `^\$.*:[MN]`) { | |
117 | return | 119 | return | |
118 | } | 120 | } | |
119 | 121 | |||
120 | ck.MkLine.Warnf("The empty() function takes a variable name as parameter, " + | 122 | ck.MkLine.Warnf("The empty() function takes a variable name as parameter, " + | |
121 | "not a variable expression.") | 123 | "not a variable expression.") | |
122 | ck.MkLine.Explain( | 124 | ck.MkLine.Explain( | |
123 | "Instead of empty(${VARNAME:Mpattern}), you should write either of the following:", | 125 | "Instead of empty(${VARNAME:Mpattern}), you should write either of the following:", | |
124 | "", | 126 | "", | |
125 | "\tempty(VARNAME:Mpattern)", | 127 | "\tempty(VARNAME:Mpattern)", | |
@@ -150,165 +152,26 @@ func (ck *MkCondChecker) checkEmptyType( | @@ -150,165 +152,26 @@ func (ck *MkCondChecker) checkEmptyType( | |||
150 | } | 152 | } | |
151 | 153 | |||
152 | // mkCondStringLiteralUnquoted contains a safe subset of the characters | 154 | // mkCondStringLiteralUnquoted contains a safe subset of the characters | |
153 | // 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 | |
154 | // ${PKGPATH} == category/package. | 156 | // ${PKGPATH} == category/package. | |
155 | // TODO: Check whether the ',' really needs to be here. | 157 | // TODO: Check whether the ',' really needs to be here. | |
156 | var mkCondStringLiteralUnquoted = textproc.NewByteSet("-+,./0-9@A-Z_a-z") | 158 | var mkCondStringLiteralUnquoted = textproc.NewByteSet("-+,./0-9@A-Z_a-z") | |
157 | 159 | |||
158 | // mkCondModifierPatternLiteral contains a safe subset of the characters | 160 | // mkCondModifierPatternLiteral contains a safe subset of the characters | |
159 | // that are interpreted literally in the :M and :N modifiers. | 161 | // that are interpreted literally in the :M and :N modifiers. | |
160 | // TODO: Check whether the ',' really needs to be here. | 162 | // TODO: Check whether the ',' really needs to be here. | |
161 | var mkCondModifierPatternLiteral = textproc.NewByteSet("-+,./0-9<=>@A-Z_a-z") | 163 | var mkCondModifierPatternLiteral = textproc.NewByteSet("-+,./0-9<=>@A-Z_a-z") | |
162 | 164 | |||
163 | // simplify replaces an unnecessarily complex condition with | |||
164 | // a simpler condition that's still equivalent. | |||
165 | // | |||
166 | // * fromEmpty is true for the form empty(VAR...), and false for ${VAR...}. | |||
167 | // | |||
168 | // * neg is true for the form !empty(VAR...), and false for empty(VAR...). | |||
169 | func (ck *MkCondChecker) simplify(varuse *MkVarUse, fromEmpty bool, neg bool) { | |||
170 | varname := varuse.varname | |||
171 | modifiers := varuse.modifiers | |||
172 | ||||
173 | n := len(modifiers) | |||
174 | if n == 0 { | |||
175 | return | |||
176 | } | |||
177 | modsExceptLast := NewMkVarUse("", modifiers[:n-1]...).Mod() | |||
178 | vartype := G.Pkgsrc.VariableType(ck.MkLines, varname) | |||
179 | ||||
180 | isDefined := func() bool { | |||
181 | if vartype.IsAlwaysInScope() && vartype.IsDefinedIfInScope() { | |||
182 | return true | |||
183 | } | |||
184 | ||||
185 | // For run time expressions, such as ${${VAR} == value:?yes:no}, | |||
186 | // the scope would need to be changed to ck.MkLines.allVars. | |||
187 | if ck.MkLines.checkAllData.vars.IsDefined(varname) { | |||
188 | return true | |||
189 | } | |||
190 | ||||
191 | return ck.MkLines.Tools.SeenPrefs && | |||
192 | vartype.Union().Contains(aclpUseLoadtime) && | |||
193 | vartype.IsDefinedIfInScope() | |||
194 | } | |||
195 | ||||
196 | // replace constructs the state before and after the autofix. | |||
197 | // The before state is constructed to ensure that only very simple | |||
198 | // patterns get replaced automatically. | |||
199 | // | |||
200 | // Before putting any cases involving special characters into | |||
201 | // production, there need to be more tests for the edge cases. | |||
202 | replace := func(positive bool, pattern string) (bool, string, string) { | |||
203 | defined := isDefined() | |||
204 | if !defined && !positive { | |||
205 | // TODO: This is a double negation, maybe even triple. | |||
206 | // There is an :N pattern, and the variable may be undefined. | |||
207 | // If it is indeed undefined, should the whole condition | |||
208 | // evaluate to true or false? | |||
209 | // The cases to be distinguished are: undefined, empty, filled. | |||
210 | ||||
211 | // For now, be conservative and don't suggest anything wrong. | |||
212 | return false, "", "" | |||
213 | } | |||
214 | uMod := condStr(!defined && !varuse.HasModifier("U"), ":U", "") | |||
215 | ||||
216 | op := condStr(neg == positive, "==", "!=") | |||
217 | ||||
218 | from := sprintf("%s%s%s%s%s%s%s", | |||
219 | condStr(neg != fromEmpty, "", "!"), | |||
220 | condStr(fromEmpty, "empty(", "${"), | |||
221 | varname, | |||
222 | modsExceptLast, | |||
223 | condStr(positive, ":M", ":N"), | |||
224 | pattern, | |||
225 | condStr(fromEmpty, ")", "}")) | |||
226 | ||||
227 | needsQuotes := textproc.NewLexer(pattern).NextBytesSet(mkCondStringLiteralUnquoted) != pattern || | |||
228 | pattern == "" || | |||
229 | matches(pattern, `^\d+\.?\d*$`) | |||
230 | quote := condStr(needsQuotes, "\"", "") | |||
231 | ||||
232 | to := sprintf( | |||
233 | "${%s%s%s} %s %s%s%s", | |||
234 | varname, uMod, modsExceptLast, op, quote, pattern, quote) | |||
235 | ||||
236 | return true, from, to | |||
237 | } | |||
238 | ||||
239 | modifier := modifiers[n-1] | |||
240 | ok, positive, pattern, exact := modifier.MatchMatch() | |||
241 | if !ok || !positive && n != 1 { | |||
242 | return | |||
243 | } | |||
244 | ||||
245 | // Replace !empty(VAR:M*.c) with ${VAR:M*.c}. | |||
246 | // Replace empty(VAR:M*.c) with !${VAR:M*.c}. | |||
247 | if fromEmpty && positive && !exact && vartype != nil && isDefined() && | |||
248 | // Restrict replacements to very simple patterns with only few | |||
249 | // special characters, for now. | |||
250 | // Before generalizing this to arbitrary strings, there has to be | |||
251 | // a proper code generator for these conditions that handles all | |||
252 | // possible escaping. | |||
253 | // The same reasoning applies to the variable name, even though the | |||
254 | // variable name typically only uses a restricted character set. | |||
255 | matches(varuse.Mod(), `^[*.:\w]+$`) { | |||
256 | ||||
257 | fixedPart := varname + modsExceptLast + ":M" + pattern | |||
258 | from := condStr(neg, "!", "") + "empty(" + fixedPart + ")" | |||
259 | to := condStr(neg, "", "!") + "${" + fixedPart + "}" | |||
260 | ||||
261 | fix := ck.MkLine.Autofix() | |||
262 | fix.Notef("%q can be simplified to %q.", from, to) | |||
263 | fix.Explain( | |||
264 | "This variable is guaranteed to be defined at this point.", | |||
265 | "Therefore it may occur on the left-hand side of a comparison", | |||
266 | "and doesn't have to be guarded by the function 'empty'.") | |||
267 | fix.Replace(from, to) | |||
268 | fix.Apply() | |||
269 | ||||
270 | return | |||
271 | } | |||
272 | ||||
273 | switch { | |||
274 | case !exact, | |||
275 | vartype == nil, | |||
276 | vartype.IsList(), | |||
277 | textproc.NewLexer(pattern).NextBytesSet(mkCondModifierPatternLiteral) != pattern: | |||
278 | return | |||
279 | } | |||
280 | ||||
281 | ok, from, to := replace(positive, pattern) | |||
282 | if !ok { | |||
283 | return | |||
284 | } | |||
285 | ||||
286 | fix := ck.MkLine.Autofix() | |||
287 | fix.Notef("%s can be compared using the simpler \"%s\" "+ | |||
288 | "instead of matching against %q.", | |||
289 | varname, to, ":"+modifier.String()) // TODO: Quoted | |||
290 | fix.Explain( | |||
291 | "This variable has a single value, not a list of values.", | |||
292 | "Therefore it feels strange to apply list operators like :M and :N onto it.", | |||
293 | "A more direct approach is to use the == and != operators.", | |||
294 | "", | |||
295 | "An entirely different case is when the pattern contains", | |||
296 | "wildcards like *, ?, [].", | |||
297 | "In such a case, using the :M or :N modifiers is useful and preferred.") | |||
298 | fix.Replace(from, to) | |||
299 | fix.Apply() | |||
300 | } | |||
301 | ||||
302 | func (ck *MkCondChecker) checkCompare(left *MkCondTerm, op string, right *MkCondTerm) { | 165 | func (ck *MkCondChecker) checkCompare(left *MkCondTerm, op string, right *MkCondTerm) { | |
303 | switch { | 166 | switch { | |
304 | case right.Num != "": | 167 | case right.Num != "": | |
305 | ck.checkCompareVarNum(op, right.Num) | 168 | ck.checkCompareVarNum(op, right.Num) | |
306 | case left.Var != nil && right.Var == nil: | 169 | case left.Var != nil && right.Var == nil: | |
307 | ck.checkCompareVarStr(left.Var, op, right.Str) | 170 | ck.checkCompareVarStr(left.Var, op, right.Str) | |
308 | } | 171 | } | |
309 | } | 172 | } | |
310 | 173 | |||
311 | func (ck *MkCondChecker) checkCompareVarStr(varuse *MkVarUse, op string, str string) { | 174 | func (ck *MkCondChecker) checkCompareVarStr(varuse *MkVarUse, op string, str string) { | |
312 | varname := varuse.varname | 175 | varname := varuse.varname | |
313 | varmods := varuse.modifiers | 176 | varmods := varuse.modifiers | |
314 | switch len(varmods) { | 177 | switch len(varmods) { |
@@ -520,743 +520,26 @@ func (s *Suite) Test_MkCondChecker_check | @@ -520,743 +520,26 @@ func (s *Suite) Test_MkCondChecker_check | |||
520 | nil...) | 520 | nil...) | |
521 | test(".if !empty(OPSYS:H:Mok)", | 521 | test(".if !empty(OPSYS:H:Mok)", | |
522 | nil...) | 522 | nil...) | |
523 | test(".if !empty(OPSYS:R:Mok)", | 523 | test(".if !empty(OPSYS:R:Mok)", | |
524 | nil...) | 524 | nil...) | |
525 | test(".if !empty(OPSYS:tl:Mok)", | 525 | test(".if !empty(OPSYS:tl:Mok)", | |
526 | nil...) | 526 | nil...) | |
527 | test(".if !empty(OPSYS:tW:Mok)", | 527 | test(".if !empty(OPSYS:tW:Mok)", | |
528 | nil...) | 528 | nil...) | |
529 | test(".if !empty(OPSYS:tW:Mok)", | 529 | test(".if !empty(OPSYS:tW:Mok)", | |
530 | nil...) | 530 | nil...) | |
531 | } | 531 | } | |
532 | 532 | |||
533 | func (s *Suite) Test_MkCondChecker_simplify(c *check.C) { | |||
534 | t := s.Init(c) | |||
535 | ||||
536 | t.CreateFileLines("mk/bsd.prefs.mk") | |||
537 | t.Chdir("category/package") | |||
538 | ||||
539 | // The Anything type suppresses the warnings from type checking. | |||
540 | // BtUnknown would not work here, see Pkgsrc.VariableType. | |||
541 | btAnything := &BasicType{"Anything", func(cv *VartypeCheck) {}} | |||
542 | ||||
543 | // For simplifying the expressions, it is necessary to know whether | |||
544 | // a variable can be undefined. Undefined variables need the | |||
545 | // :U modifier or must be enclosed in double quotes, otherwise | |||
546 | // bmake will complain about a "Malformed conditional". That error | |||
547 | // message is not entirely precise since the expression | |||
548 | // is syntactically valid, it's just the evaluation that fails. | |||
549 | // | |||
550 | // Some variables such as MACHINE_ARCH are in scope from the very | |||
551 | // beginning. | |||
552 | // | |||
553 | // Some variables such as PKGPATH are in scope after bsd.prefs.mk | |||
554 | // has been included. | |||
555 | // | |||
556 | // Some variables such as PREFIX (as of December 2019) are only in | |||
557 | // scope after bsd.pkg.mk has been included. These cannot be used | |||
558 | // in .if conditions at all. | |||
559 | // | |||
560 | // Even when they are in scope, some variables such as PKGREVISION | |||
561 | // or MAKE_JOBS may be undefined. | |||
562 | ||||
563 | t.SetUpVarType("IN_SCOPE_DEFINED", btAnything, AlwaysInScope|DefinedIfInScope, | |||
564 | "*.mk: use, use-loadtime") | |||
565 | t.SetUpVarType("IN_SCOPE", btAnything, AlwaysInScope, | |||
566 | "*.mk: use, use-loadtime") | |||
567 | t.SetUpVarType("PREFS_DEFINED", btAnything, DefinedIfInScope, | |||
568 | "*.mk: use, use-loadtime") | |||
569 | t.SetUpVarType("PREFS", btAnything, NoVartypeOptions, | |||
570 | "*.mk: use, use-loadtime") | |||
571 | t.SetUpVarType("LATER_DEFINED", btAnything, DefinedIfInScope, | |||
572 | "*.mk: use") | |||
573 | t.SetUpVarType("LATER", btAnything, NoVartypeOptions, | |||
574 | "*.mk: use") | |||
575 | // UNDEFINED is also used in the following tests, but is obviously | |||
576 | // not defined here. | |||
577 | ||||
578 | // prefs: whether to include bsd.prefs.mk before | |||
579 | // before: the directive before the condition is simplified | |||
580 | // after: the directive after the condition is simplified | |||
581 | doTest := func(prefs bool, before, after string, diagnostics ...string) { | |||
582 | if !matches(before, `IN_SCOPE|PREFS|LATER|UNDEFINED`) { | |||
583 | c.Errorf("Condition %q must include one of the above variable names.", before) | |||
584 | } | |||
585 | mklines := t.SetUpFileMkLines("filename.mk", | |||
586 | MkCvsID, | |||
587 | condStr(prefs, ".include \"../../mk/bsd.prefs.mk\"", ""), | |||
588 | before, | |||
589 | ".endif") | |||
590 | ||||
591 | action := func(autofix bool) { | |||
592 | mklines.ForEach(func(mkline *MkLine) { | |||
593 | // Sets mklines.Tools.SeenPrefs, which decides whether the :U modifier | |||
594 | // is necessary; see MkLines.checkLine. | |||
595 | mklines.Tools.ParseToolLine(mklines, mkline, false, false) | |||
596 | ||||
597 | if mkline.IsDirective() && mkline.Directive() != "endif" { | |||
598 | // TODO: Replace Check with a more | |||
599 | // specific method that does not do the type checks. | |||
600 | NewMkCondChecker(mkline, mklines).Check() | |||
601 | } | |||
602 | }) | |||
603 | ||||
604 | if autofix { | |||
605 | afterMklines := LoadMk(t.File("filename.mk"), nil, MustSucceed) | |||
606 | t.CheckEquals(afterMklines.mklines[2].Text, after) | |||
607 | } | |||
608 | } | |||
609 | ||||
610 | t.ExpectDiagnosticsAutofix(action, diagnostics...) | |||
611 | } | |||
612 | ||||
613 | testBeforePrefs := func(before, after string, diagnostics ...string) { | |||
614 | doTest(false, before, after, diagnostics...) | |||
615 | } | |||
616 | testAfterPrefs := func(before, after string, diagnostics ...string) { | |||
617 | doTest(true, before, after, diagnostics...) | |||
618 | } | |||
619 | testBeforeAndAfterPrefs := func(before, after string, diagnostics ...string) { | |||
620 | doTest(false, before, after, diagnostics...) | |||
621 | doTest(true, before, after, diagnostics...) | |||
622 | } | |||
623 | ||||
624 | testBeforeAndAfterPrefs( | |||
625 | ".if ${IN_SCOPE_DEFINED:Mpattern}", | |||
626 | ".if ${IN_SCOPE_DEFINED} == pattern", | |||
627 | ||||
628 | "NOTE: filename.mk:3: IN_SCOPE_DEFINED can be "+ | |||
629 | "compared using the simpler \"${IN_SCOPE_DEFINED} == pattern\" "+ | |||
630 | "instead of matching against \":Mpattern\".", | |||
631 | "AUTOFIX: filename.mk:3: Replacing \"${IN_SCOPE_DEFINED:Mpattern}\" "+ | |||
632 | "with \"${IN_SCOPE_DEFINED} == pattern\".") | |||
633 | ||||
634 | testBeforeAndAfterPrefs( | |||
635 | ".if ${IN_SCOPE:Mpattern}", | |||
636 | ".if ${IN_SCOPE:U} == pattern", | |||
637 | ||||
638 | "NOTE: filename.mk:3: IN_SCOPE can be "+ | |||
639 | "compared using the simpler \"${IN_SCOPE:U} == pattern\" "+ | |||
640 | "instead of matching against \":Mpattern\".", | |||
641 | "AUTOFIX: filename.mk:3: Replacing \"${IN_SCOPE:Mpattern}\" "+ | |||
642 | "with \"${IN_SCOPE:U} == pattern\".") | |||
643 | ||||
644 | // Even though PREFS_DEFINED is declared as DefinedIfInScope, | |||
645 | // it is not in scope yet. Therefore it needs the :U modifier. | |||
646 | // The warning that this variable is not yet in scope comes from | |||
647 | // a different part of pkglint. | |||
648 | testBeforePrefs( | |||
649 | ".if ${PREFS_DEFINED:Mpattern}", | |||
650 | ".if ${PREFS_DEFINED:U} == pattern", | |||
651 | ||||
652 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
653 | "compared using the simpler \"${PREFS_DEFINED:U} == pattern\" "+ | |||
654 | "instead of matching against \":Mpattern\".", | |||
655 | "WARN: filename.mk:3: To use PREFS_DEFINED at load time, "+ | |||
656 | ".include \"../../mk/bsd.prefs.mk\" first.", | |||
657 | "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpattern}\" "+ | |||
658 | "with \"${PREFS_DEFINED:U} == pattern\".") | |||
659 | ||||
660 | testAfterPrefs( | |||
661 | ".if ${PREFS_DEFINED:Mpattern}", | |||
662 | ".if ${PREFS_DEFINED} == pattern", | |||
663 | ||||
664 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
665 | "compared using the simpler \"${PREFS_DEFINED} == pattern\" "+ | |||
666 | "instead of matching against \":Mpattern\".", | |||
667 | "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpattern}\" "+ | |||
668 | "with \"${PREFS_DEFINED} == pattern\".") | |||
669 | ||||
670 | testBeforePrefs( | |||
671 | ".if ${PREFS:Mpattern}", | |||
672 | ".if ${PREFS:U} == pattern", | |||
673 | ||||
674 | "NOTE: filename.mk:3: PREFS can be "+ | |||
675 | "compared using the simpler \"${PREFS:U} == pattern\" "+ | |||
676 | "instead of matching against \":Mpattern\".", | |||
677 | "WARN: filename.mk:3: To use PREFS at load time, "+ | |||
678 | ".include \"../../mk/bsd.prefs.mk\" first.", | |||
679 | "AUTOFIX: filename.mk:3: Replacing \"${PREFS:Mpattern}\" "+ | |||
680 | "with \"${PREFS:U} == pattern\".") | |||
681 | ||||
682 | // Preferences that may be undefined always need the :U modifier, | |||
683 | // even when they are in scope. | |||
684 | testAfterPrefs( | |||
685 | ".if ${PREFS:Mpattern}", | |||
686 | ".if ${PREFS:U} == pattern", | |||
687 | ||||
688 | "NOTE: filename.mk:3: PREFS can be "+ | |||
689 | "compared using the simpler \"${PREFS:U} == pattern\" "+ | |||
690 | "instead of matching against \":Mpattern\".", | |||
691 | "AUTOFIX: filename.mk:3: Replacing \"${PREFS:Mpattern}\" "+ | |||
692 | "with \"${PREFS:U} == pattern\".") | |||
693 | ||||
694 | // Variables that are defined later always need the :U modifier. | |||
695 | // It is probably a mistake to use them in conditions at all. | |||
696 | testBeforeAndAfterPrefs( | |||
697 | ".if ${LATER_DEFINED:Mpattern}", | |||
698 | ".if ${LATER_DEFINED:U} == pattern", | |||
699 | ||||
700 | "NOTE: filename.mk:3: LATER_DEFINED can be "+ | |||
701 | "compared using the simpler \"${LATER_DEFINED:U} == pattern\" "+ | |||
702 | "instead of matching against \":Mpattern\".", | |||
703 | "WARN: filename.mk:3: "+ | |||
704 | "LATER_DEFINED should not be used at load time in any file.", | |||
705 | "AUTOFIX: filename.mk:3: Replacing \"${LATER_DEFINED:Mpattern}\" "+ | |||
706 | "with \"${LATER_DEFINED:U} == pattern\".") | |||
707 | ||||
708 | // Variables that are defined later always need the :U modifier. | |||
709 | // It is probably a mistake to use them in conditions at all. | |||
710 | testBeforeAndAfterPrefs( | |||
711 | ".if ${LATER:Mpattern}", | |||
712 | ".if ${LATER:U} == pattern", | |||
713 | ||||
714 | "NOTE: filename.mk:3: LATER can be "+ | |||
715 | "compared using the simpler \"${LATER:U} == pattern\" "+ | |||
716 | "instead of matching against \":Mpattern\".", | |||
717 | "WARN: filename.mk:3: "+ | |||
718 | "LATER should not be used at load time in any file.", | |||
719 | "AUTOFIX: filename.mk:3: Replacing \"${LATER:Mpattern}\" "+ | |||
720 | "with \"${LATER:U} == pattern\".") | |||
721 | ||||
722 | testBeforeAndAfterPrefs( | |||
723 | ".if ${UNDEFINED:Mpattern}", | |||
724 | ".if ${UNDEFINED:Mpattern}", | |||
725 | ||||
726 | "WARN: filename.mk:3: UNDEFINED is used but not defined.") | |||
727 | ||||
728 | // When the pattern contains placeholders, it cannot be converted to == or !=. | |||
729 | testAfterPrefs( | |||
730 | ".if ${PREFS_DEFINED:Mpa*n}", | |||
731 | ".if ${PREFS_DEFINED:Mpa*n}", | |||
732 | ||||
733 | nil...) | |||
734 | ||||
735 | // When deciding whether to replace the expression, only the | |||
736 | // last modifier is inspected. All the others are copied. | |||
737 | testAfterPrefs( | |||
738 | ".if ${PREFS_DEFINED:tl:Mpattern}", | |||
739 | ".if ${PREFS_DEFINED:tl} == pattern", | |||
740 | ||||
741 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
742 | "compared using the simpler \"${PREFS_DEFINED:tl} == pattern\" "+ | |||
743 | "instead of matching against \":Mpattern\".", | |||
744 | "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:tl:Mpattern}\" "+ | |||
745 | "with \"${PREFS_DEFINED:tl} == pattern\".") | |||
746 | ||||
747 | // Negated pattern matches are supported as well, | |||
748 | // as long as the variable is guaranteed to be nonempty. | |||
749 | // TODO: Actually implement this. | |||
750 | // As of December 2019, IsNonemptyIfDefined is not used anywhere. | |||
751 | testAfterPrefs( | |||
752 | ".if ${PREFS_DEFINED:Npattern}", | |||
753 | ".if ${PREFS_DEFINED} != pattern", | |||
754 | ||||
755 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
756 | "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+ | |||
757 | "instead of matching against \":Npattern\".", | |||
758 | "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Npattern}\" "+ | |||
759 | "with \"${PREFS_DEFINED} != pattern\".") | |||
760 | ||||
761 | // ${PREFS_DEFINED:None:Ntwo} is a short variant of | |||
762 | // ${PREFS_DEFINED} != "one" && ${PREFS_DEFINED} != "two". | |||
763 | // Applying the transformation would make the condition longer | |||
764 | // than before, therefore nothing can be simplified here, | |||
765 | // even though all patterns are exact matches. | |||
766 | testAfterPrefs( | |||
767 | ".if ${PREFS_DEFINED:None:Ntwo}", | |||
768 | ".if ${PREFS_DEFINED:None:Ntwo}", | |||
769 | ||||
770 | nil...) | |||
771 | ||||
772 | // Note: this combination doesn't make sense since the patterns | |||
773 | // "one" and "two" don't overlap. | |||
774 | // Nevertheless it is possible and valid to simplify the condition. | |||
775 | testAfterPrefs( | |||
776 | ".if ${PREFS_DEFINED:Mone:Mtwo}", | |||
777 | ".if ${PREFS_DEFINED:Mone} == two", | |||
778 | ||||
779 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
780 | "compared using the simpler \"${PREFS_DEFINED:Mone} == two\" "+ | |||
781 | "instead of matching against \":Mtwo\".", | |||
782 | "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mone:Mtwo}\" "+ | |||
783 | "with \"${PREFS_DEFINED:Mone} == two\".") | |||
784 | ||||
785 | // There is no ! before the empty, which is easy to miss. | |||
786 | // Because of this missing negation, the comparison operator is !=. | |||
787 | testAfterPrefs( | |||
788 | ".if empty(PREFS_DEFINED:Mpattern)", | |||
789 | ".if ${PREFS_DEFINED} != pattern", | |||
790 | ||||
791 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
792 | "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+ | |||
793 | "instead of matching against \":Mpattern\".", | |||
794 | "AUTOFIX: filename.mk:3: Replacing \"empty(PREFS_DEFINED:Mpattern)\" "+ | |||
795 | "with \"${PREFS_DEFINED} != pattern\".") | |||
796 | ||||
797 | testAfterPrefs( | |||
798 | ".if !!empty(PREFS_DEFINED:Mpattern)", | |||
799 | // TODO: The ! and == could be combined into a !=. | |||
800 | // Luckily the !! pattern doesn't occur in practice. | |||
801 | ".if !${PREFS_DEFINED} == pattern", | |||
802 | ||||
803 | // TODO: When taking all the ! into account, this is actually a | |||
804 | // test for emptiness, therefore the diagnostics should suggest | |||
805 | // the != operator instead of ==. | |||
806 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
807 | "compared using the simpler \"${PREFS_DEFINED} == pattern\" "+ | |||
808 | "instead of matching against \":Mpattern\".", | |||
809 | "AUTOFIX: filename.mk:3: Replacing \"!empty(PREFS_DEFINED:Mpattern)\" "+ | |||
810 | "with \"${PREFS_DEFINED} == pattern\".") | |||
811 | ||||
812 | // Simplifying the condition also works in complex expressions. | |||
813 | testAfterPrefs(".if empty(PREFS_DEFINED:Mpattern) || 0", | |||
814 | ".if ${PREFS_DEFINED} != pattern || 0", | |||
815 | ||||
816 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
817 | "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+ | |||
818 | "instead of matching against \":Mpattern\".", | |||
819 | "AUTOFIX: filename.mk:3: Replacing \"empty(PREFS_DEFINED:Mpattern)\" "+ | |||
820 | "with \"${PREFS_DEFINED} != pattern\".") | |||
821 | ||||
822 | // No note in this case since there is no implicit !empty around the varUse. | |||
823 | // There is no obvious way of writing this expression in a simpler form. | |||
824 | testAfterPrefs( | |||
825 | ".if ${PREFS_DEFINED:Mpattern} != ${OTHER}", | |||
826 | ".if ${PREFS_DEFINED:Mpattern} != ${OTHER}", | |||
827 | ||||
828 | "WARN: filename.mk:3: OTHER is used but not defined.") | |||
829 | ||||
830 | // The condition is also simplified if it doesn't use the !empty | |||
831 | // form but the implicit conversion to boolean. | |||
832 | testAfterPrefs( | |||
833 | ".if ${PREFS_DEFINED:Mpattern}", | |||
834 | ".if ${PREFS_DEFINED} == pattern", | |||
835 | ||||
836 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
837 | "compared using the simpler \"${PREFS_DEFINED} == pattern\" "+ | |||
838 | "instead of matching against \":Mpattern\".", | |||
839 | "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpattern}\" "+ | |||
840 | "with \"${PREFS_DEFINED} == pattern\".") | |||
841 | ||||
842 | // A single negation outside the implicit conversion is taken into | |||
843 | // account when simplifying the condition. | |||
844 | testAfterPrefs( | |||
845 | ".if !${PREFS_DEFINED:Mpattern}", | |||
846 | ".if ${PREFS_DEFINED} != pattern", | |||
847 | ||||
848 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
849 | "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+ | |||
850 | "instead of matching against \":Mpattern\".", | |||
851 | "AUTOFIX: filename.mk:3: Replacing \"!${PREFS_DEFINED:Mpattern}\" "+ | |||
852 | "with \"${PREFS_DEFINED} != pattern\".") | |||
853 | ||||
854 | // TODO: Merge the double negation into the comparison operator. | |||
855 | testAfterPrefs( | |||
856 | ".if !!${PREFS_DEFINED:Mpattern}", | |||
857 | ".if !${PREFS_DEFINED} != pattern", | |||
858 | ||||
859 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
860 | "compared using the simpler \"${PREFS_DEFINED} != pattern\" "+ | |||
861 | "instead of matching against \":Mpattern\".", | |||
862 | "AUTOFIX: filename.mk:3: Replacing \"!${PREFS_DEFINED:Mpattern}\" "+ | |||
863 | "with \"${PREFS_DEFINED} != pattern\".") | |||
864 | ||||
865 | // This pattern with spaces doesn't make sense at all in the :M | |||
866 | // modifier since it can never match. | |||
867 | // Or can it, if the PKGPATH contains quotes? | |||
868 | // TODO: How exactly does bmake apply the matching here, | |||
869 | // are both values unquoted first? Probably not, but who knows. | |||
870 | testBeforeAndAfterPrefs( | |||
871 | ".if ${IN_SCOPE_DEFINED:Mpattern with spaces}", | |||
872 | ".if ${IN_SCOPE_DEFINED:Mpattern with spaces}", | |||
873 | ||||
874 | nil...) | |||
875 | // TODO: ".if ${PKGPATH} == \"pattern with spaces\"") | |||
876 | ||||
877 | testBeforeAndAfterPrefs( | |||
878 | ".if ${IN_SCOPE_DEFINED:M'pattern with spaces'}", | |||
879 | ".if ${IN_SCOPE_DEFINED:M'pattern with spaces'}", | |||
880 | ||||
881 | nil...) | |||
882 | // TODO: ".if ${PKGPATH} == 'pattern with spaces'") | |||
883 | ||||
884 | testBeforeAndAfterPrefs( | |||
885 | ".if ${IN_SCOPE_DEFINED:M&&}", | |||
886 | ".if ${IN_SCOPE_DEFINED:M&&}", | |||
887 | ||||
888 | nil...) | |||
889 | // TODO: ".if ${PKGPATH} == '&&'") | |||
890 | ||||
891 | // The :N modifier involves another negation and is therefore more | |||
892 | // difficult to understand. That's even more reason to use the | |||
893 | // well-known == and != comparison operators instead. | |||
894 | // | |||
895 | // If PKGPATH is "", the condition is false. | |||
896 | // If PKGPATH is "negative-pattern", the condition is false. | |||
897 | // In all other cases, the condition is true. | |||
898 | // | |||
899 | // Therefore this condition cannot simply be transformed into | |||
900 | // ${PKGPATH} != negative-pattern, since that would produce a | |||
901 | // different result in the case where PKGPATH is empty. | |||
902 | // | |||
903 | // For system-provided variables that are guaranteed to be non-empty, | |||
904 | // such as OPSYS or PKGPATH, this replacement is valid. | |||
905 | // These variables are only guaranteed to be defined after bsd.prefs.mk | |||
906 | // has been included, like everywhere else. | |||
907 | // | |||
908 | // TODO: This is where NonemptyIfDefined comes into play. | |||
909 | testAfterPrefs( | |||
910 | ".if ${PREFS_DEFINED:Nnegative-pattern}", | |||
911 | ".if ${PREFS_DEFINED} != negative-pattern", | |||
912 | ||||
913 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
914 | "compared using the simpler \"${PREFS_DEFINED} != negative-pattern\" "+ | |||
915 | "instead of matching against \":Nnegative-pattern\".", | |||
916 | "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Nnegative-pattern}\" "+ | |||
917 | "with \"${PREFS_DEFINED} != negative-pattern\".") | |||
918 | ||||
919 | // Since UNDEFINED is not a well-known variable that is | |||
920 | // guaranteed to be non-empty (see the previous example), it is not | |||
921 | // transformed at all. | |||
922 | testBeforePrefs( | |||
923 | ".if ${UNDEFINED:Nnegative-pattern}", | |||
924 | ".if ${UNDEFINED:Nnegative-pattern}", | |||
925 | ||||
926 | "WARN: filename.mk:3: UNDEFINED is used but not defined.") | |||
927 | ||||
928 | testAfterPrefs( | |||
929 | ".if ${UNDEFINED:Nnegative-pattern}", | |||
930 | ".if ${UNDEFINED:Nnegative-pattern}", | |||
931 | ||||
932 | "WARN: filename.mk:3: UNDEFINED is used but not defined.") | |||
933 | ||||
934 | // A complex condition may contain several simple conditions | |||
935 | // that are each simplified independently, in the same go. | |||
936 | testAfterPrefs( | |||
937 | ".if ${PREFS_DEFINED:Mpath1} || ${PREFS_DEFINED:Mpath2}", | |||
938 | ".if ${PREFS_DEFINED} == path1 || ${PREFS_DEFINED} == path2", | |||
939 | ||||
940 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
941 | "compared using the simpler \"${PREFS_DEFINED} == path1\" "+ | |||
942 | "instead of matching against \":Mpath1\".", | |||
943 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
944 | "compared using the simpler \"${PREFS_DEFINED} == path2\" "+ | |||
945 | "instead of matching against \":Mpath2\".", | |||
946 | "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpath1}\" "+ | |||
947 | "with \"${PREFS_DEFINED} == path1\".", | |||
948 | "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpath2}\" "+ | |||
949 | "with \"${PREFS_DEFINED} == path2\".") | |||
950 | ||||
951 | // Removing redundant parentheses may be useful one day but is | |||
952 | // not urgent. | |||
953 | // Simplifying the inner expression keeps all parentheses as-is. | |||
954 | testAfterPrefs( | |||
955 | ".if (((((${PREFS_DEFINED:Mpath})))))", | |||
956 | ".if (((((${PREFS_DEFINED} == path)))))", | |||
957 | ||||
958 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
959 | "compared using the simpler \"${PREFS_DEFINED} == path\" "+ | |||
960 | "instead of matching against \":Mpath\".", | |||
961 | "AUTOFIX: filename.mk:3: Replacing \"${PREFS_DEFINED:Mpath}\" "+ | |||
962 | "with \"${PREFS_DEFINED} == path\".") | |||
963 | ||||
964 | // Several modifiers like :S and :C may change the variable value. | |||
965 | // Whether the condition can be simplified or not only depends on the | |||
966 | // last modifier in the chain. | |||
967 | testAfterPrefs( | |||
968 | ".if !empty(PREFS_DEFINED:S,NetBSD,ok,:Mok)", | |||
969 | ".if ${PREFS_DEFINED:S,NetBSD,ok,} == ok", | |||
970 | ||||
971 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
972 | "compared using the simpler \"${PREFS_DEFINED:S,NetBSD,ok,} == ok\" "+ | |||
973 | "instead of matching against \":Mok\".", | |||
974 | "AUTOFIX: filename.mk:3: Replacing \"!empty(PREFS_DEFINED:S,NetBSD,ok,:Mok)\" "+ | |||
975 | "with \"${PREFS_DEFINED:S,NetBSD,ok,} == ok\".") | |||
976 | ||||
977 | testAfterPrefs( | |||
978 | ".if empty(PREFS_DEFINED:tl:Msunos)", | |||
979 | ".if ${PREFS_DEFINED:tl} != sunos", | |||
980 | ||||
981 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
982 | "compared using the simpler \"${PREFS_DEFINED:tl} != sunos\" "+ | |||
983 | "instead of matching against \":Msunos\".", | |||
984 | "AUTOFIX: filename.mk:3: Replacing \"empty(PREFS_DEFINED:tl:Msunos)\" "+ | |||
985 | "with \"${PREFS_DEFINED:tl} != sunos\".") | |||
986 | ||||
987 | // The condition can only be simplified if the :M or :N modifier | |||
988 | // appears at the end of the chain. | |||
989 | testAfterPrefs( | |||
990 | ".if !empty(PREFS_DEFINED:O:MUnknown:S,a,b,)", | |||
991 | ".if !empty(PREFS_DEFINED:O:MUnknown:S,a,b,)", | |||
992 | ||||
993 | nil...) | |||
994 | ||||
995 | // The dot is just an ordinary character in a pattern. | |||
996 | // In comparisons, an unquoted 1.2 is interpreted as a number though. | |||
997 | testAfterPrefs( | |||
998 | ".if !empty(PREFS_DEFINED:Mpackage1.2)", | |||
999 | ".if ${PREFS_DEFINED} == package1.2", | |||
1000 | ||||
1001 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
1002 | "compared using the simpler \"${PREFS_DEFINED} == package1.2\" "+ | |||
1003 | "instead of matching against \":Mpackage1.2\".", | |||
1004 | "AUTOFIX: filename.mk:3: Replacing \"!empty(PREFS_DEFINED:Mpackage1.2)\" "+ | |||
1005 | "with \"${PREFS_DEFINED} == package1.2\".") | |||
1006 | ||||
1007 | // Numbers must be enclosed in quotes, otherwise they are compared | |||
1008 | // as numbers, not as strings. | |||
1009 | // The :M and :N modifiers always compare strings. | |||
1010 | testAfterPrefs( | |||
1011 | ".if empty(PREFS:U:M64)", | |||
1012 | ".if ${PREFS:U} != \"64\"", | |||
1013 | ||||
1014 | "NOTE: filename.mk:3: PREFS can be "+ | |||
1015 | "compared using the simpler \"${PREFS:U} != \"64\"\" "+ | |||
1016 | "instead of matching against \":M64\".", | |||
1017 | "AUTOFIX: filename.mk:3: Replacing \"empty(PREFS:U:M64)\" "+ | |||
1018 | "with \"${PREFS:U} != \\\"64\\\"\".") | |||
1019 | ||||
1020 | // Fractional numbers must also be enclosed in quotes. | |||
1021 | testAfterPrefs( | |||
1022 | ".if empty(PREFS:U:M19.12)", | |||
1023 | ".if ${PREFS:U} != \"19.12\"", | |||
1024 | ||||
1025 | "NOTE: filename.mk:3: PREFS can be "+ | |||
1026 | "compared using the simpler \"${PREFS:U} != \"19.12\"\" "+ | |||
1027 | "instead of matching against \":M19.12\".", | |||
1028 | "AUTOFIX: filename.mk:3: Replacing \"empty(PREFS:U:M19.12)\" "+ | |||
1029 | "with \"${PREFS:U} != \\\"19.12\\\"\".") | |||
1030 | ||||
1031 | testAfterPrefs( | |||
1032 | ".if !empty(LATER:Npattern)", | |||
1033 | ".if !empty(LATER:Npattern)", | |||
1034 | ||||
1035 | // No diagnostics about the :N modifier yet, | |||
1036 | // see MkCondChecker.simplify.replace. | |||
1037 | "WARN: filename.mk:3: LATER should not be used "+ | |||
1038 | "at load time in any file.") | |||
1039 | ||||
1040 | // TODO: Add a note that the :U is unnecessary, and explain why. | |||
1041 | testAfterPrefs( | |||
1042 | ".if ${PREFS_DEFINED:U:Mpattern}", | |||
1043 | ".if ${PREFS_DEFINED:U} == pattern", | |||
1044 | ||||
1045 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
1046 | "compared using the simpler \"${PREFS_DEFINED:U} == pattern\" "+ | |||
1047 | "instead of matching against \":Mpattern\".", | |||
1048 | "AUTOFIX: filename.mk:3: "+ | |||
1049 | "Replacing \"${PREFS_DEFINED:U:Mpattern}\" "+ | |||
1050 | "with \"${PREFS_DEFINED:U} == pattern\".") | |||
1051 | ||||
1052 | // Conditions without any modifiers cannot be simplified | |||
1053 | // and are therefore skipped. | |||
1054 | testBeforeAndAfterPrefs( | |||
1055 | ".if ${IN_SCOPE_DEFINED}", | |||
1056 | ".if ${IN_SCOPE_DEFINED}", | |||
1057 | ||||
1058 | nil...) | |||
1059 | ||||
1060 | // Special characters must be enclosed in quotes when they are | |||
1061 | // used in string literals. | |||
1062 | // As of December 2019, strings with special characters are not yet | |||
1063 | // replaced automatically, see mkCondLiteralChars. | |||
1064 | // TODO: Add tests for all characters that are special in string literals or patterns. | |||
1065 | // TODO: Then, extend the set of characters for which the expressions are simplified. | |||
1066 | testBeforePrefs( | |||
1067 | ".if ${PREFS_DEFINED:M&&}", | |||
1068 | ".if ${PREFS_DEFINED:M&&}", | |||
1069 | ||||
1070 | "WARN: filename.mk:3: To use PREFS_DEFINED at load time, .include \"../../mk/bsd.prefs.mk\" first.") | |||
1071 | testAfterPrefs( | |||
1072 | ".if ${PREFS_DEFINED:M&&}", | |||
1073 | ".if ${PREFS_DEFINED:M&&}", | |||
1074 | ||||
1075 | nil...) | |||
1076 | ||||
1077 | testBeforePrefs( | |||
1078 | ".if ${PREFS:M&&}", | |||
1079 | ".if ${PREFS:M&&}", | |||
1080 | ||||
1081 | // TODO: Warn that the :U is missing. | |||
1082 | "WARN: filename.mk:3: To use PREFS at load time, .include \"../../mk/bsd.prefs.mk\" first.") | |||
1083 | testAfterPrefs( | |||
1084 | ".if ${PREFS:M&&}", | |||
1085 | ".if ${PREFS:M&&}", | |||
1086 | ||||
1087 | // TODO: Warn that the :U is missing. | |||
1088 | nil...) | |||
1089 | ||||
1090 | // The + is contained in both mkCondStringLiteralUnquoted and | |||
1091 | // mkCondModifierPatternLiteral, therefore it is copied verbatim. | |||
1092 | testAfterPrefs( | |||
1093 | ".if ${PREFS_DEFINED:Mcategory/gtk+}", | |||
1094 | ".if ${PREFS_DEFINED} == category/gtk+", | |||
1095 | ||||
1096 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
1097 | "compared using the simpler \"${PREFS_DEFINED} == category/gtk+\" "+ | |||
1098 | "instead of matching against \":Mcategory/gtk+\".", | |||
1099 | "AUTOFIX: filename.mk:3: "+ | |||
1100 | "Replacing \"${PREFS_DEFINED:Mcategory/gtk+}\" "+ | |||
1101 | "with \"${PREFS_DEFINED} == category/gtk+\".") | |||
1102 | ||||
1103 | // The characters <=> may be used unescaped in :M and :N patterns | |||
1104 | // but not in .if conditions. There they must be enclosed in quotes. | |||
1105 | testBeforePrefs( | |||
1106 | ".if ${PREFS_DEFINED:M<=>}", | |||
1107 | ".if ${PREFS_DEFINED:U} == \"<=>\"", | |||
1108 | ||||
1109 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
1110 | "compared using the simpler \"${PREFS_DEFINED:U} == \"<=>\"\" "+ | |||
1111 | "instead of matching against \":M<=>\".", | |||
1112 | "WARN: filename.mk:3: To use PREFS_DEFINED at load time, "+ | |||
1113 | ".include \"../../mk/bsd.prefs.mk\" first.", | |||
1114 | "AUTOFIX: filename.mk:3: "+ | |||
1115 | "Replacing \"${PREFS_DEFINED:M<=>}\" "+ | |||
1116 | "with \"${PREFS_DEFINED:U} == \\\"<=>\\\"\".") | |||
1117 | testAfterPrefs( | |||
1118 | ".if ${PREFS_DEFINED:M<=>}", | |||
1119 | ".if ${PREFS_DEFINED} == \"<=>\"", | |||
1120 | ||||
1121 | "NOTE: filename.mk:3: PREFS_DEFINED can be "+ | |||
1122 | "compared using the simpler \"${PREFS_DEFINED} == \"<=>\"\" "+ | |||
1123 | "instead of matching against \":M<=>\".", | |||
1124 | "AUTOFIX: filename.mk:3: "+ | |||
1125 | "Replacing \"${PREFS_DEFINED:M<=>}\" "+ | |||
1126 | "with \"${PREFS_DEFINED} == \\\"<=>\\\"\".") | |||
1127 | ||||
1128 | // If pkglint replaces this particular pattern, the resulting string | |||
1129 | // literal must be escaped properly. | |||
1130 | testBeforeAndAfterPrefs( | |||
1131 | ".if ${IN_SCOPE_DEFINED:M\"}", | |||
1132 | ".if ${IN_SCOPE_DEFINED:M\"}", | |||
1133 | ||||
1134 | nil...) | |||
1135 | ||||
1136 | testBeforeAndAfterPrefs( | |||
1137 | ".if !empty(IN_SCOPE_DEFINED:M)", | |||
1138 | ".if ${IN_SCOPE_DEFINED} == \"\"", | |||
1139 | ||||
1140 | "NOTE: filename.mk:3: IN_SCOPE_DEFINED can be "+ | |||
1141 | "compared using the simpler "+"\"${IN_SCOPE_DEFINED} == \"\"\" "+ | |||
1142 | "instead of matching against \":M\".", | |||
1143 | "AUTOFIX: filename.mk:3: "+ | |||
1144 | "Replacing \"!empty(IN_SCOPE_DEFINED:M)\" "+ | |||
1145 | "with \"${IN_SCOPE_DEFINED} == \\\"\\\"\".", | |||
1146 | ) | |||
1147 | ||||
1148 | testBeforeAndAfterPrefs( | |||
1149 | ".if !empty(IN_SCOPE_DEFINED:M*.c)", | |||
1150 | ".if ${IN_SCOPE_DEFINED:M*.c}", | |||
1151 | ||||
1152 | "NOTE: filename.mk:3: \"!empty(IN_SCOPE_DEFINED:M*.c)\" "+ | |||
1153 | "can be simplified to \"${IN_SCOPE_DEFINED:M*.c}\".", | |||
1154 | "AUTOFIX: filename.mk:3: "+ | |||
1155 | "Replacing \"!empty(IN_SCOPE_DEFINED:M*.c)\" "+ | |||
1156 | "with \"${IN_SCOPE_DEFINED:M*.c}\".") | |||
1157 | ||||
1158 | testBeforeAndAfterPrefs( | |||
1159 | ".if empty(IN_SCOPE_DEFINED:M*.c)", | |||
1160 | ".if !${IN_SCOPE_DEFINED:M*.c}", | |||
1161 | ||||
1162 | "NOTE: filename.mk:3: \"empty(IN_SCOPE_DEFINED:M*.c)\" "+ | |||
1163 | "can be simplified to \"!${IN_SCOPE_DEFINED:M*.c}\".", | |||
1164 | "AUTOFIX: filename.mk:3: "+ | |||
1165 | "Replacing \"empty(IN_SCOPE_DEFINED:M*.c)\" "+ | |||
1166 | "with \"!${IN_SCOPE_DEFINED:M*.c}\".") | |||
1167 | } | |||
1168 | ||||
1169 | func (s *Suite) Test_MkCondChecker_simplify__defined_in_same_file(c *check.C) { | |||
1170 | t := s.Init(c) | |||
1171 | ||||
1172 | t.SetUpPackage("category/package") | |||
1173 | t.Chdir("category/package") | |||
1174 | t.FinishSetUp() | |||
1175 | ||||
1176 | doTest := func(before string) { | |||
1177 | mklines := t.SetUpFileMkLines("filename.mk", | |||
1178 | MkCvsID, | |||
1179 | "OK=\t\tok", | |||
1180 | "OK_DIR=\t\tok", // See Pkgsrc.guessVariableType. | |||
1181 | before, | |||
1182 | "LATER=\t\tlater", | |||
1183 | "LATER_DIR=\tlater", // See Pkgsrc.guessVariableType. | |||
1184 | ".endif", | |||
1185 | "USED=\t\t${OK} ${LATER} ${OK_DIR} ${LATER_DIR} ${USED}") | |||
1186 | ||||
1187 | // The high-level call MkLines.Check is used here to | |||
1188 | // get MkLines.Tools.SeenPrefs correct, which decides | |||
1189 | // whether the :U modifier is necessary. | |||
1190 | mklines.Check() | |||
1191 | } | |||
1192 | ||||
1193 | // before: the directive before the condition is simplified | |||
1194 | // after: the directive after the condition is simplified | |||
1195 | test := func(before, after string, diagnostics ...string) { | |||
1196 | ||||
1197 | t.ExpectDiagnosticsAutofix( | |||
1198 | func(bool) { doTest(before) }, | |||
1199 | diagnostics...) | |||
1200 | ||||
1201 | // TODO: Move this assertion above the assertion about the diagnostics. | |||
1202 | afterMklines := LoadMk(t.File("filename.mk"), nil, MustSucceed) | |||
1203 | t.CheckEquals(afterMklines.mklines[3].Text, after) | |||
1204 | } | |||
1205 | ||||
1206 | // For variables with completely unknown names, the type is nil | |||
1207 | // and the complete check is skipped. | |||
1208 | test( | |||
1209 | ".if ${OK:Mpattern}", | |||
1210 | ".if ${OK:Mpattern}", | |||
1211 | ||||
1212 | nil...) | |||
1213 | ||||
1214 | // For variables with completely unknown names, the type is nil | |||
1215 | // and the complete check is skipped. | |||
1216 | test( | |||
1217 | ".if ${LATER:Mpattern}", | |||
1218 | ".if ${LATER:Mpattern}", | |||
1219 | ||||
1220 | nil...) | |||
1221 | ||||
1222 | // OK_DIR is defined earlier than the .if condition, | |||
1223 | // which is the correct order. | |||
1224 | test( | |||
1225 | ".if ${OK_DIR:Mpattern}", | |||
1226 | ".if ${OK_DIR} == pattern", | |||
1227 | ||||
1228 | "NOTE: filename.mk:4: OK_DIR can be "+ | |||
1229 | "compared using the simpler \"${OK_DIR} == pattern\" "+ | |||
1230 | "instead of matching against \":Mpattern\".", | |||
1231 | "AUTOFIX: filename.mk:4: "+ | |||
1232 | "Replacing \"${OK_DIR:Mpattern}\" "+ | |||
1233 | "with \"${OK_DIR} == pattern\".") | |||
1234 | ||||
1235 | // LATER_DIR is defined later than the .if condition, | |||
1236 | // therefore at the time of the .if statement, it is still empty. | |||
1237 | test( | |||
1238 | ".if ${LATER_DIR:Mpattern}", | |||
1239 | ".if ${LATER_DIR:U} == pattern", | |||
1240 | ||||
1241 | // TODO: Warn that LATER_DIR is used before it is defined. | |||
1242 | "NOTE: filename.mk:4: LATER_DIR can be "+ | |||
1243 | "compared using the simpler \"${LATER_DIR:U} == pattern\" "+ | |||
1244 | "instead of matching against \":Mpattern\".", | |||
1245 | "AUTOFIX: filename.mk:4: "+ | |||
1246 | "Replacing \"${LATER_DIR:Mpattern}\" "+ | |||
1247 | "with \"${LATER_DIR:U} == pattern\".") | |||
1248 | } | |||
1249 | ||||
1250 | func (s *Suite) Test_MkCondChecker_checkCompare(c *check.C) { | 533 | func (s *Suite) Test_MkCondChecker_checkCompare(c *check.C) { | |
1251 | t := s.Init(c) | 534 | t := s.Init(c) | |
1252 | 535 | |||
1253 | t.SetUpVartypes() | 536 | t.SetUpVartypes() | |
1254 | 537 | |||
1255 | test := func(cond string, output ...string) { | 538 | test := func(cond string, output ...string) { | |
1256 | mklines := t.NewMkLines("filename.mk", | 539 | mklines := t.NewMkLines("filename.mk", | |
1257 | cond) | 540 | cond) | |
1258 | mklines.ForEach(func(mkline *MkLine) { | 541 | mklines.ForEach(func(mkline *MkLine) { | |
1259 | NewMkCondChecker(mkline, mklines).Check() | 542 | NewMkCondChecker(mkline, mklines).Check() | |
1260 | }) | 543 | }) | |
1261 | t.CheckOutput(output) | 544 | t.CheckOutput(output) | |
1262 | } | 545 | } |
package pkglint
import "netbsd.org/pkglint/textproc"
// MkCondSimplifier replaces unnecessarily complex conditions with simpler yet
// equivalent conditions.
type MkCondSimplifier struct {
MkLines *MkLines
MkLine *MkLine
}
// SimplifyVarUse replaces an unnecessarily complex condition with
// a simpler condition that's still equivalent.
//
// * fromEmpty is true for the form empty(VAR...), and false for ${VAR...}.
//
// * neg is true for the form !empty(VAR...), and false for empty(VAR...).
func (s *MkCondSimplifier) SimplifyVarUse(varuse *MkVarUse, fromEmpty bool, neg bool) {
s.simplifyMatch(varuse, fromEmpty, neg)
s.simplifyWord(varuse, fromEmpty, neg)
}
// simplifyWord simplifies a condition like '${VAR:Mword}' in the common case
// where VAR is a single-word variable. This case can be written without any
// list operators, as '${VAR} == word'.
func (s *MkCondSimplifier) simplifyWord(varuse *MkVarUse, fromEmpty bool, neg bool) {
varname := varuse.varname
modifiers := varuse.modifiers
n := len(modifiers)
if n == 0 {
return
}
modsExceptLast := NewMkVarUse("", modifiers[:n-1]...).Mod()
vartype := G.Pkgsrc.VariableType(s.MkLines, varname)
// replace constructs the state before and after the autofix.
// The before state is constructed to ensure that only very simple
// patterns get replaced automatically.
//
// Before putting any cases involving special characters into
// production, there need to be more tests for the edge cases.
replace := func(positive bool, pattern string) (bool, string, string) {
defined := s.isDefined(varname, vartype)
if !defined && !positive {
// TODO: This is a double negation, maybe even triple.
// There is an :N pattern, and the variable may be undefined.
// If it is indeed undefined, should the whole condition
// evaluate to true or false?
// The cases to be distinguished are: undefined, empty, filled.
// For now, be conservative and don't suggest anything wrong.
return false, "", ""
}
uMod := condStr(!defined && !varuse.HasModifier("U"), ":U", "")
op := condStr(neg == positive, "==", "!=")
from := sprintf("%s%s%s%s%s%s%s",
condStr(neg != fromEmpty, "", "!"),
condStr(fromEmpty, "empty(", "${"),
varname,
modsExceptLast,
condStr(positive, ":M", ":N"),
pattern,
condStr(fromEmpty, ")", "}"))
needsQuotes := textproc.NewLexer(pattern).NextBytesSet(mkCondStringLiteralUnquoted) != pattern ||
pattern == "" ||
matches(pattern, `^\d+\.?\d*$`)
quote := condStr(needsQuotes, "\"", "")
to := sprintf(
"${%s%s%s} %s %s%s%s",
varname, uMod, modsExceptLast, op, quote, pattern, quote)
return true, from, to
}
modifier := modifiers[n-1]
ok, positive, pattern, exact := modifier.MatchMatch()
if !ok || !positive && n != 1 {
return
}
switch {
case !exact,
vartype == nil,
vartype.IsList(),
textproc.NewLexer(pattern).NextBytesSet(mkCondModifierPatternLiteral) != pattern:
return
}
ok, from, to := replace(positive, pattern)
if !ok {
return
}
fix := s.MkLine.Autofix()
fix.Notef("%s can be compared using the simpler \"%s\" "+
"instead of matching against %q.",
varname, to, ":"+modifier.String()) // TODO: Quoted
fix.Explain(
"This variable has a single value, not a list of values.",
"Therefore it feels strange to apply list operators like :M and :N onto it.",
"A more direct approach is to use the == and != operators.",
"",
"An entirely different case is when the pattern contains",
"wildcards like *, ?, [].",
"In such a case, using the :M or :N modifiers is useful and preferred.")
fix.Replace(from, to)
fix.Apply()
}
// simplifyMatch replaces:
// !empty(VAR:M*.c) with ${VAR:M*.c}
// empty(VAR:M*.c) with !${VAR:M*.c}
//
// * fromEmpty is true for the form empty(VAR...), and false for ${VAR...}.
//
// * neg is true for the form !empty(VAR...), and false for empty(VAR...).
func (s *MkCondSimplifier) simplifyMatch(varuse *MkVarUse, fromEmpty bool, neg bool) {
varname := varuse.varname
modifiers := varuse.modifiers
n := len(modifiers)
if n == 0 {
return
}
modsExceptLast := NewMkVarUse("", modifiers[:n-1]...).Mod()
vartype := G.Pkgsrc.VariableType(s.MkLines, varname)
modifier := modifiers[n-1]
ok, positive, pattern, exact := modifier.MatchMatch()
if !ok || !positive && n != 1 {
return
}
if !(fromEmpty && positive && !exact && vartype != nil) {
return
}
// For now, only handle cases where the variable is guaranteed to be
// defined, to avoid having to place an additional ':U' modifier in the
// expression.
if !s.isDefined(varname, vartype) {
return
}
// For now, restrict replacements to very simple patterns with only few
// special characters.
//
// Before generalizing this to arbitrary strings, there has to be
// a proper code generator for these conditions that handles all
// possible escaping.
//
// The same reasoning applies to the variable name, even though the
// variable name typically only uses a restricted character set.
if !matches(varuse.Mod(), `^[*.:\w\[\]]+$`) {
return
}
fixedPart := varname + modsExceptLast + ":M" + pattern
from := condStr(neg, "!", "") + "empty(" + fixedPart + ")"
to := condStr(neg, "", "!") + "${" + fixedPart + "}"
fix := s.MkLine.Autofix()
fix.Notef("%q can be simplified to %q.", from, to)
fix.Explain(
"This variable is guaranteed to be defined at this point.",
"Therefore it may occur on the left-hand side of a comparison",
"and doesn't have to be guarded by the function 'empty'.")
fix.Replace(from, to)
fix.Apply()
}
// isDefined determines whether the variable is guaranteed to be defined at
// this point of reading the makefile. If it is defined, conditions do not
// need the ':U' modifier.
func (s *MkCondSimplifier) isDefined(varname string, vartype *Vartype) bool {
if vartype.IsAlwaysInScope() && vartype.IsDefinedIfInScope() {
return true
}
// For run time expressions, such as ${${VAR} == value:?yes:no},
// the scope would need to be changed to ck.MkLines.allVars.
if s.MkLines.checkAllData.vars.IsDefined(varname) {
return true
}
return s.MkLines.Tools.SeenPrefs &&
vartype.Union().Contains(aclpUseLoadtime) &&
vartype.IsDefinedIfInScope()
}
package pkglint
import (
"gopkg.in/check.v1"
)
type MkCondSimplifierTester struct {
c *check.C
*Tester
}
func (t *MkCondSimplifierTester) setUp() {
t.CreateFileLines("mk/bsd.prefs.mk")
t.Chdir("category/package")
// The Anything type suppresses the warnings from type checking.
// BtUnknown would not work here, see Pkgsrc.VariableType.
btAnything := &BasicType{"Anything", func(cv *VartypeCheck) {}}
// For simplifying the expressions, it is necessary to know whether
// a variable can be undefined. Undefined variables need the
// :U modifier or must be enclosed in double quotes, otherwise
// bmake will complain about a "Malformed conditional". That error
// message is not entirely precise since the expression
// is syntactically valid, it's just the evaluation that fails.
//
// Some variables such as MACHINE_ARCH are in scope from the very
// beginning.
//
// Some variables such as PKGPATH are in scope after bsd.prefs.mk
// has been included.
//
// Some variables such as PREFIX (as of December 2019) are only in
// scope after bsd.pkg.mk has been included. These cannot be used
// in .if conditions at all.
//
// Even when they are in scope, some variables such as PKGREVISION
// or MAKE_JOBS may be undefined.
t.SetUpVarType("IN_SCOPE_DEFINED", btAnything, AlwaysInScope|DefinedIfInScope,
"*.mk: use, use-loadtime")
t.SetUpVarType("IN_SCOPE", btAnything, AlwaysInScope,
"*.mk: use, use-loadtime")
t.SetUpVarType("PREFS_DEFINED", btAnything, DefinedIfInScope,
"*.mk: use, use-loadtime")
t.SetUpVarType("PREFS", btAnything, NoVartypeOptions,
"*.mk: use, use-loadtime")
t.SetUpVarType("LATER_DEFINED", btAnything, DefinedIfInScope,
"*.mk: use")
t.SetUpVarType("LATER", btAnything, NoVartypeOptions,
"*.mk: use")
// UNDEFINED is also used in the following tests, but is obviously
// not defined here.
}
func (t *MkCondSimplifierTester) testBeforePrefs(before, after string, diagnostics ...string) {
t.doTest(false, before, after, diagnostics...)
}
func (t *MkCondSimplifierTester) testAfterPrefs(before, after string, diagnostics ...string) {
t.doTest(true, before, after, diagnostics...)
}
func (t *MkCondSimplifierTester) testBeforeAndAfterPrefs(before, after string, diagnostics ...string) {
t.doTest(false, before, after, diagnostics...)
t.doTest(true, before, after, diagnostics...)
}
// prefs: whether to include bsd.prefs.mk before the condition
// before: the directive before the condition is simplified
// after: the directive after the condition is simplified
func (t *MkCondSimplifierTester) doTest(prefs bool, before, after string, diagnostics ...string) {
if !matches(before, `IN_SCOPE|PREFS|LATER|UNDEFINED`) {
t.c.Errorf("Condition %q must include one of the above variable names.", before)
}
mklines := t.SetUpFileMkLines("filename.mk",
MkCvsID,
condStr(prefs, ".include \"../../mk/bsd.prefs.mk\"", ""),
"", // a few spare lines
"", // a few spare lines
"", // a few spare lines
before,
".endif")
action := func(autofix bool) {
mklines.ForEach(func(mkline *MkLine) {
// Sets mklines.Tools.SeenPrefs, which decides whether the :U modifier
// is necessary; see MkLines.checkLine.
mklines.Tools.ParseToolLine(mklines, mkline, false, false)
if mkline.IsDirective() && mkline.Directive() != "endif" {
// TODO: Replace Check with a more
// specific method that does not do the type checks.
NewMkCondChecker(mkline, mklines).Check()
}
})
if autofix {
afterMklines := LoadMk(t.File("filename.mk"), nil, MustSucceed)
t.CheckEquals(afterMklines.mklines[5].Text, after)
}
}
t.ExpectDiagnosticsAutofix(action, diagnostics...)
}
func (s *Suite) Test_MkCondSimplifier_SimplifyVarUse(c *check.C) {
t := MkCondSimplifierTester{c, s.Init(c)}
t.setUp()
t.testBeforeAndAfterPrefs(
".if ${IN_SCOPE_DEFINED:Mpattern}",
".if ${IN_SCOPE_DEFINED} == pattern",
"NOTE: filename.mk:6: IN_SCOPE_DEFINED can be "+
"compared using the simpler \"${IN_SCOPE_DEFINED} == pattern\" "+
"instead of matching against \":Mpattern\".",
"AUTOFIX: filename.mk:6: Replacing \"${IN_SCOPE_DEFINED:Mpattern}\" "+
"with \"${IN_SCOPE_DEFINED} == pattern\".")
t.testBeforeAndAfterPrefs(
".if !empty(IN_SCOPE_DEFINED:M[Nn][Oo])",
".if ${IN_SCOPE_DEFINED:M[Nn][Oo]}",
"NOTE: filename.mk:6: \"!empty(IN_SCOPE_DEFINED:M[Nn][Oo])\" "+
"can be simplified to \"${IN_SCOPE_DEFINED:M[Nn][Oo]}\".",
"AUTOFIX: filename.mk:6: "+
"Replacing \"!empty(IN_SCOPE_DEFINED:M[Nn][Oo])\" "+
"with \"${IN_SCOPE_DEFINED:M[Nn][Oo]}\".")
}
func (s *Suite) Test_MkCondSimplifier_simplifyWord(c *check.C) {
t := MkCondSimplifierTester{c, s.Init(c)}
t.setUp()
t.testBeforeAndAfterPrefs(
".if ${IN_SCOPE_DEFINED:Mpattern}",
".if ${IN_SCOPE_DEFINED} == pattern",
"NOTE: filename.mk:6: IN_SCOPE_DEFINED can be "+
"compared using the simpler \"${IN_SCOPE_DEFINED} == pattern\" "+
"instead of matching against \":Mpattern\".",
"AUTOFIX: filename.mk:6: Replacing \"${IN_SCOPE_DEFINED:Mpattern}\" "+
"with \"${IN_SCOPE_DEFINED} == pattern\".")
t.testBeforeAndAfterPrefs(
".if ${IN_SCOPE:Mpattern}",
".if ${IN_SCOPE:U} == pattern",
"NOTE: filename.mk:6: IN_SCOPE can be "+
"compared using the simpler \"${IN_SCOPE:U} == pattern\" "+
"instead of matching against \":Mpattern\".",
"AUTOFIX: filename.mk:6: Replacing \"${IN_SCOPE:Mpattern}\" "+
"with \"${IN_SCOPE:U} == pattern\".")
// Even though PREFS_DEFINED is declared as DefinedIfInScope,
// it is not in scope yet. Therefore it needs the :U modifier.
// The warning that this variable is not yet in scope comes from
// a different part of pkglint.
t.testBeforePrefs(
".if ${PREFS_DEFINED:Mpattern}",
".if ${PREFS_DEFINED:U} == pattern",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED:U} == pattern\" "+
"instead of matching against \":Mpattern\".",
"WARN: filename.mk:6: To use PREFS_DEFINED at load time, "+
".include \"../../mk/bsd.prefs.mk\" first.",
"AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Mpattern}\" "+
"with \"${PREFS_DEFINED:U} == pattern\".")
t.testAfterPrefs(
".if ${PREFS_DEFINED:Mpattern}",
".if ${PREFS_DEFINED} == pattern",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED} == pattern\" "+
"instead of matching against \":Mpattern\".",
"AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Mpattern}\" "+
"with \"${PREFS_DEFINED} == pattern\".")
t.testBeforePrefs(
".if ${PREFS:Mpattern}",
".if ${PREFS:U} == pattern",
"NOTE: filename.mk:6: PREFS can be "+
"compared using the simpler \"${PREFS:U} == pattern\" "+
"instead of matching against \":Mpattern\".",
"WARN: filename.mk:6: To use PREFS at load time, "+
".include \"../../mk/bsd.prefs.mk\" first.",
"AUTOFIX: filename.mk:6: Replacing \"${PREFS:Mpattern}\" "+
"with \"${PREFS:U} == pattern\".")
// Preferences that may be undefined always need the :U modifier,
// even when they are in scope.
t.testAfterPrefs(
".if ${PREFS:Mpattern}",
".if ${PREFS:U} == pattern",
"NOTE: filename.mk:6: PREFS can be "+
"compared using the simpler \"${PREFS:U} == pattern\" "+
"instead of matching against \":Mpattern\".",
"AUTOFIX: filename.mk:6: Replacing \"${PREFS:Mpattern}\" "+
"with \"${PREFS:U} == pattern\".")
// Variables that are defined later always need the :U modifier.
// It is probably a mistake to use them in conditions at all.
t.testBeforeAndAfterPrefs(
".if ${LATER_DEFINED:Mpattern}",
".if ${LATER_DEFINED:U} == pattern",
"NOTE: filename.mk:6: LATER_DEFINED can be "+
"compared using the simpler \"${LATER_DEFINED:U} == pattern\" "+
"instead of matching against \":Mpattern\".",
"WARN: filename.mk:6: "+
"LATER_DEFINED should not be used at load time in any file.",
"AUTOFIX: filename.mk:6: Replacing \"${LATER_DEFINED:Mpattern}\" "+
"with \"${LATER_DEFINED:U} == pattern\".")
// Variables that are defined later always need the :U modifier.
// It is probably a mistake to use them in conditions at all.
t.testBeforeAndAfterPrefs(
".if ${LATER:Mpattern}",
".if ${LATER:U} == pattern",
"NOTE: filename.mk:6: LATER can be "+
"compared using the simpler \"${LATER:U} == pattern\" "+
"instead of matching against \":Mpattern\".",
"WARN: filename.mk:6: "+
"LATER should not be used at load time in any file.",
"AUTOFIX: filename.mk:6: Replacing \"${LATER:Mpattern}\" "+
"with \"${LATER:U} == pattern\".")
t.testBeforeAndAfterPrefs(
".if ${UNDEFINED:Mpattern}",
".if ${UNDEFINED:Mpattern}",
"WARN: filename.mk:6: UNDEFINED is used but not defined.")
// When the pattern contains placeholders, it cannot be converted to == or !=.
t.testAfterPrefs(
".if ${PREFS_DEFINED:Mpa*n}",
".if ${PREFS_DEFINED:Mpa*n}",
nil...)
// When deciding whether to replace the expression, only the
// last modifier is inspected. All the others are copied.
t.testAfterPrefs(
".if ${PREFS_DEFINED:tl:Mpattern}",
".if ${PREFS_DEFINED:tl} == pattern",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED:tl} == pattern\" "+
"instead of matching against \":Mpattern\".",
"AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:tl:Mpattern}\" "+
"with \"${PREFS_DEFINED:tl} == pattern\".")
// Negated pattern matches are supported as well,
// as long as the variable is guaranteed to be nonempty.
// TODO: Actually implement this.
// As of December 2019, IsNonemptyIfDefined is not used anywhere.
t.testAfterPrefs(
".if ${PREFS_DEFINED:Npattern}",
".if ${PREFS_DEFINED} != pattern",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED} != pattern\" "+
"instead of matching against \":Npattern\".",
"AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Npattern}\" "+
"with \"${PREFS_DEFINED} != pattern\".")
// ${PREFS_DEFINED:None:Ntwo} is a short variant of
// ${PREFS_DEFINED} != "one" && ${PREFS_DEFINED} != "two".
// Applying the transformation would make the condition longer
// than before, therefore nothing can be simplified here,
// even though all patterns are exact matches.
t.testAfterPrefs(
".if ${PREFS_DEFINED:None:Ntwo}",
".if ${PREFS_DEFINED:None:Ntwo}",
nil...)
// Note: this combination doesn't make sense since the patterns
// "one" and "two" don't overlap.
// Nevertheless it is possible and valid to simplify the condition.
t.testAfterPrefs(
".if ${PREFS_DEFINED:Mone:Mtwo}",
".if ${PREFS_DEFINED:Mone} == two",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED:Mone} == two\" "+
"instead of matching against \":Mtwo\".",
"AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Mone:Mtwo}\" "+
"with \"${PREFS_DEFINED:Mone} == two\".")
// There is no ! before the empty, which is easy to miss.
// Because of this missing negation, the comparison operator is !=.
t.testAfterPrefs(
".if empty(PREFS_DEFINED:Mpattern)",
".if ${PREFS_DEFINED} != pattern",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED} != pattern\" "+
"instead of matching against \":Mpattern\".",
"AUTOFIX: filename.mk:6: Replacing \"empty(PREFS_DEFINED:Mpattern)\" "+
"with \"${PREFS_DEFINED} != pattern\".")
t.testAfterPrefs(
".if !!empty(PREFS_DEFINED:Mpattern)",
// TODO: The ! and == could be combined into a !=.
// Luckily the !! pattern doesn't occur in practice.
".if !${PREFS_DEFINED} == pattern",
// TODO: When taking all the ! into account, this is actually a
// test for emptiness, therefore the diagnostics should suggest
// the != operator instead of ==.
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED} == pattern\" "+
"instead of matching against \":Mpattern\".",
"AUTOFIX: filename.mk:6: Replacing \"!empty(PREFS_DEFINED:Mpattern)\" "+
"with \"${PREFS_DEFINED} == pattern\".")
// Simplifying the condition also works in complex expressions.
t.testAfterPrefs(".if empty(PREFS_DEFINED:Mpattern) || 0",
".if ${PREFS_DEFINED} != pattern || 0",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED} != pattern\" "+
"instead of matching against \":Mpattern\".",
"AUTOFIX: filename.mk:6: Replacing \"empty(PREFS_DEFINED:Mpattern)\" "+
"with \"${PREFS_DEFINED} != pattern\".")
// No note in this case since there is no implicit !empty around the varUse.
// There is no obvious way of writing this expression in a simpler form.
t.testAfterPrefs(
".if ${PREFS_DEFINED:Mpattern} != ${OTHER}",
".if ${PREFS_DEFINED:Mpattern} != ${OTHER}",
"WARN: filename.mk:6: OTHER is used but not defined.")
// The condition is also simplified if it doesn't use the !empty
// form but the implicit conversion to boolean.
t.testAfterPrefs(
".if ${PREFS_DEFINED:Mpattern}",
".if ${PREFS_DEFINED} == pattern",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED} == pattern\" "+
"instead of matching against \":Mpattern\".",
"AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Mpattern}\" "+
"with \"${PREFS_DEFINED} == pattern\".")
// A single negation outside the implicit conversion is taken into
// account when simplifying the condition.
t.testAfterPrefs(
".if !${PREFS_DEFINED:Mpattern}",
".if ${PREFS_DEFINED} != pattern",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED} != pattern\" "+
"instead of matching against \":Mpattern\".",
"AUTOFIX: filename.mk:6: Replacing \"!${PREFS_DEFINED:Mpattern}\" "+
"with \"${PREFS_DEFINED} != pattern\".")
// TODO: Merge the double negation into the comparison operator.
t.testAfterPrefs(
".if !!${PREFS_DEFINED:Mpattern}",
".if !${PREFS_DEFINED} != pattern",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED} != pattern\" "+
"instead of matching against \":Mpattern\".",
"AUTOFIX: filename.mk:6: Replacing \"!${PREFS_DEFINED:Mpattern}\" "+
"with \"${PREFS_DEFINED} != pattern\".")
// This pattern with spaces doesn't make sense at all in the :M
// modifier since it can never match.
// Or can it, if the PKGPATH contains quotes?
// TODO: How exactly does bmake apply the matching here,
// are both values unquoted first? Probably not, but who knows.
t.testBeforeAndAfterPrefs(
".if ${IN_SCOPE_DEFINED:Mpattern with spaces}",
".if ${IN_SCOPE_DEFINED:Mpattern with spaces}",
nil...)
// TODO: ".if ${PKGPATH} == \"pattern with spaces\"")
t.testBeforeAndAfterPrefs(
".if ${IN_SCOPE_DEFINED:M'pattern with spaces'}",
".if ${IN_SCOPE_DEFINED:M'pattern with spaces'}",
nil...)
// TODO: ".if ${PKGPATH} == 'pattern with spaces'")
t.testBeforeAndAfterPrefs(
".if ${IN_SCOPE_DEFINED:M&&}",
".if ${IN_SCOPE_DEFINED:M&&}",
nil...)
// TODO: ".if ${PKGPATH} == '&&'")
// The :N modifier involves another negation and is therefore more
// difficult to understand. That's even more reason to use the
// well-known == and != comparison operators instead.
//
// If PKGPATH is "", the condition is false.
// If PKGPATH is "negative-pattern", the condition is false.
// In all other cases, the condition is true.
//
// Therefore this condition cannot simply be transformed into
// ${PKGPATH} != negative-pattern, since that would produce a
// different result in the case where PKGPATH is empty.
//
// For system-provided variables that are guaranteed to be non-empty,
// such as OPSYS or PKGPATH, this replacement is valid.
// These variables are only guaranteed to be defined after bsd.prefs.mk
// has been included, like everywhere else.
//
// TODO: This is where NonemptyIfDefined comes into play.
t.testAfterPrefs(
".if ${PREFS_DEFINED:Nnegative-pattern}",
".if ${PREFS_DEFINED} != negative-pattern",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED} != negative-pattern\" "+
"instead of matching against \":Nnegative-pattern\".",
"AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Nnegative-pattern}\" "+
"with \"${PREFS_DEFINED} != negative-pattern\".")
// Since UNDEFINED is not a well-known variable that is
// guaranteed to be non-empty (see the previous example), it is not
// transformed at all.
t.testBeforePrefs(
".if ${UNDEFINED:Nnegative-pattern}",
".if ${UNDEFINED:Nnegative-pattern}",
"WARN: filename.mk:6: UNDEFINED is used but not defined.")
t.testAfterPrefs(
".if ${UNDEFINED:Nnegative-pattern}",
".if ${UNDEFINED:Nnegative-pattern}",
"WARN: filename.mk:6: UNDEFINED is used but not defined.")
// A complex condition may contain several simple conditions
// that are each simplified independently, in the same go.
t.testAfterPrefs(
".if ${PREFS_DEFINED:Mpath1} || ${PREFS_DEFINED:Mpath2}",
".if ${PREFS_DEFINED} == path1 || ${PREFS_DEFINED} == path2",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED} == path1\" "+
"instead of matching against \":Mpath1\".",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED} == path2\" "+
"instead of matching against \":Mpath2\".",
"AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Mpath1}\" "+
"with \"${PREFS_DEFINED} == path1\".",
"AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Mpath2}\" "+
"with \"${PREFS_DEFINED} == path2\".")
// Removing redundant parentheses may be useful one day but is
// not urgent.
// Simplifying the inner expression keeps all parentheses as-is.
t.testAfterPrefs(
".if (((((${PREFS_DEFINED:Mpath})))))",
".if (((((${PREFS_DEFINED} == path)))))",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED} == path\" "+
"instead of matching against \":Mpath\".",
"AUTOFIX: filename.mk:6: Replacing \"${PREFS_DEFINED:Mpath}\" "+
"with \"${PREFS_DEFINED} == path\".")
// Several modifiers like :S and :C may change the variable value.
// Whether the condition can be simplified or not only depends on the
// last modifier in the chain.
t.testAfterPrefs(
".if !empty(PREFS_DEFINED:S,NetBSD,ok,:Mok)",
".if ${PREFS_DEFINED:S,NetBSD,ok,} == ok",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED:S,NetBSD,ok,} == ok\" "+
"instead of matching against \":Mok\".",
"AUTOFIX: filename.mk:6: Replacing \"!empty(PREFS_DEFINED:S,NetBSD,ok,:Mok)\" "+
"with \"${PREFS_DEFINED:S,NetBSD,ok,} == ok\".")
t.testAfterPrefs(
".if empty(PREFS_DEFINED:tl:Msunos)",
".if ${PREFS_DEFINED:tl} != sunos",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED:tl} != sunos\" "+
"instead of matching against \":Msunos\".",
"AUTOFIX: filename.mk:6: Replacing \"empty(PREFS_DEFINED:tl:Msunos)\" "+
"with \"${PREFS_DEFINED:tl} != sunos\".")
// The condition can only be simplified if the :M or :N modifier
// appears at the end of the chain.
t.testAfterPrefs(
".if !empty(PREFS_DEFINED:O:MUnknown:S,a,b,)",
".if !empty(PREFS_DEFINED:O:MUnknown:S,a,b,)",
nil...)
// The dot is just an ordinary character in a pattern.
// In comparisons, an unquoted 1.2 is interpreted as a number though.
t.testAfterPrefs(
".if !empty(PREFS_DEFINED:Mpackage1.2)",
".if ${PREFS_DEFINED} == package1.2",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED} == package1.2\" "+
"instead of matching against \":Mpackage1.2\".",
"AUTOFIX: filename.mk:6: Replacing \"!empty(PREFS_DEFINED:Mpackage1.2)\" "+
"with \"${PREFS_DEFINED} == package1.2\".")
// Numbers must be enclosed in quotes, otherwise they are compared
// as numbers, not as strings.
// The :M and :N modifiers always compare strings.
t.testAfterPrefs(
".if empty(PREFS:U:M64)",
".if ${PREFS:U} != \"64\"",
"NOTE: filename.mk:6: PREFS can be "+
"compared using the simpler \"${PREFS:U} != \"64\"\" "+
"instead of matching against \":M64\".",
"AUTOFIX: filename.mk:6: Replacing \"empty(PREFS:U:M64)\" "+
"with \"${PREFS:U} != \\\"64\\\"\".")
// Fractional numbers must also be enclosed in quotes.
t.testAfterPrefs(
".if empty(PREFS:U:M19.12)",
".if ${PREFS:U} != \"19.12\"",
"NOTE: filename.mk:6: PREFS can be "+
"compared using the simpler \"${PREFS:U} != \"19.12\"\" "+
"instead of matching against \":M19.12\".",
"AUTOFIX: filename.mk:6: Replacing \"empty(PREFS:U:M19.12)\" "+
"with \"${PREFS:U} != \\\"19.12\\\"\".")
t.testAfterPrefs(
".if !empty(LATER:Npattern)",
".if !empty(LATER:Npattern)",
// No diagnostics about the :N modifier yet,
// see MkCondChecker.simplify.replace.
"WARN: filename.mk:6: LATER should not be used "+
"at load time in any file.")
// TODO: Add a note that the :U is unnecessary, and explain why.
t.testAfterPrefs(
".if ${PREFS_DEFINED:U:Mpattern}",
".if ${PREFS_DEFINED:U} == pattern",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED:U} == pattern\" "+
"instead of matching against \":Mpattern\".",
"AUTOFIX: filename.mk:6: "+
"Replacing \"${PREFS_DEFINED:U:Mpattern}\" "+
"with \"${PREFS_DEFINED:U} == pattern\".")
// Conditions without any modifiers cannot be simplified
// and are therefore skipped.
t.testBeforeAndAfterPrefs(
".if ${IN_SCOPE_DEFINED}",
".if ${IN_SCOPE_DEFINED}",
nil...)
// Special characters must be enclosed in quotes when they are
// used in string literals.
// As of December 2019, strings with special characters are not yet
// replaced automatically, see mkCondLiteralChars.
// TODO: Add tests for all characters that are special in string literals or patterns.
// TODO: Then, extend the set of characters for which the expressions are simplified.
t.testBeforePrefs(
".if ${PREFS_DEFINED:M&&}",
".if ${PREFS_DEFINED:M&&}",
"WARN: filename.mk:6: To use PREFS_DEFINED at load time, .include \"../../mk/bsd.prefs.mk\" first.")
t.testAfterPrefs(
".if ${PREFS_DEFINED:M&&}",
".if ${PREFS_DEFINED:M&&}",
nil...)
t.testBeforePrefs(
".if ${PREFS:M&&}",
".if ${PREFS:M&&}",
// TODO: Warn that the :U is missing.
"WARN: filename.mk:6: To use PREFS at load time, .include \"../../mk/bsd.prefs.mk\" first.")
t.testAfterPrefs(
".if ${PREFS:M&&}",
".if ${PREFS:M&&}",
// TODO: Warn that the :U is missing.
nil...)
// The + is contained in both mkCondStringLiteralUnquoted and
// mkCondModifierPatternLiteral, therefore it is copied verbatim.
t.testAfterPrefs(
".if ${PREFS_DEFINED:Mcategory/gtk+}",
".if ${PREFS_DEFINED} == category/gtk+",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED} == category/gtk+\" "+
"instead of matching against \":Mcategory/gtk+\".",
"AUTOFIX: filename.mk:6: "+
"Replacing \"${PREFS_DEFINED:Mcategory/gtk+}\" "+
"with \"${PREFS_DEFINED} == category/gtk+\".")
// The characters <=> may be used unescaped in :M and :N patterns
// but not in .if conditions. There they must be enclosed in quotes.
t.testBeforePrefs(
".if ${PREFS_DEFINED:M<=>}",
".if ${PREFS_DEFINED:U} == \"<=>\"",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED:U} == \"<=>\"\" "+
"instead of matching against \":M<=>\".",
"WARN: filename.mk:6: To use PREFS_DEFINED at load time, "+
".include \"../../mk/bsd.prefs.mk\" first.",
"AUTOFIX: filename.mk:6: "+
"Replacing \"${PREFS_DEFINED:M<=>}\" "+
"with \"${PREFS_DEFINED:U} == \\\"<=>\\\"\".")
t.testAfterPrefs(
".if ${PREFS_DEFINED:M<=>}",
".if ${PREFS_DEFINED} == \"<=>\"",
"NOTE: filename.mk:6: PREFS_DEFINED can be "+
"compared using the simpler \"${PREFS_DEFINED} == \"<=>\"\" "+
"instead of matching against \":M<=>\".",
"AUTOFIX: filename.mk:6: "+
"Replacing \"${PREFS_DEFINED:M<=>}\" "+
"with \"${PREFS_DEFINED} == \\\"<=>\\\"\".")
// If pkglint replaces this particular pattern, the resulting string
// literal must be escaped properly.
t.testBeforeAndAfterPrefs(
".if ${IN_SCOPE_DEFINED:M\"}",
".if ${IN_SCOPE_DEFINED:M\"}",
nil...)
t.testBeforeAndAfterPrefs(
".if !empty(IN_SCOPE_DEFINED:M)",
".if ${IN_SCOPE_DEFINED} == \"\"",
"NOTE: filename.mk:6: IN_SCOPE_DEFINED can be "+
"compared using the simpler "+"\"${IN_SCOPE_DEFINED} == \"\"\" "+
"instead of matching against \":M\".",
"AUTOFIX: filename.mk:6: "+
"Replacing \"!empty(IN_SCOPE_DEFINED:M)\" "+
"with \"${IN_SCOPE_DEFINED} == \\\"\\\"\".",
)
}
func (s *Suite) Test_MkCondSimplifier_simplifyWord__defined_in_same_file(c *check.C) {
t := s.Init(c)
t.SetUpPackage("category/package")
t.Chdir("category/package")
t.FinishSetUp()
doTest := func(before string) {
mklines := t.SetUpFileMkLines("filename.mk",
MkCvsID,
"OK=\t\tok",
"OK_DIR=\t\tok", // See Pkgsrc.guessVariableType.
before,
"LATER=\t\tlater",
"LATER_DIR=\tlater", // See Pkgsrc.guessVariableType.
".endif",
"USED=\t\t${OK} ${LATER} ${OK_DIR} ${LATER_DIR} ${USED}")
// The high-level call MkLines.Check is used here to
// get MkLines.Tools.SeenPrefs correct, which decides
// whether the :U modifier is necessary.
mklines.Check()
}
// before: the directive before the condition is simplified
// after: the directive after the condition is simplified
test := func(before, after string, diagnostics ...string) {
t.ExpectDiagnosticsAutofix(
func(bool) { doTest(before) },
diagnostics...)
// TODO: Move this assertion above the assertion about the diagnostics.
afterMklines := LoadMk(t.File("filename.mk"), nil, MustSucceed)
t.CheckEquals(afterMklines.mklines[3].Text, after)
}
// For variables with completely unknown names, the type is nil
// and the complete check is skipped.
test(
".if ${OK:Mpattern}",
".if ${OK:Mpattern}",
nil...)
// For variables with completely unknown names, the type is nil
// and the complete check is skipped.
test(
".if ${LATER:Mpattern}",
".if ${LATER:Mpattern}",
nil...)
// OK_DIR is defined earlier than the .if condition,
// which is the correct order.
test(
".if ${OK_DIR:Mpattern}",
".if ${OK_DIR} == pattern",
"NOTE: filename.mk:4: OK_DIR can be "+
"compared using the simpler \"${OK_DIR} == pattern\" "+
"instead of matching against \":Mpattern\".",
"AUTOFIX: filename.mk:4: "+
"Replacing \"${OK_DIR:Mpattern}\" "+
"with \"${OK_DIR} == pattern\".")
// LATER_DIR is defined later than the .if condition,
// therefore at the time of the .if statement, it is still empty.
test(
".if ${LATER_DIR:Mpattern}",
".if ${LATER_DIR:U} == pattern",
// TODO: Warn that LATER_DIR is used before it is defined.
"NOTE: filename.mk:4: LATER_DIR can be "+
"compared using the simpler \"${LATER_DIR:U} == pattern\" "+
"instead of matching against \":Mpattern\".",
"AUTOFIX: filename.mk:4: "+
"Replacing \"${LATER_DIR:Mpattern}\" "+
"with \"${LATER_DIR:U} == pattern\".")
}
func (s *Suite) Test_MkCondSimplifier_simplifyMatch(c *check.C) {
t := MkCondSimplifierTester{c, s.Init(c)}
t.setUp()
t.testBeforeAndAfterPrefs(
".if !empty(IN_SCOPE_DEFINED:M*.c)",
".if ${IN_SCOPE_DEFINED:M*.c}",
"NOTE: filename.mk:6: \"!empty(IN_SCOPE_DEFINED:M*.c)\" "+
"can be simplified to \"${IN_SCOPE_DEFINED:M*.c}\".",
"AUTOFIX: filename.mk:6: "+
"Replacing \"!empty(IN_SCOPE_DEFINED:M*.c)\" "+
"with \"${IN_SCOPE_DEFINED:M*.c}\".")
t.testBeforeAndAfterPrefs(
".if empty(IN_SCOPE_DEFINED:M*.c)",
".if !${IN_SCOPE_DEFINED:M*.c}",
"NOTE: filename.mk:6: \"empty(IN_SCOPE_DEFINED:M*.c)\" "+
"can be simplified to \"!${IN_SCOPE_DEFINED:M*.c}\".",
"AUTOFIX: filename.mk:6: "+
"Replacing \"empty(IN_SCOPE_DEFINED:M*.c)\" "+
"with \"!${IN_SCOPE_DEFINED:M*.c}\".")
t.testBeforeAndAfterPrefs(
".if !empty(IN_SCOPE_DEFINED:M[Nn][Oo])",
".if ${IN_SCOPE_DEFINED:M[Nn][Oo]}",
"NOTE: filename.mk:6: \"!empty(IN_SCOPE_DEFINED:M[Nn][Oo])\" "+
"can be simplified to \"${IN_SCOPE_DEFINED:M[Nn][Oo]}\".",
"AUTOFIX: filename.mk:6: "+
"Replacing \"!empty(IN_SCOPE_DEFINED:M[Nn][Oo])\" "+
"with \"${IN_SCOPE_DEFINED:M[Nn][Oo]}\".")
}
func (s *Suite) Test_MkCondSimplifier_isDefined(c *check.C) {
t := s.Init(c)
mklines := t.NewMkLines("filename.mk",
MkCvsID,
"DEFINED=\tyes",
".if defined(UNDEFINED) && defined(DEFINED)",
".endif")
// Initialize whether the variables are defined.
mklines.Check()
mklines.ForEach(func(mkline *MkLine) {
if mkline.IsDirective() && mkline.Directive() == "if" {
mcs := MkCondSimplifier{mklines, mkline}
vartype := NewVartype(BtMessage, 0, nil...)
t.AssertEquals(false, mcs.isDefined("UNDEFINED", vartype))
t.AssertEquals(true, mcs.isDefined("DEFINED", vartype))
}
})
t.CheckOutputLines(
"WARN: filename.mk:3: UNDEFINED is used but not defined.")
}
@@ -1,21 +1,21 @@ | @@ -1,21 +1,21 @@ | |||
1 | package pkglint | 1 | package pkglint | |
2 | 2 | |||
3 | import ( | 3 | import ( | |
4 | "netbsd.org/pkglint/textproc" | 4 | "netbsd.org/pkglint/textproc" | |
5 | "strings" | 5 | "strings" | |
6 | ) | 6 | ) | |
7 | 7 | |||
8 | // MkParser wraps a Parser and provides methods for parsing | 8 | // MkParser wraps a MkLexer and provides methods for parsing | |
9 | // things related to Makefiles. | 9 | // things related to Makefiles. | |
10 | type MkParser struct { | 10 | type MkParser struct { | |
11 | diag Diagnoser | 11 | diag Diagnoser | |
12 | mklex *MkLexer | 12 | mklex *MkLexer | |
13 | lexer *textproc.Lexer | 13 | lexer *textproc.Lexer | |
14 | EmitWarnings bool | 14 | EmitWarnings bool | |
15 | } | 15 | } | |
16 | 16 | |||
17 | func (p *MkParser) EOF() bool { | 17 | func (p *MkParser) EOF() bool { | |
18 | return p.lexer.EOF() | 18 | return p.lexer.EOF() | |
19 | } | 19 | } | |
20 | 20 | |||
21 | func (p *MkParser) Rest() string { | 21 | func (p *MkParser) Rest() string { |
@@ -120,30 +120,32 @@ func (MkVarUseModifier) EvalSubst(s stri | @@ -120,30 +120,32 @@ func (MkVarUseModifier) EvalSubst(s stri | |||
120 | done := false | 120 | done := false | |
121 | gflag := contains(flags, "g") | 121 | gflag := contains(flags, "g") | |
122 | return true, replaceAllFunc(s, re, func(match string) string { | 122 | return true, replaceAllFunc(s, re, func(match string) string { | |
123 | if gflag || !done { | 123 | if gflag || !done { | |
124 | done = !gflag | 124 | done = !gflag | |
125 | return to | 125 | return to | |
126 | } | 126 | } | |
127 | return match | 127 | return match | |
128 | }) | 128 | }) | |
129 | } | 129 | } | |
130 | 130 | |||
131 | // MatchMatch tries to match the modifier to a :M or a :N pattern matching. | 131 | // MatchMatch tries to match the modifier to a :M or a :N pattern matching. | |
132 | // Examples: | 132 | // Examples: | |
133 | // :Mpattern => true, true, "pattern", true | 133 | // modifier => ok positive pattern exact | |
134 | // :M* => true, true, "*", false | 134 | // ------------------------------------------------ | |
135 | // :M${VAR} => true, true, "${VAR}", false | 135 | // :Mpattern => true, true, "pattern", true | |
136 | // :Npattern => true, false, "pattern", true | 136 | // :M* => true, true, "*", false | |
137 | // :M${VAR} => true, true, "${VAR}", false | |||
138 | // :Npattern => true, false, "pattern", true | |||
137 | // :X => false | 139 | // :X => false | |
138 | func (m MkVarUseModifier) MatchMatch() (ok bool, positive bool, pattern string, exact bool) { | 140 | func (m MkVarUseModifier) MatchMatch() (ok bool, positive bool, pattern string, exact bool) { | |
139 | if m.HasPrefix("M") || m.HasPrefix("N") { | 141 | if m.HasPrefix("M") || m.HasPrefix("N") { | |
140 | str := m.String() | 142 | str := m.String() | |
141 | // See devel/bmake/files/str.c:^Str_Match | 143 | // See devel/bmake/files/str.c:^Str_Match | |
142 | exact := !strings.ContainsAny(str[1:], "*?[\\$") | 144 | exact := !strings.ContainsAny(str[1:], "*?[\\$") | |
143 | return true, str[0] == 'M', str[1:], exact | 145 | return true, str[0] == 'M', str[1:], exact | |
144 | } | 146 | } | |
145 | return false, false, "", false | 147 | return false, false, "", false | |
146 | } | 148 | } | |
147 | 149 | |||
148 | func (m MkVarUseModifier) IsToLower() bool { return m == "tl" } | 150 | func (m MkVarUseModifier) IsToLower() bool { return m == "tl" } | |
149 | 151 |
@@ -1331,26 +1331,39 @@ func (s *Suite) Test_Package_check__patc | @@ -1331,26 +1331,39 @@ func (s *Suite) Test_Package_check__patc | |||
1331 | 1331 | |||
1332 | t.SetUpPackage("category/package") | 1332 | t.SetUpPackage("category/package") | |
1333 | t.CreateFileLines("category/package/patches/Makefile", | 1333 | t.CreateFileLines("category/package/patches/Makefile", | |
1334 | "This file may contain anything.") | 1334 | "This file may contain anything.") | |
1335 | 1335 | |||
1336 | t.Main("category/package") | 1336 | t.Main("category/package") | |
1337 | 1337 | |||
1338 | t.CheckOutputLines( | 1338 | t.CheckOutputLines( | |
1339 | "WARN: ~/category/package/patches/Makefile: Patch files should be "+ | 1339 | "WARN: ~/category/package/patches/Makefile: Patch files should be "+ | |
1340 | "named \"patch-\", followed by letters, '-', '_', '.', and digits only.", | 1340 | "named \"patch-\", followed by letters, '-', '_', '.', and digits only.", | |
1341 | "1 warning found.") | 1341 | "1 warning found.") | |
1342 | } | 1342 | } | |
1343 | 1343 | |||
1344 | func (s *Suite) Test_Package_check__redundant_WRKSRC(c *check.C) { | |||
1345 | t := s.Init(c) | |||
1346 | ||||
1347 | t.SetUpPackage("category/package", | |||
1348 | "WRKSRC=\t${WRKDIR}/package-1.0") | |||
1349 | ||||
1350 | t.Main("-q", "category/package") | |||
1351 | ||||
1352 | t.CheckOutputLines( | |||
1353 | "NOTE: ~/category/package/Makefile:20: " + | |||
1354 | "Setting WRKSRC to \"${WRKDIR}/package-1.0\" is redundant.") | |||
1355 | } | |||
1356 | ||||
1344 | func (s *Suite) Test_Package_checkDescr__DESCR_SRC(c *check.C) { | 1357 | func (s *Suite) Test_Package_checkDescr__DESCR_SRC(c *check.C) { | |
1345 | t := s.Init(c) | 1358 | t := s.Init(c) | |
1346 | 1359 | |||
1347 | t.SetUpPackage("other/package") | 1360 | t.SetUpPackage("other/package") | |
1348 | t.SetUpPackage("category/package", | 1361 | t.SetUpPackage("category/package", | |
1349 | "DESCR_SRC=\t../../other/package/DESCR") | 1362 | "DESCR_SRC=\t../../other/package/DESCR") | |
1350 | t.Remove("category/package/DESCR") | 1363 | t.Remove("category/package/DESCR") | |
1351 | t.FinishSetUp() | 1364 | t.FinishSetUp() | |
1352 | 1365 | |||
1353 | G.Check(t.File("category/package")) | 1366 | G.Check(t.File("category/package")) | |
1354 | 1367 | |||
1355 | t.CheckOutputEmpty() | 1368 | t.CheckOutputEmpty() | |
1356 | } | 1369 | } |
@@ -273,27 +273,27 @@ func (reg *VarTypeRegistry) cmdline(varn | @@ -273,27 +273,27 @@ func (reg *VarTypeRegistry) cmdline(varn | |||
273 | func (reg *VarTypeRegistry) infralist(varname string, basicType *BasicType) { | 273 | func (reg *VarTypeRegistry) infralist(varname string, basicType *BasicType) { | |
274 | reg.DefineName(varname, basicType, List, "infralist") | 274 | reg.DefineName(varname, basicType, List, "infralist") | |
275 | } | 275 | } | |
276 | 276 | |||
277 | // compilerLanguages reads the available languages that are typically | 277 | // compilerLanguages reads the available languages that are typically | |
278 | // bundled in a single compiler framework, such as GCC or Clang. | 278 | // bundled in a single compiler framework, such as GCC or Clang. | |
279 | func (reg *VarTypeRegistry) compilerLanguages(src *Pkgsrc) *BasicType { | 279 | func (reg *VarTypeRegistry) compilerLanguages(src *Pkgsrc) *BasicType { | |
280 | mklines := src.LoadMkExisting("mk/compiler.mk") | 280 | mklines := src.LoadMkExisting("mk/compiler.mk") | |
281 | 281 | |||
282 | languages := make(map[string]bool) | 282 | languages := make(map[string]bool) | |
283 | if mklines != nil { | 283 | if mklines != nil { | |
284 | for _, mkline := range mklines.mklines { | 284 | for _, mkline := range mklines.mklines { | |
285 | 285 | |||
286 | if mkline.IsVarassign() && mkline.Varname() == "_CXX_STD_VERSIONS" { | 286 | if mkline.IsVarassign() && hasSuffix(mkline.Varname(), "_STD_VERSIONS") { | |
287 | words := mkline.ValueFields(mkline.Value()) | 287 | words := mkline.ValueFields(mkline.Value()) | |
288 | for _, word := range words { | 288 | for _, word := range words { | |
289 | languages[intern(word)] = true | 289 | languages[intern(word)] = true | |
290 | } | 290 | } | |
291 | } | 291 | } | |
292 | 292 | |||
293 | if mkline.IsDirective() && mkline.NeedsCond() && mkline.Cond() != nil { | 293 | if mkline.IsDirective() && mkline.NeedsCond() && mkline.Cond() != nil { | |
294 | mkline.Cond().Walk(&MkCondCallback{ | 294 | mkline.Cond().Walk(&MkCondCallback{ | |
295 | VarUse: func(varuse *MkVarUse) { | 295 | VarUse: func(varuse *MkVarUse) { | |
296 | if varuse.varname == "USE_LANGUAGES" && len(varuse.modifiers) == 1 { | 296 | if varuse.varname == "USE_LANGUAGES" && len(varuse.modifiers) == 1 { | |
297 | ok, _, pattern, exact := varuse.modifiers[0].MatchMatch() | 297 | ok, _, pattern, exact := varuse.modifiers[0].MatchMatch() | |
298 | if ok && exact { | 298 | if ok && exact { | |
299 | languages[intern(pattern)] = true | 299 | languages[intern(pattern)] = true | |
@@ -1078,26 +1078,27 @@ func (reg *VarTypeRegistry) Init(src *Pk | @@ -1078,26 +1078,27 @@ func (reg *VarTypeRegistry) Init(src *Pk | |||
1078 | reg.pkglist("CHECK_HEADERS_SKIP", BtPathPattern) | 1078 | reg.pkglist("CHECK_HEADERS_SKIP", BtPathPattern) | |
1079 | reg.usr("CHECK_INTERPRETER", BtYesNo) | 1079 | reg.usr("CHECK_INTERPRETER", BtYesNo) | |
1080 | reg.pkglist("CHECK_INTERPRETER_SKIP", BtPathPattern) | 1080 | reg.pkglist("CHECK_INTERPRETER_SKIP", BtPathPattern) | |
1081 | reg.usr("CHECK_PERMS", BtYesNo) | 1081 | reg.usr("CHECK_PERMS", BtYesNo) | |
1082 | reg.pkglist("CHECK_PERMS_SKIP", BtPathPattern) | 1082 | reg.pkglist("CHECK_PERMS_SKIP", BtPathPattern) | |
1083 | reg.usr("CHECK_PORTABILITY", BtYesNo) | 1083 | reg.usr("CHECK_PORTABILITY", BtYesNo) | |
1084 | reg.pkglist("CHECK_PORTABILITY_SKIP", BtPathPattern) | 1084 | reg.pkglist("CHECK_PORTABILITY_SKIP", BtPathPattern) | |
1085 | reg.usr("CHECK_RELRO", BtYesNo) | 1085 | reg.usr("CHECK_RELRO", BtYesNo) | |
1086 | reg.pkglist("CHECK_RELRO_SKIP", BtPathPattern) | 1086 | reg.pkglist("CHECK_RELRO_SKIP", BtPathPattern) | |
1087 | reg.pkg("CHECK_RELRO_SUPPORTED", BtYesNo) | 1087 | reg.pkg("CHECK_RELRO_SUPPORTED", BtYesNo) | |
1088 | reg.pkg("CHECK_SHLIBS", BtYesNo) | 1088 | reg.pkg("CHECK_SHLIBS", BtYesNo) | |
1089 | reg.pkglist("CHECK_SHLIBS_SKIP", BtPathPattern) | 1089 | reg.pkglist("CHECK_SHLIBS_SKIP", BtPathPattern) | |
1090 | reg.pkg("CHECK_SHLIBS_SUPPORTED", BtYesNo) | 1090 | reg.pkg("CHECK_SHLIBS_SUPPORTED", BtYesNo) | |
1091 | reg.usrlist("CHECK_WRKREF", enum("tools wrappers home wrksrc work wrkobjdir pkgsrc buildlink extra")) | |||
1091 | reg.pkglist("CHECK_WRKREF_SKIP", BtPathPattern) | 1092 | reg.pkglist("CHECK_WRKREF_SKIP", BtPathPattern) | |
1092 | reg.pkg("CMAKE_ARG_PATH", BtPathname) | 1093 | reg.pkg("CMAKE_ARG_PATH", BtPathname) | |
1093 | reg.pkglist("CMAKE_ARGS", BtShellWord) | 1094 | reg.pkglist("CMAKE_ARGS", BtShellWord) | |
1094 | reg.pkglist("CMAKE_ARGS.*", BtShellWord) | 1095 | reg.pkglist("CMAKE_ARGS.*", BtShellWord) | |
1095 | reg.pkglist("CMAKE_DEPENDENCIES_REWRITE", BtWrksrcPathPattern) | 1096 | reg.pkglist("CMAKE_DEPENDENCIES_REWRITE", BtWrksrcPathPattern) | |
1096 | reg.pkglist("CMAKE_MODULE_PATH_OVERRIDE", BtWrksrcPathPattern) | 1097 | reg.pkglist("CMAKE_MODULE_PATH_OVERRIDE", BtWrksrcPathPattern) | |
1097 | reg.pkg("CMAKE_PKGSRC_BUILD_FLAGS", BtYesNo) | 1098 | reg.pkg("CMAKE_PKGSRC_BUILD_FLAGS", BtYesNo) | |
1098 | reg.pkglist("CMAKE_PREFIX_PATH", BtPathPattern) | 1099 | reg.pkglist("CMAKE_PREFIX_PATH", BtPathPattern) | |
1099 | reg.pkg("CMAKE_USE_GNU_INSTALL_DIRS", BtYesNo) | 1100 | reg.pkg("CMAKE_USE_GNU_INSTALL_DIRS", BtYesNo) | |
1100 | reg.pkg("CMAKE_INSTALL_PREFIX", BtPathname) // The default is ${PREFIX}. | 1101 | reg.pkg("CMAKE_INSTALL_PREFIX", BtPathname) // The default is ${PREFIX}. | |
1101 | reg.pkgappend("COMMENT", BtComment) | 1102 | reg.pkgappend("COMMENT", BtComment) | |
1102 | reg.sys("COMPILE.*", BtShellCommand) | 1103 | reg.sys("COMPILE.*", BtShellCommand) | |
1103 | reg.sys("COMPILER_RPATH_FLAG", enum("-Wl,-rpath")) | 1104 | reg.sys("COMPILER_RPATH_FLAG", enum("-Wl,-rpath")) |
@@ -1596,26 +1596,42 @@ func (cv *VartypeCheck) WrapperTransform | @@ -1596,26 +1596,42 @@ func (cv *VartypeCheck) WrapperTransform | |||
1596 | matches(cmd, `^(R|l|rpath):([^:]+):(.+)$`), | 1596 | matches(cmd, `^(R|l|rpath):([^:]+):(.+)$`), | |
1597 | matches(cmd, `^'?(opt|rename|rm-optarg|rmdir):.*$`), | 1597 | matches(cmd, `^'?(opt|rename|rm-optarg|rmdir):.*$`), | |
1598 | cmd == "-e", | 1598 | cmd == "-e", | |
1599 | matches(cmd, `^["']?s[|:,]`): | 1599 | matches(cmd, `^["']?s[|:,]`): | |
1600 | break | 1600 | break | |
1601 | 1601 | |||
1602 | default: | 1602 | default: | |
1603 | cv.Warnf("Unknown wrapper transform command %q.", cmd) | 1603 | cv.Warnf("Unknown wrapper transform command %q.", cmd) | |
1604 | } | 1604 | } | |
1605 | } | 1605 | } | |
1606 | 1606 | |||
1607 | func (cv *VartypeCheck) WrkdirSubdirectory() { | 1607 | func (cv *VartypeCheck) WrkdirSubdirectory() { | |
1608 | cv.Pathname() | 1608 | cv.Pathname() | |
1609 | ||||
1610 | if hasPrefix(cv.Value, "${WRKDIR}/") && cv.MkLines.pkg != nil { | |||
1611 | distname := cv.MkLines.pkg.redundant.get("DISTNAME").vari | |||
1612 | if distname.IsConstant() && cv.Value[10:] == distname.ConstantValue() { | |||
1613 | fix := cv.Autofix() | |||
1614 | fix.Notef("Setting WRKSRC to %q is redundant.", cv.Value) | |||
1615 | fix.Delete() | |||
1616 | fix.Apply() | |||
1617 | ||||
1618 | // XXX: Resetting the Line and MkLine should be handled universally | |||
1619 | // by Autofix. Without this hack, pkglint would crash later when | |||
1620 | // aligning the variable assignments. | |||
1621 | cv.MkLine.Line.Text = "" | |||
1622 | cv.MkLine.data = mkLineEmpty{} | |||
1623 | } | |||
1624 | } | |||
1609 | } | 1625 | } | |
1610 | 1626 | |||
1611 | // WrksrcPathPattern is a shell pattern for existing pathnames, possibly including slashes. | 1627 | // WrksrcPathPattern is a shell pattern for existing pathnames, possibly including slashes. | |
1612 | func (cv *VartypeCheck) WrksrcPathPattern() { | 1628 | func (cv *VartypeCheck) WrksrcPathPattern() { | |
1613 | cv.PathPattern() | 1629 | cv.PathPattern() | |
1614 | 1630 | |||
1615 | if hasPrefix(cv.Value, "${WRKSRC}/") { | 1631 | if hasPrefix(cv.Value, "${WRKSRC}/") { | |
1616 | fix := cv.Autofix() | 1632 | fix := cv.Autofix() | |
1617 | fix.Notef("The pathname patterns in %s don't need to mention ${WRKSRC}.", cv.Varname) | 1633 | fix.Notef("The pathname patterns in %s don't need to mention ${WRKSRC}.", cv.Varname) | |
1618 | fix.Explain( | 1634 | fix.Explain( | |
1619 | "These pathname patters are interpreted relative to ${WRKSRC} by definition.", | 1635 | "These pathname patters are interpreted relative to ${WRKSRC} by definition.", | |
1620 | "Therefore that part can be left out.") | 1636 | "Therefore that part can be left out.") | |
1621 | fix.Replace("${WRKSRC}/", "") | 1637 | fix.Replace("${WRKSRC}/", "") |
@@ -2399,47 +2399,64 @@ func (s *Suite) Test_VartypeCheck_Wrappe | @@ -2399,47 +2399,64 @@ func (s *Suite) Test_VartypeCheck_Wrappe | |||
2399 | "rename:src:dst", | 2399 | "rename:src:dst", | |
2400 | "rm-optarg:-option", | 2400 | "rm-optarg:-option", | |
2401 | "rmdir:/usr/include", | 2401 | "rmdir:/usr/include", | |
2402 | "rpath:/usr/lib:/usr/pkg/lib", | 2402 | "rpath:/usr/lib:/usr/pkg/lib", | |
2403 | "rpath:/usr/lib", | 2403 | "rpath:/usr/lib", | |
2404 | "unknown", | 2404 | "unknown", | |
2405 | "-e 's,-Wall,-Wall -Wextra,'") | 2405 | "-e 's,-Wall,-Wall -Wextra,'") | |
2406 | vt.Output( | 2406 | vt.Output( | |
2407 | "WARN: filename.mk:7: Unknown wrapper transform command \"rpath:/usr/lib\".", | 2407 | "WARN: filename.mk:7: Unknown wrapper transform command \"rpath:/usr/lib\".", | |
2408 | "WARN: filename.mk:8: Unknown wrapper transform command \"unknown\".") | 2408 | "WARN: filename.mk:8: Unknown wrapper transform command \"unknown\".") | |
2409 | } | 2409 | } | |
2410 | 2410 | |||
2411 | func (s *Suite) Test_VartypeCheck_WrkdirSubdirectory(c *check.C) { | 2411 | func (s *Suite) Test_VartypeCheck_WrkdirSubdirectory(c *check.C) { | |
2412 | vt := NewVartypeCheckTester(s.Init(c), BtWrkdirSubdirectory) | 2412 | t := s.Init(c) | |
2413 | pkg := NewPackage(t.SetUpPackage("category/package")) | |||
2414 | t.FinishSetUp() | |||
2415 | vt := NewVartypeCheckTester(t, BtWrkdirSubdirectory) | |||
2416 | pkg.Check() // To initialize pkg.redundant. | |||
2413 | 2417 | |||
2418 | vt.Package(pkg) | |||
2414 | vt.Varname("WRKSRC") | 2419 | vt.Varname("WRKSRC") | |
2415 | vt.Op(opAssign) | 2420 | vt.Op(opAssign) | |
2416 | vt.Values( | 2421 | vt.Values( | |
2417 | "${WRKDIR}", | 2422 | "${WRKDIR}", | |
2418 | "${WRKDIR}/", | 2423 | "${WRKDIR}/", | |
2419 | "${WRKDIR}/.", | 2424 | "${WRKDIR}/.", | |
2420 | "${WRKDIR}/subdir", | 2425 | "${WRKDIR}/subdir", | |
2421 | ".", | 2426 | ".", | |
2422 | "${DISTNAME}", | 2427 | "${DISTNAME}", | |
2423 | "${PKGNAME_NOREV}", | 2428 | "${PKGNAME_NOREV}", | |
2424 | "two words", | 2429 | "two words", | |
2425 | "../other", | 2430 | "../other", | |
2426 | "${WRKSRC}", // Recursive definition. | 2431 | "${WRKSRC}", // Recursive definition. | |
2427 | "${PKGDIR}/files") | 2432 | "${PKGDIR}/files") | |
2428 | 2433 | |||
2429 | // XXX: Many more consistency checks are possible here. | 2434 | // XXX: Many more consistency checks are possible here. | |
2430 | vt.Output( | 2435 | vt.Output( | |
2431 | "WARN: filename.mk:8: The pathname \"two words\" " + | 2436 | "WARN: filename.mk:8: The pathname \"two words\" " + | |
2432 | "contains the invalid character \" \".") | 2437 | "contains the invalid character \" \".") | |
2438 | ||||
2439 | vt.Values( | |||
2440 | // TODO: Note the redundant definition. | |||
2441 | "${WRKDIR}/package-1.0", | |||
2442 | "${WRKDIR}/pkg-1.0", // different package base | |||
2443 | "${WRKDIR}/package-1.000", // different version string | |||
2444 | "${WRKDIR}/package-1.1", // different version | |||
2445 | ) | |||
2446 | ||||
2447 | vt.Output( | |||
2448 | "NOTE: filename.mk:21: " + | |||
2449 | "Setting WRKSRC to \"${WRKDIR}/package-1.0\" is redundant.") | |||
2433 | } | 2450 | } | |
2434 | 2451 | |||
2435 | func (s *Suite) Test_VartypeCheck_WrksrcPathPattern(c *check.C) { | 2452 | func (s *Suite) Test_VartypeCheck_WrksrcPathPattern(c *check.C) { | |
2436 | t := s.Init(c) | 2453 | t := s.Init(c) | |
2437 | vt := NewVartypeCheckTester(t, BtWrksrcPathPattern) | 2454 | vt := NewVartypeCheckTester(t, BtWrksrcPathPattern) | |
2438 | 2455 | |||
2439 | vt.Varname("SUBST_FILES.class") | 2456 | vt.Varname("SUBST_FILES.class") | |
2440 | vt.Op(opAssign) | 2457 | vt.Op(opAssign) | |
2441 | vt.Values( | 2458 | vt.Values( | |
2442 | "relative/*.sh", | 2459 | "relative/*.sh", | |
2443 | "${WRKSRC}/relative/*.sh") | 2460 | "${WRKSRC}/relative/*.sh") | |
2444 | 2461 | |||
2445 | vt.Output( | 2462 | vt.Output( | |
@@ -2611,37 +2628,48 @@ type VartypeCheckTester struct { | @@ -2611,37 +2628,48 @@ type VartypeCheckTester struct { | |||
2611 | // NewVartypeCheckTester starts the test with a filename of "filename", at line 1, | 2628 | // NewVartypeCheckTester starts the test with a filename of "filename", at line 1, | |
2612 | // with "=" as the operator. The variable has to be initialized explicitly. | 2629 | // with "=" as the operator. The variable has to be initialized explicitly. | |
2613 | func NewVartypeCheckTester(t *Tester, basicType *BasicType) *VartypeCheckTester { | 2630 | func NewVartypeCheckTester(t *Tester, basicType *BasicType) *VartypeCheckTester { | |
2614 | 2631 | |||
2615 | // This is necessary to know whether the variable name is a list type | 2632 | // This is necessary to know whether the variable name is a list type | |
2616 | // since in such a case each value is split into the list elements. | 2633 | // since in such a case each value is split into the list elements. | |
2617 | if G.Pkgsrc.VariableType(nil, "USE_CWRAPPERS") == nil { | 2634 | if G.Pkgsrc.VariableType(nil, "USE_CWRAPPERS") == nil { | |
2618 | t.SetUpVartypes() | 2635 | t.SetUpVartypes() | |
2619 | } | 2636 | } | |
2620 | 2637 | |||
2621 | return &VartypeCheckTester{t, basicType, "filename.mk", 1, "", opAssign, nil} | 2638 | return &VartypeCheckTester{t, basicType, "filename.mk", 1, "", opAssign, nil} | |
2622 | } | 2639 | } | |
2623 | 2640 | |||
2641 | // Package sets the package that gives context to the MkLines that are | |||
2642 | // temporarily created in all following calls to Values. | |||
2643 | // | |||
2644 | // Depending on the test case at hand, it may be enough to have a bare | |||
2645 | // package created by NewPackage, in other cases the package data needs to be | |||
2646 | // loaded using Package.load. | |||
2624 | func (vt *VartypeCheckTester) Package(pkg *Package) { vt.pkg = pkg } | 2647 | func (vt *VartypeCheckTester) Package(pkg *Package) { vt.pkg = pkg } | |
2625 | 2648 | |||
2649 | // Varname sets the variable name that will be used in all following calls to | |||
2650 | // Values. | |||
2626 | func (vt *VartypeCheckTester) Varname(varname string) { | 2651 | func (vt *VartypeCheckTester) Varname(varname string) { | |
2627 | vartype := G.Pkgsrc.VariableType(nil, varname) | 2652 | vartype := G.Pkgsrc.VariableType(nil, varname) | |
2628 | assertNotNil(vartype) | 2653 | assertNotNil(vartype) | |
2629 | assert(vartype.basicType == vt.basicType) | 2654 | assert(vartype.basicType == vt.basicType) | |
2630 | 2655 | |||
2631 | vt.varname = varname | 2656 | vt.varname = varname | |
2632 | vt.nextSection() | 2657 | vt.nextSection() | |
2633 | } | 2658 | } | |
2634 | 2659 | |||
2660 | // File sets the filename that will be used in all following calls to Values. | |||
2661 | // This is useful when testing the permissions of the variable, see | |||
2662 | // VarTypeRegistry. | |||
2635 | func (vt *VartypeCheckTester) File(filename CurrPath) { | 2663 | func (vt *VartypeCheckTester) File(filename CurrPath) { | |
2636 | vt.filename = filename | 2664 | vt.filename = filename | |
2637 | vt.lineno = 1 | 2665 | vt.lineno = 1 | |
2638 | } | 2666 | } | |
2639 | 2667 | |||
2640 | // Op sets the operator for the following tests. | 2668 | // Op sets the operator for the following tests. | |
2641 | // The line number is advanced to the next number ending in 1, e.g. 11, 21, 31. | 2669 | // The line number is advanced to the next number ending in 1, e.g. 11, 21, 31. | |
2642 | func (vt *VartypeCheckTester) Op(op MkOperator) { | 2670 | func (vt *VartypeCheckTester) Op(op MkOperator) { | |
2643 | vt.op = op | 2671 | vt.op = op | |
2644 | vt.nextSection() | 2672 | vt.nextSection() | |
2645 | } | 2673 | } | |
2646 | 2674 | |||
2647 | // Values feeds each of the values to the actual check. | 2675 | // Values feeds each of the values to the actual check. |