Received: by mail.netbsd.org (Postfix, from userid 605) id 4821184E4E; Thu, 21 Feb 2019 22:49:14 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by mail.netbsd.org (Postfix) with ESMTP id C084484E49 for ; Thu, 21 Feb 2019 22:49:13 +0000 (UTC) X-Virus-Scanned: amavisd-new at netbsd.org Received: from mail.netbsd.org ([127.0.0.1]) by localhost (mail.netbsd.org [127.0.0.1]) (amavisd-new, port 10025) with ESMTP id bTJAFtau4Gzg for ; Thu, 21 Feb 2019 22:49:04 +0000 (UTC) Received: from cvs.NetBSD.org (ivanova.NetBSD.org [IPv6:2001:470:a085:999:28c:faff:fe03:5984]) by mail.netbsd.org (Postfix) with ESMTP id CB96684C81 for ; Thu, 21 Feb 2019 22:49:04 +0000 (UTC) Received: by cvs.NetBSD.org (Postfix, from userid 500) id C5594FB16; Thu, 21 Feb 2019 22:49:04 +0000 (UTC) Content-Transfer-Encoding: 7bit Content-Type: multipart/mixed; boundary="_----------=_1550789344110970" MIME-Version: 1.0 Date: Thu, 21 Feb 2019 22:49:04 +0000 From: "Roland Illig" Subject: CVS commit: pkgsrc/pkgtools/pkglint To: pkgsrc-changes@NetBSD.org Reply-To: rillig@netbsd.org X-Mailer: log_accum Message-Id: <20190221224904.C5594FB16@cvs.NetBSD.org> Sender: pkgsrc-changes-owner@NetBSD.org List-Id: pkgsrc-changes.NetBSD.org Precedence: bulk List-Unsubscribe: This is a multi-part message in MIME format. --_----------=_1550789344110970 Content-Disposition: inline Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="US-ASCII" Module Name: pkgsrc Committed By: rillig Date: Thu Feb 21 22:49:04 UTC 2019 Modified Files: pkgsrc/pkgtools/pkglint: Makefile pkgsrc/pkgtools/pkglint/files: alternatives_test.go autofix.go autofix_test.go buildlink3.go buildlink3_test.go category.go category_test.go check_test.go distinfo.go distinfo_test.go files.go licenses.go line.go line_test.go linelexer.go linelexer_test.go lines_test.go logging.go logging_test.go mkline.go mkline_test.go mklinechecker.go mklinechecker_test.go mklines.go mklines_test.go mkparser.go mkparser_test.go mktypes.go options.go options_test.go package.go package_test.go patches.go pkglint.0 pkglint.1 pkglint.go pkglint_test.go pkgsrc.go pkgsrc_test.go plist.go plist_test.go shell_test.go substcontext_test.go toplevel.go util.go util_test.go vartypecheck.go vartypecheck_test.go pkgsrc/pkgtools/pkglint/files/getopt: getopt.go pkgsrc/pkgtools/pkglint/files/textproc: lexer.go lexer_test.go pkgsrc/pkgtools/pkglint/files/trace: tracing.go Log Message: pkgtools/pkglint: update to 5.7.0 Changes since 5.6.12: * Many of the -C and -W command line options have been removed since they are not used in practice. The -Wall and -Call options continue to work though; these are the only options mentioned in the pkgsrc guide. * When a PLIST file contains redundant libtool libraries (.la and the corresponding .so), there is only a single warning per file. * Warnings about the package COMMENT are now strictly ordered from left to right. * The hashes for all distfiles must now contain the SHA512 hash. This hash has been added to many distfiles in 2015. It's time now to enforce it on all other distfiles as well. * Makefile fragments that are included inside an .elif exists(...) are not reported as missing. * The check for redundant variables and accidentally overwritten variables has been improved. Now the warning occurs at the later definition. This especially applies to cases where a file is included and after that, some of its variables are overridden. Variables in unrelated files are no longer marked as redundant. * When a package contains multiple definitions of a single variable (typical for Makefile.common), the later definition overrides the earlier definition. That way, the location of DISTINFO_FILE and PATCHDIR is resolved correctly. To generate a diff of this commit: cvs rdiff -u -r1.566 -r1.567 pkgsrc/pkgtools/pkglint/Makefile cvs rdiff -u -r1.12 -r1.13 pkgsrc/pkgtools/pkglint/files/alternatives_test.go cvs rdiff -u -r1.16 -r1.17 pkgsrc/pkgtools/pkglint/files/autofix.go \ pkgsrc/pkgtools/pkglint/files/line_test.go \ pkgsrc/pkgtools/pkglint/files/toplevel.go cvs rdiff -u -r1.17 -r1.18 pkgsrc/pkgtools/pkglint/files/autofix_test.go \ pkgsrc/pkgtools/pkglint/files/category.go cvs rdiff -u -r1.19 -r1.20 pkgsrc/pkgtools/pkglint/files/buildlink3.go \ pkgsrc/pkgtools/pkglint/files/logging.go cvs rdiff -u -r1.26 -r1.27 pkgsrc/pkgtools/pkglint/files/buildlink3_test.go cvs rdiff -u -r1.18 -r1.19 pkgsrc/pkgtools/pkglint/files/category_test.go \ pkgsrc/pkgtools/pkglint/files/pkgsrc.go cvs rdiff -u -r1.33 -r1.34 pkgsrc/pkgtools/pkglint/files/check_test.go cvs rdiff -u -r1.27 -r1.28 pkgsrc/pkgtools/pkglint/files/distinfo.go cvs rdiff -u -r1.24 -r1.25 pkgsrc/pkgtools/pkglint/files/distinfo_test.go \ pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go cvs rdiff -u -r1.23 -r1.24 pkgsrc/pkgtools/pkglint/files/files.go \ pkgsrc/pkgtools/pkglint/files/mkparser.go cvs rdiff -u -r1.20 -r1.21 pkgsrc/pkgtools/pkglint/files/licenses.go cvs rdiff -u -r1.32 -r1.33 pkgsrc/pkgtools/pkglint/files/line.go \ pkgsrc/pkgtools/pkglint/files/pkglint_test.go \ pkgsrc/pkgtools/pkglint/files/plist_test.go cvs rdiff -u -r1.1 -r1.2 pkgsrc/pkgtools/pkglint/files/linelexer.go \ pkgsrc/pkgtools/pkglint/files/linelexer_test.go cvs rdiff -u -r1.7 -r1.8 pkgsrc/pkgtools/pkglint/files/lines_test.go cvs rdiff -u -r1.11 -r1.12 pkgsrc/pkgtools/pkglint/files/logging_test.go \ pkgsrc/pkgtools/pkglint/files/options.go cvs rdiff -u -r1.45 -r1.46 pkgsrc/pkgtools/pkglint/files/mkline.go cvs rdiff -u -r1.50 -r1.51 pkgsrc/pkgtools/pkglint/files/mkline_test.go cvs rdiff -u -r1.28 -r1.29 pkgsrc/pkgtools/pkglint/files/mklinechecker.go \ pkgsrc/pkgtools/pkglint/files/patches.go cvs rdiff -u -r1.40 -r1.41 pkgsrc/pkgtools/pkglint/files/mklines.go cvs rdiff -u -r1.35 -r1.36 pkgsrc/pkgtools/pkglint/files/mklines_test.go cvs rdiff -u -r1.21 -r1.22 pkgsrc/pkgtools/pkglint/files/mkparser_test.go \ pkgsrc/pkgtools/pkglint/files/substcontext_test.go cvs rdiff -u -r1.10 -r1.11 pkgsrc/pkgtools/pkglint/files/mktypes.go \ pkgsrc/pkgtools/pkglint/files/options_test.go cvs rdiff -u -r1.44 -r1.45 pkgsrc/pkgtools/pkglint/files/package.go cvs rdiff -u -r1.38 -r1.39 pkgsrc/pkgtools/pkglint/files/package_test.go \ pkgsrc/pkgtools/pkglint/files/pkglint.0 \ pkgsrc/pkgtools/pkglint/files/shell_test.go cvs rdiff -u -r1.52 -r1.53 pkgsrc/pkgtools/pkglint/files/pkglint.1 cvs rdiff -u -r1.46 -r1.47 pkgsrc/pkgtools/pkglint/files/pkglint.go cvs rdiff -u -r1.15 -r1.16 pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go cvs rdiff -u -r1.36 -r1.37 pkgsrc/pkgtools/pkglint/files/plist.go \ pkgsrc/pkgtools/pkglint/files/util.go cvs rdiff -u -r1.22 -r1.23 pkgsrc/pkgtools/pkglint/files/util_test.go cvs rdiff -u -r1.48 -r1.49 pkgsrc/pkgtools/pkglint/files/vartypecheck.go cvs rdiff -u -r1.41 -r1.42 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go cvs rdiff -u -r1.7 -r1.8 pkgsrc/pkgtools/pkglint/files/getopt/getopt.go cvs rdiff -u -r1.3 -r1.4 pkgsrc/pkgtools/pkglint/files/textproc/lexer.go \ pkgsrc/pkgtools/pkglint/files/textproc/lexer_test.go cvs rdiff -u -r1.5 -r1.6 pkgsrc/pkgtools/pkglint/files/trace/tracing.go Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files. --_----------=_1550789344110970 Content-Disposition: inline Content-Length: 177200 Content-Transfer-Encoding: binary Content-Type: text/x-diff; charset=us-ascii Modified files: Index: pkgsrc/pkgtools/pkglint/Makefile diff -u pkgsrc/pkgtools/pkglint/Makefile:1.566 pkgsrc/pkgtools/pkglint/Makefile:1.567 --- pkgsrc/pkgtools/pkglint/Makefile:1.566 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/Makefile Thu Feb 21 22:49:03 2019 @@ -1,6 +1,6 @@ -# $NetBSD: Makefile,v 1.566 2019/01/26 16:31:33 rillig Exp $ +# $NetBSD: Makefile,v 1.567 2019/02/21 22:49:03 rillig Exp $ -PKGNAME= pkglint-5.6.12 +PKGNAME= pkglint-5.7.0 CATEGORIES= pkgtools DISTNAME= tools MASTER_SITES= ${MASTER_SITE_GITHUB:=golang/} Index: pkgsrc/pkgtools/pkglint/files/alternatives_test.go diff -u pkgsrc/pkgtools/pkglint/files/alternatives_test.go:1.12 pkgsrc/pkgtools/pkglint/files/alternatives_test.go:1.13 --- pkgsrc/pkgtools/pkglint/files/alternatives_test.go:1.12 Sun Jan 13 19:55:52 2019 +++ pkgsrc/pkgtools/pkglint/files/alternatives_test.go Thu Feb 21 22:49:03 2019 @@ -59,3 +59,23 @@ func (s *Suite) Test_CheckFileAlternativ t.CheckOutputLines( "ERROR: ALTERNATIVES: Must not be empty.") } + +func (s *Suite) Test_CheckFileAlternatives__ALTERNATIVES_SRC(c *check.C) { + t := s.Init(c) + + // It's a strange situation, having an ALTERNATIVES file defined by + // the package but then referring to another package's file by means + // of ALTERNATIVES_SRC. As of February 2019 I don't remember if I + // really had this case in mind when I initially wrote the code in + // CheckFileAlternatives. + t.SetUpPackage("category/package", + "ALTERNATIVES_SRC=\talts") + t.CreateFileLines("category/package/ALTERNATIVES", + "bin/pgm @PREFIX@/bin/gnu-program", + "bin/pgm @PREFIX@/bin/nb-program") + G.Pkgsrc.LoadInfrastructure() + + G.Check(t.File("category/package")) + + t.CheckOutputEmpty() +} Index: pkgsrc/pkgtools/pkglint/files/autofix.go diff -u pkgsrc/pkgtools/pkglint/files/autofix.go:1.16 pkgsrc/pkgtools/pkglint/files/autofix.go:1.17 --- pkgsrc/pkgtools/pkglint/files/autofix.go:1.16 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/autofix.go Thu Feb 21 22:49:03 2019 @@ -96,15 +96,13 @@ func (fix *Autofix) ReplaceAfter(prefix, } for _, rawLine := range fix.line.raw { - if rawLine.Lineno != 0 { - replaced := strings.Replace(rawLine.textnl, prefix+from, prefix+to, 1) - if replaced != rawLine.textnl { - if G.Logger.IsAutofix() { - rawLine.textnl = replaced - } - fix.Describef(rawLine.Lineno, "Replacing %q with %q.", from, to) - return + replaced := strings.Replace(rawLine.textnl, prefix+from, prefix+to, 1) + if replaced != rawLine.textnl { + if G.Logger.IsAutofix() { + rawLine.textnl = replaced } + fix.Describef(rawLine.Lineno, "Replacing %q with %q.", from, to) + return } } } @@ -122,26 +120,24 @@ func (fix *Autofix) ReplaceRegex(from re done := 0 for _, rawLine := range fix.line.raw { - if rawLine.Lineno != 0 { - var froms []string // The strings that have actually changed + var froms []string // The strings that have actually changed - replace := func(fromText string) string { - if howOften >= 0 && done >= howOften { - return fromText - } - froms = append(froms, fromText) - done++ - return toText + replace := func(fromText string) string { + if howOften >= 0 && done >= howOften { + return fromText } + froms = append(froms, fromText) + done++ + return toText + } - replaced := replaceAllFunc(rawLine.textnl, from, replace) - if replaced != rawLine.textnl { - if G.Logger.IsAutofix() { - rawLine.textnl = replaced - } - for _, fromText := range froms { - fix.Describef(rawLine.Lineno, "Replacing %q with %q.", fromText, toText) - } + replaced := replaceAllFunc(rawLine.textnl, from, replace) + if replaced != rawLine.textnl { + if G.Logger.IsAutofix() { + rawLine.textnl = replaced + } + for _, fromText := range froms { + fix.Describef(rawLine.Lineno, "Replacing %q with %q.", fromText, toText) } } } @@ -231,9 +227,10 @@ func (fix *Autofix) Delete() { } // Anyway has the effect of showing the diagnostic even when nothing can -// be fixed automatically. +// be fixed automatically, but only if neither --show-autofix nor +// --autofix mode is given. func (fix *Autofix) Anyway() { - fix.anyway = true + fix.anyway = !G.Logger.IsAutofix() } // Apply does the actual work. @@ -262,21 +259,25 @@ func (fix *Autofix) Apply() { fix.autofixShortTerm = autofixShortTerm{} } - if !G.Logger.Relevant(fix.diagFormat) || !fix.anyway && len(fix.actions) == 0 { + if !(G.Logger.Relevant(fix.diagFormat) && (len(fix.actions) > 0 || fix.anyway)) { reset() return } - logDiagnostic := (G.Logger.Opts.ShowAutofix || !G.Logger.Opts.Autofix) && - fix.diagFormat != SilentAutofixFormat + logDiagnostic := true + switch { + case fix.diagFormat == SilentAutofixFormat: + logDiagnostic = false + case G.Logger.Opts.Autofix && !G.Logger.Opts.ShowAutofix: + logDiagnostic = false + } + logFix := G.Logger.IsAutofix() if logDiagnostic { msg := sprintf(fix.diagFormat, fix.diagArgs...) - if !logFix { - if fix.diagFormat == AutofixFormat || G.Logger.FirstTime(line.Filename, line.Linenos(), msg) { - line.showSource(G.out) - } + if !logFix && G.Logger.FirstTime(line.Filename, line.Linenos(), msg) { + line.showSource(G.out) } G.Logf(fix.level, line.Filename, line.Linenos(), fix.diagFormat, msg) } @@ -299,7 +300,7 @@ func (fix *Autofix) Apply() { G.Explain(fix.explanation...) } if G.Logger.Opts.ShowSource { - if !G.Logger.Opts.Explain || !logDiagnostic || len(fix.explanation) == 0 { + if !(G.Logger.Opts.Explain && logDiagnostic && len(fix.explanation) > 0) { G.out.Separate() } } @@ -327,8 +328,8 @@ func (fix *Autofix) Realign(mkline MkLin { // Parsing the continuation marker as variable value is cheating but works well. text := strings.TrimSuffix(mkline.raw[0].orignl, "\n") - m, _, _, _, _, valueAlign, value, _, _ := MatchVarassign(text) - if m && value != "\\" { + _, _, _, _, _, valueAlign, value, _, _ := MatchVarassign(text) + if value != "\\" { oldWidth = tabWidth(valueAlign) } } @@ -336,7 +337,7 @@ func (fix *Autofix) Realign(mkline MkLin for _, rawLine := range fix.line.raw[1:] { _, comment, space := match2(rawLine.textnl, `^(#?)([ \t]*)`) width := tabWidth(comment + space) - if (oldWidth == 0 || width < oldWidth) && width >= 8 && rawLine.textnl != "\n" { + if (oldWidth == 0 || width < oldWidth) && width >= 8 { oldWidth = width } if !matches(space, `^\t* {0,7}$`) { Index: pkgsrc/pkgtools/pkglint/files/line_test.go diff -u pkgsrc/pkgtools/pkglint/files/line_test.go:1.16 pkgsrc/pkgtools/pkglint/files/line_test.go:1.17 --- pkgsrc/pkgtools/pkglint/files/line_test.go:1.16 Mon Dec 17 00:15:39 2018 +++ pkgsrc/pkgtools/pkglint/files/line_test.go Thu Feb 21 22:49:03 2019 @@ -12,6 +12,23 @@ func (s *Suite) Test_RawLine_String(c *c c.Check(line.raw[0].String(), equals, "123:text\n") } +func (s *Suite) Test_NewLine__assertion(c *check.C) { + t := s.Init(c) + + t.ExpectPanic( + func() { NewLine("filename", 123, "text", nil) }, + "Pkglint internal error: use NewLineMulti for creating a Line with no RawLine attached to it") +} + +func (s *Suite) Test_Line_IsMultiline(c *check.C) { + t := s.Init(c) + + t.Check(t.NewLine("filename", 123, "text").IsMultiline(), equals, false) + t.Check(NewLineEOF("filename").IsMultiline(), equals, false) + + t.Check(NewLineMulti("filename", 123, 125, "text", nil).IsMultiline(), equals, true) +} + // In case of a fatal error, pkglint quits in a controlled manner, // and the trace log shows where the fatal error happened. func (s *Suite) Test_Line_Fatalf__trace(c *check.C) { Index: pkgsrc/pkgtools/pkglint/files/toplevel.go diff -u pkgsrc/pkgtools/pkglint/files/toplevel.go:1.16 pkgsrc/pkgtools/pkglint/files/toplevel.go:1.17 --- pkgsrc/pkgtools/pkglint/files/toplevel.go:1.16 Mon Dec 17 00:15:39 2018 +++ pkgsrc/pkgtools/pkglint/files/toplevel.go Thu Feb 21 22:49:03 2019 @@ -29,8 +29,8 @@ func CheckdirToplevel(dir string) { if G.Opts.Recursive { if G.Opts.CheckGlobal { - G.Pkgsrc.UsedLicenses = make(map[string]bool) - G.Pkgsrc.Hashes = make(map[string]*Hash) + G.UsedLicenses = make(map[string]bool) + G.Hashes = make(map[string]*Hash) } G.Todo = append(append([]string(nil), ctx.subdirs...), G.Todo...) } Index: pkgsrc/pkgtools/pkglint/files/autofix_test.go diff -u pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.17 pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.18 --- pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.17 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/autofix_test.go Thu Feb 21 22:49:03 2019 @@ -210,6 +210,35 @@ func (s *Suite) Test_Autofix_ReplaceRege "+\tYXXXX") } +// When an autofix replaces text, it does not touch those +// lines that have been inserted before since these are +// usually already correct. +func (s *Suite) Test_Autofix_ReplaceAfter__after_inserting_a_line(c *check.C) { + t := s.Init(c) + + t.SetUpCommandLine("--show-autofix") + line := t.NewLine("filename", 5, "initial text") + + fix := line.Autofix() + fix.Notef("Inserting a line.") + fix.InsertBefore("line before") + fix.InsertAfter("line after") + fix.Apply() + + fix.Notef("Replacing text.") + fix.Replace("l", "L") + fix.ReplaceRegex(`i`, "I", 1) + fix.Apply() + + t.CheckOutputLines( + "NOTE: filename:5: Inserting a line.", + "AUTOFIX: filename:5: Inserting a line \"line before\" before this line.", + "AUTOFIX: filename:5: Inserting a line \"line after\" after this line.", + "NOTE: filename:5: Replacing text.", + "AUTOFIX: filename:5: Replacing \"l\" with \"L\".", + "AUTOFIX: filename:5: Replacing \"i\" with \"I\".") +} + func (s *Suite) Test_SaveAutofixChanges(c *check.C) { t := s.Init(c) @@ -627,6 +656,35 @@ func (s *Suite) Test_Autofix__suppress_u "+\tTODO") } +// With the default command line options, this warning is printed. +// With the --show-autofix option this warning is NOT printed since it +// cannot be fixed automatically. +func (s *Suite) Test_Autofix_Anyway__options(c *check.C) { + t := s.Init(c) + + t.SetUpCommandLine("--show-autofix") + + line := t.NewLine("filename", 3, "") + fix := line.Autofix() + + fix.Warnf("This autofix doesn't match.") + fix.Replace("doesn't match", "") + fix.Anyway() + fix.Apply() + + t.CheckOutputEmpty() + + t.SetUpCommandLine() + + fix.Warnf("This autofix doesn't match.") + fix.Replace("doesn't match", "") + fix.Anyway() + fix.Apply() + + t.CheckOutputLines( + "WARN: filename:3: This autofix doesn't match.") +} + // If an Autofix doesn't do anything, it must not log any diagnostics. func (s *Suite) Test_Autofix__noop_replace(c *check.C) { t := s.Init(c) @@ -809,6 +867,210 @@ func (s *Suite) Test_Autofix_Apply__expl "NOTE: README.txt:123: A note without fix.") } +func (s *Suite) Test_Autofix_Anyway__autofix_option(c *check.C) { + t := s.Init(c) + + t.SetUpCommandLine("--autofix") + line := t.NewLine("filename", 5, "text") + + fix := line.Autofix() + fix.Notef("This line is quite short.") + fix.Replace("not found", "needle") + fix.Anyway() + fix.Apply() + + // The replacement text is not found, therefore the note should not be logged. + // Because of fix.Anyway, the note should be logged anyway. + // But because of the --autofix option, the note should not be logged. + // Therefore, in the end nothing is shown in this case. + t.CheckOutputEmpty() +} + +func (s *Suite) Test_Autofix_Anyway__autofix_and_show_autofix_options(c *check.C) { + t := s.Init(c) + + t.SetUpCommandLine("--autofix", "--show-autofix") + line := t.NewLine("filename", 5, "text") + + fix := line.Autofix() + fix.Notef("This line is quite short.") + fix.Replace("not found", "needle") + fix.Anyway() + fix.Apply() + + // The replacement text is not found, therefore the note should not be logged. + // Because of fix.Anyway, the note should be logged anyway. + // But because of the --autofix option, the note should not be logged. + // Even if the --show-autofix option is explicitly given, the note should not be logged. + // Therefore, in the end nothing is shown in this case. + t.CheckOutputEmpty() +} + +// The --autofix option normally suppresses the diagnostics and just logs +// the actual fixes. Adding the --show-autofix option logs both. +func (s *Suite) Test_Autofix_Apply__autofix_and_show_autofix_options(c *check.C) { + t := s.Init(c) + + t.SetUpCommandLine("--autofix", "--show-autofix") + line := t.NewLine("filename", 5, "text") + + fix := line.Autofix() + fix.Notef("This line is quite short.") + fix.Replace("text", "replacement") + fix.Apply() + + t.CheckOutputLines( + "NOTE: filename:5: This line is quite short.", + "AUTOFIX: filename:5: Replacing \"text\" with \"replacement\".") +} + +// Ensures that without explanations, the separator between the individual +// diagnostics are generated. +func (s *Suite) Test_Autofix_Apply__source_without_explain(c *check.C) { + t := s.Init(c) + + t.SetUpCommandLine("--source", "--explain", "--show-autofix") + line := t.NewLine("filename", 5, "text") + + fix := line.Autofix() + fix.Notef("This line is quite short.") + fix.Replace("text", "replacement") + fix.Apply() + + fix.Warnf("Follow-up warning, separated.") + fix.Replace("replacement", "text again") + fix.Apply() + + t.CheckOutputLines( + "NOTE: filename:5: This line is quite short.", + "AUTOFIX: filename:5: Replacing \"text\" with \"replacement\".", + "-\ttext", + "+\treplacement", + "", + "WARN: filename:5: Follow-up warning, separated.", + "AUTOFIX: filename:5: Replacing \"replacement\" with \"text again\".", + "-\ttext", + "+\ttext again") +} + +func (s *Suite) Test_Autofix_Realign__wrong_line_type(c *check.C) { + t := s.Init(c) + + mklines := t.NewMkLines("file.mk", + MkRcsID, + ".if \\", + "${PKGSRC_RUN_TESTS}") + + mkline := mklines.mklines[1] + fix := mkline.Autofix() + + t.ExpectPanic( + func() { fix.Realign(mkline, 16) }, + "Pkglint internal error: Line must be a variable assignment.") +} + +func (s *Suite) Test_Autofix_Realign__short_continuation_line(c *check.C) { + t := s.Init(c) + + t.SetUpCommandLine("--autofix") + mklines := t.SetUpFileMkLines("file.mk", + MkRcsID, + "BUILD_DIRS= \\", + "\tdir \\", + "") + mkline := mklines.mklines[1] + fix := mkline.Autofix() + fix.Warnf("Line should be realigned.") + + // In this case realigning has no effect since the oldWidth == 8, + // which counts as "sufficiently intentional not to be modified". + fix.Realign(mkline, 16) + + mklines.SaveAutofixChanges() + + t.CheckOutputEmpty() + t.CheckFileLines("file.mk", + MkRcsID, + "BUILD_DIRS= \\", + "\tdir \\", + "") +} + +func (s *Suite) Test_Autofix_Realign__multiline_indented_with_spaces(c *check.C) { + t := s.Init(c) + + t.SetUpCommandLine("--autofix") + mklines := t.SetUpFileMkLines("file.mk", + MkRcsID, + "BUILD_DIRS= \\", + "\t dir1 \\", + "\t\tdir2 \\", + " ") // Trailing whitespace is not fixed by Autofix.Realign. + + mkline := mklines.mklines[1] + + fix := mkline.Autofix() + fix.Warnf("Warning.") + fix.Realign(mkline, 16) + fix.Apply() + + mklines.SaveAutofixChanges() + + t.CheckOutputLines( + "AUTOFIX: ~/file.mk:3: Replacing indentation \"\\t \" with \"\\t\\t\".") + t.CheckFileLines("file.mk", + MkRcsID, + "BUILD_DIRS= \\", + "\t\tdir1 \\", + "\t\tdir2 \\", + " ") +} + +// Just for branch coverage. +func (s *Suite) Test_Autofix_setDiag__no_testing_mode(c *check.C) { + t := s.Init(c) + + line := t.NewLine("file.mk", 123, "text") + + G.Testing = false + + fix := line.Autofix() + fix.Notef("Note.") + fix.Replace("from", "to") + fix.Apply() + + t.CheckOutputEmpty() +} + +func (s *Suite) Test_Autofix_setDiag__bad_call_sequence(c *check.C) { + t := s.Init(c) + + line := t.NewLine("file.mk", 123, "text") + fix := line.Autofix() + fix.Notef("Note.") + + t.ExpectPanic( + func() { fix.Notef("Note 2.") }, + "Pkglint internal error: Autofix can only have a single diagnostic.") + + fix.level = nil // To cover the second assertion. + t.ExpectPanic( + func() { fix.Notef("Note 2.") }, + "Pkglint internal error: Autofix can only have a single diagnostic.") +} + +func (s *Suite) Test_Autofix_assertRealLine(c *check.C) { + t := s.Init(c) + + line := NewLineEOF("filename.mk") + fix := line.Autofix() + fix.Warnf("Warning.") + + t.ExpectPanic( + func() { fix.Replace("from", "to") }, + "Pkglint internal error: Cannot autofix this line since it is not a real line.") +} + func (s *Suite) Test_SaveAutofixChanges__file_removed(c *check.C) { t := s.Init(c) Index: pkgsrc/pkgtools/pkglint/files/category.go diff -u pkgsrc/pkgtools/pkglint/files/category.go:1.17 pkgsrc/pkgtools/pkglint/files/category.go:1.18 --- pkgsrc/pkgtools/pkglint/files/category.go:1.17 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/category.go Thu Feb 21 22:49:03 2019 @@ -123,7 +123,7 @@ func CheckdirCategory(dir string) { } fRest = fRest[1:] - } else if len(mRest) > 0 && (len(fRest) == 0 || mRest[0].name < fRest[0]) { + } else if len(fRest) == 0 || mRest[0].name < fRest[0] { if !fCheck[mRest[0].name] { fix := mRest[0].line.Autofix() fix.Errorf("%q exists in the Makefile but not in the file system.", mRest[0].name) Index: pkgsrc/pkgtools/pkglint/files/buildlink3.go diff -u pkgsrc/pkgtools/pkglint/files/buildlink3.go:1.19 pkgsrc/pkgtools/pkglint/files/buildlink3.go:1.20 --- pkgsrc/pkgtools/pkglint/files/buildlink3.go:1.19 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/buildlink3.go Thu Feb 21 22:49:03 2019 @@ -205,7 +205,8 @@ func (ck *Buildlink3Checker) checkVarass } func (ck *Buildlink3Checker) checkVaruseInPkgbase(pkgbase string, pkgbaseLine MkLine) { - for _, token := range pkgbaseLine.ValueTokens() { + tokens, _ := pkgbaseLine.ValueTokens() + for _, token := range tokens { if token.Varuse == nil { continue } Index: pkgsrc/pkgtools/pkglint/files/logging.go diff -u pkgsrc/pkgtools/pkglint/files/logging.go:1.19 pkgsrc/pkgtools/pkglint/files/logging.go:1.20 --- pkgsrc/pkgtools/pkglint/files/logging.go:1.19 Fri Dec 21 08:05:24 2018 +++ pkgsrc/pkgtools/pkglint/files/logging.go Thu Feb 21 22:49:03 2019 @@ -4,6 +4,7 @@ import ( "bytes" "io" "netbsd.org/pkglint/histogram" + "netbsd.org/pkglint/textproc" "path" ) @@ -51,6 +52,7 @@ var ( var dummyLine = NewLineMulti("", 0, 0, "", nil) +// IsAutofix returns whether one of the --show-autofix or --autofix options is active. func (l *Logger) IsAutofix() bool { return l.Opts.Autofix || l.Opts.ShowAutofix } // Relevant decides and remembers whether the given diagnostic is relevant and should be logged. @@ -186,8 +188,17 @@ func (l *Logger) Logf(level *LogLevel, f return } - if G.Testing && format != AutofixFormat && !hasSuffix(format, ": %s") && !hasSuffix(format, ". %s") { - G.Assertf(hasSuffix(format, "."), "Diagnostic format %q must end in a period.", format) + if G.Testing && format != AutofixFormat { + if textproc.Alpha.Contains(format[0]) { + G.Assertf( + textproc.Upper.Contains(format[0]), + "Diagnostic %q must start with an uppercase letter.", + format) + } + + if !hasSuffix(format, ": %s") && !hasSuffix(format, ". %s") { + G.Assertf(hasSuffix(format, "."), "Diagnostic format %q must end in a period.", format) + } } if filename != "" { Index: pkgsrc/pkgtools/pkglint/files/buildlink3_test.go diff -u pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.26 pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.27 --- pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.26 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/buildlink3_test.go Thu Feb 21 22:49:03 2019 @@ -493,6 +493,192 @@ func (s *Suite) Test_CheckLinesBuildlink "WARN: buildlink3.mk:3: Please replace \"${LICENSE}\" with a simple string (also in other variables in this file).") } +func (s *Suite) Test_Buildlink3Checker_checkMainPart__if_else_endif(c *check.C) { + t := s.Init(c) + + t.SetUpPackage("category/package", + "PKGNAME=\tpackage-1.0") + t.CreateFileDummyBuildlink3("category/package/buildlink3.mk", + ".if ${X11_TYPE} == modular", + ".else", + ".endif") + + G.Check(t.File("category/package")) + + t.CheckOutputEmpty() +} + +func (s *Suite) Test_Buildlink3Checker_checkVarassign__dependencies_with_path(c *check.C) { + t := s.Init(c) + + t.SetUpPackage("category/package", + "PKGNAME=\tpackage-1.0") + t.CreateFileDummyBuildlink3("category/package/buildlink3.mk", + "BUILDLINK_ABI_DEPENDS.package+=\tpackage>=1.0:../../category/package", + "BUILDLINK_API_DEPENDS.package+=\tpackage>=1.5:../../category/package") + + G.Check(t.File("category/package")) + + // Since these dependencies are malformed, they are not processed further. + // Doing that would reveal that the ABI version should be higher than the API version. + t.CheckOutputLines( + "WARN: ~/category/package/buildlink3.mk:12: "+ + "Invalid dependency pattern \"package>=1.0:../../category/package\".", + "WARN: ~/category/package/buildlink3.mk:13: "+ + "Invalid dependency pattern \"package>=1.5:../../category/package\".") +} + +func (s *Suite) Test_Buildlink3Checker_checkVarassign__abi_without_api(c *check.C) { + t := s.Init(c) + + t.SetUpPackage("category/package", + "PKGNAME=\tpackage-1.0") + // t.CreateFileDummyBuildlink3() cannot be used here since it always adds an API line. + t.CreateFileLines("category/package/buildlink3.mk", + MkRcsID, + "", + "BUILDLINK_TREE+=\tpackage", + "", + ".if !defined(PACKAGE_BUILDLINK3_MK)", + "PACKAGE_BUILDLINK3_MK:=", + "", + "BUILDLINK_PKGSRCDIR.package?=\t../../category/package", + "BUILDLINK_DEPMETHOD.package?=\tbuild", + "BUILDLINK_ABI_DEPENDS.package+=\tpackage>=1.0", + "", + ".endif # PACKAGE_BUILDLINK3_MK", + "", + "BUILDLINK_TREE+=\t-package") + + G.Check(t.File("category/package")) + + // Since only ABI is given but not API, the check for ABI >= API cannot be done. + t.CheckOutputLines( + "WARN: ~/category/package/buildlink3.mk:13: Definition of BUILDLINK_API_DEPENDS is missing.") +} + +func (s *Suite) Test_Buildlink3Checker_checkVarassign__abi_and_api_with_variables(c *check.C) { + t := s.Init(c) + + t.SetUpPackage("category/package", + "PKGNAME=\tpackage-1.0") + t.CreateFileDummyBuildlink3("category/package/buildlink3.mk", + "BUILDLINK_ABI_DEPENDS.package+=\tpackage>=${ABI_VERSION}", + "BUILDLINK_API_DEPENDS.package+=\tpackage>=${API_VERSION}", + "", + "ABI_VERSION=\t1.0", + "API_VERSION=\t1.5") + + G.Check(t.File("category/package")) + + // Since the versions contain variable references, pkglint refuses to compare them. + t.CheckOutputEmpty() +} + +func (s *Suite) Test_Buildlink3Checker_checkVarassign__api_with_variable(c *check.C) { + t := s.Init(c) + + t.SetUpPackage("category/package", + "PKGNAME=\tpackage-1.0") + t.CreateFileDummyBuildlink3("category/package/buildlink3.mk", + "BUILDLINK_ABI_DEPENDS.package+=\tpackage>=1.0", + "BUILDLINK_API_DEPENDS.package+=\tpackage>=${API_VERSION}", + "", + "API_VERSION=\t1.5") + + G.Check(t.File("category/package")) + + // Since the versions contain variable references, pkglint refuses to compare them. + t.CheckOutputEmpty() +} + +func (s *Suite) Test_Buildlink3Checker_checkVarassign__abi_and_api_with_pattern(c *check.C) { + t := s.Init(c) + + t.SetUpPackage("category/package", + "PKGNAME=\tpackage-1.0") + t.CreateFileDummyBuildlink3("category/package/buildlink3.mk", + "BUILDLINK_ABI_DEPENDS.package+=\tpackage-1.*", + "BUILDLINK_API_DEPENDS.package+=\tpackage-2.*") + + G.Check(t.File("category/package")) + + // Since the versions do not contain lower bounds (they are package-1.* + // instead of package>=1), pkglint refuses to compare them. + t.CheckOutputEmpty() +} + +func (s *Suite) Test_Buildlink3Checker_checkVarassign__api_with_pattern(c *check.C) { + t := s.Init(c) + + t.SetUpPackage("category/package", + "PKGNAME=\tpackage-1.0") + t.CreateFileDummyBuildlink3("category/package/buildlink3.mk", + "BUILDLINK_ABI_DEPENDS.package+=\tpackage>=1", + "BUILDLINK_API_DEPENDS.package+=\tpackage-1.*") + + G.Check(t.File("category/package")) + + // Since the API version does not contain lower bounds (it is package-1.* + // instead of package>=1), pkglint refuses to compare the versions. + t.CheckOutputEmpty() +} + +func (s *Suite) Test_Buildlink3Checker_checkVarassign__other_variables(c *check.C) { + t := s.Init(c) + + t.SetUpPackage("category/package", + "PKGNAME=\tpackage-1.0") + t.CreateFileDummyBuildlink3("category/package/buildlink3.mk", + "BUILDLINK_TREE+=\tmistake", // Wrong, but doesn't happen in practice. + "", + "LDFLAGS.NetBSD+=\t-ldl", + "", + "BUILDLINK_DEPMETHOD.other+=\tbuild", + "", + "BUILDLINK_API_DEPENDS.other+=\tother>=3") + + G.Check(t.File("category/package")) + + // FIXME: Why is appending to LDFLAGS forbidden? It sounds useful. + t.CheckOutputLines( + "WARN: ~/category/package/buildlink3.mk:14: "+ + "The variable LDFLAGS.NetBSD may not be appended to in this file; "+ + "it would be ok in Makefile, Makefile.common, options.mk or *.mk.", + "WARN: ~/category/package/buildlink3.mk:16: "+ + "Only buildlink variables for \"package\", "+ + "not \"other\" may be set in this file.") +} + +// Just for branch coverage. +func (s *Suite) Test_Buildlink3Checker_Check__no_tracing(c *check.C) { + t := s.Init(c) + + t.SetUpPackage("category/package") + t.CreateFileDummyBuildlink3("category/package/buildlink3.mk") + t.DisableTracing() + + G.Check(t.File("category/package/buildlink3.mk")) + + t.CheckOutputEmpty() +} + +func (s *Suite) Test_Buildlink3Checker_checkSecondParagraph__missing_mkbase(c *check.C) { + t := s.Init(c) + + t.SetUpPackage("category/package", + "DISTNAME=\t# empty", + "PKGNAME=\t# empty, to force mkbase to be empty") + t.CreateFileDummyBuildlink3("category/package/buildlink3.mk") + + G.Check(t.File("category/package")) + + // There is no warning from buildlink3.mk about mismatched package names + // since that is only a follow-up error of being unable to parse the pkgbase. + t.CheckOutputLines( + "WARN: ~/category/package/Makefile:4: \"\" is not a valid package name.") +} + // Since the buildlink3 checker does not use MkLines.ForEach, it has to keep // track of the nesting depth of .if directives. func (s *Suite) Test_Buildlink3Checker_checkMainPart__nested_if(c *check.C) { Index: pkgsrc/pkgtools/pkglint/files/category_test.go diff -u pkgsrc/pkgtools/pkglint/files/category_test.go:1.18 pkgsrc/pkgtools/pkglint/files/category_test.go:1.19 --- pkgsrc/pkgtools/pkglint/files/category_test.go:1.18 Sun Jan 13 19:55:52 2019 +++ pkgsrc/pkgtools/pkglint/files/category_test.go Thu Feb 21 22:49:03 2019 @@ -130,6 +130,89 @@ func (s *Suite) Test_CheckdirCategory__s "ERROR: ~/category/Makefile:11: \"commented-mk-only\" exists in the Makefile but not in the file system.") } +func (s *Suite) Test_CheckdirCategory__only_in_Makefile(c *check.C) { + t := s.Init(c) + + t.SetUpPkgsrc() + t.SetUpVartypes() + t.CreateFileLines("mk/misc/category.mk") + t.CreateFileLines("category/both/Makefile") + t.CreateFileLines("category/Makefile", + MkRcsID, + "", + "COMMENT=\tCategory comment", + "", + "SUBDIR+=\tabove-only-in-makefile", + "SUBDIR+=\tboth", + "SUBDIR+=\tonly-in-makefile", + "", + ".include \"../mk/misc/category.mk\"") + + CheckdirCategory(t.File("category")) + + t.CheckOutputLines( + "ERROR: ~/category/Makefile:5: \"above-only-in-makefile\" exists in the Makefile "+ + "but not in the file system.", + "ERROR: ~/category/Makefile:7: \"only-in-makefile\" exists in the Makefile "+ + "but not in the file system.") +} + +func (s *Suite) Test_CheckdirCategory__only_in_file_system(c *check.C) { + t := s.Init(c) + + t.SetUpPkgsrc() + t.SetUpVartypes() + t.CreateFileLines("mk/misc/category.mk") + t.CreateFileLines("category/above-only-in-fs/Makefile") + t.CreateFileLines("category/both/Makefile") + t.CreateFileLines("category/only-in-fs/Makefile") + t.CreateFileLines("category/Makefile", + MkRcsID, + "", + "COMMENT=\tCategory comment", + "", + "SUBDIR+=\tboth", + "", + ".include \"../mk/misc/category.mk\"") + + CheckdirCategory(t.File("category")) + + t.CheckOutputLines( + "ERROR: ~/category/Makefile:5: \"above-only-in-fs\" exists in the file system "+ + "but not in the Makefile.", + "ERROR: ~/category/Makefile:6: \"only-in-fs\" exists in the file system "+ + "but not in the Makefile.") +} + +func (s *Suite) Test_CheckdirCategory__recursive(c *check.C) { + t := s.Init(c) + + t.SetUpCommandLine("-r") + t.SetUpPkgsrc() + t.SetUpVartypes() + t.CreateFileLines("mk/misc/category.mk") + t.CreateFileLines("category/commented/Makefile") + t.CreateFileLines("category/package/Makefile") + t.CreateFileLines("category/Makefile", + MkRcsID, + "", + "COMMENT=\tCategory comment", + "", + "#SUBDIR+=\tcommented\t# reason", + "SUBDIR+=\tpackage", + "", + ".include \"../mk/misc/category.mk\"") + t.Chdir("category") + + CheckdirCategory(".") + + t.CheckOutputEmpty() + t.Check( + G.Todo, + deepEquals, + []string{"./package"}) +} + // Ensures that a directory in the file system can be added at the very // end of the SUBDIR list. This case takes a different code path than // an addition in the middle. @@ -187,6 +270,61 @@ func (s *Suite) Test_CheckdirCategory__i "NOTE: ~/category/Makefile:5: This variable value should be aligned with tabs, not spaces, to column 17.") } +func (s *Suite) Test_CheckdirCategory__comment_at_the_top(c *check.C) { + t := s.Init(c) + + t.SetUpPkgsrc() + t.SetUpVartypes() + t.CreateFileLines("mk/misc/category.mk") + t.CreateFileLines("category/package/Makefile") + t.CreateFileLines("category/Makefile", + MkRcsID, + "", + "# This category collects all programs that don't fit anywhere else.", + "", + "COMMENT=\tCategory comment", + "", + "SUBDIR+=\tpackage", + "", + ".include \"../mk/misc/category.mk\"") + + CheckdirCategory(t.File("category")) + + // FIXME: Wow. These are quite a few warnings and errors, just because there is + // an additional comment above the COMMENT definition. + t.CheckOutputLines( + "ERROR: ~/category/Makefile:3: COMMENT= line expected.", + "NOTE: ~/category/Makefile:2: Empty line expected after this line.", + "ERROR: ~/category/Makefile:3: SUBDIR+= line or empty line expected.", + "ERROR: ~/category/Makefile:3: \"package\" exists in the file system but not in the Makefile.", + "NOTE: ~/category/Makefile:2: Empty line expected after this line.", + "WARN: ~/category/Makefile:3: This line should contain the following text: .include \"../mk/misc/category.mk\"", + "ERROR: ~/category/Makefile:3: The file should end here.") +} + +func (s *Suite) Test_CheckdirCategory__unexpected_EOF_while_reading_SUBDIR(c *check.C) { + t := s.Init(c) + + t.SetUpPkgsrc() + t.SetUpVartypes() + t.CreateFileLines("mk/misc/category.mk") + t.CreateFileLines("category/package/Makefile") + t.CreateFileLines("category/Makefile", + MkRcsID, + "", + "COMMENT=\tCategory comment", + "", + "SUBDIR+=\tpackage") + + CheckdirCategory(t.File("category")) + + // Doesn't happen in practice since categories are created very seldom. + t.CheckOutputLines( + "NOTE: ~/category/Makefile:5: Empty line expected after this line.", + "WARN: ~/category/Makefile:EOF: This line should contain the following text: "+ + ".include \"../mk/misc/category.mk\"") +} + func (s *Suite) Test_CheckdirCategory__no_Makefile(c *check.C) { t := s.Init(c) Index: pkgsrc/pkgtools/pkglint/files/pkgsrc.go diff -u pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.18 pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.19 --- pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.18 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/pkgsrc.go Thu Feb 21 22:49:03 2019 @@ -34,18 +34,15 @@ type Pkgsrc struct { LastChange map[string]*Change // listVersions map[string][]string // See ListVersions - // Variables that may be overridden by the pkgsrc user. Used for checking BUILD_DEFS. + // Variables that may be overridden by the pkgsrc user. + // They are typically defined in mk/defaults/mk.conf. + // + // Whenever a package uses such a variable, it must add the variable name + // to BUILD_DEFS. UserDefinedVars Scope Deprecated map[string]string // vartypes map[string]*Vartype // varcanon => type - - // TODO: The Hashes and UsedLicenses are modified after the initialization. - // This contradicts the expectation that all pkgsrc data is constant. - // These two fields should probably be moved to the Pkglint type. - - Hashes map[string]*Hash // Maps "alg:filename" => hash (inter-package check). - UsedLicenses map[string]bool // Maps "license name" => true (inter-package check). } func NewPkgsrc(dir string) *Pkgsrc { @@ -62,9 +59,7 @@ func NewPkgsrc(dir string) *Pkgsrc { make(map[string][]string), NewScope(), make(map[string]string), - make(map[string]*Vartype), - nil, // Only initialized when pkglint is run for a whole pkgsrc installation - nil} + make(map[string]*Vartype)} return &src } @@ -222,7 +217,7 @@ func (src *Pkgsrc) ListVersions(category // written without dots, which leads to ambiguities: // // databases/postgresql: 94 < 95 < 96 < 10 < 11 - // lang/go: go19 < go110 < go111 < go2 + // lang/go: 19 < 110 < 111 < 2 keys := make(map[string]int) for _, name := range names { if m, pkgbase, versionStr := match2(name, `^(\D+)(\d+)$`); m { @@ -240,9 +235,7 @@ func (src *Pkgsrc) ListVersions(category } sort.SliceStable(names, func(i, j int) bool { - // TODO: Check if this Less implementation is really transitive. - // examples: ps ps5 ps10 ps96 pq px - if keyI, keyJ := keys[names[i]], keys[names[j]]; keyI != 0 && keyJ != 0 { + if keyI, keyJ := keys[names[i]], keys[names[j]]; keyI != keyJ { return keyI < keyJ } return naturalLess(names[i], names[j]) @@ -258,7 +251,7 @@ func (src *Pkgsrc) ListVersions(category } func (src *Pkgsrc) checkToplevelUnusedLicenses() { - usedLicenses := src.UsedLicenses + usedLicenses := G.UsedLicenses if usedLicenses == nil { return } @@ -827,6 +820,9 @@ func (src *Pkgsrc) addBuildDefs(varnames } } +// IsBuildDef returns whether the given variable is automatically added +// to BUILD_DEFS by the pkgsrc infrastructure. In such a case, the +// package doesn't need to add the variable to BUILD_DEFS itself. func (src *Pkgsrc) IsBuildDef(varname string) bool { return src.buildDefs[varname] } Index: pkgsrc/pkgtools/pkglint/files/check_test.go diff -u pkgsrc/pkgtools/pkglint/files/check_test.go:1.33 pkgsrc/pkgtools/pkglint/files/check_test.go:1.34 --- pkgsrc/pkgtools/pkglint/files/check_test.go:1.33 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/check_test.go Thu Feb 21 22:49:03 2019 @@ -301,6 +301,15 @@ func (t *Tester) SetUpPackage(pkgpath st PlistRcsID, "bin/program") + // Because the package Makefile includes this file, the check for the + // correct ordering of variables is skipped. As of February 2019, the + // SetupPackage function does not insert the custom variables in the + // correct position. To prevent the tests from having to mention the + // unrelated warnings about the variable order, that check is suppressed + // here. + t.CreateFileLines(pkgpath+"/suppress-varorder.mk", + MkRcsID) + // This distinfo file contains dummy hashes since pkglint cannot check the // distfiles hashes anyway. It can only check the hashes for the patches. t.CreateFileLines(pkgpath+"/distinfo", @@ -323,7 +332,8 @@ func (t *Tester) SetUpPackage(pkgpath st "HOMEPAGE=\t# none", "COMMENT=\tDummy package", "LICENSE=\t2-clause-bsd", - ""} + "", + ".include \"suppress-varorder.mk\""} for len(mlines) < 19 { mlines = append(mlines, "# empty") } @@ -665,9 +675,6 @@ func (t *Tester) CheckOutputLines(expect // // This is useful when stepping through the code, especially // in combination with SetUpCommandLine("--debug"). -// -// In JetBrains GoLand, the tracing output is suppressed after the first -// failed check, see https://youtrack.jetbrains.com/issue/GO-6154. func (t *Tester) EnableTracing() { G.out = NewSeparatorWriter(io.MultiWriter(os.Stdout, &t.stdout)) trace.Out = os.Stdout @@ -677,8 +684,9 @@ func (t *Tester) EnableTracing() { // EnableTracingToLog enables the tracing and writes the tracing output // to the test log that can be examined with Tester.Output. func (t *Tester) EnableTracingToLog() { - t.EnableTracing() + G.out = NewSeparatorWriter(&t.stdout) trace.Out = &t.stdout + trace.Tracing = true } // EnableSilentTracing enables tracing mode but discards any tracing output. Index: pkgsrc/pkgtools/pkglint/files/distinfo.go diff -u pkgsrc/pkgtools/pkglint/files/distinfo.go:1.27 pkgsrc/pkgtools/pkglint/files/distinfo.go:1.28 --- pkgsrc/pkgtools/pkglint/files/distinfo.go:1.27 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/distinfo.go Thu Feb 21 22:49:03 2019 @@ -124,7 +124,7 @@ func (ck *distinfoLinesChecker) checkAlg "", "If the patches directory looks wrong, pkglint needs to be improved.") - case algorithms != "SHA1, RMD160, Size" && algorithms != "SHA1, RMD160, SHA512, Size": + case algorithms != "SHA1, RMD160, SHA512, Size": line.Errorf("Expected SHA1, RMD160, SHA512, Size checksums for %q, got %s.", filename, algorithms) } } @@ -144,7 +144,7 @@ func (ck *distinfoLinesChecker) checkUnr for _, file := range patchFiles { patchName := file.Name() if file.Mode().IsRegular() && !ck.patches[patchName] && hasPrefix(patchName, "patch-") { - ck.distinfoLines.Errorf("patch %q is not recorded. Run %q.", + ck.distinfoLines.Errorf("Patch %q is not recorded. Run %q.", cleanpath(relpath(path.Dir(ck.distinfoLines.FileName), G.Pkg.File(ck.patchdir+"/"+patchName))), bmake("makepatchsum")) } @@ -161,7 +161,7 @@ func (ck *distinfoLinesChecker) checkGlo return } - hashes := G.Pkgsrc.Hashes + hashes := G.Hashes if hashes == nil { return } @@ -174,25 +174,23 @@ func (ck *distinfoLinesChecker) checkGlo return } - if hashes != nil && !hasPrefix(filename, "patch-") { - key := alg + ":" + filename - otherHash := hashes[key] - - // See https://github.com/golang/go/issues/29802 - hashBytes := make([]byte, hex.DecodedLen(len(hash))) - _, err := hex.Decode(hashBytes, []byte(hash)) - if err != nil { - line.Errorf("The %s hash for %s contains a non-hex character.", alg, filename) - } - - if otherHash != nil { - if !bytes.Equal(otherHash.hash, hashBytes) { - line.Errorf("The %s hash for %s is %s, which conflicts with %s in %s.", - alg, filename, hash, hex.EncodeToString(otherHash.hash), line.RefToLocation(otherHash.location)) - } - } else { - hashes[key] = &Hash{hashBytes, line.Location} + key := alg + ":" + filename + otherHash := hashes[key] + + // See https://github.com/golang/go/issues/29802 + hashBytes := make([]byte, hex.DecodedLen(len(hash))) + _, err := hex.Decode(hashBytes, []byte(hash)) + if err != nil { + line.Errorf("The %s hash for %s contains a non-hex character.", alg, filename) + } + + if otherHash != nil { + if !bytes.Equal(otherHash.hash, hashBytes) { + line.Errorf("The %s hash for %s is %s, which conflicts with %s in %s.", + alg, filename, hash, hex.EncodeToString(otherHash.hash), line.RefToLocation(otherHash.location)) } + } else { + hashes[key] = &Hash{hashBytes, line.Location} } } @@ -244,16 +242,3 @@ func computePatchSha1Hex(patchFilename s } return sprintf("%x", hasher.Sum(nil)), nil } - -func AutofixDistinfo(oldSha1, newSha1 string) { - distinfoFilename := G.Pkg.File(G.Pkg.DistinfoFile) - if lines := Load(distinfoFilename, NotEmpty|LogErrors); lines != nil { - for _, line := range lines.Lines { - fix := line.Autofix() - fix.Warnf(SilentAutofixFormat) - fix.Replace(oldSha1, newSha1) - fix.Apply() - } - SaveAutofixChanges(lines) - } -} Index: pkgsrc/pkgtools/pkglint/files/distinfo_test.go diff -u pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.24 pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.25 --- pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.24 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/distinfo_test.go Thu Feb 21 22:49:03 2019 @@ -34,6 +34,65 @@ func (s *Suite) Test_CheckLinesDistinfo( "WARN: distinfo:9: Patch file \"patch-nonexistent\" does not exist in directory \"patches\".") } +func (s *Suite) Test_CheckLinesDistinfo__nonexistent_distfile_called_patch(c *check.C) { + t := s.Init(c) + + t.Chdir("category/package") + lines := t.SetUpFileLines("distinfo", + RcsID, + "", + "MD5 (patch-5.3.tar.gz) = 12345678901234567890123456789012", + "SHA1 (patch-5.3.tar.gz) = 1234567890123456789012345678901234567890") + G.Pkg = NewPackage(".") + + CheckLinesDistinfo(lines) + + // Even though the filename starts with "patch-" and therefore looks like + // a patch, it is a normal distfile because it has other hash algorithms + // than exactly SHA1. + t.CheckOutputLines( + "ERROR: distinfo:EOF: Expected SHA1, RMD160, SHA512, Size checksums " + + "for \"patch-5.3.tar.gz\", got MD5, SHA1.") +} + +func (s *Suite) Test_CheckLinesDistinfo__wrong_distfile_algorithms(c *check.C) { + t := s.Init(c) + + t.Chdir("category/package") + lines := t.SetUpFileLines("distinfo", + RcsID, + "", + "MD5 (distfile.tar.gz) = 12345678901234567890123456789012", + "SHA1 (distfile.tar.gz) = 1234567890123456789012345678901234567890") + + CheckLinesDistinfo(lines) + + t.CheckOutputLines( + "ERROR: distinfo:EOF: Expected SHA1, RMD160, SHA512, Size checksums " + + "for \"distfile.tar.gz\", got MD5, SHA1.") +} + +func (s *Suite) Test_CheckLinesDistinfo__wrong_patch_algorithms(c *check.C) { + t := s.Init(c) + + t.SetUpPackage("category/package") + t.Chdir("category/package") + t.CreateFileDummyPatch("patches/patch-aa") + t.SetUpFileLines("distinfo", + RcsID, + "", + "MD5 (patch-aa) = 12345678901234567890123456789012", + "SHA1 (patch-aa) = 1234567890123456789012345678901234567890") + + G.Check(".") + + t.CheckOutputLines( + "ERROR: distinfo:4: SHA1 hash of patches/patch-aa differs "+ + "(distinfo has 1234567890123456789012345678901234567890, "+ + "patch file has ebbf34b0641bcb508f17d5a27f2bf2a536d810ac).", + "ERROR: distinfo:EOF: Expected SHA1 hash for patch-aa, got MD5, SHA1.") +} + // When checking the complete pkgsrc tree, pkglint has all information it needs // to check whether different packages use the same distfile but require // different hashes for it. @@ -94,8 +153,8 @@ func (s *Suite) Test_distinfoLinesChecke "7 errors and 1 warning found.") // Ensure that hex.DecodeString does not waste memory here. - t.Check(len(G.Pkgsrc.Hashes["SHA512:distfile-1.0.tar.gz"].hash), equals, 8) - t.Check(cap(G.Pkgsrc.Hashes["SHA512:distfile-1.0.tar.gz"].hash), equals, 8) + t.Check(len(G.Hashes["SHA512:distfile-1.0.tar.gz"].hash), equals, 8) + t.Check(cap(G.Hashes["SHA512:distfile-1.0.tar.gz"].hash), equals, 8) } func (s *Suite) Test_CheckLinesDistinfo__uncommitted_patch(c *check.C) { @@ -136,8 +195,8 @@ func (s *Suite) Test_CheckLinesDistinfo_ G.checkdirPackage(".") t.CheckOutputLines( - "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\".") + "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\".") } // The distinfo file and the patches are usually placed in the package @@ -167,7 +226,7 @@ func (s *Suite) Test_CheckLinesDistinfo_ "(distinfo has ..., patch file has ebbf34b0641bcb508f17d5a27f2bf2a536d810ac).", "WARN: ../../other/common/distinfo:4: Patch file \"patch-only-in-distinfo\" "+ "does not exist in directory \"../../devel/patches/patches\".", - "ERROR: ../../other/common/distinfo: patch \"../../devel/patches/patches/patch-only-in-patches\" "+ + "ERROR: ../../other/common/distinfo: Patch \"../../devel/patches/patches/patch-only-in-patches\" "+ "is not recorded. Run \""+confMake+" makepatchsum\".") } @@ -197,7 +256,7 @@ func (s *Suite) Test_CheckLinesDistinfo_ "(distinfo has ..., patch file has ebbf34b0641bcb508f17d5a27f2bf2a536d810ac).", "WARN: ../../other/common/distinfo:4: Patch file \"patch-only-in-distinfo\" "+ "does not exist in directory \"patches\".", - "ERROR: ../../other/common/distinfo: patch \"patches/patch-only-in-patches\" "+ + "ERROR: ../../other/common/distinfo: Patch \"patches/patch-only-in-patches\" "+ "is not recorded. Run \""+confMake+" makepatchsum\".") } @@ -281,6 +340,26 @@ func (s *Suite) Test_CheckLinesDistinfo_ t.CheckOutputEmpty() } +func (s *Suite) Test_distinfoLinesChecker_checkUncommittedPatch(c *check.C) { + t := s.Init(c) + + t.SetUpPackage("category/package") + t.Chdir("category/package") + t.CreateFileDummyPatch("patches/patch-aa") + t.CreateFileLines("CVS/Entries", + "/distinfo/...") + t.CreateFileLines("patches/CVS/Entries", + "/patch-aa/...") + t.SetUpFileLines("distinfo", + RcsID, + "", + "SHA1 (patch-aa) = ebbf34b0641bcb508f17d5a27f2bf2a536d810ac") + + G.checkdirPackage(".") + + t.CheckOutputEmpty() +} + func (s *Suite) Test_distinfoLinesChecker_checkPatchSha1(c *check.C) { t := s.Init(c) Index: pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.24 pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.25 --- pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.24 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go Thu Feb 21 22:49:03 2019 @@ -34,18 +34,22 @@ func (s *Suite) Test_MkLineChecker_Check t.SetUpVartypes() t.CreateFileLines("mk/bsd.prefs.mk") + t.CreateFileLines("mk/bsd.fast.prefs.mk") mklines := t.SetUpFileMkLines("category/package/buildlink3.mk", - ".include \"../../mk/bsd.prefs.mk\"") + MkRcsID, + ".include \"../../mk/bsd.prefs.mk\"", + ".include \"../../mk/bsd.fast.prefs.mk\"") + // If the buildlink3.mk file doesn't actually exist, resolving the // relative path fails since that depends on the actual file system, // not on syntactical paths; see os.Stat in CheckRelativePath. // // TODO: Refactor relpath to be independent of a filesystem. - MkLineChecker{mklines.mklines[0]}.Check() + mklines.Check() t.CheckOutputLines( - "NOTE: ~/category/package/buildlink3.mk:1: For efficiency reasons, " + + "NOTE: ~/category/package/buildlink3.mk:2: For efficiency reasons, " + "please include bsd.fast.prefs.mk instead of bsd.prefs.mk.") } @@ -130,7 +134,7 @@ func (s *Suite) Test_MkLineChecker_check "", ".for var in a b c", ".endfor", - ".undef var") + ".undef var unrelated") mklines.Check() @@ -176,6 +180,67 @@ func (s *Suite) Test_MkLineChecker_check "ERROR: filename.mk:12: Invalid variable name \"${VAR}\".") } +func (s *Suite) Test_MkLineChecker_checkDirectiveEnd__ending_comments(c *check.C) { + t := s.Init(c) + + t.SetUpVartypes() + mklines := t.NewMkLines("opsys.mk", + MkRcsID, + "", + ".for i in 1 2 3 4 5", + ". if ${OPSYS} == NetBSD", + ". if ${MACHINE_ARCH} == x86_64", + ". if ${OS_VERSION:M8.*}", + ". endif # MACHINE_ARCH", // Wrong, should be OS_VERSION. + ". endif # OS_VERSION", // Wrong, should be MACHINE_ARCH. + ". endif # OPSYS", // Correct. + ".endfor # j", // Wrong, should be i. + "", + ".if ${PKG_OPTIONS:Moption}", + ".endif # option", // Correct. + "", + ".if ${PKG_OPTIONS:Moption}", + ".endif # opti", // This typo goes unnoticed since "opti" is a substring of the condition. + "", + ".if ${OPSYS} == NetBSD", + ".elif ${OPSYS} == FreeBSD", + ".endif # NetBSD", // Wrong, should be FreeBSD from the .elif. + "", + ".for ii in 1 2", + ". for jj in 1 2", + ". endfor # ii", // Note: a simple "i" would not generate a warning because it is found in the word "in". + ".endfor # ii") + + // See MkLineChecker.checkDirective + mklines.Check() + + t.CheckOutputLines( + "WARN: opsys.mk:7: Comment \"MACHINE_ARCH\" does not match condition \"${OS_VERSION:M8.*}\".", + "WARN: opsys.mk:8: Comment \"OS_VERSION\" does not match condition \"${MACHINE_ARCH} == x86_64\".", + "WARN: opsys.mk:10: Comment \"j\" does not match loop \"i in 1 2 3 4 5\".", + "WARN: opsys.mk:12: Unknown option \"option\".", + "WARN: opsys.mk:20: Comment \"NetBSD\" does not match condition \"${OPSYS} == FreeBSD\".", + "WARN: opsys.mk:24: Comment \"ii\" does not match loop \"jj in 1 2\".") +} + +func (s *Suite) Test_MkLineChecker_checkDirectiveFor(c *check.C) { + t := s.Init(c) + + t.SetUpPkgsrc() + t.CreateFileLines("mk/file.mk", + MkRcsID, + ".for i = 1 2 3", // The "=" should rather be "in". + ".endfor", + "", + ".for _i_ in 1 2 3", // Underscores are only allowed in infrastructure files. + ".endfor") + + G.Check(t.File("mk/file.mk")) + + // Pkglint doesn't care about trivial syntax errors, bmake will already catch these. + t.CheckOutputEmpty() +} + func (s *Suite) Test_MkLineChecker_checkDependencyRule(c *check.C) { t := s.Init(c) @@ -200,7 +265,6 @@ func (s *Suite) Test_MkLineChecker_check func (s *Suite) Test_MkLineChecker_checkVartype__simple_type(c *check.C) { t := s.Init(c) - t.SetUpCommandLine("-Wtypes") t.SetUpVartypes() // Since COMMENT is defined in vardefs.go its type is certain instead of guessed. @@ -229,21 +293,6 @@ func (s *Suite) Test_MkLineChecker_check t.CheckOutputEmpty() } -// The command line option -Wno-types can be used to suppress the type checks. -// Suppressing it is rarely needed and comes from Feb 12 2005 when this feature was introduced. -// Since then the type system has matured and proven effective. -func (s *Suite) Test_MkLineChecker_checkVartype__skip(c *check.C) { - t := s.Init(c) - - t.SetUpCommandLine("-Wno-types") - t.SetUpVartypes() - mkline := t.NewMkLine("filename", 1, "DISTNAME=invalid:::distname") - - MkLineChecker{mkline}.Check() - - t.CheckOutputEmpty() -} - func (s *Suite) Test_MkLineChecker_checkVartype__append_to_non_list(c *check.C) { t := s.Init(c) @@ -279,7 +328,6 @@ func (s *Suite) Test_MkLineChecker_check func (s *Suite) Test_MkLineChecker_checkDirectiveCond(c *check.C) { t := s.Init(c) - t.SetUpCommandLine("-Wtypes") t.SetUpVartypes() test := func(cond string, output ...string) { @@ -296,20 +344,28 @@ func (s *Suite) Test_MkLineChecker_check "{ ccache ccc clang distcc f2c gcc hp icc ido "+ "mipspro mipspro-ucode pcc sunpro xlc } for PKGSRC_COMPILER.") - test(".elif ${A} != ${B}") + test(".elif ${A} != ${B}", + "WARN: filename:1: A is used but not defined.", + "WARN: filename:1: B is used but not defined.") test(".if ${HOMEPAGE} == \"mailto:someone@example.org\"", - "WARN: filename:1: \"mailto:someone@example.org\" is not a valid URL.") + "WARN: filename:1: \"mailto:someone@example.org\" is not a valid URL.", + "WARN: filename:1: HOMEPAGE should not be evaluated at load time.", + "WARN: filename:1: HOMEPAGE may not be used in any file; it is a write-only variable.") test(".if !empty(PKGSRC_RUN_TEST:M[Y][eE][sS])", "WARN: filename:1: PKGSRC_RUN_TEST should be matched "+ "against \"[yY][eE][sS]\" or \"[nN][oO]\", not \"[Y][eE][sS]\".") - test(".if !empty(IS_BUILTIN.Xfixes:M[yY][eE][sS])") + test(".if !empty(IS_BUILTIN.Xfixes:M[yY][eE][sS])", + "WARN: filename:1: IS_BUILTIN.Xfixes should not be evaluated at load time.", + "WARN: filename:1: IS_BUILTIN.Xfixes may not be used in this file; it would be ok in builtin.mk.") test(".if !empty(${IS_BUILTIN.Xfixes:M[yY][eE][sS]})", "WARN: filename:1: The empty() function takes a variable name as parameter, "+ - "not a variable expression.") + "not a variable expression.", + "WARN: filename:1: IS_BUILTIN.Xfixes should not be evaluated at load time.", + "WARN: filename:1: IS_BUILTIN.Xfixes may not be used in this file; it would be ok in builtin.mk.") test(".if ${PKGSRC_COMPILER} == \"msvc\"", "WARN: filename:1: \"msvc\" is not valid for PKGSRC_COMPILER. "+ @@ -317,7 +373,9 @@ func (s *Suite) Test_MkLineChecker_check "WARN: filename:1: Use ${PKGSRC_COMPILER:Mmsvc} instead of the == operator.") test(".if ${PKG_LIBTOOL:Mlibtool}", - "NOTE: filename:1: PKG_LIBTOOL should be compared using == instead of matching against \":Mlibtool\".") + "NOTE: filename:1: PKG_LIBTOOL should be compared using == instead of matching against \":Mlibtool\".", + "WARN: filename:1: PKG_LIBTOOL should not be evaluated at load time.", + "WARN: filename:1: PKG_LIBTOOL may not be used in any file; it is a write-only variable.") test(".if ${MACHINE_PLATFORM:MUnknownOS-*-*} || ${MACHINE_ARCH:Mx86}", "WARN: filename:1: "+ @@ -336,7 +394,8 @@ func (s *Suite) Test_MkLineChecker_check "NOTE: filename:1: MACHINE_ARCH should be compared using == instead of matching against \":Mx86\".") test(".if ${MASTER_SITES:Mftp://*} == \"ftp://netbsd.org/\"", - nil...) + "WARN: filename:1: MASTER_SITES should not be evaluated at load time.", + "WARN: filename:1: MASTER_SITES may not be used in any file; it is a write-only variable.") // The only interesting line from the below tracing output is the one // containing "checkCompareVarStr". @@ -350,6 +409,10 @@ func (s *Suite) Test_MkLineChecker_check "TRACE: 1 2 + (*Pkgsrc).VariableType(\"VAR\")", "TRACE: 1 2 3 No type definition found for \"VAR\".", "TRACE: 1 2 - (*Pkgsrc).VariableType(\"VAR\", \"=>\", (*pkglint.Vartype)(nil))", + "WARN: filename:1: VAR is used but not defined.", + "TRACE: 1 2 + MkLineChecker.checkVarusePermissions(\"VAR\", (no-type time:parse quoting:plain wordpart:false))", + "TRACE: 1 2 3 No type definition found for \"VAR\".", + "TRACE: 1 2 - MkLineChecker.checkVarusePermissions(\"VAR\", (no-type time:parse quoting:plain wordpart:false))", "TRACE: 1 2 + (*MkLineImpl).VariableNeedsQuoting(\"VAR\", (*pkglint.Vartype)(nil), (no-type time:parse quoting:plain wordpart:false))", "TRACE: 1 2 - (*MkLineImpl).VariableNeedsQuoting(\"VAR\", (*pkglint.Vartype)(nil), (no-type time:parse quoting:plain wordpart:false), \"=>\", unknown)", "TRACE: 1 - MkLineChecker.CheckVaruse(filename:1, ${VAR:Mpattern1:Mpattern2}, (no-type time:parse quoting:plain wordpart:false))", @@ -375,19 +438,37 @@ func (s *Suite) Test_MkLineChecker_check func (s *Suite) Test_MkLineChecker_checkVarassignLeftPermissions(c *check.C) { t := s.Init(c) - t.SetUpCommandLine("-Wall,no-space") t.SetUpVartypes() + t.SetUpTool("awk", "AWK", AtRunTime) mklines := t.NewMkLines("options.mk", MkRcsID, - "PKG_DEVELOPER?= yes", - "BUILD_DEFS?= VARBASE") + "PKG_DEVELOPER?=\tyes", + "BUILD_DEFS?=\tVARBASE", + "USE_TOOLS:=\t${USE_TOOLS:Nunwanted-tool}", + "USE_TOOLS:=\t${MY_TOOLS}", + "USE_TOOLS:=\tawk") mklines.Check() t.CheckOutputLines( "WARN: options.mk:2: The variable PKG_DEVELOPER may not be given a default value by any package.", "WARN: options.mk:2: Please include \"../../mk/bsd.prefs.mk\" before using \"?=\".", - "WARN: options.mk:3: The variable BUILD_DEFS may not be given a default value (only appended to) in this file.") + "WARN: options.mk:3: The variable BUILD_DEFS may not be given a default value (only appended to) in this file.", + "WARN: options.mk:5: The variable USE_TOOLS may not be set (only appended to) in this file.", + "WARN: options.mk:5: MY_TOOLS is used but not defined.", + "WARN: options.mk:6: The variable USE_TOOLS may not be set (only appended to) in this file.") +} + +func (s *Suite) Test_MkLineChecker_checkVarassignLeftPermissions__no_tracing(c *check.C) { + t := s.Init(c) + + t.SetUpVartypes() + t.DisableTracing() // Just to reach branch coverage for unknown permissions. + mklines := t.NewMkLines("options.mk", + MkRcsID, + "COMMENT=\tShort package description") + + mklines.Check() } // Don't check the permissions for infrastructure files since they have their own rules. @@ -581,6 +662,51 @@ func (s *Suite) Test_MkLineChecker_check "WARN: any.mk:2: PKGREVISION may not be used in any file; it is a write-only variable.") } +func (s *Suite) Test_MkLineChecker_checkVarusePermissions__indirectly(c *check.C) { + t := s.Init(c) + + t.SetUpVartypes() + mklines := t.NewMkLines("file.mk", + MkRcsID, + "IGNORE_PKG.package=\t${ONLY_FOR_UNPRIVILEGED}") + + mklines.Check() + + t.CheckOutputLines( + "WARN: file.mk:2: IGNORE_PKG.package should be set to YES or yes.", + "WARN: file.mk:2: ONLY_FOR_UNPRIVILEGED should not be evaluated indirectly at load time.") +} + +func (s *Suite) Test_MkLineChecker_warnVaruseToolLoadTime(c *check.C) { + t := s.Init(c) + + t.SetUpVartypes() + t.SetUpTool("nowhere", "NOWHERE", Nowhere) + t.SetUpTool("after-prefs", "AFTER_PREFS", AfterPrefsMk) + t.SetUpTool("at-runtime", "AT_RUNTIME", AtRunTime) + mklines := t.NewMkLines("Makefile", + MkRcsID, + ".if ${NOWHERE} && ${AFTER_PREFS} && ${AT_RUNTIME} && ${MK_TOOL}", + ".endif", + "", + "TOOLS_CREATE+=\t\tmk-tool", + "_TOOLS_VARNAME.mk-tool=\tMK_TOOL") + + mklines.Check() + + t.CheckOutputLines( + "WARN: Makefile:2: To use the tool ${NOWHERE} at load time, "+ + "it has to be added to USE_TOOLS before including bsd.prefs.mk.", + "WARN: Makefile:2: To use the tool ${AFTER_PREFS} at load time, "+ + "bsd.prefs.mk has to be included before.", + "WARN: Makefile:2: The tool ${AT_RUNTIME} cannot be used at load time.", + "WARN: Makefile:2: To use the tool ${MK_TOOL} at load time, "+ + "bsd.prefs.mk has to be included before.", + "WARN: Makefile:6: Variable names starting with an underscore "+ + "(_TOOLS_VARNAME.mk-tool) are reserved for internal pkgsrc use.", + "WARN: Makefile:6: _TOOLS_VARNAME.mk-tool is defined but not used.") +} + func (s *Suite) Test_MkLineChecker_Check__warn_varuse_LOCALBASE(c *check.C) { t := s.Init(c) @@ -764,6 +890,17 @@ func (s *Suite) Test_MkLineChecker_check "WARN: Makefile:2: Compiler flag \"%s\\\\\\\"\" should start with a hyphen.") } +func (s *Suite) Test_MkLineChecker_checkDirectiveIndentation(c *check.C) { + t := s.Init(c) + + mkline := t.NewMkLine("filename.mk", 123, ".if 0") + + // Calling this method is only useful in the context of a whole file. + MkLineChecker{mkline}.checkDirectiveIndentation(4) + + t.CheckOutputEmpty() +} + func (s *Suite) Test_MkLineChecker_checkDirectiveIndentation__autofix(c *check.C) { t := s.Init(c) @@ -1021,7 +1158,64 @@ func (s *Suite) Test_MkLineChecker_Check "WARN: ~/options.mk:2: The user-defined variable VARBASE is used but not added to BUILD_DEFS.") } -func (s *Suite) Test_MkLineChecker_CheckVaruse__complicated_range(c *check.C) { +// The LOCALBASE variable may be defined and used in the infrastructure. +// It is always equivalent to PREFIX and only exists for historic reasons. +func (s *Suite) Test_MkLineChecker_CheckVaruse__LOCALBASE_in_infrastructure(c *check.C) { + t := s.Init(c) + + t.SetUpPkgsrc() + t.CreateFileLines("mk/infra.mk", + MkRcsID, + "LOCALBASE?=\t${PREFIX}", + "DEFAULT_PREFIX=\t${LOCALBASE}") + G.Pkgsrc.LoadInfrastructure() + + G.Check(t.File("mk/infra.mk")) + + // No warnings about LOCALBASE being used; in packages LOCALBASE is deprecated. + t.CheckOutputLines( + "WARN: ~/mk/infra.mk:2: PREFIX should not be evaluated indirectly at load time.") +} + +func (s *Suite) Test_MkLineChecker_CheckVaruse__user_defined_variable_and_BUILD_DEFS(c *check.C) { + t := s.Init(c) + + t.SetUpPkgsrc() + t.CreateFileLines("mk/defaults/mk.conf", + "VARBASE?=\t${PREFIX}/var", + "PYTHON_VER?=\t36") + G.Pkgsrc.LoadInfrastructure() + mklines := t.NewMkLines("file.mk", + MkRcsID, + "BUILD_DEFS+=\tPYTHON_VER", + "\t: ${VARBASE}", + "\t: ${VARBASE}", + "\t: ${PYTHON_VER}") + + mklines.Check() + + t.CheckOutputLines( + "WARN: file.mk:3: The user-defined variable VARBASE is used but not added to BUILD_DEFS.") +} + +func (s *Suite) Test_MkLineChecker_checkVaruseModifiersSuffix(c *check.C) { + t := s.Init(c) + + t.SetUpVartypes() + mklines := t.NewMkLines("file.mk", + MkRcsID, + "\t: ${HOMEPAGE:=subdir/:Q}", // wrong + "\t: ${BUILD_DIRS:=subdir/}", // correct + "\t: ${BIN_PROGRAMS:=.exe}") // unknown since BIN_PROGRAMS doesn't have a type + + mklines.Check() + + t.CheckOutputLines( + "WARN: file.mk:2: The :from=to modifier should only be used with lists, not with HOMEPAGE.", + "WARN: file.mk:4: BIN_PROGRAMS is used but not defined.") +} + +func (s *Suite) Test_MkLineChecker_checkVaruseModifiersRange(c *check.C) { t := s.Init(c) t.SetUpCommandLine("--show-autofix", "--source") @@ -1039,6 +1233,24 @@ func (s *Suite) Test_MkLineChecker_Check "Replacing \":C/^/_asdf_/1:M_asdf_*:S/^_asdf_//\" with \":[1]\".", "-\tCC:=\t${CC:C/^/_asdf_/1:M_asdf_*:S/^_asdf_//}", "+\tCC:=\t${CC:[1]}") + + // Now go through all the "almost" cases, to reach full branch coverage. + mklines := t.NewMkLines("gcc.mk", + MkRcsID, + "\t: ${CC:M1:M2:M3}", + "\t: ${CC:C/^begin//:M2:M3}", // M1 pattern not exactly ^ + "\t: ${CC:C/^/_asdf_/g:M2:M3}", // M1 options != "1" + "\t: ${CC:C/^/....../g:M2:M3}", // M1 replacement doesn't match \w+ + "\t: ${CC:C/^/_asdf_/1:O:M3}", // M2 is not a match modifier + "\t: ${CC:C/^/_asdf_/1:N2:M3}", // M2 is :N instead of :M + "\t: ${CC:C/^/_asdf_/1:M_asdf_:M3}", // M2 pattern is missing the * at the end + "\t: ${CC:C/^/_asdf_/1:Mother:M3}", // M2 pattern differs from the M1 pattern + "\t: ${CC:C/^/_asdf_/1:M_asdf_*:M3}", // M3 ist not a substitution modifier + "\t: ${CC:C/^/_asdf_/1:M_asdf_*:S,from,to,}", // M3 pattern differs from the M1 pattern + "\t: ${CC:C/^/_asdf_/1:M_asdf_*:S,^_asdf_,to,}", // M3 replacement is not empty + "\t: ${CC:C/^/_asdf_/1:M_asdf_*:S,^_asdf_,,g}") // M3 modifier has options + + mklines.Check() } func (s *Suite) Test_MkLineChecker_CheckVaruse__deprecated_PKG_DEBUG(c *check.C) { Index: pkgsrc/pkgtools/pkglint/files/files.go diff -u pkgsrc/pkgtools/pkglint/files/files.go:1.23 pkgsrc/pkgtools/pkglint/files/files.go:1.24 --- pkgsrc/pkgtools/pkglint/files/files.go:1.23 Mon Dec 17 00:15:39 2018 +++ pkgsrc/pkgtools/pkglint/files/files.go Thu Feb 21 22:49:03 2019 @@ -102,7 +102,7 @@ func nextLogicalLine(filename string, ra func matchContinuationLine(textnl string) (leadingWhitespace, text, trailingWhitespace, cont string) { j := len(textnl) - if j > 0 && textnl[j-1] == '\n' { + if textnl[j-1] == '\n' { j-- } Index: pkgsrc/pkgtools/pkglint/files/mkparser.go diff -u pkgsrc/pkgtools/pkglint/files/mkparser.go:1.23 pkgsrc/pkgtools/pkglint/files/mkparser.go:1.24 --- pkgsrc/pkgtools/pkglint/files/mkparser.go:1.23 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/mkparser.go Thu Feb 21 22:49:03 2019 @@ -27,8 +27,6 @@ func (p *MkParser) Rest() string { // // The text argument is assumed to be after unescaping the # character, // which means the # is a normal character and does not introduce a Makefile comment. -// -// TODO: Remove the emitWarnings argument in order to separate parsing from checking. func NewMkParser(line Line, text string, emitWarnings bool) *MkParser { G.Assertf((line != nil) == emitWarnings, "line must be given iff emitWarnings is set") return &MkParser{line, textproc.NewLexer(text), emitWarnings} @@ -441,7 +439,7 @@ func (p *MkParser) mkCondAtom() MkCond { m := lexer.NextRegexp(G.res.Compile(`^[\t ]*(<|<=|==|!=|>=|>)[\t ]*`)) if m == nil { - return &mkCond{Not: &mkCond{Empty: lhs}} // See devel/bmake/files/cond.c:/\* For \.if \$/ + return &mkCond{Var: lhs} // See devel/bmake/files/cond.c:/\* For \.if \$/ } op := m[1] @@ -697,6 +695,7 @@ type mkCond struct { Defined string Empty *MkVarUse + Var *MkVarUse CompareVarNum *MkCondCompareVarNum CompareVarStr *MkCondCompareVarStr CompareVarVar *MkCondCompareVarVar @@ -730,6 +729,7 @@ type MkCondCallback struct { CompareVarStr func(varuse *MkVarUse, op string, str string) CompareVarVar func(left *MkVarUse, op string, right *MkVarUse) Call func(name string, arg string) + Var func(varuse *MkVarUse) VarUse func(varuse *MkVarUse) } @@ -764,6 +764,14 @@ func (w *MkCondWalker) Walk(cond MkCond, callback.VarUse(&MkVarUse{cond.Defined, nil}) } + case cond.Var != nil: + if callback.Var != nil { + callback.Var(cond.Var) + } + if callback.VarUse != nil { + callback.VarUse(cond.Var) + } + case cond.Empty != nil: if callback.Empty != nil { callback.Empty(cond.Empty) Index: pkgsrc/pkgtools/pkglint/files/licenses.go diff -u pkgsrc/pkgtools/pkglint/files/licenses.go:1.20 pkgsrc/pkgtools/pkglint/files/licenses.go:1.21 --- pkgsrc/pkgtools/pkglint/files/licenses.go:1.20 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/licenses.go Thu Feb 21 22:49:03 2019 @@ -31,8 +31,8 @@ func (lc *LicenseChecker) checkName(lice } if licenseFile == "" { licenseFile = G.Pkgsrc.File("licenses/" + license) - if G.Pkgsrc.UsedLicenses != nil { - G.Pkgsrc.UsedLicenses[intern(license)] = true + if G.UsedLicenses != nil { + G.UsedLicenses[intern(license)] = true } } Index: pkgsrc/pkgtools/pkglint/files/line.go diff -u pkgsrc/pkgtools/pkglint/files/line.go:1.32 pkgsrc/pkgtools/pkglint/files/line.go:1.33 --- pkgsrc/pkgtools/pkglint/files/line.go:1.32 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/line.go Thu Feb 21 22:49:03 2019 @@ -19,9 +19,16 @@ import ( ) type RawLine struct { - Lineno int // Counting starts at 1; 0 means inserted by Autofix - orignl string // The line as read in from the file, including newline - textnl string // The line as modified by Autofix, including newline + Lineno int // Counting starts at 1 + + // The line as read in from the file, including newline; + // can never be empty. Only in the very last line of each file, + // the trailing newline might be missing. + orignl string + + // The line as modified by Autofix, including newline; + // empty string for deleted lines. + textnl string // XXX: Since only Autofix needs to distinguish between orignl and textnl, // one of these fields should probably be moved there. @@ -145,9 +152,7 @@ func (line *LineImpl) showSource(out *Se for _, rawLine := range rawLines { if rawLine.textnl != rawLine.orignl { - if rawLine.orignl != "" { - writeLine("-\t", rawLine.orignl) - } + writeLine("-\t", rawLine.orignl) if rawLine.textnl != "" { writeLine("+\t", rawLine.textnl) } Index: pkgsrc/pkgtools/pkglint/files/pkglint_test.go diff -u pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.32 pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.33 --- pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.32 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/pkglint_test.go Thu Feb 21 22:49:03 2019 @@ -38,35 +38,19 @@ func (s *Suite) Test_Pkglint_Main__help( " -W, --warning=warning,... enable or disable groups of warnings", "", " Flags for -C, --check:", - " all all of the following", - " none none of the following", - " ALTERNATIVES check ALTERNATIVES files (enabled)", - " bl3 check buildlink3.mk files (enabled)", - " DESCR check DESCR file (enabled)", - " distinfo check distinfo file (enabled)", - " extra check various additional files (disabled)", - " global inter-package checks (disabled)", - " INSTALL check INSTALL and DEINSTALL scripts (enabled)", - " Makefile check Makefiles (enabled)", - " MESSAGE check MESSAGE file (enabled)", - " mk check other .mk files (enabled)", - " options check options.mk files (enabled)", - " patches check patches (enabled)", - " PLIST check PLIST files (enabled)", + " all all of the following", + " none none of the following", + " extra check various additional files (disabled)", + " global inter-package checks (disabled)", "", " Flags for -W, --warning:", - " all all of the following", - " none none of the following", - " directcmd warn about use of direct command names instead of Make variables (enabled)", - " extra enable some extra warnings (disabled)", - " order warn if Makefile entries are unordered (enabled)", - " perm warn about unforeseen variable definition and use (disabled)", - " plist-depr warn about deprecated paths in PLISTs (disabled)", - " plist-sort warn about unsorted entries in PLISTs (disabled)", - " quoting warn about quoting issues (disabled)", - " space warn about inconsistent use of whitespace (disabled)", - " style warn about stylistic issues (disabled)", - " types do some simple type checking in Makefiles (enabled)", + " all all of the following", + " none none of the following", + " extra enable some extra warnings (disabled)", + " perm warn about unforeseen variable definition and use (disabled)", + " quoting warn about quoting issues (disabled)", + " space warn about inconsistent use of whitespace (disabled)", + " style warn about stylistic issues (disabled)", "", " (Prefix a flag with \"no-\" to disable it.)") } Index: pkgsrc/pkgtools/pkglint/files/plist_test.go diff -u pkgsrc/pkgtools/pkglint/files/plist_test.go:1.32 pkgsrc/pkgtools/pkglint/files/plist_test.go:1.33 --- pkgsrc/pkgtools/pkglint/files/plist_test.go:1.32 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/plist_test.go Thu Feb 21 22:49:03 2019 @@ -48,6 +48,23 @@ func (s *Suite) Test_CheckLinesPlist(c * "ERROR: PLIST:18: Duplicate filename \"share/tzinfo\", already appeared in line 17.") } +// When a PLIST contains multiple libtool libraries, USE_LIBTOOL needs only +// be defined once in the package Makefile. Therefore, a single warning is enough. +func (s *Suite) Test_CheckLinesPlist__multiple_libtool_libraries(c *check.C) { + t := s.Init(c) + + G.Pkg = NewPackage(t.File("category/pkgbase")) + lines := t.NewLines("PLIST", + PlistRcsID, + "lib/libc.la", + "lib/libm.la") + + CheckLinesPlist(lines) + + t.CheckOutputLines( + "WARN: PLIST:2: Packages that install libtool libraries should define USE_LIBTOOL.") +} + func (s *Suite) Test_CheckLinesPlist__empty(c *check.C) { t := s.Init(c) @@ -92,7 +109,6 @@ func (s *Suite) Test_CheckLinesPlist__co func (s *Suite) Test_CheckLinesPlist__sorting(c *check.C) { t := s.Init(c) - t.SetUpCommandLine("-Wplist-sort") lines := t.NewLines("PLIST", PlistRcsID, "@comment Do not remove", @@ -471,7 +487,7 @@ func (s *Suite) Test_PlistChecker_checkP CheckLinesPlist(lines) t.CheckOutputLines( - "WARN: ~/PLIST:2: perllocal.pod files should not be in the PLIST.", + "WARN: ~/PLIST:2: The perllocal.pod file should not be in the PLIST.", "WARN: ~/PLIST:3: CVS files should not be in the PLIST.", "WARN: ~/PLIST:4: .orig files should not be in the PLIST.") } @@ -560,7 +576,7 @@ func (s *Suite) Test_PlistLine_CheckTrai CheckLinesPlist(lines) t.CheckOutputLines( - "ERROR: ~/PLIST:2: pkgsrc does not support filenames ending in whitespace.") + "ERROR: ~/PLIST:2: Pkgsrc does not support filenames ending in whitespace.") } func (s *Suite) Test_PlistLine_CheckDirective(c *check.C) { @@ -580,7 +596,7 @@ func (s *Suite) Test_PlistLine_CheckDire t.CheckOutputLines( "WARN: ~/PLIST:2: Please remove this line. It is no longer necessary.", - "ERROR: ~/PLIST:3: ldconfig must be used with \"||/usr/bin/true\".", + "ERROR: ~/PLIST:3: The ldconfig command must be used with \"||/usr/bin/true\".", "WARN: ~/PLIST:5: @dirrm is obsolete. Please remove this line.", "WARN: ~/PLIST:6: Invalid number of arguments for imake-man, should be 3.", "WARN: ~/PLIST:7: IMAKE_MANNEWSUFFIX is not meant to appear in PLISTs.", Index: pkgsrc/pkgtools/pkglint/files/linelexer.go diff -u pkgsrc/pkgtools/pkglint/files/linelexer.go:1.1 pkgsrc/pkgtools/pkglint/files/linelexer.go:1.2 --- pkgsrc/pkgtools/pkglint/files/linelexer.go:1.1 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/linelexer.go Thu Feb 21 22:49:03 2019 @@ -1,9 +1,6 @@ package pkglint -import ( - "netbsd.org/pkglint/regex" - "strings" -) +import "netbsd.org/pkglint/regex" // LinesLexer records the state when checking a list of lines from top to bottom. type LinesLexer struct { @@ -66,7 +63,11 @@ func (llex *LinesLexer) SkipPrefix(prefi defer trace.Call2(llex.CurrentLine().Text, prefix)() } - return !llex.EOF() && strings.HasPrefix(llex.lines.Lines[llex.index].Text, prefix) && llex.Skip() + if !llex.EOF() && hasPrefix(llex.lines.Lines[llex.index].Text, prefix) { + llex.Skip() + return true + } + return false } func (llex *LinesLexer) SkipString(text string) bool { @@ -74,7 +75,11 @@ func (llex *LinesLexer) SkipString(text defer trace.Call2(llex.CurrentLine().Text, text)() } - return !llex.EOF() && llex.lines.Lines[llex.index].Text == text && llex.Skip() + if !llex.EOF() && llex.lines.Lines[llex.index].Text == text { + llex.Skip() + return true + } + return false } func (llex *LinesLexer) SkipEmptyOrNote() bool { @@ -135,5 +140,9 @@ func (mlex *MkLinesLexer) SkipWhile(pred } func (mlex *MkLinesLexer) SkipIf(pred func(mkline MkLine) bool) bool { - return !mlex.EOF() && pred(mlex.CurrentMkLine()) && mlex.Skip() + if !mlex.EOF() && pred(mlex.CurrentMkLine()) { + mlex.Skip() + return true + } + return false } Index: pkgsrc/pkgtools/pkglint/files/linelexer_test.go diff -u pkgsrc/pkgtools/pkglint/files/linelexer_test.go:1.1 pkgsrc/pkgtools/pkglint/files/linelexer_test.go:1.2 --- pkgsrc/pkgtools/pkglint/files/linelexer_test.go:1.1 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/linelexer_test.go Thu Feb 21 22:49:03 2019 @@ -34,3 +34,18 @@ func (s *Suite) Test_LinesLexer_SkipEmpt t.CheckOutputLines( "NOTE: file.txt:2: Empty line expected after this line.") } + +func (s *Suite) Test_LinesLexer_SkipPrefix(c *check.C) { + t := s.Init(c) + + lines := t.NewLines("file.txt", + "line 1", + "line 2") + llex := NewLinesLexer(lines) + + t.Check(llex.SkipPrefix("line 1"), equals, true) + t.Check(llex.SkipPrefix("line 1"), equals, false) + t.Check(llex.SkipPrefix("line 2"), equals, true) + t.Check(llex.SkipPrefix("line 2"), equals, false) + t.Check(llex.SkipPrefix(""), equals, false) +} Index: pkgsrc/pkgtools/pkglint/files/lines_test.go diff -u pkgsrc/pkgtools/pkglint/files/lines_test.go:1.7 pkgsrc/pkgtools/pkglint/files/lines_test.go:1.8 --- pkgsrc/pkgtools/pkglint/files/lines_test.go:1.7 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/lines_test.go Thu Feb 21 22:49:03 2019 @@ -60,4 +60,15 @@ func (s *Suite) Test_Lines_CheckRcsID__w "AUTOFIX: ~/wip/package/file3.mk:1: Inserting a line \"# $"+"NetBSD$\" before this line.", "AUTOFIX: ~/wip/package/file4.mk:1: Inserting a line \"# $"+"NetBSD$\" before this line.", "AUTOFIX: ~/wip/package/file5.mk:1: Inserting a line \"# $"+"NetBSD$\" before this line.") + + // In production mode, this error is disabled since it doesn't provide + // enough benefit compared to the work it would produce. + // + // To make it useful, the majority of pkgsrc-wip packages would first + // have to follow this style. + G.Testing = false + + G.Check(t.File("wip/package")) + + t.CheckOutputEmpty() } Index: pkgsrc/pkgtools/pkglint/files/logging_test.go diff -u pkgsrc/pkgtools/pkglint/files/logging_test.go:1.11 pkgsrc/pkgtools/pkglint/files/logging_test.go:1.12 --- pkgsrc/pkgtools/pkglint/files/logging_test.go:1.11 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/logging_test.go Thu Feb 21 22:49:03 2019 @@ -2,6 +2,7 @@ package pkglint import ( "gopkg.in/check.v1" + "netbsd.org/pkglint/histogram" "strings" ) @@ -52,6 +53,67 @@ func (s *Suite) Test_Logger_Logf__mixed_ "ERROR: filename:3: Logf output 3.\n") } +func (s *Suite) Test_Logger_Logf__production(c *check.C) { + var sw strings.Builder + logger := Logger{out: NewSeparatorWriter(&sw)} + + // In production mode, the checks for the diagnostic messages are + // turned off, for performance reasons. The unit tests provide + // enough coverage. + G.Testing = false + logger.Logf(Error, "filename", "3", "diagnostic", "message") + + c.Check(sw.String(), equals, ""+ + "ERROR: filename:3: message\n") +} + +func (s *Suite) Test_Logger_Logf__profiling(c *check.C) { + t := s.Init(c) + + line := t.NewLine("filename", 123, "text") + + G.Opts.Profiling = true + G.Logger.histo = histogram.New() + line.Warnf("Warning.") + + G.Logger.histo.PrintStats(G.out.out, "loghisto", -1) + + t.CheckOutputLines( + "WARN: filename:123: Warning.", + "loghisto 1 Warning.") +} + +func (s *Suite) Test_Logger_Logf__profiling_autofix(c *check.C) { + t := s.Init(c) + + t.SetUpCommandLine("--show-autofix", "--source", "--explain") + line := t.NewLine("filename", 123, "text") + + G.Opts.Profiling = true + G.Logger.histo = histogram.New() + + fix := line.Autofix() + fix.Notef("Autofix note.") + fix.Explain( + "Autofix explanation.") + fix.Replace("text", "replacement") + fix.Apply() + + // The AUTOFIX line is not counted in the histogram although + // it uses the same code path as the other messages. + G.Logger.histo.PrintStats(G.out.out, "loghisto", -1) + + t.CheckOutputLines( + "NOTE: filename:123: Autofix note.", + "AUTOFIX: filename:123: Replacing \"text\" with \"replacement\".", + "-\ttext", + "+\treplacement", + "", + "\tAutofix explanation.", + "", + "loghisto 1 Autofix note.") +} + // Diag filters duplicate messages, unlike Logf. func (s *Suite) Test_Logger_Diag__duplicates(c *check.C) { t := s.Init(c) @@ -686,14 +748,17 @@ func (s *Suite) Test_Logger_Diag__source G.Main("pkglint", "--source", "-Wall", t.File("category/package1"), t.File("category/package2")) t.CheckOutputLines( - "ERROR: ~/category/package1/distinfo: patch \"../dependency/patches/patch-aa\" "+ - "is not recorded. Run \""+confMake+" makepatchsum\".", + "ERROR: ~/category/package1/distinfo: "+ + "Patch \"../dependency/patches/patch-aa\" is not recorded. "+ + "Run \""+confMake+" makepatchsum\".", "", ">\t--- old file", - "ERROR: ~/category/dependency/patches/patch-aa:3: Each patch must be documented.", + "ERROR: ~/category/dependency/patches/patch-aa:3: "+ + "Each patch must be documented.", "", - "ERROR: ~/category/package2/distinfo: patch \"../dependency/patches/patch-aa\" "+ - "is not recorded. Run \""+confMake+" makepatchsum\".", + "ERROR: ~/category/package2/distinfo: "+ + "Patch \"../dependency/patches/patch-aa\" is not recorded. "+ + "Run \""+confMake+" makepatchsum\".", "", "3 errors and 0 warnings found.", "(Run \"pkglint -e\" to show explanations.)") Index: pkgsrc/pkgtools/pkglint/files/options.go diff -u pkgsrc/pkgtools/pkglint/files/options.go:1.11 pkgsrc/pkgtools/pkglint/files/options.go:1.12 --- pkgsrc/pkgtools/pkglint/files/options.go:1.11 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/options.go Thu Feb 21 22:49:03 2019 @@ -73,18 +73,20 @@ loop: continue } - cond.Walk(&MkCondCallback{ - Empty: func(varuse *MkVarUse) { - if varuse.varname == "PKG_OPTIONS" && len(varuse.modifiers) == 1 { - if m, positive, pattern := varuse.modifiers[0].MatchMatch(); m && positive { - option := pattern - if !containsVarRef(option) { - handledOptions[option] = mkline - optionsInDeclarationOrder = append(optionsInDeclarationOrder, option) - } + recordUsedOption := func(varuse *MkVarUse) { + if varuse.varname == "PKG_OPTIONS" && len(varuse.modifiers) == 1 { + if m, positive, pattern := varuse.modifiers[0].MatchMatch(); m && positive { + option := pattern + if !containsVarRef(option) { + handledOptions[option] = mkline + optionsInDeclarationOrder = append(optionsInDeclarationOrder, option) } } - }}) + } + } + cond.Walk(&MkCondCallback{ + Empty: recordUsedOption, + Var: recordUsedOption}) if cond.Empty != nil && mkline.HasElseBranch() { mkline.Notef("The positive branch of the .if/.else should be the one where the option is set.") @@ -101,14 +103,14 @@ loop: declared := declaredOptions[option] handled := handledOptions[option] - if handled == nil { + switch { + case handled == nil: declared.Warnf("Option %q should be handled below in an .if block.", option) G.Explain( "If an option is not processed in this file, it may either be a", "typo, or the option does not have any effect.") - } - if declared == nil { + case declared == nil: handled.Warnf("Option %q is handled but not added to PKG_SUPPORTED_OPTIONS.", option) G.Explain( "This block of code will never be run since PKG_OPTIONS cannot", @@ -117,5 +119,5 @@ loop: } } - SaveAutofixChanges(mklines.lines) + mklines.SaveAutofixChanges() } Index: pkgsrc/pkgtools/pkglint/files/mkline.go diff -u pkgsrc/pkgtools/pkglint/files/mkline.go:1.45 pkgsrc/pkgtools/pkglint/files/mkline.go:1.46 --- pkgsrc/pkgtools/pkglint/files/mkline.go:1.45 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/mkline.go Thu Feb 21 22:49:03 2019 @@ -354,10 +354,18 @@ func (mkline *MkLineImpl) Tokenize(text defer trace.Call(mkline, text)() } - p := NewMkParser(mkline.Line, text, true) - tokens := p.MkTokens() - if warn && p.Rest() != "" { - mkline.Warnf("Internal pkglint error in MkLine.Tokenize at %q.", p.Rest()) + var tokens []*MkToken + var rest string + if (mkline.IsVarassign() || mkline.IsCommentedVarassign()) && text == mkline.Value() { + tokens, rest = mkline.ValueTokens() + } else { + p := NewMkParser(mkline.Line, text, true) + tokens = p.MkTokens() + rest = p.Rest() + } + + if warn && rest != "" { + mkline.Warnf("Internal pkglint error in MkLine.Tokenize at %q.", rest) } return tokens } @@ -458,21 +466,21 @@ func (mkline *MkLineImpl) ValueFields(va return split } -func (mkline *MkLineImpl) ValueTokens() []*MkToken { +func (mkline *MkLineImpl) ValueTokens() ([]*MkToken, string) { value := mkline.Value() if value == "" { - return nil + return nil, "" } assign := mkline.data.(mkLineAssign) if assign.valueMk != nil || assign.valueMkRest != "" { - return assign.valueMk + return assign.valueMk, assign.valueMkRest } p := NewMkParser(mkline.Line, value, true) assign.valueMk = p.MkTokens() assign.valueMkRest = p.Rest() - return assign.valueMk + return assign.valueMk, assign.valueMkRest } // Fields applies to variable assignments and .for loops. @@ -528,22 +536,23 @@ func (mkline *MkLineImpl) ResolveVarsInR } else { basedir = path.Dir(mkline.Filename) } - pkgsrcdir := relpath(basedir, G.Pkgsrc.File(".")) - - if G.Testing { - // Relative pkgsrc paths usually only contain two or three levels. - // A possible reason for reaching this assertion is: - // Tests that access the file system must create their lines - // using t.SetUpFileMkLines, not using t.NewMkLines. - G.Assertf(!contains(pkgsrcdir, "../../../../.."), - "Relative path %q for %q is too deep below the pkgsrc root %q.", - pkgsrcdir, basedir, G.Pkgsrc.File(".")) - } tmp := relativePath - tmp = strings.Replace(tmp, "${PKGSRCDIR}", pkgsrcdir, -1) - tmp = strings.Replace(tmp, "${.CURDIR}", ".", -1) - tmp = strings.Replace(tmp, "${.PARSEDIR}", ".", -1) + if contains(tmp, "PKGSRCDIR") { + pkgsrcdir := relpath(basedir, G.Pkgsrc.File(".")) + + if G.Testing { + // Relative pkgsrc paths usually only contain two or three levels. + // A possible reason for reaching this assertion is a pkglint unit test + // that uses t.NewMkLines instead of the correct t.SetUpFileMkLines. + G.Assertf(!contains(pkgsrcdir, "../../../../.."), + "Relative path %q for %q is too deep below the pkgsrc root %q.", + pkgsrcdir, basedir, G.Pkgsrc.File(".")) + } + tmp = strings.Replace(tmp, "${PKGSRCDIR}", pkgsrcdir, -1) + } + tmp = strings.Replace(tmp, "${.CURDIR}", ".", -1) // TODO: Replace with the "typical" os.Getwd(). + tmp = strings.Replace(tmp, "${.PARSEDIR}", ".", -1) // FIXME replaceLatest := func(varuse, category string, pattern regex.Pattern, replacement string) { if contains(tmp, varuse) { @@ -551,11 +560,16 @@ func (mkline *MkLineImpl) ResolveVarsInR tmp = strings.Replace(tmp, varuse, latest, -1) } } + + // These variables are only used in pkgsrc packages, therefore they + // are replaced with the fixed "../.." regardless of where the text appears. replaceLatest("${LUA_PKGSRCDIR}", "lang", `^lua[0-9]+$`, "../../lang/$0") replaceLatest("${PHPPKGSRCDIR}", "lang", `^php[0-9]+$`, "../../lang/$0") - replaceLatest("${SUSE_DIR_PREFIX}", "emulators", `^(suse[0-9]+)_base$`, "$1") replaceLatest("${PYPKGSRCDIR}", "lang", `^python[0-9]+$`, "../../lang/$0") + replaceLatest("${PYPACKAGE}", "lang", `^python[0-9]+$`, "$0") + replaceLatest("${SUSE_DIR_PREFIX}", "emulators", `^(suse[0-9]+)_base$`, "$1") + if G.Pkg != nil { // XXX: Even if these variables are defined indirectly, // pkglint should be able to resolve them properly. @@ -668,7 +682,7 @@ func (mkline *MkLineImpl) VariableNeedsQ } return no } - if vartype.kindOfList == lkShell && !vuc.IsWordPart { + if !vuc.IsWordPart { return no } } @@ -690,10 +704,8 @@ func (mkline *MkLineImpl) VariableNeedsQ // Both of these can be correct, depending on the situation: // 1. echo ${PERL5:Q} // 2. xargs ${PERL5} - if !vuc.IsWordPart && vuc.quoting == VucQuotPlain { - if wantList && haveList { - return unknown - } + if !vuc.IsWordPart && wantList && haveList { + return unknown } // Pkglint assumes that the tool definitions don't include very @@ -737,7 +749,7 @@ func (mkline *MkLineImpl) VariableNeedsQ // Bad: LDADD+= -l${LIBS} // Good: LDADD+= ${LIBS:S,^,-l,} - if wantList && haveList && vuc.IsWordPart { + if wantList { return yes } @@ -1139,17 +1151,6 @@ func (ind *Indentation) TrackAfter(mklin ind.top().depth += 2 } - if cond != nil { - ind.RememberUsedVariables(cond) - - cond.Walk(&MkCondCallback{ - Call: func(name string, arg string) { - if name == "exists" { - ind.AddCheckedFile(arg) - } - }}) - } - case "for", "ifdef", "ifndef": ind.top().depth += 2 @@ -1169,6 +1170,23 @@ func (ind *Indentation) TrackAfter(mklin } } + switch directive { + case "if", "elif": + cond := mkline.Cond() + if cond == nil { + break + } + + ind.RememberUsedVariables(cond) + + cond.Walk(&MkCondCallback{ + Call: func(name string, arg string) { + if name == "exists" { + ind.AddCheckedFile(arg) + } + }}) + } + if trace.Tracing { trace.Stepf("Indentation after line %s: %s", mkline.Linenos(), ind) } Index: pkgsrc/pkgtools/pkglint/files/mkline_test.go diff -u pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.50 pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.51 --- pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.50 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/mkline_test.go Thu Feb 21 22:49:03 2019 @@ -17,6 +17,20 @@ func (s *Suite) Test_NewMkLine__varassig c.Check(mkline.VarassignComment(), equals, "# varassign comment") } +func (s *Suite) Test_NewMkLine__varassign_space_around_operator(c *check.C) { + t := s.Init(c) + + t.SetUpCommandLine("--show-autofix", "--source") + t.NewMkLine("test.mk", 101, + "pkgbase = package") + + t.CheckOutputLines( + "NOTE: test.mk:101: Unnecessary space after variable name \"pkgbase\".", + "AUTOFIX: test.mk:101: Replacing \"pkgbase =\" with \"pkgbase=\".", + "-\tpkgbase = package", + "+\tpkgbase= package") +} + func (s *Suite) Test_NewMkLine__shellcmd(c *check.C) { t := s.Init(c) @@ -453,6 +467,25 @@ func (s *Suite) Test_MkLine_VariableNeed t.CheckOutputEmpty() // Don't suggest to use ${HOMEPAGE:Q}. } +func (s *Suite) Test_MkLine_VariableNeedsQuoting__MASTER_SITES_and_HOMEPAGE(c *check.C) { + t := s.Init(c) + + t.SetUpVartypes() + mklines := t.NewMkLines("Makefile", + MkRcsID, + "MASTER_SITES=\t${HOMEPAGE}", + "MASTER_SITES=\t${PATH}", // Some nonsense just for branch coverage. + "HOMEPAGE=\t${MASTER_SITES}", + "HOMEPAGE=\t${BUILD_DIRS}") // Some nonsense just for branch coverage. + + mklines.Check() + + t.CheckOutputLines( + "WARN: Makefile:3: Please use ${PATH:Q} instead of ${PATH}.", + "WARN: Makefile:4: HOMEPAGE should not be defined in terms of MASTER_SITEs.", + "WARN: Makefile:5: Please use ${BUILD_DIRS:Q} instead of ${BUILD_DIRS}.") +} + // Before November 2018, pkglint did not parse $$(subshell) commands very well. // As a side effect, it sometimes issued wrong warnings about the :Q modifier. // @@ -743,6 +776,13 @@ func (s *Suite) Test_MkLine_VariableNeed "WARN: ~/Makefile:6: The variable PATH may not be set by any package.", "WARN: ~/Makefile:6: PREFIX should not be evaluated at load time.", "WARN: ~/Makefile:6: PATH should not be evaluated at load time.") + + // Just for branch coverage. + trace.Tracing = false + MkLineChecker{mklines.mklines[2]}.Check() + + t.CheckOutputLines( + "WARN: ~/Makefile:3: GO_SRCPATH is defined but not used.") } func (s *Suite) Test_MkLine__shell_varuse_in_backt_dquot(c *check.C) { @@ -832,6 +872,17 @@ func (s *Suite) Test_MkLine_ValueSplit(c "", "${VAR2}two", "words") + +} + +func (s *Suite) Test_MkLine_ValueSplit__invalid_argument(c *check.C) { + t := s.Init(c) + + mkline := t.NewMkLine("filename.mk", 123, "VAR=\tvalue") + + t.ExpectPanic( + func() { mkline.ValueSplit("value", "") }, + "Pkglint internal error: Separator must not be empty; use ValueFields to split on whitespace") } func (s *Suite) Test_MkLine_Fields__varassign(c *check.C) { @@ -934,8 +985,8 @@ func (s *Suite) Test_MkLine_ValueTokens( testTokens := func(value string, expected ...*MkToken) { mkline := t.NewMkLine("Makefile", 1, "PATH=\t"+value) - split := mkline.ValueTokens() - c.Check(split, deepEquals, expected) + tokens, _ := mkline.ValueTokens() + c.Check(tokens, deepEquals, expected) } testTokens("#empty", @@ -957,14 +1008,46 @@ func (s *Suite) Test_MkLine_ValueTokens_ t := s.Init(c) mkline := t.NewMkLine("Makefile", 1, "PATH=\tvalue ${UNFINISHED") - split := mkline.ValueTokens() + tokens, rest := mkline.ValueTokens() + + c.Check(tokens, deepEquals, []*MkToken{{"value ", nil}}) + c.Check(rest, equals, "${UNFINISHED") + + tokens2, rest2 := mkline.ValueTokens() // This time the slice is taken from the cache. + + // In Go, it's not possible to compare slices for reference equality. + c.Check(tokens2, deepEquals, tokens) + c.Check(rest2, equals, rest) +} + +func (s *Suite) Test_MkLine_ValueTokens__caching_parse_error(c *check.C) { + t := s.Init(c) - c.Check(split, deepEquals, []*MkToken{{"value ", nil}}) + mkline := t.NewMkLine("Makefile", 1, "PATH=\t${UNFINISHED") + tokens, rest := mkline.ValueTokens() - split2 := mkline.ValueTokens() // This time the slice is taken from the cache. + c.Check(tokens, check.IsNil) + c.Check(rest, equals, "${UNFINISHED") + + tokens2, rest2 := mkline.ValueTokens() // This time the slice is taken from the cache. // In Go, it's not possible to compare slices for reference equality. - c.Check(split2, deepEquals, split) + c.Check(tokens2, deepEquals, tokens) + c.Check(rest2, equals, rest) +} + +func (s *Suite) Test_MkLine_ValueTokens__warnings(c *check.C) { + t := s.Init(c) + + mklines := t.NewMkLines("Makefile", + MkRcsID, + "ROUND=\t$(ROUND)") + + mklines.mklines[1].ValueTokens() + mklines.Check() + + t.CheckOutputLines( + "WARN: Makefile:2: Please use curly braces {} instead of round parentheses () for ROUND.") } func (s *Suite) Test_MkLine_ResolveVarsInRelativePath(c *check.C) { @@ -983,6 +1066,7 @@ func (s *Suite) Test_MkLine_ResolveVarsI } test("", ".") + test("${PKGSRCDIR}", ".") test("${LUA_PKGSRCDIR}", "../../lang/lua53") test("${PHPPKGSRCDIR}", "../../lang/php72") test("${SUSE_DIR_PREFIX}", "suse100") @@ -995,6 +1079,10 @@ func (s *Suite) Test_MkLine_ResolveVarsI test("${FILESDIR}", "files") test("${PKGDIR}", ".") + + // Just for branch coverage. + G.Testing = false + test("${PKGSRCDIR}", "../..") } func (s *Suite) Test_MkLine_ResolveVarsInRelativePath__directory_depth(c *check.C) { @@ -1128,6 +1216,44 @@ func (s *Suite) Test_Indentation_Remembe c.Check(ind.Varnames(), deepEquals, []string{"PKGREVISION"}) } +func (s *Suite) Test_Indentation_TrackAfter__checked_files(c *check.C) { + t := s.Init(c) + + mklines := t.NewMkLines("file.mk", + MkRcsID, + "", + ".if make(other.mk)", + ". include \"other.mk\"", + ".endif", + "", + ".if exists(checked.mk)", + ". include \"checked.mk\"", + ".elif exists(other-checked.mk)", + ". include \"other-checked.mk\"", + ".endif") + + mklines.Check() + + t.CheckOutputLines( + "ERROR: file.mk:4: Relative path \"other.mk\" does not exist.") +} + +func (s *Suite) Test_Indentation_TrackAfter__lonely_else(c *check.C) { + t := s.Init(c) + + mklines := t.NewMkLines("file.mk", + MkRcsID, + "", + ".else") + + mklines.Check() + + // Surprisingly, pkglint doesn't report an error about this trivial bug. + // This will be caught by bmake, though. Therefore the only purpose of + // this test is the branch coverage in the "top.mkline != nil" case. + t.CheckOutputEmpty() +} + func (s *Suite) Test_MkLine_DetermineUsedVariables(c *check.C) { t := s.Init(c) @@ -1214,8 +1340,58 @@ func (s *Suite) Test_matchMkDirective(c []interface{}{true, expectedIndent, expectedDirective, expectedArgs, expectedComment}) } - test(".if ${VAR} == value", "", "if", "${VAR} == value", "") - test(".\tendif # comment", "\t", "endif", "", "comment") - test(".if ${VAR} == \"#\"", "", "if", "${VAR} == \"", "\"") - test(".if ${VAR:[#]}", "", "if", "${VAR:[#]}", "") + test(".if ${VAR} == value", + "", "if", "${VAR} == value", "") + + test(".\tendif # comment", + "\t", "endif", "", "comment") + + test(".if ${VAR} == \"#\"", + "", "if", "${VAR} == \"", "\"") + + test(".if ${VAR:[#]}", + "", "if", "${VAR:[#]}", "") + + test(".if ${VAR} == \\", + "", "if", "${VAR} == \\", "") +} + +func (s *Suite) Test_MatchMkInclude(c *check.C) { + t := s.Init(c) + + test := func(input, expectedIndent, expectedDirective, expectedFilename string) { + m, indent, directive, args := MatchMkInclude(input) + c.Check( + []interface{}{m, indent, directive, args}, + deepEquals, + []interface{}{true, expectedIndent, expectedDirective, expectedFilename}) + } + + testFail := func(input string) { + m, _, _, _ := MatchMkInclude(input) + if m { + c.Errorf("Text %q unexpectedly matched.", input) + } + } + + testFail("") + testFail(".") + testFail(".include") + testFail(".include \"") + testFail(".include \"other.mk") + testFail(".include \"other.mk\" \"") + + test(".include \"other.mk\"", + "", "include", "other.mk") + + test(".include \"other.mk\"\t", + "", "include", "other.mk") + + test(".include \"other.mk\"\t#", + "", "include", "other.mk") + + test(".include \"other.mk\"\t# comment", + "", "include", "other.mk") + + t.CheckOutputEmpty() } Index: pkgsrc/pkgtools/pkglint/files/mklinechecker.go diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.28 pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.29 --- pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.28 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/mklinechecker.go Thu Feb 21 22:49:03 2019 @@ -4,6 +4,7 @@ import ( "netbsd.org/pkglint/regex" "os" "path" + "path/filepath" "strconv" "strings" ) @@ -298,9 +299,6 @@ func (ck MkLineChecker) checkVarassignLe op := mkline.Op() vartype := G.Pkgsrc.VariableType(varname) if vartype == nil { - if trace.Tracing { - trace.Step1("No type definition found for %q.", varname) - } return } @@ -308,7 +306,7 @@ func (ck MkLineChecker) checkVarassignLe // E.g. USE_TOOLS:= ${USE_TOOLS:Nunwanted-tool} if op == opAssignEval && perms&aclpAppend != 0 { - tokens := mkline.ValueTokens() + tokens, _ := mkline.ValueTokens() if len(tokens) == 1 && tokens[0].Varuse != nil && tokens[0].Varuse.varname == varname { return } @@ -563,15 +561,8 @@ func (ck MkLineChecker) checkVarusePermi if !tool.UsableAtLoadTime(G.Mk.Tools.SeenPrefs) { ck.warnVaruseToolLoadTime(varname, tool) } - } else { - // Might the variable be used indirectly at load time, for example - // by assigning it to another variable which then gets evaluated? - isIndirect := vuc.time != vucTimeParse && // Otherwise it would be directly. - // The context might be used at load time somewhere. - vuc.vartype != nil && vuc.vartype.Union().Contains(aclpUseLoadtime) - - ck.warnVaruseLoadTime(varname, isIndirect) + ck.warnVaruseLoadTime(varname, indirectly) } } @@ -1051,10 +1042,6 @@ func (ck MkLineChecker) checkVartype(var defer trace.Call(varname, op, value, comment)() } - if !G.Opts.WarnTypes { - return - } - mkline := ck.MkLine vartype := G.Pkgsrc.VariableType(varname) @@ -1191,6 +1178,7 @@ func (ck MkLineChecker) checkDirectiveCo cond.Walk(&MkCondCallback{ Empty: ck.checkDirectiveCondEmpty, + Var: ck.checkDirectiveCondEmpty, CompareVarStr: checkCompareVarStr, VarUse: checkVarUse}) } @@ -1298,11 +1286,11 @@ func (ck MkLineChecker) CheckRelativePat } abs := resolvedPath - if !hasPrefix(abs, "/") { + if !filepath.IsAbs(abs) { abs = path.Dir(mkline.Filename) + "/" + abs } if _, err := os.Stat(abs); err != nil { - if mustExist { + if mustExist && !(G.Mk != nil && G.Mk.indentation.IsCheckedFile(resolvedPath)) { mkline.Errorf("Relative path %q does not exist.", resolvedPath) } return Index: pkgsrc/pkgtools/pkglint/files/patches.go diff -u pkgsrc/pkgtools/pkglint/files/patches.go:1.28 pkgsrc/pkgtools/pkglint/files/patches.go:1.29 --- pkgsrc/pkgtools/pkglint/files/patches.go:1.28 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/patches.go Thu Feb 21 22:49:03 2019 @@ -98,7 +98,7 @@ func (ck *PatchChecker) Check() { if SaveAutofixChanges(ck.lines) && G.Pkg != nil && err == nil { sha1After, err := computePatchSha1Hex(ck.lines.FileName) if err == nil { - AutofixDistinfo(sha1Before, sha1After) + G.Pkg.AutofixDistinfo(sha1Before, sha1After) } } } Index: pkgsrc/pkgtools/pkglint/files/mklines.go diff -u pkgsrc/pkgtools/pkglint/files/mklines.go:1.40 pkgsrc/pkgtools/pkglint/files/mklines.go:1.41 --- pkgsrc/pkgtools/pkglint/files/mklines.go:1.40 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/mklines.go Thu Feb 21 22:49:03 2019 @@ -405,18 +405,15 @@ func (mklines *MkLinesImpl) CheckRedunda scope := NewRedundantScope() isRelevant := func(old, new MkLine) bool { - if old.Basename != "Makefile" && new.Basename == "Makefile" { - return false - } if new.Op() == opAssignEval { return false } return true } - scope.OnIgnore = func(old, new MkLine) { + scope.OnRedundant = func(old, new MkLine) { if isRelevant(old, new) && old.Value() == new.Value() { - old.Notef("Definition of %s is redundant because of %s.", new.Varname(), old.RefTo(new)) + new.Notef("Definition of %s is redundant because of %s.", old.Varname(), new.RefTo(old)) } } Index: pkgsrc/pkgtools/pkglint/files/mklines_test.go diff -u pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.35 pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.36 --- pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.35 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/mklines_test.go Thu Feb 21 22:49:03 2019 @@ -511,43 +511,6 @@ func (s *Suite) Test_MkLines_Check__inde "NOTE: ~/module.mk:7: This directive should be indented by 2 spaces.") } -func (s *Suite) Test_MkLines_Check__endif_comment(c *check.C) { - t := s.Init(c) - - t.SetUpVartypes() - mklines := t.NewMkLines("opsys.mk", - MkRcsID, - "", - ".for i in 1 2 3 4 5", - ". if ${OPSYS} == NetBSD", - ". if ${MACHINE_ARCH} == x86_64", - ". if ${OS_VERSION:M8.*}", - ". endif # MACHINE_ARCH", // Wrong, should be OS_VERSION. - ". endif # OS_VERSION", // Wrong, should be MACHINE_ARCH. - ". endif # OPSYS", // Correct. - ".endfor # j", // Wrong, should be i. - "", - ".if ${PKG_OPTIONS:Moption}", - ".endif # option", // Correct. - "", - ".if ${PKG_OPTIONS:Moption}", - ".endif # opti", // This typo goes unnoticed since "opti" is a substring of the condition. - "", - ".if ${OPSYS} == NetBSD", - ".elif ${OPSYS} == FreeBSD", - ".endif # NetBSD") // Wrong, should be FreeBSD from the .elif. - - // See MkLineChecker.checkDirective - mklines.Check() - - t.CheckOutputLines( - "WARN: opsys.mk:7: Comment \"MACHINE_ARCH\" does not match condition \"${OS_VERSION:M8.*}\".", - "WARN: opsys.mk:8: Comment \"OS_VERSION\" does not match condition \"${MACHINE_ARCH} == x86_64\".", - "WARN: opsys.mk:10: Comment \"j\" does not match loop \"i in 1 2 3 4 5\".", - "WARN: opsys.mk:12: Unknown option \"option\".", - "WARN: opsys.mk:20: Comment \"NetBSD\" does not match condition \"${OPSYS} == FreeBSD\".") -} - func (s *Suite) Test_MkLines_Check__unfinished_directives(c *check.C) { t := s.Init(c) @@ -747,25 +710,22 @@ func (s *Suite) Test_MkLines_CheckRedund "OVERRIDE=\tprevious value", "REDUNDANT=\tredundant") including := t.NewMkLines("including.mk", + ".include \"included.mk\"", "OVERRIDE=\toverridden value", "REDUNDANT=\tredundant") var allLines []Line + allLines = append(allLines, including.lines.Lines[:1]...) allLines = append(allLines, included.lines.Lines...) - allLines = append(allLines, including.lines.Lines...) + allLines = append(allLines, including.lines.Lines[1:]...) mklines := NewMkLines(NewLines(included.lines.FileName, allLines)) // XXX: The warnings from here are not in the same order as the other warnings. // XXX: There may be some warnings for the same file separated by warnings for other files. mklines.CheckRedundantAssignments() - // No warning for VAR=... in Makefile since it makes sense to have common files - // with default values for variables, overriding some of them in each package. t.CheckOutputLines( - // FIXME: The below warning is wrong because overwriting in a different file is ok. - "WARN: included.mk:1: Variable OVERRIDE is overwritten in including.mk:1.", - // FIXME: It's the other way round: including.mk:2 is redundant because of included.mk:2. - "NOTE: included.mk:2: Definition of REDUNDANT is redundant because of including.mk:2.") + "NOTE: including.mk:3: Definition of REDUNDANT is redundant because of included.mk:2.") } func (s *Suite) Test_MkLines_CheckRedundantAssignments__override_in_Makefile(c *check.C) { @@ -774,13 +734,15 @@ func (s *Suite) Test_MkLines_CheckRedund "VAR=\tvalue ${OTHER}", "VAR?=\tvalue ${OTHER}", "VAR=\tnew value") - makefile := t.NewMkLines("Makefile", + including := t.NewMkLines("Makefile", + ".include \"module.mk\"", "VAR=\tthe package may overwrite variables from other files") var allLines []Line + allLines = append(allLines, including.lines.Lines[:1]...) allLines = append(allLines, included.lines.Lines...) - allLines = append(allLines, makefile.lines.Lines...) - mklines := NewMkLines(NewLines(included.lines.FileName, allLines)) + allLines = append(allLines, including.lines.Lines[1:]...) + mklines := NewMkLines(NewLines(including.lines.FileName, allLines)) // XXX: The warnings from here are not in the same order as the other warnings. // XXX: There may be some warnings for the same file separated by warnings for other files. @@ -789,7 +751,7 @@ func (s *Suite) Test_MkLines_CheckRedund // No warning for VAR=... in Makefile since it makes sense to have common files // with default values for variables, overriding some of them in each package. t.CheckOutputLines( - "NOTE: module.mk:1: Definition of VAR is redundant because of line 2.", + "NOTE: module.mk:2: Definition of VAR is redundant because of line 1.", "WARN: module.mk:1: Variable VAR is overwritten in line 3.") } @@ -826,7 +788,7 @@ func (s *Suite) Test_MkLines_CheckRedund mklines.CheckRedundantAssignments() t.CheckOutputLines( - "NOTE: module.mk:1: Definition of VAR is redundant because of line 2.") + "NOTE: module.mk:2: Definition of VAR is redundant because of line 1.") } func (s *Suite) Test_MkLines_CheckRedundantAssignments__conditional_overwrite(c *check.C) { @@ -869,7 +831,7 @@ func (s *Suite) Test_MkLines_CheckRedund t.CheckOutputLines( "WARN: module.mk:1: Variable OTHER is overwritten in line 3.", - "NOTE: module.mk:2: Definition of VAR is redundant because of line 4.") + "NOTE: module.mk:4: Definition of VAR is redundant because of line 2.") } func (s *Suite) Test_MkLines_CheckRedundantAssignments__overwrite_different_value_used_between(c *check.C) { @@ -894,8 +856,7 @@ func (s *Suite) Test_MkLines_CheckRedund t.CheckOutputLines( "WARN: module.mk:1: Variable OTHER is overwritten in line 4.", - // FIXME: It's the other way round: line 6 is redundant because of line 2. - "NOTE: module.mk:2: Definition of VAR is redundant because of line 6.") + "NOTE: module.mk:6: Definition of VAR is redundant because of line 2.") } func (s *Suite) Test_MkLines_CheckRedundantAssignments__procedure_call(c *check.C) { Index: pkgsrc/pkgtools/pkglint/files/mkparser_test.go diff -u pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.21 pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.22 --- pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.21 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/mkparser_test.go Thu Feb 21 22:49:03 2019 @@ -375,12 +375,8 @@ func (s *Suite) Test_MkParser_MkCond(c * } varuse := NewMkVarUse - // TODO: Add tests for &&, ||, !. - - // TODO: Add test for !empty(VAR:M}). - test("${OPSYS:MNetBSD}", - &mkCond{Not: &mkCond{Empty: varuse("OPSYS", "MNetBSD")}}) + &mkCond{Var: varuse("OPSYS", "MNetBSD")}) test("defined(VARNAME)", &mkCond{Defined: "VARNAME"}) @@ -443,8 +439,8 @@ func (s *Suite) Test_MkParser_MkCond(c * test("${MACHINE_ARCH:Mi386} || ${MACHINE_OPSYS:MNetBSD}", &mkCond{Or: []*mkCond{ - {Not: &mkCond{Empty: varuse("MACHINE_ARCH", "Mi386")}}, - {Not: &mkCond{Empty: varuse("MACHINE_OPSYS", "MNetBSD")}}}}) + {Var: varuse("MACHINE_ARCH", "Mi386")}, + {Var: varuse("MACHINE_OPSYS", "MNetBSD")}}}) // Exotic cases @@ -485,7 +481,7 @@ func (s *Suite) Test_MkParser_MkCond(c * &mkCond{Defined: "VARNAME"}) test("${\"${PKG_OPTIONS:Moption}\":?--enable-option:--disable-option}", - &mkCond{Not: &mkCond{Empty: varuse("\"${PKG_OPTIONS:Moption}\"", "?--enable-option:--disable-option")}}) + &mkCond{Var: varuse("\"${PKG_OPTIONS:Moption}\"", "?--enable-option:--disable-option")}) // Errors @@ -494,7 +490,7 @@ func (s *Suite) Test_MkParser_MkCond(c * "|| defined(PKG_OPTIONS:Msamplerate)") testRest("${LEFT} &&", - &mkCond{Not: &mkCond{Empty: varuse("LEFT")}}, + &mkCond{Var: varuse("LEFT")}, "&&") testRest("\"unfinished string literal", @@ -821,6 +817,9 @@ func (s *Suite) Test_MkCondWalker_Walk(c Call: func(name string, arg string) { addEvent("call", name, arg) }, + Var: func(varuse *MkVarUse) { + addEvent("var", varuseStr(varuse)) + }, VarUse: func(varuse *MkVarUse) { addEvent("varUse", varuseStr(varuse)) }}) @@ -836,6 +835,6 @@ func (s *Suite) Test_MkCondWalker_Walk(c " defined VAR", " varUse VAR", " call exists, file.mk", - " empty NONEMPTY", + " var NONEMPTY", " varUse NONEMPTY"}) } Index: pkgsrc/pkgtools/pkglint/files/substcontext_test.go diff -u pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.21 pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.22 --- pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.21 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/substcontext_test.go Thu Feb 21 22:49:03 2019 @@ -370,18 +370,19 @@ func (s *Suite) Test_SubstContext_sugges "SUBST_CLASSES+=\t\ttest", "SUBST_STAGE.test=\tpre-configure", "SUBST_FILES.test=\tfilename", - "SUBST_SED.test+=\t-e s,@SH@,${SH},g", // Can be replaced. - "SUBST_SED.test+=\t-e s,@SH@,${SH:Q},g", // Can be replaced, with or without the :Q modifier. - "SUBST_SED.test+=\t-e s,@SH@,${SH:T},g", // Cannot be replaced because of the :T modifier. - "SUBST_SED.test+=\t-e s,@SH@,${SH},", // Can be replaced, even without the g option. - "SUBST_SED.test+=\t-e 's,@SH@,${SH},'", // Can be replaced, whether in single quotes or not. - "SUBST_SED.test+=\t-e \"s,@SH@,${SH},\"", // Can be replaced, whether in double quotes or not. - "SUBST_SED.test+=\t-e s,'@SH@','${SH}',", // Can be replaced, even when the quoting changes midways. - "SUBST_SED.test+=\ts,'@SH@','${SH}',", // Can be replaced manually, even when the -e is missing. - "SUBST_SED.test+=\t-e s,@SH@,${PKGNAME},", // Cannot be replaced since the variable name differs. - "SUBST_SED.test+=\t-e s,@SH@,'\"'${SH:Q}'\"',g", // Cannot be replaced since the double quotes are added. - "SUBST_SED.test+=\t-e s", // Just to get 100% code coverage. - "SUBST_SED.test+=\t-e s,@SH@,${SH:Q}", // Just to get 100% code coverage. + "SUBST_SED.test+=\t-e s,@SH@,${SH},g", // Can be replaced. + "SUBST_SED.test+=\t-e s,@SH@,${SH:Q},g", // Can be replaced, with or without the :Q modifier. + "SUBST_SED.test+=\t-e s,@SH@,${SH:T},g", // Cannot be replaced because of the :T modifier. + "SUBST_SED.test+=\t-e s,@SH@,${SH},", // Can be replaced, even without the g option. + "SUBST_SED.test+=\t-e 's,@SH@,${SH},'", // Can be replaced, whether in single quotes or not. + "SUBST_SED.test+=\t-e \"s,@SH@,${SH},\"", // Can be replaced, whether in double quotes or not. + "SUBST_SED.test+=\t-e s,'@SH@','${SH}',", // Can be replaced, even when the quoting changes midways. + "SUBST_SED.test+=\ts,'@SH@','${SH}',", // Can be replaced manually, even when the -e is missing. + "SUBST_SED.test+=\t-e s,@SH@,${PKGNAME},", // Cannot be replaced since the variable name differs. + "SUBST_SED.test+=\t-e s,@SH@,'\"'${SH:Q}'\"',g", // Cannot be replaced since the double quotes are added. + "SUBST_SED.test+=\t-e s", // Just to get 100% code coverage. + "SUBST_SED.test+=\t-e s,@SH@,${SH:Q}", // Just to get 100% code coverage. + "SUBST_SED.test+=\t-e s,@SH@,${SH:Q}, # comment", // This is not fixed automatically. "# end") mklines.Check() @@ -405,6 +406,8 @@ func (s *Suite) Test_SubstContext_sugges "NOTE: subst.mk:13: Please always use \"-e\" in sed commands, "+ "even if there is only one substitution.", "NOTE: subst.mk:13: The substitution command \"s,'@SH@','${SH}',\" "+ + "can be replaced with \"SUBST_VARS.test+= SH\".", + "NOTE: subst.mk:18: The substitution command \"s,@SH@,${SH:Q},\" "+ "can be replaced with \"SUBST_VARS.test+= SH\".") t.SetUpCommandLine("--show-autofix") @@ -435,9 +438,7 @@ func (s *Suite) Test_SubstContext_sugges "NOTE: subst.mk:12: The substitution command \"s,'@SH@','${SH}',\" "+ "can be replaced with \"SUBST_VARS.test+= SH\".", "AUTOFIX: subst.mk:12: Replacing \"SUBST_SED.test+=\\t-e s,'@SH@','${SH}',\" "+ - "with \"SUBST_VARS.test+=\\tSH\".", - "NOTE: subst.mk:13: The substitution command \"s,'@SH@','${SH}',\" "+ - "can be replaced with \"SUBST_VARS.test+= SH\".") + "with \"SUBST_VARS.test+=\\tSH\".") } // simulateSubstLines only tests some of the inner workings of SubstContext. Index: pkgsrc/pkgtools/pkglint/files/mktypes.go diff -u pkgsrc/pkgtools/pkglint/files/mktypes.go:1.10 pkgsrc/pkgtools/pkglint/files/mktypes.go:1.11 --- pkgsrc/pkgtools/pkglint/files/mktypes.go:1.10 Sun Jan 13 19:55:53 2019 +++ pkgsrc/pkgtools/pkglint/files/mktypes.go Thu Feb 21 22:49:03 2019 @@ -133,9 +133,8 @@ func (vu *MkVarUse) Mod() string { return mod.String() } -// IsExpression returns whether the varname is interpreted as a variable -// name (the usual case) or as an expression (rare, only the modifiers -// "?:" and "L" do this). +// IsExpression returns whether the varname is interpreted as an expression +// instead of a variable name (rare, only the modifiers :? and :L do this). func (vu *MkVarUse) IsExpression() bool { if len(vu.modifiers) == 0 { return false Index: pkgsrc/pkgtools/pkglint/files/options_test.go diff -u pkgsrc/pkgtools/pkglint/files/options_test.go:1.10 pkgsrc/pkgtools/pkglint/files/options_test.go:1.11 --- pkgsrc/pkgtools/pkglint/files/options_test.go:1.10 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/options_test.go Thu Feb 21 22:49:03 2019 @@ -192,3 +192,44 @@ func (s *Suite) Test_CheckLinesOptionsMk t.CheckOutputLines( "WARN: ~/category/package/options.mk:13: Invalid condition, unrecognized part: \"${OPSYS} == 'Darwin'\".") } + +func (s *Suite) Test_CheckLinesOptionsMk__PLIST_VARS_based_on_PKG_SUPPORTED_OPTIONS(c *check.C) { + t := s.Init(c) + + t.SetUpOption("one", "") + t.SetUpOption("two", "") + t.SetUpOption("three", "") + t.SetUpPackage("category/package") + t.CreateFileLines("mk/bsd.options.mk") + t.SetUpFileMkLines("category/package/options.mk", + MkRcsID, + "", + "PKG_OPTIONS_VAR=\tPKG_OPTIONS.package", + "PKG_SUPPORTED_OPTIONS+=\tone", + "PKG_SUPPORTED_OPTIONS+=\ttwo", + "PKG_SUPPORTED_OPTIONS+=\tthree", + "", + ".include \"../../mk/bsd.options.mk\"", + "", + "PLIST_VARS+=\t${PKG_SUPPORTED_OPTIONS}", + "", + ".if ${PKG_OPTIONS:Mone}", + "PLIST.one=\tyes", + ".endif", + "", + ".if ${PKG_OPTIONS:Mthree}", + "PLIST.three=\tyes", + ".endif") + t.Chdir("category/package") + + G.Check(".") + + // Even though PLIST_VARS is defined indirectly by referencing + // PKG_SUPPORTED_OPTIONS and that variable is defined in several + // lines, pkglint gets all the facts correct and knows that + // only PLIST.two is missing. + t.CheckOutputLines( + "WARN: options.mk:10: "+ + "\"two\" is added to PLIST_VARS, but PLIST.two is not defined in this file.", + "WARN: options.mk:5: Option \"two\" should be handled below in an .if block.") +} Index: pkgsrc/pkgtools/pkglint/files/package.go diff -u pkgsrc/pkgtools/pkglint/files/package.go:1.44 pkgsrc/pkgtools/pkglint/files/package.go:1.45 --- pkgsrc/pkgtools/pkglint/files/package.go:1.44 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/package.go Thu Feb 21 22:49:03 2019 @@ -185,10 +185,10 @@ func (pkg *Package) loadPackageMakefile( allLines.collectUsedVariables() allLines.CheckRedundantAssignments() - pkg.Pkgdir, _ = pkg.vars.Value("PKGDIR") - pkg.DistinfoFile, _ = pkg.vars.Value("DISTINFO_FILE") - pkg.Filesdir, _ = pkg.vars.Value("FILESDIR") - pkg.Patchdir, _ = pkg.vars.Value("PATCHDIR") + pkg.Pkgdir = pkg.vars.LastValue("PKGDIR") + pkg.DistinfoFile = pkg.vars.LastValue("DISTINFO_FILE") + pkg.Filesdir = pkg.vars.LastValue("FILESDIR") + pkg.Patchdir = pkg.vars.LastValue("PATCHDIR") // See lang/php/ext.mk if varIsDefinedSimilar("PHPEXT_MK") { @@ -438,6 +438,11 @@ func (pkg *Package) checkGnuConfigureUse vars := pkg.vars if gnuLine := vars.FirstDefinition("GNU_CONFIGURE"); gnuLine != nil { + + // FIXME: Instead of using the first definition here, a better approach + // is probably to use all the definitions except those from mk/compiler.mk. + // In real pkgsrc, the last definition is typically from mk/compiler.mk + // and only contains c++. if useLine := vars.FirstDefinition("USE_LANGUAGES"); useLine != nil { if matches(useLine.VarassignComment(), `(?-i)\b(?:c|empty|none)\b`) { @@ -460,7 +465,7 @@ func (pkg *Package) checkGnuConfigureUse // It is only used inside pkgsrc to mark changes that are // independent from the upstream package. func (pkg *Package) nbPart() string { - pkgrevision, _ := pkg.vars.Value("PKGREVISION") + pkgrevision := pkg.vars.LastValue("PKGREVISION") if rev, err := strconv.Atoi(pkgrevision); err == nil { return "nb" + strconv.Itoa(rev) } @@ -591,7 +596,7 @@ func (pkg *Package) CheckVarorder(mkline defer trace.Call0()() } - if !G.Opts.WarnOrder || pkg.seenMakefileCommon { + if pkg.seenMakefileCommon { return } @@ -666,12 +671,10 @@ func (pkg *Package) CheckVarorder(mkline variable("TOOL_DEPENDS", many), variable("DEPENDS", many))} - firstRelevant := -1 - lastRelevant := -1 + relevantLines := (func() []MkLine { + firstRelevant := -1 + lastRelevant := -1 - // TODO: understand and explain this code. - // It is much longer and much more complicated than it should be. - skip := func() bool { relevantVars := make(map[string]bool) for _, section := range sections { for _, variable := range section.vars { @@ -693,7 +696,7 @@ func (pkg *Package) CheckVarorder(mkline trace.Stepf("Skipping varorder because of line %s.", mklines.mklines[firstIrrelevant].Linenos()) } - return true + return nil } lastRelevant = i } else { @@ -713,9 +716,13 @@ func (pkg *Package) CheckVarorder(mkline } if firstRelevant == -1 { - return true + return nil } - interesting := mklines.mklines[firstRelevant : lastRelevant+1] + return mklines.mklines[firstRelevant : lastRelevant+1] + })() + + skip := func() bool { + interesting := relevantLines varcanon := func() string { for len(interesting) > 0 && interesting[0].IsComment() { @@ -760,7 +767,7 @@ func (pkg *Package) CheckVarorder(mkline return len(interesting) == 0 } - if skip() { + if len(relevantLines) == 0 || skip() { return } @@ -768,7 +775,7 @@ func (pkg *Package) CheckVarorder(mkline for _, section := range sections { for _, variable := range section.vars { found := false - for _, mkline := range mklines.mklines[firstRelevant : lastRelevant+1] { + for _, mkline := range relevantLines { if mkline.IsVarassign() || mkline.IsCommentedVarassign() { if mkline.Varcanon() == variable.varname { canonical = append(canonical, mkline.Varname()) @@ -791,7 +798,7 @@ func (pkg *Package) CheckVarorder(mkline // TODO: This leads to very long and complicated warnings. // Those parts that are correct should not be mentioned, // except if they are helpful for locating the mistakes. - mkline := mklines.mklines[firstRelevant] + mkline := relevantLines[0] mkline.Warnf("The canonical order of the variables is %s.", strings.Join(canonical, ", ")) G.Explain( "In simple package Makefiles, some common variables should be", @@ -812,8 +819,8 @@ func (pkg *Package) checkLocallyModified defer trace.Call(filename)() } - owner, _ := pkg.vars.Value("OWNER") - maintainer, _ := pkg.vars.Value("MAINTAINER") + owner := pkg.vars.LastValue("OWNER") + maintainer := pkg.vars.LastValue("MAINTAINER") if maintainer == "pkgsrc-users@NetBSD.org" { maintainer = "" } @@ -898,6 +905,19 @@ func (pkg *Package) loadPlistDirs(plistF } } +func (pkg *Package) AutofixDistinfo(oldSha1, newSha1 string) { + distinfoFilename := pkg.File(pkg.DistinfoFile) + if lines := Load(distinfoFilename, NotEmpty|LogErrors); lines != nil { + for _, line := range lines.Lines { + fix := line.Autofix() + fix.Warnf(SilentAutofixFormat) + fix.Replace(oldSha1, newSha1) + fix.Apply() + } + lines.SaveAutofixChanges() + } +} + type PlistContent struct { Dirs map[string]bool Files map[string]bool Index: pkgsrc/pkgtools/pkglint/files/package_test.go diff -u pkgsrc/pkgtools/pkglint/files/package_test.go:1.38 pkgsrc/pkgtools/pkglint/files/package_test.go:1.39 --- pkgsrc/pkgtools/pkglint/files/package_test.go:1.38 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/package_test.go Thu Feb 21 22:49:03 2019 @@ -72,7 +72,6 @@ func (s *Suite) Test_Package_pkgnameFrom func (s *Suite) Test_Package_CheckVarorder(c *check.C) { t := s.Init(c) - t.SetUpCommandLine("-Worder") pkg := NewPackage(t.File("x11/9term")) pkg.CheckVarorder(t.NewMkLines("Makefile", @@ -106,7 +105,6 @@ func (s *Suite) Test_Package_CheckVarord func (s *Suite) Test_Package_CheckVarorder__comments_do_not_crash(c *check.C) { t := s.Init(c) - t.SetUpCommandLine("-Worder") pkg := NewPackage(t.File("x11/9term")) pkg.CheckVarorder(t.NewMkLines("Makefile", @@ -129,8 +127,6 @@ func (s *Suite) Test_Package_CheckVarord func (s *Suite) Test_Package_CheckVarorder__comments_are_ignored(c *check.C) { t := s.Init(c) - t.SetUpCommandLine("-Worder") - pkg := NewPackage(t.File("x11/9term")) pkg.CheckVarorder(t.NewMkLines("Makefile", @@ -150,8 +146,6 @@ func (s *Suite) Test_Package_CheckVarord func (s *Suite) Test_Package_CheckVarorder__skip_if_there_are_directives(c *check.C) { t := s.Init(c) - t.SetUpCommandLine("-Worder") - pkg := NewPackage(t.File("category/package")) pkg.CheckVarorder(t.NewMkLines("Makefile", @@ -175,7 +169,6 @@ func (s *Suite) Test_Package_CheckVarord func (s *Suite) Test_Package_CheckVarorder__GITHUB_PROJECT_at_the_top(c *check.C) { t := s.Init(c) - t.SetUpCommandLine("-Worder") pkg := NewPackage(t.File("x11/9term")) pkg.CheckVarorder(t.NewMkLines("Makefile", @@ -196,7 +189,6 @@ func (s *Suite) Test_Package_CheckVarord func (s *Suite) Test_Package_CheckVarorder__GITHUB_PROJECT_at_the_bottom(c *check.C) { t := s.Init(c) - t.SetUpCommandLine("-Worder") pkg := NewPackage(t.File("x11/9term")) pkg.CheckVarorder(t.NewMkLines("Makefile", @@ -217,8 +209,6 @@ func (s *Suite) Test_Package_CheckVarord func (s *Suite) Test_Package_CheckVarorder__license(c *check.C) { t := s.Init(c) - t.SetUpCommandLine("-Worder") - t.CreateFileLines("mk/bsd.pkg.mk", "# dummy") t.CreateFileLines("x11/Makefile", MkRcsID) t.CreateFileLines("x11/9term/PLIST", PlistRcsID, "bin/9term") @@ -226,10 +216,10 @@ func (s *Suite) Test_Package_CheckVarord t.CreateFileLines("x11/9term/Makefile", MkRcsID, "", - "DISTNAME=9term-1.0", - "CATEGORIES=x11", + "DISTNAME=\t9term-1.0", + "CATEGORIES=\tx11", "", - "COMMENT=Terminal", + "COMMENT=\tTerminal", "", ".include \"../../mk/bsd.pkg.mk\"") @@ -246,7 +236,6 @@ func (s *Suite) Test_Package_CheckVarord func (s *Suite) Test_Package_CheckVarorder__MASTER_SITES(c *check.C) { t := s.Init(c) - t.SetUpCommandLine("-Worder") pkg := NewPackage(t.File("category/package")) pkg.CheckVarorder(t.NewMkLines("Makefile", @@ -267,7 +256,6 @@ func (s *Suite) Test_Package_CheckVarord func (s *Suite) Test_Package_CheckVarorder__diagnostics(c *check.C) { t := s.Init(c) - t.SetUpCommandLine("-Worder") t.SetUpVartypes() pkg := NewPackage(t.File("category/package")) @@ -354,7 +342,6 @@ func (s *Suite) Test_Package_determineEf func (s *Suite) Test_Package_determineEffectivePkgVars__same(c *check.C) { t := s.Init(c) - t.SetUpCommandLine("-Wall,no-order") pkg := t.SetUpPackage("category/package", "DISTNAME=\tdistname-1.0", "PKGNAME=\tdistname-1.0") @@ -369,7 +356,6 @@ func (s *Suite) Test_Package_determineEf func (s *Suite) Test_Package_determineEffectivePkgVars__invalid_DISTNAME(c *check.C) { t := s.Init(c) - t.SetUpCommandLine("-Wall,no-order") pkg := t.SetUpPackage("category/package", "DISTNAME=\tpkgname-version") @@ -541,10 +527,10 @@ func (s *Suite) Test_Package_loadPackage // A file including itself does not lead to an endless loop while parsing // but may still produce unexpected warnings, such as redundant definitions. t.CheckOutputLines( - "NOTE: ~/category/package/Makefile:3: Definition of PKGNAME is redundant "+ - "because of ../../category/package/Makefile:3.", - "NOTE: ~/category/package/Makefile:4: Definition of DISTNAME is redundant "+ - "because of ../../category/package/Makefile:4.") + "NOTE: ~/category/package/Makefile:3: "+ + "Definition of PKGNAME is redundant because of ../../category/package/Makefile:3.", + "NOTE: ~/category/package/Makefile:4: "+ + "Definition of DISTNAME is redundant because of ../../category/package/Makefile:4.") } func (s *Suite) Test_Package_loadPackageMakefile__PECL_VERSION(c *check.C) { @@ -632,9 +618,8 @@ func (s *Suite) Test_Package__include_af G.checkdirPackage(t.File("category/package")) - // FIXME: This error message should not appear at all because of the .if exists before. - t.CheckOutputLines( - "ERROR: ~/category/package/Makefile:21: Relative path \"options.mk\" does not exist.") + // No error message at all because of the .if exists before. + t.CheckOutputEmpty() } // See https://github.com/rillig/pkglint/issues/1 @@ -657,11 +642,8 @@ func (s *Suite) Test_Package_readMakefil func (s *Suite) Test_Package__redundant_master_sites(c *check.C) { t := s.Init(c) - t.SetUpVartypes() + t.SetUpPkgsrc() t.SetUpMasterSite("MASTER_SITE_R_CRAN", "http://cran.r-project.org/src/") - t.CreateFileLines("mk/bsd.pkg.mk") - t.CreateFileLines("licenses/gnu-gpl-v2", - "The licenses for most software are designed to take away ...") t.CreateFileLines("math/R/Makefile.extension", MkRcsID, "", @@ -680,13 +662,20 @@ func (s *Suite) Test_Package__redundant_ "", ".include \"../../math/R/Makefile.extension\"", ".include \"../../mk/bsd.pkg.mk\"") + G.Pkgsrc.LoadInfrastructure() // See Package.checkfilePackageMakefile - // See Scope.uncond G.checkdirPackage(t.File("math/R-date")) + // The definition in Makefile:6 is redundant because the same definition + // occurs later in Makefile.extension:4. Usually the later definition gets + // the note. In this case though, it would be wrong to mark the + // definition in Makefile.extension as redundant because that definition is + // probably used by other packages as well. Therefore in this case the roles + // of the two lines are swapped; see RedundantScope.Handle, the ".includes" line. t.CheckOutputLines( - "NOTE: ~/math/R-date/Makefile:6: Definition of MASTER_SITES is redundant because of ../../math/R/Makefile.extension:4.") + "NOTE: ~/math/R-date/Makefile:6: " + + "Definition of MASTER_SITES is redundant because of ../../math/R/Makefile.extension:4.") } func (s *Suite) Test_Package_checkUpdate(c *check.C) { @@ -709,7 +698,7 @@ func (s *Suite) Test_Package_checkUpdate "\t"+"o package3-3.0 [security update]") t.Chdir(".") - G.Main("pkglint", "-Wall,no-space,no-order", "category/pkg1", "category/pkg2", "category/pkg3") + G.Main("pkglint", "-Wall,no-space", "category/pkg1", "category/pkg2", "category/pkg3") t.CheckOutputLines( "WARN: category/pkg1/../../doc/TODO:3: Invalid line format \"\".", @@ -928,8 +917,6 @@ func (s *Suite) Test_Package_readMakefil func (s *Suite) Test_Package_checkLocallyModified(c *check.C) { t := s.Init(c) - // no-order since SetUpPackage doesn't place OWNER correctly. - t.SetUpCommandLine("-Wall,no-order") G.Username = "example-user" t.CreateFileLines("category/package/CVS/Entries", "/Makefile//modified//") @@ -988,3 +975,68 @@ func (s *Suite) Test_Package_checkLocall t.CheckOutputEmpty() } + +// In practice the distinfo file can always be autofixed since it has +// just been read successfully and the corresponding patch file could +// also be autofixed right before. +func (s *Suite) Test_Package_AutofixDistinfo__missing_file(c *check.C) { + t := s.Init(c) + + t.SetUpPkgsrc() + G.Pkg = NewPackage(t.File("category/package")) + + G.Pkg.AutofixDistinfo("old", "new") + + t.CheckOutputLines( + "ERROR: ~/category/package/distinfo: Cannot be read.") +} + +func (s *Suite) Test_Package__using_common_Makefile_overriding_DISTINFO_FILE(c *check.C) { + t := s.Init(c) + + t.SetUpPackage("security/pinentry") + t.CreateFileLines("security/pinentry/Makefile.common", + MkRcsID, + "DISTINFO_FILE=\t${.CURDIR}/../../security/pinentry/distinfo") + t.SetUpPackage("security/pinentry-fltk", + ".include \"../../security/pinentry/Makefile.common\"", + "DISTINFO_FILE=\t${.CURDIR}/distinfo") + t.CreateFileDummyPatch("security/pinentry-fltk/patches/patch-aa") + t.CreateFileLines("security/pinentry-fltk/distinfo", + RcsID, + "", + "SHA1 (patch-aa) = ebbf34b0641bcb508f17d5a27f2bf2a536d810ac") + G.Pkgsrc.LoadInfrastructure() + + G.Check(t.File("security/pinentry")) + + t.CheckOutputEmpty() + + G.Check(t.File("security/pinentry-fltk")) + + // The DISTINFO_FILE definition from pinentry-fltk overrides + // the one from pinentry since it appears later. + // Therefore the patch is searched for at the right location. + t.CheckOutputEmpty() +} + +func (s *Suite) Test_Package__redundant_variable_in_unrelated_files(c *check.C) { + t := s.Init(c) + + t.SetUpPackage("databases/py-trytond-ldap-authentication", + ".include \"../../devel/py-trytond/Makefile.common\"", + ".include \"../../lang/python/egg.mk\"") + t.CreateFileLines("devel/py-trytond/Makefile.common", + MkRcsID, + "PY_PATCHPLIST=\tyes") + t.CreateFileLines("lang/python/egg.mk", + MkRcsID, + "PY_PATCHPLIST=\tyes") + G.Pkgsrc.LoadInfrastructure() + + G.Check(t.File("databases/py-trytond-ldap-authentication")) + + // Since egg.mk and Makefile.common are unrelated, the definition of + // PY_PATCHPLIST is not redundant in these files. + t.CheckOutputEmpty() +} Index: pkgsrc/pkgtools/pkglint/files/pkglint.0 diff -u pkgsrc/pkgtools/pkglint/files/pkglint.0:1.38 pkgsrc/pkgtools/pkglint/files/pkglint.0:1.39 --- pkgsrc/pkgtools/pkglint/files/pkglint.0:1.38 Sat Jan 13 23:56:14 2018 +++ pkgsrc/pkgtools/pkglint/files/pkglint.0 Thu Feb 21 22:49:03 2019 @@ -1,7 +1,7 @@ -PKGLINT(1) NetBSD General Commands Manual PKGLINT(1) +PKGLINT(1) General Commands Manual PKGLINT(1) NNAAMMEE - ppkkgglliinntt -- static analyzer for pkgsrc packages + ppkkgglliinntt - static analyzer for pkgsrc packages SSYYNNOOPPSSIISS ppkkgglliinntt [--ooppttiioonnss] [_d_i_r _._._.] @@ -10,6 +10,7 @@ DDEESSCCRRIIPPTTIIOONN ppkkgglliinntt attempts to detect features of the named pkgsrc packages that are likely to be bugs, or that are simply deprecated. + OOppttiioonnss --CC{{[[nnoo--]]cchheecckk,,......}} Enable or disable specific checks. For a list of checks, see below. @@ -62,78 +63,39 @@ DDEESSCCRRIIPPTTIIOONN nnoonnee Disable all checks. - [[nnoo--]]AALLTTEERRNNAATTIIVVEESS Check the alternatives file. - - [[nnoo--]]DDEESSCCRR Check the DESCR file. - - [[nnoo--]]IINNSSTTAALLLL Check the INSTALL and DEINSTALL scripts. - - [[nnoo--]]MMaakkeeffiillee Check the package Makefile, including all included - files. - - [[nnoo--]]MMEESSSSAAGGEE Check MESSAGE files. - - [[nnoo--]]PPLLIISSTT Check PLIST files. - - [[nnoo--]]bbll33 Check buildlink3 Makefiles. - - [[nnoo--]]ddiissttiinnffoo Check the distinfo file. - [[nnoo--]]eexxttrraa Check remaining files in the package directory. - [[nnoo--]]mmkk Check Makefile fragments besides buildlink3. - - [[nnoo--]]ppaattcchheess Check the pkgsrc specific patch files. + [[nnoo--]]gglloobbaall Check inter-package consistency for distfile hashes + and used licenses. WWaarrnniinnggss aallll Enable all warnings. nnoonnee Disable all warnings. - [[nnoo--]]aabbssnnaammee Warn if a file contains an absolute pathname. - - [[nnoo--]]ddiirreeccttccmmdd Warn if a system command name is used instead of a - variable (e.g. sed instead of ${SED}). - [[nnoo--]]eexxttrraa Emit some additional warnings that are not enabled by default. - [[nnoo--]]oorrddeerr Warn if Makefile variables are not in the preferred - order. - [[nnoo--]]ppeerrmm Warn if a variable is used or modified outside its specified scope. - [[nnoo--]]pplliisstt--ddeepprr Warn if deprecated pathnames are used in _P_L_I_S_T files. - This warning is disabled by default. - - [[nnoo--]]pplliisstt--ssoorrtt Warn if items of a PLIST file are not sorted alpha- - betically. This warning is disabled by default. - [[nnoo--]]qquuoottiinngg Warn for possibly invalid quoting of make variables in shell programs and shell variables themselves. - [[nnoo--]]ssppaaccee Emit notes for inconsistent use of white-space. + [[nnoo--]]ssppaaccee Emit notes for inconsistent use of whitespace. [[nnoo--]]ssttyyllee Warn for stylistic issues that don't affect the build process. - [[nnoo--]]ttyyppeess Warn for some _M_a_k_e_f_i_l_e variables if their assigned - values do not match their type. - - [[nnoo--]]vvaarroorrddeerr Warn if the variables in a package _M_a_k_e_f_i_l_es are not - ordered in the way it is described the pkgsrc guide. - OOtthheerr aarrgguummeennttss - _d_i_r _._._. The pkgsrc directories to be checked. If omit- - ted, the current directory is checked. + _d_i_r _._._. The pkgsrc directories to be checked. If + omitted, the current directory is checked. FFIILLEESS - pkgsrc/mk/* Files from the pkgsrc infrastructure. + _p_k_g_s_r_c_/_m_k_/_* Files from the pkgsrc infrastructure. EEXXAAMMPPLLEESS - ppkkgglliinntt --CCnnoonnee,,ppaattcchheess .. - Checks the patches of the package in the current directory. + ppkkgglliinntt .. Checks the package in the current directory. ppkkgglliinntt --WWaallll //uussrr//ppkkggssrrcc//ddeevveell Checks the category Makefile and reports any warnings it can @@ -155,4 +117,4 @@ BBUUGGSS If you don't understand the messages, feel free to ask the author or on the mailing list. -NetBSD 7.0.2 January 14, 2018 NetBSD 7.0.2 +NetBSD 8.0 January 14, 2018 NetBSD 8.0 Index: pkgsrc/pkgtools/pkglint/files/shell_test.go diff -u pkgsrc/pkgtools/pkglint/files/shell_test.go:1.38 pkgsrc/pkgtools/pkglint/files/shell_test.go:1.39 --- pkgsrc/pkgtools/pkglint/files/shell_test.go:1.38 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/shell_test.go Thu Feb 21 22:49:03 2019 @@ -1148,7 +1148,7 @@ func (s *Suite) Test_SimpleCommandChecke // FIXME: In PERL5:Q and PERL6:Q, the :Q is wrong. t.CheckOutputLines( "WARN: Makefile:3: PERL5_VARS_CMD is defined but not used.", - "WARN: Makefile:4: The \"perl6\" tool is used but not added to USE_TOOLS.") + "WARN: Makefile:4: The \"${PERL6:Q}\" tool is used but not added to USE_TOOLS.") } // The package Makefile and other .mk files in a package directory @@ -1189,6 +1189,26 @@ func (s *Suite) Test_SimpleCommandChecke "WARN: file.mk:3: A shell comment should not contain semicolons.") } +// This test ensures that the command line options to INSTALL_*_DIR are properly +// parsed and do not lead to "can only handle one directory at a time" warnings. +func (s *Suite) Test_SimpleCommandChecker_checkInstallMulti(c *check.C) { + t := s.Init(c) + + t.SetUpVartypes() + mklines := t.NewMkLines("install.mk", + MkRcsID, + "", + "do-install:", + "\t${INSTALL_PROGRAM_DIR} -m 0555 -g ${APACHE_GROUP} -o ${APACHE_USER} \\", + "\t\t${DESTDIR}${PREFIX}/lib/apache-modules") + + mklines.Check() + + t.CheckOutputLines( + "NOTE: install.mk:4--5: You can use \"INSTALLATION_DIRS+= lib/apache-modules\" " + + "instead of \"${INSTALL_PROGRAM_DIR}\".") +} + func (s *Suite) Test_SimpleCommandChecker_checkPaxPe(c *check.C) { t := s.Init(c) Index: pkgsrc/pkgtools/pkglint/files/pkglint.1 diff -u pkgsrc/pkgtools/pkglint/files/pkglint.1:1.52 pkgsrc/pkgtools/pkglint/files/pkglint.1:1.53 --- pkgsrc/pkgtools/pkglint/files/pkglint.1:1.52 Sat Jan 13 23:56:14 2018 +++ pkgsrc/pkgtools/pkglint/files/pkglint.1 Thu Feb 21 22:49:03 2019 @@ -1,4 +1,4 @@ -.\" $NetBSD: pkglint.1,v 1.52 2018/01/13 23:56:14 rillig Exp $ +.\" $NetBSD: pkglint.1,v 1.53 2019/02/21 22:49:03 rillig Exp $ .\" From FreeBSD: portlint.1,v 1.8 1997/11/25 14:53:14 itojun Exp .\" .\" Copyright (c) 1997 by Jun-ichiro Itoh . @@ -6,7 +6,7 @@ .\" .\" Roland Illig , 2004, 2005. .\" Thomas Klausner , 2012. -.\" Roland Illig , 2015-2018. +.\" Roland Illig , 2015-2019. .\" .Dd January 14, 2018 .Dt PKGLINT 1 @@ -88,28 +88,10 @@ For a list of warnings, see below. Enable all checks. .It Cm none Disable all checks. -.It Cm [no-]ALTERNATIVES -Check the alternatives file. -.It Cm [no-]DESCR -Check the DESCR file. -.It Cm [no-]INSTALL -Check the INSTALL and DEINSTALL scripts. -.It Cm [no-]Makefile -Check the package Makefile, including all included files. -.It Cm [no-]MESSAGE -Check MESSAGE files. -.It Cm [no-]PLIST -Check PLIST files. -.It Cm [no-]bl3 -Check buildlink3 Makefiles. -.It Cm [no-]distinfo -Check the distinfo file. .It Cm [no-]extra Check remaining files in the package directory. -.It Cm [no-]mk -Check Makefile fragments besides buildlink3. -.It Cm [no-]patches -Check the pkgsrc specific patch files. +.It Cm [no-]global +Check inter-package consistency for distfile hashes and used licenses. .El .\" ======================================================================= .Ss Warnings @@ -118,41 +100,17 @@ Check the pkgsrc specific patch files. Enable all warnings. .It Cm none Disable all warnings. -.It Cm [no-]absname -Warn if a file contains an absolute pathname. -.It Cm [no-]directcmd -Warn if a system command name is used instead of a variable (e.g. sed -instead of ${SED}). .It Cm [no-]extra Emit some additional warnings that are not enabled by default. -.It Cm [no-]order -Warn if Makefile variables are not in the preferred order. .It Cm [no-]perm Warn if a variable is used or modified outside its specified scope. -.It Cm [no-]plist-depr -Warn if deprecated pathnames are used in -.Pa PLIST -files. -This warning is disabled by default. -.It Cm [no-]plist-sort -Warn if items of a PLIST file are not sorted alphabetically. -This warning is disabled by default. .It Cm [no-]quoting Warn for possibly invalid quoting of make variables in shell programs and shell variables themselves. .It Cm [no-]space -Emit notes for inconsistent use of white-space. +Emit notes for inconsistent use of whitespace. .It Cm [no-]style Warn for stylistic issues that don't affect the build process. -.It Cm [no-]types -Warn for some -.Pa Makefile -variables if their assigned values do not match -their type. -.It Cm [no-]varorder -Warn if the variables in a package -.Pa Makefile Ns -s are not ordered in the way it is described the pkgsrc guide. .El .\" ======================================================================= .Ss Other arguments @@ -168,8 +126,8 @@ Files from the pkgsrc infrastructure. .El .Sh EXAMPLES .Bl -tag -width Fl -.It Ic pkglint \-Cnone,patches \&. -Checks the patches of the package in the current directory. +.It Ic pkglint \&. +Checks the package in the current directory. .It Ic pkglint \-Wall /usr/pkgsrc/devel Checks the category Makefile and reports any warnings it can find. .El Index: pkgsrc/pkgtools/pkglint/files/pkglint.go diff -u pkgsrc/pkgtools/pkglint/files/pkglint.go:1.46 pkgsrc/pkgtools/pkglint/files/pkglint.go:1.47 --- pkgsrc/pkgtools/pkglint/files/pkglint.go:1.46 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/pkglint.go Thu Feb 21 22:49:03 2019 @@ -40,6 +40,9 @@ type Pkglint struct { res regex.Registry fileCache *FileCache interner StringInterner + + Hashes map[string]*Hash // Maps "alg:filename" => hash (inter-package check). + UsedLicenses map[string]bool // Maps "license name" => true (inter-package check). } func NewPkglint() Pkglint { @@ -50,24 +53,8 @@ func NewPkglint() Pkglint { } type CmdOpts struct { - // TODO: Are these Check* options really necessary? - // - // They had been introduced in order to make pkglint more flexible, - // but without any actual need. - - CheckAlternatives, - CheckBuildlink3, - CheckDescr, - CheckDistinfo, CheckExtra, - CheckGlobal, - CheckInstall, - CheckMakefile, - CheckMessage, - CheckMk, - CheckOptions, - CheckPatches, - CheckPlist bool + CheckGlobal bool // TODO: Are these Warn* options really all necessary? // @@ -76,16 +63,11 @@ type CmdOpts struct { // could be contrasted by a future --ignore option, in order to suppress // individual checks. - WarnDirectcmd, WarnExtra, - WarnOrder, WarnPerm, - WarnPlistDepr, - WarnPlistSort, WarnQuoting, WarnSpace, - WarnStyle, - WarnTypes bool + WarnStyle bool Profiling, ShowHelp, @@ -255,30 +237,14 @@ func (pkglint *Pkglint) ParseCommandLine opts.AddFlagVar('V', "version", &gopts.ShowVersion, false, "show the version number of pkglint") warn := opts.AddFlagGroup('W', "warning", "warning,...", "enable or disable groups of warnings") - check.AddFlagVar("ALTERNATIVES", &gopts.CheckAlternatives, true, "check ALTERNATIVES files") - check.AddFlagVar("bl3", &gopts.CheckBuildlink3, true, "check buildlink3.mk files") - check.AddFlagVar("DESCR", &gopts.CheckDescr, true, "check DESCR file") - check.AddFlagVar("distinfo", &gopts.CheckDistinfo, true, "check distinfo file") check.AddFlagVar("extra", &gopts.CheckExtra, false, "check various additional files") check.AddFlagVar("global", &gopts.CheckGlobal, false, "inter-package checks") - check.AddFlagVar("INSTALL", &gopts.CheckInstall, true, "check INSTALL and DEINSTALL scripts") - check.AddFlagVar("Makefile", &gopts.CheckMakefile, true, "check Makefiles") - check.AddFlagVar("MESSAGE", &gopts.CheckMessage, true, "check MESSAGE file") - check.AddFlagVar("mk", &gopts.CheckMk, true, "check other .mk files") - check.AddFlagVar("options", &gopts.CheckOptions, true, "check options.mk files") - check.AddFlagVar("patches", &gopts.CheckPatches, true, "check patches") - check.AddFlagVar("PLIST", &gopts.CheckPlist, true, "check PLIST files") - warn.AddFlagVar("directcmd", &gopts.WarnDirectcmd, true, "warn about use of direct command names instead of Make variables") warn.AddFlagVar("extra", &gopts.WarnExtra, false, "enable some extra warnings") - warn.AddFlagVar("order", &gopts.WarnOrder, true, "warn if Makefile entries are unordered") warn.AddFlagVar("perm", &gopts.WarnPerm, false, "warn about unforeseen variable definition and use") - warn.AddFlagVar("plist-depr", &gopts.WarnPlistDepr, false, "warn about deprecated paths in PLISTs") - warn.AddFlagVar("plist-sort", &gopts.WarnPlistSort, false, "warn about unsorted entries in PLISTs") warn.AddFlagVar("quoting", &gopts.WarnQuoting, false, "warn about quoting issues") warn.AddFlagVar("space", &gopts.WarnSpace, false, "warn about inconsistent use of whitespace") warn.AddFlagVar("style", &gopts.WarnStyle, false, "warn about stylistic issues") - warn.AddFlagVar("types", &gopts.WarnTypes, true, "do some simple type checking in Makefiles") remainingArgs, err := opts.Parse(args) if err != nil { @@ -471,9 +437,7 @@ func (pkglint *Pkglint) checkdirPackage( case path.Base(filename) == "Makefile": pkglint.checkExecutable(filename, st.Mode()) - if pkglint.Opts.CheckMakefile { - pkg.checkfilePackageMakefile(filename, mklines) - } + pkg.checkfilePackageMakefile(filename, mklines) default: pkglint.checkDirent(filename, st.Mode()) @@ -487,7 +451,7 @@ func (pkglint *Pkglint) checkdirPackage( pkg.checkLocallyModified(filename) } - if pkg.Pkgdir == "." && pkglint.Opts.CheckDistinfo && pkglint.Opts.CheckPatches { + if pkg.Pkgdir == "." { if havePatches && !haveDistinfo { // TODO: Add Line.RefTo to make the context clear. NewLineWhole(pkg.File(pkg.DistinfoFile)).Warnf("File not found. Please run %q.", bmake("makepatchsum")) @@ -543,12 +507,12 @@ func resolveVariableRefs(text string) (r if !visited[varname] { visited[varname] = true if G.Pkg != nil { - if value, ok := G.Pkg.vars.Value(varname); ok { + if value, ok := G.Pkg.vars.LastValueFound(varname); ok { return value } } if G.Mk != nil { - if value, ok := G.Mk.vars.Value(varname); ok { + if value, ok := G.Mk.vars.LastValueFound(varname); ok { return value } } @@ -700,55 +664,39 @@ func (pkglint *Pkglint) checkReg(filenam switch { case basename == "ALTERNATIVES": - if pkglint.Opts.CheckAlternatives { - CheckFileAlternatives(filename) - } + CheckFileAlternatives(filename) case basename == "buildlink3.mk": - if pkglint.Opts.CheckBuildlink3 { - if mklines := LoadMk(filename, NotEmpty|LogErrors); mklines != nil { - CheckLinesBuildlink3Mk(mklines) - } + if mklines := LoadMk(filename, NotEmpty|LogErrors); mklines != nil { + CheckLinesBuildlink3Mk(mklines) } case hasPrefix(basename, "DESCR"): - if pkglint.Opts.CheckDescr { - if lines := Load(filename, NotEmpty|LogErrors); lines != nil { - CheckLinesDescr(lines) - } + if lines := Load(filename, NotEmpty|LogErrors); lines != nil { + CheckLinesDescr(lines) } case basename == "distinfo": - if pkglint.Opts.CheckDistinfo { - if lines := Load(filename, NotEmpty|LogErrors); lines != nil { - CheckLinesDistinfo(lines) - } + if lines := Load(filename, NotEmpty|LogErrors); lines != nil { + CheckLinesDistinfo(lines) } case basename == "DEINSTALL" || basename == "INSTALL": - if pkglint.Opts.CheckInstall { - CheckFileOther(filename) - } + CheckFileOther(filename) case hasPrefix(basename, "MESSAGE"): - if pkglint.Opts.CheckMessage { - if lines := Load(filename, NotEmpty|LogErrors); lines != nil { - CheckLinesMessage(lines) - } + if lines := Load(filename, NotEmpty|LogErrors); lines != nil { + CheckLinesMessage(lines) } case basename == "options.mk": - if pkglint.Opts.CheckOptions { - if mklines := LoadMk(filename, NotEmpty|LogErrors); mklines != nil { - CheckLinesOptionsMk(mklines) - } + if mklines := LoadMk(filename, NotEmpty|LogErrors); mklines != nil { + CheckLinesOptionsMk(mklines) } case matches(basename, `^patch-[-\w.~+]*\w$`): - if pkglint.Opts.CheckPatches { - if lines := Load(filename, NotEmpty|LogErrors); lines != nil { - CheckLinesPatch(lines) - } + if lines := Load(filename, NotEmpty|LogErrors); lines != nil { + CheckLinesPatch(lines) } case matches(filename, `(?:^|/)patches/manual[^/]*$`): @@ -760,17 +708,13 @@ func (pkglint *Pkglint) checkReg(filenam NewLineWhole(filename).Warnf("Patch files should be named \"patch-\", followed by letters, '-', '_', '.', and digits only.") case (hasPrefix(basename, "Makefile") || hasSuffix(basename, ".mk")) && - !contains(filename, "files/") && - !contains(filename, "patches/"): - if pkglint.Opts.CheckMk { - CheckFileMk(filename) - } + !(hasPrefix(filename, "files/") || contains(filename, "/files/")) && + !(hasPrefix(filename, "patches/") || contains(filename, "/patches/")): + CheckFileMk(filename) case hasPrefix(basename, "PLIST"): - if pkglint.Opts.CheckPlist { - if lines := Load(filename, NotEmpty|LogErrors); lines != nil { - CheckLinesPlist(lines) - } + if lines := Load(filename, NotEmpty|LogErrors); lines != nil { + CheckLinesPlist(lines) } case hasPrefix(basename, "CHANGES-"): @@ -801,7 +745,7 @@ func (pkglint *Pkglint) matchesLicenseFi return false } - licenseFile, _ := pkglint.Pkg.vars.Value("LICENSE_FILE") + licenseFile := pkglint.Pkg.vars.LastValue("LICENSE_FILE") return basename == path.Base(licenseFile) } @@ -857,9 +801,9 @@ func CheckLinesTrailingEmptyLines(lines // to USE_TOOLS in the current scope (file or package). func (pkglint *Pkglint) Tool(command string, time ToolTime) (tool *Tool, usable bool) { varname := "" - // TODO: Replace regex with proper VarUse. - if m, toolVarname := match1(command, `^\$\{(\w+)\}$`); m { - varname = toolVarname + p := NewMkParser(nil, command, false) + if varUse := p.VarUse(); varUse != nil && p.EOF() { + varname = varUse.varname } tools := pkglint.tools() Index: pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go diff -u pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.15 pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.16 --- pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.15 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go Thu Feb 21 22:49:03 2019 @@ -435,6 +435,50 @@ func (s *Suite) Test_Pkgsrc_ListVersions "postgresql11"}) } +func (s *Suite) Test_Pkgsrc_ListVersions__ensure_transitive(c *check.C) { + names := []string{ + "base", + "base0", + "base000", + "base-client", + "base1", + "base01", + "base-client1", + "base5", + "base10"} + + keys := make(map[string]int) + for _, name := range names { + if m, _, versionStr := match2(name, `^(\D+)(\d+)$`); m { + keys[name] = toInt(versionStr, 0) + } + } + + less := func(a, b string) bool { + if keyI, keyJ := keys[a], keys[b]; keyI != keyJ { + return keyI < keyJ + } + return naturalLess(a, b) + } + + test := func(i int, j int) { + actual := less(names[i], names[j]) + expected := i < j + if actual != expected { + c.Check( + []interface{}{names[i], ifelseStr(actual, "<", "!<"), names[j]}, + check.DeepEquals, + []interface{}{names[i], ifelseStr(expected, "<", "!<"), names[j]}) + } + } + + for i := range names { + for j := range names { + test(i, j) + } + } +} + func (s *Suite) Test_Pkgsrc_ListVersions__numeric_multiple_numbers(c *check.C) { t := s.Init(c) Index: pkgsrc/pkgtools/pkglint/files/plist.go diff -u pkgsrc/pkgtools/pkglint/files/plist.go:1.36 pkgsrc/pkgtools/pkglint/files/plist.go:1.37 --- pkgsrc/pkgtools/pkglint/files/plist.go:1.36 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/plist.go Thu Feb 21 22:49:03 2019 @@ -65,13 +65,9 @@ func (ck *PlistChecker) Check(plainLines } CheckLinesTrailingEmptyLines(plainLines) - if G.Opts.WarnPlistSort { - sorter := NewPlistLineSorter(plines) - sorter.Sort() - if !sorter.autofixed { - SaveAutofixChanges(plainLines) - } - } else { + sorter := NewPlistLineSorter(plines) + sorter.Sort() + if !sorter.autofixed { SaveAutofixChanges(plainLines) } } @@ -112,8 +108,6 @@ func (ck *PlistChecker) collectFilesAndD ck.allDirs[dir] = pline } case first == '@': - // TODO: Check if this directive is still used, - // or if it has been removed during a pkg_install re-implementation. if m, dirname := match1(text, `^@exec \$\{MKDIR\} %D/(.*)$`); m { for dir := dirname; dir != "."; dir = path.Dir(dir) { ck.allDirs[dir] = pline @@ -199,7 +193,7 @@ func (ck *PlistChecker) checkPath(pline pline.Warnf(".orig files should not be in the PLIST.") } if hasSuffix(text, "/perllocal.pod") { - pline.Warnf("perllocal.pod files should not be in the PLIST.") + pline.Warnf("The perllocal.pod file should not be in the PLIST.") G.Explain( "This file is handled automatically by the INSTALL/DEINSTALL scripts", "since its contents depends on more than one package.") @@ -210,7 +204,7 @@ func (ck *PlistChecker) checkPath(pline } func (ck *PlistChecker) checkSorted(pline *PlistLine) { - if text := pline.text; G.Opts.WarnPlistSort && hasAlnumPrefix(text) && !containsVarRef(text) { + if text := pline.text; hasAlnumPrefix(text) && !containsVarRef(text) { if ck.lastFname != "" { if ck.lastFname > text && !G.Logger.Opts.Autofix { pline.Warnf("%q should be sorted before %q.", text, ck.lastFname) @@ -292,7 +286,7 @@ func (ck *PlistChecker) checkPathLib(pli switch ext := path.Ext(basename); ext { case ".la": - if G.Pkg != nil && !G.Pkg.vars.Defined("USE_LIBTOOL") { + if G.Pkg != nil && !G.Pkg.vars.Defined("USE_LIBTOOL") && ck.once.FirstTime("USE_LIBTOOL") { pline.Warnf("Packages that install libtool libraries should define USE_LIBTOOL.") } } @@ -378,9 +372,7 @@ func (ck *PlistChecker) checkPathShare(p } case hasPrefix(text, "share/doc/html/"): - if G.Opts.WarnPlistDepr { - pline.Warnf("Use of \"share/doc/html\" is deprecated. Use \"share/doc/${PKGBASE}\" instead.") - } + pline.Warnf("Use of \"share/doc/html\" is deprecated. Use \"share/doc/${PKGBASE}\" instead.") case G.Pkg != nil && G.Pkg.EffectivePkgbase != "" && (hasPrefix(text, "share/doc/"+G.Pkg.EffectivePkgbase+"/") || hasPrefix(text, "share/examples/"+G.Pkg.EffectivePkgbase+"/")): @@ -398,7 +390,7 @@ func (ck *PlistChecker) checkPathShare(p func (pline *PlistLine) CheckTrailingWhitespace() { if hasSuffix(pline.text, " ") || hasSuffix(pline.text, "\t") { - pline.Errorf("pkgsrc does not support filenames ending in whitespace.") + pline.Errorf("Pkgsrc does not support filenames ending in whitespace.") G.Explain( "Each character in the PLIST is relevant, even trailing whitespace.") } @@ -420,7 +412,7 @@ func (pline *PlistLine) CheckDirective(c case "exec", "unexec": switch { case contains(arg, "ldconfig") && !contains(arg, "/usr/bin/true"): - pline.Errorf("ldconfig must be used with \"||/usr/bin/true\".") + pline.Errorf("The ldconfig command must be used with \"||/usr/bin/true\".") } case "comment": Index: pkgsrc/pkgtools/pkglint/files/util.go diff -u pkgsrc/pkgtools/pkglint/files/util.go:1.36 pkgsrc/pkgtools/pkglint/files/util.go:1.37 --- pkgsrc/pkgtools/pkglint/files/util.go:1.36 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/util.go Thu Feb 21 22:49:03 2019 @@ -347,7 +347,8 @@ func mkopSubst(s string, left bool, from }) } -// relpath returns the relative path from `from` to `to`. +// relpath returns the relative path from the directory "from" +// to the filesystem entry "to". func relpath(from, to string) string { // From "dir" to "dir/subdir/...". @@ -454,30 +455,56 @@ func (o *Once) check(key uint64) bool { // Scope remembers which variables are defined and which are used // in a certain scope, such as a package or a file. type Scope struct { - defined map[string]MkLine + firstDef map[string]MkLine // TODO: Can this be removed? + lastDef map[string]MkLine + value map[string]string used map[string]MkLine fallback map[string]string } func NewScope() Scope { - return Scope{make(map[string]MkLine), make(map[string]MkLine), make(map[string]string)} + return Scope{ + make(map[string]MkLine), + make(map[string]MkLine), + make(map[string]string), + make(map[string]MkLine), + make(map[string]string)} } // Define marks the variable and its canonicalized form as defined. func (s *Scope) Define(varname string, mkline MkLine) { - if s.defined[varname] == nil { - s.defined[varname] = mkline - if trace.Tracing { - trace.Step2("Defining %q in %s", varname, mkline.String()) + def := func(name string) { + if s.firstDef[name] == nil { + s.firstDef[name] = mkline + if trace.Tracing { + trace.Step2("Defining %q for the first time in %s", name, mkline.String()) + } + } else if trace.Tracing { + trace.Step2("Defining %q in %s", name, mkline.String()) + } + + s.lastDef[name] = mkline + + // In most cases the defining lines are indeed variable assignments. + // Exceptions are comments that only document the variable but still mark + // it as defined so that it doesn't produce the "used but not defined" warning. + if mkline.IsVarassign() || mkline.IsCommentedVarassign() { + + switch mkline.Op() { + case opAssign, opAssignEval, opAssignShell: + s.value[name] = mkline.Value() + case opAssignAppend: + s.value[name] += " " + mkline.Value() + case opAssignDefault: + // No change to the value. + } } } + def(varname) varcanon := varnameCanon(varname) - if varcanon != varname && s.defined[varcanon] == nil { - s.defined[varcanon] = mkline - if trace.Tracing { - trace.Step2("Defining %q in %s", varcanon, mkline.String()) - } + if varcanon != varname { + def(varcanon) } } @@ -509,12 +536,12 @@ func (s *Scope) Use(varname string, line // Even if Defined returns true, FirstDefinition doesn't necessarily return true // since the latter ignores the default definitions from vardefs.go, keyword dummyVardefMkline. func (s *Scope) Defined(varname string) bool { - return s.defined[varname] != nil + return s.firstDef[varname] != nil } // DefinedSimilar tests whether the variable or its canonicalized form is defined. func (s *Scope) DefinedSimilar(varname string) bool { - if s.defined[varname] != nil { + if s.firstDef[varname] != nil { if trace.Tracing { trace.Step1("Variable %q is defined", varname) } @@ -522,7 +549,7 @@ func (s *Scope) DefinedSimilar(varname s } varcanon := varnameCanon(varname) - if s.defined[varcanon] != nil { + if s.firstDef[varcanon] != nil { if trace.Tracing { trace.Step2("Variable %q (similar to %q) is defined", varcanon, varname) } @@ -549,7 +576,25 @@ func (s *Scope) UsedSimilar(varname stri // // Having multiple definitions is typical in the branches of "if" statements. func (s *Scope) FirstDefinition(varname string) MkLine { - mkline := s.defined[varname] + mkline := s.firstDef[varname] + if mkline != nil && mkline.IsVarassign() { + lastLine := s.LastDefinition(varname) + if lastLine != mkline { + mkline.Notef("FirstDefinition differs from LastDefinition in %s.", mkline.RefTo(lastLine)) + } + return mkline + } + return nil // See NewPackage and G.Pkgsrc.UserDefinedVars +} + +// LastDefinition returns the line in which the variable has been defined last. +// +// Having multiple definitions is typical in the branches of "if" statements. +// +// Another typical case involves two files: the included file defines a default +// value, and the including file later overrides that value. +func (s *Scope) LastDefinition(varname string) MkLine { + mkline := s.lastDef[varname] if mkline != nil && mkline.IsVarassign() { return mkline } @@ -560,8 +605,23 @@ func (s *Scope) FirstUse(varname string) return s.used[varname] } -func (s *Scope) Value(varname string) (value string, found bool) { - mkline := s.FirstDefinition(varname) +// LastValue returns the value from the last variable definition. +// +// If an empty string is returned this can mean either that the +// variable value is indeed the empty string or that the variable +// was not found. To distinguish these cases, call LastValueFound instead. +func (s *Scope) LastValue(varname string) string { + value, _ := s.LastValueFound(varname) + return value +} + +func (s *Scope) LastValueFound(varname string) (value string, found bool) { + value, found = s.value[varname] + if found { + return + } + + mkline := s.LastDefinition(varname) if mkline != nil { return mkline.Value(), true } @@ -573,13 +633,14 @@ func (s *Scope) Value(varname string) (v func (s *Scope) DefineAll(other Scope) { var varnames []string - for varname := range other.defined { + for varname := range other.firstDef { varnames = append(varnames, varname) } sort.Strings(varnames) for _, varname := range varnames { - s.Define(varname, other.defined[varname]) + s.Define(varname, other.firstDef[varname]) + s.Define(varname, other.lastDef[varname]) } } @@ -661,22 +722,26 @@ func naturalLess(str1, str2 string) bool return len1 < len2 } -// RedundantScope checks for redundant variable definitions and accidentally -// overwriting variables. It tries to be as correct as possible by not flagging -// anything that is defined conditionally. There may be some edge cases though -// like defining PKGNAME, then evaluating it using :=, then defining it again. -// This pattern is so error-prone that it should not appear in pkgsrc at all, -// thus pkglint doesn't even expect it. (Well, except for the PKGNAME case, -// but that's deep in the infrastructure and only affects the "nb13" extension.) +// RedundantScope checks for redundant variable definitions and for variables +// that are accidentally overwritten. It tries to be as correct as possible +// by not flagging anything that is defined conditionally. +// +// There may be some edge cases though like defining PKGNAME, then evaluating +// it using :=, then defining it again. This pattern is so error-prone that +// it should not appear in pkgsrc at all, thus pkglint doesn't even expect it. +// (Well, except for the PKGNAME case, but that's deep in the infrastructure +// and only affects the "nb13" extension.) type RedundantScope struct { vars map[string]*redundantScopeVarinfo dirLevel int // The number of enclosing directives (.if, .for). - OnIgnore func(old, new MkLine) + includePath includePath + OnRedundant func(old, new MkLine) OnOverwrite func(old, new MkLine) } type redundantScopeVarinfo struct { - mkline MkLine - value string + mkline MkLine + includePath includePath + value string } func NewRedundantScope() *RedundantScope { @@ -684,10 +749,19 @@ func NewRedundantScope() *RedundantScope } func (s *RedundantScope) Handle(mkline MkLine) { + if mkline.firstLine == 1 { + s.includePath.push(mkline.Location.Filename) + } else { + s.includePath.popUntil(mkline.Location.Filename) + } + switch { case mkline.IsVarassign(): varname := mkline.Varname() if s.dirLevel != 0 { + // Since the variable is defined or assigned conditionally, + // it becomes too complicated for pkglint to check all possible + // code paths. Therefore ignore the variable from now on. s.vars[varname] = nil break } @@ -707,7 +781,7 @@ func (s *RedundantScope) Handle(mkline M if op == opAssignAppend { value = " " + value } - s.vars[varname] = &redundantScopeVarinfo{mkline, value} + s.vars[varname] = &redundantScopeVarinfo{mkline, s.includePath.copy(), value} } } else if existing != nil { @@ -717,12 +791,24 @@ func (s *RedundantScope) Handle(mkline M switch op { case opAssign: - s.OnOverwrite(existing.mkline, mkline) + if s.includePath.includes(existing.includePath) { + // This is the usual pattern of including a file and + // then overwriting some of them. Although technically + // this overwrites the previous definition, it is not + // worth a warning since this is used a lot and + // intentionally. + } else { + s.OnOverwrite(existing.mkline, mkline) + } existing.value = value case opAssignAppend: existing.value += " " + value case opAssignDefault: - s.OnIgnore(existing.mkline, mkline) + if existing.includePath.includes(s.includePath) { + s.OnRedundant(mkline, existing.mkline) + } else if s.includePath.includes(existing.includePath) || s.includePath.equals(existing.includePath) { + s.OnRedundant(existing.mkline, mkline) + } case opAssignShell, opAssignEval: s.vars[varname] = nil // Won't be checked further. } @@ -738,6 +824,46 @@ func (s *RedundantScope) Handle(mkline M } } +type includePath struct { + files []string +} + +func (p *includePath) push(filename string) { + p.files = append(p.files, filename) +} + +func (p *includePath) popUntil(filename string) { + for p.files[len(p.files)-1] != filename { + p.files = p.files[:len(p.files)-1] + } +} + +func (p *includePath) includes(other includePath) bool { + for i, filename := range p.files { + if i < len(other.files) && other.files[i] == filename { + continue + } + return false + } + return len(p.files) < len(other.files) +} + +func (p *includePath) equals(other includePath) bool { + if len(p.files) != len(other.files) { + return false + } + for i, filename := range p.files { + if other.files[i] != filename { + return false + } + } + return true +} + +func (p *includePath) copy() includePath { + return includePath{append([]string(nil), p.files...)} +} + // IsPrefs returns whether the given file, when included, loads the user // preferences. func IsPrefs(filename string) bool { Index: pkgsrc/pkgtools/pkglint/files/util_test.go diff -u pkgsrc/pkgtools/pkglint/files/util_test.go:1.22 pkgsrc/pkgtools/pkglint/files/util_test.go:1.23 --- pkgsrc/pkgtools/pkglint/files/util_test.go:1.22 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/util_test.go Thu Feb 21 22:49:03 2019 @@ -322,6 +322,24 @@ func (s *Suite) Test_isLocallyModified(c c.Check(isLocallyModified(t.File("unmodified")), equals, false) } +func (s *Suite) Test_Scope_Define(c *check.C) { + t := s.Init(c) + + scope := NewScope() + scope.Define("BUILD_DIRS", t.NewMkLine("file.mk", 121, "BUILD_DIRS=\tone two three")) + + c.Check(scope.LastValue("BUILD_DIRS"), equals, "one two three") + + scope.Define("BUILD_DIRS", t.NewMkLine("file.mk", 123, "BUILD_DIRS+=\tfour")) + + c.Check(scope.LastValue("BUILD_DIRS"), equals, "one two three four") + + // Later default assignments do not have an effect. + scope.Define("BUILD_DIRS", t.NewMkLine("file.mk", 123, "BUILD_DIRS?=\tdefault")) + + c.Check(scope.LastValue("BUILD_DIRS"), equals, "one two three four") +} + func (s *Suite) Test_Scope_Defined(c *check.C) { t := s.Init(c) @@ -360,7 +378,8 @@ func (s *Suite) Test_Scope_DefineAll(c * dst := NewScope() dst.DefineAll(src) - c.Check(dst.defined, check.HasLen, 0) + c.Check(dst.firstDef, check.HasLen, 0) + c.Check(dst.lastDef, check.HasLen, 0) c.Check(dst.used, check.HasLen, 0) src.Define("VAR", t.NewMkLine("file.mk", 1, "VAR=value")) @@ -373,7 +392,7 @@ func (s *Suite) Test_Scope_FirstDefiniti t := s.Init(c) mkline1 := t.NewMkLine("fname.mk", 3, "VAR=\tvalue") - mkline2 := t.NewMkLine("fname.mk", 3, ".if ${VAR::=value}") + mkline2 := t.NewMkLine("fname.mk", 3, ".if ${SNEAKY::=value}") scope := NewScope() scope.Define("VAR", mkline1) @@ -388,6 +407,25 @@ func (s *Suite) Test_Scope_FirstDefiniti t.Check(scope.FirstDefinition("SNEAKY"), check.IsNil) } +func (s *Suite) Test_Scope_LastValue(c *check.C) { + t := s.Init(c) + + mklines := t.NewMkLines("file.mk", + MkRcsID, + "VAR=\tfirst", + "VAR=\tsecond", + ".if 1", + "VAR=\tthird (conditional)", + ".endif") + + mklines.Check() + + t.Check(mklines.vars.LastValue("VAR"), equals, "third (conditional)") + + t.CheckOutputLines( + "WARN: file.mk:2: VAR is defined but not used.") +} + func (s *Suite) Test_Scope__no_tracing(c *check.C) { t := s.Init(c) @@ -672,3 +710,28 @@ func (s *Suite) Test_StringInterner(c *c t.Check(si.Intern("Hello, world"), equals, "Hello, world") t.Check(si.Intern("Hello, world"[0:5]), equals, "Hello") } + +func (s *Suite) Test_includePath_includes(c *check.C) { + t := s.Init(c) + + path := func(locations ...string) includePath { + return includePath{locations} + } + + var ( + m = path("Makefile") + mc = path("Makefile", "Makefile.common") + mco = path("Makefile", "Makefile.common", "other.mk") + mo = path("Makefile", "other.mk") + ) + + t.Check(m.includes(m), equals, false) + + t.Check(m.includes(mc), equals, true) + t.Check(m.includes(mco), equals, true) + t.Check(mc.includes(mco), equals, true) + + t.Check(mc.includes(m), equals, false) + t.Check(mc.includes(mo), equals, false) + t.Check(mo.includes(mc), equals, false) +} Index: pkgsrc/pkgtools/pkglint/files/vartypecheck.go diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.48 pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.49 --- pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.48 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/vartypecheck.go Thu Feb 21 22:49:03 2019 @@ -232,14 +232,6 @@ func (cv *VartypeCheck) Comment() { cv.Warnf("COMMENT should not begin with %q.", first) } - if m, isA := match1(value, `\b(is an?)\b`); m { - cv.Warnf("COMMENT should not contain %q.", isA) - G.Explain( - "The words \"package is a\" are redundant.", - "Since every package comment could start with them,", - "it is better to remove this redundancy in all cases.") - } - if G.Pkg != nil && G.Pkg.EffectivePkgbase != "" { pkgbase := G.Pkg.EffectivePkgbase if hasPrefix(strings.ToLower(value), strings.ToLower(pkgbase+" ")) { @@ -255,6 +247,14 @@ func (cv *VartypeCheck) Comment() { cv.Warnf("COMMENT should start with a capital letter.") } + if m, isA := match1(value, `\b(is an?)\b`); m { + cv.Warnf("COMMENT should not contain %q.", isA) + G.Explain( + "The words \"package is a\" are redundant.", + "Since every package comment could start with them,", + "it is better to remove this redundancy in all cases.") + } + if hasSuffix(value, ".") { cv.Warnf("COMMENT should not end with a period.") } Index: pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.41 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.42 --- pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.41 Sat Jan 26 16:31:33 2019 +++ pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go Thu Feb 21 22:49:03 2019 @@ -134,6 +134,7 @@ func (s *Suite) Test_VartypeCheck_Commen "Package is an awesome package", "The Big New Package is a great package", "Converter converts between measurement units", + "Converter is a unit converter", "\"Official\" office suite", "'SQL injection fuzzer") @@ -148,7 +149,9 @@ func (s *Suite) Test_VartypeCheck_Commen "WARN: filename:7: COMMENT should not contain \"is a\".", "WARN: filename:8: COMMENT should not contain \"is an\".", "WARN: filename:9: COMMENT should not contain \"is a\".", - "WARN: filename:10: COMMENT should not start with the package name.") + "WARN: filename:10: COMMENT should not start with the package name.", + "WARN: filename:11: COMMENT should not start with the package name.", + "WARN: filename:11: COMMENT should not contain \"is a\".") } func (s *Suite) Test_VartypeCheck_ConfFiles(c *check.C) { @@ -516,7 +519,8 @@ func (s *Suite) Test_VartypeCheck_Homepa vt.Output( "WARN: filename:4: HOMEPAGE should not be defined in terms of MASTER_SITEs.") - delete(G.Pkg.vars.defined, "MASTER_SITES") + delete(G.Pkg.vars.firstDef, "MASTER_SITES") + delete(G.Pkg.vars.lastDef, "MASTER_SITES") G.Pkg.vars.Define("MASTER_SITES", t.NewMkLine(G.Pkg.File("Makefile"), 5, "MASTER_SITES=\thttps://cdn.NetBSD.org/pub/pkgsrc/distfiles/")) @@ -527,7 +531,8 @@ func (s *Suite) Test_VartypeCheck_Homepa "WARN: filename:5: HOMEPAGE should not be defined in terms of MASTER_SITEs. " + "Use https://cdn.NetBSD.org/pub/pkgsrc/distfiles/ directly.") - delete(G.Pkg.vars.defined, "MASTER_SITES") + delete(G.Pkg.vars.firstDef, "MASTER_SITES") + delete(G.Pkg.vars.lastDef, "MASTER_SITES") G.Pkg.vars.Define("MASTER_SITES", t.NewMkLine(G.Pkg.File("Makefile"), 5, "MASTER_SITES=\t${MASTER_SITE_GITHUB}")) Index: pkgsrc/pkgtools/pkglint/files/getopt/getopt.go diff -u pkgsrc/pkgtools/pkglint/files/getopt/getopt.go:1.7 pkgsrc/pkgtools/pkglint/files/getopt/getopt.go:1.8 --- pkgsrc/pkgtools/pkglint/files/getopt/getopt.go:1.7 Sun Dec 2 01:57:48 2018 +++ pkgsrc/pkgtools/pkglint/files/getopt/getopt.go Thu Feb 21 22:49:04 2019 @@ -34,18 +34,55 @@ func (o *Options) AddFlagGroup(shortName return grp } +// AddFlagVar adds a boolean flag to the options. +// +// Example: +// var verbose bool +// +// opts := NewOptions() +// opts.AddFlagVar('v', "verbose", &verbose, false, "Enable verbose output") +// +// This option can be used in the following ways: +// -v +// --verbose +// --verbose=on (or yes, 1, true, enabled) +// --verbose=off (or no, 0, false, disabled) func (o *Options) AddFlagVar(shortName rune, longName string, pflag *bool, defval bool, description string) { *pflag = defval opt := option{shortName, longName, "", description, pflag} o.options = append(o.options, &opt) } +// AddStrVar adds a string option to the options. +// +// Example: +// var outputFilename string +// +// opts := NewOptions() +// opts.AddStrVar('o', "output", &outputFilename, "", "Write the output to the given file") +// +// This option can be used in the following ways: +// -o output.txt +// --output output.txt +// --output=output.txt func (o *Options) AddStrVar(shortName rune, longName string, pstr *string, defval string, description string) { *pstr = defval opt := option{shortName, longName, "", description, pstr} o.options = append(o.options, &opt) } +// AddStrList adds a string option to the options that can be used multiple times. +// +// Example: +// var includes []string +// +// opts := NewOptions() +// opts.AddStrList('i', "include", &includes, nil, "Include the files matching the pattern") +// +// This option can be used in the following ways: +// -i "*.txt" -i "*.docx" +// --include "*.txt" --include "*.md" +// --include="*.txt" --include="*.md" func (o *Options) AddStrList(shortName rune, longName string, plist *[]string, description string) { *plist = []string{} opt := option{shortName, longName, "", description, plist} Index: pkgsrc/pkgtools/pkglint/files/textproc/lexer.go diff -u pkgsrc/pkgtools/pkglint/files/textproc/lexer.go:1.3 pkgsrc/pkgtools/pkglint/files/textproc/lexer.go:1.4 --- pkgsrc/pkgtools/pkglint/files/textproc/lexer.go:1.3 Mon Dec 17 00:15:39 2018 +++ pkgsrc/pkgtools/pkglint/files/textproc/lexer.go Thu Feb 21 22:49:04 2019 @@ -264,6 +264,7 @@ func (bs *ByteSet) Contains(b byte) bool var ( Alnum = NewByteSet("A-Za-z0-9") // Alphanumerical, without underscore AlnumU = NewByteSet("A-Za-z0-9_") // Alphanumerical, including underscore + Alpha = NewByteSet("A-Za-z") // Alphabetical, without underscore Digit = NewByteSet("0-9") // The digits zero to nine Upper = NewByteSet("A-Z") // The uppercase letters from A to Z Lower = NewByteSet("a-z") // The lowercase letters from a to z Index: pkgsrc/pkgtools/pkglint/files/textproc/lexer_test.go diff -u pkgsrc/pkgtools/pkglint/files/textproc/lexer_test.go:1.3 pkgsrc/pkgtools/pkglint/files/textproc/lexer_test.go:1.4 --- pkgsrc/pkgtools/pkglint/files/textproc/lexer_test.go:1.3 Mon Dec 17 00:15:39 2018 +++ pkgsrc/pkgtools/pkglint/files/textproc/lexer_test.go Thu Feb 21 22:49:04 2019 @@ -391,6 +391,18 @@ func (s *Suite) Test__XPrint(c *check.C) c.Check(set.Contains(0xA0), equals, false) } +func (s *Suite) Test__Alpha(c *check.C) { + set := Alpha + + c.Check(set.Contains(0x00), equals, false) + c.Check(set.Contains('@'), equals, false) + c.Check(set.Contains('A'), equals, true) + c.Check(set.Contains('Z'), equals, true) + c.Check(set.Contains('`'), equals, false) + c.Check(set.Contains('a'), equals, true) + c.Check(set.Contains('z'), equals, true) +} + func (s *Suite) Test__test_names(c *check.C) { ck := intqa.NewTestNameChecker(c) ck.AllowCamelCaseDescriptions( Index: pkgsrc/pkgtools/pkglint/files/trace/tracing.go diff -u pkgsrc/pkgtools/pkglint/files/trace/tracing.go:1.5 pkgsrc/pkgtools/pkglint/files/trace/tracing.go:1.6 --- pkgsrc/pkgtools/pkglint/files/trace/tracing.go:1.5 Fri Dec 21 08:05:24 2018 +++ pkgsrc/pkgtools/pkglint/files/trace/tracing.go Thu Feb 21 22:49:04 2019 @@ -37,14 +37,32 @@ func (t *Tracer) Step2(format string, ar t.Stepf(format, arg0, arg1) } +// Call0 is used to trace a no-arguments function call. +// +// Usage: +// if trace.Tracing { +// defer trace.Call0()() +// } func (t *Tracer) Call0() func() { return t.traceCall() } +// Call1 is used to trace a function call with a single string argument. +// +// Usage: +// if trace.Tracing { +// defer trace.Call1(str1)() +// } func (t *Tracer) Call1(arg1 string) func() { return t.traceCall(arg1) } +// Call2 is used to trace a function call with 2 string arguments. +// +// Usage: +// if trace.Tracing { +// defer trace.Call2(str1, str2)() +// } func (t *Tracer) Call2(arg1, arg2 string) func() { return t.traceCall(arg1, arg2) } @@ -96,19 +114,19 @@ func (t *Tracer) traceCall(args ...inter panic("Internal pkglint error: calls to trace.Call must only occur in tracing mode") } - funcname := "?" + functionName := "?" if pc, _, _, ok := runtime.Caller(2); ok { if fn := runtime.FuncForPC(pc); fn != nil { - funcname = strings.TrimPrefix(fn.Name(), "netbsd.org/pkglint.") + functionName = strings.TrimPrefix(fn.Name(), "netbsd.org/pkglint.") } } indent := t.traceIndent() - _, _ = fmt.Fprintf(t.Out, "TRACE: %s+ %s(%s)\n", indent, funcname, argsStr(withoutResults(args))) + _, _ = fmt.Fprintf(t.Out, "TRACE: %s+ %s(%s)\n", indent, functionName, argsStr(withoutResults(args))) t.depth++ return func() { t.depth-- - _, _ = fmt.Fprintf(t.Out, "TRACE: %s- %s(%s)\n", indent, funcname, argsStr(withResults(args))) + _, _ = fmt.Fprintf(t.Out, "TRACE: %s- %s(%s)\n", indent, functionName, argsStr(withResults(args))) } } --_----------=_1550789344110970--