pkgtools/pkglint: update to 5.6.1

Changes since 5.6.0:

* Fix output of relative paths in the diagnostics (thanks @wiz)
* Fix parsing of ${VAR:ts---}; it is now a syntax error
* Load more type definitions from mk/* instead of hard-coding them
* Lots of refactoring to improve test coverage, fixing several
  small bugs as they were found
This commit is contained in:
rillig 2018-08-16 20:41:42 +00:00
parent 0507c0fcf0
commit 3505d7d933
48 changed files with 1073 additions and 628 deletions

View file

@ -1,6 +1,6 @@
# $NetBSD: Makefile,v 1.546 2018/08/12 16:31:56 rillig Exp $
# $NetBSD: Makefile,v 1.547 2018/08/16 20:41:42 rillig Exp $
PKGNAME= pkglint-5.6.0
PKGNAME= pkglint-5.6.1
DISTFILES= # none
CATEGORIES= pkgtools

View file

@ -6,8 +6,8 @@ import (
)
func CheckfileAlternatives(filename string, plistFiles map[string]bool) {
lines, err := readLines(filename, false)
if err != nil {
lines := Load(filename, NotEmpty|LogErrors)
if lines == nil {
return
}

View file

@ -5,23 +5,24 @@ import "gopkg.in/check.v1"
func (s *Suite) Test_Alternatives_PLIST(c *check.C) {
t := s.Init(c)
t.Chdir("category/package")
t.SetupFileLines("ALTERNATIVES",
"sbin/sendmail @PREFIX@/sbin/sendmail.postfix@POSTFIXVER@",
"sbin/sendmail @PREFIX@/sbin/sendmail.exim@EXIMVER@",
"bin/echo bin/gnu-echo",
"bin/editor bin/vim -e")
G.Pkg = NewPackage("")
G.Pkg = NewPackage(".")
G.Pkg.PlistFiles["bin/echo"] = true
G.Pkg.PlistFiles["bin/vim"] = true
G.Pkg.PlistFiles["sbin/sendmail.exim${EXIMVER}"] = true
CheckfileAlternatives(t.File("ALTERNATIVES"), G.Pkg.PlistFiles)
CheckfileAlternatives("ALTERNATIVES", G.Pkg.PlistFiles)
t.CheckOutputLines(
"ERROR: ~/ALTERNATIVES:1: Alternative implementation \"@PREFIX@/sbin/sendmail.postfix@POSTFIXVER@\" must appear in the PLIST as \"sbin/sendmail.postfix${POSTFIXVER}\".",
"NOTE: ~/ALTERNATIVES:1: @PREFIX@/ can be omitted from the file name.",
"NOTE: ~/ALTERNATIVES:2: @PREFIX@/ can be omitted from the file name.",
"ERROR: ~/ALTERNATIVES:3: Alternative wrapper \"bin/echo\" must not appear in the PLIST.",
"ERROR: ~/ALTERNATIVES:3: Alternative implementation \"bin/gnu-echo\" must appear in the PLIST.")
"ERROR: ALTERNATIVES:1: Alternative implementation \"@PREFIX@/sbin/sendmail.postfix@POSTFIXVER@\" must appear in the PLIST as \"sbin/sendmail.postfix${POSTFIXVER}\".",
"NOTE: ALTERNATIVES:1: @PREFIX@/ can be omitted from the file name.",
"NOTE: ALTERNATIVES:2: @PREFIX@/ can be omitted from the file name.",
"ERROR: ALTERNATIVES:3: Alternative wrapper \"bin/echo\" must not appear in the PLIST.",
"ERROR: ALTERNATIVES:3: Alternative implementation \"bin/gnu-echo\" must appear in the PLIST.")
}

View file

@ -117,7 +117,7 @@ func (s *Suite) Test_autofix_MkLines(c *check.C) {
"line1 := value1",
"line2 := value2",
"line3 := value3")
pkg := NewPackage("category/basename")
pkg := NewPackage(t.File("category/basename"))
G.Pkg = pkg
mklines := pkg.loadPackageMakefile()
G.Pkg = nil
@ -259,7 +259,7 @@ func (s *Suite) Test_Autofix_show_source_code(c *check.C) {
t.SetupCommandLine("--show-autofix", "--source")
mklines := t.SetupFileMkLines("Makefile",
MkRcsID,
"before \\",
"# before \\",
"The old song \\",
"after")
line := mklines.lines[1]
@ -274,7 +274,7 @@ func (s *Suite) Test_Autofix_show_source_code(c *check.C) {
t.CheckOutputLines(
"WARN: ~/Makefile:2--4: Using \"old\" is deprecated.",
"AUTOFIX: ~/Makefile:3: Replacing \"old\" with \"new\".",
">\tbefore \\",
">\t# before \\",
"-\tThe old song \\",
"+\tThe new song \\",
">\tafter")

View file

@ -41,7 +41,7 @@ func (s *Suite) Test_ChecklinesBuildlink3Mk_name_mismatch(c *check.C) {
t := s.Init(c)
t.SetupVartypes()
G.Pkg = NewPackage("x11/hs-X11")
G.Pkg = NewPackage(t.File("x11/hs-X11"))
G.Pkg.EffectivePkgbase = "X11"
G.Pkg.EffectivePkgnameLine = t.NewMkLine("Makefile", 3, "DISTNAME=\tX11-1.0")
mklines := t.NewMkLines("buildlink3.mk",

View file

@ -10,12 +10,11 @@ func CheckdirCategory(dir string) {
defer trace.Call1(dir)()
}
lines := LoadNonemptyLines(dir+"/Makefile", true)
if lines == nil {
mklines := LoadMk(dir+"/Makefile", NotEmpty|LogErrors)
if mklines == nil {
return
}
mklines := NewMkLines(lines)
mklines.Check()
exp := NewMkExpecter(mklines)
@ -164,7 +163,7 @@ func CheckdirCategory(dir string) {
exp.CurrentLine().Errorf("The file should end here.")
}
SaveAutofixChanges(lines)
mklines.SaveAutofixChanges()
if G.opts.Recursive {
G.Todo = append(append([]string(nil), subdirs...), G.Todo...)

View file

@ -58,12 +58,23 @@ func (s *Suite) SetUpTest(c *check.C) {
t.checkC = nil
G.opts.LogVerbose = true // To detect duplicate work being done
t.EnableSilentTracing()
prevdir, err := os.Getwd()
if err != nil {
c.Fatalf("Cannot get current working directory: %s", err)
}
t.prevdir = prevdir
}
func (s *Suite) TearDownTest(c *check.C) {
t := s.Tester
t.checkC = nil // No longer usable; see https://github.com/go-check/check/issues/22
if err := os.Chdir(t.prevdir); err != nil {
fmt.Fprintf(os.Stderr, "Cannot chdir back to previous dir: %s", err)
}
G = Pkglint{} // unusable because of missing logOut and logErr
textproc.Testing = false
if out := t.Output(); out != "" {
@ -71,6 +82,7 @@ func (s *Suite) TearDownTest(c *check.C) {
c.TestName(), strings.Split(out, "\n"))
}
t.tmpdir = ""
t.DisableTracing()
}
var _ = check.Suite(new(Suite))
@ -82,10 +94,12 @@ func Test(t *testing.T) { check.TestingT(t) }
// all the test methods, which makes it difficult to find
// a method by auto-completion.
type Tester struct {
stdout bytes.Buffer
stderr bytes.Buffer
tmpdir string
checkC *check.C
stdout bytes.Buffer
stderr bytes.Buffer
tmpdir string
checkC *check.C // Only usable during the test method itself
prevdir string // The current working directory before the test started
relcwd string
}
func (t *Tester) c() *check.C {
@ -98,6 +112,11 @@ func (t *Tester) c() *check.C {
// SetupCommandLine simulates a command line for the remainder of the test.
// See Pkglint.ParseCommandLine.
func (t *Tester) SetupCommandLine(args ...string) {
// Prevent tracing from being disabled; see EnableSilentTracing.
prevTracing := trace.Tracing
defer func() { trace.Tracing = prevTracing }()
exitcode := G.ParseCommandLine(append([]string{"pkglint"}, args...))
if exitcode != nil && *exitcode != 0 {
t.CheckOutputEmpty()
@ -149,15 +168,14 @@ func (t *Tester) SetupTool(tool *Tool) {
// The file is then read in, without considering line continuations.
func (t *Tester) SetupFileLines(relativeFilename string, lines ...string) []Line {
filename := t.CreateFileLines(relativeFilename, lines...)
return LoadExistingLines(filename, false)
return Load(filename, MustSucceed)
}
// SetupFileLines creates a temporary file and writes the given lines to it.
// The file is then read in, handling line continuations for Makefiles.
func (t *Tester) SetupFileMkLines(relativeFilename string, lines ...string) *MkLines {
filename := t.CreateFileLines(relativeFilename, lines...)
plainLines := LoadExistingLines(filename, true)
return NewMkLines(plainLines)
return LoadMk(filename, MustSucceed)
}
// SetupPkgsrc sets up a minimal but complete pkgsrc installation in the
@ -222,21 +240,62 @@ func (t *Tester) CreateFileLines(relativeFilename string, lines ...string) (file
// File returns the absolute path to the given file in the
// temporary directory. It doesn't check whether that file exists.
// Calls to Tester.Chdir change the base directory for the relative file name.
func (t *Tester) File(relativeFilename string) string {
if t.tmpdir == "" {
t.tmpdir = filepath.ToSlash(t.c().MkDir())
}
return t.tmpdir + "/" + relativeFilename
if t.relcwd != "" {
return cleanpath(relativeFilename)
}
return cleanpath(t.tmpdir + "/" + relativeFilename)
}
// ExpectFatalError, when run in a defer statement, runs the action
// if the current function panics with a pkglintFatal
// (typically from line.Fatalf).
func (t *Tester) ExpectFatalError(action func()) {
// Chdir changes the current working directory to the given subdirectory
// of the temporary directory, creating it if necessary.
//
// After this call, all files loaded from the temporary directory via
// SetupFileLines or CreateFileLines or similar methods will use path names
// relative to this directory.
//
// After the test, the previous working directory is restored, so that
// the other tests are unaffected.
//
// As long as this method is not called in a test, the current working
// directory is indeterminate.
func (t *Tester) Chdir(relativeFilename string) {
if t.relcwd != "" {
// When multiple calls of Chdir are mixed with calls to CreateFileLines,
// the resulting []Line and MkLines variables will use relative file names,
// and these will point to different areas in the file system. This is
// usually not indented and therefore prevented.
t.checkC.Fatalf("Chdir must only be called once per test; already in %q.", t.relcwd)
}
_ = os.MkdirAll(t.File(relativeFilename), 0700)
if err := os.Chdir(t.File(relativeFilename)); err != nil {
t.checkC.Fatalf("Cannot chdir: %s", err)
}
t.relcwd = relativeFilename
}
// ExpectFatalError promises that in the remainder of the current function
// call, a panic with a pkglintFatal will occur (typically from Line.Fatalf).
//
// Usage:
// func() {
// defer t.ExpectFatalError()
//
// // The code that causes the fatal error.
// Load(t.File("nonexistent"), MustSucceed)
// }()
// t.CheckOutputLines(
// "FATAL: ~/nonexistent: Does not exist.")
func (t *Tester) ExpectFatalError() {
r := recover()
if _, ok := r.(pkglintFatal); ok {
action()
} else {
if r == nil {
panic("Expected a pkglint fatal error, but didn't get one.")
} else if _, ok := r.(pkglintFatal); !ok {
panic(r)
}
}
@ -344,12 +403,28 @@ func (t *Tester) EnableTracing() {
trace.Tracing = true
}
// EnableTracingToLog enables the tracing and writes the tracing output
// to the test log that can be examined with Tester.Output.
func (t *Tester) EnableTracingToLog() {
G.logOut = NewSeparatorWriter(io.MultiWriter(os.Stdout, &t.stdout))
trace.Out = &t.stdout
trace.Tracing = true
}
// EnableSilentTracing enables tracing mode, but discards any tracing output.
// This can be used to improve code coverage without any side-effects,
// since tracing output is quite large.
func (t *Tester) EnableSilentTracing() {
trace.Out = ioutil.Discard
trace.Tracing = true
}
// DisableTracing logs the output to the buffers again, ready to be
// checked with CheckOutputLines.
func (t *Tester) DisableTracing() {
G.logOut = NewSeparatorWriter(&t.stdout)
trace.Out = &t.stdout
trace.Tracing = false
trace.Out = nil
}
// CheckFileLines loads the lines from the temporary file and checks that
@ -368,10 +443,7 @@ func (t *Tester) CheckFileLines(relativeFileName string, lines ...string) {
// for indentation, while the lines in the code use spaces exclusively,
// in order to make the depth of the indentation clearly visible.
func (t *Tester) CheckFileLinesDetab(relativeFileName string, lines ...string) {
actualLines, err := readLines(t.File(relativeFileName), false)
if !t.c().Check(err, check.IsNil) {
return
}
actualLines := Load(t.File(relativeFileName), MustSucceed)
var detabbed []string
for _, line := range actualLines {

View file

@ -68,7 +68,7 @@ func (ck *distinfoLinesChecker) checkLines(lines []Line) {
ck.algorithms = append(ck.algorithms, alg)
ck.checkGlobalMismatch(line, filename, alg, hash)
ck.checkUncommittedPatch(line, filename, hash)
ck.checkUncommittedPatch(line, filename, alg, hash)
}
ck.onFilenameChange(NewLineEOF(ck.distinfoFilename), "")
}
@ -149,13 +149,15 @@ func (ck *distinfoLinesChecker) checkGlobalMismatch(line Line, fname, alg, hash
}
}
func (ck *distinfoLinesChecker) checkUncommittedPatch(line Line, patchName, sha1Hash string) {
func (ck *distinfoLinesChecker) checkUncommittedPatch(line Line, patchName, alg, hash string) {
if ck.isPatch == yes {
patchFname := ck.patchdir + "/" + patchName
if ck.distinfoIsCommitted && !isCommitted(G.Pkg.File(patchFname)) {
line.Warnf("%s is registered in distinfo but not added to CVS.", patchFname)
}
ck.checkPatchSha1(line, patchFname, sha1Hash)
if alg == "SHA1" {
ck.checkPatchSha1(line, patchFname, hash)
}
ck.patches[patchName] = true
}
}
@ -194,7 +196,7 @@ func computePatchSha1Hex(patchFilename string) (string, error) {
func AutofixDistinfo(oldSha1, newSha1 string) {
distinfoFilename := G.Pkg.File(G.Pkg.DistinfoFile)
if lines, err := readLines(distinfoFilename, false); err == nil {
if lines := Load(distinfoFilename, NotEmpty|LogErrors); lines != nil {
for _, line := range lines {
fix := line.Autofix()
fix.Warnf("Silent-Magic-Diagnostic")

View file

@ -5,28 +5,33 @@ import "gopkg.in/check.v1"
func (s *Suite) Test_ChecklinesDistinfo(c *check.C) {
t := s.Init(c)
t.SetupFileLines("category/package/patches/patch-aa",
"$"+"NetBSD$ line is ignored",
t.Chdir("category/package")
t.SetupFileLines("patches/patch-aa",
RcsID+" line is ignored for computing the SHA1 hash",
"patch contents")
t.SetupFileLines("category/package/patches/patch-ab",
t.SetupFileLines("patches/patch-ab",
"patch contents")
lines := t.SetupFileLines("category/package/distinfo",
lines := t.SetupFileLines("distinfo",
"should be the RCS ID",
"should be empty",
"MD5 (distfile.tar.gz) = 12345678901234567890123456789012",
"SHA1 (distfile.tar.gz) = 1234567890123456789012345678901234567890",
"SHA1 (patch-aa) = 6b98dd609f85a9eb9c4c1e4e7055a6aaa62b7cc7",
"Size (patch-aa) = 104",
"SHA1 (patch-ab) = 6b98dd609f85a9eb9c4c1e4e7055a6aaa62b7cc7",
"Another invalid line",
"SHA1 (patch-nonexistent) = 1234")
G.Pkg = NewPackage("category/package")
G.Pkg = NewPackage(".")
ChecklinesDistinfo(lines)
t.CheckOutputLines(
"ERROR: ~/category/package/distinfo:1: Expected \"$"+"NetBSD$\".",
"NOTE: ~/category/package/distinfo:2: Empty line expected.",
"ERROR: ~/category/package/distinfo:5: Expected SHA1, RMD160, SHA512, Size checksums for \"distfile.tar.gz\", got MD5, SHA1.",
"WARN: ~/category/package/distinfo:7: Patch file \"patch-nonexistent\" does not exist in directory \"patches\".")
"ERROR: distinfo:1: Expected \"$"+"NetBSD$\".",
"NOTE: distinfo:2: Empty line expected.",
"ERROR: distinfo:5: Expected SHA1, RMD160, SHA512, Size checksums for \"distfile.tar.gz\", got MD5, SHA1.",
"ERROR: distinfo:7: Expected SHA1 hash for patch-aa, got SHA1, Size.",
"ERROR: distinfo:8: Invalid line.",
"WARN: distinfo:9: Patch file \"patch-nonexistent\" does not exist in directory \"patches\".")
}
func (s *Suite) Test_ChecklinesDistinfo_global_hash_mismatch(c *check.C) {
@ -49,7 +54,8 @@ func (s *Suite) Test_ChecklinesDistinfo_global_hash_mismatch(c *check.C) {
func (s *Suite) Test_ChecklinesDistinfo_uncommitted_patch(c *check.C) {
t := s.Init(c)
t.SetupFileLines("category/package/patches/patch-aa",
t.Chdir("category/package")
t.SetupFileLines("patches/patch-aa",
RcsID,
"",
"--- oldfile",
@ -57,45 +63,47 @@ func (s *Suite) Test_ChecklinesDistinfo_uncommitted_patch(c *check.C) {
"@@ -1,1 +1,1 @@",
"-old",
"+new")
t.SetupFileLines("category/package/CVS/Entries",
t.SetupFileLines("CVS/Entries",
"/distinfo/...")
lines := t.SetupFileLines("category/package/distinfo",
lines := t.SetupFileLines("distinfo",
RcsID,
"",
"SHA1 (patch-aa) = 5ad1fb9b3c328fff5caa1a23e8f330e707dd50c0")
G.Pkg = NewPackage("category/package")
G.Pkg = NewPackage(".")
ChecklinesDistinfo(lines)
t.CheckOutputLines(
"WARN: ~/category/package/distinfo:3: patches/patch-aa is registered in distinfo but not added to CVS.")
"WARN: distinfo:3: patches/patch-aa is registered in distinfo but not added to CVS.")
}
func (s *Suite) Test_ChecklinesDistinfo_unrecorded_patches(c *check.C) {
t := s.Init(c)
t.SetupFileLines("category/package/patches/CVS/Entries")
t.SetupFileLines("category/package/patches/patch-aa")
t.SetupFileLines("category/package/patches/patch-src-Makefile")
lines := t.SetupFileLines("category/package/distinfo",
t.Chdir("category/package")
t.SetupFileLines("patches/CVS/Entries")
t.SetupFileLines("patches/patch-aa")
t.SetupFileLines("patches/patch-src-Makefile")
lines := t.SetupFileLines("distinfo",
RcsID,
"",
"SHA1 (distfile.tar.gz) = ...",
"RMD160 (distfile.tar.gz) = ...",
"SHA512 (distfile.tar.gz) = ...",
"Size (distfile.tar.gz) = 1024 bytes")
G.Pkg = NewPackage("category/package")
G.Pkg = NewPackage(".")
ChecklinesDistinfo(lines)
t.CheckOutputLines(
"ERROR: ~/category/package/distinfo: patch \"patches/patch-aa\" is not recorded. Run \""+confMake+" makepatchsum\".",
"ERROR: ~/category/package/distinfo: patch \"patches/patch-src-Makefile\" is not recorded. Run \""+confMake+" makepatchsum\".")
"ERROR: distinfo: patch \"patches/patch-aa\" is not recorded. Run \""+confMake+" makepatchsum\".",
"ERROR: distinfo: patch \"patches/patch-src-Makefile\" is not recorded. Run \""+confMake+" makepatchsum\".")
}
func (s *Suite) Test_ChecklinesDistinfo_manual_patches(c *check.C) {
t := s.Init(c)
t.Chdir("category/package")
t.CreateFileLines("patches/manual-libtool.m4")
lines := t.SetupFileLines("distinfo",
RcsID,
@ -115,7 +123,7 @@ func (s *Suite) Test_ChecklinesDistinfo_manual_patches(c *check.C) {
// When a distinfo file is checked in the context of a package,
// the PATCHDIR is known, therefore the checks are active.
t.CheckOutputLines(
"WARN: ~/distinfo:3: Patch file \"patch-aa\" does not exist in directory \"patches\".")
"WARN: distinfo:3: Patch file \"patch-aa\" does not exist in directory \"patches\".")
}
// PHP modules that are not PECL use the distinfo file from lang/php* but

View file

@ -6,29 +6,51 @@ import (
"strings"
)
// LoadNonemptyLines loads the given file.
// If the file doesn't exist or is empty, an error is logged.
//
// See [LoadExistingLines].
func LoadNonemptyLines(fname string, joinBackslashLines bool) []Line {
lines, err := readLines(fname, joinBackslashLines)
type LoadOptions uint8
const (
MustSucceed LoadOptions = 1 << iota // It's a fatal error if loading fails.
NotEmpty // It is an error if the file is empty.
Makefile // Lines ending in a backslash are continued in the next line.
LogErrors //
)
func Load(fileName string, options LoadOptions) []Line {
rawBytes, err := ioutil.ReadFile(fileName)
if err != nil {
NewLineWhole(fname).Errorf("Cannot be read.")
switch {
case options&MustSucceed != 0:
NewLineWhole(fileName).Fatalf("Cannot be read.")
case options&LogErrors != 0:
NewLineWhole(fileName).Errorf("Cannot be read.")
}
return nil
}
if len(lines) == 0 {
NewLineWhole(fname).Errorf("Must not be empty.")
rawText := string(rawBytes)
if rawText == "" && options&NotEmpty != 0 {
switch {
case options&MustSucceed != 0:
NewLineWhole(fileName).Fatalf("Must not be empty.")
case options&LogErrors != 0:
NewLineWhole(fileName).Errorf("Must not be empty.")
}
return nil
}
return lines
if G.opts.Profiling {
G.loaded.Add(path.Clean(fileName), 1)
}
return convertToLogicalLines(fileName, rawText, options&Makefile != 0)
}
func LoadExistingLines(fname string, joinBackslashLines bool) []Line {
lines, err := readLines(fname, joinBackslashLines)
if err != nil {
NewLineWhole(fname).Fatalf("Cannot be read.")
func LoadMk(fileName string, options LoadOptions) *MkLines {
lines := Load(fileName, options|Makefile)
if lines == nil {
return nil
}
return lines
return NewMkLines(lines)
}
func nextLogicalLine(fname string, rawLines []*RawLine, pindex *int) Line {
@ -106,18 +128,6 @@ func splitRawLine(textnl string) (leadingWhitespace, text, trailingWhitespace, c
return
}
func readLines(fname string, joinBackslashLines bool) ([]Line, error) {
rawText, err := ioutil.ReadFile(fname)
if err != nil {
return nil, err
}
if G.opts.Profiling {
G.loaded.Add(path.Clean(fname), 1)
}
return convertToLogicalLines(fname, string(rawText), joinBackslashLines), nil
}
func convertToLogicalLines(fname string, rawText string, joinBackslashLines bool) []Line {
var rawLines []*RawLine
for lineno, rawLine := range strings.SplitAfter(rawText, "\n") {

View file

@ -150,3 +150,23 @@ func (s *Suite) Test_splitRawLine(c *check.C) {
c.Check(trailingWhitespace, equals, " ")
c.Check(continuation, equals, "\\")
}
func (s *Suite) Test_Load(c *check.C) {
t := s.Init(c)
t.CreateFileLines("empty")
func() {
defer t.ExpectFatalError()
Load(t.File("does-not-exist"), MustSucceed)
}()
func() {
defer t.ExpectFatalError()
Load(t.File("empty"), MustSucceed|NotEmpty)
}()
t.CheckOutputLines(
"FATAL: ~/does-not-exist: Cannot be read.",
"FATAL: ~/empty: Must not be empty.")
}

View file

@ -47,16 +47,7 @@ func (s *Suite) Test_checklineLicense(c *check.C) {
func (s *Suite) Test_checkToplevelUnusedLicenses(c *check.C) {
t := s.Init(c)
t.SetupFileLines("mk/bsd.pkg.mk", "# dummy")
t.SetupFileLines("mk/fetch/sites.mk", "# dummy")
t.SetupFileLines("mk/defaults/options.description", "option\tdescription")
t.SetupFileLines("doc/TODO")
t.SetupFileLines("mk/defaults/mk.conf")
t.SetupFileLines("mk/tools/bsd.tools.mk",
".include \"actual-tools.mk\"")
t.SetupFileLines("mk/tools/actual-tools.mk")
t.SetupFileLines("mk/tools/defaults.mk")
t.SetupFileLines("mk/bsd.prefs.mk")
t.SetupPkgsrc()
t.SetupFileLines("mk/misc/category.mk")
t.SetupFileLines("licenses/2-clause-bsd")
t.SetupFileLines("licenses/gnu-gpl-v3")
@ -90,3 +81,36 @@ func (s *Suite) Test_checkToplevelUnusedLicenses(c *check.C) {
"WARN: ~/licenses/gnu-gpl-v3: This license seems to be unused.",
"0 errors and 1 warning found.")
}
func (s *Suite) Test_LicenseChecker_checkLicenseName__LICENSE_FILE(c *check.C) {
t := s.Init(c)
t.SetupPkgsrc()
t.SetupCommandLine("-Wno-space")
t.SetupFileLines("category/package/DESCR",
"Package description")
t.SetupFileMkLines("category/package/Makefile",
MkRcsID,
"",
"CATEGORIES= chinese",
"",
"COMMENT= Useful tools",
"LICENSE= my-license",
"",
"LICENSE_FILE= my-license",
"NO_CHECKSUM= yes",
"",
".include \"../../mk/bsd.pkg.mk\"")
t.SetupFileLines("category/package/PLIST",
PlistRcsID,
"bin/program")
t.SetupFileLines("category/package/my-license",
"An individual license file.")
G.Main("pkglint", t.File("category/package"))
// FIXME: It should be allowed to place a license file directly into
// the package directory.
t.CheckOutputLines(
"WARN: ~/category/package/my-license: Unexpected file found.", "0 errors and 1 warning found.")
}

View file

@ -21,6 +21,7 @@ var (
)
var dummyLine = NewLine("", 0, "", nil)
var dummyMkLine = NewMkLine(dummyLine)
func shallBeLogged(msg string) bool {
if len(G.opts.LogOnly) > 0 {
@ -57,7 +58,7 @@ func logs(level *LogLevel, fname, lineno, format, msg string) bool {
fname = cleanpath(fname)
}
if G.Testing && format != "Magic-Autofix-Format" && !hasSuffix(format, ".") && !hasSuffix(format, ": %s") && !hasSuffix(format, ". %s") {
panic(fmt.Sprintf("Format %q must end in a period.", format))
panic(fmt.Sprintf("Diagnostic format %q must end in a period.", format))
}
if !G.opts.LogVerbose && loggedAlready(fname, lineno, msg) {

View file

@ -35,7 +35,7 @@ type mkLineShell struct {
}
type mkLineComment struct{}
type mkLineEmpty struct{}
type mkLineConditional struct {
type mkLineDirective struct {
indent string
directive string
args string
@ -43,11 +43,11 @@ type mkLineConditional struct {
elseLine MkLine // (filled in later)
}
type mkLineInclude struct {
mustExist bool
sys bool
indent string
includeFile string
conditionVars string // (filled in later)
mustExist bool
sys bool
indent string
includeFile string
conditionalVars string // (filled in later)
}
type mkLineDependency struct {
targets string
@ -115,8 +115,8 @@ func NewMkLine(line Line) *MkLineImpl {
return &MkLineImpl{line, mkLineEmpty{}}
}
if m, indent, directive, args, comment := matchMkCond(text); m {
return &MkLineImpl{line, mkLineConditional{indent, directive, args, comment, nil}}
if m, indent, directive, args, comment := matchMkDirective(text); m {
return &MkLineImpl{line, mkLineDirective{indent, directive, args, comment, nil}}
}
if m, indent, directive, includefile := MatchMkInclude(text); m {
@ -179,9 +179,9 @@ func (mkline *MkLineImpl) IsEmpty() bool {
return ok
}
// IsCond checks whether the line is a conditional (.if/.ifelse/.else/.if) or a loop (.for/.endfor).
func (mkline *MkLineImpl) IsCond() bool {
_, ok := mkline.data.(mkLineConditional)
// IsDirective checks whether the line is a conditional (.if/.elif/.else/.if) or a loop (.for/.endfor).
func (mkline *MkLineImpl) IsDirective() bool {
_, ok := mkline.data.(mkLineDirective)
return ok
}
@ -233,8 +233,8 @@ func (mkline *MkLineImpl) Value() string { return mkline.data.(mkLineAssign
func (mkline *MkLineImpl) VarassignComment() string { return mkline.data.(mkLineAssign).comment }
func (mkline *MkLineImpl) ShellCommand() string { return mkline.data.(mkLineShell).command }
func (mkline *MkLineImpl) Indent() string {
if mkline.IsCond() {
return mkline.data.(mkLineConditional).indent
if mkline.IsDirective() {
return mkline.data.(mkLineDirective).indent
} else {
return mkline.data.(mkLineInclude).indent
}
@ -242,15 +242,17 @@ func (mkline *MkLineImpl) Indent() string {
// Directive returns one of "if", "ifdef", "ifndef", "else", "elif", "endif", "for", "endfor", "undef".
//
// See matchMkCond.
func (mkline *MkLineImpl) Directive() string { return mkline.data.(mkLineConditional).directive }
func (mkline *MkLineImpl) Args() string { return mkline.data.(mkLineConditional).args }
// See matchMkDirective.
func (mkline *MkLineImpl) Directive() string { return mkline.data.(mkLineDirective).directive }
// CondComment is the trailing end-of-line comment, typically at a deeply nested .endif or .endfor.
func (mkline *MkLineImpl) CondComment() string { return mkline.data.(mkLineConditional).comment }
func (mkline *MkLineImpl) HasElseBranch() bool { return mkline.data.(mkLineConditional).elseLine != nil }
// Args returns the arguments from an .if, .ifdef, .ifndef, .elif, .for, .undef.
func (mkline *MkLineImpl) Args() string { return mkline.data.(mkLineDirective).args }
// DirectiveComment is the trailing end-of-line comment, typically at a deeply nested .endif or .endfor.
func (mkline *MkLineImpl) DirectiveComment() string { return mkline.data.(mkLineDirective).comment }
func (mkline *MkLineImpl) HasElseBranch() bool { return mkline.data.(mkLineDirective).elseLine != nil }
func (mkline *MkLineImpl) SetHasElseBranch(elseLine MkLine) {
data := mkline.data.(mkLineConditional)
data := mkline.data.(mkLineDirective)
data.elseLine = elseLine
mkline.data = data
}
@ -261,13 +263,13 @@ func (mkline *MkLineImpl) IncludeFile() string { return mkline.data.(mkLineInclu
func (mkline *MkLineImpl) Targets() string { return mkline.data.(mkLineDependency).targets }
func (mkline *MkLineImpl) Sources() string { return mkline.data.(mkLineDependency).sources }
// ConditionVars is a space-separated list of those variable names
// ConditionalVars is a space-separated list of those variable names
// on which the inclusion depends. It is initialized later,
// step by step, when parsing other lines
func (mkline *MkLineImpl) ConditionVars() string { return mkline.data.(mkLineInclude).conditionVars }
func (mkline *MkLineImpl) SetConditionVars(varnames string) {
func (mkline *MkLineImpl) ConditionalVars() string { return mkline.data.(mkLineInclude).conditionalVars }
func (mkline *MkLineImpl) SetConditionalVars(varnames string) {
include := mkline.data.(mkLineInclude)
include.conditionVars = varnames
include.conditionalVars = varnames
mkline.data = include
}
@ -406,7 +408,7 @@ func (mkline *MkLineImpl) ExplainRelativeDirs() {
"main pkgsrc repository.")
}
func matchMkCond(text string) (m bool, indent, directive, args, comment string) {
func matchMkDirective(text string) (m bool, indent, directive, args, comment string) {
i, n := 0, len(text)
if i < n && text[i] == '.' {
i++
@ -552,13 +554,6 @@ func (mkline *MkLineImpl) VariableNeedsQuoting(varname string, vartype *Vartype,
return nqNo
}
// Assigning lists to lists does not require any quoting, though
// there may be cases like "CONFIGURE_ARGS+= -libs ${LDFLAGS:Q}"
// where quoting is necessary.
if wantList && haveList && !vuc.IsWordPart {
return nqDoesntMatter
}
if wantList != haveList {
if vuc.vartype != nil && vartype != nil {
if vuc.vartype.basicType == BtFetchURL && vartype.basicType == BtHomepage {
@ -602,7 +597,7 @@ func (mkline *MkLineImpl) VariableType(varname string) *Vartype {
if trace.Tracing {
trace.Stepf("Use of tool %+v", tool)
}
if tool.UsableAtLoadtime {
if tool.UsableAtLoadTime {
if G.Pkg == nil || G.Pkg.SeenBsdPrefsMk || G.Pkg.loadTimeTools[tool.Name] {
perms |= aclpUseLoadtime
}
@ -650,8 +645,6 @@ func (mkline *MkLineImpl) VariableType(varname string) *Vartype {
gtype = &Vartype{lkShell, BtLdFlag, allowRuntime, true}
case hasSuffix(varbase, "_MK"):
gtype = &Vartype{lkNone, BtUnknown, allowAll, true}
case hasPrefix(varbase, "PLIST."):
gtype = &Vartype{lkNone, BtYes, allowAll, true}
}
if trace.Tracing {
@ -753,8 +746,8 @@ type vucTime uint8
const (
vucTimeUnknown vucTime = iota
// When Makefiles are loaded, the operators := and != are evaluated,
// as well as the conditionals .if, .elif and .for.
// When Makefiles are loaded, the operators := and != evaluate their
// right-hand side, as well as the directives .if, .elif and .for.
// During loading, not all variables are available yet.
// Variable values are still subject to change, especially lists.
vucTimeParse
@ -815,18 +808,18 @@ func (ind *Indentation) String() string {
s := ""
for _, level := range ind.levels[1:] {
s += fmt.Sprintf(" %d", level.depth)
if len(level.conditionVars) != 0 {
s += " (" + strings.Join(level.conditionVars, " ") + ")"
if len(level.conditionalVars) != 0 {
s += " (" + strings.Join(level.conditionalVars, " ") + ")"
}
}
return "[" + strings.TrimSpace(s) + "]"
}
type indentationLevel struct {
mkline MkLine // The line in which the indentation started; the .if/.for
depth int // Number of space characters; always a multiple of 2
condition string // The corresponding condition from the .if or .elif
conditionVars []string // Variables on which the current path depends
mkline MkLine // The line in which the indentation started; the .if/.for
depth int // Number of space characters; always a multiple of 2
condition string // The corresponding condition from the .if or latest .elif
conditionalVars []string // Variables on which the current path depends
// Files whose existence has been checked in a related path.
// The check counts for both the "if" and the "else" branch,
@ -861,7 +854,7 @@ func (ind *Indentation) Push(mkline MkLine, indent int, condition string) {
}
func (ind *Indentation) AddVar(varname string) {
vars := &ind.top().conditionVars
vars := &ind.top().conditionalVars
for _, existingVarname := range *vars {
if varname == existingVarname {
return
@ -873,7 +866,7 @@ func (ind *Indentation) AddVar(varname string) {
func (ind *Indentation) DependsOn(varname string) bool {
for _, level := range ind.levels {
for _, levelVarname := range level.conditionVars {
for _, levelVarname := range level.conditionalVars {
if varname == levelVarname {
return true
}
@ -882,9 +875,11 @@ func (ind *Indentation) DependsOn(varname string) bool {
return false
}
// IsConditional returns whether the current line depends on evaluating
// any variable in an .if or .elif expression or from a .for loop.
func (ind *Indentation) IsConditional() bool {
for _, level := range ind.levels {
for _, varname := range level.conditionVars {
for _, varname := range level.conditionalVars {
if !hasSuffix(varname, "_MK") {
return true
}
@ -900,7 +895,7 @@ func (ind *Indentation) Varnames() string {
sep := ""
varnames := ""
for _, level := range ind.levels {
for _, levelVarname := range level.conditionVars {
for _, levelVarname := range level.conditionalVars {
if !hasSuffix(levelVarname, "_MK") {
varnames += sep + levelVarname
sep = ", "
@ -910,7 +905,7 @@ func (ind *Indentation) Varnames() string {
return varnames
}
// Condition returns the condition for the innermost .if, .elif or .for.
// Condition returns the condition of the innermost .if, .elif or .for.
func (ind *Indentation) Condition() string {
return ind.top().condition
}
@ -932,7 +927,7 @@ func (ind *Indentation) IsCheckedFile(filename string) bool {
}
func (ind *Indentation) TrackBefore(mkline MkLine) {
if !mkline.IsCond() {
if !mkline.IsDirective() {
return
}
if trace.Tracing {
@ -949,7 +944,7 @@ func (ind *Indentation) TrackBefore(mkline MkLine) {
}
func (ind *Indentation) TrackAfter(mkline MkLine) {
if !mkline.IsCond() {
if !mkline.IsDirective() {
return
}
@ -966,7 +961,7 @@ func (ind *Indentation) TrackAfter(mkline MkLine) {
}
// Note: adding the used variables for arbitrary conditions
// happens in MkLineChecker.CheckCond for performance reasons.
// happens in MkLineChecker.checkDirectiveCond for performance reasons.
if contains(args, "exists") {
cond := NewMkParser(mkline.Line, args, false).MkCond()
@ -984,7 +979,7 @@ func (ind *Indentation) TrackAfter(mkline MkLine) {
ind.top().depth += 2
case "elif":
// Handled here instead of TrackAfter to allow the action to access the previous condition.
// Handled here instead of TrackBefore to allow the action to access the previous condition.
ind.top().condition = args
case "else":

View file

@ -123,12 +123,13 @@ func (s *Suite) Test_NewMkLine(c *check.C) {
"\tshell command # shell comment",
"# whole line comment",
"",
". if !empty(PKGNAME:M*-*) && ${RUBY_RAILS_SUPPORTED:[\\#]} == 1 # cond comment",
". if !empty(PKGNAME:M*-*) && ${RUBY_RAILS_SUPPORTED:[\\#]} == 1 # directive comment",
". include \"../../mk/bsd.prefs.mk\" # include comment",
". include <subdir.mk> # sysinclude comment",
"target1 target2: source1 source2",
"target : source",
"VARNAME+=value")
"VARNAME+=value",
"<<<<<<<<<<<<<<<<<")
ln := mklines.mklines
c.Check(ln[0].IsVarassign(), equals, true)
@ -146,11 +147,11 @@ func (s *Suite) Test_NewMkLine(c *check.C) {
c.Check(ln[3].IsEmpty(), equals, true)
c.Check(ln[4].IsCond(), equals, true)
c.Check(ln[4].IsDirective(), equals, true)
c.Check(ln[4].Indent(), equals, " ")
c.Check(ln[4].Directive(), equals, "if")
c.Check(ln[4].Args(), equals, "!empty(PKGNAME:M*-*) && ${RUBY_RAILS_SUPPORTED:[#]} == 1")
c.Check(ln[4].CondComment(), equals, "cond comment")
c.Check(ln[4].DirectiveComment(), equals, "directive comment")
c.Check(ln[5].IsInclude(), equals, true)
c.Check(ln[5].Indent(), equals, " ")
@ -171,6 +172,16 @@ func (s *Suite) Test_NewMkLine(c *check.C) {
c.Check(ln[9].Varcanon(), equals, "VARNAME")
c.Check(ln[9].Varparam(), equals, "")
// Merge conflicts are of neither type.
c.Check(ln[10].IsVarassign(), equals, false)
c.Check(ln[10].IsDirective(), equals, false)
c.Check(ln[10].IsInclude(), equals, false)
c.Check(ln[10].IsEmpty(), equals, false)
c.Check(ln[10].IsComment(), equals, false)
c.Check(ln[10].IsDependency(), equals, false)
c.Check(ln[10].IsShellCommand(), equals, false)
c.Check(ln[10].IsSysinclude(), equals, false)
t.CheckOutputLines(
"WARN: test.mk:9: Space before colon in dependency line.")
}
@ -207,6 +218,7 @@ func (s *Suite) Test_NewMkLine__autofix_space_after_varname(c *check.C) {
"pkgbase := pkglint")
}
// Guessing the variable type works for both plain and parameterized variable names.
func (s *Suite) Test_MkLine_VariableType_varparam(c *check.C) {
t := s.Init(c)
@ -216,12 +228,12 @@ func (s *Suite) Test_MkLine_VariableType_varparam(c *check.C) {
t1 := mkline.VariableType("FONT_DIRS")
c.Assert(t1, check.NotNil)
c.Check(t1.String(), equals, "ShellList of Pathmask")
c.Check(t1.String(), equals, "ShellList of Pathmask (guessed)")
t2 := mkline.VariableType("FONT_DIRS.ttf")
c.Assert(t2, check.NotNil)
c.Check(t2.String(), equals, "ShellList of Pathmask")
c.Check(t2.String(), equals, "ShellList of Pathmask (guessed)")
}
func (s *Suite) Test_VarUseContext_String(c *check.C) {
@ -277,7 +289,7 @@ func (s *Suite) Test_MkLines_Check__extra(c *check.C) {
t.SetupCommandLine("-Wextra")
t.SetupVartypes()
G.Pkg = NewPackage("category/pkgbase")
G.Pkg = NewPackage(t.File("category/pkgbase"))
G.Mk = t.NewMkLines("options.mk",
MkRcsID,
".for word in ${PKG_FAIL_REASON}",
@ -379,7 +391,7 @@ func (s *Suite) Test_MkLine_variableNeedsQuoting__command_in_command(c *check.C)
t.SetupVartypes()
t.SetupTool(&Tool{Name: "find", Varname: "FIND", Predefined: true})
t.SetupTool(&Tool{Name: "sort", Varname: "SORT", Predefined: true})
G.Pkg = NewPackage("category/pkgbase")
G.Pkg = NewPackage(t.File("category/pkgbase"))
G.Mk = t.NewMkLines("Makefile",
MkRcsID,
"GENERATE_PLIST= cd ${DESTDIR}${PREFIX}; ${FIND} * \\( -type f -or -type l \\) | ${SORT};")
@ -629,7 +641,7 @@ func (s *Suite) Test_MkLine_variableNeedsQuoting__tool_in_CONFIGURE_ENV(c *check
t.SetupCommandLine("-Wall")
t.SetupVartypes()
G.Pkgsrc.Tools.RegisterVarname("tar", "TAR", dummyLine)
G.Pkgsrc.Tools.RegisterVarname("tar", "TAR", dummyMkLine)
mklines := t.NewMkLines("Makefile",
MkRcsID,
"",
@ -650,7 +662,7 @@ func (s *Suite) Test_MkLine_variableNeedsQuoting__backticks(c *check.C) {
t.SetupCommandLine("-Wall")
t.SetupVartypes()
G.Pkgsrc.Tools.RegisterVarname("cat", "CAT", dummyLine)
G.Pkgsrc.Tools.RegisterVarname("cat", "CAT", dummyMkLine)
mklines := t.NewMkLines("Makefile",
MkRcsID,
"",
@ -698,6 +710,69 @@ func (s *Suite) Test_MkLine_variableNeedsQuoting__only_remove_known(c *check.C)
"\t${ECHO} ${FOODIR:Q}")
}
// TODO: COMPILER_RPATH_FLAG and LINKER_RPATH_FLAG have different types
// defined in vardefs.go; examine why.
func (s *Suite) Test_MkLine_variableNeedsQuoting__shellword_part(c *check.C) {
t := s.Init(c)
t.SetupCommandLine("-Wall,no-space")
t.SetupVartypes()
mklines := t.SetupFileMkLines("Makefile",
MkRcsID,
"",
"SUBST_CLASSES+= class",
"SUBST_STAGE.class= pre-configure",
"SUBST_FILES.class= files",
"SUBST_SED.class=-e s:@LINKER_RPATH_FLAG@:${LINKER_RPATH_FLAG}:g")
mklines.Check()
t.CheckOutputEmpty()
}
// Tools, when used in a shell command, must not be quoted.
func (s *Suite) Test_MkLine_variableNeedsQuoting__tool_in_shell_command(c *check.C) {
t := s.Init(c)
t.SetupCommandLine("-Wall,no-space")
t.SetupVartypes()
t.SetupTool(&Tool{Varname: "BASH"})
mklines := t.SetupFileMkLines("Makefile",
MkRcsID,
"",
"CONFIG_SHELL= ${BASH}")
mklines.Check()
t.CheckOutputEmpty()
}
// These examples from real pkgsrc end up in the final nqDontKnow case.
func (s *Suite) Test_MkLine_variableNeedsQuoting__uncovered_cases(c *check.C) {
t := s.Init(c)
t.SetupCommandLine("-Wall,no-space")
t.SetupVartypes()
mklines := t.SetupFileMkLines("Makefile",
MkRcsID,
"",
"GO_SRCPATH= ${HOMEPAGE:S,https://,,}",
"LINKER_RPATH_FLAG:= ${LINKER_RPATH_FLAG:S/-rpath/& /}",
"HOMEPAGE= http://godoc.org/${GO_SRCPATH}",
"PATH:= ${PREFIX}/cross/bin:${PATH}",
"NO_SRC_ON_FTP= ${RESTRICTED}")
mklines.Check()
t.CheckOutputLines(
"WARN: ~/Makefile:4: The variable LINKER_RPATH_FLAG may not be set by any package.",
"WARN: ~/Makefile:4: Please use ${LINKER_RPATH_FLAG:S/-rpath/& /:Q} instead of ${LINKER_RPATH_FLAG:S/-rpath/& /}.",
"WARN: ~/Makefile:4: LINKER_RPATH_FLAG should not be evaluated at load time.")
}
func (s *Suite) Test_MkLine_Pkgmandir(c *check.C) {
t := s.Init(c)
@ -767,11 +842,31 @@ func (s *Suite) Test_MkLine_shell_varuse_in_backt_dquot(c *check.C) {
// See PR 46570, Ctrl+F "3. In lang/perl5".
func (s *Suite) Test_MkLine_VariableType(c *check.C) {
mkline := NewMkLine(dummyLine)
t := s.Init(c)
c.Check(mkline.VariableType("_PERL5_PACKLIST_AWK_STRIP_DESTDIR"), check.IsNil)
c.Check(mkline.VariableType("SOME_DIR").guessed, equals, true)
c.Check(mkline.VariableType("SOMEDIR").guessed, equals, true)
t.SetupVartypes()
checkType := func(varname string, vartype string) {
actualType := dummyMkLine.VariableType(varname)
if vartype == "" {
c.Check(actualType, check.IsNil)
} else {
if c.Check(actualType, check.NotNil) {
c.Check(actualType.String(), equals, vartype)
}
}
}
checkType("_PERL5_PACKLIST_AWK_STRIP_DESTDIR", "")
checkType("SOME_DIR", "Pathname (guessed)")
checkType("SOMEDIR", "Pathname (guessed)")
checkType("SEARCHPATHS", "ShellList of Pathname (guessed)")
checkType("APACHE_USER", "UserGroupName (guessed)")
checkType("APACHE_GROUP", "UserGroupName (guessed)")
checkType("MY_CMD_ENV", "ShellList of ShellWord (guessed)")
checkType("MY_CMD_ARGS", "ShellList of ShellWord (guessed)")
checkType("MY_CMD_CFLAGS", "ShellList of CFlag (guessed)")
checkType("PLIST.abcde", "Yes")
}
// PR 51696, security/py-pbkdf2/Makefile, r1.2
@ -789,16 +884,16 @@ func (s *Suite) Test_MkLine__comment_in_comment(c *check.C) {
"WARN: Makefile:2: The # character starts a comment.")
}
func (s *Suite) Test_MkLine_ConditionVars(c *check.C) {
func (s *Suite) Test_MkLine_ConditionalVars(c *check.C) {
t := s.Init(c)
mkline := t.NewMkLine("Makefile", 45, ".include \"../../category/package/buildlink3.mk\"")
c.Check(mkline.ConditionVars(), equals, "")
c.Check(mkline.ConditionalVars(), equals, "")
mkline.SetConditionVars("OPSYS")
mkline.SetConditionalVars("OPSYS")
c.Check(mkline.ConditionVars(), equals, "OPSYS")
c.Check(mkline.ConditionalVars(), equals, "OPSYS")
}
func (s *Suite) Test_MkLine_ValueSplit(c *check.C) {
@ -830,6 +925,33 @@ func (s *Suite) Test_MkLine_ValueSplit(c *check.C) {
"${DESTDIR:S,/,\\:,:S,:,:,}/sbin")
}
func (s *Suite) Test_MkLine_ResolveVarsInRelativePath(c *check.C) {
t := s.Init(c)
checkResolve := func(before string, after string) {
c.Check(dummyMkLine.ResolveVarsInRelativePath(before, false), equals, after)
}
t.CreateFileLines("lang/lua53/Makefile")
t.CreateFileLines("lang/php72/Makefile")
t.CreateFileLines("emulators/suse100_base/Makefile")
t.CreateFileLines("lang/python36/Makefile")
checkResolve("", "")
checkResolve("${LUA_PKGSRCDIR}", "../../lang/lua53")
checkResolve("${PHPPKGSRCDIR}", "../../lang/php72")
checkResolve("${SUSE_DIR_PREFIX}", "suse100")
checkResolve("${PYPKGSRCDIR}", "../../lang/python36")
checkResolve("${PYPACKAGE}", "python36")
checkResolve("${FILESDIR}", "${FILESDIR}")
checkResolve("${PKGDIR}", "${PKGDIR}")
G.Pkg = NewPackage(t.File("category/package"))
checkResolve("${FILESDIR}", "files")
checkResolve("${PKGDIR}", ".")
}
func (s *Suite) Test_MatchVarassign(c *check.C) {
s.Init(c)
@ -918,3 +1040,31 @@ func (s *Suite) Test_Indentation(c *check.C) {
c.Check(ind.IsConditional(), equals, false)
c.Check(ind.String(), equals, "[]")
}
func (s *Suite) Test_Indentation_RememberUsedVariables(c *check.C) {
t := s.Init(c)
mkline := t.NewMkLine("Makefile", 123, ".if ${PKGREVISION} > 0")
ind := NewIndentation()
cond := NewMkParser(mkline.Line, mkline.Args(), false)
ind.RememberUsedVariables(cond.MkCond())
t.CheckOutputEmpty()
c.Check(ind.Varnames(), equals, "PKGREVISION")
}
func (s *Suite) Test_MkLine_ExtractUsedVariables(c *check.C) {
t := s.Init(c)
mkline := t.NewMkLine("Makefile", 123, "")
nestedVarnames := mkline.ExtractUsedVariables("${VAR.${param}}")
// ExtractUsedVariables is very old code, should be more advanced.
c.Check(nestedVarnames, check.IsNil)
plainVarnames := mkline.ExtractUsedVariables("${VAR}and${VAR2}")
c.Check(plainVarnames, deepEquals, []string{"VAR", "VAR2"})
}

View file

@ -105,32 +105,17 @@ func (ck MkLineChecker) checkInclude() {
}
}
func (ck MkLineChecker) checkCond(forVars map[string]bool, indentation *Indentation) {
func (ck MkLineChecker) checkDirective(forVars map[string]bool, ind *Indentation) {
mkline := ck.MkLine
directive := mkline.Directive()
args := mkline.Args()
comment := mkline.CondComment()
expectedDepth := indentation.Depth(directive)
expectedDepth := ind.Depth(directive)
ck.checkDirectiveIndentation(expectedDepth)
if directive == "endfor" || directive == "endif" {
if directive == "endif" && comment != "" {
if condition := indentation.Condition(); !contains(condition, comment) {
mkline.Warnf("Comment %q does not match condition %q.", comment, condition)
}
}
if directive == "endfor" && comment != "" {
if condition := indentation.Condition(); !contains(condition, comment) {
mkline.Warnf("Comment %q does not match loop %q.", comment, condition)
}
}
if indentation.Len() <= 1 {
mkline.Errorf("Unmatched .%s.", directive)
}
ck.checkDirectiveEnd(ind)
}
needsArgument := false
@ -150,54 +135,81 @@ func (ck MkLineChecker) checkCond(forVars map[string]bool, indentation *Indentat
}
} else if directive == "if" || directive == "elif" {
ck.CheckCond()
ck.checkDirectiveCond()
} else if directive == "ifdef" || directive == "ifndef" {
mkline.Warnf("The \".%s\" directive is deprecated. Please use \".if %sdefined(%s)\" instead.",
directive, ifelseStr(directive == "ifdef", "", "!"), args)
} else if directive == "for" {
if m, vars, values := match2(args, `^(\S+(?:\s*\S+)*?)\s+in\s+(.*)$`); m {
for _, forvar := range splitOnSpace(vars) {
indentation.AddVar(forvar)
if !G.Infrastructure && hasPrefix(forvar, "_") {
mkline.Warnf("Variable names starting with an underscore (%s) are reserved for internal pkgsrc use.", forvar)
}
ck.checkDirectiveFor(forVars, ind)
if matches(forvar, `^[_a-z][_a-z0-9]*$`) {
// Fine.
} else if matches(forvar, `[A-Z]`) {
mkline.Warnf(".for variable names should not contain uppercase letters.")
} else {
mkline.Errorf("Invalid variable name %q.", forvar)
}
} else if directive == "undef" && args != "" {
for _, varname := range splitOnSpace(args) {
if forVars[varname] {
mkline.Notef("Using \".undef\" after a \".for\" loop is unnecessary.")
}
}
}
}
forVars[forvar] = true
func (ck MkLineChecker) checkDirectiveEnd(ind *Indentation) {
mkline := ck.MkLine
directive := mkline.Directive()
comment := mkline.DirectiveComment()
if directive == "endif" && comment != "" {
if condition := ind.Condition(); !contains(condition, comment) {
mkline.Warnf("Comment %q does not match condition %q.", comment, condition)
}
}
if directive == "endfor" && comment != "" {
if condition := ind.Condition(); !contains(condition, comment) {
mkline.Warnf("Comment %q does not match loop %q.", comment, condition)
}
}
if ind.Len() <= 1 {
mkline.Errorf("Unmatched .%s.", directive)
}
}
func (ck MkLineChecker) checkDirectiveFor(forVars map[string]bool, indentation *Indentation) {
mkline := ck.MkLine
args := mkline.Args()
if m, vars, values := match2(args, `^(\S+(?:\s*\S+)*?)\s+in\s+(.*)$`); m {
for _, forvar := range splitOnSpace(vars) {
indentation.AddVar(forvar)
if !G.Infrastructure && hasPrefix(forvar, "_") {
mkline.Warnf("Variable names starting with an underscore (%s) are reserved for internal pkgsrc use.", forvar)
}
// Check if any of the value's types is not guessed.
guessed := true
for _, value := range splitOnSpace(values) {
if m, vname := match1(value, `^\$\{(.*)\}`); m {
vartype := mkline.VariableType(vname)
if vartype != nil && !vartype.guessed {
guessed = false
}
}
if matches(forvar, `^[_a-z][_a-z0-9]*$`) {
// Fine.
} else if matches(forvar, `[A-Z]`) {
mkline.Warnf(".for variable names should not contain uppercase letters.")
} else {
mkline.Errorf("Invalid variable name %q.", forvar)
}
forLoopType := &Vartype{lkSpace, BtUnknown, []ACLEntry{{"*", aclpAllRead}}, guessed}
forLoopContext := &VarUseContext{forLoopType, vucTimeParse, vucQuotFor, false}
for _, forLoopVar := range mkline.ExtractUsedVariables(values) {
ck.CheckVaruse(&MkVarUse{forLoopVar, nil}, forLoopContext)
forVars[forvar] = true
}
// Check if any of the value's types is not guessed.
guessed := true
for _, value := range splitOnSpace(values) {
if m, vname := match1(value, `^\$\{(.*)\}`); m {
vartype := mkline.VariableType(vname)
if vartype != nil && !vartype.guessed {
guessed = false
}
}
}
} else if directive == "undef" && args != "" {
for _, uvar := range splitOnSpace(args) {
if forVars[uvar] {
mkline.Notef("Using \".undef\" after a \".for\" loop is unnecessary.")
}
forLoopType := &Vartype{lkSpace, BtUnknown, []ACLEntry{{"*", aclpAllRead}}, guessed}
forLoopContext := &VarUseContext{forLoopType, vucTimeParse, vucQuotFor, false}
for _, forLoopVar := range mkline.ExtractUsedVariables(values) {
ck.CheckVaruse(&MkVarUse{forLoopVar, nil}, forLoopContext)
}
}
}
@ -330,11 +342,14 @@ func (ck MkLineChecker) CheckVaruse(varuse *MkVarUse, vuc *VarUseContext) {
varname := varuse.varname
vartype := mkline.VariableType(varname)
if G.opts.WarnExtra &&
(vartype == nil || vartype.guessed) &&
!varIsUsed(varname) &&
!(G.Mk != nil && G.Mk.forVars[varname]) &&
!containsVarRef(varname) {
switch {
case !G.opts.WarnExtra:
case vartype != nil && !vartype.guessed:
// Well-known variables are probably defined by the infrastructure.
case varIsUsed(varname):
case G.Mk != nil && G.Mk.forVars[varname]:
case containsVarRef(varname):
default:
mkline.Warnf("%s is used but not defined.", varname)
}
@ -886,7 +901,7 @@ func (ck MkLineChecker) checkVarassignPlistComment(varname, value string) {
contains(value, "@comment") && !matches(value, `="@comment "`) {
ck.MkLine.Warnf("Please don't use @comment in %s.", varname)
Explain(
"If you are defining a PLIST conditional here, use one of the",
"If you are defining a PLIST condition here, use one of the",
"following patterns instead:",
"",
"1. The direct way, without intermediate variable",
@ -941,18 +956,6 @@ func (ck MkLineChecker) CheckVartype(varname string, op MkOperator, value, comme
case vartype.kindOfList == lkNone:
ck.CheckVartypePrimitive(varname, vartype.basicType, op, value, comment, vartype.guessed)
if op == opUseMatch && matches(value, `^[\w-/]+$`) && !vartype.IsConsideredList() {
mkline.Notef("%s should be compared using == instead of the :M or :N modifier without wildcards.", varname)
Explain(
"This variable has a single value, not a list of values. Therefore",
"it feels strange to apply list operators like :M and :N onto it.",
"A more direct approach is to use the == and != operators.",
"",
"An entirely different case is when the pattern contains wildcards",
"like ^, *, $. In such a case, using the :M or :N modifiers is",
"useful and preferred.")
}
case value == "":
break
@ -1030,7 +1033,7 @@ func (ck MkLineChecker) checkText(text string) {
}
}
func (ck MkLineChecker) CheckCond() {
func (ck MkLineChecker) checkDirectiveCond() {
mkline := ck.MkLine
if trace.Tracing {
defer trace.Call1(mkline.Args())()
@ -1039,7 +1042,7 @@ func (ck MkLineChecker) CheckCond() {
p := NewMkParser(mkline.Line, mkline.Args(), false)
cond := p.MkCond()
if !p.EOF() {
mkline.Warnf("Invalid conditional %q.", mkline.Args())
mkline.Warnf("Invalid condition, unrecognized part: %q.", p.Rest())
return
}
@ -1060,9 +1063,25 @@ func (ck MkLineChecker) CheckCond() {
"\t!empty(VARNAME:Mpattern)",
"\t${VARNAME:Mpattern}")
}
for _, modifier := range varuse.modifiers {
if modifier[0] == 'M' || modifier[0] == 'N' {
modifiers := varuse.modifiers
for _, modifier := range modifiers {
if modifier[0] == 'M' || (modifier[0] == 'N' && len(modifiers) == 1) {
ck.CheckVartype(varname, opUseMatch, modifier[1:], "")
value := modifier[1:]
vartype := mkline.VariableType(varname)
if matches(value, `^[\w-/]+$`) && vartype != nil && !vartype.IsConsideredList() {
mkline.Notef("%s should be compared using == instead of the :M or :N modifier without wildcards.", varname)
Explain(
"This variable has a single value, not a list of values. Therefore",
"it feels strange to apply list operators like :M and :N onto it.",
"A more direct approach is to use the == and != operators.",
"",
"An entirely different case is when the pattern contains wildcards",
"like ^, *, $. In such a case, using the :M or :N modifiers is",
"useful and preferred.")
}
}
}
}

View file

@ -42,7 +42,7 @@ func (s *Suite) Test_MkLineChecker_CheckVartype(c *check.C) {
func (s *Suite) Test_MkLineChecker_checkVarassign__URL_with_shell_special_characters(c *check.C) {
t := s.Init(c)
G.Pkg = NewPackage("graphics/gimp-fix-ca")
G.Pkg = NewPackage(t.File("graphics/gimp-fix-ca"))
t.SetupVartypes()
mkline := t.NewMkLine("fname", 10, "MASTER_SITES=http://registry.gimp.org/file/fix-ca.c?action=download&id=9884&file=")
@ -57,37 +57,37 @@ func (s *Suite) Test_MkLineChecker_Check__conditions(c *check.C) {
t.SetupCommandLine("-Wtypes")
t.SetupVartypes()
MkLineChecker{t.NewMkLine("fname", 1, ".if !empty(PKGSRC_COMPILER:Mmycc)")}.CheckCond()
MkLineChecker{t.NewMkLine("fname", 1, ".if !empty(PKGSRC_COMPILER:Mmycc)")}.checkDirectiveCond()
t.CheckOutputLines(
"WARN: fname:1: The pattern \"mycc\" cannot match any of " +
"{ ccache ccc clang distcc f2c gcc hp icc ido " +
"mipspro mipspro-ucode pcc sunpro xlc } for PKGSRC_COMPILER.")
MkLineChecker{t.NewMkLine("fname", 1, ".elif ${A} != ${B}")}.CheckCond()
MkLineChecker{t.NewMkLine("fname", 1, ".elif ${A} != ${B}")}.checkDirectiveCond()
t.CheckOutputEmpty()
MkLineChecker{t.NewMkLine("fname", 1, ".if ${HOMEPAGE} == \"mailto:someone@example.org\"")}.CheckCond()
MkLineChecker{t.NewMkLine("fname", 1, ".if ${HOMEPAGE} == \"mailto:someone@example.org\"")}.checkDirectiveCond()
t.CheckOutputLines(
"WARN: fname:1: \"mailto:someone@example.org\" is not a valid URL.")
MkLineChecker{t.NewMkLine("fname", 1, ".if !empty(PKGSRC_RUN_TEST:M[Y][eE][sS])")}.CheckCond()
MkLineChecker{t.NewMkLine("fname", 1, ".if !empty(PKGSRC_RUN_TEST:M[Y][eE][sS])")}.checkDirectiveCond()
t.CheckOutputLines(
"WARN: fname:1: PKGSRC_RUN_TEST should be matched against \"[yY][eE][sS]\" or \"[nN][oO]\", not \"[Y][eE][sS]\".")
MkLineChecker{t.NewMkLine("fname", 1, ".if !empty(IS_BUILTIN.Xfixes:M[yY][eE][sS])")}.CheckCond()
MkLineChecker{t.NewMkLine("fname", 1, ".if !empty(IS_BUILTIN.Xfixes:M[yY][eE][sS])")}.checkDirectiveCond()
t.CheckOutputEmpty()
MkLineChecker{t.NewMkLine("fname", 1, ".if !empty(${IS_BUILTIN.Xfixes:M[yY][eE][sS]})")}.CheckCond()
MkLineChecker{t.NewMkLine("fname", 1, ".if !empty(${IS_BUILTIN.Xfixes:M[yY][eE][sS]})")}.checkDirectiveCond()
t.CheckOutputLines(
"WARN: fname:1: The empty() function takes a variable name as parameter, not a variable expression.")
MkLineChecker{t.NewMkLine("fname", 1, ".if ${EMUL_PLATFORM} == \"linux-x386\"")}.CheckCond()
MkLineChecker{t.NewMkLine("fname", 1, ".if ${EMUL_PLATFORM} == \"linux-x386\"")}.checkDirectiveCond()
t.CheckOutputLines(
"WARN: fname:1: " +
@ -100,7 +100,7 @@ func (s *Suite) Test_MkLineChecker_Check__conditions(c *check.C) {
"mlrisc ns32k pc532 pmax powerpc powerpc64 rs6000 s390 sh3eb sh3el sparc sparc64 vax x86_64 " +
"} instead.")
MkLineChecker{t.NewMkLine("fname", 1, ".if ${EMUL_PLATFORM:Mlinux-x386}")}.CheckCond()
MkLineChecker{t.NewMkLine("fname", 1, ".if ${EMUL_PLATFORM:Mlinux-x386}")}.checkDirectiveCond()
t.CheckOutputLines(
"WARN: fname:1: "+
@ -113,7 +113,7 @@ func (s *Suite) Test_MkLineChecker_Check__conditions(c *check.C) {
"for the hardware architecture part of EMUL_PLATFORM.",
"NOTE: fname:1: EMUL_PLATFORM should be compared using == instead of the :M or :N modifier without wildcards.")
MkLineChecker{t.NewMkLine("fname", 98, ".if ${MACHINE_PLATFORM:MUnknownOS-*-*} || ${MACHINE_ARCH:Mx86}")}.CheckCond()
MkLineChecker{t.NewMkLine("fname", 98, ".if ${MACHINE_PLATFORM:MUnknownOS-*-*} || ${MACHINE_ARCH:Mx86}")}.checkDirectiveCond()
t.CheckOutputLines(
"WARN: fname:98: "+

View file

@ -140,9 +140,9 @@ func (mklines *MkLines) Check() {
G.Pkg.CheckInclude(mkline, mklines.indentation)
}
case mkline.IsCond():
ck.checkCond(mklines.forVars, mklines.indentation)
substcontext.Conditional(mkline)
case mkline.IsDirective():
ck.checkDirective(mklines.forVars, mklines.indentation)
substcontext.Directive(mkline)
case mkline.IsDependency():
ck.checkDependencyRule(allowedTargets)
@ -173,7 +173,7 @@ func (mklines *MkLines) Check() {
}
// ForEach calls the action for each line, until the action returns false.
// It keeps track of the indentation and all conditionals.
// It keeps track of the indentation and all conditional variables.
func (mklines *MkLines) ForEach(action func(mkline MkLine) bool, atEnd func(mkline MkLine)) {
mklines.indentation = NewIndentation()
@ -256,7 +256,7 @@ func (mklines *MkLines) DetermineDefinedVariables() {
}
}
mklines.toolRegistry.ParseToolLine(mkline.Line)
mklines.toolRegistry.ParseToolLine(mkline)
}
}
@ -396,6 +396,10 @@ func (mklines *MkLines) CheckRedundantVariables() {
func(mkline MkLine) {})
}
func (mklines *MkLines) SaveAutofixChanges() {
SaveAutofixChanges(mklines.lines)
}
// VaralignBlock checks that all variable assignments from a paragraph
// use the same indentation depth for their values.
// It also checks that the indentation uses tabs instead of spaces.
@ -433,7 +437,7 @@ func (va *VaralignBlock) Check(mkline MkLine) {
case mkline.IsComment():
return
case mkline.IsCond():
case mkline.IsDirective():
return
case !mkline.IsVarassign():
@ -613,7 +617,7 @@ func (va *VaralignBlock) realign(mkline MkLine, varnameOp, oldSpace string, cont
"alignment at all.",
"",
"When the block contains something else than variable definitions",
"and conditionals, it is not checked at all.")
"and directives like .if or .for, it is not checked at all.")
}
fix.ReplaceAfter(varnameOp, oldSpace, newSpace)
fix.Apply()

View file

@ -6,7 +6,7 @@ import (
"sort"
)
func (s *Suite) Test_MkLines_Check__autofix_conditional_indentation(c *check.C) {
func (s *Suite) Test_MkLines_Check__autofix_directive_indentation(c *check.C) {
t := s.Init(c)
t.SetupCommandLine("--autofix", "-Wspace")
@ -69,7 +69,7 @@ func (s *Suite) Test_MkLines_quoting_LDFLAGS_for_GNU_configure(c *check.C) {
t.SetupCommandLine("-Wall")
t.SetupVartypes()
G.Pkg = NewPackage("category/pkgbase")
G.Pkg = NewPackage(t.File("category/pkgbase"))
mklines := t.NewMkLines("Makefile",
MkRcsID,
"GNU_CONFIGURE=\tyes",
@ -443,7 +443,7 @@ func (s *Suite) Test_MkLines_Check__endif_comment(c *check.C) {
".elif ${OPSYS} == FreeBSD",
".endif # NetBSD") // Wrong, should be FreeBSD from the .elif.
// See MkLineChecker.checkCond
// See MkLineChecker.checkDirective
mklines.Check()
t.CheckOutputLines(""+

View file

@ -108,6 +108,7 @@ func (p *MkParser) VarUseModifiers(varname, closing string) []string {
var modifiers []string
mayOmitColon := false
loop:
for repl.AdvanceStr(":") || mayOmitColon {
mayOmitColon = false
modifierMark := repl.Mark()
@ -125,7 +126,7 @@ func (p *MkParser) VarUseModifiers(varname, closing string) []string {
} else if len(rest) >= 1 && (rest[0] == closing[0] || rest[0] == ':') {
} else if repl.AdvanceRegexp(`^\\\d+`) {
} else {
break
break loop
}
modifiers = append(modifiers, repl.Since(modifierMark))
continue

View file

@ -110,6 +110,10 @@ func (s *Suite) Test_MkParser_MkTokens(c *check.C) {
check("${VAR:ts\\000012}", varuse("VAR", "ts\\000012")) // The separator character can be a long octal number.
check("${VAR:ts\\124}", varuse("VAR", "ts\\124")) // Or even decimal.
checkRest("${VAR:ts---}", nil, "${VAR:ts---}") // The :ts modifier only takes single-character separators.
check("$<", varuseText("$<", "<")) // Same as ${.IMPSRC}
check("$(GNUSTEP_USER_ROOT)", varuseText("$(GNUSTEP_USER_ROOT)", "GNUSTEP_USER_ROOT"))
t.CheckOutputLines(
"WARN: Test_MkParser_MkTokens.mk:1: Please use curly braces {} instead of round parentheses () for GNUSTEP_USER_ROOT.")
@ -222,6 +226,15 @@ func (s *Suite) Test_MkParser_MkCond(c *check.C) {
checkRest("!empty(PKG_OPTIONS:Msndfile) || defined(PKG_OPTIONS:Msamplerate)",
&mkCond{Not: &mkCond{Empty: varuse("PKG_OPTIONS", "Msndfile")}},
" || defined(PKG_OPTIONS:Msamplerate)")
checkRest("${LEFT} &&",
&mkCond{Not: &mkCond{Empty: varuse("LEFT")}},
" &&")
checkRest("\"unfinished string literal",
nil,
"\"unfinished string literal")
checkRest("${VAR} == \"unfinished string literal",
nil, // Not even the ${VAR} gets through here, although that can be expected.
"${VAR} == \"unfinished string literal")
}
func (s *Suite) Test_MkParser__varuse_parentheses_autofix(c *check.C) {

View file

@ -45,7 +45,7 @@ loop:
}
}
case mkline.IsCond():
case mkline.IsDirective():
// The conditionals are typically for OPSYS and MACHINE_ARCH.
case mkline.IsInclude():
@ -74,7 +74,7 @@ loop:
for ; !exp.EOF(); exp.Advance() {
mkline := exp.CurrentMkLine()
if mkline.IsCond() && (mkline.Directive() == "if" || mkline.Directive() == "elif") {
if mkline.IsDirective() && (mkline.Directive() == "if" || mkline.Directive() == "elif") {
cond := NewMkParser(mkline.Line, mkline.Args(), false).MkCond()
if cond == nil {
continue

View file

@ -87,7 +87,7 @@ func (s *Suite) Test_ChecklinesOptionsMk__unexpected_line(c *check.C) {
"WARN: ~/category/package/options.mk:7: Expected inclusion of \"../../mk/bsd.options.mk\".")
}
func (s *Suite) Test_ChecklinesOptionsMk__malformed_conditional(c *check.C) {
func (s *Suite) Test_ChecklinesOptionsMk__malformed_condition(c *check.C) {
t := s.Init(c)
t.SetupCommandLine("-Wno-space")
@ -115,5 +115,5 @@ func (s *Suite) Test_ChecklinesOptionsMk__malformed_conditional(c *check.C) {
ChecklinesOptionsMk(mklines)
t.CheckOutputLines(
"WARN: ~/category/package/options.mk:9: Invalid conditional \"${OPSYS} == 'Darwin'\".")
"WARN: ~/category/package/options.mk:9: Invalid condition, unrecognized part: \"${OPSYS} == 'Darwin'\".")
}

View file

@ -15,6 +15,7 @@ const rePkgname = `^([\w\-.+]+)-(\d[.0-9A-Z_a-z]*)$`
// Package contains data for the pkgsrc package that is currently checked.
type Package struct {
dir string // The directory of the package, for resolving files
Pkgpath string // e.g. "category/pkgdir"
Pkgdir string // PKGDIR from the package Makefile
Filesdir string // FILESDIR from the package Makefile
@ -30,7 +31,7 @@ type Package struct {
vars Scope
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.
plistSubstCond map[string]bool // varname => true; all variables that are used as conditions (@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.
@ -40,12 +41,19 @@ type Package struct {
IgnoreMissingPatches bool // In distinfo, don't warn about patches that cannot be found.
}
func NewPackage(pkgpath string) *Package {
func NewPackage(dir string) *Package {
pkgpath := G.Pkgsrc.ToRel(dir)
if strings.Count(pkgpath, "/") != 1 {
NewLineWhole(dir).Errorf("Package directory %q must be two subdirectories below the pkgsrc root %q.", dir, G.Pkgsrc.File("."))
}
pkg := &Package{
dir: dir,
Pkgpath: pkgpath,
Pkgdir: ".",
Filesdir: "files",
Patchdir: "patches",
DistinfoFile: "distinfo",
PlistDirs: make(map[string]bool),
PlistFiles: make(map[string]bool),
vars: NewScope(),
@ -65,7 +73,7 @@ func NewPackage(pkgpath string) *Package {
// File returns the (possibly absolute) path to relativeFilename,
// as resolved from the package's directory.
func (pkg *Package) File(relativeFilename string) string {
return G.Pkgsrc.File(pkg.Pkgpath + "/" + relativeFilename)
return cleanpath(pkg.dir + "/" + relativeFilename)
}
func (pkg *Package) varValue(varname string) (string, bool) {
@ -151,21 +159,14 @@ func (pkg *Package) checklinesBuildlink3Inclusion(mklines *MkLines) {
}
}
// Given the package path relative to the pkgsrc top directory,
// checks a complete pkgsrc package.
//
// Example:
// checkdirPackage("category/pkgbase")
func (pkglint *Pkglint) checkdirPackage(pkgpath string) {
// checkdirPackage checks a complete pkgsrc package, including each
// of the files individually, and also when seen in combination.
func (pkglint *Pkglint) checkdirPackage(dir string) {
if trace.Tracing {
defer trace.Call1(pkgpath)()
defer trace.Call1(dir)()
}
if strings.Count(pkgpath, "/") != 1 {
dummyLine.Fatalf("Internal pkglint error: Wrong pkgpath %q.", pkgpath)
}
G.Pkg = NewPackage(pkgpath)
G.Pkg = NewPackage(dir)
defer func() { G.Pkg = nil }()
pkg := G.Pkg
@ -195,8 +196,8 @@ func (pkglint *Pkglint) checkdirPackage(pkgpath string) {
!matches(fname, `patch-`) &&
!contains(fname, pkg.Pkgdir+"/") &&
!contains(fname, pkg.Filesdir+"/") {
if lines, err := readLines(fname, true); err == nil && lines != nil {
NewMkLines(lines).DetermineUsedVariables()
if mklines := LoadMk(fname, MustSucceed); mklines != nil {
mklines.DetermineUsedVariables()
}
}
if hasPrefix(path.Base(fname), "PLIST") {
@ -301,11 +302,10 @@ func (pkg *Package) readMakefile(fname string, mainLines *MkLines, allLines *MkL
defer trace.Call1(fname)()
}
fileLines := LoadNonemptyLines(fname, true)
if fileLines == nil {
fileMklines := LoadMk(fname, NotEmpty|LogErrors)
if fileMklines == nil {
return false
}
fileMklines := NewMkLines(fileLines)
isMainMakefile := len(mainLines.mklines) == 0
@ -916,10 +916,10 @@ func (pkg *Package) checkLocallyModified(fname string) {
}
func (pkg *Package) CheckInclude(mkline MkLine, indentation *Indentation) {
conditionVars := mkline.ConditionVars()
if conditionVars == "" {
conditionVars = indentation.Varnames()
mkline.SetConditionVars(conditionVars)
conditionalVars := mkline.ConditionalVars()
if conditionalVars == "" {
conditionalVars = indentation.Varnames()
mkline.SetConditionalVars(conditionalVars)
}
if path.Dir(abspath(mkline.Filename)) == abspath(pkg.File(".")) {
@ -929,27 +929,23 @@ func (pkg *Package) CheckInclude(mkline MkLine, indentation *Indentation) {
pkg.conditionalIncludes[includefile] = mkline
if other := pkg.unconditionalIncludes[includefile]; other != nil {
mkline.Warnf("%q is included conditionally here (depending on %s) and unconditionally in %s.",
cleanpath(includefile), mkline.ConditionVars(), other.ReferenceFrom(mkline.Line))
cleanpath(includefile), mkline.ConditionalVars(), other.ReferenceFrom(mkline.Line))
}
} else {
pkg.unconditionalIncludes[includefile] = mkline
if other := pkg.conditionalIncludes[includefile]; other != nil {
mkline.Warnf("%q is included unconditionally here and conditionally in %s (depending on %s).",
cleanpath(includefile), other.ReferenceFrom(mkline.Line), other.ConditionVars())
cleanpath(includefile), other.ReferenceFrom(mkline.Line), other.ConditionalVars())
}
}
}
}
func (pkg *Package) loadPlistDirs(plistFilename string) {
lines, err := readLines(plistFilename, false)
if err != nil {
return
}
lines := Load(plistFilename, MustSucceed)
for _, line := range lines {
text := line.Text
pkg.PlistFiles[text] = true // XXX: ignores PLIST conditionals for now
pkg.PlistFiles[text] = true // XXX: ignores PLIST conditions for now
// Keep in sync with PlistChecker.collectFilesAndDirs
if !contains(text, "$") && !contains(text, "@") {
for dir := path.Dir(text); dir != "."; dir = path.Dir(dir) {

View file

@ -5,7 +5,7 @@ import "gopkg.in/check.v1"
func (s *Suite) Test_Package_pkgnameFromDistname(c *check.C) {
t := s.Init(c)
pkg := NewPackage("dummy")
pkg := NewPackage(t.File("category/package"))
pkg.vars.Define("PKGNAME", t.NewMkLine("Makefile", 5, "PKGNAME=dummy"))
c.Check(pkg.pkgnameFromDistname("pkgname-1.0", "whatever"), equals, "pkgname-1.0")
@ -25,7 +25,7 @@ func (s *Suite) Test_Package_CheckVarorder(c *check.C) {
t := s.Init(c)
t.SetupCommandLine("-Worder")
pkg := NewPackage("x11/9term")
pkg := NewPackage(t.File("x11/9term"))
pkg.CheckVarorder(t.NewMkLines("Makefile",
MkRcsID,
@ -56,7 +56,7 @@ func (s *Suite) Test_Package_CheckVarorder__comments_do_not_crash(c *check.C) {
t := s.Init(c)
t.SetupCommandLine("-Worder")
pkg := NewPackage("x11/9term")
pkg := NewPackage(t.File("x11/9term"))
pkg.CheckVarorder(t.NewMkLines("Makefile",
MkRcsID,
@ -79,7 +79,7 @@ func (s *Suite) Test_Package_CheckVarorder__comments_are_ignored(c *check.C) {
t.SetupCommandLine("-Worder")
pkg := NewPackage("x11/9term")
pkg := NewPackage(t.File("x11/9term"))
pkg.CheckVarorder(t.NewMkLines("Makefile",
MkRcsID,
@ -95,12 +95,12 @@ func (s *Suite) Test_Package_CheckVarorder__comments_are_ignored(c *check.C) {
t.CheckOutputEmpty()
}
func (s *Suite) Test_Package_CheckVarorder__conditionals_skip(c *check.C) {
func (s *Suite) Test_Package_CheckVarorder__skip_if_there_are_directives(c *check.C) {
t := s.Init(c)
t.SetupCommandLine("-Worder")
pkg := NewPackage("x11/9term")
pkg := NewPackage(t.File("category/package"))
pkg.CheckVarorder(t.NewMkLines("Makefile",
MkRcsID,
@ -113,8 +113,8 @@ func (s *Suite) Test_Package_CheckVarorder__conditionals_skip(c *check.C) {
".endif",
"LICENSE=\tgnu-gpl-v2"))
// No warning about the missing COMMENT since the conditional
// skips the whole check.
// No warning about the missing COMMENT since the directive
// causes the whole check to be skipped.
t.CheckOutputEmpty()
}
@ -122,7 +122,7 @@ func (s *Suite) Test_Package_CheckVarorder_GitHub(c *check.C) {
t := s.Init(c)
t.SetupCommandLine("-Worder")
pkg := NewPackage("x11/9term")
pkg := NewPackage(t.File("x11/9term"))
pkg.CheckVarorder(t.NewMkLines("Makefile",
MkRcsID,
@ -172,7 +172,7 @@ func (s *Suite) Test_Package_CheckVarorder__MASTER_SITES(c *check.C) {
t := s.Init(c)
t.SetupCommandLine("-Worder")
pkg := NewPackage("category/package")
pkg := NewPackage(t.File("category/package"))
pkg.CheckVarorder(t.NewMkLines("Makefile",
MkRcsID,
@ -196,7 +196,7 @@ func (s *Suite) Test_Package_CheckVarorder__diagnostics(c *check.C) {
t.SetupCommandLine("-Worder")
t.SetupVartypes()
pkg := NewPackage("category/package")
pkg := NewPackage(t.File("category/package"))
pkg.CheckVarorder(t.NewMkLines("Makefile",
MkRcsID,
@ -246,7 +246,7 @@ func (s *Suite) Test_Package_CheckVarorder__diagnostics(c *check.C) {
func (s *Suite) Test_Package_getNbpart(c *check.C) {
t := s.Init(c)
pkg := NewPackage("category/pkgbase")
pkg := NewPackage(t.File("category/pkgbase"))
pkg.vars.Define("PKGREVISION", t.NewMkLine("Makefile", 1, "PKGREVISION=14"))
c.Check(pkg.getNbpart(), equals, "nb14")
@ -260,7 +260,7 @@ func (s *Suite) Test_Package_getNbpart(c *check.C) {
func (s *Suite) Test_Package_determineEffectivePkgVars__precedence(c *check.C) {
t := s.Init(c)
pkg := NewPackage("category/pkgbase")
pkg := NewPackage(t.File("category/pkgbase"))
pkgnameLine := t.NewMkLine("Makefile", 3, "PKGNAME=pkgname-1.0")
distnameLine := t.NewMkLine("Makefile", 4, "DISTNAME=distname-1.0")
pkgrevisionLine := t.NewMkLine("Makefile", 5, "PKGREVISION=13")
@ -279,21 +279,19 @@ func (s *Suite) Test_Package_determineEffectivePkgVars__precedence(c *check.C) {
func (s *Suite) Test_Package_checkPossibleDowngrade(c *check.C) {
t := s.Init(c)
G.Pkg = NewPackage("category/pkgbase")
t.CreateFileLines("doc/CHANGES-2018",
"\tUpdated category/pkgbase to 1.8 [committer 2018-01-05]")
G.Pkgsrc.loadDocChanges()
t.Chdir("category/pkgbase")
G.Pkg = NewPackage(".")
G.Pkg.EffectivePkgname = "package-1.0nb15"
G.Pkg.EffectivePkgnameLine = t.NewMkLine("category/pkgbase/Makefile", 5, "PKGNAME=dummy")
G.Pkgsrc.LastChange = map[string]*Change{
"category/pkgbase": {
Action: "Updated",
Version: "1.8",
Line: t.NewLine("doc/CHANGES", 116, "dummy"),
},
}
G.Pkg.EffectivePkgnameLine = t.NewMkLine("Makefile", 5, "PKGNAME=dummy")
G.Pkg.checkPossibleDowngrade()
t.CheckOutputLines(
"WARN: category/pkgbase/Makefile:5: The package is being downgraded from 1.8 (see ../../doc/CHANGES:116) to 1.0nb15.")
"WARN: Makefile:5: The package is being downgraded from 1.8 (see ../../doc/CHANGES-2018:1) to 1.0nb15.")
G.Pkgsrc.LastChange["category/pkgbase"].Version = "1.0nb22"
@ -305,31 +303,33 @@ func (s *Suite) Test_Package_checkPossibleDowngrade(c *check.C) {
func (s *Suite) Test_checkdirPackage(c *check.C) {
t := s.Init(c)
t.SetupFileLines("category/package/Makefile",
t.Chdir("category/package")
t.SetupFileLines("Makefile",
MkRcsID)
G.checkdirPackage("category/package")
G.checkdirPackage(".")
t.CheckOutputLines(
"WARN: ~/category/package/Makefile: Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset.",
"WARN: ~/category/package/distinfo: File not found. Please run \""+confMake+" makesum\" or define NO_CHECKSUM=yes in the package Makefile.",
"ERROR: ~/category/package/Makefile: Each package must define its LICENSE.",
"WARN: ~/category/package/Makefile: No COMMENT given.")
"WARN: Makefile: Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset.",
"WARN: distinfo: File not found. Please run \""+confMake+" makesum\" or define NO_CHECKSUM=yes in the package Makefile.",
"ERROR: Makefile: Each package must define its LICENSE.",
"WARN: Makefile: No COMMENT given.")
}
func (s *Suite) Test_Pkglint_checkdirPackage__meta_package_without_license(c *check.C) {
t := s.Init(c)
t.CreateFileLines("category/package/Makefile",
t.Chdir("category/package")
t.CreateFileLines("Makefile",
MkRcsID,
"",
"META_PACKAGE=\tyes")
t.SetupVartypes()
G.checkdirPackage("category/package")
G.checkdirPackage(".")
t.CheckOutputLines(
"WARN: ~/category/package/Makefile: No COMMENT given.") // No error about missing LICENSE.
"WARN: Makefile: No COMMENT given.") // No error about missing LICENSE.
}
func (s *Suite) Test_Package__varuse_at_load_time(c *check.C) {
@ -391,10 +391,9 @@ func (s *Suite) Test_Package_loadPackageMakefile(c *check.C) {
"PKGNAME=pkgname-1.67",
"DISTNAME=distfile_1_67",
".include \"../../category/package/Makefile\"")
pkg := NewPackage("category/package")
G.Pkg = pkg
G.Pkg = NewPackage(t.File("category/package"))
pkg.loadPackageMakefile()
G.Pkg.loadPackageMakefile()
// Including a package Makefile directly is an error (in the last line),
// but that is checked later.
@ -409,7 +408,13 @@ func (s *Suite) Test_Package_conditionalAndUnconditionalInclude(c *check.C) {
t := s.Init(c)
t.SetupVartypes()
t.CreateFileLines("category/package/Makefile",
t.CreateFileLines("devel/zlib/buildlink3.mk", "")
t.CreateFileLines("licenses/gnu-gpl-v2", "")
t.CreateFileLines("mk/bsd.pkg.mk", "")
t.CreateFileLines("sysutils/coreutils/buildlink3.mk", "")
t.Chdir("category/package")
t.CreateFileLines("Makefile",
MkRcsID,
"",
"COMMENT\t=Description",
@ -419,37 +424,29 @@ func (s *Suite) Test_Package_conditionalAndUnconditionalInclude(c *check.C) {
".include \"../../sysutils/coreutils/buildlink3.mk\"",
".endif",
".include \"../../mk/bsd.pkg.mk\"")
t.CreateFileLines("category/package/options.mk",
t.CreateFileLines("options.mk",
MkRcsID,
"",
".if !empty(PKG_OPTIONS:Mzlib)",
". include \"../../devel/zlib/buildlink3.mk\"",
".endif",
".include \"../../sysutils/coreutils/buildlink3.mk\"")
t.CreateFileLines("category/package/PLIST",
t.CreateFileLines("PLIST",
PlistRcsID,
"bin/program")
t.CreateFileLines("category/package/distinfo",
t.CreateFileLines("distinfo",
RcsID)
t.CreateFileLines("devel/zlib/buildlink3.mk", "")
t.CreateFileLines("licenses/gnu-gpl-v2", "")
t.CreateFileLines("mk/bsd.pkg.mk", "")
t.CreateFileLines("sysutils/coreutils/buildlink3.mk", "")
pkg := NewPackage("category/package")
G.Pkg = pkg
G.checkdirPackage("category/package")
G.checkdirPackage(".")
t.CheckOutputLines(
"WARN: ~/category/package/Makefile:3: The canonical order of the variables is CATEGORIES, empty line, COMMENT, LICENSE.",
"WARN: ~/category/package/options.mk:3: Unknown option \"zlib\".",
"WARN: ~/category/package/options.mk:4: \"../../devel/zlib/buildlink3.mk\" is "+
"WARN: Makefile:3: The canonical order of the variables is CATEGORIES, empty line, COMMENT, LICENSE.",
"WARN: options.mk:3: Unknown option \"zlib\".",
"WARN: options.mk:4: \"../../devel/zlib/buildlink3.mk\" is "+
"included conditionally here (depending on PKG_OPTIONS) and unconditionally in Makefile:5.",
"WARN: ~/category/package/options.mk:6: \"../../sysutils/coreutils/buildlink3.mk\" is "+
"WARN: options.mk:6: \"../../sysutils/coreutils/buildlink3.mk\" is "+
"included unconditionally here and conditionally in Makefile:7 (depending on OPSYS).",
"WARN: ~/category/package/options.mk:3: Expected definition of PKG_OPTIONS_VAR.")
"WARN: options.mk:3: Expected definition of PKG_OPTIONS_VAR.")
}
// See https://github.com/rillig/pkglint/issues/1
@ -465,7 +462,7 @@ func (s *Suite) Test_Package_includeWithoutExists(c *check.C) {
"",
".include \"../../mk/bsd.pkg.mk\"")
G.checkdirPackage("category/package")
G.checkdirPackage(t.File("category/package"))
t.CheckOutputLines(
"ERROR: ~/category/package/options.mk: Cannot be read.")
@ -477,7 +474,8 @@ func (s *Suite) Test_Package_includeAfterExists(c *check.C) {
t.SetupVartypes()
t.CreateFileLines("mk/bsd.pkg.mk")
t.CreateFileLines("category/package/Makefile",
t.Chdir("category/package")
t.CreateFileLines("Makefile",
MkRcsID,
"",
".if exists(options.mk)",
@ -486,14 +484,14 @@ func (s *Suite) Test_Package_includeAfterExists(c *check.C) {
"",
".include \"../../mk/bsd.pkg.mk\"")
G.checkdirPackage("category/package")
G.checkdirPackage(".")
t.CheckOutputLines(
"WARN: ~/category/package/Makefile: Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset.",
"WARN: ~/category/package/distinfo: File not found. Please run \""+confMake+" makesum\" or define NO_CHECKSUM=yes in the package Makefile.",
"ERROR: ~/category/package/Makefile: Each package must define its LICENSE.",
"WARN: ~/category/package/Makefile: No COMMENT given.",
"ERROR: ~/category/package/Makefile:4: \"options.mk\" does not exist.")
"WARN: Makefile: Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset.",
"WARN: distinfo: File not found. Please run \""+confMake+" makesum\" or define NO_CHECKSUM=yes in the package Makefile.",
"ERROR: Makefile: Each package must define its LICENSE.",
"WARN: Makefile: No COMMENT given.",
"ERROR: Makefile:4: \"options.mk\" does not exist.")
}
// See https://github.com/rillig/pkglint/issues/1
@ -511,7 +509,7 @@ func (s *Suite) Test_Package_includeOtherAfterExists(c *check.C) {
"",
".include \"../../mk/bsd.pkg.mk\"")
G.checkdirPackage("category/package")
G.checkdirPackage(t.File("category/package"))
t.CheckOutputLines(
"ERROR: ~/category/package/another.mk: Cannot be read.")
@ -547,7 +545,7 @@ func (s *Suite) Test_Package__redundant_master_sites(c *check.C) {
// See Package.checkfilePackageMakefile
// See Scope.uncond
G.checkdirPackage("math/R-date")
G.checkdirPackage(t.File("math/R-date"))
t.CheckOutputLines(
"NOTE: ~/math/R-date/Makefile:6: Definition of MASTER_SITES is redundant because of ../R/Makefile.extension:4.")

View file

@ -31,6 +31,14 @@ func (s *Suite) Test_Parser_Dependency(c *check.C) {
}
}
checkNil := func(pattern string) {
parser := NewParser(dummyLine, pattern, false)
dp := parser.Dependency()
if c.Check(dp, check.IsNil) {
c.Check(parser.Rest(), equals, pattern)
}
}
check := func(pattern string, expected DependencyPattern) {
checkRest(pattern, expected, "")
}
@ -50,5 +58,7 @@ func (s *Suite) Test_Parser_Dependency(c *check.C) {
check("ncurses-${NC_VERS}{,nb*}", DependencyPattern{"ncurses", "", "", "", "", "${NC_VERS}{,nb*}"})
check("xulrunner10>=${MOZ_BRANCH}${MOZ_BRANCH_MINOR}", DependencyPattern{"xulrunner10", ">=", "${MOZ_BRANCH}${MOZ_BRANCH_MINOR}", "", "", ""})
checkRest("gnome-control-center>=2.20.1{,nb*}", DependencyPattern{"gnome-control-center", ">=", "2.20.1", "", "", ""}, "{,nb*}")
checkNil(">=2.20.1{,nb*}")
checkNil("pkgbase<=")
// "{ssh{,6}-[0-9]*,openssh-[0-9]*}" is not representable using the current data structure
}

View file

@ -31,6 +31,7 @@ func (s *Suite) Test_ChecklinesPatch__with_comment(c *check.C) {
func (s *Suite) Test_ChecklinesPatch__without_empty_line__autofix(c *check.C) {
t := s.Init(c)
t.Chdir("category/package")
patchLines := t.SetupFileLines("patch-WithoutEmptyLines",
RcsID,
"Text",
@ -48,14 +49,14 @@ func (s *Suite) Test_ChecklinesPatch__without_empty_line__autofix(c *check.C) {
"SHA1 (some patch) = 49abd735b7e706ea9ed6671628bb54e91f7f5ffb")
t.SetupCommandLine("-Wall", "--autofix")
G.Pkg = &Package{DistinfoFile: "distinfo"}
G.Pkg = NewPackage(".")
ChecklinesPatch(patchLines)
t.CheckOutputLines(
"AUTOFIX: ~/patch-WithoutEmptyLines:2: Inserting a line \"\" before this line.",
"AUTOFIX: ~/patch-WithoutEmptyLines:3: Inserting a line \"\" before this line.",
"AUTOFIX: ~/distinfo:3: Replacing \"49abd735b7e706ea9ed6671628bb54e91f7f5ffb\" "+
"AUTOFIX: patch-WithoutEmptyLines:2: Inserting a line \"\" before this line.",
"AUTOFIX: patch-WithoutEmptyLines:3: Inserting a line \"\" before this line.",
"AUTOFIX: distinfo:3: Replacing \"49abd735b7e706ea9ed6671628bb54e91f7f5ffb\" "+
"with \"4938fc8c0b483dc2e33e741b0da883d199e78164\".")
t.CheckFileLines("patch-WithoutEmptyLines",

View file

@ -138,7 +138,10 @@ func (pkglint *Pkglint) Main(argv ...string) (exitcode int) {
dummyLine.Fatalf("Cannot create profiling file: %s", err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
defer func() {
pprof.StopCPUProfile()
f.Close()
}()
regex.Profiling = true
pkglint.loghisto = histogram.New()
@ -146,7 +149,7 @@ func (pkglint *Pkglint) Main(argv ...string) (exitcode int) {
defer func() {
pkglint.logOut.Write("")
pkglint.loghisto.PrintStats("loghisto", pkglint.logOut.out, -1)
regex.PrintStats()
regex.PrintStats(pkglint.logOut.out)
pkglint.loaded.PrintStats("loaded", pkglint.logOut.out, 50)
}()
}
@ -168,7 +171,7 @@ func (pkglint *Pkglint) Main(argv ...string) (exitcode int) {
}
pkglint.Pkgsrc = NewPkgsrc(firstArg + "/" + relTopdir)
pkglint.Pkgsrc.Load()
pkglint.Pkgsrc.LoadInfrastructure()
currentUser, err := user.Current()
if err == nil {
@ -296,13 +299,13 @@ func (pkglint *Pkglint) CheckDirent(fname string) {
isDir := st.Mode().IsDir()
isReg := st.Mode().IsRegular()
currentDir := ifelseStr(isReg, path.Dir(fname), fname)
absCurrentDir := abspath(currentDir)
dir := ifelseStr(isReg, path.Dir(fname), fname)
absCurrentDir := abspath(dir)
pkglint.Wip = !pkglint.opts.Import && matches(absCurrentDir, `/wip/|/wip$`)
pkglint.Infrastructure = matches(absCurrentDir, `/mk/|/mk$`)
pkgsrcdir := findPkgsrcTopdir(currentDir)
pkgsrcdir := findPkgsrcTopdir(dir)
if pkgsrcdir == "" {
NewLineWhole(fname).Errorf("Cannot determine the pkgsrc root directory for %q.", cleanpath(currentDir))
NewLineWhole(fname).Errorf("Cannot determine the pkgsrc root directory for %q.", cleanpath(dir))
return
}
@ -316,11 +319,11 @@ func (pkglint *Pkglint) CheckDirent(fname string) {
switch pkgsrcdir {
case "../..":
pkglint.checkdirPackage(pkglint.Pkgsrc.ToRel(currentDir))
pkglint.checkdirPackage(dir)
case "..":
CheckdirCategory(currentDir)
CheckdirCategory(dir)
case ".":
CheckdirToplevel(currentDir)
CheckdirToplevel(dir)
default:
NewLineWhole(fname).Errorf("Cannot check directories outside a pkgsrc tree.")
}
@ -374,7 +377,7 @@ func CheckfileExtra(fname string) {
defer trace.Call1(fname)()
}
if lines := LoadNonemptyLines(fname, false); lines != nil {
if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
ChecklinesTrailingEmptyLines(lines)
}
}
@ -458,13 +461,13 @@ func CheckfileMk(fname string) {
defer trace.Call1(fname)()
}
lines := LoadNonemptyLines(fname, true)
if lines == nil {
mklines := LoadMk(fname, NotEmpty|LogErrors)
if mklines == nil {
return
}
NewMkLines(lines).Check()
SaveAutofixChanges(lines)
mklines.Check()
mklines.SaveAutofixChanges()
}
func (pkglint *Pkglint) Checkfile(fname string) {
@ -514,21 +517,21 @@ func (pkglint *Pkglint) Checkfile(fname string) {
case basename == "buildlink3.mk":
if pkglint.opts.CheckBuildlink3 {
if lines := LoadNonemptyLines(fname, true); lines != nil {
ChecklinesBuildlink3Mk(NewMkLines(lines))
if mklines := LoadMk(fname, NotEmpty|LogErrors); mklines != nil {
ChecklinesBuildlink3Mk(mklines)
}
}
case hasPrefix(basename, "DESCR"):
if pkglint.opts.CheckDescr {
if lines := LoadNonemptyLines(fname, false); lines != nil {
if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
ChecklinesDescr(lines)
}
}
case basename == "distinfo":
if pkglint.opts.CheckDistinfo {
if lines := LoadNonemptyLines(fname, false); lines != nil {
if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
ChecklinesDistinfo(lines)
}
}
@ -540,21 +543,21 @@ func (pkglint *Pkglint) Checkfile(fname string) {
case hasPrefix(basename, "MESSAGE"):
if pkglint.opts.CheckMessage {
if lines := LoadNonemptyLines(fname, false); lines != nil {
if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
ChecklinesMessage(lines)
}
}
case basename == "options.mk":
if pkglint.opts.CheckOptions {
if lines := LoadNonemptyLines(fname, true); lines != nil {
ChecklinesOptionsMk(NewMkLines(lines))
if mklines := LoadMk(fname, NotEmpty|LogErrors); mklines != nil {
ChecklinesOptionsMk(mklines)
}
}
case matches(basename, `^patch-[-A-Za-z0-9_.~+]*[A-Za-z0-9_]$`):
if pkglint.opts.CheckPatches {
if lines := LoadNonemptyLines(fname, false); lines != nil {
if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
ChecklinesPatch(lines)
}
}
@ -574,7 +577,7 @@ func (pkglint *Pkglint) Checkfile(fname string) {
case hasPrefix(basename, "PLIST"):
if pkglint.opts.CheckPlist {
if lines := LoadNonemptyLines(fname, false); lines != nil {
if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
ChecklinesPlist(lines)
}
}

View file

@ -291,7 +291,7 @@ func (s *Suite) Test_resolveVariableRefs__circular_reference(c *check.C) {
t := s.Init(c)
mkline := t.NewMkLine("fname", 1, "GCC_VERSION=${GCC_VERSION}")
G.Pkg = NewPackage(".")
G.Pkg = NewPackage(t.File("category/pkgbase"))
G.Pkg.vars.Define("GCC_VERSION", mkline)
resolved := resolveVariableRefs("gcc-${GCC_VERSION}")
@ -305,7 +305,7 @@ func (s *Suite) Test_resolveVariableRefs__multilevel(c *check.C) {
mkline1 := t.NewMkLine("fname", 10, "_=${SECOND}")
mkline2 := t.NewMkLine("fname", 11, "_=${THIRD}")
mkline3 := t.NewMkLine("fname", 12, "_=got it")
G.Pkg = NewPackage(".")
G.Pkg = NewPackage(t.File("category/pkgbase"))
defineVar(mkline1, "FIRST")
defineVar(mkline2, "SECOND")
defineVar(mkline3, "THIRD")
@ -322,7 +322,7 @@ func (s *Suite) Test_resolveVariableRefs__special_chars(c *check.C) {
t := s.Init(c)
mkline := t.NewMkLine("fname", 10, "_=x11")
G.Pkg = NewPackage("category/pkg")
G.Pkg = NewPackage(t.File("category/pkg"))
G.Pkg.vars.Define("GST_PLUGINS0.10_TYPE", mkline)
resolved := resolveVariableRefs("gst-plugins0.10-${GST_PLUGINS0.10_TYPE}/distinfo")
@ -484,3 +484,52 @@ func (s *Suite) Test_Pkglint_Checkfile__alternatives(c *check.C) {
"Looks fine.",
"(Run \"pkglint -e\" to show explanations.)")
}
func (s *Suite) Test_Pkglint__profiling(c *check.C) {
t := s.Init(c)
t.SetupPkgsrc()
G.Main("pkglint", "--profiling", t.File("."))
// Pkglint always writes the profiling data into the current directory.
// Luckily, this directory is usually writable.
c.Check(fileExists("pkglint.pprof"), equals, true)
err := os.Remove("pkglint.pprof")
c.Check(err, check.IsNil)
// Everything but the first few lines of output is not easily testable
// or not interesting enough, since that info includes the exact timing
// that the top time-consuming regular expressions took.
firstOutput := strings.Split(t.Output(), "\n")[0]
c.Check(firstOutput, equals, "ERROR: ~/Makefile: Cannot be read.")
}
func (s *Suite) Test_Pkglint_Checkfile__in_current_working_directory(c *check.C) {
t := s.Init(c)
t.SetupPkgsrc()
t.SetupVartypes()
t.CreateFileLines("licenses/mit")
t.Chdir("category/package")
t.CreateFileLines("log")
t.CreateFileLines("Makefile",
MkRcsID,
"",
"NO_CHECKSUM= yes",
"COMMENT= Useful utilities",
"LICENSE= mit",
"",
".include \"../../mk/bsd.pkg.mk\"")
t.CreateFileLines("PLIST",
PlistRcsID,
"bin/program")
t.CreateFileLines("DESCR",
"Package description")
G.Main("pkglint")
t.CheckOutputLines(
"WARN: log: Unexpected file found.",
"0 errors and 1 warning found.")
}

View file

@ -79,14 +79,14 @@ func NewPkgsrc(dir string) *Pkgsrc {
return src
}
// Load reads the pkgsrc infrastructure files to
// LoadInfrastructure reads the pkgsrc infrastructure files to
// extract information like the tools, packages to update,
// user-defined variables.
//
// This work is not done in the constructor to keep the tests
// simple, since setting up a realistic pkgsrc environment requires
// a lot of files.
func (src *Pkgsrc) Load() {
func (src *Pkgsrc) LoadInfrastructure() {
src.InitVartypes()
src.loadMasterSites()
src.loadPkgOptions()
@ -148,9 +148,10 @@ func (src *Pkgsrc) loadTools() {
toolFiles := []string{"defaults.mk"}
{
toc := G.Pkgsrc.File("mk/tools/bsd.tools.mk")
lines := LoadExistingLines(toc, true)
for _, line := range lines {
if m, _, _, includefile := MatchMkInclude(line.Text); m {
mklines := LoadMk(toc, MustSucceed|NotEmpty)
for _, mkline := range mklines.mklines {
if mkline.IsInclude() {
includefile := mkline.IncludeFile()
if !contains(includefile, "/") {
toolFiles = append(toolFiles, includefile)
}
@ -162,41 +163,38 @@ func (src *Pkgsrc) loadTools() {
}
reg := src.Tools
reg.RegisterTool(&Tool{"echo", "ECHO", true, true, true}, dummyLine)
reg.RegisterTool(&Tool{"echo -n", "ECHO_N", true, true, true}, dummyLine)
reg.RegisterTool(&Tool{"false", "FALSE", true /*why?*/, true, false}, dummyLine)
reg.RegisterTool(&Tool{"test", "TEST", true, true, true}, dummyLine)
reg.RegisterTool(&Tool{"true", "TRUE", true /*why?*/, true, true}, dummyLine)
reg.RegisterTool(&Tool{"echo", "ECHO", true, true, true}, dummyMkLine)
reg.RegisterTool(&Tool{"echo -n", "ECHO_N", true, true, true}, dummyMkLine)
reg.RegisterTool(&Tool{"false", "FALSE", true /*why?*/, true, false}, dummyMkLine)
reg.RegisterTool(&Tool{"test", "TEST", true, true, true}, dummyMkLine)
reg.RegisterTool(&Tool{"true", "TRUE", true /*why?*/, true, true}, dummyMkLine)
for _, basename := range toolFiles {
lines := G.Pkgsrc.LoadExistingLines("mk/tools/"+basename, true)
for _, line := range lines {
reg.ParseToolLine(line)
mklines := G.Pkgsrc.LoadMk("mk/tools/"+basename, MustSucceed|NotEmpty)
for _, mkline := range mklines.mklines {
reg.ParseToolLine(mkline)
}
}
for _, relativeName := range [...]string{"mk/bsd.prefs.mk", "mk/bsd.pkg.mk"} {
condDepth := 0
dirDepth := 0
lines := G.Pkgsrc.LoadExistingLines(relativeName, true)
for _, line := range lines {
text := line.Text
if hasPrefix(text, "#") {
continue
}
if m, _, varname, _, _, _, value, _, _ := MatchVarassign(text); m {
mklines := G.Pkgsrc.LoadMk(relativeName, MustSucceed|NotEmpty)
for _, mkline := range mklines.mklines {
if mkline.IsVarassign() {
varname := mkline.Varname()
value := mkline.Value()
if varname == "USE_TOOLS" {
if trace.Tracing {
trace.Stepf("[condDepth=%d] %s", condDepth, value)
trace.Stepf("[dirDepth=%d] %s", dirDepth, value)
}
if condDepth == 0 || condDepth == 1 && relativeName == "mk/bsd.prefs.mk" {
if dirDepth == 0 || dirDepth == 1 && relativeName == "mk/bsd.prefs.mk" {
for _, toolname := range splitOnSpace(value) {
if !containsVarRef(toolname) {
tool := reg.Register(toolname, line)
tool := reg.Register(toolname, mkline)
tool.Predefined = true
if relativeName == "mk/bsd.prefs.mk" {
tool.UsableAtLoadtime = true
tool.UsableAtLoadTime = true
}
}
}
@ -208,12 +206,12 @@ func (src *Pkgsrc) loadTools() {
}
}
} else if m, _, cond, _, _ := matchMkCond(text); m {
switch cond {
} else if mkline.IsDirective() {
switch mkline.Directive() {
case "if", "ifdef", "ifndef", "for":
condDepth++
dirDepth++
case "endif", "endfor":
condDepth--
dirDepth--
}
}
}
@ -224,11 +222,6 @@ func (src *Pkgsrc) loadTools() {
}
}
func (src *Pkgsrc) loadSuggestedUpdatesFile(fname string) []SuggestedUpdate {
lines := LoadExistingLines(fname, false)
return src.parseSuggestedUpdates(lines)
}
func (src *Pkgsrc) parseSuggestedUpdates(lines []Line) []SuggestedUpdate {
var updates []SuggestedUpdate
state := 0
@ -261,14 +254,11 @@ func (src *Pkgsrc) parseSuggestedUpdates(lines []Line) []SuggestedUpdate {
}
func (src *Pkgsrc) loadSuggestedUpdates() {
src.suggestedUpdates = src.loadSuggestedUpdatesFile(G.Pkgsrc.File("doc/TODO"))
if wipFilename := G.Pkgsrc.File("wip/TODO"); fileExists(wipFilename) {
src.suggestedWipUpdates = src.loadSuggestedUpdatesFile(wipFilename)
}
src.suggestedUpdates = src.parseSuggestedUpdates(Load(G.Pkgsrc.File("doc/TODO"), MustSucceed))
src.suggestedWipUpdates = src.parseSuggestedUpdates(Load(G.Pkgsrc.File("wip/TODO"), NotEmpty))
}
func (src *Pkgsrc) loadDocChangesFromFile(fname string) []*Change {
lines := LoadExistingLines(fname, false)
parseChange := func(line Line) *Change {
text := line.Text
@ -306,6 +296,7 @@ func (src *Pkgsrc) loadDocChangesFromFile(fname string) []*Change {
year = yyyy
}
lines := Load(fname, MustSucceed|NotEmpty)
var changes []*Change
for _, line := range lines {
if change := parseChange(line); change != nil {
@ -371,8 +362,7 @@ func (src *Pkgsrc) loadDocChanges() {
}
func (src *Pkgsrc) loadUserDefinedVars() {
lines := G.Pkgsrc.LoadExistingLines("mk/defaults/mk.conf", true)
mklines := NewMkLines(lines)
mklines := G.Pkgsrc.LoadMk("mk/defaults/mk.conf", MustSucceed|NotEmpty)
for _, mkline := range mklines.mklines {
if mkline.IsVarassign() {
@ -542,9 +532,14 @@ func (src *Pkgsrc) initDeprecatedVars() {
}
}
// LoadExistingLines loads the file relative to the pkgsrc top directory.
func (src *Pkgsrc) LoadExistingLines(fileName string, joinBackslashLines bool) []Line {
return LoadExistingLines(src.File(fileName), joinBackslashLines)
// Load loads the file relative to the pkgsrc top directory.
func (src *Pkgsrc) Load(fileName string, options LoadOptions) []Line {
return Load(src.File(fileName), options)
}
// LoadMk loads the Makefile relative to the pkgsrc top directory.
func (src *Pkgsrc) LoadMk(fileName string, options LoadOptions) *MkLines {
return LoadMk(src.File(fileName), options)
}
// File resolves a file name relative to the pkgsrc top directory.
@ -572,14 +567,15 @@ func (src *Pkgsrc) IsBuildDef(varname string) bool {
}
func (src *Pkgsrc) loadMasterSites() {
lines := src.LoadExistingLines("mk/fetch/sites.mk", true)
mklines := src.LoadMk("mk/fetch/sites.mk", MustSucceed|NotEmpty)
nameToUrl := src.MasterSiteVarToURL
urlToName := src.MasterSiteURLToVar
for _, line := range lines {
if m, commented, varname, _, _, _, urls, _, _ := MatchVarassign(line.Text); m {
if !commented && hasPrefix(varname, "MASTER_SITE_") && varname != "MASTER_SITE_BACKUP" {
for _, url := range splitOnSpace(urls) {
for _, mkline := range mklines.mklines {
if mkline.IsVarassign() {
varname := mkline.Varname()
if hasPrefix(varname, "MASTER_SITE_") && varname != "MASTER_SITE_BACKUP" {
for _, url := range splitOnSpace(mkline.Value()) {
if matches(url, `^(?:http://|https://|ftp://)`) {
if nameToUrl[varname] == "" {
nameToUrl[varname] = url
@ -600,7 +596,7 @@ func (src *Pkgsrc) loadMasterSites() {
}
func (src *Pkgsrc) loadPkgOptions() {
lines := src.LoadExistingLines("mk/defaults/options.description", false)
lines := src.Load("mk/defaults/options.description", MustSucceed)
for _, line := range lines {
if m, optname, optdescr := match2(line.Text, `^([-0-9a-z_+]+)(?:\s+(.*))?$`); m {

View file

@ -1,9 +1,6 @@
package main
import (
"gopkg.in/check.v1"
"netbsd.org/pkglint/trace"
)
import "gopkg.in/check.v1"
// Ensures that pkglint can handle MASTER_SITES definitions with and
// without line continuations.
@ -99,25 +96,26 @@ func (s *Suite) Test_Pkgsrc_loadTools(c *check.C) {
G.Pkgsrc.loadTools()
trace.Tracing = true
t.EnableTracingToLog()
G.Pkgsrc.Tools.Trace()
t.DisableTracing()
t.CheckOutputLines(
"TRACE: + (*ToolRegistry).Trace()",
"TRACE: 1 tool &{Name:bzcat Varname: MustUseVarForm:false Predefined:false UsableAtLoadtime:false}",
"TRACE: 1 tool &{Name:bzip2 Varname: MustUseVarForm:false Predefined:false UsableAtLoadtime:false}",
"TRACE: 1 tool &{Name:chown Varname:CHOWN MustUseVarForm:false Predefined:false UsableAtLoadtime:false}",
"TRACE: 1 tool &{Name:echo Varname:ECHO MustUseVarForm:true Predefined:true UsableAtLoadtime:true}",
"TRACE: 1 tool &{Name:echo -n Varname:ECHO_N MustUseVarForm:true Predefined:true UsableAtLoadtime:true}",
"TRACE: 1 tool &{Name:false Varname:FALSE MustUseVarForm:true Predefined:true UsableAtLoadtime:false}",
"TRACE: 1 tool &{Name:gawk Varname:AWK MustUseVarForm:false Predefined:false UsableAtLoadtime:false}",
"TRACE: 1 tool &{Name:m4 Varname: MustUseVarForm:false Predefined:true UsableAtLoadtime:true}",
"TRACE: 1 tool &{Name:msgfmt Varname: MustUseVarForm:false Predefined:false UsableAtLoadtime:false}",
"TRACE: 1 tool &{Name:mv Varname:MV MustUseVarForm:false Predefined:true UsableAtLoadtime:false}",
"TRACE: 1 tool &{Name:pwd Varname:PWD MustUseVarForm:false Predefined:true UsableAtLoadtime:true}",
"TRACE: 1 tool &{Name:strip Varname: MustUseVarForm:false Predefined:false UsableAtLoadtime:false}",
"TRACE: 1 tool &{Name:test Varname:TEST MustUseVarForm:true Predefined:true UsableAtLoadtime:true}",
"TRACE: 1 tool &{Name:true Varname:TRUE MustUseVarForm:true Predefined:true UsableAtLoadtime:true}",
"TRACE: 1 tool &{Name:bzcat Varname: MustUseVarForm:false Predefined:false UsableAtLoadTime:false}",
"TRACE: 1 tool &{Name:bzip2 Varname: MustUseVarForm:false Predefined:false UsableAtLoadTime:false}",
"TRACE: 1 tool &{Name:chown Varname:CHOWN MustUseVarForm:false Predefined:false UsableAtLoadTime:false}",
"TRACE: 1 tool &{Name:echo Varname:ECHO MustUseVarForm:true Predefined:true UsableAtLoadTime:true}",
"TRACE: 1 tool &{Name:echo -n Varname:ECHO_N MustUseVarForm:true Predefined:true UsableAtLoadTime:true}",
"TRACE: 1 tool &{Name:false Varname:FALSE MustUseVarForm:true Predefined:true UsableAtLoadTime:false}",
"TRACE: 1 tool &{Name:gawk Varname:AWK MustUseVarForm:false Predefined:false UsableAtLoadTime:false}",
"TRACE: 1 tool &{Name:m4 Varname: MustUseVarForm:false Predefined:true UsableAtLoadTime:true}",
"TRACE: 1 tool &{Name:msgfmt Varname: MustUseVarForm:false Predefined:false UsableAtLoadTime:false}",
"TRACE: 1 tool &{Name:mv Varname:MV MustUseVarForm:false Predefined:true UsableAtLoadTime:false}",
"TRACE: 1 tool &{Name:pwd Varname:PWD MustUseVarForm:false Predefined:true UsableAtLoadTime:true}",
"TRACE: 1 tool &{Name:strip Varname: MustUseVarForm:false Predefined:false UsableAtLoadTime:false}",
"TRACE: 1 tool &{Name:test Varname:TEST MustUseVarForm:true Predefined:true UsableAtLoadTime:true}",
"TRACE: 1 tool &{Name:true Varname:TRUE MustUseVarForm:true Predefined:true UsableAtLoadTime:true}",
"TRACE: - (*ToolRegistry).Trace()")
}

View file

@ -45,9 +45,9 @@ type PlistChecker struct {
}
type PlistLine struct {
line Line
conditional string // e.g. PLIST.docs
text string // Like line.text, without the conditional
line Line
condition string // e.g. PLIST.docs
text string // Like line.text, without the condition
}
func (ck *PlistChecker) Check(plainLines []Line) {
@ -55,8 +55,8 @@ func (ck *PlistChecker) Check(plainLines []Line) {
ck.collectFilesAndDirs(plines)
if fname := plines[0].line.Filename; path.Base(fname) == "PLIST.common_end" {
commonLines, err := readLines(strings.TrimSuffix(fname, "_end"), false)
if err == nil {
commonLines := Load(strings.TrimSuffix(fname, "_end"), NotEmpty)
if commonLines != nil {
ck.collectFilesAndDirs(ck.NewLines(commonLines))
}
}
@ -81,13 +81,13 @@ func (ck *PlistChecker) Check(plainLines []Line) {
func (ck *PlistChecker) NewLines(lines []Line) []*PlistLine {
plines := make([]*PlistLine, len(lines))
for i, line := range lines {
conditional, text := "", line.Text
condition, text := "", line.Text
if hasPrefix(text, "${PLIST.") {
if m, cond, rest := match2(text, `^(?:\$\{(PLIST\.[\w-.]+)\})+(.*)`); m {
conditional, text = cond, rest
condition, text = cond, rest
}
}
plines[i] = &PlistLine{line, conditional, text}
plines[i] = &PlistLine{line, condition, text}
}
return plines
}
@ -101,7 +101,7 @@ func (ck *PlistChecker) collectFilesAndDirs(plines []*PlistLine) {
first == '$',
'A' <= first && first <= 'Z',
'0' <= first && first <= '9':
if prev := ck.allFiles[text]; prev == nil || pline.conditional < prev.conditional {
if prev := ck.allFiles[text]; prev == nil || pline.condition < prev.condition {
ck.allFiles[text] = pline
}
for dir := path.Dir(text); dir != "."; dir = path.Dir(dir) {
@ -224,7 +224,7 @@ func (ck *PlistChecker) checkDuplicate(pline *PlistLine) {
}
prev := ck.allFiles[text]
if prev == pline || prev.conditional != "" {
if prev == pline || prev.condition != "" {
return
}
@ -518,7 +518,7 @@ func (s *plistLineSorter) Sort() {
mi := s.middle[i]
mj := s.middle[j]
less := mi.text < mj.text || (mi.text == mj.text &&
mi.conditional < mj.conditional)
mi.condition < mj.condition)
if (i < j) != less {
s.changed = true
}

View file

@ -6,7 +6,7 @@ func (s *Suite) Test_ChecklinesPlist(c *check.C) {
t := s.Init(c)
t.SetupCommandLine("-Wall")
G.Pkg = NewPackage("category/pkgbase")
G.Pkg = NewPackage(t.File("category/pkgbase"))
lines := t.NewLines("PLIST",
"bin/i386/6c",
"bin/program",
@ -76,10 +76,10 @@ func (s *Suite) Test_ChecklinesPlist__commonEnd(c *check.C) {
t.CheckOutputEmpty()
}
func (s *Suite) Test_ChecklinesPlist__conditional(c *check.C) {
func (s *Suite) Test_ChecklinesPlist__condition(c *check.C) {
t := s.Init(c)
G.Pkg = NewPackage("category/pkgbase")
G.Pkg = NewPackage(t.File("category/pkgbase"))
G.Pkg.plistSubstCond["PLIST.bincmds"] = true
lines := t.NewLines("PLIST",
PlistRcsID,
@ -101,7 +101,7 @@ func (s *Suite) Test_ChecklinesPlist__sorting(c *check.C) {
"sbin/i386/6c",
"sbin/program",
"bin/otherprogram",
"${PLIST.conditional}bin/cat")
"${PLIST.condition}bin/cat")
ChecklinesPlist(lines)
@ -129,7 +129,7 @@ func (s *Suite) Test_PlistLineSorter_Sort(c *check.C) {
"man/man1/program.1",
"${PLIST.two}bin/program2",
"lib/before.la",
"${PLIST.linux}${PLIST.x86_64}lib/lib-linux-x86_64.so", // Double conditional, see graphics/graphviz
"${PLIST.linux}${PLIST.x86_64}lib/lib-linux-x86_64.so", // Double condition, see graphics/graphviz
"lib/after.la",
"@exec echo \"after lib/after.la\"")
ck := &PlistChecker{nil, nil, "", Once{}}
@ -155,7 +155,7 @@ func (s *Suite) Test_PlistLineSorter_Sort(c *check.C) {
"C",
"CCC",
"b",
"${PLIST.one}bin/program", // Conditionals are ignored while sorting
"${PLIST.one}bin/program", // Conditional lines are ignored during sorting
"${PLIST.two}bin/program2",
"ddd",
"lib/after.la",
@ -169,7 +169,7 @@ func (s *Suite) Test_PlistLineSorter_Sort(c *check.C) {
func (s *Suite) Test_PlistChecker_checkpathMan_gz(c *check.C) {
t := s.Init(c)
G.Pkg = NewPackage("category/pkgbase")
G.Pkg = NewPackage(t.File("category/pkgbase"))
lines := t.NewLines("PLIST",
PlistRcsID,
"man/man3/strerror.3.gz")
@ -211,7 +211,7 @@ func (s *Suite) Test_PlistChecker__autofix(c *check.C) {
t.SetupCommandLine("-Wall")
fname := t.CreateFileLines("PLIST",
lines := t.SetupFileLines("PLIST",
PlistRcsID,
"lib/libvirt/connection-driver/libvirt_driver_storage.la",
"${PLIST.hal}lib/libvirt/connection-driver/libvirt_driver_nodedev.la",
@ -232,7 +232,7 @@ func (s *Suite) Test_PlistChecker__autofix(c *check.C) {
"@pkgdir etc/libvirt/qemu/networks/autostart",
"@pkgdir etc/logrotate.d",
"@pkgdir etc/sasl2")
lines := LoadExistingLines(fname, false)
ChecklinesPlist(lines)
t.CheckOutputLines(
@ -245,12 +245,9 @@ func (s *Suite) Test_PlistChecker__autofix(c *check.C) {
t.SetupCommandLine("-Wall", "--autofix")
ChecklinesPlist(lines)
fixedLines := LoadExistingLines(fname, false)
t.CheckOutputLines(
"AUTOFIX: ~/PLIST:6: Replacing \"${PKGMANDIR}/\" with \"man/\".",
"AUTOFIX: ~/PLIST:2: Sorting the whole file.")
c.Check(len(lines), equals, len(fixedLines))
t.CheckFileLines("PLIST",
PlistRcsID,
"${PLIST.xen}lib/libvirt/connection-driver/libvirt_driver_libxl.la",
@ -274,9 +271,9 @@ func (s *Suite) Test_PlistChecker__autofix(c *check.C) {
"@pkgdir etc/sasl2")
}
// When the same entry appears both with and without a conditional,
// the one with the conditional can be removed.
// When the same entry appears with several different conditionals,
// When the same entry appears both with and without a condition,
// the one with the condition can be removed.
// When the same entry appears with several different conditions,
// all of them must stay.
func (s *Suite) Test_PlistChecker__remove_same_entries(c *check.C) {
t := s.Init(c)

View file

@ -6,8 +6,8 @@ package regex
import (
"fmt"
"io"
"netbsd.org/pkglint/histogram"
"os"
"regexp"
"time"
)
@ -19,16 +19,13 @@ var (
)
var (
res map[Pattern]*regexp.Regexp
rematch *histogram.Histogram
renomatch *histogram.Histogram
retime *histogram.Histogram
res = make(map[Pattern]*regexp.Regexp)
rematch = histogram.New()
renomatch = histogram.New()
retime = histogram.New()
)
func Compile(re Pattern) *regexp.Regexp {
if res == nil {
res = make(map[Pattern]*regexp.Regexp)
}
cre := res[re]
if cre == nil {
cre = regexp.MustCompile(string(re))
@ -50,12 +47,6 @@ func Match(s string, re Pattern) []string {
delay := immediatelyBefore.UnixNano() - before.UnixNano()
timeTaken := after.UnixNano() - immediatelyBefore.UnixNano() - delay
if retime == nil {
retime = histogram.New()
rematch = histogram.New()
renomatch = histogram.New()
}
retime.Add(string(re), int(timeTaken))
if m != nil {
rematch.Add(string(re), 1)
@ -124,11 +115,11 @@ func ReplaceFirst(s string, re Pattern, replacement string) ([]string, string) {
return nil, s
}
func PrintStats() {
func PrintStats(out io.Writer) {
if Profiling {
rematch.PrintStats("rematch", os.Stdout, 10)
renomatch.PrintStats("renomatch", os.Stdout, 10)
retime.PrintStats("retime", os.Stdout, 10)
rematch.PrintStats("rematch", out, 10)
renomatch.PrintStats("renomatch", out, 10)
retime.PrintStats("retime", out, 10)
}
}

View file

@ -184,7 +184,7 @@ func (s *Suite) Test_ShellLine_CheckShellCommandLine(c *check.C) {
t.CheckOutputEmpty()
checkShellCommandLine("${RUN} echo $${variable+set}")
checkShellCommandLine("${RUN} set +x; echo $${variable+set}")
t.CheckOutputEmpty()
@ -234,7 +234,7 @@ func (s *Suite) Test_ShellLine_CheckShellCommandLine(c *check.C) {
"WARN: fname:1: The shell command \"cp\" should not be hidden.",
"WARN: fname:1: Unknown shell command \"cp\".")
G.Pkg = NewPackage("category/pkgbase")
G.Pkg = NewPackage(t.File("category/pkgbase"))
G.Pkg.PlistDirs["share/pkgbase"] = true
// A directory that is found in the PLIST.

View file

@ -32,3 +32,10 @@ func (s *Suite) Test_ShAtom_String(c *check.C) {
func (s *Suite) Test_ShQuoting_String(c *check.C) {
c.Check(shqDquotBacktSquot.String(), equals, "dbs")
}
func (s *Suite) Test_ShToken_String(c *check.C) {
tokenizer := NewShTokenizer(dummyLine, "${ECHO} \"hello, world\"", false)
c.Check(tokenizer.ShToken().String(), equals, "ShToken([varuse(\"ECHO\")])")
c.Check(tokenizer.ShToken().String(), equals, "ShToken([ShAtom(word, \"\\\"\", d) ShAtom(word, \"hello, world\", d) \"\\\"\"])")
}

View file

@ -134,13 +134,13 @@ func (ctx *SubstContext) Varassign(mkline MkLine) {
}
}
func (ctx *SubstContext) Conditional(mkline MkLine) {
func (ctx *SubstContext) Directive(mkline MkLine) {
if ctx.id == "" || !G.opts.WarnExtra {
return
}
if trace.Tracing {
trace.Stepf("+ SubstContext.Conditional %#v %v#", ctx.curr, ctx.inAllBranches)
trace.Stepf("+ SubstContext.Directive %#v %v#", ctx.curr, ctx.inAllBranches)
}
dir := mkline.Directive()
if dir == "if" {
@ -159,7 +159,7 @@ func (ctx *SubstContext) Conditional(mkline MkLine) {
ctx.curr.Or(ctx.inAllBranches)
}
if trace.Tracing {
trace.Stepf("- SubstContext.Conditional %#v %v#", ctx.curr, ctx.inAllBranches)
trace.Stepf("- SubstContext.Directive %#v %v#", ctx.curr, ctx.inAllBranches)
}
}

View file

@ -90,7 +90,7 @@ func (s *Suite) Test_SubstContext__no_class(c *check.C) {
"WARN: Makefile:13: Incomplete SUBST block: SUBST_STAGE.repl missing.")
}
func (s *Suite) Test_SubstContext__conditionals(c *check.C) {
func (s *Suite) Test_SubstContext__directives(c *check.C) {
t := s.Init(c)
t.SetupCommandLine("-Wextra")
@ -119,7 +119,7 @@ func (s *Suite) Test_SubstContext__conditionals(c *check.C) {
"WARN: Makefile:18: All but the first \"SUBST_SED.os\" lines should use the \"+=\" operator.")
}
func (s *Suite) Test_SubstContext__one_conditional_missing_transformation(c *check.C) {
func (s *Suite) Test_SubstContext__missing_transformation_in_one_branch(c *check.C) {
t := s.Init(c)
t.SetupCommandLine("-Wextra")
@ -254,7 +254,7 @@ func simulateSubstLines(t *Tester, texts ...string) {
case text == "":
ctx.Finish(line)
case hasPrefix(text, "."):
ctx.Conditional(line)
ctx.Directive(line)
default:
ctx.Varassign(line)
}

View file

@ -6,13 +6,17 @@ import (
"strings"
)
// Tool is one of the many standard shell utilities that are typically
// provided by the operating system, or, if missing, are installed via
// pkgsrc.
//
// See `mk/tools/`.
type Tool struct {
Name string // e.g. "sed", "gzip"
Varname string // e.g. "SED", "GZIP_CMD"
MustUseVarForm bool // True for `echo`, because of many differing implementations.
Predefined bool // This tool is used by the pkgsrc infrastructure, therefore the package does not need to add it to `USE_TOOLS` explicitly.
UsableAtLoadtime bool // May be used after including `bsd.prefs.mk`.
UsableAtLoadTime bool // May be used after including `bsd.prefs.mk`.
}
type ToolRegistry struct {
@ -28,9 +32,9 @@ func NewToolRegistry() ToolRegistry {
// The tool may then be used by this name (e.g. "awk"),
// but not by a corresponding variable (e.g. ${AWK}).
// The toolname may include the scope (:pkgsrc, :run, etc.).
func (tr *ToolRegistry) Register(toolname string, line Line) *Tool {
func (tr *ToolRegistry) Register(toolname string, mkline MkLine) *Tool {
name := strings.SplitN(toolname, ":", 2)[0]
tr.validateToolName(name, line)
tr.validateToolName(name, mkline)
tool := tr.byName[name]
if tool == nil {
@ -40,15 +44,15 @@ func (tr *ToolRegistry) Register(toolname string, line Line) *Tool {
return tool
}
func (tr *ToolRegistry) RegisterVarname(toolname, varname string, line Line) *Tool {
tool := tr.Register(toolname, line)
func (tr *ToolRegistry) RegisterVarname(toolname, varname string, mkline MkLine) *Tool {
tool := tr.Register(toolname, mkline)
tool.Varname = varname
tr.byVarname[varname] = tool
return tool
}
func (tr *ToolRegistry) RegisterTool(tool *Tool, line Line) {
tr.validateToolName(tool.Name, line)
func (tr *ToolRegistry) RegisterTool(tool *Tool, mkline MkLine) {
tr.validateToolName(tool.Name, mkline)
if tool.Name != "" && tr.byName[tool.Name] == nil {
tr.byName[tool.Name] = tool
@ -90,24 +94,23 @@ func (tr *ToolRegistry) Trace() {
// ParseToolLine parses a tool definition from the pkgsrc infrastructure,
// e.g. in mk/tools/replace.mk.
func (tr *ToolRegistry) ParseToolLine(line Line) {
if m, commented, varname, _, _, _, value, _, _ := MatchVarassign(line.Text); m {
if commented {
return
}
func (tr *ToolRegistry) ParseToolLine(mkline MkLine) {
if mkline.IsVarassign() {
varname := mkline.Varname()
value := mkline.Value()
if varname == "TOOLS_CREATE" && (value == "[" || matches(value, `^?[-\w.]+$`)) {
tr.Register(value, line)
tr.Register(value, mkline)
} else if m, toolname := match1(varname, `^_TOOLS_VARNAME\.([-\w.]+|\[)$`); m {
tr.RegisterVarname(toolname, value, line)
tr.RegisterVarname(toolname, value, mkline)
} else if m, toolname := match1(varname, `^(?:TOOLS_PATH|_TOOLS_DEPMETHOD)\.([-\w.]+|\[)$`); m {
tr.Register(toolname, line)
} else if m, toolname = match1(varname, `^(?:TOOLS_PATH|_TOOLS_DEPMETHOD)\.([-\w.]+|\[)$`); m {
tr.Register(toolname, mkline)
} else if m, toolname := match1(varname, `^_TOOLS\.(.*)`); m {
tr.Register(toolname, line)
} else if m, toolname = match1(varname, `^_TOOLS\.(.*)`); m {
tr.Register(toolname, mkline)
for _, tool := range splitOnSpace(value) {
tr.Register(tool, line)
tr.Register(tool, mkline)
}
}
}
@ -127,8 +130,8 @@ func (tr *ToolRegistry) ForEach(action func(tool *Tool)) {
}
}
func (tr *ToolRegistry) validateToolName(toolName string, line Line) {
func (tr *ToolRegistry) validateToolName(toolName string, mkline MkLine) {
if toolName != "echo -n" && !matches(toolName, `^([-a-z0-9.]+|\[)$`) {
line.Errorf("Invalid tool name %q", toolName)
mkline.Errorf("Invalid tool name %q.", toolName)
}
}

View file

@ -17,3 +17,16 @@ func (s *Suite) Test_ToolRegistry_ParseToolLine(c *check.C) {
// No error about "Unknown tool \"NetBSD\"."
t.CheckOutputEmpty()
}
func (s *Suite) Test_ToolRegistry_validateToolName__invalid(c *check.C) {
t := s.Init(c)
reg := NewToolRegistry()
reg.Register("tool_name", dummyMkLine)
// Currently, the underscore is not used in any tool name.
// If there should ever be such a case, just use a different character.
t.CheckOutputLines(
"ERROR: Invalid tool name \"tool_name\".")
}

View file

@ -18,18 +18,18 @@ func CheckdirToplevel(dir string) {
ctx := &Toplevel{dir, "", nil}
fname := dir + "/Makefile"
lines := LoadNonemptyLines(fname, true)
if lines == nil {
mklines := LoadMk(fname, NotEmpty|LogErrors)
if mklines == nil {
return
}
for _, line := range lines {
if m, commentedOut, indentation, subdir, comment := match4(line.Text, `^(#?)SUBDIR\s*\+=(\s*)(\S+)\s*(?:#\s*(.*?)\s*|)$`); m {
ctx.checkSubdir(line, commentedOut == "#", indentation, subdir, comment)
for _, mkline := range mklines.mklines {
if (mkline.IsVarassign() || mkline.IsCommentedVarassign()) && mkline.Varname() == "SUBDIR" {
ctx.checkSubdir(mkline)
}
}
NewMkLines(lines).Check()
mklines.Check()
if G.opts.Recursive {
if G.opts.CheckGlobal {
@ -40,13 +40,15 @@ func CheckdirToplevel(dir string) {
}
}
func (ctx *Toplevel) checkSubdir(line Line, commentedOut bool, indentation, subdir, comment string) {
if commentedOut && comment == "" {
line.Warnf("%q commented out without giving a reason.", subdir)
func (ctx *Toplevel) checkSubdir(mkline MkLine) {
subdir := mkline.Value()
if mkline.IsCommentedVarassign() && (mkline.VarassignComment() == "#" || mkline.VarassignComment() == "") {
mkline.Warnf("%q commented out without giving a reason.", subdir)
}
if indentation != "\t" {
line.Warnf("Indentation should be a single tab character.")
if !hasSuffix(mkline.ValueAlign(), "=\t") {
mkline.Warnf("Indentation should be a single tab character.")
}
if contains(subdir, "$") || !fileExists(ctx.dir+"/"+subdir+"/Makefile") {
@ -58,15 +60,15 @@ func (ctx *Toplevel) checkSubdir(line Line, commentedOut bool, indentation, subd
case subdir > prev:
// Correctly ordered
case subdir == prev:
line.Errorf("Each subdir must only appear once.")
mkline.Errorf("Each subdir must only appear once.")
case subdir == "archivers" && prev == "x11":
// This exception is documented in the top-level Makefile.
default:
line.Warnf("%s should come before %s.", subdir, prev)
mkline.Warnf("%s should come before %s.", subdir, prev)
}
ctx.previousSubdir = subdir
if !commentedOut {
if !mkline.IsCommentedVarassign() {
ctx.subdirs = append(ctx.subdirs, ctx.dir+"/"+subdir)
}
}

View file

@ -169,8 +169,8 @@ func loadCvsEntries(fname string) []Line {
return G.CvsEntriesLines
}
lines, err := readLines(dir+"/CVS/Entries", false)
if err != nil {
lines := Load(dir+"/CVS/Entries", 0)
if lines == nil {
return nil
}
G.CvsEntriesDir = dir
@ -301,7 +301,7 @@ func dirglob(dirname string) []string {
var fnames []string
for _, fi := range fis {
if !(isIgnoredFilename(fi.Name())) {
fnames = append(fnames, dirname+"/"+fi.Name())
fnames = append(fnames, cleanpath(dirname+"/"+fi.Name()))
}
}
return fnames
@ -585,7 +585,7 @@ func naturalLess(str1, str2 string) bool {
// but that's deep in the infrastructure and only affects the "nb13" extension.)
type RedundantScope struct {
vars map[string]*redundantScopeVarinfo
condLevel int
dirLevel int
OnIgnore func(old, new MkLine)
OnOverwrite func(old, new MkLine)
}
@ -602,7 +602,7 @@ func (s *RedundantScope) Handle(mkline MkLine) {
switch {
case mkline.IsVarassign():
varname := mkline.Varname()
if s.condLevel != 0 {
if s.dirLevel != 0 {
s.vars[varname] = nil
break
}
@ -645,12 +645,12 @@ func (s *RedundantScope) Handle(mkline MkLine) {
}
}
case mkline.IsCond():
case mkline.IsDirective():
switch mkline.Directive() {
case "for", "if", "ifdef", "ifndef":
s.condLevel++
s.dirLevel++
case "endfor", "endif":
s.condLevel--
s.dirLevel--
}
}
}

View file

@ -86,11 +86,13 @@ func (s *Suite) Test_isEmptyDir_and_getSubdirs(c *check.C) {
if absent := t.File("nonexistent"); true {
c.Check(isEmptyDir(absent), equals, true) // Counts as empty.
defer t.ExpectFatalError(func() {
c.Check(t.Output(), check.Matches, `FATAL: (.+): Cannot be read: open (.+): (.+)\n`)
})
getSubdirs(absent) // Panics with a pkglintFatal.
c.FailNow()
func() {
defer t.ExpectFatalError()
getSubdirs(absent) // Panics with a pkglintFatal.
}()
// The last group from the error message is localized, therefore the matching.
c.Check(t.Output(), check.Matches, `FATAL: ~/nonexistent: Cannot be read: open ~/nonexistent: (.+)\n`)
}
}

View file

@ -71,41 +71,18 @@ func (src *Pkgsrc) InitVartypes() {
acl(varname, kindOfList, checker, "buildlink3.mk, builtin.mk:; *: use-loadtime, use")
}
jvms := enum(func() string {
lines, _ := readLines(src.File("mk/java-vm.mk"), true)
mklines := NewMkLines(lines)
jvms := make(map[string]bool)
for _, mkline := range mklines.mklines {
if mkline.IsVarassign() && mkline.Varcanon() == "_PKG_JVMS.*" {
words, _ := splitIntoMkWords(mkline.Line, mkline.Value())
for _, word := range words {
if !contains(word, "$") {
jvms[word] = true
}
}
}
}
if len(jvms) == 0 {
return "openjdk8 oracle-jdk8 openjdk7 sun-jdk7 sun-jdk6 jdk16 jdk15 kaffe"
}
joined := keysJoined(jvms)
if trace.Tracing {
trace.Stepf("JVMs from mk/java-vm.mk: %s", joined)
}
return joined
}())
languages := enum(
func() string {
lines, _ := readLines(src.File("mk/compiler.mk"), true)
mklines := NewMkLines(lines)
mklines := LoadMk(src.File("mk/compiler.mk"), NotEmpty)
languages := make(map[string]bool)
for _, mkline := range mklines.mklines {
if mkline.IsCond() && mkline.Directive() == "for" {
words := splitOnSpace(mkline.Args())
if len(words) > 2 && words[0] == "_version_" {
for _, word := range words[2:] {
languages[word] = true
if mklines != nil {
for _, mkline := range mklines.mklines {
if mkline.IsDirective() && mkline.Directive() == "for" {
words := splitOnSpace(mkline.Args())
if len(words) > 2 && words[0] == "_version_" {
for _, word := range words[2:] {
languages[word] = true
}
}
}
}
@ -121,17 +98,68 @@ func (src *Pkgsrc) InitVartypes() {
return joined
}())
enumFrom := func(fileName, varname, defval string) *BasicType {
lines, _ := readLines(src.File(fileName), true)
mklines := NewMkLines(lines)
for _, mkline := range mklines.mklines {
if mkline.IsVarassign() && mkline.Varname() == varname {
return enum(mkline.Value())
enumFrom := func(fileName string, defval string, varcanons ...string) *BasicType {
mklines := LoadMk(src.File(fileName), NotEmpty)
values := make(map[string]bool)
if mklines != nil {
for _, mkline := range mklines.mklines {
if mkline.IsVarassign() {
varcanon := mkline.Varcanon()
for _, vc := range varcanons {
if vc == varcanon {
words, _ := splitIntoMkWords(mkline.Line, mkline.Value())
for _, word := range words {
if !contains(word, "$") {
values[word] = true
}
}
}
}
}
}
}
if len(values) != 0 {
joined := keysJoined(values)
if trace.Tracing {
trace.Stepf("Enum from %s in: %s", strings.Join(varcanons, " "), fileName, joined)
}
return enum(joined)
}
if trace.Tracing {
trace.Stepf("Enum from default value: %s", defval)
}
return enum(defval)
}
compilers := enumFrom(
"mk/compiler.mk",
"ccache ccc clang distcc f2c gcc hp icc ido mipspro mipspro-ucode pcc sunpro xlc",
"_COMPILERS",
"_PSEUDO_COMPILERS")
emacsVersions := enumFrom(
"editors/emacs/modules.mk",
"emacs25 emacs21 emacs21nox emacs20 xemacs215 xemacs215nox xemacs214 xemacs214nox",
"_EMACS_VERSIONS_ALL")
mysqlVersions := enumFrom(
"mk/mysql.buildlink3.mk",
"57 56 55 51 MARIADB55",
"MYSQL_VERSIONS_ACCEPTED")
pgsqlVersions := enumFrom(
"mk/pgsql.buildlink3.mk",
"10 96 95 94 93",
"PGSQL_VERSIONS_ACCEPTED")
jvms := enumFrom(
"mk/java-vm.mk",
"openjdk8 oracle-jdk8 openjdk7 sun-jdk7 sun-jdk6 jdk16 jdk15 kaffe",
"_PKG_JVMS.*")
// Last synced with mk/defaults/mk.conf revision 1.269
usr("USE_CWRAPPERS", lkNone, enum("yes no auto"))
usr("ALLOW_VULNERABLE_PACKAGES", lkNone, BtYes)
@ -153,7 +181,7 @@ func (src *Pkgsrc) InitVartypes() {
usr("PKG_DEVELOPER", lkNone, BtYesNo)
usr("USE_ABI_DEPENDS", lkNone, BtYesNo)
usr("PKG_REGISTER_SHELLS", lkNone, enum("YES NO"))
usr("PKGSRC_COMPILER", lkShell, enum("ccache ccc clang distcc f2c gcc hp icc ido mipspro mipspro-ucode pcc sunpro xlc"))
usr("PKGSRC_COMPILER", lkShell, compilers)
usr("PKGSRC_KEEP_BIN_PKGS", lkNone, BtYesNo)
usr("PKGSRC_MESSAGE_RECIPIENTS", lkShell, BtMailAddress)
usr("PKGSRC_SHOW_BUILD_DEFS", lkNone, BtYesNo)
@ -599,10 +627,10 @@ func (src *Pkgsrc) InitVartypes() {
sys("EMACS_PKGNAME_PREFIX", lkNone, BtIdentifier) // Or the empty string.
sys("EMACS_TYPE", lkNone, enum("emacs xemacs"))
acl("EMACS_USE_LEIM", lkNone, BtYes, "")
acl("EMACS_VERSIONS_ACCEPTED", lkShell, enumFrom("editors/emacs/modules.mk", "_EMACS_VERSIONS_ALL", "emacs25 emacs21 emacs21nox emacs20 xemacs215 xemacs215nox xemacs214 xemacs214nox"), "Makefile: set")
acl("EMACS_VERSIONS_ACCEPTED", lkShell, emacsVersions, "Makefile: set")
sys("EMACS_VERSION_MAJOR", lkNone, BtInteger)
sys("EMACS_VERSION_MINOR", lkNone, BtInteger)
acl("EMACS_VERSION_REQD", lkShell, enum("emacs25 emacs25nox emacs21 emacs21nox emacs20 xemacs215 xemacs214"), "Makefile: set, append")
acl("EMACS_VERSION_REQD", lkShell, emacsVersions, "Makefile: set, append")
sys("EMULDIR", lkNone, BtPathname)
sys("EMULSUBDIR", lkNone, BtPathname)
sys("OPSYS_EMULDIR", lkNone, BtPathname)
@ -796,11 +824,11 @@ func (src *Pkgsrc) InitVartypes() {
acl("MESSAGE_SUBST", lkShell, BtShellWord, "Makefile, Makefile.common, options.mk: append")
pkg("META_PACKAGE", lkNone, BtYes)
sys("MISSING_FEATURES", lkShell, BtIdentifier)
acl("MYSQL_VERSIONS_ACCEPTED", lkShell, enumFrom("mk/mysql.buildlink3.mk", "MYSQL_VERSIONS_ACCEPTED", "57 56 55 51 MARIADB55"), "Makefile: set")
acl("MYSQL_VERSIONS_ACCEPTED", lkShell, mysqlVersions, "Makefile: set")
usr("MYSQL_VERSION_DEFAULT", lkNone, BtVersion)
sys("NM", lkNone, BtShellCommand)
sys("NONBINMODE", lkNone, BtFileMode)
pkg("NOT_FOR_COMPILER", lkShell, enum("ccache ccc clang distcc f2c gcc hp icc ido mipspro mipspro-ucode pcc sunpro xlc"))
pkg("NOT_FOR_COMPILER", lkShell, compilers)
pkglist("NOT_FOR_BULK_PLATFORM", lkSpace, BtMachinePlatformPattern)
pkglist("NOT_FOR_PLATFORM", lkSpace, BtMachinePlatformPattern)
pkg("NOT_FOR_UNPRIVILEGED", lkNone, BtYesNo)
@ -817,7 +845,7 @@ func (src *Pkgsrc) InitVartypes() {
acl("NO_PKGTOOLS_REQD_CHECK", lkNone, BtYes, "Makefile: set")
acl("NO_SRC_ON_CDROM", lkNone, BtRestricted, "Makefile, Makefile.common: set")
acl("NO_SRC_ON_FTP", lkNone, BtRestricted, "Makefile, Makefile.common: set")
pkglist("ONLY_FOR_COMPILER", lkShell, enum("ccc clang gcc hp icc ido mipspro mipspro-ucode pcc sunpro xlc"))
pkglist("ONLY_FOR_COMPILER", lkShell, compilers)
pkglist("ONLY_FOR_PLATFORM", lkSpace, BtMachinePlatformPattern)
pkg("ONLY_FOR_UNPRIVILEGED", lkNone, BtYesNo)
sys("OPSYS", lkNone, BtIdentifier)
@ -844,7 +872,7 @@ func (src *Pkgsrc) InitVartypes() {
pkg("PERL5_REQD", lkShell, BtVersion)
pkg("PERL5_USE_PACKLIST", lkNone, BtYesNo)
sys("PGSQL_PREFIX", lkNone, BtPathname)
acl("PGSQL_VERSIONS_ACCEPTED", lkShell, enumFrom("mk/pgsql.buildlink3.mk", "PGSQL_VERSIONS_ACCEPTED", "10 96 95 94 93"), "")
acl("PGSQL_VERSIONS_ACCEPTED", lkShell, pgsqlVersions, "")
usr("PGSQL_VERSION_DEFAULT", lkNone, BtVersion)
sys("PG_LIB_EXT", lkNone, enum("dylib so"))
sys("PGSQL_TYPE", lkNone, enum("postgresql81-client postgresql80-client"))
@ -924,7 +952,7 @@ func (src *Pkgsrc) InitVartypes() {
acl("PKG_USERS", lkShell, BtShellWord, "Makefile: set, append")
pkg("PKG_USERS_VARS", lkShell, BtVariableName)
acl("PKG_USE_KERBEROS", lkNone, BtYes, "Makefile, Makefile.common: set")
// PLIST.* has special handling code
pkg("PLIST.*", lkNone, BtYes)
pkglist("PLIST_VARS", lkShell, BtIdentifier)
pkglist("PLIST_SRC", lkShell, BtRelativePkgPath)
pkglist("PLIST_SUBST", lkShell, BtShellWord)

View file

@ -8,14 +8,39 @@ func (s *Suite) Test_InitVartypes__enumFrom(c *check.C) {
t.SetupFileMkLines("editors/emacs/modules.mk",
MkRcsID,
"",
"_EMACS_VERSIONS_ALL=\temacs31",
"_EMACS_VERSIONS_ALL=\tignored")
"_EMACS_VERSIONS_ALL= emacs31",
"_EMACS_VERSIONS_ALL+= emacs29")
t.SetupFileMkLines("mk/java-vm.mk",
MkRcsID,
"",
"_PKG_JVMS.8= openjdk8 oracle-jdk8",
"_PKG_JVMS.7= ${_PKG_JVMS.8} openjdk7 sun-jdk7",
"_PKG_JVMS.6= ${_PKG_JVMS.7} sun-jdk6 jdk16")
t.SetupFileMkLines("mk/compiler.mk",
MkRcsID,
"",
"_COMPILERS= gcc ido mipspro-ucode \\",
" sunpro",
"_PSEUDO_COMPILERS= ccache distcc f2c g95",
"",
".for _version_ in gnu++14 c++14 gnu++11 c++11 gnu++0x c++0x gnu++03 c++03",
". if !empty(USE_LANGUAGES:M${_version_})",
"USE_LANGUAGES+= c++",
". endif",
".endfor")
mklines := t.SetupFileMkLines("Makefile",
MkRcsID,
"")
t.SetupVartypes()
vartype := mklines.mklines[1].VariableType("EMACS_VERSIONS_ACCEPTED")
c.Check(vartype.String(), equals, "ShellList of enum: emacs31 ")
checkEnumValues := func(varname, values string) {
vartype := mklines.mklines[1].VariableType(varname).String()
c.Check(vartype, equals, values)
}
checkEnumValues("EMACS_VERSIONS_ACCEPTED", "ShellList of enum: emacs29 emacs31 ")
checkEnumValues("PKG_JVM", "enum: jdk16 openjdk7 openjdk8 oracle-jdk8 sun-jdk6 sun-jdk7 ")
checkEnumValues("USE_LANGUAGES", "ShellList of enum: ada c c++ c++03 c++0x c++11 c++14 c99 fortran fortran77 gnu++03 gnu++0x gnu++11 gnu++14 java obj-c++ objc ")
checkEnumValues("PKGSRC_COMPILER", "ShellList of enum: ccache distcc f2c g95 gcc ido mipspro-ucode sunpro ")
}

View file

@ -122,16 +122,21 @@ func (vt *Vartype) MayBeAppendedTo() bool {
}
func (vt *Vartype) String() string {
listPrefix := ""
switch vt.kindOfList {
case lkNone:
return vt.basicType.name
listPrefix = ""
case lkSpace:
return "SpaceList of " + vt.basicType.name
listPrefix = "SpaceList of "
case lkShell:
return "ShellList of " + vt.basicType.name
listPrefix = "ShellList of "
default:
panic("Unknown list type")
}
guessedSuffix := ifelseStr(vt.guessed, " (guessed)", "")
return listPrefix + vt.basicType.name + guessedSuffix
}
func (vt *Vartype) IsShell() bool {

View file

@ -39,7 +39,7 @@ const (
opAssignEval // :=
opAssignAppend // +=
opAssignDefault // ?=
opUseCompare // A variable is compared to a value, e.g. in a conditional.
opUseCompare // A variable is compared to a value, e.g. in a condition.
opUseMatch // A variable is matched using the :M or :N modifier.
)
@ -1085,7 +1085,7 @@ func (cv *VartypeCheck) WrksrcSubdirectory() {
func (cv *VartypeCheck) Yes() {
switch cv.Op {
case opUseMatch:
cv.Line.Warnf("%s should only be used in a \".if defined(...)\" conditional.", cv.Varname)
cv.Line.Warnf("%s should only be used in a \".if defined(...)\" condition.", cv.Varname)
Explain(
"This variable can have only two values: defined or undefined.",
"When it is defined, it means \"yes\", even when its value is",

View file

@ -78,7 +78,7 @@ func (s *Suite) Test_VartypeCheck_CFlag(c *check.C) {
func (s *Suite) Test_VartypeCheck_Comment(c *check.C) {
t := s.Init(c)
G.Pkg = NewPackage("category/converter")
G.Pkg = NewPackage(t.File("category/converter"))
G.Pkg.EffectivePkgbase = "converter"
runVartypeChecks(t, "COMMENT", opAssign, (*VartypeCheck).Comment,
@ -168,13 +168,13 @@ func (s *Suite) Test_VartypeCheck_DependencyWithPath(c *check.C) {
t.CreateFileLines("x11/alacarte/Makefile")
t.CreateFileLines("category/package/Makefile")
G.Pkg = NewPackage("category/package")
G.Pkg = NewPackage(t.File("category/package"))
// Since this test involves relative paths, the filename of the line must be realistic.
// Therefore this custom implementation of runVartypeChecks.
runChecks := func(values ...string) {
for i, value := range values {
mkline := t.NewMkLine(t.File("category/package/fname.mk"), i+1, "DEPENDS+=\t"+value)
mkline := t.NewMkLine(G.Pkg.File("fname.mk"), i+1, "DEPENDS+=\t"+value)
mkline.Tokenize(mkline.Value())
valueNovar := mkline.WithoutMakeVariables(mkline.Value())
vc := &VartypeCheck{mkline, mkline.Line, mkline.Varname(), mkline.Op(), mkline.Value(), valueNovar, "", false}
@ -272,6 +272,8 @@ func (s *Suite) Test_VartypeCheck_Enum__use_match(c *check.C) {
".if !empty(MACHINE_ARCH:Mi386) || ${MACHINE_ARCH} == i386",
".endif",
".if !empty(PKGSRC_COMPILER:Mclang) || ${PKGSRC_COMPILER} == clang",
".endif",
".if ${MACHINE_ARCH:Ni386:Nx86_64:Nsparc64}",
".endif")
mklines.Check()
@ -661,9 +663,9 @@ func (s *Suite) Test_VartypeCheck_Yes(c *check.C) {
"${YESVAR}")
t.CheckOutputLines(
"WARN: fname:1: PKG_DEVELOPER should only be used in a \".if defined(...)\" conditional.",
"WARN: fname:2: PKG_DEVELOPER should only be used in a \".if defined(...)\" conditional.",
"WARN: fname:3: PKG_DEVELOPER should only be used in a \".if defined(...)\" conditional.")
"WARN: fname:1: PKG_DEVELOPER should only be used in a \".if defined(...)\" condition.",
"WARN: fname:2: PKG_DEVELOPER should only be used in a \".if defined(...)\" condition.",
"WARN: fname:3: PKG_DEVELOPER should only be used in a \".if defined(...)\" condition.")
}
func (s *Suite) Test_VartypeCheck_YesNo(c *check.C) {