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

Annotation of pkgsrc/pkgtools/pkglint/files/plist.go, Revision 1.58

1.33      rillig      1: package pkglint
1.1       rillig      2:
                      3: import (
1.33      rillig      4:        "netbsd.org/pkglint/textproc"
1.4       rillig      5:        "sort"
1.1       rillig      6:        "strings"
                      7: )
                      8:
1.42      rillig      9: func CheckLinesPlist(pkg *Package, lines *Lines) {
1.12      rillig     10:        if trace.Tracing {
1.45      rillig     11:                defer trace.Call(lines.Filename)()
1.4       rillig     12:        }
1.1       rillig     13:
1.42      rillig     14:        idOk := lines.CheckCvsID(0, `@comment `, "@comment ")
1.1       rillig     15:
1.42      rillig     16:        if idOk && lines.Len() == 1 {
1.40      rillig     17:                line := lines.Lines[0]
1.50      rillig     18:                line.Errorf("PLIST files must not be empty.")
1.40      rillig     19:                line.Explain(
1.1       rillig     20:                        "One reason for empty PLISTs is that this is a newly created package",
1.35      rillig     21:                        sprintf("and that the author didn't run %q after installing the files.", bmake("print-PLIST")),
1.1       rillig     22:                        "",
1.35      rillig     23:                        "For most Perl packages, the final PLIST is generated automatically.",
                     24:                        "Since the source PLIST is not used at all, it can be removed for these packages.",
1.1       rillig     25:                        "",
1.35      rillig     26:                        "Meta packages also don't need a PLIST file",
                     27:                        "since their only purpose is to declare dependencies.")
1.42      rillig     28:                return
1.1       rillig     29:        }
                     30:
1.48      rillig     31:        ck := NewPlistChecker(pkg)
1.4       rillig     32:        ck.Check(lines)
                     33: }
                     34:
                     35: type PlistChecker struct {
1.39      rillig     36:        pkg             *Package
1.47      rillig     37:        allFiles        map[RelPath]*PlistLine
                     38:        allDirs         map[RelPath]*PlistLine
1.48      rillig     39:        lastFname       RelPath
1.39      rillig     40:        once            Once
                     41:        nonAsciiAllowed bool
1.4       rillig     42: }
                     43:
1.48      rillig     44: func NewPlistChecker(pkg *Package) *PlistChecker {
                     45:        return &PlistChecker{
                     46:                pkg,
                     47:                make(map[RelPath]*PlistLine),
                     48:                make(map[RelPath]*PlistLine),
                     49:                "",
                     50:                Once{},
                     51:                false}
                     52: }
                     53:
1.43      rillig     54: func (ck *PlistChecker) Load(lines *Lines) []*PlistLine {
1.48      rillig     55:        plines := ck.newLines(lines)
1.4       rillig     56:        ck.collectFilesAndDirs(plines)
                     57:
1.43      rillig     58:        if lines.BaseName == "PLIST.common_end" {
1.45      rillig     59:                commonLines := Load(lines.Filename.TrimSuffix("_end"), NotEmpty)
1.27      rillig     60:                if commonLines != nil {
1.48      rillig     61:                        ck.collectFilesAndDirs(ck.newLines(commonLines))
1.1       rillig     62:                }
                     63:        }
                     64:
1.43      rillig     65:        return plines
                     66: }
                     67:
                     68: func (ck *PlistChecker) Check(plainLines *Lines) {
                     69:        plines := ck.Load(plainLines)
                     70:
1.4       rillig     71:        for _, pline := range plines {
1.35      rillig     72:                ck.checkLine(pline)
1.4       rillig     73:                pline.CheckTrailingWhitespace()
                     74:        }
1.54      rillig     75:        ck.checkOmf(plines)
1.34      rillig     76:        CheckLinesTrailingEmptyLines(plainLines)
1.1       rillig     77:
1.37      rillig     78:        sorter := NewPlistLineSorter(plines)
                     79:        sorter.Sort()
                     80:        if !sorter.autofixed {
1.4       rillig     81:                SaveAutofixChanges(plainLines)
                     82:        }
                     83: }
1.1       rillig     84:
1.48      rillig     85: func (*PlistChecker) newLines(lines *Lines) []*PlistLine {
1.31      rillig     86:        plines := make([]*PlistLine, lines.Len())
                     87:        for i, line := range lines.Lines {
1.36      rillig     88:                var conditions []string
                     89:                text := line.Text
                     90:
                     91:                for hasPrefix(text, "${PLIST.") /* just for performance */ {
                     92:                        if m, cond, rest := match2(text, `^(?:\$\{(PLIST\.[\w-.]+)\})(.*)`); m {
                     93:                                conditions = append(conditions, cond)
                     94:                                text = rest
                     95:                        } else {
                     96:                                break
1.1       rillig     97:                        }
                     98:                }
1.36      rillig     99:
                    100:                plines[i] = &PlistLine{line, conditions, text}
1.4       rillig    101:        }
                    102:        return plines
                    103: }
1.1       rillig    104:
1.33      rillig    105: var plistLineStart = textproc.NewByteSet("$0-9A-Za-z")
                    106:
