Annotation of pkgsrc/pkgtools/pkglint/files/vartypecheck.go, Revision 1.74
1.45 rillig 1: package pkglint
1.1 rillig 2:
3: import (
1.56 rillig 4: "netbsd.org/pkglint/regex"
1.63 rillig 5: "netbsd.org/pkglint/textproc"
1.1 rillig 6: "path"
7: "strings"
8: )
9:
1.47 rillig 10: // VartypeCheck groups together the various checks for variables of the different types.
1.1 rillig 11: type VartypeCheck struct {
1.58 rillig 12: MkLines *MkLines
1.66 rillig 13: MkLine *MkLine
1.39 rillig 14:
1.43 rillig 15: // The name of the variable being checked.
16: //
17: // In some cases (see WithVarnameValueMatch) it contains not the
18: // variable name but more a "description" of a part of a variable.
19: // See MachinePlatform for an example.
1.16 rillig 20: Varname string
21: Op MkOperator
22: Value string
23: ValueNoVar string
1.47 rillig 24: MkComment string // The comment including the "#".
25: Guessed bool // Whether the type definition is guessed (based on the variable name) or explicitly defined (see vardefs.go).
1.9 rillig 26: }
27:
1.47 rillig 28: func (cv *VartypeCheck) Errorf(format string, args ...interface{}) { cv.MkLine.Errorf(format, args...) }
29: func (cv *VartypeCheck) Warnf(format string, args ...interface{}) { cv.MkLine.Warnf(format, args...) }
30: func (cv *VartypeCheck) Notef(format string, args ...interface{}) { cv.MkLine.Notef(format, args...) }
31: func (cv *VartypeCheck) Explain(explanation ...string) { cv.MkLine.Explain(explanation...) }
1.43 rillig 32:
33: // Autofix returns the autofix instance belonging to the line.
34: //
35: // Usage:
36: //
37: // fix := cv.Autofix()
38: //
39: // fix.Errorf("Must not be ...")
40: // fix.Warnf("Should not be ...")
41: // fix.Notef("It is also possible ...")
42: //
43: // fix.Explain(
44: // "Explanation ...",
45: // "... end of explanation.")
46: //
47: // fix.Replace("from", "to")
48: // fix.ReplaceAfter("prefix", "from", "to")
49: // fix.ReplaceRegex(`[\t ]+`, "space", -1)
50: // fix.InsertBefore("new line")
51: // fix.InsertAfter("new line")
52: // fix.Delete()
53: // fix.Custom(func(showAutofix, autofix bool) {})
54: //
55: // fix.Apply()
1.47 rillig 56: func (cv *VartypeCheck) Autofix() *Autofix { return cv.MkLine.Autofix() }
1.43 rillig 57:
58: // WithValue returns a new VartypeCheck context by copying all
59: // fields except the value.
60: //
61: // This is typically used when calling a related check.
62: func (cv *VartypeCheck) WithValue(value string) *VartypeCheck {
63: return cv.WithVarnameValue(cv.Varname, value)
64: }
65:
66: // WithVarnameValue returns a new VartypeCheck context by copying all
67: // fields except the variable name and the value.
68: //
69: // This is typically used when checking parts of composite types.
70: func (cv *VartypeCheck) WithVarnameValue(varname, value string) *VartypeCheck {
71: newVc := *cv
72: newVc.Varname = varname
1.41 rillig 73: newVc.Value = value
1.43 rillig 74: newVc.ValueNoVar = cv.MkLine.WithoutMakeVariables(value)
75: return &newVc
76: }
77:
78: // WithVarnameValueMatch returns a new VartypeCheck context by copying all
79: // fields except the variable name, the operator (it is set to opUseMatch)
80: // and the value.
81: //
82: // This is typically used when checking parts of composite types,
1.71 rillig 83: // such as the patterns from ONLY_FOR_PLATFORM.
1.43 rillig 84: func (cv *VartypeCheck) WithVarnameValueMatch(varname, value string) *VartypeCheck {
85: newVc := *cv
86: newVc.Varname = varname
87: newVc.Op = opUseMatch
88: newVc.Value = value
89: newVc.ValueNoVar = cv.MkLine.WithoutMakeVariables(value)
1.41 rillig 90: return &newVc
1.24 rillig 91: }
92:
1.1 rillig 93: func (cv *VartypeCheck) AwkCommand() {
1.24 rillig 94: if trace.Tracing {
95: trace.Step1("Unchecked AWK command: %q", cv.Value)
1.9 rillig 96: }
1.1 rillig 97: }
98:
1.63 rillig 99: // BasicRegularExpression checks for a basic regular expression, as
100: // defined by POSIX.
101: //
102: // When they are used in a list variable (as for CHECK_FILES_SKIP), they
103: // cannot include spaces. Instead, a dot or [[:space:]] must be used.
104: // The regular expressions do not need any quotation for the shell; all
105: // quoting issues are handled by the pkgsrc infrastructure.
106: //
107: // The opposite situation is when the regular expression is part of a sed
108: // command. In such a case the shell quoting is undone before checking the
109: // regular expression, and this is where spaces and tabs can appear.
110: //
111: // TODO: Add check for EREs as well.
112: //
113: // See https://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap09.html#tag_09_03.
114: // See https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03.
1.1 rillig 115: func (cv *VartypeCheck) BasicRegularExpression() {
1.63 rillig 116:
117: // same order as in the OpenGroup spec
118: allowedAfterBackslash := textproc.NewByteSet(")({}1-9.[\\*^$")
119:
120: lexer := textproc.NewLexer(cv.ValueNoVar)
121:
122: parseCharacterClass := func() {
123: for !lexer.EOF() {
124: if lexer.SkipByte('\\') {
125: if !lexer.EOF() {
126: lexer.Skip(1)
127: }
128: } else if lexer.SkipByte(']') {
129: return
130: } else {
131: lexer.Skip(1)
132: }
133: }
134: }
135:
136: parseBackslash := func() {
137: if lexer.EOF() {
138: return
139: }
140:
141: if !lexer.TestByteSet(allowedAfterBackslash) {
142: cv.Warnf("In a basic regular expression, a backslash followed by %q is undefined.", lexer.Rest()[:1])
143: cv.Explain(
144: "Only the characters . [ \\ * ^ $ may be escaped using a backslash.",
145: "Except when the escaped character appears in a character class like [\\.a-z].",
146: "",
147: "To fix this, remove the backslash before the character.")
148: }
149: lexer.Skip(1)
150: }
151:
152: for !lexer.EOF() {
153: switch {
154: case lexer.SkipByte('['):
155: parseCharacterClass()
156:
157: case lexer.SkipByte('\\'):
158: parseBackslash()
159:
160: case lexer.Skip(1):
161: }
1.9 rillig 162: }
1.1 rillig 163: }
164:
165: func (cv *VartypeCheck) BuildlinkDepmethod() {
1.43 rillig 166: if cv.Value == cv.ValueNoVar && cv.Value != "build" && cv.Value != "full" {
167: cv.Warnf("Invalid dependency method %q. Valid methods are \"build\" or \"full\".", cv.Value)
1.1 rillig 168: }
169: }
170:
171: func (cv *VartypeCheck) Category() {
1.69 rillig 172: if cv.Value != "wip" && G.Pkgsrc.File(NewPkgsrcPath(NewPath(cv.Value)).JoinNoClean("Makefile")).IsFile() {
1.1 rillig 173: return
174: }
1.48 rillig 175:
1.16 rillig 176: switch cv.Value {
1.1 rillig 177: case
1.69 rillig 178: "chinese",
1.1 rillig 179: "gnome", "gnustep",
180: "japanese", "java",
181: "kde", "korean",
182: "linux", "local",
1.69 rillig 183: "perl5", "plan9", "python",
1.61 rillig 184: "R", "ruby",
1.1 rillig 185: "scm",
186: "tcl", "tk",
187: "windowmaker",
188: "xmms":
189: default:
1.43 rillig 190: cv.Errorf("Invalid category %q.", cv.Value)
1.1 rillig 191: }
192: }
193:
1.43 rillig 194: // CFlag is a single option to the C/C++ compiler.
195: //
196: // XXX: How can flags like "-D NAME" be handled?
1.1 rillig 197: func (cv *VartypeCheck) CFlag() {
1.16 rillig 198: if cv.Op == opUseMatch {
1.11 rillig 199: return
200: }
1.58 rillig 201:
1.16 rillig 202: cflag := cv.Value
1.1 rillig 203: switch {
1.58 rillig 204: case hasPrefix(cflag, "-l"), hasPrefix(cflag, "-L"):
205: cv.Warnf("%q is a linker flag and belong to LDFLAGS, LIBS or LDADD instead of %s.",
206: cflag, cv.Varname)
207: }
208:
209: if strings.Count(cflag, "\"")%2 != 0 {
210: cv.Warnf("Compiler flag %q has unbalanced double quotes.", cflag)
211: }
212: if strings.Count(cflag, "'")%2 != 0 {
213: cv.Warnf("Compiler flag %q has unbalanced single quotes.", cflag)
1.1 rillig 214: }
215: }
216:
1.47 rillig 217: // Comment checks for the single-line description of a package.
218: //
219: // The comment for categories is checked in CheckdirCategory since these
220: // almost never change.
1.1 rillig 221: func (cv *VartypeCheck) Comment() {
1.43 rillig 222: value := cv.Value
1.1 rillig 223:
1.47 rillig 224: // See pkgtools/url2pkg/files/url2pkg.pl, keyword "COMMENT".
225: if value == "TODO: Short description of the package" {
1.43 rillig 226: cv.Errorf("COMMENT must be set.")
1.1 rillig 227: }
1.47 rillig 228:
1.43 rillig 229: if m, first := match1(value, `^(?i)(a|an)[\t ]`); m {
230: cv.Warnf("COMMENT should not begin with %q.", first)
1.1 rillig 231: }
1.47 rillig 232:
1.30 rillig 233: if G.Pkg != nil && G.Pkg.EffectivePkgbase != "" {
234: pkgbase := G.Pkg.EffectivePkgbase
1.43 rillig 235: if hasPrefix(strings.ToLower(value), strings.ToLower(pkgbase+" ")) {
236: cv.Warnf("COMMENT should not start with the package name.")
1.54 rillig 237: cv.Explain(
1.30 rillig 238: "The COMMENT is usually displayed together with the package name.",
239: "Therefore it does not need to repeat the package name but should",
240: "provide additional information instead.")
241: }
242: }
1.47 rillig 243:
1.41 rillig 244: if matches(value, `^[a-z]`) && cv.Op == opAssign {
1.43 rillig 245: cv.Warnf("COMMENT should start with a capital letter.")
1.1 rillig 246: }
1.47 rillig 247:
1.49 rillig 248: if m, isA := match1(value, `\b(is an?)\b`); m {
249: cv.Warnf("COMMENT should not contain %q.", isA)
1.54 rillig 250: cv.Explain(
1.49 rillig 251: "The words \"package is a\" are redundant.",
252: "Since every package comment could start with them,",
253: "it is better to remove this redundancy in all cases.")
254: }
255:
1.1 rillig 256: if hasSuffix(value, ".") {
1.43 rillig 257: cv.Warnf("COMMENT should not end with a period.")
1.1 rillig 258: }
1.47 rillig 259:
1.1 rillig 260: if len(value) > 70 {
1.43 rillig 261: cv.Warnf("COMMENT should not be longer than 70 characters.")
1.1 rillig 262: }
1.47 rillig 263:
1.21 rillig 264: if hasPrefix(value, "\"") && hasSuffix(value, "\"") ||
265: hasPrefix(value, "'") && hasSuffix(value, "'") {
1.43 rillig 266: cv.Warnf("COMMENT should not be enclosed in quotes.")
1.21 rillig 267: }
1.1 rillig 268: }
269:
1.43 rillig 270: // ConfFiles checks pairs of example file, configuration file.
271: //
272: // When a package is installed, the example file is installed as usual
273: // and is then copied to its final location.
1.23 rillig 274: func (cv *VartypeCheck) ConfFiles() {
1.53 rillig 275: words := cv.MkLine.ValueFields(cv.Value)
1.23 rillig 276: if len(words)%2 != 0 {
1.43 rillig 277: cv.Warnf("Values for %s should always be pairs of paths.", cv.Varname)
1.23 rillig 278: }
1.24 rillig 279:
280: for i, word := range words {
1.44 rillig 281: cv.WithValue(word).Pathname()
1.24 rillig 282:
283: if i%2 == 1 && !hasPrefix(word, "${") {
1.43 rillig 284: cv.Warnf("The destination file %q should start with a variable reference.", word)
1.54 rillig 285: cv.Explain(
1.24 rillig 286: "Since pkgsrc can be installed in different locations, the",
287: "configuration files will also end up in different locations.",
288: "Typical variables that are used for configuration files are",
289: "PKG_SYSCONFDIR, PKG_SYSCONFBASE, PREFIX, VARBASE.")
290: }
291: }
1.23 rillig 292: }
293:
1.1 rillig 294: func (cv *VartypeCheck) Dependency() {
1.43 rillig 295: value := cv.Value
1.1 rillig 296:
1.59 rillig 297: parser := NewMkParser(nil, value)
1.9 rillig 298: deppat := parser.Dependency()
1.53 rillig 299: rest := parser.Rest()
300:
301: if deppat != nil && deppat.Wildcard == "" && (rest == "{,nb*}" || rest == "{,nb[0-9]*}") {
1.43 rillig 302: cv.Warnf("Dependency patterns of the form pkgbase>=1.0 don't need the \"{,nb*}\" extension.")
1.54 rillig 303: cv.Explain(
1.9 rillig 304: "The \"{,nb*}\" extension is only necessary for dependencies of the",
305: "form \"pkgbase-1.2\", since the pattern \"pkgbase-1.2\" doesn't match",
1.45 rillig 306: "the version \"pkgbase-1.2nb5\".",
307: "For dependency patterns using the comparison operators,",
308: "this is not necessary.")
1.9 rillig 309:
1.53 rillig 310: } else if deppat == nil && contains(cv.ValueNoVar, "{") {
1.47 rillig 311: // Don't warn about complicated patterns like "{ssh{,6}>=0,openssh>=0}"
312: // that pkglint doesn't understand as of January 2019.
313: return
314:
1.53 rillig 315: } else if deppat == nil || rest != "" {
1.43 rillig 316: cv.Warnf("Invalid dependency pattern %q.", value)
1.54 rillig 317: cv.Explain(
1.9 rillig 318: "Typical dependencies have the following forms:",
319: "",
320: "\tpackage>=2.5",
321: "\tpackage-[0-9]*",
322: "\tpackage-3.141",
323: "\tpackage>=2.71828<=3.1415")
1.1 rillig 324: return
325: }
326:
1.24 rillig 327: wildcard := deppat.Wildcard
1.9 rillig 328: if m, inside := match1(wildcard, `^\[(.*)\]\*$`); m {
329: if inside != "0-9" {
1.43 rillig 330: cv.Warnf("Only [0-9]* is allowed in the numeric part of a dependency.")
1.54 rillig 331: cv.Explain(
1.45 rillig 332: "The pattern -[0-9] means any version.",
333: "All other version patterns should be expressed using",
334: "the comparison operators like < or >= or even >=2<3.",
1.35 rillig 335: "",
336: "Patterns like -[0-7] will only match the first digit of the version",
337: "number and will not do the correct thing when the package reaches",
338: "version 10.")
1.9 rillig 339: }
340:
341: } else if m, ver, suffix := match2(wildcard, `^(\d\w*(?:\.\w+)*)(\.\*|\{,nb\*\}|\{,nb\[0-9\]\*\}|\*|)$`); m {
342: if suffix == "" {
1.43 rillig 343: cv.Warnf("Please use %q instead of %q as the version pattern.", ver+"{,nb*}", ver)
1.54 rillig 344: cv.Explain(
1.9 rillig 345: "Without the \"{,nb*}\" suffix, this version pattern only matches",
346: "package versions that don't have a PKGREVISION (which is the part",
347: "after the \"nb\").")
348: }
349: if suffix == "*" {
1.43 rillig 350: cv.Warnf("Please use %q instead of %q as the version pattern.", ver+".*", ver+"*")
1.54 rillig 351: cv.Explain(
1.9 rillig 352: "For example, the version \"1*\" also matches \"10.0.0\", which is",
353: "probably not intended.")
354: }
355:
356: } else if wildcard == "*" {
1.43 rillig 357: cv.Warnf("Please use \"%[1]s-[0-9]*\" instead of \"%[1]s-*\".", deppat.Pkgbase)
1.54 rillig 358: cv.Explain(
1.9 rillig 359: "If you use a * alone, the package specification may match other",
1.45 rillig 360: "packages that have the same prefix but a longer name.",
361: "For example, foo-* matches foo-1.2 but also",
362: "foo-client-1.2 and foo-server-1.2.")
1.9 rillig 363: }
364:
1.40 rillig 365: withoutCharClasses := replaceAll(wildcard, `\[[\d-]+\]`, "")
366: if contains(withoutCharClasses, "-") {
1.43 rillig 367: cv.Warnf("The version pattern %q should not contain a hyphen.", wildcard)
1.54 rillig 368: cv.Explain(
1.9 rillig 369: "Pkgsrc interprets package names with version numbers like this:",
370: "",
371: "\t\"foo-2.0-2.1.x\" => pkgbase \"foo\", version \"2.0-2.1.x\"",
1.1 rillig 372: "",
1.9 rillig 373: "To make the \"2.0\" above part of the package basename, the hyphen",
374: "must be omitted, so the full package name becomes \"foo2.0-2.1.x\".")
1.1 rillig 375: }
376: }
377:
378: func (cv *VartypeCheck) DependencyWithPath() {
1.43 rillig 379: value := cv.Value
1.53 rillig 380: valueNoVar := cv.ValueNoVar
381:
382: if valueNoVar == "" || valueNoVar == ":" {
383: return
1.1 rillig 384: }
385:
1.53 rillig 386: parts := cv.MkLine.ValueSplit(value, ":")
387: if len(parts) == 2 {
388: pattern := parts[0]
1.69 rillig 389: relpath := NewRelPathString(parts[1])
390: pathParts := relpath.Parts()
1.53 rillig 391: pkg := pathParts[len(pathParts)-1]
392:
1.69 rillig 393: if len(pathParts) >= 2 && pathParts[0] == ".." && pathParts[1] != ".." {
1.53 rillig 394: cv.Warnf("Dependency paths should have the form \"../../category/package\".")
395: cv.MkLine.ExplainRelativeDirs()
396: }
397:
1.69 rillig 398: if !containsVarRef(relpath.String()) {
399: MkLineChecker{cv.MkLines, cv.MkLine}.CheckRelativePkgdir(relpath)
1.53 rillig 400: }
1.1 rillig 401:
402: switch pkg {
1.39 rillig 403: case "gettext":
1.43 rillig 404: cv.Warnf("Please use USE_TOOLS+=msgfmt instead of this dependency.")
1.1 rillig 405: case "perl5":
1.43 rillig 406: cv.Warnf("Please use USE_TOOLS+=perl:run instead of this dependency.")
1.1 rillig 407: case "gmake":
1.43 rillig 408: cv.Warnf("Please use USE_TOOLS+=gmake instead of this dependency.")
1.1 rillig 409: }
410:
1.47 rillig 411: cv.WithValue(pattern).Dependency()
1.1 rillig 412:
413: return
414: }
415:
1.43 rillig 416: cv.Warnf("Invalid dependency pattern with path %q.", value)
1.54 rillig 417: cv.Explain(
1.9 rillig 418: "Examples for valid dependency patterns with path are:",
1.1 rillig 419: " package-[0-9]*:../../category/package",
420: " package>=3.41:../../category/package",
1.47 rillig 421: " package-2.718{,nb*}:../../category/package")
1.1 rillig 422: }
423:
424: func (cv *VartypeCheck) DistSuffix() {
1.47 rillig 425: if cv.Value == ".tar.gz" && !hasPrefix(cv.MkComment, "#") {
1.43 rillig 426: cv.Notef("%s is \".tar.gz\" by default, so this definition may be redundant.", cv.Varname)
1.47 rillig 427: cv.Explain(
428: "To check whether the definition is really redundant:",
429: "",
430: sprintf("\t1. run %q", bmake("show-var VARNAME="+cv.Varname)),
431: "\t2. remove this definition",
432: sprintf("\t3. run %q again", bmake("show-var VARNAME="+cv.Varname)),
433: "",
434: "If the values from step 1 and step 3 really differ, add a comment to this line",
435: "in the Makefile that this (apparently redundant) definition is really needed.")
1.1 rillig 436: }
437: }
438:
439: func (cv *VartypeCheck) EmulPlatform() {
1.12 rillig 440: const rePart = `(?:\[[^\]]+\]|[^-\[])+`
441: const rePair = `^(` + rePart + `)-(` + rePart + `)$`
1.16 rillig 442: if m, opsysPattern, archPattern := match2(cv.Value, rePair); m {
1.43 rillig 443: opsysCv := cv.WithVarnameValue("the operating system part of "+cv.Varname, opsysPattern)
1.71 rillig 444: BtEmulOpsys.checker(opsysCv)
1.1 rillig 445:
1.43 rillig 446: archCv := cv.WithVarnameValue("the hardware architecture part of "+cv.Varname, archPattern)
1.71 rillig 447: BtEmulArch.checker(archCv)
1.1 rillig 448: } else {
1.43 rillig 449: cv.Warnf("%q is not a valid emulation platform.", cv.Value)
1.54 rillig 450: cv.Explain(
1.1 rillig 451: "An emulation platform has the form <OPSYS>-<MACHINE_ARCH>.",
1.9 rillig 452: "OPSYS is the lower-case name of the operating system, and",
453: "MACHINE_ARCH is the hardware architecture.",
1.1 rillig 454: "",
1.12 rillig 455: "Examples:",
456: "* linux-i386",
457: "* irix-mipsel")
1.1 rillig 458: }
459: }
460:
1.47 rillig 461: // Enum checks an enumeration for valid values.
462: //
463: // The given allowedValues contains all allowed enum values.
464: // The given basicType is only provided to lazily access the allowed enum values as a sorted list.
465: func (cv *VartypeCheck) Enum(allowedValues map[string]bool, basicType *BasicType) {
1.39 rillig 466: if cv.Op == opUseMatch {
1.47 rillig 467: if !allowedValues[cv.Value] && cv.Value == cv.ValueNoVar {
1.39 rillig 468: canMatch := false
1.47 rillig 469: for value := range allowedValues {
1.39 rillig 470: if ok, err := path.Match(cv.Value, value); err != nil {
1.43 rillig 471: cv.Warnf("Invalid match pattern %q.", cv.Value)
1.47 rillig 472: return
1.39 rillig 473: } else if ok {
474: canMatch = true
475: }
476: }
477: if !canMatch {
1.47 rillig 478: cv.Warnf("The pattern %q cannot match any of { %s } for %s.",
479: cv.Value, basicType.AllowedEnums(), cv.Varname)
1.39 rillig 480: }
481: }
482: return
483: }
484:
1.47 rillig 485: if cv.Value == cv.ValueNoVar && !allowedValues[cv.Value] {
486: cv.Warnf("%q is not valid for %s. Use one of { %s } instead.",
487: cv.Value, cv.Varname, basicType.AllowedEnums())
1.39 rillig 488: }
489: }
490:
1.1 rillig 491: func (cv *VartypeCheck) FetchURL() {
1.63 rillig 492: fetchURL := cv.Value
493: url := strings.TrimPrefix(fetchURL, "-")
494: hyphen := condStr(len(fetchURL) > len(url), "-", "")
495: hyphenSubst := condStr(hyphen != "", ":S,^,-,", "")
1.47 rillig 496:
1.63 rillig 497: cv.WithValue(url).URL()
1.1 rillig 498:
1.64 rillig 499: trimURL := url[len(url)-len(replaceAll(url, `^\w+://`, "")):]
500: protoLen := len(url) - len(trimURL)
501:
502: for trimSiteURL, siteName := range G.Pkgsrc.MasterSiteURLToVar {
503: if !hasPrefix(trimURL, trimSiteURL) {
504: continue
505: }
506:
507: subdir := trimURL[len(trimSiteURL):]
508: if hasPrefix(trimURL, "github.com/") {
509: subdir = strings.SplitAfter(subdir, "/")[0]
510: commonPrefix := hyphen + url[:protoLen+len(trimSiteURL)+len(subdir)]
511: cv.Warnf("Please use ${%s%s:=%s} instead of %q and run %q for further instructions.",
512: siteName, hyphenSubst, subdir, commonPrefix, bmakeHelp("github"))
513: } else {
514: cv.Warnf("Please use ${%s%s:=%s} instead of %q.", siteName, hyphenSubst, subdir, hyphen+url)
1.1 rillig 515: }
1.64 rillig 516: return
1.1 rillig 517: }
1.9 rillig 518:
1.63 rillig 519: tokens := cv.MkLine.Tokenize(url, false)
1.56 rillig 520: for _, token := range tokens {
521: varUse := token.Varuse
522: if varUse == nil {
523: continue
524: }
525:
526: name := varUse.varname
527: if !hasPrefix(name, "MASTER_SITE_") {
528: continue
529: }
530:
1.31 rillig 531: if G.Pkgsrc.MasterSiteVarToURL[name] == "" {
1.66 rillig 532: if G.Pkg == nil || !G.Pkg.vars.IsDefined(name) {
1.56 rillig 533: cv.Errorf("The site %s does not exist.", name)
534: }
1.9 rillig 535: }
1.56 rillig 536:
537: if len(varUse.modifiers) != 1 || !hasPrefix(varUse.modifiers[0].Text, "=") {
538: continue
539: }
540:
541: subdir := varUse.modifiers[0].Text[1:]
1.9 rillig 542: if !hasSuffix(subdir, "/") {
1.43 rillig 543: cv.Errorf("The subdirectory in %s must end with a slash.", name)
1.9 rillig 544: }
545: }
1.63 rillig 546:
547: switch {
548: case cv.Op == opUseMatch,
549: hasSuffix(fetchURL, "/"),
550: hasSuffix(fetchURL, "="),
551: hasSuffix(fetchURL, ":"),
552: hasPrefix(fetchURL, "-"),
1.65 rillig 553: tokens[len(tokens)-1].Varuse != nil:
1.63 rillig 554: break
555:
556: default:
557: cv.Warnf("The fetch URL %q should end with a slash.", fetchURL)
558: cv.Explain(
559: "The filename from DISTFILES is appended directly to this base URL.",
560: "Therefore it should typically end with a slash, or sometimes with",
561: "an equals sign or a colon.",
562: "",
563: "To specify a full URL directly, prefix it with a hyphen, such as in",
564: "-https://example.org/distfile-1.0.tar.gz.")
565: }
1.1 rillig 566: }
567:
1.44 rillig 568: // Filename checks that filenames use only limited special characters.
1.39 rillig 569: //
1.1 rillig 570: // See http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_169
1.44 rillig 571: func (cv *VartypeCheck) Filename() {
1.59 rillig 572: valid := regex.Pattern(condStr(
1.56 rillig 573: cv.Op == opUseMatch,
574: `[%*+,\-.0-9?@A-Z\[\]_a-z~]`,
575: `[%+,\-.0-9@A-Z_a-z~]`))
576:
577: invalid := replaceAll(cv.ValueNoVar, valid, "")
578: if invalid == "" {
579: return
1.1 rillig 580: }
1.56 rillig 581:
582: cv.Warnf(
1.59 rillig 583: condStr(cv.Op == opUseMatch,
1.56 rillig 584: "The filename pattern %q contains the invalid character%s %q.",
585: "The filename %q contains the invalid character%s %q."),
586: cv.Value,
1.59 rillig 587: condStr(len(invalid) > 1, "s", ""),
1.56 rillig 588: invalid)
1.1 rillig 589: }
590:
1.63 rillig 591: func (cv *VartypeCheck) FilePattern() {
1.47 rillig 592:
593: // TODO: Decide whether to call this a "mask" or a "pattern", and use only that word everywhere.
594:
1.56 rillig 595: invalid := replaceAll(cv.ValueNoVar, `[%*+,\-.0-9?@A-Z\[\]_a-z~]`, "")
596: if invalid == "" {
597: return
1.1 rillig 598: }
1.56 rillig 599:
600: cv.Warnf("The filename pattern %q contains the invalid character%s %q.",
601: cv.Value,
1.59 rillig 602: condStr(len(invalid) > 1, "s", ""),
1.56 rillig 603: invalid)
1.1 rillig 604: }
605:
606: func (cv *VartypeCheck) FileMode() {
607: switch {
1.16 rillig 608: case cv.Value != "" && cv.ValueNoVar == "":
1.1 rillig 609: // Fine.
1.39 rillig 610: case matches(cv.Value, `^[0-7]{3,4}$`):
1.1 rillig 611: // Fine.
612: default:
1.43 rillig 613: cv.Warnf("Invalid file mode %q.", cv.Value)
1.1 rillig 614: }
615: }
616:
1.42 rillig 617: func (cv *VartypeCheck) GccReqd() {
618: cv.Version()
619:
620: if m, major := match1(cv.Value, `^([5-9])\.\d+$`); m {
1.43 rillig 621: fix := cv.Autofix()
1.42 rillig 622:
623: fix.Warnf("GCC version numbers should only contain the major version (%s).", major)
624: fix.Explain(
625: "For GCC up to 4.x, the major version consists of the first and",
626: "second number, such as 4.8.",
627: "",
628: "Starting with GCC >= 5, the major version is only the first number",
629: "such as 5 or 7.")
630: fix.Replace(cv.Value, major)
631: fix.Apply()
632: }
633: }
634:
1.13 rillig 635: func (cv *VartypeCheck) Homepage() {
1.47 rillig 636: cv.URL()
1.13 rillig 637:
1.58 rillig 638: m, wrong, sitename, subdir := match3(cv.Value, `^(\$\{(MASTER_SITE\w+)(?::=([\w\-/]+))?\})`)
639: if !m {
640: return
641: }
642:
643: baseURL := G.Pkgsrc.MasterSiteVarToURL[sitename]
644: if sitename == "MASTER_SITES" && G.Pkg != nil {
645: mkline := G.Pkg.vars.FirstDefinition("MASTER_SITES")
646: if mkline != nil {
647: if !containsVarRef(mkline.Value()) {
648: masterSites := cv.MkLine.ValueFields(mkline.Value())
649: if len(masterSites) > 0 {
650: baseURL = masterSites[0]
1.39 rillig 651: }
1.13 rillig 652: }
653: }
1.58 rillig 654: }
1.47 rillig 655:
1.58 rillig 656: fixedURL := baseURL + subdir
1.47 rillig 657:
1.58 rillig 658: fix := cv.Autofix()
659: if baseURL != "" {
660: fix.Warnf("HOMEPAGE should not be defined in terms of MASTER_SITEs. Use %s directly.", fixedURL)
661: } else {
662: fix.Warnf("HOMEPAGE should not be defined in terms of MASTER_SITEs.")
663: }
664: fix.Explain(
665: "The HOMEPAGE is a single URL, while MASTER_SITES is a list of URLs.",
666: "As long as this list has exactly one element, this works, but as",
667: "soon as another site is added, the HOMEPAGE would not be a valid",
668: "URL anymore.",
669: "",
670: "Defining MASTER_SITES=${HOMEPAGE} is ok, though.")
671: if baseURL != "" {
1.28 rillig 672: fix.Replace(wrong, fixedURL)
1.13 rillig 673: }
1.58 rillig 674: fix.Apply()
1.13 rillig 675: }
676:
1.47 rillig 677: // Identifier checks for valid identifiers in various contexts, limiting the
678: // valid characters to A-Za-z0-9_.
1.73 rillig 679: func (cv *VartypeCheck) IdentifierDirect() {
1.16 rillig 680: if cv.Op == opUseMatch {
1.47 rillig 681: if cv.Value == cv.ValueNoVar && !matches(cv.Value, `^[\w*\-?\[\]]+$`) {
1.43 rillig 682: cv.Warnf("Invalid identifier pattern %q for %s.", cv.Value, cv.Varname)
1.11 rillig 683: }
684: return
685: }
1.47 rillig 686:
1.16 rillig 687: if cv.Value != cv.ValueNoVar {
1.73 rillig 688: cv.Errorf("Identifiers for %s must not refer to other variables.", cv.Varname)
689: return
1.1 rillig 690: }
1.47 rillig 691:
1.1 rillig 692: switch {
1.47 rillig 693: case matches(cv.ValueNoVar, `^[+\-.\w]+$`):
1.1 rillig 694: // Fine.
1.47 rillig 695:
1.16 rillig 696: case cv.Value != "" && cv.ValueNoVar == "":
1.1 rillig 697: // Don't warn here.
1.47 rillig 698:
1.1 rillig 699: default:
1.43 rillig 700: cv.Warnf("Invalid identifier %q.", cv.Value)
1.1 rillig 701: }
702: }
703:
1.73 rillig 704: // Identifier checks for valid identifiers in various contexts, limiting the
705: // valid characters to A-Za-z0-9_.
706: func (cv *VartypeCheck) IdentifierIndirect() {
707: if cv.Value == cv.ValueNoVar {
708: cv.IdentifierDirect()
709: }
710: }
711:
1.1 rillig 712: func (cv *VartypeCheck) Integer() {
1.16 rillig 713: if !matches(cv.Value, `^\d+$`) {
1.43 rillig 714: cv.Warnf("Invalid integer %q.", cv.Value)
1.1 rillig 715: }
716: }
717:
718: func (cv *VartypeCheck) LdFlag() {
1.16 rillig 719: if cv.Op == opUseMatch {
1.11 rillig 720: return
721: }
1.58 rillig 722:
1.16 rillig 723: ldflag := cv.Value
1.9 rillig 724: if m, rpathFlag := match1(ldflag, `^(-Wl,(?:-R|-rpath|--rpath))`); m {
1.43 rillig 725: cv.Warnf("Please use \"${COMPILER_RPATH_FLAG}\" instead of %q.", rpathFlag)
1.1 rillig 726: return
1.9 rillig 727: }
1.1 rillig 728:
1.9 rillig 729: switch {
1.58 rillig 730: case ldflag == "-P",
731: ldflag == "-E",
732: hasPrefix(ldflag, "-D"),
733: hasPrefix(ldflag, "-U"),
734: hasPrefix(ldflag, "-I"):
735: cv.Warnf("%q is a compiler flag and belongs on CFLAGS, CPPFLAGS, CXXFLAGS or FFLAGS instead of %s.", cv.Value, cv.Varname)
1.1 rillig 736: }
737: }
738:
739: func (cv *VartypeCheck) License() {
1.53 rillig 740: (&LicenseChecker{cv.MkLines, cv.MkLine}).Check(cv.Value, cv.Op)
1.1 rillig 741: }
742:
1.13 rillig 743: func (cv *VartypeCheck) MachineGnuPlatform() {
1.16 rillig 744: if cv.Value != cv.ValueNoVar {
1.13 rillig 745: return
746: }
747:
748: const rePart = `(?:\[[^\]]+\]|[^-\[])+`
749: const rePair = `^(` + rePart + `)-(` + rePart + `)$`
750: const reTriple = `^(` + rePart + `)-(` + rePart + `)-(` + rePart + `)$`
751:
1.16 rillig 752: pattern := cv.Value
1.13 rillig 753: if matches(pattern, rePair) && hasSuffix(pattern, "*") {
754: pattern += "-*"
755: }
756:
757: if m, archPattern, vendorPattern, opsysPattern := match3(pattern, reTriple); m {
1.43 rillig 758: archCv := cv.WithVarnameValueMatch(
759: "the hardware architecture part of "+cv.Varname,
760: archPattern)
1.71 rillig 761: BtMachineGnuArch.checker(archCv)
1.13 rillig 762:
763: _ = vendorPattern
764:
1.43 rillig 765: opsysCv := cv.WithVarnameValueMatch(
766: "the operating system part of "+cv.Varname,
767: opsysPattern)
1.71 rillig 768: BtMachineGnuPlatformOpsys.checker(opsysCv)
1.13 rillig 769:
770: } else {
1.43 rillig 771: cv.Warnf("%q is not a valid platform pattern.", cv.Value)
1.54 rillig 772: cv.Explain(
1.13 rillig 773: "A platform pattern has the form <OPSYS>-<OS_VERSION>-<MACHINE_ARCH>.",
1.47 rillig 774: "Each of these components may use wildcards.",
1.13 rillig 775: "",
776: "Examples:",
777: "* NetBSD-[456].*-i386",
778: "* *-*-*",
779: "* Linux-*-*")
780: }
781: }
782:
1.66 rillig 783: func (cv *VartypeCheck) MachinePlatform() {
784: cv.MachinePlatformPattern()
785: }
786:
787: func (cv *VartypeCheck) MachinePlatformPattern() {
788: if cv.Value != cv.ValueNoVar {
789: return
790: }
791:
792: const rePart = `(?:\[[^\]]+\]|[^-\[])+`
793: const rePair = `^(` + rePart + `)-(` + rePart + `)$`
794: const reTriple = `^(` + rePart + `)-(` + rePart + `)-(` + rePart + `)$`
795:
796: pattern := cv.Value
797: if matches(pattern, rePair) && hasSuffix(pattern, "*") {
798: pattern += "-*"
799: }
800:
801: if m, opsysPattern, versionPattern, archPattern := match3(pattern, reTriple); m {
1.74 ! rillig 802: // This is cheated, but the dynamically loaded enums are not
! 803: // provided in a type registry where they could be looked up
! 804: // by a name. The following line therefore assumes that OPSYS
! 805: // is an operating system name, which sounds like a safe bet.
! 806: btOpsys := G.Pkgsrc.vartypes.types["OPSYS"].basicType
! 807:
1.66 rillig 808: opsysCv := cv.WithVarnameValueMatch("the operating system part of "+cv.Varname, opsysPattern)
1.74 ! rillig 809: btOpsys.checker(opsysCv)
1.66 rillig 810:
811: versionCv := cv.WithVarnameValueMatch("the version part of "+cv.Varname, versionPattern)
812: versionCv.Version()
813:
814: archCv := cv.WithVarnameValueMatch("the hardware architecture part of "+cv.Varname, archPattern)
1.71 rillig 815: BtMachineArch.checker(archCv)
1.66 rillig 816:
817: } else {
818: cv.Warnf("%q is not a valid platform pattern.", cv.Value)
819: cv.Explain(
820: "A platform pattern has the form <OPSYS>-<OS_VERSION>-<MACHINE_ARCH>.",
821: "Each of these components may be a shell globbing expression.",
822: "",
823: "Examples:",
824: "* NetBSD-[456].*-i386",
825: "* *-*-*",
826: "* Linux-*-*")
827: }
828: }
829:
1.1 rillig 830: func (cv *VartypeCheck) MailAddress() {
1.43 rillig 831: value := cv.Value
1.1 rillig 832:
1.43 rillig 833: m, _, domain := match2(value, `^([+\-.0-9A-Z_a-z]+)@([-\w\d.]+)$`)
834: if !m {
835: cv.Warnf("\"%s\" is not a valid mail address.", value)
836: return
837: }
1.1 rillig 838:
1.43 rillig 839: if strings.EqualFold(domain, "NetBSD.org") && domain != "NetBSD.org" {
840: cv.Warnf("Please write \"NetBSD.org\" instead of %q.", domain)
841: }
1.47 rillig 842:
1.43 rillig 843: if matches(value, `(?i)^(tech-pkg|packages)@NetBSD\.org$`) {
844: cv.Errorf("This mailing list address is obsolete. Use pkgsrc-users@NetBSD.org instead.")
1.1 rillig 845: }
846: }
847:
1.70 rillig 848: // Message is a plain string. When defining a message variable, it should
849: // not be enclosed in quotes since that is the job of the code that uses
850: // the message.
1.43 rillig 851: //
852: // Lists of messages use a different type since they need the quotes
853: // around each message; see PKG_FAIL_REASON.
1.1 rillig 854: func (cv *VartypeCheck) Message() {
1.43 rillig 855: varname, value := cv.Varname, cv.Value
1.1 rillig 856:
857: if matches(value, `^[\"'].*[\"']$`) {
1.43 rillig 858: cv.Warnf("%s should not be quoted.", varname)
1.54 rillig 859: cv.Explain(
1.1 rillig 860: "The quoting is only needed for variables which are interpreted as",
1.45 rillig 861: "multiple words (or, generally speaking, a list of something).",
862: "A single text message does not belong to this class,",
863: "since it is only printed as a whole.")
1.1 rillig 864: }
865: }
866:
1.34 rillig 867: // Option checks whether a single package option from options.mk conforms to the naming conventions.
1.1 rillig 868: func (cv *VartypeCheck) Option() {
1.43 rillig 869: value := cv.Value
1.1 rillig 870:
1.43 rillig 871: if value != cv.ValueNoVar {
1.1 rillig 872: return
873: }
874:
1.62 rillig 875: validName := regex.Pattern(`^-?([a-z][-0-9a-z_+]*)$`)
876: if cv.Op == opUseMatch {
877: validName = `^-?([a-z][*+\-0-9?\[\]_a-z]*)$`
878: }
879:
880: // TODO: Distinguish between:
881: // - a bare option name (must start with a letter),
882: // - an option selection (may have a leading hyphen).
883: m, optname := match1(value, validName)
884: if !m {
885: cv.Errorf("Invalid option name %q. Option names must start with a lowercase letter and be all-lowercase.", value)
886: return
887: }
888:
889: if !cv.MkLines.once.FirstTimeSlice("option:", optname) {
890: return
891: }
892:
893: if contains(optname, "_") {
894: cv.Warnf("Use of the underscore character in option names is deprecated.")
895: return
896: }
1.34 rillig 897:
1.62 rillig 898: if !strings.ContainsAny(optname, "*?[") {
1.47 rillig 899: if _, found := G.Pkgsrc.PkgOptions[optname]; !found {
1.43 rillig 900: cv.Warnf("Unknown option %q.", optname)
1.54 rillig 901: cv.Explain(
1.45 rillig 902: "This option is not documented in the mk/defaults/options.description file.",
903: "Please think of a brief but precise description and either",
1.9 rillig 904: "update that file yourself or suggest a description for this option",
905: "on the tech-pkg@NetBSD.org mailing list.")
1.1 rillig 906: }
907: }
908: }
909:
1.36 rillig 910: // Pathlist checks variables like the PATH environment variable.
1.1 rillig 911: func (cv *VartypeCheck) Pathlist() {
1.43 rillig 912: value := cv.Value
913:
1.36 rillig 914: // Sometimes, variables called PATH contain a single pathname,
1.58 rillig 915: // especially those with auto-guessed type from MkLine.VariableType.
1.43 rillig 916: if !contains(value, ":") && cv.Guessed {
1.44 rillig 917: cv.Pathname()
1.1 rillig 918: return
919: }
920:
1.44 rillig 921: for _, dir := range cv.MkLine.ValueSplit(value, ":") {
1.1 rillig 922:
1.44 rillig 923: dirNoVar := cv.MkLine.WithoutMakeVariables(dir)
924: if !matches(dirNoVar, `^[-0-9A-Za-z._~+%/]*$`) {
925: cv.Warnf("%q is not a valid pathname.", dir)
1.1 rillig 926: }
927:
1.44 rillig 928: if !hasPrefix(dir, "/") && !hasPrefix(dir, "${") {
929: cv.Errorf("The component %q of %s must be an absolute path.", dir, cv.Varname)
1.54 rillig 930: cv.Explain(
1.44 rillig 931: "Relative paths in the PATH variable are a security risk.",
932: "They also make the execution unreliable since they are",
933: "evaluated relative to the current directory of the process,",
934: "which means that after a \"cd\", different commands might be run.")
1.1 rillig 935: }
936: }
937: }
938:
1.63 rillig 939: // PathPattern is a shell pattern for pathnames, possibly including slashes.
1.43 rillig 940: //
1.63 rillig 941: // See FilePattern.
942: func (cv *VartypeCheck) PathPattern() {
1.56 rillig 943: invalid := replaceAll(cv.ValueNoVar, `[%*+,\-./0-9?@A-Z\[\]_a-z~]`, "")
944: if invalid == "" {
1.11 rillig 945: return
946: }
1.47 rillig 947:
1.56 rillig 948: cv.Warnf("The pathname pattern %q contains the invalid character%s %q.",
949: cv.Value,
1.59 rillig 950: condStr(len(invalid) > 1, "s", ""),
1.56 rillig 951: invalid)
1.1 rillig 952: }
953:
1.44 rillig 954: // Pathname checks for pathnames.
1.43 rillig 955: //
1.44 rillig 956: // Like Filename, but including slashes.
1.29 rillig 957: //
1.1 rillig 958: // See http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_266
1.44 rillig 959: func (cv *VartypeCheck) Pathname() {
1.59 rillig 960: valid := regex.Pattern(condStr(
1.56 rillig 961: cv.Op == opUseMatch,
962: `[%*+,\-./0-9?@A-Z\[\]_a-z~]`,
963: `[%+,\-./0-9@A-Z_a-z~]`))
964: invalid := replaceAll(cv.ValueNoVar, valid, "")
965: if invalid == "" {
1.11 rillig 966: return
967: }
1.47 rillig 968:
1.56 rillig 969: cv.Warnf(
1.59 rillig 970: condStr(cv.Op == opUseMatch,
1.56 rillig 971: "The pathname pattern %q contains the invalid character%s %q.",
972: "The pathname %q contains the invalid character%s %q."),
973: cv.Value,
1.59 rillig 974: condStr(len(invalid) > 1, "s", ""),
1.56 rillig 975: invalid)
1.1 rillig 976: }
977:
978: func (cv *VartypeCheck) Perl5Packlist() {
1.16 rillig 979: if cv.Value != cv.ValueNoVar {
1.43 rillig 980: cv.Warnf("%s should not depend on other variables.", cv.Varname)
1.1 rillig 981: }
982: }
983:
1.14 rillig 984: func (cv *VartypeCheck) Perms() {
1.16 rillig 985: if cv.Value == "${ROOT_USER}" || cv.Value == "${ROOT_GROUP}" {
1.43 rillig 986: valueName := cv.Value[2 : len(cv.Value)-1]
987: fix := cv.Autofix()
988: fix.Errorf("%s must not be used in permission definitions. Use REAL_%[1]s instead.", valueName)
989: fix.Replace(valueName, "REAL_"+valueName)
990: fix.Apply()
1.14 rillig 991: }
992: }
993:
1.40 rillig 994: func (cv *VartypeCheck) Pkgname() {
1.43 rillig 995: value := cv.Value
996:
997: if cv.Op != opUseMatch && value == cv.ValueNoVar && !matches(value, rePkgname) {
998: cv.Warnf("%q is not a valid package name.", value)
1.54 rillig 999: cv.Explain(
1.43 rillig 1000: "A valid package name has the form packagename-version, where version",
1001: "consists only of digits, letters and dots.")
1.1 rillig 1002: }
1.62 rillig 1003:
1004: if matches(value, `nb\d+$`) {
1005: cv.Errorf("The \"nb\" part of the version number belongs in PKGREVISION.")
1006: }
1.1 rillig 1007: }
1008:
1009: func (cv *VartypeCheck) PkgOptionsVar() {
1.43 rillig 1010: cv.VariableName()
1011:
1.47 rillig 1012: // TODO: Replace regex with proper VarUse.
1.16 rillig 1013: if matches(cv.Value, `\$\{PKGBASE[:\}]`) {
1.43 rillig 1014: cv.Errorf("PKGBASE must not be used in PKG_OPTIONS_VAR.")
1.54 rillig 1015: cv.Explain(
1.1 rillig 1016: "PKGBASE is defined in bsd.pkg.mk, which is included as the",
1017: "very last file, but PKG_OPTIONS_VAR is evaluated earlier.",
1018: "Use ${PKGNAME:C/-[0-9].*//} instead.")
1019: }
1.15 rillig 1020:
1021: // PR 46570, item "6. It should complain in PKG_OPTIONS_VAR is wrong"
1.16 rillig 1022: if !hasPrefix(cv.Value, "PKG_OPTIONS.") {
1.43 rillig 1023: cv.Errorf("PKG_OPTIONS_VAR must be of the form %q, not %q.", "PKG_OPTIONS.*", cv.Value)
1.15 rillig 1024: }
1.1 rillig 1025: }
1026:
1.63 rillig 1027: // Pkgpath checks a directory name relative to the top-level pkgsrc directory.
1.47 rillig 1028: //
1.1 rillig 1029: // Despite its name, it is more similar to RelativePkgDir than to RelativePkgPath.
1.63 rillig 1030: func (cv *VartypeCheck) Pkgpath() {
1031: cv.Pathname()
1032:
1.67 rillig 1033: value := cv.Value
1034: if value != cv.ValueNoVar || cv.Op == opUseMatch {
1.63 rillig 1035: return
1036: }
1037:
1.69 rillig 1038: pkgpath := NewPkgsrcPath(NewPath(value))
1.67 rillig 1039: if !G.Wip && pkgpath.HasPrefixPath("wip") {
1.70 rillig 1040: cv.Errorf("A main pkgsrc package must not depend on a pkgsrc-wip package.")
1.63 rillig 1041: }
1042:
1.68 rillig 1043: pkgdir := G.Pkgsrc.File(pkgpath)
1044: if !pkgdir.JoinNoClean("Makefile").IsFile() {
1.70 rillig 1045: cv.Errorf("There is no package in %q.",
1046: cv.MkLine.Rel(pkgdir))
1.63 rillig 1047: return
1048: }
1049:
1.67 rillig 1050: if !matches(value, `^([^./][^/]*/[^./][^/]*)$`) {
1.70 rillig 1051: cv.Errorf("%q is not a valid path to a package.", pkgpath.String())
1052: cv.Explain(
1.63 rillig 1053: "A path to a package has the form \"category/pkgbase\".",
1054: "It is relative to the pkgsrc root.")
1055: }
1.1 rillig 1056: }
1057:
1.63 rillig 1058: func (cv *VartypeCheck) Pkgrevision() {
1.16 rillig 1059: if !matches(cv.Value, `^[1-9]\d*$`) {
1.71 rillig 1060: cv.Errorf("%s must be a positive integer number.", cv.Varname)
1.1 rillig 1061: }
1.47 rillig 1062: if cv.MkLine.Basename != "Makefile" {
1.43 rillig 1063: cv.Errorf("%s only makes sense directly in the package Makefile.", cv.Varname)
1.54 rillig 1064: cv.Explain(
1.1 rillig 1065: "Usually, different packages using the same Makefile.common have",
1.9 rillig 1066: "different dependencies and will be bumped at different times (e.g.",
1067: "for shlib major bumps) and thus the PKGREVISIONs must be in the",
1.45 rillig 1068: "separate Makefiles.",
1069: "There is no practical way of having this information in a commonly used Makefile.")
1.1 rillig 1070: }
1071: }
1072:
1.43 rillig 1073: // PrefixPathname checks for a pathname relative to ${PREFIX}.
1.1 rillig 1074: func (cv *VartypeCheck) PrefixPathname() {
1.70 rillig 1075: if NewPath(cv.Value).IsAbs() {
1076: cv.Errorf("The pathname %q in %s must be relative to ${PREFIX}.",
1077: cv.Value, cv.Varname)
1078: return
1079: }
1080:
1.43 rillig 1081: if m, manSubdir := match1(cv.Value, `^man/(.+)`); m {
1082: from := "${PKGMANDIR}/" + manSubdir
1083: fix := cv.Autofix()
1084: fix.Warnf("Please use %q instead of %q.", from, cv.Value)
1085: fix.Replace(cv.Value, from)
1086: fix.Apply()
1.1 rillig 1087: }
1088: }
1089:
1090: func (cv *VartypeCheck) PythonDependency() {
1.16 rillig 1091: if cv.Value != cv.ValueNoVar {
1.43 rillig 1092: cv.Warnf("Python dependencies should not contain variables.")
1.16 rillig 1093: } else if !matches(cv.ValueNoVar, `^[+\-.0-9A-Z_a-z]+(?:|:link|:build)$`) {
1.43 rillig 1094: cv.Warnf("Invalid Python dependency %q.", cv.Value)
1.54 rillig 1095: cv.Explain(
1.9 rillig 1096: "Python dependencies must be an identifier for a package, as",
1.45 rillig 1097: "specified in lang/python/versioned_dependencies.mk.",
1098: "This identifier may be followed by :build for a build-time only",
1.9 rillig 1099: "dependency, or by :link for a run-time only dependency.")
1.1 rillig 1100: }
1101: }
1102:
1.62 rillig 1103: func (cv *VartypeCheck) RPkgName() {
1104: if cv.Op == opUseMatch {
1105: return
1106: }
1107:
1108: if cv.Value != cv.ValueNoVar {
1109: cv.Warnf("The R package name should not contain variables.")
1110: return
1111: }
1112:
1113: if hasPrefix(cv.Value, "R-") {
1114: cv.Warnf("The %s does not need the %q prefix.", cv.Varname, "R-")
1115: }
1116:
1117: invalid := replaceAll(cv.Value, `[\w\-.+]`, "")
1118: if invalid != "" {
1119: cv.Warnf("The R package name contains the invalid characters %q.", invalid)
1120: }
1121: }
1122:
1123: func (cv *VartypeCheck) RPkgVer() {
1124: value := cv.Value
1125: if cv.Op != opUseMatch && value == cv.ValueNoVar && !matches(value, `^\d[\w-.]*$`) {
1126: cv.Warnf("Invalid R version number %q.", value)
1127: }
1128: }
1129:
1.43 rillig 1130: // RelativePkgDir refers to a package directory, e.g. ../../category/pkgbase.
1.1 rillig 1131: func (cv *VartypeCheck) RelativePkgDir() {
1.70 rillig 1132: if NewPath(cv.Value).IsAbs() {
1133: cv.Errorf("The path %q must be relative.", cv.Value)
1134: return
1135: }
1136:
1.69 rillig 1137: MkLineChecker{cv.MkLines, cv.MkLine}.CheckRelativePkgdir(NewRelPathString(cv.Value))
1.1 rillig 1138: }
1139:
1.43 rillig 1140: // RelativePkgPath refers to a file or directory, e.g. ../../category/pkgbase,
1141: // ../../category/pkgbase/Makefile.
1142: //
1143: // See RelativePkgDir, which requires a directory, not a file.
1.1 rillig 1144: func (cv *VartypeCheck) RelativePkgPath() {
1.70 rillig 1145: if NewPath(cv.Value).IsAbs() {
1146: cv.Errorf("The path %q must be relative.", cv.Value)
1147: return
1148: }
1149:
1.69 rillig 1150: MkLineChecker{cv.MkLines, cv.MkLine}.CheckRelativePath(NewRelPathString(cv.Value), true)
1.1 rillig 1151: }
1152:
1153: func (cv *VartypeCheck) Restricted() {
1.16 rillig 1154: if cv.Value != "${RESTRICTED}" {
1.43 rillig 1155: cv.Warnf("The only valid value for %s is ${RESTRICTED}.", cv.Varname)
1.54 rillig 1156: cv.Explain(
1.9 rillig 1157: "These variables are used to control which files may be mirrored on",
1.45 rillig 1158: "FTP servers or CD-ROM collections.",
1159: "They are not intended to mark",
1.9 rillig 1160: "packages whose only MASTER_SITES are on ftp.NetBSD.org.")
1.1 rillig 1161: }
1162: }
1163:
1164: func (cv *VartypeCheck) SedCommands() {
1.47 rillig 1165: tokens, rest := splitIntoShellTokens(cv.MkLine.Line, cv.Value)
1.1 rillig 1166: if rest != "" {
1.47 rillig 1167: if contains(cv.MkLine.Text, "#") {
1.43 rillig 1168: cv.Errorf("Invalid shell words %q in sed commands.", rest)
1.54 rillig 1169: cv.Explain(
1.1 rillig 1170: "When sed commands have embedded \"#\" characters, they need to be",
1171: "escaped with a backslash, otherwise make(1) will interpret them as a",
1.47 rillig 1172: "comment, even if they occur in single or double quotes or whatever.")
1.1 rillig 1173: }
1174: return
1175: }
1176:
1.9 rillig 1177: ntokens := len(tokens)
1.1 rillig 1178: ncommands := 0
1.63 rillig 1179: extended := false
1180:
1181: checkSedCommand := func(quotedCommand string) {
1182: // TODO: Remember the extended flag for the whole file, especially
1183: // for SUBST_SED.* variables.
1184: if !extended {
1185: command := cv.MkLine.UnquoteShell(quotedCommand, true)
1186: if !hasPrefix(command, "s") {
1187: return
1188: }
1189:
1190: // The :C modifier is similar enough for parsing.
1191: ok, _, from, _, _ := MkVarUseModifier{"C" + command[1:]}.MatchSubst()
1192: if !ok {
1193: return
1194: }
1195:
1196: cv.WithValue(from).BasicRegularExpression()
1197: }
1198: }
1.1 rillig 1199:
1.9 rillig 1200: for i := 0; i < ntokens; i++ {
1201: token := tokens[i]
1.1 rillig 1202:
1203: switch {
1.9 rillig 1204: case token == "-e":
1.63 rillig 1205: if i+1 >= ntokens {
1.43 rillig 1206: cv.Errorf("The -e option to sed requires an argument.")
1.63 rillig 1207: break
1.1 rillig 1208: }
1.63 rillig 1209:
1210: // Check the real sed command here.
1211: i++
1212: ncommands++
1213: if ncommands > 1 {
1.71 rillig 1214: cv.Warnf("Each sed command should appear in an assignment of its own.")
1.63 rillig 1215: cv.Explain(
1216: "For example, instead of",
1217: " SUBST_SED.foo+= -e s,command1,, -e s,command2,,",
1218: "use",
1219: " SUBST_SED.foo+= -e s,command1,,",
1220: " SUBST_SED.foo+= -e s,command2,,",
1221: "",
1222: "This way, short sed commands cannot be hidden at the end of a line.")
1223: }
1224:
1225: checkSedCommand(tokens[i])
1226:
1.9 rillig 1227: case token == "-E":
1.1 rillig 1228: // Switch to extended regular expressions mode.
1.63 rillig 1229: extended = true
1.1 rillig 1230:
1.9 rillig 1231: case token == "-n":
1.1 rillig 1232: // Don't print lines per default.
1233:
1.47 rillig 1234: case matches(token, `^["']?(\d+|/.*/)?s`):
1.63 rillig 1235: // TODO: "prefix with" instead of "use".
1.43 rillig 1236: cv.Notef("Please always use \"-e\" in sed commands, even if there is only one substitution.")
1.63 rillig 1237: checkSedCommand(token)
1.1 rillig 1238:
1239: default:
1.43 rillig 1240: cv.Warnf("Unknown sed command %q.", token)
1.1 rillig 1241: }
1242: }
1243: }
1244:
1245: func (cv *VartypeCheck) ShellCommand() {
1.58 rillig 1246: if cv.Op == opUseMatch || cv.Op == opUseCompare || cv.Op == opAssignAppend {
1.11 rillig 1247: return
1248: }
1.9 rillig 1249: setE := true
1.54 rillig 1250: ck := NewShellLineChecker(cv.MkLines, cv.MkLine)
1251: ck.checkVarUse = false
1252: ck.CheckShellCommand(cv.Value, &setE, RunTime)
1.9 rillig 1253: }
1254:
1.44 rillig 1255: // ShellCommands checks for zero or more shell commands, each terminated with a semicolon.
1.9 rillig 1256: func (cv *VartypeCheck) ShellCommands() {
1.54 rillig 1257: ck := NewShellLineChecker(cv.MkLines, cv.MkLine)
1258: ck.checkVarUse = false
1259: ck.CheckShellCommands(cv.Value, RunTime)
1.1 rillig 1260: }
1261:
1262: func (cv *VartypeCheck) ShellWord() {
1.54 rillig 1263: ck := NewShellLineChecker(cv.MkLines, cv.MkLine)
1264: ck.checkVarUse = false
1265: ck.CheckWord(cv.Value, true, RunTime)
1.1 rillig 1266: }
1267:
1268: func (cv *VartypeCheck) Stage() {
1.16 rillig 1269: if !matches(cv.Value, `^(?:pre|do|post)-(?:extract|patch|configure|build|test|install)`) {
1.43 rillig 1270: cv.Warnf("Invalid stage name %q. Use one of {pre,do,post}-{extract,patch,configure,build,test,install}.", cv.Value)
1.1 rillig 1271: }
1272: }
1273:
1.32 rillig 1274: // Tool checks for tool names like "awk", "m4:pkgsrc", "digest:bootstrap".
1.47 rillig 1275: //
1276: // TODO: Distinguish between Tool and ToolDependency.
1.1 rillig 1277: func (cv *VartypeCheck) Tool() {
1.16 rillig 1278: if cv.Varname == "TOOLS_NOOP" && cv.Op == opAssignAppend {
1.1 rillig 1279: // no warning for package-defined tool definitions
1280:
1.16 rillig 1281: } else if m, toolname, tooldep := match2(cv.Value, `^([-\w]+|\[)(?::(\w+))?$`); m {
1.53 rillig 1282: if tool, _ := G.Tool(cv.MkLines, toolname, RunTime); tool == nil {
1.43 rillig 1283: cv.Errorf("Unknown tool %q.", toolname)
1.1 rillig 1284: }
1.39 rillig 1285:
1.1 rillig 1286: switch tooldep {
1.33 rillig 1287: case "", "bootstrap", "build", "pkgsrc", "run", "test":
1.1 rillig 1288: default:
1.47 rillig 1289: cv.Errorf("Invalid tool dependency %q. Use one of \"bootstrap\", \"build\", \"pkgsrc\", \"run\" or \"test\".", tooldep)
1.1 rillig 1290: }
1.47 rillig 1291:
1.35 rillig 1292: } else if cv.Op != opUseMatch && cv.Value == cv.ValueNoVar {
1.47 rillig 1293: cv.Errorf("Invalid tool dependency %q.", cv.Value)
1.54 rillig 1294: cv.Explain(
1.35 rillig 1295: "A tool dependency typically looks like \"sed\" or \"sed:run\".")
1.1 rillig 1296: }
1297: }
1298:
1.40 rillig 1299: // Unknown doesn't check for anything.
1.52 rillig 1300: //
1301: // Used for:
1302: // - infrastructure variables that are not in vardefs.go
1303: // - other variables whose type is unknown or uninteresting enough to
1304: // warrant the creation of a specialized type
1.18 rillig 1305: func (cv *VartypeCheck) Unknown() {
1306: // Do nothing.
1.1 rillig 1307: }
1308:
1309: func (cv *VartypeCheck) URL() {
1.43 rillig 1310: value := cv.Value
1.1 rillig 1311:
1.16 rillig 1312: if value == "" && hasPrefix(cv.MkComment, "#") {
1.1 rillig 1313: // Ok
1314:
1315: } else if containsVarRef(value) {
1316: // No further checks
1317:
1.9 rillig 1318: } else if m, _, host, _, _ := match4(value, `^(https?|ftp|gopher)://([-0-9A-Za-z.]+)(?::(\d+))?/([-%&+,./0-9:;=?@A-Z_a-z~]|#)*$`); m {
1.1 rillig 1319: if matches(host, `(?i)\.NetBSD\.org$`) && !matches(host, `\.NetBSD\.org$`) {
1.43 rillig 1320: fix := cv.Autofix()
1.39 rillig 1321: fix.Warnf("Please write NetBSD.org instead of %s.", host)
1322: fix.ReplaceRegex(`(?i)NetBSD\.org`, "NetBSD.org", 1)
1323: fix.Apply()
1.1 rillig 1324: }
1325:
1326: } else if m, scheme, _, absPath := match3(value, `^([0-9A-Za-z]+)://([^/]+)(.*)$`); m {
1327: switch {
1328: case scheme != "ftp" && scheme != "http" && scheme != "https" && scheme != "gopher":
1.43 rillig 1329: cv.Warnf("%q is not a valid URL. Only ftp, gopher, http, and https URLs are allowed here.", value)
1.1 rillig 1330:
1331: case absPath == "":
1.43 rillig 1332: cv.Notef("For consistency, please add a trailing slash to %q.", value)
1.1 rillig 1333:
1334: default:
1.43 rillig 1335: cv.Warnf("%q is not a valid URL.", value)
1.1 rillig 1336: }
1337:
1338: } else {
1.43 rillig 1339: cv.Warnf("%q is not a valid URL.", value)
1.1 rillig 1340: }
1341: }
1342:
1343: func (cv *VartypeCheck) UserGroupName() {
1.72 rillig 1344: value := cv.Value
1345: if value != cv.ValueNoVar {
1346: return
1347: }
1348: invalid := invalidCharacters(value, textproc.NewByteSet("---0-9_a-z"))
1349: if invalid != "" {
1350: cv.Warnf("User or group name %q contains invalid characters: %s",
1351: value, invalid)
1352: return
1353: }
1354:
1355: if hasPrefix(value, "-") {
1356: cv.Errorf("User or group name %q must not start with a hyphen.", value)
1357: }
1358: if hasSuffix(value, "-") {
1359: cv.Errorf("User or group name %q must not end with a hyphen.", value)
1.1 rillig 1360: }
1361: }
1362:
1.46 rillig 1363: // VariableName checks that the value is a valid variable name to be used in Makefiles.
1.16 rillig 1364: func (cv *VartypeCheck) VariableName() {
1.58 rillig 1365: // TODO: sync with MkParser.Varname
1.16 rillig 1366: if cv.Value == cv.ValueNoVar && !matches(cv.Value, `^[A-Z_][0-9A-Z_]*(?:[.].*)?$`) {
1.43 rillig 1367: cv.Warnf("%q is not a valid variable name.", cv.Value)
1.54 rillig 1368: cv.Explain(
1.1 rillig 1369: "Variable names are restricted to only uppercase letters and the",
1370: "underscore in the basename, and arbitrary characters in the",
1371: "parameterized part, following the dot.",
1372: "",
1373: "Examples:",
1.47 rillig 1374: "* PKGNAME",
1375: "* PKG_OPTIONS.gtk+-2.0")
1.1 rillig 1376: }
1377: }
1378:
1.58 rillig 1379: func (cv *VartypeCheck) VariableNamePattern() {
1380: if cv.Value != cv.ValueNoVar {
1381: return
1382: }
1383:
1384: // TODO: sync with MkParser.Varname
1.65 rillig 1385: if matches(cv.Value, `^[*A-Z_.][*0-9A-Z_]*(?:[.].*)?$`) {
1.58 rillig 1386: return
1387: }
1388:
1389: cv.Warnf("%q is not a valid variable name pattern.", cv.Value)
1390: cv.Explain(
1391: "Variable names are restricted to only uppercase letters and the",
1392: "underscore in the basename, and arbitrary characters in the",
1393: "parameterized part, following the dot.",
1394: "",
1395: "In addition to these characters, variable name patterns may use",
1396: "the * placeholder.",
1397: "",
1398: "Examples:",
1399: "* PKGNAME",
1400: "* PKG_OPTIONS.gtk+-2.0")
1401: }
1402:
1.1 rillig 1403: func (cv *VartypeCheck) Version() {
1.40 rillig 1404: value := cv.Value
1405:
1.16 rillig 1406: if cv.Op == opUseMatch {
1.40 rillig 1407: if value != "*" && !matches(value, `^[\d?\[][\w\-.*?\[\]]+$`) {
1.43 rillig 1408: cv.Warnf("Invalid version number pattern %q.", value)
1.40 rillig 1409: return
1410: }
1411:
1412: const digit = `(?:\d|\[[\d-]+\])`
1413: const alnum = `(?:\w|\[[\d-]+\])`
1414: if m, ver, suffix := match2(value, `^(`+digit+alnum+`*(?:\.`+alnum+`+)*)(\.\*|\*|)$`); m {
1415: if suffix == "*" && ver != "[0-9]" {
1.43 rillig 1416: cv.Warnf("Please use %q instead of %q as the version pattern.", ver+".*", ver+"*")
1.54 rillig 1417: cv.Explain(
1.40 rillig 1418: "For example, the version \"1*\" also matches \"10.0.0\", which is",
1419: "probably not intended.")
1420: }
1.11 rillig 1421: }
1.40 rillig 1422: return
1423: }
1424:
1425: if value == cv.ValueNoVar && !matches(value, `^\d[\w.]*$`) {
1.43 rillig 1426: cv.Warnf("Invalid version number %q.", value)
1.1 rillig 1427: }
1428: }
1429:
1430: func (cv *VartypeCheck) WrapperReorder() {
1.16 rillig 1431: if !matches(cv.Value, `^reorder:l:([\w\-]+):([\w\-]+)$`) {
1.43 rillig 1432: cv.Warnf("Unknown wrapper reorder command %q.", cv.Value)
1.1 rillig 1433: }
1434: }
1435:
1436: func (cv *VartypeCheck) WrapperTransform() {
1.16 rillig 1437: cmd := cv.Value
1.47 rillig 1438: switch {
1439: case hasPrefix(cmd, "rm:-"),
1440: matches(cmd, `^(R|l|rpath):([^:]+):(.+)$`),
1441: matches(cmd, `^'?(opt|rename|rm-optarg|rmdir):.*$`),
1442: cmd == "-e",
1443: matches(cmd, `^["']?s[|:,]`):
1444: break
1445:
1446: default:
1447: cv.Warnf("Unknown wrapper transform command %q.", cmd)
1.1 rillig 1448: }
1449: }
1450:
1451: func (cv *VartypeCheck) WrkdirSubdirectory() {
1.47 rillig 1452: cv.Pathname()
1.1 rillig 1453: }
1454:
1.44 rillig 1455: // WrksrcSubdirectory checks a directory relative to ${WRKSRC},
1456: // for use in CONFIGURE_DIRS and similar variables.
1.1 rillig 1457: func (cv *VartypeCheck) WrksrcSubdirectory() {
1.16 rillig 1458: if m, _, rest := match2(cv.Value, `^(\$\{WRKSRC\})(?:/(.*))?`); m {
1.1 rillig 1459: if rest == "" {
1460: rest = "."
1461: }
1.47 rillig 1462:
1463: fix := cv.Autofix()
1464: fix.Notef("You can use %q instead of %q.", rest, cv.Value)
1465: fix.Explain(
1.9 rillig 1466: "These directories are interpreted relative to ${WRKSRC}.")
1.47 rillig 1467: fix.Replace(cv.Value, rest)
1468: fix.Apply()
1.1 rillig 1469:
1.47 rillig 1470: } else if cv.ValueNoVar == "" {
1.1 rillig 1471: // The value of another variable
1472:
1.16 rillig 1473: } else if !matches(cv.ValueNoVar, `^(?:\.|[0-9A-Za-z_@][-0-9A-Za-z_@./+]*)$`) {
1.43 rillig 1474: cv.Warnf("%q is not a valid subdirectory of ${WRKSRC}.", cv.Value)
1.47 rillig 1475: cv.Explain(
1476: "WRKSRC should be defined so that there is no need to do anything",
1477: "outside of this directory.",
1478: "",
1479: "Example:",
1480: "",
1481: "\tWRKSRC=\t${WRKDIR}",
1482: "\tCONFIGURE_DIRS=\t${WRKSRC}/lib ${WRKSRC}/src",
1483: "\tBUILD_DIRS=\t${WRKSRC}/lib ${WRKSRC}/src ${WRKSRC}/cmd",
1484: "",
1485: seeGuide("Directories used during the build process", "build.builddirs"))
1.1 rillig 1486: }
1.47 rillig 1487:
1488: // TODO: Check for ${WRKSRC}/.. or a simple .., like in checkTextWrksrcDotDot.
1.1 rillig 1489: }
1490:
1491: func (cv *VartypeCheck) Yes() {
1.16 rillig 1492: switch cv.Op {
1.11 rillig 1493: case opUseMatch:
1.43 rillig 1494: cv.Warnf("%s should only be used in a \".if defined(...)\" condition.", cv.Varname)
1.54 rillig 1495: cv.Explain(
1.11 rillig 1496: "This variable can have only two values: defined or undefined.",
1497: "When it is defined, it means \"yes\", even when its value is",
1498: "\"no\" or the empty string.",
1499: "",
1500: "Therefore, it should not be checked by comparing its value",
1501: "but using \".if defined(VARNAME)\" alone.")
1502:
1503: default:
1.72 rillig 1504: if cv.Value != "YES" && cv.Value != "yes" {
1.43 rillig 1505: cv.Warnf("%s should be set to YES or yes.", cv.Varname)
1.54 rillig 1506: cv.Explain(
1.45 rillig 1507: "This variable means \"yes\" if it is defined, and \"no\" if it is undefined.",
1508: "Even when it has the value \"no\", this means \"yes\".",
1509: "Therefore when it is defined, its value should correspond to its meaning.")
1.11 rillig 1510: }
1.1 rillig 1511: }
1512: }
1513:
1.52 rillig 1514: // YesNo checks for variables that can be set to either yes or no. Undefined
1515: // means no.
1516: //
1517: // Most of these variables use the lowercase yes/no variant. Some use the
1518: // uppercase YES/NO, and the mixed forms Yes/No are practically never seen.
1519: // Testing these variables using the however-mixed pattern is done solely
1520: // because writing this pattern is shorter than repeating the variable name.
1.1 rillig 1521: func (cv *VartypeCheck) YesNo() {
1.13 rillig 1522: const (
1523: yes1 = "[yY][eE][sS]"
1524: yes2 = "[Yy][Ee][Ss]"
1525: no1 = "[nN][oO]"
1526: no2 = "[Nn][Oo]"
1527: )
1.16 rillig 1528: if cv.Op == opUseMatch {
1529: switch cv.Value {
1.13 rillig 1530: case yes1, yes2, no1, no2:
1.52 rillig 1531: break
1.11 rillig 1532: default:
1.43 rillig 1533: cv.Warnf("%s should be matched against %q or %q, not %q.", cv.Varname, yes1, no1, cv.Value)
1.11 rillig 1534: }
1.20 rillig 1535: } else if cv.Op == opUseCompare {
1.43 rillig 1536: cv.Warnf("%s should be matched against %q or %q, not compared with %q.", cv.Varname, yes1, no1, cv.Value)
1.54 rillig 1537: cv.Explain(
1.13 rillig 1538: "The yes/no value can be written in either upper or lower case, and",
1.45 rillig 1539: "both forms are actually used.",
1540: "As long as this is the case, when checking the variable value,",
1541: "both must be accepted.")
1.72 rillig 1542: } else if !matches(cv.Value, `^(?:YES|yes|NO|no)$`) {
1.43 rillig 1543: cv.Warnf("%s should be set to YES, yes, NO, or no.", cv.Varname)
1.1 rillig 1544: }
1545: }
1546:
1.3 rillig 1547: func (cv *VartypeCheck) YesNoIndirectly() {
1.16 rillig 1548: if cv.ValueNoVar != "" {
1.11 rillig 1549: cv.YesNo()
1.1 rillig 1550: }
1551: }
CVSweb <webmaster@jp.NetBSD.org>