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

Annotation of pkgsrc/pkgtools/pkglint/files/package.go, Revision 1.36

1.1       rillig      1: package main
                      2:
                      3: import (
1.35      rillig      4:        "fmt"
1.15      rillig      5:        "netbsd.org/pkglint/pkgver"
1.16      rillig      6:        "netbsd.org/pkglint/regex"
                      7:        "netbsd.org/pkglint/trace"
1.27      rillig      8:        "os"
1.1       rillig      9:        "path"
                     10:        "regexp"
                     11:        "strconv"
                     12:        "strings"
                     13: )
                     14:
1.31      rillig     15: const rePkgname = `^([\w\-.+]+)-(\d[.0-9A-Z_a-z]*)$`
1.15      rillig     16:
1.3       rillig     17: // Package contains data for the pkgsrc package that is currently checked.
                     18: type Package struct {
1.34      rillig     19:        dir                  string          // The directory of the package, for resolving files
1.22      rillig     20:        Pkgpath              string          // e.g. "category/pkgdir"
                     21:        Pkgdir               string          // PKGDIR from the package Makefile
                     22:        Filesdir             string          // FILESDIR from the package Makefile
                     23:        Patchdir             string          // PATCHDIR from the package Makefile
                     24:        DistinfoFile         string          // DISTINFO_FILE from the package Makefile
                     25:        EffectivePkgname     string          // PKGNAME or DISTNAME from the package Makefile, including nb13
                     26:        EffectivePkgbase     string          // The effective PKGNAME without the version
                     27:        EffectivePkgversion  string          // The version part of the effective PKGNAME, excluding nb13
                     28:        EffectivePkgnameLine MkLine          // The origin of the three effective_* values
                     29:        PlistDirs            map[string]bool // Directories mentioned in the PLIST files
1.30      rillig     30:        PlistFiles           map[string]bool // Regular files mentioned in the PLIST files
1.18      rillig     31:
1.26      rillig     32:        vars                  Scope
                     33:        bl3                   map[string]Line // buildlink3.mk name => line; contains only buildlink3.mk files that are directly included.
                     34:        included              map[string]Line // fname => line
                     35:        seenMakefileCommon    bool            // Does the package have any .includes?
1.18      rillig     36:        conditionalIncludes   map[string]MkLine
                     37:        unconditionalIncludes map[string]MkLine
1.21      rillig     38:        once                  Once
1.33      rillig     39:        IgnoreMissingPatches  bool // In distinfo, don't warn about patches that cannot be found.
1.3       rillig     40: }
                     41:
1.34      rillig     42: func NewPackage(dir string) *Package {
                     43:        pkgpath := G.Pkgsrc.ToRel(dir)
                     44:        if strings.Count(pkgpath, "/") != 1 {
1.35      rillig     45:                panic(fmt.Sprintf("Package directory %q must be two subdirectories below the pkgsrc root %q.", dir, G.Pkgsrc.File(".")))
1.34      rillig     46:        }
                     47:
1.3       rillig     48:        pkg := &Package{
1.34      rillig     49:                dir:                   dir,
1.12      rillig     50:                Pkgpath:               pkgpath,
1.33      rillig     51:                Pkgdir:                ".",
                     52:                Filesdir:              "files",
                     53:                Patchdir:              "patches",
1.36    ! rillig     54:                DistinfoFile:          "${PKGDIR}/distinfo",
1.22      rillig     55:                PlistDirs:             make(map[string]bool),
1.30      rillig     56:                PlistFiles:            make(map[string]bool),
1.26      rillig     57:                vars:                  NewScope(),
1.20      rillig     58:                bl3:                   make(map[string]Line),
                     59:                included:              make(map[string]Line),
1.18      rillig     60:                conditionalIncludes:   make(map[string]MkLine),
                     61:                unconditionalIncludes: make(map[string]MkLine),
1.3       rillig     62:        }
1.35      rillig     63:        pkg.vars.DefineAll(G.Pkgsrc.UserDefinedVars)
1.36    ! rillig     64:
        !            65:        pkg.vars.Fallback("PKGDIR", ".")
        !            66:        pkg.vars.Fallback("DISTINFO_FILE", "${PKGDIR}/distinfo")
        !            67:        pkg.vars.Fallback("FILESDIR", "files")
        !            68:        pkg.vars.Fallback("PATCHDIR", "patches")
        !            69:        pkg.vars.Fallback("KRB5_TYPE", "heimdal")
        !            70:        pkg.vars.Fallback("PGSQL_VERSION", "95")
        !            71:        pkg.vars.Fallback(".CURDIR", ".") // FIXME: In reality, this is an absolute pathname.
        !            72:
1.3       rillig     73:        return pkg
                     74: }
                     75:
1.33      rillig     76: // File returns the (possibly absolute) path to relativeFilename,
                     77: // as resolved from the package's directory.
1.36    ! rillig     78: // Variables that are known in the package are resolved, e.g. ${PKGDIR}.
1.33      rillig     79: func (pkg *Package) File(relativeFilename string) string {
1.36    ! rillig     80:        return cleanpath(resolveVariableRefs(pkg.dir + "/" + relativeFilename))
1.33      rillig     81: }
                     82:
1.3       rillig     83: func (pkg *Package) checkPossibleDowngrade() {
1.16      rillig     84:        if trace.Tracing {
                     85:                defer trace.Call0()()
1.3       rillig     86:        }
                     87:
                     88:        m, _, pkgversion := match2(pkg.EffectivePkgname, rePkgname)
1.1       rillig     89:        if !m {
                     90:                return
                     91:        }
                     92:
1.3       rillig     93:        mkline := pkg.EffectivePkgnameLine
1.1       rillig     94:
1.27      rillig     95:        change := G.Pkgsrc.LastChange[pkg.Pkgpath]
1.1       rillig     96:        if change == nil {
1.16      rillig     97:                if trace.Tracing {
                     98:                        trace.Step1("No change log for package %q", pkg.Pkgpath)
1.3       rillig     99:                }
1.1       rillig    100:                return
                    101:        }
                    102:
1.3       rillig    103:        if change.Action == "Updated" {
1.36    ! rillig    104:                changeVersion := replaceAll(change.Version, `nb\d+$`, "")
1.15      rillig    105:                if pkgver.Compare(pkgversion, changeVersion) < 0 {
1.31      rillig    106:                        mkline.Warnf("The package is being downgraded from %s (see %s) to %s.", change.Version, change.Line.ReferenceFrom(mkline.Line), pkgversion)
1.14      rillig    107:                        Explain(
1.4       rillig    108:                                "The files in doc/CHANGES-*, in which all version changes are",
                    109:                                "recorded, have a higher version number than what the package says.",
                    110:                                "This is unusual, since packages are typically upgraded instead of",
                    111:                                "downgraded.")
1.1       rillig    112:                }
                    113:        }
                    114: }
                    115:
1.3       rillig    116: func (pkg *Package) checklinesBuildlink3Inclusion(mklines *MkLines) {
1.16      rillig    117:        if trace.Tracing {
                    118:                defer trace.Call0()()
1.1       rillig    119:        }
                    120:
                    121:        // Collect all the included buildlink3.mk files from the file.
1.18      rillig    122:        includedFiles := make(map[string]MkLine)
1.3       rillig    123:        for _, mkline := range mklines.mklines {
                    124:                if mkline.IsInclude() {
1.31      rillig    125:                        file := mkline.IncludeFile()
1.1       rillig    126:                        if m, bl3 := match1(file, `^\.\./\.\./(.*)/buildlink3\.mk`); m {
1.3       rillig    127:                                includedFiles[bl3] = mkline
                    128:                                if pkg.bl3[bl3] == nil {
1.14      rillig    129:                                        mkline.Warnf("%s/buildlink3.mk is included by this file but not by the package.", bl3)
1.1       rillig    130:                                }
                    131:                        }
                    132:                }
                    133:        }
                    134:
1.16      rillig    135:        if trace.Tracing {
1.8       rillig    136:                for packageBl3 := range pkg.bl3 {
1.1       rillig    137:                        if includedFiles[packageBl3] == nil {
1.16      rillig    138:                                trace.Step1("%s/buildlink3.mk is included by the package but not by the buildlink3.mk file.", packageBl3)
1.1       rillig    139:                        }
                    140:                }
                    141:        }
                    142: }
                    143:
1.34      rillig    144: // checkdirPackage checks a complete pkgsrc package, including each
                    145: // of the files individually, and also when seen in combination.
                    146: func (pkglint *Pkglint) checkdirPackage(dir string) {
1.16      rillig    147:        if trace.Tracing {
1.34      rillig    148:                defer trace.Call1(dir)()
1.3       rillig    149:        }
                    150:
1.34      rillig    151:        G.Pkg = NewPackage(dir)
1.3       rillig    152:        defer func() { G.Pkg = nil }()
                    153:        pkg := G.Pkg
1.1       rillig    154:
                    155:        // we need to handle the Makefile first to get some variables
1.33      rillig    156:        lines := pkg.loadPackageMakefile()
1.1       rillig    157:        if lines == nil {
                    158:                return
                    159:        }
                    160:
1.33      rillig    161:        files := dirglob(pkg.File("."))
1.3       rillig    162:        if pkg.Pkgdir != "." {
1.33      rillig    163:                files = append(files, dirglob(pkg.File(pkg.Pkgdir))...)
1.1       rillig    164:        }
                    165:        if G.opts.CheckExtra {
1.33      rillig    166:                files = append(files, dirglob(pkg.File(pkg.Filesdir))...)
1.1       rillig    167:        }
1.33      rillig    168:        files = append(files, dirglob(pkg.File(pkg.Patchdir))...)
1.36    ! rillig    169:        if pkg.DistinfoFile != pkg.vars.fallback["DISTINFO_FILE"] {
1.33      rillig    170:                files = append(files, pkg.File(pkg.DistinfoFile))
1.1       rillig    171:        }
                    172:        haveDistinfo := false
                    173:        havePatches := false
                    174:
1.22      rillig    175:        // Determine the used variables and PLIST directories before checking any of the Makefile fragments.
1.1       rillig    176:        for _, fname := range files {
                    177:                if (hasPrefix(path.Base(fname), "Makefile.") || hasSuffix(fname, ".mk")) &&
                    178:                        !matches(fname, `patch-`) &&
1.3       rillig    179:                        !contains(fname, pkg.Pkgdir+"/") &&
                    180:                        !contains(fname, pkg.Filesdir+"/") {
1.34      rillig    181:                        if mklines := LoadMk(fname, MustSucceed); mklines != nil {
                    182:                                mklines.DetermineUsedVariables()
1.1       rillig    183:                        }
                    184:                }
1.22      rillig    185:                if hasPrefix(path.Base(fname), "PLIST") {
                    186:                        pkg.loadPlistDirs(fname)
                    187:                }
1.1       rillig    188:        }
                    189:
                    190:        for _, fname := range files {
1.7       rillig    191:                if containsVarRef(fname) {
1.36    ! rillig    192:                        if trace.Tracing {
        !           193:                                trace.Stepf("Skipping file %q because the name contains an unresolved variable.", fname)
        !           194:                        }
1.7       rillig    195:                        continue
                    196:                }
1.33      rillig    197:                if fname == pkg.File("Makefile") {
1.27      rillig    198:                        if st, err := os.Lstat(fname); err == nil {
1.36    ! rillig    199:                                pkglint.checkExecutable(st)
1.27      rillig    200:                        }
1.1       rillig    201:                        if G.opts.CheckMakefile {
1.3       rillig    202:                                pkg.checkfilePackageMakefile(fname, lines)
1.1       rillig    203:                        }
                    204:                } else {
1.26      rillig    205:                        pkglint.Checkfile(fname)
1.1       rillig    206:                }
1.3       rillig    207:                if contains(fname, "/patches/patch-") {
1.1       rillig    208:                        havePatches = true
                    209:                } else if hasSuffix(fname, "/distinfo") {
                    210:                        haveDistinfo = true
                    211:                }
1.9       rillig    212:                pkg.checkLocallyModified(fname)
1.1       rillig    213:        }
                    214:
1.36    ! rillig    215:        if pkg.Pkgdir == "." && G.opts.CheckDistinfo && G.opts.CheckPatches {
1.1       rillig    216:                if havePatches && !haveDistinfo {
1.33      rillig    217:                        NewLineWhole(pkg.File(pkg.DistinfoFile)).Warnf("File not found. Please run \"%s makepatchsum\".", confMake)
1.1       rillig    218:                }
                    219:        }
                    220:
1.30      rillig    221:        if G.opts.CheckAlternatives {
                    222:                for _, fname := range files {
                    223:                        if path.Base(fname) == "ALTERNATIVES" {
                    224:                                CheckfileAlternatives(fname, pkg.PlistFiles)
                    225:                        }
                    226:                }
                    227:        }
1.1       rillig    228: }
                    229:
1.33      rillig    230: func (pkg *Package) loadPackageMakefile() *MkLines {
                    231:        fname := pkg.File("Makefile")
1.16      rillig    232:        if trace.Tracing {
                    233:                defer trace.Call1(fname)()
1.3       rillig    234:        }
                    235:
                    236:        mainLines, allLines := NewMkLines(nil), NewMkLines(nil)
1.36    ! rillig    237:        if _, result := pkg.readMakefile(fname, mainLines, allLines, ""); !result {
        !           238:                LoadMk(fname, NotEmpty|LogErrors) // Just for the LogErrors.
1.3       rillig    239:                return nil
                    240:        }
                    241:
                    242:        if G.opts.DumpMakefile {
1.24      rillig    243:                G.logOut.WriteLine("Whole Makefile (with all included files) follows:")
1.3       rillig    244:                for _, line := range allLines.lines {
1.24      rillig    245:                        G.logOut.WriteLine(line.String())
1.3       rillig    246:                }
                    247:        }
                    248:
                    249:        allLines.DetermineUsedVariables()
1.32      rillig    250:        allLines.CheckRedundantVariables()
1.3       rillig    251:
1.36    ! rillig    252:        pkg.Pkgdir, _ = pkg.vars.Value("PKGDIR")
        !           253:        pkg.DistinfoFile, _ = pkg.vars.Value("DISTINFO_FILE")
        !           254:        pkg.Filesdir, _ = pkg.vars.Value("FILESDIR")
        !           255:        pkg.Patchdir, _ = pkg.vars.Value("PATCHDIR")
1.3       rillig    256:
1.33      rillig    257:        // See lang/php/ext.mk
1.3       rillig    258:        if varIsDefined("PHPEXT_MK") {
                    259:                if !varIsDefined("USE_PHP_EXT_PATCHES") {
                    260:                        pkg.Patchdir = "patches"
                    261:                }
                    262:                if varIsDefined("PECL_VERSION") {
                    263:                        pkg.DistinfoFile = "distinfo"
1.33      rillig    264:                } else {
                    265:                        pkg.IgnoreMissingPatches = true
1.3       rillig    266:                }
1.33      rillig    267:
                    268:                // For PHP modules that are not PECL, this combination means that
                    269:                // the patches in the distinfo cannot be found in PATCHDIR.
1.3       rillig    270:        }
                    271:
1.16      rillig    272:        if trace.Tracing {
                    273:                trace.Step1("DISTINFO_FILE=%s", pkg.DistinfoFile)
                    274:                trace.Step1("FILESDIR=%s", pkg.Filesdir)
                    275:                trace.Step1("PATCHDIR=%s", pkg.Patchdir)
                    276:                trace.Step1("PKGDIR=%s", pkg.Pkgdir)
1.3       rillig    277:        }
                    278:
                    279:        return mainLines
                    280: }
                    281:
1.36    ! rillig    282: func (pkg *Package) readMakefile(fname string, mainLines *MkLines, allLines *MkLines, includingFnameForUsedCheck string) (exists bool, result bool) {
1.16      rillig    283:        if trace.Tracing {
                    284:                defer trace.Call1(fname)()
1.3       rillig    285:        }
                    286:
1.36    ! rillig    287:        fileMklines := LoadMk(fname, NotEmpty)
1.34      rillig    288:        if fileMklines == nil {
1.36    ! rillig    289:                return false, false
1.3       rillig    290:        }
1.36    ! rillig    291:        exists = true
1.3       rillig    292:
                    293:        isMainMakefile := len(mainLines.mklines) == 0
1.1       rillig    294:
1.36    ! rillig    295:        result = true
1.32      rillig    296:        lineAction := func(mkline MkLine) bool {
1.3       rillig    297:                if isMainMakefile {
                    298:                        mainLines.mklines = append(mainLines.mklines, mkline)
1.20      rillig    299:                        mainLines.lines = append(mainLines.lines, mkline.Line)
1.3       rillig    300:                }
                    301:                allLines.mklines = append(allLines.mklines, mkline)
1.20      rillig    302:                allLines.lines = append(allLines.lines, mkline.Line)
1.3       rillig    303:
                    304:                var includeFile, incDir, incBase string
                    305:                if mkline.IsInclude() {
1.31      rillig    306:                        inc := mkline.IncludeFile()
1.18      rillig    307:                        includeFile = resolveVariableRefs(mkline.ResolveVarsInRelativePath(inc, true))
1.3       rillig    308:                        if containsVarRef(includeFile) {
                    309:                                if !contains(fname, "/mk/") {
1.18      rillig    310:                                        mkline.Notef("Skipping include file %q. This may result in false warnings.", includeFile)
1.3       rillig    311:                                }
                    312:                                includeFile = ""
                    313:                        }
                    314:                        incDir, incBase = path.Split(includeFile)
                    315:                }
                    316:
                    317:                if includeFile != "" {
                    318:                        if path.Base(fname) != "buildlink3.mk" {
                    319:                                if m, bl3File := match1(includeFile, `^\.\./\.\./(.*)/buildlink3\.mk$`); m {
1.36    ! rillig    320:                                        pkg.bl3[bl3File] = mkline.Line
1.16      rillig    321:                                        if trace.Tracing {
                    322:                                                trace.Step1("Buildlink3 file in package: %q", bl3File)
1.3       rillig    323:                                        }
                    324:                                }
                    325:                        }
                    326:                }
                    327:
1.36    ! rillig    328:                if includeFile != "" && pkg.included[includeFile] == nil {
        !           329:                        pkg.included[includeFile] = mkline.Line
1.3       rillig    330:
                    331:                        if matches(includeFile, `^\.\./[^./][^/]*/[^/]+`) {
1.14      rillig    332:                                mkline.Warnf("References to other packages should look like \"../../category/package\", not \"../package\".")
1.18      rillig    333:                                mkline.ExplainRelativeDirs()
1.3       rillig    334:                        }
                    335:
                    336:                        if path.Base(fname) == "Makefile" && !hasPrefix(incDir, "../../mk/") && incBase != "buildlink3.mk" && incBase != "builtin.mk" && incBase != "options.mk" {
1.16      rillig    337:                                if trace.Tracing {
                    338:                                        trace.Step1("Including %q sets seenMakefileCommon.", includeFile)
1.3       rillig    339:                                }
1.36    ! rillig    340:                                pkg.seenMakefileCommon = true
1.3       rillig    341:                        }
                    342:
1.35      rillig    343:                        skip := contains(fname, "/mk/") || hasSuffix(includeFile, "/bsd.pkg.mk") || IsPrefs(includeFile)
1.7       rillig    344:                        if !skip {
1.3       rillig    345:                                dirname, _ := path.Split(fname)
                    346:                                dirname = cleanpath(dirname)
                    347:
1.36    ! rillig    348:                                fullIncluded := dirname + "/" + includeFile
        !           349:                                if trace.Tracing {
        !           350:                                        trace.Step1("Including %q.", fullIncluded)
        !           351:                                }
        !           352:                                fullIncluding := ifelseStr(incBase == "Makefile.common" && incDir != "", fname, "")
        !           353:                                innerExists, innerResult := pkg.readMakefile(fullIncluded, mainLines, allLines, fullIncluding)
1.29      rillig    354:
1.36    ! rillig    355:                                if !innerExists {
1.29      rillig    356:                                        if fileMklines.indentation.IsCheckedFile(includeFile) {
1.32      rillig    357:                                                return true // See https://github.com/rillig/pkglint/issues/1
1.36    ! rillig    358:                                        }
        !           359:
        !           360:                                        // Only look in the directory relative to the
        !           361:                                        // current file and in the package directory.
        !           362:                                        // Make(1) has a list of include directories, but pkgsrc
        !           363:                                        // doesn't make use of that, so pkglint also doesn't
        !           364:                                        // need this extra complexity.
        !           365:                                        pkgBasedir := pkg.File(".")
        !           366:                                        if dirname != pkgBasedir { // Prevent unnecessary syscalls
        !           367:                                                dirname = pkgBasedir
        !           368:
        !           369:                                                fullIncludedFallback := dirname + "/" + includeFile
        !           370:                                                innerExists, innerResult = pkg.readMakefile(fullIncludedFallback, mainLines, allLines, fullIncluding)
        !           371:                                        }
1.29      rillig    372:
1.36    ! rillig    373:                                        if !innerExists {
        !           374:                                                mkline.Errorf("Cannot read %q.", includeFile)
1.13      rillig    375:                                        }
1.3       rillig    376:                                }
                    377:
1.36    ! rillig    378:                                if !innerResult {
1.32      rillig    379:                                        result = false
1.3       rillig    380:                                        return false
                    381:                                }
                    382:                        }
                    383:                }
                    384:
                    385:                if mkline.IsVarassign() {
                    386:                        varname, op, value := mkline.Varname(), mkline.Op(), mkline.Value()
                    387:
1.36    ! rillig    388:                        if op != opAssignDefault || !pkg.vars.Defined(varname) {
1.16      rillig    389:                                if trace.Tracing {
                    390:                                        trace.Stepf("varassign(%q, %q, %q)", varname, op, value)
1.3       rillig    391:                                }
1.36    ! rillig    392:                                pkg.vars.Define(varname, mkline)
1.3       rillig    393:                        }
                    394:                }
1.32      rillig    395:                return true
1.3       rillig    396:        }
1.32      rillig    397:        atEnd := func(mkline MkLine) {}
1.35      rillig    398:        fileMklines.ForEachEnd(lineAction, atEnd)
1.3       rillig    399:
                    400:        if includingFnameForUsedCheck != "" {
1.27      rillig    401:                fileMklines.checkForUsedComment(G.Pkgsrc.ToRel(includingFnameForUsedCheck))
1.3       rillig    402:        }
                    403:
1.36    ! rillig    404:        return
1.3       rillig    405: }
                    406:
                    407: func (pkg *Package) checkfilePackageMakefile(fname string, mklines *MkLines) {
1.16      rillig    408:        if trace.Tracing {
                    409:                defer trace.Call1(fname)()
1.3       rillig    410:        }
                    411:
1.26      rillig    412:        vars := pkg.vars
                    413:        if !vars.Defined("PLIST_SRC") &&
                    414:                !vars.Defined("GENERATE_PLIST") &&
                    415:                !vars.Defined("META_PACKAGE") &&
1.33      rillig    416:                !fileExists(pkg.File(pkg.Pkgdir+"/PLIST")) &&
                    417:                !fileExists(pkg.File(pkg.Pkgdir+"/PLIST.common")) {
1.31      rillig    418:                NewLineWhole(fname).Warnf("Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset.")
1.1       rillig    419:        }
                    420:
1.33      rillig    421:        if (vars.Defined("NO_CHECKSUM") || vars.Defined("META_PACKAGE")) && isEmptyDir(pkg.File(pkg.Patchdir)) {
                    422:                if distinfoFile := pkg.File(pkg.DistinfoFile); fileExists(distinfoFile) {
1.14      rillig    423:                        NewLineWhole(distinfoFile).Warnf("This file should not exist if NO_CHECKSUM or META_PACKAGE is set.")
1.1       rillig    424:                }
                    425:        } else {
1.33      rillig    426:                if distinfoFile := pkg.File(pkg.DistinfoFile); !containsVarRef(distinfoFile) && !fileExists(distinfoFile) {
1.27      rillig    427:                        NewLineWhole(distinfoFile).Warnf("File not found. Please run \"%s makesum\" or define NO_CHECKSUM=yes in the package Makefile.", confMake)
1.1       rillig    428:                }
                    429:        }
                    430:
1.26      rillig    431:        if perlLine, noconfLine := vars.FirstDefinition("REPLACE_PERL"), vars.FirstDefinition("NO_CONFIGURE"); perlLine != nil && noconfLine != nil {
1.36    ! rillig    432:                perlLine.Warnf("REPLACE_PERL is ignored when NO_CONFIGURE is set (in %s).", noconfLine.ReferenceFrom(perlLine.Line))
1.1       rillig    433:        }
                    434:
1.26      rillig    435:        if !vars.Defined("LICENSE") && !vars.Defined("META_PACKAGE") && pkg.once.FirstTime("LICENSE") {
1.14      rillig    436:                NewLineWhole(fname).Errorf("Each package must define its LICENSE.")
1.1       rillig    437:        }
                    438:
1.36    ! rillig    439:        pkg.checkGnuConfigureUseLanguages()
1.3       rillig    440:        pkg.determineEffectivePkgVars()
                    441:        pkg.checkPossibleDowngrade()
                    442:
1.26      rillig    443:        if !vars.Defined("COMMENT") {
1.14      rillig    444:                NewLineWhole(fname).Warnf("No COMMENT given.")
1.3       rillig    445:        }
                    446:
1.26      rillig    447:        if imake, x11 := vars.FirstDefinition("USE_IMAKE"), vars.FirstDefinition("USE_X11"); imake != nil && x11 != nil {
1.20      rillig    448:                if !hasSuffix(x11.Filename, "/mk/x11.buildlink3.mk") {
                    449:                        imake.Notef("USE_IMAKE makes USE_X11 in %s superfluous.", x11.ReferenceFrom(imake.Line))
1.7       rillig    450:                }
1.3       rillig    451:        }
                    452:
                    453:        pkg.checkUpdate()
                    454:        mklines.Check()
1.25      rillig    455:        pkg.CheckVarorder(mklines)
1.3       rillig    456:        SaveAutofixChanges(mklines.lines)
                    457: }
                    458:
1.36    ! rillig    459: func (pkg *Package) checkGnuConfigureUseLanguages() {
        !           460:        vars := pkg.vars
        !           461:
        !           462:        if gnuLine, useLine := vars.FirstDefinition("GNU_CONFIGURE"), vars.FirstDefinition("USE_LANGUAGES"); gnuLine != nil && useLine != nil {
        !           463:                if matches(useLine.VarassignComment(), `(?-i)\b(?:c|empty|none)\b`) {
        !           464:                        // Don't emit a warning, since the comment
        !           465:                        // probably contains a statement that C is
        !           466:                        // really not needed.
        !           467:
        !           468:                } else if !matches(useLine.Value(), `(?:^|\s+)(?:c|c99|objc)(?:\s+|$)`) {
        !           469:                        gnuLine.Warnf("GNU_CONFIGURE almost always needs a C compiler, but \"c\" is not added to USE_LANGUAGES in %s.",
        !           470:                                useLine.ReferenceFrom(gnuLine.Line))
        !           471:                }
        !           472:        }
        !           473: }
        !           474:
1.3       rillig    475: func (pkg *Package) getNbpart() string {
1.35      rillig    476:        pkgrevision, _ := pkg.vars.Value("PKGREVISION")
1.3       rillig    477:        if rev, err := strconv.Atoi(pkgrevision); err == nil {
                    478:                return "nb" + strconv.Itoa(rev)
                    479:        }
                    480:        return ""
                    481: }
                    482:
                    483: func (pkg *Package) determineEffectivePkgVars() {
1.26      rillig    484:        distnameLine := pkg.vars.FirstDefinition("DISTNAME")
                    485:        pkgnameLine := pkg.vars.FirstDefinition("PKGNAME")
1.1       rillig    486:
                    487:        distname := ""
                    488:        if distnameLine != nil {
1.3       rillig    489:                distname = distnameLine.Value()
1.1       rillig    490:        }
                    491:        pkgname := ""
                    492:        if pkgnameLine != nil {
1.3       rillig    493:                pkgname = pkgnameLine.Value()
1.1       rillig    494:        }
                    495:
                    496:        if distname != "" && pkgname != "" {
1.3       rillig    497:                pkgname = pkg.pkgnameFromDistname(pkgname, distname)
1.1       rillig    498:        }
                    499:
1.11      rillig    500:        if pkgname != "" && pkgname == distname && pkgnameLine.VarassignComment() == "" {
1.14      rillig    501:                pkgnameLine.Notef("PKGNAME is ${DISTNAME} by default. You probably don't need to define PKGNAME.")
1.1       rillig    502:        }
                    503:
                    504:        if pkgname == "" && distname != "" && !containsVarRef(distname) && !matches(distname, rePkgname) {
1.14      rillig    505:                distnameLine.Warnf("As DISTNAME is not a valid package name, please define the PKGNAME explicitly.")
1.1       rillig    506:        }
                    507:
1.3       rillig    508:        if pkgname != "" && !containsVarRef(pkgname) {
                    509:                if m, m1, m2 := match2(pkgname, rePkgname); m {
                    510:                        pkg.EffectivePkgname = pkgname + pkg.getNbpart()
                    511:                        pkg.EffectivePkgnameLine = pkgnameLine
                    512:                        pkg.EffectivePkgbase = m1
                    513:                        pkg.EffectivePkgversion = m2
                    514:                }
                    515:        }
                    516:        if pkg.EffectivePkgnameLine == nil && distname != "" && !containsVarRef(distname) {
                    517:                if m, m1, m2 := match2(distname, rePkgname); m {
                    518:                        pkg.EffectivePkgname = distname + pkg.getNbpart()
                    519:                        pkg.EffectivePkgnameLine = distnameLine
                    520:                        pkg.EffectivePkgbase = m1
                    521:                        pkg.EffectivePkgversion = m2
                    522:                }
                    523:        }
                    524:        if pkg.EffectivePkgnameLine != nil {
1.16      rillig    525:                if trace.Tracing {
                    526:                        trace.Stepf("Effective name=%q base=%q version=%q",
1.3       rillig    527:                                pkg.EffectivePkgname, pkg.EffectivePkgbase, pkg.EffectivePkgversion)
                    528:                }
1.1       rillig    529:        }
1.3       rillig    530: }
1.1       rillig    531:
1.3       rillig    532: func (pkg *Package) pkgnameFromDistname(pkgname, distname string) string {
1.9       rillig    533:        tokens := NewMkParser(dummyLine, pkgname, false).MkTokens()
1.1       rillig    534:
1.36    ! rillig    535:        // Example:
        !           536:        //  subst("distname-1.0", "S,name,file,g") => "distfile-1.0"
        !           537:        subst := func(str, smod string) string {
1.9       rillig    538:                qsep := regexp.QuoteMeta(smod[1:2])
1.36    ! rillig    539:                m, left, from, right, to, flags := match5(smod, regex.Pattern(`^S`+qsep+`(\^?)([^:]*?)(\$?)`+qsep+`([^:]*)`+qsep+`([1g]*)$`))
        !           540:                G.Assertf(m, "pkgnameFromDistname %q", smod)
        !           541:                result := mkopSubst(str, left != "", from, right != "", to, flags)
        !           542:                if trace.Tracing && result != str {
        !           543:                        trace.Stepf("pkgnameFromDistname.subst: %q %q => %q", str, smod, result)
1.3       rillig    544:                }
1.36    ! rillig    545:                return result
1.1       rillig    546:        }
1.9       rillig    547:
                    548:        result := ""
                    549:        for _, token := range tokens {
                    550:                if token.Varuse != nil && token.Varuse.varname == "DISTNAME" {
                    551:                        newDistname := distname
                    552:                        for _, mod := range token.Varuse.modifiers {
                    553:                                if mod == "tl" {
                    554:                                        newDistname = strings.ToLower(newDistname)
                    555:                                } else if hasPrefix(mod, "S") {
                    556:                                        newDistname = subst(newDistname, mod)
                    557:                                } else {
                    558:                                        newDistname = token.Text
                    559:                                        break
                    560:                                }
                    561:                        }
                    562:                        result += newDistname
                    563:                } else {
                    564:                        result += token.Text
                    565:                }
                    566:        }
                    567:        return result
1.3       rillig    568: }
1.1       rillig    569:
1.3       rillig    570: func (pkg *Package) checkUpdate() {
                    571:        if pkg.EffectivePkgbase != "" {
1.27      rillig    572:                for _, sugg := range G.Pkgsrc.GetSuggestedPackageUpdates() {
1.3       rillig    573:                        if pkg.EffectivePkgbase != sugg.Pkgname {
1.1       rillig    574:                                continue
                    575:                        }
                    576:
1.3       rillig    577:                        suggver, comment := sugg.Version, sugg.Comment
1.1       rillig    578:                        if comment != "" {
                    579:                                comment = " (" + comment + ")"
                    580:                        }
                    581:
1.3       rillig    582:                        pkgnameLine := pkg.EffectivePkgnameLine
1.15      rillig    583:                        cmp := pkgver.Compare(pkg.EffectivePkgversion, suggver)
1.1       rillig    584:                        switch {
                    585:                        case cmp < 0:
1.14      rillig    586:                                pkgnameLine.Warnf("This package should be updated to %s%s.", sugg.Version, comment)
                    587:                                Explain(
1.1       rillig    588:                                        "The wishlist for package updates in doc/TODO mentions that a newer",
                    589:                                        "version of this package is available.")
                    590:                        case cmp > 0:
1.14      rillig    591:                                pkgnameLine.Notef("This package is newer than the update request to %s%s.", suggver, comment)
1.1       rillig    592:                        default:
1.14      rillig    593:                                pkgnameLine.Notef("The update request to %s from doc/TODO%s has been done.", suggver, comment)
1.1       rillig    594:                        }
                    595:                }
                    596:        }
                    597: }
                    598:
1.25      rillig    599: // CheckVarorder checks that in simple package Makefiles,
                    600: // the most common variables appear in a fixed order.
                    601: // The order itself is a little arbitrary but provides
                    602: // at least a bit of consistency.
                    603: func (pkg *Package) CheckVarorder(mklines *MkLines) {
1.16      rillig    604:        if trace.Tracing {
                    605:                defer trace.Call0()()
1.1       rillig    606:        }
                    607:
1.3       rillig    608:        if !G.opts.WarnOrder || pkg.seenMakefileCommon {
1.1       rillig    609:                return
                    610:        }
                    611:
1.25      rillig    612:        type Repetition uint8
1.1       rillig    613:        const (
1.25      rillig    614:                optional Repetition = iota
                    615:                once
1.1       rillig    616:                many
                    617:        )
1.25      rillig    618:        type Variable struct {
                    619:                varname    string
                    620:                repetition Repetition
                    621:        }
                    622:        type Section struct {
                    623:                repetition Repetition
                    624:                vars       []Variable
                    625:        }
                    626:        variable := func(name string, repetition Repetition) Variable { return Variable{name, repetition} }
                    627:        section := func(repetition Repetition, vars ...Variable) Section { return Section{repetition, vars} }
                    628:
                    629:        var sections = []Section{
                    630:                section(once,
                    631:                        variable("GITHUB_PROJECT", optional), // either here or below MASTER_SITES
                    632:                        variable("GITHUB_TAG", optional),
                    633:                        variable("DISTNAME", optional),
                    634:                        variable("PKGNAME", optional),
                    635:                        variable("PKGREVISION", optional),
                    636:                        variable("CATEGORIES", once),
                    637:                        variable("MASTER_SITES", many),
                    638:                        variable("GITHUB_PROJECT", optional), // either here or at the very top
                    639:                        variable("GITHUB_TAG", optional),
                    640:                        variable("DIST_SUBDIR", optional),
                    641:                        variable("EXTRACT_SUFX", optional),
                    642:                        variable("DISTFILES", many),
                    643:                        variable("SITES.*", many)),
                    644:                section(optional,
                    645:                        variable("PATCH_SITES", optional), // or once?
                    646:                        variable("PATCH_SITE_SUBDIR", optional),
                    647:                        variable("PATCHFILES", optional), // or once?
                    648:                        variable("PATCH_DIST_ARGS", optional),
                    649:                        variable("PATCH_DIST_STRIP", optional),
                    650:                        variable("PATCH_DIST_CAT", optional)),
                    651:                section(once,
                    652:                        variable("MAINTAINER", optional),
                    653:                        variable("OWNER", optional),
                    654:                        variable("HOMEPAGE", optional),
                    655:                        variable("COMMENT", once),
                    656:                        variable("LICENSE", once)),
                    657:                section(optional,
                    658:                        variable("LICENSE_FILE", optional),
                    659:                        variable("RESTRICTED", optional),
                    660:                        variable("NO_BIN_ON_CDROM", optional),
                    661:                        variable("NO_BIN_ON_FTP", optional),
                    662:                        variable("NO_SRC_ON_CDROM", optional),
                    663:                        variable("NO_SRC_ON_FTP", optional)),
                    664:                section(optional,
                    665:                        variable("BROKEN_EXCEPT_ON_PLATFORM", many),
                    666:                        variable("BROKEN_ON_PLATFORM", many),
                    667:                        variable("NOT_FOR_PLATFORM", many),
                    668:                        variable("ONLY_FOR_PLATFORM", many),
                    669:                        variable("NOT_FOR_COMPILER", many),
                    670:                        variable("ONLY_FOR_COMPILER", many),
                    671:                        variable("NOT_FOR_UNPRIVILEGED", optional),
                    672:                        variable("ONLY_FOR_UNPRIVILEGED", optional)),
                    673:                section(optional,
                    674:                        variable("BUILD_DEPENDS", many),
                    675:                        variable("TOOL_DEPENDS", many),
                    676:                        variable("DEPENDS", many)),
                    677:        }
                    678:
                    679:        firstRelevant := -1
                    680:        lastRelevant := -1
                    681:        skip := func() bool {
                    682:                relevantVars := make(map[string]bool)
                    683:                for _, section := range sections {
                    684:                        for _, variable := range section.vars {
                    685:                                relevantVars[variable.varname] = true
                    686:                        }
                    687:                }
1.1       rillig    688:
1.25      rillig    689:                firstIrrelevant := -1
                    690:                for i, mkline := range mklines.mklines {
                    691:                        switch {
                    692:                        case mkline.IsVarassign(), mkline.IsCommentedVarassign():
                    693:                                varcanon := mkline.Varcanon()
                    694:                                if relevantVars[varcanon] {
                    695:                                        if firstRelevant == -1 {
                    696:                                                firstRelevant = i
                    697:                                        }
                    698:                                        if firstIrrelevant != -1 {
                    699:                                                if trace.Tracing {
                    700:                                                        trace.Stepf("Skipping varorder because of line %s.",
                    701:                                                                mklines.mklines[firstIrrelevant].Linenos())
                    702:                                                }
                    703:                                                return true
                    704:                                        }
                    705:                                        lastRelevant = i
                    706:                                } else {
                    707:                                        if firstIrrelevant == -1 {
                    708:                                                firstIrrelevant = i
                    709:                                        }
                    710:                                }
1.1       rillig    711:
1.25      rillig    712:                        case mkline.IsComment(), mkline.IsEmpty():
1.1       rillig    713:                                break
1.25      rillig    714:
                    715:                        default:
                    716:                                if firstIrrelevant == -1 {
                    717:                                        firstIrrelevant = i
                    718:                                }
1.1       rillig    719:                        }
                    720:                }
                    721:
1.28      rillig    722:                if firstRelevant == -1 {
                    723:                        return true
                    724:                }
1.25      rillig    725:                interesting := mklines.mklines[firstRelevant : lastRelevant+1]
1.1       rillig    726:
1.25      rillig    727:                varcanon := func() string {
                    728:                        for len(interesting) != 0 && interesting[0].IsComment() {
                    729:                                interesting = interesting[1:]
                    730:                        }
                    731:                        if len(interesting) != 0 && (interesting[0].IsVarassign() || interesting[0].IsCommentedVarassign()) {
                    732:                                return interesting[0].Varcanon()
                    733:                        }
                    734:                        return ""
                    735:                }
1.1       rillig    736:
1.25      rillig    737:                for _, section := range sections {
                    738:                        for _, variable := range section.vars {
                    739:                                switch variable.repetition {
                    740:                                case optional:
                    741:                                        if varcanon() == variable.varname {
                    742:                                                interesting = interesting[1:]
                    743:                                        }
                    744:                                case once:
                    745:                                        if varcanon() == variable.varname {
                    746:                                                interesting = interesting[1:]
                    747:                                        } else if section.repetition == once {
                    748:                                                if variable.varname != "LICENSE" {
                    749:                                                        if trace.Tracing {
                    750:                                                                trace.Stepf("Wrong varorder because %s is missing.", variable.varname)
                    751:                                                        }
                    752:                                                        return false
                    753:                                                }
                    754:                                        }
                    755:                                case many:
                    756:                                        for varcanon() == variable.varname {
                    757:                                                interesting = interesting[1:]
                    758:                                        }
1.1       rillig    759:                                }
                    760:                        }
                    761:
1.25      rillig    762:                        for len(interesting) != 0 && (interesting[0].IsEmpty() || interesting[0].IsComment()) {
                    763:                                interesting = interesting[1:]
1.1       rillig    764:                        }
1.25      rillig    765:                }
1.1       rillig    766:
1.25      rillig    767:                return len(interesting) == 0
                    768:        }
1.1       rillig    769:
1.25      rillig    770:        if skip() {
                    771:                return
                    772:        }
1.1       rillig    773:
1.25      rillig    774:        var canonical []string
                    775:        for _, section := range sections {
                    776:                for _, variable := range section.vars {
                    777:                        found := false
                    778:                        for _, mkline := range mklines.mklines[firstRelevant : lastRelevant+1] {
                    779:                                if mkline.IsVarassign() || mkline.IsCommentedVarassign() {
                    780:                                        if mkline.Varcanon() == variable.varname {
                    781:                                                canonical = append(canonical, mkline.Varname())
                    782:                                                found = true
1.21      rillig    783:                                        }
1.1       rillig    784:                                }
                    785:                        }
1.25      rillig    786:                        if !found && section.repetition == once && variable.repetition == once {
                    787:                                canonical = append(canonical, variable.varname)
1.1       rillig    788:                        }
                    789:                }
1.25      rillig    790:                if len(canonical) != 0 && canonical[len(canonical)-1] != "empty line" {
                    791:                        canonical = append(canonical, "empty line")
                    792:                }
                    793:        }
                    794:        if len(canonical) != 0 && canonical[len(canonical)-1] == "empty line" {
                    795:                canonical = canonical[:len(canonical)-1]
1.1       rillig    796:        }
1.25      rillig    797:
                    798:        mkline := mklines.mklines[firstRelevant]
                    799:        mkline.Warnf("The canonical order of the variables is %s.", strings.Join(canonical, ", "))
                    800:        Explain(
                    801:                "In simple package Makefiles, some common variables should be",
                    802:                "arranged in a specific order.",
                    803:                "",
                    804:                "See doc/Makefile-example or the pkgsrc guide, section",
                    805:                "\"Package components\", subsection \"Makefile\" for more information.")
1.1       rillig    806: }
1.3       rillig    807:
                    808: func (mklines *MkLines) checkForUsedComment(relativeName string) {
                    809:        lines := mklines.lines
                    810:        if len(lines) < 3 {
                    811:                return
                    812:        }
                    813:
                    814:        expected := "# used by " + relativeName
                    815:        for _, line := range lines {
1.20      rillig    816:                if line.Text == expected {
1.3       rillig    817:                        return
                    818:                }
                    819:        }
                    820:
                    821:        i := 0
1.20      rillig    822:        for i < 2 && hasPrefix(lines[i].Text, "#") {
1.3       rillig    823:                i++
                    824:        }
                    825:
1.23      rillig    826:        fix := lines[i].Autofix()
                    827:        fix.Warnf("Please add a line %q here.", expected)
                    828:        fix.Explain(
                    829:                "Since Makefile.common files usually don't have any comments and",
                    830:                "therefore not a clearly defined interface, they should at least",
                    831:                "contain references to all files that include them, so that it is",
                    832:                "easier to see what effects future changes may have.",
                    833:                "",
                    834:                "If there are more than five packages that use a Makefile.common,",
                    835:                "you should think about giving it a proper name (maybe plugin.mk) and",
                    836:                "documenting its interface.")
                    837:        fix.InsertBefore(expected)
                    838:        fix.Apply()
                    839:
1.3       rillig    840:        SaveAutofixChanges(lines)
                    841: }