1.4       rillig    107: func (ck *PlistChecker) collectFilesAndDirs(plines []*PlistLine) {
1.36      rillig    108:
1.4       rillig    109:        for _, pline := range plines {
1.48      rillig    110:                text := pline.text
                    111:                switch {
                    112:                case text == "":
                    113:                        break
                    114:                case plistLineStart.Contains(text[0]):
                    115:                        ck.collectPath(NewRelPathString(text), pline)
                    116:                case text[0] == '@':
                    117:                        ck.collectDirective(pline)
1.1       rillig    118:                }
                    119:        }
                    120: }
                    121:
1.48      rillig    122: func (ck *PlistChecker) collectPath(rel RelPath, pline *PlistLine) {
                    123:
                    124:        // TODO: What about paths containing variables?
                    125:        //  Are they intended to be collected as well?
                    126:
                    127:        if prev := ck.allFiles[rel]; prev == nil || stringSliceLess(pline.conditions, prev.conditions) {
                    128:                ck.allFiles[rel] = pline
                    129:        }
1.53      rillig    130:        for dir := rel.Dir(); dir != "."; dir = dir.Dir() {
1.48      rillig    131:                ck.allDirs[dir] = pline
                    132:        }
                    133: }
                    134:
                    135: func (ck *PlistChecker) collectDirective(pline *PlistLine) {
                    136:        m, dirname := match1(pline.text, `^@exec \$\{MKDIR\} %D/(.*)$`)
                    137:        if !m || NewPath(dirname).IsAbs() {
                    138:                return
                    139:        }
1.53      rillig    140:        for dir := NewRelPathString(dirname); dir != "."; dir = dir.Dir() {
1.48      rillig    141:                ck.allDirs[dir] = pline
                    142:        }
                    143: }
                    144:
1.35      rillig    145: func (ck *PlistChecker) checkLine(pline *PlistLine) {
1.4       rillig    146:        text := pline.text
1.35      rillig    147:
                    148:        if text == "" {
1.28      rillig    149:                fix := pline.Autofix()
1.19      rillig    150:                fix.Warnf("PLISTs should not contain empty lines.")
                    151:                fix.Delete()
                    152:                fix.Apply()
1.35      rillig    153:
1.48      rillig    154:        } else if plistLineStart.Contains(text[0]) {
                    155:                ck.checkPath(pline, pline.Path())
1.35      rillig    156:
                    157:        } else if m, cmd, arg := match2(text, `^@([a-z-]+)[\t ]*(.*)`); m {
                    158:                pline.CheckDirective(cmd, arg)
1.53      rillig    159:                if cmd == "comment" && pline.Location.lineno > 1 {
1.48      rillig    160:                        ck.nonAsciiAllowed = true
                    161:                }
1.35      rillig    162:
1.1       rillig    163:        } else {
1.48      rillig    164:                pline.Errorf("Invalid line type: %s", pline.Line.Text)
1.1       rillig    165:        }
                    166: }
                    167:
