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:           Sun Mar 24 13:58:38 UTC 2019

Modified Files:
        pkgsrc/pkgtools/pkglint: Makefile
        pkgsrc/pkgtools/pkglint/files: buildlink3_test.go check_test.go
            distinfo.go distinfo_test.go mkline.go mkline_test.go
            mklinechecker.go mklinechecker_test.go mklines.go mklines_test.go
            mklines_varalign_test.go mkparser_test.go mkshparser_test.go
            mktypes.go mktypes_test.go options.go package.go package_test.go
            pkglint.go pkglint_test.go pkgsrc.go pkgsrc_test.go plist.go
            plist_test.go redundantscope.go redundantscope_test.go shell.go
            shell_test.go tools.go tools_test.go util.go util_test.go
            vardefs.go vardefs_test.go vartype.go vartype_test.go
            vartypecheck.go vartypecheck_test.go
        pkgsrc/pkgtools/pkglint/files/cmd/pkglint: main_test.go

Log Message:
pkgtools/pkglint: update to 5.7.3

Changes since 5.7.2:

PLIST files are checked for non-ASCII characters. Even though pkgsrc
sets up the environment with LC_ALL=C, there are still some cases
of encoding errors. The case discussed on the tech-pkg mailing list
was lang/go112.

The checks for variable permissions ("may not be set in this file")
have been reworked completely. Many of the variable permissions had
different rules for Makefile and Makefile.common. These different
rules tried to prevent accidental overwriting of variables. Starting
in July 2018, pkglint got a check for redundant variables that is
far more accurate than the previous variable permissions. Therefore
these fine-grained permissions are no longer necessary. This removes
a few hundred wrong warnings about insufficient permissions.

The check that adds missing SHA512 hashes to distinfo files has been
fixed to work correctly in DIST_SUBDIR cases.

Improved the checks regarding tools that are used by a package but
not added to USE_TOOLS. For example, the "make" tool is always
available, as are all tools that are added to TOOLS_CREATE.

Lots of small improvements, as always.


To generate a diff of this commit:
cvs rdiff -u -r1.571 -r1.572 pkgsrc/pkgtools/pkglint/Makefile
cvs rdiff -u -r1.28 -r1.29 pkgsrc/pkgtools/pkglint/files/buildlink3_test.go \
    pkgsrc/pkgtools/pkglint/files/vartype.go
cvs rdiff -u -r1.35 -r1.36 pkgsrc/pkgtools/pkglint/files/check_test.go \
    pkgsrc/pkgtools/pkglint/files/shell.go
cvs rdiff -u -r1.29 -r1.30 pkgsrc/pkgtools/pkglint/files/distinfo.go
cvs rdiff -u -r1.26 -r1.27 pkgsrc/pkgtools/pkglint/files/distinfo_test.go
cvs rdiff -u -r1.48 -r1.49 pkgsrc/pkgtools/pkglint/files/mkline.go \
    pkgsrc/pkgtools/pkglint/files/pkglint.go
cvs rdiff -u -r1.53 -r1.54 pkgsrc/pkgtools/pkglint/files/mkline_test.go
cvs rdiff -u -r1.31 -r1.32 pkgsrc/pkgtools/pkglint/files/mklinechecker.go
cvs rdiff -u -r1.27 -r1.28 \
    pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go
cvs rdiff -u -r1.43 -r1.44 pkgsrc/pkgtools/pkglint/files/mklines.go \
    pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
cvs rdiff -u -r1.38 -r1.39 pkgsrc/pkgtools/pkglint/files/mklines_test.go \
    pkgsrc/pkgtools/pkglint/files/plist.go
cvs rdiff -u -r1.8 -r1.9 \
    pkgsrc/pkgtools/pkglint/files/mklines_varalign_test.go
cvs rdiff -u -r1.24 -r1.25 pkgsrc/pkgtools/pkglint/files/mkparser_test.go \
    pkgsrc/pkgtools/pkglint/files/util_test.go
cvs rdiff -u -r1.11 -r1.12 pkgsrc/pkgtools/pkglint/files/mkshparser_test.go \
    pkgsrc/pkgtools/pkglint/files/mktypes.go \
    pkgsrc/pkgtools/pkglint/files/tools.go
cvs rdiff -u -r1.7 -r1.8 pkgsrc/pkgtools/pkglint/files/mktypes_test.go
cvs rdiff -u -r1.12 -r1.13 pkgsrc/pkgtools/pkglint/files/options.go \
    pkgsrc/pkgtools/pkglint/files/tools_test.go
cvs rdiff -u -r1.47 -r1.48 pkgsrc/pkgtools/pkglint/files/package.go
cvs rdiff -u -r1.40 -r1.41 pkgsrc/pkgtools/pkglint/files/package_test.go
cvs rdiff -u -r1.34 -r1.35 pkgsrc/pkgtools/pkglint/files/pkglint_test.go \
    pkgsrc/pkgtools/pkglint/files/plist_test.go
cvs rdiff -u -r1.20 -r1.21 pkgsrc/pkgtools/pkglint/files/pkgsrc.go
cvs rdiff -u -r1.18 -r1.19 pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go
cvs rdiff -u -r1.1 -r1.2 pkgsrc/pkgtools/pkglint/files/redundantscope.go \
    pkgsrc/pkgtools/pkglint/files/redundantscope_test.go
cvs rdiff -u -r1.41 -r1.42 pkgsrc/pkgtools/pkglint/files/shell_test.go
cvs rdiff -u -r1.39 -r1.40 pkgsrc/pkgtools/pkglint/files/util.go
cvs rdiff -u -r1.56 -r1.57 pkgsrc/pkgtools/pkglint/files/vardefs.go
cvs rdiff -u -r1.10 -r1.11 pkgsrc/pkgtools/pkglint/files/vardefs_test.go
cvs rdiff -u -r1.15 -r1.16 pkgsrc/pkgtools/pkglint/files/vartype_test.go
cvs rdiff -u -r1.51 -r1.52 pkgsrc/pkgtools/pkglint/files/vartypecheck.go
cvs rdiff -u -r1.1 -r1.2 \
    pkgsrc/pkgtools/pkglint/files/cmd/pkglint/main_test.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.571 pkgsrc/pkgtools/pkglint/Makefile:1.572
--- pkgsrc/pkgtools/pkglint/Makefile:1.571      Sat Mar 16 08:35:48 2019
+++ pkgsrc/pkgtools/pkglint/Makefile    Sun Mar 24 13:58:38 2019
@@ -1,7 +1,6 @@
-# $NetBSD: Makefile,v 1.571 2019/03/16 08:35:48 bsiegert Exp $
+# $NetBSD: Makefile,v 1.572 2019/03/24 13:58:38 rillig Exp $
 
-PKGNAME=       pkglint-5.7.2
-PKGREVISION=   1
+PKGNAME=       pkglint-5.7.3
 CATEGORIES=    pkgtools
 DISTNAME=      tools
 MASTER_SITES=  ${MASTER_SITE_GITHUB:=golang/}

Index: pkgsrc/pkgtools/pkglint/files/buildlink3_test.go
diff -u pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.28 pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.29
--- pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.28       Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/buildlink3_test.go    Sun Mar 24 13:58:38 2019
@@ -130,6 +130,38 @@ func (s *Suite) Test_CheckLinesBuildlink
        t.CheckOutputEmpty()
 }
 
+func (s *Suite) Test_CheckLinesBuildlink3Mk__name_mismatch__Perl(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPackage("x11/p5-gtk2",
+               "DISTNAME=\tGtk2-1.0",
+               "PKGNAME=\t${DISTNAME:C:Gtk2:p5-gtk2:}")
+       t.CreateFileLines("x11/p5-gtk2/buildlink3.mk",
+               MkRcsID,
+               "",
+               "BUILDLINK_TREE+=\tp5-gtk2",
+               "",
+               ".if !defined(P5_GTK2_BUILDLINK3_MK)",
+               "P5_GTK2_BUILDLINK3_MK:=",
+               "",
+               "BUILDLINK_API_DEPENDS.p5-gtk2+=\tp5-gtk2>=1.0",
+               "BUILDLINK_ABI_DEPENDS.p5-gtk2+=\tp5-gtk2>=1.0",
+               "",
+               ".endif\t# P5_GTK2_BUILDLINK3_MK",
+               "",
+               "BUILDLINK_TREE+=\t-p5-gtk2")
+
+       G.Check(t.File("x11/p5-gtk2"))
+
+       // Up to 2019-03-17, pkglint wrongly complained about a mismatch
+       // between the package name from buildlink3.mk (p5-gtk2) and the
+       // one from the package Makefile (Gtk2).
+       //
+       // Pkglint had taken this information from the DISTNAME variable,
+       // ignoring the fact that PKGNAME was also defined.
+       t.CheckOutputEmpty()
+}
+
 func (s *Suite) Test_CheckLinesBuildlink3Mk__name_mismatch_multiple_inclusion(c *check.C) {
        t := s.Init(c)
 
@@ -472,16 +504,14 @@ func (s *Suite) Test_CheckLinesBuildlink
        CheckLinesBuildlink3Mk(mklines)
 
        t.CheckOutputLines(
-               "WARN: buildlink3.mk:3: LICENSE may not be used in any file; it is a write-only variable.",
+               "WARN: buildlink3.mk:3: LICENSE should not be used in this file; "+
+                       "it would be ok in Makefile, Makefile.* or *.mk, but not buildlink3.mk or builtin.mk.",
                "WARN: buildlink3.mk:3: The variable LICENSE should be quoted as part of a shell word.",
-               "WARN: buildlink3.mk:8: LICENSE should not be evaluated at load time.",
-               "WARN: buildlink3.mk:8: LICENSE should not be evaluated indirectly at load time.",
                "WARN: buildlink3.mk:8: The variable LICENSE should be quoted as part of a shell word.",
-               "WARN: buildlink3.mk:9: LICENSE should not be evaluated at load time.",
-               "WARN: buildlink3.mk:9: LICENSE should not be evaluated indirectly at load time.",
                "WARN: buildlink3.mk:9: The variable LICENSE should be quoted as part of a shell word.",
                "WARN: buildlink3.mk:13: The variable LICENSE should be quoted as part of a shell word.",
-               "WARN: buildlink3.mk:3: Please replace \"${LICENSE}\" with a simple string (also in other variables in this file).")
+               "WARN: buildlink3.mk:3: Please replace \"${LICENSE}\" with a simple string "+
+                       "(also in other variables in this file).")
 }
 
 func (s *Suite) Test_Buildlink3Checker_checkMainPart__if_else_endif(c *check.C) {
Index: pkgsrc/pkgtools/pkglint/files/vartype.go
diff -u pkgsrc/pkgtools/pkglint/files/vartype.go:1.28 pkgsrc/pkgtools/pkglint/files/vartype.go:1.29
--- pkgsrc/pkgtools/pkglint/files/vartype.go:1.28       Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/vartype.go    Sun Mar 24 13:58:38 2019
@@ -39,16 +39,16 @@ const (
        aclpUseLoadtime                            // OTHER := ${VAR}, OTHER != ${VAR}
        aclpUse                                    // OTHER = ${VAR}
 
-       // TODO: Try what happens if this constant is removed.
-       //  All variables should have proper permission definitions for all files.
-       //  Missing permission definitions could also count as "none".
-       aclpUnknown
+       aclpNone ACLPermissions = 0
+
        aclpAllWrite   = aclpSet | aclpSetDefault | aclpAppend
        aclpAllRead    = aclpUseLoadtime | aclpUse
        aclpAll        = aclpAllWrite | aclpAllRead
        aclpAllRuntime = aclpAll &^ aclpUseLoadtime
 )
 
+// Contains returns whether each permission of the given subset is
+// contained in this permission set.
 func (perms ACLPermissions) Contains(subset ACLPermissions) bool {
        return perms&subset == subset
 }
@@ -62,8 +62,7 @@ func (perms ACLPermissions) String() str
                ifelseStr(perms.Contains(aclpSetDefault), "set-default", ""),
                ifelseStr(perms.Contains(aclpAppend), "append", ""),
                ifelseStr(perms.Contains(aclpUseLoadtime), "use-loadtime", ""),
-               ifelseStr(perms.Contains(aclpUse), "use", ""),
-               ifelseStr(perms.Contains(aclpUnknown), "unknown", ""))
+               ifelseStr(perms.Contains(aclpUse), "use", ""))
 }
 
 func (perms ACLPermissions) HumanString() string {
@@ -81,7 +80,7 @@ func (vt *Vartype) EffectivePermissions(
                        return aclEntry.permissions
                }
        }
-       return aclpUnknown
+       return aclpNone
 }
 
 // Union returns the union of all possible permissions.
@@ -95,15 +94,63 @@ func (vt *Vartype) Union() ACLPermission
        return permissions
 }
 
-// AllowedFiles lists the file patterns in which the given permissions are allowed.
-func (vt *Vartype) AllowedFiles(perms ACLPermissions) string {
-       files := make([]string, 0, len(vt.aclEntries))
+// AlternativeFiles lists the file patterns in which all of the given
+// permissions are allowed, readily formatted to be used in a diagnostic.
+//
+// If the permission is allowed nowhere, an empty string is returned.
+func (vt *Vartype) AlternativeFiles(perms ACLPermissions) string {
+       pos := make([]string, 0, len(vt.aclEntries))
+       neg := make([]string, 0, len(vt.aclEntries))
+
+       merge := func(slice []string) []string {
+               di := 0
+               for si, early := range slice {
+                       redundant := false
+                       for _, late := range slice[si+1:] {
+                               matched, err := path.Match(late, early)
+                               if err == nil && matched {
+                                       redundant = true
+                                       break
+                               }
+                       }
+                       if !redundant {
+                               slice[di] = early
+                               di++
+                       }
+               }
+               return slice[:di]
+       }
+
        for _, aclEntry := range vt.aclEntries {
                if aclEntry.permissions.Contains(perms) {
-                       files = append(files, aclEntry.glob)
+                       pos = append(pos, aclEntry.glob)
+               } else {
+                       neg = append(neg, aclEntry.glob)
                }
        }
-       return joinSkipEmptyCambridge("or", files...)
+
+       if len(neg) == 0 {
+               pos = merge(pos)
+       }
+       if len(pos) == 0 {
+               neg = merge(neg)
+       }
+
+       positive := joinSkipEmptyCambridge("or", pos...)
+       if positive == "" {
+               return ""
+       }
+
+       negative := joinSkipEmptyCambridge("or", neg...)
+       if negative == "" {
+               return positive
+       }
+
+       if negative == "*" {
+               return positive + " only"
+       }
+
+       return positive + ", but not " + negative
 }
 
 // IsConsideredList returns whether the type is considered a list.
@@ -193,11 +240,7 @@ func (bt *BasicType) NeedsQ() bool {
 }
 
 func (vt *Vartype) IsPlainString() bool {
-       switch vt.basicType {
-       case BtComment, BtMessage, BtUnknown:
-               return true
-       }
-       return false
+       return vt.basicType == BtComment || vt.basicType == BtMessage
 }
 
 type BasicType struct {
@@ -281,6 +324,8 @@ var (
        BtYes                    = &BasicType{"Yes", (*VartypeCheck).Yes}
        BtYesNo                  = &BasicType{"YesNo", (*VartypeCheck).YesNo}
        BtYesNoIndirectly        = &BasicType{"YesNoIndirectly", (*VartypeCheck).YesNoIndirectly}
+
+       btForLoop = &BasicType{".for loop", nil /* never called */}
 )
 
 // Necessary due to circular dependencies between the checkers.

Index: pkgsrc/pkgtools/pkglint/files/check_test.go
diff -u pkgsrc/pkgtools/pkglint/files/check_test.go:1.35 pkgsrc/pkgtools/pkglint/files/check_test.go:1.36
--- pkgsrc/pkgtools/pkglint/files/check_test.go:1.35    Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/check_test.go Sun Mar 24 13:58:38 2019
@@ -162,7 +162,7 @@ func (t *Tester) SetUpCommandLine(args .
 //
 // See SetUpTool for registering tools like echo, awk, perl.
 func (t *Tester) SetUpVartypes() {
-       G.Pkgsrc.InitVartypes()
+       G.Pkgsrc.vartypes.Init(G.Pkgsrc)
 }
 
 func (t *Tester) SetUpMasterSite(varname string, urls ...string) {
@@ -505,11 +505,13 @@ func (t *Tester) Chdir(relativeDirName s
                t.c.Fatalf("Chdir must only be called once per test; already in %q.", t.relCwd)
        }
 
-       _ = os.MkdirAll(t.File(relativeDirName), 0700)
-       if err := os.Chdir(t.File(relativeDirName)); err != nil {
+       absDirName := t.File(relativeDirName)
+       _ = os.MkdirAll(absDirName, 0700)
+       if err := os.Chdir(absDirName); err != nil {
                t.c.Fatalf("Cannot chdir: %s", err)
        }
        t.relCwd = relativeDirName
+       G.cwd = absDirName
 }
 
 // Remove removes the file from the temporary directory. The file must exist.
@@ -689,8 +691,8 @@ func (t *Tester) NewMkLine(filename stri
        return NewMkLine(t.NewLine(filename, lineno, text))
 }
 
-func (t *Tester) NewShellLine(filename string, lineno int, text string) *ShellLine {
-       return NewShellLine(t.NewMkLine(filename, lineno, text))
+func (t *Tester) NewShellLineChecker(filename string, lineno int, text string) *ShellLineChecker {
+       return NewShellLineChecker(t.NewMkLine(filename, lineno, text))
 }
 
 // NewLines returns a list of simple lines that belong together.
@@ -759,11 +761,7 @@ func (t *Tester) Output() string {
 //
 // See CheckOutputLines.
 func (t *Tester) CheckOutputEmpty() {
-       output := t.Output()
-
-       actualLines := strings.Split(output, "\n")
-       actualLines = actualLines[:len(actualLines)-1]
-       t.Check(emptyToNil(actualLines), deepEquals, emptyToNil(nil))
+       t.CheckOutput(nil)
 }
 
 // CheckOutputLines checks that the output up to now equals the given lines.
@@ -773,7 +771,19 @@ func (t *Tester) CheckOutputEmpty() {
 // See CheckOutputEmpty.
 func (t *Tester) CheckOutputLines(expectedLines ...string) {
        G.Assertf(len(expectedLines) > 0, "To check empty lines, use CheckLinesEmpty instead.")
+       t.CheckOutput(expectedLines)
+}
 
+// CheckOutput checks that the output up to now equals the given lines.
+// After the comparison, the output buffers are cleared so that later
+// calls only check against the newly added output.
+//
+// The expectedLines can be either empty or non-empty.
+//
+// When the output is always empty, use CheckOutputEmpty instead.
+// When the output always contain some lines, use CheckOutputLines instead.
+// This variant should only be used when the expectedLines are generated dynamically.
+func (t *Tester) CheckOutput(expectedLines []string) {
        output := t.Output()
        actualLines := strings.Split(output, "\n")
        actualLines = actualLines[:len(actualLines)-1]
Index: pkgsrc/pkgtools/pkglint/files/shell.go
diff -u pkgsrc/pkgtools/pkglint/files/shell.go:1.35 pkgsrc/pkgtools/pkglint/files/shell.go:1.36
--- pkgsrc/pkgtools/pkglint/files/shell.go:1.35 Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/shell.go      Sun Mar 24 13:58:38 2019
@@ -10,23 +10,23 @@ import (
 
 // TODO: Can ShellLine and ShellProgramChecker be merged into one type?
 
-// ShellLine is either a line from a Makefile starting with a tab,
+// ShellLineChecker is either a line from a Makefile starting with a tab,
 // thereby containing shell commands to be executed.
 //
 // Or it is a variable assignment line from a Makefile with a left-hand
 // side variable that is of some shell-like type; see Vartype.IsShell.
-type ShellLine struct {
+type ShellLineChecker struct {
        mkline MkLine
 }
 
-func NewShellLine(mkline MkLine) *ShellLine {
-       return &ShellLine{mkline}
+func NewShellLineChecker(mkline MkLine) *ShellLineChecker {
+       return &ShellLineChecker{mkline}
 }
 
 var shellCommandsType = &Vartype{lkNone, BtShellCommands, []ACLEntry{{"*", aclpAllRuntime}}, false}
 var shellWordVuc = &VarUseContext{shellCommandsType, vucTimeUnknown, VucQuotPlain, false}
 
-func (shline *ShellLine) CheckWord(token string, checkQuoting bool, time ToolTime) {
+func (ck *ShellLineChecker) CheckWord(token string, checkQuoting bool, time ToolTime) {
        if trace.Tracing {
                defer trace.Call(token, checkQuoting)()
        }
@@ -35,13 +35,13 @@ func (shline *ShellLine) CheckWord(token
                return
        }
 
-       var line = shline.mkline.Line
+       var line = ck.mkline.Line
 
        // Delegate check for shell words consisting of a single variable use
        // to the MkLineChecker. Examples for these are ${VAR:Mpattern} or $@.
        p := NewMkParser(nil, token, false)
        if varuse := p.VarUse(); varuse != nil && p.EOF() {
-               MkLineChecker{shline.mkline}.CheckVaruse(varuse, shellWordVuc)
+               MkLineChecker{ck.mkline}.CheckVaruse(varuse, shellWordVuc)
                return
        }
 
@@ -53,11 +53,11 @@ func (shline *ShellLine) CheckWord(token
                line.Warnf("Please use the RCD_SCRIPTS mechanism to install rc.d scripts automatically to ${RCD_SCRIPTS_EXAMPLEDIR}.")
        }
 
-       shline.checkWordQuoting(token, checkQuoting, time)
+       ck.checkWordQuoting(token, checkQuoting, time)
 }
 
-func (shline *ShellLine) checkWordQuoting(token string, checkQuoting bool, time ToolTime) {
-       line := shline.mkline.Line
+func (ck *ShellLineChecker) checkWordQuoting(token string, checkQuoting bool, time ToolTime) {
+       line := ck.mkline.Line
        tok := NewShTokenizer(line, token, false)
 
        atoms := tok.ShAtoms()
@@ -74,22 +74,22 @@ outer:
 
                switch {
                case atom.Quoting == shqBackt || atom.Quoting == shqDquotBackt:
-                       backtCommand := shline.unescapeBackticks(&atoms, quoting)
+                       backtCommand := ck.unescapeBackticks(&atoms, quoting)
                        if backtCommand != "" {
                                // TODO: Wrap the setE into a struct.
                                setE := true
-                               shline.CheckShellCommand(backtCommand, &setE, time)
+                               ck.CheckShellCommand(backtCommand, &setE, time)
                        }
                        continue
 
                        // Make(1) variables have the same syntax, no matter in which state the shell parser is currently.
-               case shline.checkVaruseToken(&atoms, quoting):
+               case ck.checkVaruseToken(&atoms, quoting):
                        continue
 
                case quoting == shqPlain:
                        switch {
                        case atom.Type == shtShVarUse:
-                               shline.checkShVarUsePlain(atom, checkQuoting)
+                               ck.checkShVarUsePlain(atom, checkQuoting)
 
                        case atom.Type == shtSubshell:
                                line.Warnf("Invoking subshells via $(...) is not portable enough.")
@@ -120,14 +120,14 @@ outer:
        }
 }
 
-func (shline *ShellLine) checkShVarUsePlain(atom *ShAtom, checkQuoting bool) {
-       line := shline.mkline.Line
+func (ck *ShellLineChecker) checkShVarUsePlain(atom *ShAtom, checkQuoting bool) {
+       line := ck.mkline.Line
        shVarname := atom.ShVarname()
 
        if shVarname == "@" {
                line.Warnf("The $@ shell variable should only be used in double quotes.")
 
-       } else if G.Opts.WarnQuoting && checkQuoting && shline.variableNeedsQuoting(shVarname) {
+       } else if G.Opts.WarnQuoting && checkQuoting && ck.variableNeedsQuoting(shVarname) {
                line.Warnf("Unquoted shell variable %q.", shVarname)
                G.Explain(
                        "When a shell variable contains whitespace, it is expanded (split into multiple words)",
@@ -151,7 +151,7 @@ func (shline *ShellLine) checkShVarUsePl
        }
 }
 
-func (shline *ShellLine) checkVaruseToken(atoms *[]*ShAtom, quoting ShQuoting) bool {
+func (ck *ShellLineChecker) checkVaruseToken(atoms *[]*ShAtom, quoting ShQuoting) bool {
        varuse := (*atoms)[0].VarUse()
        if varuse == nil {
                return false
@@ -161,7 +161,7 @@ func (shline *ShellLine) checkVaruseToke
        varname := varuse.varname
 
        if varname == "@" {
-               shline.mkline.Warnf("Please use \"${.TARGET}\" instead of \"$@\".")
+               ck.mkline.Warnf("Please use \"${.TARGET}\" instead of \"$@\".")
                G.Explain(
                        "The variable $@ can easily be confused with the shell variable of",
                        "the same name, which has a completely different meaning.")
@@ -177,14 +177,14 @@ func (shline *ShellLine) checkVaruseToke
                // This is ok as long as these variables don't have embedded [$\\"'`].
 
        case quoting == shqDquot && varuse.IsQ():
-               shline.mkline.Warnf("The :Q modifier should not be used inside double quotes.")
+               ck.mkline.Warnf("The :Q modifier should not be used inside double quotes.")
                G.Explain(
                        "To fix this warning, either remove the :Q or the double quotes.",
                        "In most cases, it is more appropriate to remove the double quotes.")
        }
 
        vuc := VarUseContext{shellCommandsType, vucTimeUnknown, quoting.ToVarUseContext(), true}
-       MkLineChecker{shline.mkline}.CheckVaruse(varuse, &vuc)
+       MkLineChecker{ck.mkline}.CheckVaruse(varuse, &vuc)
 
        return true
 }
@@ -197,8 +197,8 @@ func (shline *ShellLine) checkVaruseToke
 // all backslashes are unescaped.
 //
 // See http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_03
-func (shline *ShellLine) unescapeBackticks(atoms *[]*ShAtom, quoting ShQuoting) string {
-       line := shline.mkline.Line
+func (ck *ShellLineChecker) unescapeBackticks(atoms *[]*ShAtom, quoting ShQuoting) string {
+       line := ck.mkline.Line
 
        // Skip the initial backtick.
        *atoms = (*atoms)[1:]
@@ -250,7 +250,7 @@ func (shline *ShellLine) unescapeBacktic
        return unescaped.String()
 }
 
-func (shline *ShellLine) variableNeedsQuoting(shVarname string) bool {
+func (ck *ShellLineChecker) variableNeedsQuoting(shVarname string) bool {
        switch shVarname {
        case "#", "?", "$":
                return false // Definitely ok
@@ -263,12 +263,12 @@ func (shline *ShellLine) variableNeedsQu
        return true
 }
 
-func (shline *ShellLine) CheckShellCommandLine(shelltext string) {
+func (ck *ShellLineChecker) CheckShellCommandLine(shelltext string) {
        if trace.Tracing {
                defer trace.Call1(shelltext)()
        }
 
-       line := shline.mkline.Line
+       line := ck.mkline.Line
 
        // TODO: Add sed and mv in addition to ${SED} and ${MV}.
        // TODO: Now that a shell command parser is available, be more precise in the condition.
@@ -300,7 +300,7 @@ func (shline *ShellLine) CheckShellComma
        lexer.NextHspace()
        hiddenAndSuppress := lexer.NextBytesFunc(func(b byte) bool { return b == '-' || b == '@' })
        if hiddenAndSuppress != "" {
-               shline.checkHiddenAndSuppress(hiddenAndSuppress, lexer.Rest())
+               ck.checkHiddenAndSuppress(hiddenAndSuppress, lexer.Rest())
        }
        setE := lexer.SkipString("${RUN}")
        if !setE {
@@ -309,15 +309,15 @@ func (shline *ShellLine) CheckShellComma
                }
        }
 
-       shline.CheckShellCommand(lexer.Rest(), &setE, RunTime)
+       ck.CheckShellCommand(lexer.Rest(), &setE, RunTime)
 }
 
-func (shline *ShellLine) CheckShellCommand(shellcmd string, pSetE *bool, time ToolTime) {
+func (ck *ShellLineChecker) CheckShellCommand(shellcmd string, pSetE *bool, time ToolTime) {
        if trace.Tracing {
                defer trace.Call0()()
        }
 
-       line := shline.mkline.Line
+       line := ck.mkline.Line
        program, err := parseShellProgram(line, shellcmd)
        // FIXME: This code is duplicated in checkWordQuoting.
        if err != nil && contains(shellcmd, "$$(") { // Hack until the shell parser can handle subshells.
@@ -329,12 +329,12 @@ func (shline *ShellLine) CheckShellComma
                return
        }
 
-       spc := ShellProgramChecker{shline}
+       spc := ShellProgramChecker{ck}
        spc.checkConditionalCd(program)
 
        walker := NewMkShWalker()
        walker.Callback.SimpleCommand = func(command *MkShSimpleCommand) {
-               scc := NewSimpleCommandChecker(shline, command, time)
+               scc := NewSimpleCommandChecker(ck, command, time)
                scc.Check()
                // TODO: Implement getopt parsing for StrCommand.
                if scc.strcmd.Name == "set" && scc.strcmd.AnyArgMatches(`^-.*e`) {
@@ -352,21 +352,21 @@ func (shline *ShellLine) CheckShellComma
        walker.Callback.Word = func(word *ShToken) {
                // TODO: Try to replace false with true here; it had been set to false
                //  in 2016 for no apparent reason.
-               spc.shline.CheckWord(word.MkText, false, time)
+               spc.CheckWord(word.MkText, false, time)
        }
 
        walker.Walk(program)
 }
 
-func (shline *ShellLine) CheckShellCommands(shellcmds string, time ToolTime) {
+func (ck *ShellLineChecker) CheckShellCommands(shellcmds string, time ToolTime) {
        setE := true
-       shline.CheckShellCommand(shellcmds, &setE, time)
+       ck.CheckShellCommand(shellcmds, &setE, time)
        if !hasSuffix(shellcmds, ";") {
-               shline.mkline.Warnf("This shell command list should end with a semicolon.")
+               ck.mkline.Warnf("This shell command list should end with a semicolon.")
        }
 }
 
-func (shline *ShellLine) checkHiddenAndSuppress(hiddenAndSuppress, rest string) {
+func (ck *ShellLineChecker) checkHiddenAndSuppress(hiddenAndSuppress, rest string) {
        if trace.Tracing {
                defer trace.Call(hiddenAndSuppress, rest)()
        }
@@ -382,7 +382,7 @@ func (shline *ShellLine) checkHiddenAndS
                // Shell comments may be hidden, since they cannot have side effects.
 
        default:
-               tokens, _ := splitIntoShellTokens(shline.mkline.Line, rest)
+               tokens, _ := splitIntoShellTokens(ck.mkline.Line, rest)
                if len(tokens) > 0 {
                        cmd := tokens[0]
                        switch cmd {
@@ -395,7 +395,7 @@ func (shline *ShellLine) checkHiddenAndS
                                "${WARNING_CAT}", "${WARNING_MSG}":
                                break
                        default:
-                               shline.mkline.Warnf("The shell command %q should not be hidden.", cmd)
+                               ck.mkline.Warnf("The shell command %q should not be hidden.", cmd)
                                G.Explain(
                                        "Hidden shell commands do not appear on the terminal",
                                        "or in the log file when they are executed.",
@@ -409,7 +409,7 @@ func (shline *ShellLine) checkHiddenAndS
        }
 
        if contains(hiddenAndSuppress, "-") {
-               shline.mkline.Warnf("Using a leading \"-\" to suppress errors is deprecated.")
+               ck.mkline.Warnf("Using a leading \"-\" to suppress errors is deprecated.")
                G.Explain(
                        "If you really want to ignore any errors from this command, append \"|| ${TRUE}\" to the command.",
                        "This is more visible than a single hyphen, and it should be.")
@@ -417,15 +417,15 @@ func (shline *ShellLine) checkHiddenAndS
 }
 
 type SimpleCommandChecker struct {
-       shline *ShellLine
+       *ShellLineChecker
        cmd    *MkShSimpleCommand
        strcmd *StrCommand
        time   ToolTime
 }
 
-func NewSimpleCommandChecker(shline *ShellLine, cmd *MkShSimpleCommand, time ToolTime) *SimpleCommandChecker {
+func NewSimpleCommandChecker(ck *ShellLineChecker, cmd *MkShSimpleCommand, time ToolTime) *SimpleCommandChecker {
        strcmd := NewStrCommand(cmd)
-       return &SimpleCommandChecker{shline, cmd, strcmd, time}
+       return &SimpleCommandChecker{ck, cmd, strcmd, time}
 
 }
 
@@ -448,7 +448,7 @@ func (scc *SimpleCommandChecker) checkCo
        }
 
        shellword := scc.strcmd.Name
-       scc.shline.checkInstallCommand(shellword)
+       scc.checkInstallCommand(shellword)
 
        switch {
        case shellword == "${RUN}" || shellword == "":
@@ -471,7 +471,7 @@ func (scc *SimpleCommandChecker) checkCo
                break
        default:
                if G.Opts.WarnExtra && !(G.Mk != nil && G.Mk.indentation.DependsOn("OPSYS")) {
-                       scc.shline.mkline.Warnf("Unknown shell command %q.", shellword)
+                       scc.mkline.Warnf("Unknown shell command %q.", shellword)
                        G.Explain(
                                "To make the package portable to all platforms that pkgsrc supports,",
                                "it should only use shell commands that are covered by the tools framework.",
@@ -493,11 +493,11 @@ func (scc *SimpleCommandChecker) handleT
        tool, usable := G.Tool(command, scc.time)
 
        if tool != nil && !usable {
-               scc.shline.mkline.Warnf("The %q tool is used but not added to USE_TOOLS.", command)
+               scc.mkline.Warnf("The %q tool is used but not added to USE_TOOLS.", command)
        }
 
        if tool != nil && tool.MustUseVarForm && !containsVarRef(command) {
-               scc.shline.mkline.Warnf("Please use \"${%s}\" instead of %q.", tool.Varname, command)
+               scc.mkline.Warnf("Please use \"${%s}\" instead of %q.", tool.Varname, command)
        }
 
        return tool != nil
@@ -511,7 +511,7 @@ func (scc *SimpleCommandChecker) handleF
        shellword := scc.strcmd.Name
        switch path.Base(shellword) {
        case "mktexlsr", "texconfig":
-               scc.shline.mkline.Errorf("%q must not be used in Makefiles.", shellword)
+               scc.mkline.Errorf("%q must not be used in Makefiles.", shellword)
                G.Explain(
                        "This command may only appear in INSTALL scripts, not in the package Makefile,",
                        "so that the package also works if it is installed as a binary package.")
@@ -531,7 +531,7 @@ func (scc *SimpleCommandChecker) handleC
                varname := varuse.varname
 
                if vartype := G.Pkgsrc.VariableType(varname); vartype != nil && vartype.basicType.name == "ShellCommand" {
-                       scc.shline.checkInstallCommand(shellword)
+                       scc.checkInstallCommand(shellword)
                        return true
                }
 
@@ -573,16 +573,16 @@ func (scc *SimpleCommandChecker) handleC
        }
 
        semicolon := contains(shellword, ";")
-       multiline := scc.shline.mkline.IsMultiline()
+       multiline := scc.mkline.IsMultiline()
 
        if semicolon {
-               scc.shline.mkline.Warnf("A shell comment should not contain semicolons.")
+               scc.mkline.Warnf("A shell comment should not contain semicolons.")
                // TODO: Explain.
                // TODO: Check whether the existing warnings are useful.
        }
 
        if multiline {
-               scc.shline.mkline.Warnf("A shell comment does not stop at the end of line.")
+               scc.mkline.Warnf("A shell comment does not stop at the end of line.")
        }
 
        if semicolon || multiline {
@@ -614,7 +614,7 @@ func (scc *SimpleCommandChecker) checkRe
        isSubst := false
        for _, arg := range scc.strcmd.Args {
                if G.Testing && isSubst && !matches(arg, `"^[\"\'].*[\"\']$`) {
-                       scc.shline.mkline.Warnf("Substitution commands like %q should always be quoted.", arg)
+                       scc.mkline.Warnf("Substitution commands like %q should always be quoted.", arg)
                        G.Explain(
                                "Usually these substitution commands contain characters like '*' or",
                                "other shell metacharacters that might lead to lookup of matching",
@@ -647,7 +647,7 @@ func (scc *SimpleCommandChecker) checkAu
                if !contains(arg, "$$") && !matches(arg, `\$\{[_.]*[a-z]`) {
                        if m, dirname := match1(arg, `^(?:\$\{DESTDIR\})?\$\{PREFIX(?:|:Q)\}/(.*)`); m {
                                if G.Pkg != nil && G.Pkg.Plist.Dirs[dirname] {
-                                       scc.shline.mkline.Notef("You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= %s\" instead of %q.", dirname, cmdname)
+                                       scc.mkline.Notef("You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= %s\" instead of %q.", dirname, cmdname)
                                        G.Explain(
                                                "Many packages include a list of all needed directories in their",
                                                "PLIST file.",
@@ -661,7 +661,7 @@ func (scc *SimpleCommandChecker) checkAu
                                                "of the many INSTALL_*_DIR variables is appropriate, since",
                                                "INSTALLATION_DIRS takes care of that.")
                                } else {
-                                       scc.shline.mkline.Notef("You can use \"INSTALLATION_DIRS+= %s\" instead of %q.", dirname, cmdname)
+                                       scc.mkline.Notef("You can use \"INSTALLATION_DIRS+= %s\" instead of %q.", dirname, cmdname)
                                        G.Explain(
                                                "To create directories during installation, it is easier to just",
                                                "list them in INSTALLATION_DIRS than to execute the commands",
@@ -692,7 +692,7 @@ func (scc *SimpleCommandChecker) checkIn
                                break
                        default:
                                if prevdir != "" {
-                                       scc.shline.mkline.Warnf("The INSTALL_*_DIR commands can only handle one directory at a time.")
+                                       scc.mkline.Warnf("The INSTALL_*_DIR commands can only handle one directory at a time.")
                                        G.Explain(
                                                "Many implementations of install(1) can handle more, but pkgsrc aims",
                                                "at maximum portability.")
@@ -710,7 +710,7 @@ func (scc *SimpleCommandChecker) checkPa
        }
 
        if (scc.strcmd.Name == "${PAX}" || scc.strcmd.Name == "pax") && scc.strcmd.HasOption("-pe") {
-               scc.shline.mkline.Warnf("Please use the -pp option to pax(1) instead of -pe.")
+               scc.mkline.Warnf("Please use the -pp option to pax(1) instead of -pe.")
                G.Explain(
                        "The -pe option tells pax to preserve the ownership of the files.",
                        "",
@@ -729,12 +729,12 @@ func (scc *SimpleCommandChecker) checkEc
        }
 
        if scc.strcmd.Name == "${ECHO}" && scc.strcmd.HasOption("-n") {
-               scc.shline.mkline.Warnf("Please use ${ECHO_N} instead of \"echo -n\".")
+               scc.mkline.Warnf("Please use ${ECHO_N} instead of \"echo -n\".")
        }
 }
 
 type ShellProgramChecker struct {
-       shline *ShellLine
+       *ShellLineChecker
 }
 
 func (spc *ShellProgramChecker) checkConditionalCd(list *MkShList) {
@@ -755,7 +755,7 @@ func (spc *ShellProgramChecker) checkCon
 
        checkConditionalCd := func(cmd *MkShSimpleCommand) {
                if NewStrCommand(cmd).Name == "cd" {
-                       spc.shline.mkline.Errorf("The Solaris /bin/sh cannot handle \"cd\" inside conditionals.")
+                       spc.mkline.Errorf("The Solaris /bin/sh cannot handle \"cd\" inside conditionals.")
                        G.Explain(
                                "When the Solaris shell is in \"set -e\" mode and \"cd\" fails, the",
                                "shell will exit, no matter if it is protected by an \"if\" or the",
@@ -780,7 +780,7 @@ func (spc *ShellProgramChecker) checkCon
        }
        walker.Callback.Pipeline = func(pipeline *MkShPipeline) {
                if pipeline.Negated {
-                       spc.shline.mkline.Warnf("The Solaris /bin/sh does not support negation of shell commands.")
+                       spc.mkline.Warnf("The Solaris /bin/sh does not support negation of shell commands.")
                        G.Explain(
                                "The GNU Autoconf manual has many more details of what shell",
                                "features to avoid for portable programs.",
@@ -908,7 +908,7 @@ func (spc *ShellProgramChecker) checkSet
                return
        }
 
-       line := spc.shline.mkline.Line
+       line := spc.mkline.Line
        if !line.FirstTime("switch to set -e") {
                return
        }
@@ -934,7 +934,7 @@ func (spc *ShellProgramChecker) checkSet
 }
 
 // Some shell commands should not be used in the install phase.
-func (shline *ShellLine) checkInstallCommand(shellcmd string) {
+func (ck *ShellLineChecker) checkInstallCommand(shellcmd string) {
        if trace.Tracing {
                defer trace.Call0()()
        }
@@ -943,7 +943,7 @@ func (shline *ShellLine) checkInstallCom
                return
        }
 
-       line := shline.mkline.Line
+       line := ck.mkline.Line
        switch shellcmd {
        case "${INSTALL}",
                "${INSTALL_DATA}", "${INSTALL_DATA_DIR}",

Index: pkgsrc/pkgtools/pkglint/files/distinfo.go
diff -u pkgsrc/pkgtools/pkglint/files/distinfo.go:1.29 pkgsrc/pkgtools/pkglint/files/distinfo.go:1.30
--- pkgsrc/pkgtools/pkglint/files/distinfo.go:1.29      Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/distinfo.go   Sun Mar 24 13:58:38 2019
@@ -13,15 +13,15 @@ import (
        "strings"
 )
 
-func CheckLinesDistinfo(lines Lines) {
+func CheckLinesDistinfo(pkg *Package, lines Lines) {
        if trace.Tracing {
                defer trace.Call1(lines.FileName)()
        }
 
        filename := lines.FileName
        patchdir := "patches"
-       if G.Pkg != nil && dirExists(G.Pkg.File(G.Pkg.Patchdir)) {
-               patchdir = G.Pkg.Patchdir
+       if pkg != nil && dirExists(pkg.File(pkg.Patchdir)) {
+               patchdir = pkg.Patchdir
        }
        if trace.Tracing {
                trace.Step1("patchdir=%q", patchdir)
@@ -29,7 +29,7 @@ func CheckLinesDistinfo(lines Lines) {
 
        distinfoIsCommitted := isCommitted(filename)
        ck := distinfoLinesChecker{
-               lines, patchdir, distinfoIsCommitted,
+               pkg, lines, patchdir, distinfoIsCommitted,
                nil, make(map[string]distinfoFileInfo)}
        ck.parse()
        ck.check()
@@ -40,8 +40,9 @@ func CheckLinesDistinfo(lines Lines) {
 }
 
 type distinfoLinesChecker struct {
+       pkg                 *Package
        lines               Lines
-       patchdir            string // Relative to G.Pkg
+       patchdir            string // Relative to pkg
        distinfoIsCommitted bool
 
        filenames []string // For keeping the order from top to bottom
@@ -64,9 +65,9 @@ func (ck *distinfoLinesChecker) parse() 
                switch {
                case !hasPrefix(prevFilename, "patch-"):
                        return no
-               case G.Pkg == nil:
+               case ck.pkg == nil:
                        return unknown
-               case fileExists(G.Pkg.File(ck.patchdir + "/" + prevFilename)):
+               case fileExists(ck.pkg.File(ck.patchdir + "/" + prevFilename)):
                        return yes
                default:
                        return no
@@ -146,12 +147,12 @@ func (ck *distinfoLinesChecker) checkAlg
        // At this point, the file is either a missing patch file or a distfile.
 
        case hasPrefix(filename, "patch-") && algorithms == "SHA1":
-               if G.Pkg.IgnoreMissingPatches {
+               if ck.pkg.IgnoreMissingPatches {
                        break
                }
 
                line.Warnf("Patch file %q does not exist in directory %q.",
-                       filename, line.PathToFile(G.Pkg.File(ck.patchdir)))
+                       filename, line.PathToFile(ck.pkg.File(ck.patchdir)))
                G.Explain(
                        "If the patches directory looks correct, the patch may have been",
                        "removed without updating the distinfo file.",
@@ -193,10 +194,6 @@ func (ck *distinfoLinesChecker) checkAlg
        }
 
        distdir := G.Pkgsrc.File("distfiles")
-       distSubdir := ""
-       if G.Pkg != nil {
-               distSubdir = G.Pkg.vars.LastValue("DIST_SUBDIR")
-       }
 
        // It's a rare situation that the explanation is generated
        // this far from the corresponding diagnostic.
@@ -206,7 +203,7 @@ func (ck *distinfoLinesChecker) checkAlg
                "To add the missing lines to the distinfo file, run",
                sprintf("\t%s", bmake("distinfo")),
                "for each variant of the package until all distfiles are downloaded to",
-               sprintf("%q.", cleanpath("${PKGSRCDIR}/distfiles/"+distSubdir)),
+               "${PKGSRCDIR}/distfiles.",
                "",
                "The variants are typically selected by setting EMUL_PLATFORM",
                "or similar variables in the command line.",
@@ -221,7 +218,7 @@ func (ck *distinfoLinesChecker) checkAlg
                "which will find the downloaded distfiles and add the missing",
                "hashes to the distinfo file.")
 
-       distfile := cleanpath(distdir + "/" + distSubdir + "/" + info.filename())
+       distfile := cleanpath(distdir + "/" + info.filename())
        if !fileExists(distfile) {
                return
        }
@@ -301,10 +298,10 @@ func (ck *distinfoLinesChecker) checkAlg
 }
 
 func (ck *distinfoLinesChecker) checkUnrecordedPatches() {
-       if G.Pkg == nil {
+       if ck.pkg == nil {
                return
        }
-       patchFiles, err := ioutil.ReadDir(G.Pkg.File(ck.patchdir))
+       patchFiles, err := ioutil.ReadDir(ck.pkg.File(ck.patchdir))
        if err != nil {
                if trace.Tracing {
                        trace.Stepf("Cannot read patchdir %q: %s", ck.patchdir, err)
@@ -317,7 +314,7 @@ func (ck *distinfoLinesChecker) checkUnr
                if file.Mode().IsRegular() && ck.infos[patchName].isPatch != yes && hasPrefix(patchName, "patch-") {
                        line := NewLineWhole(ck.lines.FileName)
                        line.Errorf("Patch %q is not recorded. Run %q.",
-                               line.PathToFile(G.Pkg.File(ck.patchdir+"/"+patchName)),
+                               line.PathToFile(ck.pkg.File(ck.patchdir+"/"+patchName)),
                                bmake("makepatchsum"))
                }
        }
@@ -377,7 +374,7 @@ func (ck *distinfoLinesChecker) checkUnc
        line := info.line
 
        patchFileName := ck.patchdir + "/" + patchName
-       resolvedPatchFileName := G.Pkg.File(patchFileName)
+       resolvedPatchFileName := ck.pkg.File(patchFileName)
        if ck.distinfoIsCommitted && !isCommitted(resolvedPatchFileName) {
                line.Warnf("%s is registered in distinfo but not added to CVS.", line.PathToFile(resolvedPatchFileName))
        }
@@ -387,7 +384,7 @@ func (ck *distinfoLinesChecker) checkUnc
 }
 
 func (ck *distinfoLinesChecker) checkPatchSha1(line Line, patchFileName, distinfoSha1Hex string) {
-       fileSha1Hex, err := computePatchSha1Hex(G.Pkg.File(patchFileName))
+       fileSha1Hex, err := computePatchSha1Hex(ck.pkg.File(patchFileName))
        if err != nil {
                line.Errorf("Patch %s does not exist.", patchFileName)
                return
@@ -395,7 +392,7 @@ func (ck *distinfoLinesChecker) checkPat
        if distinfoSha1Hex != fileSha1Hex {
                fix := line.Autofix()
                fix.Errorf("SHA1 hash of %s differs (distinfo has %s, patch file has %s).",
-                       line.PathToFile(G.Pkg.File(patchFileName)), distinfoSha1Hex, fileSha1Hex)
+                       line.PathToFile(ck.pkg.File(patchFileName)), distinfoSha1Hex, fileSha1Hex)
                fix.Explain(
                        "To fix the hashes, either let pkglint --autofix do the work",
                        sprintf("or run %q.", bmake("makepatchsum")))

Index: pkgsrc/pkgtools/pkglint/files/distinfo_test.go
diff -u pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.26 pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.27
--- pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.26 Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/distinfo_test.go      Sun Mar 24 13:58:38 2019
@@ -23,7 +23,7 @@ func (s *Suite) Test_CheckLinesDistinfo_
                "SHA1 (patch-nonexistent) = 1234")
        G.Pkg = NewPackage(".")
 
-       CheckLinesDistinfo(lines)
+       CheckLinesDistinfo(G.Pkg, lines)
 
        t.CheckOutputLines(
                "ERROR: distinfo:1: Expected \"$"+"NetBSD$\".",
@@ -47,7 +47,7 @@ func (s *Suite) Test_distinfoLinesChecke
                "SHA1 (patch-5.3.tar.gz) = 1234567890123456789012345678901234567890")
        G.Pkg = NewPackage(".")
 
-       CheckLinesDistinfo(lines)
+       CheckLinesDistinfo(G.Pkg, lines)
 
        // Even though the filename starts with "patch-" and therefore looks like
        // a patch, it is a normal distfile because it has other hash algorithms
@@ -67,7 +67,7 @@ func (s *Suite) Test_distinfoLinesChecke
                "MD5 (distfile.tar.gz) = 12345678901234567890123456789012",
                "SHA1 (distfile.tar.gz) = 1234567890123456789012345678901234567890")
 
-       CheckLinesDistinfo(lines)
+       CheckLinesDistinfo(nil, lines)
 
        t.CheckOutputLines(
                "ERROR: distinfo:3: Expected SHA1, RMD160, SHA512, Size checksums " +
@@ -90,7 +90,7 @@ func (s *Suite) Test_distinfoLinesChecke
                "",
                "MD5 (patch-4.2.tar.gz) = 12345678901234567890123456789012")
 
-       CheckLinesDistinfo(lines)
+       CheckLinesDistinfo(nil, lines)
 
        t.CheckOutputLines(
                "ERROR: distinfo:3: Wrong checksum algorithms MD5 for patch-4.2.tar.gz.",
@@ -130,7 +130,7 @@ func (s *Suite) Test_distinfoLinesChecke
                RcsID,
                "")
 
-       CheckLinesDistinfo(lines)
+       CheckLinesDistinfo(nil, lines)
 
        t.CheckOutputLines(
                "NOTE: ~/distinfo:2: Trailing empty lines.")
@@ -218,7 +218,7 @@ func (s *Suite) Test_distinfoLinesChecke
                "SHA512 (patch-aa) = ...",
                "Size (patch-aa) = ... bytes")
 
-       CheckLinesDistinfo(lines)
+       CheckLinesDistinfo(nil, lines)
 
        // The file name certainly looks like a pkgsrc patch, but there
        // is no corresponding file in the file system, and there is no
@@ -409,7 +409,7 @@ func (s *Suite) Test_CheckLinesDistinfo_
                "",
                "SHA1 (patch-aa) = ...")
 
-       CheckLinesDistinfo(lines)
+       CheckLinesDistinfo(nil, lines)
 
        // When a distinfo file is checked on its own, without belonging to a package,
        // the PATCHDIR is not known and therefore no diagnostics are logged.
@@ -417,7 +417,7 @@ func (s *Suite) Test_CheckLinesDistinfo_
 
        G.Pkg = NewPackage(".")
 
-       CheckLinesDistinfo(lines)
+       CheckLinesDistinfo(G.Pkg, lines)
 
        // When a distinfo file is checked in the context of a package,
        // the PATCHDIR is known, therefore the check is active.
@@ -485,7 +485,7 @@ func (s *Suite) Test_distinfoLinesChecke
        G.Pkg = NewPackage(t.File("category/package"))
        distinfoLine := t.NewLine(t.File("category/package/distinfo"), 5, "")
 
-       checker := distinfoLinesChecker{}
+       checker := distinfoLinesChecker{G.Pkg, nil, "", false, nil, nil}
        checker.checkPatchSha1(distinfoLine, "patch-nonexistent", "distinfo-sha1")
 
        t.CheckOutputLines(
@@ -522,7 +522,7 @@ func (s *Suite) Test_distinfoLinesChecke
                "\tTo add the missing lines to the distinfo file, run",
                "\t\t"+confMake+" distinfo",
                "\tfor each variant of the package until all distfiles are downloaded",
-               "\tto \"${PKGSRCDIR}/distfiles\".",
+               "\tto ${PKGSRCDIR}/distfiles.",
                "",
                "\tThe variants are typically selected by setting EMUL_PLATFORM or",
                "\tsimilar variables in the command line.",

Index: pkgsrc/pkgtools/pkglint/files/mkline.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline.go:1.48 pkgsrc/pkgtools/pkglint/files/mkline.go:1.49
--- pkgsrc/pkgtools/pkglint/files/mkline.go:1.48        Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/mkline.go     Sun Mar 24 13:58:38 2019
@@ -1,7 +1,5 @@
 package pkglint
 
-// Checks concerning single lines in Makefiles.
-
 import (
        "fmt"
        "netbsd.org/pkglint/regex"
Index: pkgsrc/pkgtools/pkglint/files/pkglint.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint.go:1.48 pkgsrc/pkgtools/pkglint/files/pkglint.go:1.49
--- pkgsrc/pkgtools/pkglint/files/pkglint.go:1.48       Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/pkglint.go    Sun Mar 24 13:58:38 2019
@@ -41,14 +41,23 @@ type Pkglint struct {
        fileCache *FileCache
        interner  StringInterner
 
+       // cwd is the slash-separated absolute path to the current working
+       // directory. It is used for speeding up relpath and abspath.
+       // There is no other use for it.
+       cwd string
+
        Hashes       map[string]*Hash // Maps "alg:filename" => hash (inter-package check).
        UsedLicenses map[string]bool  // Maps "license name" => true (inter-package check).
 }
 
 func NewPkglint() Pkglint {
+       cwd, err := os.Getwd()
+       assertNil(err, "os.Getwd")
+
        return Pkglint{
                res:       regex.NewRegistry(),
                fileCache: NewFileCache(200),
+               cwd:       filepath.ToSlash(cwd),
                interner:  NewStringInterner()}
 }
 
@@ -89,7 +98,7 @@ type Hash struct {
 type pkglintFatal struct{}
 
 // G is the abbreviation for "global state";
-// these are the only global variable in this Go package
+// this and the tracer are the only global variables in this Go package.
 var (
        G     = NewPkglint()
        trace tracePkg.Tracer
@@ -111,8 +120,8 @@ func Main() int {
 // argv[0] is the program name.
 //
 // Note: during tests, calling this method disables tracing
-// because the command line option --debug sets trace.Tracing
-// back to false.
+// because the getopt parser resets all options before the actual parsing.
+// One of these options is trace.Tracing, which is connected to --debug.
 //
 // It also discards the -Wall option that is used by default in other tests.
 func (pkglint *Pkglint) Main(argv ...string) (exitCode int) {
@@ -378,85 +387,8 @@ func (pkglint *Pkglint) checkdirPackage(
        defer func() { pkglint.Pkg = nil }()
        pkg := pkglint.Pkg
 
-       // Load the package Makefile and all included files,
-       // to collect all used and defined variables and similar data.
-       mklines, allLines := pkg.loadPackageMakefile()
-       if mklines == nil {
-               return
-       }
-
-       files := dirglob(pkg.File("."))
-       if pkg.Pkgdir != "." {
-               files = append(files, dirglob(pkg.File(pkg.Pkgdir))...)
-       }
-       if pkglint.Opts.CheckExtra {
-               files = append(files, dirglob(pkg.File(pkg.Filesdir))...)
-       }
-       files = append(files, dirglob(pkg.File(pkg.Patchdir))...)
-       if pkg.DistinfoFile != pkg.vars.fallback["DISTINFO_FILE"] {
-               files = append(files, pkg.File(pkg.DistinfoFile))
-       }
-
-       haveDistinfo := false
-       havePatches := false
-
-       // Determine the used variables and PLIST directories before checking any of the Makefile fragments.
-       // TODO: Why is this code necessary? What effect does it have?
-       for _, filename := range files {
-               basename := path.Base(filename)
-               if (hasPrefix(basename, "Makefile.") || hasSuffix(filename, ".mk")) &&
-                       !matches(filename, `patch-`) &&
-                       !contains(filename, pkg.Pkgdir+"/") &&
-                       !contains(filename, pkg.Filesdir+"/") {
-                       if fragmentMklines := LoadMk(filename, MustSucceed); fragmentMklines != nil {
-                               fragmentMklines.collectUsedVariables()
-                       }
-               }
-               if hasPrefix(basename, "PLIST") {
-                       pkg.loadPlistDirs(filename)
-               }
-       }
-
-       for _, filename := range files {
-               if containsVarRef(filename) {
-                       if trace.Tracing {
-                               trace.Stepf("Skipping file %q because the name contains an unresolved variable.", filename)
-                       }
-                       continue
-               }
-
-               st, err := os.Lstat(filename)
-               switch {
-               case err != nil:
-                       // For missing custom distinfo file, an error message is already generated
-                       // for the line where DISTINFO_FILE is defined.
-                       //
-                       // For all other cases it is next to impossible to reach this branch
-                       // since all those files come from calls to dirglob.
-                       break
-
-               case path.Base(filename) == "Makefile":
-                       pkglint.checkExecutable(filename, st.Mode())
-                       pkg.checkfilePackageMakefile(filename, mklines, allLines)
-
-               default:
-                       pkglint.checkDirent(filename, st.Mode())
-               }
-
-               if contains(filename, "/patches/patch-") {
-                       havePatches = true
-               } else if hasSuffix(filename, "/distinfo") {
-                       haveDistinfo = true
-               }
-               pkg.checkLocallyModified(filename)
-       }
-
-       if pkg.Pkgdir == "." {
-               if havePatches && !haveDistinfo {
-                       // TODO: Add Line.RefTo to make the context clear.
-                       NewLineWhole(pkg.File(pkg.DistinfoFile)).Warnf("File not found. Please run %q.", bmake("makepatchsum"))
-               }
-       }
+       files, mklines, allLines := pkg.load()
+       pkg.check(files, mklines, allLines)
 }
 
 // Assertf checks that the condition is true. Otherwise it terminates the
@@ -478,9 +410,7 @@ func (pkglint *Pkglint) Assertf(cond boo
 // Other than Assertf, this method does not require any comparison operator in the calling code.
 // This makes it possible to get 100% branch coverage for cases that "really can never fail".
 func (pkglint *Pkglint) AssertNil(err error, format string, args ...interface{}) {
-       if err != nil {
-               panic("Pkglint internal error: " + sprintf(format, args...) + ": " + err.Error())
-       }
+       assertNil(err, format, args...)
 }
 
 // Returns the pkgsrc top-level directory, relative to the given directory.
@@ -678,7 +608,7 @@ func (pkglint *Pkglint) checkReg(filenam
 
        case basename == "distinfo":
                if lines := Load(filename, NotEmpty|LogErrors); lines != nil {
-                       CheckLinesDistinfo(lines)
+                       CheckLinesDistinfo(G.Pkg, lines)
                }
 
        case basename == "DEINSTALL" || basename == "INSTALL":
@@ -714,7 +644,7 @@ func (pkglint *Pkglint) checkReg(filenam
 
        case hasPrefix(basename, "PLIST"):
                if lines := Load(filename, NotEmpty|LogErrors); lines != nil {
-                       CheckLinesPlist(lines)
+                       CheckLinesPlist(G.Pkg, lines)
                }
 
        case hasPrefix(basename, "CHANGES-"):

Index: pkgsrc/pkgtools/pkglint/files/mkline_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.53 pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.54
--- pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.53   Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/mkline_test.go        Sun Mar 24 13:58:38 2019
@@ -374,8 +374,8 @@ func (s *Suite) Test_MkLine_VariableNeed
        t.SetUpMasterSite("MASTER_SITE_SOURCEFORGE", "http://downloads.sourceforge.net/sourceforge/";)
        mkline := t.NewMkLine("Makefile", 95, "MASTER_SITES=\t${HOMEPAGE}")
 
-       vuc := VarUseContext{G.Pkgsrc.vartypes["MASTER_SITES"], vucTimeRun, VucQuotPlain, false}
-       nq := mkline.VariableNeedsQuoting("HOMEPAGE", G.Pkgsrc.vartypes["HOMEPAGE"], &vuc)
+       vuc := VarUseContext{G.Pkgsrc.vartypes.Canon("MASTER_SITES"), vucTimeRun, VucQuotPlain, false}
+       nq := mkline.VariableNeedsQuoting("HOMEPAGE", G.Pkgsrc.vartypes.Canon("HOMEPAGE"), &vuc)
 
        c.Check(nq, equals, no)
 
@@ -407,7 +407,7 @@ func (s *Suite) Test_MkLine_VariableNeed
        MkLineChecker{mkline}.checkVarassign()
 
        t.CheckOutputLines(
-               "WARN: builtin.mk:3: PKG_ADMIN should not be evaluated at load time.",
+               "WARN: builtin.mk:3: PKG_ADMIN should not be used at load time in any file.",
                "NOTE: builtin.mk:3: The :Q operator isn't necessary for ${BUILTIN_PKG.Xfixes} here.")
 }
 
@@ -701,7 +701,6 @@ func (s *Suite) Test_MkLine_VariableNeed
        // only appear completely unquoted. There is no practical way of
        // using it inside backticks, and luckily there is no need for it.
        t.CheckOutputLines(
-               "WARN: Makefile:4: COMMENT may not be used in any file; it is a write-only variable.",
                // TODO: Better suggest that COMMENT should not be used inside backticks or other quotes.
                "WARN: Makefile:4: The variable COMMENT should be quoted as part of a shell word.")
 }
@@ -781,7 +780,7 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__uncovered_cases(c *check.C) {
        t := s.Init(c)
 
-       t.SetUpCommandLine("-Wall,no-space")
+       t.SetUpCommandLine("-Wall,no-space", "--explain")
        t.SetUpVartypes()
 
        mklines := t.SetUpFileMkLines("Makefile",
@@ -796,20 +795,107 @@ func (s *Suite) Test_MkLine_VariableNeed
        mklines.Check()
 
        t.CheckOutputLines(
-               // TODO: Explain why the variable may not be set, by listing the current rules.
-               "WARN: ~/Makefile:4: The variable LINKER_RPATH_FLAG may not be set by any package.",
-               "WARN: ~/Makefile:4: Please use ${LINKER_RPATH_FLAG:S/-rpath/& /:Q} instead of ${LINKER_RPATH_FLAG:S/-rpath/& /}.",
-               "WARN: ~/Makefile:4: LINKER_RPATH_FLAG should not be evaluated at load time.",
-               "WARN: ~/Makefile:6: The variable PATH may not be set by any package.",
-               "WARN: ~/Makefile:6: PREFIX should not be evaluated at load time.",
-               "WARN: ~/Makefile:6: PATH should not be evaluated at load time.")
+               "WARN: ~/Makefile:4: The variable LINKER_RPATH_FLAG should not be set by any package.",
+               "",
+               "\tThe allowed actions for a variable are determined based on the file",
+               "\tname in which the variable is used or defined. The rules for",
+               "\tLINKER_RPATH_FLAG are:",
+               "",
+               "\t* in buildlink3.mk, it should not be accessed at all",
+               "\t* in any file, it may be used",
+               "",
+               "\tIf these rules seem to be incorrect, please ask on the",
+               "\ttech-pkg%NetBSD.org@localhost mailing list.",
+               "",
+               "WARN: ~/Makefile:4: Please use ${LINKER_RPATH_FLAG:S/-rpath/& /:Q} "+
+                       "instead of ${LINKER_RPATH_FLAG:S/-rpath/& /}.",
+               "",
+               "\tSee the pkgsrc guide, section \"Echoing a string exactly as-is\":",
+               "\thttps://www.NetBSD.org/docs/pkgsrc/pkgsrc.html#echo-literal";,
+               "",
+               "WARN: ~/Makefile:4: LINKER_RPATH_FLAG should not be used at load time in any file.",
+               "",
+               "\tMany variables, especially lists of something, get their values",
+               "\tincrementally. Therefore it is generally unsafe to rely on their",
+               "\tvalue until it is clear that it will never change again. This point",
+               "\tis reached when the whole package Makefile is loaded and execution",
+               "\tof the shell commands starts; in some cases earlier.",
+               "",
+               "\tAdditionally, when using the \":=\" operator, each $$ is replaced with",
+               "\ta single $, so variables that have references to shell variables or",
+               "\tregular expressions are modified in a subtle way.",
+               "",
+               "\tThe allowed actions for a variable are determined based on the file",
+               "\tname in which the variable is used or defined. The rules for",
+               "\tLINKER_RPATH_FLAG are:",
+               "",
+               "\t* in buildlink3.mk, it should not be accessed at all",
+               "\t* in any file, it may be used",
+               "",
+               "\tIf these rules seem to be incorrect, please ask on the",
+               "\ttech-pkg%NetBSD.org@localhost mailing list.",
+               "",
+               "WARN: ~/Makefile:6: The variable PATH should not be set by any package.",
+               "",
+               "\tThe allowed actions for a variable are determined based on the file",
+               "\tname in which the variable is used or defined. The rules for PATH",
+               "\tare:",
+               "",
+               "\t* in buildlink3.mk, it should not be accessed at all",
+               "\t* in any file, it may be used",
+               "",
+               "\tIf these rules seem to be incorrect, please ask on the",
+               "\ttech-pkg%NetBSD.org@localhost mailing list.",
+               "",
+               "WARN: ~/Makefile:6: PREFIX should not be used at load time in any file.",
+               "",
+               "\tMany variables, especially lists of something, get their values",
+               "\tincrementally. Therefore it is generally unsafe to rely on their",
+               "\tvalue until it is clear that it will never change again. This point",
+               "\tis reached when the whole package Makefile is loaded and execution",
+               "\tof the shell commands starts; in some cases earlier.",
+               "",
+               "\tAdditionally, when using the \":=\" operator, each $$ is replaced with",
+               "\ta single $, so variables that have references to shell variables or",
+               "\tregular expressions are modified in a subtle way.",
+               "",
+               "\tThe allowed actions for a variable are determined based on the file",
+               "\tname in which the variable is used or defined. The rules for PREFIX",
+               "\tare:",
+               "",
+               "\t* in any file, it may be used",
+               "",
+               "\tIf these rules seem to be incorrect, please ask on the",
+               "\ttech-pkg%NetBSD.org@localhost mailing list.",
+               "",
+               "WARN: ~/Makefile:6: PATH should not be used at load time in any file.",
+               "",
+               "\tMany variables, especially lists of something, get their values",
+               "\tincrementally. Therefore it is generally unsafe to rely on their",
+               "\tvalue until it is clear that it will never change again. This point",
+               "\tis reached when the whole package Makefile is loaded and execution",
+               "\tof the shell commands starts; in some cases earlier.",
+               "",
+               "\tAdditionally, when using the \":=\" operator, each $$ is replaced with",
+               "\ta single $, so variables that have references to shell variables or",
+               "\tregular expressions are modified in a subtle way.",
+               "",
+               "\tThe allowed actions for a variable are determined based on the file",
+               "\tname in which the variable is used or defined. The rules for PATH",
+               "\tare:",
+               "",
+               "\t* in buildlink3.mk, it should not be accessed at all",
+               "\t* in any file, it may be used",
+               "",
+               "\tIf these rules seem to be incorrect, please ask on the",
+               "\ttech-pkg%NetBSD.org@localhost mailing list.",
+               "")
 
        // Just for branch coverage.
        trace.Tracing = false
        MkLineChecker{mklines.mklines[2]}.Check()
 
-       t.CheckOutputLines(
-               "WARN: ~/Makefile:3: GO_SRCPATH is defined but not used.")
+       t.CheckOutputEmpty()
 }
 
 func (s *Suite) Test_MkLine__shell_varuse_in_backt_dquot(c *check.C) {
@@ -1131,6 +1217,9 @@ func (s *Suite) Test_MkLine_ResolveVarsI
        mklines.Check()
 
        t.CheckOutputLines(
+               "WARN: ~/multimedia/totem/bla.mk:2: "+
+                       "The variable BUILDLINK_PKGSRCDIR.totem should not be given a default value in this file; "+
+                       "it would be ok in buildlink3.mk.",
                "ERROR: ~/multimedia/totem/bla.mk:2: There is no package in \"multimedia/totem\".")
 }
 

Index: pkgsrc/pkgtools/pkglint/files/mklinechecker.go
diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.31 pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.32
--- pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.31 Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/mklinechecker.go      Sun Mar 24 13:58:38 2019
@@ -9,6 +9,7 @@ import (
        "strings"
 )
 
+// MkLineChecker provides checks for a single line from a Makefile fragment.
 type MkLineChecker struct {
        MkLine MkLine
 }
@@ -59,7 +60,7 @@ func (ck MkLineChecker) checkShellComman
        }
 
        ck.checkText(shellCommand)
-       NewShellLine(mkline).CheckShellCommandLine(shellCommand)
+       NewShellLineChecker(mkline).CheckShellCommandLine(shellCommand)
 }
 
 func (ck MkLineChecker) checkInclude() {
@@ -220,7 +221,7 @@ func (ck MkLineChecker) checkDirectiveFo
                // The guessed flag could also be determined more correctly. As of November 2018,
                // running pkglint over the whole pkgsrc tree did not produce any different result
                // whether guessed was true or false.
-               forLoopType := Vartype{lkShell, BtUnknown, []ACLEntry{{"*", aclpAllRead}}, false}
+               forLoopType := Vartype{lkShell, btForLoop, []ACLEntry{{"*", aclpAllRead}}, false}
                forLoopContext := VarUseContext{&forLoopType, vucTimeParse, VucQuotPlain, false}
                for _, itemsVar := range mkline.DetermineUsedVariables() {
                        ck.CheckVaruse(&MkVarUse{itemsVar, nil}, &forLoopContext)
@@ -325,38 +326,39 @@ func (ck MkLineChecker) checkVarassignLe
        switch {
        case perms.Contains(needed):
                break
-       case perms == aclpUnknown:
-               if trace.Tracing {
-                       trace.Step1("Unknown permissions for %q.", varname)
-               }
        default:
                alternativeActions := perms & aclpAllWrite
-               alternativeFiles := vartype.AllowedFiles(needed)
+               alternativeFiles := vartype.AlternativeFiles(needed)
                switch {
                case alternativeActions != 0 && alternativeFiles != "":
-                       // FIXME: Sometimes the message says "ok in *", which is missing that in buildlink3.mk, none of the actions are allowed.
-                       mkline.Warnf("The variable %s may not be %s (only %s) in this file; it would be ok in %s.",
+                       mkline.Warnf("The variable %s should not be %s (only %s) in this file; it would be ok in %s.",
                                varname, needed.HumanString(), alternativeActions.HumanString(), alternativeFiles)
                case alternativeFiles != "":
-                       mkline.Warnf("The variable %s may not be %s in this file; it would be ok in %s.",
+                       mkline.Warnf("The variable %s should not be %s in this file; it would be ok in %s.",
                                varname, needed.HumanString(), alternativeFiles)
                case alternativeActions != 0:
-                       mkline.Warnf("The variable %s may not be %s (only %s) in this file.",
+                       mkline.Warnf("The variable %s should not be %s (only %s) in this file.",
                                varname, needed.HumanString(), alternativeActions.HumanString())
                default:
-                       mkline.Warnf("The variable %s may not be %s by any package.",
+                       mkline.Warnf("The variable %s should not be %s by any package.",
                                varname, needed.HumanString())
                }
                ck.explainPermissions(varname, vartype)
        }
 }
 
-func (ck MkLineChecker) explainPermissions(varname string, vartype *Vartype) {
+func (ck MkLineChecker) explainPermissions(varname string, vartype *Vartype, intro ...string) {
        if !G.Logger.Opts.Explain {
                return
        }
 
        var expl []string
+
+       if len(intro) > 0 {
+               expl = append(expl, intro...)
+               expl = append(expl, "")
+       }
+
        expl = append(expl,
                "The allowed actions for a variable are determined based on the file",
                "name in which the variable is used or defined.",
@@ -374,7 +376,7 @@ func (ck MkLineChecker) explainPermissio
                if perms != "" {
                        expl = append(expl, sprintf("* in %s, it may be %s", files, perms))
                } else {
-                       expl = append(expl, sprintf("* in %s, it may not be accessed at all", files))
+                       expl = append(expl, sprintf("* in %s, it should not be accessed at all", files))
                }
        }
 
@@ -450,15 +452,13 @@ func (ck MkLineChecker) checkVaruseUndef
                break
        case vartype != nil && !vartype.guessed:
                // Well-known variables are probably defined by the infrastructure.
-       case varIsDefinedSimilar(varname):
+       case varIsDefinedSimilar(G.Pkg, G.Mk, varname):
                break
        case containsVarRef(varname):
                break
-       case G.Pkgsrc.vartypes[varname] != nil:
+       case G.Pkgsrc.vartypes.DefinedCanon(varname):
                break
-       case G.Pkgsrc.vartypes[varnameCanon(varname)] != nil:
-               break
-       case G.Mk != nil && !G.Mk.FirstTimeSlice("used but not defined: ", varname):
+       case G.Mk == nil || !G.Mk.FirstTimeSlice("used but not defined: ", varname):
                break
 
        default:
@@ -544,50 +544,136 @@ func (ck MkLineChecker) checkVarusePermi
 
        mkline := ck.MkLine
        effPerms := vartype.EffectivePermissions(mkline.Basename)
-       if effPerms == aclpUnknown {
-               return
-       }
        if effPerms.Contains(aclpUseLoadtime) {
+               // Skip any checks, assuming that if a variable may be used at
+               // load time, it may also be used at run time.
                return
        }
 
-       // Is the variable used at load time although that is not allowed?
+       // At this point the variable must not be used at load time.
+       // Now determine whether it is directly used at load time because
+       // the context already says so or, a little trickier, if it might
+       // be used at load time somewhere in the future because it is
+       // assigned to another variable, and that variable is allowed
+       // to be used at load time.
        directly := vuc.time == vucTimeParse
-       indirectly := !directly && vuc.vartype != nil && vuc.vartype.Union().Contains(aclpUseLoadtime)
+       indirectly := !directly && vuc.vartype != nil &&
+               vuc.vartype.Union().Contains(aclpUseLoadtime)
+
+       if vartype.guessed {
+               return
+       }
+
+       if directly || indirectly {
+               // At this point the variable is used at load time although that
+               // is not allowed.
 
-       if (directly || indirectly) && !vartype.guessed {
+               // Whether a tool variable may be used at load time depends on
+               // whether bsd.prefs.mk has been included. That file examines the
+               // tools that have been added to USE_TOOLS up to this point and
+               // makes their variables available for use at load time.
                if tool := G.ToolByVarname(varname); tool != nil {
                        if !tool.UsableAtLoadTime(G.Mk.Tools.SeenPrefs) {
                                ck.warnVaruseToolLoadTime(varname, tool)
                        }
-               } else {
-                       ck.warnVaruseLoadTime(varname, indirectly)
+                       return
                }
+
+               // Continue to get a detailed warning showing alternative
+               // permissions and/or alternative files.
+
+       } else if effPerms.Contains(aclpUse) {
+               // At this point the variable is used at run time. Since that is
+               // allowed by the permissions, there is nothing more to check for.
+               return
        }
 
-       if !effPerms.Contains(aclpUse) {
-               needed := aclpUse
-               if directly || indirectly {
-                       needed = aclpUseLoadtime
-               }
-               alternativeFiles := vartype.AllowedFiles(needed)
-               if alternativeFiles != "" {
-                       if G.Mk == nil || G.Mk.FirstTimeSlice("don't-use", varname, mkline.Filename) {
-                               mkline.Warnf("%s may not be used in this file; it would be ok in %s.",
-                                       varname, alternativeFiles)
-                       }
-               } else {
-                       if G.Mk == nil || G.Mk.FirstTimeSlice("write-only", varname) {
-                               mkline.Warnf("%s may not be used in any file; it is a write-only variable.", varname)
-                       }
-               }
+       if G.Mk != nil && !G.Mk.FirstTimeSlice("checkVarusePermissions", varname) {
+               return
+       }
+
+       // Do not warn about unknown infrastructure variables.
+       // These have all permissions to prevent warnings when they are used.
+       // But when other variables are assigned to them it would seem as if
+       // these other variables could become evaluated at load time.
+       // And this is something that most variables do not allow.
+       if vuc.vartype != nil && vuc.vartype.basicType == BtUnknown {
+               return
+       }
 
+       // At this point the variable is used either at load time or at run
+       // time, and that particular use is not allowed in this file.
+       //
+       // If the variable is used at run time, it may or may not be used at
+       // load time in this file. Having a variable that may be used at load
+       // time but not at run time is not a practically important case.
+       // Therefore it is not handled specially here.
+       //
+       // Anyway, there must be a warning now since the requested use is not
+       // allowed. The only remaining question is about how detailed the
+       // warning will be.
+
+       anyPerms := vartype.Union()
+       if !anyPerms.Contains(aclpUse) && !anyPerms.Contains(aclpUseLoadtime) {
+               mkline.Warnf("%s should not be used in any file; it is a write-only variable.", varname)
                ck.explainPermissions(varname, vartype)
+               return
+       }
+
+       if indirectly {
+               mkline.Warnf("%s should not be used indirectly at load time (via %s).",
+                       varname, mkline.Varname())
+               ck.explainPermissions(varname, vartype,
+                       "The variable on the left-hand side may be evaluated at load time,",
+                       "but the variable on the right-hand side should not.",
+                       "Because of the assignment in this line, the variable might be",
+                       "used indirectly at load time, before it is guaranteed to be",
+                       "properly initialized.")
+               return
+       }
+
+       needed := aclpUse
+       if directly {
+               needed = aclpUseLoadtime
        }
+       alternativeFiles := vartype.AlternativeFiles(needed)
+
+       loadTimeExplanation := func() []string {
+               return []string{
+                       "Many variables, especially lists of something, get their values incrementally.",
+                       "Therefore it is generally unsafe to rely on their",
+                       "value until it is clear that it will never change again.",
+                       "This point is reached when the whole package Makefile is loaded and",
+                       "execution of the shell commands starts; in some cases earlier.",
+                       "",
+                       "Additionally, when using the \":=\" operator, each $$ is replaced",
+                       "with a single $, so variables that have references to shell",
+                       "variables or regular expressions are modified in a subtle way."}
+       }
+
+       switch {
+       case alternativeFiles == "" && directly:
+               mkline.Warnf("%s should not be used at load time in any file.", varname)
+               ck.explainPermissions(varname, vartype, loadTimeExplanation()...)
+
+       case directly:
+               mkline.Warnf(
+                       "%s should not be used at load time in this file; "+
+                               "it would be ok in %s.",
+                       varname, alternativeFiles)
+               ck.explainPermissions(varname, vartype, loadTimeExplanation()...)
+
+       default:
+               mkline.Warnf(
+                       "%s should not be used in this file; it would be ok in %s.",
+                       varname, alternativeFiles)
+               ck.explainPermissions(varname, vartype)
+       }
+
 }
 
 // warnVaruseToolLoadTime logs a warning that the tool ${varname}
-// may not be used at load time.
+// should not be used at load time.
 func (ck MkLineChecker) warnVaruseToolLoadTime(varname string, tool *Tool) {
        // TODO: While using a tool by its variable name may be ok at load time,
        //  doing the same with the plain name of a tool is never ok.
@@ -629,32 +715,6 @@ func (ck MkLineChecker) warnVaruseToolLo
                "except in the package Makefile itself.")
 }
 
-func (ck MkLineChecker) warnVaruseLoadTime(varname string, isIndirect bool) {
-       mkline := ck.MkLine
-
-       if !isIndirect {
-               mkline.Warnf("%s should not be evaluated at load time.", varname)
-               G.Explain(
-                       "Many variables, especially lists of something, get their values incrementally.",
-                       "Therefore it is generally unsafe to rely on their",
-                       "value until it is clear that it will never change again.",
-                       "This point is reached when the whole package Makefile is loaded and",
-                       "execution of the shell commands starts; in some cases earlier.",
-                       "",
-                       "Additionally, when using the \":=\" operator, each $$ is replaced",
-                       "with a single $, so variables that have references to shell",
-                       "variables or regular expressions are modified in a subtle way.")
-               return
-       }
-
-       mkline.Warnf("%s should not be evaluated indirectly at load time.", varname)
-       G.Explain(
-               "The variable on the left-hand side may be evaluated at load time,",
-               "but the variable on the right-hand side may not.",
-               "Because of the assignment in this line, the variable might be used indirectly",
-               "at load time, before it is guaranteed to be properly initialized.")
-}
-
 // CheckVaruseShellword checks whether a variable use of the form ${VAR}
 // or ${VAR:modifiers} is allowed in a certain context.
 func (ck MkLineChecker) CheckVaruseShellword(varname string, vartype *Vartype, vuc *VarUseContext, mod string, needsQuoting bool) {
@@ -870,22 +930,27 @@ func (ck MkLineChecker) checkVarassignLe
                return
        }
 
-       if varIsUsedSimilar(varname) {
+       if varIsUsedSimilar(G.Pkg, G.Mk, varname) {
                return
        }
 
-       if vartypes := G.Pkgsrc.vartypes; vartypes[varname] != nil || vartypes[varcanon] != nil {
+       vartypes := G.Pkgsrc.vartypes
+       if vartypes.DefinedExact(varname) || vartypes.DefinedExact(varcanon) {
                return
        }
 
-       if deprecated := G.Pkgsrc.Deprecated; deprecated[varname] != "" || deprecated[varcanon] != "" {
+       deprecated := G.Pkgsrc.Deprecated
+       if deprecated[varname] != "" || deprecated[varcanon] != "" {
                return
        }
 
-       if G.Mk != nil && !G.Mk.FirstTimeSlice("defined but not used: ", varname) {
+       if G.Mk == nil || !G.Mk.FirstTimeSlice("defined but not used: ", varname) {
                return
        }
 
+       // FIXME: Explain how to fix this warning.
+       //  For files like module.mk that are used by other packages,
+       //  documenting the variable already makes the warning disappear.
        ck.MkLine.Warnf("%s is defined but not used.", varname)
 }
 

Index: pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.27 pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.28
--- pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.27    Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go Sun Mar 24 13:58:38 2019
@@ -8,14 +8,16 @@ import (
 func (s *Suite) Test_MkLineChecker_checkVarassignLeft(c *check.C) {
        t := s.Init(c)
 
-       mkline := t.NewMkLine("module.mk", 123, "_VARNAME=\tvalue")
+       mklines := t.NewMkLines("module.mk",
+               MkRcsID,
+               "_VARNAME=\tvalue")
 
-       MkLineChecker{mkline}.checkVarassignLeft()
+       mklines.vars.Use("_VARNAME", mklines.mklines[1])
+       mklines.Check()
 
        t.CheckOutputLines(
-               "WARN: module.mk:123: Variable names starting with an underscore "+
-                       "(_VARNAME) are reserved for internal pkgsrc use.",
-               "WARN: module.mk:123: _VARNAME is defined but not used.")
+               "WARN: module.mk:2: Variable names starting with an underscore " +
+                       "(_VARNAME) are reserved for internal pkgsrc use.")
 }
 
 func (s *Suite) Test_MkLineChecker_checkVarassignLeftNotUsed__procedure_call(c *check.C) {
@@ -44,6 +46,34 @@ func (s *Suite) Test_MkLineChecker_check
                "WARN: ~/category/package/filename.mk:6: VAR is defined but not used.")
 }
 
+func (s *Suite) Test_MkLineChecker_checkVarassignLeftNotUsed__infra(c *check.C) {
+       t := s.Init(c)
+
+       t.CreateFileLines("mk/infra.mk",
+               MkRcsID,
+               "#",
+               "# Package-settable variables:",
+               "#",
+               "# SHORT_DOCUMENTATION",
+               "#\tIf set to no, ...",
+               "#\tsecond line.",
+               "#",
+               "#",
+               ".if ${USED_IN_INFRASTRUCTURE:Uyes:tl} == yes",
+               ".endif")
+       t.SetUpPackage("category/package",
+               "USED_IN_INFRASTRUCTURE=\t${SHORT_DOCUMENTATION}",
+               "",
+               "UNUSED_INFRA=\t${UNDOCUMENTED}")
+       G.Pkgsrc.LoadInfrastructure()
+
+       G.Check(t.File("category/package"))
+
+       t.CheckOutputLines(
+               "WARN: ~/category/package/Makefile:22: UNUSED_INFRA is defined but not used.",
+               "WARN: ~/category/package/Makefile:22: UNDOCUMENTED is used but not defined.")
+}
+
 // Files from the pkgsrc infrastructure may define and use variables
 // whose name starts with an underscore.
 func (s *Suite) Test_MkLineChecker_checkVarassignLeft__infrastructure(c *check.C) {
@@ -65,12 +95,14 @@ func (s *Suite) Test_MkLineChecker_Check
 
        t.SetUpVartypes()
 
-       mkline := t.NewMkLine("filename.mk", 1, "# url2pkg-marker")
+       mklines := t.NewMkLines("filename.mk",
+               MkRcsID,
+               "# url2pkg-marker")
 
-       MkLineChecker{mkline}.Check()
+       mklines.Check()
 
        t.CheckOutputLines(
-               "ERROR: filename.mk:1: This comment indicates unfinished work (url2pkg).")
+               "ERROR: filename.mk:2: This comment indicates unfinished work (url2pkg).")
 }
 
 func (s *Suite) Test_MkLineChecker_Check__buildlink3_include_prefs(c *check.C) {
@@ -134,9 +166,11 @@ func (s *Suite) Test_MkLineChecker_check
 func (s *Suite) Test_MkLineChecker_checkInclude__Makefile(c *check.C) {
        t := s.Init(c)
 
-       mkline := t.NewMkLine(t.File("Makefile"), 2, ".include \"../../other/package/Makefile\"")
+       mklines := t.NewMkLines(t.File("Makefile"),
+               MkRcsID,
+               ".include \"../../other/package/Makefile\"")
 
-       MkLineChecker{mkline}.checkInclude()
+       mklines.Check()
 
        t.CheckOutputLines(
                "ERROR: ~/Makefile:2: Relative path \"../../other/package/Makefile\" does not exist.",
@@ -320,20 +354,25 @@ func (s *Suite) Test_MkLineChecker_check
        c.Check(vartype.guessed, equals, false)
        c.Check(vartype.kindOfList, equals, lkNone)
 
-       mkline := t.NewMkLine("Makefile", 123, "COMMENT=\tA nice package")
-       MkLineChecker{mkline}.checkVartype(mkline.Varname(), mkline.Op(), mkline.Value(), mkline.VarassignComment())
+       mklines := t.NewMkLines("Makefile",
+               MkRcsID,
+               "COMMENT=\tA nice package")
+       mklines.Check()
 
        t.CheckOutputLines(
-               "WARN: Makefile:123: COMMENT should not begin with \"A\".")
+               "WARN: Makefile:2: COMMENT should not begin with \"A\".")
 }
 
 func (s *Suite) Test_MkLineChecker_checkVartype(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
-       mkline := t.NewMkLine("filename.mk", 1, "DISTNAME=gcc-${GCC_VERSION}")
+       mklines := t.NewMkLines("filename.mk",
+               MkRcsID,
+               "DISTNAME=\tgcc-${GCC_VERSION}")
 
-       MkLineChecker{mkline}.checkVartype("DISTNAME", opAssign, "gcc-${GCC_VERSION}", "")
+       mklines.vars.Define("GCC_VERSION", mklines.mklines[1])
+       mklines.Check()
 
        t.CheckOutputEmpty()
 }
@@ -351,7 +390,7 @@ func (s *Suite) Test_MkLineChecker_check
        mklines.Check()
 
        t.CheckOutputLines(
-               "WARN: filename.mk:2: The variable DISTNAME may not be appended to "+
+               "WARN: filename.mk:2: The variable DISTNAME should not be appended to "+
                        "(only set, or given a default value) in this file.",
                "WARN: filename.mk:2: The \"+=\" operator should only be used with lists, not with DISTNAME.")
 }
@@ -363,14 +402,13 @@ func (s *Suite) Test_MkLineChecker_check
 
        G.Pkg = NewPackage(t.File("graphics/gimp-fix-ca"))
        t.SetUpVartypes()
-       mkline := t.NewMkLine("filename.mk", 10, "MASTER_SITES=http://registry.gimp.org/file/fix-ca.c?action=download&id=9884&file=";)
+       mklines := t.NewMkLines("filename.mk",
+               MkRcsID,
+               "MASTER_SITES=\thttp://registry.gimp.org/file/fix-ca.c?action=download&id=9884&file=";)
 
-       MkLineChecker{mkline}.checkVarassign()
+       mklines.Check()
 
-       t.CheckOutputLines(
-               "WARN: filename.mk:10: The variable MASTER_SITES may not be set " +
-                       "(only given a default value, or appended to) in this file; " +
-                       "it would be ok in Makefile, Makefile.common or options.mk.")
+       t.CheckOutputEmpty()
 }
 
 func (s *Suite) Test_MkLineChecker_checkDirectiveCond(c *check.C) {
@@ -379,26 +417,29 @@ func (s *Suite) Test_MkLineChecker_check
        t.SetUpVartypes()
 
        test := func(cond string, output ...string) {
-               MkLineChecker{t.NewMkLine("filename.mk", 1, cond)}.checkDirectiveCond()
-               if len(output) > 0 {
-                       t.CheckOutputLines(output...)
-               } else {
-                       t.CheckOutputEmpty()
-               }
+               mklines := t.NewMkLines("filename.mk",
+                       cond)
+               G.Mk = mklines
+               mklines.ForEach(func(mkline MkLine) {
+                       MkLineChecker{mkline}.checkDirectiveCond()
+               })
+               t.CheckOutput(output)
        }
 
-       test(".if !empty(PKGSRC_COMPILER:Mmycc)",
+       test(
+               ".if !empty(PKGSRC_COMPILER:Mmycc)",
                "WARN: filename.mk:1: The pattern \"mycc\" cannot match any of "+
                        "{ ccache ccc clang distcc f2c gcc hp icc ido "+
                        "mipspro mipspro-ucode pcc sunpro xlc } for PKGSRC_COMPILER.")
 
-       test(".elif ${A} != ${B}",
+       test(
+               ".elif ${A} != ${B}",
                "WARN: filename.mk:1: A is used but not defined.",
                "WARN: filename.mk:1: B is used but not defined.")
 
        test(".if ${HOMEPAGE} == \"mailto:someone%example.org@localhost\"";,
                "WARN: filename.mk:1: \"mailto:someone%example.org@localhost\"; is not a valid URL.",
-               "WARN: filename.mk:1: HOMEPAGE should not be evaluated at load time.")
+               "WARN: filename.mk:1: HOMEPAGE should not be used at load time in any file.")
 
        test(".if !empty(PKGSRC_RUN_TEST:M[Y][eE][sS])",
                "WARN: filename.mk:1: PKGSRC_RUN_TEST should be matched "+
@@ -416,7 +457,8 @@ func (s *Suite) Test_MkLineChecker_check
                "WARN: filename.mk:1: Use ${PKGSRC_COMPILER:Mmsvc} instead of the == operator.")
 
        test(".if ${PKG_LIBTOOL:Mlibtool}",
-               "NOTE: filename.mk:1: PKG_LIBTOOL should be compared using == instead of matching against \":Mlibtool\".")
+               "NOTE: filename.mk:1: PKG_LIBTOOL should be compared using == instead of matching against \":Mlibtool\".",
+               "WARN: filename.mk:1: PKG_LIBTOOL should not be used at load time in any file.")
 
        test(".if ${MACHINE_PLATFORM:MUnknownOS-*-*} || ${MACHINE_ARCH:Mx86}",
                "WARN: filename.mk:1: "+
@@ -438,12 +480,13 @@ func (s *Suite) Test_MkLineChecker_check
                // FIXME: Indeed, indeed, the :M modifier ends at the colon.
                //  Why doesn't pkglint complain loudly about the unknown "//*" modifier?
                "WARN: filename.mk:1: \"ftp\" is not a valid URL.",
-               "WARN: filename.mk:1: MASTER_SITES should not be evaluated at load time.")
+               "WARN: filename.mk:1: MASTER_SITES should not be used at load time in any file.")
 
        // The only interesting line from the below tracing output is the one
        // containing "checkCompareVarStr".
        t.EnableTracingToLog()
        test(".if ${VAR:Mpattern1:Mpattern2} == comparison",
+               "TRACE:   Indentation before line 1: []",
                "TRACE: + MkLineChecker.checkDirectiveCond(\"${VAR:Mpattern1:Mpattern2} == comparison\")",
                "TRACE: 1 + (*MkParser).mkCondAtom(\"${VAR:Mpattern1:Mpattern2} == comparison\")",
                "TRACE: 1 - (*MkParser).mkCondAtom(\"${VAR:Mpattern1:Mpattern2} == comparison\")",
@@ -459,7 +502,10 @@ func (s *Suite) Test_MkLineChecker_check
                "TRACE: 1 2 + (*MkLineImpl).VariableNeedsQuoting(\"VAR\", (*pkglint.Vartype)(nil), (no-type time:parse quoting:plain wordpart:false))",
                "TRACE: 1 2 - (*MkLineImpl).VariableNeedsQuoting(\"VAR\", (*pkglint.Vartype)(nil), (no-type time:parse quoting:plain wordpart:false), \"=>\", unknown)",
                "TRACE: 1 - MkLineChecker.CheckVaruse(filename.mk:1, ${VAR:Mpattern1:Mpattern2}, (no-type time:parse quoting:plain wordpart:false))",
-               "TRACE: - MkLineChecker.checkDirectiveCond(\"${VAR:Mpattern1:Mpattern2} == comparison\")")
+               "TRACE: - MkLineChecker.checkDirectiveCond(\"${VAR:Mpattern1:Mpattern2} == comparison\")",
+               "TRACE: + (*MkParser).mkCondAtom(\"${VAR:Mpattern1:Mpattern2} == comparison\")",
+               "TRACE: - (*MkParser).mkCondAtom(\"${VAR:Mpattern1:Mpattern2} == comparison\")",
+               "TRACE:   Indentation after line 1: [2 (VAR)]")
        t.EnableSilentTracing()
 }
 
@@ -468,11 +514,11 @@ func (s *Suite) Test_MkLineChecker_check
 
        t.SetUpVartypes()
 
-       G.Mk = t.NewMkLines("Makefile",
+       mklines := t.NewMkLines("Makefile",
                MkRcsID,
                "ac_cv_libpari_libs+=\t-L${BUILDLINK_PREFIX.pari}/lib") // From math/clisp-pari/Makefile, rev. 1.8
 
-       MkLineChecker{G.Mk.mklines[1]}.checkVarassign()
+       mklines.Check()
 
        t.CheckOutputLines(
                "WARN: Makefile:2: ac_cv_libpari_libs is defined but not used.")
@@ -483,23 +529,43 @@ func (s *Suite) Test_MkLineChecker_check
 
        t.SetUpVartypes()
        t.SetUpTool("awk", "AWK", AtRunTime)
+       G.Pkgsrc.vartypes.DefineParse("SET_ONLY", lkNone, BtUnknown,
+               "options.mk: set")
+       G.Pkgsrc.vartypes.DefineParse("SET_ONLY_DEFAULT_ELSEWHERE", lkNone, BtUnknown,
+               "options.mk: set",
+               "*.mk: default, set")
        mklines := t.NewMkLines("options.mk",
                MkRcsID,
                "PKG_DEVELOPER?=\tyes",
                "BUILD_DEFS?=\tVARBASE",
                "USE_TOOLS:=\t${USE_TOOLS:Nunwanted-tool}",
                "USE_TOOLS:=\t${MY_TOOLS}",
-               "USE_TOOLS:=\tawk")
+               "USE_TOOLS:=\tawk",
+               "",
+               "SET_ONLY=\tset",
+               "SET_ONLY:=\teval",
+               "SET_ONLY?=\tdefault",
+               "",
+               "SET_ONLY_DEFAULT_ELSEWHERE=\tset",
+               "SET_ONLY_DEFAULT_ELSEWHERE:=\teval",
+               "SET_ONLY_DEFAULT_ELSEWHERE?=\tdefault")
 
        mklines.Check()
 
        t.CheckOutputLines(
-               "WARN: options.mk:2: The variable PKG_DEVELOPER may not be given a default value by any package.",
+               "WARN: options.mk:2: The variable PKG_DEVELOPER should not be given a default value by any package.",
                "WARN: options.mk:2: Please include \"../../mk/bsd.prefs.mk\" before using \"?=\".",
-               "WARN: options.mk:3: The variable BUILD_DEFS may not be given a default value (only appended to) in this file.",
-               "WARN: options.mk:5: The variable USE_TOOLS may not be set (only appended to) in this file.",
+               "WARN: options.mk:3: The variable BUILD_DEFS should not be given a default value (only appended to) in this file.",
+               "WARN: options.mk:4: USE_TOOLS should not be used at load time in this file; "+
+                       "it would be ok in Makefile.common or builtin.mk, but not buildlink3.mk or *.",
                "WARN: options.mk:5: MY_TOOLS is used but not defined.",
-               "WARN: options.mk:6: The variable USE_TOOLS may not be set (only appended to) in this file.")
+               "WARN: options.mk:10: "+
+                       "The variable SET_ONLY should not be given a default value "+
+                       "(only set) in this file.",
+               "WARN: options.mk:14: "+
+                       "The variable SET_ONLY_DEFAULT_ELSEWHERE should not be given a "+
+                       "default value (only set) in this file; it would be ok in *.mk, "+
+                       "but not options.mk.")
 }
 
 func (s *Suite) Test_MkLineChecker_checkVarassignLeftPermissions__no_tracing(c *check.C) {
@@ -523,29 +589,19 @@ func (s *Suite) Test_MkLineChecker_check
        t := s.Init(c)
 
        t.SetUpVartypes()
-       mkline := t.NewMkLine("filename.mk", 123, "LICENSE?=\tgnu-gpl-v2")
-
-       MkLineChecker{mkline}.checkVarassignLeftPermissions()
-
-       t.CheckOutputEmpty()
-}
-
-// Setting a default license doesn't make sense in a package Makefile
-// since that Makefile is only used for a single package.
-// It only makes sense to set the license unconditionally there.
-func (s *Suite) Test_MkLineChecker_checkVarassignLeftPermissions__license_default_Makefile(c *check.C) {
-       t := s.Init(c)
-
-       t.SetUpVartypes()
-       mkline := t.NewMkLine("Makefile", 123, "LICENSE?=\tgnu-gpl-v2")
+       t.SetUpPkgsrc()
+       mklines := t.NewMkLines("filename.mk",
+               MkRcsID,
+               "LICENSE?=\tgnu-gpl-v2")
 
-       MkLineChecker{mkline}.checkVarassignLeftPermissions()
+       mklines.Check()
 
+       // FIXME: LICENSE is a package-settable variable. Therefore bsd.prefs.mk
+       //  does not need to be included before setting a default for this
+       //  variable. Including bsd.prefs.mk is only necessary when setting a
+       //  default value for user-settable or system-defined variables.
        t.CheckOutputLines(
-               "WARN: Makefile:123: " +
-                       "The variable LICENSE may not be given a default value " +
-                       "(only set, or appended to) in this file; " +
-                       "it would be ok in *.")
+               "WARN: filename.mk:2: Please include \"../../mk/bsd.prefs.mk\" before using \"?=\".")
 }
 
 // Don't check the permissions for infrastructure files since they have their own rules.
@@ -569,13 +625,18 @@ func (s *Suite) Test_MkLineChecker_check
 
        t.SetUpVartypes()
 
-       mkline := t.NewMkLine("module.mk", 123, "PLIST_SUBST+=\tLOCALBASE=${LOCALBASE:Q}")
+       mklines := t.NewMkLines("module.mk",
+               MkRcsID,
+               "PLIST_SUBST+=\tLOCALBASE=${LOCALBASE:Q}")
 
-       MkLineChecker{mkline}.checkVarassignRightVaruse()
+       mklines.Check()
 
+       // TODO: Duplicate diagnostics mean twice the work being done.
        t.CheckOutputLines(
-               "WARN: module.mk:123: Please use PREFIX instead of LOCALBASE.",
-               "NOTE: module.mk:123: The :Q operator isn't necessary for ${LOCALBASE} here.")
+               "WARN: module.mk:2: Please use PREFIX instead of LOCALBASE.",
+               "NOTE: module.mk:2: The :Q operator isn't necessary for ${LOCALBASE} here.",
+               "WARN: module.mk:2: Please use PREFIX instead of LOCALBASE.",
+               "NOTE: module.mk:2: The :Q operator isn't necessary for ${LOCALBASE} here.")
 }
 
 func (s *Suite) Test_MkLineChecker_checkVarusePermissions(c *check.C) {
@@ -593,9 +654,9 @@ func (s *Suite) Test_MkLineChecker_check
        mklines.Check()
 
        t.CheckOutputLines(
-               "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.")
+               "WARN: options.mk:3: PKGBASE should not be used at load time in any file.",
+               "WARN: options.mk:4: The variable PYPKGPREFIX should not be set in this file; "+
+                       "it would be ok in pyversion.mk only.")
 }
 
 func (s *Suite) Test_MkLineChecker_checkVarusePermissions__explain(c *check.C) {
@@ -614,7 +675,7 @@ func (s *Suite) Test_MkLineChecker_check
        mklines.Check()
 
        t.CheckOutputLines(
-               "WARN: options.mk:3: PKGBASE should not be evaluated at load time.",
+               "WARN: options.mk:3: PKGBASE should not be used at load time in any file.",
                "",
                "\tMany variables, especially lists of something, get their values",
                "\tincrementally. Therefore it is generally unsafe to rely on their",
@@ -626,7 +687,18 @@ func (s *Suite) Test_MkLineChecker_check
                "\ta single $, so variables that have references to shell variables or",
                "\tregular expressions are modified in a subtle way.",
                "",
-               "WARN: options.mk:4: The variable PYPKGPREFIX may not be set in this file; it would be ok in pyversion.mk.",
+               "\tThe allowed actions for a variable are determined based on the file",
+               "\tname in which the variable is used or defined. The rules for PKGBASE",
+               "\tare:",
+               "",
+               "\t* in buildlink3.mk, it should not be accessed at all",
+               "\t* in any file, it may be used",
+               "",
+               "\tIf these rules seem to be incorrect, please ask on the",
+               "\ttech-pkg%NetBSD.org@localhost mailing list.",
+               "",
+               "WARN: options.mk:4: The variable PYPKGPREFIX should not be set in this file; "+
+                       "it would be ok in pyversion.mk only.",
                "",
                "\tThe allowed actions for a variable are determined based on the file",
                "\tname in which the variable is used or defined. The rules for",
@@ -636,15 +708,7 @@ func (s *Suite) Test_MkLineChecker_check
                "\t* in any file, it may be used at load time, or used",
                "",
                "\tIf these rules seem to be incorrect, please ask on the",
-               "\ttech-pkg%NetBSD.org@localhost mailing list.",
-               "",
-               "WARN: options.mk:4: PKGBASE should not be evaluated indirectly at load time.",
-               "",
-               "\tThe variable on the left-hand side may be evaluated at load time,",
-               "\tbut the variable on the right-hand side may not. Because of the",
-               "\tassignment in this line, the variable might be used indirectly at",
-               "\tload time, before it is guaranteed to be properly initialized.",
-               "")
+               "\ttech-pkg%NetBSD.org@localhost mailing list.", "")
 }
 
 func (s *Suite) Test_MkLineChecker_explainPermissions(c *check.C) {
@@ -653,23 +717,28 @@ func (s *Suite) Test_MkLineChecker_expla
        t.SetUpCommandLine("-Wall", "--explain")
        t.SetUpVartypes()
 
-       mkline := t.NewMkLine("buildlink3.mk", 123, "AUTO_MKDIRS=\tyes")
+       mklines := t.NewMkLines("buildlink3.mk",
+               MkRcsID,
+               "AUTO_MKDIRS=\tyes")
 
-       MkLineChecker{mkline}.Check()
+       mklines.Check()
 
        t.CheckOutputLines(
-               "WARN: buildlink3.mk:123: The variable AUTO_MKDIRS may not be set in this file; "+
-                       "it would be ok in Makefile, Makefile.* or *.mk.",
+               "WARN: buildlink3.mk:2: The variable AUTO_MKDIRS should not be set in this file; "+
+                       "it would be ok in Makefile, Makefile.* or *.mk, "+
+                       "but not buildlink3.mk or builtin.mk.",
                "",
                "\tThe allowed actions for a variable are determined based on the file",
                "\tname in which the variable is used or defined. The rules for",
                "\tAUTO_MKDIRS are:",
                "",
-               "\t* in Makefile, it may be set, or used",
-               "\t* in buildlink3.mk, it may not be accessed at all",
-               "\t* in builtin.mk, it may not be accessed at all",
+               "\t* in buildlink3.mk, it should not be accessed at all",
+               "\t* in builtin.mk, it should not be accessed at all",
+               "\t* in Makefile, it may be set, given a default value, or used",
                "\t* in Makefile.*, it may be set, given a default value, or used",
                "\t* in *.mk, it may be set, given a default value, or used",
+               // TODO: Add a check for infrastructure permissions
+               //  when the "infra:" prefix is added.
                "",
                "\tIf these rules seem to be incorrect, please ask on the",
                "\ttech-pkg%NetBSD.org@localhost mailing list.",
@@ -696,6 +765,44 @@ func (s *Suite) Test_MkLineChecker_check
                "NOTE: options.mk:2: This variable value should be aligned to column 17.")
 }
 
+func (s *Suite) Test_MkLineChecker_checkVarusePermissions__load_time_in_condition(c *check.C) {
+       t := s.Init(c)
+
+       G.Pkgsrc.vartypes.DefineParse("LOAD_TIME", lkShell, BtPathmask,
+               "special:filename.mk: use-loadtime")
+       G.Pkgsrc.vartypes.DefineParse("RUN_TIME", lkShell, BtPathmask,
+               "special:filename.mk: use")
+
+       mklines := t.NewMkLines("filename.mk",
+               MkRcsID,
+               ".if ${LOAD_TIME} && ${RUN_TIME}",
+               ".endif")
+
+       mklines.Check()
+
+       t.CheckOutputLines(
+               "WARN: filename.mk:2: RUN_TIME should not be used at load time in any file.")
+}
+
+func (s *Suite) Test_MkLineChecker_checkVarusePermissions__load_time_in_for_loop(c *check.C) {
+       t := s.Init(c)
+
+       G.Pkgsrc.vartypes.DefineParse("LOAD_TIME", lkShell, BtPathmask,
+               "special:filename.mk: use-loadtime")
+       G.Pkgsrc.vartypes.DefineParse("RUN_TIME", lkShell, BtPathmask,
+               "special:filename.mk: use")
+
+       mklines := t.NewMkLines("filename.mk",
+               MkRcsID,
+               ".for pattern in ${LOAD_TIME} ${RUN_TIME}",
+               ".endfor")
+
+       mklines.Check()
+
+       t.CheckOutputLines(
+               "WARN: filename.mk:2: RUN_TIME should not be used at load time in any file.")
+}
+
 func (s *Suite) Test_MkLineChecker_checkVarusePermissions__load_time_guessed(c *check.C) {
        t := s.Init(c)
 
@@ -722,6 +829,45 @@ func (s *Suite) Test_MkLineChecker_check
        t.CheckOutputEmpty()
 }
 
+// Ensures that the warning "should not be evaluated at load time" is issued
+// only if using the variable at run time is allowed. If the latter were not
+// allowed, this warning would be confusing.
+func (s *Suite) Test_MkLineChecker_checkVarusePermissions__load_time_run_time(c *check.C) {
+       t := s.Init(c)
+
+       G.Pkgsrc.vartypes.DefineParse("LOAD_TIME", lkNone, BtUnknown,
+               "*.mk: use, use-loadtime")
+       G.Pkgsrc.vartypes.DefineParse("RUN_TIME", lkNone, BtUnknown,
+               "*.mk: use")
+       G.Pkgsrc.vartypes.DefineParse("WRITE_ONLY", lkNone, BtUnknown,
+               "*.mk: set")
+       G.Pkgsrc.vartypes.DefineParse("LOAD_TIME_ELSEWHERE", lkNone, BtUnknown,
+               "Makefile: use-loadtime",
+               "*.mk: set")
+       G.Pkgsrc.vartypes.DefineParse("RUN_TIME_ELSEWHERE", lkNone, BtUnknown,
+               "Makefile: use",
+               "*.mk: set")
+
+       mklines := t.NewMkLines("filename.mk",
+               MkRcsID,
+               ".if ${LOAD_TIME} && ${RUN_TIME} && ${WRITE_ONLY}",
+               ".elif ${LOAD_TIME_ELSEWHERE} && ${RUN_TIME_ELSEWHERE}",
+               ".endif")
+
+       mklines.Check()
+
+       t.CheckOutputLines(
+               "WARN: filename.mk:2: RUN_TIME should not be used at load time in any file.",
+               "WARN: filename.mk:2: "+
+                       "WRITE_ONLY should not be used in any file; "+
+                       "it is a write-only variable.",
+               "WARN: filename.mk:3: "+
+                       "LOAD_TIME_ELSEWHERE should not be used at load time in this file; "+
+                       "it would be ok in Makefile, but not *.mk.",
+               "WARN: filename.mk:3: "+
+                       "RUN_TIME_ELSEWHERE should not be used at load time in any file.")
+}
+
 func (s *Suite) Test_MkLineChecker_checkVarusePermissions__PKGREVISION(c *check.C) {
        t := s.Init(c)
 
@@ -736,8 +882,7 @@ func (s *Suite) Test_MkLineChecker_check
        // Since PKGREVISION may only be set in the package Makefile directly,
        // there is no other file that could be mentioned as "it would be ok in".
        t.CheckOutputLines(
-               "WARN: any.mk:2: PKGREVISION should not be evaluated at load time.",
-               "WARN: any.mk:2: PKGREVISION may not be used in any file; it is a write-only variable.")
+               "WARN: any.mk:2: PKGREVISION should not be used in any file; it is a write-only variable.")
 }
 
 func (s *Suite) Test_MkLineChecker_checkVarusePermissions__indirectly(c *check.C) {
@@ -752,7 +897,7 @@ func (s *Suite) Test_MkLineChecker_check
 
        t.CheckOutputLines(
                "WARN: file.mk:2: IGNORE_PKG.package should be set to YES or yes.",
-               "WARN: file.mk:2: ONLY_FOR_UNPRIVILEGED should not be evaluated indirectly at load time.")
+               "WARN: file.mk:2: ONLY_FOR_UNPRIVILEGED should not be used indirectly at load time (via IGNORE_PKG.package).")
 }
 
 // This test is only here for branch coverage.
@@ -768,8 +913,7 @@ func (s *Suite) Test_MkLineChecker_check
        mklines.Check()
 
        t.CheckOutputLines(
-               "WARN: file.mk:2: PKGREVISION should not be evaluated indirectly at load time.",
-               "WARN: file.mk:2: PKGREVISION may not be used in any file; it is a write-only variable.")
+               "WARN: file.mk:2: PKGREVISION should not be used in any file; it is a write-only variable.")
 }
 
 func (s *Suite) Test_MkLineChecker_checkVarusePermissions__write_only_usable_in_other_file(c *check.C) {
@@ -784,58 +928,105 @@ func (s *Suite) Test_MkLineChecker_check
 
        t.CheckOutputLines(
                "WARN: buildlink3.mk:2: " +
-                       "AUTO_MKDIRS may not be used in this file; " +
-                       "it would be ok in Makefile, Makefile.* or *.mk.")
+                       "AUTO_MKDIRS should not be used in this file; " +
+                       "it would be ok in Makefile, Makefile.* or *.mk, " +
+                       "but not buildlink3.mk or builtin.mk.")
 }
 
-func (s *Suite) Test_MkLineChecker_checkVarusePermissions__multiple_times_per_file(c *check.C) {
+func (s *Suite) Test_MkLineChecker_checkVarusePermissions__usable_only_at_loadtime_in_other_file(c *check.C) {
        t := s.Init(c)
 
-       t.SetUpVartypes()
+       G.Pkgsrc.vartypes.DefineParse("VAR", lkNone, BtFileName,
+               "*: set, use-loadtime")
+       mklines := t.NewMkLines("Makefile",
+               MkRcsID,
+               "VAR=\t${VAR}")
+
+       mklines.Check()
+
+       // Since the variable is usable at load time, pkglint assumes it is also
+       // usable at run time. This is not the case for VAR, but probably doesn't
+       // happen in practice anyway.
+       t.CheckOutputEmpty()
+}
+
+func (s *Suite) Test_MkLineChecker_checkVarusePermissions__assigned_to_infrastructure_variable(c *check.C) {
+       t := s.Init(c)
+
+       // This combination of BtUnknown and all permissions is typical for
+       // otherwise unknown variables from the pkgsrc infrastructure.
+       G.Pkgsrc.vartypes.Define("INFRA", lkNone, BtUnknown,
+               ACLEntry{"*", aclpAll})
+       G.Pkgsrc.vartypes.DefineParse("VAR", lkNone, BtUnknown,
+               "buildlink3.mk: none",
+               "*: use")
        mklines := t.NewMkLines("buildlink3.mk",
                MkRcsID,
-               "VAR=\t${VAR} ${AUTO_MKDIRS} ${AUTO_MKDIRS} ${PKGREVISION} ${PKGREVISION}",
-               "VAR=\t${VAR} ${AUTO_MKDIRS} ${AUTO_MKDIRS} ${PKGREVISION} ${PKGREVISION}")
+               "INFRA=\t${VAR}")
+
+       mklines.Check()
+
+       // Since INFRA is defined in the infrastructure and pkglint
+       // knows nothing else about this variable, it assumes that INFRA
+       // may be used at load time. This is done to prevent wrong warnings.
+       //
+       // This in turn has consequences when INFRA is used on the left-hand
+       // side of an assignment since pkglint assumes that the right-hand
+       // side may now be evaluated at load time.
+       //
+       // Therefore the check is skipped when such a variable appears at the
+       // left-hand side of an assignment.
+       //
+       // Even in this case involving an unknown infrastructure variable,
+       // it is possible to issue a warning since VAR should not be used at all,
+       // independent of any properties of INFRA.
+       t.CheckOutputEmpty()
+}
+
+func (s *Suite) Test_MkLineChecker_checkVarusePermissions__assigned_to_load_time(c *check.C) {
+       t := s.Init(c)
+
+       // LOAD_TIME may be used at load time in other.mk.
+       // Since VAR must not be used at load time at all, it would be dangerous
+       // to use its value in LOAD_TIME, as the latter might be evaluated later
+       // at load time, and at that point VAR would be evaluated as well.
+
+       G.Pkgsrc.vartypes.DefineParse("LOAD_TIME", lkNone, BtMessage,
+               "buildlink3.mk: set",
+               "*.mk: use-loadtime")
+       G.Pkgsrc.vartypes.DefineParse("VAR", lkNone, BtUnknown,
+               "buildlink3.mk: none",
+               "*.mk: use")
+       mklines := t.NewMkLines("buildlink3.mk",
+               MkRcsID,
+               "LOAD_TIME=\t${VAR}")
 
        mklines.Check()
 
-       // Since these warnings are valid for the whole file, duplicates are suppressed.
        t.CheckOutputLines(
-               "WARN: buildlink3.mk:2: "+
-                       "AUTO_MKDIRS may not be used in this file; "+
-                       "it would be ok in Makefile, Makefile.* or *.mk.",
-               "WARN: buildlink3.mk:2: "+
-                       "PKGREVISION may not be used in any file; "+
-                       "it is a write-only variable.")
+               "WARN: buildlink3.mk:2: VAR should not be used indirectly " +
+                       "at load time (via LOAD_TIME).")
 }
 
-// In some pkglint tests, the method is called directly without G.Mk being set.
-// In practice this doesn't happen.
-func (s *Suite) Test_MkLineChecker_checkVarusePermissions__without_mklines(c *check.C) {
+func (s *Suite) Test_MkLineChecker_checkVarusePermissions__multiple_times_per_file(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
-       mkline := t.NewMkLine("buildlink3.mk", 123,
+       mklines := t.NewMkLines("buildlink3.mk",
+               MkRcsID,
+               "VAR=\t${VAR} ${AUTO_MKDIRS} ${AUTO_MKDIRS} ${PKGREVISION} ${PKGREVISION}",
                "VAR=\t${VAR} ${AUTO_MKDIRS} ${AUTO_MKDIRS} ${PKGREVISION} ${PKGREVISION}")
 
-       MkLineChecker{mkline}.Check()
+       mklines.Check()
 
-       // Since G.Mk is not set, the duplicates are not suppressed.
-       // Therefore in this case there are more warnings than in realistic situations.
+       // Since these warnings are valid for the whole file, duplicates are suppressed.
        t.CheckOutputLines(
-               "WARN: buildlink3.mk:123: VAR is defined but not used.",
-               "WARN: buildlink3.mk:123: VAR is used but not defined.",
-               "WARN: buildlink3.mk:123: "+
-                       "AUTO_MKDIRS may not be used in this file; "+
-                       "it would be ok in Makefile, Makefile.* or *.mk.",
-               "WARN: buildlink3.mk:123: "+
-                       "AUTO_MKDIRS may not be used in this file; "+
-                       "it would be ok in Makefile, Makefile.* or *.mk.",
-               "WARN: buildlink3.mk:123: "+
-                       "PKGREVISION may not be used in any file; "+
-                       "it is a write-only variable.",
-               "WARN: buildlink3.mk:123: "+
-                       "PKGREVISION may not be used in any file; "+
+               "WARN: buildlink3.mk:2: "+
+                       "AUTO_MKDIRS should not be used in this file; "+
+                       "it would be ok in Makefile, Makefile.* or *.mk, "+
+                       "but not buildlink3.mk or builtin.mk.",
+               "WARN: buildlink3.mk:2: "+
+                       "PKGREVISION should not be used in any file; "+
                        "it is a write-only variable.")
 }
 
@@ -937,12 +1128,14 @@ func (s *Suite) Test_MkLineChecker_Check
        t := s.Init(c)
 
        t.SetUpVartypes()
-       mkline := t.NewMkLine("options.mk", 56, "PKGNAME=${LOCALBASE}")
+       mklines := t.NewMkLines("options.mk",
+               MkRcsID,
+               "PKGNAME=\t${LOCALBASE}")
 
-       MkLineChecker{mkline}.Check()
+       mklines.Check()
 
        t.CheckOutputLines(
-               "WARN: options.mk:56: Please use PREFIX instead of LOCALBASE.")
+               "WARN: options.mk:2: Please use PREFIX instead of LOCALBASE.")
 }
 
 func (s *Suite) Test_MkLineChecker_CheckRelativePkgdir(c *check.C) {
@@ -952,11 +1145,14 @@ func (s *Suite) Test_MkLineChecker_Check
        // Must be in the filesystem because of directory references.
        mklines := t.SetUpFileMkLines("category/package/Makefile",
                "# dummy")
-       ck := MkLineChecker{mklines.mklines[0]}
 
-       ck.CheckRelativePkgdir("../pkgbase")
-       ck.CheckRelativePkgdir("../../other/package")
-       ck.CheckRelativePkgdir("../../other/does-not-exist")
+       mklines.ForEach(func(mkline MkLine) {
+               ck := MkLineChecker{mkline}
+
+               ck.CheckRelativePkgdir("../pkgbase")
+               ck.CheckRelativePkgdir("../../other/package")
+               ck.CheckRelativePkgdir("../../other/does-not-exist")
+       })
 
        // FIXME: The diagnostics for does-not-exist are redundant.
        t.CheckOutputLines(
@@ -1092,13 +1288,13 @@ func (s *Suite) Test_MkLineChecker_check
        t := s.Init(c)
 
        t.SetUpVartypes()
-       G.Mk = t.NewMkLines("Makefile",
+       mklines := t.NewMkLines("Makefile",
                MkRcsID,
                ".if ${PKGSRC_COMPILER} == \"clang\"",
                ".elif ${PKGSRC_COMPILER} != \"gcc\"",
                ".endif")
 
-       G.Mk.Check()
+       mklines.Check()
 
        t.CheckOutputLines(
                "WARN: Makefile:2: Use ${PKGSRC_COMPILER:Mclang} instead of the == operator.",
@@ -1426,9 +1622,14 @@ func (s *Suite) Test_MkLineChecker_Check
 
        G.Check(t.File("mk/infra.mk"))
 
-       // No warnings about LOCALBASE being used; in packages LOCALBASE is deprecated.
+       // No warnings about LOCALBASE being used; the infrastructure files may
+       // do this. In packages though, LOCALBASE is deprecated.
+
+       // There is no warning about DEFAULT_PREFIX being "defined but not used"
+       // since Pkgsrc.loadUntypedVars calls Pkgsrc.vartypes.DefineType, which
+       // registers that variable globally.
        t.CheckOutputLines(
-               "WARN: ~/mk/infra.mk:2: PREFIX should not be evaluated indirectly at load time.")
+               "WARN: ~/mk/infra.mk:2: PREFIX should not be used indirectly at load time (via LOCALBASE).")
 }
 
 func (s *Suite) Test_MkLineChecker_CheckVaruse__user_defined_variable_and_BUILD_DEFS(c *check.C) {
@@ -1527,9 +1728,11 @@ func (s *Suite) Test_MkLineChecker_check
        t := s.Init(c)
 
        t.SetUpTool("echo", "ECHO", AfterPrefsMk)
-       mkline := t.NewMkLine("net/uucp/Makefile", 123, "\techo ${UUCP_${var}}")
+       mklines := t.NewMkLines("net/uucp/Makefile",
+               MkRcsID,
+               "\techo ${UUCP_${var}}")
 
-       MkLineChecker{mkline}.Check()
+       mklines.Check()
 
        // No warning about UUCP_${var} being used but not defined.
        //
@@ -1539,7 +1742,7 @@ func (s *Suite) Test_MkLineChecker_check
        //
        // It does warn about simple variable names though, like ${var} in this example.
        t.CheckOutputLines(
-               "WARN: net/uucp/Makefile:123: var is used but not defined.")
+               "WARN: net/uucp/Makefile:2: var is used but not defined.")
 }
 
 func (s *Suite) Test_MkLineChecker_checkVarassignMisc(c *check.C) {

Index: pkgsrc/pkgtools/pkglint/files/mklines.go
diff -u pkgsrc/pkgtools/pkglint/files/mklines.go:1.43 pkgsrc/pkgtools/pkglint/files/mklines.go:1.44
--- pkgsrc/pkgtools/pkglint/files/mklines.go:1.43       Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/mklines.go    Sun Mar 24 13:58:38 2019
@@ -244,7 +244,7 @@ func (mklines *MkLinesImpl) collectDefin
                        continue
                }
 
-               defineVar(mkline, mkline.Varname())
+               defineVar(G.Pkg, mklines, mkline, mkline.Varname())
 
                varcanon := mkline.Varcanon()
                switch varcanon {
@@ -291,7 +291,7 @@ func (mklines *MkLinesImpl) collectDefin
                case "OPSYSVARS":
                        for _, opsysVar := range mkline.Fields() {
                                mklines.UseVar(mkline, opsysVar+".*")
-                               defineVar(mkline, opsysVar)
+                               defineVar(G.Pkg, mklines, mkline, opsysVar)
                        }
                }
        }
@@ -352,6 +352,8 @@ func (mklines *MkLinesImpl) collectDocum
        //  "list of" and other types.
 
        finish := func() {
+               // The commentLines include the the line containing the variable name,
+               // leaving 2 of these 3 lines for the actual documentation.
                if commentLines >= 3 && relevant {
                        for varname, mkline := range scope.used {
                                mklines.vars.Define(varname, mkline)
Index: pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.43 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.44
--- pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.43     Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go  Sun Mar 24 13:58:38 2019
@@ -625,10 +625,14 @@ func (s *Suite) Test_VartypeCheck_Licens
        t := s.Init(c)
        t.SetUpPkgsrc() // Adds the gnu-gpl-v2 and 2-clause-bsd licenses
 
-       G.Mk = t.NewMkLines("perl5.mk",
+       t.SetUpPackage("category/package")
+       G.Pkg = NewPackage(t.File("category/package"))
+
+       mklines := t.NewMkLines("perl5.mk",
                MkRcsID,
                "PERL5_LICENSE= gnu-gpl-v2 OR artistic")
-       G.Mk.collectDefinedVariables()
+       // Also registers the PERL5_LICENSE variable in the package.
+       mklines.collectDefinedVariables()
 
        vt := NewVartypeCheckTester(t, (*VartypeCheck).License)
 
@@ -1402,26 +1406,26 @@ func (vt *VartypeCheckTester) Op(op MkOp
 // Each value is interpreted as if it were written verbatim into a Makefile line.
 // That is, # starts a comment, and for the opUseMatch operator, all closing braces must be escaped.
 func (vt *VartypeCheckTester) Values(values ...string) {
-       for _, value := range values {
+
+       toText := func(value string) string {
                op := vt.op
                opStr := op.String()
                varname := vt.varname
 
-               var text string
-               switch {
-               case contains(opStr, "="):
-                       if hasSuffix(varname, "+") && opStr == "=" {
-                               text = varname + " " + opStr + value
-                       } else {
-                               text = varname + opStr + value
-                       }
-               case op == opUseMatch:
-                       text = sprintf(".if ${%s:M%s} == \"\"", varname, value)
-               default:
+               if op == opUseMatch {
+                       return sprintf(".if ${%s:M%s} == \"\"", varname, value)
+               }
+
+               if !contains(opStr, "=") {
                        panic("Invalid operator: " + opStr)
                }
 
-               mkline := vt.tester.NewMkLine(vt.filename, vt.lineno, text)
+               space := ifelseStr(hasSuffix(varname, "+") && opStr == "=", " ", "")
+               return varname + space + opStr + value
+       }
+
+       test := func(mkline MkLine, value string) {
+               varname := vt.varname
                comment := ""
                if mkline.IsVarassign() {
                        mkline.Tokenize(value, true) // Produce some warnings as side-effects.
@@ -1447,11 +1451,19 @@ func (vt *VartypeCheckTester) Values(val
 
                for _, lineValue := range lineValues {
                        valueNovar := mkline.WithoutMakeVariables(lineValue)
-                       vc := VartypeCheck{mkline, varname, op, lineValue, valueNovar, comment, false}
+                       vc := VartypeCheck{mkline, varname, vt.op, lineValue, valueNovar, comment, false}
                        vt.checker(&vc)
                }
+       }
 
+       for _, value := range values {
+               text := toText(value)
+
+               line := vt.tester.NewLine(vt.filename, vt.lineno, text)
+               mklines := NewMkLines(NewLines(vt.filename, []Line{line}))
                vt.lineno++
+
+               mklines.ForEach(func(mkline MkLine) { test(mkline, value) })
        }
 }
 

Index: pkgsrc/pkgtools/pkglint/files/mklines_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.38 pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.39
--- pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.38  Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/mklines_test.go       Sun Mar 24 13:58:38 2019
@@ -234,11 +234,7 @@ func (s *Suite) Test_MkLines_CheckForUse
 
                mklines.CheckForUsedComment(pkgpath)
 
-               if len(diagnostics) > 0 {
-                       t.CheckOutputLines(diagnostics...)
-               } else {
-                       t.CheckOutputEmpty()
-               }
+               t.CheckOutput(diagnostics)
        }
 
        lines := func(lines ...string) []string { return lines }
@@ -315,7 +311,6 @@ func (s *Suite) Test_MkLines_collectDefi
                MkRcsID,
                "",
                "USE_TOOLS+=             autoconf213 autoconf",
-               "USE_TOOLS:=             ${USE_TOOLS:Ntbl}",
                "",
                "OPSYSVARS+=             OSV",
                "OSV.NetBSD=             NetBSD-specific value",
@@ -334,7 +329,8 @@ func (s *Suite) Test_MkLines_collectDefi
 
        // The tools autoreconf and autoheader213 are known at this point because of the USE_TOOLS line.
        // The SUV variable is used implicitly by the SUBST framework, therefore no warning.
-       // The OSV.NetBSD variable is used implicitly via the OSV variable, therefore no warning.
+       // The OSV.NetBSD variable is used indirectly because OSV is declared
+       // as being OPSYS-specific, therefore no warning.
        t.CheckOutputEmpty()
 }
 
@@ -416,6 +412,8 @@ func (s *Suite) Test_MkLines__private_to
                "WARN: filename.mk:3: Unknown shell command \"md5sum\".")
 }
 
+// Tools that are defined by a package by adding to TOOLS_CREATE can
+// be used without adding them to USE_TOOLS again.
 func (s *Suite) Test_MkLines__private_tool_defined(c *check.C) {
        t := s.Init(c)
 
@@ -428,9 +426,7 @@ func (s *Suite) Test_MkLines__private_to
 
        mklines.Check()
 
-       // TODO: Is it necessary to add the tool to USE_TOOLS? If not, why not?
-       t.CheckOutputLines(
-               "WARN: filename.mk:4: The \"md5sum\" tool is used but not added to USE_TOOLS.")
+       t.CheckOutputEmpty()
 }
 
 func (s *Suite) Test_MkLines_Check__indentation(c *check.C) {
Index: pkgsrc/pkgtools/pkglint/files/plist.go
diff -u pkgsrc/pkgtools/pkglint/files/plist.go:1.38 pkgsrc/pkgtools/pkglint/files/plist.go:1.39
--- pkgsrc/pkgtools/pkglint/files/plist.go:1.38 Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/plist.go      Sun Mar 24 13:58:38 2019
@@ -7,7 +7,7 @@ import (
        "strings"
 )
 
-func CheckLinesPlist(lines Lines) {
+func CheckLinesPlist(pkg *Package, lines Lines) {
        if trace.Tracing {
                defer trace.Call1(lines.FileName)()
        }
@@ -28,18 +28,22 @@ func CheckLinesPlist(lines Lines) {
        }
 
        ck := PlistChecker{
+               pkg,
                make(map[string]*PlistLine),
                make(map[string]*PlistLine),
                "",
-               Once{}}
+               Once{},
+               false}
        ck.Check(lines)
 }
 
 type PlistChecker struct {
-       allFiles  map[string]*PlistLine
-       allDirs   map[string]*PlistLine
-       lastFname string
-       once      Once
+       pkg             *Package
+       allFiles        map[string]*PlistLine
+       allDirs         map[string]*PlistLine
+       lastFname       string
+       once            Once
+       nonAsciiAllowed bool
 }
 
 type PlistLine struct {
@@ -132,6 +136,7 @@ func (ck *PlistChecker) checkLine(pline 
 
        } else if m, cmd, arg := match2(text, `^@([a-z-]+)[\t ]*(.*)`); m {
                pline.CheckDirective(cmd, arg)
+               ck.nonAsciiAllowed = pline.firstLine > 1
 
        } else {
                pline.Warnf("Invalid line type: %s", pline.Line.Text)
@@ -143,6 +148,7 @@ func (ck *PlistChecker) checkPath(pline 
        dirSlash, basename := path.Split(text)
        dirname := strings.TrimSuffix(dirSlash, "/")
 
+       ck.checkPathNonAscii(pline)
        ck.checkSorted(pline)
        ck.checkDuplicate(pline)
 
@@ -182,7 +188,7 @@ func (ck *PlistChecker) checkPath(pline 
                ck.checkPathShare(pline)
        }
 
-       if contains(text, "${PKGLOCALEDIR}") && G.Pkg != nil && !G.Pkg.vars.Defined("USE_PKGLOCALEDIR") {
+       if contains(text, "${PKGLOCALEDIR}") && ck.pkg != nil && !ck.pkg.vars.Defined("USE_PKGLOCALEDIR") {
                pline.Warnf("PLIST contains ${PKGLOCALEDIR}, but USE_PKGLOCALEDIR is not set in the package Makefile.")
        }
 
@@ -203,6 +209,34 @@ func (ck *PlistChecker) checkPath(pline 
        }
 }
 
+func (ck *PlistChecker) checkPathNonAscii(pline *PlistLine) {
+       text := pline.text
+
+       lex := textproc.NewLexer(text)
+       lex.NextBytesFunc(func(b byte) bool { return b >= ' ' && b <= '~' })
+       ascii := lex.EOF()
+
+       switch {
+       case !ck.nonAsciiAllowed && !ascii:
+               ck.nonAsciiAllowed = true
+
+               pline.Warnf("Non-ASCII filename %q.", escapePrintable(text))
+               pline.Explain(
+                       "The great majority of filenames installed by pkgsrc packages",
+                       "are ASCII-only. Filenames containing non-ASCII characters",
+                       "can cause various problems since their name may already be",
+                       "different when another character encoding is set in the locale.",
+                       "",
+                       "To mark a filename as intentionally non-ASCII, insert a PLIST",
+                       "@comment with a convincing reason directly above this line.",
+                       "That comment will allow this line and the lines directly",
+                       "below it to contain non-ASCII filenames.")
+
+       case ck.nonAsciiAllowed && ascii:
+               ck.nonAsciiAllowed = false
+       }
+}
+
 func (ck *PlistChecker) checkSorted(pline *PlistLine) {
        if text := pline.text; hasAlnumPrefix(text) && !containsVarRef(text) {
                if ck.lastFname != "" {
@@ -265,17 +299,19 @@ func (ck *PlistChecker) checkPathInfo(pl
                return
        }
 
-       if G.Pkg != nil && !G.Pkg.vars.Defined("INFO_FILES") {
+       if ck.pkg != nil && !ck.pkg.vars.Defined("INFO_FILES") {
                pline.Warnf("Packages that install info files should set INFO_FILES in the Makefile.")
        }
 }
 
 func (ck *PlistChecker) checkPathLib(pline *PlistLine, dirname, basename string) {
+       pkg := ck.pkg
+
        switch {
-       case G.Pkg != nil && G.Pkg.EffectivePkgbase != "" && hasPrefix(pline.text, "lib/"+G.Pkg.EffectivePkgbase+"/"):
+       case pkg != nil && pkg.EffectivePkgbase != "" && hasPrefix(pline.text, "lib/"+pkg.EffectivePkgbase+"/"):
                return
 
-       case pline.text == "lib/charset.alias" && (G.Pkg == nil || G.Pkg.Pkgpath != "converters/libiconv"):
+       case pline.text == "lib/charset.alias" && (pkg == nil || pkg.Pkgpath != "converters/libiconv"):
                pline.Errorf("Only the libiconv package may install lib/charset.alias.")
                return
 
@@ -286,7 +322,7 @@ func (ck *PlistChecker) checkPathLib(pli
 
        switch ext := path.Ext(basename); ext {
        case ".la":
-               if G.Pkg != nil && !G.Pkg.vars.Defined("USE_LIBTOOL") && ck.once.FirstTime("USE_LIBTOOL") {
+               if pkg != nil && !pkg.vars.Defined("USE_LIBTOOL") && ck.once.FirstTime("USE_LIBTOOL") {
                        pline.Warnf("Packages that install libtool libraries should define USE_LIBTOOL.")
                }
        }
@@ -339,17 +375,19 @@ func (ck *PlistChecker) checkPathMan(pli
 }
 
 func (ck *PlistChecker) checkPathShare(pline *PlistLine) {
+       pkg := ck.pkg
        text := pline.text
+
        switch {
-       case hasPrefix(text, "share/icons/") && G.Pkg != nil:
-               if hasPrefix(text, "share/icons/hicolor/") && G.Pkg.Pkgpath != "graphics/hicolor-icon-theme" {
+       case hasPrefix(text, "share/icons/") && pkg != nil:
+               if hasPrefix(text, "share/icons/hicolor/") && pkg.Pkgpath != "graphics/hicolor-icon-theme" {
                        f := "../../graphics/hicolor-icon-theme/buildlink3.mk"
-                       if !G.Pkg.included.Seen(f) && ck.once.FirstTime("hicolor-icon-theme") {
+                       if !pkg.included.Seen(f) && ck.once.FirstTime("hicolor-icon-theme") {
                                pline.Errorf("Packages that install hicolor icons must include %q in the Makefile.", f)
                        }
                }
 
-               if text == "share/icons/hicolor/icon-theme.cache" && G.Pkg.Pkgpath != "graphics/hicolor-icon-theme" {
+               if text == "share/icons/hicolor/icon-theme.cache" && pkg.Pkgpath != "graphics/hicolor-icon-theme" {
                        pline.Errorf("The file icon-theme.cache must not appear in any PLIST file.")
                        G.Explain(
                                "Remove this line and add the following line to the package Makefile.",
@@ -357,9 +395,9 @@ func (ck *PlistChecker) checkPathShare(p
                                ".include \"../../graphics/hicolor-icon-theme/buildlink3.mk\"")
                }
 
-               if hasPrefix(text, "share/icons/gnome") && G.Pkg.Pkgpath != "graphics/gnome-icon-theme" {
+               if hasPrefix(text, "share/icons/gnome") && pkg.Pkgpath != "graphics/gnome-icon-theme" {
                        f := "../../graphics/gnome-icon-theme/buildlink3.mk"
-                       if !G.Pkg.included.Seen(f) {
+                       if !pkg.included.Seen(f) {
                                pline.Errorf("The package Makefile must include %q.", f)
                                G.Explain(
                                        "Packages that install GNOME icons must maintain the icon theme",
@@ -367,15 +405,15 @@ func (ck *PlistChecker) checkPathShare(p
                        }
                }
 
-               if contains(text[12:], "/") && !G.Pkg.vars.Defined("ICON_THEMES") && ck.once.FirstTime("ICON_THEMES") {
+               if contains(text[12:], "/") && !pkg.vars.Defined("ICON_THEMES") && ck.once.FirstTime("ICON_THEMES") {
                        pline.Warnf("Packages that install icon theme files should set ICON_THEMES.")
                }
 
        case hasPrefix(text, "share/doc/html/"):
                pline.Warnf("Use of \"share/doc/html\" is deprecated. Use \"share/doc/${PKGBASE}\" instead.")
 
-       case G.Pkg != nil && G.Pkg.EffectivePkgbase != "" && (hasPrefix(text, "share/doc/"+G.Pkg.EffectivePkgbase+"/") ||
-               hasPrefix(text, "share/examples/"+G.Pkg.EffectivePkgbase+"/")):
+       case pkg != nil && pkg.EffectivePkgbase != "" && (hasPrefix(text, "share/doc/"+pkg.EffectivePkgbase+"/") ||
+               hasPrefix(text, "share/examples/"+pkg.EffectivePkgbase+"/")):
                // Fine.
 
        case hasPrefix(text, "share/info/"):

Index: pkgsrc/pkgtools/pkglint/files/mklines_varalign_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mklines_varalign_test.go:1.8 pkgsrc/pkgtools/pkglint/files/mklines_varalign_test.go:1.9
--- pkgsrc/pkgtools/pkglint/files/mklines_varalign_test.go:1.8  Sun Jan 13 19:55:52 2019
+++ pkgsrc/pkgtools/pkglint/files/mklines_varalign_test.go      Sun Mar 24 13:58:38 2019
@@ -66,20 +66,12 @@ func (vt *VaralignTester) run(autofix bo
        varalign.Finish()
 
        if autofix {
-               if len(vt.autofixes) > 0 {
-                       t.CheckOutputLines(vt.autofixes...)
-               } else {
-                       t.CheckOutputEmpty()
-               }
+               t.CheckOutput(vt.autofixes)
 
                SaveAutofixChanges(mklines.lines)
                t.CheckFileLinesDetab("Makefile", vt.fixed...)
        } else {
-               if len(vt.diagnostics) > 0 {
-                       t.CheckOutputLines(vt.diagnostics...)
-               } else {
-                       t.CheckOutputEmpty()
-               }
+               t.CheckOutput(vt.diagnostics)
        }
 }
 

Index: pkgsrc/pkgtools/pkglint/files/mkparser_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.24 pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.25
--- pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.24 Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/mkparser_test.go      Sun Mar 24 13:58:38 2019
@@ -559,11 +559,7 @@ func (s *Suite) Test_MkParser_VarUseModi
 
                t.Check(actual, deepEquals, varUse)
                t.Check(p.Rest(), equals, rest)
-               if len(diagnostics) > 0 {
-                       t.CheckOutputLines(diagnostics...)
-               } else {
-                       t.CheckOutputEmpty()
-               }
+               t.CheckOutput(diagnostics)
        }
 
        // The !command! modifier is used so seldom that pkglint does not
@@ -599,11 +595,7 @@ func (s *Suite) Test_MkParser_varUseModi
 
                t.Check(actual, deepEquals, varUse)
                t.Check(p.Rest(), equals, rest)
-               if len(diagnostics) > 0 {
-                       t.CheckOutputLines(diagnostics...)
-               } else {
-                       t.CheckOutputEmpty()
-               }
+               t.CheckOutput(diagnostics)
        }
 
        test("${VAR:S", nil, "${VAR:S",
@@ -643,11 +635,7 @@ func (s *Suite) Test_MkParser_varUseModi
 
                t.Check(actual, deepEquals, varUse)
                t.Check(p.Rest(), equals, rest)
-               if len(diagnostics) > 0 {
-                       t.CheckOutputLines(diagnostics...)
-               } else {
-                       t.CheckOutputEmpty()
-               }
+               t.CheckOutput(diagnostics)
        }
 
        test("${VAR:@", nil, "${VAR:@",
Index: pkgsrc/pkgtools/pkglint/files/util_test.go
diff -u pkgsrc/pkgtools/pkglint/files/util_test.go:1.24 pkgsrc/pkgtools/pkglint/files/util_test.go:1.25
--- pkgsrc/pkgtools/pkglint/files/util_test.go:1.24     Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/util_test.go  Sun Mar 24 13:58:38 2019
@@ -3,7 +3,6 @@ package pkglint
 import (
        "gopkg.in/check.v1"
        "os"
-       "runtime"
        "testing"
        "time"
 )
@@ -50,14 +49,6 @@ func (s *Suite) Test__regex_ReplaceFirst
        c.Check(rest, equals, "X+c+d")
 }
 
-func (s *Suite) Test_mustMatch(c *check.C) {
-       t := s.Init(c)
-
-       t.ExpectPanic(
-               func() { mustMatch("aaa", `b`) },
-               "Pkglint internal error: mustMatch \"aaa\" \"b\"")
-}
-
 func (s *Suite) Test_shorten(c *check.C) {
        c.Check(shorten("aaaaa", 3), equals, "aaa...")
        c.Check(shorten("aaaaa", 5), equals, "aaaaa")
@@ -169,32 +160,6 @@ func (s *Suite) Test_relpath__quick(c *c
        test("some/dir/.", ".", "../..")
 }
 
-// This is not really an internal error but won't happen in practice anyway.
-// Therefore using ExpectPanic instead of ExpectFatal is ok.
-func (s *Suite) Test_relpath__failure_on_Windows(c *check.C) {
-       t := s.Init(c)
-
-       if runtime.GOOS == "windows" && hasPrefix(t.tmpdir, "C:/") {
-               t.ExpectPanic(
-                       func() { relpath("c:/", "d:/") },
-                       sprintf(
-                               "Pkglint internal error: "+
-                                       "relpath from topdir %q to %q: "+
-                                       "Rel: can't make %s relative to %s",
-                               t.tmpdir, "D:/", "D:/", t.tmpdir))
-       }
-}
-
-func (s *Suite) Test_abspath__failure_on_Windows(c *check.C) {
-       t := s.Init(c)
-
-       if runtime.GOOS == "windows" {
-               t.ExpectPanic(
-                       func() { abspath("file\u0000name") },
-                       "Pkglint internal error: abspath \"file\\x00name\": invalid argument")
-       }
-}
-
 func (s *Suite) Test_fileExists(c *check.C) {
        t := s.Init(c)
 
@@ -682,7 +647,7 @@ func (s *Suite) Test_escapePrintable(c *
        c.Check(escapePrintable(""), equals, "")
        c.Check(escapePrintable("ASCII only~\n\t"), equals, "ASCII only~\n\t")
        c.Check(escapePrintable("Beep \u0007 control \u001F"), equals, "Beep <U+0007> control <U+001F>")
-       c.Check(escapePrintable("Bad \xFF character"), equals, "Bad <\\xFF> character")
+       c.Check(escapePrintable("Bad \xFF character"), equals, "Bad <0xFF> character")
        c.Check(escapePrintable("Unicode \uFFFD replacement"), equals, "Unicode <U+FFFD> replacement")
 }
 

Index: pkgsrc/pkgtools/pkglint/files/mkshparser_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkshparser_test.go:1.11 pkgsrc/pkgtools/pkglint/files/mkshparser_test.go:1.12
--- pkgsrc/pkgtools/pkglint/files/mkshparser_test.go:1.11       Sat Jan 26 16:31:33 2019
+++ pkgsrc/pkgtools/pkglint/files/mkshparser_test.go    Sun Mar 24 13:58:38 2019
@@ -10,22 +10,20 @@ func (s *Suite) Test_parseShellProgram__
        t := s.Init(c)
 
        test := func(text string, expProgram *MkShList, expError error, expDiagnostics ...string) {
-               shline := t.NewShellLine("module.mk", 123, "\t"+text)
+               mklines := t.NewMkLines("module.mk", "\t"+text)
 
-               if len(expDiagnostics) > 0 {
-                       defer t.CheckOutputLines(expDiagnostics...)
-               } else {
-                       defer t.CheckOutputEmpty()
-               }
+               mklines.ForEach(func(mkline MkLine) {
+                       program, err := parseShellProgram(mkline.Line, text)
 
-               program, err := parseShellProgram(shline.mkline.Line, text)
+                       if err == nil {
+                               c.Check(err, equals, expError)
+                       } else {
+                               c.Check(err, deepEquals, expError)
+                               c.Check(program, deepEquals, expProgram)
+                       }
 
-               if err == nil {
-                       c.Check(err, equals, expError)
-               } else {
-                       c.Check(err, deepEquals, expError)
-                       c.Check(program, deepEquals, expProgram)
-               }
+                       t.CheckOutput(expDiagnostics)
+               })
        }
 
        test("$$",
@@ -37,7 +35,7 @@ func (s *Suite) Test_parseShellProgram__
                "$${",
                nil,
                fmt.Errorf("splitIntoShellTokens couldn't parse \"$${\""),
-               "WARN: module.mk:123: Unclosed shell variable starting at \"$${\".")
+               "WARN: module.mk:1: Unclosed shell variable starting at \"$${\".")
 
        test(
                "$$;",
Index: pkgsrc/pkgtools/pkglint/files/mktypes.go
diff -u pkgsrc/pkgtools/pkglint/files/mktypes.go:1.11 pkgsrc/pkgtools/pkglint/files/mktypes.go:1.12
--- pkgsrc/pkgtools/pkglint/files/mktypes.go:1.11       Thu Feb 21 22:49:03 2019
+++ pkgsrc/pkgtools/pkglint/files/mktypes.go    Sun Mar 24 13:58:38 2019
@@ -89,25 +89,38 @@ func (m MkVarUseModifier) MatchSubst() (
 //
 // Example:
 //  MkVarUseModifier{"S,name,file,g"}.Subst("distname-1.0") => "distfile-1.0"
-func (m MkVarUseModifier) Subst(str string) string {
+func (m MkVarUseModifier) Subst(str string) (string, bool) {
        // XXX: The call to MatchSubst is usually redundant because MatchSubst
        // is typically called directly before calling Subst.
        ok, regex, from, to, options := m.MatchSubst()
-       G.Assertf(ok && !regex, "Subst must only be called after MatchSubst.")
+       if !ok {
+               return "", false
+       }
+
        leftAnchor := hasPrefix(from, "^")
        if leftAnchor {
                from = from[1:]
        }
+
        rightAnchor := hasSuffix(from, "$")
        if rightAnchor {
                from = from[:len(from)-1]
        }
 
+       if regex {
+               if matches(from, `^[\w-]+$`) && matches(to, `^[^&$\\]*$`) {
+                       regex = false
+               } else {
+                       // TODO: Maybe implement regular expression substitutions later.
+                       return "", false
+               }
+       }
+
        result := mkopSubst(str, leftAnchor, from, rightAnchor, to, options)
        if trace.Tracing && result != str {
                trace.Stepf("Subst: %q %q => %q", str, m.Text, result)
        }
-       return result
+       return result, true
 }
 
 // MatchMatch tries to match the modifier to a :M or a :N pattern matching.
Index: pkgsrc/pkgtools/pkglint/files/tools.go
diff -u pkgsrc/pkgtools/pkglint/files/tools.go:1.11 pkgsrc/pkgtools/pkglint/files/tools.go:1.12
--- pkgsrc/pkgtools/pkglint/files/tools.go:1.11 Sun Jan 13 19:55:53 2019
+++ pkgsrc/pkgtools/pkglint/files/tools.go      Sun Mar 24 13:58:38 2019
@@ -214,7 +214,7 @@ func (tr *Tools) ParseToolLine(mkline Mk
                switch mkline.Varcanon() {
                case "TOOLS_CREATE":
                        if tr.IsValidToolName(value) {
-                               tr.Define(value, "", mkline)
+                               tr.def(value, "", false, AtRunTime)
                        }
 
                case "_TOOLS_VARNAME.*":

Index: pkgsrc/pkgtools/pkglint/files/mktypes_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mktypes_test.go:1.7 pkgsrc/pkgtools/pkglint/files/mktypes_test.go:1.8
--- pkgsrc/pkgtools/pkglint/files/mktypes_test.go:1.7   Sat Jan 26 16:31:33 2019
+++ pkgsrc/pkgtools/pkglint/files/mktypes_test.go       Sun Mar 24 13:58:38 2019
@@ -51,3 +51,25 @@ func (s *Suite) Test_MkVarUseModifier_Ma
        c.Check(to, equals, "\\:")
        c.Check(options, equals, "")
 }
+
+// As of 2019-03-24, pkglint doesn't know how to handle complicated
+// :C modifiers.
+func (s *Suite) Test_MkVarUseModifier_Subst__regexp(c *check.C) {
+       mod := MkVarUseModifier{"C,.*,,"}
+
+       empty, ok := mod.Subst("anything")
+
+       c.Check(ok, equals, false)
+       c.Check(empty, equals, "")
+}
+
+// When given a modifier that is not actually a :S or :C, Subst
+// doesn't do anything.
+func (s *Suite) Test_MkVarUseModifier_Subst__invalid_argument(c *check.C) {
+       mod := MkVarUseModifier{"Mpattern"}
+
+       empty, ok := mod.Subst("anything")
+
+       c.Check(ok, equals, false)
+       c.Check(empty, equals, "")
+}

Index: pkgsrc/pkgtools/pkglint/files/options.go
diff -u pkgsrc/pkgtools/pkglint/files/options.go:1.12 pkgsrc/pkgtools/pkglint/files/options.go:1.13
--- pkgsrc/pkgtools/pkglint/files/options.go:1.12       Thu Feb 21 22:49:03 2019
+++ pkgsrc/pkgtools/pkglint/files/options.go    Sun Mar 24 13:58:38 2019
@@ -88,13 +88,24 @@ loop:
                                Empty: recordUsedOption,
                                Var:   recordUsedOption})
 
+                       // FIXME: Is this note also issued for the following lines?
+                       //  .if empty(ANY_OTHER_VARIABLE)
+                       //  .else
+                       //  .endif
                        if cond.Empty != nil && mkline.HasElseBranch() {
                                mkline.Notef("The positive branch of the .if/.else should be the one where the option is set.")
                                G.Explain(
                                        "For consistency among packages, the upper branch of this",
                                        ".if/.else statement should always handle the case where the",
                                        "option is activated.",
-                                       "A missing exclamation mark at this point can easily be overlooked.")
+                                       "A missing exclamation mark at this point can easily be overlooked.",
+                                       "",
+                                       "If that seems too much to type and the exclamation mark",
+                                       "seems wrong for a positive test, switch the blocks nevertheless",
+                                       "and write the condition like this, which has the same effect",
+                                       "as the !empty(...).",
+                                       "",
+                                       "\t.if ${PKG_OPTIONS.packagename:Moption}")
                        }
                }
        }
Index: pkgsrc/pkgtools/pkglint/files/tools_test.go
diff -u pkgsrc/pkgtools/pkglint/files/tools_test.go:1.12 pkgsrc/pkgtools/pkglint/files/tools_test.go:1.13
--- pkgsrc/pkgtools/pkglint/files/tools_test.go:1.12    Sun Jan 13 19:55:53 2019
+++ pkgsrc/pkgtools/pkglint/files/tools_test.go Sun Mar 24 13:58:38 2019
@@ -142,26 +142,26 @@ func (s *Suite) Test_Tools__load_from_in
 
        // All tools are defined by name, but their variable names are not yet known.
        // At this point they may not be used, neither by the pkgsrc infrastructure nor by a package.
-       c.Check(load.String(), equals, "load:::Nowhere")
-       c.Check(run.String(), equals, "run:::Nowhere")
-       c.Check(nowhere.String(), equals, "nowhere:::Nowhere")
+       c.Check(load.String(), equals, "load:::AtRunTime")
+       c.Check(run.String(), equals, "run:::AtRunTime")
+       c.Check(nowhere.String(), equals, "nowhere:::AtRunTime")
 
-       // The name RUN_CMD avoids conflicts with RUN.
+       // The variable name RUN is reserved by pkgsrc, therefore RUN_CMD.
        tools.ParseToolLine(t.NewMkLine("varnames.mk", 2, "_TOOLS_VARNAME.load=    LOAD"), true, false)
        tools.ParseToolLine(t.NewMkLine("varnames.mk", 3, "_TOOLS_VARNAME.run=     RUN_CMD"), true, false)
        tools.ParseToolLine(t.NewMkLine("varnames.mk", 4, "_TOOLS_VARNAME.nowhere= NOWHERE"), true, false)
 
        // At this point the tools can be found by their variable names, too.
        // They still may not be used.
-       c.Check(load.String(), equals, "load:LOAD::Nowhere")
-       c.Check(run.String(), equals, "run:RUN_CMD::Nowhere")
-       c.Check(nowhere.String(), equals, "nowhere:NOWHERE::Nowhere")
+       c.Check(load.String(), equals, "load:LOAD::AtRunTime")
+       c.Check(run.String(), equals, "run:RUN_CMD::AtRunTime")
+       c.Check(nowhere.String(), equals, "nowhere:NOWHERE::AtRunTime")
        c.Check(tools.ByVarname("LOAD"), equals, load)
        c.Check(tools.ByVarname("RUN_CMD"), equals, run)
        c.Check(tools.ByVarname("NOWHERE"), equals, nowhere)
-       c.Check(load.String(), equals, "load:LOAD::Nowhere")
-       c.Check(run.String(), equals, "run:RUN_CMD::Nowhere")
-       c.Check(nowhere.String(), equals, "nowhere:NOWHERE::Nowhere")
+       c.Check(load.String(), equals, "load:LOAD::AtRunTime")
+       c.Check(run.String(), equals, "run:RUN_CMD::AtRunTime")
+       c.Check(nowhere.String(), equals, "nowhere:NOWHERE::AtRunTime")
 
        tools.ParseToolLine(t.NewMkLine("bsd.prefs.mk", 2, "USE_TOOLS+= load"), true, true)
 
@@ -213,7 +213,7 @@ func (s *Suite) Test_Tools__package_Make
 
        c.Check(load.UsableAtRunTime(), equals, true)
        c.Check(run.UsableAtRunTime(), equals, true)
-       c.Check(nowhere.UsableAtRunTime(), equals, false)
+       c.Check(nowhere.UsableAtRunTime(), equals, true)
 
        // The seenPrefs variable is only relevant for the package Makefile.
        // All other files must not use the tools at load time.
@@ -504,3 +504,29 @@ func (s *Suite) Test_Tools__cmake(c *che
 
        t.CheckOutputEmpty()
 }
+
+func (s *Suite) Test_Tools__gmake(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPackage("category/package",
+               "USE_TOOLS=\tgmake",
+               "",
+               "do-test:",
+               "\tcd ${WRKSRC} && make tests")
+       t.CreateFileLines("mk/tools/bsd.tools.mk",
+               ".include \"defaults.mk\"",
+               ".include \"replace.mk\"",
+               ".include \"make.mk\"")
+       t.CreateFileLines("mk/tools/make.mk",
+               "TOOLS_CREATE+=\tmake",
+               "TOOLS_PATH.make=\t/usr/bin/make")
+       t.CreateFileLines("mk/tools/replace.mk",
+               "TOOLS_CREATE+=\tgmake",
+               "TOOLS_PATH.gmake=\t/usr/bin/gnu-make")
+
+       G.Pkgsrc.LoadInfrastructure()
+
+       G.Check(t.File("category/package"))
+
+       t.CheckOutputEmpty()
+}

Index: pkgsrc/pkgtools/pkglint/files/package.go
diff -u pkgsrc/pkgtools/pkglint/files/package.go:1.47 pkgsrc/pkgtools/pkglint/files/package.go:1.48
--- pkgsrc/pkgtools/pkglint/files/package.go:1.47       Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/package.go    Sun Mar 24 13:58:38 2019
@@ -2,6 +2,7 @@ package pkglint
 
 import (
        "netbsd.org/pkglint/pkgver"
+       "os"
        "path"
        "strconv"
        "strings"
@@ -163,6 +164,92 @@ func (pkg *Package) checkLinesBuildlink3
        }
 }
 
+func (pkg *Package) load() ([]string, MkLines, MkLines) {
+       // Load the package Makefile and all included files,
+       // to collect all used and defined variables and similar data.
+       mklines, allLines := pkg.loadPackageMakefile()
+       if mklines == nil {
+               return nil, nil, nil
+       }
+
+       files := dirglob(pkg.File("."))
+       if pkg.Pkgdir != "." {
+               files = append(files, dirglob(pkg.File(pkg.Pkgdir))...)
+       }
+       if G.Opts.CheckExtra {
+               files = append(files, dirglob(pkg.File(pkg.Filesdir))...)
+       }
+       files = append(files, dirglob(pkg.File(pkg.Patchdir))...)
+       if pkg.DistinfoFile != pkg.vars.fallback["DISTINFO_FILE"] {
+               files = append(files, pkg.File(pkg.DistinfoFile))
+       }
+
+       // Determine the used variables and PLIST directories before checking any of the Makefile fragments.
+       // TODO: Why is this code necessary? What effect does it have?
+       for _, filename := range files {
+               basename := path.Base(filename)
+               if (hasPrefix(basename, "Makefile.") || hasSuffix(filename, ".mk")) &&
+                       !matches(filename, `patch-`) &&
+                       !contains(filename, pkg.Pkgdir+"/") &&
+                       !contains(filename, pkg.Filesdir+"/") {
+                       if fragmentMklines := LoadMk(filename, MustSucceed); fragmentMklines != nil {
+                               fragmentMklines.collectUsedVariables()
+                       }
+               }
+               if hasPrefix(basename, "PLIST") {
+                       pkg.loadPlistDirs(filename)
+               }
+       }
+
+       return files, mklines, allLines
+}
+
+func (pkg *Package) check(files []string, mklines, allLines MkLines) {
+       haveDistinfo := false
+       havePatches := false
+
+       for _, filename := range files {
+               if containsVarRef(filename) {
+                       if trace.Tracing {
+                               trace.Stepf("Skipping file %q because the name contains an unresolved variable.", filename)
+                       }
+                       continue
+               }
+
+               st, err := os.Lstat(filename)
+               switch {
+               case err != nil:
+                       // For missing custom distinfo file, an error message is already generated
+                       // for the line where DISTINFO_FILE is defined.
+                       //
+                       // For all other cases it is next to impossible to reach this branch
+                       // since all those files come from calls to dirglob.
+                       break
+
+               case path.Base(filename) == "Makefile":
+                       G.checkExecutable(filename, st.Mode())
+                       pkg.checkfilePackageMakefile(filename, mklines, allLines)
+
+               default:
+                       G.checkDirent(filename, st.Mode())
+               }
+
+               if contains(filename, "/patches/patch-") {
+                       havePatches = true
+               } else if hasSuffix(filename, "/distinfo") {
+                       haveDistinfo = true
+               }
+               pkg.checkLocallyModified(filename)
+       }
+
+       if pkg.Pkgdir == "." {
+               if havePatches && !haveDistinfo {
+                       // TODO: Add Line.RefTo to make the context clear.
+                       NewLineWhole(pkg.File(pkg.DistinfoFile)).Warnf("File not found. Please run %q.", bmake("makepatchsum"))
+               }
+       }
+}
+
 func (pkg *Package) loadPackageMakefile() (MkLines, MkLines) {
        filename := pkg.File("Makefile")
        if trace.Tracing {
@@ -201,11 +288,11 @@ func (pkg *Package) loadPackageMakefile(
        pkg.Patchdir = pkg.vars.LastValue("PATCHDIR")
 
        // See lang/php/ext.mk
-       if varIsDefinedSimilar("PHPEXT_MK") {
-               if !varIsDefinedSimilar("USE_PHP_EXT_PATCHES") {
+       if varIsDefinedSimilar(pkg, nil, "PHPEXT_MK") {
+               if !varIsDefinedSimilar(pkg, nil, "USE_PHP_EXT_PATCHES") {
                        pkg.Patchdir = "patches"
                }
-               if varIsDefinedSimilar("PECL_VERSION") {
+               if varIsDefinedSimilar(pkg, nil, "PECL_VERSION") {
                        pkg.DistinfoFile = "distinfo"
                } else {
                        pkg.IgnoreMissingPatches = true
@@ -358,14 +445,33 @@ func (pkg *Package) readMakefile(filenam
 }
 
 func (pkg *Package) diveInto(includingFile string, includedFile string) bool {
-       skip := hasSuffix(includedFile, "/bsd.pkg.mk") || IsPrefs(includedFile)
-       if !skip && contains(includingFile, "/mk/") {
-               skip = true
-               if contains(includingFile, "buildlink3.mk") && contains(includedFile, "builtin.mk") {
-                       skip = false
-               }
+
+       // The variables that appear in these files are largely modeled by
+       // pkglint in the file vardefs.go. Therefore parsing these files again
+       // doesn't make much sense.
+       if hasSuffix(includedFile, "/bsd.pkg.mk") || IsPrefs(includedFile) {
+               return false
+       }
+
+       // All files that are included from outside of the pkgsrc infrastructure
+       // are relevant. This is typically mk/compiler.mk or the various
+       // mk/*.buildlink3.mk files.
+       if !contains(includingFile, "/mk/") {
+               return true
        }
-       return !skip
+
+       // The mk/*.buildlink3.mk files often come with a companion file called
+       // mk/*.builtin.mk, which also defines variables that are visible from
+       // the package.
+       //
+       // This case is needed for getting the redundancy check right. Without it
+       // there will be warnings about redundant assignments to the
+       // BUILTIN_CHECK.pthread variable.
+       if contains(includingFile, "buildlink3.mk") && contains(includedFile, "builtin.mk") {
+               return true
+       }
+
+       return false
 }
 
 func (pkg *Package) collectUsedBy(mkline MkLine, incDir string, incBase string, includedFile string) {
@@ -546,26 +652,40 @@ func (pkg *Package) determineEffectivePk
        if distnameLine != nil {
                distname = distnameLine.Value()
        }
+
        pkgname := ""
        if pkgnameLine != nil {
                pkgname = pkgnameLine.Value()
        }
 
-       if distname != "" && pkgname != "" {
-               pkgname = pkg.pkgnameFromDistname(pkgname, distname)
+       effname := pkgname
+       if distname != "" && effname != "" {
+               merged, ok := pkg.pkgnameFromDistname(effname, distname)
+               if ok {
+                       effname = merged
+               }
        }
 
-       if pkgname != "" && pkgname == distname && pkgnameLine.VarassignComment() == "" {
-               pkgnameLine.Notef("This assignment is probably redundant since PKGNAME is ${DISTNAME} by default.")
+       if pkgname != "" && (pkgname == distname || pkgname == "${DISTNAME}") {
+               if pkgnameLine.VarassignComment() == "" {
+                       pkgnameLine.Notef("This assignment is probably redundant " +
+                               "since PKGNAME is ${DISTNAME} by default.")
+                       pkgnameLine.Explain(
+                               "To mark this assignment as necessary, add a comment to the end of this line.")
+               }
        }
 
        if pkgname == "" && distname != "" && !containsVarRef(distname) && !matches(distname, rePkgname) {
                distnameLine.Warnf("As DISTNAME is not a valid package name, please define the PKGNAME explicitly.")
        }
 
-       if pkgname != "" && !containsVarRef(pkgname) {
-               if m, m1, m2 := match2(pkgname, rePkgname); m {
-                       pkg.EffectivePkgname = pkgname + pkg.nbPart()
+       if pkgname != "" {
+               distname = ""
+       }
+
+       if effname != "" && !containsVarRef(effname) {
+               if m, m1, m2 := match2(effname, rePkgname); m {
+                       pkg.EffectivePkgname = effname + pkg.nbPart()
                        pkg.EffectivePkgnameLine = pkgnameLine
                        pkg.EffectivePkgbase = m1
                        pkg.EffectivePkgversion = m2
@@ -589,31 +709,34 @@ func (pkg *Package) determineEffectivePk
        }
 }
 
-func (pkg *Package) pkgnameFromDistname(pkgname, distname string) string {
+func (pkg *Package) pkgnameFromDistname(pkgname, distname string) (string, bool) {
        tokens := NewMkParser(nil, pkgname, false).MkTokens()
 
        // TODO: Make this resolving of variable references available to all other variables as well.
 
-       result := ""
+       var result strings.Builder
        for _, token := range tokens {
-               if token.Varuse != nil && token.Varuse.varname == "DISTNAME" {
+               if token.Varuse != nil {
+                       if token.Varuse.varname != "DISTNAME" {
+                               return "", false
+                       }
+
                        newDistname := distname
                        for _, mod := range token.Varuse.modifiers {
                                if mod.IsToLower() {
                                        newDistname = strings.ToLower(newDistname)
-                               } else if m, regex, _, _, _ := mod.MatchSubst(); m && !regex {
-                                       newDistname = mod.Subst(newDistname)
+                               } else if subst, ok := mod.Subst(newDistname); ok {
+                                       newDistname = subst
                                } else {
-                                       newDistname = token.Text
-                                       break
+                                       return "", false
                                }
                        }
-                       result += newDistname
+                       result.WriteString(newDistname)
                } else {
-                       result += token.Text
+                       result.WriteString(token.Text)
                }
        }
-       return result
+       return result.String(), true
 }
 
 func (pkg *Package) checkUpdate() {

Index: pkgsrc/pkgtools/pkglint/files/package_test.go
diff -u pkgsrc/pkgtools/pkglint/files/package_test.go:1.40 pkgsrc/pkgtools/pkglint/files/package_test.go:1.41
--- pkgsrc/pkgtools/pkglint/files/package_test.go:1.40  Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/package_test.go       Sun Mar 24 13:58:38 2019
@@ -53,7 +53,11 @@ func (s *Suite) Test_Package_pkgnameFrom
        pkg.vars.Define("PKGNAME", t.NewMkLine("Makefile", 5, "PKGNAME=dummy"))
 
        test := func(pkgname, distname, expectedPkgname string) {
-               c.Check(pkg.pkgnameFromDistname(pkgname, distname), equals, expectedPkgname)
+               merged, ok := pkg.pkgnameFromDistname(pkgname, distname)
+               if !ok {
+                       merged = ""
+               }
+               c.Check(merged, equals, expectedPkgname)
        }
 
        test("pkgname-1.0", "whatever", "pkgname-1.0")
@@ -63,11 +67,15 @@ func (s *Suite) Test_Package_pkgnameFrom
        test("${DISTNAME:S|^lib||}", "libncurses", "ncurses")
        test("${DISTNAME:S|^lib||}", "mylib", "mylib")
        test("${DISTNAME:tl:S/-/./g:S/he/-/1}", "SaxonHE9-5-0-1J", "saxon-9.5.0.1j")
-       test("${DISTNAME:C/beta/.0./}", "fspanel-0.8beta1", "${DISTNAME:C/beta/.0./}")
+       test("${DISTNAME:C/beta/.0./}", "fspanel-0.8beta1", "fspanel-0.8.0.1")
+       test("${DISTNAME:C/Gtk2/p5-gtk2/}", "Gtk2-1.0", "p5-gtk2-1.0")
        test("${DISTNAME:S/-0$/.0/1}", "aspell-af-0.50-0", "aspell-af-0.50.0")
+       test("${DISTNAME:M*.tar.gz:C,\\..*,,}", "aspell-af-0.50-0", "")
 
        // FIXME: Should produce a parse error since the :S modifier is malformed; see Test_MkParser_MkTokens.
        test("${DISTNAME:S,a,b,c,d}", "aspell-af-0.50-0", "bspell-af-0.50-0")
+
+       test("${DISTFILE:C,\\..*,,}", "aspell-af-0.50-0", "")
 }
 
 func (s *Suite) Test_Package_CheckVarorder(c *check.C) {
@@ -356,6 +364,20 @@ func (s *Suite) Test_Package_determineEf
                        "This assignment is probably redundant since PKGNAME is ${DISTNAME} by default.")
 }
 
+func (s *Suite) Test_Package_determineEffectivePkgVars__simple_reference(c *check.C) {
+       t := s.Init(c)
+
+       pkg := t.SetUpPackage("category/package",
+               "DISTNAME=\tdistname-1.0",
+               "PKGNAME=\t${DISTNAME}")
+
+       G.Check(pkg)
+
+       t.CheckOutputLines(
+               "NOTE: ~/category/package/Makefile:4: " +
+                       "This assignment is probably redundant since PKGNAME is ${DISTNAME} by default.")
+}
+
 func (s *Suite) Test_Package_determineEffectivePkgVars__invalid_DISTNAME(c *check.C) {
        t := s.Init(c)
 
@@ -369,6 +391,39 @@ func (s *Suite) Test_Package_determineEf
                        "As DISTNAME is not a valid package name, please define the PKGNAME explicitly.")
 }
 
+func (s *Suite) Test_Package_determineEffectivePkgVars__C_modifier(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPackage("x11/p5-gtk2",
+               "DISTNAME=\tGtk2-1.0",
+               "PKGNAME=\t${DISTNAME:C:Gtk2:p5-gtk2:}")
+       pkg := NewPackage(t.File("x11/p5-gtk2"))
+
+       files, mklines, allLines := pkg.load()
+       pkg.check(files, mklines, allLines)
+
+       t.Check(pkg.EffectivePkgname, equals, "p5-gtk2-1.0")
+}
+
+// In some cases the PKGNAME is derived from DISTNAME, and it seems as
+// if the :C modifier would not affect anything. This may nevertheless
+// be on purpose since the modifier may apply to future versions and
+// do things like replacing a "-1" with a ".1".
+func (s *Suite) Test_Package_determineEffectivePkgVars__ineffective_C_modifier(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPackage("category/package",
+               "DISTNAME=\tdistname-1.0",
+               "PKGNAME=\t${DISTNAME:C:does_not_match:replacement:}")
+       pkg := NewPackage(t.File("category/package"))
+
+       files, mklines, allLines := pkg.load()
+       pkg.check(files, mklines, allLines)
+
+       t.Check(pkg.EffectivePkgname, equals, "distname-1.0")
+       t.CheckOutputEmpty()
+}
+
 func (s *Suite) Test_Package_checkPossibleDowngrade(c *check.C) {
        t := s.Init(c)
 
@@ -507,9 +562,10 @@ func (s *Suite) Test_Package__varuse_at_
        t.CheckOutputLines(
                "WARN: ~/category/pkgbase/Makefile:14: To use the tool ${FALSE} at load time, bsd.prefs.mk has to be included before.",
                // TODO: "before including bsd.prefs.mk in line ###".
-               "WARN: ~/category/pkgbase/Makefile:15: To use the tool ${NICE} at load time, it has to be added to USE_TOOLS before including bsd.prefs.mk.",
+               // TODO: ${NICE} could be used at load time if it were added to USE_TOOLS earlier.
+               "WARN: ~/category/pkgbase/Makefile:15: The tool ${NICE} cannot be used at load time.",
                "WARN: ~/category/pkgbase/Makefile:16: To use the tool ${TRUE} at load time, bsd.prefs.mk has to be included before.",
-               "WARN: ~/category/pkgbase/Makefile:25: To use the tool ${NICE} at load time, it has to be added to USE_TOOLS before including bsd.prefs.mk.")
+               "WARN: ~/category/pkgbase/Makefile:25: The tool ${NICE} cannot be used at load time.")
 }
 
 func (s *Suite) Test_Package_loadPackageMakefile(c *check.C) {
@@ -1169,6 +1225,34 @@ func (s *Suite) Test_Package_checkLocall
        t.CheckOutputEmpty()
 }
 
+func (s *Suite) Test_Package_checkLocallyModified__directory(c *check.C) {
+       t := s.Init(c)
+
+       G.Username = "example-user"
+       t.CreateFileLines("category/package/CVS/Entries",
+               "/Makefile//modified//",
+               "D/patches////")
+       t.CreateFileDummyPatch("category/package/patches/patch-aa")
+
+       pkg := t.SetUpPackage("category/package",
+               "MAINTAINER=\tmaintainer%example.org@localhost")
+       t.CreateFileLines("category/package/distinfo",
+               RcsID,
+               "",
+               "SHA1 (patch-aa) = ebbf34b0641bcb508f17d5a27f2bf2a536d810ac")
+
+       G.Check(pkg)
+
+       t.CheckOutputLines(
+               "NOTE: ~/category/package/Makefile: "+
+                       "Please only commit changes that "+
+                       "maintainer%example.org@localhost would approve.",
+               // FIXME: There must be no warning for directories.
+               "NOTE: ~/category/package/patches: "+
+                       "Please only commit changes that "+
+                       "maintainer%example.org@localhost would approve.")
+}
+
 // In practice the distinfo file can always be autofixed since it has
 // just been read successfully and the corresponding patch file could
 // also be autofixed right before.

Index: pkgsrc/pkgtools/pkglint/files/pkglint_test.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.34 pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.35
--- pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.34  Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/pkglint_test.go       Sun Mar 24 13:58:38 2019
@@ -437,9 +437,9 @@ func (s *Suite) Test_resolveVariableRefs
        mkline2 := t.NewMkLine("filename.mk", 11, "SECOND=\t${THIRD}")
        mkline3 := t.NewMkLine("filename.mk", 12, "THIRD=\tgot it")
        G.Pkg = NewPackage(t.File("category/pkgbase"))
-       defineVar(mkline1, "FIRST")
-       defineVar(mkline2, "SECOND")
-       defineVar(mkline3, "THIRD")
+       defineVar(G.Pkg, nil, mkline1, "FIRST")
+       defineVar(G.Pkg, nil, mkline2, "SECOND")
+       defineVar(G.Pkg, nil, mkline3, "THIRD")
 
        // TODO: Add a similar test in which some of the variables are defined
        //  conditionally or with differing values, just to see what pkglint does
Index: pkgsrc/pkgtools/pkglint/files/plist_test.go
diff -u pkgsrc/pkgtools/pkglint/files/plist_test.go:1.34 pkgsrc/pkgtools/pkglint/files/plist_test.go:1.35
--- pkgsrc/pkgtools/pkglint/files/plist_test.go:1.34    Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/plist_test.go Sun Mar 24 13:58:38 2019
@@ -26,7 +26,7 @@ func (s *Suite) Test_CheckLinesPlist(c *
                "share/tzinfo",
                "share/tzinfo")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(G.Pkg, lines)
 
        t.CheckOutputLines(
                "ERROR: PLIST:1: Expected \"@comment $"+"NetBSD$\".",
@@ -59,7 +59,7 @@ func (s *Suite) Test_CheckLinesPlist__mu
                "lib/libc.la",
                "lib/libm.la")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(G.Pkg, lines)
 
        t.CheckOutputLines(
                "WARN: PLIST:2: Packages that install libtool libraries should define USE_LIBTOOL.")
@@ -71,7 +71,7 @@ func (s *Suite) Test_CheckLinesPlist__em
        lines := t.NewLines("PLIST",
                PlistRcsID)
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "WARN: PLIST:1: PLIST files shouldn't be empty.")
@@ -87,7 +87,7 @@ func (s *Suite) Test_CheckLinesPlist__co
                PlistRcsID,
                "sbin/common_end")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputEmpty()
 }
@@ -100,7 +100,7 @@ func (s *Suite) Test_CheckLinesPlist__co
                PlistRcsID,
                "${PLIST.bincmds}bin/subdir/command")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(G.Pkg, lines)
 
        t.CheckOutputLines(
                "WARN: PLIST:2: The bin/ directory should not have subdirectories.")
@@ -117,7 +117,7 @@ func (s *Suite) Test_CheckLinesPlist__so
                "bin/otherprogram",
                "${PLIST.condition}bin/cat")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "WARN: PLIST:5: \"bin/otherprogram\" should be sorted before \"sbin/program\".",
@@ -154,7 +154,7 @@ func (s *Suite) Test_plistLineSorter_Sor
                "${PLIST.linux}${PLIST.x86_64}lib/lib-linux-x86_64.so", // Double condition, see graphics/graphviz
                "lib/after.la",
                "@exec echo \"after lib/after.la\"")
-       ck := PlistChecker{nil, nil, "", Once{}}
+       ck := PlistChecker{nil, nil, nil, "", Once{}, false}
        plines := ck.NewLines(lines)
 
        sorter1 := NewPlistLineSorter(plines)
@@ -162,7 +162,8 @@ func (s *Suite) Test_plistLineSorter_Sor
 
        cleanedLines := append(append(lines.Lines[0:5], lines.Lines[6:8]...), lines.Lines[9:]...) // Remove ${UNKNOWN} and @exec
 
-       sorter2 := NewPlistLineSorter((&PlistChecker{nil, nil, "", Once{}}).NewLines(NewLines(lines.FileName, cleanedLines)))
+       sorter2 := NewPlistLineSorter((&PlistChecker{nil, nil, nil, "", Once{}, false}).
+               NewLines(NewLines(lines.FileName, cleanedLines)))
 
        c.Check(sorter2.unsortable, check.IsNil)
 
@@ -211,7 +212,7 @@ func (s *Suite) Test_PlistChecker_checkL
                "${PLIST.man:Q}man/cat3/strlcpy.3",
                "<<<<<<<<< merge conflict")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "WARN: PLIST:3: \"bin/conditional-program\" should be sorted before \"bin/program\".",
@@ -229,7 +230,7 @@ func (s *Suite) Test_PlistChecker_checkP
                PlistRcsID,
                "man/man3/strerror.3.gz")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(G.Pkg, lines)
 
        t.CheckOutputLines(
                "NOTE: PLIST:2: The .gz extension is unnecessary for manual pages.")
@@ -242,7 +243,7 @@ func (s *Suite) Test_PlistChecker_checkP
                PlistRcsID,
                "${PKGMANDIR}/man1/sh.1")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "NOTE: PLIST:2: PLIST files should use \"man/\" instead of \"${PKGMANDIR}\".")
@@ -255,7 +256,7 @@ func (s *Suite) Test_PlistChecker_checkP
                PlistRcsID,
                "${PYSITELIB}/gdspy-${PKGVERSION}-py${PYVERSSUFFIX}.egg-info/PKG-INFO")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "WARN: PLIST:2: Include \"../../lang/python/egg.mk\" instead of listing .egg-info files directly.")
@@ -287,7 +288,7 @@ func (s *Suite) Test_PlistChecker__autof
                "@pkgdir        etc/logrotate.d",
                "@pkgdir        etc/sasl2")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "WARN: ~/PLIST:3: \"lib/libvirt/connection-driver/libvirt_driver_nodedev.la\" "+
@@ -297,7 +298,7 @@ func (s *Suite) Test_PlistChecker__autof
                "NOTE: ~/PLIST:6: PLIST files should use \"man/\" instead of \"${PKGMANDIR}\".")
 
        t.SetUpCommandLine("-Wall", "--autofix")
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "AUTOFIX: ~/PLIST:6: Replacing \"${PKGMANDIR}/\" with \"man/\".",
@@ -342,7 +343,7 @@ func (s *Suite) Test_PlistChecker__remov
                "${PLIST.option2}bin/false",
                "bin/true")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "ERROR: ~/PLIST:2: Duplicate filename \"bin/true\", already appeared in line 3.",
@@ -353,7 +354,7 @@ func (s *Suite) Test_PlistChecker__remov
 
        t.SetUpCommandLine("-Wall", "--autofix")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "AUTOFIX: ~/PLIST:2: Deleting this line.",
@@ -381,7 +382,7 @@ func (s *Suite) Test_PlistChecker__autof
                "sbin/program",
                "bin/program")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputEmpty()
        t.CheckFileLines("PLIST",
@@ -398,7 +399,7 @@ func (s *Suite) Test_PlistChecker__exec_
                "bin/program",
                "@exec ${MKDIR} %D/share/mk/subdir")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputEmpty()
 }
@@ -411,14 +412,14 @@ func (s *Suite) Test_PlistChecker__empty
                "",
                "bin/program")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "WARN: ~/PLIST:2: PLISTs should not contain empty lines.")
 
        t.SetUpCommandLine("-Wall", "--autofix")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "AUTOFIX: ~/PLIST:2: Deleting this line.")
@@ -438,7 +439,7 @@ func (s *Suite) Test_PlistChecker__inval
                "======== merge conflict",
                ">>>>>>>> merge conflict")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "WARN: ~/PLIST:2: Invalid line type: ---invalid",
@@ -448,6 +449,59 @@ func (s *Suite) Test_PlistChecker__inval
                "WARN: ~/PLIST:6: Invalid line type: >>>>>>>> merge conflict")
 }
 
+func (s *Suite) Test_PlistChecker_checkPathNonAscii(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpCommandLine("-Wall", "--explain")
+       lines := t.NewLines("PLIST",
+               PlistRcsID,
+
+               "dir1/fr\xFCher", // German, "back then", encoded in ISO 8859-1
+
+               // Subsequent non-ASCII filenames do not generate further messages
+               // since these filenames typically appear in groups, and issuing
+               // too many warnings quickly gets boring.
+               "dir1/\u00C4thernetz", // German
+
+               // This ASCII-only pathname enables the check again.
+               "dir2/aaa",
+               "dir2/\u0633\u0644\u0627\u0645", // Arabic: salaam
+
+               "dir2/\uC548\uB148", // Korean: annyeong
+
+               // This ASCII-only pathname enables the check again.
+               "dir3/ascii-only",
+
+               // Any comment suppresses the check for the next contiguous
+               // sequence of non-ASCII filenames.
+               "@comment The next file is non-ASCII on purpose.",
+               "dir3/\U0001F603", // Smiling face with open mouth
+
+               // This ASCII-only pathname enables the check again.
+               "sbin/iconv",
+
+               "sbin/\U0001F603", // Smiling face with open mouth
+       )
+
+       CheckLinesPlist(nil, lines)
+
+       t.CheckOutputLines(
+               "WARN: PLIST:2: Non-ASCII filename \"dir1/fr<0xFC>her\".",
+               "",
+               "\tThe great majority of filenames installed by pkgsrc packages are",
+               "\tASCII-only. Filenames containing non-ASCII characters can cause",
+               "\tvarious problems since their name may already be different when",
+               "\tanother character encoding is set in the locale.",
+               "",
+               "\tTo mark a filename as intentionally non-ASCII, insert a PLIST",
+               "\t@comment with a convincing reason directly above this line. That",
+               "\tcomment will allow this line and the lines directly below it to",
+               "\tcontain non-ASCII filenames.",
+               "",
+               "WARN: PLIST:5: Non-ASCII filename \"dir2/<U+0633><U+0644><U+0627><U+0645>\".",
+               "WARN: PLIST:11: Non-ASCII filename \"sbin/<U+1F603>\".")
+}
+
 func (s *Suite) Test_PlistChecker__doc(c *check.C) {
        t := s.Init(c)
 
@@ -455,7 +509,7 @@ func (s *Suite) Test_PlistChecker__doc(c
                PlistRcsID,
                "doc/html/index.html")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "ERROR: ~/PLIST:2: Documentation must be installed under share/doc, not doc.")
@@ -469,7 +523,7 @@ func (s *Suite) Test_PlistChecker__PKGLO
                "${PKGLOCALEDIR}/file")
        G.Pkg = NewPackage(t.File("category/package"))
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(G.Pkg, lines)
 
        t.CheckOutputLines(
                "WARN: ~/PLIST:2: PLIST contains ${PKGLOCALEDIR}, but USE_PKGLOCALEDIR is not set in the package Makefile.")
@@ -484,7 +538,7 @@ func (s *Suite) Test_PlistChecker_checkP
                "share/pkgbase/CVS/Entries",
                "share/pkgbase/Makefile.orig")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "WARN: ~/PLIST:2: The perllocal.pod file should not be in the PLIST.",
@@ -500,7 +554,7 @@ func (s *Suite) Test_PlistChecker_checkP
                "info/gmake.1.info")
        G.Pkg = NewPackage(t.File("category/package"))
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(G.Pkg, lines)
 
        t.CheckOutputLines(
                "WARN: ~/PLIST:2: Packages that install info files should set INFO_FILES in the Makefile.")
@@ -518,7 +572,7 @@ func (s *Suite) Test_PlistChecker_checkP
        G.Pkg = NewPackage(t.File("category/package"))
        G.Pkg.EffectivePkgbase = "package"
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(G.Pkg, lines)
 
        t.CheckOutputLines(
                "ERROR: ~/PLIST:2: Only the libiconv package may install lib/charset.alias.",
@@ -535,7 +589,7 @@ func (s *Suite) Test_PlistChecker_checkP
                "man/man1/program.8",
                "man/manx/program.x")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "WARN: ~/PLIST:2: Mismatch between the section (1) and extension (8) of the manual page.",
@@ -555,7 +609,7 @@ func (s *Suite) Test_PlistChecker_checkP
        G.Pkg = NewPackage(t.File("category/package"))
        G.Pkg.EffectivePkgbase = "package"
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(G.Pkg, lines)
 
        t.CheckOutputLines(
                "WARN: ~/PLIST:2: Use of \"share/doc/html\" is deprecated. Use \"share/doc/${PKGBASE}\" instead.",
@@ -601,9 +655,10 @@ func (s *Suite) Test_PlistLine_CheckTrai
                PlistRcsID,
                "bin/program \t")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
+               "WARN: ~/PLIST:2: Non-ASCII filename \"bin/program \\t\".",
                "ERROR: ~/PLIST:2: Pkgsrc does not support filenames ending in whitespace.")
 }
 
@@ -620,7 +675,7 @@ func (s *Suite) Test_PlistLine_CheckDire
                "@imake-man 1 2 ${IMAKE_MANNEWSUFFIX}",
                "@unknown")
 
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "WARN: ~/PLIST:2: Please remove this line. It is no longer necessary.",
@@ -642,7 +697,7 @@ func (s *Suite) Test_plistLineSorter__un
                "bin/program1")
 
        t.EnableTracingToLog()
-       CheckLinesPlist(lines)
+       CheckLinesPlist(nil, lines)
 
        t.CheckOutputLines(
                "TRACE: + CheckLinesPlist(\"~/PLIST\")",

Index: pkgsrc/pkgtools/pkglint/files/pkgsrc.go
diff -u pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.20 pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.21
--- pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.20        Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/pkgsrc.go     Sun Mar 24 13:58:38 2019
@@ -41,8 +41,8 @@ type Pkgsrc struct {
        // to BUILD_DEFS.
        UserDefinedVars Scope
 
-       Deprecated map[string]string   //
-       vartypes   map[string]*Vartype // varcanon => type
+       Deprecated map[string]string
+       vartypes   VarTypeRegistry
 }
 
 func NewPkgsrc(dir string) *Pkgsrc {
@@ -59,7 +59,7 @@ func NewPkgsrc(dir string) *Pkgsrc {
                make(map[string][]string),
                NewScope(),
                make(map[string]string),
-               make(map[string]*Vartype)}
+               NewVarTypeRegistry()}
 
        return &src
 }
@@ -144,7 +144,7 @@ func (src *Pkgsrc) loadDefaultBuildDefs(
 // simple, since setting up a realistic pkgsrc environment requires
 // a lot of files.
 func (src *Pkgsrc) LoadInfrastructure() {
-       src.InitVartypes()
+       src.vartypes.Init(src)
        src.loadMasterSites()
        src.loadPkgOptions()
        src.loadDocChanges()
@@ -348,31 +348,37 @@ func (src *Pkgsrc) loadUntypedVars() {
        // Setting guessed to false prevents the vartype.guessed case in MkLineChecker.CheckVaruse.
        unknownType := Vartype{lkNone, BtUnknown, []ACLEntry{{"*", aclpAll}}, false}
 
-       handleLine := func(mkline MkLine) {
-               if mkline.IsVarassign() {
-                       varcanon := mkline.Varcanon()
-
-                       switch {
-                       case
-                               src.vartypes[varcanon] != nil,        // Already defined
-                               src.Tools.ByVarname(varcanon) != nil, // Already known as a tool
-                               hasPrefix(varcanon, "_"),             // Skip internal variables
-                               contains(varcanon, "$"),              // Indirect or parameterized
-                               hasSuffix(varcanon, "_MK"):           // Multiple-inclusion guard
-
-                       default:
-                               if trace.Tracing {
-                                       trace.Stepf("Untyped variable %q in %s", varcanon, mkline)
-                               }
-                               src.vartypes[varcanon] = &unknownType
+       define := func(varcanon string, mkline MkLine) {
+               switch {
+               case
+                       src.vartypes.DefinedCanon(varcanon),  // Already defined
+                       src.Tools.ByVarname(varcanon) != nil, // Already known as a tool
+                       hasPrefix(varcanon, "_"),             // Skip internal variables
+                       contains(varcanon, "$"),              // Indirect or parameterized
+                       hasSuffix(varcanon, "_MK"):           // Multiple-inclusion guard
+
+               default:
+                       if trace.Tracing {
+                               trace.Stepf("Untyped variable %q in %s", varcanon, mkline)
                        }
+                       src.vartypes.DefineType(varcanon, &unknownType)
                }
        }
 
        handleMkFile := func(path string) {
                mklines := LoadMk(path, 0)
                if mklines != nil && len(mklines.mklines) > 0 {
-                       mklines.ForEach(handleLine)
+                       G.Assertf(G.Mk == nil, "asdf")
+                       G.Mk = mklines // FIXME: This is because defineVar uses G.Mk instead of the method receiver.
+                       mklines.collectDefinedVariables()
+                       mklines.collectUsedVariables()
+                       for varname, mkline := range mklines.vars.firstDef {
+                               define(varnameCanon(varname), mkline)
+                       }
+                       for varname, mkline := range mklines.vars.used {
+                               define(varnameCanon(varname), mkline)
+                       }
+                       G.Mk = nil
                }
        }
 
@@ -895,10 +901,8 @@ func (src *Pkgsrc) VariableType(varname 
        // When scanning mk/** for otherwise unknown variables, their type
        // is set to BtUnknown. These variables must not override the guess
        // based on the variable name.
-       if vartype = src.vartypes[varname]; vartype != nil && vartype.basicType != BtUnknown {
-               return vartype
-       }
-       if vartype = src.vartypes[varnameCanon(varname)]; vartype != nil && vartype.basicType != BtUnknown {
+       vartype = src.vartypes.Canon(varname)
+       if vartype != nil && vartype.basicType != BtUnknown {
                return vartype
        }
 
@@ -965,10 +969,8 @@ func (src *Pkgsrc) guessVariableType(var
        }
 
        if gtype == nil {
-               if vartype = src.vartypes[varname]; vartype != nil {
-                       return vartype
-               }
-               if vartype = src.vartypes[varnameCanon(varname)]; vartype != nil {
+               vartype = src.vartypes.Canon(varname)
+               if vartype != nil {
                        return vartype
                }
        }

Index: pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go
diff -u pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.18 pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.19
--- pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.18   Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go        Sun Mar 24 13:58:38 2019
@@ -133,10 +133,10 @@ func (s *Suite) Test_Pkgsrc_loadTools(c 
                "TRACE: 1   tool false:FALSE:var:AtRunTime",
                "TRACE: 1   tool gawk:AWK::Nowhere",
                "TRACE: 1   tool m4:::AfterPrefsMk",
-               "TRACE: 1   tool msgfmt:::Nowhere",
+               "TRACE: 1   tool msgfmt:::AtRunTime",
                "TRACE: 1   tool mv:MV::AtRunTime",
                "TRACE: 1   tool pwd:PWD::AfterPrefsMk",
-               "TRACE: 1   tool strip:::Nowhere",
+               "TRACE: 1   tool strip:::AtRunTime",
                "TRACE: 1   tool test:TEST:var:AfterPrefsMk",
                "TRACE: 1   tool true:TRUE:var:AfterPrefsMk",
                "TRACE: - (*Tools).Trace()")

Index: pkgsrc/pkgtools/pkglint/files/redundantscope.go
diff -u pkgsrc/pkgtools/pkglint/files/redundantscope.go:1.1 pkgsrc/pkgtools/pkglint/files/redundantscope.go:1.2
--- pkgsrc/pkgtools/pkglint/files/redundantscope.go:1.1 Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/redundantscope.go     Sun Mar 24 13:58:38 2019
@@ -29,19 +29,15 @@ func NewRedundantScope() *RedundantScope
 
 func (s *RedundantScope) Check(mklines MkLines) {
        mklines.ForEach(func(mkline MkLine) {
-               s.Handle(mkline, mklines.indentation)
-       })
-}
-
-func (s *RedundantScope) Handle(mkline MkLine, ind *Indentation) {
-       s.updateIncludePath(mkline)
+               s.updateIncludePath(mkline)
 
-       switch {
-       case mkline.IsVarassign():
-               s.handleVarassign(mkline, ind)
-       }
+               switch {
+               case mkline.IsVarassign():
+                       s.handleVarassign(mkline, mklines.indentation)
+               }
 
-       s.handleVarUse(mkline)
+               s.handleVarUse(mkline)
+       })
 }
 
 func (s *RedundantScope) updateIncludePath(mkline MkLine) {
@@ -156,7 +152,7 @@ func (s *RedundantScope) handleVarassign
 
 func (s *RedundantScope) handleVarUse(mkline MkLine) {
        switch {
-       case mkline.IsVarassign(), mkline.IsCommentedVarassign():
+       case mkline.IsVarassign():
                for _, varname := range mkline.DetermineUsedVariables() {
                        info := s.get(varname)
                        info.vari.Read(mkline)
Index: pkgsrc/pkgtools/pkglint/files/redundantscope_test.go
diff -u pkgsrc/pkgtools/pkglint/files/redundantscope_test.go:1.1 pkgsrc/pkgtools/pkglint/files/redundantscope_test.go:1.2
--- pkgsrc/pkgtools/pkglint/files/redundantscope_test.go:1.1    Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/redundantscope_test.go        Sun Mar 24 13:58:38 2019
@@ -8,25 +8,25 @@ func (s *Suite) Test_RedundantScope__sin
        t := s.Init(c)
 
        mklines := t.NewMkLines("file.mk",
-               "DEFAULT?=\tvalue",
-               "ASSIGN?=\tvalue",
-               "APPEND?=\tvalue",
-               "EVAL?=\tvalue",
-               "SHELL?=\tvalue",
+               "VAR.def?=       value",
+               "VAR.asg?=       value",
+               "VAR.app?=       value",
+               "VAR.evl?=       value",
+               "VAR.shl?=       value",
                "",
-               "DEFAULT?=\tvalue",
-               "ASSIGN=\tvalue",
-               "APPEND+=\tvalue",
-               "EVAL:=\tvalue",
-               "SHELL!=\tvalue")
+               "VAR.def?=       value",
+               "VAR.asg=        value",
+               "VAR.app+=       value",
+               "VAR.evl:=       value",
+               "VAR.shl!=       value")
 
        NewRedundantScope().Check(mklines)
 
        t.CheckOutputLines(
-               "NOTE: file.mk:7: Default assignment of DEFAULT has no effect because of line 1.",
-               "NOTE: file.mk:8: Definition of ASSIGN is redundant because of line 2.",
-               "WARN: file.mk:4: Variable EVAL is overwritten in line 10.")
-       // TODO: "5: is overwritten later"
+               "NOTE: file.mk:7: Default assignment of VAR.def has no effect because of line 1.",
+               "NOTE: file.mk:8: Definition of VAR.asg is redundant because of line 2.",
+               "WARN: file.mk:4: Variable VAR.evl is overwritten in line 10.")
+       // TODO: "VAR.shl: is overwritten later"
 }
 
 // In a single file, five variables get assigned are value and are later overridden
@@ -35,25 +35,25 @@ func (s *Suite) Test_RedundantScope__sin
        t := s.Init(c)
 
        mklines := t.NewMkLines("file.mk",
-               "DEFAULT=\tvalue",
-               "ASSIGN=\tvalue",
-               "APPEND=\tvalue",
-               "EVAL=\tvalue",
-               "SHELL=\tvalue",
+               "VAR.def=        value",
+               "VAR.asg=        value",
+               "VAR.app=        value",
+               "VAR.evl=        value",
+               "VAR.shl=        value",
                "",
-               "DEFAULT?=\tvalue",
-               "ASSIGN=\tvalue",
-               "APPEND+=\tvalue",
-               "EVAL:=\tvalue",
-               "SHELL!=\tvalue")
+               "VAR.def?=       value",
+               "VAR.asg=        value",
+               "VAR.app+=       value",
+               "VAR.evl:=       value",
+               "VAR.shl!=       value")
 
        NewRedundantScope().Check(mklines)
 
        t.CheckOutputLines(
-               "NOTE: file.mk:7: Default assignment of DEFAULT has no effect because of line 1.",
-               "NOTE: file.mk:8: Definition of ASSIGN is redundant because of line 2.",
-               "WARN: file.mk:4: Variable EVAL is overwritten in line 10.")
-       // TODO: "5: is overwritten later"
+               "NOTE: file.mk:7: Default assignment of VAR.def has no effect because of line 1.",
+               "NOTE: file.mk:8: Definition of VAR.asg is redundant because of line 2.",
+               "WARN: file.mk:4: Variable VAR.evl is overwritten in line 10.")
+       // TODO: "VAR.shl: is overwritten later"
 }
 
 // In a single file, five variables get appended a value and are later overridden
@@ -62,25 +62,25 @@ func (s *Suite) Test_RedundantScope__sin
        t := s.Init(c)
 
        mklines := t.NewMkLines("file.mk",
-               "DEFAULT+=\tvalue",
-               "ASSIGN+=\tvalue",
-               "APPEND+=\tvalue",
-               "EVAL+=\tvalue",
-               "SHELL+=\tvalue",
+               "VAR.def+=       value",
+               "VAR.asg+=       value",
+               "VAR.app+=       value",
+               "VAR.evl+=       value",
+               "VAR.shl+=       value",
                "",
-               "DEFAULT?=\tvalue",
-               "ASSIGN=\tvalue",
-               "APPEND+=\tvalue",
-               "EVAL:=\tvalue",
-               "SHELL!=\tvalue")
+               "VAR.def?=       value",
+               "VAR.asg=        value",
+               "VAR.app+=       value",
+               "VAR.evl:=       value",
+               "VAR.shl!=       value")
 
        NewRedundantScope().Check(mklines)
 
        t.CheckOutputLines(
-               "NOTE: file.mk:7: Default assignment of DEFAULT has no effect because of line 1.",
-               "WARN: file.mk:2: Variable ASSIGN is overwritten in line 8.",
-               "WARN: file.mk:4: Variable EVAL is overwritten in line 10.")
-       // TODO: "5: is overwritten later"
+               "NOTE: file.mk:7: Default assignment of VAR.def has no effect because of line 1.",
+               "WARN: file.mk:2: Variable VAR.asg is overwritten in line 8.",
+               "WARN: file.mk:4: Variable VAR.evl is overwritten in line 10.")
+       // TODO: "VAR.shl: is overwritten later"
 }
 
 // In a single file, five variables get assigned a value using the := operator,
@@ -90,25 +90,25 @@ func (s *Suite) Test_RedundantScope__sin
        t := s.Init(c)
 
        mklines := t.NewMkLines("file.mk",
-               "DEFAULT:=\tvalue",
-               "ASSIGN:=\tvalue",
-               "APPEND:=\tvalue",
-               "EVAL:=\tvalue",
-               "SHELL:=\tvalue",
+               "VAR.def:=       value",
+               "VAR.asg:=       value",
+               "VAR.app:=       value",
+               "VAR.evl:=       value",
+               "VAR.shl:=       value",
                "",
-               "DEFAULT?=\tvalue",
-               "ASSIGN=\tvalue",
-               "APPEND+=\tvalue",
-               "EVAL:=\tvalue",
-               "SHELL!=\tvalue")
+               "VAR.def?=       value",
+               "VAR.asg=        value",
+               "VAR.app+=       value",
+               "VAR.evl:=       value",
+               "VAR.shl!=       value")
 
        NewRedundantScope().Check(mklines)
 
        t.CheckOutputLines(
-               "NOTE: file.mk:7: Default assignment of DEFAULT has no effect because of line 1.",
-               "NOTE: file.mk:8: Definition of ASSIGN is redundant because of line 2.",
-               "WARN: file.mk:4: Variable EVAL is overwritten in line 10.")
-       // TODO: "5: is overwritten later"
+               "NOTE: file.mk:7: Default assignment of VAR.def has no effect because of line 1.",
+               "NOTE: file.mk:8: Definition of VAR.asg is redundant because of line 2.",
+               "WARN: file.mk:4: Variable VAR.evl is overwritten in line 10.")
+       // TODO: "VAR.shl: is overwritten later"
 }
 
 // In a single file, five variables get assigned a value using the != operator,
@@ -119,25 +119,25 @@ func (s *Suite) Test_RedundantScope__sin
        t := s.Init(c)
 
        mklines := t.NewMkLines("file.mk",
-               "DEFAULT!=\tvalue",
-               "ASSIGN!=\tvalue",
-               "APPEND!=\tvalue",
-               "EVAL!=\tvalue",
-               "SHELL!=\tvalue",
+               "VAR.def!=       value",
+               "VAR.asg!=       value",
+               "VAR.app!=       value",
+               "VAR.evl!=       value",
+               "VAR.shl!=       value",
                "",
-               "DEFAULT?=\tvalue",
-               "ASSIGN=\tvalue",
-               "APPEND+=\tvalue",
-               "EVAL:=\tvalue",
-               "SHELL!=\tvalue")
+               "VAR.def?=       value",
+               "VAR.asg=        value",
+               "VAR.app+=       value",
+               "VAR.evl:=       value",
+               "VAR.shl!=       value")
 
        NewRedundantScope().Check(mklines)
 
        t.CheckOutputLines(
-               "NOTE: file.mk:7: Default assignment of DEFAULT has no effect because of line 1.",
-               "WARN: file.mk:2: Variable ASSIGN is overwritten in line 8.",
-               "WARN: file.mk:4: Variable EVAL is overwritten in line 10.")
-       // TODO: "5: is overwritten later"
+               "NOTE: file.mk:7: Default assignment of VAR.def has no effect because of line 1.",
+               "WARN: file.mk:2: Variable VAR.asg is overwritten in line 8.",
+               "WARN: file.mk:4: Variable VAR.evl is overwritten in line 10.")
+       // TODO: "VAR.shl: is overwritten later"
 }
 
 // In a single file, five variables get a default value and are later overridden
@@ -146,25 +146,25 @@ func (s *Suite) Test_RedundantScope__sin
        t := s.Init(c)
 
        mklines := t.NewMkLines("file.mk",
-               "DEFAULT?=\t${OTHER}",
-               "ASSIGN?=\t${OTHER}",
-               "APPEND?=\t${OTHER}",
-               "EVAL?=\t${OTHER}",
-               "SHELL?=\t${OTHER}",
+               "VAR.def?=       ${OTHER}",
+               "VAR.asg?=       ${OTHER}",
+               "VAR.app?=       ${OTHER}",
+               "VAR.evl?=       ${OTHER}",
+               "VAR.shl?=       ${OTHER}",
                "",
-               "DEFAULT?=\t${OTHER}",
-               "ASSIGN=\t${OTHER}",
-               "APPEND+=\t${OTHER}",
-               "EVAL:=\t${OTHER}",
-               "SHELL!=\t${OTHER}")
+               "VAR.def?=       ${OTHER}",
+               "VAR.asg=        ${OTHER}",
+               "VAR.app+=       ${OTHER}",
+               "VAR.evl:=       ${OTHER}",
+               "VAR.shl!=       ${OTHER}")
 
        NewRedundantScope().Check(mklines)
 
        t.CheckOutputLines(
-               "NOTE: file.mk:7: Default assignment of DEFAULT has no effect because of line 1.",
-               "NOTE: file.mk:8: Definition of ASSIGN is redundant because of line 2.")
-       // TODO: "4: is overwritten later",
-       // TODO: "5: is overwritten later"
+               "NOTE: file.mk:7: Default assignment of VAR.def has no effect because of line 1.",
+               "NOTE: file.mk:8: Definition of VAR.asg is redundant because of line 2.")
+       // TODO: "VAR.evl: is overwritten later",
+       // TODO: "VAR.shl: is overwritten later"
 }
 
 // In a single file, five variables get assigned are value and are later overridden
@@ -173,25 +173,25 @@ func (s *Suite) Test_RedundantScope__sin
        t := s.Init(c)
 
        mklines := t.NewMkLines("file.mk",
-               "DEFAULT=\t${OTHER}",
-               "ASSIGN=\t${OTHER}",
-               "APPEND=\t${OTHER}",
-               "EVAL=\t${OTHER}",
-               "SHELL=\t${OTHER}",
+               "VAR.def=        ${OTHER}",
+               "VAR.asg=        ${OTHER}",
+               "VAR.app=        ${OTHER}",
+               "VAR.evl=        ${OTHER}",
+               "VAR.shl=        ${OTHER}",
                "",
-               "DEFAULT?=\t${OTHER}",
-               "ASSIGN=\t${OTHER}",
-               "APPEND+=\t${OTHER}",
-               "EVAL:=\t${OTHER}",
-               "SHELL!=\t${OTHER}")
+               "VAR.def?=       ${OTHER}",
+               "VAR.asg=        ${OTHER}",
+               "VAR.app+=       ${OTHER}",
+               "VAR.evl:=       ${OTHER}",
+               "VAR.shl!=       ${OTHER}")
 
        NewRedundantScope().Check(mklines)
 
        t.CheckOutputLines(
-               "NOTE: file.mk:7: Default assignment of DEFAULT has no effect because of line 1.",
-               "NOTE: file.mk:8: Definition of ASSIGN is redundant because of line 2.")
-       // TODO: "4: is overwritten later",
-       // TODO: "5: is overwritten later"
+               "NOTE: file.mk:7: Default assignment of VAR.def has no effect because of line 1.",
+               "NOTE: file.mk:8: Definition of VAR.asg is redundant because of line 2.")
+       // TODO: "VAR.evl: is overwritten later",
+       // TODO: "VAR.shl: is overwritten later"
 }
 
 // In a single file, five variables get appended a value and are later overridden
@@ -200,25 +200,25 @@ func (s *Suite) Test_RedundantScope__sin
        t := s.Init(c)
 
        mklines := t.NewMkLines("file.mk",
-               "DEFAULT+=\t${OTHER}",
-               "ASSIGN+=\t${OTHER}",
-               "APPEND+=\t${OTHER}",
-               "EVAL+=\t${OTHER}",
-               "SHELL+=\t${OTHER}",
+               "VAR.def+=       ${OTHER}",
+               "VAR.asg+=       ${OTHER}",
+               "VAR.app+=       ${OTHER}",
+               "VAR.evl+=       ${OTHER}",
+               "VAR.shl+=       ${OTHER}",
                "",
-               "DEFAULT?=\t${OTHER}",
-               "ASSIGN=\t${OTHER}",
-               "APPEND+=\t${OTHER}",
-               "EVAL:=\t${OTHER}",
-               "SHELL!=\t${OTHER}")
+               "VAR.def?=       ${OTHER}",
+               "VAR.asg=        ${OTHER}",
+               "VAR.app+=       ${OTHER}",
+               "VAR.evl:=       ${OTHER}",
+               "VAR.shl!=       ${OTHER}")
 
        NewRedundantScope().Check(mklines)
 
        t.CheckOutputLines(
-               "NOTE: file.mk:7: Default assignment of DEFAULT has no effect because of line 1.",
-               "WARN: file.mk:2: Variable ASSIGN is overwritten in line 8.")
-       // TODO: "4: is overwritten later",
-       // TODO: "5: is overwritten later"
+               "NOTE: file.mk:7: Default assignment of VAR.def has no effect because of line 1.",
+               "WARN: file.mk:2: Variable VAR.asg is overwritten in line 8.")
+       // TODO: "VAR.evl: is overwritten later",
+       // TODO: "VAR.shl: is overwritten later"
 }
 
 // In a single file, five variables get assigned a value using the := operator,
@@ -228,25 +228,25 @@ func (s *Suite) Test_RedundantScope__sin
        t := s.Init(c)
 
        mklines := t.NewMkLines("file.mk",
-               "DEFAULT:=\t${OTHER}",
-               "ASSIGN:=\t${OTHER}",
-               "APPEND:=\t${OTHER}",
-               "EVAL:=\t${OTHER}",
-               "SHELL:=\t${OTHER}",
+               "VAR.def:=       ${OTHER}",
+               "VAR.asg:=       ${OTHER}",
+               "VAR.app:=       ${OTHER}",
+               "VAR.evl:=       ${OTHER}",
+               "VAR.shl:=       ${OTHER}",
                "",
-               "DEFAULT?=\t${OTHER}",
-               "ASSIGN=\t${OTHER}",
-               "APPEND+=\t${OTHER}",
-               "EVAL:=\t${OTHER}",
-               "SHELL!=\t${OTHER}")
+               "VAR.def?=       ${OTHER}",
+               "VAR.asg=        ${OTHER}",
+               "VAR.app+=       ${OTHER}",
+               "VAR.evl:=       ${OTHER}",
+               "VAR.shl!=       ${OTHER}")
 
        NewRedundantScope().Check(mklines)
 
        t.CheckOutputLines(
-               "NOTE: file.mk:7: Default assignment of DEFAULT has no effect because of line 1.",
-               "NOTE: file.mk:8: Definition of ASSIGN is redundant because of line 2.")
-       // TODO: "4: is overwritten later",
-       // TODO: "5: is overwritten later"
+               "NOTE: file.mk:7: Default assignment of VAR.def has no effect because of line 1.",
+               "NOTE: file.mk:8: Definition of VAR.asg is redundant because of line 2.")
+       // TODO: "VAR.evl: is overwritten later",
+       // TODO: "VAR.shl: is overwritten later"
 }
 
 // In a single file, five variables get assigned a value using the != operator,
@@ -257,73 +257,104 @@ func (s *Suite) Test_RedundantScope__sin
        t := s.Init(c)
 
        mklines := t.NewMkLines("file.mk",
-               "DEFAULT!=\t${OTHER}",
-               "ASSIGN!=\t${OTHER}",
-               "APPEND!=\t${OTHER}",
-               "EVAL!=\t${OTHER}",
-               "SHELL!=\t${OTHER}",
+               "VAR.def!=       ${OTHER}",
+               "VAR.asg!=       ${OTHER}",
+               "VAR.app!=       ${OTHER}",
+               "VAR.evl!=       ${OTHER}",
+               "VAR.shl!=       ${OTHER}",
                "",
-               "DEFAULT?=\t${OTHER}",
-               "ASSIGN=\t${OTHER}",
-               "APPEND+=\t${OTHER}",
-               "EVAL:=\t${OTHER}",
-               "SHELL!=\t${OTHER}")
+               "VAR.def?=       ${OTHER}",
+               "VAR.asg=        ${OTHER}",
+               "VAR.app+=       ${OTHER}",
+               "VAR.evl:=       ${OTHER}",
+               "VAR.shl!=       ${OTHER}")
 
        NewRedundantScope().Check(mklines)
 
        t.CheckOutputLines(
-               "NOTE: file.mk:7: Default assignment of DEFAULT has no effect because of line 1.",
-               "WARN: file.mk:2: Variable ASSIGN is overwritten in line 8.")
-       // TODO: "4: is overwritten later",
-       // TODO: "5: is overwritten later"
+               "NOTE: file.mk:7: Default assignment of VAR.def has no effect because of line 1.",
+               "WARN: file.mk:2: Variable VAR.asg is overwritten in line 8.")
+       // TODO: "VAR.evl: is overwritten later",
+       // TODO: "VAR.shl: is overwritten later"
 }
 
 func (s *Suite) Test_RedundantScope__after_including_same_value(c *check.C) {
        t := s.Init(c)
 
-       // Only test the ?=, = and += operators since the others are ignored,
-       // as of March 2019.
-       include, get := t.SetUpHierarchy()
-       include("including.mk",
-               include("included.mk",
-                       "VAR.def.def?= ${OTHER}",
-                       "VAR.def.asg?= ${OTHER}",
-                       "VAR.def.app?= ${OTHER}",
-                       "VAR.asg.def=  ${OTHER}",
-                       "VAR.asg.asg=  ${OTHER}",
-                       "VAR.asg.app=  ${OTHER}",
-                       "VAR.app.def+= ${OTHER}",
-                       "VAR.app.asg+= ${OTHER}",
-                       "VAR.app.app+= ${OTHER}"),
-               "VAR.def.def?= ${OTHER}",
-               "VAR.def.asg=  ${OTHER}",
-               "VAR.def.app+= ${OTHER}",
-               "VAR.asg.def?= ${OTHER}",
-               "VAR.asg.asg=  ${OTHER}",
-               "VAR.asg.app+= ${OTHER}",
-               "VAR.app.def?= ${OTHER}",
-               "VAR.app.asg=  ${OTHER}",
-               "VAR.app.app+= ${OTHER}")
-       mklines := get("including.mk")
+       // including.mk:1:  include "included.mk"
+       //   included.mk:1:   VAR.x.y op1 ${OTHER}
+       // including.mk:2:  VAR.x.y op2 ${OTHER}
+       //
+       test := func(includedOp, includingOp string, diagnostics ...string) {
+               opName := [...]string{"asg", "shl", "evl", "app", "def"}
+               varname := sprintf("VAR.%s.%s",
+                       opName[NewMkOperator(includedOp)],
+                       opName[NewMkOperator(includingOp)])
+
+               include, get := t.SetUpHierarchy()
+               include("including.mk",
+                       include("included.mk",
+                               sprintf("%s%s ${OTHER}", varname, includedOp)),
+                       sprintf("%s%s ${OTHER}", varname, includingOp))
 
-       NewRedundantScope().Check(mklines)
+               NewRedundantScope().Check(get("including.mk"))
 
-       t.CheckOutputLines(
-               "NOTE: including.mk:2: Default assignment of VAR.def.def has no effect because of included.mk:1.",
-               "NOTE: including.mk:3: Definition of VAR.def.asg is redundant because of included.mk:2.",
-               // VAR.def.app defines a default value and then appends to it. This is a common pattern.
-               // Appending the same value feels redundant but probably doesn't happen in practice.
-               // If it does, there should be a note for it.
-               "NOTE: including.mk:5: Default assignment of VAR.asg.def has no effect because of included.mk:4.",
-               "NOTE: including.mk:6: Definition of VAR.asg.asg is redundant because of included.mk:5.",
-               // VAR.asg.app defines a variable and later appends to it. This is a common pattern.
-               // Appending the same value feels redundant but probably doesn't happen in practice.
-               // If it does, there should be a note for it.
-               "NOTE: including.mk:8: Default assignment of VAR.app.def has no effect because of included.mk:7.",
-               // VAR.app.asg first appends and then overwrites. This might be a mistake.
-               // TODO: Find out whether this case happens in actual pkgsrc and if it's accidental.
-               // VAR.app.app first appends and then appends one more. This is a common pattern.
-       )
+               t.CheckOutput(diagnostics)
+       }
+
+       // As of March 2019, the != operator is ignored for the redundancy check.
+       // TODO: Add the != operator.
+
+       test("?=", "?=",
+               "NOTE: including.mk:2: Default assignment of VAR.def.def has no effect because of included.mk:1.")
+
+       test("?=", "=",
+               "NOTE: including.mk:2: Definition of VAR.def.asg is redundant because of included.mk:1.")
+
+       // VAR.def.app defines a default value and then appends to it. This is a common pattern.
+       // Appending the same value feels redundant but probably doesn't happen in practice.
+       // If it does, there should be a note for it.
+       test("?=", "+=")
+
+       // VAR.def.evl introduces a subtle difference since := evaluates the variable immediately.
+       // Therefore the assignment is not redundant.
+       test("?=", ":=")
+
+       test("=", "?=",
+               "NOTE: including.mk:2: Default assignment of VAR.asg.def has no effect because of included.mk:1.")
+
+       test("=", "=",
+               "NOTE: including.mk:2: Definition of VAR.asg.asg is redundant because of included.mk:1.")
+
+       // VAR.asg.app defines a variable and later appends to it. This is a common pattern.
+       // Appending the same value feels redundant but probably doesn't happen in practice.
+       // If it does, there should be a note for it.
+       test("=", "+=")
+
+       // VAR.asg.evl evaluates the variable immediately and is thus not redundant.
+       test("=", ":=")
+
+       test("+=", "?=",
+               "NOTE: including.mk:2: Default assignment of VAR.app.def has no effect because of included.mk:1.")
+
+       // VAR.app.asg first appends and then overwrites. This might be a mistake.
+       // TODO: Find out whether this case happens in actual pkgsrc and if it's accidental.
+       // VAR.app.app first appends and then appends one more. This is a common pattern.
+       test("+=", "=")
+
+       test("+=", "+=")
+
+       test("+=", ":=")
+
+       test(":=", "?=",
+               "NOTE: including.mk:2: Default assignment of VAR.evl.def has no effect because of included.mk:1.")
+
+       test(":=", "=",
+               "NOTE: including.mk:2: Definition of VAR.evl.asg is redundant because of included.mk:1.")
+
+       test(":=", "+=")
+
+       test(":=", ":=")
 }
 
 func (s *Suite) Test_RedundantScope__after_including_different_value(c *check.C) {
@@ -334,24 +365,24 @@ func (s *Suite) Test_RedundantScope__aft
        include, get := t.SetUpHierarchy()
        include("including.mk",
                include("included.mk",
-                       "VAR.def.def?= ${VALUE}",
-                       "VAR.def.asg?= ${VALUE}",
-                       "VAR.def.app?= ${VALUE}",
-                       "VAR.asg.def=  ${VALUE}",
-                       "VAR.asg.asg=  ${VALUE}",
-                       "VAR.asg.app=  ${VALUE}",
-                       "VAR.app.def+= ${VALUE}",
-                       "VAR.app.asg+= ${VALUE}",
-                       "VAR.app.app+= ${VALUE}"),
-               "VAR.def.def?= ${OTHER}",
-               "VAR.def.asg=  ${OTHER}",
-               "VAR.def.app+= ${OTHER}",
-               "VAR.asg.def?= ${OTHER}",
-               "VAR.asg.asg=  ${OTHER}",
-               "VAR.asg.app+= ${OTHER}",
-               "VAR.app.def?= ${OTHER}",
-               "VAR.app.asg=  ${OTHER}",
-               "VAR.app.app+= ${OTHER}")
+                       "VAR.def.def?=   ${VALUE}",
+                       "VAR.def.asg?=   ${VALUE}",
+                       "VAR.def.app?=   ${VALUE}",
+                       "VAR.asg.def=    ${VALUE}",
+                       "VAR.asg.asg=    ${VALUE}",
+                       "VAR.asg.app=    ${VALUE}",
+                       "VAR.app.def+=   ${VALUE}",
+                       "VAR.app.asg+=   ${VALUE}",
+                       "VAR.app.app+=   ${VALUE}"),
+               "VAR.def.def?=   ${OTHER}",
+               "VAR.def.asg=    ${OTHER}",
+               "VAR.def.app+=   ${OTHER}",
+               "VAR.asg.def?=   ${OTHER}",
+               "VAR.asg.asg=    ${OTHER}",
+               "VAR.asg.app+=   ${OTHER}",
+               "VAR.app.def?=   ${OTHER}",
+               "VAR.app.asg=    ${OTHER}",
+               "VAR.app.app+=   ${OTHER}")
        mklines := get("including.mk")
 
        NewRedundantScope().Check(mklines)
@@ -369,25 +400,25 @@ func (s *Suite) Test_RedundantScope__bef
        // as of March 2019.
        include, get := t.SetUpHierarchy()
        include("including.mk",
-               "VAR.def.def?= ${OTHER}",
-               "VAR.def.asg?= ${OTHER}",
-               "VAR.def.app?= ${OTHER}",
-               "VAR.asg.def=  ${OTHER}",
-               "VAR.asg.asg=  ${OTHER}",
-               "VAR.asg.app=  ${OTHER}",
-               "VAR.app.def+= ${OTHER}",
-               "VAR.app.asg+= ${OTHER}",
-               "VAR.app.app+= ${OTHER}",
+               "VAR.def.def?=   ${OTHER}",
+               "VAR.def.asg?=   ${OTHER}",
+               "VAR.def.app?=   ${OTHER}",
+               "VAR.asg.def=    ${OTHER}",
+               "VAR.asg.asg=    ${OTHER}",
+               "VAR.asg.app=    ${OTHER}",
+               "VAR.app.def+=   ${OTHER}",
+               "VAR.app.asg+=   ${OTHER}",
+               "VAR.app.app+=   ${OTHER}",
                include("included.mk",
-                       "VAR.def.def?= ${OTHER}",
-                       "VAR.def.asg=  ${OTHER}",
-                       "VAR.def.app+= ${OTHER}",
-                       "VAR.asg.def?= ${OTHER}",
-                       "VAR.asg.asg=  ${OTHER}",
-                       "VAR.asg.app+= ${OTHER}",
-                       "VAR.app.def?= ${OTHER}",
-                       "VAR.app.asg=  ${OTHER}",
-                       "VAR.app.app+= ${OTHER}"))
+                       "VAR.def.def?=   ${OTHER}",
+                       "VAR.def.asg=    ${OTHER}",
+                       "VAR.def.app+=   ${OTHER}",
+                       "VAR.asg.def?=   ${OTHER}",
+                       "VAR.asg.asg=    ${OTHER}",
+                       "VAR.asg.app+=   ${OTHER}",
+                       "VAR.app.def?=   ${OTHER}",
+                       "VAR.app.asg=    ${OTHER}",
+                       "VAR.app.app+=   ${OTHER}"))
        mklines := get("including.mk")
 
        NewRedundantScope().Check(mklines)
@@ -407,25 +438,25 @@ func (s *Suite) Test_RedundantScope__bef
        // as of March 2019.
        include, get := t.SetUpHierarchy()
        include("including.mk",
-               "VAR.def.def?= ${VALUE}",
-               "VAR.def.asg?= ${VALUE}",
-               "VAR.def.app?= ${VALUE}",
-               "VAR.asg.def=  ${VALUE}",
-               "VAR.asg.asg=  ${VALUE}",
-               "VAR.asg.app=  ${VALUE}",
-               "VAR.app.def+= ${VALUE}",
-               "VAR.app.asg+= ${VALUE}",
-               "VAR.app.app+= ${VALUE}",
+               "VAR.def.def?=   ${VALUE}",
+               "VAR.def.asg?=   ${VALUE}",
+               "VAR.def.app?=   ${VALUE}",
+               "VAR.asg.def=    ${VALUE}",
+               "VAR.asg.asg=    ${VALUE}",
+               "VAR.asg.app=    ${VALUE}",
+               "VAR.app.def+=   ${VALUE}",
+               "VAR.app.asg+=   ${VALUE}",
+               "VAR.app.app+=   ${VALUE}",
                include("included.mk",
-                       "VAR.def.def?= ${OTHER}",
-                       "VAR.def.asg=  ${OTHER}",
-                       "VAR.def.app+= ${OTHER}",
-                       "VAR.asg.def?= ${OTHER}",
-                       "VAR.asg.asg=  ${OTHER}",
-                       "VAR.asg.app+= ${OTHER}",
-                       "VAR.app.def?= ${OTHER}",
-                       "VAR.app.asg=  ${OTHER}",
-                       "VAR.app.app+= ${OTHER}"))
+                       "VAR.def.def?=   ${OTHER}",
+                       "VAR.def.asg=    ${OTHER}",
+                       "VAR.def.app+=   ${OTHER}",
+                       "VAR.asg.def?=   ${OTHER}",
+                       "VAR.asg.asg=    ${OTHER}",
+                       "VAR.asg.app+=   ${OTHER}",
+                       "VAR.app.def?=   ${OTHER}",
+                       "VAR.app.asg=    ${OTHER}",
+                       "VAR.app.app+=   ${OTHER}"))
        mklines := get("including.mk")
 
        NewRedundantScope().Check(mklines)
@@ -444,25 +475,25 @@ func (s *Suite) Test_RedundantScope__ind
        include, get := t.SetUpHierarchy()
        include("including.mk",
                include("included1.mk",
-                       "VAR.def.def?= ${OTHER}",
-                       "VAR.def.asg?= ${OTHER}",
-                       "VAR.def.app?= ${OTHER}",
-                       "VAR.asg.def=  ${OTHER}",
-                       "VAR.asg.asg=  ${OTHER}",
-                       "VAR.asg.app=  ${OTHER}",
-                       "VAR.app.def+= ${OTHER}",
-                       "VAR.app.asg+= ${OTHER}",
-                       "VAR.app.app+= ${OTHER}"),
+                       "VAR.def.def?=   ${OTHER}",
+                       "VAR.def.asg?=   ${OTHER}",
+                       "VAR.def.app?=   ${OTHER}",
+                       "VAR.asg.def=    ${OTHER}",
+                       "VAR.asg.asg=    ${OTHER}",
+                       "VAR.asg.app=    ${OTHER}",
+                       "VAR.app.def+=   ${OTHER}",
+                       "VAR.app.asg+=   ${OTHER}",
+                       "VAR.app.app+=   ${OTHER}"),
                include("included2.mk",
-                       "VAR.def.def?= ${OTHER}",
-                       "VAR.def.asg=  ${OTHER}",
-                       "VAR.def.app+= ${OTHER}",
-                       "VAR.asg.def?= ${OTHER}",
-                       "VAR.asg.asg=  ${OTHER}",
-                       "VAR.asg.app+= ${OTHER}",
-                       "VAR.app.def?= ${OTHER}",
-                       "VAR.app.asg=  ${OTHER}",
-                       "VAR.app.app+= ${OTHER}"))
+                       "VAR.def.def?=   ${OTHER}",
+                       "VAR.def.asg=    ${OTHER}",
+                       "VAR.def.app+=   ${OTHER}",
+                       "VAR.asg.def?=   ${OTHER}",
+                       "VAR.asg.asg=    ${OTHER}",
+                       "VAR.asg.app+=   ${OTHER}",
+                       "VAR.app.def?=   ${OTHER}",
+                       "VAR.app.asg=    ${OTHER}",
+                       "VAR.app.app+=   ${OTHER}"))
        mklines := get("including.mk")
 
        NewRedundantScope().Check(mklines)
@@ -481,25 +512,25 @@ func (s *Suite) Test_RedundantScope__ind
        include, get := t.SetUpHierarchy()
        include("including.mk",
                include("included1.mk",
-                       "VAR.def.def?= ${VALUE}",
-                       "VAR.def.asg?= ${VALUE}",
-                       "VAR.def.app?= ${VALUE}",
-                       "VAR.asg.def=  ${VALUE}",
-                       "VAR.asg.asg=  ${VALUE}",
-                       "VAR.asg.app=  ${VALUE}",
-                       "VAR.app.def+= ${VALUE}",
-                       "VAR.app.asg+= ${VALUE}",
-                       "VAR.app.app+= ${VALUE}"),
+                       "VAR.def.def?=   ${VALUE}",
+                       "VAR.def.asg?=   ${VALUE}",
+                       "VAR.def.app?=   ${VALUE}",
+                       "VAR.asg.def=    ${VALUE}",
+                       "VAR.asg.asg=    ${VALUE}",
+                       "VAR.asg.app=    ${VALUE}",
+                       "VAR.app.def+=   ${VALUE}",
+                       "VAR.app.asg+=   ${VALUE}",
+                       "VAR.app.app+=   ${VALUE}"),
                include("included2.mk",
-                       "VAR.def.def?= ${OTHER}",
-                       "VAR.def.asg=  ${OTHER}",
-                       "VAR.def.app+= ${OTHER}",
-                       "VAR.asg.def?= ${OTHER}",
-                       "VAR.asg.asg=  ${OTHER}",
-                       "VAR.asg.app+= ${OTHER}",
-                       "VAR.app.def?= ${OTHER}",
-                       "VAR.app.asg=  ${OTHER}",
-                       "VAR.app.app+= ${OTHER}"))
+                       "VAR.def.def?=   ${OTHER}",
+                       "VAR.def.asg=    ${OTHER}",
+                       "VAR.def.app+=   ${OTHER}",
+                       "VAR.asg.def?=   ${OTHER}",
+                       "VAR.asg.asg=    ${OTHER}",
+                       "VAR.asg.app+=   ${OTHER}",
+                       "VAR.app.def?=   ${OTHER}",
+                       "VAR.app.asg=    ${OTHER}",
+                       "VAR.app.app+=   ${OTHER}"))
        mklines := get("including.mk")
 
        NewRedundantScope().Check(mklines)
@@ -517,13 +548,13 @@ func (s *Suite) Test_RedundantScope__fil
 
        include("including.mk",
                include("other.mk",
-                       "VAR= other"),
+                       "VAR=    other"),
                include("module.mk",
-                       "VAR= module",
+                       "VAR=    module",
                        include("version.mk",
-                               "VAR= version"),
+                               "VAR=    version"),
                        include("env.mk",
-                               "VAR= env")))
+                               "VAR=     env")))
 
        NewRedundantScope().Check(get("including.mk"))
 
@@ -555,14 +586,152 @@ func (s *Suite) Test_RedundantScope__fil
                "WARN: module.mk:1: Variable VAR is overwritten in version.mk:1.")
 }
 
+// The RedundantScope keeps track of the variable values. As a consequence,
+// it reports the variable assignment in the last line as being redundant,
+// instead of warning that it destroys the previous value.
+func (s *Suite) Test_RedundantScope__assign_and_append_followed_by_assign(c *check.C) {
+       t := s.Init(c)
+
+       mklines := t.NewMkLines("redundant.mk",
+               "VAR=    first",
+               "VAR+=   second",
+               "VAR=    first second")
+
+       NewRedundantScope().Check(mklines)
+
+       t.CheckOutputLines(
+               "NOTE: redundant.mk:3: Definition of VAR is redundant because of line 2.")
+}
+
+// The redundancy analysis for a variable VAR is influenced by changes to
+// each variable that is referenced by VAR. The exact details also depend
+// on the assignment operators being used for VAR and OTHER.
+func (s *Suite) Test_RedundantScope__referenced_variable_is_modified(c *check.C) {
+       t := s.Init(c)
+
+       test := func(line1, line2, line3, line4 string, diagnostics ...string) {
+               mklines := t.NewMkLines("filename.mk",
+                       line1, line2, line3, line4)
+
+               NewRedundantScope().Check(mklines)
+
+               t.CheckOutput(diagnostics)
+       }
+
+       test(
+               "OTHER=  other-before",
+               "VAR=    ${OTHER}",
+               "OTHER?= other-after",
+               "VAR=    ${OTHER}",
+
+               // TODO: "3: has no effect"
+               "NOTE: filename.mk:4: Definition of VAR is redundant because of line 2.")
+
+       test(
+               "OTHER=  other-before",
+               "VAR=    ${OTHER}",
+               "OTHER=  other-after",
+               "VAR=    ${OTHER}",
+
+               // TODO: "3: overwrites",
+               "NOTE: filename.mk:4: Definition of VAR is redundant because of line 2.")
+
+       test(
+               "OTHER=  other-before",
+               "VAR=    ${OTHER}",
+               "OTHER+= other-after",
+               "VAR=    ${OTHER}",
+
+               "NOTE: filename.mk:4: Definition of VAR is redundant because of line 2.")
+
+       test(
+               "OTHER=  other-before",
+               "VAR=    ${OTHER}",
+               "OTHER:= other-after",
+               "VAR=    ${OTHER}",
+
+               // TODO: "3: overwrites line 1"
+               "NOTE: filename.mk:4: Definition of VAR is redundant because of line 2.")
+
+       test(
+               "OTHER=  other-before",
+               "VAR=    ${OTHER}",
+               "OTHER!= other-after",
+               "VAR=    ${OTHER}",
+
+               // TODO: "3: overwrites line 1",
+               "NOTE: filename.mk:4: Definition of VAR is redundant because of line 2.")
+}
+
+// The redundancy analysis for a variable VAR is influenced by changes to
+// each variable that is referenced by VAR. The exact details also depend
+// on the assignment operators being used for VAR and OTHER.
+func (s *Suite) Test_RedundantScope__variable_referencing_another_is_modified(c *check.C) {
+       t := s.Init(c)
+
+       test := func(line1, line2, line3, line4 string, diagnostics ...string) {
+               mklines := t.NewMkLines("filename.mk",
+                       line1, line2, line3, line4)
+
+               NewRedundantScope().Check(mklines)
+
+               t.CheckOutput(diagnostics)
+       }
+
+       // In this test, the second line is tested for each operator.
+
+       test(
+               "OTHER=  other-before",
+               "VAR?=   ${OTHER}",
+               "OTHER=  other-after",
+               "VAR=    ${OTHER}",
+
+               // TODO: "3: overwrites line 1"
+               "NOTE: filename.mk:4: Definition of VAR is redundant because of line 2.")
+
+       test(
+               "OTHER=  other-before",
+               "VAR=    ${OTHER}",
+               "OTHER=  other-after",
+               "VAR=    ${OTHER}",
+
+               // TODO: "3: overwrites",
+               "NOTE: filename.mk:4: Definition of VAR is redundant because of line 2.")
+
+       test(
+               "OTHER=  other-before",
+               "VAR+=   ${OTHER}",
+               "OTHER=  other-after",
+               "VAR=    ${OTHER}",
+
+               // TODO: "3: overwrites",
+               // The value from line 2 is prefixed by a space, therefore pkglint
+               // issues a warning here instead of an "is redundant" note.
+               "WARN: filename.mk:2: Variable VAR is overwritten in line 4.")
+
+       test(
+               "OTHER=  other-before",
+               "VAR:=   ${OTHER}",
+               "OTHER=  other-after",
+               "VAR=    ${OTHER}",
+
+               // As of March 2019, pkglint only looks at each variable in isolation.
+               // In this case, to detect that the assignment in line 1 has no effect,
+               // it's necessary to trace the assignment in line 2 and then see that
+               // the VAR from line 2 is immediately overwritten in line 4.
+               "NOTE: filename.mk:4: Definition of VAR is redundant because of line 2.")
+
+       test(
+               "OTHER=  other-before",
+               "VAR=    ${OTHER}",
+               "OTHER!= other-after",
+               "VAR=    ${OTHER}",
+
+               "NOTE: filename.mk:4: Definition of VAR is redundant because of line 2.")
+}
+
 // FIXME: Continue the systematic redundancy tests.
 //
-// A test where the operators = and += define a variable that afterwards
-// is assigned the same value using the ?= operator.
-//
-// Tests where the variables refer to other variables. These variables may
-// be read and written between the relevant assignments.
-//
 // Tests where the variables are defined conditionally using .if, .else, .endif.
 //
 // Tests where the variables are defined in a .for loop that might not be
@@ -578,22 +747,26 @@ func (s *Suite) Test_RedundantScope__fil
 // be modified by any assignment of the form BUILD_DIRS.${var} or even ${var}.
 // Without further analysis, pkglint cannot report redundancy warnings for any
 // package that uses such variable assignments.
+//
+// Tests for variables with modifiers, such as ${VAR:Uundef}, ${VAR:Mpattern},
+// ${command:sh}, ${command::=value}.
+//
+// A test that compares a package with the default values from mk/defaults/mk.conf.
+// A package doesn't need to override these defaults, and the redundancy check
+// should notify the package author of this redundancy.
 
 func (s *Suite) Test_RedundantScope__override_after_including(c *check.C) {
        t := s.Init(c)
-       t.CreateFileLines("included.mk",
-               "OVERRIDE=\tprevious value",
-               "REDUNDANT=\tredundant")
-       t.CreateFileLines("including.mk",
-               ".include \"included.mk\"",
-               "OVERRIDE=\toverridden value",
-               "REDUNDANT=\tredundant")
-       t.Chdir(".")
-       mklines := t.LoadMkInclude("including.mk")
 
-       // XXX: The warnings from here are not in the same order as the other warnings.
-       // XXX: There may be some warnings for the same file separated by warnings for other files.
-       NewRedundantScope().Check(mklines)
+       include, get := t.SetUpHierarchy()
+       include("including.mk",
+               include("included.mk",
+                       "OVERRIDE=       previous value",
+                       "REDUNDANT=      redundant"),
+               "OVERRIDE=       overridden value",
+               "REDUNDANT=      redundant")
+
+       NewRedundantScope().Check(get("including.mk"))
 
        t.CheckOutputLines(
                "NOTE: including.mk:3: Definition of REDUNDANT is redundant because of included.mk:2.")
@@ -601,15 +774,14 @@ func (s *Suite) Test_RedundantScope__ove
 
 func (s *Suite) Test_RedundantScope__redundant_assign_after_including(c *check.C) {
        t := s.Init(c)
-       t.CreateFileLines("included.mk",
-               "REDUNDANT=\tredundant")
-       t.CreateFileLines("including.mk",
-               ".include \"included.mk\"",
-               "REDUNDANT=\tredundant")
-       t.Chdir(".")
-       mklines := t.LoadMkInclude("including.mk")
 
-       NewRedundantScope().Check(mklines)
+       include, get := t.SetUpHierarchy()
+       include("including.mk",
+               include("included.mk",
+                       "REDUNDANT=      redundant"),
+               "REDUNDANT=      redundant")
+
+       NewRedundantScope().Check(get("including.mk"))
 
        t.CheckOutputLines(
                "NOTE: including.mk:2: Definition of REDUNDANT is redundant because of included.mk:1.")
@@ -617,20 +789,18 @@ func (s *Suite) Test_RedundantScope__red
 
 func (s *Suite) Test_RedundantScope__override_in_Makefile_after_including(c *check.C) {
        t := s.Init(c)
-       t.CreateFileLines("module.mk",
-               "VAR=\tvalue ${OTHER}",
-               "VAR?=\tvalue ${OTHER}",
-               "VAR=\tnew value")
-       t.CreateFileLines("Makefile",
-               ".include \"module.mk\"",
-               "VAR=\tthe package may overwrite variables from other files")
-       t.Chdir(".")
 
-       mklines := t.LoadMkInclude("Makefile")
+       include, get := t.SetUpHierarchy()
+       include("Makefile",
+               include("module.mk",
+                       "VAR=    value ${OTHER}",
+                       "VAR?=   value ${OTHER}",
+                       "VAR=    new value"),
+               "VAR=    the package may overwrite variables from other files")
 
        // XXX: The warnings from here are not in the same order as the other warnings.
        // XXX: There may be some warnings for the same file separated by warnings for other files.
-       NewRedundantScope().Check(mklines)
+       NewRedundantScope().Check(get("Makefile"))
 
        // No warning for VAR=... in Makefile since it makes sense to have common files
        // with default values for variables, overriding some of them in each package.
@@ -641,9 +811,10 @@ func (s *Suite) Test_RedundantScope__ove
 
 func (s *Suite) Test_RedundantScope__default_value_definitely_unused(c *check.C) {
        t := s.Init(c)
+
        mklines := t.NewMkLines("module.mk",
-               "VAR=\tvalue ${OTHER}",
-               "VAR?=\tdifferent value")
+               "VAR=    value ${OTHER}",
+               "VAR?=   different value")
 
        NewRedundantScope().Check(mklines)
 
@@ -655,9 +826,10 @@ func (s *Suite) Test_RedundantScope__def
 
 func (s *Suite) Test_RedundantScope__default_value_overridden(c *check.C) {
        t := s.Init(c)
+
        mklines := t.NewMkLines("module.mk",
-               "VAR?=\tdefault value",
-               "VAR=\toverridden value")
+               "VAR?=   default value",
+               "VAR=    overridden value")
 
        NewRedundantScope().Check(mklines)
 
@@ -667,9 +839,10 @@ func (s *Suite) Test_RedundantScope__def
 
 func (s *Suite) Test_RedundantScope__overwrite_same_value(c *check.C) {
        t := s.Init(c)
+
        mklines := t.NewMkLines("module.mk",
-               "VAR=\tvalue ${OTHER}",
-               "VAR=\tvalue ${OTHER}")
+               "VAR=    value ${OTHER}",
+               "VAR=    value ${OTHER}")
 
        NewRedundantScope().Check(mklines)
 
@@ -679,10 +852,11 @@ func (s *Suite) Test_RedundantScope__ove
 
 func (s *Suite) Test_RedundantScope__conditional_overwrite(c *check.C) {
        t := s.Init(c)
+
        mklines := t.NewMkLines("module.mk",
-               "VAR=\tdefault",
+               "VAR=    default",
                ".if ${OPSYS} == NetBSD",
-               "VAR=\topsys",
+               "VAR=    opsys",
                ".endif")
 
        NewRedundantScope().Check(mklines)
@@ -692,11 +866,12 @@ func (s *Suite) Test_RedundantScope__con
 
 func (s *Suite) Test_RedundantScope__overwrite_inside_conditional(c *check.C) {
        t := s.Init(c)
+
        mklines := t.NewMkLines("module.mk",
-               "VAR=\tgeneric",
+               "VAR=    generic",
                ".if ${OPSYS} == NetBSD",
-               "VAR=\tignored",
-               "VAR=\toverwritten",
+               "VAR=    ignored",
+               "VAR=    overwritten",
                ".endif")
 
        NewRedundantScope().Check(mklines)
@@ -708,17 +883,17 @@ func (s *Suite) Test_RedundantScope__ove
 
 func (s *Suite) Test_RedundantScope__conditionally_include(c *check.C) {
        t := s.Init(c)
-       t.CreateFileLines("module.mk",
-               "VAR=\tgeneric",
+
+       include, get := t.SetUpHierarchy()
+       include("module.mk",
+               "VAR=    generic",
                ".if ${OPSYS} == NetBSD",
-               ".  include \"included.mk\"",
+               include("included.mk",
+                       "VAR=    ignored",
+                       "VAR=    overwritten"),
                ".endif")
-       t.CreateFileLines("included.mk",
-               "VAR=\tignored",
-               "VAR=\toverwritten")
-       mklines := t.LoadMkInclude("module.mk")
 
-       NewRedundantScope().Check(mklines)
+       NewRedundantScope().Check(get("module.mk"))
 
        // TODO: expected a warning "WARN: module.mk:4: line 3 is ignored"
        //  Since line 3 and line 4 are in the same basic block, line 3 is definitely ignored.
@@ -727,26 +902,29 @@ func (s *Suite) Test_RedundantScope__con
 
 func (s *Suite) Test_RedundantScope__conditional_default(c *check.C) {
        t := s.Init(c)
+
        mklines := t.NewMkLines("module.mk",
-               "VAR=\tdefault",
+               "VAR=    default",
                ".if ${OPSYS} == NetBSD",
-               "VAR?=\topsys",
+               "VAR?=   opsys",
                ".endif")
 
        NewRedundantScope().Check(mklines)
 
-       // TODO: WARN: module.mk:3: The value \"opsys\" will never be assigned to VAR because it is defined unconditionally in line 1.
+       // TODO: WARN: module.mk:3: The value \"opsys\" will never be assigned
+       //  to VAR because it is defined unconditionally in line 1.
        t.CheckOutputEmpty()
 }
 
 // These warnings are precise and accurate since the value of VAR is not used between line 2 and 4.
 func (s *Suite) Test_RedundantScope__overwrite_same_variable_different_value(c *check.C) {
        t := s.Init(c)
+
        mklines := t.NewMkLines("module.mk",
-               "OTHER=\tvalue before",
-               "VAR=\tvalue ${OTHER}",
-               "OTHER=\tvalue after",
-               "VAR=\tvalue ${OTHER}")
+               "OTHER=  value before",
+               "VAR=    value ${OTHER}",
+               "OTHER=  value after",
+               "VAR=    value ${OTHER}")
 
        NewRedundantScope().Check(mklines)
 
@@ -761,21 +939,22 @@ func (s *Suite) Test_RedundantScope__ove
 
 func (s *Suite) Test_RedundantScope__overwrite_different_value_used_between(c *check.C) {
        t := s.Init(c)
+
        mklines := t.NewMkLines("module.mk",
-               "OTHER=\tvalue before",
-               "VAR=\tvalue ${OTHER}",
+               "OTHER=          value before",
+               "VAR=            value ${OTHER}",
 
                // VAR is used here at load time, therefore it must be defined at this point.
                // At this point, VAR uses the \"before\" value of OTHER.
-               "RESULT1:=\t${VAR}",
+               "RESULT1:=       ${VAR}",
 
-               "OTHER=\tvalue after",
+               "OTHER=          value after",
 
                // VAR is used here again at load time, this time using the \"after\" value of OTHER.
-               "RESULT2:=\t${VAR}",
+               "RESULT2:=       ${VAR}",
 
                // Still this definition is redundant.
-               "VAR=\tvalue ${OTHER}")
+               "VAR=            value ${OTHER}")
 
        NewRedundantScope().Check(mklines)
 
@@ -835,14 +1014,14 @@ func (s *Suite) Test_RedundantScope__pro
                "",
                ".include \"../../mk/bsd.fast.prefs.mk\"",
                "",
-               "CHECK_BUILTIN.gettext?=\tno",
+               "CHECK_BUILTIN.gettext?= no",
                ".if !empty(CHECK_BUILTIN.gettext:M[nN][oO])",
                ".endif")
        t.CreateFileLines("devel/gettext-lib/buildlink3.mk",
                MkRcsID,
-               "CHECK_BUILTIN.gettext:=\tyes",
+               "CHECK_BUILTIN.gettext:= yes",
                ".include \"builtin.mk\"",
-               "CHECK_BUILTIN.gettext:=\tno")
+               "CHECK_BUILTIN.gettext:= no")
        G.Pkgsrc.LoadInfrastructure()
 
        // Checking x11/Xaos instead of devel/gettext-lib avoids warnings
@@ -862,12 +1041,12 @@ func (s *Suite) Test_RedundantScope__pro
                ".include \"../../mk/pthread.buildlink3.mk\"")
        t.CreateFileLines("mk/pthread.buildlink3.mk",
                MkRcsID,
-               "CHECK_BUILTIN.gettext:=\tyes",
+               "CHECK_BUILTIN.gettext:= yes",
                ".include \"pthread.builtin.mk\"",
-               "CHECK_BUILTIN.gettext:=\tno")
+               "CHECK_BUILTIN.gettext:= no")
        t.CreateFileLines("mk/pthread.builtin.mk",
                MkRcsID,
-               "CHECK_BUILTIN.gettext?=\tno",
+               "CHECK_BUILTIN.gettext?= no",
                ".if !empty(CHECK_BUILTIN.gettext:M[nN][oO])",
                ".endif")
        G.Pkgsrc.LoadInfrastructure()
@@ -906,9 +1085,10 @@ func (s *Suite) Test_RedundantScope__pro
 
 func (s *Suite) Test_RedundantScope__shell_and_eval(c *check.C) {
        t := s.Init(c)
+
        mklines := t.NewMkLines("module.mk",
-               "VAR:=\tvalue ${OTHER}",
-               "VAR!=\tvalue ${OTHER}")
+               "VAR:=    value ${OTHER}",
+               "VAR!=    value ${OTHER}")
 
        NewRedundantScope().Check(mklines)
 
@@ -933,9 +1113,10 @@ func (s *Suite) Test_RedundantScope__she
 
 func (s *Suite) Test_RedundantScope__shell_and_eval_literal(c *check.C) {
        t := s.Init(c)
+
        mklines := t.NewMkLines("module.mk",
-               "VAR:=\tvalue",
-               "VAR!=\tvalue")
+               "VAR:=    value",
+               "VAR!=    value")
 
        NewRedundantScope().Check(mklines)
 
@@ -953,14 +1134,14 @@ func (s *Suite) Test_RedundantScope__inc
 
        t.SetUpPackage("category/package",
                ".include \"../../category/dependency/buildlink3.mk\"",
-               "CONFIGURE_ARGS+=\tone",
-               "CONFIGURE_ARGS=\ttwo",
-               "CONFIGURE_ARGS+=\tthree")
+               "CONFIGURE_ARGS+=        one",
+               "CONFIGURE_ARGS=         two",
+               "CONFIGURE_ARGS+=        three")
        t.SetUpPackage("category/dependency")
        t.CreateFileDummyBuildlink3("category/dependency/buildlink3.mk")
        t.CreateFileLines("category/dependency/builtin.mk",
                MkRcsID,
-               "CONFIGURE_ARGS.Darwin+=\tdarwin")
+               "CONFIGURE_ARGS.Darwin+= darwin")
 
        G.Check(t.File("category/package"))
 
@@ -971,12 +1152,11 @@ func (s *Suite) Test_RedundantScope__inc
 func (s *Suite) Test_RedundantScope__if_then_else(c *check.C) {
        t := s.Init(c)
 
-       mklines := t.SetUpFileMkLines("if-then-else.mk",
-               MkRcsID,
+       mklines := t.NewMkLines("if-then-else.mk",
                ".if exists(${FILE})",
-               "OS=\tNetBSD",
+               "OS=     NetBSD",
                ".else",
-               "OS=\tOTHER",
+               "OS=     OTHER",
                ".endif")
 
        NewRedundantScope().Check(mklines)
@@ -989,12 +1169,11 @@ func (s *Suite) Test_RedundantScope__if_
 func (s *Suite) Test_RedundantScope__if_then_else_without_variable(c *check.C) {
        t := s.Init(c)
 
-       mklines := t.SetUpFileMkLines("if-then-else.mk",
-               MkRcsID,
+       mklines := t.NewMkLines("if-then-else.mk",
                ".if exists(/nonexistent)",
-               "IT=\texists",
+               "IT=     exists",
                ".else",
-               "IT=\tdoesn't exist",
+               "IT=     doesn't exist",
                ".endif")
 
        NewRedundantScope().Check(mklines)
@@ -1009,55 +1188,51 @@ func (s *Suite) Test_RedundantScope__if_
 func (s *Suite) Test_RedundantScope__append_then_default(c *check.C) {
        t := s.Init(c)
 
-       mklines := t.SetUpFileMkLines("append-then-default.mk",
-               MkRcsID,
-               "VAR+=\tvalue",
-               "VAR?=\tvalue")
+       mklines := t.NewMkLines("append-then-default.mk",
+               "VAR+=   value",
+               "VAR?=   value")
 
        NewRedundantScope().Check(mklines)
 
        t.CheckOutputLines(
-               "NOTE: ~/append-then-default.mk:3: Default assignment of VAR has no effect because of line 2.")
+               "NOTE: append-then-default.mk:2: Default assignment of VAR has no effect because of line 1.")
 }
 
 func (s *Suite) Test_RedundantScope__assign_then_default_in_same_file(c *check.C) {
        t := s.Init(c)
 
-       mklines := t.SetUpFileMkLines("assign-then-default.mk",
-               MkRcsID,
-               "VAR=\tvalue",
-               "VAR?=\tvalue")
+       mklines := t.NewMkLines("assign-then-default.mk",
+               "VAR=    value",
+               "VAR?=   value")
 
        NewRedundantScope().Check(mklines)
 
        t.CheckOutputLines(
-               "NOTE: ~/assign-then-default.mk:3: " +
-                       "Default assignment of VAR has no effect because of line 2.")
+               "NOTE: assign-then-default.mk:2: " +
+                       "Default assignment of VAR has no effect because of line 1.")
 }
 
 func (s *Suite) Test_RedundantScope__eval_then_eval(c *check.C) {
        t := s.Init(c)
 
-       mklines := t.SetUpFileMkLines("filename.mk",
-               MkRcsID,
-               "VAR:=\tvalue",
-               "VAR:=\tvalue",
-               "VAR:=\tother")
+       mklines := t.NewMkLines("filename.mk",
+               "VAR:=   value",
+               "VAR:=   value",
+               "VAR:=   other")
 
        NewRedundantScope().Check(mklines)
 
        t.CheckOutputLines(
-               "WARN: ~/filename.mk:2: Variable VAR is overwritten in line 3.",
-               "WARN: ~/filename.mk:3: Variable VAR is overwritten in line 4.")
+               "WARN: filename.mk:1: Variable VAR is overwritten in line 2.",
+               "WARN: filename.mk:2: Variable VAR is overwritten in line 3.")
 }
 
 func (s *Suite) Test_RedundantScope__shell_then_assign(c *check.C) {
        t := s.Init(c)
 
-       mklines := t.SetUpFileMkLines("filename.mk",
-               MkRcsID,
-               "VAR!=\techo echo",
-               "VAR=\techo echo")
+       mklines := t.NewMkLines("filename.mk",
+               "VAR!=   echo echo",
+               "VAR=    echo echo")
 
        NewRedundantScope().Check(mklines)
 
@@ -1068,17 +1243,16 @@ func (s *Suite) Test_RedundantScope__she
        // It assigns a different value. Nevertheless, the shell command is
        // redundant and can be removed since its result is never used.
        t.CheckOutputLines(
-               "WARN: ~/filename.mk:2: Variable VAR is overwritten in line 3.")
+               "WARN: filename.mk:1: Variable VAR is overwritten in line 2.")
 }
 
 func (s *Suite) Test_RedundantScope__shell_then_read_then_assign(c *check.C) {
        t := s.Init(c)
 
        mklines := t.SetUpFileMkLines("filename.mk",
-               MkRcsID,
-               "VAR!=\techo echo",
-               "OUTPUT:=${VAR}",
-               "VAR=\techo echo")
+               "VAR!=           echo echo",
+               "OUTPUT:=        ${VAR}",
+               "VAR=            echo echo")
 
        NewRedundantScope().Check(mklines)
 
@@ -1089,37 +1263,31 @@ func (s *Suite) Test_RedundantScope__she
 func (s *Suite) Test_RedundantScope__assign_then_default_in_included_file(c *check.C) {
        t := s.Init(c)
 
-       t.CreateFileLines("assign-then-default.mk",
-               MkRcsID,
-               "VAR=\tvalue",
-               ".include \"included.mk\"")
-       t.CreateFileLines("included.mk",
-               MkRcsID,
-               "VAR?=\tvalue")
-       mklines := t.LoadMkInclude("assign-then-default.mk")
+       include, get := t.SetUpHierarchy()
+       include("assign-then-default.mk",
+               "VAR=    value",
+               include("included.mk",
+                       "VAR?=    value"))
 
-       NewRedundantScope().Check(mklines)
+       NewRedundantScope().Check(get("assign-then-default.mk"))
 
-       // If assign-then-default.mk:2 is deleted, VAR still has the same value.
+       // If assign-then-default.mk:1 were deleted, VAR would still have the same value.
        t.CheckOutputLines(
-               "NOTE: ~/assign-then-default.mk:2: Definition of VAR is redundant because of included.mk:2.")
+               "NOTE: assign-then-default.mk:1: Definition of VAR is redundant because of included.mk:1.")
 }
 
 func (s *Suite) Test_RedundantScope__conditionally_included_file(c *check.C) {
        t := s.Init(c)
 
-       t.CreateFileLines("including.mk",
-               MkRcsID,
-               "VAR=\tvalue",
+       include, get := t.SetUpHierarchy()
+       include("including.mk",
+               "VAR=    value",
                ".if ${COND}",
-               ".  include \"included.mk\"",
+               include("included.mk",
+                       "VAR?=   value"),
                ".endif")
-       t.CreateFileLines("included.mk",
-               MkRcsID,
-               "VAR?=\tvalue")
-       mklines := t.LoadMkInclude("including.mk")
 
-       NewRedundantScope().Check(mklines)
+       NewRedundantScope().Check(get("including.mk"))
 
        // The assignment in including.mk:2 is only redundant if included.mk is actually included.
        // Therefore both included.mk:2 nor including.mk:2 are relevant.
@@ -1129,18 +1297,17 @@ func (s *Suite) Test_RedundantScope__con
 func (s *Suite) Test_RedundantScope__procedure_parameters(c *check.C) {
        t := s.Init(c)
 
+       // TODO: make Tester.SetUpHierarchy accept a file multiple times.
        t.CreateFileLines("mk/pkg-build-options.mk",
-               MkRcsID,
-               "USED:=\t${pkgbase}")
+               "USED:=  ${pkgbase}")
        t.CreateFileLines("including.mk",
-               MkRcsID,
-               "pkgbase=\tpackage1",
+               "pkgbase= package1",
                ".include \"mk/pkg-build-options.mk\"",
                "",
-               "pkgbase=\tpackage2",
+               "pkgbase= package2",
                ".include \"mk/pkg-build-options.mk\"",
                "",
-               "pkgbase=\tpackage3",
+               "pkgbase= package3",
                ".include \"mk/pkg-build-options.mk\"")
        mklines := t.LoadMkInclude("including.mk")
 
@@ -1161,9 +1328,9 @@ func (s *Suite) Test_RedundantScope_hand
 
        include, get := t.SetUpHierarchy()
        include("including.mk",
-               "VAR!= echo 'hello, world'",
+               "VAR!=   echo 'hello, world'",
                include("included.mk",
-                       "VAR?= hello world"))
+                       "VAR?=   hello world"))
 
        NewRedundantScope().Check(get("including.mk"))
 
@@ -1176,17 +1343,14 @@ func (s *Suite) Test_RedundantScope_hand
 func (s *Suite) Test_RedundantScope__overwrite_definition_from_included_file(c *check.C) {
        t := s.Init(c)
 
-       t.CreateFileLines("included.mk",
-               MkRcsID,
-               "WRKSRC=\t${WRKDIR}/${PKGBASE}")
-       t.CreateFileLines("including.mk",
-               MkRcsID,
-               "SUBDIR=\t${WRKSRC}",
-               ".include \"included.mk\"",
-               "WRKSRC=\t${WRKDIR}/overwritten")
-       mklines := t.LoadMkInclude("including.mk")
+       include, get := t.SetUpHierarchy()
+       include("including.mk",
+               "SUBDIR= ${WRKSRC}",
+               include("included.mk",
+                       "WRKSRC= ${WRKDIR}/${PKGBASE}"),
+               "WRKSRC= ${WRKDIR}/overwritten")
 
-       NewRedundantScope().Check(mklines)
+       NewRedundantScope().Check(get("including.mk"))
 
        // Before pkglint 5.7.2 (2019-03-10), the above setup generated a warning:
        //
@@ -1215,22 +1379,39 @@ func (s *Suite) Test_RedundantScope__ove
 func (s *Suite) Test_RedundantScope_handleVarassign__conditional(c *check.C) {
        t := s.Init(c)
 
-       scope := NewRedundantScope()
        mklines := t.NewMkLines("filename.mk",
-               MkRcsID,
-               "VAR=\tvalue",
+               "VAR=    value",
                ".if 1",
-               "VAR=\tconditional",
+               "VAR=    conditional",
                ".endif")
 
-       mklines.ForEach(func(mkline MkLine) {
-               scope.Handle(mkline, mklines.indentation)
-       })
+       scope := NewRedundantScope()
+       scope.Check(mklines)
+       writeLocations := scope.get("VAR").vari.WriteLocations()
 
        t.Check(
-               scope.get("VAR").vari.WriteLocations(),
+               writeLocations,
                deepEquals,
-               []MkLine{mklines.mklines[1], mklines.mklines[3]})
+               []MkLine{mklines.mklines[0], mklines.mklines[2]})
+}
+
+// Ensures that commented variables do not influence the redundancy check.
+func (s *Suite) Test_RedundantScope__commented_variable_assignment(c *check.C) {
+       t := s.Init(c)
+
+       include, get := t.SetUpHierarchy()
+       include("main.mk",
+               include("redundant.mk",
+                       "VAR=    value"),
+               include("doc.mk",
+                       "#OTHER= ${VAR}"),
+               "VAR=     value",
+               "OTHER=   value")
+
+       NewRedundantScope().Check(get("main.mk"))
+
+       t.CheckOutputLines(
+               "NOTE: main.mk:3: Definition of VAR is redundant because of redundant.mk:1.")
 }
 
 func (s *Suite) Test_includePath_includes(c *check.C) {

Index: pkgsrc/pkgtools/pkglint/files/shell_test.go
diff -u pkgsrc/pkgtools/pkglint/files/shell_test.go:1.41 pkgsrc/pkgtools/pkglint/files/shell_test.go:1.42
--- pkgsrc/pkgtools/pkglint/files/shell_test.go:1.41    Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/shell_test.go Sun Mar 24 13:58:38 2019
@@ -143,7 +143,7 @@ func (s *Suite) Test_splitIntoShellToken
        c.Check(rest, equals, "")
 }
 
-func (s *Suite) Test_ShellLine_CheckShellCommandLine(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
@@ -153,23 +153,22 @@ func (s *Suite) Test_ShellLine_CheckShel
        t.SetUpTool("mkdir", "MKDIR", AtRunTime) // This is actually "mkdir -p".
        t.SetUpTool("unzip", "UNZIP_CMD", AtRunTime)
 
-       test := func(shellCommand string) {
+       test := func(shellCommand string, diagnostics ...string) {
                G.Mk = t.NewMkLines("filename.mk",
                        "\t"+shellCommand)
-               shline := NewShellLine(G.Mk.mklines[0])
+               ck := NewShellLineChecker(G.Mk.mklines[0])
 
                G.Mk.ForEach(func(mkline MkLine) {
-                       shline.CheckShellCommandLine(shline.mkline.ShellCommand())
+                       ck.CheckShellCommandLine(ck.mkline.ShellCommand())
                })
-       }
-
-       test("@# Comment")
 
-       t.CheckOutputEmpty()
+               t.CheckOutput(diagnostics)
+       }
 
-       test("uname=`uname`; echo $$uname; echo; ${PREFIX}/bin/command")
+       test("@# Comment",
+               nil...)
 
-       t.CheckOutputLines(
+       test("uname=`uname`; echo $$uname; echo; ${PREFIX}/bin/command",
                "WARN: filename.mk:1: Unknown shell command \"uname\".",
                "WARN: filename.mk:1: Please switch to \"set -e\" mode "+
                        "before using a semicolon (after \"uname=`uname`\") to separate commands.")
@@ -177,134 +176,103 @@ func (s *Suite) Test_ShellLine_CheckShel
        t.SetUpTool("echo", "", AtRunTime)
        t.SetUpVartypes()
 
-       test("echo ${PKGNAME:Q}") // VucQuotPlain
-
-       t.CheckOutputLines(
+       test("echo ${PKGNAME:Q}", // VucQuotPlain
                "NOTE: filename.mk:1: The :Q operator isn't necessary for ${PKGNAME} here.")
 
-       test("echo \"${CFLAGS:Q}\"") // VucQuotDquot
-
-       t.CheckOutputLines(
+       test("echo \"${CFLAGS:Q}\"", // VucQuotDquot
                "WARN: filename.mk:1: The :Q modifier should not be used inside double quotes.",
                "WARN: filename.mk:1: Please use ${CFLAGS:M*:Q} instead of ${CFLAGS:Q} "+
                        "and make sure the variable appears outside of any quoting characters.")
 
-       test("echo '${COMMENT:Q}'") // VucQuotSquot
-
-       t.CheckOutputLines(
+       test("echo '${COMMENT:Q}'", // VucQuotSquot
                "WARN: filename.mk:1: Please move ${COMMENT:Q} outside of any quoting characters.")
 
-       test("echo target=$@ exitcode=$$? '$$' \"\\$$\"")
-
-       t.CheckOutputLines(
+       test("echo target=$@ exitcode=$$? '$$' \"\\$$\"",
                "WARN: filename.mk:1: Please use \"${.TARGET}\" instead of \"$@\".",
                "WARN: filename.mk:1: The $? shell variable is often not available in \"set -e\" mode.")
 
-       test("echo $$@")
-
-       t.CheckOutputLines(
+       test("echo $$@",
                "WARN: filename.mk:1: The $@ shell variable should only be used in double quotes.")
 
-       test("echo \"$$\"") // As seen by make(1); the shell sees: echo "$"
-
        // No warning about a possibly missed variable name.
        // This occurs only rarely, and typically as part of a regular expression
        // where it is used intentionally.
-       t.CheckOutputEmpty()
-
-       test("echo \"\\n\"")
+       test("echo \"$$\"", // As seen by make(1); the shell sees: echo "$"
+               nil...)
 
-       t.CheckOutputEmpty()
+       test("echo \"\\n\"",
+               nil...)
 
-       test("${RUN} for f in *.c; do echo $${f%.c}; done")
-
-       t.CheckOutputEmpty()
+       test("${RUN} for f in *.c; do echo $${f%.c}; done",
+               nil...)
 
-       test("${RUN} set +x; echo $${variable+set}")
-
-       t.CheckOutputEmpty()
+       test("${RUN} set +x; echo $${variable+set}",
+               nil...)
 
        // Based on mail/thunderbird/Makefile, rev. 1.159
-       test("${RUN} subdir=\"`unzip -c \"$$e\" install.rdf | awk '/re/ { print \"hello\" }'`\"")
-
-       t.CheckOutputLines(
+       test("${RUN} subdir=\"`unzip -c \"$$e\" install.rdf | awk '/re/ { print \"hello\" }'`\"",
                "WARN: filename.mk:1: Double quotes inside backticks inside double quotes are error prone.",
                "WARN: filename.mk:1: The exitcode of \"unzip\" at the left of the | operator is ignored.")
 
        // From mail/thunderbird/Makefile, rev. 1.159
-       test("" +
-               "${RUN} for e in ${XPI_FILES}; do " +
-               "  subdir=\"`${UNZIP_CMD} -c \"$$e\" install.rdf | " +
-               "" + "awk '/.../ {print;exit;}'`\" && " +
-               "  ${MKDIR} \"${WRKDIR}/extensions/$$subdir\" && " +
-               "  cd \"${WRKDIR}/extensions/$$subdir\" && " +
-               "  ${UNZIP_CMD} -aqo $$e; " +
-               "done")
-
-       t.CheckOutputLines(
+       test(""+
+               "${RUN} for e in ${XPI_FILES}; do "+
+               "  subdir=\"`${UNZIP_CMD} -c \"$$e\" install.rdf | "+
+               ""+"awk '/.../ {print;exit;}'`\" && "+
+               "  ${MKDIR} \"${WRKDIR}/extensions/$$subdir\" && "+
+               "  cd \"${WRKDIR}/extensions/$$subdir\" && "+
+               "  ${UNZIP_CMD} -aqo $$e; "+
+               "done",
                "WARN: filename.mk:1: XPI_FILES is used but not defined.",
                "WARN: filename.mk:1: Double quotes inside backticks inside double quotes are error prone.",
                "WARN: filename.mk:1: The exitcode of \"${UNZIP_CMD}\" at the left of the | operator is ignored.")
 
        // From x11/wxGTK28/Makefile
-       test("" +
-               "set -e; cd ${WRKSRC}/locale; " +
-               "for lang in *.po; do " +
-               "  [ \"$${lang}\" = \"wxstd.po\" ] && continue; " +
-               "  ${TOOLS_PATH.msgfmt} -c -o \"$${lang%.po}.mo\" \"$${lang}\"; " +
-               "done")
-
        // TODO: Why is TOOLS_PATH.msgfmt not recognized?
        //  At least, the warning should be more specific, mentioning USE_TOOLS.
-       t.CheckOutputLines(
+       test(""+
+               "set -e; cd ${WRKSRC}/locale; "+
+               "for lang in *.po; do "+
+               "  [ \"$${lang}\" = \"wxstd.po\" ] && continue; "+
+               "  ${TOOLS_PATH.msgfmt} -c -o \"$${lang%.po}.mo\" \"$${lang}\"; "+
+               "done",
                "WARN: filename.mk:1: Unknown shell command \"[\".",
                "WARN: filename.mk:1: Unknown shell command \"${TOOLS_PATH.msgfmt}\".")
 
-       test("@cp from to")
-
-       t.CheckOutputLines(
+       test("@cp from to",
                "WARN: filename.mk:1: The shell command \"cp\" should not be hidden.")
 
-       test("-cp from to")
-
-       t.CheckOutputLines(
+       test("-cp from to",
                "WARN: filename.mk:1: Using a leading \"-\" to suppress errors is deprecated.")
 
-       test("-${MKDIR} deeply/nested/subdir")
-
-       t.CheckOutputLines(
+       test("-${MKDIR} deeply/nested/subdir",
                "WARN: filename.mk:1: Using a leading \"-\" to suppress errors is deprecated.")
 
        G.Pkg = NewPackage(t.File("category/pkgbase"))
        G.Pkg.Plist.Dirs["share/pkgbase"] = true
 
        // A directory that is found in the PLIST.
-       test("${RUN} ${INSTALL_DATA_DIR} share/pkgbase ${PREFIX}/share/pkgbase")
-
        // TODO: Add a test for using this command inside a conditional;
        //  the note should not appear then.
-
-       t.CheckOutputLines(
+       test("${RUN} ${INSTALL_DATA_DIR} share/pkgbase ${PREFIX}/share/pkgbase",
                "NOTE: filename.mk:1: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= share/pkgbase\" "+
                        "instead of \"${INSTALL_DATA_DIR}\".",
                "WARN: filename.mk:1: The INSTALL_*_DIR commands can only handle one directory at a time.")
 
        // A directory that is not found in the PLIST.
-       test("${RUN} ${INSTALL_DATA_DIR} ${PREFIX}/share/other")
-
-       t.CheckOutputLines(
+       test("${RUN} ${INSTALL_DATA_DIR} ${PREFIX}/share/other",
                "NOTE: filename.mk:1: You can use \"INSTALLATION_DIRS+= share/other\" instead of \"${INSTALL_DATA_DIR}\".")
 
        G.Pkg = nil
 
        // See PR 46570, item "1. It does not"
-       test("for x in 1 2 3; do echo \"$$x\" || exit 1; done")
-
-       t.CheckOutputEmpty() // No warning about missing error checking.
+       // No warning about missing error checking ("set -e").
+       test("for x in 1 2 3; do echo \"$$x\" || exit 1; done",
+               nil...)
 }
 
 // TODO: Document in detail that strip is not a regular tool.
-func (s *Suite) Test_ShellLine_CheckShellCommandLine__strip(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__strip(c *check.C) {
        t := s.Init(c)
 
        test := func(shellCommand string) {
@@ -312,8 +280,8 @@ func (s *Suite) Test_ShellLine_CheckShel
                        "\t"+shellCommand)
 
                G.Mk.ForEach(func(mkline MkLine) {
-                       shline := NewShellLine(mkline)
-                       shline.CheckShellCommandLine(mkline.ShellCommand())
+                       ck := NewShellLineChecker(mkline)
+                       ck.CheckShellCommandLine(mkline.ShellCommand())
                })
        }
 
@@ -330,22 +298,22 @@ func (s *Suite) Test_ShellLine_CheckShel
        t.CheckOutputEmpty()
 }
 
-func (s *Suite) Test_ShellLine_CheckShellCommandLine__nofix(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__nofix(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
        t.SetUpTool("echo", "", AtRunTime)
        G.Mk = t.NewMkLines("Makefile",
                "\techo ${PKGNAME:Q}")
-       shline := NewShellLine(G.Mk.mklines[0])
+       ck := NewShellLineChecker(G.Mk.mklines[0])
 
-       shline.CheckShellCommandLine("echo ${PKGNAME:Q}")
+       ck.CheckShellCommandLine("echo ${PKGNAME:Q}")
 
        t.CheckOutputLines(
                "NOTE: Makefile:1: The :Q operator isn't necessary for ${PKGNAME} here.")
 }
 
-func (s *Suite) Test_ShellLine_CheckShellCommandLine__show_autofix(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__show_autofix(c *check.C) {
        t := s.Init(c)
 
        t.SetUpCommandLine("-Wall", "--show-autofix")
@@ -353,16 +321,16 @@ func (s *Suite) Test_ShellLine_CheckShel
        t.SetUpTool("echo", "", AtRunTime)
        G.Mk = t.NewMkLines("Makefile",
                "\techo ${PKGNAME:Q}")
-       shline := NewShellLine(G.Mk.mklines[0])
+       ck := NewShellLineChecker(G.Mk.mklines[0])
 
-       shline.CheckShellCommandLine("echo ${PKGNAME:Q}")
+       ck.CheckShellCommandLine("echo ${PKGNAME:Q}")
 
        t.CheckOutputLines(
                "NOTE: Makefile:1: The :Q operator isn't necessary for ${PKGNAME} here.",
                "AUTOFIX: Makefile:1: Replacing \"${PKGNAME:Q}\" with \"${PKGNAME}\".")
 }
 
-func (s *Suite) Test_ShellLine_CheckShellCommandLine__autofix(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__autofix(c *check.C) {
        t := s.Init(c)
 
        t.SetUpCommandLine("-Wall", "--autofix")
@@ -370,9 +338,9 @@ func (s *Suite) Test_ShellLine_CheckShel
        t.SetUpTool("echo", "", AtRunTime)
        G.Mk = t.NewMkLines("Makefile",
                "\techo ${PKGNAME:Q}")
-       shline := NewShellLine(G.Mk.mklines[0])
+       ck := NewShellLineChecker(G.Mk.mklines[0])
 
-       shline.CheckShellCommandLine("echo ${PKGNAME:Q}")
+       ck.CheckShellCommandLine("echo ${PKGNAME:Q}")
 
        t.CheckOutputLines(
                "AUTOFIX: Makefile:1: Replacing \"${PKGNAME:Q}\" with \"${PKGNAME}\".")
@@ -404,8 +372,8 @@ func (s *Suite) Test_ShellProgramChecker
                "\t if :; then :; fi | right-side")
 
        for _, mkline := range G.Mk.mklines {
-               shline := NewShellLine(mkline)
-               shline.CheckShellCommandLine(mkline.ShellCommand())
+               ck := NewShellLineChecker(mkline)
+               ck.CheckShellCommandLine(mkline.ShellCommand())
        }
 
        t.CheckOutputLines(
@@ -419,13 +387,13 @@ func (s *Suite) Test_ShellProgramChecker
 }
 
 // TODO: Document the exact purpose of this test, or split it into useful tests.
-func (s *Suite) Test_ShellLine_CheckShellCommandLine__implementation(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__implementation(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
        G.Mk = t.NewMkLines("filename.mk",
                "# dummy")
-       shline := NewShellLine(G.Mk.mklines[0])
+       ck := NewShellLineChecker(G.Mk.mklines[0])
 
        // foobar="`echo \"foo   bar\"`"
        text := "foobar=\"`echo \\\"foo   bar\\\"`\""
@@ -435,41 +403,46 @@ func (s *Suite) Test_ShellLine_CheckShel
        c.Check(tokens, deepEquals, []string{text})
        c.Check(rest, equals, "")
 
-       G.Mk.ForEach(func(mkline MkLine) { shline.CheckWord(text, false, RunTime) })
+       G.Mk.ForEach(func(mkline MkLine) { ck.CheckWord(text, false, RunTime) })
 
        t.CheckOutputLines(
                "WARN: filename.mk:1: Unknown shell command \"echo\".")
 
-       G.Mk.ForEach(func(mkline MkLine) { shline.CheckShellCommandLine(text) })
+       G.Mk.ForEach(func(mkline MkLine) { ck.CheckShellCommandLine(text) })
 
        // No parse errors
        t.CheckOutputLines(
                "WARN: filename.mk:1: Unknown shell command \"echo\".")
 }
 
-func (s *Suite) Test_ShellLine_CheckShellCommandLine__dollar_without_variable(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__dollar_without_variable(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
        t.SetUpTool("pax", "", AtRunTime)
        G.Mk = t.NewMkLines("filename.mk",
                "# dummy")
-       shline := NewShellLine(G.Mk.mklines[0])
+       ck := NewShellLineChecker(G.Mk.mklines[0])
 
-       shline.CheckShellCommandLine("pax -rwpp -s /.*~$$//g . ${DESTDIR}${PREFIX}")
+       ck.CheckShellCommandLine("pax -rwpp -s /.*~$$//g . ${DESTDIR}${PREFIX}")
 
        t.CheckOutputEmpty()
 }
 
-func (s *Suite) Test_ShellLine_CheckWord(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckWord(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
 
        test := func(shellWord string, checkQuoting bool) {
-               shline := t.NewShellLine("dummy.mk", 1, "\t echo "+shellWord)
+               ck := t.NewShellLineChecker("dummy.mk", 1, "\t echo "+shellWord)
+
+               // Provide a storage for the "used but not defined" warnings.
+               // See checkVaruseUndefined and checkVarassignLeftNotUsed.
+               G.Mk = NewMkLines(NewLines("dummy.mk", nil))
+               defer func() { G.Mk = nil }()
 
-               shline.CheckWord(shellWord, checkQuoting, RunTime)
+               ck.CheckWord(shellWord, checkQuoting, RunTime)
        }
 
        test("${${list}}", false)
@@ -530,34 +503,34 @@ func (s *Suite) Test_ShellLine_CheckWord
        t.CheckOutputEmpty()
 }
 
-func (s *Suite) Test_ShellLine_CheckWord__dollar_without_variable(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckWord__dollar_without_variable(c *check.C) {
        t := s.Init(c)
 
-       shline := t.NewShellLine("filename.mk", 1, "# dummy")
+       ck := t.NewShellLineChecker("filename.mk", 1, "# dummy")
 
-       shline.CheckWord("/.*~$$//g", false, RunTime) // Typical argument to pax(1).
+       ck.CheckWord("/.*~$$//g", false, RunTime) // Typical argument to pax(1).
 
        t.CheckOutputEmpty()
 }
 
-func (s *Suite) Test_ShellLine_CheckWord__backslash_plus(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckWord__backslash_plus(c *check.C) {
        t := s.Init(c)
 
        t.SetUpTool("find", "FIND", AtRunTime)
-       shline := t.NewShellLine("filename.mk", 1, "\tfind . -exec rm -rf {} \\+")
+       ck := t.NewShellLineChecker("filename.mk", 1, "\tfind . -exec rm -rf {} \\+")
 
-       shline.CheckShellCommandLine(shline.mkline.ShellCommand())
+       ck.CheckShellCommandLine(ck.mkline.ShellCommand())
 
        // A backslash before any other character than " \ ` is discarded by the parser.
        t.CheckOutputEmpty()
 }
 
-func (s *Suite) Test_ShellLine_CheckWord__squot_dollar(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckWord__squot_dollar(c *check.C) {
        t := s.Init(c)
 
-       shline := t.NewShellLine("filename.mk", 1, "\t'$")
+       ck := t.NewShellLineChecker("filename.mk", 1, "\t'$")
 
-       shline.CheckWord(shline.mkline.ShellCommand(), false, RunTime)
+       ck.CheckWord(ck.mkline.ShellCommand(), false, RunTime)
 
        // FIXME: Should be parsed correctly. Make passes the dollar through (probably),
        //  and the shell parser should complain about the unfinished string literal.
@@ -566,30 +539,30 @@ func (s *Suite) Test_ShellLine_CheckWord
                "WARN: filename.mk:1: Internal pkglint error in ShellLine.CheckWord at \"'$\" (quoting=s), rest: $")
 }
 
-func (s *Suite) Test_ShellLine_CheckWord__dquot_dollar(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckWord__dquot_dollar(c *check.C) {
        t := s.Init(c)
 
-       shline := t.NewShellLine("filename.mk", 1, "\t\"$")
+       ck := t.NewShellLineChecker("filename.mk", 1, "\t\"$")
 
-       shline.CheckWord(shline.mkline.ShellCommand(), false, RunTime)
+       ck.CheckWord(ck.mkline.ShellCommand(), false, RunTime)
 
        // FIXME: Make consumes the dollar silently.
        //  This could be worth another pkglint warning.
        t.CheckOutputEmpty()
 }
 
-func (s *Suite) Test_ShellLine_CheckWord__dollar_subshell(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckWord__dollar_subshell(c *check.C) {
        t := s.Init(c)
 
-       shline := t.NewShellLine("filename.mk", 1, "\t$$(echo output)")
+       ck := t.NewShellLineChecker("filename.mk", 1, "\t$$(echo output)")
 
-       shline.CheckWord(shline.mkline.ShellCommand(), false, RunTime)
+       ck.CheckWord(ck.mkline.ShellCommand(), false, RunTime)
 
        t.CheckOutputLines(
                "WARN: filename.mk:1: Invoking subshells via $(...) is not portable enough.")
 }
 
-func (s *Suite) Test_ShellLine_CheckWord__PKGMANDIR(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckWord__PKGMANDIR(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
@@ -606,7 +579,7 @@ func (s *Suite) Test_ShellLine_CheckWord
                "NOTE: chat/ircII/Makefile:3: This variable value should be aligned to column 25.")
 }
 
-func (s *Suite) Test_ShellLine_unescapeBackticks__unfinished(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_unescapeBackticks__unfinished(c *check.C) {
        t := s.Init(c)
 
        mklines := t.NewMkLines("filename.mk",
@@ -626,7 +599,7 @@ func (s *Suite) Test_ShellLine_unescapeB
                "WARN: filename.mk:5: Pkglint ShellLine.CheckShellCommand: splitIntoShellTokens couldn't parse \"`${VAR}\"")
 }
 
-func (s *Suite) Test_ShellLine_unescapeBackticks__unfinished_direct(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_unescapeBackticks__unfinished_direct(c *check.C) {
        t := s.Init(c)
 
        mkline := t.NewMkLine("dummy.mk", 123, "\t# shell command")
@@ -635,17 +608,17 @@ func (s *Suite) Test_ShellLine_unescapeB
        // direct, forcing test is only to reach the code coverage.
        atoms := []*ShAtom{
                NewShAtom(shtText, "`", shqBackt)}
-       NewShellLine(mkline).
+       NewShellLineChecker(mkline).
                unescapeBackticks(&atoms, shqBackt)
 
        t.CheckOutputLines(
                "ERROR: dummy.mk:123: Unfinished backticks after \"\".")
 }
 
-func (s *Suite) Test_ShellLine_variableNeedsQuoting(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_variableNeedsQuoting(c *check.C) {
 
        test := func(shVarname string, expected bool) {
-               c.Check((*ShellLine).variableNeedsQuoting(nil, shVarname), equals, expected)
+               c.Check((*ShellLineChecker).variableNeedsQuoting(nil, shVarname), equals, expected)
        }
 
        test("#", false) // A length is always an integer.
@@ -674,7 +647,7 @@ func (s *Suite) Test_ShellLine_variableN
        test("1", true)       // Command line arguments can be arbitrary strings.
 }
 
-func (s *Suite) Test_ShellLine_variableNeedsQuoting__integration(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_variableNeedsQuoting__integration(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
@@ -696,7 +669,7 @@ func (s *Suite) Test_ShellLine_variableN
                "WARN: filename.mk:3: Unquoted shell variable \"target\".")
 }
 
-func (s *Suite) Test_ShellLine_CheckShellCommandLine__echo(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__echo(c *check.C) {
        t := s.Init(c)
 
        echo := t.SetUpTool("echo", "ECHO", AtRunTime)
@@ -709,13 +682,13 @@ func (s *Suite) Test_ShellLine_CheckShel
 
        t.CheckOutputEmpty()
 
-       NewShellLine(mkline).CheckShellCommandLine("echo \"hello, world\"")
+       NewShellLineChecker(mkline).CheckShellCommandLine("echo \"hello, world\"")
 
        t.CheckOutputLines(
                "WARN: filename.mk:3: Please use \"${ECHO}\" instead of \"echo\".")
 }
 
-func (s *Suite) Test_ShellLine_CheckShellCommandLine__shell_variables(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__shell_variables(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
@@ -725,47 +698,43 @@ func (s *Suite) Test_ShellLine_CheckShel
        t.SetUpTool("sed", "SED", AtRunTime)
        text := "\tfor f in *.pl; do ${SED} s,@PREFIX@,${PREFIX}, < $f > $f.tmp && ${MV} $f.tmp $f; done"
 
-       shline := t.NewShellLine("Makefile", 3, text)
-       shline.mkline.Tokenize(shline.mkline.ShellCommand(), true)
-       shline.CheckShellCommandLine(text)
+       ck := t.NewShellLineChecker("Makefile", 3, text)
+       ck.mkline.Tokenize(ck.mkline.ShellCommand(), true)
+       ck.CheckShellCommandLine(text)
 
        t.CheckOutputLines(
                "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Make variable or $$f if you mean a shell variable.",
                "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Make variable or $$f if you mean a shell variable.",
                "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Make variable or $$f if you mean a shell variable.",
                "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Make variable or $$f if you mean a shell variable.",
-               "NOTE: Makefile:3: Please use the SUBST framework instead of ${SED} and ${MV}.",
-               "WARN: Makefile:3: f is used but not defined.",
-               "WARN: Makefile:3: f is used but not defined.",
-               "WARN: Makefile:3: f is used but not defined.",
-               "WARN: Makefile:3: f is used but not defined.")
+               "NOTE: Makefile:3: Please use the SUBST framework instead of ${SED} and ${MV}.")
 
-       shline.CheckShellCommandLine("install -c manpage.1 ${PREFIX}/man/man1/manpage.1")
+       ck.CheckShellCommandLine("install -c manpage.1 ${PREFIX}/man/man1/manpage.1")
 
        t.CheckOutputLines(
                "WARN: Makefile:3: Please use ${PKGMANDIR} instead of \"man\".")
 
-       shline.CheckShellCommandLine("cp init-script ${PREFIX}/etc/rc.d/service")
+       ck.CheckShellCommandLine("cp init-script ${PREFIX}/etc/rc.d/service")
 
        t.CheckOutputLines(
                "WARN: Makefile:3: Please use the RCD_SCRIPTS mechanism to install rc.d scripts automatically to ${RCD_SCRIPTS_EXAMPLEDIR}.")
 }
 
-func (s *Suite) Test_ShellLine_checkInstallCommand(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_checkInstallCommand(c *check.C) {
        t := s.Init(c)
 
        G.Mk = t.NewMkLines("filename.mk",
                "# dummy")
        G.Mk.target = "do-install"
 
-       shline := t.NewShellLine("filename.mk", 1, "\tdummy")
+       ck := t.NewShellLineChecker("filename.mk", 1, "\tdummy")
 
-       shline.checkInstallCommand("sed")
+       ck.checkInstallCommand("sed")
 
        t.CheckOutputLines(
                "WARN: filename.mk:1: The shell command \"sed\" should not be used in the install phase.")
 
-       shline.checkInstallCommand("cp")
+       ck.checkInstallCommand("cp")
 
        t.CheckOutputLines(
                "WARN: filename.mk:1: ${CP} should not be used to install files.")
@@ -796,51 +765,51 @@ func (s *Suite) Test_splitIntoMkWords(c 
        c.Check(rest, equals, "'rest")
 }
 
-func (s *Suite) Test_ShellLine_CheckShellCommandLine__sed_and_mv(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__sed_and_mv(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
        t.SetUpTool("sed", "SED", AtRunTime)
        t.SetUpTool("mv", "MV", AtRunTime)
-       shline := t.NewShellLine("Makefile", 85, "\t${RUN} ${SED} 's,#,// comment:,g' filename > filename.tmp; ${MV} filename.tmp filename")
+       ck := t.NewShellLineChecker("Makefile", 85, "\t${RUN} ${SED} 's,#,// comment:,g' filename > filename.tmp; ${MV} filename.tmp filename")
 
-       shline.CheckShellCommandLine(shline.mkline.ShellCommand())
+       ck.CheckShellCommandLine(ck.mkline.ShellCommand())
 
        t.CheckOutputLines(
                "NOTE: Makefile:85: Please use the SUBST framework instead of ${SED} and ${MV}.")
 }
 
-func (s *Suite) Test_ShellLine_CheckShellCommandLine__subshell(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__subshell(c *check.C) {
        t := s.Init(c)
 
-       shline := t.NewShellLine("Makefile", 85, "\t${RUN} uname=$$(uname)")
+       ck := t.NewShellLineChecker("Makefile", 85, "\t${RUN} uname=$$(uname)")
 
-       shline.CheckShellCommandLine(shline.mkline.ShellCommand())
+       ck.CheckShellCommandLine(ck.mkline.ShellCommand())
 
        t.CheckOutputLines(
                "WARN: Makefile:85: Invoking subshells via $(...) is not portable enough.")
 }
 
-func (s *Suite) Test_ShellLine_CheckShellCommandLine__install_dir(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__install_dir(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
-       shline := t.NewShellLine("Makefile", 85, "\t${RUN} ${INSTALL_DATA_DIR} ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2")
+       ck := t.NewShellLineChecker("Makefile", 85, "\t${RUN} ${INSTALL_DATA_DIR} ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2")
 
-       shline.CheckShellCommandLine(shline.mkline.ShellCommand())
+       ck.CheckShellCommandLine(ck.mkline.ShellCommand())
 
        t.CheckOutputLines(
                "NOTE: Makefile:85: You can use \"INSTALLATION_DIRS+= dir1\" instead of \"${INSTALL_DATA_DIR}\".",
                "NOTE: Makefile:85: You can use \"INSTALLATION_DIRS+= dir2\" instead of \"${INSTALL_DATA_DIR}\".",
                "WARN: Makefile:85: The INSTALL_*_DIR commands can only handle one directory at a time.")
 
-       shline.CheckShellCommandLine("${INSTALL_DATA_DIR} -d -m 0755 ${DESTDIR}${PREFIX}/share/examples/gdchart")
+       ck.CheckShellCommandLine("${INSTALL_DATA_DIR} -d -m 0755 ${DESTDIR}${PREFIX}/share/examples/gdchart")
 
        // No warning about multiple directories, since 0755 is an option, not an argument.
        t.CheckOutputLines(
                "NOTE: Makefile:85: You can use \"INSTALLATION_DIRS+= share/examples/gdchart\" instead of \"${INSTALL_DATA_DIR}\".")
 
-       shline.CheckShellCommandLine("${INSTALL_DATA_DIR} -d -m 0755 ${DESTDIR}${PREFIX}/dir1 ${PREFIX}/dir2")
+       ck.CheckShellCommandLine("${INSTALL_DATA_DIR} -d -m 0755 ${DESTDIR}${PREFIX}/dir1 ${PREFIX}/dir2")
 
        t.CheckOutputLines(
                "NOTE: Makefile:85: You can use \"INSTALLATION_DIRS+= dir1\" instead of \"${INSTALL_DATA_DIR}\".",
@@ -848,20 +817,20 @@ func (s *Suite) Test_ShellLine_CheckShel
                "WARN: Makefile:85: The INSTALL_*_DIR commands can only handle one directory at a time.")
 }
 
-func (s *Suite) Test_ShellLine_CheckShellCommandLine__install_option_d(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine__install_option_d(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
-       shline := t.NewShellLine("Makefile", 85, "\t${RUN} ${INSTALL} -d ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2")
+       ck := t.NewShellLineChecker("Makefile", 85, "\t${RUN} ${INSTALL} -d ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2")
 
-       shline.CheckShellCommandLine(shline.mkline.ShellCommand())
+       ck.CheckShellCommandLine(ck.mkline.ShellCommand())
 
        t.CheckOutputLines(
                "NOTE: Makefile:85: You can use \"INSTALLATION_DIRS+= dir1\" instead of \"${INSTALL} -d\".",
                "NOTE: Makefile:85: You can use \"INSTALLATION_DIRS+= dir2\" instead of \"${INSTALL} -d\".")
 }
 
-func (s *Suite) Test_ShellLine__shell_comment_with_line_continuation(c *check.C) {
+func (s *Suite) Test_ShellLineChecker__shell_comment_with_line_continuation(c *check.C) {
        t := s.Init(c)
 
        mklines := t.SetUpFileMkLines("Makefile",
@@ -876,16 +845,16 @@ func (s *Suite) Test_ShellLine__shell_co
                "WARN: ~/Makefile:3--4: A shell comment does not stop at the end of line.")
 }
 
-func (s *Suite) Test_ShellLine_checkWordQuoting(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_checkWordQuoting(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
        t.SetUpTool("grep", "GREP", AtRunTime)
 
        test := func(lineno int, input string) {
-               shline := t.NewShellLine("module.mk", lineno, "\t"+input)
+               ck := t.NewShellLineChecker("module.mk", lineno, "\t"+input)
 
-               shline.checkWordQuoting(shline.mkline.ShellCommand(), true, RunTime)
+               ck.checkWordQuoting(ck.mkline.ShellCommand(), true, RunTime)
        }
 
        test(101, "socklen=`${GREP} 'expr' ${WRKSRC}/config.h`")
@@ -916,11 +885,11 @@ func (s *Suite) Test_ShellLine_checkWord
                "WARN: module.mk:106: Invoking subshells via $(...) is not portable enough.")
 }
 
-func (s *Suite) Test_ShellLine_unescapeBackticks(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_unescapeBackticks(c *check.C) {
        t := s.Init(c)
 
        test := func(lineno int, input string, expectedOutput string, expectedRest string) {
-               shline := t.NewShellLine("dummy.mk", lineno, "# dummy")
+               ck := t.NewShellLineChecker("dummy.mk", lineno, "# dummy")
 
                tok := NewShTokenizer(nil, input, false)
                atoms := tok.ShAtoms()
@@ -934,7 +903,7 @@ func (s *Suite) Test_ShellLine_unescapeB
                }
                c.Check(tok.Rest(), equals, "")
 
-               backtCommand := shline.unescapeBackticks(&atoms, q)
+               backtCommand := ck.unescapeBackticks(&atoms, q)
 
                var actualRest strings.Builder
                for _, atom := range atoms {
@@ -983,7 +952,7 @@ func (s *Suite) Test_ShellLine_unescapeB
                "WARN: dummy.mk:202: Double quotes inside backticks inside double quotes are error prone.")
 }
 
-func (s *Suite) Test_ShellLine_unescapeBackticks__dquotBacktDquot(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_unescapeBackticks__dquotBacktDquot(c *check.C) {
        t := s.Init(c)
 
        t.SetUpTool("echo", "", AtRunTime)
@@ -995,7 +964,7 @@ func (s *Suite) Test_ShellLine_unescapeB
                "WARN: dummy.mk:13: Double quotes inside backticks inside double quotes are error prone.")
 }
 
-func (s *Suite) Test_ShellLine__variable_outside_quotes(c *check.C) {
+func (s *Suite) Test_ShellLineChecker__variable_outside_quotes(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
@@ -1006,12 +975,12 @@ func (s *Suite) Test_ShellLine__variable
        mklines.Check()
 
        t.CheckOutputLines(
-               "WARN: dummy.mk:2: The variable GZIP may not be set by any package.",
+               "WARN: dummy.mk:2: The variable GZIP should not be set by any package.",
                "WARN: dummy.mk:2: Unquoted shell variable \"comment\".",
-               "WARN: dummy.mk:2: ECHO should not be evaluated indirectly at load time.")
+               "WARN: dummy.mk:2: ECHO should not be used indirectly at load time (via GZIP).")
 }
 
-func (s *Suite) Test_ShellLine_CheckShellCommand__cd_inside_if(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommand__cd_inside_if(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
@@ -1027,7 +996,7 @@ func (s *Suite) Test_ShellLine_CheckShel
                "ERROR: Makefile:3: The Solaris /bin/sh cannot handle \"cd\" inside conditionals.")
 }
 
-func (s *Suite) Test_ShellLine_CheckShellCommand__negated_pipe(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommand__negated_pipe(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
@@ -1044,7 +1013,7 @@ func (s *Suite) Test_ShellLine_CheckShel
                "WARN: Makefile:3: The Solaris /bin/sh does not support negation of shell commands.")
 }
 
-func (s *Suite) Test_ShellLine_CheckShellCommand__subshell(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommand__subshell(c *check.C) {
        t := s.Init(c)
 
        t.SetUpTool("echo", "ECHO", AtRunTime)
@@ -1072,7 +1041,7 @@ func (s *Suite) Test_ShellLine_CheckShel
                "WARN: Makefile:6: Invoking subshells via $(...) is not portable enough.")
 }
 
-func (s *Suite) Test_ShellLine_CheckShellCommand__case_patterns_from_variable(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_CheckShellCommand__case_patterns_from_variable(c *check.C) {
        t := s.Init(c)
 
        t.SetUpVartypes()
@@ -1090,7 +1059,7 @@ func (s *Suite) Test_ShellLine_CheckShel
                        "parse error at []string{\"*\", \")\", \"continue\", \";\", \"esac\"}")
 }
 
-func (s *Suite) Test_ShellLine_checkHiddenAndSuppress(c *check.C) {
+func (s *Suite) Test_ShellLineChecker_checkHiddenAndSuppress(c *check.C) {
        t := s.Init(c)
 
        t.SetUpTool("echo", "ECHO", AtRunTime)

Index: pkgsrc/pkgtools/pkglint/files/util.go
diff -u pkgsrc/pkgtools/pkglint/files/util.go:1.39 pkgsrc/pkgtools/pkglint/files/util.go:1.40
--- pkgsrc/pkgtools/pkglint/files/util.go:1.39  Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/util.go       Sun Mar 24 13:58:38 2019
@@ -118,12 +118,10 @@ func imax(a, b int) int {
        return b
 }
 
-func mustMatch(s string, re regex.Pattern) []string {
-       m := G.res.Match(s, re)
-       if m == nil {
-               G.Assertf(false, "mustMatch %q %q", s, re)
+func assertNil(err error, format string, args ...interface{}) {
+       if err != nil {
+               panic("Pkglint internal error: " + sprintf(format, args...) + ": " + err.Error())
        }
-       return m
 }
 
 func isEmptyDir(filename string) bool {
@@ -293,27 +291,27 @@ func varnameParam(varname string) string
 }
 
 // defineVar marks a variable as defined in both the current package and the current file.
-func defineVar(mkline MkLine, varname string) {
-       if G.Mk != nil {
-               G.Mk.vars.Define(varname, mkline)
+func defineVar(pkg *Package, mklines MkLines, mkline MkLine, varname string) {
+       if mklines != nil {
+               mklines.vars.Define(varname, mkline)
        }
-       if G.Pkg != nil {
-               G.Pkg.vars.Define(varname, mkline)
+       if pkg != nil {
+               pkg.vars.Define(varname, mkline)
        }
 }
 
 // varIsDefinedSimilar tests whether the variable (or its canonicalized form)
 // is defined in the current package or in the current file.
-func varIsDefinedSimilar(varname string) bool {
-       return G.Mk != nil && (G.Mk.vars.DefinedSimilar(varname) || G.Mk.forVars[varname]) ||
-               G.Pkg != nil && G.Pkg.vars.DefinedSimilar(varname)
+func varIsDefinedSimilar(pkg *Package, mklines MkLines, varname string) bool {
+       return mklines != nil && (mklines.vars.DefinedSimilar(varname) || mklines.forVars[varname]) ||
+               pkg != nil && pkg.vars.DefinedSimilar(varname)
 }
 
 // varIsUsedSimilar tests whether the variable (or its canonicalized form)
 // is used in the current package or in the current file.
-func varIsUsedSimilar(varname string) bool {
-       return G.Mk != nil && G.Mk.vars.UsedSimilar(varname) ||
-               G.Pkg != nil && G.Pkg.vars.UsedSimilar(varname)
+func varIsUsedSimilar(pkg *Package, mklines MkLines, varname string) bool {
+       return mklines != nil && mklines.vars.UsedSimilar(varname) ||
+               pkg != nil && pkg.vars.UsedSimilar(varname)
 }
 
 func fileExists(filename string) bool {
@@ -414,9 +412,11 @@ func relpath(from, to string) (result st
 }
 
 func abspath(filename string) string {
-       abs, err := filepath.Abs(filename)
-       G.AssertNil(err, "abspath %q", filename)
-       return filepath.ToSlash(abs)
+       abs := filename
+       if !filepath.IsAbs(filename) {
+               abs = G.cwd + "/" + abs
+       }
+       return path.Clean(abs)
 }
 
 // Differs from path.Clean in that only "../../" is replaced, not "../".
@@ -979,7 +979,7 @@ func escapePrintable(s string) string {
                case rune(byte(r)) == r && textproc.XPrint.Contains(byte(rest[j])):
                        escaped.WriteByte(byte(r))
                case r == 0xFFFD && !hasPrefix(rest[j:], "\uFFFD"):
-                       _, _ = fmt.Fprintf(&escaped, "<\\x%02X>", rest[j])
+                       _, _ = fmt.Fprintf(&escaped, "<0x%02X>", rest[j])
                default:
                        _, _ = fmt.Fprintf(&escaped, "<%U>", r)
                }

Index: pkgsrc/pkgtools/pkglint/files/vardefs.go
diff -u pkgsrc/pkgtools/pkglint/files/vardefs.go:1.56 pkgsrc/pkgtools/pkglint/files/vardefs.go:1.57
--- pkgsrc/pkgtools/pkglint/files/vardefs.go:1.56       Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/vardefs.go    Sun Mar 24 13:58:38 2019
@@ -6,92 +6,256 @@ import (
        "strings"
 )
 
-// This file defines the specific type of some variables.
+// This file defines the type and the access permissions of most pkgsrc
+// variables.
 //
-// Some are plain values, some are lists.
-// Lists are split like in the shell, using "double" and 'single' quotes
-// to enclose spaces.
+// Some types are plain values, some are lists. Lists are split like in the
+// shell, using "double" and 'single' quotes to enclose spaces.
 //
 // See vartypecheck.go for how these types are checked.
+//
+// The permissions depend on the name of the file where the variable is
+// either assigned or used. There are several types of Makefile fragments
+// in pkgsrc, and some of them have very specific tasks, like buildlink3.mk,
+// builtin.mk and options.mk.
+//
+// TODO: There are separate permission rules for files from the pkgsrc
+//  infrastructure since the infrastructure basically provides the API, and
+//  the packages use the API.
+//
+// Variables that are defined by packages are usually used by the
+// infrastructure, and vice versa. There are also user-defined variables,
+// which from the view point of a package, are the same as variables
+// defined by the infrastructure.
+
+type VarTypeRegistry struct {
+       types map[string]*Vartype // varcanon => type
+}
+
+func NewVarTypeRegistry() VarTypeRegistry {
+       return VarTypeRegistry{make(map[string]*Vartype)}
+}
+
+func (reg *VarTypeRegistry) Canon(varname string) *Vartype {
+       vartype := reg.types[varname]
+       if vartype == nil {
+               vartype = reg.types[varnameCanon(varname)]
+       }
+       return vartype
+}
+
+func (reg *VarTypeRegistry) DefinedExact(varname string) bool {
+       return reg.types[varname] != nil
+}
+
+func (reg *VarTypeRegistry) DefinedCanon(varname string) bool {
+       return reg.Canon(varname) != nil
+}
+
+func (reg *VarTypeRegistry) DefineType(varcanon string, vartype *Vartype) {
+       reg.types[varcanon] = vartype
+}
+
+func (reg *VarTypeRegistry) Define(varname string, kindOfList KindOfList, basicType *BasicType, aclEntries ...ACLEntry) {
+       m, varbase, varparam := match2(varname, `^([A-Z_.][A-Z0-9_]*|@)(|\*|\.\*)$`)
+       G.Assertf(m, "invalid variable name")
+
+       vartype := Vartype{kindOfList, basicType, aclEntries, false}
+
+       if varparam == "" || varparam == "*" {
+               reg.types[varbase] = &vartype
+       }
+       if varparam == "*" || varparam == ".*" {
+               reg.types[varbase+".*"] = &vartype
+       }
+}
+
+// DefineParse defines a variable with the given type and permissions.
+//
+// A permission entry looks like this:
+//  "Makefile, Makefile.*, *.mk: default, set, append, use, use-loadtime"
+// Only certain filenames are allowed in the part before the colon,
+// to prevent typos. To use arbitrary filenames, prefix them with
+// "special:".
+//
+// TODO: To be implemented: when prefixed with "infra:", the entry only
+//  applies to files within the pkgsrc infrastructure. Without this prefix,
+//  the pattern only applies to files outside the pkgsrc infrastructure.
+//
+// FIXME: Force the permissions to always be in the same order:
+//  default, set, append, use, use-loadtime.
+func (reg *VarTypeRegistry) DefineParse(varname string, kindOfList KindOfList, basicType *BasicType, aclEntries ...string) {
+       parsedEntries := reg.parseACLEntries(varname, aclEntries...)
+       reg.Define(varname, kindOfList, basicType, parsedEntries...)
+}
 
 // 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 (src *Pkgsrc) 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]
+func (reg *VarTypeRegistry) Init(src *Pkgsrc) {
 
-               vartype := Vartype{kindOfList, checker, parseACLEntries(varname, aclEntries), false}
+       // acl defines the permissions of a variable by listing the permissions
+       // individually.
+       //
+       // Each variable that uses this function directly must document:
+       //  - which of the predefined permission sets is the closest
+       //  - how this individual permission set differs
+       //  - why the predefined permission set is not good enough
+       //  - which packages need this custom permission set.
+       acl := func(varname string, basicType *BasicType, aclEntries ...string) {
+               reg.DefineParse(varname, lkNone, basicType, aclEntries...)
+       }
 
-               if varparam == "" || varparam == "*" {
-                       src.vartypes[varbase] = &vartype
-               }
-               if varparam == "*" || varparam == ".*" {
-                       src.vartypes[varbase+".*"] = &vartype
-               }
+       // acllist defines the permissions of a list variable by listing
+       // the permissions individually.
+       //
+       // Each variable that uses this function directly must document:
+       //  - which of the predefined permission sets is the closest
+       //  - how this individual permission set differs
+       //  - why the predefined permission set is not good enough
+       //  - which packages need this custom permission set.
+       acllist := func(varname string, basicType *BasicType, aclEntries ...string) {
+               reg.DefineParse(varname, lkShell, basicType, aclEntries...)
        }
 
-       // 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: none; "+
-                       "Makefile.*, *.mk: default, set, use")
+       // A package-settable variable may be set in all Makefiles except buildlink3.mk and builtin.mk.
+       pkg := func(varname string, basicType *BasicType) {
+               acl(varname, basicType,
+                       "buildlink3.mk, builtin.mk: none",
+                       "Makefile, Makefile.*, *.mk: default, set, use")
        }
 
        // pkgload is the same as pkg, except that the variable may be accessed at load time.
-       pkgload := func(varname string, kindOfList KindOfList, checker *BasicType) {
-               acl(varname, kindOfList, checker, ""+
-                       "Makefile: set, use, use-loadtime; "+
-                       "buildlink3.mk, builtin.mk: none; "+
-                       "Makefile.*, *.mk: default, set, use, use-loadtime")
-       }
-
-       // 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: none; "+
-                       "*.mk: append, default, use")
+       pkgload := func(varname string, basicType *BasicType) {
+               reg.DefineParse(varname, lkNone, basicType,
+                       "buildlink3.mk: none",
+                       "builtin.mk: use, use-loadtime",
+                       "Makefile, Makefile.*, *.mk: default, set, use, use-loadtime")
+       }
+
+       // A package-defined list may be defined and appended to in all Makefiles
+       // except buildlink3.mk and builtin.mk. Simple assignment (instead of
+       // appending) is also allowed. If this leads of an unconditional
+       // assignment overriding a previous value, the redundancy check will
+       // catch it.
+       pkglist := func(varname string, basicType *BasicType) {
+               acllist(varname, basicType,
+                       "buildlink3.mk, builtin.mk: none",
+                       "Makefile, Makefile.*, *.mk: default, set, append, use")
+       }
+
+       // pkgappend declares a variable that may use the += operator,
+       // even though it is not a list where each item can be interpreted
+       // on its own.
+       //
+       // This applies to lists in which a single logical list item is
+       // composed of several syntactical words, such as CONF_FILES, which is
+       // a list of filename pairs.
+       //
+       // This also applies to COMMENT, which is not a list at all but a string
+       // that is sometimes composed of a common prefix and a package-specific
+       // suffix.
+       pkgappend := func(varname string, basicType *BasicType) {
+               acl(varname, basicType,
+                       "buildlink3.mk, builtin.mk: none",
+                       "Makefile, Makefile.*, *.mk: default, set, append, use")
+       }
+       pkgappendbl3 := func(varname string, basicType *BasicType) {
+               acl(varname, basicType,
+                       "Makefile, Makefile.*, *.mk: default, set, append, use")
        }
 
-       // Some package-defined lists may also be appended in buildlink3.mk files,
+       // Some package-defined variables may be modified in buildlink3.mk files.
+       // These variables are typically related to compiling and linking files
+       // from C and related languages.
+       pkgbl3 := func(varname string, basicType *BasicType) {
+               reg.DefineParse(varname, lkNone, basicType,
+                       "Makefile, Makefile.*, *.mk: default, set, use")
+       }
+       // Some package-defined lists may also be modified in buildlink3.mk files,
        // for example platform-specific CFLAGS and LDFLAGS.
-       pkglistbl3 := 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")
+       pkglistbl3 := func(varname string, basicType *BasicType) {
+               reg.DefineParse(varname, lkShell, basicType,
+                       "Makefile, Makefile.*, *.mk: default, set, append, use")
        }
 
-       // sys declares a user-defined or system-defined variable that must not be modified by packages.
+       // sys declares a user-defined or system-defined variable that must not
+       // be modified by packages.
+       //
+       // It also must not be used in buildlink3.mk and builtin.mk files or at
+       // load time since the system/user preferences may not have been loaded
+       // when these files are included.
        //
-       // It also must not be used in buildlink3.mk and builtin.mk files or at load-time,
-       // since the system/user preferences may not have been loaded when these files are included.
-       sys := func(varname string, kindOfList KindOfList, checker *BasicType) {
-               acl(varname, kindOfList, checker, "buildlink3.mk: none; *: use")
+       // TODO: These timing issues should be handled separately from the permissions.
+       //  They can be made more precise.
+       sys := func(varname string, basicType *BasicType) {
+               acl(varname, basicType,
+                       "buildlink3.mk: none",
+                       "*: use")
+       }
+
+       sysbl3 := func(varname string, basicType *BasicType) {
+               acl(varname, basicType,
+                       "*: use")
+       }
+
+       syslist := func(varname string, basicType *BasicType) {
+               acllist(varname, basicType,
+                       "buildlink3.mk: none",
+                       "*: use")
        }
 
        // usr declares a user-defined variable that must not be modified by packages.
-       usr := func(varname string, kindOfList KindOfList, checker *BasicType) {
-               acl(varname, kindOfList, checker, "buildlink3.mk: none; *: use-loadtime, use")
+       usr := func(varname string, basicType *BasicType) {
+               acl(varname, basicType,
+                       // TODO: why is builtin.mk missing here?
+                       "buildlink3.mk: none",
+                       "*: use, use-loadtime")
+       }
+
+       // usr declares a user-defined list variable that must not be modified by packages.
+       usrlist := func(varname string, basicType *BasicType) {
+               acllist(varname, basicType,
+                       // TODO: why is builtin.mk missing here?
+                       "buildlink3.mk: none",
+                       "*: use, use-loadtime")
        }
 
        // sysload declares a system-provided variable that may already be used at load time.
-       sysload := func(varname string, kindOfList KindOfList, checker *BasicType) {
-               acl(varname, kindOfList, checker, "*: use-loadtime, use")
+       sysload := func(varname string, basicType *BasicType) {
+               reg.DefineParse(varname, lkNone, basicType,
+                       "*: use, use-loadtime")
+       }
+
+       sysloadlist := func(varname string, basicType *BasicType) {
+               reg.DefineParse(varname, lkShell, basicType,
+                       "*: use, use-loadtime")
+       }
+
+       // bl3list declares a list variable that is defined by buildlink3.mk and
+       // builtin.mk and can later be used by the package.
+       bl3list := func(varname string, basicType *BasicType) {
+               reg.DefineParse(varname, lkShell, basicType,
+                       "buildlink3.mk, builtin.mk: append",
+                       "*: use")
        }
 
-       bl3list := func(varname string, kindOfList KindOfList, checker *BasicType) {
-               acl(varname, kindOfList, checker, "buildlink3.mk, builtin.mk: append; *: use")
+       // cmdline declares a variable that is defined on the command line. There
+       // are only few variables of this type, such as PKG_DEBUG_LEVEL.
+       cmdline := func(varname string, basicType *BasicType) {
+               reg.DefineParse(varname, lkNone, basicType,
+                       "buildlink3.mk, builtin.mk: none",
+                       "*: use, use-loadtime")
        }
 
-       cmdline := func(varname string, kindOfList KindOfList, checker *BasicType) {
-               acl(varname, kindOfList, checker, "buildlink3.mk, builtin.mk: none; *: use-loadtime, use")
+       // Only for infrastructure files; see mk/misc/show.mk
+       infralist := func(varname string, basicType *BasicType) {
+               acllist(varname, basicType,
+                       "*: append")
        }
 
+       // compilerLanguages reads the available languages that are typically
+       // bundled in a single compiler framework, such as GCC or Clang.
        compilerLanguages := enum(
                func() string {
                        mklines := LoadMk(src.File("mk/compiler.mk"), NotEmpty)
@@ -130,21 +294,26 @@ func (src *Pkgsrc) InitVartypes() {
        // defval. This is mostly useful when testing pkglint.
        enumFrom := func(filename string, defval string, varcanons ...string) *BasicType {
                mklines := LoadMk(src.File(filename), NotEmpty)
+               if mklines == nil {
+                       return enum(defval)
+               }
+
                values := make(map[string]bool)
+               for _, mkline := range mklines.mklines {
+                       if !mkline.IsVarassign() {
+                               continue
+                       }
 
-               if mklines != nil {
-                       for _, mkline := range mklines.mklines {
-                               if mkline.IsVarassign() {
-                                       varcanon := mkline.Varcanon()
-                                       for _, vc := range varcanons {
-                                               if vc == varcanon {
-                                                       words, _ := splitIntoMkWords(mkline.Line, mkline.Value())
-                                                       for _, word := range words {
-                                                               if !contains(word, "$") {
-                                                                       values[intern(word)] = true
-                                                               }
-                                                       }
-                                               }
+                       varcanon := mkline.Varcanon()
+                       for _, vc := range varcanons {
+                               if vc != varcanon {
+                                       continue
+                               }
+
+                               words := mkline.ValueFields(mkline.Value())
+                               for _, word := range words {
+                                       if !contains(word, "$") {
+                                               values[intern(word)] = true
                                        }
                                }
                        }
@@ -153,7 +322,8 @@ func (src *Pkgsrc) InitVartypes() {
                if len(values) > 0 {
                        joined := keysJoined(values)
                        if trace.Tracing {
-                               trace.Stepf("Enum from %s in %s with values: %s", strings.Join(varcanons, " "), filename, joined)
+                               trace.Stepf("Enum from %s in %s with values: %s",
+                                       strings.Join(varcanons, " "), filename, joined)
                        }
                        return enum(joined)
                }
@@ -164,9 +334,9 @@ func (src *Pkgsrc) InitVartypes() {
                return enum(defval)
        }
 
-       // enumFromDirs reads the directories from category, takes all that have
-       // a single number in them (such as php72) and ranks them from earliest
-       // to latest.
+       // enumFromDirs reads the package directories from category, takes all
+       // that have a single number in them (such as php72) and ranks them
+       // from earliest to latest.
        //
        // If the directories cannot be found, the allowed values are taken
        // from defval. This is mostly useful when testing pkglint.
@@ -204,93 +374,102 @@ func (src *Pkgsrc) InitVartypes() {
                "openjdk8 oracle-jdk8 openjdk7 sun-jdk7 sun-jdk6 jdk16 jdk15 kaffe",
                "_PKG_JVMS.*")
 
-       // Last synced with mk/defaults/mk.conf revision 1.269 (2017-01-01).
-       usr("USE_CWRAPPERS", lkNone, enum("yes no auto"))
-       usr("ALLOW_VULNERABLE_PACKAGES", lkNone, BtYes)
-       usr("AUDIT_PACKAGES_FLAGS", lkShell, BtShellWord)
-       usr("MANINSTALL", lkShell, enum("maninstall catinstall"))
-       usr("MANZ", lkNone, BtYes)
-       usr("GZIP", lkShell, BtShellWord)
-       usr("MAKE_JOBS", lkNone, BtInteger)
-       usr("OBJHOSTNAME", lkNone, BtYes)
-       usr("OBJMACHINE", lkNone, BtYes)
-       usr("SIGN_PACKAGES", lkNone, enum("gpg x509"))
-       usr("X509_KEY", lkNone, BtPathname)
-       usr("X509_CERTIFICATE", lkNone, BtPathname)
-       usr("PATCH_DEBUG", lkNone, BtYes)
-       usr("PKG_COMPRESSION", lkNone, enum("gzip bzip2 none"))
-       usr("PKGSRC_LOCKTYPE", lkNone, enum("none sleep once"))
-       usr("PKGSRC_SLEEPSECS", lkNone, BtInteger)
-       usr("ABI", lkNone, enum("32 64"))
-       usr("PKG_DEVELOPER", lkNone, BtYesNo)
-       usr("USE_ABI_DEPENDS", lkNone, BtYesNo)
-       usr("PKG_REGISTER_SHELLS", lkNone, enum("YES NO"))
-       usr("PKGSRC_COMPILER", lkShell, compilers)
-       usr("PKGSRC_KEEP_BIN_PKGS", lkNone, BtYesNo)
-       usr("PKGSRC_MESSAGE_RECIPIENTS", lkShell, BtMailAddress)
-       usr("PKGSRC_SHOW_BUILD_DEFS", lkNone, BtYesNo)
-       usr("PKGSRC_RUN_TEST", lkNone, BtYesNo)
-       usr("PKGSRC_MKPIE", lkNone, BtYesNo)
-       usr("PKGSRC_USE_FORTIFY", lkNone, BtYesNo)
-       usr("PKGSRC_USE_RELRO", lkNone, BtYesNo)
-       usr("PKGSRC_USE_SSP", lkNone, enum("no yes strong all"))
-       usr("PREFER.*", lkNone, enum("pkgsrc native"))
-       usr("PREFER_PKGSRC", lkShell, BtIdentifier)
-       usr("PREFER_NATIVE", lkShell, BtIdentifier)
-       usr("PREFER_NATIVE_PTHREADS", lkNone, BtYesNo)
-       usr("WRKOBJDIR", lkNone, BtPathname)
-       usr("LOCALBASE", lkNone, BtPathname)
-       usr("CROSSBASE", lkNone, BtPathname)
-       usr("VARBASE", lkNone, BtPathname)
-       acl("X11_TYPE", lkNone, enum("modular native"), "*: use-loadtime, use")
-       acl("X11BASE", lkNone, BtPathname, "*: use-loadtime, use")
-       usr("MOTIFBASE", lkNone, BtPathname)
-       usr("PKGINFODIR", lkNone, BtPathname)
-       usr("PKGMANDIR", lkNone, BtPathname)
-       usr("PKGGNUDIR", lkNone, BtPathname)
-       usr("BSDSRCDIR", lkNone, BtPathname)
-       usr("BSDXSRCDIR", lkNone, BtPathname)
-       usr("DISTDIR", lkNone, BtPathname)
-       usr("DIST_PATH", lkNone, BtPathlist)
-       usr("DEFAULT_VIEW", lkNone, BtUnknown) // XXX: deprecate? pkgviews has been removed
-       usr("FETCH_CMD", lkNone, BtShellCommand)
-       usr("FETCH_USING", lkNone, enum("auto curl custom fetch ftp manual wget"))
-       usr("FETCH_BEFORE_ARGS", lkShell, BtShellWord)
-       usr("FETCH_AFTER_ARGS", lkShell, BtShellWord)
-       usr("FETCH_RESUME_ARGS", lkShell, BtShellWord)
-       usr("FETCH_OUTPUT_ARGS", lkShell, BtShellWord)
-       usr("FIX_SYSTEM_HEADERS", lkNone, BtYes)
-       usr("LIBTOOLIZE_PLIST", lkNone, BtYesNo)
-       usr("PKG_RESUME_TRANSFERS", lkNone, BtYesNo)
-       usr("PKG_SYSCONFBASE", lkNone, BtPathname)
-       usr("INIT_SYSTEM", lkNone, enum("rc.d smf"))
-       usr("RCD_SCRIPTS_DIR", lkNone, BtPathname)
-       usr("PACKAGES", lkNone, BtPathname)
-       usr("PASSIVE_FETCH", lkNone, BtYes)
-       usr("PATCH_FUZZ_FACTOR", lkNone, enum("-F0 -F1 -F2 -F3"))
-       usr("ACCEPTABLE_LICENSES", lkShell, BtIdentifier)
-       usr("SPECIFIC_PKGS", lkNone, BtYes)
-       usr("SITE_SPECIFIC_PKGS", lkShell, BtPkgPath)
-       usr("HOST_SPECIFIC_PKGS", lkShell, BtPkgPath)
-       usr("GROUP_SPECIFIC_PKGS", lkShell, BtPkgPath)
-       usr("USER_SPECIFIC_PKGS", lkShell, BtPkgPath)
-       usr("EXTRACT_USING", lkNone, enum("bsdtar gtar nbtar pax"))
-       usr("FAILOVER_FETCH", lkNone, BtYes)
-       usr("MASTER_SORT", lkShell, BtUnknown)
-       usr("MASTER_SORT_REGEX", lkShell, BtUnknown)
-       usr("MASTER_SORT_RANDOM", lkNone, BtYes)
-       usr("PATCH_DEBUG", lkNone, BtYes)
-       usr("PKG_FC", lkNone, BtShellCommand)
-       usr("IMAKEOPTS", lkShell, BtShellWord)
-       usr("PRE_ROOT_CMD", lkNone, BtShellCommand)
-       usr("SU_CMD", lkNone, BtShellCommand)
-       usr("SU_CMD_PATH_APPEND", lkNone, BtPathlist)
-       usr("FATAL_OBJECT_FMT_SKEW", lkNone, BtYesNo)
-       usr("WARN_NO_OBJECT_FMT", lkNone, BtYesNo)
-       usr("SMART_MESSAGES", lkNone, BtYes)
-       usr("BINPKG_SITES", lkShell, BtURL)
-       usr("BIN_INSTALL_FLAGS", lkShell, BtShellWord)
-       usr("LOCALPATCHES", lkNone, BtPathname)
+       // Last synced with mk/defaults/mk.conf revision 1.300 (fe3d998769f).
+       usr("USE_CWRAPPERS", enum("yes no auto"))
+       usr("ALLOW_VULNERABLE_PACKAGES", BtYes)
+       usrlist("AUDIT_PACKAGES_FLAGS", BtShellWord)
+       usrlist("MANINSTALL", enum("maninstall catinstall"))
+       usr("MANZ", BtYes)
+       usrlist("GZIP", BtShellWord)
+       usr("MAKE_JOBS", BtInteger)
+       usr("OBJHOSTNAME", BtYes)
+       usr("OBJMACHINE", BtYes)
+       usr("SIGN_PACKAGES", enum("gpg x509"))
+       usr("X509_KEY", BtPathname)
+       usr("X509_CERTIFICATE", BtPathname)
+       usr("PATCH_DEBUG", BtYes)
+       usr("PKG_COMPRESSION", enum("gzip bzip2 xz none"))
+       usr("PKGSRC_LOCKTYPE", enum("none sleep once"))
+       usr("PKGSRC_SLEEPSECS", BtInteger)
+       usr("ABI", enum("32 64"))
+       usr("PKG_DEVELOPER", BtYesNo)
+       usr("USE_ABI_DEPENDS", BtYesNo)
+       usr("PKG_REGISTER_SHELLS", enum("YES NO"))
+       usrlist("PKGSRC_COMPILER", compilers)
+       usr("PKGSRC_KEEP_BIN_PKGS", BtYesNo)
+       usrlist("PKGSRC_MESSAGE_RECIPIENTS", BtMailAddress)
+       usr("PKGSRC_SHOW_BUILD_DEFS", BtYesNo)
+       usr("PKGSRC_RUN_TEST", BtYesNo)
+       usr("PKGSRC_MKPIE", BtYesNo)
+       usr("PKGSRC_MKREPRO", BtYesNo)
+       usr("PKGSRC_USE_CTF", BtYesNo)
+       usr("PKGSRC_USE_FORTIFY", enum("no weak strong"))
+       usr("PKGSRC_USE_RELRO", enum("no partial full"))
+       usr("PKGSRC_USE_SSP", enum("no yes strong all"))
+       usr("PKGSRC_USE_STACK_CHECK", enum("no yes"))
+       usr("PREFER.*", enum("pkgsrc native"))
+       usrlist("PREFER_PKGSRC", BtIdentifier)
+       usrlist("PREFER_NATIVE", BtIdentifier)
+       usr("PREFER_NATIVE_PTHREADS", BtYesNo)
+       usr("WRKOBJDIR", BtPathname)
+       usr("LOCALBASE", BtPathname)
+       usr("CROSSBASE", BtPathname)
+       usr("VARBASE", BtPathname)
+
+       // X11_TYPE and X11BASE may be used in buildlink3.mk as well, which the
+       // standard sysload doesn't allow.
+       acl("X11_TYPE", enum("modular native"),
+               "*: use, use-loadtime")
+       acl("X11BASE", BtPathname,
+               "*: use, use-loadtime")
+
+       usr("MOTIFBASE", BtPathname)
+       usr("PKGINFODIR", BtPathname)
+       usr("PKGMANDIR", BtPathname)
+       usr("PKGGNUDIR", BtPathname)
+       usr("BSDSRCDIR", BtPathname)
+       usr("BSDXSRCDIR", BtPathname)
+       usr("DISTDIR", BtPathname)
+       usr("DIST_PATH", BtPathlist)
+       usr("DEFAULT_VIEW", BtUnknown) // XXX: deprecate? pkgviews has been removed
+       usr("FETCH_CMD", BtShellCommand)
+       usr("FETCH_USING", enum("auto curl custom fetch ftp manual wget"))
+       usrlist("FETCH_BEFORE_ARGS", BtShellWord)
+       usrlist("FETCH_AFTER_ARGS", BtShellWord)
+       usrlist("FETCH_RESUME_ARGS", BtShellWord)
+       usrlist("FETCH_OUTPUT_ARGS", BtShellWord)
+       usr("FIX_SYSTEM_HEADERS", BtYes)
+       usr("LIBTOOLIZE_PLIST", BtYesNo)
+       usr("PKG_RESUME_TRANSFERS", BtYesNo)
+       usr("PKG_SYSCONFBASE", BtPathname)
+       usr("INIT_SYSTEM", enum("rc.d smf"))
+       usr("RCD_SCRIPTS_DIR", BtPathname)
+       usr("PACKAGES", BtPathname)
+       usr("PASSIVE_FETCH", BtYes)
+       usr("PATCH_FUZZ_FACTOR", enum("none -F0 -F1 -F2 -F3"))
+       usrlist("ACCEPTABLE_LICENSES", BtIdentifier)
+       usr("SPECIFIC_PKGS", BtYes)
+       usrlist("SITE_SPECIFIC_PKGS", BtPkgPath)
+       usrlist("HOST_SPECIFIC_PKGS", BtPkgPath)
+       usrlist("GROUP_SPECIFIC_PKGS", BtPkgPath)
+       usrlist("USER_SPECIFIC_PKGS", BtPkgPath)
+       usr("EXTRACT_USING", enum("bsdtar gtar nbtar pax"))
+       usr("FAILOVER_FETCH", BtYes)
+       usrlist("MASTER_SORT", BtUnknown)
+       usrlist("MASTER_SORT_REGEX", BtUnknown)
+       usr("MASTER_SORT_RANDOM", BtYes)
+       usr("PATCH_DEBUG", BtYes)
+       usr("PKG_FC", BtShellCommand)
+       usrlist("IMAKEOPTS", BtShellWord)
+       usr("PRE_ROOT_CMD", BtShellCommand)
+       usr("SU_CMD", BtShellCommand)
+       usr("SU_CMD_PATH_APPEND", BtPathlist)
+       usr("FATAL_OBJECT_FMT_SKEW", BtYesNo)
+       usr("WARN_NO_OBJECT_FMT", BtYesNo)
+       usr("SMART_MESSAGES", BtYes)
+       usrlist("BINPKG_SITES", BtURL)
+       usrlist("BIN_INSTALL_FLAGS", BtShellWord)
+       usr("LOCALPATCHES", BtPathname)
 
        // The remaining variables from mk/defaults/mk.conf may be overridden by packages.
        // Therefore they need a separate definition of "user-settable".
@@ -302,938 +481,1116 @@ func (src *Pkgsrc) InitVartypes() {
        // omitting this necessary inclusion.
        //
        // TODO: parse all the below information directly from mk/defaults/mk.conf.
-       usrpkg := func(varname string, kindOfList KindOfList, checker *BasicType) {
-               acl(varname, kindOfList, checker, ""+
-                       "Makefile: default, set, use, use-loadtime; "+
-                       "buildlink3.mk, builtin.mk: none; "+
-                       "Makefile.*, *.mk: default, set, use, use-loadtime; "+
-                       "*: use-loadtime, use")
-       }
-
-       usrpkg("ACROREAD_FONTPATH", lkNone, BtPathlist)
-       usrpkg("AMANDA_USER", lkNone, BtUserGroupName)
-       usrpkg("AMANDA_TMP", lkNone, BtPathname)
-       usrpkg("AMANDA_VAR", lkNone, BtPathname)
-       usrpkg("APACHE_USER", lkNone, BtUserGroupName)
-       usrpkg("APACHE_GROUP", lkNone, BtUserGroupName)
-       usrpkg("APACHE_SUEXEC_CONFIGURE_ARGS", lkShell, BtShellWord)
-       usrpkg("APACHE_SUEXEC_DOCROOT", lkShell, BtPathname)
-       usrpkg("ARLA_CACHE", lkNone, BtPathname)
-       usrpkg("BIND_DIR", lkNone, BtPathname)
-       usrpkg("BIND_GROUP", lkNone, BtUserGroupName)
-       usrpkg("BIND_USER", lkNone, BtUserGroupName)
-       usrpkg("CACTI_GROUP", lkNone, BtUserGroupName)
-       usrpkg("CACTI_USER", lkNone, BtUserGroupName)
-       usrpkg("CANNA_GROUP", lkNone, BtUserGroupName)
-       usrpkg("CANNA_USER", lkNone, BtUserGroupName)
-       usrpkg("CDRECORD_CONF", lkNone, BtPathname)
-       usrpkg("CLAMAV_GROUP", lkNone, BtUserGroupName)
-       usrpkg("CLAMAV_USER", lkNone, BtUserGroupName)
-       usrpkg("CLAMAV_DBDIR", lkNone, BtPathname)
-       usrpkg("CONSERVER_DEFAULTHOST", lkNone, BtIdentifier)
-       usrpkg("CONSERVER_DEFAULTPORT", lkNone, BtInteger)
-       usrpkg("CUPS_GROUP", lkNone, BtUserGroupName)
-       usrpkg("CUPS_USER", lkNone, BtUserGroupName)
-       usrpkg("CUPS_SYSTEM_GROUPS", lkShell, BtUserGroupName)
-       usrpkg("CYRUS_IDLE", lkNone, enum("poll idled no"))
-       usrpkg("CYRUS_GROUP", lkNone, BtUserGroupName)
-       usrpkg("CYRUS_USER", lkNone, BtUserGroupName)
-       usrpkg("DBUS_GROUP", lkNone, BtUserGroupName)
-       usrpkg("DBUS_USER", lkNone, BtUserGroupName)
-       usrpkg("DEFANG_GROUP", lkNone, BtUserGroupName)
-       usrpkg("DEFANG_USER", lkNone, BtUserGroupName)
-       usrpkg("DEFANG_SPOOLDIR", lkNone, BtPathname)
-       usrpkg("DEFAULT_IRC_SERVER", lkNone, BtIdentifier)
-       usrpkg("DEFAULT_SERIAL_DEVICE", lkNone, BtPathname)
-       usrpkg("DIALER_GROUP", lkNone, BtUserGroupName)
-       usrpkg("DT_LAYOUT", lkNone, enum("US FI FR GER DV"))
-       usrpkg("ELK_GUI", lkShell, enum("none xaw motif"))
-       usrpkg("EMACS_TYPE", lkNone, emacsVersions)
-       usrpkg("EXIM_GROUP", lkNone, BtUserGroupName)
-       usrpkg("EXIM_USER", lkNone, BtUserGroupName)
-       usrpkg("FLUXBOX_USE_XINERAMA", lkNone, enum("YES NO"))
-       usrpkg("FLUXBOX_USE_KDE", lkNone, enum("YES NO"))
-       usrpkg("FLUXBOX_USE_GNOME", lkNone, enum("YES NO"))
-       usrpkg("FLUXBOX_USE_XFT", lkNone, enum("YES NO"))
-       usrpkg("FOX_USE_XUNICODE", lkNone, enum("YES NO"))
-       usrpkg("FREEWNN_USER", lkNone, BtUserGroupName)
-       usrpkg("FREEWNN_GROUP", lkNone, BtUserGroupName)
-       usrpkg("GAMES_USER", lkNone, BtUserGroupName)
-       usrpkg("GAMES_GROUP", lkNone, BtUserGroupName)
-       usrpkg("GAMEMODE", lkNone, BtFileMode)
-       usrpkg("GAMEDIRMODE", lkNone, BtFileMode)
-       usrpkg("GAMEDATAMODE", lkNone, BtFileMode)
-       usrpkg("GAMEGRP", lkNone, BtUserGroupName)
-       usrpkg("GAMEOWN", lkNone, BtUserGroupName)
-       usrpkg("GRUB_NETWORK_CARDS", lkNone, BtIdentifier)
-       usrpkg("GRUB_PRESET_COMMAND", lkNone, enum("bootp dhcp rarp"))
-       usrpkg("GRUB_SCAN_ARGS", lkShell, BtShellWord)
-       usrpkg("HASKELL_COMPILER", lkNone, enum("ghc"))
-       usrpkg("HOWL_GROUP", lkNone, BtUserGroupName)
-       usrpkg("HOWL_USER", lkNone, BtUserGroupName)
-       usrpkg("ICECAST_CHROOTDIR", lkNone, BtPathname)
-       usrpkg("ICECAST_CHUNKLEN", lkNone, BtInteger)
-       usrpkg("ICECAST_SOURCE_BUFFSIZE", lkNone, BtInteger)
-       usrpkg("IMAP_UW_CCLIENT_MBOX_FMT", lkNone, enum("mbox mbx mh mmdf mtx mx news phile tenex unix"))
-       usrpkg("IMAP_UW_MAILSPOOLHOME", lkNone, BtFileName)
-       usrpkg("IMDICTDIR", lkNone, BtPathname)
-       usrpkg("INN_DATA_DIR", lkNone, BtPathname)
-       usrpkg("INN_USER", lkNone, BtUserGroupName)
-       usrpkg("INN_GROUP", lkNone, BtUserGroupName)
-       usrpkg("IRCD_HYBRID_NICLEN", lkNone, BtInteger)
-       usrpkg("IRCD_HYBRID_TOPICLEN", lkNone, BtInteger)
-       usrpkg("IRCD_HYBRID_SYSLOG_EVENTS", lkNone, BtUnknown)
-       usrpkg("IRCD_HYBRID_SYSLOG_FACILITY", lkNone, BtIdentifier)
-       usrpkg("IRCD_HYBRID_MAXCONN", lkNone, BtInteger)
-       usrpkg("IRCD_HYBRID_IRC_USER", lkNone, BtUserGroupName)
-       usrpkg("IRCD_HYBRID_IRC_GROUP", lkNone, BtUserGroupName)
-       usrpkg("IRRD_USE_PGP", lkNone, enum("5 2"))
-       usrpkg("JABBERD_USER", lkNone, BtUserGroupName)
-       usrpkg("JABBERD_GROUP", lkNone, BtUserGroupName)
-       usrpkg("JABBERD_LOGDIR", lkNone, BtPathname)
-       usrpkg("JABBERD_SPOOLDIR", lkNone, BtPathname)
-       usrpkg("JABBERD_PIDDIR", lkNone, BtPathname)
-       usrpkg("JAKARTA_HOME", lkNone, BtPathname)
-       usrpkg("KERBEROS", lkNone, BtYes)
-       usrpkg("KERMIT_SUID_UUCP", lkNone, BtYes)
-       usrpkg("KJS_USE_PCRE", lkNone, BtYes)
-       usrpkg("KNEWS_DOMAIN_FILE", lkNone, BtPathname)
-       usrpkg("KNEWS_DOMAIN_NAME", lkNone, BtIdentifier)
-       usrpkg("LIBDVDCSS_HOMEPAGE", lkNone, BtHomepage)
-       usrpkg("LIBDVDCSS_MASTER_SITES", lkShell, BtFetchURL)
-       usrpkg("LATEX2HTML_ICONPATH", lkNone, BtURL)
-       usrpkg("LEAFNODE_DATA_DIR", lkNone, BtPathname)
-       usrpkg("LEAFNODE_USER", lkNone, BtUserGroupName)
-       usrpkg("LEAFNODE_GROUP", lkNone, BtUserGroupName)
-       usrpkg("LINUX_LOCALES", lkShell, BtIdentifier)
-       usrpkg("MAILAGENT_DOMAIN", lkNone, BtIdentifier)
-       usrpkg("MAILAGENT_EMAIL", lkNone, BtMailAddress)
-       usrpkg("MAILAGENT_FQDN", lkNone, BtIdentifier)
-       usrpkg("MAILAGENT_ORGANIZATION", lkNone, BtUnknown)
-       usrpkg("MAJORDOMO_HOMEDIR", lkNone, BtPathname)
-       usrpkg("MAKEINFO_ARGS", lkShell, BtShellWord)
-       usrpkg("MECAB_CHARSET", lkNone, BtIdentifier)
-       usrpkg("MEDIATOMB_GROUP", lkNone, BtUserGroupName)
-       usrpkg("MEDIATOMB_USER", lkNone, BtUserGroupName)
-       usrpkg("MLDONKEY_GROUP", lkNone, BtUserGroupName)
-       usrpkg("MLDONKEY_HOME", lkNone, BtPathname)
-       usrpkg("MLDONKEY_USER", lkNone, BtUserGroupName)
-       usrpkg("MONOTONE_GROUP", lkNone, BtUserGroupName)
-       usrpkg("MONOTONE_USER", lkNone, BtUserGroupName)
-       usrpkg("MOTIF_TYPE", lkNone, enum("motif openmotif lesstif dt"))
-       usrpkg("MOTIF_TYPE_DEFAULT", lkNone, enum("motif openmotif lesstif dt"))
-       usrpkg("MTOOLS_ENABLE_FLOPPYD", lkNone, BtYesNo)
-       usrpkg("MYSQL_USER", lkNone, BtUserGroupName)
-       usrpkg("MYSQL_GROUP", lkNone, BtUserGroupName)
-       usrpkg("MYSQL_DATADIR", lkNone, BtPathname)
-       usrpkg("MYSQL_CHARSET", lkNone, BtIdentifier)
-       usrpkg("MYSQL_EXTRA_CHARSET", lkShell, BtIdentifier)
-       usrpkg("NAGIOS_GROUP", lkNone, BtUserGroupName)
-       usrpkg("NAGIOS_USER", lkNone, BtUserGroupName)
-       usrpkg("NAGIOSCMD_GROUP", lkNone, BtUserGroupName)
-       usrpkg("NAGIOSDIR", lkNone, BtPathname)
-       usrpkg("NBPAX_PROGRAM_PREFIX", lkNone, BtUnknown)
-       usrpkg("NMH_EDITOR", lkNone, BtIdentifier)
-       usrpkg("NMH_MTA", lkNone, enum("smtp sendmail"))
-       usrpkg("NMH_PAGER", lkNone, BtIdentifier)
-       usrpkg("NS_PREFERRED", lkNone, enum("communicator navigator mozilla"))
-       usrpkg("OPENSSH_CHROOT", lkNone, BtPathname)
-       usrpkg("OPENSSH_USER", lkNone, BtUserGroupName)
-       usrpkg("OPENSSH_GROUP", lkNone, BtUserGroupName)
-       usrpkg("P4USER", lkNone, BtUserGroupName)
-       usrpkg("P4GROUP", lkNone, BtUserGroupName)
-       usrpkg("P4ROOT", lkNone, BtPathname)
-       usrpkg("P4PORT", lkNone, BtInteger)
-       usrpkg("PALMOS_DEFAULT_SDK", lkNone, enum("1 2 3.1 3.5"))
-       usrpkg("PAPERSIZE", lkNone, enum("A4 Letter"))
-       usrpkg("PGGROUP", lkNone, BtUserGroupName)
-       usrpkg("PGUSER", lkNone, BtUserGroupName)
-       usrpkg("PGHOME", lkNone, BtPathname)
-       usrpkg("PILRC_USE_GTK", lkNone, BtYesNo)
-       usrpkg("PKG_JVM_DEFAULT", lkNone, jvms)
-       usrpkg("POPTOP_USE_MPPE", lkNone, BtYes)
-       usrpkg("PROCMAIL_MAILSPOOLHOME", lkNone, BtFileName)
-       usrpkg("PROCMAIL_TRUSTED_IDS", lkShell, BtUnknown) // Comma-separated list of string or integer literals.
-       usrpkg("PVM_SSH", lkNone, BtPathname)
-       usrpkg("QMAILDIR", lkNone, BtPathname)
-       usrpkg("QMAIL_QFILTER_TMPDIR", lkNone, BtPathname)
-       usrpkg("QMAIL_QUEUE_DIR", lkNone, BtPathname)
-       usrpkg("QMAIL_QUEUE_EXTRA", lkNone, BtMailAddress)
-       usrpkg("QPOPPER_FAC", lkNone, BtIdentifier)
-       usrpkg("QPOPPER_USER", lkNone, BtUserGroupName)
-       usrpkg("QPOPPER_SPOOL_DIR", lkNone, BtPathname)
-       usrpkg("RASMOL_DEPTH", lkNone, enum("8 16 32"))
-       usrpkg("RELAY_CTRL_DIR", lkNone, BtPathname)
-       usrpkg("RPM_DB_PREFIX", lkNone, BtPathname)
-       usrpkg("RSSH_SCP_PATH", lkNone, BtPathname)
-       usrpkg("RSSH_SFTP_SERVER_PATH", lkNone, BtPathname)
-       usrpkg("RSSH_CVS_PATH", lkNone, BtPathname)
-       usrpkg("RSSH_RDIST_PATH", lkNone, BtPathname)
-       usrpkg("RSSH_RSYNC_PATH", lkNone, BtPathname)
-       usrpkg("SAWFISH_THEMES", lkShell, BtFileName)
-       usrpkg("SCREWS_GROUP", lkNone, BtUserGroupName)
-       usrpkg("SCREWS_USER", lkNone, BtUserGroupName)
-       usrpkg("SDIST_PAWD", lkNone, enum("pawd pwd"))
-       usrpkg("SERIAL_DEVICES", lkShell, BtPathname)
-       usrpkg("SILC_CLIENT_WITH_PERL", lkNone, BtYesNo)
-       usrpkg("SSH_SUID", lkNone, BtYesNo)
-       usrpkg("SSYNC_PAWD", lkNone, enum("pawd pwd"))
-       usrpkg("SUSE_PREFER", lkNone, enum("13.1 12.1 10.0"))
-       usrpkg("TEXMFSITE", lkNone, BtPathname)
-       usrpkg("THTTPD_LOG_FACILITY", lkNone, BtIdentifier)
-       usrpkg("UNPRIVILEGED", lkNone, BtYesNo)
-       usrpkg("USE_CROSS_COMPILE", lkNone, BtYesNo)
-       usrpkg("USERPPP_GROUP", lkNone, BtUserGroupName)
-       usrpkg("UUCP_GROUP", lkNone, BtUserGroupName)
-       usrpkg("UUCP_USER", lkNone, BtUserGroupName)
-       usrpkg("VIM_EXTRA_OPTS", lkShell, BtShellWord)
-       usrpkg("WCALC_HTMLDIR", lkNone, BtPathname)
-       usrpkg("WCALC_HTMLPATH", lkNone, BtPathname) // URL path
-       usrpkg("WCALC_CGIDIR", lkNone, BtPrefixPathname)
-       usrpkg("WCALC_CGIPATH", lkNone, BtPathname) // URL path
-       usrpkg("WDM_MANAGERS", lkShell, BtIdentifier)
-       usrpkg("X10_PORT", lkNone, BtPathname)
-       usrpkg("XAW_TYPE", lkNone, enum("standard 3d xpm neXtaw"))
-       usrpkg("XLOCK_DEFAULT_MODE", lkNone, BtIdentifier)
-       usrpkg("ZSH_STATIC", lkNone, BtYes)
+       usrpkg := func(varname string, basicType *BasicType) {
+               acl(varname, basicType,
+                       "Makefile: default, set, use, use-loadtime",
+                       "buildlink3.mk, builtin.mk: none",
+                       "Makefile.*, *.mk: default, set, use, use-loadtime",
+                       "*: use, use-loadtime")
+       }
+       usrpkglist := func(varname string, basicType *BasicType) {
+               acllist(varname, basicType,
+                       "Makefile: default, set, use, use-loadtime",
+                       "buildlink3.mk, builtin.mk: none",
+                       "Makefile.*, *.mk: default, set, use, use-loadtime",
+                       "*: use, use-loadtime")
+       }
+
+       usrpkg("ACROREAD_FONTPATH", BtPathlist)
+       usrpkg("AMANDA_USER", BtUserGroupName)
+       usrpkg("AMANDA_TMP", BtPathname)
+       usrpkg("AMANDA_VAR", BtPathname)
+       usrpkg("APACHE_USER", BtUserGroupName)
+       usrpkg("APACHE_GROUP", BtUserGroupName)
+       usrpkglist("APACHE_SUEXEC_CONFIGURE_ARGS", BtShellWord)
+       usrpkglist("APACHE_SUEXEC_DOCROOT", BtPathname)
+       usrpkg("ARLA_CACHE", BtPathname)
+       usrpkg("BIND_DIR", BtPathname)
+       usrpkg("BIND_GROUP", BtUserGroupName)
+       usrpkg("BIND_USER", BtUserGroupName)
+       usrpkg("CACTI_GROUP", BtUserGroupName)
+       usrpkg("CACTI_USER", BtUserGroupName)
+       usrpkg("CANNA_GROUP", BtUserGroupName)
+       usrpkg("CANNA_USER", BtUserGroupName)
+       usrpkg("CDRECORD_CONF", BtPathname)
+       usrpkg("CLAMAV_GROUP", BtUserGroupName)
+       usrpkg("CLAMAV_USER", BtUserGroupName)
+       usrpkg("CLAMAV_DBDIR", BtPathname)
+       usrpkg("CONSERVER_DEFAULTHOST", BtIdentifier)
+       usrpkg("CONSERVER_DEFAULTPORT", BtInteger)
+       usrpkg("CUPS_GROUP", BtUserGroupName)
+       usrpkg("CUPS_USER", BtUserGroupName)
+       usrpkglist("CUPS_SYSTEM_GROUPS", BtUserGroupName)
+       usrpkg("CYRUS_IDLE", enum("poll idled no"))
+       usrpkg("CYRUS_GROUP", BtUserGroupName)
+       usrpkg("CYRUS_USER", BtUserGroupName)
+       usrpkg("DAEMONTOOLS_LOG_USER", BtUserGroupName)
+       usrpkg("DAEMONTOOLS_GROUP", BtUserGroupName)
+       usrpkg("DBUS_GROUP", BtUserGroupName)
+       usrpkg("DBUS_USER", BtUserGroupName)
+       usrpkg("DEFANG_GROUP", BtUserGroupName)
+       usrpkg("DEFANG_USER", BtUserGroupName)
+       usrpkg("DEFANG_SPOOLDIR", BtPathname)
+       usrpkg("DEFAULT_IRC_SERVER", BtIdentifier)
+       usrpkg("DEFAULT_SERIAL_DEVICE", BtPathname)
+       usrpkg("DIALER_GROUP", BtUserGroupName)
+       usrpkg("DJBDNS_AXFR_USER", BtUserGroupName)
+       usrpkg("DJBDNS_CACHE_USER", BtUserGroupName)
+       usrpkg("DJBDNS_LOG_USER", BtUserGroupName)
+       usrpkg("DJBDNS_RBL_USER", BtUserGroupName)
+       usrpkg("DJBDNS_TINY_USER", BtUserGroupName)
+       usrpkg("DJBDNS_DJBDNS_GROUP", BtUserGroupName)
+       usrpkg("DT_LAYOUT", enum("US FI FR GER DV"))
+       usrpkglist("ELK_GUI", enum("none xaw motif"))
+       usrpkg("EMACS_TYPE", emacsVersions)
+       usrpkg("EXIM_GROUP", BtUserGroupName)
+       usrpkg("EXIM_USER", BtUserGroupName)
+       usrpkg("FLUXBOX_USE_XINERAMA", enum("YES NO"))
+       usrpkg("FLUXBOX_USE_KDE", enum("YES NO"))
+       usrpkg("FLUXBOX_USE_GNOME", enum("YES NO"))
+       usrpkg("FLUXBOX_USE_XFT", enum("YES NO"))
+       usrpkg("FOX_USE_XUNICODE", enum("YES NO"))
+       usrpkg("FREEWNN_USER", BtUserGroupName)
+       usrpkg("FREEWNN_GROUP", BtUserGroupName)
+       usrpkg("GAMES_USER", BtUserGroupName)
+       usrpkg("GAMES_GROUP", BtUserGroupName)
+       usrpkg("GAMEMODE", BtFileMode)
+       usrpkg("GAMEDIRMODE", BtFileMode)
+       usrpkg("GAMEDATAMODE", BtFileMode)
+       usrpkg("GAMEGRP", BtUserGroupName)
+       usrpkg("GAMEOWN", BtUserGroupName)
+       usrpkg("GRUB_NETWORK_CARDS", BtIdentifier)
+       usrpkg("GRUB_PRESET_COMMAND", enum("bootp dhcp rarp"))
+       usrpkglist("GRUB_SCAN_ARGS", BtShellWord)
+       usrpkg("HASKELL_COMPILER", enum("ghc"))
+       usrpkg("HOWL_GROUP", BtUserGroupName)
+       usrpkg("HOWL_USER", BtUserGroupName)
+       usrpkg("ICECAST_CHROOTDIR", BtPathname)
+       usrpkg("ICECAST_CHUNKLEN", BtInteger)
+       usrpkg("ICECAST_SOURCE_BUFFSIZE", BtInteger)
+       usrpkg("IMAP_UW_CCLIENT_MBOX_FMT",
+               enum("mbox mbx mh mmdf mtx mx news phile tenex unix"))
+       usrpkg("IMAP_UW_MAILSPOOLHOME", BtFileName)
+       usrpkg("IMDICTDIR", BtPathname)
+       usrpkg("INN_DATA_DIR", BtPathname)
+       usrpkg("INN_USER", BtUserGroupName)
+       usrpkg("INN_GROUP", BtUserGroupName)
+       usrpkg("IRCD_HYBRID_NICLEN", BtInteger)
+       usrpkg("IRCD_HYBRID_TOPICLEN", BtInteger)
+       usrpkg("IRCD_HYBRID_SYSLOG_EVENTS", BtUnknown)
+       usrpkg("IRCD_HYBRID_SYSLOG_FACILITY", BtIdentifier)
+       usrpkg("IRCD_HYBRID_MAXCONN", BtInteger)
+       usrpkg("IRCD_HYBRID_IRC_USER", BtUserGroupName)
+       usrpkg("IRCD_HYBRID_IRC_GROUP", BtUserGroupName)
+       usrpkg("IRRD_USE_PGP", enum("5 2"))
+       usrpkg("JABBERD_USER", BtUserGroupName)
+       usrpkg("JABBERD_GROUP", BtUserGroupName)
+       usrpkg("JABBERD_LOGDIR", BtPathname)
+       usrpkg("JABBERD_SPOOLDIR", BtPathname)
+       usrpkg("JABBERD_PIDDIR", BtPathname)
+       usrpkg("JAKARTA_HOME", BtPathname)
+       usrpkg("KERBEROS", BtYes)
+       usrpkg("KERMIT_SUID_UUCP", BtYes)
+       usrpkg("KJS_USE_PCRE", BtYes)
+       usrpkg("KNEWS_DOMAIN_FILE", BtPathname)
+       usrpkg("KNEWS_DOMAIN_NAME", BtIdentifier)
+       usrpkg("LIBDVDCSS_HOMEPAGE", BtHomepage)
+       usrpkglist("LIBDVDCSS_MASTER_SITES", BtFetchURL)
+       usrpkg("LIBUSB_TYPE", enum("compat native"))
+       usrpkg("LATEX2HTML_ICONPATH", BtURL)
+       usrpkg("LEAFNODE_DATA_DIR", BtPathname)
+       usrpkg("LEAFNODE_USER", BtUserGroupName)
+       usrpkg("LEAFNODE_GROUP", BtUserGroupName)
+       usrpkglist("LINUX_LOCALES", BtIdentifier)
+       usrpkg("MAILAGENT_DOMAIN", BtIdentifier)
+       usrpkg("MAILAGENT_EMAIL", BtMailAddress)
+       usrpkg("MAILAGENT_FQDN", BtIdentifier)
+       usrpkg("MAILAGENT_ORGANIZATION", BtUnknown)
+       usrpkg("MAJORDOMO_HOMEDIR", BtPathname)
+       usrpkglist("MAKEINFO_ARGS", BtShellWord)
+       usrpkg("MECAB_CHARSET", BtIdentifier)
+       usrpkg("MEDIATOMB_GROUP", BtUserGroupName)
+       usrpkg("MEDIATOMB_USER", BtUserGroupName)
+       usrpkg("MIREDO_USER", BtUserGroupName)
+       usrpkg("MIREDO_GROUP", BtUserGroupName)
+       usrpkg("MLDONKEY_GROUP", BtUserGroupName)
+       usrpkg("MLDONKEY_HOME", BtPathname)
+       usrpkg("MLDONKEY_USER", BtUserGroupName)
+       usrpkg("MONOTONE_GROUP", BtUserGroupName)
+       usrpkg("MONOTONE_USER", BtUserGroupName)
+       usrpkg("MOTIF_TYPE", enum("motif openmotif lesstif dt"))
+       usrpkg("MOTIF_TYPE_DEFAULT", enum("motif openmotif lesstif dt"))
+       usrpkg("MTOOLS_ENABLE_FLOPPYD", BtYesNo)
+       usrpkg("MYSQL_USER", BtUserGroupName)
+       usrpkg("MYSQL_GROUP", BtUserGroupName)
+       usrpkg("MYSQL_DATADIR", BtPathname)
+       usrpkg("MYSQL_CHARSET", BtIdentifier)
+       usrpkglist("MYSQL_EXTRA_CHARSET", BtIdentifier)
+       usrpkg("NAGIOS_GROUP", BtUserGroupName)
+       usrpkg("NAGIOS_USER", BtUserGroupName)
+       usrpkg("NAGIOSCMD_GROUP", BtUserGroupName)
+       usrpkg("NAGIOSDIR", BtPathname)
+       usrpkg("NBPAX_PROGRAM_PREFIX", BtUnknown)
+       usrpkg("NMH_EDITOR", BtIdentifier)
+       usrpkg("NMH_MTA", enum("smtp sendmail"))
+       usrpkg("NMH_PAGER", BtIdentifier)
+       usrpkg("NS_PREFERRED", enum("communicator navigator mozilla"))
+       usrpkg("NULLMAILER_USER", BtUserGroupName)
+       usrpkg("NULLMAILER_GROUP", BtUserGroupName)
+       usrpkg("OPENSSH_CHROOT", BtPathname)
+       usrpkg("OPENSSH_USER", BtUserGroupName)
+       usrpkg("OPENSSH_GROUP", BtUserGroupName)
+       usrpkg("P4USER", BtUserGroupName)
+       usrpkg("P4GROUP", BtUserGroupName)
+       usrpkg("P4ROOT", BtPathname)
+       usrpkg("P4PORT", BtInteger)
+       usrpkg("PALMOS_DEFAULT_SDK", enum("1 2 3.1 3.5"))
+       usrpkg("PAPERSIZE", enum("A4 Letter"))
+       usrpkg("PGGROUP", BtUserGroupName)
+       usrpkg("PGUSER", BtUserGroupName)
+       usrpkg("PGHOME", BtPathname)
+       usrpkg("PILRC_USE_GTK", BtYesNo)
+       usrpkg("PKG_JVM_DEFAULT", jvms)
+       usrpkg("POPTOP_USE_MPPE", BtYes)
+       usrpkg("PROCMAIL_MAILSPOOLHOME", BtFileName)
+       // Comma-separated list of string or integer literals.
+       usrpkg("PROCMAIL_TRUSTED_IDS", BtUnknown)
+       usrpkg("PVM_SSH", BtPathname)
+       usrpkg("QMAILDIR", BtPathname)
+       usrpkg("QMAIL_ALIAS_USER", BtUserGroupName)
+       usrpkg("QMAIL_DAEMON_USER", BtUserGroupName)
+       usrpkg("QMAIL_LOG_USER", BtUserGroupName)
+       usrpkg("QMAIL_ROOT_USER", BtUserGroupName)
+       usrpkg("QMAIL_PASSWD_USER", BtUserGroupName)
+       usrpkg("QMAIL_QUEUE_USER", BtUserGroupName)
+       usrpkg("QMAIL_REMOTE_USER", BtUserGroupName)
+       usrpkg("QMAIL_SEND_USER", BtUserGroupName)
+       usrpkg("QMAIL_QMAIL_GROUP", BtUserGroupName)
+       usrpkg("QMAIL_NOFILES_GROUP", BtUserGroupName)
+       usrpkg("QMAIL_QFILTER_TMPDIR", BtPathname)
+       usrpkg("QMAIL_QUEUE_DIR", BtPathname)
+       usrpkg("QMAIL_QUEUE_EXTRA", BtMailAddress)
+       usrpkg("QPOPPER_FAC", BtIdentifier)
+       usrpkg("QPOPPER_USER", BtUserGroupName)
+       usrpkg("QPOPPER_SPOOL_DIR", BtPathname)
+       usrpkg("RASMOL_DEPTH", enum("8 16 32"))
+       usrpkg("RELAY_CTRL_DIR", BtPathname)
+       usrpkg("RPM_DB_PREFIX", BtPathname)
+       usrpkg("RSSH_SCP_PATH", BtPathname)
+       usrpkg("RSSH_SFTP_SERVER_PATH", BtPathname)
+       usrpkg("RSSH_CVS_PATH", BtPathname)
+       usrpkg("RSSH_RDIST_PATH", BtPathname)
+       usrpkg("RSSH_RSYNC_PATH", BtPathname)
+       usrpkglist("SAWFISH_THEMES", BtFileName)
+       usrpkg("SCREWS_GROUP", BtUserGroupName)
+       usrpkg("SCREWS_USER", BtUserGroupName)
+       usrpkg("SDIST_PAWD", enum("pawd pwd"))
+       usrpkglist("SERIAL_DEVICES", BtPathname)
+       usrpkg("SILC_CLIENT_WITH_PERL", BtYesNo)
+       usrpkg("SNIPROXY_USER", BtUserGroupName)
+       usrpkg("SNIPROXY_GROUP", BtUserGroupName)
+       usrpkg("SSH_SUID", BtYesNo)
+       usrpkg("SSYNC_PAWD", enum("pawd pwd"))
+       usrpkg("SUSE_PREFER", enum("13.1 12.1 10.0")) // TODO: extract
+       usrpkg("TEXMFSITE", BtPathname)
+       usrpkg("THTTPD_LOG_FACILITY", BtIdentifier)
+       usrpkg("UCSPI_SSL_USER", BtUserGroupName)
+       usrpkg("UCSPI_SSL_GROUP", BtUserGroupName)
+       usrpkg("UNPRIVILEGED", BtYesNo)
+       usrpkg("USE_CROSS_COMPILE", BtYesNo)
+       usrpkg("USERPPP_GROUP", BtUserGroupName)
+       usrpkg("UUCP_GROUP", BtUserGroupName)
+       usrpkg("UUCP_USER", BtUserGroupName)
+       usrpkglist("VIM_EXTRA_OPTS", BtShellWord)
+       usrpkg("WCALC_HTMLDIR", BtPathname)
+       usrpkg("WCALC_HTMLPATH", BtPathname) // URL path
+       usrpkg("WCALC_CGIDIR", BtPrefixPathname)
+       usrpkg("WCALC_CGIPATH", BtPathname) // URL path
+       usrpkglist("WDM_MANAGERS", BtIdentifier)
+       usrpkg("X10_PORT", BtPathname)
+       usrpkg("XAW_TYPE", enum("standard 3d xpm neXtaw"))
+       usrpkg("XLOCK_DEFAULT_MODE", BtIdentifier)
+       usrpkg("ZSH_STATIC", BtYes)
 
        // some other variables, sorted alphabetically
 
-       acl(".CURDIR", lkNone, BtPathname, "buildlink3.mk: none; *: use, use-loadtime")
-       acl(".IMPSRC", lkShell, BtPathname, "buildlink3.mk: none; *: use, use-loadtime")
-       acl(".TARGET", lkNone, BtPathname, "buildlink3.mk: none; *: use, use-loadtime")
-       acl("@", lkNone, BtPathname, "buildlink3.mk: none; *: use, use-loadtime")
-       acl("ALL_ENV", lkShell, BtShellWord, "")
-       acl("ALTERNATIVES_FILE", lkNone, BtFileName, "")
-       acl("ALTERNATIVES_SRC", lkShell, BtPathname, "")
-       pkg("APACHE_MODULE", lkNone, BtYes)
-       sys("AR", lkNone, BtShellCommand)
-       sys("AS", lkNone, BtShellCommand)
-       pkglist("AUTOCONF_REQD", lkShell, BtVersion)
-       acl("AUTOMAKE_OVERRIDE", lkShell, BtPathmask, "")
-       pkglist("AUTOMAKE_REQD", lkShell, BtVersion)
-       pkg("AUTO_MKDIRS", lkNone, BtYesNo)
-       usr("BATCH", lkNone, BtYes)
-       acl("BDB185_DEFAULT", lkNone, BtUnknown, "")
-       sys("BDBBASE", lkNone, BtPathname)
-       pkg("BDB_ACCEPTED", lkShell, enum("db1 db2 db3 db4 db5 db6"))
-       acl("BDB_DEFAULT", lkNone, enum("db1 db2 db3 db4 db5 db6"), "")
-       sys("BDB_LIBS", lkShell, BtLdFlag)
-       sys("BDB_TYPE", lkNone, enum("db1 db2 db3 db4 db5 db6"))
-       sys("BIGENDIANPLATFORMS", lkShell, BtMachinePlatformPattern)
-       sys("BINGRP", lkNone, BtUserGroupName)
-       sys("BINMODE", lkNone, BtFileMode)
-       sys("BINOWN", lkNone, BtUserGroupName)
-       acl("BOOTSTRAP_DEPENDS", lkShell, BtDependencyWithPath, "Makefile, Makefile.common, *.mk: append")
-       pkg("BOOTSTRAP_PKG", lkNone, BtYesNo)
-       acl("BROKEN", lkNone, BtMessage, "")
-       pkg("BROKEN_GETTEXT_DETECTION", lkNone, BtYesNo)
-       pkglist("BROKEN_EXCEPT_ON_PLATFORM", lkShell, BtMachinePlatformPattern)
-       pkglist("BROKEN_ON_PLATFORM", lkShell, BtMachinePlatformPattern)
-       sys("BSD_MAKE_ENV", lkShell, BtShellWord)
+       // TODO: Instead of grouping them alphabetically, group them
+       //  by topic, like clean, fetch, patch, configure, build, install,
+       //  subst, buildlink3, checks. This will make them easier to
+       //  analyze and align the permissions.
+
+       sysload(".CURDIR", BtPathname)
+       sysload(".IMPSRC", BtPathname)
+       sys(".TARGET", BtPathname)
+       sys("@", BtPathname)
+       pkglistbl3("ALL_ENV", BtShellWord)
+       pkg("ALTERNATIVES_FILE", BtFileName)
+       pkglist("ALTERNATIVES_SRC", BtPathname)
+       pkg("APACHE_MODULE", BtYes)
+       sys("AR", BtShellCommand)
+       sys("AS", BtShellCommand)
+       pkglist("AUTOCONF_REQD", BtVersion)
+       pkglist("AUTOMAKE_OVERRIDE", BtPathmask)
+       pkglist("AUTOMAKE_REQD", BtVersion)
+       pkg("AUTO_MKDIRS", BtYesNo)
+       usr("BATCH", BtYes)
+       usr("BDB185_DEFAULT", BtUnknown)
+       sys("BDBBASE", BtPathname)
+       pkglist("BDB_ACCEPTED", enum("db1 db2 db3 db4 db5 db6"))
+       usr("BDB_DEFAULT", enum("db1 db2 db3 db4 db5 db6"))
+       syslist("BDB_LIBS", BtLdFlag)
+       sys("BDB_TYPE", enum("db1 db2 db3 db4 db5 db6"))
+       syslist("BIGENDIANPLATFORMS", BtMachinePlatformPattern)
+       sys("BINGRP", BtUserGroupName)
+       sys("BINMODE", BtFileMode)
+       sys("BINOWN", BtUserGroupName)
+       pkglist("BOOTSTRAP_DEPENDS", BtDependencyWithPath)
+       pkg("BOOTSTRAP_PKG", BtYesNo)
+       // BROKEN should better be a list of messages instead of a simple string.
+       pkgappend("BROKEN", BtMessage)
+       pkg("BROKEN_GETTEXT_DETECTION", BtYesNo)
+       pkglist("BROKEN_EXCEPT_ON_PLATFORM", BtMachinePlatformPattern)
+       pkglist("BROKEN_ON_PLATFORM", BtMachinePlatformPattern)
+       syslist("BSD_MAKE_ENV", BtShellWord)
        // TODO: Align the permissions of the various BUILDLINK_*.* variables with each other.
-       acl("BUILDLINK_ABI_DEPENDS.*", lkShell, BtDependency, "buildlink3.mk, builtin.mk: append, use-loadtime; *: append")
-       acl("BUILDLINK_API_DEPENDS.*", lkShell, BtDependency, "buildlink3.mk, builtin.mk: append, use-loadtime; *: append")
-       acl("BUILDLINK_AUTO_DIRS.*", lkNone, BtYesNo, "buildlink3.mk: append")
-       acl("BUILDLINK_CONTENTS_FILTER", lkNone, BtShellCommand, "")
-       sys("BUILDLINK_CFLAGS", lkShell, BtCFlag)
-       bl3list("BUILDLINK_CFLAGS.*", lkShell, BtCFlag)
-       sys("BUILDLINK_CPPFLAGS", lkShell, BtCFlag)
-       bl3list("BUILDLINK_CPPFLAGS.*", lkShell, BtCFlag)
-       acl("BUILDLINK_CONTENTS_FILTER.*", lkNone, BtShellCommand, "buildlink3.mk: set")
-       acl("BUILDLINK_DEPENDS", lkShell, BtIdentifier, "buildlink3.mk: append")
-       acl("BUILDLINK_DEPMETHOD.*", lkShell, BtBuildlinkDepmethod, "buildlink3.mk: default, append, use; Makefile: set, append; Makefile.common, *.mk: append")
-       acl("BUILDLINK_DIR", lkNone, BtPathname, "*: use")
-       bl3list("BUILDLINK_FILES.*", lkShell, BtPathmask)
-       acl("BUILDLINK_FILES_CMD.*", lkNone, BtShellCommand, "")
-       acl("BUILDLINK_INCDIRS.*", lkShell, BtPathname, "buildlink3.mk: default, append; Makefile, Makefile.common, *.mk: use")
-       acl("BUILDLINK_JAVA_PREFIX.*", lkNone, BtPathname, "buildlink3.mk: set, use")
-       acl("BUILDLINK_LDADD.*", lkShell, BtLdFlag, "builtin.mk: set, default, append, use; buildlink3.mk: append, use; Makefile, Makefile.common, *.mk: use")
-       acl("BUILDLINK_LDFLAGS", lkShell, BtLdFlag, "*: use")
-       bl3list("BUILDLINK_LDFLAGS.*", lkShell, BtLdFlag)
-       acl("BUILDLINK_LIBDIRS.*", lkShell, BtPathname, "buildlink3.mk, builtin.mk: append; Makefile, Makefile.common, *.mk: use")
-       acl("BUILDLINK_LIBS.*", lkShell, BtLdFlag, "buildlink3.mk: append")
-       acl("BUILDLINK_PASSTHRU_DIRS", lkShell, BtPathname, "Makefile, Makefile.common, buildlink3.mk, hacks.mk: append")
-       acl("BUILDLINK_PASSTHRU_RPATHDIRS", lkShell, BtPathname, "Makefile, Makefile.common, buildlink3.mk, hacks.mk: append")
-       acl("BUILDLINK_PKGSRCDIR.*", lkNone, BtRelativePkgDir, "buildlink3.mk: default, use-loadtime")
-       acl("BUILDLINK_PREFIX.*", lkNone, BtPathname, "builtin.mk: set, use; Makefile, Makefile.common, *.mk: use")
-       acl("BUILDLINK_RPATHDIRS.*", lkShell, BtPathname, "buildlink3.mk: append")
-       acl("BUILDLINK_TARGETS", lkShell, BtIdentifier, "")
-       acl("BUILDLINK_FNAME_TRANSFORM.*", lkNone, BtSedCommands, "Makefile, buildlink3.mk, builtin.mk, hacks.mk: append")
-       acl("BUILDLINK_TRANSFORM", lkShell, BtWrapperTransform, "*: append")
-       acl("BUILDLINK_TRANSFORM.*", lkShell, BtWrapperTransform, "*: append")
-       acl("BUILDLINK_TREE", lkShell, BtIdentifier, "buildlink3.mk: append")
-       acl("BUILDLINK_X11_DIR", lkNone, BtPathname, "*: use")
-       acl("BUILD_DEFS", lkShell, BtVariableName, "Makefile, Makefile.common, options.mk: append")
-       pkglist("BUILD_DEFS_EFFECTS", lkShell, BtVariableName)
-       acl("BUILD_DEPENDS", lkShell, BtDependencyWithPath, "Makefile, Makefile.common, *.mk: append")
-       pkglist("BUILD_DIRS", lkShell, BtWrksrcSubdirectory)
-       pkglist("BUILD_ENV", lkShell, BtShellWord)
-       sys("BUILD_MAKE_CMD", lkNone, BtShellCommand)
-       pkglist("BUILD_MAKE_FLAGS", lkShell, BtShellWord)
-       pkglist("BUILD_TARGET", lkShell, BtIdentifier)
-       pkglist("BUILD_TARGET.*", lkShell, BtIdentifier)
-       pkg("BUILD_USES_MSGFMT", lkNone, BtYes)
-       acl("BUILTIN_PKG", lkNone, BtIdentifier, "builtin.mk: set, use-loadtime, use")
-       acl("BUILTIN_PKG.*", lkNone, BtPkgName, "builtin.mk: set, use-loadtime, use")
-       acl("BUILTIN_FIND_FILES_VAR", lkShell, BtVariableName, "builtin.mk: set")
-       acl("BUILTIN_FIND_FILES.*", lkShell, BtPathname, "builtin.mk: set")
-       acl("BUILTIN_FIND_GREP.*", lkNone, BtUnknown, "builtin.mk: set")
-       acl("BUILTIN_FIND_HEADERS_VAR", lkShell, BtVariableName, "builtin.mk: set")
-       acl("BUILTIN_FIND_HEADERS.*", lkShell, BtPathname, "builtin.mk: set")
-       acl("BUILTIN_FIND_LIBS", lkShell, BtPathname, "builtin.mk: set")
-       acl("BUILTIN_IMAKE_CHECK", lkShell, BtUnknown, "builtin.mk: set")
-       acl("BUILTIN_IMAKE_CHECK.*", lkNone, BtYesNo, "")
-       sys("BUILTIN_X11_TYPE", lkNone, BtUnknown)
-       sys("BUILTIN_X11_VERSION", lkNone, BtUnknown)
-       acl("CATEGORIES", lkShell, BtCategory, "Makefile: set, append; Makefile.common: set, default, append")
-       sysload("CC_VERSION", lkNone, BtMessage)
-       sysload("CC", lkNone, BtShellCommand)
-       pkglistbl3("CFLAGS", lkShell, BtCFlag)   // may also be changed by the user
-       pkglistbl3("CFLAGS.*", lkShell, BtCFlag) // may also be changed by the user
-       acl("CHECK_BUILTIN", lkNone, BtYesNo, "builtin.mk: default; Makefile: set")
-       acl("CHECK_BUILTIN.*", lkNone, BtYesNo, "Makefile, options.mk, buildlink3.mk: set; builtin.mk: default, use-loadtime; *: use-loadtime")
-       acl("CHECK_FILES_SKIP", lkShell, BtBasicRegularExpression, "Makefile, Makefile.common: append")
-       pkg("CHECK_FILES_SUPPORTED", lkNone, BtYesNo)
-       usr("CHECK_HEADERS", lkNone, BtYesNo)
-       pkglist("CHECK_HEADERS_SKIP", lkShell, BtPathmask)
-       usr("CHECK_INTERPRETER", lkNone, BtYesNo)
-       pkglist("CHECK_INTERPRETER_SKIP", lkShell, BtPathmask)
-       usr("CHECK_PERMS", lkNone, BtYesNo)
-       pkglist("CHECK_PERMS_SKIP", lkShell, BtPathmask)
-       usr("CHECK_PORTABILITY", lkNone, BtYesNo)
-       pkglist("CHECK_PORTABILITY_SKIP", lkShell, BtPathmask)
-       usr("CHECK_RELRO", lkNone, BtYesNo)
-       pkglist("CHECK_RELRO_SKIP", lkShell, BtPathmask)
-       pkg("CHECK_RELRO_SUPPORTED", lkNone, BtYesNo)
-       acl("CHECK_SHLIBS", lkNone, BtYesNo, "Makefile: set")
-       pkglist("CHECK_SHLIBS_SKIP", lkShell, BtPathmask)
-       acl("CHECK_SHLIBS_SUPPORTED", lkNone, BtYesNo, "Makefile: set")
-       pkglist("CHECK_WRKREF_SKIP", lkShell, BtPathmask)
-       pkg("CMAKE_ARG_PATH", lkNone, BtPathname)
-       pkglist("CMAKE_ARGS", lkShell, BtShellWord)
-       pkglist("CMAKE_ARGS.*", lkShell, BtShellWord)
-       pkglist("CMAKE_DEPENDENCIES_REWRITE", lkShell, BtPathmask) // Relative to WRKSRC
-       pkglist("CMAKE_MODULE_PATH_OVERRIDE", lkShell, BtPathmask) // Relative to WRKSRC
-       pkg("CMAKE_PKGSRC_BUILD_FLAGS", lkNone, BtYesNo)
-       pkglist("CMAKE_PREFIX_PATH", lkShell, BtPathmask)
-       pkglist("CMAKE_USE_GNU_INSTALL_DIRS", lkNone, BtYesNo)
-       pkg("CMAKE_INSTALL_PREFIX", lkNone, BtPathname) // The default is ${PREFIX}.
-       acl("COMMENT", lkNone, BtComment, "Makefile, Makefile.common: set, append")
-       sys("COMPILE.*", lkNone, BtShellCommand)
-       acl("COMPILER_RPATH_FLAG", lkNone, enum("-Wl,-rpath"), "*: use")
-       pkglist("CONFIGURE_ARGS", lkShell, BtShellWord)
-       pkglist("CONFIGURE_ARGS.*", lkShell, BtShellWord)
-       pkglist("CONFIGURE_DIRS", lkShell, BtWrksrcSubdirectory)
-       acl("CONFIGURE_ENV", lkShell, BtShellWord, "Makefile, Makefile.common: append, set, use; buildlink3.mk, builtin.mk: append; *.mk: append, use")
-       acl("CONFIGURE_ENV.*", lkShell, BtShellWord, "Makefile, Makefile.common: append, set, use; buildlink3.mk, builtin.mk: append; *.mk: append, use")
-       pkg("CONFIGURE_HAS_INFODIR", lkNone, BtYesNo)
-       pkg("CONFIGURE_HAS_LIBDIR", lkNone, BtYesNo)
-       pkg("CONFIGURE_HAS_MANDIR", lkNone, BtYesNo)
-       pkg("CONFIGURE_SCRIPT", lkNone, BtPathname)
-       acl("CONFIG_GUESS_OVERRIDE", lkShell, BtPathmask, "Makefile, Makefile.common: set, append")
-       acl("CONFIG_STATUS_OVERRIDE", lkShell, BtPathmask, "Makefile, Makefile.common: set, append")
-       acl("CONFIG_SHELL", lkNone, BtPathname, "Makefile, Makefile.common: set")
-       acl("CONFIG_SUB_OVERRIDE", lkShell, BtPathmask, "Makefile, Makefile.common: set, append")
-       pkglist("CONFLICTS", lkShell, BtDependency)
-       pkglist("CONF_FILES", lkNone, BtConfFiles)
-       pkg("CONF_FILES_MODE", lkNone, enum("0644 0640 0600 0400"))
-       pkglist("CONF_FILES_PERMS", lkShell, BtPerms)
-       sys("COPY", lkNone, enum("-c")) // The flag that tells ${INSTALL} to copy a file
-       sys("CPP", lkNone, BtShellCommand)
-       pkglist("CPPFLAGS", lkShell, BtCFlag)
-       pkglist("CPPFLAGS.*", lkShell, BtCFlag)
-       sys("CXX", lkNone, BtShellCommand)
-       pkglist("CXXFLAGS", lkShell, BtCFlag)
-       pkglist("CXXFLAGS.*", lkShell, BtCFlag)
-       pkglist("CWRAPPERS_APPEND.*", lkShell, BtShellWord)
-       sys("DEFAULT_DISTFILES", lkShell, BtFetchURL) // From mk/fetch/bsd.fetch-vars.mk.
-       acl("DEINSTALL_FILE", lkNone, BtPathname, "Makefile: set")
-       acl("DEINSTALL_SRC", lkShell, BtPathname, "Makefile: set; Makefile.common: default, set")
-       acl("DEINSTALL_TEMPLATES", lkShell, BtPathname, "Makefile: set, append; Makefile.common: set, default, append")
-       sys("DELAYED_ERROR_MSG", lkNone, BtShellCommand)
-       sys("DELAYED_WARNING_MSG", lkNone, BtShellCommand)
-       pkglist("DEPENDS", lkShell, BtDependencyWithPath)
-       usr("DEPENDS_TARGET", lkShell, BtIdentifier)
-       acl("DESCR_SRC", lkShell, BtPathname, "Makefile: set, append; Makefile.common: default, set")
-       sys("DESTDIR", lkNone, BtPathname)
-       acl("DESTDIR_VARNAME", lkNone, BtVariableName, "Makefile, Makefile.common: set")
-       sys("DEVOSSAUDIO", lkNone, BtPathname)
-       sys("DEVOSSSOUND", lkNone, BtPathname)
-       pkglist("DISTFILES", lkShell, BtFileName)
-       pkg("DISTINFO_FILE", lkNone, BtRelativePkgPath)
-       pkg("DISTNAME", lkNone, BtFileName)
-       pkg("DIST_SUBDIR", lkNone, BtPathname)
-       acl("DJB_BUILD_ARGS", lkShell, BtShellWord, "")
-       acl("DJB_BUILD_TARGETS", lkShell, BtIdentifier, "")
-       acl("DJB_CONFIG_CMDS", lkNone, BtShellCommands, "options.mk: set")
-       acl("DJB_CONFIG_DIRS", lkShell, BtWrksrcSubdirectory, "")
-       acl("DJB_CONFIG_HOME", lkNone, BtFileName, "")
-       acl("DJB_CONFIG_PREFIX", lkNone, BtPathname, "")
-       acl("DJB_INSTALL_TARGETS", lkShell, BtIdentifier, "")
-       acl("DJB_MAKE_TARGETS", lkNone, BtYesNo, "")
-       acl("DJB_RESTRICTED", lkNone, BtYesNo, "Makefile: set")
-       acl("DJB_SLASHPACKAGE", lkNone, BtYesNo, "")
-       acl("DLOPEN_REQUIRE_PTHREADS", lkNone, BtYesNo, "")
-       acl("DL_AUTO_VARS", lkNone, BtYes, "Makefile, Makefile.common, options.mk: set")
-       acl("DL_LIBS", lkShell, BtLdFlag, "")
-       sys("DOCOWN", lkNone, BtUserGroupName)
-       sys("DOCGRP", lkNone, BtUserGroupName)
-       sys("DOCMODE", lkNone, BtFileMode)
-       sys("DOWNLOADED_DISTFILE", lkNone, BtPathname)
-       sys("DO_NADA", lkNone, BtShellCommand)
-       pkg("DYNAMIC_SITES_CMD", lkNone, BtShellCommand)
-       pkg("DYNAMIC_SITES_SCRIPT", lkNone, BtPathname)
-       acl("ECHO", lkNone, BtShellCommand, "*: use")
-       sys("ECHO_MSG", lkNone, BtShellCommand)
-       sys("ECHO_N", lkNone, BtShellCommand)
-       pkg("EGDIR", lkNone, BtPathname) // Not defined anywhere but used in many places like this.
-       sys("EMACS_BIN", lkNone, BtPathname)
-       sys("EMACS_ETCPREFIX", lkNone, BtPathname)
-       sys("EMACS_FLAVOR", lkNone, enum("emacs xemacs"))
-       sys("EMACS_INFOPREFIX", lkNone, BtPathname)
-       sys("EMACS_LISPPREFIX", lkNone, BtPathname)
-       acl("EMACS_MODULES", lkShell, BtIdentifier, "Makefile, Makefile.common: set, append")
-       sys("EMACS_PKGNAME_PREFIX", lkNone, BtIdentifier) // Or the empty string.
-       sys("EMACS_TYPE", lkNone, enum("emacs xemacs"))
-       acl("EMACS_USE_LEIM", lkNone, BtYes, "")
-       acl("EMACS_VERSIONS_ACCEPTED", lkShell, emacsVersions, "Makefile: set")
-       sys("EMACS_VERSION_MAJOR", lkNone, BtInteger)
-       sys("EMACS_VERSION_MINOR", lkNone, BtInteger)
-       acl("EMACS_VERSION_REQD", lkShell, emacsVersions, "Makefile: set, append")
-       sys("EMULDIR", lkNone, BtPathname)
-       sys("EMULSUBDIR", lkNone, BtPathname)
-       sys("OPSYS_EMULDIR", lkNone, BtPathname)
-       sys("EMULSUBDIRSLASH", lkNone, BtPathname)
-       sys("EMUL_ARCH", lkNone, enum("arm i386 m68k none ns32k sparc vax x86_64"))
-       sys("EMUL_DISTRO", lkNone, BtIdentifier)
-       sys("EMUL_IS_NATIVE", lkNone, BtYes)
-       pkg("EMUL_MODULES.*", lkShell, BtIdentifier)
-       sys("EMUL_OPSYS", lkNone, enum("darwin freebsd hpux irix linux osf1 solaris sunos none"))
-       pkg("EMUL_PKG_FMT", lkNone, enum("plain rpm"))
-       usr("EMUL_PLATFORM", lkNone, BtEmulPlatform)
-       pkg("EMUL_PLATFORMS", lkShell, BtEmulPlatform)
-       usr("EMUL_PREFER", lkShell, BtEmulPlatform)
-       pkg("EMUL_REQD", lkShell, BtDependency)
-       usr("EMUL_TYPE.*", lkNone, enum("native builtin suse suse-10.0 suse-12.1 suse-13.1"))
-       sys("ERROR_CAT", lkNone, BtShellCommand)
-       sys("ERROR_MSG", lkNone, BtShellCommand)
-       sys("EXPORT_SYMBOLS_LDFLAGS", lkShell, BtLdFlag)
-       sys("EXTRACT_CMD", lkNone, BtShellCommand)
-       pkg("EXTRACT_DIR", lkNone, BtPathname)
-       pkg("EXTRACT_DIR.*", lkNone, BtPathname)
-       pkglist("EXTRACT_ELEMENTS", lkShell, BtPathmask)
-       pkglist("EXTRACT_ENV", lkShell, BtShellWord)
-       pkglist("EXTRACT_ONLY", lkShell, BtPathname)
-       acl("EXTRACT_OPTS", lkShell, BtShellWord, "Makefile, Makefile.common: set, append")
-       acl("EXTRACT_OPTS_BIN", lkShell, BtShellWord, "Makefile, Makefile.common: set, append")
-       acl("EXTRACT_OPTS_LHA", lkShell, BtShellWord, "Makefile, Makefile.common: set, append")
-       acl("EXTRACT_OPTS_PAX", lkShell, BtShellWord, "Makefile, Makefile.common: set, append")
-       acl("EXTRACT_OPTS_RAR", lkShell, BtShellWord, "Makefile, Makefile.common: set, append")
-       acl("EXTRACT_OPTS_TAR", lkShell, BtShellWord, "Makefile, Makefile.common: set, append")
-       acl("EXTRACT_OPTS_ZIP", lkShell, BtShellWord, "Makefile, Makefile.common: set, append")
-       acl("EXTRACT_OPTS_ZOO", lkShell, BtShellWord, "Makefile, Makefile.common: set, append")
-       pkg("EXTRACT_SUFX", lkNone, BtDistSuffix)
-       pkg("EXTRACT_USING", lkNone, enum("bsdtar gtar nbtar pax"))
-       sys("FAIL_MSG", lkNone, BtShellCommand)
-       sys("FAMBASE", lkNone, BtPathname)
-       pkg("FAM_ACCEPTED", lkShell, enum("fam gamin"))
-       usr("FAM_DEFAULT", lkNone, enum("fam gamin"))
-       sys("FAM_TYPE", lkNone, enum("fam gamin"))
-       acl("FETCH_BEFORE_ARGS", lkShell, BtShellWord, "Makefile: set, append")
-       pkglist("FETCH_MESSAGE", lkShell, BtShellWord)
-       pkg("FILESDIR", lkNone, BtRelativePkgPath)
-       pkglist("FILES_SUBST", lkShell, BtShellWord)
-       acl("FILES_SUBST_SED", lkShell, BtShellWord, "")
-       pkglist("FIX_RPATH", lkShell, BtVariableName)
-       pkglist("FLEX_REQD", lkShell, BtVersion)
-       acl("FONTS_DIRS.*", lkShell, BtPathname, "Makefile: set, append, use; Makefile.common: append, use")
-       sys("GAMEDATAMODE", lkNone, BtFileMode)
-       sys("GAMES_GROUP", lkNone, BtUserGroupName)
-       sys("GAMEDATA_PERMS", lkShell, BtPerms)
-       sys("GAMEDIR_PERMS", lkShell, BtPerms)
-       sys("GAMEMODE", lkNone, BtFileMode)
-       sys("GAMES_USER", lkNone, BtUserGroupName)
-       pkglist("GCC_REQD", lkShell, BtGccReqd)
-       pkglist("GENERATE_PLIST", lkNone, BtShellCommands)
-       pkg("GITHUB_PROJECT", lkNone, BtIdentifier)
-       pkg("GITHUB_TAG", lkNone, BtIdentifier)
-       pkg("GITHUB_RELEASE", lkNone, BtFileName)
-       pkg("GITHUB_TYPE", lkNone, enum("tag release"))
-       pkg("GMAKE_REQD", lkNone, BtVersion)
-       acl("GNU_ARCH", lkNone, enum("mips"), "")
-       acl("GNU_ARCH.*", lkNone, BtIdentifier, "buildlink3.mk: none; *: set, use")
-       acl("GNU_CONFIGURE", lkNone, BtYes, "Makefile, Makefile.common: set")
-       acl("GNU_CONFIGURE_INFODIR", lkNone, BtPathname, "Makefile, Makefile.common: set")
-       acl("GNU_CONFIGURE_LIBDIR", lkNone, BtPathname, "Makefile, Makefile.common: set")
-       pkg("GNU_CONFIGURE_LIBSUBDIR", lkNone, BtPathname)
-       acl("GNU_CONFIGURE_MANDIR", lkNone, BtPathname, "Makefile, Makefile.common: set")
-       acl("GNU_CONFIGURE_PREFIX", lkNone, BtPathname, "Makefile: set")
-       pkg("GOPATH", lkNone, BtPathname)
-       acl("HAS_CONFIGURE", lkNone, BtYes, "Makefile, Makefile.common: set")
-       pkglist("HEADER_TEMPLATES", lkShell, BtPathname)
-       pkg("HOMEPAGE", lkNone, BtHomepage)
-       pkg("ICON_THEMES", lkNone, BtYes)
-       acl("IGNORE_PKG.*", lkNone, BtYes, "*: set, use-loadtime")
-       sys("IMAKE", lkNone, BtShellCommand)
-       acl("INCOMPAT_CURSES", lkShell, BtMachinePlatformPattern, "Makefile: set, append")
-       acl("INCOMPAT_ICONV", lkShell, BtMachinePlatformPattern, "")
-       acl("INFO_DIR", lkNone, BtPathname, "") // relative to PREFIX
-       pkg("INFO_FILES", lkNone, BtYes)
-       sys("INSTALL", lkNone, BtShellCommand)
-       pkglist("INSTALLATION_DIRS", lkShell, BtPrefixPathname)
-       pkg("INSTALLATION_DIRS_FROM_PLIST", lkNone, BtYes)
-       sys("INSTALL_DATA", lkNone, BtShellCommand)
-       sys("INSTALL_DATA_DIR", lkNone, BtShellCommand)
-       pkglist("INSTALL_DIRS", lkShell, BtWrksrcSubdirectory)
-       pkglist("INSTALL_ENV", lkShell, BtShellWord)
-       acl("INSTALL_FILE", lkNone, BtPathname, "Makefile: set")
-       sys("INSTALL_GAME", lkNone, BtShellCommand)
-       sys("INSTALL_GAME_DATA", lkNone, BtShellCommand)
-       sys("INSTALL_LIB", lkNone, BtShellCommand)
-       sys("INSTALL_LIB_DIR", lkNone, BtShellCommand)
-       pkglist("INSTALL_MAKE_FLAGS", lkShell, BtShellWord)
-       sys("INSTALL_MAN", lkNone, BtShellCommand)
-       sys("INSTALL_MAN_DIR", lkNone, BtShellCommand)
-       sys("INSTALL_PROGRAM", lkNone, BtShellCommand)
-       sys("INSTALL_PROGRAM_DIR", lkNone, BtShellCommand)
-       sys("INSTALL_SCRIPT", lkNone, BtShellCommand)
-       acl("INSTALL_SCRIPTS_ENV", lkShell, BtShellWord, "")
-       sys("INSTALL_SCRIPT_DIR", lkNone, BtShellCommand)
-       acl("INSTALL_SRC", lkShell, BtPathname, "Makefile: set; Makefile.common: default, set")
-       pkg("INSTALL_TARGET", lkShell, BtIdentifier)
-       acl("INSTALL_TEMPLATES", lkShell, BtPathname, "Makefile: set, append; Makefile.common: set, default, append")
-       acl("INSTALL_UNSTRIPPED", lkNone, BtYesNo, "Makefile, Makefile.common: set")
-       pkg("INTERACTIVE_STAGE", lkShell, enum("fetch extract configure build test install"))
-       acl("IS_BUILTIN.*", lkNone, BtYesNoIndirectly, "builtin.mk: set, use-loadtime, use")
-       sys("JAVA_BINPREFIX", lkNone, BtPathname)
-       pkg("JAVA_CLASSPATH", lkNone, BtShellWord)
-       pkg("JAVA_HOME", lkNone, BtPathname)
-       pkg("JAVA_NAME", lkNone, BtFileName)
-       pkglist("JAVA_UNLIMIT", lkShell, enum("cmdsize datasize stacksize"))
-       pkglist("JAVA_WRAPPERS", lkShell, BtFileName)
-       pkg("JAVA_WRAPPER_BIN.*", lkNone, BtPathname)
-       sys("KRB5BASE", lkNone, BtPathname)
-       acl("KRB5_ACCEPTED", lkShell, enum("heimdal mit-krb5"), "")
-       usr("KRB5_DEFAULT", lkNone, enum("heimdal mit-krb5"))
-       sys("KRB5_TYPE", lkNone, BtIdentifier)
-       sys("LD", lkNone, BtShellCommand)
-       pkglistbl3("LDFLAGS", lkShell, BtLdFlag)      // May also be changed by the user.
-       pkglistbl3("LDFLAGS.*", lkShell, BtLdFlag)    // May also be changed by the user.
-       sysload("LIBABISUFFIX", lkNone, BtIdentifier) // Can also be empty.
-       sys("LIBGRP", lkNone, BtUserGroupName)
-       sys("LIBMODE", lkNone, BtFileMode)
-       sys("LIBOWN", lkNone, BtUserGroupName)
-       sys("LIBOSSAUDIO", lkNone, BtPathname)
-       pkglist("LIBS", lkShell, BtLdFlag)
-       pkglist("LIBS.*", lkShell, BtLdFlag)
-       sys("LIBTOOL", lkNone, BtShellCommand)
-       acl("LIBTOOL_OVERRIDE", lkShell, BtPathmask, "Makefile: set, append")
-       pkglist("LIBTOOL_REQD", lkShell, BtVersion)
-       acl("LICENCE", lkNone, BtLicense, "buildlink3.mk, builtin.mk: none; Makefile: set, append; *: default, set, append")
-       acl("LICENSE", lkNone, BtLicense, "buildlink3.mk, builtin.mk: none; Makefile: set, append; *: default, set, append")
-       pkg("LICENSE_FILE", lkNone, BtPathname)
-       sys("LINK.*", lkNone, BtShellCommand)
-       sys("LINKER_RPATH_FLAG", lkNone, BtShellWord)
-       sys("LITTLEENDIANPLATFORMS", lkShell, BtMachinePlatformPattern)
-       sys("LOWER_OPSYS", lkNone, BtIdentifier)
-       sys("LOWER_VENDOR", lkNone, BtIdentifier)
-       sys("LP64PLATFORMS", lkShell, BtMachinePlatformPattern)
-       acl("LTCONFIG_OVERRIDE", lkShell, BtPathmask, "Makefile: set, append; Makefile.common: append")
-       sysload("MACHINE_ARCH", lkNone, enumMachineArch)
-       sysload("MACHINE_GNU_ARCH", lkNone, enumMachineGnuArch)
-       sysload("MACHINE_GNU_PLATFORM", lkNone, BtMachineGnuPlatform)
-       sysload("MACHINE_PLATFORM", lkNone, BtMachinePlatform)
-       acl("MAINTAINER", lkNone, BtMailAddress, "Makefile: set; Makefile.common: default")
-       sysload("MAKE", lkNone, BtShellCommand)
-       pkglist("MAKEFLAGS", lkShell, BtShellWord)
-       acl("MAKEVARS", lkShell, BtVariableName, "buildlink3.mk, builtin.mk, hacks.mk: append")
-       pkglist("MAKE_DIRS", lkShell, BtPathname)
-       pkglist("MAKE_DIRS_PERMS", lkShell, BtPerms)
-       acl("MAKE_ENV", lkShell, BtShellWord, "Makefile, Makefile.common: append, set, use; buildlink3.mk, builtin.mk: append; *.mk: append, use")
-       acl("MAKE_ENV.*", lkShell, BtShellWord, "Makefile, Makefile.common: append, set, use; buildlink3.mk, builtin.mk: append; *.mk: append, use")
-       pkg("MAKE_FILE", lkNone, BtPathname)
-       pkglist("MAKE_FLAGS", lkShell, BtShellWord)
-       pkglist("MAKE_FLAGS.*", lkShell, BtShellWord)
-       usr("MAKE_JOBS", lkNone, BtInteger)
-       pkg("MAKE_JOBS_SAFE", lkNone, BtYesNo)
-       pkg("MAKE_PROGRAM", lkNone, BtShellCommand)
-       acl("MANCOMPRESSED", lkNone, BtYesNo, "Makefile: set; Makefile.common: default, set")
-       acl("MANCOMPRESSED_IF_MANZ", lkNone, BtYes, "Makefile: set; Makefile.common: default, set")
-       sys("MANGRP", lkNone, BtUserGroupName)
-       sys("MANMODE", lkNone, BtFileMode)
-       sys("MANOWN", lkNone, BtUserGroupName)
-       pkglist("MASTER_SITES", lkShell, BtFetchURL)
-       // TODO: Extract the MASTER_SITE_* definitions from mk/fetch/sites.mk instead of listing them here.
-       sys("MASTER_SITE_APACHE", lkShell, BtFetchURL)
-       sys("MASTER_SITE_BACKUP", lkShell, BtFetchURL)
-       sys("MASTER_SITE_CRATESIO", lkShell, BtFetchURL)
-       sys("MASTER_SITE_CYGWIN", lkShell, BtFetchURL)
-       sys("MASTER_SITE_DEBIAN", lkShell, BtFetchURL)
-       sys("MASTER_SITE_FREEBSD", lkShell, BtFetchURL)
-       sys("MASTER_SITE_FREEBSD_LOCAL", lkShell, BtFetchURL)
-       sys("MASTER_SITE_GENTOO", lkShell, BtFetchURL)
-       sys("MASTER_SITE_GITHUB", lkShell, BtFetchURL)
-       sys("MASTER_SITE_GNOME", lkShell, BtFetchURL)
-       sys("MASTER_SITE_GNU", lkShell, BtFetchURL)
-       sys("MASTER_SITE_GNUSTEP", lkShell, BtFetchURL)
-       sys("MASTER_SITE_IFARCHIVE", lkShell, BtFetchURL)
-       sys("MASTER_SITE_HASKELL_HACKAGE", lkShell, BtFetchURL)
-       sys("MASTER_SITE_KDE", lkShell, BtFetchURL)
-       sys("MASTER_SITE_LOCAL", lkShell, BtFetchURL)
-       sys("MASTER_SITE_MOZILLA", lkShell, BtFetchURL)
-       sys("MASTER_SITE_MOZILLA_ALL", lkShell, BtFetchURL)
-       sys("MASTER_SITE_MOZILLA_ESR", lkShell, BtFetchURL)
-       sys("MASTER_SITE_MYSQL", lkShell, BtFetchURL)
-       sys("MASTER_SITE_NETLIB", lkShell, BtFetchURL)
-       sys("MASTER_SITE_OPENBSD", lkShell, BtFetchURL)
-       sys("MASTER_SITE_OPENOFFICE", lkShell, BtFetchURL)
-       sys("MASTER_SITE_OSDN", lkShell, BtFetchURL)
-       sys("MASTER_SITE_PERL_CPAN", lkShell, BtFetchURL)
-       sys("MASTER_SITE_PGSQL", lkShell, BtFetchURL)
-       sys("MASTER_SITE_PYPI", lkShell, BtFetchURL)
-       sys("MASTER_SITE_R_CRAN", lkShell, BtFetchURL)
-       sys("MASTER_SITE_RUBYGEMS", lkShell, BtFetchURL)
-       sys("MASTER_SITE_SOURCEFORGE", lkShell, BtFetchURL)
-       sys("MASTER_SITE_SUNSITE", lkShell, BtFetchURL)
-       sys("MASTER_SITE_SUSE", lkShell, BtFetchURL)
-       sys("MASTER_SITE_TEX_CTAN", lkShell, BtFetchURL)
-       sys("MASTER_SITE_XCONTRIB", lkShell, BtFetchURL)
-       sys("MASTER_SITE_XEMACS", lkShell, BtFetchURL)
-       sys("MASTER_SITE_XORG", lkShell, BtFetchURL)
-       pkglist("MESSAGE_SRC", lkShell, BtPathname)
-       acl("MESSAGE_SUBST", lkShell, BtShellWord, "Makefile, Makefile.common, options.mk: append")
-       pkg("META_PACKAGE", lkNone, BtYes)
-       sys("MISSING_FEATURES", lkShell, BtIdentifier)
-       acl("MYSQL_VERSIONS_ACCEPTED", lkShell, mysqlVersions, "Makefile: set")
-       usr("MYSQL_VERSION_DEFAULT", lkNone, BtVersion)
-       sys("NATIVE_CC", lkNone, BtShellCommand) // See mk/platform/tools.NetBSD.mk (and some others).
-       sys("NM", lkNone, BtShellCommand)
-       sys("NONBINMODE", lkNone, BtFileMode)
-       pkg("NOT_FOR_COMPILER", lkShell, compilers)
-       pkglist("NOT_FOR_BULK_PLATFORM", lkShell, BtMachinePlatformPattern)
-       pkglist("NOT_FOR_PLATFORM", lkShell, BtMachinePlatformPattern)
-       pkg("NOT_FOR_UNPRIVILEGED", lkNone, BtYesNo)
-       pkglist("NOT_PAX_ASLR_SAFE", lkShell, BtPathmask)
-       pkglist("NOT_PAX_MPROTECT_SAFE", lkShell, BtPathmask)
-       acl("NO_BIN_ON_CDROM", lkNone, BtRestricted, "Makefile, Makefile.common: set")
-       acl("NO_BIN_ON_FTP", lkNone, BtRestricted, "Makefile, Makefile.common: set")
-       acl("NO_BUILD", lkNone, BtYes, "Makefile, Makefile.common: set; Makefile.*: default, set")
-       pkg("NO_CHECKSUM", lkNone, BtYes)
-       pkg("NO_CONFIGURE", lkNone, BtYes)
-       acl("NO_EXPORT_CPP", lkNone, BtYes, "Makefile: set")
-       pkg("NO_EXTRACT", lkNone, BtYes)
-       pkg("NO_INSTALL_MANPAGES", lkNone, BtYes) // only has an effect for Imake packages.
-       acl("NO_PKGTOOLS_REQD_CHECK", lkNone, BtYes, "Makefile: set")
-       acl("NO_SRC_ON_CDROM", lkNone, BtRestricted, "Makefile, Makefile.common: set")
-       acl("NO_SRC_ON_FTP", lkNone, BtRestricted, "Makefile, Makefile.common: set")
-       sysload("OBJECT_FMT", lkNone, enum("COFF ECOFF ELF SOM XCOFF Mach-O PE a.out"))
-       pkglist("ONLY_FOR_COMPILER", lkShell, compilers)
-       pkglist("ONLY_FOR_PLATFORM", lkShell, BtMachinePlatformPattern)
-       pkg("ONLY_FOR_UNPRIVILEGED", lkNone, BtYesNo)
-       sysload("OPSYS", lkNone, BtIdentifier)
-       acl("OPSYSVARS", lkShell, BtVariableName, "Makefile, Makefile.common: append")
-       acl("OSVERSION_SPECIFIC", lkNone, BtYes, "Makefile, Makefile.common: set")
-       sysload("OS_VERSION", lkNone, BtVersion)
-       sysload("OSX_VERSION", lkNone, BtVersion) // See mk/platform/Darwin.mk.
-       pkg("OVERRIDE_DIRDEPTH*", lkNone, BtInteger)
-       pkg("OVERRIDE_GNU_CONFIG_SCRIPTS", lkNone, BtYes)
-       acl("OWNER", lkNone, BtMailAddress, "Makefile: set; Makefile.common: default")
-       pkglist("OWN_DIRS", lkShell, BtPathname)
-       pkglist("OWN_DIRS_PERMS", lkShell, BtPerms)
-       sys("PAMBASE", lkNone, BtPathname)
-       usr("PAM_DEFAULT", lkNone, enum("linux-pam openpam solaris-pam"))
-       acl("PATCHDIR", lkNone, BtRelativePkgPath, "Makefile: set; Makefile.common: default, set")
-       pkglist("PATCHFILES", lkShell, BtFileName)
-       acl("PATCH_ARGS", lkShell, BtShellWord, "")
-       acl("PATCH_DIST_ARGS", lkShell, BtShellWord, "Makefile: set, append")
-       acl("PATCH_DIST_CAT", lkNone, BtShellCommand, "")
-       acl("PATCH_DIST_STRIP*", lkNone, BtShellWord, "buildlink3.mk, builtin.mk: none; Makefile, Makefile.common, *.mk: set")
-       acl("PATCH_SITES", lkShell, BtFetchURL, "Makefile, Makefile.common, options.mk: set")
-       acl("PATCH_STRIP", lkNone, BtShellWord, "")
-       sys("PATH", lkNone, BtPathlist)       // From the PATH environment variable.
-       sys("PAXCTL", lkNone, BtShellCommand) // See mk/pax.mk.
-       acl("PERL5_PACKLIST", lkShell, BtPerl5Packlist, "Makefile: set; options.mk: set, append")
-       acl("PERL5_PACKLIST_DIR", lkNone, BtPathname, "")
-       pkg("PERL5_REQD", lkShell, BtVersion)
-       sys("PERL5_INSTALLARCHLIB", lkNone, BtPathname) // See lang/perl5/vars.mk
-       sys("PERL5_INSTALLSCRIPT", lkNone, BtPathname)
-       sys("PERL5_INSTALLVENDORBIN", lkNone, BtPathname)
-       sys("PERL5_INSTALLVENDORSCRIPT", lkNone, BtPathname)
-       sys("PERL5_INSTALLVENDORARCH", lkNone, BtPathname)
-       sys("PERL5_INSTALLVENDORLIB", lkNone, BtPathname)
-       sys("PERL5_INSTALLVENDORMAN1DIR", lkNone, BtPathname)
-       sys("PERL5_INSTALLVENDORMAN3DIR", lkNone, BtPathname)
-       sys("PERL5_SUB_INSTALLARCHLIB", lkNone, BtPrefixPathname) // See lang/perl5/vars.mk
-       sys("PERL5_SUB_INSTALLSCRIPT", lkNone, BtPrefixPathname)
-       sys("PERL5_SUB_INSTALLVENDORBIN", lkNone, BtPrefixPathname)
-       sys("PERL5_SUB_INSTALLVENDORSCRIPT", lkNone, BtPrefixPathname)
-       sys("PERL5_SUB_INSTALLVENDORARCH", lkNone, BtPrefixPathname)
-       sys("PERL5_SUB_INSTALLVENDORLIB", lkNone, BtPrefixPathname)
-       sys("PERL5_SUB_INSTALLVENDORMAN1DIR", lkNone, BtPrefixPathname)
-       sys("PERL5_SUB_INSTALLVENDORMAN3DIR", lkNone, BtPrefixPathname)
-       pkg("PERL5_USE_PACKLIST", lkNone, BtYesNo)
-       sys("PGSQL_PREFIX", lkNone, BtPathname)
-       acl("PGSQL_VERSIONS_ACCEPTED", lkShell, pgsqlVersions, "")
-       usr("PGSQL_VERSION_DEFAULT", lkNone, BtVersion)
-       sys("PG_LIB_EXT", lkNone, enum("dylib so"))
-       sys("PGSQL_TYPE", lkNone, enumFrom("mk/pgsql.buildlink3.mk", "postgresql11-client", "PGSQL_TYPE"))
-       sys("PGPKGSRCDIR", lkNone, BtPathname)
-       sys("PHASE_MSG", lkNone, BtShellCommand)
-       usr("PHP_VERSION_REQD", lkNone, BtVersion)
-       acl("PHP_PKG_PREFIX", lkNone, enumFromDirs("lang", `^php(\d+)$`, "php$1", "php56 php71 php72 php73"), ""+
-               "special:phpversion.mk: set; "+
-               "*: use-loadtime, use")
-       sys("PKGBASE", lkNone, BtIdentifier)
-       acl("PKGCONFIG_FILE.*", lkShell, BtPathname,
-               "builtin.mk: set, append; special:pkgconfig-builtin.mk: use-loadtime")
-       acl("PKGCONFIG_OVERRIDE", lkShell, BtPathmask,
-               "Makefile: set, append; Makefile.common: append")
-       pkg("PKGCONFIG_OVERRIDE_STAGE", lkNone, BtStage)
-       pkg("PKGDIR", lkNone, BtRelativePkgDir)
-       sys("PKGDIRMODE", lkNone, BtFileMode)
-       sys("PKGLOCALEDIR", lkNone, BtPathname)
-       pkg("PKGNAME", lkNone, BtPkgName)
-       sys("PKGNAME_NOREV", lkNone, BtPkgName)
-       sysload("PKGPATH", lkNone, BtPathname)
-       acl("PKGREPOSITORY", lkNone, BtUnknown, "")
-       acl("PKGREVISION", lkNone, BtPkgRevision, "Makefile: set; *: none")
-       sys("PKGSRCDIR", lkNone, BtPathname)
-       acl("PKGSRCTOP", lkNone, BtYes, "Makefile: set")
-       sys("PKGSRC_SETENV", lkNone, BtShellCommand)
-       acl("PKGTOOLS_ENV", lkShell, BtShellWord, "")
-       sys("PKGVERSION", lkNone, BtVersion)
-       sys("PKGVERSION_NOREV", lkNone, BtVersion) // Without the nb* part.
-       sys("PKGWILDCARD", lkNone, BtFileMask)
-       sys("PKG_ADMIN", lkNone, BtShellCommand)
-       sys("PKG_APACHE", lkNone, enum("apache24"))
-       pkg("PKG_APACHE_ACCEPTED", lkShell, enum("apache24"))
-       usr("PKG_APACHE_DEFAULT", lkNone, enum("apache24"))
-       sysload("PKG_BUILD_OPTIONS.*", lkShell, BtOption)
-       usr("PKG_CONFIG", lkNone, BtYes)
+       acllist("BUILDLINK_ABI_DEPENDS.*", BtDependency,
+               "buildlink3.mk, builtin.mk: append, use-loadtime",
+               "*: append")
+       acllist("BUILDLINK_API_DEPENDS.*", BtDependency,
+               "buildlink3.mk, builtin.mk: append, use-loadtime",
+               "*: append")
+       acl("BUILDLINK_AUTO_DIRS.*", BtYesNo,
+               "buildlink3.mk: append",
+               "Makefile: set")
+       syslist("BUILDLINK_CFLAGS", BtCFlag)
+       bl3list("BUILDLINK_CFLAGS.*", BtCFlag)
+       acl("BUILDLINK_CONTENTS_FILTER.*", BtShellCommand,
+               "buildlink3.mk: set")
+       syslist("BUILDLINK_CPPFLAGS", BtCFlag)
+       bl3list("BUILDLINK_CPPFLAGS.*", BtCFlag)
+       acllist("BUILDLINK_DEPENDS", BtIdentifier,
+               "buildlink3.mk: append")
+       acllist("BUILDLINK_DEPMETHOD.*", BtBuildlinkDepmethod,
+               "buildlink3.mk: default, append, use",
+               "Makefile, Makefile.*, *.mk: default, set, append")
+       acl("BUILDLINK_DIR", BtPathname,
+               "*: use")
+       bl3list("BUILDLINK_FILES.*", BtPathmask)
+       pkgbl3("BUILDLINK_FILES_CMD.*", BtShellCommand)
+       acllist("BUILDLINK_INCDIRS.*", BtPathname,
+               "buildlink3.mk: default, append",
+               "Makefile, Makefile.*, *.mk: use")
+       acl("BUILDLINK_JAVA_PREFIX.*", BtPathname,
+               "buildlink3.mk: set, use")
+       acllist("BUILDLINK_LDADD.*", BtLdFlag,
+               "builtin.mk: default, set, append, use",
+               "buildlink3.mk: append, use",
+               "Makefile, Makefile.*, *.mk: use")
+       acllist("BUILDLINK_LDFLAGS", BtLdFlag,
+               "*: use")
+       bl3list("BUILDLINK_LDFLAGS.*", BtLdFlag)
+       acllist("BUILDLINK_LIBDIRS.*", BtPathname,
+               "buildlink3.mk, builtin.mk: append",
+               "Makefile, Makefile.*, *.mk: use")
+       acllist("BUILDLINK_LIBS.*", BtLdFlag,
+               "buildlink3.mk: append",
+               "Makefile, Makefile.*, *.mk: set, append, use")
+       acllist("BUILDLINK_PASSTHRU_DIRS", BtPathname,
+               "Makefile, Makefile.*, *.mk: append")
+       acllist("BUILDLINK_PASSTHRU_RPATHDIRS", BtPathname,
+               "Makefile, Makefile.*, *.mk: append")
+       acl("BUILDLINK_PKGSRCDIR.*", BtRelativePkgDir,
+               "buildlink3.mk: default, use-loadtime")
+       acl("BUILDLINK_PREFIX.*", BtPathname,
+               "builtin.mk: set, use",
+               "Makefile, Makefile.*, *.mk: use")
+       acllist("BUILDLINK_RPATHDIRS.*", BtPathname,
+               "buildlink3.mk: append")
+       acllist("BUILDLINK_TARGETS", BtIdentifier,
+               "Makefile, Makefile.*, *.mk: append")
+       acl("BUILDLINK_FNAME_TRANSFORM.*", BtSedCommands,
+               "Makefile, buildlink3.mk, builtin.mk, hacks.mk, options.mk: append")
+       acllist("BUILDLINK_TRANSFORM", BtWrapperTransform,
+               "*: append")
+       acllist("BUILDLINK_TRANSFORM.*", BtWrapperTransform,
+               "*: append")
+       acllist("BUILDLINK_TREE", BtIdentifier,
+               "buildlink3.mk: append")
+       acl("BUILDLINK_X11_DIR", BtPathname,
+               "*: use")
+       acllist("BUILD_DEFS", BtVariableName,
+               "Makefile, Makefile.*, *.mk: append")
+       pkglist("BUILD_DEFS_EFFECTS", BtVariableName)
+       acllist("BUILD_DEPENDS", BtDependencyWithPath,
+               "Makefile, Makefile.*, *.mk: append")
+       pkglist("BUILD_DIRS", BtWrksrcSubdirectory)
+       pkglist("BUILD_ENV", BtShellWord)
+       sys("BUILD_MAKE_CMD", BtShellCommand)
+       pkglist("BUILD_MAKE_FLAGS", BtShellWord)
+       pkglist("BUILD_TARGET", BtIdentifier)
+       pkglist("BUILD_TARGET.*", BtIdentifier)
+       pkg("BUILD_USES_MSGFMT", BtYes)
+       acl("BUILTIN_PKG", BtIdentifier,
+               "builtin.mk: set, use, use-loadtime",
+               "Makefile, Makefile.*, *.mk: use, use-loadtime")
+       acl("BUILTIN_PKG.*", BtPkgName,
+               "builtin.mk: set, use, use-loadtime")
+       pkglistbl3("BUILTIN_FIND_FILES_VAR", BtVariableName)
+       pkglistbl3("BUILTIN_FIND_FILES.*", BtPathname)
+       acl("BUILTIN_FIND_GREP.*", BtUnknown,
+               "builtin.mk: set")
+       acllist("BUILTIN_FIND_HEADERS_VAR", BtVariableName,
+               "builtin.mk: set")
+       acllist("BUILTIN_FIND_HEADERS.*", BtPathname,
+               "builtin.mk: set")
+       acllist("BUILTIN_FIND_LIBS", BtPathname,
+               "builtin.mk: set")
+       sys("BUILTIN_X11_TYPE", BtUnknown)
+       sys("BUILTIN_X11_VERSION", BtUnknown)
+       pkglist("CATEGORIES", BtCategory)
+       sysload("CC_VERSION", BtMessage)
+       sysload("CC", BtShellCommand)
+       pkglistbl3("CFLAGS", BtCFlag)   // may also be changed by the user
+       pkglistbl3("CFLAGS.*", BtCFlag) // may also be changed by the user
+       acl("CHECK_BUILTIN", BtYesNo,
+               "builtin.mk: default",
+               "Makefile: set")
+       acl("CHECK_BUILTIN.*", BtYesNo,
+               "Makefile, options.mk, buildlink3.mk: set",
+               "builtin.mk: default, use-loadtime",
+               "*: use-loadtime")
+       pkglist("CHECK_FILES_SKIP", BtBasicRegularExpression)
+       pkg("CHECK_FILES_SUPPORTED", BtYesNo)
+       usr("CHECK_HEADERS", BtYesNo)
+       pkglist("CHECK_HEADERS_SKIP", BtPathmask)
+       usr("CHECK_INTERPRETER", BtYesNo)
+       pkglist("CHECK_INTERPRETER_SKIP", BtPathmask)
+       usr("CHECK_PERMS", BtYesNo)
+       pkglist("CHECK_PERMS_SKIP", BtPathmask)
+       usr("CHECK_PORTABILITY", BtYesNo)
+       pkglist("CHECK_PORTABILITY_SKIP", BtPathmask)
+       usr("CHECK_RELRO", BtYesNo)
+       pkglist("CHECK_RELRO_SKIP", BtPathmask)
+       pkg("CHECK_RELRO_SUPPORTED", BtYesNo)
+       pkg("CHECK_SHLIBS", BtYesNo)
+       pkglist("CHECK_SHLIBS_SKIP", BtPathmask)
+       pkg("CHECK_SHLIBS_SUPPORTED", BtYesNo)
+       pkglist("CHECK_WRKREF_SKIP", BtPathmask)
+       pkg("CMAKE_ARG_PATH", BtPathname)
+       pkglist("CMAKE_ARGS", BtShellWord)
+       pkglist("CMAKE_ARGS.*", BtShellWord)
+       pkglist("CMAKE_DEPENDENCIES_REWRITE", BtPathmask) // Relative to WRKSRC
+       pkglist("CMAKE_MODULE_PATH_OVERRIDE", BtPathmask) // Relative to WRKSRC
+       pkg("CMAKE_PKGSRC_BUILD_FLAGS", BtYesNo)
+       pkglist("CMAKE_PREFIX_PATH", BtPathmask)
+       pkg("CMAKE_USE_GNU_INSTALL_DIRS", BtYesNo)
+       pkg("CMAKE_INSTALL_PREFIX", BtPathname) // The default is ${PREFIX}.
+       pkgappend("COMMENT", BtComment)
+       sys("COMPILE.*", BtShellCommand)
+       sys("COMPILER_RPATH_FLAG", enum("-Wl,-rpath"))
+       pkglist("CONFIGURE_ARGS", BtShellWord)
+       pkglist("CONFIGURE_ARGS.*", BtShellWord)
+       pkglist("CONFIGURE_DIRS", BtWrksrcSubdirectory)
+       pkglistbl3("CONFIGURE_ENV", BtShellWord)
+       pkglistbl3("CONFIGURE_ENV.*", BtShellWord)
+       pkg("CONFIGURE_HAS_INFODIR", BtYesNo)
+       pkg("CONFIGURE_HAS_LIBDIR", BtYesNo)
+       pkg("CONFIGURE_HAS_MANDIR", BtYesNo)
+       pkg("CONFIGURE_SCRIPT", BtPathname)
+       pkglist("CONFIG_GUESS_OVERRIDE", BtPathmask)
+       pkglist("CONFIG_STATUS_OVERRIDE", BtPathmask)
+       pkg("CONFIG_SHELL", BtPathname)
+       pkglist("CONFIG_SUB_OVERRIDE", BtPathmask)
+       pkglist("CONFLICTS", BtDependency)
+       pkgappend("CONF_FILES", BtConfFiles)
+       pkg("CONF_FILES_MODE", enum("0644 0640 0600 0400"))
+       pkglist("CONF_FILES_PERMS", BtPerms)
+       sys("COPY", enum("-c")) // The flag that tells ${INSTALL} to copy a file
+       sys("CPP", BtShellCommand)
+       pkglistbl3("CPPFLAGS", BtCFlag)
+       pkglistbl3("CPPFLAGS.*", BtCFlag)
+       sys("CXX", BtShellCommand)
+       pkglistbl3("CXXFLAGS", BtCFlag)
+       pkglistbl3("CXXFLAGS.*", BtCFlag)
+       pkglistbl3("CWRAPPERS_APPEND.*", BtShellWord)
+       syslist("DEFAULT_DISTFILES", BtFetchURL) // From mk/fetch/bsd.fetch-vars.mk.
+       pkglist("DEINSTALL_SRC", BtPathname)
+       pkglist("DEINSTALL_TEMPLATES", BtPathname)
+       sys("DELAYED_ERROR_MSG", BtShellCommand)
+       sys("DELAYED_WARNING_MSG", BtShellCommand)
+       pkglistbl3("DEPENDS", BtDependencyWithPath)
+       usrlist("DEPENDS_TARGET", BtIdentifier)
+       pkglist("DESCR_SRC", BtPathname)
+       sys("DESTDIR", BtPathname)
+       pkg("DESTDIR_VARNAME", BtVariableName)
+       sys("DEVOSSAUDIO", BtPathname)
+       sys("DEVOSSSOUND", BtPathname)
+       pkglist("DISTFILES", BtFileName)
+       pkg("DISTINFO_FILE", BtRelativePkgPath)
+       pkg("DISTNAME", BtFileName)
+       pkg("DIST_SUBDIR", BtPathname)
+       pkglist("DJB_BUILD_ARGS", BtShellWord)
+       pkglist("DJB_BUILD_TARGETS", BtIdentifier)
+       pkgappend("DJB_CONFIG_CMDS", BtShellCommands)
+       pkglist("DJB_CONFIG_DIRS", BtWrksrcSubdirectory)
+       pkg("DJB_CONFIG_HOME", BtFileName)
+       pkg("DJB_CONFIG_PREFIX", BtPathname)
+       pkglist("DJB_INSTALL_TARGETS", BtIdentifier)
+       pkg("DJB_MAKE_TARGETS", BtYesNo)
+       pkg("DJB_RESTRICTED", BtYesNo)
+       pkg("DJB_SLASHPACKAGE", BtYesNo)
+       pkg("DLOPEN_REQUIRE_PTHREADS", BtYesNo)
+       pkg("DL_AUTO_VARS", BtYes)
+       acllist("DL_LIBS", BtLdFlag,
+               "*: append, use")
+       sys("DOCOWN", BtUserGroupName)
+       sys("DOCGRP", BtUserGroupName)
+       sys("DOCMODE", BtFileMode)
+       sys("DOWNLOADED_DISTFILE", BtPathname)
+       sys("DO_NADA", BtShellCommand)
+       pkg("DYNAMIC_SITES_CMD", BtShellCommand)
+       pkg("DYNAMIC_SITES_SCRIPT", BtPathname)
+       sysbl3("ECHO", BtShellCommand)
+       sysbl3("ECHO_MSG", BtShellCommand)
+       sysbl3("ECHO_N", BtShellCommand)
+       pkg("EGDIR", BtPathname) // Not defined anywhere but used in many places like this.
+       sys("EMACS_BIN", BtPathname)
+       sys("EMACS_ETCPREFIX", BtPathname)
+       sys("EMACS_FLAVOR", enum("emacs xemacs"))
+       sys("EMACS_INFOPREFIX", BtPathname)
+       sys("EMACS_LISPPREFIX", BtPathname)
+       pkglistbl3("EMACS_MODULES", BtIdentifier)
+       sys("EMACS_PKGNAME_PREFIX", BtIdentifier) // Or the empty string.
+       sys("EMACS_TYPE", enum("emacs xemacs"))
+       pkglist("EMACS_VERSIONS_ACCEPTED", emacsVersions)
+       sys("EMACS_VERSION_MAJOR", BtInteger)
+       sys("EMACS_VERSION_MINOR", BtInteger)
+       pkglist("EMACS_VERSION_REQD", emacsVersions)
+       sys("EMULDIR", BtPathname)
+       sys("EMULSUBDIR", BtPathname)
+       sys("OPSYS_EMULDIR", BtPathname)
+       sys("EMULSUBDIRSLASH", BtPathname)
+       sys("EMUL_ARCH", enum("arm i386 m68k none ns32k sparc vax x86_64"))
+       sys("EMUL_DISTRO", BtIdentifier)
+       sys("EMUL_IS_NATIVE", BtYes)
+       pkglist("EMUL_MODULES.*", BtIdentifier)
+       sys("EMUL_OPSYS", enum("darwin freebsd hpux irix linux osf1 solaris sunos none"))
+       pkg("EMUL_PKG_FMT", enum("plain rpm"))
+       usr("EMUL_PLATFORM", BtEmulPlatform)
+       pkglist("EMUL_PLATFORMS", BtEmulPlatform)
+       usrlist("EMUL_PREFER", BtEmulPlatform)
+       pkglist("EMUL_REQD", BtDependency)
+       usr("EMUL_TYPE.*", enum("native builtin suse suse-10.0 suse-12.1 suse-13.1"))
+       sys("ERROR_CAT", BtShellCommand)
+       sys("ERROR_MSG", BtShellCommand)
+       syslist("EXPORT_SYMBOLS_LDFLAGS", BtLdFlag)
+       sys("EXTRACT_CMD", BtShellCommand)
+       pkg("EXTRACT_DIR", BtPathname)
+       pkg("EXTRACT_DIR.*", BtPathname)
+       pkglist("EXTRACT_ELEMENTS", BtPathmask)
+       pkglist("EXTRACT_ENV", BtShellWord)
+       pkglist("EXTRACT_ONLY", BtPathname)
+       pkglist("EXTRACT_OPTS", BtShellWord)
+       pkglist("EXTRACT_OPTS_BIN", BtShellWord)
+       pkglist("EXTRACT_OPTS_LHA", BtShellWord)
+       pkglist("EXTRACT_OPTS_PAX", BtShellWord)
+       pkglist("EXTRACT_OPTS_RAR", BtShellWord)
+       pkglist("EXTRACT_OPTS_TAR", BtShellWord)
+       pkglist("EXTRACT_OPTS_ZIP", BtShellWord)
+       pkglist("EXTRACT_OPTS_ZOO", BtShellWord)
+       pkg("EXTRACT_SUFX", BtDistSuffix)
+       pkg("EXTRACT_USING", enum("bsdtar gtar nbtar pax"))
+       sys("FAIL_MSG", BtShellCommand)
+       sys("FAMBASE", BtPathname)
+       pkglist("FAM_ACCEPTED", enum("fam gamin"))
+       usr("FAM_DEFAULT", enum("fam gamin"))
+       sys("FAM_TYPE", enum("fam gamin"))
+       pkglist("FETCH_BEFORE_ARGS", BtShellWord)
+       pkglist("FETCH_MESSAGE", BtShellWord)
+       pkg("FILESDIR", BtRelativePkgPath)
+       pkglist("FILES_SUBST", BtShellWord)
+       syslist("FILES_SUBST_SED", BtShellWord)
+       pkglist("FIX_RPATH", BtVariableName)
+       pkglist("FLEX_REQD", BtVersion)
+       pkglist("FONTS_DIRS.*", BtPathname)
+       sys("GAMEDATAMODE", BtFileMode)
+       sys("GAMES_GROUP", BtUserGroupName)
+       syslist("GAMEDATA_PERMS", BtPerms)
+       syslist("GAMEDIR_PERMS", BtPerms)
+       sys("GAMEMODE", BtFileMode)
+       sys("GAMES_USER", BtUserGroupName)
+       pkglistbl3("GCC_REQD", BtGccReqd)
+       pkgappend("GENERATE_PLIST", BtShellCommands)
+       pkg("GITHUB_PROJECT", BtIdentifier)
+       pkg("GITHUB_TAG", BtIdentifier)
+       pkg("GITHUB_RELEASE", BtFileName)
+       pkg("GITHUB_TYPE", enum("tag release"))
+       pkg("GMAKE_REQD", BtVersion)
+       // Some packages need to set GNU_ARCH.i386 to either i486 or i586.
+       pkg("GNU_ARCH.*", BtIdentifier)
+       // GNU_CONFIGURE needs to be tested in some buildlink3.mk files,
+       // such as lang/vala.
+       acl("GNU_CONFIGURE", BtYes,
+               "buildlink3.mk: none",
+               "builtin.mk: use, use-loadtime",
+               "Makefile, Makefile.*, *.mk: default, set, use, use-loadtime")
+       pkg("GNU_CONFIGURE_INFODIR", BtPathname)
+       pkg("GNU_CONFIGURE_LIBDIR", BtPathname)
+       pkg("GNU_CONFIGURE_LIBSUBDIR", BtPathname)
+       pkg("GNU_CONFIGURE_MANDIR", BtPathname)
+       pkg("GNU_CONFIGURE_PREFIX", BtPathname)
+       pkg("GOPATH", BtPathname)
+       pkgload("HAS_CONFIGURE", BtYes)
+       pkglist("HEADER_TEMPLATES", BtPathname)
+       pkg("HOMEPAGE", BtHomepage)
+       pkg("ICON_THEMES", BtYes)
+       acl("IGNORE_PKG.*", BtYes,
+               "*: set, use-loadtime")
+       sys("IMAKE", BtShellCommand)
+       pkglistbl3("INCOMPAT_CURSES", BtMachinePlatformPattern)
+       sys("INFO_DIR", BtPathname) // relative to PREFIX
+       pkg("INFO_FILES", BtYes)
+       sys("INSTALL", BtShellCommand)
+       pkglist("INSTALLATION_DIRS", BtPrefixPathname)
+       pkg("INSTALLATION_DIRS_FROM_PLIST", BtYes)
+       sys("INSTALL_DATA", BtShellCommand)
+       sys("INSTALL_DATA_DIR", BtShellCommand)
+       pkglist("INSTALL_DIRS", BtWrksrcSubdirectory)
+       pkglist("INSTALL_ENV", BtShellWord)
+       pkg("INSTALL_FILE", BtPathname)
+       sys("INSTALL_GAME", BtShellCommand)
+       sys("INSTALL_GAME_DATA", BtShellCommand)
+       sys("INSTALL_LIB", BtShellCommand)
+       sys("INSTALL_LIB_DIR", BtShellCommand)
+       pkglist("INSTALL_MAKE_FLAGS", BtShellWord)
+       sys("INSTALL_MAN", BtShellCommand)
+       sys("INSTALL_MAN_DIR", BtShellCommand)
+       sys("INSTALL_PROGRAM", BtShellCommand)
+       sys("INSTALL_PROGRAM_DIR", BtShellCommand)
+       sys("INSTALL_SCRIPT", BtShellCommand)
+       syslist("INSTALL_SCRIPTS_ENV", BtShellWord)
+       sys("INSTALL_SCRIPT_DIR", BtShellCommand)
+       pkglist("INSTALL_SRC", BtPathname)
+       pkglist("INSTALL_TARGET", BtIdentifier)
+       pkglist("INSTALL_TEMPLATES", BtPathname)
+       pkgload("INSTALL_UNSTRIPPED", BtYesNo)
+       pkglist("INTERACTIVE_STAGE", enum("fetch extract configure build test install"))
+       acl("IS_BUILTIN.*", BtYesNoIndirectly,
+               // These two differ from the standard,
+               // they are needed for devel/ncursesw.
+               "buildlink3.mk: use, use-loadtime",
+               // The "set" differs from the standard sys.
+               "builtin.mk: set, use, use-loadtime",
+               "Makefile, Makefile.*, *.mk: default, set, use, use-loadtime")
+       sys("JAVA_BINPREFIX", BtPathname)
+       pkg("JAVA_CLASSPATH", BtShellWord)
+       pkg("JAVA_HOME", BtPathname)
+       pkg("JAVA_NAME", BtFileName)
+       pkglist("JAVA_UNLIMIT", enum("cmdsize datasize stacksize"))
+       pkglist("JAVA_WRAPPERS", BtFileName)
+       pkg("JAVA_WRAPPER_BIN.*", BtPathname)
+       sys("KRB5BASE", BtPathname)
+       pkglist("KRB5_ACCEPTED", enum("heimdal mit-krb5"))
+       usr("KRB5_DEFAULT", enum("heimdal mit-krb5"))
+       sys("KRB5_TYPE", BtIdentifier)
+       sys("LD", BtShellCommand)
+       pkglistbl3("LDFLAGS", BtLdFlag)       // May also be changed by the user.
+       pkglistbl3("LDFLAGS.*", BtLdFlag)     // May also be changed by the user.
+       sysload("LIBABISUFFIX", BtIdentifier) // Can also be empty.
+       sys("LIBGRP", BtUserGroupName)
+       sys("LIBMODE", BtFileMode)
+       sys("LIBOWN", BtUserGroupName)
+       sys("LIBOSSAUDIO", BtPathname)
+       pkglist("LIBS", BtLdFlag)
+       pkglist("LIBS.*", BtLdFlag)
+       sys("LIBTOOL", BtShellCommand)
+       pkglist("LIBTOOL_OVERRIDE", BtPathmask)
+       pkglist("LIBTOOL_REQD", BtVersion)
+       pkgappend("LICENCE", BtLicense)
+       pkgappend("LICENSE", BtLicense)
+       pkg("LICENSE_FILE", BtPathname)
+       sys("LINK.*", BtShellCommand)
+       sys("LINKER_RPATH_FLAG", BtShellWord)
+       syslist("LITTLEENDIANPLATFORMS", BtMachinePlatformPattern)
+       sys("LOWER_OPSYS", BtIdentifier)
+       sys("LOWER_VENDOR", BtIdentifier)
+       syslist("LP64PLATFORMS", BtMachinePlatformPattern)
+       pkglist("LTCONFIG_OVERRIDE", BtPathmask)
+       sysload("MACHINE_ARCH", enumMachineArch)
+       sysload("MACHINE_GNU_ARCH", enumMachineGnuArch)
+       sysload("MACHINE_GNU_PLATFORM", BtMachineGnuPlatform)
+       sysload("MACHINE_PLATFORM", BtMachinePlatform)
+       pkg("MAINTAINER", BtMailAddress)
+       sysload("MAKE", BtShellCommand)
+       pkglist("MAKEFLAGS", BtShellWord)
+       pkglistbl3("MAKEVARS", BtVariableName)
+       pkglist("MAKE_DIRS", BtPathname)
+       pkglist("MAKE_DIRS_PERMS", BtPerms)
+       pkglistbl3("MAKE_ENV", BtShellWord)
+       pkglistbl3("MAKE_ENV.*", BtShellWord)
+       pkg("MAKE_FILE", BtPathname)
+       pkglist("MAKE_FLAGS", BtShellWord)
+       pkglist("MAKE_FLAGS.*", BtShellWord)
+       usr("MAKE_JOBS", BtInteger)
+       pkg("MAKE_JOBS_SAFE", BtYesNo)
+       pkg("MAKE_PROGRAM", BtShellCommand)
+       pkg("MANCOMPRESSED", BtYesNo)
+       pkg("MANCOMPRESSED_IF_MANZ", BtYes)
+       sys("MANGRP", BtUserGroupName)
+       sys("MANMODE", BtFileMode)
+       sys("MANOWN", BtUserGroupName)
+       pkglist("MASTER_SITES", BtFetchURL)
+       // TODO: Extract the MASTER_SITE_* definitions from mk/fetch/sites.mk
+       //  instead of listing them here.
+       syslist("MASTER_SITE_APACHE", BtFetchURL)
+       syslist("MASTER_SITE_BACKUP", BtFetchURL)
+       syslist("MASTER_SITE_CRATESIO", BtFetchURL)
+       syslist("MASTER_SITE_CYGWIN", BtFetchURL)
+       syslist("MASTER_SITE_DEBIAN", BtFetchURL)
+       syslist("MASTER_SITE_FREEBSD", BtFetchURL)
+       syslist("MASTER_SITE_FREEBSD_LOCAL", BtFetchURL)
+       syslist("MASTER_SITE_GENTOO", BtFetchURL)
+       syslist("MASTER_SITE_GITHUB", BtFetchURL)
+       syslist("MASTER_SITE_GNOME", BtFetchURL)
+       syslist("MASTER_SITE_GNU", BtFetchURL)
+       syslist("MASTER_SITE_GNUSTEP", BtFetchURL)
+       syslist("MASTER_SITE_IFARCHIVE", BtFetchURL)
+       syslist("MASTER_SITE_HASKELL_HACKAGE", BtFetchURL)
+       syslist("MASTER_SITE_KDE", BtFetchURL)
+       syslist("MASTER_SITE_LOCAL", BtFetchURL)
+       syslist("MASTER_SITE_MOZILLA", BtFetchURL)
+       syslist("MASTER_SITE_MOZILLA_ALL", BtFetchURL)
+       syslist("MASTER_SITE_MOZILLA_ESR", BtFetchURL)
+       syslist("MASTER_SITE_MYSQL", BtFetchURL)
+       syslist("MASTER_SITE_NETLIB", BtFetchURL)
+       syslist("MASTER_SITE_OPENBSD", BtFetchURL)
+       syslist("MASTER_SITE_OPENOFFICE", BtFetchURL)
+       syslist("MASTER_SITE_OSDN", BtFetchURL)
+       syslist("MASTER_SITE_PERL_CPAN", BtFetchURL)
+       syslist("MASTER_SITE_PGSQL", BtFetchURL)
+       syslist("MASTER_SITE_PYPI", BtFetchURL)
+       syslist("MASTER_SITE_R_CRAN", BtFetchURL)
+       syslist("MASTER_SITE_RUBYGEMS", BtFetchURL)
+       syslist("MASTER_SITE_SOURCEFORGE", BtFetchURL)
+       syslist("MASTER_SITE_SUNSITE", BtFetchURL)
+       syslist("MASTER_SITE_SUSE", BtFetchURL)
+       syslist("MASTER_SITE_TEX_CTAN", BtFetchURL)
+       syslist("MASTER_SITE_XCONTRIB", BtFetchURL)
+       syslist("MASTER_SITE_XEMACS", BtFetchURL)
+       syslist("MASTER_SITE_XORG", BtFetchURL)
+       pkglist("MESSAGE_SRC", BtPathname)
+       pkglist("MESSAGE_SUBST", BtShellWord)
+       pkg("META_PACKAGE", BtYes)
+       syslist("MISSING_FEATURES", BtIdentifier)
+       pkglist("MYSQL_VERSIONS_ACCEPTED", mysqlVersions)
+       usr("MYSQL_VERSION_DEFAULT", BtVersion)
+       sys("NATIVE_CC", BtShellCommand) // See mk/platform/tools.NetBSD.mk (and some others).
+       sys("NM", BtShellCommand)
+       sys("NONBINMODE", BtFileMode)
+       pkglist("NOT_FOR_COMPILER", compilers)
+       pkglist("NOT_FOR_BULK_PLATFORM", BtMachinePlatformPattern)
+       pkglist("NOT_FOR_PLATFORM", BtMachinePlatformPattern)
+       pkg("NOT_FOR_UNPRIVILEGED", BtYesNo)
+       pkglist("NOT_PAX_ASLR_SAFE", BtPathmask)
+       pkglist("NOT_PAX_MPROTECT_SAFE", BtPathmask)
+       pkg("NO_BIN_ON_CDROM", BtRestricted)
+       pkg("NO_BIN_ON_FTP", BtRestricted)
+       pkgload("NO_BUILD", BtYes)
+       pkg("NO_CHECKSUM", BtYes)
+       pkg("NO_CONFIGURE", BtYes)
+       pkg("NO_EXPORT_CPP", BtYes)
+       pkg("NO_EXTRACT", BtYes)
+       pkg("NO_INSTALL_MANPAGES", BtYes) // only has an effect for Imake packages.
+       pkg("NO_PKGTOOLS_REQD_CHECK", BtYes)
+       pkg("NO_SRC_ON_CDROM", BtRestricted)
+       pkg("NO_SRC_ON_FTP", BtRestricted)
+       sysload("OBJECT_FMT", enum("COFF ECOFF ELF SOM XCOFF Mach-O PE a.out"))
+       pkglist("ONLY_FOR_COMPILER", compilers)
+       pkglist("ONLY_FOR_PLATFORM", BtMachinePlatformPattern)
+       pkg("ONLY_FOR_UNPRIVILEGED", BtYesNo)
+       sysload("OPSYS", BtIdentifier)
+       pkglistbl3("OPSYSVARS", BtVariableName)
+       pkg("OSVERSION_SPECIFIC", BtYes)
+       sysload("OS_VERSION", BtVersion)
+       sysload("OSX_VERSION", BtVersion) // See mk/platform/Darwin.mk.
+       pkg("OVERRIDE_DIRDEPTH*", BtInteger)
+       pkg("OVERRIDE_GNU_CONFIG_SCRIPTS", BtYes)
+       pkg("OWNER", BtMailAddress)
+       pkglist("OWN_DIRS", BtPathname)
+       pkglist("OWN_DIRS_PERMS", BtPerms)
+       sys("PAMBASE", BtPathname)
+       usr("PAM_DEFAULT", enum("linux-pam openpam solaris-pam"))
+       pkg("PATCHDIR", BtRelativePkgPath)
+       pkglist("PATCHFILES", BtFileName)
+       pkglist("PATCH_ARGS", BtShellWord)
+       pkglist("PATCH_DIST_ARGS", BtShellWord)
+       pkg("PATCH_DIST_CAT", BtShellCommand)
+       pkg("PATCH_DIST_STRIP*", BtShellWord)
+       pkglist("PATCH_SITES", BtFetchURL)
+       pkg("PATCH_STRIP", BtShellWord)
+       sys("PATH", BtPathlist)       // From the PATH environment variable.
+       sys("PAXCTL", BtShellCommand) // See mk/pax.mk.
+       pkglist("PERL5_PACKLIST", BtPerl5Packlist)
+       pkg("PERL5_PACKLIST_DIR", BtPathname)
+       pkglist("PERL5_REQD", BtVersion)
+       sysbl3("PERL5_INSTALLARCHLIB", BtPathname) // See lang/perl5/vars.mk
+       sysbl3("PERL5_INSTALLSCRIPT", BtPathname)
+       sysbl3("PERL5_INSTALLVENDORBIN", BtPathname)
+       sysbl3("PERL5_INSTALLVENDORSCRIPT", BtPathname)
+       sysbl3("PERL5_INSTALLVENDORARCH", BtPathname)
+       sysbl3("PERL5_INSTALLVENDORLIB", BtPathname)
+       sysbl3("PERL5_INSTALLVENDORMAN1DIR", BtPathname)
+       sysbl3("PERL5_INSTALLVENDORMAN3DIR", BtPathname)
+       sysbl3("PERL5_SUB_INSTALLARCHLIB", BtPrefixPathname) // See lang/perl5/vars.mk
+       sysbl3("PERL5_SUB_INSTALLSCRIPT", BtPrefixPathname)
+       sysbl3("PERL5_SUB_INSTALLVENDORBIN", BtPrefixPathname)
+       sysbl3("PERL5_SUB_INSTALLVENDORSCRIPT", BtPrefixPathname)
+       sysbl3("PERL5_SUB_INSTALLVENDORARCH", BtPrefixPathname)
+       sysbl3("PERL5_SUB_INSTALLVENDORLIB", BtPrefixPathname)
+       sysbl3("PERL5_SUB_INSTALLVENDORMAN1DIR", BtPrefixPathname)
+       sysbl3("PERL5_SUB_INSTALLVENDORMAN3DIR", BtPrefixPathname)
+       pkg("PERL5_USE_PACKLIST", BtYesNo)
+       sys("PGSQL_PREFIX", BtPathname)
+       acllist("PGSQL_VERSIONS_ACCEPTED", pgsqlVersions,
+               // The "set" is necessary for databases/postgresql-postgis2.
+               "Makefile, Makefile.*, *.mk: default, set, append, use")
+       usr("PGSQL_VERSION_DEFAULT", BtVersion)
+       sys("PG_LIB_EXT", enum("dylib so"))
+       sys("PGSQL_TYPE",
+               enumFrom("mk/pgsql.buildlink3.mk", "postgresql11-client", "PGSQL_TYPE"))
+       sys("PGPKGSRCDIR", BtPathname)
+       sys("PHASE_MSG", BtShellCommand)
+       usr("PHP_VERSION_REQD", BtVersion)
+       acl("PHP_PKG_PREFIX",
+               enumFromDirs("lang", `^php(\d+)$`, "php$1", "php56 php71 php72 php73"),
+               "special:phpversion.mk: set",
+               "*: use, use-loadtime")
+       sys("PKGBASE", BtIdentifier)
+       // Despite its name, this is actually a list of filenames.
+       acllist("PKGCONFIG_FILE.*", BtPathname,
+               "builtin.mk: set, append",
+               "special:pkgconfig-builtin.mk: use-loadtime")
+       pkglist("PKGCONFIG_OVERRIDE", BtPathmask)
+       pkg("PKGCONFIG_OVERRIDE_STAGE", BtStage)
+       pkg("PKGDIR", BtRelativePkgDir)
+       sys("PKGDIRMODE", BtFileMode)
+       sys("PKGLOCALEDIR", BtPathname)
+       pkg("PKGNAME", BtPkgName)
+       sys("PKGNAME_NOREV", BtPkgName)
+       sysload("PKGPATH", BtPathname)
+       sys("PKGREPOSITORY", BtUnknown)
+       // This variable is special in that it really only makes sense to
+       // be set in a package Makefile.
+       // See VartypeCheck.PkgRevision for details.
+       acl("PKGREVISION", BtPkgRevision,
+               "Makefile: set")
+       sys("PKGSRCDIR", BtPathname)
+       // This definition is only valid in the top-level Makefile,
+       // not in category or package Makefiles.
+       acl("PKGSRCTOP", BtYes,
+               "Makefile: set")
+       sys("PKGSRC_SETENV", BtShellCommand)
+       syslist("PKGTOOLS_ENV", BtShellWord)
+       sys("PKGVERSION", BtVersion)
+       sys("PKGVERSION_NOREV", BtVersion) // Without the nb* part.
+       sys("PKGWILDCARD", BtFileMask)
+       sys("PKG_ADMIN", BtShellCommand)
+       sys("PKG_APACHE", enum("apache24"))
+       pkglist("PKG_APACHE_ACCEPTED", enum("apache24"))
+       usr("PKG_APACHE_DEFAULT", enum("apache24"))
+       sysloadlist("PKG_BUILD_OPTIONS.*", BtOption)
+       usr("PKG_CONFIG", BtYes)
        // ^^ No, this is not the popular command from GNOME, but the setting
        // whether the pkgsrc user wants configuration files automatically
        // installed or not.
-       sys("PKG_CREATE", lkNone, BtShellCommand)
-       sys("PKG_DBDIR", lkNone, BtPathname)
-       cmdline("PKG_DEBUG_LEVEL", lkNone, BtInteger)
-       usr("PKG_DEFAULT_OPTIONS", lkShell, BtOption)
-       sys("PKG_DELETE", lkNone, BtShellCommand)
-       acl("PKG_DESTDIR_SUPPORT", lkShell, enum("destdir user-destdir"), "Makefile, Makefile.common: set")
-       pkglist("PKG_FAIL_REASON", lkShell, BtShellWord)
-       sysload("PKG_FORMAT", lkNone, BtIdentifier)
-       acl("PKG_GECOS.*", lkNone, BtMessage, "Makefile: set")
-       acl("PKG_GID.*", lkNone, BtInteger, "Makefile: set")
-       acl("PKG_GROUPS", lkShell, BtShellWord, "Makefile: set, append")
-       pkglist("PKG_GROUPS_VARS", lkShell, BtVariableName)
-       acl("PKG_HOME.*", lkNone, BtPathname, "Makefile: set")
-       acl("PKG_HACKS", lkShell, BtIdentifier, "hacks.mk: append")
-       sys("PKG_INFO", lkNone, BtShellCommand)
-       sys("PKG_JAVA_HOME", lkNone, BtPathname)
-       sys("PKG_JVM", lkNone, jvms)
-       acl("PKG_JVMS_ACCEPTED", lkShell, jvms, "Makefile: set; Makefile.common: default, set")
-       usr("PKG_JVM_DEFAULT", lkNone, jvms)
-       acl("PKG_LEGACY_OPTIONS", lkShell, BtOption, "")
-       acl("PKG_LIBTOOL", lkNone, BtPathname, "Makefile: set")
-       acl("PKG_OPTIONS", lkShell, BtOption, "bsd.options.mk: set; *: use-loadtime, use")
-       usr("PKG_OPTIONS.*", lkShell, BtOption)
-       acl("PKG_OPTIONS_DEPRECATED_WARNINGS", lkShell, BtShellWord, "")
-       acl("PKG_OPTIONS_GROUP.*", lkShell, BtOption, "Makefile, options.mk: set, append")
-       acl("PKG_OPTIONS_LEGACY_OPTS", lkShell, BtUnknown, "Makefile, Makefile.common, options.mk: append")
-       acl("PKG_OPTIONS_LEGACY_VARS", lkShell, BtUnknown, "Makefile, Makefile.common, options.mk: append")
-       acl("PKG_OPTIONS_NONEMPTY_SETS", lkShell, BtIdentifier, "")
-       acl("PKG_OPTIONS_OPTIONAL_GROUPS", lkShell, BtIdentifier, "options.mk: set, append")
-       acl("PKG_OPTIONS_REQUIRED_GROUPS", lkShell, BtIdentifier, "Makefile, options.mk: set")
-       acl("PKG_OPTIONS_SET.*", lkShell, BtOption, "")
-       acl("PKG_OPTIONS_VAR", lkNone, BtPkgOptionsVar, "Makefile, Makefile.common, options.mk: set; bsd.options.mk: use-loadtime")
-       acl("PKG_PRESERVE", lkNone, BtYes, "Makefile: set")
-       acl("PKG_SHELL", lkNone, BtPathname, "Makefile, Makefile.common: set")
-       acl("PKG_SHELL.*", lkNone, BtPathname, "Makefile, Makefile.common: set")
-       acl("PKG_SHLIBTOOL", lkNone, BtPathname, "")
-       pkglist("PKG_SKIP_REASON", lkShell, BtShellWord)
-       acl("PKG_SUGGESTED_OPTIONS", lkShell, BtOption, "Makefile, Makefile.common, options.mk: set, append")
-       acl("PKG_SUGGESTED_OPTIONS.*", lkShell, BtOption, "Makefile, Makefile.common, options.mk: set, append")
-       acl("PKG_SUPPORTED_OPTIONS", lkShell, BtOption, "Makefile: set, append; Makefile.common: set; options.mk: set, append, use")
-       acl("PKG_SYSCONFDIR*", lkNone, BtPathname, ""+
-               "Makefile: set, use, use-loadtime; "+
-               "buildlink3.mk, builtin.mk: use-loadtime; "+
+       sys("PKG_CREATE", BtShellCommand)
+       sys("PKG_DBDIR", BtPathname)
+       cmdline("PKG_DEBUG_LEVEL", BtInteger)
+       usrlist("PKG_DEFAULT_OPTIONS", BtOption)
+       sys("PKG_DELETE", BtShellCommand)
+       pkglist("PKG_DESTDIR_SUPPORT", enum("destdir user-destdir"))
+       pkglist("PKG_FAIL_REASON", BtShellWord)
+       sysload("PKG_FORMAT", BtIdentifier)
+       pkg("PKG_GECOS.*", BtMessage)
+       pkg("PKG_GID.*", BtInteger)
+       pkglist("PKG_GROUPS", BtShellWord)
+       pkglist("PKG_GROUPS_VARS", BtVariableName)
+       pkg("PKG_HOME.*", BtPathname)
+       // PKG_HACKS is used to record the applied hacks in the binary package.
+       // Since only the hacks.mk can define hacks, appending to it only makes
+       // sense there.
+       //
+       // TODO: Is it possible to include hacks.mk files from the dependencies?
+       acllist("PKG_HACKS", BtIdentifier,
+               "hacks.mk: append")
+       sys("PKG_INFO", BtShellCommand)
+       sys("PKG_JAVA_HOME", BtPathname)
+       sys("PKG_JVM", jvms)
+       pkglist("PKG_JVMS_ACCEPTED", jvms)
+       usr("PKG_JVM_DEFAULT", jvms)
+       pkg("PKG_LIBTOOL", BtPathname)
+
+       // begin PKG_OPTIONS section
+       //
+       // TODO: force the pkgsrc packages to only define options in the
+       //  options.mk file. Most packages already do this, but some still
+       //  define them in the Makefile or Makefile.common.
+       sysloadlist("PKG_OPTIONS", BtOption)
+       usrlist("PKG_OPTIONS.*", BtOption)
+       opt := pkg
+       optlist := pkglist
+       optlist("PKG_LEGACY_OPTIONS", BtOption)
+       optlist("PKG_OPTIONS_DEPRECATED_WARNINGS", BtShellWord)
+       optlist("PKG_OPTIONS_GROUP.*", BtOption)
+       optlist("PKG_OPTIONS_LEGACY_OPTS", BtUnknown)
+       optlist("PKG_OPTIONS_LEGACY_VARS", BtUnknown)
+       optlist("PKG_OPTIONS_NONEMPTY_SETS", BtIdentifier)
+       optlist("PKG_OPTIONS_OPTIONAL_GROUPS", BtIdentifier)
+       optlist("PKG_OPTIONS_REQUIRED_GROUPS", BtIdentifier)
+       optlist("PKG_OPTIONS_SET.*", BtOption)
+       opt("PKG_OPTIONS_VAR", BtPkgOptionsVar)
+       pkglist("PKG_SKIP_REASON", BtShellWord)
+       optlist("PKG_SUGGESTED_OPTIONS", BtOption)
+       optlist("PKG_SUGGESTED_OPTIONS.*", BtOption)
+       optlist("PKG_SUPPORTED_OPTIONS", BtOption)
+       // end PKG_OPTIONS section
+
+       pkg("PKG_PRESERVE", BtYes)
+       pkg("PKG_SHELL", BtPathname)
+       pkg("PKG_SHELL.*", BtPathname)
+       sys("PKG_SHLIBTOOL", BtPathname)
+       // The special exception for buildlink3.mk is only here because
+       // of textproc/xmlcatmgr.
+       acl("PKG_SYSCONFDIR*", BtPathname,
+               "Makefile: set, use, use-loadtime",
+               "buildlink3.mk, builtin.mk: use-loadtime",
                "Makefile.*, *.mk: default, set, use, use-loadtime")
-       pkglist("PKG_SYSCONFDIR_PERMS", lkShell, BtPerms)
-       sys("PKG_SYSCONFBASEDIR", lkNone, BtPathname)
-       pkg("PKG_SYSCONFSUBDIR", lkNone, BtPathname)
-       acl("PKG_SYSCONFVAR", lkNone, BtIdentifier, "")
-       acl("PKG_UID", lkNone, BtInteger, "Makefile: set")
-       acl("PKG_USERS", lkShell, BtShellWord, "Makefile: set, append")
-       pkglist("PKG_USERS_VARS", lkShell, BtVariableName)
-       acl("PKG_USE_KERBEROS", lkNone, BtYes, "Makefile, Makefile.common: set")
-       pkgload("PLIST.*", lkNone, BtYes)
-       pkglist("PLIST_VARS", lkShell, BtIdentifier)
-       pkglist("PLIST_SRC", lkShell, BtRelativePkgPath)
-       pkglist("PLIST_SUBST", lkShell, BtShellWord)
-       acl("PLIST_TYPE", lkNone, enum("dynamic static"), "")
-       acl("PREPEND_PATH", lkShell, BtPathname, "")
-       acl("PREFIX", lkNone, BtPathname, "*: use")
-       acl("PREV_PKGPATH", lkNone, BtPathname, "*: use") // doesn't exist any longer
-       acl("PRINT_PLIST_AWK", lkNone, BtAwkCommand, "*: append")
-       acl("PRIVILEGED_STAGES", lkShell, enum("install package clean"), "")
-       acl("PTHREAD_AUTO_VARS", lkNone, BtYesNo, "Makefile: set")
-       sys("PTHREAD_CFLAGS", lkShell, BtCFlag)
-       sys("PTHREAD_LDFLAGS", lkShell, BtLdFlag)
-       sys("PTHREAD_LIBS", lkShell, BtLdFlag)
-       acl("PTHREAD_OPTS", lkShell, enum("native optional require"), "Makefile: set, append; Makefile.common, buildlink3.mk: append")
-       sysload("PTHREAD_TYPE", lkNone, BtIdentifier) // Or "native" or "none".
-       pkg("PY_PATCHPLIST", lkNone, BtYes)
-       acl("PYPKGPREFIX", lkNone, enumFromDirs("lang", `^python(\d+)$`, "py$1", "py27 py36"), ""+
-               "special:pyversion.mk: set; "+
-               "*: use-loadtime, use")
-       pkg("PYTHON_FOR_BUILD_ONLY", lkNone, enum("yes no test tool YES")) // See lang/python/pyversion.mk
-       pkglist("REPLACE_PYTHON", lkShell, BtPathmask)
-       pkglist("PYTHON_VERSIONS_ACCEPTED", lkShell, BtVersion)
-       pkglist("PYTHON_VERSIONS_INCOMPATIBLE", lkShell, BtVersion)
-       usr("PYTHON_VERSION_DEFAULT", lkNone, BtVersion)
-       usr("PYTHON_VERSION_REQD", lkNone, BtVersion)
-       pkglist("PYTHON_VERSIONED_DEPENDENCIES", lkShell, BtPythonDependency)
-       sys("RANLIB", lkNone, BtShellCommand)
-       pkglist("RCD_SCRIPTS", lkShell, BtFileName)
-       acl("RCD_SCRIPT_SRC.*", lkNone, BtPathname, "Makefile: set")
-       acl("RCD_SCRIPT_WRK.*", lkNone, BtPathname, "Makefile: set")
-       usr("REAL_ROOT_USER", lkNone, BtUserGroupName)
-       usr("REAL_ROOT_GROUP", lkNone, BtUserGroupName)
-       acl("REPLACE.*", lkNone, BtUnknown, "Makefile: set")
-       pkglist("REPLACE_AWK", lkShell, BtPathmask)
-       pkglist("REPLACE_BASH", lkShell, BtPathmask)
-       pkglist("REPLACE_CSH", lkShell, BtPathmask)
-       acl("REPLACE_EMACS", lkShell, BtPathmask, "")
-       acl("REPLACE_FILES.*", lkShell, BtPathmask, "Makefile, Makefile.common: set, append")
-       acl("REPLACE_INTERPRETER", lkShell, BtIdentifier, "Makefile, Makefile.common: append")
-       pkglist("REPLACE_KSH", lkShell, BtPathmask)
-       pkglist("REPLACE_LOCALEDIR_PATTERNS", lkShell, BtFileMask)
-       pkglist("REPLACE_LUA", lkShell, BtPathmask)
-       pkglist("REPLACE_PERL", lkShell, BtPathmask)
-       pkglist("REPLACE_PYTHON", lkShell, BtPathmask)
-       pkglist("REPLACE_SH", lkShell, BtPathmask)
-       pkglist("REQD_DIRS", lkShell, BtPathname)
-       pkglist("REQD_DIRS_PERMS", lkShell, BtPerms)
-       pkglist("REQD_FILES", lkShell, BtPathname)
-       pkg("REQD_FILES_MODE", lkNone, enum("0644 0640 0600 0400"))
-       pkglist("REQD_FILES_PERMS", lkShell, BtPerms)
-       pkg("RESTRICTED", lkNone, BtMessage)
-       usr("ROOT_USER", lkNone, BtUserGroupName)
-       usr("ROOT_GROUP", lkNone, BtUserGroupName)
-       pkglist("RPMIGNOREPATH", lkShell, BtPathmask)
-       acl("RUBY_BASE", lkNone, enumFromDirs("lang", `^ruby(\d+)$`, "ruby$1", "ruby22 ruby23 ruby24 ruby25"), ""+
-               "special:rubyversion.mk: set; "+
-               "*: use-loadtime, use")
-       usr("RUBY_VERSION_REQD", lkNone, BtVersion)
-       acl("RUBY_PKGPREFIX", lkNone, enumFromDirs("lang", `^ruby(\d+)$`, "ruby$1", "ruby22 ruby23 ruby24 ruby25"), ""+
-               "special:rubyversion.mk: set, default, use; "+
-               "*: use-loadtime, use")
-       sys("RUN", lkNone, BtShellCommand)
-       sys("RUN_LDCONFIG", lkNone, BtYesNo)
-       acl("SCRIPTS_ENV", lkShell, BtShellWord, "Makefile, Makefile.common: append")
-       usr("SETGID_GAMES_PERMS", lkShell, BtPerms)
-       usr("SETUID_ROOT_PERMS", lkShell, BtPerms)
-       pkg("SET_LIBDIR", lkNone, BtYes)
-       sys("SHAREGRP", lkNone, BtUserGroupName)
-       sys("SHAREMODE", lkNone, BtFileMode)
-       sys("SHAREOWN", lkNone, BtUserGroupName)
-       sys("SHCOMMENT", lkNone, BtShellCommand)
-       acl("SHLIB_HANDLING", lkNone, enum("YES NO no"), "")
-       acl("SHLIBTOOL", lkNone, BtShellCommand, "Makefile: use")
-       acl("SHLIBTOOL_OVERRIDE", lkShell, BtPathmask, "Makefile: set, append; Makefile.common: append")
-       sysload("SHLIB_TYPE", lkNone, enum("COFF ECOFF ELF SOM XCOFF Mach-O PE PEwin a.out aixlib dylib none"))
-       acl("SITES.*", lkShell, BtFetchURL, "Makefile, Makefile.common, options.mk: set, append, use")
-       usr("SMF_PREFIS", lkNone, BtPathname)
-       pkg("SMF_SRCDIR", lkNone, BtPathname)
-       pkg("SMF_NAME", lkNone, BtFileName)
-       pkg("SMF_MANIFEST", lkNone, BtPathname)
-       pkg("SMF_INSTANCES", lkShell, BtIdentifier)
-       pkg("SMF_METHODS", lkShell, BtFileName)
-       pkg("SMF_METHOD_SRC.*", lkNone, BtPathname)
-       pkg("SMF_METHOD_SHELL", lkNone, BtShellCommand)
-       pkglist("SPECIAL_PERMS", lkShell, BtPerms)
-       sys("STEP_MSG", lkNone, BtShellCommand)
-       sys("STRIP", lkNone, BtShellCommand) // see mk/tools/strip.mk
-       acl("SUBDIR", lkShell, BtFileName, "Makefile: append; *: none")
-       acl("SUBST_CLASSES", lkShell, BtIdentifier, "Makefile: set, append; *: append")
-       acl("SUBST_CLASSES.*", lkShell, BtIdentifier, "Makefile: set, append; *: append") // OPSYS-specific
-       acl("SUBST_FILES.*", lkShell, BtPathmask, "Makefile, Makefile.*, *.mk: set, append")
-       acl("SUBST_FILTER_CMD.*", lkNone, BtShellCommand, "Makefile, Makefile.*, *.mk: set")
-       acl("SUBST_MESSAGE.*", lkNone, BtMessage, "Makefile, Makefile.*, *.mk: set")
-       acl("SUBST_SED.*", lkNone, BtSedCommands, "Makefile, Makefile.*, *.mk: set, append")
-       pkg("SUBST_STAGE.*", lkNone, BtStage)
-       acl("SUBST_VARS.*", lkShell, BtVariableName, "Makefile, Makefile.*, *.mk: set, append")
-       pkglist("SUPERSEDES", lkShell, BtDependency)
-       acl("TEST_DEPENDS", lkShell, BtDependencyWithPath, "Makefile, Makefile.common, *.mk: append")
-       pkglist("TEST_DIRS", lkShell, BtWrksrcSubdirectory)
-       pkglist("TEST_ENV", lkShell, BtShellWord)
-       acl("TEST_TARGET", lkShell, BtIdentifier, "Makefile: set; Makefile.common: default, set; options.mk: set, append")
-       pkglist("TEXINFO_REQD", lkShell, BtVersion)
-       acl("TOOL_DEPENDS", lkShell, BtDependencyWithPath, "Makefile, Makefile.common, *.mk: append")
-       sys("TOOLS_ALIASES", lkShell, BtFileName)
-       sys("TOOLS_BROKEN", lkShell, BtTool)
-       sys("TOOLS_CMD.*", lkNone, BtPathname)
-       acl("TOOLS_CREATE", lkShell, BtTool, "Makefile, Makefile.common, options.mk: append")
-       acl("TOOLS_DEPENDS.*", lkShell, BtDependencyWithPath, "buildlink3.mk: none; Makefile, Makefile.*: set, default; *: use")
-       sys("TOOLS_GNU_MISSING", lkShell, BtTool)
-       sys("TOOLS_NOOP", lkShell, BtTool)
-       sys("TOOLS_PATH.*", lkNone, BtPathname)
-       sysload("TOOLS_PLATFORM.*", lkNone, BtShellCommand)
-       sys("TOUCH_FLAGS", lkShell, BtShellWord)
-       pkglist("UAC_REQD_EXECS", lkShell, BtPrefixPathname)
-       acl("UNLIMIT_RESOURCES", lkShell, enum("cputime datasize memorysize stacksize"), "Makefile: set, append; Makefile.common: append")
-       usr("UNPRIVILEGED_USER", lkNone, BtUserGroupName)
-       usr("UNPRIVILEGED_GROUP", lkNone, BtUserGroupName)
-       pkglist("UNWRAP_FILES", lkShell, BtPathmask)
-       usr("UPDATE_TARGET", lkShell, BtIdentifier)
-       pkg("USERGROUP_PHASE", lkNone, enum("configure build pre-install"))
-       usr("USER_ADDITIONAL_PKGS", lkShell, BtPkgPath)
-       pkg("USE_BSD_MAKEFILE", lkNone, BtYes)
-       acl("USE_BUILTIN.*", lkNone, BtYesNoIndirectly, "buildlink3.mk: use-loadtime; builtin.mk: set, use, use-loadtime; options.mk: use-loadtime")
-       pkg("USE_CMAKE", lkNone, BtYes)
-       usr("USE_DESTDIR", lkNone, BtYes)
-       pkglist("USE_FEATURES", lkShell, BtIdentifier)
-       acl("USE_GAMESGROUP", lkNone, BtYesNo, "buildlink3.mk, builtin.mk: none; *: set, default, use")
-       pkg("USE_GCC_RUNTIME", lkNone, BtYesNo)
-       pkg("USE_GNU_CONFIGURE_HOST", lkNone, BtYesNo)
-       acl("USE_GNU_ICONV", lkNone, BtYes, "Makefile, Makefile.common, options.mk: set")
-       acl("USE_IMAKE", lkNone, BtYes, "Makefile: set")
-       pkg("USE_JAVA", lkNone, enum("run yes build"))
-       pkg("USE_JAVA2", lkNone, enum("YES yes no 1.4 1.5 6 7 8"))
-       acl("USE_LANGUAGES", lkShell, compilerLanguages, "Makefile, Makefile.common, options.mk: set, append")
-       pkg("USE_LIBTOOL", lkNone, BtYes)
-       pkg("USE_MAKEINFO", lkNone, BtYes)
-       pkg("USE_MSGFMT_PLURALS", lkNone, BtYes)
-       pkg("USE_NCURSES", lkNone, BtYes)
-       pkg("USE_OLD_DES_API", lkNone, BtYesNo)
-       pkg("USE_PKGINSTALL", lkNone, BtYes)
-       pkg("USE_PKGLOCALEDIR", lkNone, BtYesNo)
-       usr("USE_PKGSRC_GCC", lkNone, BtYes)
-       acl("USE_TOOLS", lkShell, BtTool, "*: append, use-loadtime")
-       acl("USE_TOOLS.*", lkShell, BtTool, "*: append, use-loadtime")
-       pkg("USE_X11", lkNone, BtYes)
-       sys("WARNINGS", lkShell, BtShellWord)
-       sys("WARNING_MSG", lkNone, BtShellCommand)
-       sys("WARNING_CAT", lkNone, BtShellCommand)
-       sysload("WRAPPER_DIR", lkNone, BtPathname)
-       acl("WRAPPER_REORDER_CMDS", lkShell, BtWrapperReorder, "Makefile, Makefile.common, buildlink3.mk: append")
-       pkg("WRAPPER_SHELL", lkNone, BtShellCommand)
-       acl("WRAPPER_TRANSFORM_CMDS", lkShell, BtWrapperTransform, "Makefile, Makefile.common, buildlink3.mk: append")
-       sys("WRKDIR", lkNone, BtPathname)
-       pkg("WRKSRC", lkNone, BtWrkdirSubdirectory)
-       pkglist("X11_LDFLAGS", lkShell, BtLdFlag)
-       sys("X11_PKGSRCDIR.*", lkNone, BtPathname)
-       usr("XAW_TYPE", lkNone, enum("3d neXtaw standard xpm"))
-       acl("XMKMF_FLAGS", lkShell, BtShellWord, "")
-       pkglist("_WRAP_EXTRA_ARGS.*", lkShell, BtShellWord)
+       pkglist("PKG_SYSCONFDIR_PERMS", BtPerms)
+       sys("PKG_SYSCONFBASEDIR", BtPathname)
+       pkg("PKG_SYSCONFSUBDIR", BtPathname)
+       pkg("PKG_SYSCONFVAR", BtIdentifier)
+       pkg("PKG_UID", BtInteger)
+       pkglist("PKG_USERS", BtShellWord)
+       pkglist("PKG_USERS_VARS", BtVariableName)
+       pkg("PKG_USE_KERBEROS", BtYes)
+       pkgload("PLIST.*", BtYes)
+       pkglist("PLIST_VARS", BtIdentifier)
+       pkglist("PLIST_SRC", BtRelativePkgPath)
+       pkglist("PLIST_SUBST", BtShellWord)
+       pkg("PLIST_TYPE", enum("dynamic static"))
+       pkglistbl3("PREPEND_PATH", BtPathname)
+
+       acl("PREFIX", BtPathname,
+               "*: use")
+       // BtPathname instead of BtPkgPath since the original package doesn't exist anymore.
+       // It would be more precise to check for a PkgPath that doesn't exist anymore.
+       pkg("PREV_PKGPATH", BtPathname)
+       acl("PRINT_PLIST_AWK", BtAwkCommand,
+               "*: append")
+       pkglist("PRIVILEGED_STAGES", enum("build install package clean"))
+       pkgbl3("PTHREAD_AUTO_VARS", BtYesNo)
+       syslist("PTHREAD_CFLAGS", BtCFlag)
+       syslist("PTHREAD_LDFLAGS", BtLdFlag)
+       syslist("PTHREAD_LIBS", BtLdFlag)
+       pkglistbl3("PTHREAD_OPTS", enum("native optional require"))
+       sysload("PTHREAD_TYPE", BtIdentifier) // Or "native" or "none".
+       pkg("PY_PATCHPLIST", BtYes)
+       acl("PYPKGPREFIX",
+               enumFromDirs("lang", `^python(\d+)$`, "py$1", "py27 py36"),
+               "special:pyversion.mk: set",
+               "*: use, use-loadtime")
+       // See lang/python/pyversion.mk
+       pkg("PYTHON_FOR_BUILD_ONLY", enum("yes no test tool YES"))
+       pkglist("REPLACE_PYTHON", BtPathmask)
+       pkglist("PYTHON_VERSIONS_ACCEPTED", BtVersion)
+       pkglist("PYTHON_VERSIONS_INCOMPATIBLE", BtVersion)
+       usr("PYTHON_VERSION_DEFAULT", BtVersion)
+       usr("PYTHON_VERSION_REQD", BtVersion)
+       pkglist("PYTHON_VERSIONED_DEPENDENCIES", BtPythonDependency)
+       sys("RANLIB", BtShellCommand)
+       pkglist("RCD_SCRIPTS", BtFileName)
+       // TODO: Is the definition in www/squid3/Makefile detected as being redundant?
+       //  No, but it could if the RedundancyScope were able to resolve ${FILESDIR}
+       //  to "files".
+       pkg("RCD_SCRIPT_SRC.*", BtPathname)
+       pkg("RCD_SCRIPT_WRK.*", BtPathname)
+       usr("REAL_ROOT_USER", BtUserGroupName)
+       usr("REAL_ROOT_GROUP", BtUserGroupName)
+
+       // Example:
+       //  REPLACE.sys-AWK.old=    .*awk
+       //  REPLACE.sys-AWK.new=    ${AWK}
+       // BtUnknown since one of them is a regular expression and the other
+       // is a plain string.
+       pkg("REPLACE.*", BtUnknown)
+
+       pkglist("REPLACE_AWK", BtPathmask)
+       pkglist("REPLACE_BASH", BtPathmask)
+       pkglist("REPLACE_CSH", BtPathmask)
+       pkglist("REPLACE_FILES.*", BtPathmask)
+       pkglist("REPLACE_INTERPRETER", BtIdentifier)
+       pkglist("REPLACE_KSH", BtPathmask)
+       pkglist("REPLACE_LOCALEDIR_PATTERNS", BtFileMask)
+       pkglist("REPLACE_LUA", BtPathmask)
+       pkglist("REPLACE_PERL", BtPathmask)
+       pkglist("REPLACE_PYTHON", BtPathmask)
+       pkglist("REPLACE_SH", BtPathmask)
+       pkglist("REQD_DIRS", BtPathname)
+       pkglist("REQD_DIRS_PERMS", BtPerms)
+       pkglist("REQD_FILES", BtPathname)
+       pkg("REQD_FILES_MODE", enum("0644 0640 0600 0400"))
+       pkglist("REQD_FILES_PERMS", BtPerms)
+       pkg("RESTRICTED", BtMessage)
+       usr("ROOT_USER", BtUserGroupName)
+       usr("ROOT_GROUP", BtUserGroupName)
+       pkglist("RPMIGNOREPATH", BtPathmask)
+       acl("RUBY_BASE",
+               enumFromDirs("lang", `^ruby(\d+)$`, "ruby$1", "ruby22 ruby23 ruby24 ruby25"),
+               "special:rubyversion.mk: set",
+               "*: use, use-loadtime")
+       usr("RUBY_VERSION_REQD", BtVersion)
+       acl("RUBY_PKGPREFIX",
+               enumFromDirs("lang", `^ruby(\d+)$`, "ruby$1", "ruby22 ruby23 ruby24 ruby25"),
+               "special:rubyversion.mk: default, set, use",
+               "*: use, use-loadtime")
+       sys("RUN", BtShellCommand)
+       sys("RUN_LDCONFIG", BtYesNo)
+       pkglist("SCRIPTS_ENV", BtShellWord)
+       usrlist("SETGID_GAMES_PERMS", BtPerms)
+       usrlist("SETUID_ROOT_PERMS", BtPerms)
+       pkg("SET_LIBDIR", BtYes)
+       sys("SHAREGRP", BtUserGroupName)
+       sys("SHAREMODE", BtFileMode)
+       sys("SHAREOWN", BtUserGroupName)
+       sys("SHCOMMENT", BtShellCommand)
+       sys("SHLIBTOOL", BtShellCommand)
+       pkglist("SHLIBTOOL_OVERRIDE", BtPathmask)
+       sysload("SHLIB_TYPE",
+               enum("COFF ECOFF ELF SOM XCOFF Mach-O PE PEwin a.out aixlib dylib none"))
+       pkglist("SITES.*", BtFetchURL)
+       usr("SMF_PREFIS", BtPathname)
+       pkg("SMF_SRCDIR", BtPathname)
+       pkg("SMF_NAME", BtFileName)
+       pkg("SMF_MANIFEST", BtPathname)
+       pkglist("SMF_INSTANCES", BtIdentifier)
+       pkglist("SMF_METHODS", BtFileName)
+       pkg("SMF_METHOD_SRC.*", BtPathname)
+       pkg("SMF_METHOD_SHELL", BtShellCommand)
+       pkglist("SPECIAL_PERMS", BtPerms)
+       sys("STEP_MSG", BtShellCommand)
+       sys("STRIP", BtShellCommand) // see mk/tools/strip.mk
+       // Only valid in the top-level and the category Makefiles.
+       acllist("SUBDIR", BtFileName,
+               "Makefile: append")
+
+       pkglistbl3("SUBST_CLASSES", BtIdentifier)
+       pkglistbl3("SUBST_CLASSES.*", BtIdentifier) // OPSYS-specific
+       pkglistbl3("SUBST_FILES.*", BtPathmask)
+       pkgbl3("SUBST_FILTER_CMD.*", BtShellCommand)
+       pkgbl3("SUBST_MESSAGE.*", BtMessage)
+       pkgappendbl3("SUBST_SED.*", BtSedCommands)
+       pkgbl3("SUBST_STAGE.*", BtStage)
+       pkglistbl3("SUBST_VARS.*", BtVariableName)
+
+       pkglist("SUPERSEDES", BtDependency)
+       pkglist("TEST_DEPENDS", BtDependencyWithPath)
+       pkglist("TEST_DIRS", BtWrksrcSubdirectory)
+       pkglist("TEST_ENV", BtShellWord)
+       pkglist("TEST_TARGET", BtIdentifier)
+       pkglist("TEXINFO_REQD", BtVersion)
+       pkglistbl3("TOOL_DEPENDS", BtDependencyWithPath)
+       syslist("TOOLS_ALIASES", BtFileName)
+       syslist("TOOLS_BROKEN", BtTool)
+       sys("TOOLS_CMD.*", BtPathname)
+       pkglist("TOOLS_CREATE", BtTool)
+       pkglist("TOOLS_DEPENDS.*", BtDependencyWithPath)
+       syslist("TOOLS_GNU_MISSING", BtTool)
+       syslist("TOOLS_NOOP", BtTool)
+       sys("TOOLS_PATH.*", BtPathname)
+       sysload("TOOLS_PLATFORM.*", BtShellCommand)
+       syslist("TOUCH_FLAGS", BtShellWord)
+       pkglist("UAC_REQD_EXECS", BtPrefixPathname)
+       pkglistbl3("UNLIMIT_RESOURCES",
+               enum("cputime datasize memorysize stacksize"))
+       usr("UNPRIVILEGED_USER", BtUserGroupName)
+       usr("UNPRIVILEGED_GROUP", BtUserGroupName)
+       pkglist("UNWRAP_FILES", BtPathmask)
+       usrlist("UPDATE_TARGET", BtIdentifier)
+       pkg("USERGROUP_PHASE", enum("configure build pre-install"))
+       usrlist("USER_ADDITIONAL_PKGS", BtPkgPath)
+       pkg("USE_BSD_MAKEFILE", BtYes)
+       // USE_BUILTIN.* is usually set by the builtin.mk file, after checking
+       // whether the package is available in the base system. To override
+       // this check, a package may set this variable before including the
+       // corresponding buildlink3.mk file.
+       acl("USE_BUILTIN.*", BtYesNoIndirectly,
+               "Makefile, Makefile.*, *.mk: set, use, use-loadtime")
+       pkg("USE_CMAKE", BtYes)
+       usr("USE_DESTDIR", BtYes)
+       pkglist("USE_FEATURES", BtIdentifier)
+       pkg("USE_GAMESGROUP", BtYesNo)
+       pkg("USE_GCC_RUNTIME", BtYesNo)
+       pkg("USE_GNU_CONFIGURE_HOST", BtYesNo)
+       pkgload("USE_GNU_ICONV", BtYes)
+       pkg("USE_IMAKE", BtYes)
+       pkg("USE_JAVA", enum("run yes build"))
+       pkg("USE_JAVA2", enum("YES yes no 1.4 1.5 6 7 8"))
+       pkglist("USE_LANGUAGES", compilerLanguages)
+       pkg("USE_LIBTOOL", BtYes)
+       pkg("USE_MAKEINFO", BtYes)
+       pkg("USE_MSGFMT_PLURALS", BtYes)
+       pkg("USE_NCURSES", BtYes)
+       pkg("USE_OLD_DES_API", BtYesNo)
+       pkg("USE_PKGINSTALL", BtYes)
+       pkg("USE_PKGLOCALEDIR", BtYesNo)
+       usr("USE_PKGSRC_GCC", BtYes)
 
-       // Only for infrastructure files; see mk/misc/show.mk
-       acl("_VARGROUPS", lkShell, BtIdentifier, "*: append")
-       acl("_USER_VARS.*", lkShell, BtIdentifier, "*: append")
-       acl("_PKG_VARS.*", lkShell, BtIdentifier, "*: append")
-       acl("_SYS_VARS.*", lkShell, BtIdentifier, "*: append")
-       acl("_DEF_VARS.*", lkShell, BtIdentifier, "*: append")
-       acl("_USE_VARS.*", lkShell, BtIdentifier, "*: append")
+       // USE_TOOLS is not similar to any predefined permissions set.
+       //
+       // It may be appended to in buildlink3 files to make tools available
+       // at runtime. Making tools available at load time would only work
+       // before bsd.prefs.mk has been included for the first time, and that
+       // cannot be guaranteed.
+       //
+       // There are a few builtin.mk files that check whether some tool is
+       // already contained in USE_TOOLS. For them, use-loadtime is allowed.
+       //
+       // All other files may also use = instead of +=. Cases where the tools
+       // list is accidentally overwritten are detected by the redundancy check.
+       //
+       // The use-loadtime is only for devel/ncurses/Makefile.common, which
+       // removes tbl from USE_TOOLS.
+       acllist("USE_TOOLS", BtTool,
+               "special:Makefile.common: set, append, use, use-loadtime",
+               "buildlink3.mk: append",
+               "builtin.mk: append, use-loadtime",
+               "*: set, append, use")
+       acllist("USE_TOOLS.*", BtTool, // OPSYS-specific
+               "buildlink3.mk, builtin.mk: append",
+               "*: set, append, use")
+
+       pkg("USE_X11", BtYes)
+       syslist("WARNINGS", BtShellWord)
+       sys("WARNING_MSG", BtShellCommand)
+       sys("WARNING_CAT", BtShellCommand)
+       sysload("WRAPPER_DIR", BtPathname)
+       pkglistbl3("WRAPPER_REORDER_CMDS", BtWrapperReorder)
+       pkg("WRAPPER_SHELL", BtShellCommand)
+       pkglist("WRAPPER_TRANSFORM_CMDS", BtWrapperTransform)
+       sys("WRKDIR", BtPathname)
+       pkg("WRKSRC", BtWrkdirSubdirectory)
+       pkglist("X11_LDFLAGS", BtLdFlag)
+       sys("X11_PKGSRCDIR.*", BtPathname)
+       usr("XAW_TYPE", enum("3d neXtaw standard xpm"))
+       pkglist("XMKMF_FLAGS", BtShellWord)
+       pkglist("_WRAP_EXTRA_ARGS.*", BtShellWord)
+
+       infralist("_VARGROUPS", BtIdentifier)
+       infralist("_USER_VARS.*", BtIdentifier)
+       infralist("_PKG_VARS.*", BtIdentifier)
+       infralist("_SYS_VARS.*", BtIdentifier)
+       infralist("_DEF_VARS.*", BtIdentifier)
+       infralist("_USE_VARS.*", BtIdentifier)
 }
 
 func enum(values string) *BasicType {
@@ -1249,47 +1606,33 @@ func enum(values string) *BasicType {
        return &basicType
 }
 
-func parseACLEntries(varname string, aclEntries string) []ACLEntry {
-       if aclEntries == "" {
-               return nil
-       }
+func (reg *VarTypeRegistry) parseACLEntries(varname string, aclEntries ...string) []ACLEntry {
+
+       G.Assertf(len(aclEntries) > 0, "At least one ACL entry must be given.")
+
+       // TODO: Use separate rules for infrastructure files.
+       //  These rules would have the "infra:" prefix
+       //  that works similar to the already existing prefix "special:".
+       //  The "infra:" prefix applies to both mk/* and wip/mk/* files, and globs
+       //  without that prefix only apply to all files outside the infrastructure.
 
        var result []ACLEntry
        prevperms := "(first)"
-       for _, arg := range strings.Split(aclEntries, "; ") {
-               fields := strings.SplitN(arg, ": ", 2)
-               G.Assertf(len(fields) == 2, "Invalid ACL entry %q", arg)
-               globs, perms := fields[0], ifelseStr(fields[1] == "none", "", fields[1])
+       for _, arg := range aclEntries {
+               fields := strings.Split(arg, ": ")
+               G.Assertf(len(fields) == 2, "ACL entry %q must have exactly 1 colon.", arg)
+               globs, perms := fields[0], fields[1]
 
                G.Assertf(perms != prevperms, "Repeated permissions %q for %q.", perms, varname)
                prevperms = perms
 
-               var permissions ACLPermissions
-               for _, perm := range strings.Split(perms, ", ") {
-                       switch perm {
-                       case "append":
-                               permissions |= aclpAppend
-                       case "default":
-                               permissions |= aclpSetDefault
-                       case "set":
-                               permissions |= aclpSet
-                       case "use":
-                               permissions |= aclpUse
-                       case "use-loadtime":
-                               permissions |= aclpUseLoadtime
-                       case "":
-                               break
-                       default:
-                               G.Assertf(false, "Invalid ACL permission %q for %q.", perm, varname)
-                       }
-               }
+               permissions := reg.parsePermissions(varname, globs, perms)
 
                for _, glob := range strings.Split(globs, ", ") {
                        switch glob {
                        case "*",
-                               "Makefile", "Makefile.common", "Makefile.*",
-                               "buildlink3.mk", "builtin.mk", "options.mk", "hacks.mk", "*.mk",
-                               "bsd.options.mk":
+                               "Makefile", "Makefile.*",
+                               "buildlink3.mk", "builtin.mk", "options.mk", "hacks.mk", "*.mk":
                                break
                        default:
                                withoutSpecial := strings.TrimPrefix(glob, "special:")
@@ -1307,5 +1650,50 @@ func parseACLEntries(varname string, acl
                        result = append(result, ACLEntry{glob, permissions})
                }
        }
+
        return result
 }
+
+func (reg *VarTypeRegistry) parsePermissions(varname, globs, perms string) ACLPermissions {
+       if perms == "none" {
+               return aclpNone
+       }
+
+       splitPerms := strings.Split(perms, ", ")
+       var permissions ACLPermissions
+
+       remove := func(name string, perm ACLPermissions) {
+               if len(splitPerms) > 0 && splitPerms[0] == name {
+                       permissions |= perm
+                       splitPerms = splitPerms[1:]
+               }
+       }
+
+       // The order of the assignment permissions is in perceived
+       // chronological order. First the default assignment, which
+       // can later be overridden by an unconditional assignment,
+       // and that can be appended later to add more values.
+       remove("default", aclpSetDefault)
+       remove("set", aclpSet)
+       remove("append", aclpAppend)
+
+       // When using a variable, "use" comes first because it feels
+       // more general. Most variables that can be used at load time
+       // can also be used at run time.
+       //
+       // Using a variable at load time is a special access that
+       // applies to fewer variables. Therefore it comes last.
+       remove("use", aclpUse)
+       remove("use-loadtime", aclpUseLoadtime)
+
+       if len(splitPerms) > 0 {
+               G.Assertf(
+                       false,
+                       "Invalid ACL permission %q for %q in %q. "+
+                               "Remaining parts are %q. "+
+                               "Valid permissions are default, set, append, "+
+                               "use, use-loadtime (in this order), or none.",
+                       perms, varname, globs, strings.Join(splitPerms, ", "))
+       }
+       return permissions
+}

Index: pkgsrc/pkgtools/pkglint/files/vardefs_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vardefs_test.go:1.10 pkgsrc/pkgtools/pkglint/files/vardefs_test.go:1.11
--- pkgsrc/pkgtools/pkglint/files/vardefs_test.go:1.10  Sun Jan 13 19:55:53 2019
+++ pkgsrc/pkgtools/pkglint/files/vardefs_test.go       Sun Mar 24 13:58:38 2019
@@ -2,17 +2,17 @@ package pkglint
 
 import "gopkg.in/check.v1"
 
-func (s *Suite) Test_Pkgsrc_InitVartypes(c *check.C) {
+func (s *Suite) Test_VarTypeRegistry_Init(c *check.C) {
        t := s.Init(c)
 
        src := NewPkgsrc(t.File("."))
-       src.InitVartypes()
+       src.vartypes.Init(src)
 
-       c.Check(src.vartypes["BSD_MAKE_ENV"].basicType.name, equals, "ShellWord")
-       c.Check(src.vartypes["USE_BUILTIN.*"].basicType.name, equals, "YesNoIndirectly")
+       c.Check(src.vartypes.Canon("BSD_MAKE_ENV").basicType.name, equals, "ShellWord")
+       c.Check(src.vartypes.Canon("USE_BUILTIN.*").basicType.name, equals, "YesNoIndirectly")
 }
 
-func (s *Suite) Test_Pkgsrc_InitVartypes__enumFrom(c *check.C) {
+func (s *Suite) Test_VarTypeRegistry_Init__enumFrom(c *check.C) {
        t := s.Init(c)
 
        t.CreateFileLines("editors/emacs/modules.mk",
@@ -59,7 +59,7 @@ func (s *Suite) Test_Pkgsrc_InitVartypes
        test("PKGSRC_COMPILER", "List of enum: ccache distcc f2c g95 gcc ido mipspro-ucode sunpro ")
 }
 
-func (s *Suite) Test_Pkgsrc_InitVartypes__enumFromDirs(c *check.C) {
+func (s *Suite) Test_VarTypeRegistry_Init__enumFromDirs(c *check.C) {
        t := s.Init(c)
 
        // To make the test useful, these directories must differ from the
@@ -77,15 +77,21 @@ func (s *Suite) Test_Pkgsrc_InitVartypes
        test("PYPKGPREFIX", "enum: py28 py33 ")
 }
 
-func (s *Suite) Test_parseACLEntries(c *check.C) {
+func (s *Suite) Test_VarTypeRegistry_parseACLEntries__invalid_arguments(c *check.C) {
        t := s.Init(c)
 
+       reg := NewVarTypeRegistry()
+       parseACLEntries := reg.parseACLEntries
+
        t.ExpectPanic(
-               func() { parseACLEntries("VARNAME", "buildlink3.mk: *; *: *") },
-               "Pkglint internal error: Invalid ACL permission \"*\" for \"VARNAME\".")
+               func() { parseACLEntries("VARNAME", "buildlink3.mk: *", "*: *") },
+               "Pkglint internal error: "+
+                       "Invalid ACL permission \"*\" for \"VARNAME\" in \"buildlink3.mk\". "+
+                       "Remaining parts are \"*\". "+
+                       "Valid permissions are default, set, append, use, use-loadtime (in this order), or none.")
 
        t.ExpectPanic(
-               func() { parseACLEntries("VARNAME", "buildlink3.mk: use; *: use") },
+               func() { parseACLEntries("VARNAME", "buildlink3.mk: use", "*: use") },
                "Pkglint internal error: Repeated permissions \"use\" for \"VARNAME\".")
 
        t.ExpectPanic(
@@ -93,11 +99,23 @@ func (s *Suite) Test_parseACLEntries(c *
                "Pkglint internal error: Invalid ACL glob \"*.txt\" for \"VARNAME\".")
 
        t.ExpectPanic(
-               func() { parseACLEntries("VARNAME", "*.mk: use; buildlink3.mk: append") },
+               func() { parseACLEntries("VARNAME", "*.mk: use", "buildlink3.mk: append") },
                "Pkglint internal error: Unreachable ACL pattern \"buildlink3.mk\" for \"VARNAME\".")
+
+       t.ExpectPanic(
+               func() { parseACLEntries("VARNAME", "no colon") },
+               "Pkglint internal error: ACL entry \"no colon\" must have exactly 1 colon.")
+
+       t.ExpectPanic(
+               func() { parseACLEntries("VARNAME", "too: many: colons") },
+               "Pkglint internal error: ACL entry \"too: many: colons\" must have exactly 1 colon.")
+
+       t.ExpectPanic(
+               func() { parseACLEntries("VAR") },
+               "Pkglint internal error: At least one ACL entry must be given.")
 }
 
-func (s *Suite) Test_Pkgsrc_InitVartypes__LP64PLATFORMS(c *check.C) {
+func (s *Suite) Test_VarTypeRegistry_Init__LP64PLATFORMS(c *check.C) {
        t := s.Init(c)
 
        pkg := t.SetUpPackage("category/package",
@@ -110,7 +128,7 @@ func (s *Suite) Test_Pkgsrc_InitVartypes
        t.CheckOutputEmpty()
 }
 
-func (s *Suite) Test_Pkgsrc_InitVartypes__no_tracing(c *check.C) {
+func (s *Suite) Test_VarTypeRegistry_Init__no_tracing(c *check.C) {
        t := s.Init(c)
 
        t.CreateFileLines("editors/emacs/modules.mk",

Index: pkgsrc/pkgtools/pkglint/files/vartype_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vartype_test.go:1.15 pkgsrc/pkgtools/pkglint/files/vartype_test.go:1.16
--- pkgsrc/pkgtools/pkglint/files/vartype_test.go:1.15  Sat Jan 26 16:31:33 2019
+++ pkgsrc/pkgtools/pkglint/files/vartype_test.go       Sun Mar 24 13:58:38 2019
@@ -9,19 +9,113 @@ func (s *Suite) Test_Vartype_EffectivePe
 
        t.SetUpVartypes()
 
-       if typ := G.Pkgsrc.vartypes["PREFIX"]; c.Check(typ, check.NotNil) {
+       if typ := G.Pkgsrc.vartypes.Canon("PREFIX"); c.Check(typ, check.NotNil) {
                c.Check(typ.basicType.name, equals, "Pathname")
-               c.Check(typ.aclEntries, check.DeepEquals, []ACLEntry{{glob: "*", permissions: aclpUse}})
+               c.Check(typ.aclEntries, check.DeepEquals, []ACLEntry{{"*", aclpUse}})
                c.Check(typ.EffectivePermissions("Makefile"), equals, aclpUse)
+               c.Check(typ.EffectivePermissions("buildlink3.mk"), equals, aclpUse)
        }
 
-       if typ := G.Pkgsrc.vartypes["EXTRACT_OPTS"]; c.Check(typ, check.NotNil) {
+       if typ := G.Pkgsrc.vartypes.Canon("EXTRACT_OPTS"); c.Check(typ, check.NotNil) {
                c.Check(typ.basicType.name, equals, "ShellWord")
-               c.Check(typ.EffectivePermissions("Makefile"), equals, aclpAppend|aclpSet)
-               c.Check(typ.EffectivePermissions("options.mk"), equals, aclpUnknown)
+               c.Check(typ.EffectivePermissions("Makefile"), equals, aclpAllWrite|aclpUse)
+               c.Check(typ.EffectivePermissions("options.mk"), equals, aclpAllWrite|aclpUse)
        }
 }
 
+func (s *Suite) Test_Vartype_AlternativeFiles(c *check.C) {
+
+       // test generates the files description for the "set" permission.
+       test := func(rules []string, alternatives string) {
+               aclEntries := (*VarTypeRegistry).parseACLEntries(nil, "", rules...)
+               vartype := Vartype{lkNone, BtYesNo, aclEntries, false}
+
+               alternativeFiles := vartype.AlternativeFiles(aclpSet)
+
+               c.Check(alternativeFiles, equals, alternatives)
+       }
+
+       // rules parses the given permission rules.
+       //
+       // There is a built-in check that prevents repeated adjacent permissions.
+       // The "append" permission can be added alternatively to circumvent this
+       // check, since that permission is effectively ignore by this test.
+       rules := func(rules ...string) []string { return rules }
+
+       // When there are no matching rules at all, there's nothing to describe.
+       test(
+               rules(
+                       "*: none"),
+               "")
+
+       // When there are only positive rules that are disjoint, these are
+       // listed in the given order.
+       test(
+               rules(
+                       "buildlink3.mk: set",
+                       "Makefile: set, append", // to avoid "repeated permissions" panic
+                       "Makefile.*: set"),
+               "buildlink3.mk, Makefile or Makefile.*")
+
+       // When there are only positive rules and some of them overlap,
+       // these are merged.
+       test(
+               rules(
+                       "buildlink3.mk: set",
+                       "special:b*.mk: set, append",
+                       "*.mk: set",
+                       "Makefile: set, append",
+                       "Makefile.*: set"),
+               "*.mk, Makefile or Makefile.*")
+
+       // When the last rule is "*", all previous rules are merged into that.
+       test(
+               rules(
+                       "buildlink3.mk: set",
+                       "special:b*.mk: set, append",
+                       "*.mk: set",
+                       "Makefile: set, append",
+                       "Makefile.*: set",
+                       "*: set, append"),
+               "*")
+
+       test(
+               rules(
+                       "buildlink3.mk: set",
+                       "*: none"),
+               "buildlink3.mk only")
+
+       // Everywhere except in buildlink3.mk.
+       test(
+               rules(
+                       "buildlink3.mk: none",
+                       "*: set"),
+               // TODO: should be "buildlink3.mk only".
+               "*, but not buildlink3.mk")
+
+       // If there are both positive and negative cases, preserve all the
+       // rules verbatim. Otherwise it would be too confusing for the
+       // pkglint users to see and maybe learn the actual rules.
+       test(
+               rules(
+                       "buildlink3.mk: none",
+                       "special:b*.mk: set",
+                       "*.mk: none",
+                       "Makefile: set",
+                       "Makefile.*: none",
+                       "*: set"),
+               "b*.mk, Makefile or *, but not buildlink3.mk, *.mk or Makefile.*")
+
+       test(
+               rules(
+                       "buildlink3.mk: none",
+                       "builtin.mk: set",
+                       "Makefile: none",
+                       "*.mk: append"),
+               // TODO: should be "builtin.mk only".
+               "builtin.mk, but not buildlink3.mk, Makefile or *.mk")
+}
+
 func (s *Suite) Test_BasicType_HasEnum(c *check.C) {
        vc := enum("start middle end")
 
@@ -46,7 +140,6 @@ func (s *Suite) Test_ACLPermissions_Cont
 func (s *Suite) Test_ACLPermissions_String(c *check.C) {
        c.Check(ACLPermissions(0).String(), equals, "none")
        c.Check(aclpAll.String(), equals, "set, set-default, append, use-loadtime, use")
-       c.Check(aclpUnknown.String(), equals, "unknown")
 }
 
 func (s *Suite) Test_ACLPermissions_HumanString(c *check.C) {
@@ -56,9 +149,6 @@ func (s *Suite) Test_ACLPermissions_Huma
 
        c.Check(aclpAll.HumanString(),
                equals, "set, given a default value, appended to, used at load time, or used")
-
-       c.Check(aclpUnknown.HumanString(),
-               equals, "") // Doesn't happen in practice
 }
 
 func (s *Suite) Test_Vartype_IsConsideredList(c *check.C) {

Index: pkgsrc/pkgtools/pkglint/files/vartypecheck.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.51 pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.52
--- pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.51  Sun Mar 10 19:01:50 2019
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck.go       Sun Mar 24 13:58:38 2019
@@ -1045,16 +1045,16 @@ func (cv *VartypeCheck) ShellCommand() {
                return
        }
        setE := true
-       NewShellLine(cv.MkLine).CheckShellCommand(cv.Value, &setE, RunTime)
+       NewShellLineChecker(cv.MkLine).CheckShellCommand(cv.Value, &setE, RunTime)
 }
 
 // ShellCommands checks for zero or more shell commands, each terminated with a semicolon.
 func (cv *VartypeCheck) ShellCommands() {
-       NewShellLine(cv.MkLine).CheckShellCommands(cv.Value, RunTime)
+       NewShellLineChecker(cv.MkLine).CheckShellCommands(cv.Value, RunTime)
 }
 
 func (cv *VartypeCheck) ShellWord() {
-       NewShellLine(cv.MkLine).CheckWord(cv.Value, true, RunTime)
+       NewShellLineChecker(cv.MkLine).CheckWord(cv.Value, true, RunTime)
 }
 
 func (cv *VartypeCheck) Stage() {
@@ -1089,6 +1089,11 @@ func (cv *VartypeCheck) Tool() {
 }
 
 // Unknown doesn't check for anything.
+//
+// Used for:
+//  - infrastructure variables that are not in vardefs.go
+//  - other variables whose type is unknown or uninteresting enough to
+//    warrant the creation of a specialized type
 func (cv *VartypeCheck) Unknown() {
        // Do nothing.
 }
@@ -1259,6 +1264,13 @@ func (cv *VartypeCheck) Yes() {
        }
 }
 
+// YesNo checks for variables that can be set to either yes or no. Undefined
+// means no.
+//
+// Most of these variables use the lowercase yes/no variant. Some use the
+// uppercase YES/NO, and the mixed forms Yes/No are practically never seen.
+// Testing these variables using the however-mixed pattern is done solely
+// because writing this pattern is shorter than repeating the variable name.
 func (cv *VartypeCheck) YesNo() {
        const (
                yes1 = "[yY][eE][sS]"
@@ -1269,6 +1281,7 @@ func (cv *VartypeCheck) YesNo() {
        if cv.Op == opUseMatch {
                switch cv.Value {
                case yes1, yes2, no1, no2:
+                       break
                default:
                        cv.Warnf("%s should be matched against %q or %q, not %q.", cv.Varname, yes1, no1, cv.Value)
                }

Index: pkgsrc/pkgtools/pkglint/files/cmd/pkglint/main_test.go
diff -u pkgsrc/pkgtools/pkglint/files/cmd/pkglint/main_test.go:1.1 pkgsrc/pkgtools/pkglint/files/cmd/pkglint/main_test.go:1.2
--- pkgsrc/pkgtools/pkglint/files/cmd/pkglint/main_test.go:1.1  Sat Jan 26 16:31:33 2019
+++ pkgsrc/pkgtools/pkglint/files/cmd/pkglint/main_test.go      Sun Mar 24 13:58:38 2019
@@ -44,5 +44,5 @@ func (s *Suite) Test_main(c *check.C) {
        output, err := ioutil.ReadFile(out.Name())
        c.Assert(err, check.IsNil)
 
-       c.Check(string(output), check.Matches, `^(@VERSION@|\d+(\.\d+)+)\n$`)
+       c.Check(string(output), check.Matches, `^(@VERSION@|\d+(\.\d+)+(nb\d+)?)\n$`)
 }



Home | Main Index | Thread Index | Old Index