[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.56

1.41      rillig      1: package pkglint
1.1       rillig      2:
                      3: import (
1.15      rillig      4:        "netbsd.org/pkglint/pkgver"
1.48      rillig      5:        "os"
1.1       rillig      6:        "path"
                      7:        "strconv"
                      8:        "strings"
                      9: )
                     10:
1.41      rillig     11: // TODO: What about package names that refer to other variables?
1.31      rillig     12: const rePkgname = `^([\w\-.+]+)-(\d[.0-9A-Z_a-z]*)$`
1.15      rillig     13:
1.41      rillig     14: // Package is the pkgsrc package that is currently checked.
                     15: //
                     16: // Most of the information is loaded first, and after loading the actual checks take place.
                     17: // This is necessary because variables in Makefiles may be used before they are defined,
                     18: // and such dependencies often span multiple files that are included indirectly.
1.3       rillig     19: type Package struct {
1.38      rillig     20:        dir                  string       // The directory of the package, for resolving files
                     21:        Pkgpath              string       // e.g. "category/pkgdir"
                     22:        Pkgdir               string       // PKGDIR from the package Makefile
                     23:        Filesdir             string       // FILESDIR from the package Makefile
                     24:        Patchdir             string       // PATCHDIR from the package Makefile
                     25:        DistinfoFile         string       // DISTINFO_FILE from the package Makefile
                     26:        EffectivePkgname     string       // PKGNAME or DISTNAME from the package Makefile, including nb13
1.41      rillig     27:        EffectivePkgbase     string       // EffectivePkgname without the version
1.38      rillig     28:        EffectivePkgversion  string       // The version part of the effective PKGNAME, excluding nb13
1.41      rillig     29:        EffectivePkgnameLine MkLine       // The origin of the three Effective* values
1.38      rillig     30:        Plist                PlistContent // Files and directories mentioned in the PLIST files
1.18      rillig     31:
1.47      rillig     32:        vars Scope
                     33:        bl3  map[string]MkLine // buildlink3.mk name => line; contains only buildlink3.mk files that are directly included.
                     34:
                     35:        // Remembers the Makefile fragments that have already been included.
                     36:        // The key to the map is the filename relative to the package directory.
                     37:        // Typical keys are "../../category/package/buildlink3.mk".
                     38:        //
                     39:        // TODO: Include files with multiple-inclusion guard only once.
                     40:        //
                     41:        // TODO: Include files without multiple-inclusion guard as often as needed.
                     42:        //
                     43:        // TODO: Set an upper limit, to prevent denial of service.
                     44:        included Once
                     45:
                     46:        seenMakefileCommon bool // Does the package have any .includes?
1.41      rillig     47:
                     48:        // Files from .include lines that are nested inside .if.
                     49:        // They often depend on OPSYS or on the existence of files in the build environment.
                     50:        conditionalIncludes map[string]MkLine
                     51:        // Files from .include lines that are not nested.
                     52:        // These are cross-checked with buildlink3.mk whether they are unconditional there, too.
1.18      rillig     53:        unconditionalIncludes map[string]MkLine
1.41      rillig     54:
                     55:        once                 Once
                     56:        IgnoreMissingPatches bool // In distinfo, don't warn about patches that cannot be found.
1.3       rillig     57: }
                     58:
1.34      rillig     59: func NewPackage(dir string) *Package {
                     60:        pkgpath := G.Pkgsrc.ToRel(dir)
                     61:        if strings.Count(pkgpath, "/") != 1 {
1.56    ! rillig     62:                assertf(false, "Package directory %q must be two subdirectories below the pkgsrc root %q.",
1.41      rillig     63:                        dir, G.Pkgsrc.File("."))
1.34      rillig     64:        }
                     65:
1.39      rillig     66:        pkg := Package{
1.34      rillig     67:                dir:                   dir,
1.12      rillig     68:                Pkgpath:               pkgpath,
1.33      rillig     69:                Pkgdir:                ".",
1.41      rillig     70:                Filesdir:              "files",              // TODO: Redundant, see the vars.Fallback below.
                     71:                Patchdir:              "patches",            // TODO: Redundant, see the vars.Fallback below.
                     72:                DistinfoFile:          "${PKGDIR}/distinfo", // TODO: Redundant, see the vars.Fallback below.
1.38      rillig     73:                Plist:                 NewPlistContent(),
1.26      rillig     74:                vars:                  NewScope(),
1.41      rillig     75:                bl3:                   make(map[string]MkLine),
1.47      rillig     76:                included:              Once{},
1.18      rillig     77:                conditionalIncludes:   make(map[string]MkLine),
                     78:                unconditionalIncludes: make(map[string]MkLine),
1.3       rillig     79:        }
1.35      rillig     80:        pkg.vars.DefineAll(G.Pkgsrc.UserDefinedVars)
1.36      rillig     81:
                     82:        pkg.vars.Fallback("PKGDIR", ".")
                     83:        pkg.vars.Fallback("DISTINFO_FILE", "${PKGDIR}/distinfo")
                     84:        pkg.vars.Fallback("FILESDIR", "files")
                     85:        pkg.vars.Fallback("PATCHDIR", "patches")
                     86:        pkg.vars.Fallback("KRB5_TYPE", "heimdal")
                     87:        pkg.vars.Fallback("PGSQL_VERSION", "95")
1.50      rillig     88:
                     89:        // In reality, this is an absolute pathname. Since this variable is
                     90:        // typically used in the form ${.CURDIR}/../../somewhere, this doesn't
                     91:        // matter much.
                     92:        pkg.vars.Fallback(".CURDIR", ".")
1.36      rillig     93:
1.39      rillig     94:        return &pkg
1.3       rillig     95: }
                     96:
1.38      rillig     97: // File returns the (possibly absolute) path to relativeFileName,
1.33      rillig     98: // as resolved from the package's directory.
1.36      rillig     99: // Variables that are known in the package are resolved, e.g. ${PKGDIR}.
1.38      rillig    100: func (pkg *Package) File(relativeFileName string) string {
1.49      rillig    101:        return cleanpath(resolveVariableRefs(nil, pkg.dir+"/"+relativeFileName))
1.33      rillig    102: }
                    103:
1.3       rillig    104: func (pkg *Package) checkPossibleDowngrade() {
1.16      rillig    105:        if trace.Tracing {
                    106:                defer trace.Call0()()
1.3       rillig    107:        }
                    108:
                    109:        m, _, pkgversion := match2(pkg.EffectivePkgname, rePkgname)
1.1       rillig    110:        if !m {
                    111:                return
                    112:        }
                    113:
1.3       rillig    114:        mkline := pkg.EffectivePkgnameLine
1.1       rillig    115:
1.27      rillig    116:        change := G.Pkgsrc.LastChange[pkg.Pkgpath]
1.1       rillig    117:        if change == nil {
1.16      rillig    118:                if trace.Tracing {
                    119:                        trace.Step1("No change log for package %q", pkg.Pkgpath)
1.3       rillig    120:                }
1.1       rillig    121:                return
                    122:        }
                    123:
1.3       rillig    124:        if change.Action == "Updated" {
1.50      rillig    125:                pkgversionNorev := replaceAll(pkgversion, `nb\d+$`, "")
                    126:                changeNorev := replaceAll(change.Version, `nb\d+$`, "")
                    127:                cmp := pkgver.Compare(pkgversionNorev, changeNorev)
                    128:                switch {
                    129:                case cmp < 0:
1.41      rillig    130:                        mkline.Warnf("The package is being downgraded from %s (see %s) to %s.",
1.44      rillig    131:                                change.Version, mkline.Line.RefToLocation(change.Location), pkgversion)
1.50      rillig    132:                        mkline.Explain(
1.4       rillig    133:                                "The files in doc/CHANGES-*, in which all version changes are",
                    134:                                "recorded, have a higher version number than what the package says.",
                    135:                                "This is unusual, since packages are typically upgraded instead of",
                    136:                                "downgraded.")
1.41      rillig    137:
1.52      rillig    138:                case cmp > 0 && !isLocallyModified(mkline.Filename):
1.50      rillig    139:                        mkline.Notef("Package version %q is greater than the latest %q from %s.",
                    140:                                pkgversion, change.Version, mkline.Line.RefToLocation(change.Location))
                    141:                        mkline.Explain(
                    142:                                "Each update to a package should be mentioned in the doc/CHANGES file.",
                    143:                                "To do this after updating a package, run",
                    144:                                sprintf("%q,", bmake("cce")),
                    145:                                "which is the abbreviation for commit-changes-entry.")
1.1       rillig    146:                }
                    147:        }
                    148: }
                    149:
1.42      rillig    150: // checkLinesBuildlink3Inclusion checks whether the package Makefile and
1.41      rillig    151: // the corresponding buildlink3.mk agree for all included buildlink3.mk
                    152: // files whether they are included conditionally or unconditionally.
1.42      rillig    153: func (pkg *Package) checkLinesBuildlink3Inclusion(mklines MkLines) {
1.16      rillig    154:        if trace.Tracing {
                    155:                defer trace.Call0()()
1.1       rillig    156:        }
                    157:
                    158:        // Collect all the included buildlink3.mk files from the file.
1.18      rillig    159:        includedFiles := make(map[string]MkLine)
1.3       rillig    160:        for _, mkline := range mklines.mklines {
                    161:                if mkline.IsInclude() {
1.39      rillig    162:                        includedFile := mkline.IncludedFile()
1.44      rillig    163:                        if matches(includedFile, `^\.\./\.\./.*/buildlink3\.mk`) {
                    164:                                includedFiles[includedFile] = mkline
                    165:                                if pkg.bl3[includedFile] == nil {
                    166:                                        mkline.Warnf("%s is included by this file but not by the package.", includedFile)
1.1       rillig    167:                                }
                    168:                        }
                    169:                }
                    170:        }
                    171:
1.16      rillig    172:        if trace.Tracing {
1.8       rillig    173:                for packageBl3 := range pkg.bl3 {
1.1       rillig    174:                        if includedFiles[packageBl3] == nil {
1.44      rillig    175:                                trace.Step1("%s is included by the package but not by the buildlink3.mk file.", packageBl3)
1.1       rillig    176:                        }
                    177:                }
                    178:        }
                    179: }
                    180:
1.48      rillig    181: func (pkg *Package) load() ([]string, MkLines, MkLines) {
                    182:        // Load the package Makefile and all included files,
                    183:        // to collect all used and defined variables and similar data.
                    184:        mklines, allLines := pkg.loadPackageMakefile()
                    185:        if mklines == nil {
                    186:                return nil, nil, nil
                    187:        }
                    188:
                    189:        files := dirglob(pkg.File("."))
                    190:        if pkg.Pkgdir != "." {
                    191:                files = append(files, dirglob(pkg.File(pkg.Pkgdir))...)
                    192:        }
                    193:        if G.Opts.CheckExtra {
                    194:                files = append(files, dirglob(pkg.File(pkg.Filesdir))...)
                    195:        }
                    196:        files = append(files, dirglob(pkg.File(pkg.Patchdir))...)
                    197:        if pkg.DistinfoFile != pkg.vars.fallback["DISTINFO_FILE"] {
                    198:                files = append(files, pkg.File(pkg.DistinfoFile))
                    199:        }
                    200:
                    201:        // Determine the used variables and PLIST directories before checking any of the Makefile fragments.
                    202:        // TODO: Why is this code necessary? What effect does it have?
                    203:        for _, filename := range files {
                    204:                basename := path.Base(filename)
                    205:                if (hasPrefix(basename, "Makefile.") || hasSuffix(filename, ".mk")) &&
                    206:                        !matches(filename, `patch-`) &&
                    207:                        !contains(filename, pkg.Pkgdir+"/") &&
                    208:                        !contains(filename, pkg.Filesdir+"/") {
                    209:                        if fragmentMklines := LoadMk(filename, MustSucceed); fragmentMklines != nil {
                    210:                                fragmentMklines.collectUsedVariables()
                    211:                        }
                    212:                }
                    213:                if hasPrefix(basename, "PLIST") {
                    214:                        pkg.loadPlistDirs(filename)
                    215:                }
                    216:        }
                    217:
                    218:        return files, mklines, allLines
                    219: }
                    220:
1.53      rillig    221: func (pkg *Package) check(filenames []string, mklines, allLines MkLines) {
1.48      rillig    222:        haveDistinfo := false
                    223:        havePatches := false
                    224:
1.53      rillig    225:        for _, filename := range filenames {
1.48      rillig    226:                if containsVarRef(filename) {
                    227:                        if trace.Tracing {
                    228:                                trace.Stepf("Skipping file %q because the name contains an unresolved variable.", filename)
                    229:                        }
                    230:                        continue
                    231:                }
                    232:
                    233:                st, err := os.Lstat(filename)
                    234:                switch {
                    235:                case err != nil:
                    236:                        // For missing custom distinfo file, an error message is already generated
                    237:                        // for the line where DISTINFO_FILE is defined.
                    238:                        //
                    239:                        // For all other cases it is next to impossible to reach this branch
                    240:                        // since all those files come from calls to dirglob.
                    241:                        break
                    242:
                    243:                case path.Base(filename) == "Makefile":
                    244:                        G.checkExecutable(filename, st.Mode())
                    245:                        pkg.checkfilePackageMakefile(filename, mklines, allLines)
                    246:
                    247:                default:
                    248:                        G.checkDirent(filename, st.Mode())
                    249:                }
                    250:
                    251:                if contains(filename, "/patches/patch-") {
                    252:                        havePatches = true
                    253:                } else if hasSuffix(filename, "/distinfo") {
                    254:                        haveDistinfo = true
                    255:                }
                    256:                pkg.checkLocallyModified(filename)
                    257:        }
                    258:
                    259:        if pkg.Pkgdir == "." {
                    260:                if havePatches && !haveDistinfo {
1.51      rillig    261:                        line := NewLineWhole(pkg.File(pkg.DistinfoFile))
                    262:                        line.Warnf("A package with patches should have a distinfo file.")
                    263:                        line.Explain(
                    264:                                "To generate a distinfo file for the existing patches, run",
                    265:                                sprintf("%q.", bmake("makepatchsum")))
1.48      rillig    266:                }
                    267:        }
                    268: }
                    269:
1.47      rillig    270: func (pkg *Package) loadPackageMakefile() (MkLines, MkLines) {
1.39      rillig    271:        filename := pkg.File("Makefile")
1.16      rillig    272:        if trace.Tracing {
1.39      rillig    273:                defer trace.Call1(filename)()
1.3       rillig    274:        }
                    275:
1.39      rillig    276:        mainLines := NewMkLines(NewLines(filename, nil))
1.38      rillig    277:        allLines := NewMkLines(NewLines("", nil))
1.39      rillig    278:        if _, result := pkg.readMakefile(filename, mainLines, allLines, ""); !result {
                    279:                LoadMk(filename, NotEmpty|LogErrors) // Just for the LogErrors.
1.47      rillig    280:                return nil, nil
1.3       rillig    281:        }
                    282:
1.41      rillig    283:        // TODO: Is this still necessary? This code is 20 years old and was introduced
1.46      rillig    284:        //  when pkglint loaded the package Makefile including all included files into
                    285:        //  a single string. Maybe it makes sense to print the file inclusion hierarchy
                    286:        //  to quickly see files that cannot be included because of unresolved variables.
1.38      rillig    287:        if G.Opts.DumpMakefile {
1.54      rillig    288:                G.Logger.out.WriteLine("Whole Makefile (with all included files) follows:")
1.38      rillig    289:                for _, line := range allLines.lines.Lines {
1.54      rillig    290:                        G.Logger.out.WriteLine(line.String())
1.3       rillig    291:                }
                    292:        }
                    293:
1.41      rillig    294:        // See mk/tools/cmake.mk
1.37      rillig    295:        if pkg.vars.Defined("USE_CMAKE") {
1.55      rillig    296:                allLines.Tools.def("cmake", "", false, AtRunTime, nil)
                    297:                allLines.Tools.def("cpack", "", false, AtRunTime, nil)
1.37      rillig    298:        }
                    299:
1.40      rillig    300:        allLines.collectUsedVariables()
1.3       rillig    301:
1.45      rillig    302:        pkg.Pkgdir = pkg.vars.LastValue("PKGDIR")
                    303:        pkg.DistinfoFile = pkg.vars.LastValue("DISTINFO_FILE")
                    304:        pkg.Filesdir = pkg.vars.LastValue("FILESDIR")
                    305:        pkg.Patchdir = pkg.vars.LastValue("PATCHDIR")
1.3       rillig    306:
1.33      rillig    307:        // See lang/php/ext.mk
1.49      rillig    308:        if pkg.vars.DefinedSimilar("PHPEXT_MK") {
                    309:                if !pkg.vars.DefinedSimilar("USE_PHP_EXT_PATCHES") {
1.3       rillig    310:                        pkg.Patchdir = "patches"
                    311:                }
1.49      rillig    312:                if pkg.vars.DefinedSimilar("PECL_VERSION") {
1.3       rillig    313:                        pkg.DistinfoFile = "distinfo"
1.33      rillig    314:                } else {
                    315:                        pkg.IgnoreMissingPatches = true
1.3       rillig    316:                }
1.33      rillig    317:
                    318:                // For PHP modules that are not PECL, this combination means that
                    319:                // the patches in the distinfo cannot be found in PATCHDIR.
1.3       rillig    320:        }
                    321:
1.16      rillig    322:        if trace.Tracing {
                    323:                trace.Step1("DISTINFO_FILE=%s", pkg.DistinfoFile)
                    324:                trace.Step1("FILESDIR=%s", pkg.Filesdir)
                    325:                trace.Step1("PATCHDIR=%s", pkg.Patchdir)
                    326:                trace.Step1("PKGDIR=%s", pkg.Pkgdir)
1.3       rillig    327:        }
                    328:
1.47      rillig    329:        return mainLines, allLines
1.3       rillig    330: }
                    331:
1.41      rillig    332: // TODO: What is allLines used for, is it still necessary? Would it be better as a field in Package?
                    333: func (pkg *Package) readMakefile(filename string, mainLines MkLines, allLines MkLines, includingFileForUsedCheck string) (exists bool, result bool) {
1.16      rillig    334:        if trace.Tracing {
1.39      rillig    335:                defer trace.Call1(filename)()
1.3       rillig    336:        }
                    337:
1.41      rillig    338:        fileMklines := LoadMk(filename, NotEmpty) // TODO: Document why omitting LogErrors is correct here.
1.34      rillig    339:        if fileMklines == nil {
1.36      rillig    340:                return false, false
1.3       rillig    341:        }
1.47      rillig    342:
1.36      rillig    343:        exists = true
1.47      rillig    344:        result = true
1.3       rillig    345:
                    346:        isMainMakefile := len(mainLines.mklines) == 0
1.1       rillig    347:
1.47      rillig    348:        handleIncludeLine := func(mkline MkLine) YesNoUnknown {
                    349:                includedFile, incDir, incBase := pkg.findIncludedFile(mkline, filename)
                    350:
                    351:                if includedFile == "" {
                    352:                        return unknown
1.3       rillig    353:                }
                    354:
1.47      rillig    355:                dirname, _ := path.Split(filename)
                    356:                dirname = cleanpath(dirname)
                    357:                fullIncluded := dirname + "/" + includedFile
                    358:                relIncludedFile := relpath(pkg.dir, fullIncluded)
                    359:
                    360:                if !pkg.diveInto(filename, includedFile) {
                    361:                        return unknown
                    362:                }
1.3       rillig    363:
1.47      rillig    364:                if !pkg.included.FirstTime(relIncludedFile) {
                    365:                        return unknown
                    366:                }
1.3       rillig    367:
1.47      rillig    368:                pkg.collectUsedBy(mkline, incDir, incBase, includedFile)
                    369:
                    370:                if trace.Tracing {
                    371:                        trace.Step1("Including %q.", fullIncluded)
                    372:                }
                    373:                fullIncluding := ifelseStr(incBase == "Makefile.common" && incDir != "", filename, "")
                    374:                innerExists, innerResult := pkg.readMakefile(fullIncluded, mainLines, allLines, fullIncluding)
1.3       rillig    375:
1.47      rillig    376:                if !innerExists {
                    377:                        if fileMklines.indentation.IsCheckedFile(includedFile) {
                    378:                                return yes // See https://github.com/rillig/pkglint/issues/1
                    379:                        }
1.3       rillig    380:
1.47      rillig    381:                        // Only look in the directory relative to the
                    382:                        // current file and in the package directory.
                    383:                        // Make(1) has a list of include directories, but pkgsrc
                    384:                        // doesn't make use of that, so pkglint also doesn't
                    385:                        // need this extra complexity.
                    386:                        pkgBasedir := pkg.File(".")
                    387:                        if dirname != pkgBasedir { // Prevent unnecessary syscalls
                    388:                                dirname = pkgBasedir
1.3       rillig    389:
1.47      rillig    390:                                fullIncludedFallback := dirname + "/" + includedFile
                    391:                                innerExists, innerResult = pkg.readMakefile(fullIncludedFallback, mainLines, allLines, fullIncluding)
                    392:                        }
1.29      rillig    393:
1.47      rillig    394:                        if !innerExists {
                    395:                                mkline.Errorf("Cannot read %q.", includedFile)
                    396:                        }
                    397:                }
1.36      rillig    398:
1.47      rillig    399:                if !innerResult {
                    400:                        result = false
                    401:                        return no
                    402:                }
1.36      rillig    403:
1.47      rillig    404:                return unknown
                    405:        }
1.29      rillig    406:
1.47      rillig    407:        lineAction := func(mkline MkLine) bool {
                    408:                if isMainMakefile {
                    409:                        mainLines.mklines = append(mainLines.mklines, mkline)
                    410:                        mainLines.lines.Lines = append(mainLines.lines.Lines, mkline.Line)
                    411:                }
                    412:                allLines.mklines = append(allLines.mklines, mkline)
                    413:                allLines.lines.Lines = append(allLines.lines.Lines, mkline.Line)
1.3       rillig    414:
1.47      rillig    415:                if mkline.IsInclude() {
                    416:                        includeResult := handleIncludeLine(mkline)
                    417:                        if includeResult != unknown {
                    418:                                return includeResult == yes
1.3       rillig    419:                        }
                    420:                }
                    421:
                    422:                if mkline.IsVarassign() {
                    423:                        varname, op, value := mkline.Varname(), mkline.Op(), mkline.Value()
                    424:
1.36      rillig    425:                        if op != opAssignDefault || !pkg.vars.Defined(varname) {
1.16      rillig    426:                                if trace.Tracing {
                    427:                                        trace.Stepf("varassign(%q, %q, %q)", varname, op, value)
1.3       rillig    428:                                }
1.36      rillig    429:                                pkg.vars.Define(varname, mkline)
1.3       rillig    430:                        }
                    431:                }
1.32      rillig    432:                return true
1.3       rillig    433:        }
1.47      rillig    434:
1.32      rillig    435:        atEnd := func(mkline MkLine) {}
1.35      rillig    436:        fileMklines.ForEachEnd(lineAction, atEnd)
1.3       rillig    437:
1.41      rillig    438:        if includingFileForUsedCheck != "" {
                    439:                fileMklines.CheckForUsedComment(G.Pkgsrc.ToRel(includingFileForUsedCheck))
1.3       rillig    440:        }
                    441:
1.39      rillig    442:        // For every included buildlink3.mk, include the corresponding builtin.mk
                    443:        // automatically since the pkgsrc infrastructure does the same.
1.44      rillig    444:        if path.Base(filename) == "buildlink3.mk" {
1.47      rillig    445:                builtin := cleanpath(path.Dir(filename) + "/builtin.mk")
                    446:                builtinRel := relpath(pkg.dir, builtin)
                    447:                if pkg.included.FirstTime(builtinRel) && fileExists(builtin) {
1.39      rillig    448:                        pkg.readMakefile(builtin, mainLines, allLines, "")
                    449:                }
                    450:        }
                    451:
1.36      rillig    452:        return
1.3       rillig    453: }
                    454:
1.47      rillig    455: func (pkg *Package) diveInto(includingFile string, includedFile string) bool {
1.48      rillig    456:
                    457:        // The variables that appear in these files are largely modeled by
                    458:        // pkglint in the file vardefs.go. Therefore parsing these files again
                    459:        // doesn't make much sense.
                    460:        if hasSuffix(includedFile, "/bsd.pkg.mk") || IsPrefs(includedFile) {
                    461:                return false
                    462:        }
                    463:
                    464:        // All files that are included from outside of the pkgsrc infrastructure
                    465:        // are relevant. This is typically mk/compiler.mk or the various
                    466:        // mk/*.buildlink3.mk files.
                    467:        if !contains(includingFile, "/mk/") {
                    468:                return true
                    469:        }
                    470:
                    471:        // The mk/*.buildlink3.mk files often come with a companion file called
                    472:        // mk/*.builtin.mk, which also defines variables that are visible from
                    473:        // the package.
                    474:        //
                    475:        // This case is needed for getting the redundancy check right. Without it
                    476:        // there will be warnings about redundant assignments to the
                    477:        // BUILTIN_CHECK.pthread variable.
                    478:        if contains(includingFile, "buildlink3.mk") && contains(includedFile, "builtin.mk") {
                    479:                return true
1.47      rillig    480:        }
1.48      rillig    481:
                    482:        return false
1.47      rillig    483: }
                    484:
1.41      rillig    485: func (pkg *Package) collectUsedBy(mkline MkLine, incDir string, incBase string, includedFile string) {
                    486:        switch {
                    487:        case
                    488:                mkline.Basename != "Makefile",
                    489:                hasPrefix(incDir, "../../mk/"),
                    490:                incBase == "buildlink3.mk",
                    491:                incBase == "builtin.mk",
                    492:                incBase == "options.mk":
                    493:                return
                    494:        }
                    495:
                    496:        if trace.Tracing {
                    497:                trace.Step1("Including %q sets seenMakefileCommon.", includedFile)
                    498:        }
                    499:        pkg.seenMakefileCommon = true
                    500: }
                    501:
                    502: func (pkg *Package) findIncludedFile(mkline MkLine, includingFilename string) (includedFile, incDir, incBase string) {
                    503:
1.47      rillig    504:        // TODO: resolveVariableRefs uses G.Pkg implicitly. It should be made explicit.
                    505:        // TODO: Try to combine resolveVariableRefs and ResolveVarsInRelativePath.
1.49      rillig    506:        includedFile = resolveVariableRefs(nil, mkline.ResolveVarsInRelativePath(mkline.IncludedFile()))
1.47      rillig    507:        if containsVarRef(includedFile) {
                    508:                if trace.Tracing && !contains(includingFilename, "/mk/") {
                    509:                        trace.Stepf("%s:%s: Skipping include file %q. This may result in false warnings.",
                    510:                                mkline.Filename, mkline.Linenos(), includedFile)
1.41      rillig    511:                }
1.47      rillig    512:                includedFile = ""
1.41      rillig    513:        }
1.47      rillig    514:        incDir, incBase = path.Split(includedFile)
1.41      rillig    515:
                    516:        if includedFile != "" {
                    517:                if mkline.Basename != "buildlink3.mk" {
1.44      rillig    518:                        if matches(includedFile, `^\.\./\.\./(.*)/buildlink3\.mk$`) {
                    519:                                pkg.bl3[includedFile] = mkline
1.41      rillig    520:                                if trace.Tracing {
1.44      rillig    521:                                        trace.Step1("Buildlink3 file in package: %q", includedFile)
1.41      rillig    522:                                }
                    523:                        }
                    524:                }
                    525:        }
                    526:
                    527:        return
                    528: }
                    529:
1.47      rillig    530: func (pkg *Package) checkfilePackageMakefile(filename string, mklines MkLines, allLines MkLines) {
1.16      rillig    531:        if trace.Tracing {
1.39      rillig    532:                defer trace.Call1(filename)()
1.3       rillig    533:        }
                    534:
1.26      rillig    535:        vars := pkg.vars
                    536:        if !vars.Defined("PLIST_SRC") &&
                    537:                !vars.Defined("GENERATE_PLIST") &&
                    538:                !vars.Defined("META_PACKAGE") &&
1.33      rillig    539:                !fileExists(pkg.File(pkg.Pkgdir+"/PLIST")) &&
                    540:                !fileExists(pkg.File(pkg.Pkgdir+"/PLIST.common")) {
1.41      rillig    541:                // TODO: Move these technical details into the explanation, making space for an understandable warning.
1.39      rillig    542:                NewLineWhole(filename).Warnf("Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset.")
1.1       rillig    543:        }
                    544:
1.51      rillig    545:        if (vars.Defined("NO_CHECKSUM") || vars.Defined("META_PACKAGE")) &&
                    546:                isEmptyDir(pkg.File(pkg.Patchdir)) {
1.41      rillig    547:
1.51      rillig    548:                distinfoFile := pkg.File(pkg.DistinfoFile)
                    549:                if fileExists(distinfoFile) {
                    550:                        NewLineWhole(distinfoFile).Warnf("This file should not exist since NO_CHECKSUM or META_PACKAGE is set.")
1.1       rillig    551:                }
                    552:        } else {
1.51      rillig    553:                distinfoFile := pkg.File(pkg.DistinfoFile)
                    554:                if !containsVarRef(distinfoFile) && !fileExists(distinfoFile) {
                    555:                        line := NewLineWhole(distinfoFile)
                    556:                        line.Warnf("A package that downloads files should have a distinfo file.")
                    557:                        line.Explain(
                    558:                                sprintf("To generate the distinfo file, run %q.", bmake("makesum")),
                    559:                                "",
                    560:                                "To mark the package as not needing a distinfo file, set",
                    561:                                "NO_CHECKSUM=yes in the package Makefile.")
1.1       rillig    562:                }
                    563:        }
                    564:
1.41      rillig    565:        // TODO: There are other REPLACE_* variables which are probably also affected by NO_CONFIGURE.
                    566:        if noConfigureLine := vars.FirstDefinition("NO_CONFIGURE"); noConfigureLine != nil {
                    567:                if replacePerlLine := vars.FirstDefinition("REPLACE_PERL"); replacePerlLine != nil {
                    568:                        replacePerlLine.Warnf("REPLACE_PERL is ignored when NO_CONFIGURE is set (in %s).",
                    569:                                replacePerlLine.RefTo(noConfigureLine))
                    570:                }
1.1       rillig    571:        }
                    572:
1.26      rillig    573:        if !vars.Defined("LICENSE") && !vars.Defined("META_PACKAGE") && pkg.once.FirstTime("LICENSE") {
1.47      rillig    574:                line := NewLineWhole(filename)
                    575:                line.Errorf("Each package must define its LICENSE.")
1.41      rillig    576:                // TODO: Explain why the LICENSE is necessary.
1.47      rillig    577:                line.Explain(
                    578:                        "To take a good guess on the license of a package,",
                    579:                        sprintf("run %q.", bmake("guess-license")))
1.1       rillig    580:        }
                    581:
1.47      rillig    582:        scope := NewRedundantScope()
                    583:        scope.Check(allLines) // Updates the variables in the scope
                    584:        pkg.checkGnuConfigureUseLanguages(scope)
1.50      rillig    585:        pkg.checkUseLanguagesCompilerMk(allLines)
1.47      rillig    586:
1.3       rillig    587:        pkg.determineEffectivePkgVars()
                    588:        pkg.checkPossibleDowngrade()
                    589:
1.26      rillig    590:        if !vars.Defined("COMMENT") {
1.41      rillig    591:                NewLineWhole(filename).Warnf("Each package should define a COMMENT.")
1.3       rillig    592:        }
                    593:
1.41      rillig    594:        if imake := vars.FirstDefinition("USE_IMAKE"); imake != nil {
                    595:                if x11 := vars.FirstDefinition("USE_X11"); x11 != nil {
                    596:                        if !hasSuffix(x11.Filename, "/mk/x11.buildlink3.mk") {
                    597:                                imake.Notef("USE_IMAKE makes USE_X11 in %s redundant.", imake.RefTo(x11))
                    598:                        }
1.7       rillig    599:                }
1.3       rillig    600:        }
                    601:
                    602:        pkg.checkUpdate()
1.55      rillig    603:        allLines.collectDefinedVariables() // To get the tool definitions
                    604:        mklines.Tools = allLines.Tools     // TODO: also copy the other collected data
1.3       rillig    605:        mklines.Check()
1.25      rillig    606:        pkg.CheckVarorder(mklines)
1.3       rillig    607:        SaveAutofixChanges(mklines.lines)
                    608: }
                    609:
1.47      rillig    610: func (pkg *Package) checkGnuConfigureUseLanguages(s *RedundantScope) {
                    611:
                    612:        gnuConfigure := s.vars["GNU_CONFIGURE"]
                    613:        if gnuConfigure == nil || !gnuConfigure.vari.Constant() {
                    614:                return
                    615:        }
                    616:
                    617:        useLanguages := s.vars["USE_LANGUAGES"]
                    618:        if useLanguages == nil || !useLanguages.vari.Constant() {
                    619:                return
                    620:        }
                    621:
                    622:        var wrongLines []MkLine
                    623:        for _, mkline := range useLanguages.vari.WriteLocations() {
                    624:
                    625:                if G.Pkgsrc.IsInfra(mkline.Line.Filename) {
                    626:                        continue
                    627:                }
1.36      rillig    628:
1.47      rillig    629:                if matches(mkline.VarassignComment(), `(?-i)\b(?:c|empty|none)\b`) {
                    630:                        // Don't emit a warning since the comment probably contains a
                    631:                        // statement that C is really not needed.
                    632:                        return
                    633:                }
1.45      rillig    634:
1.47      rillig    635:                languages := mkline.Value()
                    636:                if matches(languages, `(?:^|[\t ]+)(?:c|c99|objc)(?:[\t ]+|$)`) {
                    637:                        return
1.36      rillig    638:                }
1.47      rillig    639:
                    640:                wrongLines = append(wrongLines, mkline)
                    641:        }
                    642:
                    643:        gnuLine := gnuConfigure.vari.WriteLocations()[0]
                    644:        for _, useLine := range wrongLines {
                    645:                gnuLine.Warnf(
                    646:                        "GNU_CONFIGURE almost always needs a C compiler, "+
                    647:                                "but \"c\" is not added to USE_LANGUAGES in %s.",
                    648:                        gnuLine.RefTo(useLine))
1.36      rillig    649:        }
                    650: }
                    651:
1.41      rillig    652: // nbPart determines the smallest part of the package version number,
                    653: // typically "nb13" or an empty string.
                    654: //
                    655: // It is only used inside pkgsrc to mark changes that are
                    656: // independent from the upstream package.
                    657: func (pkg *Package) nbPart() string {
1.45      rillig    658:        pkgrevision := pkg.vars.LastValue("PKGREVISION")
1.3       rillig    659:        if rev, err := strconv.Atoi(pkgrevision); err == nil {
                    660:                return "nb" + strconv.Itoa(rev)
                    661:        }
                    662:        return ""
                    663: }
                    664:
                    665: func (pkg *Package) determineEffectivePkgVars() {
1.26      rillig    666:        distnameLine := pkg.vars.FirstDefinition("DISTNAME")
                    667:        pkgnameLine := pkg.vars.FirstDefinition("PKGNAME")
1.1       rillig    668:
                    669:        distname := ""
                    670:        if distnameLine != nil {
1.3       rillig    671:                distname = distnameLine.Value()
1.1       rillig    672:        }
1.48      rillig    673:
1.1       rillig    674:        pkgname := ""
                    675:        if pkgnameLine != nil {
1.3       rillig    676:                pkgname = pkgnameLine.Value()
1.1       rillig    677:        }
                    678:
1.48      rillig    679:        effname := pkgname
                    680:        if distname != "" && effname != "" {
                    681:                merged, ok := pkg.pkgnameFromDistname(effname, distname)
                    682:                if ok {
                    683:                        effname = merged
                    684:                }
1.1       rillig    685:        }
                    686:
1.48      rillig    687:        if pkgname != "" && (pkgname == distname || pkgname == "${DISTNAME}") {
                    688:                if pkgnameLine.VarassignComment() == "" {
                    689:                        pkgnameLine.Notef("This assignment is probably redundant " +
                    690:                                "since PKGNAME is ${DISTNAME} by default.")
                    691:                        pkgnameLine.Explain(
                    692:                                "To mark this assignment as necessary, add a comment to the end of this line.")
                    693:                }
1.1       rillig    694:        }
                    695:
                    696:        if pkgname == "" && distname != "" && !containsVarRef(distname) && !matches(distname, rePkgname) {
1.14      rillig    697:                distnameLine.Warnf("As DISTNAME is not a valid package name, please define the PKGNAME explicitly.")
1.1       rillig    698:        }
                    699:
1.48      rillig    700:        if pkgname != "" {
                    701:                distname = ""
                    702:        }
                    703:
                    704:        if effname != "" && !containsVarRef(effname) {
                    705:                if m, m1, m2 := match2(effname, rePkgname); m {
                    706:                        pkg.EffectivePkgname = effname + pkg.nbPart()
1.3       rillig    707:                        pkg.EffectivePkgnameLine = pkgnameLine
                    708:                        pkg.EffectivePkgbase = m1
                    709:                        pkg.EffectivePkgversion = m2
                    710:                }
                    711:        }
1.41      rillig    712:
1.3       rillig    713:        if pkg.EffectivePkgnameLine == nil && distname != "" && !containsVarRef(distname) {
                    714:                if m, m1, m2 := match2(distname, rePkgname); m {
1.41      rillig    715:                        pkg.EffectivePkgname = distname + pkg.nbPart()
1.3       rillig    716:                        pkg.EffectivePkgnameLine = distnameLine
                    717:                        pkg.EffectivePkgbase = m1
                    718:                        pkg.EffectivePkgversion = m2
                    719:                }
                    720:        }
1.41      rillig    721:
1.3       rillig    722:        if pkg.EffectivePkgnameLine != nil {
1.16      rillig    723:                if trace.Tracing {
                    724:                        trace.Stepf("Effective name=%q base=%q version=%q",
1.3       rillig    725:                                pkg.EffectivePkgname, pkg.EffectivePkgbase, pkg.EffectivePkgversion)
                    726:                }
1.1       rillig    727:        }
1.3       rillig    728: }
1.1       rillig    729:
1.48      rillig    730: func (pkg *Package) pkgnameFromDistname(pkgname, distname string) (string, bool) {
1.39      rillig    731:        tokens := NewMkParser(nil, pkgname, false).MkTokens()
1.1       rillig    732:
1.41      rillig    733:        // TODO: Make this resolving of variable references available to all other variables as well.
                    734:
1.48      rillig    735:        var result strings.Builder
1.9       rillig    736:        for _, token := range tokens {
1.48      rillig    737:                if token.Varuse != nil {
                    738:                        if token.Varuse.varname != "DISTNAME" {
                    739:                                return "", false
                    740:                        }
                    741:
1.9       rillig    742:                        newDistname := distname
                    743:                        for _, mod := range token.Varuse.modifiers {
1.38      rillig    744:                                if mod.IsToLower() {
1.9       rillig    745:                                        newDistname = strings.ToLower(newDistname)
1.48      rillig    746:                                } else if subst, ok := mod.Subst(newDistname); ok {
                    747:                                        newDistname = subst
1.9       rillig    748:                                } else {
1.48      rillig    749:                                        return "", false
1.9       rillig    750:                                }
                    751:                        }
1.48      rillig    752:                        result.WriteString(newDistname)
1.9       rillig    753:                } else {
1.48      rillig    754:                        result.WriteString(token.Text)
1.9       rillig    755:                }
                    756:        }
1.48      rillig    757:        return result.String(), true
1.3       rillig    758: }
1.1       rillig    759:
1.3       rillig    760: func (pkg *Package) checkUpdate() {
1.41      rillig    761:        if pkg.EffectivePkgbase == "" {
                    762:                return
                    763:        }
                    764:
1.43      rillig    765:        for _, sugg := range G.Pkgsrc.SuggestedUpdates() {
1.41      rillig    766:                if pkg.EffectivePkgbase != sugg.Pkgname {
                    767:                        continue
                    768:                }
                    769:
                    770:                suggver, comment := sugg.Version, sugg.Comment
                    771:                if comment != "" {
                    772:                        comment = " (" + comment + ")"
                    773:                }
                    774:
                    775:                pkgnameLine := pkg.EffectivePkgnameLine
                    776:                cmp := pkgver.Compare(pkg.EffectivePkgversion, suggver)
                    777:                switch {
1.1       rillig    778:
1.41      rillig    779:                case cmp < 0:
                    780:                        pkgnameLine.Warnf("This package should be updated to %s%s.",
                    781:                                sugg.Version, comment)
1.50      rillig    782:                        pkgnameLine.Explain(
1.41      rillig    783:                                "The wishlist for package updates in doc/TODO mentions that a newer",
                    784:                                "version of this package is available.")
1.1       rillig    785:
1.41      rillig    786:                case cmp > 0:
                    787:                        pkgnameLine.Notef("This package is newer than the update request to %s%s.",
                    788:                                suggver, comment)
                    789:
                    790:                default:
                    791:                        pkgnameLine.Notef("The update request to %s from doc/TODO%s has been done.",
                    792:                                suggver, comment)
1.1       rillig    793:                }
                    794:        }
                    795: }
                    796:
1.25      rillig    797: // CheckVarorder checks that in simple package Makefiles,
                    798: // the most common variables appear in a fixed order.
                    799: // The order itself is a little arbitrary but provides
                    800: // at least a bit of consistency.
1.38      rillig    801: func (pkg *Package) CheckVarorder(mklines MkLines) {
1.16      rillig    802:        if trace.Tracing {
                    803:                defer trace.Call0()()
1.1       rillig    804:        }
                    805:
1.45      rillig    806:        if pkg.seenMakefileCommon {
1.1       rillig    807:                return
                    808:        }
                    809:
1.56    ! rillig    810:        // TODO: Extract all this code into a separate VarOrderChecker
        !           811:        //  since it is equally useful for PKG_OPTIONS in options.mk.
        !           812:
1.25      rillig    813:        type Repetition uint8
1.1       rillig    814:        const (
1.25      rillig    815:                optional Repetition = iota
                    816:                once
1.1       rillig    817:                many
                    818:        )
1.41      rillig    819:
1.25      rillig    820:        type Variable struct {
                    821:                varname    string
                    822:                repetition Repetition
                    823:        }
1.41      rillig    824:
1.25      rillig    825:        type Section struct {
                    826:                repetition Repetition
                    827:                vars       []Variable
                    828:        }
1.41      rillig    829:
1.25      rillig    830:        variable := func(name string, repetition Repetition) Variable { return Variable{name, repetition} }
                    831:        section := func(repetition Repetition, vars ...Variable) Section { return Section{repetition, vars} }
                    832:
1.41      rillig    833:        // See doc/Makefile-example.
                    834:        // See https://netbsd.org/docs/pkgsrc/pkgsrc.html#components.Makefile.
1.25      rillig    835:        var sections = []Section{
                    836:                section(once,
                    837:                        variable("GITHUB_PROJECT", optional), // either here or below MASTER_SITES
                    838:                        variable("GITHUB_TAG", optional),
                    839:                        variable("DISTNAME", optional),
                    840:                        variable("PKGNAME", optional),
                    841:                        variable("PKGREVISION", optional),
                    842:                        variable("CATEGORIES", once),
                    843:                        variable("MASTER_SITES", many),
                    844:                        variable("GITHUB_PROJECT", optional), // either here or at the very top
                    845:                        variable("GITHUB_TAG", optional),
                    846:                        variable("DIST_SUBDIR", optional),
                    847:                        variable("EXTRACT_SUFX", optional),
                    848:                        variable("DISTFILES", many),
                    849:                        variable("SITES.*", many)),
                    850:                section(optional,
                    851:                        variable("PATCH_SITES", optional), // or once?
                    852:                        variable("PATCH_SITE_SUBDIR", optional),
                    853:                        variable("PATCHFILES", optional), // or once?
                    854:                        variable("PATCH_DIST_ARGS", optional),
                    855:                        variable("PATCH_DIST_STRIP", optional),
                    856:                        variable("PATCH_DIST_CAT", optional)),
                    857:                section(once,
                    858:                        variable("MAINTAINER", optional),
                    859:                        variable("OWNER", optional),
                    860:                        variable("HOMEPAGE", optional),
                    861:                        variable("COMMENT", once),
                    862:                        variable("LICENSE", once)),
                    863:                section(optional,
                    864:                        variable("LICENSE_FILE", optional),
                    865:                        variable("RESTRICTED", optional),
                    866:                        variable("NO_BIN_ON_CDROM", optional),
                    867:                        variable("NO_BIN_ON_FTP", optional),
                    868:                        variable("NO_SRC_ON_CDROM", optional),
                    869:                        variable("NO_SRC_ON_FTP", optional)),
                    870:                section(optional,
                    871:                        variable("BROKEN_EXCEPT_ON_PLATFORM", many),
                    872:                        variable("BROKEN_ON_PLATFORM", many),
                    873:                        variable("NOT_FOR_PLATFORM", many),
                    874:                        variable("ONLY_FOR_PLATFORM", many),
                    875:                        variable("NOT_FOR_COMPILER", many),
                    876:                        variable("ONLY_FOR_COMPILER", many),
                    877:                        variable("NOT_FOR_UNPRIVILEGED", optional),
                    878:                        variable("ONLY_FOR_UNPRIVILEGED", optional)),
                    879:                section(optional,
                    880:                        variable("BUILD_DEPENDS", many),
                    881:                        variable("TOOL_DEPENDS", many),
1.41      rillig    882:                        variable("DEPENDS", many))}
1.25      rillig    883:
1.45      rillig    884:        relevantLines := (func() []MkLine {
                    885:                firstRelevant := -1
                    886:                lastRelevant := -1
1.41      rillig    887:
1.25      rillig    888:                relevantVars := make(map[string]bool)
                    889:                for _, section := range sections {
                    890:                        for _, variable := range section.vars {
                    891:                                relevantVars[variable.varname] = true
                    892:                        }
                    893:                }
1.1       rillig    894:
1.25      rillig    895:                firstIrrelevant := -1
                    896:                for i, mkline := range mklines.mklines {
                    897:                        switch {
                    898:                        case mkline.IsVarassign(), mkline.IsCommentedVarassign():
                    899:                                varcanon := mkline.Varcanon()
                    900:                                if relevantVars[varcanon] {
                    901:                                        if firstRelevant == -1 {
                    902:                                                firstRelevant = i
                    903:                                        }
                    904:                                        if firstIrrelevant != -1 {
                    905:                                                if trace.Tracing {
                    906:                                                        trace.Stepf("Skipping varorder because of line %s.",
                    907:                                                                mklines.mklines[firstIrrelevant].Linenos())
                    908:                                                }
1.45      rillig    909:                                                return nil
1.25      rillig    910:                                        }
                    911:                                        lastRelevant = i
                    912:                                } else {
                    913:                                        if firstIrrelevant == -1 {
                    914:                                                firstIrrelevant = i
                    915:                                        }
                    916:                                }
1.1       rillig    917:
1.25      rillig    918:                        case mkline.IsComment(), mkline.IsEmpty():
1.1       rillig    919:                                break
1.25      rillig    920:
                    921:                        default:
                    922:                                if firstIrrelevant == -1 {
                    923:                                        firstIrrelevant = i
                    924:                                }
1.1       rillig    925:                        }
                    926:                }
                    927:
1.28      rillig    928:                if firstRelevant == -1 {
1.45      rillig    929:                        return nil
1.28      rillig    930:                }
1.45      rillig    931:                return mklines.mklines[firstRelevant : lastRelevant+1]
                    932:        })()
                    933:
                    934:        skip := func() bool {
                    935:                interesting := relevantLines
1.1       rillig    936:
1.25      rillig    937:                varcanon := func() string {
1.38      rillig    938:                        for len(interesting) > 0 && interesting[0].IsComment() {
1.25      rillig    939:                                interesting = interesting[1:]
                    940:                        }
1.38      rillig    941:                        if len(interesting) > 0 && (interesting[0].IsVarassign() || interesting[0].IsCommentedVarassign()) {
1.25      rillig    942:                                return interesting[0].Varcanon()
                    943:                        }
                    944:                        return ""
                    945:                }
1.1       rillig    946:
1.25      rillig    947:                for _, section := range sections {
                    948:                        for _, variable := range section.vars {
                    949:                                switch variable.repetition {
                    950:                                case optional:
                    951:                                        if varcanon() == variable.varname {
                    952:                                                interesting = interesting[1:]
                    953:                                        }
                    954:                                case once:
                    955:                                        if varcanon() == variable.varname {
                    956:                                                interesting = interesting[1:]
                    957:                                        } else if section.repetition == once {
                    958:                                                if variable.varname != "LICENSE" {
                    959:                                                        if trace.Tracing {
                    960:                                                                trace.Stepf("Wrong varorder because %s is missing.", variable.varname)
                    961:                                                        }
                    962:                                                        return false
                    963:                                                }
                    964:                                        }
                    965:                                case many:
                    966:                                        for varcanon() == variable.varname {
                    967:                                                interesting = interesting[1:]
                    968:                                        }
1.1       rillig    969:                                }
                    970:                        }
                    971:
1.38      rillig    972:                        for len(interesting) > 0 && (interesting[0].IsEmpty() || interesting[0].IsComment()) {
1.25      rillig    973:                                interesting = interesting[1:]
1.1       rillig    974:                        }
1.25      rillig    975:                }
1.1       rillig    976:
1.25      rillig    977:                return len(interesting) == 0
                    978:        }
1.1       rillig    979:
1.45      rillig    980:        if len(relevantLines) == 0 || skip() {
1.25      rillig    981:                return
                    982:        }
1.1       rillig    983:
1.25      rillig    984:        var canonical []string
                    985:        for _, section := range sections {
                    986:                for _, variable := range section.vars {
                    987:                        found := false
1.45      rillig    988:                        for _, mkline := range relevantLines {
1.25      rillig    989:                                if mkline.IsVarassign() || mkline.IsCommentedVarassign() {
                    990:                                        if mkline.Varcanon() == variable.varname {
                    991:                                                canonical = append(canonical, mkline.Varname())
                    992:                                                found = true
1.21      rillig    993:                                        }
1.1       rillig    994:                                }
                    995:                        }
1.25      rillig    996:                        if !found && section.repetition == once && variable.repetition == once {
                    997:                                canonical = append(canonical, variable.varname)
1.1       rillig    998:                        }
                    999:                }
1.38      rillig   1000:                if len(canonical) > 0 && canonical[len(canonical)-1] != "empty line" {
1.25      rillig   1001:                        canonical = append(canonical, "empty line")
                   1002:                }
                   1003:        }
1.38      rillig   1004:        if len(canonical) > 0 && canonical[len(canonical)-1] == "empty line" {
1.25      rillig   1005:                canonical = canonical[:len(canonical)-1]
1.1       rillig   1006:        }
1.25      rillig   1007:
1.41      rillig   1008:        // TODO: This leads to very long and complicated warnings.
                   1009:        //  Those parts that are correct should not be mentioned,
                   1010:        //  except if they are helpful for locating the mistakes.
1.45      rillig   1011:        mkline := relevantLines[0]
1.25      rillig   1012:        mkline.Warnf("The canonical order of the variables is %s.", strings.Join(canonical, ", "))
1.50      rillig   1013:        mkline.Explain(
1.25      rillig   1014:                "In simple package Makefiles, some common variables should be",
                   1015:                "arranged in a specific order.",
                   1016:                "",
1.39      rillig   1017:                "See doc/Makefile-example for an example Makefile.",
                   1018:                seeGuide("Package components, Makefile", "components.Makefile"))
1.1       rillig   1019: }
1.3       rillig   1020:
1.53      rillig   1021: func (pkg *Package) checkFileMakefileExt(filename string) {
                   1022:        base := path.Base(filename)
                   1023:        if !hasPrefix(base, "Makefile.") || base == "Makefile.common" {
                   1024:                return
                   1025:        }
                   1026:        ext := strings.TrimPrefix(base, "Makefile.")
                   1027:
                   1028:        line := NewLineWhole(filename)
                   1029:        line.Notef("Consider renaming %q to %q.", base, ext+".mk")
                   1030:        line.Explain(
                   1031:                "The main definition of a pkgsrc package should be in the Makefile.",
                   1032:                "Common definitions for a few very closely related packages can be",
                   1033:                "placed in a Makefile.common, these may cover various topics.",
                   1034:                "",
                   1035:                "All other definitions should be grouped by topics and implemented",
                   1036:                "in separate files named *.mk after their topics. Typical examples",
                   1037:                "are extension.mk, module.mk, version.mk.",
                   1038:                "",
                   1039:                "These topic files should be documented properly so that their",
                   1040:                sprintf("content can be queried using %q.", makeHelp("help")))
                   1041: }
                   1042:
1.41      rillig   1043: // checkLocallyModified checks files that are about to be committed.
                   1044: // Depending on whether the package has a MAINTAINER or an OWNER,
                   1045: // the wording differs.
                   1046: //
                   1047: // Pkglint assumes that the local username is the same as the NetBSD
                   1048: // username, which fits most scenarios.
1.39      rillig   1049: func (pkg *Package) checkLocallyModified(filename string) {
1.16      rillig   1050:        if trace.Tracing {
1.39      rillig   1051:                defer trace.Call(filename)()
1.9       rillig   1052:        }
                   1053:
1.45      rillig   1054:        owner := pkg.vars.LastValue("OWNER")
                   1055:        maintainer := pkg.vars.LastValue("MAINTAINER")
1.36      rillig   1056:        if maintainer == "pkgsrc-users@NetBSD.org" {
1.35      rillig   1057:                maintainer = ""
1.9       rillig   1058:        }
                   1059:        if owner == "" && maintainer == "" {
                   1060:                return
                   1061:        }
                   1062:
1.41      rillig   1063:        username := G.Username
1.16      rillig   1064:        if trace.Tracing {
                   1065:                trace.Stepf("user=%q owner=%q maintainer=%q", username, owner, maintainer)
1.9       rillig   1066:        }
                   1067:
                   1068:        if username == strings.Split(owner, "@")[0] || username == strings.Split(maintainer, "@")[0] {
                   1069:                return
                   1070:        }
                   1071:
1.50      rillig   1072:        if !isLocallyModified(filename) || !fileExists(filename) {
1.41      rillig   1073:                return
                   1074:        }
                   1075:
                   1076:        if owner != "" {
1.50      rillig   1077:                line := NewLineWhole(filename)
                   1078:                line.Warnf("Don't commit changes to this file without asking the OWNER, %s.", owner)
                   1079:                line.Explain(
1.41      rillig   1080:                        seeGuide("Package components, Makefile", "components.Makefile"))
                   1081:        }
                   1082:
                   1083:        if maintainer != "" {
1.50      rillig   1084:                line := NewLineWhole(filename)
                   1085:                line.Notef("Please only commit changes that %s would approve.", maintainer)
                   1086:                line.Explain(
1.41      rillig   1087:                        "See the pkgsrc guide, section \"Package components\",",
                   1088:                        "keyword \"maintainer\", for more information.")
1.9       rillig   1089:        }
                   1090: }
1.12      rillig   1091:
1.40      rillig   1092: func (pkg *Package) checkIncludeConditionally(mkline MkLine, indentation *Indentation) {
1.34      rillig   1093:        conditionalVars := mkline.ConditionalVars()
1.39      rillig   1094:        if len(conditionalVars) == 0 {
1.34      rillig   1095:                conditionalVars = indentation.Varnames()
                   1096:                mkline.SetConditionalVars(conditionalVars)
1.12      rillig   1097:        }
                   1098:
1.39      rillig   1099:        if path.Dir(abspath(mkline.Filename)) == abspath(pkg.File(".")) {
                   1100:                includedFile := mkline.IncludedFile()
1.12      rillig   1101:
                   1102:                if indentation.IsConditional() {
1.39      rillig   1103:                        pkg.conditionalIncludes[includedFile] = mkline
                   1104:                        if other := pkg.unconditionalIncludes[includedFile]; other != nil {
1.41      rillig   1105:                                mkline.Warnf(
                   1106:                                        "%q is included conditionally here (depending on %s) "+
                   1107:                                                "and unconditionally in %s.",
1.39      rillig   1108:                                        cleanpath(includedFile), strings.Join(mkline.ConditionalVars(), ", "), mkline.RefTo(other))
1.12      rillig   1109:                        }
1.41      rillig   1110:
1.12      rillig   1111:                } else {
1.39      rillig   1112:                        pkg.unconditionalIncludes[includedFile] = mkline
                   1113:                        if other := pkg.conditionalIncludes[includedFile]; other != nil {
1.41      rillig   1114:                                mkline.Warnf(
                   1115:                                        "%q is included unconditionally here "+
                   1116:                                                "and conditionally in %s (depending on %s).",
1.39      rillig   1117:                                        cleanpath(includedFile), mkline.RefTo(other), strings.Join(other.ConditionalVars(), ", "))
1.12      rillig   1118:                        }
                   1119:                }
1.39      rillig   1120:
                   1121:                // TODO: Check whether the conditional variables are the same on both places.
1.46      rillig   1122:                //  Ideally they should match, but there may be some differences in internal
                   1123:                //  variables, which need to be filtered out before comparing them, like it is
                   1124:                //  already done with *_MK variables.
1.12      rillig   1125:        }
                   1126: }
1.22      rillig   1127:
                   1128: func (pkg *Package) loadPlistDirs(plistFilename string) {
1.34      rillig   1129:        lines := Load(plistFilename, MustSucceed)
1.38      rillig   1130:        for _, line := range lines.Lines {
1.22      rillig   1131:                text := line.Text
1.38      rillig   1132:                pkg.Plist.Files[text] = true // XXX: ignores PLIST conditions for now
1.22      rillig   1133:                // Keep in sync with PlistChecker.collectFilesAndDirs
                   1134:                if !contains(text, "$") && !contains(text, "@") {
                   1135:                        for dir := path.Dir(text); dir != "."; dir = path.Dir(dir) {
1.38      rillig   1136:                                pkg.Plist.Dirs[dir] = true
1.22      rillig   1137:                        }
                   1138:                }
                   1139:        }
                   1140: }
1.38      rillig   1141:
1.45      rillig   1142: func (pkg *Package) AutofixDistinfo(oldSha1, newSha1 string) {
                   1143:        distinfoFilename := pkg.File(pkg.DistinfoFile)
                   1144:        if lines := Load(distinfoFilename, NotEmpty|LogErrors); lines != nil {
                   1145:                for _, line := range lines.Lines {
                   1146:                        fix := line.Autofix()
                   1147:                        fix.Warnf(SilentAutofixFormat)
                   1148:                        fix.Replace(oldSha1, newSha1)
                   1149:                        fix.Apply()
                   1150:                }
                   1151:                lines.SaveAutofixChanges()
                   1152:        }
                   1153: }
                   1154:
1.50      rillig   1155: // checkUseLanguagesCompilerMk checks that after including mk/compiler.mk
                   1156: // or mk/endian.mk for the first time, there are no more changes to
                   1157: // USE_LANGUAGES, as these would be ignored by the pkgsrc infrastructure.
                   1158: func (pkg *Package) checkUseLanguagesCompilerMk(mklines MkLines) {
                   1159:
                   1160:        var seen Once
                   1161:
                   1162:        handleVarassign := func(mkline MkLine) {
                   1163:                if mkline.Varname() != "USE_LANGUAGES" {
                   1164:                        return
                   1165:                }
                   1166:
                   1167:                if !seen.Seen("../../mk/compiler.mk") && !seen.Seen("../../mk/endian.mk") {
                   1168:                        return
                   1169:                }
                   1170:
                   1171:                if mkline.Basename == "compiler.mk" {
                   1172:                        if relpath(pkg.dir, mkline.Filename) == "../../mk/compiler.mk" {
                   1173:                                return
                   1174:                        }
                   1175:                }
                   1176:
                   1177:                mkline.Warnf("Modifying USE_LANGUAGES after including ../../mk/compiler.mk has no effect.")
                   1178:                mkline.Explain(
                   1179:                        "The file compiler.mk guards itself against multiple inclusion.")
                   1180:        }
                   1181:
                   1182:        handleInclude := func(mkline MkLine) {
                   1183:                dirname, _ := path.Split(mkline.Filename)
                   1184:                dirname = cleanpath(dirname)
                   1185:                fullIncluded := dirname + "/" + mkline.IncludedFile()
                   1186:                relIncludedFile := relpath(pkg.dir, fullIncluded)
                   1187:
                   1188:                seen.FirstTime(relIncludedFile)
                   1189:        }
                   1190:
                   1191:        mklines.ForEach(func(mkline MkLine) {
                   1192:                switch {
                   1193:                case mkline.IsVarassign():
                   1194:                        handleVarassign(mkline)
                   1195:
                   1196:                case mkline.IsInclude():
                   1197:                        handleInclude(mkline)
                   1198:                }
                   1199:        })
                   1200: }
                   1201:
1.38      rillig   1202: type PlistContent struct {
                   1203:        Dirs  map[string]bool
                   1204:        Files map[string]bool
                   1205: }
                   1206:
                   1207: func NewPlistContent() PlistContent {
                   1208:        return PlistContent{
                   1209:                make(map[string]bool),
                   1210:                make(map[string]bool)}
                   1211: }

CVSweb <webmaster@jp.NetBSD.org>