1.48      rillig    168: func (ck *PlistChecker) checkPath(pline *PlistLine, rel RelPath) {
1.39      rillig    169:        ck.checkPathNonAscii(pline)
1.4       rillig    170:        ck.checkSorted(pline)
1.21      rillig    171:        ck.checkDuplicate(pline)
1.1       rillig    172:
1.48      rillig    173:        if contains(rel.Base(), "${IMAKE_MANNEWSUFFIX}") {
1.4       rillig    174:                pline.warnImakeMannewsuffix()
1.1       rillig    175:        }
1.35      rillig    176:
1.48      rillig    177:        if rel.HasPrefixPath("${PKGMANDIR}") {
1.28      rillig    178:                fix := pline.Autofix()
1.35      rillig    179:                fix.Notef("PLIST files should use \"man/\" instead of \"${PKGMANDIR}\".")
1.19      rillig    180:                fix.Explain(
                    181:                        "The pkgsrc infrastructure takes care of replacing the correct value",
                    182:                        "when generating the actual PLIST for the package.")
                    183:                fix.Replace("${PKGMANDIR}/", "man/")
                    184:                fix.Apply()
                    185:
1.35      rillig    186:                // Since the autofix only applies to the Line, the PlistLine needs to be updated manually.
1.19      rillig    187:                pline.text = strings.Replace(pline.text, "${PKGMANDIR}/", "man/", 1)
1.9       rillig    188:        }
1.1       rillig    189:
1.48      rillig    190:        topdir := rel.Parts()[0]
1.4       rillig    191:
                    192:        switch topdir {
                    193:        case "bin":
1.48      rillig    194:                ck.checkPathBin(pline, rel)
1.4       rillig    195:        case "doc":
1.28      rillig    196:                pline.Errorf("Documentation must be installed under share/doc, not doc.")
1.4       rillig    197:        case "etc":
1.48      rillig    198:                ck.checkPathEtc(pline)
1.4       rillig    199:        case "info":
1.48      rillig    200:                ck.checkPathInfo(pline)
1.4       rillig    201:        case "lib":
1.48      rillig    202:                ck.checkPathLib(pline, rel)
1.4       rillig    203:        case "man":
1.35      rillig    204:                ck.checkPathMan(pline)
1.4       rillig    205:        case "share":
1.35      rillig    206:                ck.checkPathShare(pline)
1.4       rillig    207:        }
1.1       rillig    208:
1.48      rillig    209:        ck.checkPathMisc(rel, pline)
1.52      rillig    210:        ck.checkPathCond(pline)
1.48      rillig    211: }
                    212:
                    213: func (ck *PlistChecker) checkPathMisc(rel RelPath, pline *PlistLine) {
                    214:        if rel.ContainsText("${PKGLOCALEDIR}") && ck.pkg != nil && !ck.pkg.vars.IsDefined("USE_PKGLOCALEDIR") {
1.35      rillig    215:                pline.Warnf("PLIST contains ${PKGLOCALEDIR}, but USE_PKGLOCALEDIR is not set in the package Makefile.")
1.1       rillig    216:        }
                    217:
1.48      rillig    218:        if rel.ContainsPath("CVS") {
1.28      rillig    219:                pline.Warnf("CVS files should not be in the PLIST.")
1.4       rillig    220:        }
1.48      rillig    221:        if rel.HasSuffixText(".orig") {
1.28      rillig    222:                pline.Warnf(".orig files should not be in the PLIST.")
1.4       rillig    223:        }
1.48      rillig    224:        if rel.HasBase("perllocal.pod") {
1.37      rillig    225:                pline.Warnf("The perllocal.pod file should not be in the PLIST.")
1.40      rillig    226:                pline.Explain(
1.35      rillig    227:                        "This file is handled automatically by the INSTALL/DEINSTALL scripts",
                    228:                        "since its contents depends on more than one package.")
1.4       rillig    229:        }
1.48      rillig    230:        if rel.ContainsText(".egg-info/") {
1.28      rillig    231:                pline.Warnf("Include \"../../lang/python/egg.mk\" instead of listing .egg-info files directly.")
1.10      rillig    232:        }
1.48      rillig    233:        if rel.ContainsPath("..") {
                    234:                pline.Errorf("Paths in PLIST files must not contain \"..\".")
                    235:        } else if canonical := rel.Clean(); canonical != rel {
                    236:                pline.Errorf("Paths in PLIST files must be canonical (%s).", canonical)
                    237:        }
1.4       rillig    238: }
1.1       rillig    239:
1.39      rillig    240: func (ck *PlistChecker) checkPathNonAscii(pline *PlistLine) {
                    241:        text := pline.text
                    242:
                    243:        lex := textproc.NewLexer(text)
1.51      rillig    244:        lex.SkipBytesFunc(func(b byte) bool { return b >= ' ' && b <= '~' })
1.39      rillig    245:        ascii := lex.EOF()
                    246:
                    247:        switch {
                    248:        case !ck.nonAsciiAllowed && !ascii:
                    249:                ck.nonAsciiAllowed = true
                    250:
                    251:                pline.Warnf("Non-ASCII filename %q.", escapePrintable(text))
                    252:                pline.Explain(
                    253:                        "The great majority of filenames installed by pkgsrc packages",
                    254:                        "are ASCII-only. Filenames containing non-ASCII characters",
                    255:                        "can cause various problems since their name may already be",
                    256:                        "different when another character encoding is set in the locale.",
                    257:                        "",
                    258:                        "To mark a filename as intentionally non-ASCII, insert a PLIST",
                    259:                        "@comment with a convincing reason directly above this line.",
                    260:                        "That comment will allow this line and the lines directly",
                    261:                        "below it to contain non-ASCII filenames.")
                    262:
                    263:        case ck.nonAsciiAllowed && ascii:
                    264:                ck.nonAsciiAllowed = false
                    265:        }
                    266: }
                    267:
1.4       rillig    268: func (ck *PlistChecker) checkSorted(pline *PlistLine) {
1.48      rillig    269:        if !pline.HasPlainPath() {
                    270:                return
                    271:        }
                    272:
                    273:        rel := pline.Path()
                    274:        if ck.lastFname != "" && ck.lastFname > rel && !G.Logger.Opts.Autofix {
                    275:                pline.Warnf("%q should be sorted before %q.", rel.String(), ck.lastFname.String())
                    276:                pline.Explain(
                    277:                        "The files in the PLIST should be sorted alphabetically.",
                    278:                        "This allows human readers to quickly see whether a file is included or not.")
1.4       rillig    279:        }
1.48      rillig    280:        ck.lastFname = rel
1.4       rillig    281: }
1.1       rillig    282:
1.21      rillig    283: func (ck *PlistChecker) checkDuplicate(pline *PlistLine) {
1.48      rillig    284:        if !pline.HasPlainPath() {
1.21      rillig    285:                return
                    286:        }
                    287:
1.48      rillig    288:        prev := ck.allFiles[pline.Path()]
1.36      rillig    289:        if prev == pline || len(prev.conditions) > 0 {
1.21      rillig    290:                return
                    291:        }
                    292:
1.28      rillig    293:        fix := pline.Autofix()
1.48      rillig    294:        fix.Errorf("Duplicate filename %q, already appeared in %s.", pline.text, pline.RelLine(prev.Line))
1.21      rillig    295:        fix.Delete()
                    296:        fix.Apply()
                    297: }
                    298:
1.48      rillig    299: func (ck *PlistChecker) checkPathBin(pline *PlistLine, rel RelPath) {
                    300:        if rel.Count() > 2 {
1.28      rillig    301:                pline.Warnf("The bin/ directory should not have subdirectories.")
1.40      rillig    302:                pline.Explain(
1.26      rillig    303:                        "The programs in bin/ are collected there to be executable by the",
1.33      rillig    304:                        "user without having to type an absolute path.",
                    305:                        "This advantage does not apply to programs in subdirectories of bin/.",
                    306:                        "These programs should rather be placed in libexec/PKGBASE.")
1.4       rillig    307:                return
                    308:        }
1.1       rillig    309: }
                    310:
1.48      rillig    311: func (ck *PlistChecker) checkPathEtc(pline *PlistLine) {
1.4       rillig    312:        if hasPrefix(pline.text, "etc/rc.d/") {
1.35      rillig    313:                pline.Errorf("RCD_SCRIPTS must not be registered in the PLIST.")
                    314:                pline.Explain(
                    315:                        "Please use the RCD_SCRIPTS framework, which is described in mk/pkginstall/bsd.pkginstall.mk.")
1.4       rillig    316:                return
                    317:        }
                    318:
1.35      rillig    319:        pline.Errorf("Configuration files must not be registered in the PLIST.")
                    320:        pline.Explain(
1.4       rillig    321:                "Please use the CONF_FILES framework, which is described in mk/pkginstall/bsd.pkginstall.mk.")
                    322: }
1.1       rillig    323:
1.48      rillig    324: func (ck *PlistChecker) checkPathInfo(pline *PlistLine) {
1.4       rillig    325:        if pline.text == "info/dir" {
1.28      rillig    326:                pline.Errorf("\"info/dir\" must not be listed. Use install-info to add/remove an entry.")
1.4       rillig    327:                return
                    328:        }
1.1       rillig    329:
1.44      rillig    330:        if ck.pkg != nil && !ck.pkg.vars.IsDefined("INFO_FILES") {
1.28      rillig    331:                pline.Warnf("Packages that install info files should set INFO_FILES in the Makefile.")
1.1       rillig    332:        }
1.4       rillig    333: }
1.1       rillig    334:
1.48      rillig    335: func (ck *PlistChecker) checkPathLib(pline *PlistLine, rel RelPath) {
1.39      rillig    336:
1.1       rillig    337:        switch {
1.8       rillig    338:
1.48      rillig    339:        case rel.HasPrefixPath("lib/locale"):
1.28      rillig    340:                pline.Errorf("\"lib/locale\" must not be listed. Use ${PKGLOCALEDIR}/locale and set USE_PKGLOCALEDIR instead.")
1.4       rillig    341:                return
                    342:        }
1.1       rillig    343:
1.48      rillig    344:        basename := rel.Base()
1.4       rillig    345:        if contains(basename, ".a") || contains(basename, ".so") {
1.48      rillig    346:                la := replaceAll(pline.text, `(\.a|\.so[0-9.]*)$`, ".la")
                    347:                if la != pline.text {
                    348:                        laLine := ck.allFiles[NewRelPathString(la)]
                    349:                        if laLine != nil {
                    350:                                pline.Warnf("Redundant library found. The libtool library is in %s.",
                    351:                                        pline.RelLine(laLine.Line))
1.4       rillig    352:                        }
                    353:                }
                    354:        }
1.42      rillig    355:
                    356:        pkg := ck.pkg
                    357:        if pkg == nil {
                    358:                return
                    359:        }
                    360:
                    361:        if pline.text == "lib/charset.alias" && pkg.Pkgpath != "converters/libiconv" {
                    362:                pline.Errorf("Only the libiconv package may install lib/charset.alias.")
                    363:        }
                    364:
1.44      rillig    365:        if hasSuffix(basename, ".la") && !pkg.vars.IsDefined("USE_LIBTOOL") {
1.42      rillig    366:                if ck.once.FirstTime("USE_LIBTOOL") {
                    367:                        pline.Warnf("Packages that install libtool libraries should define USE_LIBTOOL.")
                    368:                }
                    369:        }
1.4       rillig    370: }
1.1       rillig    371:
1.35      rillig    372: func (ck *PlistChecker) checkPathMan(pline *PlistLine) {
1.57      rillig    373:        m, catOrMan, section, base := match3(pline.text, `^man/(cat|man)(\w+)/(.*)$`)
                    374:        if !m {
                    375:                // maybe: line.Warnf("Invalid filename %q for manual page.", text)
                    376:                return
                    377:        }
                    378:        m, manpage, ext, gz := match3(base, `^(.*?)\.(\w+)(\.gz)?$`)
1.4       rillig    379:        if !m {
1.32      rillig    380:                // maybe: line.Warnf("Invalid filename %q for manual page.", text)
1.4       rillig    381:                return
                    382:        }
                    383:
1.28      rillig    384:        if !matches(section, `^[0-9ln]$`) {
                    385:                pline.Warnf("Unknown section %q for manual page.", section)
1.4       rillig    386:        }
1.1       rillig    387:
1.47      rillig    388:        if catOrMan == "cat" && ck.allFiles[NewRelPathString("man/man"+section+"/"+manpage+"."+section)] == nil {
1.28      rillig    389:                pline.Warnf("Preformatted manual page without unformatted one.")
1.4       rillig    390:        }
1.1       rillig    391:
1.4       rillig    392:        if catOrMan == "cat" {
                    393:                if ext != "0" {
1.28      rillig    394:                        pline.Warnf("Preformatted manual pages should end in \".0\".")
1.1       rillig    395:                }
1.4       rillig    396:        } else {
                    397:                if !hasPrefix(ext, section) {
1.28      rillig    398:                        pline.Warnf("Mismatch between the section (%s) and extension (%s) of the manual page.", section, ext)
1.4       rillig    399:                }
                    400:        }
1.1       rillig    401:
1.19      rillig    402:        if gz != "" {
1.28      rillig    403:                fix := pline.Autofix()
1.19      rillig    404:                fix.Notef("The .gz extension is unnecessary for manual pages.")
                    405:                fix.Explain(
1.4       rillig    406:                        "Whether the manual pages are installed in compressed form or not is",
1.33      rillig    407:                        "configured by the pkgsrc user.",
                    408:                        "Compression and decompression takes place automatically,",
                    409:                        "no matter if the .gz extension is mentioned in the PLIST or not.")
1.50      rillig    410:                fix.ReplaceAt(0, len(pline.Text)-len(".gz"), ".gz", "")
1.19      rillig    411:                fix.Apply()
1.4       rillig    412:        }
                    413: }
