pkgtools/pkglint: update to 5.7.12 Changes since 5.7.11: * Fixed an alignment bug when pkglint replaced SUBST_SED with SUBST_VARS. * Added many test cases.diff -r1.22 -r1.23 pkgsrc/pkgtools/pkglint/files/autofix.go
(rillig)
@@ -90,26 +90,35 @@ func (fix *Autofix) Replace(from string, | @@ -90,26 +90,35 @@ func (fix *Autofix) Replace(from string, | |||
90 | // ReplaceAfter replaces the text "prefix+from" with "prefix+to", a single time. | 90 | // ReplaceAfter replaces the text "prefix+from" with "prefix+to", a single time. | |
91 | // In the diagnostic, only the replacement of "from" with "to" is mentioned. | 91 | // In the diagnostic, only the replacement of "from" with "to" is mentioned. | |
92 | func (fix *Autofix) ReplaceAfter(prefix, from string, to string) { | 92 | func (fix *Autofix) ReplaceAfter(prefix, from string, to string) { | |
93 | fix.assertRealLine() | 93 | fix.assertRealLine() | |
94 | if fix.skip() { | 94 | if fix.skip() { | |
95 | return | 95 | return | |
96 | } | 96 | } | |
97 | 97 | |||
98 | for _, rawLine := range fix.line.raw { | 98 | for _, rawLine := range fix.line.raw { | |
99 | replaced := strings.Replace(rawLine.textnl, prefix+from, prefix+to, 1) | 99 | replaced := strings.Replace(rawLine.textnl, prefix+from, prefix+to, 1) | |
100 | if replaced != rawLine.textnl { | 100 | if replaced != rawLine.textnl { | |
101 | if G.Logger.IsAutofix() { | 101 | if G.Logger.IsAutofix() { | |
102 | rawLine.textnl = replaced | 102 | rawLine.textnl = replaced | |
103 | ||||
104 | // Fix the parsed text as well. | |||
105 | // This is only approximate and won't work in some edge cases | |||
106 | // that involve escaped comments or replacements across line breaks. | |||
107 | // | |||
108 | // TODO: Do this properly by parsing the whole line again, | |||
109 | // and ideally everything that depends on the parsed line. | |||
110 | // This probably requires a generic notification mechanism. | |||
111 | fix.line.Text = strings.Replace(fix.line.Text, prefix+from, prefix+to, 1) | |||
103 | } | 112 | } | |
104 | fix.Describef(rawLine.Lineno, "Replacing %q with %q.", from, to) | 113 | fix.Describef(rawLine.Lineno, "Replacing %q with %q.", from, to) | |
105 | return | 114 | return | |
106 | } | 115 | } | |
107 | } | 116 | } | |
108 | } | 117 | } | |
109 | 118 | |||
110 | // ReplaceRegex replaces the first howOften or all occurrences (if negative) | 119 | // ReplaceRegex replaces the first howOften or all occurrences (if negative) | |
111 | // of the `from` pattern with the fixed string `toText`. | 120 | // of the `from` pattern with the fixed string `toText`. | |
112 | // | 121 | // | |
113 | // Placeholders like `$1` are _not_ expanded in the `toText`. | 122 | // Placeholders like `$1` are _not_ expanded in the `toText`. | |
114 | // (If you know how to do the expansion correctly, feel free to implement it.) | 123 | // (If you know how to do the expansion correctly, feel free to implement it.) | |
115 | func (fix *Autofix) ReplaceRegex(from regex.Pattern, toText string, howOften int) { | 124 | func (fix *Autofix) ReplaceRegex(from regex.Pattern, toText string, howOften int) { | |
@@ -131,26 +140,45 @@ func (fix *Autofix) ReplaceRegex(from re | @@ -131,26 +140,45 @@ func (fix *Autofix) ReplaceRegex(from re | |||
131 | return toText | 140 | return toText | |
132 | } | 141 | } | |
133 | 142 | |||
134 | replaced := replaceAllFunc(rawLine.textnl, from, replace) | 143 | replaced := replaceAllFunc(rawLine.textnl, from, replace) | |
135 | if replaced != rawLine.textnl { | 144 | if replaced != rawLine.textnl { | |
136 | if G.Logger.IsAutofix() { | 145 | if G.Logger.IsAutofix() { | |
137 | rawLine.textnl = replaced | 146 | rawLine.textnl = replaced | |
138 | } | 147 | } | |
139 | for _, fromText := range froms { | 148 | for _, fromText := range froms { | |
140 | fix.Describef(rawLine.Lineno, "Replacing %q with %q.", fromText, toText) | 149 | fix.Describef(rawLine.Lineno, "Replacing %q with %q.", fromText, toText) | |
141 | } | 150 | } | |
142 | } | 151 | } | |
143 | } | 152 | } | |
153 | ||||
154 | // Fix the parsed text as well. | |||
155 | // This is only approximate and won't work in some edge cases | |||
156 | // that involve escaped comments or replacements across line breaks. | |||
157 | // | |||
158 | // TODO: Do this properly by parsing the whole line again, | |||
159 | // and ideally everything that depends on the parsed line. | |||
160 | // This probably requires a generic notification mechanism. | |||
161 | done = 0 | |||
162 | fix.line.Text = replaceAllFunc( | |||
163 | fix.line.Text, | |||
164 | from, | |||
165 | func(fromText string) string { | |||
166 | if howOften >= 0 && done >= howOften { | |||
167 | return fromText | |||
168 | } | |||
169 | done++ | |||
170 | return toText | |||
171 | }) | |||
144 | } | 172 | } | |
145 | 173 | |||
146 | // Custom runs a custom fix action, unless the fix is skipped anyway | 174 | // Custom runs a custom fix action, unless the fix is skipped anyway | |
147 | // because of the --only option. | 175 | // because of the --only option. | |
148 | // | 176 | // | |
149 | // The fixer function must check whether it can actually fix something, | 177 | // The fixer function must check whether it can actually fix something, | |
150 | // and if so, call Describef to describe the actual fix. | 178 | // and if so, call Describef to describe the actual fix. | |
151 | // | 179 | // | |
152 | // If showAutofix and autofix are both false, the fix must only be | 180 | // If showAutofix and autofix are both false, the fix must only be | |
153 | // described by calling Describef. No observable modification must be done, | 181 | // described by calling Describef. No observable modification must be done, | |
154 | // not even in memory. | 182 | // not even in memory. | |
155 | // | 183 | // | |
156 | // If showAutofix is true but autofix is false, the fix should be done in | 184 | // If showAutofix is true but autofix is false, the fix should be done in |
@@ -955,26 +955,74 @@ func (s *Suite) Test_Autofix_Apply__sour | @@ -955,26 +955,74 @@ func (s *Suite) Test_Autofix_Apply__sour | |||
955 | 955 | |||
956 | t.CheckOutputLines( | 956 | t.CheckOutputLines( | |
957 | "NOTE: filename:5: This line is quite short.", | 957 | "NOTE: filename:5: This line is quite short.", | |
958 | "AUTOFIX: filename:5: Replacing \"text\" with \"replacement\".", | 958 | "AUTOFIX: filename:5: Replacing \"text\" with \"replacement\".", | |
959 | "-\ttext", | 959 | "-\ttext", | |
960 | "+\treplacement", | 960 | "+\treplacement", | |
961 | "", | 961 | "", | |
962 | "WARN: filename:5: Follow-up warning, separated.", | 962 | "WARN: filename:5: Follow-up warning, separated.", | |
963 | "AUTOFIX: filename:5: Replacing \"replacement\" with \"text again\".", | 963 | "AUTOFIX: filename:5: Replacing \"replacement\" with \"text again\".", | |
964 | "-\ttext", | 964 | "-\ttext", | |
965 | "+\ttext again") | 965 | "+\ttext again") | |
966 | } | 966 | } | |
967 | 967 | |||
968 | // After fixing part of a line, the whole line needs to be parsed again. | |||
969 | // | |||
970 | // As of May 2019, this is not done yet. | |||
971 | func (s *Suite) Test_Autofix_Apply__text_after_replacing_string(c *check.C) { | |||
972 | t := s.Init(c) | |||
973 | ||||
974 | t.SetUpCommandLine("-Wall", "--autofix") | |||
975 | mkline := t.NewMkLine("filename.mk", 123, "VAR=\tvalue") | |||
976 | ||||
977 | fix := mkline.Autofix() | |||
978 | fix.Notef("Just a demo.") | |||
979 | fix.Replace("value", "new value") | |||
980 | fix.Apply() | |||
981 | ||||
982 | t.CheckOutputLines( | |||
983 | "AUTOFIX: filename.mk:123: Replacing \"value\" with \"new value\".") | |||
984 | ||||
985 | t.Check(mkline.raw[0].textnl, equals, "VAR=\tnew value\n") | |||
986 | t.Check(mkline.raw[0].orignl, equals, "VAR=\tvalue\n") | |||
987 | t.Check(mkline.Text, equals, "VAR=\tnew value") | |||
988 | // FIXME: should be updated as well. | |||
989 | t.Check(mkline.Value(), equals, "value") | |||
990 | } | |||
991 | ||||
992 | // After fixing part of a line, the whole line needs to be parsed again. | |||
993 | // | |||
994 | // As of May 2019, this is not done yet. | |||
995 | func (s *Suite) Test_Autofix_Apply__text_after_replacing_regex(c *check.C) { | |||
996 | t := s.Init(c) | |||
997 | ||||
998 | t.SetUpCommandLine("-Wall", "--autofix") | |||
999 | mkline := t.NewMkLine("filename.mk", 123, "VAR=\tvalue") | |||
1000 | ||||
1001 | fix := mkline.Autofix() | |||
1002 | fix.Notef("Just a demo.") | |||
1003 | fix.ReplaceRegex(`va...`, "new value", -1) | |||
1004 | fix.Apply() | |||
1005 | ||||
1006 | t.CheckOutputLines( | |||
1007 | "AUTOFIX: filename.mk:123: Replacing \"value\" with \"new value\".") | |||
1008 | ||||
1009 | t.Check(mkline.raw[0].textnl, equals, "VAR=\tnew value\n") | |||
1010 | t.Check(mkline.raw[0].orignl, equals, "VAR=\tvalue\n") | |||
1011 | t.Check(mkline.Text, equals, "VAR=\tnew value") | |||
1012 | // FIXME: should be updated as well. | |||
1013 | t.Check(mkline.Value(), equals, "value") | |||
1014 | } | |||
1015 | ||||
968 | func (s *Suite) Test_Autofix_Realign__wrong_line_type(c *check.C) { | 1016 | func (s *Suite) Test_Autofix_Realign__wrong_line_type(c *check.C) { | |
969 | t := s.Init(c) | 1017 | t := s.Init(c) | |
970 | 1018 | |||
971 | mklines := t.NewMkLines("file.mk", | 1019 | mklines := t.NewMkLines("file.mk", | |
972 | MkRcsID, | 1020 | MkRcsID, | |
973 | ".if \\", | 1021 | ".if \\", | |
974 | "${PKGSRC_RUN_TESTS}") | 1022 | "${PKGSRC_RUN_TESTS}") | |
975 | 1023 | |||
976 | mkline := mklines.mklines[1] | 1024 | mkline := mklines.mklines[1] | |
977 | fix := mkline.Autofix() | 1025 | fix := mkline.Autofix() | |
978 | 1026 | |||
979 | t.ExpectPanic( | 1027 | t.ExpectPanic( | |
980 | func() { fix.Realign(mkline, 16) }, | 1028 | func() { fix.Realign(mkline, 16) }, |
@@ -1,19 +1,16 @@ | @@ -1,19 +1,16 @@ | |||
1 | package pkglint | 1 | package pkglint | |
2 | 2 | |||
3 | import ( | 3 | import "netbsd.org/pkglint/textproc" | |
4 | "netbsd.org/pkglint/textproc" | |||
5 | "strings" | |||
6 | ) | |||
7 | 4 | |||
8 | // SubstContext records the state of a block of variable assignments | 5 | // SubstContext records the state of a block of variable assignments | |
9 | // that make up a SUBST class (see `mk/subst.mk`). | 6 | // that make up a SUBST class (see `mk/subst.mk`). | |
10 | type SubstContext struct { | 7 | type SubstContext struct { | |
11 | id string | 8 | id string | |
12 | stage string | 9 | stage string | |
13 | message string | 10 | message string | |
14 | curr *SubstContextStats | 11 | curr *SubstContextStats | |
15 | inAllBranches SubstContextStats | 12 | inAllBranches SubstContextStats | |
16 | filterCmd string | 13 | filterCmd string | |
17 | vars map[string]bool | 14 | vars map[string]bool | |
18 | } | 15 | } | |
19 | 16 | |||
@@ -37,26 +34,37 @@ func (st *SubstContextStats) And(other * | @@ -37,26 +34,37 @@ func (st *SubstContextStats) And(other * | |||
37 | st.seenFiles = st.seenFiles && other.seenFiles | 34 | st.seenFiles = st.seenFiles && other.seenFiles | |
38 | st.seenSed = st.seenSed && other.seenSed | 35 | st.seenSed = st.seenSed && other.seenSed | |
39 | st.seenVars = st.seenVars && other.seenVars | 36 | st.seenVars = st.seenVars && other.seenVars | |
40 | st.seenTransform = st.seenTransform && other.seenTransform | 37 | st.seenTransform = st.seenTransform && other.seenTransform | |
41 | } | 38 | } | |
42 | 39 | |||
43 | func (st *SubstContextStats) Or(other SubstContextStats) { | 40 | func (st *SubstContextStats) Or(other SubstContextStats) { | |
44 | st.seenFiles = st.seenFiles || other.seenFiles | 41 | st.seenFiles = st.seenFiles || other.seenFiles | |
45 | st.seenSed = st.seenSed || other.seenSed | 42 | st.seenSed = st.seenSed || other.seenSed | |
46 | st.seenVars = st.seenVars || other.seenVars | 43 | st.seenVars = st.seenVars || other.seenVars | |
47 | st.seenTransform = st.seenTransform || other.seenTransform | 44 | st.seenTransform = st.seenTransform || other.seenTransform | |
48 | } | 45 | } | |
49 | 46 | |||
47 | func (ctx *SubstContext) Process(mkline MkLine) { | |||
48 | switch { | |||
49 | case mkline.IsEmpty(): | |||
50 | ctx.Finish(mkline) | |||
51 | case mkline.IsVarassign(): | |||
52 | ctx.Varassign(mkline) | |||
53 | case mkline.IsDirective(): | |||
54 | ctx.Directive(mkline) | |||
55 | } | |||
56 | } | |||
57 | ||||
50 | func (ctx *SubstContext) Varassign(mkline MkLine) { | 58 | func (ctx *SubstContext) Varassign(mkline MkLine) { | |
51 | if trace.Tracing { | 59 | if trace.Tracing { | |
52 | trace.Stepf("SubstContext.Varassign curr=%v all=%v", ctx.curr, ctx.inAllBranches) | 60 | trace.Stepf("SubstContext.Varassign curr=%v all=%v", ctx.curr, ctx.inAllBranches) | |
53 | } | 61 | } | |
54 | 62 | |||
55 | varname := mkline.Varname() | 63 | varname := mkline.Varname() | |
56 | varcanon := mkline.Varcanon() | 64 | varcanon := mkline.Varcanon() | |
57 | varparam := mkline.Varparam() | 65 | varparam := mkline.Varparam() | |
58 | op := mkline.Op() | 66 | op := mkline.Op() | |
59 | value := mkline.Value() | 67 | value := mkline.Value() | |
60 | if varcanon == "SUBST_CLASSES" || varcanon == "SUBST_CLASSES.*" { | 68 | if varcanon == "SUBST_CLASSES" || varcanon == "SUBST_CLASSES.*" { | |
61 | classes := mkline.ValueFields(value) | 69 | classes := mkline.ValueFields(value) | |
62 | if len(classes) > 1 { | 70 | if len(classes) > 1 { | |
@@ -193,30 +201,27 @@ func (ctx *SubstContext) Directive(mklin | @@ -193,30 +201,27 @@ func (ctx *SubstContext) Directive(mklin | |||
193 | } | 201 | } | |
194 | if dir == "if" || dir == "elif" || dir == "else" { | 202 | if dir == "if" || dir == "elif" || dir == "else" { | |
195 | ctx.curr = ctx.curr.Copy() | 203 | ctx.curr = ctx.curr.Copy() | |
196 | } | 204 | } | |
197 | if dir == "endif" { | 205 | if dir == "endif" { | |
198 | ctx.curr.Or(ctx.inAllBranches) | 206 | ctx.curr.Or(ctx.inAllBranches) | |
199 | } | 207 | } | |
200 | if trace.Tracing { | 208 | if trace.Tracing { | |
201 | trace.Stepf("- SubstContext.Directive %v %v", ctx.curr, ctx.inAllBranches) | 209 | trace.Stepf("- SubstContext.Directive %v %v", ctx.curr, ctx.inAllBranches) | |
202 | } | 210 | } | |
203 | } | 211 | } | |
204 | 212 | |||
205 | func (ctx *SubstContext) IsComplete() bool { | 213 | func (ctx *SubstContext) IsComplete() bool { | |
206 | return ctx.id != "" && | 214 | return ctx.stage != "" && ctx.curr.seenFiles && ctx.curr.seenTransform | |
207 | ctx.stage != "" && | |||
208 | ctx.curr.seenFiles && | |||
209 | ctx.curr.seenTransform | |||
210 | } | 215 | } | |
211 | 216 | |||
212 | func (ctx *SubstContext) Finish(mkline MkLine) { | 217 | func (ctx *SubstContext) Finish(mkline MkLine) { | |
213 | if ctx.id == "" { | 218 | if ctx.id == "" { | |
214 | return | 219 | return | |
215 | } | 220 | } | |
216 | 221 | |||
217 | id := ctx.id | 222 | id := ctx.id | |
218 | if ctx.stage == "" { | 223 | if ctx.stage == "" { | |
219 | mkline.Warnf("Incomplete SUBST block: SUBST_STAGE.%s missing.", id) | 224 | mkline.Warnf("Incomplete SUBST block: SUBST_STAGE.%s missing.", id) | |
220 | } | 225 | } | |
221 | if !ctx.curr.seenFiles { | 226 | if !ctx.curr.seenFiles { | |
222 | mkline.Warnf("Incomplete SUBST block: SUBST_FILES.%s missing.", id) | 227 | mkline.Warnf("Incomplete SUBST block: SUBST_FILES.%s missing.", id) | |
@@ -285,25 +290,21 @@ func (ctx *SubstContext) suggestSubstVar | @@ -285,25 +290,21 @@ func (ctx *SubstContext) suggestSubstVar | |||
285 | 290 | |||
286 | varop := sprintf("SUBST_VARS.%s%s%s", | 291 | varop := sprintf("SUBST_VARS.%s%s%s", | |
287 | ctx.id, | 292 | ctx.id, | |
288 | ifelseStr(hasSuffix(ctx.id, "+"), " ", ""), | 293 | ifelseStr(hasSuffix(ctx.id, "+"), " ", ""), | |
289 | ifelseStr(ctx.curr.seenVars, "+=", "=")) | 294 | ifelseStr(ctx.curr.seenVars, "+=", "=")) | |
290 | 295 | |||
291 | fix := mkline.Autofix() | 296 | fix := mkline.Autofix() | |
292 | fix.Notef("The substitution command %q can be replaced with \"%s %s\".", | 297 | fix.Notef("The substitution command %q can be replaced with \"%s %s\".", | |
293 | token, varop, varname) | 298 | token, varop, varname) | |
294 | fix.Explain( | 299 | fix.Explain( | |
295 | "Replacing @VAR@ with ${VAR} is such a typical pattern that pkgsrc has built-in support for it,", | 300 | "Replacing @VAR@ with ${VAR} is such a typical pattern that pkgsrc has built-in support for it,", | |
296 | "requiring only the variable name instead of the full sed command.") | 301 | "requiring only the variable name instead of the full sed command.") | |
297 | if mkline.VarassignComment() == "" && len(tokens) == 2 && tokens[0] == "-e" { | 302 | if mkline.VarassignComment() == "" && len(tokens) == 2 && tokens[0] == "-e" { | |
298 | // TODO: Extract the alignment computation somewhere else, so that it is generally available. | 303 | fix.Replace(mkline.Text, alignWith(varop, mkline.ValueAlign())+varname) | |
299 | alignBefore := tabWidth(mkline.ValueAlign()) | |||
300 | alignAfter := tabWidth(varop + "\t") | |||
301 | tabs := strings.Repeat("\t", imax((alignAfter-alignBefore)/8, 0)) | |||
302 | fix.Replace(mkline.Text, varop+"\t"+tabs+varname) | |||
303 | } | 304 | } | |
304 | fix.Anyway() | 305 | fix.Anyway() | |
305 | fix.Apply() | 306 | fix.Apply() | |
306 | 307 | |||
307 | ctx.curr.seenVars = true | 308 | ctx.curr.seenVars = true | |
308 | } | 309 | } | |
309 | } | 310 | } |
@@ -554,31 +554,26 @@ func (s *Suite) Test_MkLine_VariableNeed | @@ -554,31 +554,26 @@ func (s *Suite) Test_MkLine_VariableNeed | |||
554 | mklines := t.NewMkLines("x11/mlterm/Makefile", | 554 | mklines := t.NewMkLines("x11/mlterm/Makefile", | |
555 | MkRcsID, | 555 | MkRcsID, | |
556 | "SUBST_SED.link=-e 's|(LIBTOOL_LINK).*(LIBS)|& ${LDFLAGS:M*:Q}|g'", | 556 | "SUBST_SED.link=-e 's|(LIBTOOL_LINK).*(LIBS)|& ${LDFLAGS:M*:Q}|g'", | |
557 | "SUBST_SED.link=-e 's|(LIBTOOL_LINK).*(LIBS)|& '${LDFLAGS:M*:Q}'|g'") | 557 | "SUBST_SED.link=-e 's|(LIBTOOL_LINK).*(LIBS)|& '${LDFLAGS:M*:Q}'|g'") | |
558 | 558 | |||
559 | MkLineChecker{mklines, mklines.mklines[1]}.Check() | 559 | MkLineChecker{mklines, mklines.mklines[1]}.Check() | |
560 | MkLineChecker{mklines, mklines.mklines[2]}.Check() | 560 | MkLineChecker{mklines, mklines.mklines[2]}.Check() | |
561 | 561 | |||
562 | t.CheckOutputLines( | 562 | t.CheckOutputLines( | |
563 | "WARN: x11/mlterm/Makefile:2: Please move ${LDFLAGS:M*:Q} outside of any quoting characters.") | 563 | "WARN: x11/mlterm/Makefile:2: Please move ${LDFLAGS:M*:Q} outside of any quoting characters.") | |
564 | } | 564 | } | |
565 | 565 | |||
566 | // No quoting is necessary when lists of options are appended to each other. | 566 | // No quoting is necessary when lists of options are appended to each other. | |
567 | // PKG_OPTIONS are declared as "lkShell" although they are processed | |||
568 | // using make's .for loop, which splits them at whitespace and usually | |||
569 | // requires the variable to be declared as "lkSpace". | |||
570 | // In this case it doesn't matter though since each option is an identifier, | |||
571 | // and these do not pose any quoting or escaping problems. | |||
572 | func (s *Suite) Test_MkLine_VariableNeedsQuoting__package_options(c *check.C) { | 567 | func (s *Suite) Test_MkLine_VariableNeedsQuoting__package_options(c *check.C) { | |
573 | t := s.Init(c) | 568 | t := s.Init(c) | |
574 | 569 | |||
575 | t.SetUpVartypes() | 570 | t.SetUpVartypes() | |
576 | mklines := t.NewMkLines("Makefile", | 571 | mklines := t.NewMkLines("Makefile", | |
577 | MkRcsID, | 572 | MkRcsID, | |
578 | "PKG_SUGGESTED_OPTIONS+=\t${PKG_DEFAULT_OPTIONS:Mcdecimal} ${PKG_OPTIONS.py-trytond:Mcdecimal}") | 573 | "PKG_SUGGESTED_OPTIONS+=\t${PKG_DEFAULT_OPTIONS:Mcdecimal} ${PKG_OPTIONS.py-trytond:Mcdecimal}") | |
579 | 574 | |||
580 | MkLineChecker{mklines, mklines.mklines[1]}.Check() | 575 | MkLineChecker{mklines, mklines.mklines[1]}.Check() | |
581 | 576 | |||
582 | // No warning about a missing :Q modifier. | 577 | // No warning about a missing :Q modifier. | |
583 | t.CheckOutputEmpty() | 578 | t.CheckOutputEmpty() | |
584 | } | 579 | } | |
@@ -1149,26 +1144,43 @@ func (s *Suite) Test_MkLine_ValueTokens( | @@ -1149,26 +1144,43 @@ func (s *Suite) Test_MkLine_ValueTokens( | |||
1149 | 1144 | |||
1150 | test("value # comment", | 1145 | test("value # comment", | |
1151 | tokens( | 1146 | tokens( | |
1152 | text("value"))) | 1147 | text("value"))) | |
1153 | 1148 | |||
1154 | test("value ${UNFINISHED", | 1149 | test("value ${UNFINISHED", | |
1155 | tokens( | 1150 | tokens( | |
1156 | text("value "), | 1151 | text("value "), | |
1157 | varUseText("${UNFINISHED", "UNFINISHED")), | 1152 | varUseText("${UNFINISHED", "UNFINISHED")), | |
1158 | 1153 | |||
1159 | "WARN: Makefile:1: Missing closing \"}\" for \"UNFINISHED\".") | 1154 | "WARN: Makefile:1: Missing closing \"}\" for \"UNFINISHED\".") | |
1160 | } | 1155 | } | |
1161 | 1156 | |||
1157 | func (s *Suite) Test_MkLine_ValueTokens__parse_error(c *check.C) { | |||
1158 | t := s.Init(c) | |||
1159 | ||||
1160 | mkline := t.NewMkLine("filename.mk", 123, "VAR=\t$") | |||
1161 | ||||
1162 | tokens, rest := mkline.ValueTokens() | |||
1163 | ||||
1164 | t.Check(tokens, check.IsNil) | |||
1165 | t.Check(rest, equals, "$") | |||
1166 | ||||
1167 | // Returns the same values, this time from the cache. | |||
1168 | tokens, rest = mkline.ValueTokens() | |||
1169 | ||||
1170 | t.Check(tokens, check.IsNil) | |||
1171 | t.Check(rest, equals, "$") | |||
1172 | } | |||
1173 | ||||
1162 | func (s *Suite) Test_MkLine_ValueTokens__caching(c *check.C) { | 1174 | func (s *Suite) Test_MkLine_ValueTokens__caching(c *check.C) { | |
1163 | t := s.Init(c) | 1175 | t := s.Init(c) | |
1164 | 1176 | |||
1165 | tokens := func(tokens ...*MkToken) []*MkToken { return tokens } | 1177 | tokens := func(tokens ...*MkToken) []*MkToken { return tokens } | |
1166 | 1178 | |||
1167 | mkline := t.NewMkLine("Makefile", 1, "PATH=\tvalue ${UNFINISHED") | 1179 | mkline := t.NewMkLine("Makefile", 1, "PATH=\tvalue ${UNFINISHED") | |
1168 | valueTokens, rest := mkline.ValueTokens() | 1180 | valueTokens, rest := mkline.ValueTokens() | |
1169 | 1181 | |||
1170 | c.Check(valueTokens, deepEquals, | 1182 | c.Check(valueTokens, deepEquals, | |
1171 | tokens( | 1183 | tokens( | |
1172 | &MkToken{"value ", nil}, | 1184 | &MkToken{"value ", nil}, | |
1173 | &MkToken{"${UNFINISHED", NewMkVarUse("UNFINISHED")})) | 1185 | &MkToken{"${UNFINISHED", NewMkVarUse("UNFINISHED")})) | |
1174 | c.Check(rest, equals, "") | 1186 | c.Check(rest, equals, "") |
@@ -214,28 +214,28 @@ func (ck MkLineChecker) checkDirectiveFo | @@ -214,28 +214,28 @@ func (ck MkLineChecker) checkDirectiveFo | |||
214 | } else { | 214 | } else { | |
215 | mkline.Errorf("Invalid variable name %q.", forvar) | 215 | mkline.Errorf("Invalid variable name %q.", forvar) | |
216 | } | 216 | } | |
217 | 217 | |||
218 | forVars[forvar] = true | 218 | forVars[forvar] = true | |
219 | } | 219 | } | |
220 | 220 | |||
221 | // XXX: The type BtUnknown is very unspecific here. For known variables | 221 | // XXX: The type BtUnknown is very unspecific here. For known variables | |
222 | // or constant values this could probably be improved. | 222 | // or constant values this could probably be improved. | |
223 | // | 223 | // | |
224 | // The guessed flag could also be determined more correctly. As of November 2018, | 224 | // The guessed flag could also be determined more correctly. As of November 2018, | |
225 | // running pkglint over the whole pkgsrc tree did not produce any different result | 225 | // running pkglint over the whole pkgsrc tree did not produce any different result | |
226 | // whether guessed was true or false. | 226 | // whether guessed was true or false. | |
227 | forLoopType := Vartype{btForLoop, List, []ACLEntry{{"*", aclpAllRead}}} | 227 | forLoopType := NewVartype(btForLoop, List, NewACLEntry("*", aclpAllRead)) | |
228 | forLoopContext := VarUseContext{&forLoopType, vucTimeParse, VucQuotPlain, false} | 228 | forLoopContext := VarUseContext{forLoopType, vucTimeParse, VucQuotPlain, false} | |
229 | mkline.ForEachUsed(func(varUse *MkVarUse, time vucTime) { | 229 | mkline.ForEachUsed(func(varUse *MkVarUse, time vucTime) { | |
230 | ck.CheckVaruse(varUse, &forLoopContext) | 230 | ck.CheckVaruse(varUse, &forLoopContext) | |
231 | }) | 231 | }) | |
232 | } | 232 | } | |
233 | } | 233 | } | |
234 | 234 | |||
235 | func (ck MkLineChecker) checkDirectiveIndentation(expectedDepth int) { | 235 | func (ck MkLineChecker) checkDirectiveIndentation(expectedDepth int) { | |
236 | if ck.MkLines == nil || !G.Opts.WarnSpace { | 236 | if ck.MkLines == nil || !G.Opts.WarnSpace { | |
237 | return | 237 | return | |
238 | } | 238 | } | |
239 | mkline := ck.MkLine | 239 | mkline := ck.MkLine | |
240 | indent := mkline.Indent() | 240 | indent := mkline.Indent() | |
241 | if expected := strings.Repeat(" ", expectedDepth); indent != expected { | 241 | if expected := strings.Repeat(" ", expectedDepth); indent != expected { | |
@@ -992,27 +992,27 @@ func (ck MkLineChecker) checkVarassignLe | @@ -992,27 +992,27 @@ func (ck MkLineChecker) checkVarassignLe | |||
992 | ck.MkLine.Warnf("Variable names starting with an underscore (%s) are reserved for internal pkgsrc use.", varname) | 992 | ck.MkLine.Warnf("Variable names starting with an underscore (%s) are reserved for internal pkgsrc use.", varname) | |
993 | } | 993 | } | |
994 | 994 | |||
995 | ck.checkVarassignLeftNotUsed() | 995 | ck.checkVarassignLeftNotUsed() | |
996 | ck.checkVarassignLeftDeprecated() | 996 | ck.checkVarassignLeftDeprecated() | |
997 | ck.checkVarassignLeftBsdPrefs() | 997 | ck.checkVarassignLeftBsdPrefs() | |
998 | if !ck.checkVarassignLeftUserSettable() { | 998 | if !ck.checkVarassignLeftUserSettable() { | |
999 | ck.checkVarassignLeftPermissions() | 999 | ck.checkVarassignLeftPermissions() | |
1000 | } | 1000 | } | |
1001 | ck.checkVarassignLeftRationale() | 1001 | ck.checkVarassignLeftRationale() | |
1002 | 1002 | |||
1003 | ck.checkTextVarUse( | 1003 | ck.checkTextVarUse( | |
1004 | ck.MkLine.Varname(), | 1004 | ck.MkLine.Varname(), | |
1005 | &Vartype{BtVariableName, NoVartypeOptions, []ACLEntry{{"*", aclpAll}}}, | 1005 | NewVartype(BtVariableName, NoVartypeOptions, NewACLEntry("*", aclpAll)), | |
1006 | vucTimeParse) | 1006 | vucTimeParse) | |
1007 | } | 1007 | } | |
1008 | 1008 | |||
1009 | func (ck MkLineChecker) checkVarassignOp() { | 1009 | func (ck MkLineChecker) checkVarassignOp() { | |
1010 | ck.checkVarassignOpShell() | 1010 | ck.checkVarassignOpShell() | |
1011 | } | 1011 | } | |
1012 | 1012 | |||
1013 | func (ck MkLineChecker) checkVarassignOpShell() { | 1013 | func (ck MkLineChecker) checkVarassignOpShell() { | |
1014 | mkline := ck.MkLine | 1014 | mkline := ck.MkLine | |
1015 | 1015 | |||
1016 | switch { | 1016 | switch { | |
1017 | case mkline.Op() != opAssignShell: | 1017 | case mkline.Op() != opAssignShell: | |
1018 | return | 1018 | return |
@@ -1208,27 +1208,27 @@ func (s *Suite) Test_MkLineChecker_check | @@ -1208,27 +1208,27 @@ func (s *Suite) Test_MkLineChecker_check | |||
1208 | 1208 | |||
1209 | // Since the variable is usable at load time, pkglint assumes it is also | 1209 | // Since the variable is usable at load time, pkglint assumes it is also | |
1210 | // usable at run time. This is not the case for VAR, but probably doesn't | 1210 | // usable at run time. This is not the case for VAR, but probably doesn't | |
1211 | // happen in practice anyway. | 1211 | // happen in practice anyway. | |
1212 | t.CheckOutputEmpty() | 1212 | t.CheckOutputEmpty() | |
1213 | } | 1213 | } | |
1214 | 1214 | |||
1215 | func (s *Suite) Test_MkLineChecker_checkVarusePermissions__assigned_to_infrastructure_variable(c *check.C) { | 1215 | func (s *Suite) Test_MkLineChecker_checkVarusePermissions__assigned_to_infrastructure_variable(c *check.C) { | |
1216 | t := s.Init(c) | 1216 | t := s.Init(c) | |
1217 | 1217 | |||
1218 | // This combination of BtUnknown and all permissions is typical for | 1218 | // This combination of BtUnknown and all permissions is typical for | |
1219 | // otherwise unknown variables from the pkgsrc infrastructure. | 1219 | // otherwise unknown variables from the pkgsrc infrastructure. | |
1220 | G.Pkgsrc.vartypes.Define("INFRA", BtUnknown, NoVartypeOptions, | 1220 | G.Pkgsrc.vartypes.Define("INFRA", BtUnknown, NoVartypeOptions, | |
1221 | ACLEntry{"*", aclpAll}) | 1221 | NewACLEntry("*", aclpAll)) | |
1222 | G.Pkgsrc.vartypes.DefineParse("VAR", BtUnknown, NoVartypeOptions, | 1222 | G.Pkgsrc.vartypes.DefineParse("VAR", BtUnknown, NoVartypeOptions, | |
1223 | "buildlink3.mk: none", | 1223 | "buildlink3.mk: none", | |
1224 | "*: use") | 1224 | "*: use") | |
1225 | mklines := t.NewMkLines("buildlink3.mk", | 1225 | mklines := t.NewMkLines("buildlink3.mk", | |
1226 | MkRcsID, | 1226 | MkRcsID, | |
1227 | "INFRA=\t${VAR}") | 1227 | "INFRA=\t${VAR}") | |
1228 | 1228 | |||
1229 | mklines.Check() | 1229 | mklines.Check() | |
1230 | 1230 | |||
1231 | // Since INFRA is defined in the infrastructure and pkglint | 1231 | // Since INFRA is defined in the infrastructure and pkglint | |
1232 | // knows nothing else about this variable, it assumes that INFRA | 1232 | // knows nothing else about this variable, it assumes that INFRA | |
1233 | // may be used at load time. This is done to prevent wrong warnings. | 1233 | // may be used at load time. This is done to prevent wrong warnings. | |
1234 | // | 1234 | // |
@@ -120,47 +120,44 @@ func (mklines *MkLinesImpl) checkAll() { | @@ -120,47 +120,44 @@ func (mklines *MkLinesImpl) checkAll() { | |||
120 | isHacksMk := mklines.lines.BaseName == "hacks.mk" | 120 | isHacksMk := mklines.lines.BaseName == "hacks.mk" | |
121 | 121 | |||
122 | lineAction := func(mkline MkLine) bool { | 122 | lineAction := func(mkline MkLine) bool { | |
123 | if isHacksMk { | 123 | if isHacksMk { | |
124 | // Needs to be set here because it is reset in MkLines.ForEach. | 124 | // Needs to be set here because it is reset in MkLines.ForEach. | |
125 | mklines.Tools.SeenPrefs = true | 125 | mklines.Tools.SeenPrefs = true | |
126 | } | 126 | } | |
127 | 127 | |||
128 | ck := MkLineChecker{mklines, mkline} | 128 | ck := MkLineChecker{mklines, mkline} | |
129 | ck.Check() | 129 | ck.Check() | |
130 | 130 | |||
131 | varalign.Process(mkline) | 131 | varalign.Process(mkline) | |
132 | mklines.Tools.ParseToolLine(mklines, mkline, false, false) | 132 | mklines.Tools.ParseToolLine(mklines, mkline, false, false) | |
133 | substContext.Process(mkline) | |||
133 | 134 | |||
134 | switch { | 135 | switch { | |
135 | case mkline.IsEmpty(): | |||
136 | substContext.Finish(mkline) | |||
137 | 136 | |||
138 | case mkline.IsVarassign(): | 137 | case mkline.IsVarassign(): | |
139 | mklines.target = "" | 138 | mklines.target = "" | |
140 | mkline.Tokenize(mkline.Value(), true) // Just for the side-effect of the warnings. | 139 | mkline.Tokenize(mkline.Value(), true) // Just for the side-effect of the warnings. | |
141 | substContext.Varassign(mkline) | |||
142 | 140 | |||
143 | mklines.checkVarassignPlist(mkline) | 141 | mklines.checkVarassignPlist(mkline) | |
144 | 142 | |||
145 | case mkline.IsInclude(): | 143 | case mkline.IsInclude(): | |
146 | mklines.target = "" | 144 | mklines.target = "" | |
147 | if G.Pkg != nil { | 145 | if G.Pkg != nil { | |
148 | G.Pkg.checkIncludeConditionally(mkline, mklines.indentation) | 146 | G.Pkg.checkIncludeConditionally(mkline, mklines.indentation) | |
149 | } | 147 | } | |
150 | 148 | |||
151 | case mkline.IsDirective(): | 149 | case mkline.IsDirective(): | |
152 | ck.checkDirective(mklines.forVars, mklines.indentation) | 150 | ck.checkDirective(mklines.forVars, mklines.indentation) | |
153 | substContext.Directive(mkline) | |||
154 | 151 | |||
155 | case mkline.IsDependency(): | 152 | case mkline.IsDependency(): | |
156 | ck.checkDependencyRule(allowedTargets) | 153 | ck.checkDependencyRule(allowedTargets) | |
157 | mklines.target = mkline.Targets() | 154 | mklines.target = mkline.Targets() | |
158 | 155 | |||
159 | case mkline.IsShellCommand(): | 156 | case mkline.IsShellCommand(): | |
160 | mkline.Tokenize(mkline.ShellCommand(), true) // Just for the side-effect of the warnings. | 157 | mkline.Tokenize(mkline.ShellCommand(), true) // Just for the side-effect of the warnings. | |
161 | } | 158 | } | |
162 | 159 | |||
163 | return true | 160 | return true | |
164 | } | 161 | } | |
165 | 162 | |||
166 | atEnd := func(mkline MkLine) { | 163 | atEnd := func(mkline MkLine) { |
@@ -524,33 +524,38 @@ func (s *Suite) Test_VartypeCheck_Filena | @@ -524,33 +524,38 @@ func (s *Suite) Test_VartypeCheck_Filena | |||
524 | 524 | |||
525 | // There's no guarantee that a filename only contains [A-Za-z0-9.]. | 525 | // There's no guarantee that a filename only contains [A-Za-z0-9.]. | |
526 | // Therefore there are no useful checks in this situation. | 526 | // Therefore there are no useful checks in this situation. | |
527 | vt.Output( | 527 | vt.Output( | |
528 | "WARN: filename.mk:11: The filename pattern \"Filename with spaces.docx\" " + | 528 | "WARN: filename.mk:11: The filename pattern \"Filename with spaces.docx\" " + | |
529 | "contains the invalid characters \" \".") | 529 | "contains the invalid characters \" \".") | |
530 | } | 530 | } | |
531 | 531 | |||
532 | func (s *Suite) Test_VartypeCheck_FileMask(c *check.C) { | 532 | func (s *Suite) Test_VartypeCheck_FileMask(c *check.C) { | |
533 | vt := NewVartypeCheckTester(s.Init(c), (*VartypeCheck).FileMask) | 533 | vt := NewVartypeCheckTester(s.Init(c), (*VartypeCheck).FileMask) | |
534 | 534 | |||
535 | vt.Varname("FNAME") | 535 | vt.Varname("FNAME") | |
536 | vt.Values( | 536 | vt.Values( | |
537 | "filename.txt", | |||
538 | "*.txt", | |||
539 | "[12345].txt", | |||
540 | "[0-9].txt", | |||
541 | "???.txt", | |||
537 | "FileMask with spaces.docx", | 542 | "FileMask with spaces.docx", | |
538 | "OS/2-manual.txt") | 543 | "OS/2-manual.txt") | |
539 | 544 | |||
540 | vt.Output( | 545 | vt.Output( | |
541 | "WARN: filename.mk:1: The filename pattern \"FileMask with spaces.docx\" "+ | 546 | "WARN: filename.mk:6: The filename pattern \"FileMask with spaces.docx\" "+ | |
542 | "contains the invalid characters \" \".", | 547 | "contains the invalid characters \" \".", | |
543 | "WARN: filename.mk:2: The filename pattern \"OS/2-manual.txt\" "+ | 548 | "WARN: filename.mk:7: The filename pattern \"OS/2-manual.txt\" "+ | |
544 | "contains the invalid character \"/\".") | 549 | "contains the invalid character \"/\".") | |
545 | 550 | |||
546 | vt.Op(opUseMatch) | 551 | vt.Op(opUseMatch) | |
547 | vt.Values( | 552 | vt.Values( | |
548 | "FileMask with spaces.docx") | 553 | "FileMask with spaces.docx") | |
549 | 554 | |||
550 | // There's no guarantee that a filename only contains [A-Za-z0-9.]. | 555 | // There's no guarantee that a filename only contains [A-Za-z0-9.]. | |
551 | // Therefore it might be necessary to allow all characters here. | 556 | // Therefore it might be necessary to allow all characters here. | |
552 | // TODO: Investigate whether this restriction is useful in practice. | 557 | // TODO: Investigate whether this restriction is useful in practice. | |
553 | vt.Output( | 558 | vt.Output( | |
554 | "WARN: filename.mk:11: The filename pattern \"FileMask with spaces.docx\" " + | 559 | "WARN: filename.mk:11: The filename pattern \"FileMask with spaces.docx\" " + | |
555 | "contains the invalid characters \" \".") | 560 | "contains the invalid characters \" \".") | |
556 | } | 561 | } |
@@ -337,51 +337,51 @@ func (src *Pkgsrc) loadTools() { | @@ -337,51 +337,51 @@ func (src *Pkgsrc) loadTools() { | |||
337 | tools.Trace() | 337 | tools.Trace() | |
338 | } | 338 | } | |
339 | } | 339 | } | |
340 | 340 | |||
341 | // loadUntypedVars scans all pkgsrc infrastructure files in mk/ | 341 | // loadUntypedVars scans all pkgsrc infrastructure files in mk/ | |
342 | // to find variable definitions that are not yet covered in | 342 | // to find variable definitions that are not yet covered in | |
343 | // Pkgsrc.InitVartypes. | 343 | // Pkgsrc.InitVartypes. | |
344 | // | 344 | // | |
345 | // Even if pkglint cannot guess the type of each variable, | 345 | // Even if pkglint cannot guess the type of each variable, | |
346 | // at least prevent the "used but not defined" warnings. | 346 | // at least prevent the "used but not defined" warnings. | |
347 | func (src *Pkgsrc) loadUntypedVars() { | 347 | func (src *Pkgsrc) loadUntypedVars() { | |
348 | 348 | |||
349 | // Setting guessed to false prevents the vartype.guessed case in MkLineChecker.CheckVaruse. | 349 | // Setting guessed to false prevents the vartype.guessed case in MkLineChecker.CheckVaruse. | |
350 | unknownType := Vartype{BtUnknown, NoVartypeOptions, []ACLEntry{{"*", aclpAll}}} | 350 | unknownType := NewVartype(BtUnknown, NoVartypeOptions, NewACLEntry("*", aclpAll)) | |
351 | 351 | |||
352 | define := func(varcanon string, mkline MkLine) { | 352 | define := func(varcanon string, mkline MkLine) { | |
353 | switch { | 353 | switch { | |
354 | case src.vartypes.DefinedCanon(varcanon): | 354 | case src.vartypes.DefinedCanon(varcanon): | |
355 | // Already defined, can also be a tool. | 355 | // Already defined, can also be a tool. | |
356 | 356 | |||
357 | case hasPrefix(varcanon, "_"): | 357 | case hasPrefix(varcanon, "_"): | |
358 | // Variables starting with an underscore are reserved for the | 358 | // Variables starting with an underscore are reserved for the | |
359 | // infrastructure and are not available for use by packages. | 359 | // infrastructure and are not available for use by packages. | |
360 | 360 | |||
361 | case contains(varcanon, "$"): | 361 | case contains(varcanon, "$"): | |
362 | // Indirect, but not the usual parameterized form. Variables of | 362 | // Indirect, but not the usual parameterized form. Variables of | |
363 | // this form should not be unintentionally visible from outside | 363 | // this form should not be unintentionally visible from outside | |
364 | // the infrastructure since they don't follow the pkgsrc naming | 364 | // the infrastructure since they don't follow the pkgsrc naming | |
365 | // conventions. | 365 | // conventions. | |
366 | 366 | |||
367 | case hasSuffix(varcanon, "_MK"): | 367 | case hasSuffix(varcanon, "_MK"): | |
368 | // Multiple-inclusion guards are internal to the infrastructure. | 368 | // Multiple-inclusion guards are internal to the infrastructure. | |
369 | 369 | |||
370 | default: | 370 | default: | |
371 | if trace.Tracing { | 371 | if trace.Tracing { | |
372 | trace.Stepf("Untyped variable %q in %s", varcanon, mkline) | 372 | trace.Stepf("Untyped variable %q in %s", varcanon, mkline) | |
373 | } | 373 | } | |
374 | src.vartypes.DefineType(varcanon, &unknownType) | 374 | src.vartypes.DefineType(varcanon, unknownType) | |
375 | } | 375 | } | |
376 | } | 376 | } | |
377 | 377 | |||
378 | handleMkFile := func(path string) { | 378 | handleMkFile := func(path string) { | |
379 | mklines := LoadMk(path, 0) | 379 | mklines := LoadMk(path, 0) | |
380 | if mklines != nil && len(mklines.mklines) > 0 { | 380 | if mklines != nil && len(mklines.mklines) > 0 { | |
381 | mklines.collectDefinedVariables() | 381 | mklines.collectDefinedVariables() | |
382 | mklines.collectUsedVariables() | 382 | mklines.collectUsedVariables() | |
383 | for varname, mkline := range mklines.vars.firstDef { | 383 | for varname, mkline := range mklines.vars.firstDef { | |
384 | define(varnameCanon(varname), mkline) | 384 | define(varnameCanon(varname), mkline) | |
385 | } | 385 | } | |
386 | for varname, mkline := range mklines.vars.used { | 386 | for varname, mkline := range mklines.vars.used { | |
387 | define(varnameCanon(varname), mkline) | 387 | define(varnameCanon(varname), mkline) | |
@@ -912,95 +912,104 @@ func (src *Pkgsrc) VariableType(mklines | @@ -912,95 +912,104 @@ func (src *Pkgsrc) VariableType(mklines | |||
912 | vartype = src.vartypes.Canon(varname) | 912 | vartype = src.vartypes.Canon(varname) | |
913 | if vartype != nil && vartype.basicType != BtUnknown { | 913 | if vartype != nil && vartype.basicType != BtUnknown { | |
914 | return vartype | 914 | return vartype | |
915 | } | 915 | } | |
916 | 916 | |||
917 | if tool := G.ToolByVarname(mklines, varname); tool != nil { | 917 | if tool := G.ToolByVarname(mklines, varname); tool != nil { | |
918 | if trace.Tracing { | 918 | if trace.Tracing { | |
919 | trace.Stepf("Use of tool %+v", tool) | 919 | trace.Stepf("Use of tool %+v", tool) | |
920 | } | 920 | } | |
921 | perms := aclpUse | 921 | perms := aclpUse | |
922 | if tool.Validity == AfterPrefsMk && mklines.Tools.SeenPrefs { | 922 | if tool.Validity == AfterPrefsMk && mklines.Tools.SeenPrefs { | |
923 | perms |= aclpUseLoadtime | 923 | perms |= aclpUseLoadtime | |
924 | } | 924 | } | |
925 | return &Vartype{BtShellCommand, NoVartypeOptions, []ACLEntry{{"*", perms}}} | 925 | return NewVartype(BtShellCommand, NoVartypeOptions, NewACLEntry("*", perms)) | |
926 | } | 926 | } | |
927 | 927 | |||
928 | if m, toolVarname := match1(varname, `^TOOLS_(.*)`); m { | 928 | if m, toolVarname := match1(varname, `^TOOLS_(.*)`); m { | |
929 | if tool := G.ToolByVarname(mklines, toolVarname); tool != nil { | 929 | if tool := G.ToolByVarname(mklines, toolVarname); tool != nil { | |
930 | return &Vartype{BtPathname, NoVartypeOptions, []ACLEntry{{"*", aclpUse}}} | 930 | return NewVartype(BtPathname, NoVartypeOptions, NewACLEntry("*", aclpUse)) | |
931 | } | 931 | } | |
932 | } | 932 | } | |
933 | 933 | |||
934 | return src.guessVariableType(varname) | 934 | return src.guessVariableType(varname) | |
935 | } | 935 | } | |
936 | 936 | |||
937 | // guessVariableType guesses the data type of the variable based on naming conventions. | |||
937 | func (src *Pkgsrc) guessVariableType(varname string) (vartype *Vartype) { | 938 | func (src *Pkgsrc) guessVariableType(varname string) (vartype *Vartype) { | |
938 | allowAll := []ACLEntry{{"*", aclpAll}} | 939 | plainType := func(basicType *BasicType, permissions ACLPermissions) *Vartype { | |
939 | allowRuntime := []ACLEntry{{"*", aclpAllRuntime}} | 940 | gtype := NewVartype(basicType, Guessed, NewACLEntry("*", permissions)) | |
941 | trace.Step2("The guessed type of %q is %q.", varname, gtype.String()) | |||
942 | return gtype | |||
943 | } | |||
944 | listType := func(basicType *BasicType, permissions ACLPermissions) *Vartype { | |||
945 | gtype := NewVartype(basicType, List|Guessed, NewACLEntry("*", permissions)) | |||
946 | trace.Step2("The guessed type of %q is %q.", varname, gtype.String()) | |||
947 | return gtype | |||
948 | } | |||
940 | 949 | |||
941 | // Guess the data type of the variable based on naming conventions. | |||
942 | varbase := varnameBase(varname) | 950 | varbase := varnameBase(varname) | |
943 | var gtype *Vartype | |||
944 | switch { | 951 | switch { | |
945 | case hasSuffix(varbase, "DIRS"): | 952 | case hasSuffix(varbase, "DIRS"): | |
946 | gtype = &Vartype{BtPathmask, List | Guessed, allowRuntime} | 953 | return listType(BtPathmask, aclpAllRuntime) | |
947 | case hasSuffix(varbase, "DIR") && !hasSuffix(varbase, "DESTDIR"), hasSuffix(varname, "_HOME"): | 954 | case hasSuffix(varbase, "DIR") && !hasSuffix(varbase, "DESTDIR"), hasSuffix(varname, "_HOME"): | |
948 | // TODO: hasSuffix(varbase, "BASE") | 955 | // TODO: hasSuffix(varbase, "BASE") | |
949 | gtype = &Vartype{BtPathname, Guessed, allowRuntime} | 956 | return plainType(BtPathname, aclpAllRuntime) | |
950 | case hasSuffix(varbase, "FILES"): | 957 | case hasSuffix(varbase, "FILES"): | |
951 | gtype = &Vartype{BtPathmask, List | Guessed, allowRuntime} | 958 | return listType(BtPathmask, aclpAllRuntime) | |
952 | case hasSuffix(varbase, "FILE"): | 959 | case hasSuffix(varbase, "FILE"): | |
953 | gtype = &Vartype{BtPathname, Guessed, allowRuntime} | 960 | return plainType(BtPathname, aclpAllRuntime) | |
954 | case hasSuffix(varbase, "PATH"): | 961 | case hasSuffix(varbase, "PATH"): | |
955 | gtype = &Vartype{BtPathlist, Guessed, allowRuntime} | 962 | return plainType(BtPathlist, aclpAllRuntime) | |
956 | case hasSuffix(varbase, "PATHS"): | 963 | case hasSuffix(varbase, "PATHS"): | |
957 | gtype = &Vartype{BtPathname, List | Guessed, allowRuntime} | 964 | return listType(BtPathname, aclpAllRuntime) | |
958 | case hasSuffix(varbase, "_USER"): | 965 | case hasSuffix(varbase, "_USER"): | |
959 | gtype = &Vartype{BtUserGroupName, Guessed, allowAll} | 966 | return plainType(BtUserGroupName, aclpAll) | |
960 | case hasSuffix(varbase, "_GROUP"): | 967 | case hasSuffix(varbase, "_GROUP"): | |
961 | gtype = &Vartype{BtUserGroupName, Guessed, allowAll} | 968 | return plainType(BtUserGroupName, aclpAll) | |
962 | case hasSuffix(varbase, "_ENV"): | 969 | case hasSuffix(varbase, "_ENV"): | |
963 | gtype = &Vartype{BtShellWord, List | Guessed, allowRuntime} | 970 | return listType(BtShellWord, aclpAllRuntime) | |
964 | case hasSuffix(varbase, "_CMD"): | 971 | case hasSuffix(varbase, "_CMD"): | |
965 | gtype = &Vartype{BtShellCommand, Guessed, allowRuntime} | 972 | return plainType(BtShellCommand, aclpAllRuntime) | |
966 | case hasSuffix(varbase, "_ARGS"): | 973 | case hasSuffix(varbase, "_ARGS"): | |
967 | gtype = &Vartype{BtShellWord, List | Guessed, allowRuntime} | 974 | return listType(BtShellWord, aclpAllRuntime) | |
968 | case hasSuffix(varbase, "_CFLAGS"), hasSuffix(varname, "_CPPFLAGS"), hasSuffix(varname, "_CXXFLAGS"): | 975 | case hasSuffix(varbase, "_CFLAGS"), hasSuffix(varname, "_CPPFLAGS"), hasSuffix(varname, "_CXXFLAGS"): | |
969 | gtype = &Vartype{BtCFlag, List | Guessed, allowRuntime} | 976 | return listType(BtCFlag, aclpAllRuntime) | |
970 | case hasSuffix(varname, "_LDFLAGS"): | 977 | case hasSuffix(varname, "_LDFLAGS"): | |
971 | gtype = &Vartype{BtLdFlag, List | Guessed, allowRuntime} | 978 | return listType(BtLdFlag, aclpAllRuntime) | |
972 | case hasSuffix(varbase, "_MK"): | 979 | case hasSuffix(varbase, "_MK"): | |
973 | // TODO: Add BtGuard for inclusion guards, since these variables may only be checked using defined(). | 980 | // TODO: Add BtGuard for inclusion guards, since these variables may only be checked using defined(). | |
974 | gtype = &Vartype{BtUnknown, Guessed, allowAll} | 981 | return plainType(BtUnknown, aclpAll) | |
975 | case hasSuffix(varbase, "_SKIP"): | 982 | case hasSuffix(varbase, "_SKIP"): | |
976 | gtype = &Vartype{BtPathmask, List | Guessed, allowRuntime} | 983 | return listType(BtPathmask, aclpAllRuntime) | |
977 | } | 984 | } | |
978 | 985 | |||
979 | if gtype == nil { | 986 | // Variables whose name doesn't match the above patterns may be | |
980 | vartype = src.vartypes.Canon(varname) | 987 | // looked up from the pkgsrc infrastructure. | |
981 | if vartype != nil { | 988 | // | |
982 | return vartype | 989 | // As of May 2019, pkglint only distinguishes plain variables and | |
983 | } | 990 | // list variables, but not "unknown". Therefore the above patterns | |
991 | // must take precedence over this rule, because otherwise, list | |||
992 | // variables from the infrastructure would be guessed to be plain | |||
993 | // variables. | |||
994 | vartype = src.vartypes.Canon(varname) | |||
995 | if vartype != nil { | |||
996 | return vartype | |||
984 | } | 997 | } | |
985 | 998 | |||
986 | if trace.Tracing { | 999 | if trace.Tracing { | |
987 | if gtype != nil { | 1000 | trace.Step1("No type definition found for %q.", varname) | |
988 | trace.Step2("The guessed type of %q is %q.", varname, gtype.String()) | |||
989 | } else { | |||
990 | trace.Step1("No type definition found for %q.", varname) | |||
991 | } | |||
992 | } | 1001 | } | |
993 | return gtype | 1002 | return nil | |
994 | } | 1003 | } | |
995 | 1004 | |||
996 | // Change describes a modification to a single package, from the doc/CHANGES-* files. | 1005 | // Change describes a modification to a single package, from the doc/CHANGES-* files. | |
997 | type Change struct { | 1006 | type Change struct { | |
998 | Location Location | 1007 | Location Location | |
999 | Action string | 1008 | Action string | |
1000 | Pkgpath string | 1009 | Pkgpath string | |
1001 | Version string | 1010 | Version string | |
1002 | Author string | 1011 | Author string | |
1003 | Date string | 1012 | Date string | |
1004 | } | 1013 | } | |
1005 | 1014 | |||
1006 | // SuggestedUpdate describes a desired package update, from the doc/TODO file. | 1015 | // SuggestedUpdate describes a desired package update, from the doc/TODO file. |
@@ -19,35 +19,39 @@ type RedundantScope struct { | @@ -19,35 +19,39 @@ type RedundantScope struct { | |||
19 | } | 19 | } | |
20 | type redundantScopeVarinfo struct { | 20 | type redundantScopeVarinfo struct { | |
21 | vari *Var | 21 | vari *Var | |
22 | includePaths []includePath | 22 | includePaths []includePath | |
23 | lastAction uint8 // 0 = none, 1 = read, 2 = write | 23 | lastAction uint8 // 0 = none, 1 = read, 2 = write | |
24 | } | 24 | } | |
25 | 25 | |||
26 | func NewRedundantScope() *RedundantScope { | 26 | func NewRedundantScope() *RedundantScope { | |
27 | return &RedundantScope{vars: make(map[string]*redundantScopeVarinfo)} | 27 | return &RedundantScope{vars: make(map[string]*redundantScopeVarinfo)} | |
28 | } | 28 | } | |
29 | 29 | |||
30 | func (s *RedundantScope) Check(mklines MkLines) { | 30 | func (s *RedundantScope) Check(mklines MkLines) { | |
31 | mklines.ForEach(func(mkline MkLine) { | 31 | mklines.ForEach(func(mkline MkLine) { | |
32 | s.updateIncludePath(mkline) | 32 | s.checkLine(mklines, mkline) | |
33 | }) | |||
34 | } | |||
33 | 35 | |||
34 | switch { | 36 | func (s *RedundantScope) checkLine(mklines MkLines, mkline MkLine) { | |
35 | case mkline.IsVarassign(): | 37 | s.updateIncludePath(mkline) | |
36 | s.handleVarassign(mkline, mklines.indentation) | |||
37 | } | |||
38 | 38 | |||
39 | s.handleVarUse(mkline) | 39 | switch { | |
40 | }) | 40 | case mkline.IsVarassign(): | |
41 | s.handleVarassign(mkline, mklines.indentation) | |||
42 | } | |||
43 | ||||
44 | s.handleVarUse(mkline) | |||
41 | } | 45 | } | |
42 | 46 | |||
43 | func (s *RedundantScope) updateIncludePath(mkline MkLine) { | 47 | func (s *RedundantScope) updateIncludePath(mkline MkLine) { | |
44 | if mkline.firstLine == 1 { | 48 | if mkline.firstLine == 1 { | |
45 | s.includePath.push(mkline.Location.Filename) | 49 | s.includePath.push(mkline.Location.Filename) | |
46 | } else { | 50 | } else { | |
47 | s.includePath.popUntil(mkline.Location.Filename) | 51 | s.includePath.popUntil(mkline.Location.Filename) | |
48 | } | 52 | } | |
49 | } | 53 | } | |
50 | 54 | |||
51 | func (s *RedundantScope) handleVarassign(mkline MkLine, ind *Indentation) { | 55 | func (s *RedundantScope) handleVarassign(mkline MkLine, ind *Indentation) { | |
52 | varname := mkline.Varname() | 56 | varname := mkline.Varname() | |
53 | info := s.get(varname) | 57 | info := s.get(varname) |
@@ -25,27 +25,27 @@ type ShellLineChecker struct { | @@ -25,27 +25,27 @@ type ShellLineChecker struct { | |||
25 | } | 25 | } | |
26 | 26 | |||
27 | func NewShellLineChecker(mklines MkLines, mkline MkLine) *ShellLineChecker { | 27 | func NewShellLineChecker(mklines MkLines, mkline MkLine) *ShellLineChecker { | |
28 | return &ShellLineChecker{mklines, mkline, true} | 28 | return &ShellLineChecker{mklines, mkline, true} | |
29 | } | 29 | } | |
30 | 30 | |||
31 | func (ck *ShellLineChecker) Warnf(format string, args ...interface{}) { | 31 | func (ck *ShellLineChecker) Warnf(format string, args ...interface{}) { | |
32 | ck.mkline.Warnf(format, args...) | 32 | ck.mkline.Warnf(format, args...) | |
33 | } | 33 | } | |
34 | func (ck *ShellLineChecker) Explain(explanation ...string) { | 34 | func (ck *ShellLineChecker) Explain(explanation ...string) { | |
35 | ck.mkline.Explain(explanation...) | 35 | ck.mkline.Explain(explanation...) | |
36 | } | 36 | } | |
37 | 37 | |||
38 | var shellCommandsType = &Vartype{BtShellCommands, NoVartypeOptions, []ACLEntry{{"*", aclpAllRuntime}}} | 38 | var shellCommandsType = NewVartype(BtShellCommands, NoVartypeOptions, NewACLEntry("*", aclpAllRuntime)) | |
39 | var shellWordVuc = &VarUseContext{shellCommandsType, vucTimeUnknown, VucQuotPlain, false} | 39 | var shellWordVuc = &VarUseContext{shellCommandsType, vucTimeUnknown, VucQuotPlain, false} | |
40 | 40 | |||
41 | func (ck *ShellLineChecker) CheckWord(token string, checkQuoting bool, time ToolTime) { | 41 | func (ck *ShellLineChecker) CheckWord(token string, checkQuoting bool, time ToolTime) { | |
42 | if trace.Tracing { | 42 | if trace.Tracing { | |
43 | defer trace.Call(token, checkQuoting)() | 43 | defer trace.Call(token, checkQuoting)() | |
44 | } | 44 | } | |
45 | 45 | |||
46 | if token == "" || hasPrefix(token, "#") { | 46 | if token == "" || hasPrefix(token, "#") { | |
47 | return | 47 | return | |
48 | } | 48 | } | |
49 | 49 | |||
50 | var line = ck.mkline.Line | 50 | var line = ck.mkline.Line | |
51 | 51 | |||
@@ -638,33 +638,33 @@ func (scc *SimpleCommandChecker) handleC | @@ -638,33 +638,33 @@ func (scc *SimpleCommandChecker) handleC | |||
638 | return true | 638 | return true | |
639 | } | 639 | } | |
640 | 640 | |||
641 | func (scc *SimpleCommandChecker) checkRegexReplace() { | 641 | func (scc *SimpleCommandChecker) checkRegexReplace() { | |
642 | if trace.Tracing { | 642 | if trace.Tracing { | |
643 | defer trace.Call0()() | 643 | defer trace.Call0()() | |
644 | } | 644 | } | |
645 | 645 | |||
646 | if !G.Testing { | 646 | if !G.Testing { | |
647 | return | 647 | return | |
648 | } | 648 | } | |
649 | 649 | |||
650 | checkArg := func(arg string) { | 650 | checkArg := func(arg string) { | |
651 | if matches(arg, `"^[\"\'].*[\"\']$`) { | 651 | if matches(arg, `^["'].*["']$`) { | |
652 | return | 652 | return | |
653 | } | 653 | } | |
654 | 654 | |||
655 | // Substitution commands that consist only of safe characters cannot | 655 | // Substitution commands that consist only of safe characters cannot | |
656 | // have any side effects, therefore they don't need to be quoted. | 656 | // have any side effects, therefore they don't need to be quoted. | |
657 | if matches(arg, `^([\w,.]|\\[.])+$`) { | 657 | if matches(arg, `^([\w,.]|\\.)+$`) { | |
658 | return | 658 | return | |
659 | } | 659 | } | |
660 | 660 | |||
661 | scc.Warnf("Substitution commands like %q should always be quoted.", arg) | 661 | scc.Warnf("Substitution commands like %q should always be quoted.", arg) | |
662 | scc.Explain( | 662 | scc.Explain( | |
663 | "Usually these substitution commands contain characters like '*' or", | 663 | "Usually these substitution commands contain characters like '*' or", | |
664 | "other shell metacharacters that might lead to lookup of matching", | 664 | "other shell metacharacters that might lead to lookup of matching", | |
665 | "filenames and then expand to more than one word.") | 665 | "filenames and then expand to more than one word.") | |
666 | } | 666 | } | |
667 | 667 | |||
668 | checkArgAfter := func(opt string) { | 668 | checkArgAfter := func(opt string) { | |
669 | args := scc.strcmd.Args | 669 | args := scc.strcmd.Args | |
670 | for i, arg := range args { | 670 | for i, arg := range args { |
@@ -1204,34 +1204,48 @@ func (s *Suite) Test_SimpleCommandChecke | @@ -1204,34 +1204,48 @@ func (s *Suite) Test_SimpleCommandChecke | |||
1204 | 1204 | |||
1205 | test("${PAX} -s s,.*,, src dst", | 1205 | test("${PAX} -s s,.*,, src dst", | |
1206 | "WARN: Makefile:3: Substitution commands like \"s,.*,,\" should always be quoted.") | 1206 | "WARN: Makefile:3: Substitution commands like \"s,.*,,\" should always be quoted.") | |
1207 | 1207 | |||
1208 | test("pax -s s,.*,, src dst", | 1208 | test("pax -s s,.*,, src dst", | |
1209 | "WARN: Makefile:3: Substitution commands like \"s,.*,,\" should always be quoted.") | 1209 | "WARN: Makefile:3: Substitution commands like \"s,.*,,\" should always be quoted.") | |
1210 | 1210 | |||
1211 | test("${SED} -e s,.*,, src dst", | 1211 | test("${SED} -e s,.*,, src dst", | |
1212 | "WARN: Makefile:3: Substitution commands like \"s,.*,,\" should always be quoted.") | 1212 | "WARN: Makefile:3: Substitution commands like \"s,.*,,\" should always be quoted.") | |
1213 | 1213 | |||
1214 | test("sed -e s,.*,, src dst", | 1214 | test("sed -e s,.*,, src dst", | |
1215 | "WARN: Makefile:3: Substitution commands like \"s,.*,,\" should always be quoted.") | 1215 | "WARN: Makefile:3: Substitution commands like \"s,.*,,\" should always be quoted.") | |
1216 | 1216 | |||
1217 | // The * is properly enclosed in quotes. | |||
1218 | test("sed -e 's,.*,,' -e \"s,-*,,\"", | |||
1219 | nil...) | |||
1220 | ||||
1221 | // The * is properly escaped. | |||
1222 | test("sed -e s,.\\*,,", | |||
1223 | nil...) | |||
1224 | ||||
1217 | test("pax -s s,\\.orig,, src dst", | 1225 | test("pax -s s,\\.orig,, src dst", | |
1218 | nil...) | 1226 | nil...) | |
1219 | 1227 | |||
1220 | test("sed -e s,a,b,g src dst", | 1228 | test("sed -e s,a,b,g src dst", | |
1221 | nil...) | 1229 | nil...) | |
1222 | 1230 | |||
1223 | // TODO: Merge the code with BtSedCommands. | 1231 | // TODO: Merge the code with BtSedCommands. | |
1232 | ||||
1224 | // TODO: Finally, remove the G.Testing from the main code. | 1233 | // TODO: Finally, remove the G.Testing from the main code. | |
1234 | // Then, remove this test case. | |||
1235 | G.Testing = false | |||
1236 | test("sed -e s,.*,match,", | |||
1237 | nil...) | |||
1238 | G.Testing = true | |||
1225 | } | 1239 | } | |
1226 | 1240 | |||
1227 | func (s *Suite) Test_ShellProgramChecker_checkSetE__simple_commands(c *check.C) { | 1241 | func (s *Suite) Test_ShellProgramChecker_checkSetE__simple_commands(c *check.C) { | |
1228 | t := s.Init(c) | 1242 | t := s.Init(c) | |
1229 | 1243 | |||
1230 | t.SetUpTool("echo", "", AtRunTime) | 1244 | t.SetUpTool("echo", "", AtRunTime) | |
1231 | t.SetUpTool("rm", "", AtRunTime) | 1245 | t.SetUpTool("rm", "", AtRunTime) | |
1232 | t.SetUpTool("touch", "", AtRunTime) | 1246 | t.SetUpTool("touch", "", AtRunTime) | |
1233 | mklines := t.NewMkLines("Makefile", | 1247 | mklines := t.NewMkLines("Makefile", | |
1234 | MkRcsID, | 1248 | MkRcsID, | |
1235 | "pre-configure:", | 1249 | "pre-configure:", | |
1236 | "\techo 1; echo 2; echo 3", | 1250 | "\techo 1; echo 2; echo 3", | |
1237 | "\techo 1; touch file; rm file", | 1251 | "\techo 1; touch file; rm file", |
@@ -1,276 +1,401 @@ | @@ -1,276 +1,401 @@ | |||
1 | package pkglint | 1 | package pkglint | |
2 | 2 | |||
3 | import ( | 3 | import ( | |
4 | "fmt" | 4 | "fmt" | |
5 | "gopkg.in/check.v1" | 5 | "gopkg.in/check.v1" | |
6 | ) | 6 | ) | |
7 | 7 | |||
8 | func (s *Suite) Test_SubstContext__incomplete(c *check.C) { | 8 | func (s *Suite) Test_SubstContext__incomplete(c *check.C) { | |
9 | t := s.Init(c) | 9 | t := s.Init(c) | |
10 | 10 | |||
11 | t.SetUpCommandLine("-Wextra") | 11 | t.SetUpCommandLine("-Wextra") | |
12 | ctx := NewSubstContext() | 12 | ctx := NewSubstContext() | |
13 | 13 | |||
14 | ctx.Varassign(newSubstLine(t, 10, "PKGNAME=pkgname-1.0")) | 14 | ctx.Varassign(t.NewMkLine("Makefile", 10, "PKGNAME=pkgname-1.0")) | |
15 | 15 | |||
16 | c.Check(ctx.id, equals, "") | 16 | c.Check(ctx.id, equals, "") | |
17 | 17 | |||
18 | ctx.Varassign(newSubstLine(t, 11, "SUBST_CLASSES+=interp")) | 18 | ctx.Varassign(t.NewMkLine("Makefile", 11, "SUBST_CLASSES+=interp")) | |
19 | 19 | |||
20 | c.Check(ctx.id, equals, "interp") | 20 | c.Check(ctx.id, equals, "interp") | |
21 | 21 | |||
22 | ctx.Varassign(newSubstLine(t, 12, "SUBST_FILES.interp=Makefile")) | 22 | ctx.Varassign(t.NewMkLine("Makefile", 12, "SUBST_FILES.interp=Makefile")) | |
23 | 23 | |||
24 | c.Check(ctx.IsComplete(), equals, false) | 24 | c.Check(ctx.IsComplete(), equals, false) | |
25 | 25 | |||
26 | ctx.Varassign(newSubstLine(t, 13, "SUBST_SED.interp=s,@PREFIX@,${PREFIX},g")) | 26 | ctx.Varassign(t.NewMkLine("Makefile", 13, "SUBST_SED.interp=s,@PREFIX@,${PREFIX},g")) | |
27 | 27 | |||
28 | c.Check(ctx.IsComplete(), equals, false) | 28 | c.Check(ctx.IsComplete(), equals, false) | |
29 | 29 | |||
30 | ctx.Finish(newSubstLine(t, 14, "")) | 30 | ctx.Finish(t.NewMkLine("Makefile", 14, "")) | |
31 | 31 | |||
32 | t.CheckOutputLines( | 32 | t.CheckOutputLines( | |
33 | "NOTE: Makefile:13: The substitution command \"s,@PREFIX@,${PREFIX},g\" "+ | 33 | "NOTE: Makefile:13: The substitution command \"s,@PREFIX@,${PREFIX},g\" "+ | |
34 | "can be replaced with \"SUBST_VARS.interp= PREFIX\".", | 34 | "can be replaced with \"SUBST_VARS.interp= PREFIX\".", | |
35 | "WARN: Makefile:14: Incomplete SUBST block: SUBST_STAGE.interp missing.") | 35 | "WARN: Makefile:14: Incomplete SUBST block: SUBST_STAGE.interp missing.") | |
36 | } | 36 | } | |
37 | 37 | |||
38 | func (s *Suite) Test_SubstContext__complete(c *check.C) { | 38 | func (s *Suite) Test_SubstContext__complete(c *check.C) { | |
39 | t := s.Init(c) | 39 | t := s.Init(c) | |
40 | 40 | |||
41 | t.SetUpCommandLine("-Wextra") | 41 | t.SetUpCommandLine("-Wextra") | |
42 | ctx := NewSubstContext() | 42 | ctx := NewSubstContext() | |
43 | 43 | |||
44 | ctx.Varassign(newSubstLine(t, 10, "PKGNAME=pkgname-1.0")) | 44 | ctx.Varassign(t.NewMkLine("Makefile", 10, "PKGNAME=pkgname-1.0")) | |
45 | ctx.Varassign(newSubstLine(t, 11, "SUBST_CLASSES+=p")) | 45 | ctx.Varassign(t.NewMkLine("Makefile", 11, "SUBST_CLASSES+=p")) | |
46 | ctx.Varassign(newSubstLine(t, 12, "SUBST_FILES.p=Makefile")) | 46 | ctx.Varassign(t.NewMkLine("Makefile", 12, "SUBST_FILES.p=Makefile")) | |
47 | ctx.Varassign(newSubstLine(t, 13, "SUBST_SED.p=s,@PREFIX@,${PREFIX},g")) | 47 | ctx.Varassign(t.NewMkLine("Makefile", 13, "SUBST_SED.p=s,@PREFIX@,${PREFIX},g")) | |
48 | 48 | |||
49 | c.Check(ctx.IsComplete(), equals, false) | 49 | c.Check(ctx.IsComplete(), equals, false) | |
50 | 50 | |||
51 | ctx.Varassign(newSubstLine(t, 14, "SUBST_STAGE.p=post-configure")) | 51 | ctx.Varassign(t.NewMkLine("Makefile", 14, "SUBST_STAGE.p=post-configure")) | |
52 | 52 | |||
53 | c.Check(ctx.IsComplete(), equals, true) | 53 | c.Check(ctx.IsComplete(), equals, true) | |
54 | 54 | |||
55 | ctx.Finish(newSubstLine(t, 15, "")) | 55 | ctx.Finish(t.NewMkLine("Makefile", 15, "")) | |
56 | 56 | |||
57 | t.CheckOutputLines( | 57 | t.CheckOutputLines( | |
58 | "NOTE: Makefile:13: The substitution command \"s,@PREFIX@,${PREFIX},g\" " + | 58 | "NOTE: Makefile:13: The substitution command \"s,@PREFIX@,${PREFIX},g\" " + | |
59 | "can be replaced with \"SUBST_VARS.p= PREFIX\".") | 59 | "can be replaced with \"SUBST_VARS.p= PREFIX\".") | |
60 | } | 60 | } | |
61 | 61 | |||
62 | func (s *Suite) Test_SubstContext__OPSYSVARS(c *check.C) { | 62 | func (s *Suite) Test_SubstContext__OPSYSVARS(c *check.C) { | |
63 | t := s.Init(c) | 63 | t := s.Init(c) | |
64 | 64 | |||
65 | G.Opts.WarnExtra = true | 65 | G.Opts.WarnExtra = true | |
66 | ctx := NewSubstContext() | 66 | ctx := NewSubstContext() | |
67 | 67 | |||
68 | // SUBST_CLASSES is added to OPSYSVARS in mk/bsd.pkg.mk. | 68 | // SUBST_CLASSES is added to OPSYSVARS in mk/bsd.pkg.mk. | |
69 | ctx.Varassign(newSubstLine(t, 11, "SUBST_CLASSES.SunOS+=prefix")) | 69 | ctx.Varassign(t.NewMkLine("Makefile", 11, "SUBST_CLASSES.SunOS+=prefix")) | |
70 | ctx.Varassign(newSubstLine(t, 12, "SUBST_CLASSES.NetBSD+=prefix")) | 70 | ctx.Varassign(t.NewMkLine("Makefile", 12, "SUBST_CLASSES.NetBSD+=prefix")) | |
71 | ctx.Varassign(newSubstLine(t, 13, "SUBST_FILES.prefix=Makefile")) | 71 | ctx.Varassign(t.NewMkLine("Makefile", 13, "SUBST_FILES.prefix=Makefile")) | |
72 | ctx.Varassign(newSubstLine(t, 14, "SUBST_SED.prefix=s,@PREFIX@,${PREFIX},g")) | 72 | ctx.Varassign(t.NewMkLine("Makefile", 14, "SUBST_SED.prefix=s,@PREFIX@,${PREFIX},g")) | |
73 | ctx.Varassign(newSubstLine(t, 15, "SUBST_STAGE.prefix=post-configure")) | 73 | ctx.Varassign(t.NewMkLine("Makefile", 15, "SUBST_STAGE.prefix=post-configure")) | |
74 | 74 | |||
75 | c.Check(ctx.IsComplete(), equals, true) | 75 | c.Check(ctx.IsComplete(), equals, true) | |
76 | 76 | |||
77 | ctx.Finish(newSubstLine(t, 15, "")) | 77 | ctx.Finish(t.NewMkLine("Makefile", 15, "")) | |
78 | 78 | |||
79 | t.CheckOutputLines( | 79 | t.CheckOutputLines( | |
80 | "NOTE: Makefile:14: The substitution command \"s,@PREFIX@,${PREFIX},g\" " + | 80 | "NOTE: Makefile:14: The substitution command \"s,@PREFIX@,${PREFIX},g\" " + | |
81 | "can be replaced with \"SUBST_VARS.prefix= PREFIX\".") | 81 | "can be replaced with \"SUBST_VARS.prefix= PREFIX\".") | |
82 | } | 82 | } | |
83 | 83 | |||
84 | func (s *Suite) Test_SubstContext__no_class(c *check.C) { | 84 | func (s *Suite) Test_SubstContext__no_class(c *check.C) { | |
85 | t := s.Init(c) | 85 | t := s.Init(c) | |
86 | 86 | |||
87 | t.SetUpCommandLine("-Wextra") | 87 | t.SetUpCommandLine("-Wextra") | |
88 | ctx := NewSubstContext() | 88 | ctx := NewSubstContext() | |
89 | 89 | |||
90 | ctx.Varassign(newSubstLine(t, 10, "UNRELATED=anything")) | 90 | ctx.Varassign(t.NewMkLine("Makefile", 10, "UNRELATED=anything")) | |
91 | ctx.Varassign(newSubstLine(t, 11, "SUBST_FILES.repl+=Makefile.in")) | 91 | ctx.Varassign(t.NewMkLine("Makefile", 11, "SUBST_FILES.repl+=Makefile.in")) | |
92 | ctx.Varassign(newSubstLine(t, 12, "SUBST_SED.repl+=-e s,from,to,g")) | 92 | ctx.Varassign(t.NewMkLine("Makefile", 12, "SUBST_SED.repl+=-e s,from,to,g")) | |
93 | ctx.Finish(newSubstLine(t, 13, "")) | 93 | ctx.Finish(t.NewMkLine("Makefile", 13, "")) | |
94 | 94 | |||
95 | t.CheckOutputLines( | 95 | t.CheckOutputLines( | |
96 | "WARN: Makefile:11: SUBST_CLASSES should come before the definition of \"SUBST_FILES.repl\".", | 96 | "WARN: Makefile:11: SUBST_CLASSES should come before the definition of \"SUBST_FILES.repl\".", | |
97 | "WARN: Makefile:13: Incomplete SUBST block: SUBST_STAGE.repl missing.") | 97 | "WARN: Makefile:13: Incomplete SUBST block: SUBST_STAGE.repl missing.") | |
98 | } | 98 | } | |
99 | 99 | |||
100 | func (s *Suite) Test_SubstContext__multiple_classes_in_one_line(c *check.C) { | 100 | func (s *Suite) Test_SubstContext__multiple_classes_in_one_line(c *check.C) { | |
101 | t := s.Init(c) | 101 | t := s.Init(c) | |
102 | 102 | |||
103 | t.SetUpCommandLine("-Wextra") | 103 | t.SetUpCommandLine("-Wextra") | |
104 | 104 | |||
105 | simulateSubstLines(t, | 105 | simulateSubstLines(t, | |
106 | "10: SUBST_CLASSES+= one two", | 106 | "10: SUBST_CLASSES+= one two", | |
107 | "11: SUBST_STAGE.one= post-configure", | 107 | "11: SUBST_STAGE.one= post-configure", | |
108 | "12: SUBST_FILES.one= one.txt", | 108 | "12: SUBST_FILES.one= one.txt", | |
109 | "13: SUBST_SED.one= s,one,1,g", | 109 | "13: SUBST_SED.one= s,one,1,g", | |
110 | "14: SUBST_STAGE.two= post-configure", | 110 | "14: SUBST_STAGE.two= post-configure", | |
111 | "15: SUBST_FILES.two= two.txt", | 111 | "15: SUBST_FILES.two= two.txt") | |
112 | "17: ") | |||
113 | 112 | |||
114 | t.CheckOutputLines( | 113 | t.CheckOutputLines( | |
115 | "WARN: Makefile:10: Please add only one class at a time to SUBST_CLASSES.", | 114 | "WARN: Makefile:10: Please add only one class at a time to SUBST_CLASSES.", | |
116 | "WARN: Makefile:17: Incomplete SUBST block: SUBST_SED.two, SUBST_VARS.two or SUBST_FILTER_CMD.two missing.") | 115 | "WARN: Makefile:16: Incomplete SUBST block: SUBST_SED.two, SUBST_VARS.two or SUBST_FILTER_CMD.two missing.") | |
117 | } | 116 | } | |
118 | 117 | |||
119 | func (s *Suite) Test_SubstContext__multiple_classes_in_one_block(c *check.C) { | 118 | func (s *Suite) Test_SubstContext__multiple_classes_in_one_block(c *check.C) { | |
120 | t := s.Init(c) | 119 | t := s.Init(c) | |
121 | 120 | |||
122 | t.SetUpCommandLine("-Wextra") | 121 | t.SetUpCommandLine("-Wextra") | |
123 | 122 | |||
124 | simulateSubstLines(t, | 123 | simulateSubstLines(t, | |
125 | "10: SUBST_CLASSES+= one", | 124 | "10: SUBST_CLASSES+= one", | |
126 | "11: SUBST_STAGE.one= post-configure", | 125 | "11: SUBST_STAGE.one= post-configure", | |
127 | "12: SUBST_STAGE.one= post-configure", | 126 | "12: SUBST_STAGE.one= post-configure", | |
128 | "13: SUBST_FILES.one= one.txt", | 127 | "13: SUBST_FILES.one= one.txt", | |
129 | "14: SUBST_CLASSES+= two", // The block "one" is not finished yet. | 128 | "14: SUBST_CLASSES+= two", // The block "one" is not finished yet. | |
130 | "15: SUBST_SED.one= s,one,1,g", | 129 | "15: SUBST_SED.one= s,one,1,g", | |
131 | "16: SUBST_STAGE.two= post-configure", | 130 | "16: SUBST_STAGE.two= post-configure", | |
132 | "17: SUBST_FILES.two= two.txt", | 131 | "17: SUBST_FILES.two= two.txt", | |
133 | "18: SUBST_SED.two= s,two,2,g", | 132 | "18: SUBST_SED.two= s,two,2,g") | |
134 | "19: ") | |||
135 | 133 | |||
136 | t.CheckOutputLines( | 134 | t.CheckOutputLines( | |
137 | "WARN: Makefile:12: Duplicate definition of \"SUBST_STAGE.one\".", | 135 | "WARN: Makefile:12: Duplicate definition of \"SUBST_STAGE.one\".", | |
138 | "WARN: Makefile:14: Incomplete SUBST block: SUBST_SED.one, SUBST_VARS.one or SUBST_FILTER_CMD.one missing.", | 136 | "WARN: Makefile:14: Incomplete SUBST block: SUBST_SED.one, SUBST_VARS.one or SUBST_FILTER_CMD.one missing.", | |
139 | "WARN: Makefile:14: Subst block \"one\" should be finished before adding the next class to SUBST_CLASSES.", | 137 | "WARN: Makefile:14: Subst block \"one\" should be finished before adding the next class to SUBST_CLASSES.", | |
140 | "WARN: Makefile:15: Variable \"SUBST_SED.one\" does not match SUBST class \"two\".") | 138 | "WARN: Makefile:15: Variable \"SUBST_SED.one\" does not match SUBST class \"two\".") | |
141 | } | 139 | } | |
142 | 140 | |||
141 | func (s *Suite) Test_SubstContext__files_missing(c *check.C) { | |||
142 | t := s.Init(c) | |||
143 | ||||
144 | simulateSubstLines(t, | |||
145 | "10: SUBST_CLASSES+= one", | |||
146 | "11: SUBST_STAGE.one= pre-configure", | |||
147 | "12: SUBST_CLASSES+= two", | |||
148 | "13: SUBST_STAGE.two= pre-configure", | |||
149 | "14: SUBST_FILES.two= two.txt", | |||
150 | "15: SUBST_SED.two= s,two,2,g") | |||
151 | ||||
152 | t.CheckOutputLines( | |||
153 | "WARN: Makefile:12: Incomplete SUBST block: SUBST_FILES.one missing.", | |||
154 | "WARN: Makefile:12: Incomplete SUBST block: "+ | |||
155 | "SUBST_SED.one, SUBST_VARS.one or SUBST_FILTER_CMD.one missing.", | |||
156 | "WARN: Makefile:12: Subst block \"one\" should be finished "+ | |||
157 | "before adding the next class to SUBST_CLASSES.") | |||
158 | } | |||
159 | ||||
143 | func (s *Suite) Test_SubstContext__directives(c *check.C) { | 160 | func (s *Suite) Test_SubstContext__directives(c *check.C) { | |
144 | t := s.Init(c) | 161 | t := s.Init(c) | |
145 | 162 | |||
146 | t.SetUpCommandLine("-Wextra") | 163 | t.SetUpCommandLine("-Wextra") | |
147 | 164 | |||
148 | simulateSubstLines(t, | 165 | simulateSubstLines(t, | |
149 | "10: SUBST_CLASSES+= os", | 166 | "10: SUBST_CLASSES+= os", | |
150 | "11: SUBST_STAGE.os= post-configure", | 167 | "11: SUBST_STAGE.os= post-configure", | |
151 | "12: SUBST_MESSAGE.os= Guessing operating system", | 168 | "12: SUBST_MESSAGE.os= Guessing operating system", | |
152 | "13: SUBST_FILES.os= guess-os.h", | 169 | "13: SUBST_FILES.os= guess-os.h", | |
153 | "14: .if ${OPSYS} == NetBSD", | 170 | "14: .if ${OPSYS} == NetBSD", | |
154 | "15: SUBST_FILTER_CMD.os= ${SED} -e s,@OPSYS@,NetBSD,", | 171 | "15: SUBST_FILTER_CMD.os= ${SED} -e s,@OPSYS@,NetBSD,", | |
155 | "16: .elif ${OPSYS} == Darwin", | 172 | "16: .elif ${OPSYS} == Darwin", | |
156 | "17: SUBST_SED.os= -e s,@OPSYS@,Darwin1,", | 173 | "17: SUBST_SED.os= -e s,@OPSYS@,Darwin1,", | |
157 | "18: SUBST_SED.os= -e s,@OPSYS@,Darwin2,", | 174 | "18: SUBST_SED.os= -e s,@OPSYS@,Darwin2,", | |
158 | "19: .elif ${OPSYS} == Linux", | 175 | "19: .elif ${OPSYS} == Linux", | |
159 | "18: SUBST_SED.os= -e s,@OPSYS@,Linux,", | 176 | "20: SUBST_SED.os= -e s,@OPSYS@,Linux,", | |
160 | "19: .else", | 177 | "21: .else", | |
161 | "20: SUBST_VARS.os= OPSYS", | 178 | "22: SUBST_VARS.os= OPSYS", | |
162 | "21: .endif", | 179 | "23: .endif") | |
163 | "22: ") | |||
164 | 180 | |||
165 | // All the other lines are correctly determined as being alternatives | 181 | // All the other lines are correctly determined as being alternatives | |
166 | // to each other. And since every branch contains some transformation | 182 | // to each other. And since every branch contains some transformation | |
167 | // (SED, VARS, FILTER_CMD), everything is fine. | 183 | // (SED, VARS, FILTER_CMD), everything is fine. | |
168 | t.CheckOutputLines( | 184 | t.CheckOutputLines( | |
169 | "WARN: Makefile:18: All but the first \"SUBST_SED.os\" lines should use the \"+=\" operator.") | 185 | "WARN: Makefile:18: All but the first \"SUBST_SED.os\" lines should use the \"+=\" operator.") | |
170 | } | 186 | } | |
171 | 187 | |||
188 | func (s *Suite) Test_SubstContext__directives_around_everything_then(c *check.C) { | |||
189 | t := s.Init(c) | |||
190 | ||||
191 | t.SetUpCommandLine("-Wextra") | |||
192 | ||||
193 | simulateSubstLines(t, | |||
194 | "10: SUBST_CLASSES+= os", | |||
195 | "11: .if ${OPSYS} == NetBSD", | |||
196 | "12: SUBST_VARS.os= OPSYS", | |||
197 | "13: SUBST_SED.os= -e s,@OPSYS@,NetBSD,", | |||
198 | "14: SUBST_STAGE.os= post-configure", | |||
199 | "15: SUBST_MESSAGE.os= Guessing operating system", | |||
200 | "16: SUBST_FILES.os= guess-os.h", | |||
201 | "17: .endif") | |||
202 | ||||
203 | // TODO: The SUBST variables are not guaranteed to be defined in all cases. | |||
204 | t.CheckOutputEmpty() | |||
205 | } | |||
206 | ||||
207 | func (s *Suite) Test_SubstContext__directives_around_everything_else(c *check.C) { | |||
208 | t := s.Init(c) | |||
209 | ||||
210 | t.SetUpCommandLine("-Wextra") | |||
211 | ||||
212 | simulateSubstLines(t, | |||
213 | "10: SUBST_CLASSES+= os", | |||
214 | "11: .if ${OPSYS} == NetBSD", | |||
215 | "12: .else", | |||
216 | "13: SUBST_VARS.os= OPSYS", | |||
217 | "14: SUBST_SED.os= -e s,@OPSYS@,NetBSD,", | |||
218 | "15: SUBST_STAGE.os= post-configure", | |||
219 | "16: SUBST_MESSAGE.os= Guessing operating system", | |||
220 | "17: SUBST_FILES.os= guess-os.h", | |||
221 | "18: .endif") | |||
222 | ||||
223 | // FIXME: The warnings must be the same as in the "then" test case. | |||
224 | t.CheckOutputLines( | |||
225 | "WARN: Makefile:19: Incomplete SUBST block: SUBST_FILES.os missing.", | |||
226 | "WARN: Makefile:19: Incomplete SUBST block: SUBST_SED.os, SUBST_VARS.os or "+ | |||
227 | "SUBST_FILTER_CMD.os missing.") | |||
228 | } | |||
229 | ||||
230 | func (s *Suite) Test_SubstContext__empty_directive(c *check.C) { | |||
231 | t := s.Init(c) | |||
232 | ||||
233 | t.SetUpCommandLine("-Wextra") | |||
234 | ||||
235 | simulateSubstLines(t, | |||
236 | "10: SUBST_CLASSES+= os", | |||
237 | "11: SUBST_VARS.os= OPSYS", | |||
238 | "12: SUBST_SED.os= -e s,@OPSYS@,NetBSD,", | |||
239 | "13: SUBST_STAGE.os= post-configure", | |||
240 | "14: SUBST_MESSAGE.os= Guessing operating system", | |||
241 | "15: SUBST_FILES.os= guess-os.h", | |||
242 | "16: .if ${OPSYS} == NetBSD", | |||
243 | "17: .else", | |||
244 | "18: .endif") | |||
245 | ||||
246 | t.CheckOutputEmpty() | |||
247 | } | |||
248 | ||||
172 | func (s *Suite) Test_SubstContext__missing_transformation_in_one_branch(c *check.C) { | 249 | func (s *Suite) Test_SubstContext__missing_transformation_in_one_branch(c *check.C) { | |
173 | t := s.Init(c) | 250 | t := s.Init(c) | |
174 | 251 | |||
175 | t.SetUpCommandLine("-Wextra") | 252 | t.SetUpCommandLine("-Wextra") | |
176 | 253 | |||
177 | simulateSubstLines(t, | 254 | simulateSubstLines(t, | |
178 | "10: SUBST_CLASSES+= os", | 255 | "10: SUBST_CLASSES+= os", | |
179 | "11: SUBST_STAGE.os= post-configure", | 256 | "11: SUBST_STAGE.os= post-configure", | |
180 | "12: SUBST_MESSAGE.os= Guessing operating system", | 257 | "12: SUBST_MESSAGE.os= Guessing operating system", | |
181 | "13: SUBST_FILES.os= guess-os.h", | 258 | "13: SUBST_FILES.os= guess-os.h", | |
182 | "14: .if ${OPSYS} == NetBSD", | 259 | "14: .if ${OPSYS} == NetBSD", | |
183 | "15: SUBST_FILES.os= -e s,@OpSYS@,NetBSD,", // A simple typo, this should be SUBST_SED. | 260 | "15: SUBST_FILES.os= -e s,@OpSYS@,NetBSD,", // A simple typo, this should be SUBST_SED. | |
184 | "16: .elif ${OPSYS} == Darwin", | 261 | "16: .elif ${OPSYS} == Darwin", | |
185 | "17: SUBST_SED.os= -e s,@OPSYS@,Darwin1,", | 262 | "17: SUBST_SED.os= -e s,@OPSYS@,Darwin1,", | |
186 | "18: SUBST_SED.os= -e s,@OPSYS@,Darwin2,", | 263 | "18: SUBST_SED.os= -e s,@OPSYS@,Darwin2,", | |
187 | "19: .else", | 264 | "19: .else", | |
188 | "20: SUBST_VARS.os= OPSYS", | 265 | "20: SUBST_VARS.os= OPSYS", | |
189 | "21: .endif", | 266 | "21: .endif") | |
190 | "22: ") | |||
191 | 267 | |||
192 | t.CheckOutputLines( | 268 | t.CheckOutputLines( | |
193 | "WARN: Makefile:15: All but the first \"SUBST_FILES.os\" lines should use the \"+=\" operator.", | 269 | "WARN: Makefile:15: All but the first \"SUBST_FILES.os\" lines should use the \"+=\" operator.", | |
194 | "WARN: Makefile:18: All but the first \"SUBST_SED.os\" lines should use the \"+=\" operator.", | 270 | "WARN: Makefile:18: All but the first \"SUBST_SED.os\" lines should use the \"+=\" operator.", | |
195 | "WARN: Makefile:22: Incomplete SUBST block: SUBST_SED.os, SUBST_VARS.os or SUBST_FILTER_CMD.os missing.") | 271 | "WARN: Makefile:22: Incomplete SUBST block: SUBST_SED.os, SUBST_VARS.os or SUBST_FILTER_CMD.os missing.") | |
196 | } | 272 | } | |
197 | 273 | |||
198 | func (s *Suite) Test_SubstContext__nested_conditionals(c *check.C) { | 274 | func (s *Suite) Test_SubstContext__nested_conditionals(c *check.C) { | |
199 | t := s.Init(c) | 275 | t := s.Init(c) | |
200 | 276 | |||
201 | t.SetUpCommandLine("-Wextra") | 277 | t.SetUpCommandLine("-Wextra") | |
202 | 278 | |||
203 | simulateSubstLines(t, | 279 | simulateSubstLines(t, | |
204 | "10: SUBST_CLASSES+= os", | 280 | "10: SUBST_CLASSES+= os", | |
205 | "11: SUBST_STAGE.os= post-configure", | 281 | "11: SUBST_STAGE.os= post-configure", | |
206 | "12: SUBST_MESSAGE.os= Guessing operating system", | 282 | "12: SUBST_MESSAGE.os= Guessing operating system", | |
207 | "14: .if ${OPSYS} == NetBSD", | 283 | "13: .if ${OPSYS} == NetBSD", | |
208 | "13: SUBST_FILES.os= guess-netbsd.h", | 284 | "14: SUBST_FILES.os= guess-netbsd.h", | |
209 | "15: . if ${ARCH} == i386", | 285 | "15: . if ${ARCH} == i386", | |
210 | "16: SUBST_FILTER_CMD.os= ${SED} -e s,@OPSYS,NetBSD-i386,", | 286 | "16: SUBST_FILTER_CMD.os= ${SED} -e s,@OPSYS,NetBSD-i386,", | |
211 | "17: . elif ${ARCH} == x86_64", | 287 | "17: . elif ${ARCH} == x86_64", | |
212 | "18: SUBST_VARS.os= OPSYS", | 288 | "18: SUBST_VARS.os= OPSYS", | |
213 | "19: . else", | 289 | "19: . else", | |
214 | "20: SUBST_SED.os= -e s,@OPSYS,NetBSD-unknown", | 290 | "20: SUBST_SED.os= -e s,@OPSYS,NetBSD-unknown", | |
215 | "21: . endif", | 291 | "21: . endif", | |
216 | "22: .else", | 292 | "22: .else", | |
217 | "23: SUBST_SED.os= -e s,@OPSYS@,unknown,", | 293 | "23: SUBST_SED.os= -e s,@OPSYS@,unknown,", | |
218 | "24: .endif", | 294 | "24: .endif") | |
219 | "25: ") | |||
220 | 295 | |||
221 | // The branch in line 23 omits SUBST_FILES. | 296 | // The branch in line 23 omits SUBST_FILES. | |
222 | t.CheckOutputLines( | 297 | t.CheckOutputLines( | |
223 | "WARN: Makefile:25: Incomplete SUBST block: SUBST_FILES.os missing.") | 298 | "WARN: Makefile:25: Incomplete SUBST block: SUBST_FILES.os missing.") | |
224 | } | 299 | } | |
225 | 300 | |||
301 | func (s *Suite) Test_SubstContext__pre_patch(c *check.C) { | |||
302 | t := s.Init(c) | |||
303 | ||||
304 | t.SetUpCommandLine("-Wextra,no-space", "--show-autofix") | |||
305 | t.SetUpVartypes() | |||
306 | ||||
307 | mklines := t.NewMkLines("os.mk", | |||
308 | MkRcsID, | |||
309 | "", | |||
310 | "SUBST_CLASSES+= os", | |||
311 | "SUBST_STAGE.os= pre-patch", | |||
312 | "SUBST_FILES.os= guess-os.h", | |||
313 | "SUBST_SED.os= -e s,@OPSYS@,Darwin,") | |||
314 | ||||
315 | mklines.Check() | |||
316 | ||||
317 | t.CheckOutputLines( | |||
318 | "WARN: os.mk:4: Substitutions should not happen in the patch phase.", | |||
319 | "AUTOFIX: os.mk:4: Replacing \"pre-patch\" with \"post-extract\".") | |||
320 | } | |||
321 | ||||
226 | func (s *Suite) Test_SubstContext__post_patch(c *check.C) { | 322 | func (s *Suite) Test_SubstContext__post_patch(c *check.C) { | |
227 | t := s.Init(c) | 323 | t := s.Init(c) | |
228 | 324 | |||
229 | t.SetUpCommandLine("-Wextra,no-space", "--show-autofix") | 325 | t.SetUpCommandLine("-Wextra,no-space", "--show-autofix") | |
230 | t.SetUpVartypes() | 326 | t.SetUpVartypes() | |
231 | 327 | |||
232 | mklines := t.NewMkLines("os.mk", | 328 | mklines := t.NewMkLines("os.mk", | |
233 | MkRcsID, | 329 | MkRcsID, | |
234 | "", | 330 | "", | |
235 | "SUBST_CLASSES+= os", | 331 | "SUBST_CLASSES+= os", | |
236 | "SUBST_STAGE.os= post-patch", | 332 | "SUBST_STAGE.os= post-patch", | |
237 | "SUBST_FILES.os= guess-os.h", | 333 | "SUBST_FILES.os= guess-os.h", | |
238 | "SUBST_SED.os= -e s,@OPSYS@,Darwin,") | 334 | "SUBST_SED.os= -e s,@OPSYS@,Darwin,") | |
239 | 335 | |||
240 | mklines.Check() | 336 | mklines.Check() | |
241 | 337 | |||
242 | t.CheckOutputLines( | 338 | t.CheckOutputLines( | |
243 | "WARN: os.mk:4: Substitutions should not happen in the patch phase.", | 339 | "WARN: os.mk:4: Substitutions should not happen in the patch phase.", | |
244 | "AUTOFIX: os.mk:4: Replacing \"post-patch\" with \"pre-configure\".") | 340 | "AUTOFIX: os.mk:4: Replacing \"post-patch\" with \"pre-configure\".") | |
245 | } | 341 | } | |
246 | 342 | |||
247 | func (s *Suite) Test_SubstContext__pre_configure_with_NO_CONFIGURE(c *check.C) { | 343 | func (s *Suite) Test_SubstContext__with_NO_CONFIGURE(c *check.C) { | |
248 | t := s.Init(c) | 344 | t := s.Init(c) | |
249 | 345 | |||
250 | t.SetUpCommandLine("-Wall,no-space") | 346 | t.SetUpCommandLine("-Wall,no-space") | |
251 | pkg := t.SetUpPackage("category/package", | 347 | pkg := t.SetUpPackage("category/package", | |
252 | "SUBST_CLASSES+= os", | 348 | "SUBST_CLASSES+= pre", | |
253 | "SUBST_STAGE.os= pre-configure", | 349 | "SUBST_STAGE.pre= pre-configure", | |
254 | "SUBST_FILES.os= guess-os.h", | 350 | "SUBST_FILES.pre= guess-os.h", | |
255 | "SUBST_SED.os= -e s,@OPSYS@,Darwin,", | 351 | "SUBST_SED.pre= -e s,@OPSYS@,Darwin,", | |
352 | "", | |||
353 | "SUBST_CLASSES+= post", | |||
354 | "SUBST_STAGE.post= post-configure", | |||
355 | "SUBST_FILES.post= guess-os.h", | |||
356 | "SUBST_SED.post= -e s,@OPSYS@,Darwin,", | |||
357 | "", | |||
358 | "SUBST_CLASSES+= e", | |||
359 | "SUBST_STAGE.e= post-extract", | |||
360 | "SUBST_FILES.e= guess-os.h", | |||
361 | "SUBST_SED.e= -e s,@OPSYS@,Darwin,", | |||
256 | "", | 362 | "", | |
257 | "NO_CONFIGURE= yes") | 363 | "NO_CONFIGURE= yes") | |
258 | t.FinishSetUp() | 364 | t.FinishSetUp() | |
259 | 365 | |||
260 | G.Check(pkg) | 366 | G.Check(pkg) | |
261 | 367 | |||
262 | t.CheckOutputLines( | 368 | t.CheckOutputLines( | |
263 | "WARN: ~/category/package/Makefile:21: SUBST_STAGE pre-configure has no effect when NO_CONFIGURE is set (in line 25).") | 369 | "WARN: ~/category/package/Makefile:21: SUBST_STAGE pre-configure has no effect "+ | |
370 | "when NO_CONFIGURE is set (in line 35).", | |||
371 | "WARN: ~/category/package/Makefile:26: SUBST_STAGE post-configure has no effect "+ | |||
372 | "when NO_CONFIGURE is set (in line 35).") | |||
373 | } | |||
374 | ||||
375 | func (s *Suite) Test_SubstContext__without_NO_CONFIGURE(c *check.C) { | |||
376 | t := s.Init(c) | |||
377 | ||||
378 | t.SetUpCommandLine("-Wall,no-space") | |||
379 | pkg := t.SetUpPackage("category/package", | |||
380 | "SUBST_CLASSES+= pre", | |||
381 | "SUBST_STAGE.pre= pre-configure", | |||
382 | "SUBST_FILES.pre= guess-os.h", | |||
383 | "SUBST_SED.pre= -e s,@OPSYS@,Darwin,") | |||
384 | t.FinishSetUp() | |||
385 | ||||
386 | G.Check(pkg) | |||
387 | ||||
388 | t.CheckOutputEmpty() | |||
264 | } | 389 | } | |
265 | 390 | |||
266 | func (s *Suite) Test_SubstContext__adjacent(c *check.C) { | 391 | func (s *Suite) Test_SubstContext__adjacent(c *check.C) { | |
267 | t := s.Init(c) | 392 | t := s.Init(c) | |
268 | 393 | |||
269 | t.SetUpCommandLine("-Wextra") | 394 | t.SetUpCommandLine("-Wextra") | |
270 | t.SetUpVartypes() | 395 | t.SetUpVartypes() | |
271 | 396 | |||
272 | mklines := t.NewMkLines("os.mk", | 397 | mklines := t.NewMkLines("os.mk", | |
273 | MkRcsID, | 398 | MkRcsID, | |
274 | "", | 399 | "", | |
275 | "SUBST_CLASSES+= 1", | 400 | "SUBST_CLASSES+= 1", | |
276 | "SUBST_STAGE.1= pre-configure", | 401 | "SUBST_STAGE.1= pre-configure", | |
@@ -349,26 +474,71 @@ func (s *Suite) Test_SubstContext__SUBST | @@ -349,26 +474,71 @@ func (s *Suite) Test_SubstContext__SUBST | |||
349 | "SUBST_STAGE.os= pre-configure", | 474 | "SUBST_STAGE.os= pre-configure", | |
350 | "SUBST_FILES.os= guess-os.h", | 475 | "SUBST_FILES.os= guess-os.h", | |
351 | "SUBST_VARS.os= TODAY1", | 476 | "SUBST_VARS.os= TODAY1", | |
352 | "", | 477 | "", | |
353 | "TODAY1!= date", | 478 | "TODAY1!= date", | |
354 | "TODAY2!= date") | 479 | "TODAY2!= date") | |
355 | 480 | |||
356 | mklines.Check() | 481 | mklines.Check() | |
357 | 482 | |||
358 | t.CheckOutputLines( | 483 | t.CheckOutputLines( | |
359 | "WARN: os.mk:9: TODAY2 is defined but not used.") | 484 | "WARN: os.mk:9: TODAY2 is defined but not used.") | |
360 | } | 485 | } | |
361 | 486 | |||
487 | func (s *Suite) Test_SubstContext__multiple_SUBST_VARS(c *check.C) { | |||
488 | t := s.Init(c) | |||
489 | ||||
490 | t.SetUpCommandLine("-Wextra,no-space") | |||
491 | t.SetUpVartypes() | |||
492 | ||||
493 | mklines := t.NewMkLines("os.mk", | |||
494 | MkRcsID, | |||
495 | "", | |||
496 | "SUBST_CLASSES+= os", | |||
497 | "SUBST_STAGE.os= pre-configure", | |||
498 | "SUBST_FILES.os= guess-os.h", | |||
499 | "SUBST_VARS.os= PREFIX VARBASE") | |||
500 | ||||
501 | mklines.Check() | |||
502 | ||||
503 | t.CheckOutputEmpty() | |||
504 | } | |||
505 | ||||
506 | // Since the SUBST_CLASSES definition starts the SUBST block, all | |||
507 | // directives above it are ignored by the SUBST context. | |||
508 | func (s *Suite) Test_SubstContext_Directive__before_SUBST_CLASSES(c *check.C) { | |||
509 | t := s.Init(c) | |||
510 | ||||
511 | t.SetUpCommandLine("-Wextra,no-space") | |||
512 | t.SetUpVartypes() | |||
513 | t.DisableTracing() // Just for branch coverage. | |||
514 | ||||
515 | mklines := t.NewMkLines("os.mk", | |||
516 | MkRcsID, | |||
517 | "", | |||
518 | ".if 0", | |||
519 | ".endif", | |||
520 | "SUBST_CLASSES+= os", | |||
521 | ".elif 0") // Just for branch coverage. | |||
522 | ||||
523 | mklines.Check() | |||
524 | ||||
525 | t.CheckOutputLines( | |||
526 | "WARN: os.mk:EOF: Incomplete SUBST block: SUBST_STAGE.os missing.", | |||
527 | "WARN: os.mk:EOF: Incomplete SUBST block: SUBST_FILES.os missing.", | |||
528 | "WARN: os.mk:EOF: Incomplete SUBST block: "+ | |||
529 | "SUBST_SED.os, SUBST_VARS.os or SUBST_FILTER_CMD.os missing.") | |||
530 | } | |||
531 | ||||
362 | func (s *Suite) Test_SubstContext_suggestSubstVars(c *check.C) { | 532 | func (s *Suite) Test_SubstContext_suggestSubstVars(c *check.C) { | |
363 | t := s.Init(c) | 533 | t := s.Init(c) | |
364 | 534 | |||
365 | t.SetUpVartypes() | 535 | t.SetUpVartypes() | |
366 | t.SetUpTool("sh", "SH", AtRunTime) | 536 | t.SetUpTool("sh", "SH", AtRunTime) | |
367 | 537 | |||
368 | mklines := t.NewMkLines("subst.mk", | 538 | mklines := t.NewMkLines("subst.mk", | |
369 | MkRcsID, | 539 | MkRcsID, | |
370 | "", | 540 | "", | |
371 | "SUBST_CLASSES+=\t\ttest", | 541 | "SUBST_CLASSES+=\t\ttest", | |
372 | "SUBST_STAGE.test=\tpre-configure", | 542 | "SUBST_STAGE.test=\tpre-configure", | |
373 | "SUBST_FILES.test=\tfilename", | 543 | "SUBST_FILES.test=\tfilename", | |
374 | "SUBST_SED.test+=\t-e s,@SH@,${SH},g", // Can be replaced. | 544 | "SUBST_SED.test+=\t-e s,@SH@,${SH},g", // Can be replaced. | |
@@ -472,38 +642,219 @@ func (s *Suite) Test_SubstContext_sugges | @@ -472,38 +642,219 @@ func (s *Suite) Test_SubstContext_sugges | |||
472 | mklines.Check() | 642 | mklines.Check() | |
473 | 643 | |||
474 | t.CheckOutputLines( | 644 | t.CheckOutputLines( | |
475 | "NOTE: subst.mk:6: The substitution command \"s,@SH@,${SH:Q},g\" "+ | 645 | "NOTE: subst.mk:6: The substitution command \"s,@SH@,${SH:Q},g\" "+ | |
476 | "can be replaced with \"SUBST_VARS.gtk+ = SH\".", | 646 | "can be replaced with \"SUBST_VARS.gtk+ = SH\".", | |
477 | "AUTOFIX: subst.mk:6: Replacing \"SUBST_SED.gtk+ +=\\t-e s,@SH@,${SH:Q},g\" "+ | 647 | "AUTOFIX: subst.mk:6: Replacing \"SUBST_SED.gtk+ +=\\t-e s,@SH@,${SH:Q},g\" "+ | |
478 | "with \"SUBST_VARS.gtk+ =\\tSH\".", | 648 | "with \"SUBST_VARS.gtk+ =\\tSH\".", | |
479 | "NOTE: subst.mk:7: The substitution command \"s,@SH@,${SH:Q},g\" "+ | 649 | "NOTE: subst.mk:7: The substitution command \"s,@SH@,${SH:Q},g\" "+ | |
480 | "can be replaced with \"SUBST_VARS.gtk+ += SH\".", | 650 | "can be replaced with \"SUBST_VARS.gtk+ += SH\".", | |
481 | "AUTOFIX: subst.mk:7: Replacing \"SUBST_SED.gtk+ +=\\t-e s,@SH@,${SH:Q},g\" "+ | 651 | "AUTOFIX: subst.mk:7: Replacing \"SUBST_SED.gtk+ +=\\t-e s,@SH@,${SH:Q},g\" "+ | |
482 | "with \"SUBST_VARS.gtk+ +=\\tSH\".") | 652 | "with \"SUBST_VARS.gtk+ +=\\tSH\".") | |
483 | } | 653 | } | |
484 | 654 | |||
655 | // The last of the SUBST_SED variables is 15 characters wide. When SUBST_SED | |||
656 | // is replaced with SUBST_VARS, this becomes 16 characters and therefore | |||
657 | // requires the whole paragraph to be indented by one more tab. | |||
658 | func (s *Suite) Test_SubstContext_suggestSubstVars__autofix_realign_paragraph(c *check.C) { | |||
659 | t := s.Init(c) | |||
660 | ||||
661 | t.SetUpVartypes() | |||
662 | t.Chdir(".") | |||
663 | ||||
664 | mklines := t.SetUpFileMkLines("subst.mk", | |||
665 | MkRcsID, | |||
666 | "", | |||
667 | "SUBST_CLASSES+=\t\tpfx", | |||
668 | "SUBST_STAGE.pfx=\tpre-configure", | |||
669 | "SUBST_FILES.pfx=\tfilename", | |||
670 | "SUBST_SED.pfx=\t\t-e s,@PREFIX@,${PREFIX},g", | |||
671 | "SUBST_SED.pfx+=\t\t-e s,@PREFIX@,${PREFIX},g") | |||
672 | ||||
673 | mklines.Check() | |||
674 | ||||
675 | t.CheckOutputLines( | |||
676 | "NOTE: subst.mk:6: The substitution command \"s,@PREFIX@,${PREFIX},g\" "+ | |||
677 | "can be replaced with \"SUBST_VARS.pfx= PREFIX\".", | |||
678 | "NOTE: subst.mk:7: The substitution command \"s,@PREFIX@,${PREFIX},g\" "+ | |||
679 | "can be replaced with \"SUBST_VARS.pfx+= PREFIX\".") | |||
680 | ||||
681 | t.SetUpCommandLine("--autofix") | |||
682 | ||||
683 | mklines.Check() | |||
684 | ||||
685 | t.CheckOutputLines( | |||
686 | "AUTOFIX: subst.mk:6: Replacing \"SUBST_SED.pfx=\\t\\t-e s,@PREFIX@,${PREFIX},g\" "+ | |||
687 | "with \"SUBST_VARS.pfx=\\t\\tPREFIX\".", | |||
688 | "AUTOFIX: subst.mk:7: Replacing \"SUBST_SED.pfx+=\\t\\t-e s,@PREFIX@,${PREFIX},g\" "+ | |||
689 | "with \"SUBST_VARS.pfx+=\\tPREFIX\".") | |||
690 | ||||
691 | t.CheckFileLinesDetab("subst.mk", | |||
692 | "# $NetBSD: substcontext_test.go,v 1.25 2019/05/26 14:05:57 rillig Exp $", | |||
693 | "", | |||
694 | "SUBST_CLASSES+= pfx", | |||
695 | "SUBST_STAGE.pfx= pre-configure", | |||
696 | "SUBST_FILES.pfx= filename", | |||
697 | "SUBST_VARS.pfx= PREFIX", | |||
698 | "SUBST_VARS.pfx+= PREFIX") | |||
699 | } | |||
700 | ||||
701 | func (s *Suite) Test_SubstContext_suggestSubstVars__autofix_plus_sed(c *check.C) { | |||
702 | t := s.Init(c) | |||
703 | ||||
704 | t.SetUpVartypes() | |||
705 | t.Chdir(".") | |||
706 | ||||
707 | mklines := t.SetUpFileMkLines("subst.mk", | |||
708 | MkRcsID, | |||
709 | "", | |||
710 | "SUBST_CLASSES+=\t\tpfx", | |||
711 | "SUBST_STAGE.pfx=\tpre-configure", | |||
712 | "SUBST_FILES.pfx=\tfilename", | |||
713 | "SUBST_SED.pfx=\t\t-e s,@PREFIX@,${PREFIX},g", | |||
714 | "SUBST_SED.pfx+=\t\t-e s,@PREFIX@,other,g") | |||
715 | ||||
716 | mklines.Check() | |||
717 | ||||
718 | t.CheckOutputLines( | |||
719 | "NOTE: subst.mk:6: The substitution command \"s,@PREFIX@,${PREFIX},g\" " + | |||
720 | "can be replaced with \"SUBST_VARS.pfx= PREFIX\".") | |||
721 | ||||
722 | t.SetUpCommandLine("-Wall", "--autofix") | |||
723 | ||||
724 | mklines.Check() | |||
725 | ||||
726 | t.CheckOutputLines( | |||
727 | "AUTOFIX: subst.mk:6: Replacing \"SUBST_SED.pfx=\\t\\t-e s,@PREFIX@,${PREFIX},g\" " + | |||
728 | "with \"SUBST_VARS.pfx=\\t\\tPREFIX\".") | |||
729 | ||||
730 | t.CheckFileLinesDetab("subst.mk", | |||
731 | "# $NetBSD: substcontext_test.go,v 1.25 2019/05/26 14:05:57 rillig Exp $", | |||
732 | "", | |||
733 | "SUBST_CLASSES+= pfx", | |||
734 | "SUBST_STAGE.pfx= pre-configure", | |||
735 | "SUBST_FILES.pfx= filename", | |||
736 | "SUBST_VARS.pfx= PREFIX", | |||
737 | // TODO: If this subst class is used nowhere else, pkglint could | |||
738 | // replace this += with a simple =. | |||
739 | "SUBST_SED.pfx+= -e s,@PREFIX@,other,g") | |||
740 | } | |||
741 | ||||
742 | func (s *Suite) Test_SubstContext_suggestSubstVars__autofix_plus_vars(c *check.C) { | |||
743 | t := s.Init(c) | |||
744 | ||||
745 | t.SetUpCommandLine("-Wall", "--autofix") | |||
746 | t.SetUpVartypes() | |||
747 | t.Chdir(".") | |||
748 | ||||
749 | mklines := t.SetUpFileMkLines("subst.mk", | |||
750 | MkRcsID, | |||
751 | "", | |||
752 | "SUBST_CLASSES+=\tid", | |||
753 | "SUBST_STAGE.id=\tpre-configure", | |||
754 | "SUBST_FILES.id=\tfilename", | |||
755 | "SUBST_SED.id=\t-e s,@PREFIX@,${PREFIX},g", | |||
756 | "SUBST_VARS.id=\tPKGMANDIR") | |||
757 | ||||
758 | mklines.Check() | |||
759 | ||||
760 | t.CheckOutputLines( | |||
761 | "AUTOFIX: subst.mk:6: Replacing \"SUBST_SED.id=\\t-e s,@PREFIX@,${PREFIX},g\" " + | |||
762 | "with \"SUBST_VARS.id=\\tPREFIX\".") | |||
763 | ||||
764 | t.CheckFileLinesDetab("subst.mk", | |||
765 | "# $NetBSD: substcontext_test.go,v 1.25 2019/05/26 14:05:57 rillig Exp $", | |||
766 | "", | |||
767 | "SUBST_CLASSES+= id", | |||
768 | "SUBST_STAGE.id= pre-configure", | |||
769 | "SUBST_FILES.id= filename", | |||
770 | "SUBST_VARS.id= PREFIX", | |||
771 | // FIXME: This must be += instead of = since the previous line already uses =. | |||
772 | // Luckily the check for redundant assignments catches this already. | |||
773 | "SUBST_VARS.id= PKGMANDIR") | |||
774 | } | |||
775 | ||||
776 | func (s *Suite) Test_SubstContext_suggestSubstVars__autofix_indentation(c *check.C) { | |||
777 | t := s.Init(c) | |||
778 | ||||
779 | t.SetUpCommandLine("-Wall", "--autofix") | |||
780 | t.SetUpVartypes() | |||
781 | t.Chdir(".") | |||
782 | ||||
783 | mklines := t.SetUpFileMkLines("subst.mk", | |||
784 | MkRcsID, | |||
785 | "", | |||
786 | "SUBST_CLASSES+=\t\t\tfix-paths", | |||
787 | "SUBST_STAGE.fix-paths=\t\tpre-configure", | |||
788 | "SUBST_MESSAGE.fix-paths=\tMessage", | |||
789 | "SUBST_FILES.fix-paths=\t\tfilename", | |||
790 | "SUBST_SED.fix-paths=\t\t-e s,@PREFIX@,${PREFIX},g") | |||
791 | ||||
792 | mklines.Check() | |||
793 | ||||
794 | t.CheckOutputLines( | |||
795 | "AUTOFIX: subst.mk:7: Replacing \"SUBST_SED.fix-paths=\\t\\t-e s,@PREFIX@,${PREFIX},g\" " + | |||
796 | "with \"SUBST_VARS.fix-paths=\\t\\tPREFIX\".") | |||
797 | ||||
798 | t.CheckFileLinesDetab("subst.mk", | |||
799 | "# $NetBSD: substcontext_test.go,v 1.25 2019/05/26 14:05:57 rillig Exp $", | |||
800 | "", | |||
801 | "SUBST_CLASSES+= fix-paths", | |||
802 | "SUBST_STAGE.fix-paths= pre-configure", | |||
803 | "SUBST_MESSAGE.fix-paths= Message", | |||
804 | "SUBST_FILES.fix-paths= filename", | |||
805 | "SUBST_VARS.fix-paths= PREFIX") | |||
806 | } | |||
807 | ||||
808 | // As of May 2019, pkglint does not check the order of the variables in | |||
809 | // a SUBST block. Enforcing this order, or at least suggesting it, would | |||
810 | // make pkgsrc packages more uniform, which is a good idea, but not urgent. | |||
811 | func (s *Suite) Test_SubstContext__unusual_variable_order(c *check.C) { | |||
812 | t := s.Init(c) | |||
813 | ||||
814 | t.SetUpVartypes() | |||
815 | ||||
816 | mklines := t.NewMkLines("subst.mk", | |||
817 | MkRcsID, | |||
818 | "", | |||
819 | "SUBST_CLASSES+=\t\tid", | |||
820 | "SUBST_SED.id=\t\t-e /deleteme/d", | |||
821 | "SUBST_FILES.id=\t\tfile", | |||
822 | "SUBST_MESSAGE.id=\tMessage", | |||
823 | "SUBST_STAGE.id=\t\tpre-configure") | |||
824 | ||||
825 | mklines.Check() | |||
826 | ||||
827 | t.CheckOutputEmpty() | |||
828 | } | |||
829 | ||||
485 | // simulateSubstLines only tests some of the inner workings of SubstContext. | 830 | // simulateSubstLines only tests some of the inner workings of SubstContext. | |
486 | // It is not realistic for all cases. If in doubt, use MkLines.Check. | 831 | // It is not realistic for all cases. If in doubt, use MkLines.Check. | |
487 | func simulateSubstLines(t *Tester, texts ...string) { | 832 | func simulateSubstLines(t *Tester, texts ...string) { | |
488 | ctx := NewSubstContext() | 833 | ctx := NewSubstContext() | |
834 | lineno := 0 | |||
489 | for _, lineText := range texts { | 835 | for _, lineText := range texts { | |
490 | var lineno int | 836 | var curr int | |
491 | _, err := fmt.Sscanf(lineText[0:4], "%d: ", &lineno) | 837 | _, err := fmt.Sscanf(lineText[0:4], "%d: ", &curr) | |
492 | G.AssertNil(err, "") | 838 | G.AssertNil(err, "") | |
839 | ||||
840 | if lineno != 0 { | |||
841 | t.Check(curr, equals, lineno) | |||
842 | } | |||
843 | ||||
493 | text := lineText[4:] | 844 | text := lineText[4:] | |
494 | line := newSubstLine(t, lineno, text) | 845 | line := t.NewMkLine("Makefile", curr, text) | |
495 | 846 | |||
496 | switch { | 847 | switch { | |
497 | case text == "": | 848 | case text == "": | |
498 | ctx.Finish(line) | 849 | ctx.Finish(line) | |
499 | case hasPrefix(text, "."): | 850 | case hasPrefix(text, "."): | |
500 | ctx.Directive(line) | 851 | ctx.Directive(line) | |
501 | default: | 852 | default: | |
502 | ctx.Varassign(line) | 853 | ctx.Varassign(line) | |
503 | } | 854 | } | |
855 | ||||
856 | lineno = curr + 1 | |||
504 | } | 857 | } | |
505 | } | |||
506 | 858 | |||
507 | func newSubstLine(t *Tester, lineno int, text string) MkLine { | 859 | ctx.Finish(t.NewMkLine("Makefile", lineno, "")) | |
508 | return t.NewMkLine("Makefile", lineno, text) | |||
509 | } | 860 | } |
@@ -282,27 +282,27 @@ func (tr *Tools) ParseToolLine(mklines M | @@ -282,27 +282,27 @@ func (tr *Tools) ParseToolLine(mklines M | |||
282 | tr.SeenPrefs = true | 282 | tr.SeenPrefs = true | |
283 | } | 283 | } | |
284 | } | 284 | } | |
285 | } | 285 | } | |
286 | 286 | |||
287 | func (tr *Tools) addAlias(tool *Tool, alias string) { | 287 | func (tr *Tools) addAlias(tool *Tool, alias string) { | |
288 | tool.Aliases = append(tool.Aliases, alias) | 288 | tool.Aliases = append(tool.Aliases, alias) | |
289 | tr.AliasOf[alias] = tool.Name | 289 | tr.AliasOf[alias] = tool.Name | |
290 | } | 290 | } | |
291 | 291 | |||
292 | // parseUseTools interprets a "USE_TOOLS+=" line from a Makefile fragment. | 292 | // parseUseTools interprets a "USE_TOOLS+=" line from a Makefile fragment. | |
293 | // It determines the validity of the tool, i.e. in which places it may be used. | 293 | // It determines the validity of the tool, i.e. in which places it may be used. | |
294 | // | 294 | // | |
295 | // If createIfAbsent is true and the tools is unknown, it is registered. | 295 | // If createIfAbsent is true and the tool is unknown, it is registered. | |
296 | // This can be done only in the pkgsrc infrastructure files, where the | 296 | // This can be done only in the pkgsrc infrastructure files, where the | |
297 | // actual definition is assumed to be in some other file. In packages | 297 | // actual definition is assumed to be in some other file. In packages | |
298 | // though, this assumption cannot be made and pkglint needs to be strict. | 298 | // though, this assumption cannot be made and pkglint needs to be strict. | |
299 | func (tr *Tools) parseUseTools(mkline MkLine, createIfAbsent bool, addToUseTools bool) { | 299 | func (tr *Tools) parseUseTools(mkline MkLine, createIfAbsent bool, addToUseTools bool) { | |
300 | value := mkline.Value() | 300 | value := mkline.Value() | |
301 | if containsVarRef(value) { | 301 | if containsVarRef(value) { | |
302 | return | 302 | return | |
303 | } | 303 | } | |
304 | 304 | |||
305 | validity := tr.validity(mkline.Basename, addToUseTools) | 305 | validity := tr.validity(mkline.Basename, addToUseTools) | |
306 | for _, dep := range mkline.ValueFields(value) { | 306 | for _, dep := range mkline.ValueFields(value) { | |
307 | name := strings.Split(dep, ":")[0] | 307 | name := strings.Split(dep, ":")[0] | |
308 | if createIfAbsent || tr.ByName(name) != nil { | 308 | if createIfAbsent || tr.ByName(name) != nil { |
@@ -133,31 +133,46 @@ func (s *Suite) Test_VarTypeRegistry_par | @@ -133,31 +133,46 @@ func (s *Suite) Test_VarTypeRegistry_par | |||
133 | "Pkglint internal error: At least one ACL entry must be given.") | 133 | "Pkglint internal error: At least one ACL entry must be given.") | |
134 | } | 134 | } | |
135 | 135 | |||
136 | func (s *Suite) Test_VarTypeRegistry_Init__LP64PLATFORMS(c *check.C) { | 136 | func (s *Suite) Test_VarTypeRegistry_Init__LP64PLATFORMS(c *check.C) { | |
137 | t := s.Init(c) | 137 | t := s.Init(c) | |
138 | 138 | |||
139 | pkg := t.SetUpPackage("category/package", | 139 | pkg := t.SetUpPackage("category/package", | |
140 | "BROKEN_ON_PLATFORM=\t${LP64PLATFORMS}") | 140 | "BROKEN_ON_PLATFORM=\t${LP64PLATFORMS}") | |
141 | t.FinishSetUp() | 141 | t.FinishSetUp() | |
142 | 142 | |||
143 | G.Check(pkg) | 143 | G.Check(pkg) | |
144 | 144 | |||
145 | // No warning about a missing :Q operator. | 145 | // No warning about a missing :Q operator. | |
146 | // All PLATFORM variables must be either lkNone or lkSpace. | |||
147 | t.CheckOutputEmpty() | 146 | t.CheckOutputEmpty() | |
148 | } | 147 | } | |
149 | 148 | |||
150 | func (s *Suite) Test_VarTypeRegistry_Init__no_tracing(c *check.C) { | 149 | func (s *Suite) Test_VarTypeRegistry_Init__no_tracing(c *check.C) { | |
151 | t := s.Init(c) | 150 | t := s.Init(c) | |
152 | 151 | |||
153 | t.CreateFileLines("editors/emacs/modules.mk", | 152 | t.CreateFileLines("editors/emacs/modules.mk", | |
154 | MkRcsID, | 153 | MkRcsID, | |
155 | "", | 154 | "", | |
156 | "_EMACS_VERSIONS_ALL= emacs31", | 155 | "_EMACS_VERSIONS_ALL= emacs31", | |
157 | "_EMACS_VERSIONS_ALL+= emacs29") | 156 | "_EMACS_VERSIONS_ALL+= emacs29") | |
158 | t.DisableTracing() | 157 | t.DisableTracing() | |
159 | 158 | |||
160 | t.SetUpVartypes() // Just for code coverage. | 159 | t.SetUpVartypes() // Just for code coverage. | |
161 | 160 | |||
162 | t.CheckOutputEmpty() | 161 | t.CheckOutputEmpty() | |
163 | } | 162 | } | |
163 | ||||
164 | func (s *Suite) Test_VarTypeRegistry_Init__MASTER_SITES(c *check.C) { | |||
165 | t := s.Init(c) | |||
166 | ||||
167 | t.CreateFileLines("mk/fetch/sites.mk", | |||
168 | MkRcsID, | |||
169 | "", | |||
170 | "MASTER_SITE_GITHUB=\thttps://github.com/", | |||
171 | "", | |||
172 | "OTHER=\tvalue") // For branch coverage of hasPrefix.*MASTER_SITE_ | |||
173 | ||||
174 | t.SetUpVartypes() | |||
175 | ||||
176 | vartype := G.Pkgsrc.VariableType(nil, "MASTER_SITE_GITHUB") | |||
177 | t.Check(vartype.String(), equals, "FetchURL (list, system-provided)") | |||
178 | } |
@@ -255,32 +255,41 @@ func tabWidth(s string) int { | @@ -255,32 +255,41 @@ func tabWidth(s string) int { | |||
255 | } else { | 255 | } else { | |
256 | length++ | 256 | length++ | |
257 | } | 257 | } | |
258 | } | 258 | } | |
259 | return length | 259 | return length | |
260 | } | 260 | } | |
261 | 261 | |||
262 | func detab(s string) string { | 262 | func detab(s string) string { | |
263 | var detabbed strings.Builder | 263 | var detabbed strings.Builder | |
264 | for _, r := range s { | 264 | for _, r := range s { | |
265 | if r == '\t' { | 265 | if r == '\t' { | |
266 | detabbed.WriteString(" "[:8-detabbed.Len()%8]) | 266 | detabbed.WriteString(" "[:8-detabbed.Len()%8]) | |
267 | } else { | 267 | } else { | |
268 | detabbed.WriteString(string(r)) | 268 | detabbed.WriteRune(r) | |
269 | } | 269 | } | |
270 | } | 270 | } | |
271 | return detabbed.String() | 271 | return detabbed.String() | |
272 | } | 272 | } | |
273 | 273 | |||
274 | // alignWith extends str with as many tabs as needed to reach | |||
275 | // the same screen width as the other string. | |||
276 | func alignWith(str, other string) string { | |||
277 | alignBefore := (tabWidth(other) + 7) & -8 | |||
278 | alignAfter := tabWidth(str) & -8 | |||
279 | tabsNeeded := imax((alignBefore-alignAfter)/8, 1) | |||
280 | return str + strings.Repeat("\t", tabsNeeded) | |||
281 | } | |||
282 | ||||
274 | func shorten(s string, maxChars int) string { | 283 | func shorten(s string, maxChars int) string { | |
275 | codePoints := 0 | 284 | codePoints := 0 | |
276 | for i := range s { | 285 | for i := range s { | |
277 | if codePoints >= maxChars { | 286 | if codePoints >= maxChars { | |
278 | return s[:i] + "..." | 287 | return s[:i] + "..." | |
279 | } | 288 | } | |
280 | codePoints++ | 289 | codePoints++ | |
281 | } | 290 | } | |
282 | return s | 291 | return s | |
283 | } | 292 | } | |
284 | 293 | |||
285 | func varnameBase(varname string) string { | 294 | func varnameBase(varname string) string { | |
286 | dot := strings.IndexByte(varname, '.') | 295 | dot := strings.IndexByte(varname, '.') | |
@@ -358,27 +367,27 @@ func relpath(from, to string) (result st | @@ -358,27 +367,27 @@ func relpath(from, to string) (result st | |||
358 | 367 | |||
359 | if trace.Tracing { | 368 | if trace.Tracing { | |
360 | defer trace.Call(from, to, trace.Result(&result))() | 369 | defer trace.Call(from, to, trace.Result(&result))() | |
361 | } | 370 | } | |
362 | 371 | |||
363 | cfrom := cleanpath(from) | 372 | cfrom := cleanpath(from) | |
364 | cto := cleanpath(to) | 373 | cto := cleanpath(to) | |
365 | 374 | |||
366 | if cfrom == cto { | 375 | if cfrom == cto { | |
367 | return "." | 376 | return "." | |
368 | } | 377 | } | |
369 | 378 | |||
370 | // Take a shortcut for the common case from "dir" to "dir/subdir/...". | 379 | // Take a shortcut for the common case from "dir" to "dir/subdir/...". | |
371 | if hasPrefix(cto, cfrom) && len(cto) > len(cfrom)+1 && cto[len(cfrom)] == '/' { | 380 | if hasPrefix(cto, cfrom) && hasPrefix(cto[len(cfrom):], "/") { | |
372 | return cleanpath(cto[len(cfrom)+1:]) | 381 | return cleanpath(cto[len(cfrom)+1:]) | |
373 | } | 382 | } | |
374 | 383 | |||
375 | // Take a shortcut for the common case from "category/package" to ".". | 384 | // Take a shortcut for the common case from "category/package" to ".". | |
376 | // This is the most common variant in a complete pkgsrc scan. | 385 | // This is the most common variant in a complete pkgsrc scan. | |
377 | if cto == "." { | 386 | if cto == "." { | |
378 | fromParts := strings.FieldsFunc(cfrom, func(r rune) bool { return r == '/' }) | 387 | fromParts := strings.FieldsFunc(cfrom, func(r rune) bool { return r == '/' }) | |
379 | if len(fromParts) == 2 && !hasPrefix(fromParts[0], ".") && !hasPrefix(fromParts[1], ".") { | 388 | if len(fromParts) == 2 && !hasPrefix(fromParts[0], ".") && !hasPrefix(fromParts[1], ".") { | |
380 | return "../.." | 389 | return "../.." | |
381 | } | 390 | } | |
382 | } | 391 | } | |
383 | 392 | |||
384 | if cfrom == "." && !filepath.IsAbs(cto) { | 393 | if cfrom == "." && !filepath.IsAbs(cto) { | |
@@ -673,33 +682,33 @@ func (s *Scope) LastDefinition(varname s | @@ -673,33 +682,33 @@ func (s *Scope) LastDefinition(varname s | |||
673 | // Commented returns whether the variable has only been defined in commented | 682 | // Commented returns whether the variable has only been defined in commented | |
674 | // variable assignments. These are ignored by bmake but used heavily in | 683 | // variable assignments. These are ignored by bmake but used heavily in | |
675 | // mk/defaults/mk.conf for documentation. | 684 | // mk/defaults/mk.conf for documentation. | |
676 | func (s *Scope) Commented(varname string) MkLine { | 685 | func (s *Scope) Commented(varname string) MkLine { | |
677 | var mklines []MkLine | 686 | var mklines []MkLine | |
678 | if first := s.firstDef[varname]; first != nil { | 687 | if first := s.firstDef[varname]; first != nil { | |
679 | mklines = append(mklines, first) | 688 | mklines = append(mklines, first) | |
680 | } | 689 | } | |
681 | if last := s.lastDef[varname]; last != nil { | 690 | if last := s.lastDef[varname]; last != nil { | |
682 | mklines = append(mklines, last) | 691 | mklines = append(mklines, last) | |
683 | } | 692 | } | |
684 | 693 | |||
685 | for _, mkline := range mklines { | 694 | for _, mkline := range mklines { | |
686 | if mkline != nil && mkline.IsVarassign() { | 695 | if mkline.IsVarassign() { | |
687 | return nil | 696 | return nil | |
688 | } | 697 | } | |
689 | } | 698 | } | |
690 | 699 | |||
691 | for _, mkline := range mklines { | 700 | for _, mkline := range mklines { | |
692 | if mkline != nil && mkline.IsCommentedVarassign() { | 701 | if mkline.IsCommentedVarassign() { | |
693 | return mkline | 702 | return mkline | |
694 | } | 703 | } | |
695 | } | 704 | } | |
696 | 705 | |||
697 | return nil | 706 | return nil | |
698 | } | 707 | } | |
699 | 708 | |||
700 | func (s *Scope) FirstUse(varname string) MkLine { | 709 | func (s *Scope) FirstUse(varname string) MkLine { | |
701 | return s.used[varname] | 710 | return s.used[varname] | |
702 | } | 711 | } | |
703 | 712 | |||
704 | // LastValue returns the value from the last variable definition. | 713 | // LastValue returns the value from the last variable definition. | |
705 | // | 714 | // | |
@@ -868,27 +877,29 @@ func (c *FileCache) Put(filename string, | @@ -868,27 +877,29 @@ func (c *FileCache) Put(filename string, | |||
868 | 877 | |||
869 | entry = new(fileCacheEntry) | 878 | entry = new(fileCacheEntry) | |
870 | c.table = append(c.table, entry) | 879 | c.table = append(c.table, entry) | |
871 | c.mapping[key] = entry | 880 | c.mapping[key] = entry | |
872 | } | 881 | } | |
873 | 882 | |||
874 | entry.count = 1 | 883 | entry.count = 1 | |
875 | entry.key = key | 884 | entry.key = key | |
876 | entry.options = options | 885 | entry.options = options | |
877 | entry.lines = lines | 886 | entry.lines = lines | |
878 | } | 887 | } | |
879 | 888 | |||
880 | func (c *FileCache) removeOldEntries() { | 889 | func (c *FileCache) removeOldEntries() { | |
881 | sort.Slice(c.table, func(i, j int) bool { return c.table[j].count < c.table[i].count }) | 890 | sort.Slice(c.table, func(i, j int) bool { | |
891 | return c.table[j].count < c.table[i].count | |||
892 | }) | |||
882 | 893 | |||
883 | if G.Testing { | 894 | if G.Testing { | |
884 | for _, e := range c.table { | 895 | for _, e := range c.table { | |
885 | if trace.Tracing { | 896 | if trace.Tracing { | |
886 | trace.Stepf("FileCache %q with count %d.", e.key, e.count) | 897 | trace.Stepf("FileCache %q with count %d.", e.key, e.count) | |
887 | } | 898 | } | |
888 | } | 899 | } | |
889 | } | 900 | } | |
890 | 901 | |||
891 | minCount := c.table[len(c.table)-1].count | 902 | minCount := c.table[len(c.table)-1].count | |
892 | newLen := len(c.table) | 903 | newLen := len(c.table) | |
893 | for newLen > 0 && c.table[newLen-1].count == minCount { | 904 | for newLen > 0 && c.table[newLen-1].count == minCount { | |
894 | e := c.table[newLen-1] | 905 | e := c.table[newLen-1] |
@@ -112,35 +112,39 @@ func (s *Suite) Test_cleanpath(c *check. | @@ -112,35 +112,39 @@ func (s *Suite) Test_cleanpath(c *check. | |||
112 | } | 112 | } | |
113 | 113 | |||
114 | func (s *Suite) Test_relpath(c *check.C) { | 114 | func (s *Suite) Test_relpath(c *check.C) { | |
115 | t := s.Init(c) | 115 | t := s.Init(c) | |
116 | 116 | |||
117 | t.Chdir(".") | 117 | t.Chdir(".") | |
118 | t.Check(G.Pkgsrc.topdir, equals, t.tmpdir) | 118 | t.Check(G.Pkgsrc.topdir, equals, t.tmpdir) | |
119 | 119 | |||
120 | test := func(from, to, result string) { | 120 | test := func(from, to, result string) { | |
121 | c.Check(relpath(from, to), equals, result) | 121 | c.Check(relpath(from, to), equals, result) | |
122 | } | 122 | } | |
123 | 123 | |||
124 | test("some/dir", "some/directory", "../../some/directory") | 124 | test("some/dir", "some/directory", "../../some/directory") | |
125 | test("some/directory", "some/dir", "../../some/dir") | |||
125 | 126 | |||
126 | test("category/package/.", ".", "../..") | 127 | test("category/package/.", ".", "../..") | |
127 | 128 | |||
128 | // This case is handled by one of the shortcuts that avoid file system access. | 129 | // This case is handled by one of the shortcuts that avoid file system access. | |
129 | test( | 130 | test( | |
130 | "./.", | 131 | "./.", | |
131 | "x11/frameworkintegration/../../meta-pkgs/kde/kf5.mk", | 132 | "x11/frameworkintegration/../../meta-pkgs/kde/kf5.mk", | |
132 | "meta-pkgs/kde/kf5.mk") | 133 | "meta-pkgs/kde/kf5.mk") | |
133 | 134 | |||
135 | test(".hidden/dir", ".", "../..") | |||
136 | test("dir/.hidden", ".", "../..") | |||
137 | ||||
134 | // This happens when "pkglint -r x11" is run. | 138 | // This happens when "pkglint -r x11" is run. | |
135 | G.Pkgsrc.topdir = "x11/.." | 139 | G.Pkgsrc.topdir = "x11/.." | |
136 | 140 | |||
137 | test( | 141 | test( | |
138 | "./.", | 142 | "./.", | |
139 | "x11/frameworkintegration/../../meta-pkgs/kde/kf5.mk", | 143 | "x11/frameworkintegration/../../meta-pkgs/kde/kf5.mk", | |
140 | "meta-pkgs/kde/kf5.mk") | 144 | "meta-pkgs/kde/kf5.mk") | |
141 | test( | 145 | test( | |
142 | "x11/..", | 146 | "x11/..", | |
143 | "x11/frameworkintegration/../../meta-pkgs/kde/kf5.mk", | 147 | "x11/frameworkintegration/../../meta-pkgs/kde/kf5.mk", | |
144 | "meta-pkgs/kde/kf5.mk") | 148 | "meta-pkgs/kde/kf5.mk") | |
145 | } | 149 | } | |
146 | 150 | |||
@@ -228,26 +232,45 @@ func (s *Suite) Test_getSubdirs(c *check | @@ -228,26 +232,45 @@ func (s *Suite) Test_getSubdirs(c *check | |||
228 | c.Check(os.Remove(t.File("empty/file")), check.IsNil) | 232 | c.Check(os.Remove(t.File("empty/file")), check.IsNil) | |
229 | 233 | |||
230 | c.Check(getSubdirs(t.File(".")), deepEquals, []string{"subdir"}) | 234 | c.Check(getSubdirs(t.File(".")), deepEquals, []string{"subdir"}) | |
231 | } | 235 | } | |
232 | 236 | |||
233 | func (s *Suite) Test_detab(c *check.C) { | 237 | func (s *Suite) Test_detab(c *check.C) { | |
234 | c.Check(detab(""), equals, "") | 238 | c.Check(detab(""), equals, "") | |
235 | c.Check(detab("\t"), equals, " ") | 239 | c.Check(detab("\t"), equals, " ") | |
236 | c.Check(detab("1234\t9"), equals, "1234 9") | 240 | c.Check(detab("1234\t9"), equals, "1234 9") | |
237 | c.Check(detab("1234567\t"), equals, "1234567 ") | 241 | c.Check(detab("1234567\t"), equals, "1234567 ") | |
238 | c.Check(detab("12345678\t"), equals, "12345678 ") | 242 | c.Check(detab("12345678\t"), equals, "12345678 ") | |
239 | } | 243 | } | |
240 | 244 | |||
245 | func (s *Suite) Test_alignWith(c *check.C) { | |||
246 | t := s.Init(c) | |||
247 | ||||
248 | test := func(str, other, expected string) { | |||
249 | t.Check(alignWith(str, other), equals, expected) | |||
250 | } | |||
251 | ||||
252 | // At least one tab is _always_ added. | |||
253 | test("", "", "\t") | |||
254 | ||||
255 | test("VAR=", "1234567", "VAR=\t") | |||
256 | test("VAR=", "12345678", "VAR=\t") | |||
257 | test("VAR=", "123456789", "VAR=\t\t") | |||
258 | ||||
259 | // At least one tab is added in any case, | |||
260 | // even if the other string is shorter. | |||
261 | test("1234567890=", "V=", "1234567890=\t") | |||
262 | } | |||
263 | ||||
241 | const reMkIncludeBenchmark = `^\.([\t ]*)(s?include)[\t ]+\"([^\"]+)\"[\t ]*(?:#.*)?$` | 264 | const reMkIncludeBenchmark = `^\.([\t ]*)(s?include)[\t ]+\"([^\"]+)\"[\t ]*(?:#.*)?$` | |
242 | const reMkIncludeBenchmarkPositive = `^\.([\t ]*)(s?include)[\t ]+\"(.+)\"[\t ]*(?:#.*)?$` | 265 | const reMkIncludeBenchmarkPositive = `^\.([\t ]*)(s?include)[\t ]+\"(.+)\"[\t ]*(?:#.*)?$` | |
243 | 266 | |||
244 | func Benchmark_match3_buildlink3(b *testing.B) { | 267 | func Benchmark_match3_buildlink3(b *testing.B) { | |
245 | for i := 0; i < b.N; i++ { | 268 | for i := 0; i < b.N; i++ { | |
246 | match3(".include \"../../category/package/buildlink3.mk\"", reMkIncludeBenchmark) | 269 | match3(".include \"../../category/package/buildlink3.mk\"", reMkIncludeBenchmark) | |
247 | } | 270 | } | |
248 | } | 271 | } | |
249 | 272 | |||
250 | func Benchmark_match3_bsd_pkg_mk(b *testing.B) { | 273 | func Benchmark_match3_bsd_pkg_mk(b *testing.B) { | |
251 | for i := 0; i < b.N; i++ { | 274 | for i := 0; i < b.N; i++ { | |
252 | match3(".include \"../../mk/bsd.pkg.mk\"", reMkIncludeBenchmark) | 275 | match3(".include \"../../mk/bsd.pkg.mk\"", reMkIncludeBenchmark) | |
253 | } | 276 | } | |
@@ -301,26 +324,57 @@ func emptyToNil(slice []string) []string | @@ -301,26 +324,57 @@ func emptyToNil(slice []string) []string | |||
301 | } | 324 | } | |
302 | return slice | 325 | return slice | |
303 | } | 326 | } | |
304 | 327 | |||
305 | func (s *Suite) Test_trimHspace(c *check.C) { | 328 | func (s *Suite) Test_trimHspace(c *check.C) { | |
306 | t := s.Init(c) | 329 | t := s.Init(c) | |
307 | 330 | |||
308 | t.Check(trimHspace("a b"), equals, "a b") | 331 | t.Check(trimHspace("a b"), equals, "a b") | |
309 | t.Check(trimHspace(" a b "), equals, "a b") | 332 | t.Check(trimHspace(" a b "), equals, "a b") | |
310 | t.Check(trimHspace("\ta b\t"), equals, "a b") | 333 | t.Check(trimHspace("\ta b\t"), equals, "a b") | |
311 | t.Check(trimHspace(" \t a b\t \t"), equals, "a b") | 334 | t.Check(trimHspace(" \t a b\t \t"), equals, "a b") | |
312 | } | 335 | } | |
313 | 336 | |||
337 | func (s *Suite) Test_trimCommon(c *check.C) { | |||
338 | t := s.Init(c) | |||
339 | ||||
340 | test := func(a, b, trimmedA, trimmedB string) { | |||
341 | ta, tb := trimCommon(a, b) | |||
342 | t.Check(ta, equals, trimmedA) | |||
343 | t.Check(tb, equals, trimmedB) | |||
344 | } | |||
345 | ||||
346 | test("", "", | |||
347 | "", "") | |||
348 | ||||
349 | test("equal", "equal", | |||
350 | "", "") | |||
351 | ||||
352 | test("prefixA", "prefixB", | |||
353 | "A", "B") | |||
354 | ||||
355 | test("ASuffix", "BSuffix", | |||
356 | "A", "B") | |||
357 | ||||
358 | test("PreMiddlePost", "PreCenterPost", | |||
359 | "Middle", "Center") | |||
360 | ||||
361 | test("", "b", | |||
362 | "", "b") | |||
363 | ||||
364 | test("a", "", | |||
365 | "a", "") | |||
366 | } | |||
367 | ||||
314 | func (s *Suite) Test_isLocallyModified(c *check.C) { | 368 | func (s *Suite) Test_isLocallyModified(c *check.C) { | |
315 | t := s.Init(c) | 369 | t := s.Init(c) | |
316 | 370 | |||
317 | unmodified := t.CreateFileLines("unmodified") | 371 | unmodified := t.CreateFileLines("unmodified") | |
318 | modTime := time.Unix(1136239445, 0).UTC() | 372 | modTime := time.Unix(1136239445, 0).UTC() | |
319 | 373 | |||
320 | err := os.Chtimes(unmodified, modTime, modTime) | 374 | err := os.Chtimes(unmodified, modTime, modTime) | |
321 | c.Check(err, check.IsNil) | 375 | c.Check(err, check.IsNil) | |
322 | 376 | |||
323 | st, err := os.Lstat(unmodified) | 377 | st, err := os.Lstat(unmodified) | |
324 | c.Check(err, check.IsNil) | 378 | c.Check(err, check.IsNil) | |
325 | 379 | |||
326 | // Make sure that the file system has second precision and accuracy. | 380 | // Make sure that the file system has second precision and accuracy. | |
@@ -618,26 +672,48 @@ func (s *Suite) Test_FileCache(c *check. | @@ -618,26 +672,48 @@ func (s *Suite) Test_FileCache(c *check. | |||
618 | c.Check(cache.mapping, check.HasLen, 1) | 672 | c.Check(cache.mapping, check.HasLen, 1) | |
619 | c.Check(cache.hits, equals, 7) | 673 | c.Check(cache.hits, equals, 7) | |
620 | c.Check(cache.misses, equals, 5) | 674 | c.Check(cache.misses, equals, 5) | |
621 | 675 | |||
622 | t.CheckOutputLines( | 676 | t.CheckOutputLines( | |
623 | "TRACE: FileCache \"Makefile\" with count 4.", | 677 | "TRACE: FileCache \"Makefile\" with count 4.", | |
624 | "TRACE: FileCache \"file1.mk\" with count 2.", | 678 | "TRACE: FileCache \"file1.mk\" with count 2.", | |
625 | "TRACE: FileCache \"file2.mk\" with count 2.", | 679 | "TRACE: FileCache \"file2.mk\" with count 2.", | |
626 | "TRACE: FileCache.Evict \"file2.mk\" with count 2.", | 680 | "TRACE: FileCache.Evict \"file2.mk\" with count 2.", | |
627 | "TRACE: FileCache.Evict \"file1.mk\" with count 2.", | 681 | "TRACE: FileCache.Evict \"file1.mk\" with count 2.", | |
628 | "TRACE: FileCache.Halve \"Makefile\" with count 4.") | 682 | "TRACE: FileCache.Halve \"Makefile\" with count 4.") | |
629 | } | 683 | } | |
630 | 684 | |||
685 | func (s *Suite) Test_FileCache_removeOldEntries__branch_coverage(c *check.C) { | |||
686 | t := s.Init(c) | |||
687 | ||||
688 | t.EnableTracingToLog() | |||
689 | G.Testing = false | |||
690 | ||||
691 | lines := t.NewLines("filename.mk", | |||
692 | MkRcsID) | |||
693 | cache := NewFileCache(3) | |||
694 | cache.Put("filename1.mk", 0, lines) | |||
695 | cache.Put("filename2.mk", 0, lines) | |||
696 | cache.Get("filename2.mk", 0) | |||
697 | cache.Get("filename2.mk", 0) | |||
698 | cache.Put("filename3.mk", 0, lines) | |||
699 | cache.Put("filename4.mk", 0, lines) | |||
700 | ||||
701 | t.CheckOutputLines( | |||
702 | "TRACE: FileCache.Evict \"filename3.mk\" with count 1.", | |||
703 | "TRACE: FileCache.Evict \"filename1.mk\" with count 1.", | |||
704 | "TRACE: FileCache.Halve \"filename2.mk\" with count 3.") | |||
705 | } | |||
706 | ||||
631 | func (s *Suite) Test_makeHelp(c *check.C) { | 707 | func (s *Suite) Test_makeHelp(c *check.C) { | |
632 | c.Check(makeHelp("subst"), equals, confMake+" help topic=subst") | 708 | c.Check(makeHelp("subst"), equals, confMake+" help topic=subst") | |
633 | } | 709 | } | |
634 | 710 | |||
635 | func (s *Suite) Test_hasAlnumPrefix(c *check.C) { | 711 | func (s *Suite) Test_hasAlnumPrefix(c *check.C) { | |
636 | t := s.Init(c) | 712 | t := s.Init(c) | |
637 | 713 | |||
638 | t.Check(hasAlnumPrefix(""), equals, false) | 714 | t.Check(hasAlnumPrefix(""), equals, false) | |
639 | t.Check(hasAlnumPrefix("A"), equals, true) | 715 | t.Check(hasAlnumPrefix("A"), equals, true) | |
640 | t.Check(hasAlnumPrefix(","), equals, false) | 716 | t.Check(hasAlnumPrefix(","), equals, false) | |
641 | } | 717 | } | |
642 | 718 | |||
643 | func (s *Suite) Test_Once(c *check.C) { | 719 | func (s *Suite) Test_Once(c *check.C) { |
@@ -202,26 +202,49 @@ func (s *Suite) Test_Var_Value__initial_ | @@ -202,26 +202,49 @@ func (s *Suite) Test_Var_Value__initial_ | |||
202 | 202 | |||
203 | v := NewVar("VARNAME") | 203 | v := NewVar("VARNAME") | |
204 | 204 | |||
205 | v.Write(t.NewMkLine("write.mk", 124, "VARNAME:=\toverwritten conditionally"), true, "OPSYS") | 205 | v.Write(t.NewMkLine("write.mk", 124, "VARNAME:=\toverwritten conditionally"), true, "OPSYS") | |
206 | 206 | |||
207 | // Since there is no previous value, the simplest choice is to just | 207 | // Since there is no previous value, the simplest choice is to just | |
208 | // take the first seen value, no matter if that value is conditional | 208 | // take the first seen value, no matter if that value is conditional | |
209 | // or not. | 209 | // or not. | |
210 | t.Check(v.Conditional(), equals, true) | 210 | t.Check(v.Conditional(), equals, true) | |
211 | t.Check(v.Constant(), equals, false) | 211 | t.Check(v.Constant(), equals, false) | |
212 | t.Check(v.Value(), equals, "overwritten conditionally") | 212 | t.Check(v.Value(), equals, "overwritten conditionally") | |
213 | } | 213 | } | |
214 | 214 | |||
215 | func (s *Suite) Test_Var_Write__conditional_without_variables(c *check.C) { | |||
216 | t := s.Init(c) | |||
217 | ||||
218 | mklines := t.NewMkLines("filename.mk", | |||
219 | MkRcsID, | |||
220 | ".if exists(/usr/bin)", | |||
221 | "VAR=\tvalue", | |||
222 | ".endif") | |||
223 | ||||
224 | scope := NewRedundantScope() | |||
225 | mklines.ForEach(func(mkline MkLine) { | |||
226 | if mkline.IsVarassign() { | |||
227 | t.Check(scope.get("VAR").vari.Conditional(), equals, false) | |||
228 | } | |||
229 | ||||
230 | scope.checkLine(mklines, mkline) | |||
231 | ||||
232 | if mkline.IsVarassign() { | |||
233 | t.Check(scope.get("VAR").vari.Conditional(), equals, true) | |||
234 | } | |||
235 | }) | |||
236 | } | |||
237 | ||||
215 | func (s *Suite) Test_Var_Value__conditional_write_after_unconditional(c *check.C) { | 238 | func (s *Suite) Test_Var_Value__conditional_write_after_unconditional(c *check.C) { | |
216 | t := s.Init(c) | 239 | t := s.Init(c) | |
217 | 240 | |||
218 | v := NewVar("VARNAME") | 241 | v := NewVar("VARNAME") | |
219 | 242 | |||
220 | v.Write(t.NewMkLine("write.mk", 123, "VARNAME=\tvalue"), false) | 243 | v.Write(t.NewMkLine("write.mk", 123, "VARNAME=\tvalue"), false) | |
221 | 244 | |||
222 | t.Check(v.Value(), equals, "value") | 245 | t.Check(v.Value(), equals, "value") | |
223 | 246 | |||
224 | v.Write(t.NewMkLine("write.mk", 124, "VARNAME+=\tappended"), false) | 247 | v.Write(t.NewMkLine("write.mk", 124, "VARNAME+=\tappended"), false) | |
225 | 248 | |||
226 | t.Check(v.Value(), equals, "value appended") | 249 | t.Check(v.Value(), equals, "value appended") | |
227 | 250 |
@@ -50,33 +50,33 @@ func (reg *VarTypeRegistry) DefinedExact | @@ -50,33 +50,33 @@ func (reg *VarTypeRegistry) DefinedExact | |||
50 | 50 | |||
51 | func (reg *VarTypeRegistry) DefinedCanon(varname string) bool { | 51 | func (reg *VarTypeRegistry) DefinedCanon(varname string) bool { | |
52 | return reg.Canon(varname) != nil | 52 | return reg.Canon(varname) != nil | |
53 | } | 53 | } | |
54 | 54 | |||
55 | func (reg *VarTypeRegistry) DefineType(varcanon string, vartype *Vartype) { | 55 | func (reg *VarTypeRegistry) DefineType(varcanon string, vartype *Vartype) { | |
56 | reg.types[varcanon] = vartype | 56 | reg.types[varcanon] = vartype | |
57 | } | 57 | } | |
58 | 58 | |||
59 | func (reg *VarTypeRegistry) Define(varname string, basicType *BasicType, options vartypeOptions, aclEntries ...ACLEntry) { | 59 | func (reg *VarTypeRegistry) Define(varname string, basicType *BasicType, options vartypeOptions, aclEntries ...ACLEntry) { | |
60 | m, varbase, varparam := match2(varname, `^([A-Z_.][A-Z0-9_]*|@)(|\*|\.\*)$`) | 60 | m, varbase, varparam := match2(varname, `^([A-Z_.][A-Z0-9_]*|@)(|\*|\.\*)$`) | |
61 | G.Assertf(m, "invalid variable name") | 61 | G.Assertf(m, "invalid variable name") | |
62 | 62 | |||
63 | vartype := Vartype{basicType, options, aclEntries} | 63 | vartype := NewVartype(basicType, options, aclEntries...) | |
64 | 64 | |||
65 | if varparam == "" || varparam == "*" { | 65 | if varparam == "" || varparam == "*" { | |
66 | reg.types[varbase] = &vartype | 66 | reg.types[varbase] = vartype | |
67 | } | 67 | } | |
68 | if varparam == "*" || varparam == ".*" { | 68 | if varparam == "*" || varparam == ".*" { | |
69 | reg.types[varbase+".*"] = &vartype | 69 | reg.types[varbase+".*"] = vartype | |
70 | } | 70 | } | |
71 | } | 71 | } | |
72 | 72 | |||
73 | // DefineParse defines a variable with the given type and permissions. | 73 | // DefineParse defines a variable with the given type and permissions. | |
74 | // | 74 | // | |
75 | // A permission entry looks like this: | 75 | // A permission entry looks like this: | |
76 | // "Makefile, Makefile.*, *.mk: default, set, append, use, use-loadtime" | 76 | // "Makefile, Makefile.*, *.mk: default, set, append, use, use-loadtime" | |
77 | // Only certain filenames are allowed in the part before the colon, | 77 | // Only certain filenames are allowed in the part before the colon, | |
78 | // to prevent typos. To use arbitrary filenames, prefix them with | 78 | // to prevent typos. To use arbitrary filenames, prefix them with | |
79 | // "special:". | 79 | // "special:". | |
80 | // | 80 | // | |
81 | // TODO: To be implemented: when prefixed with "infra:", the entry only | 81 | // TODO: To be implemented: when prefixed with "infra:", the entry only | |
82 | // applies to files within the pkgsrc infrastructure. Without this prefix, | 82 | // applies to files within the pkgsrc infrastructure. Without this prefix, | |
@@ -1719,27 +1719,27 @@ func (reg *VarTypeRegistry) parseACLEntr | @@ -1719,27 +1719,27 @@ func (reg *VarTypeRegistry) parseACLEntr | |||
1719 | default: | 1719 | default: | |
1720 | withoutSpecial := strings.TrimPrefix(glob, "special:") | 1720 | withoutSpecial := strings.TrimPrefix(glob, "special:") | |
1721 | if withoutSpecial == glob { | 1721 | if withoutSpecial == glob { | |
1722 | G.Assertf(false, "Invalid ACL glob %q for %q.", glob, varname) | 1722 | G.Assertf(false, "Invalid ACL glob %q for %q.", glob, varname) | |
1723 | } else { | 1723 | } else { | |
1724 | glob = withoutSpecial | 1724 | glob = withoutSpecial | |
1725 | } | 1725 | } | |
1726 | } | 1726 | } | |
1727 | for _, prev := range result { | 1727 | for _, prev := range result { | |
1728 | matched, err := path.Match(prev.glob, glob) | 1728 | matched, err := path.Match(prev.glob, glob) | |
1729 | G.AssertNil(err, "Invalid ACL pattern %q for %q", glob, varname) | 1729 | G.AssertNil(err, "Invalid ACL pattern %q for %q", glob, varname) | |
1730 | G.Assertf(!matched, "Unreachable ACL pattern %q for %q.", glob, varname) | 1730 | G.Assertf(!matched, "Unreachable ACL pattern %q for %q.", glob, varname) | |
1731 | } | 1731 | } | |
1732 | result = append(result, ACLEntry{glob, permissions}) | 1732 | result = append(result, NewACLEntry(glob, permissions)) | |
1733 | } | 1733 | } | |
1734 | } | 1734 | } | |
1735 | 1735 | |||
1736 | return result | 1736 | return result | |
1737 | } | 1737 | } | |
1738 | 1738 | |||
1739 | func (reg *VarTypeRegistry) parsePermissions(varname, globs, perms string) ACLPermissions { | 1739 | func (reg *VarTypeRegistry) parsePermissions(varname, globs, perms string) ACLPermissions { | |
1740 | if perms == "none" { | 1740 | if perms == "none" { | |
1741 | return aclpNone | 1741 | return aclpNone | |
1742 | } | 1742 | } | |
1743 | 1743 | |||
1744 | splitPerms := strings.Split(perms, ", ") | 1744 | splitPerms := strings.Split(perms, ", ") | |
1745 | var permissions ACLPermissions | 1745 | var permissions ACLPermissions |
@@ -3,26 +3,35 @@ package pkglint | @@ -3,26 +3,35 @@ package pkglint | |||
3 | import ( | 3 | import ( | |
4 | "path" | 4 | "path" | |
5 | "strings" | 5 | "strings" | |
6 | ) | 6 | ) | |
7 | 7 | |||
8 | // Vartype is a combination of a data type and a permission specification. | 8 | // Vartype is a combination of a data type and a permission specification. | |
9 | // See vardefs.go for examples, and vartypecheck.go for the implementation. | 9 | // See vardefs.go for examples, and vartypecheck.go for the implementation. | |
10 | type Vartype struct { | 10 | type Vartype struct { | |
11 | basicType *BasicType | 11 | basicType *BasicType | |
12 | options vartypeOptions | 12 | options vartypeOptions | |
13 | aclEntries []ACLEntry | 13 | aclEntries []ACLEntry | |
14 | } | 14 | } | |
15 | 15 | |||
16 | func NewVartype(basicType *BasicType, options vartypeOptions, aclEntries ...ACLEntry) *Vartype { | |||
17 | for _, aclEntry := range aclEntries { | |||
18 | _, err := path.Match(aclEntry.glob, "") | |||
19 | G.AssertNil(err, "path.Match") | |||
20 | } | |||
21 | ||||
22 | return &Vartype{basicType, options, aclEntries} | |||
23 | } | |||
24 | ||||
16 | type vartypeOptions uint8 | 25 | type vartypeOptions uint8 | |
17 | 26 | |||
18 | const ( | 27 | const ( | |
19 | // List is a compound type, consisting of several space-separated elements. | 28 | // List is a compound type, consisting of several space-separated elements. | |
20 | // Elements can have embedded spaces by enclosing them in double or single | 29 | // Elements can have embedded spaces by enclosing them in double or single | |
21 | // quotes, like in the shell. | 30 | // quotes, like in the shell. | |
22 | // | 31 | // | |
23 | // These lists are used in the :M, :S modifiers, in .for loops, | 32 | // These lists are used in the :M, :S modifiers, in .for loops, | |
24 | // and as lists of arbitrary things. | 33 | // and as lists of arbitrary things. | |
25 | List vartypeOptions = 1 << iota | 34 | List vartypeOptions = 1 << iota | |
26 | 35 | |||
27 | Guessed | 36 | Guessed | |
28 | PackageSettable | 37 | PackageSettable | |
@@ -32,26 +41,30 @@ const ( | @@ -32,26 +41,30 @@ const ( | |||
32 | 41 | |||
33 | // NeedsRationale marks variables that should always contain a comment | 42 | // NeedsRationale marks variables that should always contain a comment | |
34 | // describing why they are set. Typical examples are NOT_FOR_* variables. | 43 | // describing why they are set. Typical examples are NOT_FOR_* variables. | |
35 | NeedsRationale | 44 | NeedsRationale | |
36 | 45 | |||
37 | NoVartypeOptions = 0 | 46 | NoVartypeOptions = 0 | |
38 | ) | 47 | ) | |
39 | 48 | |||
40 | type ACLEntry struct { | 49 | type ACLEntry struct { | |
41 | glob string // Examples: "Makefile", "*.mk" | 50 | glob string // Examples: "Makefile", "*.mk" | |
42 | permissions ACLPermissions | 51 | permissions ACLPermissions | |
43 | } | 52 | } | |
44 | 53 | |||
54 | func NewACLEntry(glob string, permissions ACLPermissions) ACLEntry { | |||
55 | return ACLEntry{glob, permissions} | |||
56 | } | |||
57 | ||||
45 | type ACLPermissions uint8 | 58 | type ACLPermissions uint8 | |
46 | 59 | |||
47 | const ( | 60 | const ( | |
48 | aclpSet ACLPermissions = 1 << iota // VAR = value | 61 | aclpSet ACLPermissions = 1 << iota // VAR = value | |
49 | aclpSetDefault // VAR ?= value | 62 | aclpSetDefault // VAR ?= value | |
50 | aclpAppend // VAR += value | 63 | aclpAppend // VAR += value | |
51 | aclpUseLoadtime // OTHER := ${VAR}, OTHER != ${VAR} | 64 | aclpUseLoadtime // OTHER := ${VAR}, OTHER != ${VAR} | |
52 | aclpUse // OTHER = ${VAR} | 65 | aclpUse // OTHER = ${VAR} | |
53 | 66 | |||
54 | aclpNone ACLPermissions = 0 | 67 | aclpNone ACLPermissions = 0 | |
55 | 68 | |||
56 | aclpAllWrite = aclpSet | aclpSetDefault | aclpAppend | 69 | aclpAllWrite = aclpSet | aclpSetDefault | aclpAppend | |
57 | aclpAllRead = aclpUseLoadtime | aclpUse | 70 | aclpAllRead = aclpUseLoadtime | aclpUse | |
@@ -109,36 +122,37 @@ func (vt *Vartype) EffectivePermissions( | @@ -109,36 +122,37 @@ func (vt *Vartype) EffectivePermissions( | |||
109 | func (vt *Vartype) Union() ACLPermissions { | 122 | func (vt *Vartype) Union() ACLPermissions { | |
110 | var permissions ACLPermissions | 123 | var permissions ACLPermissions | |
111 | for _, aclEntry := range vt.aclEntries { | 124 | for _, aclEntry := range vt.aclEntries { | |
112 | permissions |= aclEntry.permissions | 125 | permissions |= aclEntry.permissions | |
113 | } | 126 | } | |
114 | return permissions | 127 | return permissions | |
115 | } | 128 | } | |
116 | 129 | |||
117 | // AlternativeFiles lists the file patterns in which all of the given | 130 | // AlternativeFiles lists the file patterns in which all of the given | |
118 | // permissions are allowed, readily formatted to be used in a diagnostic. | 131 | // permissions are allowed, readily formatted to be used in a diagnostic. | |
119 | // | 132 | // | |
120 | // If the permission is allowed nowhere, an empty string is returned. | 133 | // If the permission is allowed nowhere, an empty string is returned. | |
121 | func (vt *Vartype) AlternativeFiles(perms ACLPermissions) string { | 134 | func (vt *Vartype) AlternativeFiles(perms ACLPermissions) string { | |
122 | pos := make([]string, 0, len(vt.aclEntries)) | 135 | var pos []string | |
123 | neg := make([]string, 0, len(vt.aclEntries)) | 136 | var neg []string | |
124 | 137 | |||
125 | merge := func(slice []string) []string { | 138 | merge := func(slice []string) []string { | |
126 | di := 0 | 139 | di := 0 | |
127 | for si, early := range slice { | 140 | for si, early := range slice { | |
128 | redundant := false | 141 | redundant := false | |
129 | for _, late := range slice[si+1:] { | 142 | for _, late := range slice[si+1:] { | |
130 | matched, err := path.Match(late, early) | 143 | matched, err := path.Match(late, early) | |
131 | if err == nil && matched { | 144 | G.AssertNil(err, "path.Match") | |
145 | if matched { | |||
132 | redundant = true | 146 | redundant = true | |
133 | break | 147 | break | |
134 | } | 148 | } | |
135 | } | 149 | } | |
136 | if !redundant { | 150 | if !redundant { | |
137 | slice[di] = early | 151 | slice[di] = early | |
138 | di++ | 152 | di++ | |
139 | } | 153 | } | |
140 | } | 154 | } | |
141 | return slice[:di] | 155 | return slice[:di] | |
142 | } | 156 | } | |
143 | 157 | |||
144 | for _, aclEntry := range vt.aclEntries { | 158 | for _, aclEntry := range vt.aclEntries { |
@@ -1,44 +1,44 @@ | @@ -1,44 +1,44 @@ | |||
1 | package pkglint | 1 | package pkglint | |
2 | 2 | |||
3 | import ( | 3 | import ( | |
4 | "gopkg.in/check.v1" | 4 | "gopkg.in/check.v1" | |
5 | ) | 5 | ) | |
6 | 6 | |||
7 | func (s *Suite) Test_Vartype_EffectivePermissions(c *check.C) { | 7 | func (s *Suite) Test_Vartype_EffectivePermissions(c *check.C) { | |
8 | t := s.Init(c) | 8 | t := s.Init(c) | |
9 | 9 | |||
10 | t.SetUpVartypes() | 10 | t.SetUpVartypes() | |
11 | 11 | |||
12 | if typ := G.Pkgsrc.vartypes.Canon("PREFIX"); c.Check(typ, check.NotNil) { | 12 | if typ := G.Pkgsrc.vartypes.Canon("PREFIX"); c.Check(typ, check.NotNil) { | |
13 | c.Check(typ.basicType.name, equals, "Pathname") | 13 | c.Check(typ.basicType.name, equals, "Pathname") | |
14 | c.Check(typ.aclEntries, check.DeepEquals, []ACLEntry{{"*", aclpUse}}) | 14 | c.Check(typ.aclEntries, deepEquals, []ACLEntry{NewACLEntry("*", aclpUse)}) | |
15 | c.Check(typ.EffectivePermissions("Makefile"), equals, aclpUse) | 15 | c.Check(typ.EffectivePermissions("Makefile"), equals, aclpUse) | |
16 | c.Check(typ.EffectivePermissions("buildlink3.mk"), equals, aclpUse) | 16 | c.Check(typ.EffectivePermissions("buildlink3.mk"), equals, aclpUse) | |
17 | } | 17 | } | |
18 | 18 | |||
19 | if typ := G.Pkgsrc.vartypes.Canon("EXTRACT_OPTS"); c.Check(typ, check.NotNil) { | 19 | if typ := G.Pkgsrc.vartypes.Canon("EXTRACT_OPTS"); c.Check(typ, check.NotNil) { | |
20 | c.Check(typ.basicType.name, equals, "ShellWord") | 20 | c.Check(typ.basicType.name, equals, "ShellWord") | |
21 | c.Check(typ.EffectivePermissions("Makefile"), equals, aclpAllWrite|aclpUse) | 21 | c.Check(typ.EffectivePermissions("Makefile"), equals, aclpAllWrite|aclpUse) | |
22 | c.Check(typ.EffectivePermissions("options.mk"), equals, aclpAllWrite|aclpUse) | 22 | c.Check(typ.EffectivePermissions("options.mk"), equals, aclpAllWrite|aclpUse) | |
23 | } | 23 | } | |
24 | } | 24 | } | |
25 | 25 | |||
26 | func (s *Suite) Test_Vartype_AlternativeFiles(c *check.C) { | 26 | func (s *Suite) Test_Vartype_AlternativeFiles(c *check.C) { | |
27 | 27 | |||
28 | // test generates the files description for the "set" permission. | 28 | // test generates the files description for the "set" permission. | |
29 | test := func(rules []string, alternatives string) { | 29 | test := func(rules []string, alternatives string) { | |
30 | aclEntries := (*VarTypeRegistry).parseACLEntries(nil, "", rules...) | 30 | aclEntries := (*VarTypeRegistry).parseACLEntries(nil, "", rules...) | |
31 | vartype := Vartype{BtYesNo, NoVartypeOptions, aclEntries} | 31 | vartype := NewVartype(BtYesNo, NoVartypeOptions, aclEntries...) | |
32 | 32 | |||
33 | alternativeFiles := vartype.AlternativeFiles(aclpSet) | 33 | alternativeFiles := vartype.AlternativeFiles(aclpSet) | |
34 | 34 | |||
35 | c.Check(alternativeFiles, equals, alternatives) | 35 | c.Check(alternativeFiles, equals, alternatives) | |
36 | } | 36 | } | |
37 | 37 | |||
38 | // rules parses the given permission rules. | 38 | // rules parses the given permission rules. | |
39 | // | 39 | // | |
40 | // There is a built-in check that prevents repeated adjacent permissions. | 40 | // There is a built-in check that prevents repeated adjacent permissions. | |
41 | // The "append" permission can be added alternatively to circumvent this | 41 | // The "append" permission can be added alternatively to circumvent this | |
42 | // check, since that permission is effectively ignore by this test. | 42 | // check, since that permission is effectively ignore by this test. | |
43 | rules := func(rules ...string) []string { return rules } | 43 | rules := func(rules ...string) []string { return rules } | |
44 | 44 |