[BACK]Return to vartypecheck.go CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / pkgsrc / pkgtools / pkglint / files

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>