1.1       rillig    414:
1.35      rillig    415: func (ck *PlistChecker) checkPathShare(pline *PlistLine) {
1.28      rillig    416:        text := pline.text
1.39      rillig    417:
1.4       rillig    418:        switch {
1.52      rillig    419:        case ck.pkg != nil && hasPrefix(text, "share/icons/"):
1.42      rillig    420:                ck.checkPathShareIcons(pline)
1.1       rillig    421:
                    422:        case hasPrefix(text, "share/doc/html/"):
1.37      rillig    423:                pline.Warnf("Use of \"share/doc/html\" is deprecated. Use \"share/doc/${PKGBASE}\" instead.")
1.1       rillig    424:
                    425:        case hasPrefix(text, "share/info/"):
1.28      rillig    426:                pline.Warnf("Info pages should be installed into info/, not share/info/.")
1.40      rillig    427:                pline.Explain(
1.28      rillig    428:                        "To fix this, add INFO_FILES=yes to the package Makefile.")
1.1       rillig    429:
                    430:        case hasPrefix(text, "share/man/"):
1.28      rillig    431:                pline.Warnf("Man pages should be installed into man/, not share/man/.")
1.1       rillig    432:        }
1.3       rillig    433: }
                    434:
1.42      rillig    435: func (ck *PlistChecker) checkPathShareIcons(pline *PlistLine) {
                    436:        pkg := ck.pkg
                    437:        text := pline.text
                    438:
                    439:        if hasPrefix(text, "share/icons/hicolor/") && pkg.Pkgpath != "graphics/hicolor-icon-theme" {
                    440:                f := "../../graphics/hicolor-icon-theme/buildlink3.mk"
                    441:                if !pkg.included.Seen(f) && ck.once.FirstTime("hicolor-icon-theme") {
                    442:                        pline.Errorf("Packages that install hicolor icons must include %q in the Makefile.", f)
                    443:                }
                    444:        }
                    445:
                    446:        if text == "share/icons/hicolor/icon-theme.cache" && pkg.Pkgpath != "graphics/hicolor-icon-theme" {
                    447:                pline.Errorf("The file icon-theme.cache must not appear in any PLIST file.")
                    448:                pline.Explain(
                    449:                        "Remove this line and add the following line to the package Makefile.",
                    450:                        "",
                    451:                        ".include \"../../graphics/hicolor-icon-theme/buildlink3.mk\"")
                    452:        }
                    453:
                    454:        if hasPrefix(text, "share/icons/gnome") && pkg.Pkgpath != "graphics/gnome-icon-theme" {
                    455:                f := "../../graphics/gnome-icon-theme/buildlink3.mk"
                    456:                if !pkg.included.Seen(f) {
                    457:                        pline.Errorf("The package Makefile must include %q.", f)
                    458:                        pline.Explain(
                    459:                                "Packages that install GNOME icons must maintain the icon theme",
                    460:                                "cache.")
                    461:                }
                    462:        }
                    463:
1.44      rillig    464:        if contains(text[12:], "/") && !pkg.vars.IsDefined("ICON_THEMES") && ck.once.FirstTime("ICON_THEMES") {
1.42      rillig    465:                pline.Warnf("Packages that install icon theme files should set ICON_THEMES.")
                    466:        }
                    467: }
                    468:
1.52      rillig    469: func (ck *PlistChecker) checkPathCond(pline *PlistLine) {
                    470:        if ck.pkg == nil {
                    471:                return
                    472:        }
                    473:
                    474:        for _, cond := range pline.conditions {
                    475:                ck.checkCond(pline, cond[6:])
                    476:        }
                    477: }
                    478:
                    479: func (ck *PlistChecker) checkCond(pline *PlistLine, cond string) {
                    480:        vars := ck.pkg.vars
                    481:        mkline := vars.LastDefinition("PLIST_VARS")
                    482:        if mkline == nil || ck.once.SeenSlice("cond", cond) {
                    483:                return
                    484:        }
                    485:
                    486:        plistVars := vars.LastValue("PLIST_VARS")
                    487:        resolvedPlistVars := resolveVariableRefs(plistVars, nil, ck.pkg)
                    488:        for _, varparam := range mkline.ValueFields(resolvedPlistVars) {
                    489:                if varparam == cond {
                    490:                        return
                    491:                }
                    492:                if containsVarUse(varparam) {
                    493:                        trace.Stepf(
                    494:                                "Skipping check for condition %q because PLIST_VARS "+
                    495:                                        "contains the unresolved %q as part of %q.",
                    496:                                cond, varparam, resolvedPlistVars)
                    497:                        return
                    498:                }
                    499:        }
                    500:
                    501:        assert(ck.once.FirstTimeSlice("cond", cond))
                    502:        pline.Warnf(
                    503:                "Condition %q should be added to PLIST_VARS in the package Makefile.",
                    504:                cond)
                    505: }
                    506:
1.54      rillig    507: func (ck *PlistChecker) checkOmf(plines []*PlistLine) {
                    508:        if ck.pkg == nil {
                    509:                return
                    510:        }
                    511:        mkline := ck.pkg.Includes("../../mk/omf-scrollkeeper.mk")
                    512:        if mkline == nil {
                    513:                return
                    514:        }
                    515:
                    516:        for _, pline := range plines {
                    517:                if hasSuffix(pline.text, ".omf") {
                    518:                        return
                    519:                }
                    520:        }
                    521:
                    522:        fix := mkline.Autofix()
                    523:        fix.Errorf("Only packages that have .omf files in their PLIST may include omf-scrollkeeper.mk.")
                    524:        if !mkline.HasRationale() {
                    525:                fix.Delete()
                    526:        }
                    527:        fix.Apply()
                    528: }
                    529:
1.44      rillig    530: type PlistLine struct {
                    531:        *Line
1.58    ! rillig    532:        // XXX: Why "PLIST.docs" and not simply "docs"?
1.44      rillig    533:        conditions []string // e.g. PLIST.docs
                    534:        text       string   // Line.Text without any conditions of the form ${PLIST.cond}
                    535: }
                    536:
1.55      rillig    537: func (pline *PlistLine) HasPath() bool {
                    538:        return pline.text != "" && plistLineStart.Contains(pline.text[0])
                    539: }
1.48      rillig    540:
                    541: func (pline *PlistLine) HasPlainPath() bool {
                    542:        text := pline.text
                    543:        return text != "" &&
                    544:                plistLineStart.Contains(text[0]) &&
1.51      rillig    545:                !containsVarUse(text)
1.48      rillig    546: }
                    547:
1.55      rillig    548: func (pline *PlistLine) Path() RelPath {
                    549:        assert(pline.HasPath())
                    550:        return NewRelPathString(pline.text)
                    551: }
                    552:
1.4       rillig    553: func (pline *PlistLine) CheckTrailingWhitespace() {
                    554:        if hasSuffix(pline.text, " ") || hasSuffix(pline.text, "\t") {
1.37      rillig    555:                pline.Errorf("Pkgsrc does not support filenames ending in whitespace.")
1.40      rillig    556:                pline.Explain(
1.32      rillig    557:                        "Each character in the PLIST is relevant, even trailing whitespace.")
1.3       rillig    558:        }
                    559: }
                    560:
1.4       rillig    561: func (pline *PlistLine) CheckDirective(cmd, arg string) {
                    562:        if cmd == "unexec" {
1.28      rillig    563:                if m, dir := match1(arg, `^(?:rmdir|\$\{RMDIR\} %D/)(.*)`); m {
1.25      rillig    564:                        if !contains(dir, "true") && !contains(dir, "${TRUE}") {
1.28      rillig    565:                                fix := pline.Autofix()
                    566:                                fix.Warnf("Please remove this line. It is no longer necessary.")
                    567:                                fix.Delete()
                    568:                                fix.Apply()
1.3       rillig    569:                        }
                    570:                }
                    571:        }
1.1       rillig    572:
1.4       rillig    573:        switch cmd {
                    574:        case "exec", "unexec":
                    575:                switch {
                    576:                case contains(arg, "ldconfig") && !contains(arg, "/usr/bin/true"):
1.37      rillig    577:                        pline.Errorf("The ldconfig command must be used with \"||/usr/bin/true\".")
1.1       rillig    578:                }
                    579:
1.4       rillig    580:        case "comment":
1.35      rillig    581:                // Nothing to check.
1.3       rillig    582:
1.4       rillig    583:        case "dirrm":
1.28      rillig    584:                pline.Warnf("@dirrm is obsolete. Please remove this line.")
1.40      rillig    585:                pline.Explain(
1.4       rillig    586:                        "Directories are removed automatically when they are empty.",
                    587:                        "When a package needs an empty directory, it can use the @pkgdir",
1.32      rillig    588:                        "command in the PLIST.")
1.3       rillig    589:
1.4       rillig    590:        case "imake-man":
1.34      rillig    591:                args := strings.Fields(arg)
1.4       rillig    592:                switch {
                    593:                case len(args) != 3:
1.34      rillig    594:                        pline.Warnf("Invalid number of arguments for imake-man, should be 3.")
1.4       rillig    595:                case args[2] == "${IMAKE_MANNEWSUFFIX}":
                    596:                        pline.warnImakeMannewsuffix()
1.3       rillig    597:                }
                    598:
1.4       rillig    599:        case "pkgdir":
                    600:                // Nothing to check.
1.3       rillig    601:
1.4       rillig    602:        default:
1.28      rillig    603:                pline.Warnf("Unknown PLIST directive \"@%s\".", cmd)
1.3       rillig    604:        }
                    605: }
                    606:
1.4       rillig    607: func (pline *PlistLine) warnImakeMannewsuffix() {
1.28      rillig    608:        pline.Warnf("IMAKE_MANNEWSUFFIX is not meant to appear in PLISTs.")
1.40      rillig    609:        pline.Explain(
1.1       rillig    610:                "This is the result of a print-PLIST call that has not been edited",
1.33      rillig    611:                "manually by the package maintainer.",
                    612:                "Please replace the IMAKE_MANNEWSUFFIX with:",
1.1       rillig    613:                "",
                    614:                "\tIMAKE_MAN_SUFFIX for programs,",
                    615:                "\tIMAKE_LIBMAN_SUFFIX for library functions,",
                    616:                "\tIMAKE_FILEMAN_SUFFIX for file formats,",
                    617:                "\tIMAKE_GAMEMAN_SUFFIX for games,",
                    618:                "\tIMAKE_MISCMAN_SUFFIX for other man pages.")
                    619: }
1.4       rillig    620:
                    621: type plistLineSorter struct {
1.16      rillig    622:        header     []*PlistLine // Does not take part in sorting
                    623:        middle     []*PlistLine // Only this part is sorted
                    624:        footer     []*PlistLine // Does not take part in sorting, typically contains @exec or @pkgdir
1.42      rillig    625:        unsortable *Line        // Some lines are so difficult to sort that only humans can do that
1.16      rillig    626:        changed    bool         // Whether the sorting actually changed something
                    627:        autofixed  bool         // Whether the newly sorted file has been written to disk
1.4       rillig    628: }
                    629:
                    630: func NewPlistLineSorter(plines []*PlistLine) *plistLineSorter {
1.16      rillig    631:        headerEnd := 0
                    632:        for headerEnd < len(plines) && hasPrefix(plines[headerEnd].text, "@comment") {
                    633:                headerEnd++
                    634:        }
                    635:
                    636:        footerStart := len(plines)
                    637:        for footerStart > headerEnd && hasPrefix(plines[footerStart-1].text, "@") {
                    638:                footerStart--
                    639:        }
                    640:
                    641:        header := plines[0:headerEnd]
                    642:        middle := plines[headerEnd:footerStart]
                    643:        footer := plines[footerStart:]
1.42      rillig    644:        var unsortable *Line
1.16      rillig    645:
                    646:        for _, pline := range middle {
                    647:                if unsortable == nil && (hasPrefix(pline.text, "@") || contains(pline.text, "$")) {
1.28      rillig    648:                        unsortable = pline.Line
1.4       rillig    649:                }
                    650:        }
1.16      rillig    651:        return &plistLineSorter{header, middle, footer, unsortable, false, false}
1.4       rillig    652: }
                    653:
                    654: func (s *plistLineSorter) Sort() {
1.16      rillig    655:        if line := s.unsortable; line != nil {
1.50      rillig    656:                if trace.Tracing {
1.28      rillig    657:                        trace.Stepf("%s: This line prevents pkglint from sorting the PLIST automatically.", line)
1.16      rillig    658:                }
                    659:                return
                    660:        }
                    661:
1.41      rillig    662:        if !G.Logger.shallBeLogged("%q should be sorted before %q.") {
1.24      rillig    663:                return
                    664:        }
1.16      rillig    665:        if len(s.middle) == 0 {
                    666:                return
                    667:        }
1.28      rillig    668:        firstLine := s.middle[0].Line
1.21      rillig    669:
                    670:        sort.SliceStable(s.middle, func(i, j int) bool {
                    671:                mi := s.middle[i]
                    672:                mj := s.middle[j]
1.36      rillig    673:                less := mi.text < mj.text ||
1.46      rillig    674:                        mi.text == mj.text && stringSliceLess(mi.conditions, mj.conditions)
                    675:                if i < j != less {
1.21      rillig    676:                        s.changed = true
                    677:                }
                    678:                return less
                    679:        })
1.4       rillig    680:
1.16      rillig    681:        if !s.changed {
1.4       rillig    682:                return
                    683:        }
                    684:
1.19      rillig    685:        fix := firstLine.Autofix()
1.31      rillig    686:        fix.Notef(SilentAutofixFormat)
1.53      rillig    687:        fix.Describef(0, "Sorting the whole file.")
1.19      rillig    688:        fix.Apply()
                    689:
1.42      rillig    690:        var lines []*Line
1.16      rillig    691:        for _, pline := range s.header {
1.28      rillig    692:                lines = append(lines, pline.Line)
1.16      rillig    693:        }
                    694:        for _, pline := range s.middle {
1.28      rillig    695:                lines = append(lines, pline.Line)
1.16      rillig    696:        }
                    697:        for _, pline := range s.footer {
1.28      rillig    698:                lines = append(lines, pline.Line)
1.4       rillig    699:        }
1.19      rillig    700:
1.53      rillig    701:        s.autofixed = SaveAutofixChanges(NewLines(lines[0].Filename(), lines))
1.4       rillig    702: }
1.55      rillig    703:
1.56      rillig    704: type PlistRank struct {
                    705:        Rank  int
                    706:        Opsys string
                    707:        Arch  string
                    708:        Rest  string
                    709: }
