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>