Files
pkgsrc-ng/pkgtools/pkglint/files/package.go
2016-11-18 22:39:22 +01:00

837 lines
24 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"fmt"
"os/user"
"path"
"regexp"
"strconv"
"strings"
)
// Package contains data for the pkgsrc package that is currently checked.
type Package struct {
Pkgpath string // e.g. "category/pkgdir"
Pkgdir string // PKGDIR from the package Makefile
Filesdir string // FILESDIR from the package Makefile
Patchdir string // PATCHDIR from the package Makefile
DistinfoFile string // DISTINFO_FILE from the package Makefile
EffectivePkgname string // PKGNAME or DISTNAME from the package Makefile, including nb13
EffectivePkgbase string // The effective PKGNAME without the version
EffectivePkgversion string // The version part of the effective PKGNAME, excluding nb13
EffectivePkgnameLine *MkLine // The origin of the three effective_* values
SeenBsdPrefsMk bool // Has bsd.prefs.mk already been included?
vardef map[string]*MkLine // (varname, varcanon) => line
varuse map[string]*MkLine // (varname, varcanon) => line
bl3 map[string]*Line // buildlink3.mk name => line; contains only buildlink3.mk files that are directly included.
plistSubstCond map[string]bool // varname => true; list of all variables that are used as conditionals (@comment or nothing) in PLISTs.
included map[string]*Line // fname => line
seenMakefileCommon bool // Does the package have any .includes?
loadTimeTools map[string]bool // true=ok, false=not ok, absent=not mentioned in USE_TOOLS.
}
func NewPackage(pkgpath string) *Package {
pkg := &Package{
Pkgpath: pkgpath,
vardef: make(map[string]*MkLine),
varuse: make(map[string]*MkLine),
bl3: make(map[string]*Line),
plistSubstCond: make(map[string]bool),
included: make(map[string]*Line),
loadTimeTools: make(map[string]bool),
}
for varname, line := range G.globalData.UserDefinedVars {
pkg.vardef[varname] = line
}
return pkg
}
func (pkg *Package) defineVar(mkline *MkLine, varname string) {
if pkg.vardef[varname] == nil {
pkg.vardef[varname] = mkline
}
varcanon := varnameCanon(varname)
if pkg.vardef[varcanon] == nil {
pkg.vardef[varcanon] = mkline
}
}
func (pkg *Package) varValue(varname string) (string, bool) {
switch varname {
case "KRB5_TYPE":
return "heimdal", true
case "PGSQL_VERSION":
return "95", true
}
if mkline := pkg.vardef[varname]; mkline != nil {
return mkline.Value(), true
}
return "", false
}
func (pkg *Package) setSeenBsdPrefsMk() {
if !pkg.SeenBsdPrefsMk {
pkg.SeenBsdPrefsMk = true
if G.opts.Debug {
traceStep("Pkg.setSeenBsdPrefsMk")
}
}
}
func (pkg *Package) checkPossibleDowngrade() {
if G.opts.Debug {
defer tracecall0()()
}
m, _, pkgversion := match2(pkg.EffectivePkgname, rePkgname)
if !m {
return
}
mkline := pkg.EffectivePkgnameLine
change := G.globalData.LastChange[pkg.Pkgpath]
if change == nil {
if G.opts.Debug {
traceStep1("No change log for package %q", pkg.Pkgpath)
}
return
}
if change.Action == "Updated" {
changeVersion := regcomp(`nb\d+$`).ReplaceAllString(change.Version, "")
if pkgverCmp(pkgversion, changeVersion) < 0 {
mkline.Line.Warnf("The package is being downgraded from %s (see %s) to %s", change.Version, change.Line.ReferenceFrom(mkline.Line), pkgversion)
Explain4(
"The files in doc/CHANGES-*, in which all version changes are",
"recorded, have a higher version number than what the package says.",
"This is unusual, since packages are typically upgraded instead of",
"downgraded.")
}
}
}
func (pkg *Package) checklinesBuildlink3Inclusion(mklines *MkLines) {
if G.opts.Debug {
defer tracecall0()()
}
// Collect all the included buildlink3.mk files from the file.
includedFiles := make(map[string]*MkLine)
for _, mkline := range mklines.mklines {
if mkline.IsInclude() {
file := mkline.Includefile()
if m, bl3 := match1(file, `^\.\./\.\./(.*)/buildlink3\.mk`); m {
includedFiles[bl3] = mkline
if pkg.bl3[bl3] == nil {
mkline.Warn1("%s/buildlink3.mk is included by this file but not by the package.", bl3)
}
}
}
}
if G.opts.Debug {
for packageBl3 := range pkg.bl3 {
if includedFiles[packageBl3] == nil {
traceStep1("%s/buildlink3.mk is included by the package but not by the buildlink3.mk file.", packageBl3)
}
}
}
}
func checkdirPackage(pkgpath string) {
if G.opts.Debug {
defer tracecall1(pkgpath)()
}
G.Pkg = NewPackage(pkgpath)
defer func() { G.Pkg = nil }()
pkg := G.Pkg
// we need to handle the Makefile first to get some variables
lines := pkg.loadPackageMakefile(G.CurrentDir + "/Makefile")
if lines == nil {
return
}
files := dirglob(G.CurrentDir)
if pkg.Pkgdir != "." {
files = append(files, dirglob(G.CurrentDir+"/"+pkg.Pkgdir)...)
}
if G.opts.CheckExtra {
files = append(files, dirglob(G.CurrentDir+"/"+pkg.Filesdir)...)
}
files = append(files, dirglob(G.CurrentDir+"/"+pkg.Patchdir)...)
if pkg.DistinfoFile != "distinfo" && pkg.DistinfoFile != "./distinfo" {
files = append(files, G.CurrentDir+"/"+pkg.DistinfoFile)
}
haveDistinfo := false
havePatches := false
// Determine the used variables before checking any of the Makefile fragments.
for _, fname := range files {
if (hasPrefix(path.Base(fname), "Makefile.") || hasSuffix(fname, ".mk")) &&
!matches(fname, `patch-`) &&
!contains(fname, pkg.Pkgdir+"/") &&
!contains(fname, pkg.Filesdir+"/") {
if lines, err := readLines(fname, true); err == nil && lines != nil {
NewMkLines(lines).DetermineUsedVariables()
}
}
}
for _, fname := range files {
if containsVarRef(fname) {
continue
}
if fname == G.CurrentDir+"/Makefile" {
if G.opts.CheckMakefile {
pkg.checkfilePackageMakefile(fname, lines)
}
} else {
Checkfile(fname)
}
if contains(fname, "/patches/patch-") {
havePatches = true
} else if hasSuffix(fname, "/distinfo") {
haveDistinfo = true
}
pkg.checkLocallyModified(fname)
}
if G.opts.CheckDistinfo && G.opts.CheckPatches {
if havePatches && !haveDistinfo {
NewLineWhole(G.CurrentDir+"/"+pkg.DistinfoFile).Warn1("File not found. Please run \"%s makepatchsum\".", confMake)
}
}
if !isEmptyDir(G.CurrentDir + "/scripts") {
NewLineWhole(G.CurrentDir + "/scripts").Warn0("This directory and its contents are deprecated! Please call the script(s) explicitly from the corresponding target(s) in the pkg's Makefile.")
}
}
func (pkg *Package) loadPackageMakefile(fname string) *MkLines {
if G.opts.Debug {
defer tracecall1(fname)()
}
mainLines, allLines := NewMkLines(nil), NewMkLines(nil)
if !pkg.readMakefile(fname, mainLines, allLines, "") {
return nil
}
if G.opts.DumpMakefile {
fmt.Printf("Whole Makefile (with all included files) follows:\n")
for _, line := range allLines.lines {
fmt.Printf("%s\n", line.String())
}
}
allLines.DetermineUsedVariables()
pkg.Pkgdir = expandVariableWithDefault("PKGDIR", ".")
pkg.DistinfoFile = expandVariableWithDefault("DISTINFO_FILE", "distinfo")
pkg.Filesdir = expandVariableWithDefault("FILESDIR", "files")
pkg.Patchdir = expandVariableWithDefault("PATCHDIR", "patches")
if varIsDefined("PHPEXT_MK") {
if !varIsDefined("USE_PHP_EXT_PATCHES") {
pkg.Patchdir = "patches"
}
if varIsDefined("PECL_VERSION") {
pkg.DistinfoFile = "distinfo"
}
}
if G.opts.Debug {
traceStep1("DISTINFO_FILE=%s", pkg.DistinfoFile)
traceStep1("FILESDIR=%s", pkg.Filesdir)
traceStep1("PATCHDIR=%s", pkg.Patchdir)
traceStep1("PKGDIR=%s", pkg.Pkgdir)
}
return mainLines
}
func (pkg *Package) readMakefile(fname string, mainLines *MkLines, allLines *MkLines, includingFnameForUsedCheck string) bool {
if G.opts.Debug {
defer tracecall1(fname)()
}
fileLines := LoadNonemptyLines(fname, true)
if fileLines == nil {
return false
}
fileMklines := NewMkLines(fileLines)
isMainMakefile := len(mainLines.mklines) == 0
for _, mkline := range fileMklines.mklines {
line := mkline.Line
if isMainMakefile {
mainLines.mklines = append(mainLines.mklines, mkline)
mainLines.lines = append(mainLines.lines, line)
}
allLines.mklines = append(allLines.mklines, mkline)
allLines.lines = append(allLines.lines, line)
var includeFile, incDir, incBase string
if mkline.IsInclude() {
inc := mkline.Includefile()
includeFile = resolveVariableRefs(resolveVarsInRelativePath(inc, true))
if containsVarRef(includeFile) {
if !contains(fname, "/mk/") {
line.Note1("Skipping include file %q. This may result in false warnings.", includeFile)
}
includeFile = ""
}
incDir, incBase = path.Split(includeFile)
}
if includeFile != "" {
if path.Base(fname) != "buildlink3.mk" {
if m, bl3File := match1(includeFile, `^\.\./\.\./(.*)/buildlink3\.mk$`); m {
G.Pkg.bl3[bl3File] = line
if G.opts.Debug {
traceStep1("Buildlink3 file in package: %q", bl3File)
}
}
}
}
if includeFile != "" && G.Pkg.included[includeFile] == nil {
G.Pkg.included[includeFile] = line
if matches(includeFile, `^\.\./[^./][^/]*/[^/]+`) {
mkline.Warn0("References to other packages should look like \"../../category/package\", not \"../package\".")
mkline.explainRelativeDirs()
}
if path.Base(fname) == "Makefile" && !hasPrefix(incDir, "../../mk/") && incBase != "buildlink3.mk" && incBase != "builtin.mk" && incBase != "options.mk" {
if G.opts.Debug {
traceStep1("Including %q sets seenMakefileCommon.", includeFile)
}
G.Pkg.seenMakefileCommon = true
}
skip := contains(fname, "/mk/") || hasSuffix(includeFile, "/bsd.pkg.mk") || hasSuffix(includeFile, "/bsd.prefs.mk")
if !skip {
dirname, _ := path.Split(fname)
dirname = cleanpath(dirname)
// Only look in the directory relative to the
// current file and in the current working directory.
// Pkglint doesnt have an include dir list, like make(1) does.
if !fileExists(dirname + "/" + includeFile) {
dirname = G.CurrentDir
}
if !fileExists(dirname + "/" + includeFile) {
line.Error1("Cannot read %q.", dirname+"/"+includeFile)
return false
}
if G.opts.Debug {
traceStep1("Including %q.", dirname+"/"+includeFile)
}
includingFname := ifelseStr(incBase == "Makefile.common" && incDir != "", fname, "")
if !pkg.readMakefile(dirname+"/"+includeFile, mainLines, allLines, includingFname) {
return false
}
}
}
if mkline.IsVarassign() {
varname, op, value := mkline.Varname(), mkline.Op(), mkline.Value()
if op != opAssignDefault || G.Pkg.vardef[varname] == nil {
if G.opts.Debug {
traceStep("varassign(%q, %q, %q)", varname, op, value)
}
G.Pkg.vardef[varname] = mkline
}
}
}
if includingFnameForUsedCheck != "" {
fileMklines.checkForUsedComment(relpath(G.globalData.Pkgsrcdir, includingFnameForUsedCheck))
}
return true
}
func (pkg *Package) checkfilePackageMakefile(fname string, mklines *MkLines) {
if G.opts.Debug {
defer tracecall1(fname)()
}
vardef := pkg.vardef
if vardef["PLIST_SRC"] == nil &&
vardef["GENERATE_PLIST"] == nil &&
vardef["META_PACKAGE"] == nil &&
!fileExists(G.CurrentDir+"/"+pkg.Pkgdir+"/PLIST") &&
!fileExists(G.CurrentDir+"/"+pkg.Pkgdir+"/PLIST.common") {
NewLineWhole(fname).Warn0("Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset. Are you sure PLIST handling is ok?")
}
if (vardef["NO_CHECKSUM"] != nil || vardef["META_PACKAGE"] != nil) && isEmptyDir(G.CurrentDir+"/"+pkg.Patchdir) {
if distinfoFile := G.CurrentDir + "/" + pkg.DistinfoFile; fileExists(distinfoFile) {
NewLineWhole(distinfoFile).Warn0("This file should not exist if NO_CHECKSUM or META_PACKAGE is set.")
}
} else {
if distinfoFile := G.CurrentDir + "/" + pkg.DistinfoFile; !containsVarRef(distinfoFile) && !fileExists(distinfoFile) {
NewLineWhole(distinfoFile).Warn1("File not found. Please run \"%s makesum\".", confMake)
}
}
if perlLine, noconfLine := vardef["REPLACE_PERL"], vardef["NO_CONFIGURE"]; perlLine != nil && noconfLine != nil {
perlLine.Warn1("REPLACE_PERL is ignored when NO_CONFIGURE is set (in %s)", noconfLine.Line.ReferenceFrom(perlLine.Line))
}
if vardef["LICENSE"] == nil && vardef["META_PACKAGE"] == nil {
NewLineWhole(fname).Error0("Each package must define its LICENSE.")
}
if gnuLine, useLine := vardef["GNU_CONFIGURE"], vardef["USE_LANGUAGES"]; gnuLine != nil && useLine != nil {
if matches(useLine.VarassignComment(), `(?-i)\b(?:c|empty|none)\b`) {
// Don't emit a warning, since the comment
// probably contains a statement that C is
// really not needed.
} else if !matches(useLine.Value(), `(?:^|\s+)(?:c|c99|objc)(?:\s+|$)`) {
gnuLine.Warn1("GNU_CONFIGURE almost always needs a C compiler, but \"c\" is not added to USE_LANGUAGES in %s.",
useLine.Line.ReferenceFrom(gnuLine.Line))
}
}
pkg.determineEffectivePkgVars()
pkg.checkPossibleDowngrade()
if vardef["COMMENT"] == nil {
NewLineWhole(fname).Warn0("No COMMENT given.")
}
if imake, x11 := vardef["USE_IMAKE"], vardef["USE_X11"]; imake != nil && x11 != nil {
if !hasSuffix(x11.Line.Fname, "/mk/x11.buildlink3.mk") {
imake.Line.Note1("USE_IMAKE makes USE_X11 in %s superfluous.", x11.Line.ReferenceFrom(imake.Line))
}
}
pkg.checkUpdate()
mklines.Check()
pkg.ChecklinesPackageMakefileVarorder(mklines)
SaveAutofixChanges(mklines.lines)
}
func (pkg *Package) getNbpart() string {
line := pkg.vardef["PKGREVISION"]
if line == nil {
return ""
}
pkgrevision := line.Value()
if rev, err := strconv.Atoi(pkgrevision); err == nil {
return "nb" + strconv.Itoa(rev)
}
return ""
}
func (pkg *Package) determineEffectivePkgVars() {
distnameLine := pkg.vardef["DISTNAME"]
pkgnameLine := pkg.vardef["PKGNAME"]
distname := ""
if distnameLine != nil {
distname = distnameLine.Value()
}
pkgname := ""
if pkgnameLine != nil {
pkgname = pkgnameLine.Value()
}
if distname != "" && pkgname != "" {
pkgname = pkg.pkgnameFromDistname(pkgname, distname)
}
if pkgname != "" && pkgname == distname && pkgnameLine.VarassignComment() == "" {
pkgnameLine.Note0("PKGNAME is ${DISTNAME} by default. You probably don't need to define PKGNAME.")
}
if pkgname == "" && distname != "" && !containsVarRef(distname) && !matches(distname, rePkgname) {
distnameLine.Warn0("As DISTNAME is not a valid package name, please define the PKGNAME explicitly.")
}
if pkgname != "" && !containsVarRef(pkgname) {
if m, m1, m2 := match2(pkgname, rePkgname); m {
pkg.EffectivePkgname = pkgname + pkg.getNbpart()
pkg.EffectivePkgnameLine = pkgnameLine
pkg.EffectivePkgbase = m1
pkg.EffectivePkgversion = m2
}
}
if pkg.EffectivePkgnameLine == nil && distname != "" && !containsVarRef(distname) {
if m, m1, m2 := match2(distname, rePkgname); m {
pkg.EffectivePkgname = distname + pkg.getNbpart()
pkg.EffectivePkgnameLine = distnameLine
pkg.EffectivePkgbase = m1
pkg.EffectivePkgversion = m2
}
}
if pkg.EffectivePkgnameLine != nil {
if G.opts.Debug {
traceStep("Effective name=%q base=%q version=%q",
pkg.EffectivePkgname, pkg.EffectivePkgbase, pkg.EffectivePkgversion)
}
}
}
func (pkg *Package) pkgnameFromDistname(pkgname, distname string) string {
tokens := NewMkParser(dummyLine, pkgname, false).MkTokens()
subst := func(str, smod string) (result string) {
if G.opts.Debug {
defer tracecall(str, smod, ref(result))()
}
qsep := regexp.QuoteMeta(smod[1:2])
if m, left, from, right, to, flags := match5(smod, RegexPattern(`^S`+qsep+`(\^?)([^:]*?)(\$?)`+qsep+`([^:]*)`+qsep+`([1g]*)$`)); m {
result := mkopSubst(str, left != "", from, right != "", to, flags)
if G.opts.Debug {
traceStep("subst %q %q => %q", str, smod, result)
}
return result
}
return str
}
result := ""
for _, token := range tokens {
if token.Varuse != nil && token.Varuse.varname == "DISTNAME" {
newDistname := distname
for _, mod := range token.Varuse.modifiers {
if mod == "tl" {
newDistname = strings.ToLower(newDistname)
} else if hasPrefix(mod, "S") {
newDistname = subst(newDistname, mod)
} else {
newDistname = token.Text
break
}
}
result += newDistname
} else {
result += token.Text
}
}
return result
}
func (pkg *Package) checkUpdate() {
if pkg.EffectivePkgbase != "" {
for _, sugg := range G.globalData.GetSuggestedPackageUpdates() {
if pkg.EffectivePkgbase != sugg.Pkgname {
continue
}
suggver, comment := sugg.Version, sugg.Comment
if comment != "" {
comment = " (" + comment + ")"
}
pkgnameLine := pkg.EffectivePkgnameLine
cmp := pkgverCmp(pkg.EffectivePkgversion, suggver)
switch {
case cmp < 0:
pkgnameLine.Warn2("This package should be updated to %s%s.", sugg.Version, comment)
Explain2(
"The wishlist for package updates in doc/TODO mentions that a newer",
"version of this package is available.")
case cmp > 0:
pkgnameLine.Note2("This package is newer than the update request to %s%s.", suggver, comment)
default:
pkgnameLine.Note2("The update request to %s from doc/TODO%s has been done.", suggver, comment)
}
}
}
}
func (pkg *Package) ChecklinesPackageMakefileVarorder(mklines *MkLines) {
if G.opts.Debug {
defer tracecall0()()
}
if !G.opts.WarnOrder || pkg.seenMakefileCommon {
return
}
type OccCount uint8
const (
once OccCount = iota
optional
many
)
type OccDef struct {
varname string
count OccCount
}
type OccGroup struct {
name string
count OccCount
occ []OccDef
}
var sections = []OccGroup{
{"Initial comments", once,
[]OccDef{},
},
{"Unsorted stuff, part 1", once,
[]OccDef{
{"DISTNAME", optional},
{"PKGNAME", optional},
{"PKGREVISION", optional},
{"CATEGORIES", once},
{"MASTER_SITES", optional},
{"DIST_SUBDIR", optional},
{"EXTRACT_SUFX", optional},
{"DISTFILES", many},
{"SITES.*", many},
},
},
{"Distribution patches", optional,
[]OccDef{
{"PATCH_SITES", optional}, // or once?
{"PATCH_SITE_SUBDIR", optional},
{"PATCHFILES", optional}, // or once?
{"PATCH_DIST_ARGS", optional},
{"PATCH_DIST_STRIP", optional},
{"PATCH_DIST_CAT", optional},
},
},
{"Unsorted stuff, part 2", once,
[]OccDef{
{"MAINTAINER", optional},
{"OWNER", optional},
{"HOMEPAGE", optional},
{"COMMENT", once},
{"LICENSE", once},
},
},
{"Legal issues", optional,
[]OccDef{
{"LICENSE_FILE", optional},
{"RESTRICTED", optional},
{"NO_BIN_ON_CDROM", optional},
{"NO_BIN_ON_FTP", optional},
{"NO_SRC_ON_CDROM", optional},
{"NO_SRC_ON_FTP", optional},
},
},
{"Technical restrictions", optional,
[]OccDef{
{"BROKEN_EXCEPT_ON_PLATFORM", many},
{"BROKEN_ON_PLATFORM", many},
{"NOT_FOR_PLATFORM", many},
{"ONLY_FOR_PLATFORM", many},
{"NOT_FOR_COMPILER", many},
{"ONLY_FOR_COMPILER", many},
{"NOT_FOR_UNPRIVILEGED", optional},
{"ONLY_FOR_UNPRIVILEGED", optional},
},
},
{"Dependencies", optional,
[]OccDef{
{"BUILD_DEPENDS", many},
{"TOOL_DEPENDS", many},
{"DEPENDS", many},
},
},
}
lineno := 0
sectindex := -1
varindex := 0
nextSection := true
var vars []OccDef
below := make(map[string]string)
var belowWhat string
// If the current section is optional but contains non-optional
// fields, the complete section may be skipped as long as there
// has not been a non-optional variable.
maySkipSection := false
// In each iteration, one of the following becomes true:
// - new lineno > old lineno
// - new sectindex > old sectindex
// - new sectindex == old sectindex && new varindex > old varindex
// - new nextSection == true && old nextSection == false
for lineno < len(mklines.lines) {
mkline := mklines.mklines[lineno]
line := mklines.lines[lineno]
text := line.Text
if G.opts.Debug {
traceStep("[varorder] section %d variable %d vars %v", sectindex, varindex, vars)
}
if nextSection {
nextSection = false
sectindex++
if !(sectindex < len(sections)) {
break
}
vars = sections[sectindex].occ
maySkipSection = sections[sectindex].count == optional
varindex = 0
}
switch {
case hasPrefix(text, "#"):
lineno++
case mkline.IsVarassign():
varcanon := mkline.Varcanon()
if belowText, exists := below[varcanon]; exists {
if belowText != "" {
line.Warn2("%s appears too late. Please put it below %s.", varcanon, belowText)
} else {
line.Warn1("%s appears too late. It should be the very first definition.", varcanon)
}
lineno++
continue
}
for varindex < len(vars) && varcanon != vars[varindex].varname && (vars[varindex].count != once || maySkipSection) {
if vars[varindex].count == once {
maySkipSection = false
}
below[vars[varindex].varname] = belowWhat
varindex++
}
switch {
case !(varindex < len(vars)):
if sections[sectindex].count != optional {
line.Warn0("Empty line expected.")
}
nextSection = true
case varcanon != vars[varindex].varname:
line.Warn2("Expected %s, but found %s.", vars[varindex].varname, varcanon)
lineno++
default:
if vars[varindex].count != many {
below[vars[varindex].varname] = belowWhat
varindex++
}
lineno++
}
belowWhat = varcanon
default:
for varindex < len(vars) {
if vars[varindex].count == once && !maySkipSection {
line.Warn1("The canonical position for the required variable %s is here.", vars[varindex].varname)
Explain(
"In simple package Makefiles, some common variables should be",
"arranged in a specific order.",
"",
"See doc/Makefile-example or the pkgsrc guide, section",
"\"Package components\", subsection \"Makefile\" for more information.")
}
below[vars[varindex].varname] = belowWhat
varindex++
}
nextSection = true
if text == "" {
belowWhat = "the previous empty line"
lineno++
}
}
}
}
func (mklines *MkLines) checkForUsedComment(relativeName string) {
lines := mklines.lines
if len(lines) < 3 {
return
}
expected := "# used by " + relativeName
for _, line := range lines {
if line.Text == expected {
return
}
}
i := 0
for i < 2 && hasPrefix(lines[i].Text, "#") {
i++
}
insertLine := lines[i]
if !insertLine.AutofixInsertBefore(expected) {
insertLine.Warn1("Please add a line %q here.", expected)
Explain(
"Since Makefile.common files usually don't have any comments and",
"therefore not a clearly defined interface, they should at least",
"contain references to all files that include them, so that it is",
"easier to see what effects future changes may have.",
"",
"If there are more than five packages that use a Makefile.common,",
"you should think about giving it a proper name (maybe plugin.mk) and",
"documenting its interface.")
}
SaveAutofixChanges(lines)
}
func (pkg *Package) checkLocallyModified(fname string) {
if G.opts.Debug {
defer tracecall(fname)()
}
ownerLine := pkg.vardef["OWNER"]
maintainerLine := pkg.vardef["MAINTAINER"]
owner := ""
maintainer := ""
if ownerLine != nil && !containsVarRef(ownerLine.Value()) {
owner = ownerLine.Value()
}
if maintainerLine != nil && !containsVarRef(maintainerLine.Value()) && maintainerLine.Value() != "pkgsrc-users@NetBSD.org" {
maintainer = maintainerLine.Value()
}
if owner == "" && maintainer == "" {
return
}
user, err := user.Current()
if err != nil || user.Username == "" {
return
}
// On Windows, this is `Computername\Username`.
username := regcomp(`^.*\\`).ReplaceAllString(user.Username, "")
if G.opts.Debug {
traceStep("user=%q owner=%q maintainer=%q", username, owner, maintainer)
}
if username == strings.Split(owner, "@")[0] || username == strings.Split(maintainer, "@")[0] {
return
}
if isLocallyModified(fname) {
if owner != "" {
NewLineWhole(fname).Warn1("Don't commit changes to this file without asking the OWNER, %s.", owner)
Explain2(
"See the pkgsrc guide, section \"Package components\",",
"keyword \"owner\", for more information.")
}
if maintainer != "" {
NewLineWhole(fname).Note1("Please only commit changes that %s would approve.", maintainer)
Explain2(
"See the pkgsrc guide, section \"Package components\",",
"keyword \"maintainer\", for more information.")
}
}
}