1.55      rillig    710:
1.56      rillig    711: var defaultPlistRank = &PlistRank{0, "", "", ""}
                    712:
                    713: func NewPlistRank(basename string) *PlistRank {
                    714:        isOpsys := func(s string) bool {
                    715:                return G.Pkgsrc.VariableType(nil, "OPSYS").basicType.HasEnum(s)
                    716:        }
                    717:        isArch := func(s string) bool {
                    718:                return G.Pkgsrc.VariableType(nil, "MACHINE_ARCH").basicType.HasEnum(s)
                    719:        }
                    720:        isEmulOpsys := func(s string) bool {
                    721:                return G.Pkgsrc.VariableType(nil, "EMUL_OPSYS").basicType.HasEnum(s)
                    722:        }
                    723:        isEmulArch := func(s string) bool {
                    724:                return G.Pkgsrc.VariableType(nil, "EMUL_ARCH").basicType.HasEnum(s)
                    725:        }
                    726:
                    727:        switch basename {
                    728:        case "PLIST":
                    729:                return defaultPlistRank
                    730:        case "PLIST.common":
                    731:                return &PlistRank{1, "", "", ""}
                    732:        case "PLIST.common_end":
                    733:                return &PlistRank{2, "", "", ""}
                    734:        }
                    735:
                    736:        parts := strings.Split(basename[6:], "-")
                    737:        rank := PlistRank{3, "", "", ""}
                    738:        if isOpsys(parts[0]) {
                    739:                rank.Opsys = parts[0]
                    740:                parts = parts[1:]
                    741:        }
                    742:        if len(parts) > 0 && isArch(parts[0]) {
                    743:                rank.Arch = parts[0]
                    744:                parts = parts[1:]
                    745:        }
                    746:        if len(parts) >= 2 && isEmulOpsys(parts[0]) && isEmulArch(parts[1]) {
                    747:                rank.Opsys = parts[0]
                    748:                rank.Arch = parts[1]
                    749:                parts = parts[2:]
                    750:        }
                    751:        rank.Rest = strings.Join(parts, "-")
                    752:        return &rank
                    753: }
1.55      rillig    754:
                    755: // The ranks among the files are:
                    756: //  PLIST
                    757: //  -> PLIST.common
                    758: //  -> PLIST.common_end
                    759: //  -> { PLIST.OPSYS, PLIST.ARCH }
                    760: //  -> { PLIST.OPSYS.ARCH, PLIST.EMUL_PLATFORM }
                    761: // Files are a later level must not mention files that are already
                    762: // mentioned at an earlier level.
1.56      rillig    763: func (r *PlistRank) MoreGeneric(other *PlistRank) bool {
                    764:        if r.Rank != 3 && other.Rank != 3 {
                    765:                return r.Rank < other.Rank
                    766:        }
                    767:        if r.Opsys != "" && r.Opsys != other.Opsys {
                    768:                return false
                    769:        }
                    770:        if r.Arch != "" && r.Arch != other.Arch {
                    771:                return false
                    772:        }
                    773:        if r.Rest != "" && r.Rest != other.Rest {
                    774:                return false
                    775:        }
                    776:        return *r != *other
1.55      rillig    777: }
                    778:
                    779: type PlistLines struct {
                    780:        all map[RelPath][]*plistLineData
                    781: }
                    782:
                    783: func NewPlistLines() *PlistLines {
                    784:        return &PlistLines{make(map[RelPath][]*plistLineData)}
                    785: }
                    786:
                    787: type plistLineData struct {
                    788:        line *PlistLine
1.56      rillig    789:        rank *PlistRank
1.55      rillig    790: }
                    791:
1.56      rillig    792: func (pl *PlistLines) Add(line *PlistLine, rank *PlistRank) {
1.55      rillig    793:        path := line.Path()
                    794:        for _, existing := range pl.all[path] {
1.56      rillig    795:                switch {
                    796:                case existing.rank == rank:
                    797:                        break
                    798:                case existing.rank.MoreGeneric(rank):
1.55      rillig    799:                        line.Errorf("Path %s is already listed in %s.",
                    800:                                path, line.RelLine(existing.line.Line))
1.56      rillig    801:                case rank.MoreGeneric(existing.rank):
                    802:                        existing.line.Errorf("Path %s is already listed in %s.",
                    803:                                path, existing.line.RelLine(line.Line))
1.55      rillig    804:                }
                    805:        }
                    806:        pl.all[path] = append(pl.all[path], &plistLineData{line, rank})
                    807: }

CVSweb <webmaster@jp.NetBSD.org>