1.9       rillig    842:
                    843: func (pkg *Package) checkLocallyModified(fname string) {
1.16      rillig    844:        if trace.Tracing {
                    845:                defer trace.Call(fname)()
1.9       rillig    846:        }
                    847:
1.35      rillig    848:        owner, _ := pkg.vars.Value("OWNER")
                    849:        maintainer, _ := pkg.vars.Value("MAINTAINER")
1.36    ! rillig    850:        if maintainer == "pkgsrc-users@NetBSD.org" {
1.35      rillig    851:                maintainer = ""
1.9       rillig    852:        }
                    853:        if owner == "" && maintainer == "" {
                    854:                return
                    855:        }
                    856:
1.13      rillig    857:        username := G.CurrentUsername
1.16      rillig    858:        if trace.Tracing {
                    859:                trace.Stepf("user=%q owner=%q maintainer=%q", username, owner, maintainer)
1.9       rillig    860:        }
                    861:
                    862:        if username == strings.Split(owner, "@")[0] || username == strings.Split(maintainer, "@")[0] {
                    863:                return
                    864:        }
                    865:
                    866:        if isLocallyModified(fname) {
                    867:                if owner != "" {
1.14      rillig    868:                        NewLineWhole(fname).Warnf("Don't commit changes to this file without asking the OWNER, %s.", owner)
                    869:                        Explain(
1.9       rillig    870:                                "See the pkgsrc guide, section \"Package components\",",
                    871:                                "keyword \"owner\", for more information.")
                    872:                }
                    873:                if maintainer != "" {
1.14      rillig    874:                        NewLineWhole(fname).Notef("Please only commit changes that %s would approve.", maintainer)
                    875:                        Explain(
1.9       rillig    876:                                "See the pkgsrc guide, section \"Package components\",",
                    877:                                "keyword \"maintainer\", for more information.")
                    878:                }
                    879:        }
                    880: }
