pkgsrc-Changes archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

CVS commit: pkgsrc/pkgtools/pkglint



Module Name:    pkgsrc
Committed By:   rillig
Date:           Sat Mar 24 14:32:49 UTC 2018

Modified Files:
        pkgsrc/pkgtools/pkglint: Makefile
        pkgsrc/pkgtools/pkglint/files: autofix.go autofix_test.go
            buildlink3_test.go category_test.go check_test.go distinfo.go
            licenses.go licenses_test.go line.go linechecker.go mkline.go
            mkline_test.go mklinechecker.go mklinechecker_test.go mklines.go
            mklines_test.go mkparser_test.go package.go package_test.go
            pkglint.go pkglint_test.go plist.go plist_test.go shell.go
            shell_test.go toplevel_test.go util.go vardefs.go vartype_test.go
            vartypecheck.go vartypecheck_test.go
Added Files:
        pkgsrc/pkgtools/pkglint/files: pkgsrc.go pkgsrc_test.go tools.go

Log Message:
Update pkglint to 5.5.7

Changes since 5.5.6:

* When pkglint warns about files that are accidentally executable, it
  offers to fix the file permissions.

* Warn about ${HOMEPAGE:=repository/}, since the := modifier should
  only be used with MASTER_SITES.

* When the distinfo file is missing, suggest setting NO_CHECKSUM.

* Several refactorings.


To generate a diff of this commit:
cvs rdiff -u -r1.531 -r1.532 pkgsrc/pkgtools/pkglint/Makefile
cvs rdiff -u -r1.5 -r1.6 pkgsrc/pkgtools/pkglint/files/autofix.go \
    pkgsrc/pkgtools/pkglint/files/vartype_test.go
cvs rdiff -u -r1.4 -r1.5 pkgsrc/pkgtools/pkglint/files/autofix_test.go
cvs rdiff -u -r1.12 -r1.13 pkgsrc/pkgtools/pkglint/files/buildlink3_test.go
cvs rdiff -u -r1.8 -r1.9 pkgsrc/pkgtools/pkglint/files/category_test.go \
    pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go
cvs rdiff -u -r1.18 -r1.19 pkgsrc/pkgtools/pkglint/files/check_test.go \
    pkgsrc/pkgtools/pkglint/files/distinfo.go \
    pkgsrc/pkgtools/pkglint/files/mklines_test.go
cvs rdiff -u -r1.10 -r1.11 pkgsrc/pkgtools/pkglint/files/licenses.go \
    pkgsrc/pkgtools/pkglint/files/licenses_test.go \
    pkgsrc/pkgtools/pkglint/files/mklinechecker.go
cvs rdiff -u -r1.21 -r1.22 pkgsrc/pkgtools/pkglint/files/line.go \
    pkgsrc/pkgtools/pkglint/files/mklines.go
cvs rdiff -u -r1.6 -r1.7 pkgsrc/pkgtools/pkglint/files/linechecker.go
cvs rdiff -u -r1.29 -r1.30 pkgsrc/pkgtools/pkglint/files/mkline.go
cvs rdiff -u -r1.31 -r1.32 pkgsrc/pkgtools/pkglint/files/mkline_test.go
cvs rdiff -u -r1.9 -r1.10 pkgsrc/pkgtools/pkglint/files/mkparser_test.go \
    pkgsrc/pkgtools/pkglint/files/toplevel_test.go
cvs rdiff -u -r1.26 -r1.27 pkgsrc/pkgtools/pkglint/files/package.go
cvs rdiff -u -r1.19 -r1.20 pkgsrc/pkgtools/pkglint/files/package_test.go
cvs rdiff -u -r1.28 -r1.29 pkgsrc/pkgtools/pkglint/files/pkglint.go
cvs rdiff -u -r1.16 -r1.17 pkgsrc/pkgtools/pkglint/files/pkglint_test.go
cvs rdiff -u -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/pkgsrc.go \
    pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go \
    pkgsrc/pkgtools/pkglint/files/tools.go
cvs rdiff -u -r1.23 -r1.24 pkgsrc/pkgtools/pkglint/files/plist.go
cvs rdiff -u -r1.22 -r1.23 pkgsrc/pkgtools/pkglint/files/plist_test.go \
    pkgsrc/pkgtools/pkglint/files/shell.go \
    pkgsrc/pkgtools/pkglint/files/shell_test.go \
    pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
cvs rdiff -u -r1.20 -r1.21 pkgsrc/pkgtools/pkglint/files/util.go
cvs rdiff -u -r1.37 -r1.38 pkgsrc/pkgtools/pkglint/files/vardefs.go
cvs rdiff -u -r1.30 -r1.31 pkgsrc/pkgtools/pkglint/files/vartypecheck.go

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: pkgsrc/pkgtools/pkglint/Makefile
diff -u pkgsrc/pkgtools/pkglint/Makefile:1.531 pkgsrc/pkgtools/pkglint/Makefile:1.532
--- pkgsrc/pkgtools/pkglint/Makefile:1.531      Sun Mar  4 20:34:32 2018
+++ pkgsrc/pkgtools/pkglint/Makefile    Sat Mar 24 14:32:49 2018
@@ -1,6 +1,6 @@
-# $NetBSD: Makefile,v 1.531 2018/03/04 20:34:32 rillig Exp $
+# $NetBSD: Makefile,v 1.532 2018/03/24 14:32:49 rillig Exp $
 
-PKGNAME=       pkglint-5.5.6
+PKGNAME=       pkglint-5.5.7
 DISTFILES=     # none
 CATEGORIES=    pkgtools
 

Index: pkgsrc/pkgtools/pkglint/files/autofix.go
diff -u pkgsrc/pkgtools/pkglint/files/autofix.go:1.5 pkgsrc/pkgtools/pkglint/files/autofix.go:1.6
--- pkgsrc/pkgtools/pkglint/files/autofix.go:1.5        Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/autofix.go    Sat Mar 24 14:32:49 2018
@@ -38,6 +38,22 @@ func NewAutofix(line Line) *Autofix {
                lines: append([]*RawLine{}, line.raw...)}
 }
 
+// Custom runs a custom fix action, unless the fix is skipped anyway
+// because of the --only option.
+//
+// The fixer function must always call Describef.
+// If printAutofix or autofix is true, the fix should be done in
+// memory as far as possible (e.g. changes to the text of the line).
+// If autofix is true, the fix should be done permanently
+// (e.g. direct changes to the file system).
+func (fix *Autofix) Custom(fixer func(printAutofix, autofix bool)) {
+       if fix.skip() {
+               return
+       }
+
+       fixer(G.opts.PrintAutofix, G.opts.Autofix)
+}
+
 func (fix *Autofix) Replace(from string, to string) {
        fix.ReplaceAfter("", from, to)
 }
@@ -62,8 +78,10 @@ func (fix *Autofix) ReplaceAfter(prefix,
        }
 }
 
