Annotation of pkgsrc/pkgtools/pkglint/files/pkglint.go, Revision 1.36
1.1 rillig 1: package main
2:
3: import (
1.17 rillig 4: "fmt"
5: "netbsd.org/pkglint/getopt"
1.18 rillig 6: "netbsd.org/pkglint/histogram"
7: "netbsd.org/pkglint/regex"
8: "netbsd.org/pkglint/trace"
1.1 rillig 9: "os"
1.17 rillig 10: "os/user"
1.1 rillig 11: "path"
1.17 rillig 12: "path/filepath"
13: "runtime/pprof"
1.1 rillig 14: "strings"
15: )
16:
1.17 rillig 17: const confMake = "@BMAKE@"
18: const confVersion = "@VERSION@"
19:
1.28 rillig 20: // Pkglint contains all global variables of this Go package.
21: // The rest of the global state is in the other packages:
22: // regex.Profiling (not thread-local)
23: // regex.res (and related variables; not thread-safe)
24: // textproc.Testing (not thread-local; harmless)
25: // tracing.Tracing (not thread-safe)
26: // tracing.Out (not thread-safe)
27: // tracing.traceDepth (not thread-safe)
28: type Pkglint struct {
1.29 rillig 29: opts CmdOpts // Command line options.
1.32 rillig 30: Pkgsrc *Pkgsrc // Global data, mostly extracted from mk/*.
1.29 rillig 31: Pkg *Package // The package that is currently checked.
32: Mk *MkLines // The Makefile (or fragment) that is currently checked.
1.28 rillig 33:
34: Todo []string // The files or directories that still need to be checked.
1.35 rillig 35: Wip bool // Is the currently checked item from pkgsrc-wip?
1.28 rillig 36: Infrastructure bool // Is the currently checked item from the pkgsrc infrastructure?
37: Testing bool // Is pkglint in self-testing mode (only during development)?
38: CurrentUsername string // For checking against OWNER and MAINTAINER
39: CvsEntriesDir string // Cached to avoid I/O
40: CvsEntriesLines []Line
41:
42: errors int
43: warnings int
44: explainNext bool
45: logged map[string]bool
46: explanationsAvailable bool
47: explanationsGiven map[string]bool
48: autofixAvailable bool
49: logOut *SeparatorWriter
50: logErr *SeparatorWriter
51:
52: loghisto *histogram.Histogram
1.35 rillig 53: loaded *histogram.Histogram
1.28 rillig 54: }
55:
56: type CmdOpts struct {
57: CheckAlternatives,
58: CheckBuildlink3,
59: CheckDescr,
60: CheckDistinfo,
61: CheckExtra,
62: CheckGlobal,
63: CheckInstall,
64: CheckMakefile,
65: CheckMessage,
66: CheckMk,
1.34 rillig 67: CheckOptions,
1.28 rillig 68: CheckPatches,
69: CheckPlist bool
70:
71: WarnAbsname,
72: WarnDirectcmd,
73: WarnExtra,
74: WarnOrder,
75: WarnPerm,
76: WarnPlistDepr,
77: WarnPlistSort,
78: WarnQuoting,
79: WarnSpace,
80: WarnStyle,
81: WarnTypes bool
82:
83: Explain,
84: Autofix,
85: GccOutput,
86: PrintHelp,
87: DumpMakefile,
88: Import,
89: LogVerbose,
90: Profiling,
91: Quiet,
92: Recursive,
93: PrintAutofix,
94: PrintSource,
95: PrintVersion bool
96:
97: LogOnly []string
98:
99: args []string
100: }
101:
102: type Hash struct {
103: hash string
104: line Line
105: }
106:
107: // G is the abbreviation for "global state";
108: // it is the only global variable in this Go package
109: var G Pkglint
110:
1.17 rillig 111: func main() {
1.25 rillig 112: G.logOut = NewSeparatorWriter(os.Stdout)
113: G.logErr = NewSeparatorWriter(os.Stderr)
114: trace.Out = os.Stdout
1.28 rillig 115: os.Exit(G.Main(os.Args...))
1.17 rillig 116: }
117:
1.27 rillig 118: // Main runs the main program with the given arguments.
1.31 rillig 119: // argv[0] is the program name.
120: func (pkglint *Pkglint) Main(argv ...string) (exitcode int) {
1.17 rillig 121: defer func() {
122: if r := recover(); r != nil {
123: if _, ok := r.(pkglintFatal); ok {
124: exitcode = 1
125: } else {
126: panic(r)
127: }
128: }
129: }()
130:
1.31 rillig 131: if exitcode := pkglint.ParseCommandLine(argv); exitcode != nil {
1.17 rillig 132: return *exitcode
133: }
134:
1.28 rillig 135: if pkglint.opts.Profiling {
1.17 rillig 136: f, err := os.Create("pkglint.pprof")
137: if err != nil {
138: dummyLine.Fatalf("Cannot create profiling file: %s", err)
139: }
140: pprof.StartCPUProfile(f)
1.36 ! rillig 141: defer func() {
! 142: pprof.StopCPUProfile()
! 143: f.Close()
! 144: }()
1.17 rillig 145:
1.18 rillig 146: regex.Profiling = true
1.28 rillig 147: pkglint.loghisto = histogram.New()
1.35 rillig 148: pkglint.loaded = histogram.New()
1.18 rillig 149: defer func() {
1.28 rillig 150: pkglint.logOut.Write("")
1.35 rillig 151: pkglint.loghisto.PrintStats("loghisto", pkglint.logOut.out, -1)
1.36 ! rillig 152: regex.PrintStats(pkglint.logOut.out)
1.35 rillig 153: pkglint.loaded.PrintStats("loaded", pkglint.logOut.out, 50)
1.18 rillig 154: }()
1.17 rillig 155: }
156:
1.28 rillig 157: for _, arg := range pkglint.opts.args {
158: pkglint.Todo = append(pkglint.Todo, filepath.ToSlash(arg))
1.17 rillig 159: }
1.28 rillig 160: if len(pkglint.Todo) == 0 {
161: pkglint.Todo = []string{"."}
1.17 rillig 162: }
163:
1.29 rillig 164: firstArg := G.Todo[0]
165: if fileExists(firstArg) {
166: firstArg = path.Dir(firstArg)
167: }
168: relTopdir := findPkgsrcTopdir(firstArg)
169: if relTopdir == "" {
170: dummyLine.Fatalf("%q is not inside a pkgsrc tree.", firstArg)
171: }
172:
173: pkglint.Pkgsrc = NewPkgsrc(firstArg + "/" + relTopdir)
1.36 ! rillig 174: pkglint.Pkgsrc.LoadInfrastructure()
1.17 rillig 175:
176: currentUser, err := user.Current()
177: if err == nil {
178: // On Windows, this is `Computername\Username`.
1.28 rillig 179: pkglint.CurrentUsername = regex.Compile(`^.*\\`).ReplaceAllString(currentUser.Username, "")
1.17 rillig 180: }
181:
1.28 rillig 182: for len(pkglint.Todo) != 0 {
183: item := pkglint.Todo[0]
184: pkglint.Todo = pkglint.Todo[1:]
1.17 rillig 185: pkglint.CheckDirent(item)
186: }
187:
188: checkToplevelUnusedLicenses()
189: pkglint.PrintSummary()
1.28 rillig 190: if pkglint.errors != 0 {
1.17 rillig 191: return 1
192: }
193: return 0
194: }
195:
196: func (pkglint *Pkglint) ParseCommandLine(args []string) *int {
1.28 rillig 197: gopts := &pkglint.opts
1.17 rillig 198: opts := getopt.NewOptions()
199:
200: check := opts.AddFlagGroup('C', "check", "check,...", "enable or disable specific checks")
1.18 rillig 201: opts.AddFlagVar('d', "debug", &trace.Tracing, false, "log verbose call traces for debugging")
1.17 rillig 202: opts.AddFlagVar('e', "explain", &gopts.Explain, false, "explain the diagnostics or give further help")
203: opts.AddFlagVar('f', "show-autofix", &gopts.PrintAutofix, false, "show what pkglint can fix automatically")
204: opts.AddFlagVar('F', "autofix", &gopts.Autofix, false, "try to automatically fix some errors (experimental)")
205: opts.AddFlagVar('g', "gcc-output-format", &gopts.GccOutput, false, "mimic the gcc output format")
206: opts.AddFlagVar('h', "help", &gopts.PrintHelp, false, "print a detailed usage message")
207: opts.AddFlagVar('I', "dumpmakefile", &gopts.DumpMakefile, false, "dump the Makefile after parsing")
208: opts.AddFlagVar('i', "import", &gopts.Import, false, "prepare the import of a wip package")
209: opts.AddFlagVar('m', "log-verbose", &gopts.LogVerbose, false, "allow the same log message more than once")
1.24 rillig 210: opts.AddStrList('o', "only", &gopts.LogOnly, "only log messages containing the given text")
1.17 rillig 211: opts.AddFlagVar('p', "profiling", &gopts.Profiling, false, "profile the executing program")
212: opts.AddFlagVar('q', "quiet", &gopts.Quiet, false, "don't print a summary line when finishing")
213: opts.AddFlagVar('r', "recursive", &gopts.Recursive, false, "check subdirectories, too")
214: opts.AddFlagVar('s', "source", &gopts.PrintSource, false, "show the source lines together with diagnostics")
215: opts.AddFlagVar('V', "version", &gopts.PrintVersion, false, "print the version number of pkglint")
216: warn := opts.AddFlagGroup('W', "warning", "warning,...", "enable or disable groups of warnings")
217:
218: check.AddFlagVar("ALTERNATIVES", &gopts.CheckAlternatives, true, "check ALTERNATIVES files")
219: check.AddFlagVar("bl3", &gopts.CheckBuildlink3, true, "check buildlink3.mk files")
220: check.AddFlagVar("DESCR", &gopts.CheckDescr, true, "check DESCR file")
221: check.AddFlagVar("distinfo", &gopts.CheckDistinfo, true, "check distinfo file")
222: check.AddFlagVar("extra", &gopts.CheckExtra, false, "check various additional files")
223: check.AddFlagVar("global", &gopts.CheckGlobal, false, "inter-package checks")
224: check.AddFlagVar("INSTALL", &gopts.CheckInstall, true, "check INSTALL and DEINSTALL scripts")
225: check.AddFlagVar("Makefile", &gopts.CheckMakefile, true, "check Makefiles")
226: check.AddFlagVar("MESSAGE", &gopts.CheckMessage, true, "check MESSAGE file")
227: check.AddFlagVar("mk", &gopts.CheckMk, true, "check other .mk files")
1.34 rillig 228: check.AddFlagVar("options", &gopts.CheckOptions, true, "check options.mk files")
1.17 rillig 229: check.AddFlagVar("patches", &gopts.CheckPatches, true, "check patches")
230: check.AddFlagVar("PLIST", &gopts.CheckPlist, true, "check PLIST files")
231:
232: warn.AddFlagVar("absname", &gopts.WarnAbsname, true, "warn about use of absolute file names")
233: warn.AddFlagVar("directcmd", &gopts.WarnDirectcmd, true, "warn about use of direct command names instead of Make variables")
234: warn.AddFlagVar("extra", &gopts.WarnExtra, false, "enable some extra warnings")
1.30 rillig 235: warn.AddFlagVar("order", &gopts.WarnOrder, true, "warn if Makefile entries are unordered")
1.17 rillig 236: warn.AddFlagVar("perm", &gopts.WarnPerm, false, "warn about unforeseen variable definition and use")
237: warn.AddFlagVar("plist-depr", &gopts.WarnPlistDepr, false, "warn about deprecated paths in PLISTs")
238: warn.AddFlagVar("plist-sort", &gopts.WarnPlistSort, false, "warn about unsorted entries in PLISTs")
239: warn.AddFlagVar("quoting", &gopts.WarnQuoting, false, "warn about quoting issues")
240: warn.AddFlagVar("space", &gopts.WarnSpace, false, "warn about inconsistent use of white-space")
241: warn.AddFlagVar("style", &gopts.WarnStyle, false, "warn about stylistic issues")
242: warn.AddFlagVar("types", &gopts.WarnTypes, true, "do some simple type checking in Makefiles")
243:
244: remainingArgs, err := opts.Parse(args)
245: if err != nil {
1.28 rillig 246: fmt.Fprintf(pkglint.logErr.out, "%s\n\n", err)
247: opts.Help(pkglint.logErr.out, "pkglint [options] dir...")
1.17 rillig 248: exitcode := 1
249: return &exitcode
250: }
251: gopts.args = remainingArgs
252:
253: if gopts.PrintHelp {
1.28 rillig 254: opts.Help(pkglint.logOut.out, "pkglint [options] dir...")
1.17 rillig 255: exitcode := 0
256: return &exitcode
257: }
258:
1.28 rillig 259: if pkglint.opts.PrintVersion {
260: fmt.Fprintf(pkglint.logOut.out, "%s\n", confVersion)
1.17 rillig 261: exitcode := 0
262: return &exitcode
263: }
264:
265: return nil
266: }
267:
268: func (pkglint *Pkglint) PrintSummary() {
1.28 rillig 269: if !pkglint.opts.Quiet && !pkglint.opts.Autofix {
270: if pkglint.errors != 0 || pkglint.warnings != 0 {
271: pkglint.logOut.Printf("%d %s and %d %s found.\n",
272: pkglint.errors, ifelseStr(pkglint.errors == 1, "error", "errors"),
273: pkglint.warnings, ifelseStr(pkglint.warnings == 1, "warning", "warnings"))
1.17 rillig 274: } else {
1.28 rillig 275: pkglint.logOut.WriteLine("Looks fine.")
1.17 rillig 276: }
1.28 rillig 277: if pkglint.explanationsAvailable && !pkglint.opts.Explain {
278: pkglint.logOut.WriteLine("(Run \"pkglint -e\" to show explanations.)")
1.17 rillig 279: }
1.28 rillig 280: if pkglint.autofixAvailable && !pkglint.opts.PrintAutofix {
281: pkglint.logOut.WriteLine("(Run \"pkglint -fs\" to show what can be fixed automatically.)")
1.17 rillig 282: }
1.28 rillig 283: if pkglint.autofixAvailable && !pkglint.opts.Autofix {
284: pkglint.logOut.WriteLine("(Run \"pkglint -F\" to automatically fix some issues.)")
1.17 rillig 285: }
286: }
287: }
288:
289: func (pkglint *Pkglint) CheckDirent(fname string) {
1.18 rillig 290: if trace.Tracing {
291: defer trace.Call1(fname)()
1.17 rillig 292: }
293:
294: st, err := os.Lstat(fname)
295: if err != nil || !st.Mode().IsDir() && !st.Mode().IsRegular() {
296: NewLineWhole(fname).Errorf("No such file or directory.")
297: return
298: }
299: isDir := st.Mode().IsDir()
300: isReg := st.Mode().IsRegular()
301:
1.36 ! rillig 302: dir := ifelseStr(isReg, path.Dir(fname), fname)
! 303: absCurrentDir := abspath(dir)
1.28 rillig 304: pkglint.Wip = !pkglint.opts.Import && matches(absCurrentDir, `/wip/|/wip$`)
305: pkglint.Infrastructure = matches(absCurrentDir, `/mk/|/mk$`)
1.36 ! rillig 306: pkgsrcdir := findPkgsrcTopdir(dir)
1.35 rillig 307: if pkgsrcdir == "" {
1.36 ! rillig 308: NewLineWhole(fname).Errorf("Cannot determine the pkgsrc root directory for %q.", cleanpath(dir))
1.17 rillig 309: return
310: }
311:
312: switch {
313: case isDir && isEmptyDir(fname):
314: return
315: case isReg:
1.28 rillig 316: pkglint.Checkfile(fname)
1.17 rillig 317: return
318: }
319:
1.35 rillig 320: switch pkgsrcdir {
1.17 rillig 321: case "../..":
1.36 ! rillig 322: pkglint.checkdirPackage(dir)
1.17 rillig 323: case "..":
1.36 ! rillig 324: CheckdirCategory(dir)
1.17 rillig 325: case ".":
1.36 ! rillig 326: CheckdirToplevel(dir)
1.17 rillig 327: default:
328: NewLineWhole(fname).Errorf("Cannot check directories outside a pkgsrc tree.")
329: }
330: }
1.1 rillig 331:
332: // Returns the pkgsrc top-level directory, relative to the given file or directory.
333: func findPkgsrcTopdir(fname string) string {
1.18 rillig 334: for _, dir := range [...]string{".", "..", "../..", "../../.."} {
1.1 rillig 335: if fileExists(fname + "/" + dir + "/mk/bsd.pkg.mk") {
336: return dir
337: }
338: }
339: return ""
340: }
341:
1.6 rillig 342: func resolveVariableRefs(text string) string {
1.18 rillig 343: if trace.Tracing {
344: defer trace.Call1(text)()
1.1 rillig 345: }
346:
347: visited := make(map[string]bool) // To prevent endless loops
348:
349: str := text
350: for {
1.18 rillig 351: replaced := regex.Compile(`\$\{([\w.]+)\}`).ReplaceAllStringFunc(str, func(m string) string {
1.1 rillig 352: varname := m[2 : len(m)-1]
353: if !visited[varname] {
354: visited[varname] = true
1.6 rillig 355: if G.Pkg != nil {
356: if value, ok := G.Pkg.varValue(varname); ok {
1.1 rillig 357: return value
358: }
359: }
1.6 rillig 360: if G.Mk != nil {
361: if value, ok := G.Mk.VarValue(varname); ok {
1.1 rillig 362: return value
363: }
364: }
365: }
1.6 rillig 366: return "${" + varname + "}"
1.1 rillig 367: })
368: if replaced == str {
369: return replaced
370: }
371: str = replaced
372: }
373: }
374:
1.6 rillig 375: func CheckfileExtra(fname string) {
1.18 rillig 376: if trace.Tracing {
377: defer trace.Call1(fname)()
1.1 rillig 378: }
379:
1.36 ! rillig 380: if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
1.6 rillig 381: ChecklinesTrailingEmptyLines(lines)
382: }
1.1 rillig 383: }
384:
1.22 rillig 385: func ChecklinesDescr(lines []Line) {
1.18 rillig 386: if trace.Tracing {
1.22 rillig 387: defer trace.Call1(lines[0].Filename)()
1.1 rillig 388: }
389:
1.6 rillig 390: for _, line := range lines {
1.22 rillig 391: CheckLineLength(line, 80)
392: CheckLineTrailingWhitespace(line)
393: CheckLineValidCharacters(line, `[\t -~]`)
394: if contains(line.Text, "${") {
1.15 rillig 395: line.Notef("Variables are not expanded in the DESCR file.")
1.1 rillig 396: }
397: }
1.6 rillig 398: ChecklinesTrailingEmptyLines(lines)
1.1 rillig 399:
1.6 rillig 400: if maxlines := 24; len(lines) > maxlines {
401: line := lines[maxlines]
1.1 rillig 402:
1.6 rillig 403: line.Warnf("File too long (should be no more than %d lines).", maxlines)
1.15 rillig 404: Explain(
1.6 rillig 405: "The DESCR file should fit on a traditional terminal of 80x25",
406: "characters. It is also intended to give a _brief_ summary about",
407: "the package's contents.")
1.1 rillig 408: }
409:
1.6 rillig 410: SaveAutofixChanges(lines)
1.1 rillig 411: }
412:
1.22 rillig 413: func ChecklinesMessage(lines []Line) {
1.18 rillig 414: if trace.Tracing {
1.22 rillig 415: defer trace.Call1(lines[0].Filename)()
1.1 rillig 416: }
417:
1.26 rillig 418: explanation := []string{
419: "A MESSAGE file should consist of a header line, having 75 \"=\"",
420: "characters, followed by a line containing only the RCS Id, then an",
421: "empty line, your text and finally the footer line, which is the",
422: "same as the header line."}
1.1 rillig 423:
424: if len(lines) < 3 {
425: lastLine := lines[len(lines)-1]
1.15 rillig 426: lastLine.Warnf("File too short.")
1.26 rillig 427: Explain(explanation...)
1.1 rillig 428: return
429: }
430:
431: hline := strings.Repeat("=", 75)
1.22 rillig 432: if line := lines[0]; line.Text != hline {
1.26 rillig 433: fix := line.Autofix()
434: fix.Warnf("Expected a line of exactly 75 \"=\" characters.")
435: fix.Explain(explanation...)
436: fix.InsertBefore(hline)
437: fix.Apply()
438: CheckLineRcsid(lines[0], ``, "")
439: } else if 1 < len(lines) {
440: CheckLineRcsid(lines[1], ``, "")
1.1 rillig 441: }
442: for _, line := range lines {
1.22 rillig 443: CheckLineLength(line, 80)
444: CheckLineTrailingWhitespace(line)
445: CheckLineValidCharacters(line, `[\t -~]`)
1.1 rillig 446: }
1.22 rillig 447: if lastLine := lines[len(lines)-1]; lastLine.Text != hline {
1.26 rillig 448: fix := lastLine.Autofix()
449: fix.Warnf("Expected a line of exactly 75 \"=\" characters.")
450: fix.Explain(explanation...)
451: fix.InsertAfter(hline)
452: fix.Apply()
1.1 rillig 453: }
1.6 rillig 454: ChecklinesTrailingEmptyLines(lines)
1.26 rillig 455:
456: SaveAutofixChanges(lines)
1.1 rillig 457: }
458:
1.6 rillig 459: func CheckfileMk(fname string) {
1.18 rillig 460: if trace.Tracing {
461: defer trace.Call1(fname)()
1.1 rillig 462: }
463:
1.36 ! rillig 464: mklines := LoadMk(fname, NotEmpty|LogErrors)
! 465: if mklines == nil {
1.1 rillig 466: return
467: }
468:
1.36 ! rillig 469: mklines.Check()
! 470: mklines.SaveAutofixChanges()
1.1 rillig 471: }
472:
1.28 rillig 473: func (pkglint *Pkglint) Checkfile(fname string) {
1.18 rillig 474: if trace.Tracing {
475: defer trace.Call1(fname)()
1.6 rillig 476: }
1.1 rillig 477:
478: basename := path.Base(fname)
1.6 rillig 479: if hasPrefix(basename, "work") || hasSuffix(basename, "~") || hasSuffix(basename, ".orig") || hasSuffix(basename, ".rej") {
1.28 rillig 480: if pkglint.opts.Import {
1.15 rillig 481: NewLineWhole(fname).Errorf("Must be cleaned up before committing the package.")
1.1 rillig 482: }
483: return
484: }
485:
486: st, err := os.Lstat(fname)
487: if err != nil {
1.8 rillig 488: NewLineWhole(fname).Errorf("%s", err)
1.1 rillig 489: return
490: }
491:
1.29 rillig 492: pkglint.checkExecutable(st, fname)
1.1 rillig 493:
494: switch {
495: case st.Mode().IsDir():
496: switch {
1.23 rillig 497: case basename == "files" || basename == "patches" || isIgnoredFilename(basename):
1.1 rillig 498: // Ok
499: case matches(fname, `(?:^|/)files/[^/]*$`):
500: // Ok
501: case !isEmptyDir(fname):
1.15 rillig 502: NewLineWhole(fname).Warnf("Unknown directory name.")
1.1 rillig 503: }
504:
505: case st.Mode()&os.ModeSymlink != 0:
1.9 rillig 506: if !hasPrefix(basename, "work") {
1.15 rillig 507: NewLineWhole(fname).Warnf("Unknown symlink name.")
1.1 rillig 508: }
509:
510: case !st.Mode().IsRegular():
1.15 rillig 511: NewLineWhole(fname).Errorf("Only files and directories are allowed in pkgsrc.")
1.1 rillig 512:
513: case basename == "ALTERNATIVES":
1.28 rillig 514: if pkglint.opts.CheckAlternatives {
1.35 rillig 515: CheckfileAlternatives(fname, nil)
1.1 rillig 516: }
517:
518: case basename == "buildlink3.mk":
1.28 rillig 519: if pkglint.opts.CheckBuildlink3 {
1.36 ! rillig 520: if mklines := LoadMk(fname, NotEmpty|LogErrors); mklines != nil {
! 521: ChecklinesBuildlink3Mk(mklines)
1.1 rillig 522: }
523: }
524:
525: case hasPrefix(basename, "DESCR"):
1.28 rillig 526: if pkglint.opts.CheckDescr {
1.36 ! rillig 527: if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
1.6 rillig 528: ChecklinesDescr(lines)
1.1 rillig 529: }
530: }
531:
1.6 rillig 532: case basename == "distinfo":
1.28 rillig 533: if pkglint.opts.CheckDistinfo {
1.36 ! rillig 534: if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
1.6 rillig 535: ChecklinesDistinfo(lines)
1.1 rillig 536: }
537: }
538:
539: case basename == "DEINSTALL" || basename == "INSTALL":
1.28 rillig 540: if pkglint.opts.CheckInstall {
1.6 rillig 541: CheckfileExtra(fname)
1.1 rillig 542: }
543:
544: case hasPrefix(basename, "MESSAGE"):
1.28 rillig 545: if pkglint.opts.CheckMessage {
1.36 ! rillig 546: if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
1.6 rillig 547: ChecklinesMessage(lines)
1.1 rillig 548: }
549: }
550:
1.34 rillig 551: case basename == "options.mk":
552: if pkglint.opts.CheckOptions {
1.36 ! rillig 553: if mklines := LoadMk(fname, NotEmpty|LogErrors); mklines != nil {
! 554: ChecklinesOptionsMk(mklines)
1.34 rillig 555: }
556: }
557:
1.1 rillig 558: case matches(basename, `^patch-[-A-Za-z0-9_.~+]*[A-Za-z0-9_]$`):
1.28 rillig 559: if pkglint.opts.CheckPatches {
1.36 ! rillig 560: if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
1.6 rillig 561: ChecklinesPatch(lines)
1.1 rillig 562: }
563: }
564:
565: case matches(fname, `(?:^|/)patches/manual[^/]*$`):
1.18 rillig 566: if trace.Tracing {
567: trace.Step1("Unchecked file %q.", fname)
1.1 rillig 568: }
569:
570: case matches(fname, `(?:^|/)patches/[^/]*$`):
1.15 rillig 571: NewLineWhole(fname).Warnf("Patch files should be named \"patch-\", followed by letters, '-', '_', '.', and digits only.")
1.1 rillig 572:
573: case matches(basename, `^(?:.*\.mk|Makefile.*)$`) && !matches(fname, `files/`) && !matches(fname, `patches/`):
1.28 rillig 574: if pkglint.opts.CheckMk {
1.6 rillig 575: CheckfileMk(fname)
1.1 rillig 576: }
577:
578: case hasPrefix(basename, "PLIST"):
1.28 rillig 579: if pkglint.opts.CheckPlist {
1.36 ! rillig 580: if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
1.6 rillig 581: ChecklinesPlist(lines)
1.1 rillig 582: }
583: }
584:
585: case basename == "TODO" || basename == "README":
586: // Ok
587:
588: case hasPrefix(basename, "CHANGES-"):
1.17 rillig 589: // This only checks the file, but doesn't register the changes globally.
1.29 rillig 590: _ = pkglint.Pkgsrc.loadDocChangesFromFile(fname)
1.1 rillig 591:
592: case matches(fname, `(?:^|/)files/[^/]*$`):
593: // Skip
594:
1.9 rillig 595: case basename == "spec":
596: // Ok in regression tests
597:
1.1 rillig 598: default:
1.15 rillig 599: NewLineWhole(fname).Warnf("Unexpected file found.")
1.28 rillig 600: if pkglint.opts.CheckExtra {
1.6 rillig 601: CheckfileExtra(fname)
1.1 rillig 602: }
603: }
604: }
605:
1.29 rillig 606: func (pkglint *Pkglint) checkExecutable(st os.FileInfo, fname string) {
607: if st.Mode().IsRegular() && st.Mode().Perm()&0111 != 0 && !isCommitted(fname) {
608: line := NewLine(fname, 0, "", nil)
609: fix := line.Autofix()
610: fix.Warnf("Should not be executable.")
611: fix.Explain(
612: "No package file should ever be executable. Even the INSTALL and",
613: "DEINSTALL scripts are usually not usable in the form they have in",
614: "the package, as the pathnames get adjusted during installation.",
615: "So there is no need to have any file executable.")
616: fix.Custom(func(printAutofix, autofix bool) {
617: fix.Describef(0, "Clearing executable bits")
618: if autofix {
619: if err := os.Chmod(line.Filename, st.Mode()&^0111); err != nil {
620: line.Errorf("Cannot clear executable bits: %s", err)
621: }
622: }
623: })
624: fix.Apply()
625: }
626: }
627:
1.22 rillig 628: func ChecklinesTrailingEmptyLines(lines []Line) {
1.1 rillig 629: max := len(lines)
630: last := max
1.22 rillig 631: for last > 1 && lines[last-1].Text == "" {
1.1 rillig 632: last--
633: }
634: if last != max {
1.15 rillig 635: lines[last].Notef("Trailing empty lines.")
1.1 rillig 636: }
637: }
CVSweb <webmaster@jp.NetBSD.org>