Annotation of pkgsrc/pkgtools/pkglint/files/vartypecheck.go, Revision 1.9
1.1 rillig 1: package main
2:
3: import (
4: "path"
5: "strings"
6: )
7:
8: type VartypeCheck struct {
1.9 ! rillig 9: mkline *MkLine
1.1 rillig 10: line *Line
11: varname string
1.9 ! rillig 12: op MkOperator
1.1 rillig 13: value string
14: valueNovar string
15: comment string
16: listContext bool
1.9 ! rillig 17: guessed bool // Whether the type definition is guessed (based on the variable name) or explicitly defined (see vardefs.go).
! 18: }
! 19:
! 20: type MkOperator uint8
! 21:
! 22: const (
! 23: opAssign MkOperator = iota // =
! 24: opAssignShell // !=
! 25: opAssignEval // :=
! 26: opAssignAppend // +=
! 27: opAssignDefault // ?=
! 28: opUseLoadtime
! 29: opUse
! 30: )
! 31:
! 32: func NewMkOperator(op string) MkOperator {
! 33: switch op {
! 34: case "=":
! 35: return opAssign
! 36: case "!=":
! 37: return opAssignShell
! 38: case ":=":
! 39: return opAssignEval
! 40: case "+=":
! 41: return opAssignAppend
! 42: case "?=":
! 43: return opAssignDefault
! 44: }
! 45: return opAssign
! 46: }
! 47:
! 48: func (op MkOperator) String() string {
! 49: return [...]string{"=", "!=", ":=", "+=", "?=", "use", "use-loadtime"}[op]
1.1 rillig 50: }
51:
52: func (cv *VartypeCheck) AwkCommand() {
1.9 ! rillig 53: if G.opts.DebugUnchecked {
! 54: cv.line.Debug1("Unchecked AWK command: %q", cv.value)
! 55: }
1.1 rillig 56: }
57:
58: func (cv *VartypeCheck) BasicRegularExpression() {
1.9 ! rillig 59: if G.opts.DebugUnchecked {
! 60: cv.line.Debug1("Unchecked basic regular expression: %q", cv.value)
! 61: }
1.1 rillig 62: }
63:
64: func (cv *VartypeCheck) BuildlinkDepmethod() {
65: if !containsVarRef(cv.value) && cv.value != "build" && cv.value != "full" {
1.9 ! rillig 66: cv.line.Warn1("Invalid dependency method %q. Valid methods are \"build\" or \"full\".", cv.value)
1.1 rillig 67: }
68: }
69:
70: func (cv *VartypeCheck) Category() {
1.9 ! rillig 71: if fileExists(G.CurrentDir + "/" + G.CurPkgsrcdir + "/" + cv.value + "/Makefile") {
1.1 rillig 72: return
73: }
74: switch cv.value {
75: case
76: "chinese", "crosspkgtools",
77: "gnome", "gnustep",
78: "japanese", "java",
79: "kde", "korean",
80: "linux", "local",
81: "packages", "perl5", "plan9", "python",
82: "ruby",
83: "scm",
84: "tcl", "tk",
85: "windowmaker",
86: "xmms":
87: default:
1.9 ! rillig 88: cv.line.Error1("Invalid category %q.", cv.value)
1.1 rillig 89: }
90: }
91:
92: // A single option to the C/C++ compiler.
93: func (cv *VartypeCheck) CFlag() {
1.9 ! rillig 94: cflag := cv.value
1.1 rillig 95: switch {
1.9 ! rillig 96: case matches(cflag, `^-[DILOUWfgm]`),
! 97: hasPrefix(cflag, "-std="),
! 98: cflag == "-c99",
! 99: cflag == "-c",
! 100: cflag == "-no-integrated-as",
! 101: cflag == "-pthread",
! 102: hasPrefix(cflag, "`") && hasSuffix(cflag, "`"),
! 103: containsVarRef(cflag):
! 104: return
! 105: case hasPrefix(cflag, "-"):
! 106: cv.line.Warn1("Unknown compiler flag %q.", cflag)
! 107: default:
! 108: cv.line.Warn1("Compiler flag %q should start with a hyphen.", cflag)
1.1 rillig 109: }
110: }
111:
112: // The single-line description of the package.
113: func (cv *VartypeCheck) Comment() {
114: line, value := cv.line, cv.value
115:
1.9 ! rillig 116: if value == "TODO: Short description of the package" { // See pkgtools/url2pkg/files/url2pkg.pl, keyword "COMMENT".
! 117: line.Error0("COMMENT must be set.")
1.1 rillig 118: }
119: if m, first := match1(value, `^(?i)(a|an)\s`); m {
1.9 ! rillig 120: line.Warn1("COMMENT should not begin with %q.", first)
1.1 rillig 121: }
122: if matches(value, `^[a-z]`) {
1.9 ! rillig 123: line.Warn0("COMMENT should start with a capital letter.")
1.1 rillig 124: }
125: if hasSuffix(value, ".") {
1.9 ! rillig 126: line.Warn0("COMMENT should not end with a period.")
1.1 rillig 127: }
128: if len(value) > 70 {
1.9 ! rillig 129: line.Warn0("COMMENT should not be longer than 70 characters.")
1.1 rillig 130: }
131: }
132:
133: func (cv *VartypeCheck) Dependency() {
134: line, value := cv.line, cv.value
135:
1.9 ! rillig 136: parser := NewParser(value)
! 137: deppat := parser.Dependency()
! 138: if deppat != nil && deppat.wildcard == "" && (parser.Rest() == "{,nb*}" || parser.Rest() == "{,nb[0-9]*}") {
! 139: line.Warn0("Dependency patterns of the form pkgbase>=1.0 don't need the \"{,nb*}\" extension.")
! 140: Explain4(
! 141: "The \"{,nb*}\" extension is only necessary for dependencies of the",
! 142: "form \"pkgbase-1.2\", since the pattern \"pkgbase-1.2\" doesn't match",
! 143: "the version \"pkgbase-1.2nb5\". For dependency patterns using the",
! 144: "comparison operators, this is not necessary.")
! 145:
! 146: } else if deppat == nil || !parser.EOF() {
! 147: line.Warn1("Unknown dependency pattern %q.", value)
! 148: Explain(
! 149: "Typical dependencies have the following forms:",
! 150: "",
! 151: "\tpackage>=2.5",
! 152: "\tpackage-[0-9]*",
! 153: "\tpackage-3.141",
! 154: "\tpackage>=2.71828<=3.1415")
1.1 rillig 155: return
156: }
157:
1.9 ! rillig 158: wildcard := deppat.wildcard
! 159: if m, inside := match1(wildcard, `^\[(.*)\]\*$`); m {
! 160: if inside != "0-9" {
! 161: line.Warn0("Only [0-9]* is allowed in the numeric part of a dependency.")
! 162: }
! 163:
! 164: } else if m, ver, suffix := match2(wildcard, `^(\d\w*(?:\.\w+)*)(\.\*|\{,nb\*\}|\{,nb\[0-9\]\*\}|\*|)$`); m {
! 165: if suffix == "" {
! 166: line.Warn2("Please use %q instead of %q as the version pattern.", ver+"{,nb*}", ver)
! 167: Explain3(
! 168: "Without the \"{,nb*}\" suffix, this version pattern only matches",
! 169: "package versions that don't have a PKGREVISION (which is the part",
! 170: "after the \"nb\").")
! 171: }
! 172: if suffix == "*" {
! 173: line.Warn2("Please use %q instead of %q as the version pattern.", ver+".*", ver+"*")
! 174: Explain2(
! 175: "For example, the version \"1*\" also matches \"10.0.0\", which is",
! 176: "probably not intended.")
! 177: }
! 178:
! 179: } else if wildcard == "*" {
! 180: line.Warn1("Please use \"%[1]s-[0-9]*\" instead of \"%[1]s-*\".", deppat.pkgbase)
! 181: Explain3(
! 182: "If you use a * alone, the package specification may match other",
! 183: "packages that have the same prefix, but a longer name. For example,",
! 184: "foo-* matches foo-1.2, but also foo-client-1.2 and foo-server-1.2.")
! 185: }
! 186:
! 187: if nocclasses := regcomp(`\[[\d-]+\]`).ReplaceAllString(wildcard, ""); contains(nocclasses, "-") {
! 188: line.Warn1("The version pattern %q should not contain a hyphen.", wildcard)
! 189: Explain(
! 190: "Pkgsrc interprets package names with version numbers like this:",
! 191: "",
! 192: "\t\"foo-2.0-2.1.x\" => pkgbase \"foo\", version \"2.0-2.1.x\"",
1.1 rillig 193: "",
1.9 ! rillig 194: "To make the \"2.0\" above part of the package basename, the hyphen",
! 195: "must be omitted, so the full package name becomes \"foo2.0-2.1.x\".")
1.1 rillig 196: }
197: }
198:
199: func (cv *VartypeCheck) DependencyWithPath() {
200: line, value := cv.line, cv.value
201: if value != cv.valueNovar {
202: return // It's probably not worth checking this.
203: }
204:
1.9 ! rillig 205: if m, pattern, relpath, pkg := match3(value, `(.*):(\.\./\.\./[^/]+/([^/]+))$`); m {
! 206: cv.mkline.CheckRelativePkgdir(relpath)
1.1 rillig 207:
208: switch pkg {
209: case "msgfmt", "gettext":
1.9 ! rillig 210: line.Warn0("Please use USE_TOOLS+=msgfmt instead of this dependency.")
1.1 rillig 211: case "perl5":
1.9 ! rillig 212: line.Warn0("Please use USE_TOOLS+=perl:run instead of this dependency.")
1.1 rillig 213: case "gmake":
1.9 ! rillig 214: line.Warn0("Please use USE_TOOLS+=gmake instead of this dependency.")
1.1 rillig 215: }
216:
1.9 ! rillig 217: cv.mkline.CheckVartypePrimitive(cv.varname, CheckvarDependency, cv.op, pattern, cv.comment, cv.listContext, cv.guessed)
1.1 rillig 218: return
219: }
220:
221: if matches(value, `:\.\./[^/]+$`) {
1.9 ! rillig 222: line.Warn0("Dependencies should have the form \"../../category/package\".")
! 223: cv.mkline.explainRelativeDirs()
1.1 rillig 224: return
225: }
226:
1.9 ! rillig 227: line.Warn1("Unknown dependency pattern with path %q.", value)
! 228: Explain4(
! 229: "Examples for valid dependency patterns with path are:",
1.1 rillig 230: " package-[0-9]*:../../category/package",
231: " package>=3.41:../../category/package",
232: " package-2.718:../../category/package")
233: }
234:
235: func (cv *VartypeCheck) DistSuffix() {
236: if cv.value == ".tar.gz" {
1.9 ! rillig 237: cv.line.Note1("%s is \".tar.gz\" by default, so this definition may be redundant.", cv.varname)
1.1 rillig 238: }
239: }
240:
241: func (cv *VartypeCheck) EmulPlatform() {
242:
243: if m, opsys, arch := match2(cv.value, `^(\w+)-(\w+)$`); m {
1.8 dholland 244: if !matches(opsys, `^(?:bitrig|bsdos|cygwin|darwin|dragonfly|freebsd|haiku|hpux|interix|irix|linux|mirbsd|netbsd|openbsd|osf1|sunos|solaris)$`) {
1.9 ! rillig 245: cv.line.Warnf("Unknown operating system: %s", opsys)
1.1 rillig 246: }
247: // no check for os_version
248: if !matches(arch, `^(?:i386|alpha|amd64|arc|arm|arm32|cobalt|convex|dreamcast|hpcmips|hpcsh|hppa|ia64|m68k|m88k|mips|mips64|mipsel|mipseb|mipsn32|ns32k|pc532|pmax|powerpc|rs6000|s390|sparc|sparc64|vax|x86_64)$`) {
1.9 ! rillig 249: cv.line.Warn1("Unknown hardware architecture: %s", arch)
1.1 rillig 250: }
251:
252: } else {
1.9 ! rillig 253: cv.line.Warn1("%q is not a valid emulation platform.", cv.value)
! 254: Explain(
1.1 rillig 255: "An emulation platform has the form <OPSYS>-<MACHINE_ARCH>.",
1.9 ! rillig 256: "OPSYS is the lower-case name of the operating system, and",
! 257: "MACHINE_ARCH is the hardware architecture.",
1.1 rillig 258: "",
259: "Examples: linux-i386, irix-mipsel.")
260: }
261: }
262:
263: func (cv *VartypeCheck) FetchURL() {
1.9 ! rillig 264: cv.mkline.CheckVartypePrimitive(cv.varname, CheckvarURL, cv.op, cv.value, cv.comment, cv.listContext, cv.guessed)
1.1 rillig 265:
1.9 ! rillig 266: for siteURL, siteName := range G.globalData.MasterSiteUrls {
! 267: if hasPrefix(cv.value, siteURL) {
! 268: subdir := cv.value[len(siteURL):]
! 269: if hasPrefix(cv.value, "https://github.com/") {
1.1 rillig 270: subdir = strings.SplitAfter(subdir, "/")[0]
1.9 ! rillig 271: cv.line.Warnf("Please use ${%s:=%s} instead of %q and run \"%s help topic=github\" for further tips.",
! 272: siteName, subdir, cv.value, confMake)
! 273: } else {
! 274: cv.line.Warnf("Please use ${%s:=%s} instead of %q.", siteName, subdir, cv.value)
1.1 rillig 275: }
276: return
277: }
278: }
1.9 ! rillig 279:
! 280: if m, name, subdir := match2(cv.value, `\$\{(MASTER_SITE_[^:]*).*:=(.*)\}$`); m {
! 281: if !G.globalData.MasterSiteVars[name] {
! 282: cv.line.Error1("%s does not exist.", name)
! 283: }
! 284: if !hasSuffix(subdir, "/") {
! 285: cv.line.Error1("The subdirectory in %s must end with a slash.", name)
! 286: }
! 287: }
1.1 rillig 288: }
289:
290: // See Pathname
291: // See http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_169
292: func (cv *VartypeCheck) Filename() {
293: switch {
294: case contains(cv.valueNovar, "/"):
1.9 ! rillig 295: cv.line.Warn0("A filename should not contain a slash.")
1.1 rillig 296: case !matches(cv.valueNovar, `^[-0-9@A-Za-z.,_~+%]*$`):
1.9 ! rillig 297: cv.line.Warn1("%q is not a valid filename.", cv.value)
1.1 rillig 298: }
299: }
300:
301: func (cv *VartypeCheck) Filemask() {
302: if !matches(cv.valueNovar, `^[-0-9A-Za-z._~+%*?]*$`) {
1.9 ! rillig 303: cv.line.Warn1("%q is not a valid filename mask.", cv.value)
1.1 rillig 304: }
305: }
306:
307: func (cv *VartypeCheck) FileMode() {
308: switch {
309: case cv.value != "" && cv.valueNovar == "":
310: // Fine.
311: case matches(cv.value, `^[0-7]{3,4}`):
312: // Fine.
313: default:
1.9 ! rillig 314: cv.line.Warn1("Invalid file mode %q.", cv.value)
1.1 rillig 315: }
316: }
317:
318: func (cv *VartypeCheck) Identifier() {
319: if cv.value != cv.valueNovar {
320: //line.logWarning("Identifiers should be given directly.")
321: }
322: switch {
323: case matches(cv.valueNovar, `^[+\-.0-9A-Z_a-z]+$`):
324: // Fine.
325: case cv.value != "" && cv.valueNovar == "":
326: // Don't warn here.
327: default:
1.9 ! rillig 328: cv.line.Warn1("Invalid identifier %q.", cv.value)
1.1 rillig 329: }
330: }
331:
332: func (cv *VartypeCheck) Integer() {
333: if !matches(cv.value, `^\d+$`) {
1.9 ! rillig 334: cv.line.Warn1("Invalid integer %q.", cv.value)
1.1 rillig 335: }
336: }
337:
338: func (cv *VartypeCheck) LdFlag() {
1.9 ! rillig 339: ldflag := cv.value
! 340: if m, rpathFlag := match1(ldflag, `^(-Wl,(?:-R|-rpath|--rpath))`); m {
! 341: cv.line.Warn1("Please use \"${COMPILER_RPATH_FLAG}\" instead of %q.", rpathFlag)
1.1 rillig 342: return
1.9 ! rillig 343: }
1.1 rillig 344:
1.9 ! rillig 345: switch {
! 346: case hasPrefix(ldflag, "-L"),
! 347: hasPrefix(ldflag, "-l"),
! 348: ldflag == "-pthread",
! 349: ldflag == "-static",
! 350: hasPrefix(ldflag, "-static-"),
! 351: hasPrefix(ldflag, "-Wl,-"),
! 352: hasPrefix(ldflag, "`") && hasSuffix(ldflag, "`"),
! 353: ldflag != cv.valueNovar:
! 354: return
! 355: case hasPrefix(ldflag, "-"):
! 356: cv.line.Warn1("Unknown linker flag %q.", cv.value)
! 357: default:
! 358: cv.line.Warn1("Linker flag %q should start with a hypen.", cv.value)
1.1 rillig 359: }
360: }
361:
362: func (cv *VartypeCheck) License() {
1.9 ! rillig 363: checklineLicense(cv.mkline, cv.value)
1.1 rillig 364: }
365:
366: func (cv *VartypeCheck) MailAddress() {
367: line, value := cv.line, cv.value
368:
369: if m, _, domain := match2(value, `^([+\-.0-9A-Z_a-z]+)@([-\w\d.]+)$`); m {
370: if strings.EqualFold(domain, "NetBSD.org") && domain != "NetBSD.org" {
1.9 ! rillig 371: line.Warn1("Please write \"NetBSD.org\" instead of %q.", domain)
1.1 rillig 372: }
373: if matches(value, `(?i)^(tech-pkg|packages)@NetBSD\.org$`) {
1.9 ! rillig 374: line.Error0("This mailing list address is obsolete. Use pkgsrc-users@NetBSD.org instead.")
1.1 rillig 375: }
376:
377: } else {
1.9 ! rillig 378: line.Warn1("\"%s\" is not a valid mail address.", value)
1.1 rillig 379: }
380: }
381:
382: // See ${STEP_MSG}, ${PKG_FAIL_REASON}
383: func (cv *VartypeCheck) Message() {
384: line, varname, value := cv.line, cv.varname, cv.value
385:
386: if matches(value, `^[\"'].*[\"']$`) {
1.9 ! rillig 387: line.Warn1("%s should not be quoted.", varname)
! 388: Explain(
1.1 rillig 389: "The quoting is only needed for variables which are interpreted as",
1.9 ! rillig 390: "multiple words (or, generally speaking, a list of something). A",
! 391: "single text message does not belong to this class, since it is only",
! 392: "printed as a whole.",
1.1 rillig 393: "",
1.9 ! rillig 394: "On the other hand, PKG_FAIL_REASON is a _list_ of text messages, so",
! 395: "in that case, the quoting has to be done.")
1.1 rillig 396: }
397: }
398:
399: // A package option from options.mk
400: func (cv *VartypeCheck) Option() {
401: line, value, valueNovar := cv.line, cv.value, cv.valueNovar
402:
403: if value != valueNovar {
1.9 ! rillig 404: if G.opts.DebugUnchecked {
! 405: line.Debug1("Unchecked option name: %q", value)
! 406: }
1.1 rillig 407: return
408: }
409:
1.9 ! rillig 410: if m, optname := match1(value, `^-?([a-z][-0-9a-z+]*)$`); m {
! 411: if _, found := G.globalData.PkgOptions[optname]; !found { // There’s a difference between empty and absent here.
! 412: line.Warn1("Unknown option \"%s\".", optname)
! 413: Explain4(
1.1 rillig 414: "This option is not documented in the mk/defaults/options.description",
1.9 ! rillig 415: "file. Please think of a brief but precise description and either",
! 416: "update that file yourself or suggest a description for this option",
! 417: "on the tech-pkg@NetBSD.org mailing list.")
1.1 rillig 418: }
419: return
420: }
421:
422: if matches(value, `^-?([a-z][-0-9a-z_\+]*)$`) {
1.9 ! rillig 423: line.Warn0("Use of the underscore character in option names is deprecated.")
1.1 rillig 424: return
425: }
426:
1.9 ! rillig 427: line.Error1("Invalid option name %q. Option names must start with a lowercase letter and be all-lowercase.", value)
1.1 rillig 428: }
429:
430: // The PATH environment variable
431: func (cv *VartypeCheck) Pathlist() {
1.9 ! rillig 432: if !contains(cv.value, ":") && cv.guessed {
! 433: cv.mkline.CheckVartypePrimitive(cv.varname, CheckvarPathname, cv.op, cv.value, cv.comment, cv.listContext, cv.guessed)
1.1 rillig 434: return
435: }
436:
437: for _, path := range strings.Split(cv.value, ":") {
438: if contains(path, "${") {
439: continue
440: }
441:
442: if !matches(path, `^[-0-9A-Za-z._~+%/]*$`) {
1.9 ! rillig 443: cv.line.Warn1("%q is not a valid pathname.", path)
1.1 rillig 444: }
445:
446: if !hasPrefix(path, "/") {
1.9 ! rillig 447: cv.line.Warn2("All components of %s (in this case %q) should be absolute paths.", cv.varname, path)
1.1 rillig 448: }
449: }
450: }
451:
452: // Shell globbing including slashes.
453: // See Filemask
454: func (cv *VartypeCheck) Pathmask() {
455: if !matches(cv.valueNovar, `^[#\-0-9A-Za-z._~+%*?/\[\]]*`) {
1.9 ! rillig 456: cv.line.Warn1("%q is not a valid pathname mask.", cv.value)
1.1 rillig 457: }
1.9 ! rillig 458: cv.line.CheckAbsolutePathname(cv.value)
1.1 rillig 459: }
460:
461: // Like Filename, but including slashes
462: // See http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_266
463: func (cv *VartypeCheck) Pathname() {
464: if !matches(cv.valueNovar, `^[#\-0-9A-Za-z._~+%/]*$`) {
1.9 ! rillig 465: cv.line.Warn1("%q is not a valid pathname.", cv.value)
1.1 rillig 466: }
1.9 ! rillig 467: cv.line.CheckAbsolutePathname(cv.value)
1.1 rillig 468: }
469:
470: func (cv *VartypeCheck) Perl5Packlist() {
471: if cv.value != cv.valueNovar {
1.9 ! rillig 472: cv.line.Warn1("%s should not depend on other variables.", cv.varname)
1.1 rillig 473: }
474: }
475:
476: func (cv *VartypeCheck) PkgName() {
477: if cv.value == cv.valueNovar && !matches(cv.value, rePkgname) {
1.9 ! rillig 478: cv.line.Warn1("%q is not a valid package name. A valid package name has the form packagename-version, where version consists only of digits, letters and dots.", cv.value)
1.1 rillig 479: }
480: }
481:
482: func (cv *VartypeCheck) PkgOptionsVar() {
1.9 ! rillig 483: cv.mkline.CheckVartypePrimitive(cv.varname, CheckvarVarname, cv.op, cv.value, cv.comment, false, cv.guessed)
1.1 rillig 484: if matches(cv.value, `\$\{PKGBASE[:\}]`) {
1.9 ! rillig 485: cv.line.Error0("PKGBASE must not be used in PKG_OPTIONS_VAR.")
! 486: Explain3(
1.1 rillig 487: "PKGBASE is defined in bsd.pkg.mk, which is included as the",
488: "very last file, but PKG_OPTIONS_VAR is evaluated earlier.",
489: "Use ${PKGNAME:C/-[0-9].*//} instead.")
490: }
491: }
492:
493: // A directory name relative to the top-level pkgsrc directory.
494: // Despite its name, it is more similar to RelativePkgDir than to RelativePkgPath.
495: func (cv *VartypeCheck) PkgPath() {
1.9 ! rillig 496: cv.mkline.CheckRelativePkgdir(G.CurPkgsrcdir + "/" + cv.value)
1.1 rillig 497: }
498:
499: func (cv *VartypeCheck) PkgRevision() {
500: if !matches(cv.value, `^[1-9]\d*$`) {
1.9 ! rillig 501: cv.line.Warn1("%s must be a positive integer number.", cv.varname)
1.1 rillig 502: }
1.9 ! rillig 503: if path.Base(cv.line.Fname) != "Makefile" {
! 504: cv.line.Error1("%s only makes sense directly in the package Makefile.", cv.varname)
! 505: Explain(
1.1 rillig 506: "Usually, different packages using the same Makefile.common have",
1.9 ! rillig 507: "different dependencies and will be bumped at different times (e.g.",
! 508: "for shlib major bumps) and thus the PKGREVISIONs must be in the",
! 509: "separate Makefiles. There is no practical way of having this",
! 510: "information in a commonly used Makefile.")
1.1 rillig 511: }
512: }
513:
514: func (cv *VartypeCheck) PlatformTriple() {
515: if cv.value != cv.valueNovar {
516: return
517: }
518:
519: rePart := `(?:\[[^\]]+\]|[^-\[])+`
520: reTriple := `^(` + rePart + `)-(` + rePart + `)-(` + rePart + `)$`
521: if m, opsys, _, arch := match3(cv.value, reTriple); m {
1.8 dholland 522: if !matches(opsys, `^(?:\*|Bitrig|BSDOS|Cygwin|Darwin|DragonFly|FreeBSD|Haiku|HPUX|Interix|IRIX|Linux|MirBSD|NetBSD|OpenBSD|OSF1|QNX|SunOS)$`) {
1.9 ! rillig 523: cv.line.Warnf("Unknown operating system: %s", opsys)
1.1 rillig 524: }
525: // no check for os_version
526: if !matches(arch, `^(?:\*|i386|alpha|amd64|arc|arm|arm32|cobalt|convex|dreamcast|hpcmips|hpcsh|hppa|ia64|m68k|m88k|mips|mips64|mipsel|mipseb|mipsn32|ns32k|pc532|pmax|powerpc|rs6000|s390|sparc|sparc64|vax|x86_64)$`) {
1.9 ! rillig 527: cv.line.Warn1("Unknown hardware architecture: %s", arch)
1.1 rillig 528: }
529:
530: } else {
1.9 ! rillig 531: cv.line.Warn1("%q is not a valid platform triple.", cv.value)
! 532: Explain3(
1.1 rillig 533: "A platform triple has the form <OPSYS>-<OS_VERSION>-<MACHINE_ARCH>.",
534: "Each of these components may be a shell globbing expression.",
535: "Examples: NetBSD-*-i386, *-*-*, Linux-*-*.")
536: }
537: }
538:
539: func (cv *VartypeCheck) PrefixPathname() {
540: if m, mansubdir := match1(cv.value, `^man/(.+)`); m {
1.9 ! rillig 541: cv.line.Warn2("Please use \"${PKGMANDIR}/%s\" instead of %q.", mansubdir, cv.value)
1.1 rillig 542: }
543: }
544:
545: func (cv *VartypeCheck) PythonDependency() {
546: if cv.value != cv.valueNovar {
1.9 ! rillig 547: cv.line.Warn0("Python dependencies should not contain variables.")
! 548: } else if !matches(cv.valueNovar, `^[+\-.0-9A-Z_a-z]+(?:|:link|:build)$`) {
! 549: cv.line.Warn1("Invalid Python dependency %q.", cv.value)
! 550: Explain4(
! 551: "Python dependencies must be an identifier for a package, as",
! 552: "specified in lang/python/versioned_dependencies.mk. This",
! 553: "identifier may be followed by :build for a build-time only",
! 554: "dependency, or by :link for a run-time only dependency.")
1.1 rillig 555: }
556: }
557:
558: // Refers to a package directory.
559: func (cv *VartypeCheck) RelativePkgDir() {
1.9 ! rillig 560: cv.mkline.CheckRelativePkgdir(cv.value)
1.1 rillig 561: }
562:
563: // Refers to a file or directory.
564: func (cv *VartypeCheck) RelativePkgPath() {
1.9 ! rillig 565: cv.mkline.CheckRelativePath(cv.value, true)
1.1 rillig 566: }
567:
568: func (cv *VartypeCheck) Restricted() {
569: if cv.value != "${RESTRICTED}" {
1.9 ! rillig 570: cv.line.Warn1("The only valid value for %s is ${RESTRICTED}.", cv.varname)
! 571: Explain3(
! 572: "These variables are used to control which files may be mirrored on",
! 573: "FTP servers or CD-ROM collections. They are not intended to mark",
! 574: "packages whose only MASTER_SITES are on ftp.NetBSD.org.")
1.1 rillig 575: }
576: }
577:
578: func (cv *VartypeCheck) SedCommand() {
579: }
580:
581: func (cv *VartypeCheck) SedCommands() {
582: line := cv.line
1.9 ! rillig 583: mkline := cv.mkline
! 584: shline := NewShellLine(mkline)
1.1 rillig 585:
1.9 ! rillig 586: tokens, rest := splitIntoShellTokens(line, cv.value)
1.1 rillig 587: if rest != "" {
1.9 ! rillig 588: if strings.Contains(line.Text, "#") {
! 589: line.Error1("Invalid shell words %q in sed commands.", rest)
! 590: Explain4(
1.1 rillig 591: "When sed commands have embedded \"#\" characters, they need to be",
592: "escaped with a backslash, otherwise make(1) will interpret them as a",
593: "comment, no matter if they occur in single or double quotes or",
594: "whatever.")
595: }
596: return
597: }
598:
1.9 ! rillig 599: ntokens := len(tokens)
1.1 rillig 600: ncommands := 0
601:
1.9 ! rillig 602: for i := 0; i < ntokens; i++ {
! 603: token := tokens[i]
! 604: shline.CheckToken(token, true)
1.1 rillig 605:
606: switch {
1.9 ! rillig 607: case token == "-e":
! 608: if i+1 < ntokens {
1.1 rillig 609: // Check the real sed command here.
610: i++
611: ncommands++
612: if ncommands > 1 {
1.9 ! rillig 613: line.Note0("Each sed command should appear in an assignment of its own.")
! 614: Explain(
1.1 rillig 615: "For example, instead of",
616: " SUBST_SED.foo+= -e s,command1,, -e s,command2,,",
617: "use",
618: " SUBST_SED.foo+= -e s,command1,,",
619: " SUBST_SED.foo+= -e s,command2,,",
620: "",
621: "This way, short sed commands cannot be hidden at the end of a line.")
622: }
1.9 ! rillig 623: shline.CheckToken(tokens[i-1], true)
! 624: shline.CheckToken(tokens[i], true)
! 625: mkline.CheckVartypePrimitive(cv.varname, CheckvarSedCommand, cv.op, tokens[i], cv.comment, cv.listContext, cv.guessed)
1.1 rillig 626: } else {
1.9 ! rillig 627: line.Error0("The -e option to sed requires an argument.")
1.1 rillig 628: }
1.9 ! rillig 629: case token == "-E":
1.1 rillig 630: // Switch to extended regular expressions mode.
631:
1.9 ! rillig 632: case token == "-n":
1.1 rillig 633: // Don't print lines per default.
634:
1.9 ! rillig 635: case i == 0 && matches(token, `^(["']?)(?:\d*|/.*/)s.+["']?$`):
! 636: line.Note0("Please always use \"-e\" in sed commands, even if there is only one substitution.")
1.1 rillig 637:
638: default:
1.9 ! rillig 639: line.Warn1("Unknown sed command %q.", token)
1.1 rillig 640: }
641: }
642: }
643:
644: func (cv *VartypeCheck) ShellCommand() {
1.9 ! rillig 645: setE := true
! 646: NewShellLine(cv.mkline).CheckShellCommand(cv.value, &setE)
! 647: }
! 648:
! 649: // Zero or more shell commands, each terminated with a semicolon.
! 650: func (cv *VartypeCheck) ShellCommands() {
! 651: NewShellLine(cv.mkline).CheckShellCommands(cv.value)
1.1 rillig 652: }
653:
654: func (cv *VartypeCheck) ShellWord() {
655: if !cv.listContext {
1.9 ! rillig 656: NewShellLine(cv.mkline).CheckToken(cv.value, true)
1.1 rillig 657: }
658: }
659:
660: func (cv *VartypeCheck) Stage() {
661: if !matches(cv.value, `^(?:pre|do|post)-(?:extract|patch|configure|build|test|install)`) {
1.9 ! rillig 662: cv.line.Warn1("Invalid stage name %q. Use one of {pre,do,post}-{extract,patch,configure,build,test,install}.", cv.value)
1.1 rillig 663: }
664: }
665:
666: func (cv *VartypeCheck) String() {
667: // No further checks possible.
668: }
669:
670: func (cv *VartypeCheck) Tool() {
1.9 ! rillig 671: if cv.varname == "TOOLS_NOOP" && cv.op == opAssignAppend {
1.1 rillig 672: // no warning for package-defined tool definitions
673:
674: } else if m, toolname, tooldep := match2(cv.value, `^([-\w]+|\[)(?::(\w+))?$`); m {
1.9 ! rillig 675: if !G.globalData.Tools[toolname] {
! 676: cv.line.Error1("Unknown tool %q.", toolname)
1.1 rillig 677: }
678: switch tooldep {
679: case "", "bootstrap", "build", "pkgsrc", "run":
680: default:
1.9 ! rillig 681: cv.line.Error1("Unknown tool dependency %q. Use one of \"build\", \"pkgsrc\" or \"run\".", tooldep)
1.1 rillig 682: }
683: } else {
1.9 ! rillig 684: cv.line.Error1("Invalid tool syntax: %q.", cv.value)
1.1 rillig 685: }
686: }
687:
688: func (cv *VartypeCheck) Unchecked() {
689: // Do nothing, as the name says.
690: }
691:
692: func (cv *VartypeCheck) URL() {
693: line, value := cv.line, cv.value
694:
695: if value == "" && hasPrefix(cv.comment, "#") {
696: // Ok
697:
698: } else if containsVarRef(value) {
699: // No further checks
700:
1.9 ! rillig 701: } else if m, _, host, _, _ := match4(value, `^(https?|ftp|gopher)://([-0-9A-Za-z.]+)(?::(\d+))?/([-%&+,./0-9:;=?@A-Z_a-z~]|#)*$`); m {
1.1 rillig 702: if matches(host, `(?i)\.NetBSD\.org$`) && !matches(host, `\.NetBSD\.org$`) {
1.9 ! rillig 703: line.Warn1("Please write NetBSD.org instead of %s.", host)
1.1 rillig 704: }
705:
706: } else if m, scheme, _, absPath := match3(value, `^([0-9A-Za-z]+)://([^/]+)(.*)$`); m {
707: switch {
708: case scheme != "ftp" && scheme != "http" && scheme != "https" && scheme != "gopher":
1.9 ! rillig 709: line.Warn1("%q is not a valid URL. Only ftp, gopher, http, and https URLs are allowed here.", value)
1.1 rillig 710:
711: case absPath == "":
1.9 ! rillig 712: line.Note1("For consistency, please add a trailing slash to %q.", value)
1.1 rillig 713:
714: default:
1.9 ! rillig 715: line.Warn1("%q is not a valid URL.", value)
1.1 rillig 716: }
717:
718: } else {
1.9 ! rillig 719: line.Warn1("%q is not a valid URL.", value)
1.1 rillig 720: }
721: }
722:
723: func (cv *VartypeCheck) UserGroupName() {
724: if cv.value == cv.valueNovar && !matches(cv.value, `^[0-9_a-z]+$`) {
1.9 ! rillig 725: cv.line.Warn1("Invalid user or group name %q.", cv.value)
1.1 rillig 726: }
727: }
728:
729: func (cv *VartypeCheck) Varname() {
730: if cv.value == cv.valueNovar && !matches(cv.value, `^[A-Z_][0-9A-Z_]*(?:[.].*)?$`) {
1.9 ! rillig 731: cv.line.Warn1("%q is not a valid variable name.", cv.value)
! 732: Explain(
1.1 rillig 733: "Variable names are restricted to only uppercase letters and the",
734: "underscore in the basename, and arbitrary characters in the",
735: "parameterized part, following the dot.",
736: "",
737: "Examples:",
738: "\t* PKGNAME",
739: "\t* PKG_OPTIONS.gnuchess")
740: }
741: }
742:
743: func (cv *VartypeCheck) Version() {
744: if !matches(cv.value, `^([\d.])+$`) {
1.9 ! rillig 745: cv.line.Warn1("Invalid version number %q.", cv.value)
1.1 rillig 746: }
747: }
748:
749: func (cv *VartypeCheck) WrapperReorder() {
750: if !matches(cv.value, `^reorder:l:([\w\-]+):([\w\-]+)$`) {
1.9 ! rillig 751: cv.line.Warn1("Unknown wrapper reorder command %q.", cv.value)
1.1 rillig 752: }
753: }
754:
755: func (cv *VartypeCheck) WrapperTransform() {
1.9 ! rillig 756: cmd := cv.value
! 757: if hasPrefix(cmd, "rm:-") ||
! 758: matches(cmd, `^(R|l|rpath):([^:]+):(.+)$`) ||
! 759: matches(cmd, `^'?(opt|rename|rm-optarg|rmdir):.*$`) ||
! 760: cmd == "-e" ||
! 761: matches(cmd, `^["']?s[|:,]`) {
! 762: return
1.1 rillig 763: }
1.9 ! rillig 764: cv.line.Warn1("Unknown wrapper transform command %q.", cmd)
1.1 rillig 765: }
766:
767: func (cv *VartypeCheck) WrkdirSubdirectory() {
1.9 ! rillig 768: cv.mkline.CheckVartypePrimitive(cv.varname, CheckvarPathname, cv.op, cv.value, cv.comment, cv.listContext, cv.guessed)
1.1 rillig 769: }
770:
771: // A directory relative to ${WRKSRC}, for use in CONFIGURE_DIRS and similar variables.
772: func (cv *VartypeCheck) WrksrcSubdirectory() {
773: if m, _, rest := match2(cv.value, `^(\$\{WRKSRC\})(?:/(.*))?`); m {
774: if rest == "" {
775: rest = "."
776: }
1.9 ! rillig 777: cv.line.Note2("You can use %q instead of %q.", rest, cv.value)
! 778: Explain1(
! 779: "These directories are interpreted relative to ${WRKSRC}.")
1.1 rillig 780:
781: } else if cv.value != "" && cv.valueNovar == "" {
782: // The value of another variable
783:
784: } else if !matches(cv.valueNovar, `^(?:\.|[0-9A-Za-z_@][-0-9A-Za-z_@./+]*)$`) {
1.9 ! rillig 785: cv.line.Warn1("%q is not a valid subdirectory of ${WRKSRC}.", cv.value)
1.1 rillig 786: }
787: }
788:
789: // Used for variables that are checked using `.if defined(VAR)`.
790: func (cv *VartypeCheck) Yes() {
791: if !matches(cv.value, `^(?:YES|yes)(?:\s+#.*)?$`) {
1.9 ! rillig 792: cv.line.Warn1("%s should be set to YES or yes.", cv.varname)
! 793: Explain4(
1.1 rillig 794: "This variable means \"yes\" if it is defined, and \"no\" if it is",
1.9 ! rillig 795: "undefined. Even when it has the value \"no\", this means \"yes\".",
1.1 rillig 796: "Therefore when it is defined, its value should correspond to its",
797: "meaning.")
798: }
799: }
800:
801: // The type YesNo is used for variables that are checked using
802: // .if defined(VAR) && !empty(VAR:M[Yy][Ee][Ss])
803: //
804: func (cv *VartypeCheck) YesNo() {
805: if !matches(cv.value, `^(?:YES|yes|NO|no)(?:\s+#.*)?$`) {
1.9 ! rillig 806: cv.line.Warn1("%s should be set to YES, yes, NO, or no.", cv.varname)
1.1 rillig 807: }
808: }
809:
810: // Like YesNo, but the value may be produced by a shell command using the
811: // != operator.
1.3 rillig 812: func (cv *VartypeCheck) YesNoIndirectly() {
1.1 rillig 813: if cv.valueNovar != "" && !matches(cv.value, `^(?:YES|yes|NO|no)(?:\s+#.*)?$`) {
1.9 ! rillig 814: cv.line.Warn1("%s should be set to YES, yes, NO, or no.", cv.varname)
1.1 rillig 815: }
816: }
CVSweb <webmaster@jp.NetBSD.org>