-// ReplaceRegex replaces the first or all occurrences of the `from` pattern
-// with the fixed string `toText`. Placeholders like `$1` are _not_ expanded.
+// ReplaceRegex replaces the first howOften or all occurrences (if negative)
+// of the `from` pattern with the fixed string `toText`.
+//
+// Placeholders like `$1` are _not_ expanded in the `toText`.
 // (If you know how to do the expansion correctly, feel free to implement it.)
 func (fix *Autofix) ReplaceRegex(from regex.Pattern, toText string, howOften int) {
        if fix.skip() {
@@ -223,10 +241,10 @@ func (fix *Autofix) Explain(explanation 
 // * records the fixes in the line (--autofix)
 func (fix *Autofix) Apply() {
        line := fix.line
-       if line.firstLine < 1 {
-               return
-       }
 
+       if fix.diagFormat == "" {
+               panic("Each autofix must have a diagnostic.")
+       }
        G.explainNext = shallBeLogged(fix.diagFormat)
        if G.explainNext {
                logDiagnostic := fix.level != nil && fix.diagFormat != "Silent-Magic-Diagnostic" &&
@@ -239,7 +257,11 @@ func (fix *Autofix) Apply() {
                logRepair := len(fix.actions) > 0 && (G.opts.Autofix || G.opts.PrintAutofix)
                if logRepair {
                        for _, action := range fix.actions {
-                               logs(llAutofix, line.Filename, strconv.Itoa(action.lineno), "", action.description)
+                               lineno := ""
+                               if action.lineno != 0 {
+                                       lineno = strconv.Itoa(action.lineno)
+                               }
+                               logs(llAutofix, line.Filename, lineno, "", action.description)
                        }
                }
 
Index: pkgsrc/pkgtools/pkglint/files/vartype_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vartype_test.go:1.5 pkgsrc/pkgtools/pkglint/files/vartype_test.go:1.6
--- pkgsrc/pkgtools/pkglint/files/vartype_test.go:1.5   Mon Feb 19 12:40:38 2018
+++ pkgsrc/pkgtools/pkglint/files/vartype_test.go       Sat Mar 24 14:32:49 2018
@@ -5,15 +5,17 @@ import (
 )
 
 func (s *Suite) Test_Vartype_EffectivePermissions(c *check.C) {
-       G.globalData.InitVartypes()
+       t := s.Init(c)
 
-       if t := G.globalData.vartypes["PREFIX"]; c.Check(t, check.NotNil) {
+       t.SetupVartypes()
+
+       if t := G.Pkgsrc.vartypes["PREFIX"]; c.Check(t, check.NotNil) {
                c.Check(t.basicType.name, equals, "Pathname")
                c.Check(t.aclEntries, check.DeepEquals, []ACLEntry{{glob: "*", permissions: aclpUse}})
                c.Check(t.EffectivePermissions("Makefile"), equals, aclpUse)
        }
 
-       if t := G.globalData.vartypes["EXTRACT_OPTS"]; c.Check(t, check.NotNil) {
+       if t := G.Pkgsrc.vartypes["EXTRACT_OPTS"]; c.Check(t, check.NotNil) {
                c.Check(t.basicType.name, equals, "ShellWord")
                c.Check(t.EffectivePermissions("Makefile"), equals, aclpAppend|aclpSet)
                c.Check(t.EffectivePermissions("../Makefile"), equals, aclpAppend|aclpSet)

Index: pkgsrc/pkgtools/pkglint/files/autofix_test.go
diff -u pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.4 pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.5
--- pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.4   Mon Feb 19 12:40:38 2018
+++ pkgsrc/pkgtools/pkglint/files/autofix_test.go       Sat Mar 24 14:32:49 2018
@@ -1,6 +1,9 @@
 package main
 
-import "gopkg.in/check.v1"
+import (
+       "gopkg.in/check.v1"
+       "strings"
+)
 
 func (s *Suite) Test_Autofix_ReplaceRegex(c *check.C) {
        t := s.Init(c)
@@ -387,3 +390,46 @@ func (s *Suite) Test_SaveAutofixChanges(
        // And therefore, no AUTOFIX action must appear in the log.
        t.CheckOutputEmpty()
 }
+
+func (s *Suite) Test_Autofix_CustomFix(c *check.C) {
+       t := s.Init(c)
+
+       lines := t.NewLines("Makefile",
+               "line1",
+               "line2",
+               "line3")
+
+       doFix := func(line Line) {
+               fix := line.Autofix()
+               fix.Warnf("Please write in ALL-UPPERCASE")
+               fix.Custom(func(printAutofix, autofix bool) {
+                       fix.Describef(int(line.firstLine), "Converting to uppercase")
+                       if printAutofix || autofix {
+                               line.Text = strings.ToUpper(line.Text)
+                       }
+               })
+               fix.Apply()
+       }
+
+       doFix(lines[0])
+
+       t.CheckOutputLines(
+               "WARN: Makefile:1: Please write in ALL-UPPERCASE")
+
+       t.SetupCommandLine("--show-autofix")
+
+       doFix(lines[1])
+
+       t.CheckOutputLines(
+               "WARN: Makefile:2: Please write in ALL-UPPERCASE",
+               "AUTOFIX: Makefile:2: Converting to uppercase")
+       c.Check(lines[1].Text, equals, "LINE2")
+
+       t.SetupCommandLine("--autofix")
+
+       doFix(lines[2])
+
+       t.CheckOutputLines(
+               "AUTOFIX: Makefile:3: Converting to uppercase")
+       c.Check(lines[2].Text, equals, "LINE3")
+}

Index: pkgsrc/pkgtools/pkglint/files/buildlink3_test.go
diff -u pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.12 pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.13
--- pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.12       Mon Feb 19 12:40:38 2018
+++ pkgsrc/pkgtools/pkglint/files/buildlink3_test.go    Sat Mar 24 14:32:49 2018
@@ -5,7 +5,7 @@ import "gopkg.in/check.v1"
 func (s *Suite) Test_ChecklinesBuildlink3Mk(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("buildlink3.mk",
                MkRcsID,
                "# XXX This file was created automatically using createbuildlink-@PKGVERSION@",
@@ -40,7 +40,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_name_mismatch(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Pkg = NewPackage("x11/hs-X11")
        G.Pkg.EffectivePkgbase = "X11"
        G.Pkg.EffectivePkgnameLine = t.NewMkLine("Makefile", 3, "DISTNAME=\tX11-1.0")
@@ -68,7 +68,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_name_mismatch_multiple_inclusion(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("buildlink3.mk",
                MkRcsID,
                "",
@@ -91,7 +91,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_name_mismatch_abi_api(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("buildlink3.mk",
                MkRcsID,
                "",
@@ -116,7 +116,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_abi_api_versions(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("buildlink3.mk",
                MkRcsID,
                "",
@@ -141,7 +141,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_no_BUILDLINK_TREE_at_beginning(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("buildlink3.mk",
                MkRcsID,
                "",
@@ -165,7 +165,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_no_BUILDLINK_TREE_at_end(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("buildlink3.mk",
                MkRcsID,
                "",
@@ -194,7 +194,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_multiple_inclusion_wrong(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("buildlink3.mk",
                MkRcsID,
                "",
@@ -213,7 +213,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_missing_endif(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("buildlink3.mk",
                MkRcsID,
                "",
@@ -231,7 +231,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_unknown_dependency_patterns(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("buildlink3.mk",
                MkRcsID,
                "",
@@ -258,7 +258,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_PKGBASE_with_variable(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("buildlink3.mk",
                MkRcsID,
                "",
@@ -283,7 +283,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_PKGBASE_with_unknown_variable(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("buildlink3.mk",
                MkRcsID,
                "",
@@ -314,7 +314,7 @@ func (s *Suite) Test_ChecklinesBuildlink
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("buildlink3.mk",
                MkRcsID,
                "",

Index: pkgsrc/pkgtools/pkglint/files/category_test.go
diff -u pkgsrc/pkgtools/pkglint/files/category_test.go:1.8 pkgsrc/pkgtools/pkglint/files/category_test.go:1.9
--- pkgsrc/pkgtools/pkglint/files/category_test.go:1.8  Mon Feb 19 12:40:38 2018
+++ pkgsrc/pkgtools/pkglint/files/category_test.go      Sat Mar 24 14:32:49 2018
@@ -5,7 +5,7 @@ import "gopkg.in/check.v1"
 func (s *Suite) Test_CheckdirCategory_totally_broken(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        t.SetupFileLines("archivers/Makefile",
                "# $",
                "SUBDIR+=pkg1",
@@ -36,7 +36,7 @@ func (s *Suite) Test_CheckdirCategory_to
 func (s *Suite) Test_CheckdirCategory_invalid_comment(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        t.SetupFileLines("archivers/Makefile",
                MkRcsID,
                "COMMENT=\t\\Make $$$$ fast\"",
Index: pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.8 pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.9
--- pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.8     Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go Sat Mar 24 14:32:49 2018
@@ -6,10 +6,10 @@ func (s *Suite) Test_MkLineChecker_Check
        t := s.Init(c)
 
        t.SetupCommandLine("-Wtypes")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mkline := t.NewMkLine("fname", 1, "COMMENT=\tA nice package")
 
-       vartype1 := G.globalData.vartypes["COMMENT"]
+       vartype1 := G.Pkgsrc.vartypes["COMMENT"]
        c.Assert(vartype1, check.NotNil)
        c.Check(vartype1.guessed, equals, false)
 
@@ -29,7 +29,7 @@ func (s *Suite) Test_MkLineChecker_Check
 func (s *Suite) Test_MkLineChecker_CheckVartype(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mkline := t.NewMkLine("fname", 1, "DISTNAME=gcc-${GCC_VERSION}")
 
        MkLineChecker{mkline}.CheckVartype("DISTNAME", opAssign, "gcc-${GCC_VERSION}", "")
@@ -43,7 +43,7 @@ func (s *Suite) Test_MkLineChecker_check
        t := s.Init(c)
 
        G.Pkg = NewPackage("graphics/gimp-fix-ca")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mkline := t.NewMkLine("fname", 10, "MASTER_SITES=http://registry.gimp.org/file/fix-ca.c?action=download&id=9884&file=";)
 
        MkLineChecker{mkline}.checkVarassign()
@@ -55,7 +55,7 @@ func (s *Suite) Test_MkLineChecker_Check
        t := s.Init(c)
 
        t.SetupCommandLine("-Wtypes")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
 
        MkLineChecker{t.NewMkLine("fname", 1, ".if !empty(PKGSRC_COMPILER:Mmycc)")}.CheckCond()
 
@@ -133,7 +133,7 @@ func (s *Suite) Test_MkLineChecker_Check
 func (s *Suite) Test_MkLineChecker_checkVarassign(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
 
        G.Mk = t.NewMkLines("Makefile",
                MkRcsID,
@@ -149,7 +149,7 @@ func (s *Suite) Test_MkLineChecker_check
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mkline := t.NewMkLine("options.mk", 2, "PKG_DEVELOPER?=\tyes")
 
        MkLineChecker{mkline}.checkVarassignDefPermissions()
@@ -162,20 +162,19 @@ func (s *Suite) Test_MkLineChecker_Check
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("options.mk",
                MkRcsID,
                "COMMENT=\t${GAMES_USER}",
                "COMMENT:=\t${PKGBASE}",
                "PYPKGPREFIX=${PKGBASE}")
-       G.globalData.UserDefinedVars = map[string]MkLine{
+       G.Pkgsrc.UserDefinedVars = map[string]MkLine{
                "GAMES_USER": mklines.mklines[0],
        }
 
        mklines.Check()
 
        t.CheckOutputLines(
-               "WARN: options.mk:2: The user-defined variable GAMES_USER is used but not added to BUILD_DEFS.",
                "WARN: options.mk:3: PKGBASE should not be evaluated at load time.",
                "WARN: options.mk:4: The variable PYPKGPREFIX may not be set in this file; it would be ok in pyversion.mk.",
                "WARN: options.mk:4: PKGBASE should not be evaluated indirectly at load time.",
@@ -186,7 +185,7 @@ func (s *Suite) Test_MkLineChecker_Check
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("options.mk",
                MkRcsID,
                "WRKSRC:=${.CURDIR}")
@@ -242,7 +241,7 @@ func (s *Suite) Test_MkLineChecker__Varu
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("x11/xkeyboard-config/Makefile",
                "FILES_SUBST+=XKBCOMP_SYMLINK=${${XKBBASE}/xkbcomp:L:Q}")
 
@@ -256,7 +255,7 @@ func (s *Suite) Test_MkLineChecker_Check
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("security/openssl/Makefile",
                MkRcsID,
                ".if ${PKGSRC_COMPILER} == \"gcc\" && ${CC} == \"cc\"",
@@ -273,7 +272,7 @@ func (s *Suite) Test_MkLine_CheckCond_co
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("audio/pulseaudio/Makefile",
                MkRcsID,
                ".if ${OPSYS} == \"Darwin\" && ${PKGSRC_COMPILER} == \"clang\"",
@@ -289,7 +288,7 @@ func (s *Suite) Test_MkLineChecker_Check
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("chat/pidgin-icb/Makefile",
                MkRcsID,
                "CFLAGS+=\t`pkg-config pidgin --cflags`")
@@ -312,7 +311,7 @@ func (s *Suite) Test_MkLineChecker_Check
 func (s *Suite) Test_MkLineChecker_CheckVartype_CFLAGS(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
                "CPPFLAGS.SunOS+=\t-DPIPECOMMAND=\\\"/usr/sbin/sendmail -bs %s\\\"")
@@ -330,7 +329,7 @@ func (s *Suite) Test_MkLineChecker_check
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall", "--autofix")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        lines := t.SetupFileLinesContinuation("options.mk",
                MkRcsID,
                ".if ${PKGNAME} == pkgname",
@@ -359,7 +358,7 @@ func (s *Suite) Test_MkLineChecker_Check
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        lines := t.SetupFileLinesContinuation("options.mk",
                MkRcsID,
                "GOPATH=\t${WRKDIR}",
@@ -379,3 +378,23 @@ func (s *Suite) Test_MkLineChecker_Check
        t.CheckOutputLines(
                "WARN: ~/options.mk:4: The variable PATH should be quoted as part of a shell word.")
 }
+
+// The ${VARNAME:=suffix} should only be used with lists.
+// It typically appears in MASTER_SITE definitions.
+func (s *Suite) Test_MkLineChecker_CheckVaruse_eq_nonlist(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wall")
+       t.SetupVartypes()
+       t.SetupMasterSite("MASTER_SITE_GITHUB", "https://github.com/";)
+       lines := t.SetupFileLinesContinuation("options.mk",
+               MkRcsID,
+               "WRKSRC=\t\t${WRKDIR:=/subdir}",
+               "MASTER_SITES=\t${MASTER_SITE_GITHUB:=organization/}")
+       mklines := NewMkLines(lines)
+
+       mklines.Check()
+
+       t.CheckOutputLines(
+               "WARN: ~/options.mk:2: The :from=to modifier should only be used with lists.")
+}

Index: pkgsrc/pkgtools/pkglint/files/check_test.go
diff -u pkgsrc/pkgtools/pkglint/files/check_test.go:1.18 pkgsrc/pkgtools/pkglint/files/check_test.go:1.19
--- pkgsrc/pkgtools/pkglint/files/check_test.go:1.18    Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/check_test.go Sat Mar 24 14:32:49 2018
@@ -50,6 +50,7 @@ func (s *Suite) SetUpTest(c *check.C) {
        G.logOut = NewSeparatorWriter(&t.stdout)
        G.logErr = NewSeparatorWriter(&t.stderr)
        trace.Out = &t.stdout
+       G.Pkgsrc = NewPkgsrc(t.TmpDir())
 
        t.checkC = c
        t.SetupCommandLine( /* no arguments */ )
@@ -104,9 +105,15 @@ func (t *Tester) SetupCommandLine(args .
        G.opts.LogVerbose = true // See SetUpTest
 }
 
+// SetupVartypes registers a few hundred variables like MASTER_SITES,
+// WRKSRC, SUBST_SED.*, so that their data types are known to pkglint.
+func (t *Tester) SetupVartypes() {
+       G.Pkgsrc.InitVartypes()
+}
+
 func (t *Tester) SetupMasterSite(varname string, urls ...string) {
-       name2url := &G.globalData.MasterSiteVarToURL
-       url2name := &G.globalData.MasterSiteURLToVar
+       name2url := &G.Pkgsrc.MasterSiteVarToURL
+       url2name := &G.Pkgsrc.MasterSiteURLToVar
        if *name2url == nil {
                *name2url = make(map[string]string)
                *url2name = make(map[string]string)
@@ -118,11 +125,11 @@ func (t *Tester) SetupMasterSite(varname
 }
 
 func (t *Tester) SetupTool(tool *Tool) {
-       reg := G.globalData.Tools
+       reg := G.Pkgsrc.Tools
 
        if len(reg.byName) == 0 && len(reg.byVarname) == 0 {
                reg = NewToolRegistry()
-               G.globalData.Tools = reg
+               G.Pkgsrc.Tools = reg
        }
        if tool.Name != "" {
                reg.byName[tool.Name] = tool
Index: pkgsrc/pkgtools/pkglint/files/distinfo.go
diff -u pkgsrc/pkgtools/pkglint/files/distinfo.go:1.18 pkgsrc/pkgtools/pkglint/files/distinfo.go:1.19
--- pkgsrc/pkgtools/pkglint/files/distinfo.go:1.18      Sat Jan 27 18:50:36 2018
+++ pkgsrc/pkgtools/pkglint/files/distinfo.go   Sat Mar 24 14:32:49 2018
@@ -18,7 +18,7 @@ func ChecklinesDistinfo(lines []Line) {
        patchesDir := "patches"
        patchesDirSet := false
        if G.Pkg != nil && contains(fname, "lang/php") {
-               phpdir := G.globalData.Latest("lang", `^php[0-9]+$`, "/lang/$0")
+               phpdir := G.Pkgsrc.Latest("lang", `^php[0-9]+$`, "/lang/$0")
                if hasSuffix(fname, phpdir+"/distinfo") {
                        patchesDir = G.CurPkgsrcdir + phpdir + "/patches"
                        patchesDirSet = true
Index: pkgsrc/pkgtools/pkglint/files/mklines_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.18 pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.19
--- pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.18  Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/mklines_test.go       Sat Mar 24 14:32:49 2018
@@ -68,7 +68,7 @@ func (s *Suite) Test_MkLines_quoting_LDF
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Pkg = NewPackage("category/pkgbase")
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
@@ -109,7 +109,7 @@ func (s *Suite) Test_MkLines__comparing_
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("databases/gdbm_compat/builtin.mk",
                MkRcsID,
                ".if ${USE_BUILTIN.gdbm} == \"no\"",
@@ -128,7 +128,7 @@ func (s *Suite) Test_MkLines__varuse_sh_
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("lang/qore/module.mk",
                MkRcsID,
                "qore-version=\tqore --short-version | ${SED} -e s/-.*//",
@@ -152,7 +152,7 @@ func (s *Suite) Test_MkLines__varuse_par
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("converters/wv2/Makefile",
                MkRcsID,
                "CONFIGURE_ARGS+=\t\t${CONFIGURE_ARGS.${ICONV_TYPE}-iconv}",
@@ -172,7 +172,7 @@ func (s *Suite) Test_MkLines__loop_modif
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("chat/xchat/Makefile",
                MkRcsID,
                "GCONF_SCHEMAS=\tapps_xchat_url_handler.schemas",
@@ -190,7 +190,7 @@ func (s *Suite) Test_MkLines__loop_modif
 func (s *Suite) Test_MkLines__PKG_SKIP_REASON_depending_on_OPSYS(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
                "PKG_SKIP_REASON+=\t\"Fails everywhere\"",
@@ -243,7 +243,7 @@ func (s *Suite) Test_MkLines_Check__abso
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("games/heretic2-demo/Makefile",
                MkRcsID,
                ".if ${OPSYS} == \"DragonFly\"",
@@ -347,7 +347,7 @@ func (s *Suite) Test_MkLines_PrivateTool
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("fname",
                MkRcsID,
                "",
@@ -363,7 +363,7 @@ func (s *Suite) Test_MkLines_PrivateTool
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("fname",
                MkRcsID,
                "TOOLS_CREATE+=\tmd5sum",
@@ -423,7 +423,7 @@ func (s *Suite) Test_MkLines_wip_categor
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        t.SetupTool(&Tool{Name: "rm", Varname: "RM", Predefined: true})
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
@@ -451,7 +451,7 @@ func (s *Suite) Test_MkLines_ExtractDocu
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        t.SetupTool(&Tool{Name: "rm", Varname: "RM", Predefined: true})
        mklines := t.NewMkLines("Makefile",
                MkRcsID,

Index: pkgsrc/pkgtools/pkglint/files/licenses.go
diff -u pkgsrc/pkgtools/pkglint/files/licenses.go:1.10 pkgsrc/pkgtools/pkglint/files/licenses.go:1.11
--- pkgsrc/pkgtools/pkglint/files/licenses.go:1.10      Sun Jan 29 14:27:48 2017
+++ pkgsrc/pkgtools/pkglint/files/licenses.go   Sat Mar 24 14:32:49 2018
@@ -10,7 +10,7 @@ func checkToplevelUnusedLicenses() {
                return
        }
 
-       licensedir := G.globalData.Pkgsrcdir + "/licenses"
+       licensedir := G.Pkgsrc.File("licenses")
        files, _ := ioutil.ReadDir(licensedir)
        for _, licensefile := range files {
                licensename := licensefile.Name()
@@ -51,7 +51,7 @@ func (lc *LicenseChecker) checkLicenseNa
                }
        }
        if licenseFile == "" {
-               licenseFile = G.globalData.Pkgsrcdir + "/licenses/" + license
+               licenseFile = G.Pkgsrc.File("licenses/" + license)
                if G.UsedLicenses != nil {
                        G.UsedLicenses[license] = true
                }
Index: pkgsrc/pkgtools/pkglint/files/licenses_test.go
diff -u pkgsrc/pkgtools/pkglint/files/licenses_test.go:1.10 pkgsrc/pkgtools/pkglint/files/licenses_test.go:1.11
--- pkgsrc/pkgtools/pkglint/files/licenses_test.go:1.10 Sat Jan 27 18:50:36 2018
+++ pkgsrc/pkgtools/pkglint/files/licenses_test.go      Sat Mar 24 14:32:49 2018
@@ -10,7 +10,6 @@ func (s *Suite) Test_checklineLicense(c 
        t.SetupFileLines("licenses/gnu-gpl-v2",
                "Most software \u2026")
        mkline := t.NewMkLine("Makefile", 7, "LICENSE=dummy")
-       G.globalData.Pkgsrcdir = t.TmpDir()
        G.CurrentDir = t.TmpDir()
 
        licenseChecker := &LicenseChecker{mkline}
Index: pkgsrc/pkgtools/pkglint/files/mklinechecker.go
diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.10 pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.11
--- pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.10 Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/mklinechecker.go      Sat Mar 24 14:32:49 2018
@@ -318,6 +318,18 @@ func (ck MkLineChecker) CheckVaruse(varu
                mkline.Warnf("%s is used but not defined. Spelling mistake?", varname)
        }
 
+       if hasPrefix(varuse.Mod(), ":=") && vartype != nil && !vartype.IsConsideredList() {
+               mkline.Warnf("The :from=to modifier should only be used with lists.")
+               Explain(
+                       "Instead of:",
+                       "\tMASTER_SITES=\t${HOMEPAGE:=repository/}",
+                       "",
+                       "Write:",
+                       "\tMASTER_SITES=\t${HOMEPAGE}repository/",
+                       "",
+                       "This is a much clearer expression of the same thought.")
+       }
+
        ck.CheckVarusePermissions(varname, vartype, vuc)
 
        if varname == "LOCALBASE" && !G.Infrastructure {
@@ -334,7 +346,7 @@ func (ck MkLineChecker) CheckVaruse(varu
                ck.CheckVaruseShellword(varname, vartype, vuc, varuse.Mod(), needsQuoting)
        }
 
-       if G.globalData.UserDefinedVars[varname] != nil && !G.globalData.SystemBuildDefs[varname] && !G.Mk.buildDefs[varname] {
+       if G.Pkgsrc.UserDefinedVars[varname] != nil && !G.Pkgsrc.IsBuildDef(varname) && !G.Mk.buildDefs[varname] {
                mkline.Warnf("The user-defined variable %s is used but not added to BUILD_DEFS.", varname)
                Explain(
                        "When a pkgsrc package is built, many things can be configured by the",
@@ -386,7 +398,7 @@ func (ck MkLineChecker) CheckVarusePermi
        }
 
        done := false
-       tool := G.globalData.Tools.byVarname[varname]
+       tool := G.Pkgsrc.Tools.ByVarname(varname)
 
        if isLoadTime && tool != nil {
                done = tool.Predefined && (G.Mk == nil || G.Mk.SeenBsdPrefsMk || G.Pkg == nil || G.Pkg.SeenBsdPrefsMk)
@@ -663,9 +675,9 @@ func (ck MkLineChecker) checkVarassign()
                }
 
        } else if !varIsUsed(varname) {
-               if vartypes := G.globalData.vartypes; vartypes[varname] != nil || vartypes[varcanon] != nil {
+               if vartypes := G.Pkgsrc.vartypes; vartypes[varname] != nil || vartypes[varcanon] != nil {
                        // Ok
-               } else if deprecated := G.globalData.Deprecated; deprecated[varname] != "" || deprecated[varcanon] != "" {
+               } else if deprecated := G.Pkgsrc.Deprecated; deprecated[varname] != "" || deprecated[varcanon] != "" {
                        // Ok
                } else {
                        mkline.Warnf("%s is defined but not used. Spelling mistake?", varname)
@@ -703,9 +715,9 @@ func (ck MkLineChecker) checkVarassign()
                }
        }
 
-       if fix := G.globalData.Deprecated[varname]; fix != "" {
+       if fix := G.Pkgsrc.Deprecated[varname]; fix != "" {
                mkline.Warnf("Definition of %s is deprecated. %s", varname, fix)
-       } else if fix := G.globalData.Deprecated[varcanon]; fix != "" {
+       } else if fix := G.Pkgsrc.Deprecated[varcanon]; fix != "" {
                mkline.Warnf("Definition of %s is deprecated. %s", varname, fix)
        }
 
@@ -972,9 +984,9 @@ func (ck MkLineChecker) checkText(text s
                varbase, varext := m[1], m[2]
                varname := varbase + varext
                varcanon := varnameCanon(varname)
-               instead := G.globalData.Deprecated[varname]
+               instead := G.Pkgsrc.Deprecated[varname]
                if instead == "" {
-                       instead = G.globalData.Deprecated[varcanon]
+                       instead = G.Pkgsrc.Deprecated[varcanon]
                }
                if instead != "" {
                        mkline.Warnf("Use of %q is deprecated. %s", varname, instead)
@@ -1071,7 +1083,7 @@ func (ck MkLineChecker) CheckRelativePkg
        pkgdir = mkline.ResolveVarsInRelativePath(pkgdir, false)
 
        if m, otherpkgpath := match1(pkgdir, `^(?:\./)?\.\./\.\./([^/]+/[^/]+)$`); m {
-               if !fileExists(G.globalData.Pkgsrcdir + "/" + otherpkgpath + "/Makefile") {
+               if !fileExists(G.Pkgsrc.File(otherpkgpath + "/Makefile")) {
                        mkline.Errorf("There is no package in %q.", otherpkgpath)
                }
 

Index: pkgsrc/pkgtools/pkglint/files/line.go
diff -u pkgsrc/pkgtools/pkglint/files/line.go:1.21 pkgsrc/pkgtools/pkglint/files/line.go:1.22
--- pkgsrc/pkgtools/pkglint/files/line.go:1.21  Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/line.go       Sat Mar 24 14:32:49 2018
@@ -160,6 +160,27 @@ func (line *LineImpl) String() string {
        return line.Filename + ":" + line.Linenos() + ": " + line.Text
 }
 
+// Usage:
+//
+//  fix := mkline.Line.Autofix()
+//
+//  fix.Errorf("Must not be ...")
+//  fix.Warnf("Should not be ...")
+//  fix.Notef("It is also possible ...")
+//
+//  fix.Explain(
+//      "Explanation ...",
+//      "... end of explanation.")
+//
+//  fix.Replace("from", "to")
+//  fix.ReplaceAfter("prefix", "from", "to")
+//  fix.ReplaceRegex(`\s+`, "space", "from", "to")
+//  fix.InsertBefore("new line")
+//  fix.InsertAfter("new line")
+//  fix.Delete()
+//  fix.Custom(func(...))
+//
+//  fix.Apply()
 func (line *LineImpl) Autofix() *Autofix {
        if line.autofix == nil {
                line.autofix = NewAutofix(line)
Index: pkgsrc/pkgtools/pkglint/files/mklines.go
diff -u pkgsrc/pkgtools/pkglint/files/mklines.go:1.21 pkgsrc/pkgtools/pkglint/files/mklines.go:1.22
--- pkgsrc/pkgtools/pkglint/files/mklines.go:1.21       Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/mklines.go    Sat Mar 24 14:32:49 2018
@@ -27,11 +27,11 @@ func NewMkLines(lines []Line) *MkLines {
                mklines[i] = NewMkLine(line)
        }
        tools := make(map[string]bool)
-       for toolname, tool := range G.globalData.Tools.byName {
+       G.Pkgsrc.Tools.ForEach(func(tool *Tool) {
                if tool.Predefined {
-                       tools[toolname] = true
+                       tools[tool.Name] = true
                }
-       }
+       })
 
        return &MkLines{
                mklines,

Index: pkgsrc/pkgtools/pkglint/files/linechecker.go
diff -u pkgsrc/pkgtools/pkglint/files/linechecker.go:1.6 pkgsrc/pkgtools/pkglint/files/linechecker.go:1.7
--- pkgsrc/pkgtools/pkglint/files/linechecker.go:1.6    Sun Jan 28 23:21:16 2018
+++ pkgsrc/pkgtools/pkglint/files/linechecker.go        Sat Mar 24 14:32:49 2018
@@ -95,15 +95,22 @@ func CheckwordAbsolutePathname(line Line
        case regex.Matches(word, `^/(?:[a-z]|\$[({])`):
                // Absolute paths probably start with a lowercase letter.
                line.Warnf("Found absolute pathname: %s", word)
-               Explain(
-                       "Absolute pathnames are often an indicator for unportable code.  As",
-                       "pkgsrc aims to be a portable system, absolute pathnames should be",
-                       "avoided whenever possible.",
-                       "",
-                       "A special variable in this context is ${DESTDIR}, which is used in",
-                       "GNU projects to specify a different directory for installation than",
-                       "what the programs see later when they are executed.  Usually it is",
-                       "empty, so if anything after that variable starts with a slash, it is",
-                       "considered an absolute pathname.")
+               if contains(line.Text, "DESTDIR") {
+                       Explain(
+                               "Absolute pathnames are often an indicator for unportable code.  As",
+                               "pkgsrc aims to be a portable system, absolute pathnames should be",
+                               "avoided whenever possible.",
+                               "",
+                               "A special variable in this context is ${DESTDIR}, which is used in",
+                               "GNU projects to specify a different directory for installation than",
+                               "what the programs see later when they are executed.  Usually it is",
+                               "empty, so if anything after that variable starts with a slash, it is",
+                               "considered an absolute pathname.")
+               } else {
+                       Explain(
+                               "Absolute pathnames are often an indicator for unportable code.  As",
+                               "pkgsrc aims to be a portable system, absolute pathnames should be",
+                               "avoided whenever possible.")
+               }
        }
 }

Index: pkgsrc/pkgtools/pkglint/files/mkline.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline.go:1.29 pkgsrc/pkgtools/pkglint/files/mkline.go:1.30
--- pkgsrc/pkgtools/pkglint/files/mkline.go:1.29        Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/mkline.go     Sat Mar 24 14:32:49 2018
@@ -195,7 +195,11 @@ func (mkline *MkLineImpl) IsDependency()
        return ok
 }
 
-// Varname applies to variable assignments and returns the variable name, exactly as given in the Makefile.
+// Varname applies to variable assignments and returns the name
+// of the variable that is assigned or appended to.
+//
+// Example:
+//  VARNAME?=       value
 func (mkline *MkLineImpl) Varname() string { return mkline.data.(mkLineAssign).varname }
 
 // Varcanon applies to variable assignments and returns the canonicalized variable name for parameterized variables.
@@ -276,20 +280,20 @@ func (mkline *MkLineImpl) ResolveVarsInR
        tmp = strings.Replace(tmp, "${.CURDIR}", ".", -1)
        tmp = strings.Replace(tmp, "${.PARSEDIR}", ".", -1)
        if contains(tmp, "${LUA_PKGSRCDIR}") {
-               tmp = strings.Replace(tmp, "${LUA_PKGSRCDIR}", G.globalData.Latest("lang", `^lua[0-9]+$`, "../../lang/$0"), -1)
+               tmp = strings.Replace(tmp, "${LUA_PKGSRCDIR}", G.Pkgsrc.Latest("lang", `^lua[0-9]+$`, "../../lang/$0"), -1)
        }
        if contains(tmp, "${PHPPKGSRCDIR}") {
-               tmp = strings.Replace(tmp, "${PHPPKGSRCDIR}", G.globalData.Latest("lang", `^php[0-9]+$`, "../../lang/$0"), -1)
+               tmp = strings.Replace(tmp, "${PHPPKGSRCDIR}", G.Pkgsrc.Latest("lang", `^php[0-9]+$`, "../../lang/$0"), -1)
        }
        if contains(tmp, "${SUSE_DIR_PREFIX}") {
-               suseDirPrefix := G.globalData.Latest("emulators", `^(suse[0-9]+)_base`, "$1")
+               suseDirPrefix := G.Pkgsrc.Latest("emulators", `^(suse[0-9]+)_base`, "$1")
                tmp = strings.Replace(tmp, "${SUSE_DIR_PREFIX}", suseDirPrefix, -1)
        }
        if contains(tmp, "${PYPKGSRCDIR}") {
-               tmp = strings.Replace(tmp, "${PYPKGSRCDIR}", G.globalData.Latest("lang", `^python[0-9]+$`, "../../lang/$0"), -1)
+               tmp = strings.Replace(tmp, "${PYPKGSRCDIR}", G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0"), -1)
        }
        if contains(tmp, "${PYPACKAGE}") {
-               tmp = strings.Replace(tmp, "${PYPACKAGE}", G.globalData.Latest("lang", `^python[0-9]+$`, "$0"), -1)
+               tmp = strings.Replace(tmp, "${PYPACKAGE}", G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "$0"), -1)
        }
        if G.Pkg != nil {
                tmp = strings.Replace(tmp, "${FILESDIR}", G.Pkg.Filesdir, -1)
@@ -446,9 +450,9 @@ func (mkline *MkLineImpl) VariableNeedsQ
                }
        }
 
-       // Assuming the tool definitions don't include very special characters,
-       // so they can safely be used inside any quotes.
-       if G.globalData.Tools.byVarname[varname] != nil {
+       // Pkglint assumes that the tool definitions don't include very
+       // special characters, so they can safely be used inside any quotes.
+       if G.Pkgsrc.Tools.ByVarname(varname) != nil {
                switch vuc.quoting {
                case vucQuotPlain:
                        if !vuc.IsWordPart {
@@ -512,14 +516,14 @@ func (mkline *MkLineImpl) VariableType(v
                defer trace.Call1(varname)()
        }
 
-       if vartype := G.globalData.vartypes[varname]; vartype != nil {
+       if vartype := G.Pkgsrc.vartypes[varname]; vartype != nil {
                return vartype
        }
-       if vartype := G.globalData.vartypes[varnameCanon(varname)]; vartype != nil {
+       if vartype := G.Pkgsrc.vartypes[varnameCanon(varname)]; vartype != nil {
                return vartype
        }
 
-       if tool := G.globalData.Tools.byVarname[varname]; tool != nil {
+       if tool := G.Pkgsrc.Tools.ByVarname(varname); tool != nil {
                perms := aclpUse
                if trace.Tracing {
                        trace.Stepf("Use of tool %+v", tool)
@@ -532,14 +536,15 @@ func (mkline *MkLineImpl) VariableType(v
                return &Vartype{lkNone, BtShellCommand, []ACLEntry{{"*", perms}}, false}
        }
 
-       if m, toolvarname := match1(varname, `^TOOLS_(.*)`); m && G.globalData.Tools.byVarname[toolvarname] != nil {
+       m, toolvarname := match1(varname, `^TOOLS_(.*)`)
+       if m && G.Pkgsrc.Tools.ByVarname(toolvarname) != nil {
                return &Vartype{lkNone, BtPathname, []ACLEntry{{"*", aclpUse}}, false}
        }
 
        allowAll := []ACLEntry{{"*", aclpAll}}
        allowRuntime := []ACLEntry{{"*", aclpAllRuntime}}
 
-       // Guess the datatype of the variable based on naming conventions.
+       // Guess the data type of the variable based on naming conventions.
        varbase := varnameBase(varname)
        var gtype *Vartype
        switch {

Index: pkgsrc/pkgtools/pkglint/files/mkline_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.31 pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.32
--- pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.31   Mon Feb 19 12:40:38 2018
+++ pkgsrc/pkgtools/pkglint/files/mkline_test.go        Sat Mar 24 14:32:49 2018
@@ -210,7 +210,7 @@ func (s *Suite) Test_MkLine_VariableType
        t := s.Init(c)
 
        mkline := t.NewMkLine("fname", 1, "# dummy")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
 
        t1 := mkline.VariableType("FONT_DIRS")
 
@@ -226,7 +226,7 @@ func (s *Suite) Test_MkLine_VariableType
 func (s *Suite) Test_VarUseContext_String(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mkline := t.NewMkLine("fname", 1, "# dummy")
        vartype := mkline.VariableType("PKGNAME")
        vuc := &VarUseContext{vartype, vucTimeUnknown, vucQuotBackt, false}
@@ -275,7 +275,7 @@ func (s *Suite) Test_MkLines_Check__extr
        t := s.Init(c)
 
        t.SetupCommandLine("-Wextra")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Pkg = NewPackage("category/pkgbase")
        G.Mk = t.NewMkLines("options.mk",
                MkRcsID,
@@ -303,9 +303,9 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        mkline := t.NewMkLine("fname", 1, "PKGNAME := ${UNKNOWN}")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
 
-       vuc := &VarUseContext{G.globalData.vartypes["PKGNAME"], vucTimeParse, vucQuotUnknown, false}
+       vuc := &VarUseContext{G.Pkgsrc.vartypes["PKGNAME"], vucTimeParse, vucQuotUnknown, false}
        nq := mkline.VariableNeedsQuoting("UNKNOWN", nil, vuc)
 
        c.Check(nq, equals, nqDontKnow)
@@ -315,12 +315,12 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        t.SetupMasterSite("MASTER_SITE_SOURCEFORGE", "http://downloads.sourceforge.net/sourceforge/";)
        mkline := t.NewMkLine("Makefile", 95, "MASTER_SITES=\t${HOMEPAGE}")
 
-       vuc := &VarUseContext{G.globalData.vartypes["MASTER_SITES"], vucTimeRun, vucQuotPlain, false}
-       nq := mkline.VariableNeedsQuoting("HOMEPAGE", G.globalData.vartypes["HOMEPAGE"], vuc)
+       vuc := &VarUseContext{G.Pkgsrc.vartypes["MASTER_SITES"], vucTimeRun, vucQuotPlain, false}
+       nq := mkline.VariableNeedsQuoting("HOMEPAGE", G.Pkgsrc.vartypes["HOMEPAGE"], vuc)
 
        c.Check(nq, equals, nqNo)
 
@@ -333,7 +333,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        t.SetupMasterSite("MASTER_SITE_SOURCEFORGE", "http://downloads.sourceforge.net/sourceforge/";)
        mkline := t.NewMkLine("Makefile", 96, "MASTER_SITES=\t${MASTER_SITE_SOURCEFORGE:=squirrel-sql/}")
 
@@ -347,7 +347,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mkline := t.NewMkLine("builtin.mk", 3, "USE_BUILTIN.Xfixes!=\t${PKG_ADMIN} pmatch 'pkg-[0-9]*' ${BUILTIN_PKG.Xfixes:Q}")
 
        MkLineChecker{mkline}.checkVarassign()
@@ -361,7 +361,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mkline := t.NewMkLine("Makefile", 3, "SUBST_SED.hpath=\t-e 's|^\\(INSTALL[\t:]*=\\).*|\\1${INSTALL}|'")
 
        MkLineChecker{mkline}.checkVarassign()
@@ -374,7 +374,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        t.SetupTool(&Tool{Name: "find", Varname: "FIND", Predefined: true})
        t.SetupTool(&Tool{Name: "sort", Varname: "SORT", Predefined: true})
        G.Pkg = NewPackage("category/pkgbase")
@@ -393,7 +393,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("Makefile",
                MkRcsID,
                "EGDIR=\t${EGDIR}/${MACHINE_GNU_PLATFORM}")
@@ -415,7 +415,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t.SetupCommandLine("-Wall")
        t.SetupTool(&Tool{Name: "perl", Varname: "PERL5", Predefined: true})
        t.SetupTool(&Tool{Name: "bash", Varname: "BASH", Predefined: true})
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("Makefile",
                MkRcsID,
                "\t${RUN} cd ${WRKSRC} && ( ${ECHO} ${PERL5:Q} ; ${ECHO} ) | ${BASH} ./install",
@@ -434,7 +434,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("Makefile",
                MkRcsID,
                "MASTER_SITES=${HOMEPAGE}archive/")
@@ -453,7 +453,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        t.SetupTool(&Tool{Name: "awk", Varname: "AWK", Predefined: true})
        t.SetupTool(&Tool{Name: "echo", Varname: "ECHO", Predefined: true})
        G.Mk = t.NewMkLines("xpi.mk",
@@ -476,7 +476,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("x11/mlterm/Makefile",
                MkRcsID,
                "SUBST_SED.link=-e 's|(LIBTOOL_LINK).*(LIBS)|& ${LDFLAGS:M*:Q}|g'",
@@ -499,7 +499,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("Makefile",
                MkRcsID,
                "PKG_SUGGESTED_OPTIONS+=\t${PKG_DEFAULT_OPTIONS:Mcdecimal} ${PKG_OPTIONS.py-trytond:Mcdecimal}")
@@ -514,7 +514,7 @@ func (s *Suite) Test_MkLines_Check__MAST
 
        t.SetupCommandLine("-Wall")
        t.SetupMasterSite("MASTER_SITE_GITHUB", "https://github.com/";)
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("devel/catch/Makefile",
                MkRcsID,
                "HOMEPAGE=\t${MASTER_SITE_GITHUB:=philsquared/Catch/}",
@@ -537,7 +537,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t.SetupCommandLine("-Wall")
        t.SetupTool(&Tool{Name: "echo", Varname: "ECHO", Predefined: true})
        t.SetupTool(&Tool{Name: "sh", Varname: "SH", Predefined: true})
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("x11/labltk/Makefile",
                MkRcsID,
                "CONFIGURE_ARGS+=\t-tklibs \"`${SH} -c '${ECHO} $$TK_LD_FLAGS'`\"")
@@ -552,7 +552,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("x11/qt5-qtbase/Makefile.common",
                "BUILDLINK_TRANSFORM+=opt:-ldl:${BUILDLINK_LDADD.dl:M*}")
 
@@ -567,7 +567,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("benchmarks/iozone/Makefile",
                "SUBST_MESSAGE.crlf=\tStripping EOL CR in ${REPLACE_PERL}")
 
@@ -581,7 +581,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("audio/jack-rack/Makefile",
                MkRcsID,
                "LADSPA_PLUGIN_PATH?=\t${PREFIX}/lib/ladspa",
@@ -597,7 +597,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("x11/eterm/Makefile",
                MkRcsID,
                "DISTFILES=\t${DEFAULT_DISTFILES} ${PIXMAP_FILES}")
@@ -612,7 +612,7 @@ func (s *Suite) Test_MkLine_variableNeed
 
        t.SetupCommandLine("-Wall")
        t.SetupMasterSite("MASTER_SITE_GNOME", "http://ftp.gnome.org/";)
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("x11/gtk3/Makefile",
                MkRcsID,
                "MASTER_SITES=\tftp://ftp.gtk.org/${PKGNAME}/ ${MASTER_SITE_GNOME:=subdir/}")
@@ -626,9 +626,8 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
-       G.globalData.Tools = NewToolRegistry()
-       G.globalData.Tools.RegisterVarname("tar", "TAR")
+       t.SetupVartypes()
+       G.Pkgsrc.Tools.RegisterVarname("tar", "TAR")
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
                "",
@@ -648,9 +647,8 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
-       G.globalData.Tools = NewToolRegistry()
-       G.globalData.Tools.RegisterVarname("cat", "CAT")
+       t.SetupVartypes()
+       G.Pkgsrc.Tools.RegisterVarname("cat", "CAT")
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
                "",
@@ -677,7 +675,7 @@ func (s *Suite) Test_MkLine_variableNeed
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall", "--autofix")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
 
        lines := t.SetupFileLinesContinuation("Makefile",
                MkRcsID,
@@ -703,7 +701,7 @@ func (s *Suite) Test_MkLine_Pkgmandir(c 
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("chat/ircII/Makefile",
                MkRcsID,
                "CONFIGURE_ARGS+=--mandir=${DESTDIR}${PREFIX}/man",
@@ -721,7 +719,7 @@ func (s *Suite) Test_MkLines_Check__VERS
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("geography/viking/Makefile",
                MkRcsID,
                "MASTER_SITES=\t${MASTER_SITE_SOURCEFORGE:=viking/}${VERSION}/")
@@ -737,7 +735,7 @@ func (s *Suite) Test_MkLines_Check__shel
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("x11/lablgtk1/Makefile",
                MkRcsID,
                "CONFIGURE_ENV+=\tCC=${CC}")
@@ -753,7 +751,7 @@ func (s *Suite) Test_MkLine_shell_varuse
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("x11/motif/Makefile",
                MkRcsID,
                "post-patch:",
@@ -779,7 +777,7 @@ func (s *Suite) Test_MkLine_VariableType
 func (s *Suite) Test_MkLine__comment_in_comment(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
                "COMMENT=\tPKCS#5 v2.0 PBKDF2 Module")

Index: pkgsrc/pkgtools/pkglint/files/mkparser_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.9 pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.10
--- pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.9  Mon Feb 19 12:40:38 2018
+++ pkgsrc/pkgtools/pkglint/files/mkparser_test.go      Sat Mar 24 14:32:49 2018
@@ -221,7 +221,7 @@ func (s *Suite) Test_MkParser__varuse_pa
        t := s.Init(c)
 
        t.SetupCommandLine("--autofix")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        lines := t.SetupFileLines("Makefile",
                MkRcsID,
                "COMMENT=$(P1) $(P2)) $(P3:Q) ${BRACES}")
Index: pkgsrc/pkgtools/pkglint/files/toplevel_test.go
diff -u pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.9 pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.10
--- pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.9  Mon Feb 19 12:40:38 2018
+++ pkgsrc/pkgtools/pkglint/files/toplevel_test.go      Sat Mar 24 14:32:49 2018
@@ -19,7 +19,7 @@ func (s *Suite) Test_CheckdirToplevel(c 
        t.SetupFileLines("bbb/Makefile")
        t.SetupFileLines("ccc/Makefile")
        t.SetupFileLines("x11/Makefile")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
 
        G.CurrentDir = t.TmpDir()
        CheckdirToplevel()

Index: pkgsrc/pkgtools/pkglint/files/package.go
diff -u pkgsrc/pkgtools/pkglint/files/package.go:1.26 pkgsrc/pkgtools/pkglint/files/package.go:1.27
--- pkgsrc/pkgtools/pkglint/files/package.go:1.26       Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/package.go    Sat Mar 24 14:32:49 2018
@@ -4,6 +4,7 @@ import (
        "netbsd.org/pkglint/pkgver"
        "netbsd.org/pkglint/regex"
        "netbsd.org/pkglint/trace"
+       "os"
        "path"
        "regexp"
        "strconv"
@@ -49,7 +50,7 @@ func NewPackage(pkgpath string) *Package
                conditionalIncludes:   make(map[string]MkLine),
                unconditionalIncludes: make(map[string]MkLine),
        }
-       for varname, line := range G.globalData.UserDefinedVars {
+       for varname, line := range G.Pkgsrc.UserDefinedVars {
                pkg.vars.Define(varname, line)
        }
        return pkg
@@ -89,7 +90,7 @@ func (pkg *Package) checkPossibleDowngra
 
        mkline := pkg.EffectivePkgnameLine
 
-       change := G.globalData.LastChange[pkg.Pkgpath]
+       change := G.Pkgsrc.LastChange[pkg.Pkgpath]
        if change == nil {
                if trace.Tracing {
                        trace.Step1("No change log for package %q", pkg.Pkgpath)
@@ -138,6 +139,11 @@ func (pkg *Package) checklinesBuildlink3
        }
 }
 
+// Given the package path relative to the pkgsrc top directory,
+// checks a complete pkgsrc package.
+//
+// Example:
+//  checkdirPackage("category/pkgbase")
 func (pkglint *Pkglint) checkdirPackage(pkgpath string) {
        if trace.Tracing {
                defer trace.Call1(pkgpath)()
@@ -187,6 +193,9 @@ func (pkglint *Pkglint) checkdirPackage(
                        continue
                }
                if fname == G.CurrentDir+"/Makefile" {
+                       if st, err := os.Lstat(fname); err == nil {
+                               pkglint.checkExecutable(st, fname)
+                       }
                        if G.opts.CheckMakefile {
                                pkg.checkfilePackageMakefile(fname, lines)
                        }
@@ -357,7 +366,7 @@ func (pkg *Package) readMakefile(fname s
        }
 
        if includingFnameForUsedCheck != "" {
-               fileMklines.checkForUsedComment(relpath(G.globalData.Pkgsrcdir, includingFnameForUsedCheck))
+               fileMklines.checkForUsedComment(G.Pkgsrc.ToRel(includingFnameForUsedCheck))
        }
 
        return true
@@ -383,7 +392,7 @@ func (pkg *Package) checkfilePackageMake
                }
        } else {
                if distinfoFile := G.CurrentDir + "/" + pkg.DistinfoFile; !containsVarRef(distinfoFile) && !fileExists(distinfoFile) {
-                       NewLineWhole(distinfoFile).Warnf("File not found. Please run \"%s makesum\".", confMake)
+                       NewLineWhole(distinfoFile).Warnf("File not found. Please run \"%s makesum\" or define NO_CHECKSUM=yes in the package Makefile.", confMake)
                }
        }
 
@@ -549,7 +558,7 @@ func (pkg *Package) expandVariableWithDe
 
 func (pkg *Package) checkUpdate() {
        if pkg.EffectivePkgbase != "" {
-               for _, sugg := range G.globalData.GetSuggestedPackageUpdates() {
+               for _, sugg := range G.Pkgsrc.GetSuggestedPackageUpdates() {
                        if pkg.EffectivePkgbase != sugg.Pkgname {
                                continue
                        }

Index: pkgsrc/pkgtools/pkglint/files/package_test.go
diff -u pkgsrc/pkgtools/pkglint/files/package_test.go:1.19 pkgsrc/pkgtools/pkglint/files/package_test.go:1.20
--- pkgsrc/pkgtools/pkglint/files/package_test.go:1.19  Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/package_test.go       Sat Mar 24 14:32:49 2018
@@ -158,8 +158,7 @@ func (s *Suite) Test_Package_varorder_li
                "",
                ".include \"../../mk/bsd.pkg.mk\"")
 
-       G.globalData.InitVartypes()
-       G.globalData.Pkgsrcdir = t.TmpDir()
+       t.SetupVartypes()
        G.CurrentDir = t.TmpDir()
 
        G.CheckDirent(t.TmpDir() + "/x11/9term")
@@ -197,7 +196,7 @@ func (s *Suite) Test_Package_CheckVarord
        t := s.Init(c)
 
        t.SetupCommandLine("-Worder")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        pkg := NewPackage("category/package")
 
        pkg.CheckVarorder(t.NewMkLines("Makefile",
@@ -285,7 +284,7 @@ func (s *Suite) Test_Package_checkPossib
        G.CurPkgsrcdir = "../.."
        G.Pkg.EffectivePkgname = "package-1.0nb15"
        G.Pkg.EffectivePkgnameLine = t.NewMkLine("category/pkgbase/Makefile", 5, "PKGNAME=dummy")
-       G.globalData.LastChange = map[string]*Change{
+       G.Pkgsrc.LastChange = map[string]*Change{
                "category/pkgbase": {
                        Action:  "Updated",
                        Version: "1.8",
@@ -298,7 +297,7 @@ func (s *Suite) Test_Package_checkPossib
        t.CheckOutputLines(
                "WARN: category/pkgbase/Makefile:5: The package is being downgraded from 1.8 (see ../../doc/CHANGES:116) to 1.0nb15")
 
-       G.globalData.LastChange["category/pkgbase"].Version = "1.0nb22"
+       G.Pkgsrc.LastChange["category/pkgbase"].Version = "1.0nb22"
 
        G.Pkg.checkPossibleDowngrade()
 
@@ -316,7 +315,7 @@ func (s *Suite) Test_checkdirPackage(c *
 
        t.CheckOutputLines(
                "WARN: ~/Makefile: Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset. Are you sure PLIST handling is ok?",
-               "WARN: ~/distinfo: File not found. Please run \""+confMake+" makesum\".",
+               "WARN: ~/distinfo: File not found. Please run \""+confMake+" makesum\" or define NO_CHECKSUM=yes in the package Makefile.",
                "ERROR: ~/Makefile: Each package must define its LICENSE.",
                "WARN: ~/Makefile: No COMMENT given.")
 }
@@ -329,7 +328,7 @@ func (s *Suite) Test_checkdirPackage__me
                "",
                "META_PACKAGE=\tyes")
        G.CurrentDir = t.TmpDir()
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
 
        G.checkdirPackage(t.TmpDir())
 
@@ -425,7 +424,7 @@ func (s *Suite) Test_Package_loadPackage
 func (s *Suite) Test_Package_conditionalAndUnconditionalInclude(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        t.CreateFileLines("category/package/Makefile",
                MkRcsID,
                "",
@@ -455,7 +454,6 @@ func (s *Suite) Test_Package_conditional
        t.CreateFileLines("sysutils/coreutils/buildlink3.mk", "")
 
        pkg := NewPackage("category/package")
-       G.globalData.Pkgsrcdir = t.TmpDir()
        G.CurrentDir = t.TmpDir() + "/category/package"
        G.CurPkgsrcdir = "../.."
        G.Pkg = pkg

Index: pkgsrc/pkgtools/pkglint/files/pkglint.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint.go:1.28 pkgsrc/pkgtools/pkglint/files/pkglint.go:1.29
--- pkgsrc/pkgtools/pkglint/files/pkglint.go:1.28       Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/pkglint.go    Sat Mar 24 14:32:49 2018
@@ -26,10 +26,10 @@ const confVersion = "@VERSION@"
 //  tracing.Out        (not thread-safe)
 //  tracing.traceDepth (not thread-safe)
 type Pkglint struct {
-       opts       CmdOpts    //
-       globalData GlobalData //
-       Pkg        *Package   // The package that is currently checked.
-       Mk         *MkLines   // The Makefile (or fragment) that is currently checked.
+       opts   CmdOpts  // Command line options.
+       Pkgsrc Pkgsrc   // Global data, mostly extracted from mk/*.
+       Pkg    *Package // The package that is currently checked.
+       Mk     *MkLines // The Makefile (or fragment) that is currently checked.
 
        Todo            []string // The files or directories that still need to be checked.
        CurrentDir      string   // The currently checked directory, relative to the cwd
@@ -159,7 +159,17 @@ func (pkglint *Pkglint) Main(args ...str
                pkglint.Todo = []string{"."}
        }
 
-       pkglint.globalData.Initialize()
+       firstArg := G.Todo[0]
+       if fileExists(firstArg) {
+               firstArg = path.Dir(firstArg)
+       }
+       relTopdir := findPkgsrcTopdir(firstArg)
+       if relTopdir == "" {
+               dummyLine.Fatalf("%q is not inside a pkgsrc tree.", firstArg)
+       }
+
+       pkglint.Pkgsrc = NewPkgsrc(firstArg + "/" + relTopdir)
+       pkglint.Pkgsrc.Load()
 
        currentUser, err := user.Current()
        if err == nil {
@@ -307,7 +317,7 @@ func (pkglint *Pkglint) CheckDirent(fnam
 
        switch pkglint.CurPkgsrcdir {
        case "../..":
-               pkglint.checkdirPackage(relpath(pkglint.globalData.Pkgsrcdir, currentDir))
+               pkglint.checkdirPackage(pkglint.Pkgsrc.ToRel(currentDir))
        case "..":
                CheckdirCategory()
        case ".":
@@ -477,15 +487,7 @@ func (pkglint *Pkglint) Checkfile(fname 
                return
        }
 
-       if st.Mode().IsRegular() && st.Mode().Perm()&0111 != 0 && !isCommitted(fname) {
-               line := NewLine(fname, 0, "", nil)
-               line.Warnf("Should not be executable.")
-               Explain(
-                       "No package file should ever be executable.  Even the INSTALL and",
-                       "DEINSTALL scripts are usually not usable in the form they have in",
-                       "the package, as the pathnames get adjusted during installation.",
-                       "So there is no need to have any file executable.")
-       }
+       pkglint.checkExecutable(st, fname)
 
        switch {
        case st.Mode().IsDir():
@@ -576,7 +578,7 @@ func (pkglint *Pkglint) Checkfile(fname 
 
        case hasPrefix(basename, "CHANGES-"):
                // This only checks the file, but doesn't register the changes globally.
-               _ = pkglint.globalData.loadDocChangesFromFile(fname)
+               _ = pkglint.Pkgsrc.loadDocChangesFromFile(fname)
 
        case matches(fname, `(?:^|/)files/[^/]*$`):
                // Skip
@@ -592,6 +594,28 @@ func (pkglint *Pkglint) Checkfile(fname 
        }
 }
 
+func (pkglint *Pkglint) checkExecutable(st os.FileInfo, fname string) {
+       if st.Mode().IsRegular() && st.Mode().Perm()&0111 != 0 && !isCommitted(fname) {
+               line := NewLine(fname, 0, "", nil)
+               fix := line.Autofix()
+               fix.Warnf("Should not be executable.")
+               fix.Explain(
+                       "No package file should ever be executable.  Even the INSTALL and",
+                       "DEINSTALL scripts are usually not usable in the form they have in",
+                       "the package, as the pathnames get adjusted during installation.",
+                       "So there is no need to have any file executable.")
+               fix.Custom(func(printAutofix, autofix bool) {
+                       fix.Describef(0, "Clearing executable bits")
+                       if autofix {
+                               if err := os.Chmod(line.Filename, st.Mode()&^0111); err != nil {
+                                       line.Errorf("Cannot clear executable bits: %s", err)
+                               }
+                       }
+               })
+               fix.Apply()
+       }
+}
+
 func ChecklinesTrailingEmptyLines(lines []Line) {
        max := len(lines)
        last := max

Index: pkgsrc/pkgtools/pkglint/files/pkglint_test.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.16 pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.17
--- pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.16  Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/pkglint_test.go       Sat Mar 24 14:32:49 2018
@@ -151,7 +151,7 @@ func (s *Suite) Test_Pkglint_Main__compl
                "# dummy")
 
        // The MASTER_SITES in the package Makefile are searched here.
-       // See GlobalData.loadDistSites.
+       // See Pkgsrc.loadMasterSites.
        t.CreateFileLines("mk/fetch/sites.mk",
                MkRcsID,
                "",
@@ -288,7 +288,6 @@ func (s *Suite) Test_Pkglint_CheckDirent
        t.SetupFileLines("category/package/Makefile")
        t.SetupFileLines("category/Makefile")
        t.SetupFileLines("Makefile")
-       G.globalData.Pkgsrcdir = t.TmpDir()
 
        G.CheckDirent(t.TmpDir())
 
@@ -434,39 +433,60 @@ func (s *Suite) Test_ChecklinesMessage__
                "===========================================================================")
 }
 
-func (s *Suite) Test_GlobalData_Latest(c *check.C) {
+func (s *Suite) Test_GlobalData_Latest_no_basedir(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.Pkgsrcdir = t.TmpDir()
-
-       latest1 := G.globalData.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
+       latest1 := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
 
        c.Check(latest1, equals, "")
        t.CheckOutputLines(
-               "ERROR: Cannot find latest version of \"^python[0-9]+$\" in \"~\".")
+               "ERROR: Cannot find latest version of \"^python[0-9]+$\" in \"~/lang\".")
+}
+
+func (s *Suite) Test_GlobalData_Latest_no_subdirs(c *check.C) {
+       t := s.Init(c)
 
        t.SetupFileLines("lang/Makefile")
-       G.globalData.latest = nil
 
-       latest2 := G.globalData.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
+       latest2 := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
 
        c.Check(latest2, equals, "")
        t.CheckOutputLines(
-               "ERROR: Cannot find latest version of \"^python[0-9]+$\" in \"~\".")
+               "ERROR: Cannot find latest version of \"^python[0-9]+$\" in \"~/lang\".")
+}
 
+func (s *Suite) Test_GlobalData_Latest_single(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupFileLines("lang/Makefile")
        t.SetupFileLines("lang/python27/Makefile")
-       G.globalData.latest = nil
 
-       latest3 := G.globalData.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
+       latest3 := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
 
        c.Check(latest3, equals, "../../lang/python27")
-       t.CheckOutputEmpty()
+}
 
+func (s *Suite) Test_GlobalData_Latest_multi(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupFileLines("lang/Makefile")
+       t.SetupFileLines("lang/python27/Makefile")
        t.SetupFileLines("lang/python35/Makefile")
-       G.globalData.latest = nil
 
-       latest4 := G.globalData.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
+       latest4 := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
 
        c.Check(latest4, equals, "../../lang/python35")
-       t.CheckOutputEmpty()
+}
+
+func (s *Suite) Test_GlobalData_Latest_numeric(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupFileLines("databases/postgresql95/Makefile")
+       t.SetupFileLines("databases/postgresql97/Makefile")
+       t.SetupFileLines("databases/postgresql100/Makefile")
+       t.SetupFileLines("databases/postgresql104/Makefile")
+
+       latest := G.Pkgsrc.Latest("databases", `^postgresql[0-9]+$`, "$0")
+
+       c.Check(latest, equals, "postgresql104")
 }

Index: pkgsrc/pkgtools/pkglint/files/plist.go
diff -u pkgsrc/pkgtools/pkglint/files/plist.go:1.23 pkgsrc/pkgtools/pkglint/files/plist.go:1.24
--- pkgsrc/pkgtools/pkglint/files/plist.go:1.23 Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/plist.go      Sat Mar 24 14:32:49 2018
@@ -501,6 +501,9 @@ func (s *plistLineSorter) Sort() {
                return
        }
 
+       if !shallBeLogged("%q should be sorted before %q.") {
+               return
+       }
        if len(s.middle) == 0 {
                return
        }
@@ -522,6 +525,7 @@ func (s *plistLineSorter) Sort() {
        }
 
        fix := firstLine.Autofix()
+       fix.Notef("Silent-Magic-Diagnostic")
        fix.Describef(int(firstLine.firstLine), "Sorting the whole file.")
        fix.Apply()
 

Index: pkgsrc/pkgtools/pkglint/files/plist_test.go
diff -u pkgsrc/pkgtools/pkglint/files/plist_test.go:1.22 pkgsrc/pkgtools/pkglint/files/plist_test.go:1.23
--- pkgsrc/pkgtools/pkglint/files/plist_test.go:1.22    Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/plist_test.go Sat Mar 24 14:32:49 2018
@@ -317,3 +317,25 @@ func (s *Suite) Test_PlistChecker__remov
                "${PLIST.option3}bin/false",
                "bin/true")
 }
+
+// When pkglint is run with the --only option, only the matched
+// diagnostics must be autofixed. Up to 2018-03-12, the PLIST was
+// sorted even if it didn't match the --only pattern.
+func (s *Suite) Test_PlistChecker__autofix_with_only(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wall", "--autofix", "--only", "matches nothing")
+
+       lines := t.SetupFileLines("PLIST",
+               PlistRcsID,
+               "sbin/program",
+               "bin/program")
+
+       ChecklinesPlist(lines)
+
+       t.CheckOutputEmpty()
+       t.CheckFileLines("PLIST",
+               PlistRcsID,
+               "sbin/program",
+               "bin/program")
+}
Index: pkgsrc/pkgtools/pkglint/files/shell.go
diff -u pkgsrc/pkgtools/pkglint/files/shell.go:1.22 pkgsrc/pkgtools/pkglint/files/shell.go:1.23
--- pkgsrc/pkgtools/pkglint/files/shell.go:1.22 Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/shell.go      Sat Mar 24 14:32:49 2018
@@ -476,7 +476,7 @@ func (scc *SimpleCommandChecker) handleT
        }
 
        shellword := scc.strcmd.Name
-       tool, localTool := G.globalData.Tools.byName[shellword], false
+       tool, localTool := G.Pkgsrc.Tools.ByName(shellword), false
        if tool == nil && G.Mk != nil {
                tool, localTool = G.Mk.toolRegistry.byName[shellword], true
        }
@@ -524,7 +524,7 @@ func (scc *SimpleCommandChecker) handleC
        if varuse := parser.VarUse(); varuse != nil && parser.EOF() {
                varname := varuse.varname
 
-               if tool := G.globalData.Tools.byVarname[varname]; tool != nil {
+               if tool := G.Pkgsrc.Tools.ByVarname(varname); tool != nil {
                        if !G.Mk.tools[tool.Name] {
                                scc.shline.mkline.Warnf("The %q tool is used but not added to USE_TOOLS.", tool.Name)
                        }
@@ -791,6 +791,12 @@ func (spc *ShellProgramChecker) checkPip
                return false
        }
 
+       // canFail tests whether one of the left-hand side commands of a
+       // shell pipeline can fail.
+       //
+       // Examples:
+       //  echo "hello" | sed 's,$, world,,'   => cannot fail
+       //  find . -print | xargs cat | wc -l   => can fail
        canFail := func() (bool, string) {
                for _, cmd := range pipeline.Cmds[:len(pipeline.Cmds)-1] {
                        simple := cmd.Simple
@@ -800,7 +806,7 @@ func (spc *ShellProgramChecker) checkPip
                        if len(simple.Redirections) != 0 {
                                return true, simple.Name.MkText
                        }
-                       tool := G.globalData.Tools.FindByCommand(simple.Name)
+                       tool := G.Pkgsrc.Tools.FindByCommand(simple.Name)
                        switch {
                        case tool == nil:
                                return true, simple.Name.MkText
Index: pkgsrc/pkgtools/pkglint/files/shell_test.go
diff -u pkgsrc/pkgtools/pkglint/files/shell_test.go:1.22 pkgsrc/pkgtools/pkglint/files/shell_test.go:1.23
--- pkgsrc/pkgtools/pkglint/files/shell_test.go:1.22    Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/shell_test.go Sat Mar 24 14:32:49 2018
@@ -109,7 +109,7 @@ func (s *Suite) Test_ShellLine_CheckShel
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
 
        checkShellCommandLine := func(shellCommand string) {
                G.Mk = t.NewMkLines("fname",
@@ -131,7 +131,7 @@ func (s *Suite) Test_ShellLine_CheckShel
                "WARN: fname:1: Unknown shell command \"echo\".")
 
        t.SetupTool(&Tool{Name: "echo", Predefined: true})
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
 
        checkShellCommandLine("echo ${PKGNAME:Q}") // vucQuotPlain
 
@@ -258,7 +258,7 @@ func (s *Suite) Test_ShellLine_CheckShel
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        t.SetupTool(&Tool{Name: "echo", Predefined: true})
        G.Mk = t.NewMkLines("Makefile",
                "\techo ${PKGNAME:Q}")
@@ -274,7 +274,7 @@ func (s *Suite) Test_ShellLine_CheckShel
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall", "--show-autofix")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        t.SetupTool(&Tool{Name: "echo", Predefined: true})
        G.Mk = t.NewMkLines("Makefile",
                "\techo ${PKGNAME:Q}")
@@ -292,7 +292,7 @@ func (s *Suite) Test_ShellLine_CheckShel
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        t.SetupTool(&Tool{Name: "cat", Predefined: true})
        t.SetupTool(&Tool{Name: "echo", Predefined: true})
        t.SetupTool(&Tool{Name: "printf", Predefined: true})
@@ -325,7 +325,7 @@ func (s *Suite) Test_ShellLine_CheckShel
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall", "--autofix")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        t.SetupTool(&Tool{Name: "echo", Predefined: true})
        G.Mk = t.NewMkLines("Makefile",
                "\techo ${PKGNAME:Q}")
@@ -341,7 +341,7 @@ func (s *Suite) Test_ShellLine_CheckShel
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("fname",
                "# dummy")
        shline := NewShellLine(G.Mk.mklines[0])
@@ -369,7 +369,7 @@ func (s *Suite) Test_ShellLine_CheckShel
 func (s *Suite) Test_ShellLine_CheckShelltext__dollar_without_variable(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        G.Mk = t.NewMkLines("fname",
                "# dummy")
        shline := NewShellLine(G.Mk.mklines[0])
@@ -385,7 +385,7 @@ func (s *Suite) Test_ShellLine_CheckWord
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
 
        checkWord := func(shellWord string, checkQuoting bool) {
                shline := t.NewShellLine("dummy.mk", 1, "\t echo "+shellWord)
@@ -622,7 +622,7 @@ func (s *Suite) Test_ShellLine__variable
        t := s.Init(c)
 
        t.SetupCommandLine("-Wall")
-       G.globalData.InitVartypes()
+       t.SetupVartypes()
        mklines := t.NewMkLines("dummy.mk",
                MkRcsID,
                "GZIP=\t${ECHO} $$comment")
Index: pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.22 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.23
--- pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.22     Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go  Sat Mar 24 14:32:49 2018
@@ -172,7 +172,6 @@ func (s *Suite) Test_VartypeCheck_Depend
                "# empty")
        t.SetupFileLines("category/package/Makefile",
                "# empty")
-       G.globalData.Pkgsrcdir = t.TmpDir()
        G.CurrentDir = t.TmpDir() + "/category/package"
        G.CurPkgsrcdir = "../.."
 
@@ -323,7 +322,7 @@ func (s *Suite) Test_VartypeCheck_Licens
                "AND mit")
 
        t.CheckOutputLines(
-               "WARN: fname:1: License file /licenses/gnu-gpl-v2 does not exist.",
+               "WARN: fname:1: License file ~/licenses/gnu-gpl-v2 does not exist.",
                "ERROR: fname:2: Parse error for license condition \"AND mit\".")
 
        runVartypeChecks(t, "LICENSE", opAssignAppend, (*VartypeCheck).License,
@@ -332,7 +331,7 @@ func (s *Suite) Test_VartypeCheck_Licens
 
        t.CheckOutputLines(
                "ERROR: fname:1: Parse error for appended license condition \"gnu-gpl-v2\".",
-               "WARN: fname:2: License file /licenses/mit does not exist.")
+               "WARN: fname:2: License file ~/licenses/mit does not exist.")
 }
 
 func (s *Suite) Test_VartypeCheck_MachineGnuPlatform(c *check.C) {
@@ -379,10 +378,8 @@ func (s *Suite) Test_VartypeCheck_Messag
 func (s *Suite) Test_VartypeCheck_Option(c *check.C) {
        t := s.Init(c)
 
-       G.globalData.PkgOptions = map[string]string{
-               "documented":   "Option description",
-               "undocumented": "",
-       }
+       G.Pkgsrc.PkgOptions["documented"] = "Option description"
+       G.Pkgsrc.PkgOptions["undocumented"] = ""
 
        runVartypeChecks(t, "PKG_OPTIONS.pkgbase", opAssign, (*VartypeCheck).Option,
                "documented",

Index: pkgsrc/pkgtools/pkglint/files/util.go
diff -u pkgsrc/pkgtools/pkglint/files/util.go:1.20 pkgsrc/pkgtools/pkglint/files/util.go:1.21
--- pkgsrc/pkgtools/pkglint/files/util.go:1.20  Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/util.go       Sat Mar 24 14:32:49 2018
@@ -302,13 +302,15 @@ func mkopSubst(s string, left bool, from
        })
 }
 
+// relpath returns the relative path from `from` to `to`.
+// If `to` is not within `from`, it panics.
 func relpath(from, to string) string {
        absFrom, err1 := filepath.Abs(from)
        absTo, err2 := filepath.Abs(to)
        rel, err3 := filepath.Rel(absFrom, absTo)
        if err1 != nil || err2 != nil || err3 != nil {
                trace.Stepf("relpath.panic", from, to, err1, err2, err3)
-               panic("relpath")
+               panic(fmt.Sprintf("relpath %q, %q", from, to))
        }
        result := filepath.ToSlash(rel)
        if trace.Tracing {
@@ -471,3 +473,81 @@ func (s *Scope) FirstDefinition(varname 
 func (s *Scope) FirstUse(varname string) MkLine {
        return s.used[varname]
 }
+
+// The MIT License (MIT)
+//
+// Copyright (c) 2015 Frits van Bommel
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+// Taken from https://github.com/fvbommel/util/blob/11997822f8/sortorder/natsort.go
+func naturalLess(str1, str2 string) bool {
+
+       isDigit := func(b byte) bool { return '0' <= b && b <= '9' }
+
+       idx := 0
+       len1, len2 := len(str1), len(str2)
+       len := len1 + len2 - imax(len1, len2)
+       for idx < len {
+               c1, c2 := str1[idx], str2[idx]
+               dig1, dig2 := isDigit(c1), isDigit(c2)
+               switch {
+               case dig1 != dig2: // Digits before other characters.
+                       return dig1 // True if LHS is a digit, false if the RHS is one.
+               case !dig1: // && !dig2, because dig1 == dig2
+                       // UTF-8 compares bytewise-lexicographically, no need to decode
+                       // codepoints.
+                       if c1 != c2 {
+                               return c1 < c2
+                       }
+                       idx++
+               default: // Digits
+                       // Eat zeros.
+                       idx1, idx2 := idx, idx
+                       for ; idx1 < len1 && str1[idx1] == '0'; idx1++ {
+                       }
+                       for ; idx2 < len2 && str2[idx2] == '0'; idx2++ {
+                       }
+                       // Eat all digits.
+                       nonZero1, nonZero2 := idx1, idx2
+                       for ; idx1 < len1 && isDigit(str1[idx1]); idx1++ {
+                       }
+                       for ; idx2 < len2 && isDigit(str2[idx2]); idx2++ {
+                       }
+                       // If lengths of numbers with non-zero prefix differ, the shorter
+                       // one is less.
+                       if len1, len2 := idx1-nonZero1, idx2-nonZero2; len1 != len2 {
+                               return len1 < len2
+                       }
+                       // If they're not equal, string comparison is correct.
+                       if nr1, nr2 := str1[nonZero1:idx1], str2[nonZero2:idx2]; nr1 != nr2 {
+                               return nr1 < nr2
+                       }
+                       // Otherwise, the one with less zeros is less.
+                       // Because everything up to the number is equal, comparing the index
+                       // after the zeros is sufficient.
+                       if nonZero1 != nonZero2 {
+                               return nonZero1 < nonZero2
+                       }
+                       idx = idx1
+               }
+               // They're identical so far, so continue comparing.
+       }
+       // So far they are identical. At least one is ended. If the other continues,
+       // it sorts last.
+       return len1 < len2
+}

Index: pkgsrc/pkgtools/pkglint/files/vardefs.go
diff -u pkgsrc/pkgtools/pkglint/files/vardefs.go:1.37 pkgsrc/pkgtools/pkglint/files/vardefs.go:1.38
--- pkgsrc/pkgtools/pkglint/files/vardefs.go:1.37       Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/vardefs.go    Sat Mar 24 14:32:49 2018
@@ -17,17 +17,40 @@ import (
 // InitVartypes initializes the long list of predefined pkgsrc variables.
 // After this is done, ${PKGNAME}, ${MAKE_ENV} and all the other variables
 // can be used in Makefiles without triggering warnings about typos.
-func (gd *GlobalData) InitVartypes() {
+func (src *PkgsrcImpl) InitVartypes() {
+
+       acl := func(varname string, kindOfList KindOfList, checker *BasicType, aclentries string) {
+               m := mustMatch(varname, `^([A-Z_.][A-Z0-9_]*)(|\*|\.\*)$`)
+               varbase, varparam := m[1], m[2]
+
+               vtype := &Vartype{kindOfList, checker, parseACLEntries(varname, aclentries), false}
+
+               if src.vartypes == nil {
+                       src.vartypes = make(map[string]*Vartype)
+               }
+               if varparam == "" || varparam == "*" {
+                       src.vartypes[varbase] = vtype
+               }
+               if varparam == "*" || varparam == ".*" {
+                       src.vartypes[varbase+".*"] = vtype
+               }
+       }
 
        // A package-defined variable may be set in all Makefiles except buildlink3.mk and builtin.mk.
        pkg := func(varname string, kindOfList KindOfList, checker *BasicType) {
-               acl(varname, kindOfList, checker, "Makefile: set, use; buildlink3.mk, builtin.mk:; Makefile.*, *.mk: default, set, use")
+               acl(varname, kindOfList, checker, ""+
+                       "Makefile: set, use; "+
+                       "buildlink3.mk, builtin.mk:; "+
+                       "Makefile.*, *.mk: default, set, use")
        }
 
        // A package-defined list may be appended to in all Makefiles except buildlink3.mk and builtin.mk.
        // Simple assignment (instead of appending) is only allowed in Makefile and Makefile.common.
        pkglist := func(varname string, kindOfList KindOfList, checker *BasicType) {
-               acl(varname, kindOfList, checker, "Makefile, Makefile.common, options.mk: append, default, set, use; buildlink3.mk, builtin.mk:; *.mk: append, default, use")
+               acl(varname, kindOfList, checker, ""+
+                       "Makefile, Makefile.common, options.mk: append, default, set, use; "+
+                       "buildlink3.mk, builtin.mk:; "+
+                       "*.mk: append, default, use")
        }
 
        // A user-defined or system-defined variable must not be set by any
@@ -1023,23 +1046,6 @@ func enum(values string) *BasicType {
        }}
 }
 
-func acl(varname string, kindOfList KindOfList, checker *BasicType, aclentries string) {
-       m := mustMatch(varname, `^([A-Z_.][A-Z0-9_]*)(|\*|\.\*)$`)
-       varbase, varparam := m[1], m[2]
-
-       vtype := &Vartype{kindOfList, checker, parseACLEntries(varname, aclentries), false}
-
-       if G.globalData.vartypes == nil {
-               G.globalData.vartypes = make(map[string]*Vartype)
-       }
-       if varparam == "" || varparam == "*" {
-               G.globalData.vartypes[varbase] = vtype
-       }
-       if varparam == "*" || varparam == ".*" {
-               G.globalData.vartypes[varbase+".*"] = vtype
-       }
-}
-
 func parseACLEntries(varname string, aclentries string) []ACLEntry {
        if aclentries == "" {
                return nil

Index: pkgsrc/pkgtools/pkglint/files/vartypecheck.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.30 pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.31
--- pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.30  Sun Mar  4 20:34:33 2018
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck.go       Sat Mar 24 14:32:49 2018
@@ -396,7 +396,7 @@ func (cv *VartypeCheck) EmulPlatform() {
 func (cv *VartypeCheck) FetchURL() {
        MkLineChecker{cv.MkLine}.CheckVartypePrimitive(cv.Varname, BtURL, cv.Op, cv.Value, cv.MkComment, cv.Guessed)
 
-       for siteURL, siteName := range G.globalData.MasterSiteURLToVar {
+       for siteURL, siteName := range G.Pkgsrc.MasterSiteURLToVar {
                if hasPrefix(cv.Value, siteURL) {
                        subdir := cv.Value[len(siteURL):]
                        if hasPrefix(cv.Value, "https://github.com/";) {
@@ -411,7 +411,7 @@ func (cv *VartypeCheck) FetchURL() {
        }
 
        if m, name, subdir := match2(cv.Value, `\$\{(MASTER_SITE_[^:]*).*:=(.*)\}$`); m {
-               if G.globalData.MasterSiteVarToURL[name] == "" {
+               if G.Pkgsrc.MasterSiteVarToURL[name] == "" {
                        cv.Line.Errorf("The site %s does not exist.", name)
                }
                if !hasSuffix(subdir, "/") {
@@ -457,7 +457,7 @@ func (cv *VartypeCheck) Homepage() {
        MkLineChecker{cv.MkLine}.CheckVartypePrimitive(cv.Varname, BtURL, cv.Op, cv.Value, cv.MkComment, cv.Guessed)
 
        if m, wrong, sitename, subdir := match3(cv.Value, `^(\$\{(MASTER_SITE\w+)(?::=([\w\-/]+))?\})`); m {
-               baseURL := G.globalData.MasterSiteVarToURL[sitename]
+               baseURL := G.Pkgsrc.MasterSiteVarToURL[sitename]
                if sitename == "MASTER_SITES" && G.Pkg != nil {
                        masterSites, _ := G.Pkg.varValue("MASTER_SITES")
                        if !containsVarRef(masterSites) {
@@ -638,7 +638,7 @@ func (cv *VartypeCheck) Option() {
        }
 
        if m, optname := match1(value, `^-?([a-z][-0-9a-z+]*)$`); m {
-               if _, found := G.globalData.PkgOptions[optname]; !found { // There's a difference between empty and absent here.
+               if _, found := G.Pkgsrc.PkgOptions[optname]; !found { // There's a difference between empty and absent here.
                        line.Warnf("Unknown option \"%s\".", optname)
                        Explain(
                                "This option is not documented in the mk/defaults/options.description",
@@ -945,7 +945,7 @@ func (cv *VartypeCheck) Tool() {
                // no warning for package-defined tool definitions
 
        } else if m, toolname, tooldep := match2(cv.Value, `^([-\w]+|\[)(?::(\w+))?$`); m {
-               if G.globalData.Tools.byName[toolname] == nil && (G.Mk == nil || G.Mk.toolRegistry.byName[toolname] == nil) {
+               if G.Pkgsrc.Tools.ByName(toolname) == nil && (G.Mk == nil || G.Mk.toolRegistry.byName[toolname] == nil) {
                        cv.Line.Errorf("Unknown tool %q.", toolname)
                }
                switch tooldep {

Added files:

Index: pkgsrc/pkgtools/pkglint/files/pkgsrc.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.1
--- /dev/null   Sat Mar 24 14:32:50 2018
+++ pkgsrc/pkgtools/pkglint/files/pkgsrc.go     Sat Mar 24 14:32:49 2018
@@ -0,0 +1,605 @@
+package main
+
+import (
+       "io/ioutil"
+       "netbsd.org/pkglint/regex"
+       "netbsd.org/pkglint/trace"
+       "sort"
+       "strings"
+)
+
+// Pkgsrc describes a pkgsrc installation.
+// In each pkglint run, only a single pkgsrc installation is ever loaded.
+// It just doesn't make sense to check multiple pkgsrc installations at once.
+type Pkgsrc = *PkgsrcImpl
+
+type PkgsrcImpl struct {
+
+       // The top directory (PKGSRCDIR), either absolute or relative to
+       // the current working directory.
+       topdir string
+
+       // The set of user-defined variables that are added to BUILD_DEFS
+       // within the bsd.pkg.mk file.
+       buildDefs map[string]bool
+
+       Tools ToolRegistry
+
+       MasterSiteURLToVar map[string]string // "https://github.com/"; => "MASTER_SITE_GITHUB"
+       MasterSiteVarToURL map[string]string // "MASTER_SITE_GITHUB" => "https://github.com/";
+
+       PkgOptions map[string]string // "x11" => "Provides X11 support"
+
+       suggestedUpdates    []SuggestedUpdate  //
+       suggestedWipUpdates []SuggestedUpdate  //
+       LastChange          map[string]*Change //
+       latest              map[string]string  // "lang/php[0-9]*" => "lang/php70"
+
+       UserDefinedVars map[string]MkLine   // varname => line; used for checking BUILD_DEFS
+       Deprecated      map[string]string   //
+       vartypes        map[string]*Vartype // varcanon => type
+}
+
+func NewPkgsrc(dir string) Pkgsrc {
+       src := &PkgsrcImpl{
+               dir,
+               make(map[string]bool),
+               NewToolRegistry(),
+               make(map[string]string),
+               make(map[string]string),
+               make(map[string]string),
+               nil,
+               nil,
+               make(map[string]*Change),
+               make(map[string]string),
+               make(map[string]MkLine),
+               make(map[string]string),
+               make(map[string]*Vartype)}
+
+       // Some user-defined variables do not influence the binary
+       // package at all and therefore do not have to be added to
+       // BUILD_DEFS; therefore they are marked as "already added".
+       src.AddBuildDef("DISTDIR")
+       src.AddBuildDef("FETCH_CMD")
+       src.AddBuildDef("FETCH_OUTPUT_ARGS")
+
+       // The following variables are not expected to be modified
+       // by the pkgsrc user. They are added here to prevent unnecessary
+       // warnings by pkglint.
+       src.AddBuildDef("GAMES_USER")
+       src.AddBuildDef("GAMES_GROUP")
+       src.AddBuildDef("GAMEDATAMODE")
+       src.AddBuildDef("GAMEDIRMODE")
+       src.AddBuildDef("GAMEMODE")
+       src.AddBuildDef("GAMEOWN")
+       src.AddBuildDef("GAMEGRP")
+
+       return src
+}
+
+// Load reads the pkgsrc infrastructure files to
+// extract information like the tools, packages to update,
+// user-defined variables.
+//
+// This work is not done in the constructor to keep the tests
+// simple, since setting up a realistic pkgsrc environment requires
+// a lot of files.
+func (src *PkgsrcImpl) Load() {
+       src.InitVartypes()
+       src.loadMasterSites()
+       src.loadPkgOptions()
+       src.loadDocChanges()
+       src.loadSuggestedUpdates()
+       src.loadUserDefinedVars()
+       src.loadTools()
+       src.initDeprecatedVars()
+}
+
+// Latest returns the latest package matching the given pattern.
+// It searches the `category` for subdirectories matching the given
+// regular expression, and returns the `repl` string, in which the
+// placeholder is filled with the best result.
+//
+// Example:
+//  Latest("lang", `^php[0-9]+$`, "../../lang/$0") => "../../lang/php72"
+func (src *PkgsrcImpl) Latest(category string, re regex.Pattern, repl string) string {
+       key := category + "/" + string(re) + " => " + repl
+       if latest, found := src.latest[key]; found {
+               return latest
+       }
+
+       if src.latest == nil {
+               src.latest = make(map[string]string)
+       }
+
+       categoryDir := src.File(category)
+       error := func() string {
+               dummyLine.Errorf("Cannot find latest version of %q in %q.", re, categoryDir)
+               src.latest[key] = ""
+               return ""
+       }
+
+       all, err := ioutil.ReadDir(categoryDir)
+       sort.SliceStable(all, func(i, j int) bool {
+               return naturalLess(all[i].Name(), all[j].Name())
+       })
+       if err != nil {
+               return error()
+       }
+
+       latest := ""
+       for _, fileInfo := range all {
+               if matches(fileInfo.Name(), re) {
+                       latest = regex.Compile(re).ReplaceAllString(fileInfo.Name(), repl)
+               }
+       }
+       if latest == "" {
+               return error()
+       }
+
+       src.latest[key] = latest
+       return latest
+}
+
+// loadTools loads the tool definitions from `mk/tools/*`.
+func (src *PkgsrcImpl) loadTools() {
+       toolFiles := []string{"defaults.mk"}
+       {
+               lines := G.Pkgsrc.LoadExistingLines("mk/tools/bsd.tools.mk", true)
+               for _, line := range lines {
+                       if m, _, _, includefile := MatchMkInclude(line.Text); m {
+                               if !contains(includefile, "/") {
+                                       toolFiles = append(toolFiles, includefile)
+                               }
+                       }
+               }
+               if len(toolFiles) <= 1 {
+                       lines[0].Fatalf("Too few tool files.")
+               }
+       }
+
+       reg := src.Tools
+       reg.RegisterTool(&Tool{"echo", "ECHO", true, true, true})
+       reg.RegisterTool(&Tool{"echo -n", "ECHO_N", true, true, true})
+       reg.RegisterTool(&Tool{"false", "FALSE", true /*why?*/, true, false})
+       reg.RegisterTool(&Tool{"test", "TEST", true, true, true})
+       reg.RegisterTool(&Tool{"true", "TRUE", true /*why?*/, true, true})
+
+       for _, basename := range toolFiles {
+               lines := G.Pkgsrc.LoadExistingLines("mk/tools/"+basename, true)
+               for _, line := range lines {
+                       reg.ParseToolLine(line)
+               }
+       }
+
+       for _, relativeName := range [...]string{"mk/bsd.prefs.mk", "mk/bsd.pkg.mk"} {
+               condDepth := 0
+
+               lines := G.Pkgsrc.LoadExistingLines(relativeName, true)
+               for _, line := range lines {
+                       text := line.Text
+                       if hasPrefix(text, "#") {
+                               continue
+                       }
+
+                       if m, _, varname, _, _, _, value, _, _ := MatchVarassign(text); m {
+                               if varname == "USE_TOOLS" {
+                                       if trace.Tracing {
+                                               trace.Stepf("[condDepth=%d] %s", condDepth, value)
+                                       }
+                                       if condDepth == 0 || condDepth == 1 && relativeName == "mk/bsd.prefs.mk" {
+                                               for _, toolname := range splitOnSpace(value) {
+                                                       if !containsVarRef(toolname) {
+                                                               for _, tool := range [...]*Tool{reg.Register(toolname), reg.Register("TOOLS_" + toolname)} {
+                                                                       tool.Predefined = true
+                                                                       if relativeName == "mk/bsd.prefs.mk" {
+                                                                               tool.UsableAtLoadtime = true
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+
+                               } else if varname == "_BUILD_DEFS" {
+                                       for _, bdvar := range splitOnSpace(value) {
+                                               src.AddBuildDef(bdvar)
+                                       }
+                               }
+
+                       } else if m, _, cond, _ := matchMkCond(text); m {
+                               switch cond {
+                               case "if", "ifdef", "ifndef", "for":
+                                       condDepth++
+                               case "endif", "endfor":
+                                       condDepth--
+                               }
+                       }
+               }
+       }
+
+       if trace.Tracing {
+               reg.Trace()
+       }
+}
+
+func (src *PkgsrcImpl) loadSuggestedUpdatesFile(fname string) []SuggestedUpdate {
+       lines := LoadExistingLines(fname, false)
+       return src.parseSuggestedUpdates(lines)
+}
+
+func (src *PkgsrcImpl) parseSuggestedUpdates(lines []Line) []SuggestedUpdate {
+       var updates []SuggestedUpdate
+       state := 0
+       for _, line := range lines {
+               text := line.Text
+
+               if state == 0 && text == "Suggested package updates" {
+                       state = 1
+               } else if state == 1 && text == "" {
+                       state = 2
+               } else if state == 2 {
+                       state = 3
+               } else if state == 3 && text == "" {
+                       state = 4
+               }
+
+               if state == 3 {
+                       if m, pkgname, comment := match2(text, `^\to\s(\S+)(?:\s*(.+))?$`); m {
+                               if m, pkgbase, pkgversion := match2(pkgname, rePkgname); m {
+                                       updates = append(updates, SuggestedUpdate{line, pkgbase, pkgversion, comment})
+                               } else {
+                                       line.Warnf("Invalid package name %q", pkgname)
+                               }
+                       } else {
+                               line.Warnf("Invalid line format %q", text)
+                       }
+               }
+       }
+       return updates
+}
+
+func (src *PkgsrcImpl) loadSuggestedUpdates() {
+       src.suggestedUpdates = src.loadSuggestedUpdatesFile(G.Pkgsrc.File("doc/TODO"))
+       if wipFilename := G.Pkgsrc.File("wip/TODO"); fileExists(wipFilename) {
+               src.suggestedWipUpdates = src.loadSuggestedUpdatesFile(wipFilename)
+       }
+}
+
+func (src *PkgsrcImpl) loadDocChangesFromFile(fname string) []*Change {
+       lines := LoadExistingLines(fname, false)
+
+       parseChange := func(line Line) *Change {
+               text := line.Text
+               if !hasPrefix(text, "\t") {
+                       return nil
+               }
+
+               f := strings.Fields(text)
+               n := len(f)
+               if n != 4 && n != 6 {
+                       return nil
+               }
+
+               action, pkgpath, author, date := f[0], f[1], f[len(f)-2], f[len(f)-1]
+               if !hasPrefix(author, "[") || !hasSuffix(date, "]") {
+                       return nil
+               }
+               author, date = author[1:], date[:len(date)-1]
+
+               switch {
+               case action == "Added" && f[2] == "version" && n == 6:
+                       return &Change{line, action, pkgpath, f[3], author, date}
+               case (action == "Updated" || action == "Downgraded") && f[2] == "to" && n == 6:
+                       return &Change{line, action, pkgpath, f[3], author, date}
+               case action == "Removed" && (n == 6 && f[2] == "successor" || n == 4):
+                       return &Change{line, action, pkgpath, "", author, date}
+               case (action == "Renamed" || action == "Moved") && f[2] == "to" && n == 6:
+                       return &Change{line, action, pkgpath, "", author, date}
+               }
+               return nil
+       }
+
+       var changes []*Change
+       for _, line := range lines {
+               if change := parseChange(line); change != nil {
+                       changes = append(changes, change)
+               } else if text := line.Text; len(text) >= 2 && text[0] == '\t' && 'A' <= text[1] && text[1] <= 'Z' {
+                       line.Warnf("Unknown doc/CHANGES line: %q", text)
+                       Explain("See mk/misc/developer.mk for the rules.")
+               }
+       }
+       return changes
+}
+
+func (src *PkgsrcImpl) GetSuggestedPackageUpdates() []SuggestedUpdate {
+       if G.Wip {
+               return src.suggestedWipUpdates
+       } else {
+               return src.suggestedUpdates
+       }
+}
+
+func (src *PkgsrcImpl) loadDocChanges() {
+       docdir := G.Pkgsrc.File("doc")
+       files, err := ioutil.ReadDir(docdir)
+       if err != nil {
+               NewLineWhole(docdir).Fatalf("Cannot be read.")
+       }
+
+       var fnames []string
+       for _, file := range files {
+               fname := file.Name()
+               if matches(fname, `^CHANGES-20\d\d$`) && fname >= "CHANGES-2011" {
+                       fnames = append(fnames, fname)
+               }
+       }
+
+       sort.Strings(fnames)
+       src.LastChange = make(map[string]*Change)
+       for _, fname := range fnames {
+               changes := src.loadDocChangesFromFile(docdir + "/" + fname)
+               for _, change := range changes {
+                       src.LastChange[change.Pkgpath] = change
+               }
+       }
+}
+
+func (src *PkgsrcImpl) loadUserDefinedVars() {
+       lines := G.Pkgsrc.LoadExistingLines("mk/defaults/mk.conf", true)
+       mklines := NewMkLines(lines)
+
+       for _, mkline := range mklines.mklines {
+               if mkline.IsVarassign() {
+                       src.UserDefinedVars[mkline.Varname()] = mkline
+               }
+       }
+}
+
+func (src *PkgsrcImpl) initDeprecatedVars() {
+       src.Deprecated = map[string]string{
+
+               // December 2003
+               "FIX_RPATH": "It has been removed from pkgsrc in 2003.",
+
+               // February 2005
+               "LIB_DEPENDS":    "Use DEPENDS instead.",
+               "ONLY_FOR_ARCHS": "Use ONLY_FOR_PLATFORM instead.",
+               "NOT_FOR_ARCHS":  "Use NOT_FOR_PLATFORM instead.",
+               "ONLY_FOR_OPSYS": "Use ONLY_FOR_PLATFORM instead.",
+               "NOT_FOR_OPSYS":  "Use NOT_FOR_PLATFORM instead.",
+
+               // May 2005
+               "ALL_TARGET":         "Use BUILD_TARGET instead.",
+               "DIGEST_FILE":        "Use DISTINFO_FILE instead.",
+               "IGNORE":             "Use PKG_FAIL_REASON or PKG_SKIP_REASON instead.",
+               "IS_INTERACTIVE":     "Use INTERACTIVE_STAGE instead.",
+               "KERBEROS":           "Use the PKG_OPTIONS framework instead.",
+               "MASTER_SITE_SUBDIR": "Use some form of MASTER_SITES instead.",
+               "MD5_FILE":           "Use DISTINFO_FILE instead.",
+               "MIRROR_DISTFILE":    "Use NO_BIN_ON_FTP and/or NO_SRC_ON_FTP instead.",
+               "NO_CDROM":           "Use NO_BIN_ON_CDROM and/or NO_SRC_ON_CDROM instead.",
+               "NO_PATCH":           "You can just remove it.",
+               "NO_WRKSUBDIR":       "Use WRKSRC=${WRKDIR} instead.",
+               "PATCH_SITE_SUBDIR":  "Use some form of PATCHES_SITES instead.",
+               "PATCH_SUM_FILE":     "Use DISTINFO_FILE instead.",
+               "PKG_JVM":            "Use PKG_DEFAULT_JVM instead.",
+               "USE_BUILDLINK2":     "You can just remove it.",
+               "USE_BUILDLINK3":     "You can just remove it.",
+               "USE_CANNA":          "Use the PKG_OPTIONS framework instead.",
+               "USE_DB4":            "Use the PKG_OPTIONS framework instead.",
+               "USE_DIRS":           "You can just remove it.",
+               "USE_ESOUND":         "Use the PKG_OPTIONS framework instead.",
+               "USE_GIF":            "Use the PKG_OPTIONS framework instead.",
+               "USE_GMAKE":          "Use USE_TOOLS+=gmake instead.",
+               "USE_GNU_TOOLS":      "Use USE_TOOLS instead.",
+               "USE_IDEA":           "Use the PKG_OPTIONS framework instead.",
+               "USE_LIBCRACK":       "Use the PKG_OPTIONS framework instead.",
+               "USE_MMX":            "Use the PKG_OPTIONS framework instead.",
+               "USE_PKGLIBTOOL":     "Use USE_LIBTOOL instead.",
+               "USE_SSL":            "Include \"../../security/openssl/buildlink3.mk\" instead.",
+
+               // July 2005
+               "USE_PERL5": "Use USE_TOOLS+=perl or USE_TOOLS+=perl:run instead.",
+
+               // October 2005
+               "NO_TOOLS":   "You can just remove it.",
+               "NO_WRAPPER": "You can just remove it.",
+
+               // November 2005
+               "ALLFILES":       "Use CKSUMFILES instead.",
+               "DEPENDS_TARGET": "Use DEPENDS instead.",
+               "FETCH_DEPENDS":  "Use DEPENDS instead.",
+               "RUN_DEPENDS":    "Use DEPENDS instead.",
+
+               // December 2005
+               "USE_CUPS":     "Use the PKG_OPTIONS framework (option cups) instead.",
+               "USE_I586":     "Use the PKG_OPTIONS framework (option i586) instead.",
+               "USE_INN":      "Use the PKG_OPTIONS framework instead.",
+               "USE_OPENLDAP": "Use the PKG_OPTIONS framework (option openldap) instead.",
+               "USE_OSS":      "Use the PKG_OPTIONS framework (option oss) instead.",
+               "USE_RSAREF2":  "Use the PKG_OPTIONS framework (option rsaref) instead.",
+               "USE_SASL":     "Use the PKG_OPTIONS framework (option sasl) instead.",
+               "USE_SASL2":    "Use the PKG_OPTIONS framework (option sasl) instead.",
+               "USE_SJ3":      "Use the PKG_OPTIONS framework (option sj3) instead.",
+               "USE_SOCKS":    "Use the PKG_OPTIONS framework (socks4 and socks5 options) instead.",
+               "USE_WNN4":     "Use the PKG_OPTIONS framework (option wnn4) instead.",
+               "USE_XFACE":    "Use the PKG_OPTIONS framework instead.",
+
+               // February 2006
+               "TOOLS_DEPMETHOD":     "Use the :build or :run modifiers in USE_TOOLS instead.",
+               "MANDIR":              "Please use ${PREFIX}/${PKGMANDIR} instead.",
+               "DOWNLOADED_DISTFILE": "Use the shell variable $$extract_file instead.",
+               "DECOMPRESS_CMD":      "Use EXTRACT_CMD instead.",
+
+               // March 2006
+               "INSTALL_EXTRA_TMPL":   "Use INSTALL_TEMPLATE instead.",
+               "DEINSTALL_EXTRA_TMPL": "Use DEINSTALL_TEMPLATE instead.",
+
+               // April 2006
+               "RECOMMENDED":        "Use ABI_DEPENDS instead.",
+               "BUILD_USES_MSGFMT":  "Use USE_TOOLS+=msgfmt instead.",
+               "USE_MSGFMT_PLURALS": "Use USE_TOOLS+=msgfmt instead.",
+
+               // May 2006
+               "EXTRACT_USING_PAX":       "Use \"EXTRACT_OPTS=-t pax\" instead.",
+               "NO_EXTRACT":              "It doesn't exist anymore.",
+               "_FETCH_MESSAGE":          "Use FETCH_MESSAGE (different format) instead.",
+               "BUILDLINK_DEPENDS.*":     "Use BUILDLINK_API_DEPENDS.* instead.",
+               "BUILDLINK_RECOMMENDED.*": "Use BUILDLINK_ABI_DEPENDS.* instead.",
+               "SHLIB_HANDLING":          "Use CHECK_SHLIBS_SUPPORTED instead.",
+               "USE_RMAN":                "It has been removed.",
+
+               // June 2006
+               "DEINSTALL_SRC":      "Use the pkginstall framework instead.",
+               "INSTALL_SRC":        "Use the pkginstall framework instead.",
+               "DEINSTALL_TEMPLATE": "Use DEINSTALL_TEMPLATES instead.",
+               "INSTALL_TEMPLATE":   "Use INSTALL_TEMPLATES instead.",
+               "HEADER_TEMPLATE":    "Use HEADER_TEMPLATES instead.",
+               "_REPLACE.*":         "Use REPLACE.* instead.",
+               "_REPLACE_FILES.*":   "Use REPLACE_FILES.* instead.",
+               "MESSAGE":            "Use MESSAGE_SRC instead.",
+               "INSTALL_FILE":       "It may only be used internally by pkgsrc.",
+               "DEINSTALL_FILE":     "It may only be used internally by pkgsrc.",
+
+               // July 2006
+               "USE_DIGEST":           "You can just remove it.",
+               "LTCONFIG_OVERRIDE":    "You can just remove it.",
+               "USE_GNU_GETTEXT":      "You can just remove it.",
+               "BUILD_ENV":            "Use PKGSRC_MAKE_ENV instead.",
+               "DYNAMIC_MASTER_SITES": "You can just remove it.",
+
+               // September 2006
+               "MAKEFILE": "Use MAKE_FILE instead.",
+
+               // November 2006
+               "SKIP_PORTABILITY_CHECK": "Use CHECK_PORTABILITY_SKIP (a list of patterns) instead.",
+
+               // January 2007
+               "BUILDLINK_TRANSFORM.*": "Use BUILDLINK_FNAME_TRANSFORM.* instead.",
+
+               // March 2007
+               "SCRIPTDIR":       "You can just remove it.",
+               "NO_PKG_REGISTER": "You can just remove it.",
+               "NO_DEPENDS":      "You can just remove it.",
+
+               // October 2007
+               "_PKG_SILENT": "Use RUN (with more error checking) instead.",
+               "_PKG_DEBUG":  "Use RUN (with more error checking) instead.",
+               "LICENCE":     "Use LICENSE instead.",
+
+               // November 2007
+               //USE_NCURSES           Include "../../devel/ncurses/buildlink3.mk" instead.
+
+               // December 2007
+               "INSTALLATION_DIRS_FROM_PLIST": "Use AUTO_MKDIRS instead.",
+
+               // April 2009
+               "NO_PACKAGE": "It doesn't exist anymore.",
+               "NO_MTREE":   "You can just remove it.",
+
+               // July 2012
+               "SETGIDGAME": "Use USE_GAMESGROUP instead.",
+               "GAMEGRP":    "Use GAMES_GROUP instead.",
+               "GAMEOWN":    "Use GAMES_USER instead.",
+
+               // July 2013
+               "USE_GNU_READLINE": "Include \"../../devel/readline/buildlink3.mk\" instead.",
+
+               // October 2014
+               "SVR4_PKGNAME":           "Just remove it.",
+               "PKG_INSTALLATION_TYPES": "Just remove it.",
+
+               // January 2016
+               "SUBST_POSTCMD.*": "Has been removed, as it seemed unused.",
+
+               // June 2016
+               "USE_CROSSBASE": "Has been removed.",
+       }
+}
+
+// LoadExistingLines loads the file relative to the pkgsrc top directory.
+func (src *PkgsrcImpl) LoadExistingLines(fileName string, joinBackslashLines bool) []Line {
+       return LoadExistingLines(src.topdir+"/"+fileName, joinBackslashLines)
+}
+
+// File resolves a file name relative to the pkgsrc top directory.
+//
+// Example:
+//  NewPkgsrc("/usr/pkgsrc").File("distfiles") => "/usr/pkgsrc/distfiles"
+func (src *PkgsrcImpl) File(relativeName string) string {
+       return src.topdir + "/" + relativeName
+}
+
+// ToRel returns the path of `fileName`, relative to the pkgsrc top directory.
+//
+// Example:
+//  NewPkgsrc("/usr/pkgsrc").ToRel("/usr/pkgsrc/distfiles") => "distfiles"
+func (src *PkgsrcImpl) ToRel(fileName string) string {
+       return relpath(src.topdir, fileName)
+}
+
+func (src *PkgsrcImpl) AddBuildDef(varname string) {
+       src.buildDefs[varname] = true
+}
+
+func (src *PkgsrcImpl) IsBuildDef(varname string) bool {
+       return src.buildDefs[varname]
+}
+
+func (src *PkgsrcImpl) loadMasterSites() {
+       lines := src.LoadExistingLines("mk/fetch/sites.mk", true)
+
+       nameToUrl := src.MasterSiteVarToURL
+       urlToName := src.MasterSiteURLToVar
+       for _, line := range lines {
+               if m, commented, varname, _, _, _, urls, _, _ := MatchVarassign(line.Text); m {
+                       if !commented && hasPrefix(varname, "MASTER_SITE_") && varname != "MASTER_SITE_BACKUP" {
+                               for _, url := range splitOnSpace(urls) {
+                                       if matches(url, `^(?:http://|https://|ftp://)`) {
+                                               if nameToUrl[varname] == "" {
+                                                       nameToUrl[varname] = url
+                                               }
+                                               urlToName[url] = varname
+                                       }
+                               }
+                       }
+               }
+       }
+
+       // Explicitly allowed, although not defined in mk/fetch/sites.mk.
+       nameToUrl["MASTER_SITE_LOCAL"] = "ftp://ftp.NetBSD.org/pub/pkgsrc/distfiles/LOCAL_PORTS/";
+
+       if trace.Tracing {
+               trace.Stepf("Loaded %d MASTER_SITE_* URLs.", len(urlToName))
+       }
+}
+
+func (src *PkgsrcImpl) loadPkgOptions() {
+       lines := src.LoadExistingLines("mk/defaults/options.description", false)
+
+       for _, line := range lines {
+               if m, optname, optdescr := match2(line.Text, `^([-0-9a-z_+]+)(?:\s+(.*))?$`); m {
+                       src.PkgOptions[optname] = optdescr
+               } else {
+                       line.Fatalf("Unknown line format.")
+               }
+       }
+}
+
+// Change is a change entry from the `doc/CHANGES-*` files.
+type Change struct {
+       Line    Line
+       Action  string
+       Pkgpath string
+       Version string
+       Author  string
+       Date    string
+}
+
+// SuggestedUpdate is from the `doc/TODO` file.
+type SuggestedUpdate struct {
+       Line    Line
+       Pkgname string
+       Version string
+       Comment string
+}
Index: pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.1
--- /dev/null   Sat Mar 24 14:32:50 2018
+++ pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go        Sat Mar 24 14:32:49 2018
@@ -0,0 +1,143 @@
+package main
+
+import (
+       "gopkg.in/check.v1"
+       "netbsd.org/pkglint/trace"
+)
+
+// Ensures that pkglint can handle MASTER_SITES definitions with and
+// without line continuations.
+//
+// See https://mail-index.netbsd.org/tech-pkg/2017/01/18/msg017698.html.
+func (s *Suite) Test_Pkgsrc_loadMasterSites(c *check.C) {
+       t := s.Init(c)
+
+       t.CreateFileLines("mk/fetch/sites.mk",
+               MkRcsID,
+               "",
+               "MASTER_SITE_A+= https://example.org/distfiles/";,
+               "MASTER_SITE_B+= https://b.example.org/distfiles/ \\",
+               "  https://b2.example.org/distfiles/";,
+               "MASTER_SITE_A+= https://a.example.org/distfiles/";)
+
+       G.Pkgsrc.loadMasterSites()
+
+       c.Check(G.Pkgsrc.MasterSiteURLToVar["https://example.org/distfiles/";], equals, "MASTER_SITE_A")
+       c.Check(G.Pkgsrc.MasterSiteURLToVar["https://b.example.org/distfiles/";], equals, "MASTER_SITE_B")
+       c.Check(G.Pkgsrc.MasterSiteURLToVar["https://b2.example.org/distfiles/";], equals, "MASTER_SITE_B")
+       c.Check(G.Pkgsrc.MasterSiteURLToVar["https://a.example.org/distfiles/";], equals, "MASTER_SITE_A")
+       c.Check(G.Pkgsrc.MasterSiteVarToURL["MASTER_SITE_A"], equals, "https://example.org/distfiles/";)
+       c.Check(G.Pkgsrc.MasterSiteVarToURL["MASTER_SITE_B"], equals, "https://b.example.org/distfiles/";)
+}
+
+func (s *Suite) Test_Pkgsrc_InitVartypes(c *check.C) {
+       t := s.Init(c)
+
+       src := NewPkgsrc(t.TmpDir())
+       src.InitVartypes()
+
+       c.Check(src.vartypes["BSD_MAKE_ENV"].basicType.name, equals, "ShellWord")
+       c.Check(src.vartypes["USE_BUILTIN.*"].basicType.name, equals, "YesNoIndirectly")
+}
+
+func (s *Suite) Test_Pkgsrc_parseSuggestedUpdates(c *check.C) {
+       t := s.Init(c)
+
+       lines := t.NewLines("doc/TODO",
+               "",
+               "Suggested package updates",
+               "==============",
+               "For Perl updates \u2026",
+               "",
+               "\t"+"o CSP-0.34",
+               "\t"+"o freeciv-client-2.5.0 (urgent)",
+               "",
+               "\t"+"o ignored-0.0")
+
+       todo := G.Pkgsrc.parseSuggestedUpdates(lines)
+
+       c.Check(todo, check.DeepEquals, []SuggestedUpdate{
+               {lines[5], "CSP", "0.34", ""},
+               {lines[6], "freeciv-client", "2.5.0", "(urgent)"}})
+}
+
+func (s *Suite) Test_GlobalData_loadTools(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupFileLines("mk/tools/bsd.tools.mk",
+               ".include \"flex.mk\"",
+               ".include \"gettext.mk\"")
+       t.SetupFileLines("mk/tools/defaults.mk",
+               "_TOOLS_VARNAME.chown=CHOWN",
+               "_TOOLS_VARNAME.gawk=AWK",
+               "_TOOLS_VARNAME.mv=MV",
+               "_TOOLS_VARNAME.pwd=PWD")
+       t.SetupFileLines("mk/tools/flex.mk",
+               "# empty")
+       t.SetupFileLines("mk/tools/gettext.mk",
+               "USE_TOOLS+=msgfmt",
+               "TOOLS_CREATE+=msgfmt")
+       t.SetupFileLines("mk/bsd.prefs.mk",
+               "USE_TOOLS+=\tpwd")
+       t.SetupFileLines("mk/bsd.pkg.mk",
+               "USE_TOOLS+=\tmv")
+       G.CurrentDir = t.TmpDir()
+       G.CurPkgsrcdir = "."
+
+       G.Pkgsrc.loadTools()
+
+       trace.Tracing = true
+       G.Pkgsrc.Tools.Trace()
+
+       t.CheckOutputLines(
+               "TRACE: + (*ToolRegistry).Trace()",
+               "TRACE: 1   tool &{Name:TOOLS_mv Varname: MustUseVarForm:false Predefined:true UsableAtLoadtime:false}",
+               "TRACE: 1   tool &{Name:TOOLS_pwd Varname: MustUseVarForm:false Predefined:true UsableAtLoadtime:true}",
+               "TRACE: 1   tool &{Name:chown Varname:CHOWN MustUseVarForm:false Predefined:false UsableAtLoadtime:false}",
+               "TRACE: 1   tool &{Name:echo Varname:ECHO MustUseVarForm:true Predefined:true UsableAtLoadtime:true}",
+               "TRACE: 1   tool &{Name:echo -n Varname:ECHO_N MustUseVarForm:true Predefined:true UsableAtLoadtime:true}",
+               "TRACE: 1   tool &{Name:false Varname:FALSE MustUseVarForm:true Predefined:true UsableAtLoadtime:false}",
+               "TRACE: 1   tool &{Name:gawk Varname:AWK MustUseVarForm:false Predefined:false UsableAtLoadtime:false}",
+               "TRACE: 1   tool &{Name:msgfmt Varname: MustUseVarForm:false Predefined:false UsableAtLoadtime:false}",
+               "TRACE: 1   tool &{Name:mv Varname:MV MustUseVarForm:false Predefined:true UsableAtLoadtime:false}",
+               "TRACE: 1   tool &{Name:pwd Varname:PWD MustUseVarForm:false Predefined:true UsableAtLoadtime:true}",
+               "TRACE: 1   tool &{Name:test Varname:TEST MustUseVarForm:true Predefined:true UsableAtLoadtime:true}",
+               "TRACE: 1   tool &{Name:true Varname:TRUE MustUseVarForm:true Predefined:true UsableAtLoadtime:true}",
+               "TRACE: - (*ToolRegistry).Trace()")
+}
+
+func (s *Suite) Test_GlobalData_loadDocChangesFromFile(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupFileLines("doc/CHANGES-2015",
+               "\tAdded category/package version 1.0 [author1 2015-01-01]",
+               "\tUpdated category/package to 1.5 [author2 2015-01-02]",
+               "\tRenamed category/package to category/pkg [author3 2015-01-03]",
+               "\tMoved category/package to other/package [author4 2015-01-04]",
+               "\tRemoved category/package [author5 2015-01-05]",
+               "\tRemoved category/package successor category/package2 [author6 2015-01-06]",
+               "\tDowngraded category/package to 1.2 [author7 2015-01-07]")
+
+       changes := G.Pkgsrc.loadDocChangesFromFile(t.TmpDir() + "/doc/CHANGES-2015")
+
+       c.Assert(len(changes), equals, 7)
+       c.Check(*changes[0], equals, Change{changes[0].Line, "Added", "category/package", "1.0", "author1", "2015-01-01"})
+       c.Check(*changes[1], equals, Change{changes[1].Line, "Updated", "category/package", "1.5", "author2", "2015-01-02"})
+       c.Check(*changes[2], equals, Change{changes[2].Line, "Renamed", "category/package", "", "author3", "2015-01-03"})
+       c.Check(*changes[3], equals, Change{changes[3].Line, "Moved", "category/package", "", "author4", "2015-01-04"})
+       c.Check(*changes[4], equals, Change{changes[4].Line, "Removed", "category/package", "", "author5", "2015-01-05"})
+       c.Check(*changes[5], equals, Change{changes[5].Line, "Removed", "category/package", "", "author6", "2015-01-06"})
+       c.Check(*changes[6], equals, Change{changes[6].Line, "Downgraded", "category/package", "1.2", "author7", "2015-01-07"})
+}
+
+func (s *Suite) Test_GlobalData_deprecated(c *check.C) {
+       t := s.Init(c)
+
+       G.Pkgsrc.initDeprecatedVars()
+       mkline := t.NewMkLine("Makefile", 5, "USE_PERL5=\tyes")
+
+       MkLineChecker{mkline}.checkVarassign()
+
+       t.CheckOutputLines(
+               "WARN: Makefile:5: Definition of USE_PERL5 is deprecated. Use USE_TOOLS+=perl or USE_TOOLS+=perl:run instead.")
+}
Index: pkgsrc/pkgtools/pkglint/files/tools.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/tools.go:1.1
--- /dev/null   Sat Mar 24 14:32:50 2018
+++ pkgsrc/pkgtools/pkglint/files/tools.go      Sat Mar 24 14:32:49 2018
@@ -0,0 +1,116 @@
+package main
+
+import (
+       "netbsd.org/pkglint/trace"
+       "sort"
+)
+
+// See `mk/tools/`.
+type Tool struct {
+       Name             string // e.g. "sed", "gzip"
+       Varname          string // e.g. "SED", "GZIP_CMD"
+       MustUseVarForm   bool   // True for `echo`, because of many differing implementations.
+       Predefined       bool   // This tool is used by the pkgsrc infrastructure, therefore the package does not need to add it to `USE_TOOLS` explicitly.
+       UsableAtLoadtime bool   // May be used after including `bsd.prefs.mk`.
+}
+
+type ToolRegistry struct {
+       byName    map[string]*Tool
+       byVarname map[string]*Tool
+}
+
+func NewToolRegistry() ToolRegistry {
+       return ToolRegistry{make(map[string]*Tool), make(map[string]*Tool)}
+}
+
+func (tr *ToolRegistry) Register(toolname string) *Tool {
+       tool := tr.byName[toolname]
+       if tool == nil {
+               tool = &Tool{Name: toolname}
+               tr.byName[toolname] = tool
+       }
+       return tool
+}
+
+func (tr *ToolRegistry) RegisterVarname(toolname, varname string) *Tool {
+       tool := tr.Register(toolname)
+       tool.Varname = varname
+       tr.byVarname[varname] = tool
+       return tool
+}
+
+func (tr *ToolRegistry) RegisterTool(tool *Tool) {
+       if tool.Name != "" && tr.byName[tool.Name] == nil {
+               tr.byName[tool.Name] = tool
+       }
+       if tool.Varname != "" && tr.byVarname[tool.Varname] == nil {
+               tr.byVarname[tool.Varname] = tool
+       }
+}
+
+func (tr *ToolRegistry) FindByCommand(cmd *ShToken) *Tool {
+       if tool := tr.byName[cmd.MkText]; tool != nil {
+               return tool
+       }
+       if len(cmd.Atoms) == 1 {
+               if varuse := cmd.Atoms[0].VarUse(); varuse != nil {
+                       if tool := tr.byVarname[varuse.varname]; tool != nil {
+                               return tool
+                       }
+               }
+       }
+       return nil
+}
+
+func (tr *ToolRegistry) Trace() {
+       if trace.Tracing {
+               defer trace.Call0()()
+       }
+
+       var keys []string
+       for k := range tr.byName {
+               keys = append(keys, k)
+       }
+       sort.Strings(keys)
+
+       for _, toolname := range keys {
+               trace.Stepf("tool %+v", tr.byName[toolname])
+       }
+}
+
+func (tr *ToolRegistry) ParseToolLine(line Line) {
+       if m, commented, varname, _, _, _, value, _, _ := MatchVarassign(line.Text); m {
+               if commented {
+                       return
+               }
+               if varname == "TOOLS_CREATE" && (value == "[" || matches(value, `^?[-\w.]+$`)) {
+                       tr.Register(value)
+
+               } else if m, toolname := match1(varname, `^_TOOLS_VARNAME\.([-\w.]+|\[)$`); m {
+                       tr.RegisterVarname(toolname, value)
+
+               } else if m, toolname := match1(varname, `^(?:TOOLS_PATH|_TOOLS_DEPMETHOD)\.([-\w.]+|\[)$`); m {
+                       tr.Register(toolname)
+
+               } else if m, toolname := match1(varname, `_TOOLS\.(.*)`); m {
+                       tr.Register(toolname)
+                       for _, tool := range splitOnSpace(value) {
+                               tr.Register(tool)
+                       }
+               }
+       }
+}
+
+func (tr *ToolRegistry) ByVarname(varname string) *Tool {
+       return tr.byVarname[varname]
+}
+
+func (tr *ToolRegistry) ByName(name string) *Tool {
+       return tr.byName[name]
+}
+
+func (tr *ToolRegistry) ForEach(action func(tool *Tool)) {
+       for _, tool := range tr.byName {
+               action(tool)
+       }
+}



Home | Main Index | Thread Index | Old Index