1.12      rillig    881:
1.18      rillig    882: func (pkg *Package) CheckInclude(mkline MkLine, indentation *Indentation) {
1.34      rillig    883:        conditionalVars := mkline.ConditionalVars()
                    884:        if conditionalVars == "" {
                    885:                conditionalVars = indentation.Varnames()
                    886:                mkline.SetConditionalVars(conditionalVars)
1.12      rillig    887:        }
                    888:
1.33      rillig    889:        if path.Dir(abspath(mkline.Filename)) == abspath(pkg.File(".")) {
1.31      rillig    890:                includefile := mkline.IncludeFile()
1.12      rillig    891:
                    892:                if indentation.IsConditional() {
                    893:                        pkg.conditionalIncludes[includefile] = mkline
                    894:                        if other := pkg.unconditionalIncludes[includefile]; other != nil {
1.16      rillig    895:                                mkline.Warnf("%q is included conditionally here (depending on %s) and unconditionally in %s.",
1.34      rillig    896:                                        cleanpath(includefile), mkline.ConditionalVars(), other.ReferenceFrom(mkline.Line))
1.12      rillig    897:                        }
                    898:                } else {
                    899:                        pkg.unconditionalIncludes[includefile] = mkline
                    900:                        if other := pkg.conditionalIncludes[includefile]; other != nil {
1.16      rillig    901:                                mkline.Warnf("%q is included unconditionally here and conditionally in %s (depending on %s).",
1.34      rillig    902:                                        cleanpath(includefile), other.ReferenceFrom(mkline.Line), other.ConditionalVars())
1.12      rillig    903:                        }
                    904:                }
                    905:        }
                    906: }
1.22      rillig    907:
                    908: func (pkg *Package) loadPlistDirs(plistFilename string) {
1.34      rillig    909:        lines := Load(plistFilename, MustSucceed)
1.22      rillig    910:        for _, line := range lines {
                    911:                text := line.Text
1.34      rillig    912:                pkg.PlistFiles[text] = true // XXX: ignores PLIST conditions for now
1.22      rillig    913:                // Keep in sync with PlistChecker.collectFilesAndDirs
                    914:                if !contains(text, "$") && !contains(text, "@") {
                    915:                        for dir := path.Dir(text); dir != "."; dir = path.Dir(dir) {
                    916:                                pkg.PlistDirs[dir] = true
                    917:                        }
                    918:                }
                    919:        }
                    920: }

CVSweb <webmaster@jp.NetBSD.org>