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

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>