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 Aug 12 16:31:56 UTC 2018

Modified Files:
        pkgsrc/pkgtools/pkglint: Makefile
        pkgsrc/pkgtools/pkglint/files: alternatives.go alternatives_test.go
            autofix_test.go buildlink3_test.go category.go category_test.go
            check_test.go codewalk.md distinfo.go distinfo_test.go files.go
            licenses.go licenses_test.go mkline.go mkline_test.go
            mklinechecker.go mklinechecker_test.go mklines.go mklines_test.go
            mkparser.go mkparser_test.go mkshwalker.go mkshwalker_test.go
            options.go options_test.go package.go package_test.go
            patches_test.go pkglint.go pkglint_test.go pkgsrc.go pkgsrc_test.go
            shell.go shell_test.go tools_test.go toplevel.go toplevel_test.go
            util.go util_test.go vartypecheck.go vartypecheck_test.go
        pkgsrc/pkgtools/pkglint/files/histogram: histogram.go
        pkgsrc/pkgtools/pkglint/files/pkgver: vercmp_test.go
Added Files:
        pkgsrc/pkgtools/pkglint/files: vardefs_test.go

Log Message:
pkgtools/pkglint: update to 5.6.0

Changes since 5.5.16:

* Check for negated shell commands (if ! test -z "foo"); they are not
  supported by Solaris.

* Don't check variable permissions for infrastructure files. A warning
  like "may not be set by any package" doesn't make sense for them.

* Check that PLIST_VARS matches PLIST.*, which is especially useful in
  options.mk files.

* Improve checks for options.mk files (for PKG_OPTIONS_SET).

* Prefer options handling with !empty() over checking empty() first.

* Prefer ${MACHINE_ARCH} == i386 over !empty(MACHINE_ARCH:Mi386), for
  single-valued variables.


To generate a diff of this commit:
cvs rdiff -u -r1.545 -r1.546 pkgsrc/pkgtools/pkglint/Makefile
cvs rdiff -u -r1.2 -r1.3 pkgsrc/pkgtools/pkglint/files/alternatives.go \
    pkgsrc/pkgtools/pkglint/files/codewalk.md \
    pkgsrc/pkgtools/pkglint/files/mkshwalker_test.go
cvs rdiff -u -r1.1 -r1.2 pkgsrc/pkgtools/pkglint/files/alternatives_test.go \
    pkgsrc/pkgtools/pkglint/files/options.go \
    pkgsrc/pkgtools/pkglint/files/options_test.go \
    pkgsrc/pkgtools/pkglint/files/tools_test.go
cvs rdiff -u -r1.7 -r1.8 pkgsrc/pkgtools/pkglint/files/autofix_test.go
cvs rdiff -u -r1.14 -r1.15 pkgsrc/pkgtools/pkglint/files/buildlink3_test.go \
    pkgsrc/pkgtools/pkglint/files/distinfo_test.go
cvs rdiff -u -r1.12 -r1.13 pkgsrc/pkgtools/pkglint/files/category.go \
    pkgsrc/pkgtools/pkglint/files/licenses.go \
    pkgsrc/pkgtools/pkglint/files/licenses_test.go
cvs rdiff -u -r1.10 -r1.11 pkgsrc/pkgtools/pkglint/files/category_test.go \
    pkgsrc/pkgtools/pkglint/files/util_test.go
cvs rdiff -u -r1.22 -r1.23 pkgsrc/pkgtools/pkglint/files/check_test.go
cvs rdiff -u -r1.20 -r1.21 pkgsrc/pkgtools/pkglint/files/distinfo.go
cvs rdiff -u -r1.16 -r1.17 pkgsrc/pkgtools/pkglint/files/files.go
cvs rdiff -u -r1.34 -r1.35 pkgsrc/pkgtools/pkglint/files/mkline.go \
    pkgsrc/pkgtools/pkglint/files/pkglint.go
cvs rdiff -u -r1.38 -r1.39 pkgsrc/pkgtools/pkglint/files/mkline_test.go
cvs rdiff -u -r1.15 -r1.16 pkgsrc/pkgtools/pkglint/files/mklinechecker.go
cvs rdiff -u -r1.11 -r1.12 \
    pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go \
    pkgsrc/pkgtools/pkglint/files/mkparser_test.go \
    pkgsrc/pkgtools/pkglint/files/toplevel.go \
    pkgsrc/pkgtools/pkglint/files/toplevel_test.go
cvs rdiff -u -r1.28 -r1.29 pkgsrc/pkgtools/pkglint/files/mklines.go \
    pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
cvs rdiff -u -r1.24 -r1.25 pkgsrc/pkgtools/pkglint/files/mklines_test.go
cvs rdiff -u -r1.13 -r1.14 pkgsrc/pkgtools/pkglint/files/mkparser.go
cvs rdiff -u -r1.3 -r1.4 pkgsrc/pkgtools/pkglint/files/mkshwalker.go
cvs rdiff -u -r1.32 -r1.33 pkgsrc/pkgtools/pkglint/files/package.go
cvs rdiff -u -r1.26 -r1.27 pkgsrc/pkgtools/pkglint/files/package_test.go
cvs rdiff -u -r1.17 -r1.18 pkgsrc/pkgtools/pkglint/files/patches_test.go
cvs rdiff -u -r1.21 -r1.22 pkgsrc/pkgtools/pkglint/files/pkglint_test.go
cvs rdiff -u -r1.6 -r1.7 pkgsrc/pkgtools/pkglint/files/pkgsrc.go
cvs rdiff -u -r1.4 -r1.5 pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go
cvs rdiff -u -r1.23 -r1.24 pkgsrc/pkgtools/pkglint/files/shell.go
cvs rdiff -u -r1.27 -r1.28 pkgsrc/pkgtools/pkglint/files/shell_test.go
cvs rdiff -u -r1.25 -r1.26 pkgsrc/pkgtools/pkglint/files/util.go
cvs rdiff -u -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/vardefs_test.go
cvs rdiff -u -r1.36 -r1.37 pkgsrc/pkgtools/pkglint/files/vartypecheck.go
cvs rdiff -u -r1.2 -r1.3 pkgsrc/pkgtools/pkglint/files/histogram/histogram.go
cvs rdiff -u -r1.3 -r1.4 pkgsrc/pkgtools/pkglint/files/pkgver/vercmp_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.545 pkgsrc/pkgtools/pkglint/Makefile:1.546
--- pkgsrc/pkgtools/pkglint/Makefile:1.545      Thu Aug  9 20:21:42 2018
+++ pkgsrc/pkgtools/pkglint/Makefile    Sun Aug 12 16:31:56 2018
@@ -1,6 +1,6 @@
-# $NetBSD: Makefile,v 1.545 2018/08/09 20:21:42 rillig Exp $
+# $NetBSD: Makefile,v 1.546 2018/08/12 16:31:56 rillig Exp $
 
-PKGNAME=       pkglint-5.5.16
+PKGNAME=       pkglint-5.6.0
 DISTFILES=     # none
 CATEGORIES=    pkgtools
 

Index: pkgsrc/pkgtools/pkglint/files/alternatives.go
diff -u pkgsrc/pkgtools/pkglint/files/alternatives.go:1.2 pkgsrc/pkgtools/pkglint/files/alternatives.go:1.3
--- pkgsrc/pkgtools/pkglint/files/alternatives.go:1.2   Thu Jul 12 16:23:36 2018
+++ pkgsrc/pkgtools/pkglint/files/alternatives.go       Sun Aug 12 16:31:56 2018
@@ -13,17 +13,19 @@ func CheckfileAlternatives(filename stri
 
        for _, line := range lines {
                if m, wrapper, space, implementation := match3(line.Text, `^(\S+)([ \t]+)(\S+)`); m {
-                       if plistFiles[wrapper] {
-                               line.Errorf("Alternative wrapper %q must not appear in the PLIST.", wrapper)
-                       }
+                       if plistFiles != nil {
+                               if plistFiles[wrapper] {
+                                       line.Errorf("Alternative wrapper %q must not appear in the PLIST.", wrapper)
+                               }
 
-                       relImplementation := strings.Replace(implementation, "@PREFIX@/", "", 1)
-                       plistName := regex.Compile(`@(\w+)@`).ReplaceAllString(relImplementation, "${$1}")
-                       if !plistFiles[plistName] && !G.Pkg.vars.Defined("ALTERNATIVES_SRC") {
-                               if plistName != implementation {
-                                       line.Errorf("Alternative implementation %q must appear in the PLIST as %q.", implementation, plistName)
-                               } else {
-                                       line.Errorf("Alternative implementation %q must appear in the PLIST.", implementation)
+                               relImplementation := strings.Replace(implementation, "@PREFIX@/", "", 1)
+                               plistName := regex.Compile(`@(\w+)@`).ReplaceAllString(relImplementation, "${$1}")
+                               if !plistFiles[plistName] && !G.Pkg.vars.Defined("ALTERNATIVES_SRC") {
+                                       if plistName != implementation {
+                                               line.Errorf("Alternative implementation %q must appear in the PLIST as %q.", implementation, plistName)
+                                       } else {
+                                               line.Errorf("Alternative implementation %q must appear in the PLIST.", implementation)
+                                       }
                                }
                        }
 
Index: pkgsrc/pkgtools/pkglint/files/codewalk.md
diff -u pkgsrc/pkgtools/pkglint/files/codewalk.md:1.2 pkgsrc/pkgtools/pkglint/files/codewalk.md:1.3
--- pkgsrc/pkgtools/pkglint/files/codewalk.md:1.2       Thu Jul 12 16:23:36 2018
+++ pkgsrc/pkgtools/pkglint/files/codewalk.md   Sun Aug 12 16:31:56 2018
@@ -222,3 +222,66 @@ file   shell.go
 start  ^type ShellLine struct
 end    ^\}
 ```
+
+## Testing pkglint
+
+### Standard shape of a test
+
+```go
+func (s *Suite) Test_Type_Method__description(c *check.C) {
+       t := s.Init(c)       // Every test needs this.
+
+       t.Setup…(…)          // Set up the testing environment.
+
+       lines := t.New…(…)   // Set up the test data.
+
+       CodeToBeTested()     // The code to be tested.
+
+       t.Check…(…)          // Check the result (typically diagnostics).
+}
+```
+
+The `t` variable is the center of most tests.
+It is of type `Tester` and provides a high-level interface
+for setting up tests and checking the results.
+
+```codewalk
+file   check_test.go
+start  /^type Tester/ upwhile /^\/\//
+end    ^\}
+```
+
+The `s` variable is not used in tests.
+The only purpose of its type `Suite` is to group the tests so they are all run together.
+
+The `c` variable comes from [gocheck](https://godoc.org/gopkg.in/check.v1),
+which is the underlying testing framework.
+Most pkglint tests don't need this variable.
+Low-level tests call `c.Check` to compare their results to the expected values.
+
+```codewalk
+file   util_test.go
+start  ^func .* Test_tabLength
+end    ^\}
+```
+
+### Logging detailed information during tests
+
+When testing complicated code, it sometimes helps to have a detailed trace
+of the code that is run. This is done via these two methods:
+
+```go
+t.EnableTracing()
+t.DisableTracing()
+```
+
+### Setting up a realistic pkgsrc environment
+
+To see how to setup complicated tests, have a look at the following test,
+which sets up a realistic environment to run the tests in.
+
+```codewalk
+file   pkglint_test.go
+start  ^func .* Test_Pkglint_Main__complete_package
+end    ^\}
+```
Index: pkgsrc/pkgtools/pkglint/files/mkshwalker_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkshwalker_test.go:1.2 pkgsrc/pkgtools/pkglint/files/mkshwalker_test.go:1.3
--- pkgsrc/pkgtools/pkglint/files/mkshwalker_test.go:1.2        Sat Jan 27 18:50:36 2018
+++ pkgsrc/pkgtools/pkglint/files/mkshwalker_test.go    Sun Aug 12 16:31:56 2018
@@ -1,33 +1,159 @@
 package main
 
 import (
+       "fmt"
        "gopkg.in/check.v1"
 )
 
 func (s *Suite) Test_MkShWalker_Walk(c *check.C) {
        list, err := parseShellProgram(dummyLine, ""+
                "if condition; then action; else case selector in pattern) case-item-action ;; esac; fi; "+
-               "set -e; cd ${WRKSRC}/locale; "+
+               "set -e; "+
+               "cd ${WRKSRC}/locale; "+
                "for lang in *.po; do "+
                "  [ \"$${lang}\" = \"wxstd.po\" ] && continue; "+
                "  ${TOOLS_PATH.msgfmt} -c -o \"$${lang%.po}.mo\" \"$${lang}\"; "+
-               "done")
+               "done; "+
+               "while :; do fun() { :; } 1>&2; done")
 
        if c.Check(err, check.IsNil) && c.Check(list, check.NotNil) {
                var commands []string
-               (*MkShWalker).Walk(nil, list, func(node interface{}) {
-                       if cmd, ok := node.(*MkShSimpleCommand); ok {
-                               commands = append(commands, NewStrCommand(cmd).String())
+               add := func(kind string, format string, args ...interface{}) {
+                       if format != "" && !contains(format, "%") {
+                               panic(format)
                        }
-               })
+                       detail := fmt.Sprintf(format, args...)
+                       commands = append(commands, fmt.Sprintf("%16s %s", kind, detail))
+               }
+
+               callback := NewMkShWalkCallback()
+               callback.List = func(list *MkShList) { add("List", "with %d andOrs", len(list.AndOrs)) }
+               callback.AndOr = func(andor *MkShAndOr) { add("AndOr", "with %d pipelines", len(andor.Pipes)) }
+               callback.Pipeline = func(pipeline *MkShPipeline) { add("Pipeline", "with %d commands", len(pipeline.Cmds)) }
+               callback.Command = func(command *MkShCommand) { add("Command", "") }
+               callback.SimpleCommand = func(command *MkShSimpleCommand) { add("SimpleCommand", "%s", NewStrCommand(command).String()) }
+               callback.CompoundCommand = func(command *MkShCompoundCommand) { add("CompoundCommand", "") }
+               callback.Case = func(caseClause *MkShCaseClause) { add("Case", "with %d items", len(caseClause.Cases)) }
+               callback.CaseItem = func(caseItem *MkShCaseItem) { add("CaseItem", "with %d patterns", len(caseItem.Patterns)) }
+               callback.FunctionDefinition = func(funcdef *MkShFunctionDefinition) { add("FunctionDef", "for %s", funcdef.Name) }
+               callback.If = func(ifClause *MkShIfClause) { add("If", "with %d then-branches", len(ifClause.Conds)) }
+               callback.Loop = func(loop *MkShLoopClause) { add("Loop", "") }
+               callback.Words = func(words []*ShToken) { add("Words", "with %d words", len(words)) }
+               callback.Word = func(word *ShToken) { add("Word", "%s", word.MkText) }
+               callback.Redirects = func(redirects []*MkShRedirection) { add("Redirects", "with %d redirects", len(redirects)) }
+               callback.Redirect = func(redirect *MkShRedirection) { add("Redirect", "%s", redirect.Op) }
+               callback.For = func(forClause *MkShForClause) { add("For", "variable %s", forClause.Varname) }
+               callback.Varname = func(varname string) { add("Varname", "%s", varname) }
+
+               NewMkShWalker().Walk(list, callback)
+
                c.Check(commands, deepEquals, []string{
-                       "[] condition []",
-                       "[] action []",
-                       "[] case-item-action []",
-                       "[] set [-e]",
-                       "[] cd [${WRKSRC}/locale]",
-                       "[] [ [\"$${lang}\" = \"wxstd.po\" ]]",
-                       "[] continue []",
-                       "[] ${TOOLS_PATH.msgfmt} [-c -o \"$${lang%.po}.mo\" \"$${lang}\"]"})
+                       "            List with 5 andOrs",
+                       "           AndOr with 1 pipelines",
+                       "        Pipeline with 1 commands",
+                       "         Command ",
+                       " CompoundCommand ",
+                       "              If with 1 then-branches",
+                       "            List with 1 andOrs",
+                       "           AndOr with 1 pipelines",
+                       "        Pipeline with 1 commands",
+                       "         Command ",
+                       "   SimpleCommand [] condition []",
+                       "            Word condition",
+                       "            List with 1 andOrs",
+                       "           AndOr with 1 pipelines",
+                       "        Pipeline with 1 commands",
+                       "         Command ",
+                       "   SimpleCommand [] action []",
+                       "            Word action",
+                       "            List with 1 andOrs",
+                       "           AndOr with 1 pipelines",
+                       "        Pipeline with 1 commands",
+                       "         Command ",
+                       " CompoundCommand ",
+                       "            Case with 1 items",
+                       "            Word selector",
+                       "        CaseItem with 1 patterns",
+                       "           Words with 1 words",
+                       "            Word pattern",
+                       "            List with 1 andOrs",
+                       "           AndOr with 1 pipelines",
+                       "        Pipeline with 1 commands",
+                       "         Command ",
+                       "   SimpleCommand [] case-item-action []",
+                       "            Word case-item-action",
+                       "           AndOr with 1 pipelines",
+                       "        Pipeline with 1 commands",
+                       "         Command ",
+                       "   SimpleCommand [] set [-e]",
+                       "            Word set",
+                       "           Words with 1 words",
+                       "            Word -e",
+                       "           AndOr with 1 pipelines",
+                       "        Pipeline with 1 commands",
+                       "         Command ",
+                       "   SimpleCommand [] cd [${WRKSRC}/locale]",
+                       "            Word cd",
+                       "           Words with 1 words",
+                       "            Word ${WRKSRC}/locale",
+                       "           AndOr with 1 pipelines",
+                       "        Pipeline with 1 commands",
+                       "         Command ",
+                       " CompoundCommand ",
+                       "             For variable lang",
+                       "         Varname lang",
+                       "           Words with 1 words",
+                       "            Word *.po",
+                       "            List with 2 andOrs",
+                       "           AndOr with 2 pipelines",
+                       "        Pipeline with 1 commands",
+                       "         Command ",
+                       "   SimpleCommand [] [ [\"$${lang}\" = \"wxstd.po\" ]]",
+                       "            Word [",
+                       "           Words with 4 words",
+                       "            Word \"$${lang}\"",
+                       "            Word =",
+                       "            Word \"wxstd.po\"",
+                       "            Word ]",
+                       "        Pipeline with 1 commands",
+                       "         Command ",
+                       "   SimpleCommand [] continue []",
+                       "            Word continue",
+                       "           AndOr with 1 pipelines",
+                       "        Pipeline with 1 commands",
+                       "         Command ",
+                       "   SimpleCommand [] ${TOOLS_PATH.msgfmt} [-c -o \"$${lang%.po}.mo\" \"$${lang}\"]",
+                       "            Word ${TOOLS_PATH.msgfmt}",
+                       "           Words with 4 words",
+                       "            Word -c",
+                       "            Word -o",
+                       "            Word \"$${lang%.po}.mo\"",
+                       "            Word \"$${lang}\"",
+                       "           AndOr with 1 pipelines",
+                       "        Pipeline with 1 commands",
+                       "         Command ",
+                       " CompoundCommand ",
+                       "            Loop ",
+                       "            List with 1 andOrs",
+                       "           AndOr with 1 pipelines",
+                       "        Pipeline with 1 commands",
+                       "         Command ",
+                       "   SimpleCommand [] : []",
+                       "            Word :",
+                       "            List with 1 andOrs",
+                       "           AndOr with 1 pipelines",
+                       "        Pipeline with 1 commands",
+                       "         Command ",
+                       "     FunctionDef for fun",
+                       " CompoundCommand ",
+                       "            List with 1 andOrs",
+                       "           AndOr with 1 pipelines",
+                       "        Pipeline with 1 commands",
+                       "         Command ",
+                       "   SimpleCommand [] : []",
+                       "            Word :",
+                       "       Redirects with 1 redirects",
+                       "        Redirect >&",
+                       "            Word 2"})
        }
 }

Index: pkgsrc/pkgtools/pkglint/files/alternatives_test.go
diff -u pkgsrc/pkgtools/pkglint/files/alternatives_test.go:1.1 pkgsrc/pkgtools/pkglint/files/alternatives_test.go:1.2
--- pkgsrc/pkgtools/pkglint/files/alternatives_test.go:1.1      Sat May 19 12:58:25 2018
+++ pkgsrc/pkgtools/pkglint/files/alternatives_test.go  Sun Aug 12 16:31:56 2018
@@ -16,7 +16,7 @@ func (s *Suite) Test_Alternatives_PLIST(
        G.Pkg.PlistFiles["bin/vim"] = true
        G.Pkg.PlistFiles["sbin/sendmail.exim${EXIMVER}"] = true
 
-       CheckfileAlternatives(t.TempFilename("ALTERNATIVES"), G.Pkg.PlistFiles)
+       CheckfileAlternatives(t.File("ALTERNATIVES"), G.Pkg.PlistFiles)
 
        t.CheckOutputLines(
                "ERROR: ~/ALTERNATIVES:1: Alternative implementation \"@PREFIX@/sbin/sendmail.postfix@POSTFIXVER@\" must appear in the PLIST as \"sbin/sendmail.postfix${POSTFIXVER}\".",
Index: pkgsrc/pkgtools/pkglint/files/options.go
diff -u pkgsrc/pkgtools/pkglint/files/options.go:1.1 pkgsrc/pkgtools/pkglint/files/options.go:1.2
--- pkgsrc/pkgtools/pkglint/files/options.go:1.1        Thu Aug  9 20:08:12 2018
+++ pkgsrc/pkgtools/pkglint/files/options.go    Sun Aug 12 16:31:56 2018
@@ -27,24 +27,27 @@ func ChecklinesOptionsMk(mklines *MkLine
        handledOptions := make(map[string]MkLine)
        var optionsInDeclarationOrder []string
 
-       // The conditionals are typically for OPSYS and MACHINE_ARCH.
 loop:
        for ; !exp.EOF(); exp.Advance() {
                mkline := exp.CurrentMkLine()
                switch {
                case mkline.IsComment():
                case mkline.IsEmpty():
+
                case mkline.IsVarassign():
-                       varname := mkline.Varname()
-                       if varname == "PKG_SUPPORTED_OPTIONS" || hasPrefix(varname, "PKG_OPTIONS_GROUP.") {
+                       switch mkline.Varcanon() {
+                       case "PKG_SUPPORTED_OPTIONS", "PKG_OPTIONS_GROUP.*", "PKG_OPTIONS_SET.*":
                                for _, option := range splitOnSpace(mkline.Value()) {
-                                       if option == mkline.WithoutMakeVariables(option) {
+                                       if !containsVarRef(option) {
                                                declaredOptions[option] = mkline
                                                optionsInDeclarationOrder = append(optionsInDeclarationOrder, option)
                                        }
                                }
                        }
+
                case mkline.IsCond():
+                       // The conditionals are typically for OPSYS and MACHINE_ARCH.
+
                case mkline.IsInclude():
                        includedFile := mkline.IncludeFile()
                        switch {
@@ -57,6 +60,7 @@ loop:
                                exp.Advance()
                                break loop
                        }
+
                default:
                        exp.CurrentLine().Warnf("Expected inclusion of \"../../mk/bsd.options.mk\".")
                        Explain(
@@ -70,17 +74,30 @@ loop:
 
        for ; !exp.EOF(); exp.Advance() {
                mkline := exp.CurrentMkLine()
-               if mkline.IsCond() && mkline.Directive() == "if" {
+               if mkline.IsCond() && (mkline.Directive() == "if" || mkline.Directive() == "elif") {
                        cond := NewMkParser(mkline.Line, mkline.Args(), false).MkCond()
-                       if cond != nil {
-                               cond.Visit("empty", func(t *Tree) {
-                                       varuse := t.args[0].(MkVarUse)
+                       if cond == nil {
+                               continue
+                       }
+
+                       NewMkCondWalker().Walk(cond, &MkCondCallback{
+                               Empty: func(varuse *MkVarUse) {
                                        if varuse.varname == "PKG_OPTIONS" && len(varuse.modifiers) == 1 && hasPrefix(varuse.modifiers[0], "M") {
                                                option := varuse.modifiers[0][1:]
-                                               handledOptions[option] = mkline
-                                               optionsInDeclarationOrder = append(optionsInDeclarationOrder, option)
+                                               if !containsVarRef(option) {
+                                                       handledOptions[option] = mkline
+                                                       optionsInDeclarationOrder = append(optionsInDeclarationOrder, option)
+                                               }
                                        }
-                               })
+                               }})
+
+                       if cond.Empty != nil && mkline.HasElseBranch() {
+                               mkline.Notef("The positive branch of the .if/.else should be the one where the option is set.")
+                               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.")
                        }
                }
        }
@@ -95,7 +112,7 @@ loop:
                                "typo, or the option does not have any effect.")
                }
                if declared == nil && handled != nil {
-                       handled.Warnf("Option %q is handled but not declared above.", option)
+                       handled.Warnf("Option %q is handled but not added to PKG_SUPPORTED_OPTIONS.", option)
                        Explain(
                                "This block of code will never be run since PKG_OPTIONS cannot",
                                "contain this value.  This is most probably a typo.")
Index: pkgsrc/pkgtools/pkglint/files/options_test.go
diff -u pkgsrc/pkgtools/pkglint/files/options_test.go:1.1 pkgsrc/pkgtools/pkglint/files/options_test.go:1.2
--- pkgsrc/pkgtools/pkglint/files/options_test.go:1.1   Thu Aug  9 20:08:12 2018
+++ pkgsrc/pkgtools/pkglint/files/options_test.go       Sun Aug 12 16:31:56 2018
@@ -8,8 +8,11 @@ func (s *Suite) Test_ChecklinesOptionsMk
        t.SetupCommandLine("-Wno-space")
        t.SetupVartypes()
        t.SetupOption("mc-charset", "")
+       t.SetupOption("mysql", "")
        t.SetupOption("ncurses", "")
+       t.SetupOption("negative", "Demonstrates negated .if/.else")
        t.SetupOption("slang", "")
+       t.SetupOption("sqlite", "")
        t.SetupOption("x11", "")
 
        t.SetupFileMkLines("mk/bsd.options.mk",
@@ -21,8 +24,10 @@ func (s *Suite) Test_ChecklinesOptionsMk
                "PKG_OPTIONS_VAR=                PKG_OPTIONS.mc",
                "PKG_OPTIONS_REQUIRED_GROUPS=    screen",
                "PKG_OPTIONS_GROUP.screen=       ncurses slang",
-               "PKG_SUPPORTED_OPTIONS=          mc-charset x11 lang-${l}",
+               "PKG_SUPPORTED_OPTIONS=          mc-charset x11 lang-${l} negative",
                "PKG_SUGGESTED_OPTIONS=          mc-charset slang",
+               "PKG_OPTIONS_NONEMPTY_SETS+=     db",
+               "PKG_OPTIONS_SET.db=             mysql sqlite",
                "",
                ".include \"../../mk/bsd.options.mk\"",
                "",
@@ -30,19 +35,27 @@ func (s *Suite) Test_ChecklinesOptionsMk
                ".endif",
                "",
                ".if !empty(PKG_OPTIONS:Mundeclared)",
+               ".endif",
+               "",
+               ".if empty(PKG_OPTIONS:Mnegative)",
+               ".else",
+               ".endif",
+               "",
+               ".if !empty(PKG_OPTIONS:Mncurses)",
+               ".elif !empty(PKG_OPTIONS:Mslang)",
+               ".endif",
+               "",
+               ".if !empty(PKG_OPTIONS:Mmysql)",
+               ".elif !empty(PKG_OPTIONS:Msqlite)",
                ".endif")
 
-       G.CurrentDir = t.TmpDir()
-       G.CurPkgsrcdir = "."
-
        ChecklinesOptionsMk(mklines)
 
        t.CheckOutputLines(
-               "WARN: ~/category/package/options.mk:14: Unknown option \"undeclared\".",
-               "WARN: ~/category/package/options.mk:5: Option \"ncurses\" should be handled below in an .if block.",
-               "WARN: ~/category/package/options.mk:5: Option \"slang\" should be handled below in an .if block.",
+               "WARN: ~/category/package/options.mk:16: Unknown option \"undeclared\".",
+               "NOTE: ~/category/package/options.mk:19: The positive branch of the .if/.else should be the one where the option is set.",
                "WARN: ~/category/package/options.mk:6: Option \"mc-charset\" should be handled below in an .if block.",
-               "WARN: ~/category/package/options.mk:14: Option \"undeclared\" is handled but not declared above.")
+               "WARN: ~/category/package/options.mk:16: Option \"undeclared\" is handled but not added to PKG_SUPPORTED_OPTIONS.")
 }
 
 func (s *Suite) Test_ChecklinesOptionsMk__unexpected_line(c *check.C) {
@@ -68,11 +81,39 @@ func (s *Suite) Test_ChecklinesOptionsMk
                "pre-configure:",
                "\techo \"In the pre-configure stage.\"")
 
-       G.CurrentDir = t.TmpDir()
-       G.CurPkgsrcdir = "."
-
        ChecklinesOptionsMk(mklines)
 
        t.CheckOutputLines(
                "WARN: ~/category/package/options.mk:7: Expected inclusion of \"../../mk/bsd.options.mk\".")
 }
+
+func (s *Suite) Test_ChecklinesOptionsMk__malformed_conditional(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wno-space")
+       t.SetupVartypes()
+       t.SetupOption("mc-charset", "")
+       t.SetupOption("ncurses", "")
+       t.SetupOption("slang", "")
+       t.SetupOption("x11", "")
+
+       t.SetupFileMkLines("mk/bsd.options.mk",
+               MkRcsID)
+
+       mklines := t.SetupFileMkLines("category/package/options.mk",
+               MkRcsID,
+               "",
+               "PKG_OPTIONS_VAR=                PKG_OPTIONS.mc",
+               "PKG_SUPPORTED_OPTIONS=          # none",
+               "PKG_SUGGESTED_OPTIONS=          # none",
+               "",
+               ".include \"../../mk/bsd.options.mk\"",
+               "",
+               ".if ${OPSYS} == 'Darwin'",
+               ".endif")
+
+       ChecklinesOptionsMk(mklines)
+
+       t.CheckOutputLines(
+               "WARN: ~/category/package/options.mk:9: Invalid conditional \"${OPSYS} == 'Darwin'\".")
+}
Index: pkgsrc/pkgtools/pkglint/files/tools_test.go
diff -u pkgsrc/pkgtools/pkglint/files/tools_test.go:1.1 pkgsrc/pkgtools/pkglint/files/tools_test.go:1.2
--- pkgsrc/pkgtools/pkglint/files/tools_test.go:1.1     Sat Apr 28 23:32:52 2018
+++ pkgsrc/pkgtools/pkglint/files/tools_test.go Sun Aug 12 16:31:56 2018
@@ -12,8 +12,7 @@ func (s *Suite) Test_ToolRegistry_ParseT
                "",
                "USE_TOOLS.NetBSD+=\ttool1")
 
-       G.CurrentDir = t.TmpDir()
-       CheckdirToplevel()
+       CheckdirToplevel(t.File("."))
 
        // No error about "Unknown tool \"NetBSD\"."
        t.CheckOutputEmpty()

Index: pkgsrc/pkgtools/pkglint/files/autofix_test.go
diff -u pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.7 pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.8
--- pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.7   Thu Aug  9 20:08:12 2018
+++ pkgsrc/pkgtools/pkglint/files/autofix_test.go       Sun Aug 12 16:31:56 2018
@@ -113,13 +113,13 @@ func (s *Suite) Test_autofix_MkLines(c *
        t := s.Init(c)
 
        t.SetupCommandLine("--autofix")
-       t.SetupFileLines("Makefile",
+       t.SetupFileLines("category/basename/Makefile",
                "line1 := value1",
                "line2 := value2",
                "line3 := value3")
        pkg := NewPackage("category/basename")
        G.Pkg = pkg
-       mklines := pkg.loadPackageMakefile(t.TempFilename("Makefile"))
+       mklines := pkg.loadPackageMakefile()
        G.Pkg = nil
 
        fix := mklines.mklines[1].Autofix()
@@ -135,19 +135,19 @@ func (s *Suite) Test_autofix_MkLines(c *
        SaveAutofixChanges(mklines.lines)
 
        t.CheckOutputLines(
-               "AUTOFIX: ~/Makefile:2: Replacing \"lin\" with \"XXX\".",
-               "AUTOFIX: ~/Makefile:2: Replacing \"e2 \" with \"XXX\".",
-               "AUTOFIX: ~/Makefile:2: Replacing \":= \" with \"XXX\".",
-               "AUTOFIX: ~/Makefile:2: Replacing \"val\" with \"XXX\".",
-               "AUTOFIX: ~/Makefile:2: Replacing \"ue2\" with \"XXX\".",
-               "AUTOFIX: ~/Makefile:3: Replacing \"lin\" with \"XXX\".")
-       t.CheckFileLines("Makefile",
+               "AUTOFIX: ~/category/basename/Makefile:2: Replacing \"lin\" with \"XXX\".",
+               "AUTOFIX: ~/category/basename/Makefile:2: Replacing \"e2 \" with \"XXX\".",
+               "AUTOFIX: ~/category/basename/Makefile:2: Replacing \":= \" with \"XXX\".",
+               "AUTOFIX: ~/category/basename/Makefile:2: Replacing \"val\" with \"XXX\".",
+               "AUTOFIX: ~/category/basename/Makefile:2: Replacing \"ue2\" with \"XXX\".",
+               "AUTOFIX: ~/category/basename/Makefile:3: Replacing \"lin\" with \"XXX\".")
+       t.CheckFileLines("category/basename/Makefile",
                "line1 := value1",
                "XXXXXXXXXXXXXXX",
                "XXXe3 := value3")
 }
 
-func (s *Suite) Test_Autofix_multiple_modifications(c *check.C) {
+func (s *Suite) Test_Autofix__multiple_modifications(c *check.C) {
        t := s.Init(c)
 
        t.SetupCommandLine("--show-autofix", "--explain")
@@ -449,3 +449,32 @@ func (s *Suite) Test_Autofix_Explain(c *
                "WARN: Makefile:74: Please write row instead of line.")
        c.Check(G.explanationsAvailable, equals, true)
 }
+
+// Since the diagnostic doesn't contain the string "few", nothing happens.
+func (s *Suite) Test_Autofix__skip(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("--only", "few", "--autofix")
+
+       lines := t.SetupFileLines("fname",
+               "111 222 333 444 555")
+
+       fix := lines[0].Autofix()
+       fix.Warnf("Many.")
+       fix.Explain(
+               "Explanation.")
+       fix.Replace("111", "___")
+       fix.ReplaceAfter(" ", "222", "___")
+       fix.ReplaceRegex(`\d+`, "___", 1)
+       fix.InsertBefore("before")
+       fix.InsertAfter("after")
+       fix.Delete()
+       fix.Apply()
+
+       SaveAutofixChanges(lines)
+
+       t.CheckOutputEmpty()
+       t.CheckFileLines("fname",
+               "111 222 333 444 555")
+       c.Check(lines[0].raw[0].textnl, equals, "111 222 333 444 555\n")
+}

Index: pkgsrc/pkgtools/pkglint/files/buildlink3_test.go
diff -u pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.14 pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.15
--- pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.14       Thu Jul 12 16:23:36 2018
+++ pkgsrc/pkgtools/pkglint/files/buildlink3_test.go    Sun Aug 12 16:31:56 2018
@@ -6,7 +6,7 @@ func (s *Suite) Test_ChecklinesBuildlink
        t := s.Init(c)
 
        t.SetupVartypes()
-       mklines := t.NewMkLines("buildlink3.mk",
+       mklines := t.SetupFileMkLines("buildlink3.mk",
                MkRcsID,
                "# XXX This file was created automatically using createbuildlink-@PKGVERSION@",
                "",
@@ -28,10 +28,10 @@ func (s *Suite) Test_ChecklinesBuildlink
        ChecklinesBuildlink3Mk(mklines)
 
        t.CheckOutputLines(
-               "ERROR: buildlink3.mk:12: \"/x11/Xbae\" does not exist.",
-               "ERROR: buildlink3.mk:12: There is no package in \"x11/Xbae\".",
-               "ERROR: buildlink3.mk:14: \"/mk/motif.buildlink3.mk\" does not exist.",
-               "ERROR: buildlink3.mk:2: This comment indicates unfinished work (url2pkg).")
+               "ERROR: ~/buildlink3.mk:12: \"x11/Xbae\" does not exist.",
+               "ERROR: ~/buildlink3.mk:12: There is no package in \"x11/Xbae\".",
+               "ERROR: ~/buildlink3.mk:14: \"mk/motif.buildlink3.mk\" does not exist.",
+               "ERROR: ~/buildlink3.mk:2: This comment indicates unfinished work (url2pkg).")
 }
 
 // Before version 5.3, pkglint wrongly warned here.
@@ -315,7 +315,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 
        t.SetupCommandLine("-Wall")
        t.SetupVartypes()
-       mklines := t.NewMkLines("buildlink3.mk",
+       mklines := t.SetupFileMkLines("buildlink3.mk",
                MkRcsID,
                "",
                ".if ${VAAPI_AVAILABLE} == \"yes\"",
@@ -340,8 +340,8 @@ func (s *Suite) Test_ChecklinesBuildlink
 
        // No warning about the indentation of the .include lines.
        t.CheckOutputLines(
-               "ERROR: buildlink3.mk:11: \"/multimedia/libva\" does not exist.",
-               "ERROR: buildlink3.mk:11: There is no package in \"multimedia/libva\".",
-               "ERROR: buildlink3.mk:13: \"/x11/libX11/buildlink3.mk\" does not exist.",
-               "WARN: buildlink3.mk:3: Expected a BUILDLINK_TREE line.")
+               "ERROR: ~/buildlink3.mk:11: \"multimedia/libva\" does not exist.",
+               "ERROR: ~/buildlink3.mk:11: There is no package in \"multimedia/libva\".",
+               "ERROR: ~/buildlink3.mk:13: \"x11/libX11/buildlink3.mk\" does not exist.",
+               "WARN: ~/buildlink3.mk:3: Expected a BUILDLINK_TREE line.")
 }
Index: pkgsrc/pkgtools/pkglint/files/distinfo_test.go
diff -u pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.14 pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.15
--- pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.14 Sat May 19 12:58:25 2018
+++ pkgsrc/pkgtools/pkglint/files/distinfo_test.go      Sun Aug 12 16:31:56 2018
@@ -5,27 +5,28 @@ import "gopkg.in/check.v1"
 func (s *Suite) Test_ChecklinesDistinfo(c *check.C) {
        t := s.Init(c)
 
-       t.SetupFileLines("patches/patch-aa",
+       t.SetupFileLines("category/package/patches/patch-aa",
                "$"+"NetBSD$ line is ignored",
                "patch contents")
-       t.SetupFileLines("patches/patch-ab",
+       t.SetupFileLines("category/package/patches/patch-ab",
                "patch contents")
-       G.CurrentDir = t.TmpDir()
-
-       ChecklinesDistinfo(t.NewLines("distinfo",
+       lines := t.SetupFileLines("category/package/distinfo",
                "should be the RCS ID",
                "should be empty",
                "MD5 (distfile.tar.gz) = 12345678901234567890123456789012",
                "SHA1 (distfile.tar.gz) = 1234567890123456789012345678901234567890",
                "SHA1 (patch-aa) = 6b98dd609f85a9eb9c4c1e4e7055a6aaa62b7cc7",
                "SHA1 (patch-ab) = 6b98dd609f85a9eb9c4c1e4e7055a6aaa62b7cc7",
-               "SHA1 (patch-nonexistent) = 1234"))
+               "SHA1 (patch-nonexistent) = 1234")
+       G.Pkg = NewPackage("category/package")
+
+       ChecklinesDistinfo(lines)
 
        t.CheckOutputLines(
-               "ERROR: distinfo:1: Expected \"$"+"NetBSD$\".",
-               "NOTE: distinfo:2: Empty line expected.",
-               "ERROR: distinfo:5: Expected SHA1, RMD160, SHA512, Size checksums for \"distfile.tar.gz\", got MD5, SHA1.",
-               "WARN: distinfo:7: Patch file \"patch-nonexistent\" does not exist in directory \"patches\".")
+               "ERROR: ~/category/package/distinfo:1: Expected \"$"+"NetBSD$\".",
+               "NOTE: ~/category/package/distinfo:2: Empty line expected.",
+               "ERROR: ~/category/package/distinfo:5: Expected SHA1, RMD160, SHA512, Size checksums for \"distfile.tar.gz\", got MD5, SHA1.",
+               "WARN: ~/category/package/distinfo:7: Patch file \"patch-nonexistent\" does not exist in directory \"patches\".")
 }
 
 func (s *Suite) Test_ChecklinesDistinfo_global_hash_mismatch(c *check.C) {
@@ -48,7 +49,7 @@ func (s *Suite) Test_ChecklinesDistinfo_
 func (s *Suite) Test_ChecklinesDistinfo_uncommitted_patch(c *check.C) {
        t := s.Init(c)
 
-       t.SetupFileLines("patches/patch-aa",
+       t.SetupFileLines("category/package/patches/patch-aa",
                RcsID,
                "",
                "--- oldfile",
@@ -56,54 +57,122 @@ func (s *Suite) Test_ChecklinesDistinfo_
                "@@ -1,1 +1,1 @@",
                "-old",
                "+new")
-       t.SetupFileLines("CVS/Entries",
+       t.SetupFileLines("category/package/CVS/Entries",
                "/distinfo/...")
-       lines := t.SetupFileLines("distinfo",
+       lines := t.SetupFileLines("category/package/distinfo",
                RcsID,
                "",
                "SHA1 (patch-aa) = 5ad1fb9b3c328fff5caa1a23e8f330e707dd50c0")
-       G.CurrentDir = t.TmpDir()
+       G.Pkg = NewPackage("category/package")
 
        ChecklinesDistinfo(lines)
 
        t.CheckOutputLines(
-               "WARN: ~/distinfo:3: patches/patch-aa is registered in distinfo but not added to CVS.")
+               "WARN: ~/category/package/distinfo:3: patches/patch-aa is registered in distinfo but not added to CVS.")
 }
 
 func (s *Suite) Test_ChecklinesDistinfo_unrecorded_patches(c *check.C) {
        t := s.Init(c)
 
-       t.SetupFileLines("patches/CVS/Entries")
-       t.SetupFileLines("patches/patch-aa")
-       t.SetupFileLines("patches/patch-src-Makefile")
-       lines := t.SetupFileLines("distinfo",
+       t.SetupFileLines("category/package/patches/CVS/Entries")
+       t.SetupFileLines("category/package/patches/patch-aa")
+       t.SetupFileLines("category/package/patches/patch-src-Makefile")
+       lines := t.SetupFileLines("category/package/distinfo",
                RcsID,
                "",
                "SHA1 (distfile.tar.gz) = ...",
                "RMD160 (distfile.tar.gz) = ...",
                "SHA512 (distfile.tar.gz) = ...",
                "Size (distfile.tar.gz) = 1024 bytes")
-       G.CurrentDir = t.TmpDir()
+       G.Pkg = NewPackage("category/package")
 
        ChecklinesDistinfo(lines)
 
        t.CheckOutputLines(
-               "ERROR: ~/distinfo: patch \"patches/patch-aa\" is not recorded. Run \""+confMake+" makepatchsum\".",
-               "ERROR: ~/distinfo: patch \"patches/patch-src-Makefile\" is not recorded. Run \""+confMake+" makepatchsum\".")
+               "ERROR: ~/category/package/distinfo: patch \"patches/patch-aa\" is not recorded. Run \""+confMake+" makepatchsum\".",
+               "ERROR: ~/category/package/distinfo: patch \"patches/patch-src-Makefile\" is not recorded. Run \""+confMake+" makepatchsum\".")
 }
 
 func (s *Suite) Test_ChecklinesDistinfo_manual_patches(c *check.C) {
        t := s.Init(c)
 
-       t.SetupFileLines("patches/manual-libtool.m4")
+       t.CreateFileLines("patches/manual-libtool.m4")
        lines := t.SetupFileLines("distinfo",
                RcsID,
                "",
                "SHA1 (patch-aa) = ...")
-       G.CurrentDir = t.TmpDir()
 
        ChecklinesDistinfo(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.
+       t.CheckOutputEmpty()
+
+       G.Pkg = NewPackage(".")
+
+       ChecklinesDistinfo(lines)
+
+       // When a distinfo file is checked in the context of a package,
+       // the PATCHDIR is known, therefore the checks are active.
        t.CheckOutputLines(
                "WARN: ~/distinfo:3: Patch file \"patch-aa\" does not exist in directory \"patches\".")
 }
+
+// PHP modules that are not PECL use the distinfo file from lang/php* but
+// their own patches directory. Therefore the distinfo file refers to missing
+// patches. Since this strange situation is caused by the pkgsrc
+// infrastructure, there is nothing a package author can do about.
+func (s *Suite) Test_ChecklinesDistinfo__missing_php_patches(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupPkgsrc()
+       t.SetupCommandLine("-Wall,no-space")
+       t.CreateFileLines("licenses/unknown-license")
+       t.CreateFileLines("lang/php/ext.mk",
+               MkRcsID,
+               "",
+               "PHPEXT_MK=      # defined",
+               "PHPPKGSRCDIR=   lang/php72",
+               "LICENSE?=        unknown-license",
+               "COMMENT?=       Some PHP package",
+               "GENERATE_PLIST+=# none",
+               "",
+               ".if !defined(PECL_VERSION)",
+               "DISTINFO_FILE=  ${.CURDIR}/${PHPPKGSRCDIR}/distinfo",
+               ".endif",
+               ".if defined(USE_PHP_EXT_PATCHES)",
+               "PATCHDIR=       ${.CURDIR}/${PHPPKGSRCDIR}/patches",
+               ".endif")
+       t.CreateFileLines("lang/php72/patches/patch-php72",
+               RcsID,
+               "",
+               "Documentation",
+               "",
+               "--- old file",
+               "+++ new file",
+               "@@ -1,1 +1,1 @@",
+               "-old",
+               "+new")
+       t.SetupFileLines("lang/php72/distinfo",
+               RcsID,
+               "",
+               "SHA1 (patch-php72) = c109b2089f5ddbc5372b2ab28115ff558ee4187d")
+
+       t.CreateFileLines("archivers/php-bz2/Makefile",
+               MkRcsID,
+               "",
+               "USE_PHP_EXT_PATCHES=    yes",
+               "",
+               ".include \"../../lang/php/ext.mk\"",
+               ".include \"../../mk/bsd.pkg.mk\"")
+       t.CreateFileLines("archivers/php-zlib/Makefile",
+               MkRcsID,
+               "",
+               ".include \"../../lang/php/ext.mk\"",
+               ".include \"../../mk/bsd.pkg.mk\"")
+
+       G.CheckDirent(t.File("archivers/php-bz2"))
+       G.CheckDirent(t.File("archivers/php-zlib"))
+
+       t.CheckOutputEmpty()
+}

Index: pkgsrc/pkgtools/pkglint/files/category.go
diff -u pkgsrc/pkgtools/pkglint/files/category.go:1.12 pkgsrc/pkgtools/pkglint/files/category.go:1.13
--- pkgsrc/pkgtools/pkglint/files/category.go:1.12      Thu Aug  9 20:21:42 2018
+++ pkgsrc/pkgtools/pkglint/files/category.go   Sun Aug 12 16:31:56 2018
@@ -5,12 +5,12 @@ import (
        "sort"
 )
 
-func CheckdirCategory() {
+func CheckdirCategory(dir string) {
        if trace.Tracing {
-               defer trace.Call1(G.CurrentDir)()
+               defer trace.Call1(dir)()
        }
 
-       lines := LoadNonemptyLines(G.CurrentDir+"/Makefile", true)
+       lines := LoadNonemptyLines(dir+"/Makefile", true)
        if lines == nil {
                return
        }
@@ -40,7 +40,7 @@ func CheckdirCategory() {
        // the (hopefully) sorted list of SUBDIRs. The first step is to
        // collect the SUBDIRs in the Makefile and in the file system.
 
-       fSubdirs := getSubdirs(G.CurrentDir)
+       fSubdirs := getSubdirs(dir)
        sort.Strings(fSubdirs)
        var mSubdirs []subdir
 
@@ -147,7 +147,7 @@ func CheckdirCategory() {
                        fNeednext = true
                        mNeednext = true
                        if mActive {
-                               subdirs = append(subdirs, G.CurrentDir+"/"+mCurrent)
+                               subdirs = append(subdirs, dir+"/"+mCurrent)
                        }
                }
        }
Index: pkgsrc/pkgtools/pkglint/files/licenses.go
diff -u pkgsrc/pkgtools/pkglint/files/licenses.go:1.12 pkgsrc/pkgtools/pkglint/files/licenses.go:1.13
--- pkgsrc/pkgtools/pkglint/files/licenses.go:1.12      Sat May 19 12:58:25 2018
+++ pkgsrc/pkgtools/pkglint/files/licenses.go   Sun Aug 12 16:31:56 2018
@@ -48,7 +48,7 @@ func (lc *LicenseChecker) checkLicenseNa
        var licenseFile string
        if G.Pkg != nil {
                if licenseFileValue, ok := G.Pkg.varValue("LICENSE_FILE"); ok {
-                       licenseFile = G.CurrentDir + "/" + lc.MkLine.ResolveVarsInRelativePath(licenseFileValue, false)
+                       licenseFile = G.Pkg.File(lc.MkLine.ResolveVarsInRelativePath(licenseFileValue, false))
                }
        }
        if licenseFile == "" {
Index: pkgsrc/pkgtools/pkglint/files/licenses_test.go
diff -u pkgsrc/pkgtools/pkglint/files/licenses_test.go:1.12 pkgsrc/pkgtools/pkglint/files/licenses_test.go:1.13
--- pkgsrc/pkgtools/pkglint/files/licenses_test.go:1.12 Sat Apr 28 23:32:52 2018
+++ pkgsrc/pkgtools/pkglint/files/licenses_test.go      Sun Aug 12 16:31:56 2018
@@ -10,7 +10,6 @@ func (s *Suite) Test_checklineLicense(c 
        t.SetupFileLines("licenses/gnu-gpl-v2",
                "Most software \u2026")
        mkline := t.NewMkLine("Makefile", 7, "LICENSE=dummy")
-       G.CurrentDir = t.TmpDir()
 
        licenseChecker := &LicenseChecker{mkline}
        licenseChecker.Check("gpl-v2", opAssign)
@@ -85,7 +84,7 @@ func (s *Suite) Test_checkToplevelUnused
                PlistRcsID,
                "bin/program")
 
-       G.Main("pkglint", "-r", "-Cglobal", t.TmpDir())
+       G.Main("pkglint", "-r", "-Cglobal", t.File("."))
 
        t.CheckOutputLines(
                "WARN: ~/licenses/gnu-gpl-v3: This license seems to be unused.",

Index: pkgsrc/pkgtools/pkglint/files/category_test.go
diff -u pkgsrc/pkgtools/pkglint/files/category_test.go:1.10 pkgsrc/pkgtools/pkglint/files/category_test.go:1.11
--- pkgsrc/pkgtools/pkglint/files/category_test.go:1.10 Thu Jul 12 16:23:36 2018
+++ pkgsrc/pkgtools/pkglint/files/category_test.go      Sun Aug 12 16:31:56 2018
@@ -13,9 +13,8 @@ func (s *Suite) Test_CheckdirCategory_to
                "SUBDIR-=unknown #doesn\u2019t work",
                "",
                ".include \"../mk/category.mk\"")
-       G.CurrentDir = t.TempFilename("archivers")
 
-       CheckdirCategory()
+       CheckdirCategory(t.File("archivers"))
 
        t.CheckOutputLines(
                "ERROR: ~/archivers/Makefile:1: Expected \"# $"+"NetBSD$\".",
@@ -48,10 +47,8 @@ func (s *Suite) Test_CheckdirCategory_in
                "# dummy")
        t.SetupFileLines("mk/misc/category.mk",
                "# dummy")
-       G.CurrentDir = t.TempFilename("archivers")
-       G.CurPkgsrcdir = ".."
 
-       CheckdirCategory()
+       CheckdirCategory(t.File("archivers"))
 
        t.CheckOutputLines(
                "WARN: ~/archivers/Makefile:2: COMMENT contains invalid characters (U+005C U+0024 U+0024 U+0024 U+0024 U+0022).")
Index: pkgsrc/pkgtools/pkglint/files/util_test.go
diff -u pkgsrc/pkgtools/pkglint/files/util_test.go:1.10 pkgsrc/pkgtools/pkglint/files/util_test.go:1.11
--- pkgsrc/pkgtools/pkglint/files/util_test.go:1.10     Sat Apr 28 23:32:52 2018
+++ pkgsrc/pkgtools/pkglint/files/util_test.go  Sun Aug 12 16:31:56 2018
@@ -7,6 +7,12 @@ import (
        "testing"
 )
 
+func (s *Suite) Test_YesNoUnknown_String(c *check.C) {
+       c.Check(yes.String(), equals, "yes")
+       c.Check(no.String(), equals, "no")
+       c.Check(unknown.String(), equals, "unknown")
+}
+
 func (s *Suite) Test_MkopSubst__middle(c *check.C) {
        c.Check(mkopSubst("pkgname", false, "kgna", false, "ri", ""), equals, "prime")
        c.Check(mkopSubst("pkgname", false, "pkgname", false, "replacement", ""), equals, "replacement")
@@ -68,20 +74,22 @@ func (s *Suite) Test_isEmptyDir_and_getS
        t.SetupFileLines("CVS/Entries",
                "dummy")
 
-       c.Check(isEmptyDir(t.TmpDir()), equals, true)
-       c.Check(getSubdirs(t.TmpDir()), check.DeepEquals, []string(nil))
+       if dir := t.File("."); true {
+               c.Check(isEmptyDir(dir), equals, true)
+               c.Check(getSubdirs(dir), check.DeepEquals, []string(nil))
 
-       t.SetupFileLines("somedir/file")
+               t.SetupFileLines("somedir/file")
 
-       c.Check(isEmptyDir(t.TmpDir()), equals, false)
-       c.Check(getSubdirs(t.TmpDir()), check.DeepEquals, []string{"somedir"})
+               c.Check(isEmptyDir(dir), equals, false)
+               c.Check(getSubdirs(dir), check.DeepEquals, []string{"somedir"})
+       }
 
-       if nodir := t.TmpDir() + "/nonexistent"; true {
-               c.Check(isEmptyDir(nodir), equals, true) // Counts as empty.
+       if absent := t.File("nonexistent"); true {
+               c.Check(isEmptyDir(absent), equals, true) // Counts as empty.
                defer t.ExpectFatalError(func() {
                        c.Check(t.Output(), check.Matches, `FATAL: (.+): Cannot be read: open (.+): (.+)\n`)
                })
-               c.Check(getSubdirs(nodir), check.DeepEquals, []string(nil))
+               getSubdirs(absent) // Panics with a pkglintFatal.
                c.FailNow()
        }
 }

Index: pkgsrc/pkgtools/pkglint/files/check_test.go
diff -u pkgsrc/pkgtools/pkglint/files/check_test.go:1.22 pkgsrc/pkgtools/pkglint/files/check_test.go:1.23
--- pkgsrc/pkgtools/pkglint/files/check_test.go:1.22    Thu Aug  9 20:08:12 2018
+++ pkgsrc/pkgtools/pkglint/files/check_test.go Sun Aug 12 16:31:56 2018
@@ -51,7 +51,7 @@ func (s *Suite) SetUpTest(c *check.C) {
        G.logOut = NewSeparatorWriter(&t.stdout)
        G.logErr = NewSeparatorWriter(&t.stderr)
        trace.Out = &t.stdout
-       G.Pkgsrc = NewPkgsrc(t.TmpDir())
+       G.Pkgsrc = NewPkgsrc(t.File("."))
 
        t.checkC = c
        t.SetupCommandLine( /* no arguments */ )
@@ -160,13 +160,57 @@ func (t *Tester) SetupFileMkLines(relati
        return NewMkLines(plainLines)
 }
 
+// SetupPkgsrc sets up a minimal but complete pkgsrc installation in the
+// temporary folder, so that pkglint runs without any errors.
+// Individual files may be overwritten by calling other Setup* methods.
+// This setup is especially interesting for testing Pkglint.Main.
+func (t *Tester) SetupPkgsrc() {
+
+       // This file is needed to locate the pkgsrc root directory.
+       // See findPkgsrcTopdir.
+       t.CreateFileLines("mk/bsd.pkg.mk",
+               MkRcsID)
+
+       // See Pkgsrc.loadDocChanges.
+       t.CreateFileLines("doc/CHANGES-2018",
+               RcsID)
+
+       // See Pkgsrc.loadSuggestedUpdates.
+       t.CreateFileLines("doc/TODO",
+               RcsID)
+
+       // The MASTER_SITES in the package Makefile are searched here.
+       // See Pkgsrc.loadMasterSites.
+       t.CreateFileLines("mk/fetch/sites.mk",
+               MkRcsID)
+
+       // The options for the PKG_OPTIONS framework must be readable.
+       // See Pkgsrc.loadPkgOptions.
+       t.CreateFileLines("mk/defaults/options.description")
+
+       // The user-defined variables are read in to check for missing
+       // BUILD_DEFS declarations in the package Makefile.
+       t.CreateFileLines("mk/defaults/mk.conf",
+               MkRcsID)
+
+       // The tool definitions are read in to check for missing
+       // USE_TOOLS declarations in the package Makefile.
+       // They spread over several files from the pkgsrc infrastructure.
+       t.CreateFileLines("mk/tools/bsd.tools.mk",
+               ".include \"defaults.mk\"")
+       t.CreateFileLines("mk/tools/defaults.mk",
+               MkRcsID)
+       t.CreateFileLines("mk/bsd.prefs.mk", // Some tools are defined here.
+               MkRcsID)
+}
+
 func (t *Tester) CreateFileLines(relativeFilename string, lines ...string) (filename string) {
        content := ""
        for _, line := range lines {
                content += line + "\n"
        }
 
-       filename = t.TempFilename(relativeFilename)
+       filename = t.File(relativeFilename)
        err := os.MkdirAll(path.Dir(filename), 0777)
        t.c().Assert(err, check.IsNil)
 
@@ -176,31 +220,23 @@ func (t *Tester) CreateFileLines(relativ
        return filename
 }
 
-func (t *Tester) LoadTmpFile(relFname string) (absFname string) {
-       bytes, err := ioutil.ReadFile(t.TmpDir() + "/" + relFname)
-       t.c().Assert(err, check.IsNil)
-       return string(bytes)
-}
-
-func (t *Tester) TmpDir() string {
+// File returns the absolute path to the given file in the
+// temporary directory. It doesn't check whether that file exists.
+func (t *Tester) File(relativeFilename string) string {
        if t.tmpdir == "" {
                t.tmpdir = filepath.ToSlash(t.c().MkDir())
        }
-       return t.tmpdir
-}
-
-// TempFilename returns the absolute path to the given file in the
-// temporary directory. It doesn't check whether that file exists.
-func (t *Tester) TempFilename(relativeFilename string) string {
-       return t.TmpDir() + "/" + relativeFilename
+       return t.tmpdir + "/" + relativeFilename
 }
 
+// ExpectFatalError, when run in a defer statement, runs the action
+// if the current function panics with a pkglintFatal
+// (typically from line.Fatalf).
 func (t *Tester) ExpectFatalError(action func()) {
-       if r := recover(); r != nil {
-               if _, ok := r.(pkglintFatal); ok {
-                       action()
-                       return
-               }
+       r := recover()
+       if _, ok := r.(pkglintFatal); ok {
+               action()
+       } else {
                panic(r)
        }
 }
@@ -319,7 +355,9 @@ func (t *Tester) DisableTracing() {
 // CheckFileLines loads the lines from the temporary file and checks that
 // they equal the given lines.
 func (t *Tester) CheckFileLines(relativeFileName string, lines ...string) {
-       text := t.LoadTmpFile(relativeFileName)
+       content, err := ioutil.ReadFile(t.File(relativeFileName))
+       t.c().Assert(err, check.IsNil)
+       text := string(content)
        actualLines := strings.Split(text, "\n")
        actualLines = actualLines[:len(actualLines)-1]
        t.c().Check(emptyToNil(actualLines), deepEquals, emptyToNil(lines))
@@ -330,7 +368,7 @@ func (t *Tester) CheckFileLines(relative
 // for indentation, while the lines in the code use spaces exclusively,
 // in order to make the depth of the indentation clearly visible.
 func (t *Tester) CheckFileLinesDetab(relativeFileName string, lines ...string) {
-       actualLines, err := readLines(t.TempFilename(relativeFileName), false)
+       actualLines, err := readLines(t.File(relativeFileName), false)
        if !t.c().Check(err, check.IsNil) {
                return
        }

Index: pkgsrc/pkgtools/pkglint/files/distinfo.go
diff -u pkgsrc/pkgtools/pkglint/files/distinfo.go:1.20 pkgsrc/pkgtools/pkglint/files/distinfo.go:1.21
--- pkgsrc/pkgtools/pkglint/files/distinfo.go:1.20      Sat May 19 12:58:25 2018
+++ pkgsrc/pkgtools/pkglint/files/distinfo.go   Sun Aug 12 16:31:56 2018
@@ -6,6 +6,7 @@ import (
        "fmt"
        "io/ioutil"
        "netbsd.org/pkglint/trace"
+       "path"
        "strings"
 )
 
@@ -15,25 +16,18 @@ func ChecklinesDistinfo(lines []Line) {
        }
 
        fname := lines[0].Filename
-       patchesDir := "patches"
-       patchesDirSet := false
-       if G.Pkg != nil && contains(fname, "lang/php") {
-               phpdir := G.Pkgsrc.Latest("lang", `^php[0-9]+$`, "/lang/$0")
-               if hasSuffix(fname, phpdir+"/distinfo") {
-                       patchesDir = G.CurPkgsrcdir + phpdir + "/patches"
-                       patchesDirSet = true
-               }
-       }
-       if G.Pkg != nil && !patchesDirSet && dirExists(G.CurrentDir+"/"+G.Pkg.Patchdir) {
-               patchesDir = G.Pkg.Patchdir
+       patchdir := "patches"
+       if G.Pkg != nil && dirExists(G.Pkg.File(G.Pkg.Patchdir)) {
+               patchdir = G.Pkg.Patchdir
        }
        if trace.Tracing {
-               trace.Step1("patchesDir=%q", patchesDir)
+               trace.Step1("patchdir=%q", patchdir)
        }
 
+       distinfoIsCommitted := isCommitted(fname)
        ck := &distinfoLinesChecker{
-               fname, patchesDir, isCommitted(fname),
-               make(map[string]bool), nil, "", false, nil}
+               fname, patchdir, distinfoIsCommitted,
+               make(map[string]bool), nil, "", unknown, nil}
        ck.checkLines(lines)
        ChecklinesTrailingEmptyLines(lines)
        ck.checkUnrecordedPatches()
@@ -42,13 +36,13 @@ func ChecklinesDistinfo(lines []Line) {
 
 type distinfoLinesChecker struct {
        distinfoFilename    string
-       patchdir            string // Relative to G.currentDir
+       patchdir            string // Relative to G.Pkg
        distinfoIsCommitted bool
 
        patches          map[string]bool // "patch-aa" => true
        currentFirstLine Line
        currentFilename  string
-       isPatch          bool
+       isPatch          YesNoUnknown
        algorithms       []string
 }
 
@@ -83,12 +77,15 @@ func (ck *distinfoLinesChecker) onFilena
        currentFname := ck.currentFilename
        if currentFname != "" {
                algorithms := strings.Join(ck.algorithms, ", ")
-               if ck.isPatch {
+               if ck.isPatch == yes {
                        if algorithms != "SHA1" {
                                line.Errorf("Expected SHA1 hash for %s, got %s.", currentFname, algorithms)
                        }
+               } else if ck.isPatch == unknown {
+               } else if G.Pkg != nil && G.Pkg.IgnoreMissingPatches {
                } else if hasPrefix(currentFname, "patch-") && algorithms == "SHA1" {
-                       ck.currentFirstLine.Warnf("Patch file %q does not exist in directory %q.", currentFname, cleanpath(ck.patchdir))
+                       pathToPatchdir := relpath(path.Dir(ck.currentFirstLine.Filename), G.Pkg.File(ck.patchdir))
+                       ck.currentFirstLine.Warnf("Patch file %q does not exist in directory %q.", currentFname, pathToPatchdir)
                        Explain(
                                "If the patches directory looks correct, the patch may have been",
                                "removed without updating the distinfo file.  In such a case please",
@@ -100,46 +97,26 @@ func (ck *distinfoLinesChecker) onFilena
                }
        }
 
-       ck.isPatch = hasPrefix(nextFname, "patch-") && fileExists(G.CurrentDir+"/"+ck.patchdir+"/"+nextFname)
+       if !hasPrefix(nextFname, "patch-") {
+               ck.isPatch = no
+       } else if G.Pkg == nil {
+               ck.isPatch = unknown
+       } else if fileExists(G.Pkg.File(ck.patchdir + "/" + nextFname)) {
+               ck.isPatch = yes
+       } else {
+               ck.isPatch = no
+       }
+
        ck.currentFilename = nextFname
        ck.currentFirstLine = line
        ck.algorithms = nil
 }
 
-// Same as in mk/checksum/distinfo.awk:/function patchsum/
-func computePatchSha1Hex(patchFilename string) (string, error) {
-       patchBytes, err := ioutil.ReadFile(patchFilename)
-       if err != nil {
-               return "", err
-       }
-
-       hash := sha1.New()
-       netbsd := []byte("$" + "NetBSD")
-       for _, patchLine := range bytes.SplitAfter(patchBytes, []byte("\n")) {
-               if !bytes.Contains(patchLine, netbsd) {
-                       hash.Write(patchLine)
-               }
-       }
-       return fmt.Sprintf("%x", hash.Sum(nil)), nil
-}
-
-func (ck *distinfoLinesChecker) checkPatchSha1(line Line, patchFname, distinfoSha1Hex string) {
-       fileSha1Hex, err := computePatchSha1Hex(G.CurrentDir + "/" + patchFname)
-       if err != nil {
-               line.Errorf("%s does not exist.", patchFname)
+func (ck *distinfoLinesChecker) checkUnrecordedPatches() {
+       if G.Pkg == nil {
                return
        }
-       if distinfoSha1Hex != fileSha1Hex {
-               fix := line.Autofix()
-               fix.Errorf("%s hash of %s differs (distinfo has %s, patch file has %s). Run \"%s makepatchsum\".",
-                       "SHA1", patchFname, distinfoSha1Hex, fileSha1Hex, confMake)
-               fix.Replace(distinfoSha1Hex, fileSha1Hex)
-               fix.Apply()
-       }
-}
-
-func (ck *distinfoLinesChecker) checkUnrecordedPatches() {
-       files, err := ioutil.ReadDir(G.CurrentDir + "/" + ck.patchdir)
+       files, err := ioutil.ReadDir(G.Pkg.File(ck.patchdir))
        if err != nil {
                if trace.Tracing {
                        trace.Stepf("Cannot read patchesDir %q: %s", ck.patchdir, err)
@@ -173,9 +150,9 @@ func (ck *distinfoLinesChecker) checkGlo
 }
 
 func (ck *distinfoLinesChecker) checkUncommittedPatch(line Line, patchName, sha1Hash string) {
-       if ck.isPatch {
+       if ck.isPatch == yes {
                patchFname := ck.patchdir + "/" + patchName
-               if ck.distinfoIsCommitted && !isCommitted(G.CurrentDir+"/"+patchFname) {
+               if ck.distinfoIsCommitted && !isCommitted(G.Pkg.File(patchFname)) {
                        line.Warnf("%s is registered in distinfo but not added to CVS.", patchFname)
                }
                ck.checkPatchSha1(line, patchFname, sha1Hash)
@@ -183,8 +160,40 @@ func (ck *distinfoLinesChecker) checkUnc
        }
 }
 
+func (ck *distinfoLinesChecker) checkPatchSha1(line Line, patchFname, distinfoSha1Hex string) {
+       fileSha1Hex, err := computePatchSha1Hex(G.Pkg.File(patchFname))
+       if err != nil {
+               line.Errorf("%s does not exist.", patchFname)
+               return
+       }
+       if distinfoSha1Hex != fileSha1Hex {
+               fix := line.Autofix()
+               fix.Errorf("%s hash of %s differs (distinfo has %s, patch file has %s). Run \"%s makepatchsum\".",
+                       "SHA1", patchFname, distinfoSha1Hex, fileSha1Hex, confMake)
+               fix.Replace(distinfoSha1Hex, fileSha1Hex)
+               fix.Apply()
+       }
+}
+
+// Same as in mk/checksum/distinfo.awk:/function patchsum/
+func computePatchSha1Hex(patchFilename string) (string, error) {
+       patchBytes, err := ioutil.ReadFile(patchFilename)
+       if err != nil {
+               return "", err
+       }
+
+       hash := sha1.New()
+       netbsd := []byte("$" + "NetBSD")
+       for _, patchLine := range bytes.SplitAfter(patchBytes, []byte("\n")) {
+               if !bytes.Contains(patchLine, netbsd) {
+                       hash.Write(patchLine)
+               }
+       }
+       return fmt.Sprintf("%x", hash.Sum(nil)), nil
+}
+
 func AutofixDistinfo(oldSha1, newSha1 string) {
-       distinfoFilename := G.CurrentDir + "/" + G.Pkg.DistinfoFile
+       distinfoFilename := G.Pkg.File(G.Pkg.DistinfoFile)
        if lines, err := readLines(distinfoFilename, false); err == nil {
                for _, line := range lines {
                        fix := line.Autofix()

Index: pkgsrc/pkgtools/pkglint/files/files.go
diff -u pkgsrc/pkgtools/pkglint/files/files.go:1.16 pkgsrc/pkgtools/pkglint/files/files.go:1.17
--- pkgsrc/pkgtools/pkglint/files/files.go:1.16 Mon Feb 19 12:40:38 2018
+++ pkgsrc/pkgtools/pkglint/files/files.go      Sun Aug 12 16:31:56 2018
@@ -2,6 +2,7 @@ package main
 
 import (
        "io/ioutil"
+       "path"
        "strings"
 )
 
@@ -111,6 +112,9 @@ func readLines(fname string, joinBacksla
                return nil, err
        }
 
+       if G.opts.Profiling {
+               G.loaded.Add(path.Clean(fname), 1)
+       }
        return convertToLogicalLines(fname, string(rawText), joinBackslashLines), nil
 }
 

Index: pkgsrc/pkgtools/pkglint/files/mkline.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline.go:1.34 pkgsrc/pkgtools/pkglint/files/mkline.go:1.35
--- pkgsrc/pkgtools/pkglint/files/mkline.go:1.34        Sat Jul 28 20:44:45 2018
+++ pkgsrc/pkgtools/pkglint/files/mkline.go     Sun Aug 12 16:31:56 2018
@@ -6,6 +6,7 @@ import (
        "fmt"
        "netbsd.org/pkglint/regex"
        "netbsd.org/pkglint/trace"
+       "path"
        "strings"
 )
 
@@ -39,6 +40,7 @@ type mkLineConditional struct {
        directive string
        args      string
        comment   string
+       elseLine  MkLine // (filled in later)
 }
 type mkLineInclude struct {
        mustExist     bool
@@ -114,7 +116,7 @@ func NewMkLine(line Line) *MkLineImpl {
        }
 
        if m, indent, directive, args, comment := matchMkCond(text); m {
-               return &MkLineImpl{line, mkLineConditional{indent, directive, args, comment}}
+               return &MkLineImpl{line, mkLineConditional{indent, directive, args, comment, nil}}
        }
 
        if m, indent, directive, includefile := MatchMkInclude(text); m {
@@ -246,6 +248,12 @@ func (mkline *MkLineImpl) Args() string 
 
 // CondComment is the trailing end-of-line comment, typically at a deeply nested .endif or .endfor.
 func (mkline *MkLineImpl) CondComment() string { return mkline.data.(mkLineConditional).comment }
+func (mkline *MkLineImpl) HasElseBranch() bool { return mkline.data.(mkLineConditional).elseLine != nil }
+func (mkline *MkLineImpl) SetHasElseBranch(elseLine MkLine) {
+       data := mkline.data.(mkLineConditional)
+       data.elseLine = elseLine
+       mkline.data = data
+}
 
 func (mkline *MkLineImpl) MustExist() bool     { return mkline.data.(mkLineInclude).mustExist }
 func (mkline *MkLineImpl) IncludeFile() string { return mkline.data.(mkLineInclude).includeFile }
@@ -263,6 +271,11 @@ func (mkline *MkLineImpl) SetConditionVa
        mkline.data = include
 }
 
+// Tokenize extracts variable uses and other text from the string.
+//
+// Example:
+//  input:  ${PREFIX}/bin abc
+//  output: [MkToken("${PREFIX}", MkVarUse("PREFIX")), MkToken("/bin abc")]
 func (mkline *MkLineImpl) Tokenize(s string) []*MkToken {
        if trace.Tracing {
                defer trace.Call(mkline, s)()
@@ -280,6 +293,8 @@ func (mkline *MkLineImpl) Tokenize(s str
 // taking care of variable references. For example, when the value
 // "/bin:${PATH:S,::,::,}" is split at ":", it results in
 // {"/bin", "${PATH:S,::,::,}"}.
+//
+// If the separator is empty, splitting is done on whitespace.
 func (mkline *MkLineImpl) ValueSplit(value string, separator string) []string {
        tokens := mkline.Tokenize(value)
        var split []string
@@ -288,7 +303,12 @@ func (mkline *MkLineImpl) ValueSplit(val
                        split = []string{""}
                }
                if token.Varuse == nil && contains(token.Text, separator) {
-                       subs := strings.Split(token.Text, separator)
+                       var subs []string
+                       if separator == "" {
+                               subs = splitOnSpace(token.Text)
+                       } else {
+                               subs = strings.Split(token.Text, separator)
+                       }
                        split[len(split)-1] += subs[0]
                        split = append(split, subs[1:]...)
                } else {
@@ -310,9 +330,18 @@ func (mkline *MkLineImpl) WithoutMakeVar
        }
 }
 
-func (mkline *MkLineImpl) ResolveVarsInRelativePath(relpath string, adjustDepth bool) string {
-       tmp := relpath
-       tmp = strings.Replace(tmp, "${PKGSRCDIR}", G.CurPkgsrcdir, -1)
+func (mkline *MkLineImpl) ResolveVarsInRelativePath(relativePath string, adjustDepth bool) string {
+
+       var basedir string
+       if G.Pkg != nil {
+               basedir = G.Pkg.File(".")
+       } else {
+               basedir = path.Dir(mkline.Filename)
+       }
+       pkgsrcdir := relpath(basedir, G.Pkgsrc.File("."))
+
+       tmp := relativePath
+       tmp = strings.Replace(tmp, "${PKGSRCDIR}", pkgsrcdir, -1)
        tmp = strings.Replace(tmp, "${.CURDIR}", ".", -1)
        tmp = strings.Replace(tmp, "${.PARSEDIR}", ".", -1)
        if contains(tmp, "${LUA_PKGSRCDIR}") {
@@ -338,35 +367,36 @@ func (mkline *MkLineImpl) ResolveVarsInR
 
        if adjustDepth {
                if m, pkgpath := match1(tmp, `^\.\./\.\./([^.].*)$`); m {
-                       tmp = G.CurPkgsrcdir + "/" + pkgpath
+                       tmp = pkgsrcdir + "/" + pkgpath
                }
        }
 
+       tmp = cleanpath(tmp)
+
        if trace.Tracing {
-               trace.Step2("resolveVarsInRelativePath: %q => %q", relpath, tmp)
+               trace.Step2("resolveVarsInRelativePath: %q => %q", relativePath, tmp)
        }
        return tmp
 }
 
-func (ind *Indentation) RememberUsedVariables(cond *Tree) {
-       arg0varname := func(node *Tree) {
-               varname := node.args[0].(string)
-               ind.AddVar(varname)
-       }
-       arg0varuse := func(node *Tree) {
-               varuse := node.args[0].(MkVarUse)
-               ind.AddVar(varuse.varname)
-       }
-       arg2varuse := func(node *Tree) {
-               varuse := node.args[2].(MkVarUse)
-               ind.AddVar(varuse.varname)
-       }
-       cond.Visit("defined", arg0varname)
-       cond.Visit("empty", arg0varuse)
-       cond.Visit("compareVarNum", arg0varuse)
-       cond.Visit("compareVarStr", arg0varuse)
-       cond.Visit("compareVarVar", arg0varuse)
-       cond.Visit("compareVarVar", arg2varuse)
+func (ind *Indentation) RememberUsedVariables(cond MkCond) {
+       NewMkCondWalker().Walk(cond, &MkCondCallback{
+               Defined: func(varname string) {
+                       ind.AddVar(varname)
+               },
+               Empty: func(varuse *MkVarUse) {
+                       ind.AddVar(varuse.varname)
+               },
+               CompareVarNum: func(varuse *MkVarUse, op string, num string) {
+                       ind.AddVar(varuse.varname)
+               },
+               CompareVarStr: func(varuse *MkVarUse, op string, str string) {
+                       ind.AddVar(varuse.varname)
+               },
+               CompareVarVar: func(left *MkVarUse, op string, right *MkVarUse) {
+                       ind.AddVar(left.varname)
+                       ind.AddVar(right.varname)
+               }})
 }
 
 func (mkline *MkLineImpl) ExplainRelativeDirs() {
@@ -777,11 +807,23 @@ type Indentation struct {
 
 func NewIndentation() *Indentation {
        ind := &Indentation{}
-       ind.Push(0, "") // Dummy
+       ind.Push(nil, 0, "") // Dummy
        return ind
 }
 
+func (ind *Indentation) String() string {
+       s := ""
+       for _, level := range ind.levels[1:] {
+               s += fmt.Sprintf(" %d", level.depth)
+               if len(level.conditionVars) != 0 {
+                       s += " (" + strings.Join(level.conditionVars, " ") + ")"
+               }
+       }
+       return "[" + strings.TrimSpace(s) + "]"
+}
+
 type indentationLevel struct {
+       mkline        MkLine   // The line in which the indentation started; the .if/.for
        depth         int      // Number of space characters; always a multiple of 2
        condition     string   // The corresponding condition from the .if or .elif
        conditionVars []string // Variables on which the current path depends
@@ -814,8 +856,8 @@ func (ind *Indentation) Pop() {
        ind.levels = ind.levels[:ind.Len()-1]
 }
 
-func (ind *Indentation) Push(indent int, condition string) {
-       ind.levels = append(ind.levels, indentationLevel{indent, condition, nil, nil})
+func (ind *Indentation) Push(mkline MkLine, indent int, condition string) {
+       ind.levels = append(ind.levels, indentationLevel{mkline, indent, condition, nil, nil})
 }
 
 func (ind *Indentation) AddVar(varname string) {
@@ -894,7 +936,7 @@ func (ind *Indentation) TrackBefore(mkli
                return
        }
        if trace.Tracing {
-               trace.Stepf("Indentation before line %s: %+v", mkline.Linenos(), ind.levels)
+               trace.Stepf("Indentation before line %s: %s", mkline.Linenos(), ind)
        }
 
        directive := mkline.Directive()
@@ -902,7 +944,7 @@ func (ind *Indentation) TrackBefore(mkli
 
        switch directive {
        case "for", "if", "ifdef", "ifndef":
-               ind.Push(ind.top().depth, args)
+               ind.Push(mkline, ind.top().depth, args)
        }
 }
 
@@ -928,9 +970,14 @@ func (ind *Indentation) TrackAfter(mklin
 
                if contains(args, "exists") {
                        cond := NewMkParser(mkline.Line, args, false).MkCond()
-                       cond.Visit("exists", func(node *Tree) {
-                               ind.AddCheckedFile(node.args[0].(string))
-                       })
+                       if cond != nil {
+                               NewMkCondWalker().Walk(cond, &MkCondCallback{
+                                       Call: func(name string, arg string) {
+                                               if name == "exists" {
+                                                       ind.AddCheckedFile(arg)
+                                               }
+                                       }})
+                       }
                }
 
        case "for", "ifdef", "ifndef":
@@ -940,6 +987,12 @@ func (ind *Indentation) TrackAfter(mklin
                // Handled here instead of TrackAfter to allow the action to access the previous condition.
                ind.top().condition = args
 
+       case "else":
+               top := ind.top()
+               if top.mkline != nil {
+                       top.mkline.SetHasElseBranch(mkline)
+               }
+
        case "endfor", "endif":
                if ind.Len() > 1 { // Can only be false in unbalanced files.
                        ind.Pop()
@@ -947,7 +1000,7 @@ func (ind *Indentation) TrackAfter(mklin
        }
 
        if trace.Tracing {
-               trace.Stepf("Indentation after line %s: %+v", mkline.Linenos(), ind.levels)
+               trace.Stepf("Indentation after line %s: %s", mkline.Linenos(), ind)
        }
 }
 
Index: pkgsrc/pkgtools/pkglint/files/pkglint.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint.go:1.34 pkgsrc/pkgtools/pkglint/files/pkglint.go:1.35
--- pkgsrc/pkgtools/pkglint/files/pkglint.go:1.34       Thu Aug  9 20:08:12 2018
+++ pkgsrc/pkgtools/pkglint/files/pkglint.go    Sun Aug 12 16:31:56 2018
@@ -32,9 +32,7 @@ type Pkglint struct {
        Mk     *MkLines // The Makefile (or fragment) that is currently checked.
 
        Todo            []string // The files or directories that still need to be checked.
-       CurrentDir      string   // The currently checked directory, relative to the cwd
-       CurPkgsrcdir    string   // The pkgsrc directory, relative to currentDir
-       Wip             bool     // Is the currently checked directory from pkgsrc-wip?
+       Wip             bool     // Is the currently checked item from pkgsrc-wip?
        Infrastructure  bool     // Is the currently checked item from the pkgsrc infrastructure?
        Testing         bool     // Is pkglint in self-testing mode (only during development)?
        CurrentUsername string   // For checking against OWNER and MAINTAINER
@@ -52,6 +50,7 @@ type Pkglint struct {
        logErr                *SeparatorWriter
 
        loghisto *histogram.Histogram
+       loaded   *histogram.Histogram
 }
 
 type CmdOpts struct {
@@ -143,10 +142,12 @@ func (pkglint *Pkglint) Main(argv ...str
 
                regex.Profiling = true
                pkglint.loghisto = histogram.New()
+               pkglint.loaded = histogram.New()
                defer func() {
                        pkglint.logOut.Write("")
-                       pkglint.loghisto.PrintStats("loghisto", pkglint.logOut.out, 0)
+                       pkglint.loghisto.PrintStats("loghisto", pkglint.logOut.out, -1)
                        regex.PrintStats()
+                       pkglint.loaded.PrintStats("loaded", pkglint.logOut.out, 50)
                }()
        }
 
@@ -296,13 +297,12 @@ func (pkglint *Pkglint) CheckDirent(fnam
        isReg := st.Mode().IsRegular()
 
        currentDir := ifelseStr(isReg, path.Dir(fname), fname)
-       pkglint.CurrentDir = currentDir
        absCurrentDir := abspath(currentDir)
        pkglint.Wip = !pkglint.opts.Import && matches(absCurrentDir, `/wip/|/wip$`)
        pkglint.Infrastructure = matches(absCurrentDir, `/mk/|/mk$`)
-       pkglint.CurPkgsrcdir = findPkgsrcTopdir(currentDir)
-       if pkglint.CurPkgsrcdir == "" {
-               NewLineWhole(fname).Errorf("Cannot determine the pkgsrc root directory for %q.", currentDir)
+       pkgsrcdir := findPkgsrcTopdir(currentDir)
+       if pkgsrcdir == "" {
+               NewLineWhole(fname).Errorf("Cannot determine the pkgsrc root directory for %q.", cleanpath(currentDir))
                return
        }
 
@@ -314,13 +314,13 @@ func (pkglint *Pkglint) CheckDirent(fnam
                return
        }
 
-       switch pkglint.CurPkgsrcdir {
+       switch pkgsrcdir {
        case "../..":
                pkglint.checkdirPackage(pkglint.Pkgsrc.ToRel(currentDir))
        case "..":
-               CheckdirCategory()
+               CheckdirCategory(currentDir)
        case ".":
-               CheckdirToplevel()
+               CheckdirToplevel(currentDir)
        default:
                NewLineWhole(fname).Errorf("Cannot check directories outside a pkgsrc tree.")
        }
@@ -509,7 +509,7 @@ func (pkglint *Pkglint) Checkfile(fname 
 
        case basename == "ALTERNATIVES":
                if pkglint.opts.CheckAlternatives {
-                       CheckfileExtra(fname)
+                       CheckfileAlternatives(fname, nil)
                }
 
        case basename == "buildlink3.mk":

Index: pkgsrc/pkgtools/pkglint/files/mkline_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.38 pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.39
--- pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.38   Thu Aug  9 20:08:12 2018
+++ pkgsrc/pkgtools/pkglint/files/mkline_test.go        Sun Aug 12 16:31:56 2018
@@ -876,12 +876,16 @@ func (s *Suite) Test_MatchVarassign(c *c
 }
 
 func (s *Suite) Test_Indentation(c *check.C) {
+       t := s.Init(c)
+
        ind := NewIndentation()
 
+       mkline := t.NewMkLine("dummy.mk", 5, ".if 0")
+
        c.Check(ind.Depth("if"), equals, 0)
        c.Check(ind.DependsOn("VARNAME"), equals, false)
 
-       ind.Push(2, "")
+       ind.Push(mkline, 2, "")
 
        c.Check(ind.Depth("if"), equals, 0) // Because "if" is handled in MkLines.TrackBefore.
        c.Check(ind.Depth("endfor"), equals, 0)
@@ -896,11 +900,12 @@ func (s *Suite) Test_Indentation(c *chec
        c.Check(ind.DependsOn("LEVEL1.VAR1"), equals, true)
        c.Check(ind.DependsOn("OTHER_VAR"), equals, false)
 
-       ind.Push(2, "")
+       ind.Push(mkline, 2, "")
 
        ind.AddVar("LEVEL2.VAR")
 
        c.Check(ind.Varnames(), equals, "LEVEL1.VAR1, LEVEL1.VAR2, LEVEL2.VAR")
+       c.Check(ind.String(), equals, "[2 (LEVEL1.VAR1 LEVEL1.VAR2) 2 (LEVEL2.VAR)]")
 
        ind.Pop()
 
@@ -911,4 +916,5 @@ func (s *Suite) Test_Indentation(c *chec
 
        c.Check(ind.Varnames(), equals, "")
        c.Check(ind.IsConditional(), equals, false)
+       c.Check(ind.String(), equals, "[]")
 }

Index: pkgsrc/pkgtools/pkglint/files/mklinechecker.go
diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.15 pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.16
--- pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.15 Sat Jul 28 18:31:23 2018
+++ pkgsrc/pkgtools/pkglint/files/mklinechecker.go      Sun Aug 12 16:31:56 2018
@@ -257,7 +257,7 @@ func (ck MkLineChecker) checkDependencyR
 }
 
 func (ck MkLineChecker) checkVarassignDefPermissions() {
-       if !G.opts.WarnPerm {
+       if !G.opts.WarnPerm || G.Infrastructure {
                return
        }
        if trace.Tracing {
@@ -941,6 +941,18 @@ func (ck MkLineChecker) CheckVartype(var
        case vartype.kindOfList == lkNone:
                ck.CheckVartypePrimitive(varname, vartype.basicType, op, value, comment, vartype.guessed)
 
+               if op == opUseMatch && matches(value, `^[\w-/]+$`) && !vartype.IsConsideredList() {
+                       mkline.Notef("%s should be compared using == instead of the :M or :N modifier without wildcards.", varname)
+                       Explain(
+                               "This variable has a single value, not a list of values.  Therefore",
+                               "it feels strange to apply list operators like :M and :N onto it.",
+                               "A more direct approach is to use the == and != operators.",
+                               "",
+                               "An entirely different case is when the pattern contains wildcards",
+                               "like ^, *, $.  In such a case, using the :M or :N modifiers is",
+                               "useful and preferred.")
+               }
+
        case value == "":
                break
 
@@ -1031,8 +1043,7 @@ func (ck MkLineChecker) CheckCond() {
                return
        }
 
-       cond.Visit("empty", func(node *Tree) {
-               varuse := node.args[0].(MkVarUse)
+       checkEmpty := func(varuse *MkVarUse) {
                varname := varuse.varname
                if matches(varname, `^\$.*:[MN]`) {
                        mkline.Warnf("The empty() function takes a variable name as parameter, not a variable expression.")
@@ -1054,19 +1065,21 @@ func (ck MkLineChecker) CheckCond() {
                                ck.CheckVartype(varname, opUseMatch, modifier[1:], "")
                        }
                }
-       })
+       }
 
-       cond.Visit("compareVarStr", func(node *Tree) {
-               varuse := node.args[0].(MkVarUse)
+       checkCompareVarStr := func(varuse *MkVarUse, op string, value string) {
                varname := varuse.varname
                varmods := varuse.modifiers
-               value := node.args[2].(string)
                if len(varmods) == 0 {
-                       ck.checkCompareVarStr(varname, node.args[1].(string), value)
+                       ck.checkCompareVarStr(varname, op, value)
                } else if len(varmods) == 1 && matches(varmods[0], `^[MN]`) && value != "" {
                        ck.CheckVartype(varname, opUseMatch, value, "")
                }
-       })
+       }
+
+       NewMkCondWalker().Walk(cond, &MkCondCallback{
+               Empty:         checkEmpty,
+               CompareVarStr: checkCompareVarStr})
 
        if G.Mk != nil {
                G.Mk.indentation.RememberUsedVariables(cond)
@@ -1120,24 +1133,24 @@ func (ck MkLineChecker) CheckRelativePkg
        }
 }
 
-func (ck MkLineChecker) CheckRelativePath(path string, mustExist bool) {
+func (ck MkLineChecker) CheckRelativePath(relativePath string, mustExist bool) {
        if trace.Tracing {
-               defer trace.Call(path, mustExist)()
+               defer trace.Call(relativePath, mustExist)()
        }
 
        mkline := ck.MkLine
-       if !G.Wip && contains(path, "/wip/") {
+       if !G.Wip && contains(relativePath, "/wip/") {
                mkline.Errorf("A main pkgsrc package must not depend on a pkgsrc-wip package.")
        }
 
-       resolvedPath := mkline.ResolveVarsInRelativePath(path, true)
+       resolvedPath := mkline.ResolveVarsInRelativePath(relativePath, true)
        if containsVarRef(resolvedPath) {
                return
        }
 
        abs := resolvedPath
        if !hasPrefix(abs, "/") {
-               abs = G.CurrentDir + "/" + abs
+               abs = path.Dir(mkline.Filename) + "/" + abs
        }
        if _, err := os.Stat(abs); err != nil {
                if mustExist {
@@ -1146,10 +1159,15 @@ func (ck MkLineChecker) CheckRelativePat
                return
        }
 
-       if hasPrefix(path, "../") &&
-               !matches(path, `^\.\./\.\./[^/]+/[^/]`) &&
-               !(G.CurPkgsrcdir == ".." && hasPrefix(path, "../mk/")) && // For category Makefiles.
-               !hasPrefix(path, "../../mk/") {
-               mkline.Warnf("Invalid relative path %q.", path)
+       switch {
+       case !hasPrefix(relativePath, "../"):
+       case matches(relativePath, `^\.\./\.\./[^/]+/[^/]`):
+               // From a package to another package.
+       case hasPrefix(relativePath, "../../mk/"):
+               // From a package to the infrastructure.
+       case hasPrefix(relativePath, "../mk/") && relpath(path.Dir(mkline.Filename), G.Pkgsrc.File(".")) == "..":
+               // For category Makefiles.
+       default:
+               mkline.Warnf("Invalid relative path %q.", relativePath)
        }
 }

Index: pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.11 pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.12
--- pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.11    Thu Aug  9 20:08:12 2018
+++ pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go Sun Aug 12 16:31:56 2018
@@ -103,14 +103,15 @@ func (s *Suite) Test_MkLineChecker_Check
        MkLineChecker{t.NewMkLine("fname", 1, ".if ${EMUL_PLATFORM:Mlinux-x386}")}.CheckCond()
 
        t.CheckOutputLines(
-               "WARN: fname:1: " +
-                       "The pattern \"x386\" cannot match any of { aarch64 aarch64eb alpha amd64 arc arm arm26 " +
-                       "arm32 cobalt coldfire convex dreamcast earm earmeb earmhf earmhfeb earmv4 earmv4eb " +
-                       "earmv5 earmv5eb earmv6 earmv6eb earmv6hf earmv6hfeb earmv7 earmv7eb earmv7hf " +
-                       "earmv7hfeb evbarm hpcmips hpcsh hppa hppa64 i386 i586 i686 ia64 m68000 m68k m88k " +
-                       "mips mips64 mips64eb mips64el mipseb mipsel mipsn32 mlrisc ns32k pc532 pmax powerpc powerpc64 " +
-                       "rs6000 s390 sh3eb sh3el sparc sparc64 vax x86_64 } " +
-                       "for the hardware architecture part of EMUL_PLATFORM.")
+               "WARN: fname:1: "+
+                       "The pattern \"x386\" cannot match any of { aarch64 aarch64eb alpha amd64 arc arm arm26 "+
+                       "arm32 cobalt coldfire convex dreamcast earm earmeb earmhf earmhfeb earmv4 earmv4eb "+
+                       "earmv5 earmv5eb earmv6 earmv6eb earmv6hf earmv6hfeb earmv7 earmv7eb earmv7hf "+
+                       "earmv7hfeb evbarm hpcmips hpcsh hppa hppa64 i386 i586 i686 ia64 m68000 m68k m88k "+
+                       "mips mips64 mips64eb mips64el mipseb mipsel mipsn32 mlrisc ns32k pc532 pmax powerpc powerpc64 "+
+                       "rs6000 s390 sh3eb sh3el sparc sparc64 vax x86_64 } "+
+                       "for the hardware architecture part of EMUL_PLATFORM.",
+               "NOTE: fname:1: EMUL_PLATFORM should be compared using == instead of the :M or :N modifier without wildcards.")
 
        MkLineChecker{t.NewMkLine("fname", 98, ".if ${MACHINE_PLATFORM:MUnknownOS-*-*} || ${MACHINE_ARCH:Mx86}")}.CheckCond()
 
@@ -127,7 +128,8 @@ func (s *Suite) Test_MkLineChecker_Check
                        "earmv7 earmv7eb earmv7hf earmv7hfeb evbarm hpcmips hpcsh hppa hppa64 i386 i586 i686 ia64 "+
                        "m68000 m68k m88k mips mips64 mips64eb mips64el mipseb mipsel mipsn32 mlrisc ns32k pc532 pmax "+
                        "powerpc powerpc64 rs6000 s390 sh3eb sh3el sparc sparc64 vax x86_64 "+
-                       "} for MACHINE_ARCH.")
+                       "} for MACHINE_ARCH.",
+               "NOTE: fname:98: MACHINE_ARCH should be compared using == instead of the :M or :N modifier without wildcards.")
 }
 
 func (s *Suite) Test_MkLineChecker_checkVarassign(c *check.C) {
@@ -158,6 +160,23 @@ func (s *Suite) Test_MkLineChecker_check
                "WARN: options.mk:2: The variable PKG_DEVELOPER may not be given a default value by any package.")
 }
 
+// Don't check the permissions for infrastructure files since they have their own rules.
+func (s *Suite) Test_MkLineChecker_checkVarassignDefPermissions__infrastructure(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wall")
+       t.SetupVartypes()
+       t.SetupFileMkLines("mk/infra.mk",
+               MkRcsID,
+               "",
+               "PKG_DEVELOPER?=\tyes")
+       t.SetupFileMkLines("mk/bsd.pkg.mk")
+
+       G.CheckDirent(t.File("mk/infra.mk"))
+
+       t.CheckOutputEmpty()
+}
+
 func (s *Suite) Test_MkLineChecker_CheckVarusePermissions(c *check.C) {
        t := s.Init(c)
 
Index: pkgsrc/pkgtools/pkglint/files/mkparser_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.11 pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.12
--- pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.11 Sat Jul 28 18:31:23 2018
+++ pkgsrc/pkgtools/pkglint/files/mkparser_test.go      Sun Aug 12 16:31:56 2018
@@ -135,87 +135,92 @@ func (s *Suite) Test_MkParser_MkTokens(c
 }
 
 func (s *Suite) Test_MkParser_MkCond(c *check.C) {
-       checkRest := func(input string, expectedTree *Tree, expectedRest string) {
+       checkRest := func(input string, expectedTree MkCond, expectedRest string) {
                p := NewMkParser(dummyLine, input, false)
                actualTree := p.MkCond()
                c.Check(actualTree, deepEquals, expectedTree)
                c.Check(p.Rest(), equals, expectedRest)
        }
-       check := func(input string, expectedTree *Tree) {
+       check := func(input string, expectedTree MkCond) {
                checkRest(input, expectedTree, "")
        }
-       varuse := func(varname string, modifiers ...string) MkVarUse {
-               return MkVarUse{varname: varname, modifiers: modifiers}
+       varuse := func(varname string, modifiers ...string) *MkVarUse {
+               return &MkVarUse{varname: varname, modifiers: modifiers}
        }
 
        check("${OPSYS:MNetBSD}",
-               NewTree("not", NewTree("empty", varuse("OPSYS", "MNetBSD"))))
+               &mkCond{Not: &mkCond{Empty: varuse("OPSYS", "MNetBSD")}})
        check("defined(VARNAME)",
-               NewTree("defined", "VARNAME"))
+               &mkCond{Defined: "VARNAME"})
        check("empty(VARNAME)",
-               NewTree("empty", varuse("VARNAME")))
+               &mkCond{Empty: varuse("VARNAME")})
        check("!empty(VARNAME)",
-               NewTree("not", NewTree("empty", varuse("VARNAME"))))
+               &mkCond{Not: &mkCond{Empty: varuse("VARNAME")}})
        check("!empty(VARNAME:M[yY][eE][sS])",
-               NewTree("not", NewTree("empty", varuse("VARNAME", "M[yY][eE][sS]"))))
+               &mkCond{Not: &mkCond{Empty: varuse("VARNAME", "M[yY][eE][sS]")}})
        check("${VARNAME} != \"Value\"",
-               NewTree("compareVarStr", varuse("VARNAME"), "!=", "Value"))
+               &mkCond{CompareVarStr: &MkCondCompareVarStr{varuse("VARNAME"), "!=", "Value"}})
        check("${VARNAME:Mi386} != \"Value\"",
-               NewTree("compareVarStr", varuse("VARNAME", "Mi386"), "!=", "Value"))
+               &mkCond{CompareVarStr: &MkCondCompareVarStr{varuse("VARNAME", "Mi386"), "!=", "Value"}})
        check("${VARNAME} != Value",
-               NewTree("compareVarStr", varuse("VARNAME"), "!=", "Value"))
+               &mkCond{CompareVarStr: &MkCondCompareVarStr{varuse("VARNAME"), "!=", "Value"}})
        check("\"${VARNAME}\" != Value",
-               NewTree("compareVarStr", varuse("VARNAME"), "!=", "Value"))
+               &mkCond{CompareVarStr: &MkCondCompareVarStr{varuse("VARNAME"), "!=", "Value"}})
        check("${pkg} == \"${name}\"",
-               NewTree("compareVarVar", varuse("pkg"), "==", varuse("name")))
+               &mkCond{CompareVarVar: &MkCondCompareVarVar{varuse("pkg"), "==", varuse("name")}})
        check("\"${pkg}\" == \"${name}\"",
-               NewTree("compareVarVar", varuse("pkg"), "==", varuse("name")))
+               &mkCond{CompareVarVar: &MkCondCompareVarVar{varuse("pkg"), "==", varuse("name")}})
        check("(defined(VARNAME))",
-               NewTree("defined", "VARNAME"))
+               &mkCond{Defined: "VARNAME"})
        check("exists(/etc/hosts)",
-               NewTree("exists", "/etc/hosts"))
+               &mkCond{Call: &MkCondCall{"exists", "/etc/hosts"}})
        check("exists(${PREFIX}/var)",
-               NewTree("exists", "${PREFIX}/var"))
+               &mkCond{Call: &MkCondCall{"exists", "${PREFIX}/var"}})
        check("${OPSYS} == \"NetBSD\" || ${OPSYS} == \"OpenBSD\"",
-               NewTree("or",
-                       NewTree("compareVarStr", varuse("OPSYS"), "==", "NetBSD"),
-                       NewTree("compareVarStr", varuse("OPSYS"), "==", "OpenBSD")))
+               &mkCond{Or: []*mkCond{
+                       {CompareVarStr: &MkCondCompareVarStr{varuse("OPSYS"), "==", "NetBSD"}},
+                       {CompareVarStr: &MkCondCompareVarStr{varuse("OPSYS"), "==", "OpenBSD"}}}})
        check("${OPSYS} == \"NetBSD\" && ${MACHINE_ARCH} == \"i386\"",
-               NewTree("and",
-                       NewTree("compareVarStr", varuse("OPSYS"), "==", "NetBSD"),
-                       NewTree("compareVarStr", varuse("MACHINE_ARCH"), "==", "i386")))
+               &mkCond{And: []*mkCond{
+                       {CompareVarStr: &MkCondCompareVarStr{varuse("OPSYS"), "==", "NetBSD"}},
+                       {CompareVarStr: &MkCondCompareVarStr{varuse("MACHINE_ARCH"), "==", "i386"}}}})
        check("defined(A) && defined(B) || defined(C) && defined(D)",
-               NewTree("or",
-                       NewTree("and",
-                               NewTree("defined", "A"),
-                               NewTree("defined", "B")),
-                       NewTree("and",
-                               NewTree("defined", "C"),
-                               NewTree("defined", "D"))))
+               &mkCond{Or: []*mkCond{
+                       {And: []*mkCond{
+                               {Defined: "A"},
+                               {Defined: "B"}}},
+                       {And: []*mkCond{
+                               {Defined: "C"},
+                               {Defined: "D"}}}}})
        check("${MACHINE_ARCH:Mi386} || ${MACHINE_OPSYS:MNetBSD}",
-               NewTree("or",
-                       NewTree("not", NewTree("empty", varuse("MACHINE_ARCH", "Mi386"))),
-                       NewTree("not", NewTree("empty", varuse("MACHINE_OPSYS", "MNetBSD")))))
+               &mkCond{Or: []*mkCond{
+                       {Not: &mkCond{Empty: varuse("MACHINE_ARCH", "Mi386")}},
+                       {Not: &mkCond{Empty: varuse("MACHINE_OPSYS", "MNetBSD")}}}})
 
        // Exotic cases
        check("0",
-               NewTree("literalNum", "0"))
+               &mkCond{Num: "0"})
        check("! ( defined(A)  && empty(VARNAME) )",
-               NewTree("not", NewTree("and", NewTree("defined", "A"), NewTree("empty", varuse("VARNAME")))))
+               &mkCond{Not: &mkCond{
+                       And: []*mkCond{
+                               {Defined: "A"},
+                               {Empty: varuse("VARNAME")}}}})
        check("${REQD_MAJOR} > ${MAJOR}",
-               NewTree("compareVarVar", varuse("REQD_MAJOR"), ">", varuse("MAJOR")))
+               &mkCond{CompareVarVar: &MkCondCompareVarVar{varuse("REQD_MAJOR"), ">", varuse("MAJOR")}})
        check("${OS_VERSION} >= 6.5",
-               NewTree("compareVarNum", varuse("OS_VERSION"), ">=", "6.5"))
+               &mkCond{CompareVarNum: &MkCondCompareVarNum{varuse("OS_VERSION"), ">=", "6.5"}})
        check("${OS_VERSION} == 5.3",
-               NewTree("compareVarNum", varuse("OS_VERSION"), "==", "5.3"))
+               &mkCond{CompareVarNum: &MkCondCompareVarNum{varuse("OS_VERSION"), "==", "5.3"}})
        check("!empty(${OS_VARIANT:MIllumos})", // Probably not intended
-               NewTree("not", NewTree("empty", varuse("${OS_VARIANT:MIllumos}"))))
+               &mkCond{Not: &mkCond{Empty: varuse("${OS_VARIANT:MIllumos}")}})
        check("defined (VARNAME)", // There may be whitespace before the parenthesis; see devel/bmake/files/cond.c:^compare_function.
-               NewTree("defined", "VARNAME"))
+               &mkCond{Defined: "VARNAME"})
+       check("${\"${PKG_OPTIONS:Moption}\":?--enable-option:--disable-option}",
+               &mkCond{Not: &mkCond{Empty: varuse("\"${PKG_OPTIONS:Moption}\"", "?--enable-option:--disable-option")}})
 
        // Errors
        checkRest("!empty(PKG_OPTIONS:Msndfile) || defined(PKG_OPTIONS:Msamplerate)",
-               NewTree("not", NewTree("empty", varuse("PKG_OPTIONS", "Msndfile"))),
+               &mkCond{Not: &mkCond{Empty: varuse("PKG_OPTIONS", "Msndfile")}},
                " || defined(PKG_OPTIONS:Msamplerate)")
 }
 
Index: pkgsrc/pkgtools/pkglint/files/toplevel.go
diff -u pkgsrc/pkgtools/pkglint/files/toplevel.go:1.11 pkgsrc/pkgtools/pkglint/files/toplevel.go:1.12
--- pkgsrc/pkgtools/pkglint/files/toplevel.go:1.11      Thu Jul 12 16:23:36 2018
+++ pkgsrc/pkgtools/pkglint/files/toplevel.go   Sun Aug 12 16:31:56 2018
@@ -5,17 +5,18 @@ import (
 )
 
 type Toplevel struct {
+       dir            string
        previousSubdir string
        subdirs        []string
 }
 
-func CheckdirToplevel() {
+func CheckdirToplevel(dir string) {
        if trace.Tracing {
-               defer trace.Call1(G.CurrentDir)()
+               defer trace.Call1(dir)()
        }
 
-       ctx := new(Toplevel)
-       fname := G.CurrentDir + "/Makefile"
+       ctx := &Toplevel{dir, "", nil}
+       fname := dir + "/Makefile"
 
        lines := LoadNonemptyLines(fname, true)
        if lines == nil {
@@ -48,7 +49,7 @@ func (ctx *Toplevel) checkSubdir(line Li
                line.Warnf("Indentation should be a single tab character.")
        }
 
-       if contains(subdir, "$") || !fileExists(G.CurrentDir+"/"+subdir+"/Makefile") {
+       if contains(subdir, "$") || !fileExists(ctx.dir+"/"+subdir+"/Makefile") {
                return
        }
 
@@ -66,6 +67,6 @@ func (ctx *Toplevel) checkSubdir(line Li
        ctx.previousSubdir = subdir
 
        if !commentedOut {
-               ctx.subdirs = append(ctx.subdirs, G.CurrentDir+"/"+subdir)
+               ctx.subdirs = append(ctx.subdirs, ctx.dir+"/"+subdir)
        }
 }
Index: pkgsrc/pkgtools/pkglint/files/toplevel_test.go
diff -u pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.11 pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.12
--- pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.11 Thu Jul 12 16:23:36 2018
+++ pkgsrc/pkgtools/pkglint/files/toplevel_test.go      Sun Aug 12 16:31:56 2018
@@ -21,8 +21,7 @@ func (s *Suite) Test_CheckdirToplevel(c 
        t.SetupFileLines("x11/Makefile")
        t.SetupVartypes()
 
-       G.CurrentDir = t.TmpDir()
-       CheckdirToplevel()
+       CheckdirToplevel(t.File("."))
 
        t.CheckOutputLines(
                "WARN: ~/Makefile:3: Indentation should be a single tab character.",

Index: pkgsrc/pkgtools/pkglint/files/mklines.go
diff -u pkgsrc/pkgtools/pkglint/files/mklines.go:1.28 pkgsrc/pkgtools/pkglint/files/mklines.go:1.29
--- pkgsrc/pkgtools/pkglint/files/mklines.go:1.28       Sat Jul 28 20:44:45 2018
+++ pkgsrc/pkgtools/pkglint/files/mklines.go    Sun Aug 12 16:31:56 2018
@@ -13,10 +13,12 @@ type MkLines struct {
        forVars        map[string]bool // The variables currently used in .for loops
        target         string          // Current make(1) target
        vars           Scope
-       buildDefs      map[string]bool // Variables that are registered in BUILD_DEFS, to ensure that all user-defined variables are added to it.
-       plistVars      map[string]bool // Variables that are registered in PLIST_VARS, to ensure that all user-defined variables are added to it.
-       tools          map[string]bool // Set of tools that are declared to be used.
-       toolRegistry   ToolRegistry    // Tools defined in file scope.
+       buildDefs      map[string]bool   // Variables that are registered in BUILD_DEFS, to ensure that all user-defined variables are added to it.
+       plistVarAdded  map[string]MkLine // Identifiers that are added to PLIST_VARS.
+       plistVarSet    map[string]MkLine // Identifiers for which PLIST.${id} is defined.
+       plistVarSkip   bool              // True if any of the PLIST_VARS identifiers refers to a variable.
+       tools          map[string]bool   // Set of tools that are declared to be used.
+       toolRegistry   ToolRegistry      // Tools defined in file scope.
        SeenBsdPrefsMk bool
        indentation    *Indentation // Indentation depth of preprocessing directives; only available during MkLines.ForEach.
        Once
@@ -42,7 +44,9 @@ func NewMkLines(lines []Line) *MkLines {
                "",
                NewScope(),
                make(map[string]bool),
-               make(map[string]bool),
+               make(map[string]MkLine),
+               make(map[string]MkLine),
+               false,
                tools,
                NewToolRegistry(),
                false,
@@ -85,6 +89,8 @@ func (mklines *MkLines) Check() {
        // are collected to make the order of the definitions irrelevant.
        mklines.DetermineUsedVariables()
        mklines.DetermineDefinedVariables()
+       mklines.collectPlistVars()
+       mklines.collectElse()
 
        // In the second pass, the actual checks are done.
 
@@ -105,9 +111,25 @@ func (mklines *MkLines) Check() {
 
                case mkline.IsVarassign():
                        mklines.target = ""
-                       mkline.Tokenize(mkline.Value())
+                       mkline.Tokenize(mkline.Value()) // Just for the side-effect of the warning.
                        substcontext.Varassign(mkline)
 
+                       switch mkline.Varcanon() {
+                       case "PLIST_VARS":
+                               value := mkline.ValueSplit(resolveVariableRefs(mkline.Value()), "")
+                               for _, id := range value {
+                                       if !mklines.plistVarSkip && mklines.plistVarSet[id] == nil {
+                                               mkline.Warnf("%q is added to PLIST_VARS, but PLIST.%s is not defined in this file.", id, id)
+                                       }
+                               }
+
+                       case "PLIST.*":
+                               id := mkline.Varparam()
+                               if !mklines.plistVarSkip && mklines.plistVarAdded[id] == nil {
+                                       mkline.Warnf("PLIST.%s is defined, but %q is not added to PLIST_VARS in this file.", id, id)
+                               }
+                       }
+
                case mkline.IsInclude():
                        mklines.target = ""
                        switch path.Base(mkline.IncludeFile()) {
@@ -177,6 +199,8 @@ func (mklines *MkLines) DetermineDefined
                        continue
                }
 
+               defineVar(mkline, mkline.Varname())
+
                varcanon := mkline.Varcanon()
                switch varcanon {
                case "BUILD_DEFS", "PKG_GROUPS_VARS", "PKG_USERS_VARS":
@@ -188,12 +212,17 @@ func (mklines *MkLines) DetermineDefined
                        }
 
                case "PLIST_VARS":
-                       for _, id := range splitOnSpace(mkline.Value()) {
-                               mklines.plistVars["PLIST."+id] = true
+                       value := mkline.ValueSplit(resolveVariableRefs(mkline.Value()), "")
+                       for _, id := range value {
                                if trace.Tracing {
                                        trace.Step1("PLIST.%s is added to PLIST_VARS.", id)
                                }
-                               mklines.UseVar(mkline, "PLIST."+id)
+                               if containsVarRef(id) {
+                                       mklines.UseVar(mkline, "PLIST.*")
+                                       mklines.plistVarSkip = true
+                               } else {
+                                       mklines.UseVar(mkline, "PLIST."+id)
+                               }
                        }
 
                case "USE_TOOLS":
@@ -231,6 +260,38 @@ func (mklines *MkLines) DetermineDefined
        }
 }
 
+func (mklines *MkLines) collectPlistVars() {
+       for _, mkline := range mklines.mklines {
+               if mkline.IsVarassign() {
+                       switch mkline.Varcanon() {
+                       case "PLIST_VARS":
+                               value := mkline.ValueSplit(resolveVariableRefs(mkline.Value()), "")
+                               for _, id := range value {
+                                       if containsVarRef(id) {
+                                               mklines.plistVarSkip = true
+                                       } else {
+                                               mklines.plistVarAdded[id] = mkline
+                                       }
+                               }
+                       case "PLIST.*":
+                               id := mkline.Varparam()
+                               if containsVarRef(id) {
+                                       mklines.plistVarSkip = true
+                               } else {
+                                       mklines.plistVarSet[id] = mkline
+                               }
+                       }
+               }
+       }
+}
+
+func (mklines *MkLines) collectElse() {
+       // Make a dry-run over the lines, which sets data.elseLine (in mkline.go) as a side-effect.
+       mklines.ForEach(
+               func(mkline MkLine) bool { return true },
+               func(mkline MkLine) {})
+}
+
 func (mklines *MkLines) DetermineUsedVariables() {
        for _, mkline := range mklines.mklines {
                varnames := mkline.DetermineUsedVariables()
Index: pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.28 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.29
--- pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.28     Sat Jul 28 20:44:45 2018
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go  Sun Aug 12 16:31:56 2018
@@ -46,8 +46,6 @@ func (s *Suite) Test_VartypeCheck_Catego
                "# empty")
        t.SetupFileLines("wip/Makefile",
                "# empty")
-       G.CurrentDir = t.TmpDir()
-       G.CurPkgsrcdir = "."
 
        runVartypeChecks(t, "CATEGORIES", opAssign, (*VartypeCheck).Category,
                "chinese",
@@ -168,14 +166,23 @@ func (s *Suite) Test_VartypeCheck_Depend
 func (s *Suite) Test_VartypeCheck_DependencyWithPath(c *check.C) {
        t := s.Init(c)
 
-       t.SetupFileLines("x11/alacarte/Makefile",
-               "# empty")
-       t.SetupFileLines("category/package/Makefile",
-               "# empty")
-       G.CurrentDir = t.TmpDir() + "/category/package"
-       G.CurPkgsrcdir = "../.."
+       t.CreateFileLines("x11/alacarte/Makefile")
+       t.CreateFileLines("category/package/Makefile")
+       G.Pkg = NewPackage("category/package")
+
+       // Since this test involves relative paths, the filename of the line must be realistic.
+       // Therefore this custom implementation of runVartypeChecks.
+       runChecks := func(values ...string) {
+               for i, value := range values {
+                       mkline := t.NewMkLine(t.File("category/package/fname.mk"), i+1, "DEPENDS+=\t"+value)
+                       mkline.Tokenize(mkline.Value())
+                       valueNovar := mkline.WithoutMakeVariables(mkline.Value())
+                       vc := &VartypeCheck{mkline, mkline.Line, mkline.Varname(), mkline.Op(), mkline.Value(), valueNovar, "", false}
+                       (*VartypeCheck).DependencyWithPath(vc)
+               }
+       }
 
-       runVartypeChecks(t, "DEPENDS", opAssignAppend, (*VartypeCheck).DependencyWithPath,
+       runChecks(
                "Perl",
                "perl5>=5.22:../perl5",
                "perl5>=5.24:../../lang/perl5",
@@ -190,19 +197,19 @@ func (s *Suite) Test_VartypeCheck_Depend
                "gtk2+>=2.16:../../x11/alacarte")
 
        t.CheckOutputLines(
-               "WARN: fname:1: Unknown dependency pattern with path \"Perl\".",
-               "WARN: fname:2: Dependencies should have the form \"../../category/package\".",
-               "ERROR: fname:3: \"../../lang/perl5\" does not exist.",
-               "ERROR: fname:3: There is no package in \"lang/perl5\".",
-               "WARN: fname:3: Please use USE_TOOLS+=perl:run instead of this dependency.",
-               "WARN: fname:4: Unknown dependency pattern \"broken0.12.1\".",
-               "WARN: fname:5: Unknown dependency pattern \"broken[0-9]*\".",
-               "WARN: fname:6: Unknown dependency pattern with path \"broken[0-9]*../../x11/alacarte\".",
-               "WARN: fname:7: Unknown dependency pattern \"broken>=\".",
-               "WARN: fname:8: Unknown dependency pattern \"broken=0\".",
-               "WARN: fname:9: Unknown dependency pattern \"broken=\".",
-               "WARN: fname:10: Unknown dependency pattern \"broken-\".",
-               "WARN: fname:11: Unknown dependency pattern \"broken>\".")
+               "WARN: ~/category/package/fname.mk:1: Unknown dependency pattern with path \"Perl\".",
+               "WARN: ~/category/package/fname.mk:2: Dependencies should have the form \"../../category/package\".",
+               "ERROR: ~/category/package/fname.mk:3: \"../../lang/perl5\" does not exist.",
+               "ERROR: ~/category/package/fname.mk:3: There is no package in \"lang/perl5\".",
+               "WARN: ~/category/package/fname.mk:3: Please use USE_TOOLS+=perl:run instead of this dependency.",
+               "WARN: ~/category/package/fname.mk:4: Unknown dependency pattern \"broken0.12.1\".",
+               "WARN: ~/category/package/fname.mk:5: Unknown dependency pattern \"broken[0-9]*\".",
+               "WARN: ~/category/package/fname.mk:6: Unknown dependency pattern with path \"broken[0-9]*../../x11/alacarte\".",
+               "WARN: ~/category/package/fname.mk:7: Unknown dependency pattern \"broken>=\".",
+               "WARN: ~/category/package/fname.mk:8: Unknown dependency pattern \"broken=0\".",
+               "WARN: ~/category/package/fname.mk:9: Unknown dependency pattern \"broken=\".",
+               "WARN: ~/category/package/fname.mk:10: Unknown dependency pattern \"broken-\".",
+               "WARN: ~/category/package/fname.mk:11: Unknown dependency pattern \"broken>\".")
 }
 
 func (s *Suite) Test_VartypeCheck_DistSuffix(c *check.C) {
@@ -254,6 +261,26 @@ func (s *Suite) Test_VartypeCheck_Enum(c
                "WARN: fname:3: The pattern \"sun-jdk*\" cannot match any of { jdk1 jdk2 jdk4 } for JDK.")
 }
 
+func (s *Suite) Test_VartypeCheck_Enum__use_match(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupVartypes()
+
+       mklines := t.NewMkLines("module.mk",
+               MkRcsID,
+               "",
+               ".if !empty(MACHINE_ARCH:Mi386) || ${MACHINE_ARCH} == i386",
+               ".endif",
+               ".if !empty(PKGSRC_COMPILER:Mclang) || ${PKGSRC_COMPILER} == clang",
+               ".endif")
+
+       mklines.Check()
+
+       t.CheckOutputLines(
+               "NOTE: module.mk:3: MACHINE_ARCH should be compared using == instead of the :M or :N modifier without wildcards.",
+               "WARN: module.mk:5: Use ${PKGSRC_COMPILER:Mclang} instead of the == operator.")
+}
+
 func (s *Suite) Test_VartypeCheck_FetchURL(c *check.C) {
        t := s.Init(c)
 

Index: pkgsrc/pkgtools/pkglint/files/mklines_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.24 pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.25
--- pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.24  Thu Aug  9 20:08:12 2018
+++ pkgsrc/pkgtools/pkglint/files/mklines_test.go       Sun Aug 12 16:31:56 2018
@@ -55,13 +55,13 @@ func (s *Suite) Test_MkLines_Check__unus
 func (s *Suite) Test_MkLineChecker_checkInclude__Makefile(c *check.C) {
        t := s.Init(c)
 
-       mkline := t.NewMkLine("Makefile", 2, ".include \"../../other/package/Makefile\"")
+       mkline := t.NewMkLine(t.File("Makefile"), 2, ".include \"../../other/package/Makefile\"")
 
        MkLineChecker{mkline}.checkInclude()
 
        t.CheckOutputLines(
-               "ERROR: Makefile:2: \"/other/package/Makefile\" does not exist.",
-               "ERROR: Makefile:2: Other Makefiles must not be included directly.")
+               "ERROR: ~/Makefile:2: \"other/package/Makefile\" does not exist.",
+               "ERROR: ~/Makefile:2: Other Makefiles must not be included directly.")
 }
 
 func (s *Suite) Test_MkLines_quoting_LDFLAGS_for_GNU_configure(c *check.C) {
@@ -453,14 +453,17 @@ func (s *Suite) Test_MkLines_Check__endi
                "WARN: opsys.mk:20: Comment \"NetBSD\" does not match condition \"${OPSYS} == FreeBSD\".")
 }
 
-// Demonstrates how to define your own make(1) targets.
+// Demonstrates how to define your own make(1) targets for creating
+// files in the current directory. The pkgsrc-wip category Makefile
+// does this, while all other categories don't need any custom code.
 func (s *Suite) Test_MkLines_wip_category_Makefile(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
+       t.SetupCommandLine("-Wall", "--explain")
        t.SetupVartypes()
        t.SetupTool(&Tool{Name: "rm", Varname: "RM", Predefined: true})
-       mklines := t.NewMkLines("Makefile",
+       t.CreateFileLines("mk/misc/category.mk")
+       mklines := t.SetupFileMkLines("wip/Makefile",
                MkRcsID,
                "",
                "COMMENT=\tWIP pkgsrc packages",
@@ -474,12 +477,25 @@ func (s *Suite) Test_MkLines_wip_categor
                "${.CURDIR}/INDEX:",
                "\t${RM} -f ${.CURDIR}/INDEX",
                "",
-               ".include \"../../mk/misc/category.mk\"")
+               "clean-tmpdir:",
+               "\t${RUN} rm -rf tmpdir",
+               "",
+               ".include \"../mk/misc/category.mk\"")
 
        mklines.Check()
 
        t.CheckOutputLines(
-               "ERROR: Makefile:14: \"/mk/misc/category.mk\" does not exist.")
+               "WARN: ~/wip/Makefile:14: Unusual target \"clean-tmpdir\".",
+               "",
+               "\tIf you want to define your own target, declare it like this:",
+               "\t",
+               "\t\t.PHONY: my-target",
+               "\t",
+               "\tIn the rare case that you actually want a file-based make(1)",
+               "\ttarget, write it like this:",
+               "\t",
+               "\t\t${.CURDIR}/my-filename:",
+               "")
 }
 
 func (s *Suite) Test_MkLines_ExtractDocumentedVariables(c *check.C) {
@@ -622,3 +638,150 @@ func (s *Suite) Test_MkLines_CheckRedund
 
        t.CheckOutputEmpty()
 }
+
+func (s *Suite) Test_MkLines_Check__PLIST_VARS(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wno-space")
+       t.SetupVartypes()
+       t.SetupOption("both", "")
+       t.SetupOption("only-added", "")
+       t.SetupOption("only-defined", "")
+
+       mklines := t.SetupFileMkLines("options.mk",
+               MkRcsID,
+               "",
+               "PKG_OPTIONS_VAR=        PKG_OPTIONS.pkg",
+               "PKG_SUPPORTED_OPTIONS=  both only-added only-defined",
+               "PKG_SUGGESTED_OPTIONS=  # none",
+               "",
+               ".include \"../../mk/bsd.options.mk\"",
+               "",
+               "PLIST_VARS+=            both only-added",
+               "",
+               ".if !empty(PKG_OPTIONS:Mboth)",
+               "PLIST.both=             yes",
+               ".endif",
+               "",
+               ".if !empty(PKG_OPTIONS:Monly-defined)",
+               "PLIST.only-defined=     yes",
+               ".endif")
+
+       mklines.Check()
+
+       t.CheckOutputLines(
+               "ERROR: ~/options.mk:7: \"mk/bsd.options.mk\" does not exist.", // Not relevant for this test.
+               "WARN: ~/options.mk:9: \"only-added\" is added to PLIST_VARS, but PLIST.only-added is not defined in this file.",
+               "WARN: ~/options.mk:16: PLIST.only-defined is defined, but \"only-defined\" is not added to PLIST_VARS in this file.")
+}
+
+func (s *Suite) Test_MkLines_Check__PLIST_VARS_indirect(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wno-space")
+       t.SetupVartypes()
+       t.SetupOption("option1", "")
+       t.SetupOption("option2", "")
+
+       mklines := t.SetupFileMkLines("module.mk",
+               MkRcsID,
+               "",
+               "MY_PLIST_VARS=  option1 option2",
+               "PLIST_VARS+=    ${MY_PLIST_VARS}",
+               ".for option in option3",
+               "PLIST_VARS+=    ${option}",
+               ".endfor",
+               "",
+               ".if 0",
+               "PLIST.option1=  yes",
+               ".endif",
+               "",
+               ".if 1",
+               "PLIST.option2=  yes",
+               ".endif")
+
+       mklines.Check()
+
+       t.CheckOutputEmpty()
+}
+
+func (s *Suite) Test_MkLines_Check__if_else(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wno-space")
+       t.SetupVartypes()
+
+       mklines := t.NewMkLines("module.mk",
+               MkRcsID,
+               "",
+               ".if 0",
+               ".endif",
+               "",
+               ".if 0",
+               ".else",
+               ".endif",
+               "",
+               ".if 0",
+               ".elif 0",
+               ".endif")
+
+       mklines.collectElse()
+
+       c.Check(mklines.mklines[2].HasElseBranch(), equals, false)
+       c.Check(mklines.mklines[5].HasElseBranch(), equals, true)
+       c.Check(mklines.mklines[9].HasElseBranch(), equals, false)
+}
+
+func (s *Suite) Test_MkLines_Check__defined_and_used_variables(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wno-space")
+       t.SetupVartypes()
+
+       mklines := t.NewMkLines("module.mk",
+               MkRcsID,
+               "",
+               ".for lang in de fr",
+               "PLIST_VARS+=            ${lang}",
+               ".endif",
+               "",
+               ".for language in de fr",
+               "PLIST.${language}=      yes",
+               ".endif",
+               "",
+               "PLIST.other=            yes")
+
+       mklines.Check()
+
+       // If there are variable involved in the definition of PLIST_VARS or PLIST.*,
+       // it becomes too difficult for pkglint to decide whether the IDs can still match.
+       // Therefore, in such a case, no diagnostics are logged at all.
+       t.CheckOutputEmpty()
+}
+
+func (s *Suite) Test_MkLines_Check__indirect_PLIST_VARS(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wno-space")
+       t.SetupVartypes()
+       t.SetupOption("a", "")
+       t.SetupOption("b", "")
+       t.SetupOption("c", "")
+
+       mklines := t.NewMkLines("module.mk",
+               MkRcsID,
+               "",
+               "PKG_SUPPORTED_OPTIONS=  a b c",
+               "PLIST_VARS+=            ${PKG_SUPPORTED_OPTIONS:S,a,,g}",
+               "",
+               "PLIST_VARS+=            only-added",
+               "",
+               "PLIST.only-defined=     yes")
+
+       mklines.Check()
+
+       // If the PLIST_VARS contain complex expressions that involve other variables,
+       // it becomes too difficult for pkglint to decide whether the IDs can still match.
+       // Therefore, in such a case, no diagnostics are logged at all.
+       t.CheckOutputEmpty()
+}

Index: pkgsrc/pkgtools/pkglint/files/mkparser.go
diff -u pkgsrc/pkgtools/pkglint/files/mkparser.go:1.13 pkgsrc/pkgtools/pkglint/files/mkparser.go:1.14
--- pkgsrc/pkgtools/pkglint/files/mkparser.go:1.13      Sat Jul 28 18:31:23 2018
+++ pkgsrc/pkgtools/pkglint/files/mkparser.go   Sun Aug 12 16:31:56 2018
@@ -203,17 +203,13 @@ func (p *MkParser) VarUseModifiers(varna
 
 // MkCond parses a condition like ${OPSYS} == "NetBSD".
 // See devel/bmake/files/cond.c.
-func (p *MkParser) MkCond() *Tree {
-       return p.mkCondOr()
-}
-
-func (p *MkParser) mkCondOr() *Tree {
+func (p *MkParser) MkCond() MkCond {
        and := p.mkCondAnd()
        if and == nil {
                return nil
        }
 
-       ands := append([]interface{}(nil), and)
+       ands := []MkCond{and}
        for {
                mark := p.repl.Mark()
                if !p.repl.AdvanceRegexp(`^\s*\|\|\s*`) {
@@ -229,16 +225,16 @@ func (p *MkParser) mkCondOr() *Tree {
        if len(ands) == 1 {
                return and
        }
-       return NewTree("or", ands...)
+       return &mkCond{Or: ands}
 }
 
-func (p *MkParser) mkCondAnd() *Tree {
+func (p *MkParser) mkCondAnd() MkCond {
        atom := p.mkCondAtom()
        if atom == nil {
                return nil
        }
 
-       atoms := append([]interface{}(nil), atom)
+       atoms := []MkCond{atom}
        for {
                mark := p.repl.Mark()
                if !p.repl.AdvanceRegexp(`^\s*&&\s*`) {
@@ -254,10 +250,10 @@ func (p *MkParser) mkCondAnd() *Tree {
        if len(atoms) == 1 {
                return atom
        }
-       return NewTree("and", atoms...)
+       return &mkCond{And: atoms}
 }
 
-func (p *MkParser) mkCondAtom() *Tree {
+func (p *MkParser) mkCondAtom() MkCond {
        if trace.Tracing {
                defer trace.Call1(p.Rest())()
        }
@@ -269,7 +265,7 @@ func (p *MkParser) mkCondAtom() *Tree {
        case repl.AdvanceStr("!"):
                cond := p.mkCondAtom()
                if cond != nil {
-                       return NewTree("not", cond)
+                       return &mkCond{Not: cond}
                }
        case repl.AdvanceStr("("):
                cond := p.MkCond()
@@ -282,14 +278,14 @@ func (p *MkParser) mkCondAtom() *Tree {
        case repl.AdvanceRegexp(`^defined\s*\(`):
                if varname := p.Varname(); varname != "" {
                        if repl.AdvanceStr(")") {
-                               return NewTree("defined", varname)
+                               return &mkCond{Defined: varname}
                        }
                }
        case repl.AdvanceRegexp(`^empty\s*\(`):
                if varname := p.Varname(); varname != "" {
                        modifiers := p.VarUseModifiers(varname, ")")
                        if repl.AdvanceStr(")") {
-                               return NewTree("empty", MkVarUse{varname, modifiers})
+                               return &mkCond{Empty: &MkVarUse{varname, modifiers}}
                        }
                }
        case repl.AdvanceRegexp(`^(commands|exists|make|target)\s*\(`):
@@ -299,7 +295,7 @@ func (p *MkParser) mkCondAtom() *Tree {
                }
                arg := repl.Since(argMark)
                if repl.AdvanceStr(")") {
-                       return NewTree(funcname, arg)
+                       return &mkCond{Call: &MkCondCall{funcname, arg}}
                }
        default:
                lhs := p.VarUse()
@@ -313,33 +309,33 @@ func (p *MkParser) mkCondAtom() *Tree {
                }
                if lhs != nil {
                        if repl.AdvanceRegexp(`^\s*(<|<=|==|!=|>=|>)\s*(\d+(?:\.\d+)?)`) {
-                               return NewTree("compareVarNum", *lhs, repl.Group(1), repl.Group(2))
+                               return &mkCond{CompareVarNum: &MkCondCompareVarNum{lhs, repl.Group(1), repl.Group(2)}}
                        }
                        if repl.AdvanceRegexp(`^\s*(<|<=|==|!=|>=|>)\s*`) {
                                op := repl.Group(1)
                                if (op == "!=" || op == "==") && repl.AdvanceRegexp(`^"([^"\$\\]*)"`) {
-                                       return NewTree("compareVarStr", *lhs, op, repl.Group(1))
+                                       return &mkCond{CompareVarStr: &MkCondCompareVarStr{lhs, op, repl.Group(1)}}
                                } else if repl.AdvanceRegexp(`^\w+`) {
-                                       return NewTree("compareVarStr", *lhs, op, repl.Group(0))
+                                       return &mkCond{CompareVarStr: &MkCondCompareVarStr{lhs, op, repl.Group(0)}}
                                } else if rhs := p.VarUse(); rhs != nil {
-                                       return NewTree("compareVarVar", *lhs, op, *rhs)
+                                       return &mkCond{CompareVarVar: &MkCondCompareVarVar{lhs, op, rhs}}
                                } else if repl.PeekByte() == '"' {
                                        mark := repl.Mark()
                                        if repl.AdvanceStr("\"") {
                                                if quotedRHS := p.VarUse(); quotedRHS != nil {
                                                        if repl.AdvanceStr("\"") {
-                                                               return NewTree("compareVarVar", *lhs, op, *quotedRHS)
+                                                               return &mkCond{CompareVarVar: &MkCondCompareVarVar{lhs, op, quotedRHS}}
                                                        }
                                                }
                                        }
                                        repl.Reset(mark)
                                }
                        } else {
-                               return NewTree("not", NewTree("empty", *lhs)) // See devel/bmake/files/cond.c:/\* For \.if \$/
+                               return &mkCond{Not: &mkCond{Empty: lhs}} // See devel/bmake/files/cond.c:/\* For \.if \$/
                        }
                }
                if repl.AdvanceRegexp(`^\d+(?:\.\d+)?`) {
-                       return NewTree("literalNum", repl.Group(0))
+                       return &mkCond{Num: repl.Group(0)}
                }
        }
        repl.Reset(mark)
@@ -358,3 +354,95 @@ func (p *MkParser) Varname() string {
        }
        return repl.Since(mark)
 }
+
+type MkCond = *mkCond
+
+type mkCond struct {
+       Or  []*mkCond
+       And []*mkCond
+       Not *mkCond
+
+       Defined       string
+       Empty         *MkVarUse
+       CompareVarNum *MkCondCompareVarNum
+       CompareVarStr *MkCondCompareVarStr
+       CompareVarVar *MkCondCompareVarVar
+       Call          *MkCondCall
+       Num           string
+}
+type MkCondCompareVarNum struct {
+       Var *MkVarUse
+       Op  string // One of <, <=, ==, !=, >=, >.
+       Num string
+}
+type MkCondCompareVarStr struct {
+       Var *MkVarUse
+       Op  string // One of ==, !=.
+       Str string
+}
+type MkCondCompareVarVar struct {
+       Left  *MkVarUse
+       Op    string // One of <, <=, ==, !=, >=, >.
+       Right *MkVarUse
+}
+type MkCondCall struct {
+       Name string
+       Arg  string
+}
+
+type MkCondCallback struct {
+       Defined       func(varname string)
+       Empty         func(empty *MkVarUse)
+       CompareVarNum func(varuse *MkVarUse, op string, num string)
+       CompareVarStr func(varuse *MkVarUse, op string, str string)
+       CompareVarVar func(left *MkVarUse, op string, right *MkVarUse)
+       Call          func(name string, arg string)
+}
+
+type MkCondWalker struct{}
+
+func NewMkCondWalker() *MkCondWalker { return &MkCondWalker{} }
+
+func (w *MkCondWalker) Walk(cond MkCond, callback *MkCondCallback) {
+       switch {
+       case cond.Or != nil:
+               for _, or := range cond.Or {
+                       w.Walk(or, callback)
+               }
+       case cond.And != nil:
+               for _, and := range cond.And {
+                       w.Walk(and, callback)
+               }
+       case cond.Not != nil:
+               w.Walk(cond.Not, callback)
+
+       case cond.Defined != "":
+               if callback.Defined != nil {
+                       callback.Defined(cond.Defined)
+               }
+       case cond.Empty != nil:
+               if callback.Empty != nil {
+                       callback.Empty(cond.Empty)
+               }
+       case cond.CompareVarVar != nil:
+               if callback.CompareVarVar != nil {
+                       cvv := cond.CompareVarVar
+                       callback.CompareVarVar(cvv.Left, cvv.Op, cvv.Right)
+               }
+       case cond.CompareVarStr != nil:
+               if callback.CompareVarStr != nil {
+                       cvs := cond.CompareVarStr
+                       callback.CompareVarStr(cvs.Var, cvs.Op, cvs.Str)
+               }
+       case cond.CompareVarNum != nil:
+               if callback.CompareVarNum != nil {
+                       cvn := cond.CompareVarNum
+                       callback.CompareVarNum(cvn.Var, cvn.Op, cvn.Num)
+               }
+       case cond.Call != nil:
+               if callback.Call != nil {
+                       call := cond.Call
+                       callback.Call(call.Name, call.Arg)
+               }
+       }
+}

Index: pkgsrc/pkgtools/pkglint/files/mkshwalker.go
diff -u pkgsrc/pkgtools/pkglint/files/mkshwalker.go:1.3 pkgsrc/pkgtools/pkglint/files/mkshwalker.go:1.4
--- pkgsrc/pkgtools/pkglint/files/mkshwalker.go:1.3     Mon Jan  1 18:04:15 2018
+++ pkgsrc/pkgtools/pkglint/files/mkshwalker.go Sun Aug 12 16:31:56 2018
@@ -3,153 +3,209 @@ package main
 type MkShWalker struct {
 }
 
-// Walk calls the given callback for each node of the parsed shell program.
-// See the types in mkshtypes.go for possible node types.
-func (w *MkShWalker) Walk(list *MkShList, callback func(node interface{})) {
-       for element := range w.iterate(list) {
-               callback(element)
-       }
+func NewMkShWalker() *MkShWalker {
+       return &MkShWalker{}
 }
 
-func (w *MkShWalker) iterate(list *MkShList) <-chan interface{} {
-       elements := make(chan interface{})
-
-       go func() {
-               w.walkList(list, elements)
-               close(elements)
-       }()
-
-       return elements
+// Walk calls the given callback for each node of the parsed shell program,
+// in visiting order from large to small.
+func (w *MkShWalker) Walk(list *MkShList, callback *MkShWalkCallback) {
+       w.walkList(list, callback)
 }
 
-func (w *MkShWalker) walkList(list *MkShList, collector chan<- interface{}) {
-       collector <- list
+func (w *MkShWalker) walkList(list *MkShList, callback *MkShWalkCallback) {
+       if callback.List != nil {
+               callback.List(list)
+       }
 
        for _, andor := range list.AndOrs {
-               w.walkAndOr(andor, collector)
+               w.walkAndOr(andor, callback)
        }
 }
 
-func (w *MkShWalker) walkAndOr(andor *MkShAndOr, collector chan<- interface{}) {
-       collector <- andor
+func (w *MkShWalker) walkAndOr(andor *MkShAndOr, callback *MkShWalkCallback) {
+       if callback.AndOr != nil {
+               callback.AndOr(andor)
+       }
 
        for _, pipeline := range andor.Pipes {
-               w.walkPipeline(pipeline, collector)
+               w.walkPipeline(pipeline, callback)
        }
 }
 
-func (w *MkShWalker) walkPipeline(pipeline *MkShPipeline, collector chan<- interface{}) {
-       collector <- pipeline
+func (w *MkShWalker) walkPipeline(pipeline *MkShPipeline, callback *MkShWalkCallback) {
+       if callback.Pipeline != nil {
+               callback.Pipeline(pipeline)
+       }
 
        for _, command := range pipeline.Cmds {
-               w.walkCommand(command, collector)
+               w.walkCommand(command, callback)
        }
 }
 
-func (w *MkShWalker) walkCommand(command *MkShCommand, collector chan<- interface{}) {
-       collector <- command
+func (w *MkShWalker) walkCommand(command *MkShCommand, callback *MkShWalkCallback) {
+       if callback.Command != nil {
+               callback.Command(command)
+       }
 
        switch {
        case command.Simple != nil:
-               w.walkSimpleCommand(command.Simple, collector)
+               w.walkSimpleCommand(command.Simple, callback)
        case command.Compound != nil:
-               w.walkCompoundCommand(command.Compound, collector)
-               w.walkRedirects(command.Redirects, collector)
+               w.walkCompoundCommand(command.Compound, callback)
+               w.walkRedirects(command.Redirects, callback)
        case command.FuncDef != nil:
-               w.walkFunctionDefinition(command.FuncDef, collector)
-               w.walkRedirects(command.Redirects, collector)
+               w.walkFunctionDefinition(command.FuncDef, callback)
+               w.walkRedirects(command.Redirects, callback)
        }
 }
 
-func (w *MkShWalker) walkSimpleCommand(command *MkShSimpleCommand, collector chan<- interface{}) {
-       collector <- command
+func (w *MkShWalker) walkSimpleCommand(command *MkShSimpleCommand, callback *MkShWalkCallback) {
+       if callback.SimpleCommand != nil {
+               callback.SimpleCommand(command)
+       }
 
-       w.walkWords(command.Assignments, collector)
+       w.walkWords(command.Assignments, callback)
        if command.Name != nil {
-               w.walkWord(command.Name, collector)
+               w.walkWord(command.Name, callback)
        }
-       w.walkWords(command.Args, collector)
-       w.walkRedirects(command.Redirections, collector)
+       w.walkWords(command.Args, callback)
+       w.walkRedirects(command.Redirections, callback)
 }
 
-func (w *MkShWalker) walkCompoundCommand(command *MkShCompoundCommand, collector chan<- interface{}) {
-       collector <- command
+func (w *MkShWalker) walkCompoundCommand(command *MkShCompoundCommand, callback *MkShWalkCallback) {
+       if callback.CompoundCommand != nil {
+               callback.CompoundCommand(command)
+       }
 
        switch {
        case command.Brace != nil:
-               w.walkList(command.Brace, collector)
+               w.walkList(command.Brace, callback)
        case command.Case != nil:
-               w.walkCase(command.Case, collector)
+               w.walkCase(command.Case, callback)
        case command.For != nil:
-               w.walkFor(command.For, collector)
+               w.walkFor(command.For, callback)
        case command.If != nil:
-               w.walkIf(command.If, collector)
+               w.walkIf(command.If, callback)
        case command.Loop != nil:
-               w.walkLoop(command.Loop, collector)
+               w.walkLoop(command.Loop, callback)
        case command.Subshell != nil:
-               w.walkList(command.Subshell, collector)
+               w.walkList(command.Subshell, callback)
        }
 }
 
-func (w *MkShWalker) walkCase(caseClause *MkShCaseClause, collector chan<- interface{}) {
-       collector <- caseClause
+func (w *MkShWalker) walkCase(caseClause *MkShCaseClause, callback *MkShWalkCallback) {
+       if callback.Case != nil {
+               callback.Case(caseClause)
+       }
 
-       w.walkWord(caseClause.Word, collector)
+       w.walkWord(caseClause.Word, callback)
        for _, caseItem := range caseClause.Cases {
-               collector <- caseItem
-               w.walkWords(caseItem.Patterns, collector)
-               w.walkList(caseItem.Action, collector)
+               if callback.CaseItem != nil {
+                       callback.CaseItem(caseItem)
+               }
+               w.walkWords(caseItem.Patterns, callback)
+               w.walkList(caseItem.Action, callback)
        }
 }
 
-func (w *MkShWalker) walkFunctionDefinition(funcdef *MkShFunctionDefinition, collector chan<- interface{}) {
-       collector <- funcdef
+func (w *MkShWalker) walkFunctionDefinition(funcdef *MkShFunctionDefinition, callback *MkShWalkCallback) {
+       if callback.FunctionDefinition != nil {
+               callback.FunctionDefinition(funcdef)
+       }
 
-       w.walkCompoundCommand(funcdef.Body, collector)
+       w.walkCompoundCommand(funcdef.Body, callback)
 }
 
-func (w *MkShWalker) walkIf(ifClause *MkShIfClause, collector chan<- interface{}) {
-       collector <- ifClause
+func (w *MkShWalker) walkIf(ifClause *MkShIfClause, callback *MkShWalkCallback) {
+       if callback.If != nil {
+               callback.If(ifClause)
+       }
+
        for i, cond := range ifClause.Conds {
-               w.walkList(cond, collector)
-               w.walkList(ifClause.Actions[i], collector)
+               w.walkList(cond, callback)
+               w.walkList(ifClause.Actions[i], callback)
        }
        if ifClause.Else != nil {
-               w.walkList(ifClause.Else, collector)
+               w.walkList(ifClause.Else, callback)
        }
 }
 
-func (w *MkShWalker) walkLoop(loop *MkShLoopClause, collector chan<- interface{}) {
-       collector <- loop
-       w.walkList(loop.Cond, collector)
-       w.walkList(loop.Action, collector)
+func (w *MkShWalker) walkLoop(loop *MkShLoopClause, callback *MkShWalkCallback) {
+       if callback.Loop != nil {
+               callback.Loop(loop)
+       }
+
+       w.walkList(loop.Cond, callback)
+       w.walkList(loop.Action, callback)
 }
 
-func (w *MkShWalker) walkWords(words []*ShToken, collector chan<- interface{}) {
-       collector <- words
+func (w *MkShWalker) walkWords(words []*ShToken, callback *MkShWalkCallback) {
+       if len(words) != 0 {
+               if callback.Words != nil {
+                       callback.Words(words)
+               }
 
-       for _, word := range words {
-               w.walkWord(word, collector)
+               for _, word := range words {
+                       w.walkWord(word, callback)
+               }
        }
 }
 
-func (w *MkShWalker) walkWord(word *ShToken, collector chan<- interface{}) {
-       collector <- word
+func (w *MkShWalker) walkWord(word *ShToken, callback *MkShWalkCallback) {
+       if callback.Word != nil {
+               callback.Word(word)
+       }
 }
 
-func (w *MkShWalker) walkRedirects(redirects []*MkShRedirection, collector chan<- interface{}) {
-       collector <- redirects
+func (w *MkShWalker) walkRedirects(redirects []*MkShRedirection, callback *MkShWalkCallback) {
+       if len(redirects) != 0 {
+               if callback.Redirects != nil {
+                       callback.Redirects(redirects)
+               }
+
+               for _, redirect := range redirects {
+                       if callback.Redirect != nil {
+                               callback.Redirect(redirect)
+                       }
 
-       for _, redirect := range redirects {
-               collector <- redirect
-               w.walkWord(redirect.Target, collector)
+                       w.walkWord(redirect.Target, callback)
+               }
        }
 }
 
-func (w *MkShWalker) walkFor(forClause *MkShForClause, collector chan<- interface{}) {
-       collector <- forClause
+func (w *MkShWalker) walkFor(forClause *MkShForClause, callback *MkShWalkCallback) {
+       if callback.For != nil {
+               callback.For(forClause)
+       }
+       if callback.Varname != nil {
+               callback.Varname(forClause.Varname)
+       }
+
+       w.walkWords(forClause.Values, callback)
+       w.walkList(forClause.Body, callback)
+}
+
+type MkShWalkCallback struct {
+       List               func(list *MkShList)
+       AndOr              func(andor *MkShAndOr)
+       Pipeline           func(pipeline *MkShPipeline)
+       Command            func(command *MkShCommand)
+       SimpleCommand      func(command *MkShSimpleCommand)
+       CompoundCommand    func(command *MkShCompoundCommand)
+       Case               func(caseClause *MkShCaseClause)
+       CaseItem           func(caseItem *MkShCaseItem)
+       FunctionDefinition func(funcdef *MkShFunctionDefinition)
+       If                 func(ifClause *MkShIfClause)
+       Loop               func(loop *MkShLoopClause)
+       Words              func(words []*ShToken)
+       Word               func(word *ShToken)
+       Redirects          func(redirects []*MkShRedirection)
+       Redirect           func(redirect *MkShRedirection)
+       For                func(forClause *MkShForClause)
+       Varname            func(varname string)
+}
 
-       collector <- forClause.Varname
-       w.walkWords(forClause.Values, collector)
-       w.walkList(forClause.Body, collector)
+func NewMkShWalkCallback() *MkShWalkCallback {
+       return &MkShWalkCallback{}
 }

Index: pkgsrc/pkgtools/pkglint/files/package.go
diff -u pkgsrc/pkgtools/pkglint/files/package.go:1.32 pkgsrc/pkgtools/pkglint/files/package.go:1.33
--- pkgsrc/pkgtools/pkglint/files/package.go:1.32       Sat Jul 28 18:31:23 2018
+++ pkgsrc/pkgtools/pkglint/files/package.go    Sun Aug 12 16:31:56 2018
@@ -37,11 +37,15 @@ type Package struct {
        conditionalIncludes   map[string]MkLine
        unconditionalIncludes map[string]MkLine
        once                  Once
+       IgnoreMissingPatches  bool // In distinfo, don't warn about patches that cannot be found.
 }
 
 func NewPackage(pkgpath string) *Package {
        pkg := &Package{
                Pkgpath:               pkgpath,
+               Pkgdir:                ".",
+               Filesdir:              "files",
+               Patchdir:              "patches",
                PlistDirs:             make(map[string]bool),
                PlistFiles:            make(map[string]bool),
                vars:                  NewScope(),
@@ -58,6 +62,12 @@ func NewPackage(pkgpath string) *Package
        return pkg
 }
 
+// File returns the (possibly absolute) path to relativeFilename,
+// as resolved from the package's directory.
+func (pkg *Package) File(relativeFilename string) string {
+       return G.Pkgsrc.File(pkg.Pkgpath + "/" + relativeFilename)
+}
+
 func (pkg *Package) varValue(varname string) (string, bool) {
        switch varname {
        case "KRB5_TYPE":
@@ -151,26 +161,30 @@ func (pkglint *Pkglint) checkdirPackage(
                defer trace.Call1(pkgpath)()
        }
 
+       if strings.Count(pkgpath, "/") != 1 {
+               dummyLine.Fatalf("Internal pkglint error: Wrong pkgpath %q.", pkgpath)
+       }
+
        G.Pkg = NewPackage(pkgpath)
        defer func() { G.Pkg = nil }()
        pkg := G.Pkg
 
        // we need to handle the Makefile first to get some variables
-       lines := pkg.loadPackageMakefile(G.CurrentDir + "/Makefile")
+       lines := pkg.loadPackageMakefile()
        if lines == nil {
                return
        }
 
-       files := dirglob(G.CurrentDir)
+       files := dirglob(pkg.File("."))
        if pkg.Pkgdir != "." {
-               files = append(files, dirglob(G.CurrentDir+"/"+pkg.Pkgdir)...)
+               files = append(files, dirglob(pkg.File(pkg.Pkgdir))...)
        }
        if G.opts.CheckExtra {
-               files = append(files, dirglob(G.CurrentDir+"/"+pkg.Filesdir)...)
+               files = append(files, dirglob(pkg.File(pkg.Filesdir))...)
        }
-       files = append(files, dirglob(G.CurrentDir+"/"+pkg.Patchdir)...)
+       files = append(files, dirglob(pkg.File(pkg.Patchdir))...)
        if pkg.DistinfoFile != "distinfo" && pkg.DistinfoFile != "./distinfo" {
-               files = append(files, G.CurrentDir+"/"+pkg.DistinfoFile)
+               files = append(files, pkg.File(pkg.DistinfoFile))
        }
        haveDistinfo := false
        havePatches := false
@@ -194,7 +208,7 @@ func (pkglint *Pkglint) checkdirPackage(
                if containsVarRef(fname) {
                        continue
                }
-               if fname == G.CurrentDir+"/Makefile" {
+               if fname == pkg.File("Makefile") {
                        if st, err := os.Lstat(fname); err == nil {
                                pkglint.checkExecutable(st, fname)
                        }
@@ -214,7 +228,7 @@ func (pkglint *Pkglint) checkdirPackage(
 
        if G.opts.CheckDistinfo && G.opts.CheckPatches {
                if havePatches && !haveDistinfo {
-                       NewLineWhole(G.CurrentDir+"/"+pkg.DistinfoFile).Warnf("File not found. Please run \"%s makepatchsum\".", confMake)
+                       NewLineWhole(pkg.File(pkg.DistinfoFile)).Warnf("File not found. Please run \"%s makepatchsum\".", confMake)
                }
        }
 
@@ -226,12 +240,13 @@ func (pkglint *Pkglint) checkdirPackage(
                }
        }
 
-       if !isEmptyDir(G.CurrentDir + "/scripts") {
-               NewLineWhole(G.CurrentDir + "/scripts").Warnf("This directory and its contents are deprecated! Please call the script(s) explicitly from the corresponding target(s) in the pkg's 
Makefile.")
+       if !isEmptyDir(pkg.File("scripts")) {
+               NewLineWhole(pkg.File("scripts")).Warnf("This directory and its contents are deprecated! Please call the script(s) explicitly from the corresponding target(s) in the pkg's Makefile.")
        }
 }
 
-func (pkg *Package) loadPackageMakefile(fname string) *MkLines {
+func (pkg *Package) loadPackageMakefile() *MkLines {
+       fname := pkg.File("Makefile")
        if trace.Tracing {
                defer trace.Call1(fname)()
        }
@@ -256,13 +271,19 @@ func (pkg *Package) loadPackageMakefile(
        pkg.Filesdir = pkg.expandVariableWithDefault("FILESDIR", "files")
        pkg.Patchdir = pkg.expandVariableWithDefault("PATCHDIR", "patches")
 
+       // See lang/php/ext.mk
        if varIsDefined("PHPEXT_MK") {
                if !varIsDefined("USE_PHP_EXT_PATCHES") {
                        pkg.Patchdir = "patches"
                }
                if varIsDefined("PECL_VERSION") {
                        pkg.DistinfoFile = "distinfo"
+               } else {
+                       pkg.IgnoreMissingPatches = true
                }
+
+               // For PHP modules that are not PECL, this combination means that
+               // the patches in the distinfo cannot be found in PATCHDIR.
        }
 
        if trace.Tracing {
@@ -349,8 +370,8 @@ func (pkg *Package) readMakefile(fname s
                                        if fileMklines.indentation.IsCheckedFile(includeFile) {
                                                return true // See https://github.com/rillig/pkglint/issues/1
 
-                                       } else if dirname != G.CurrentDir { // Prevent unnecessary syscalls
-                                               dirname = G.CurrentDir
+                                       } else if dirname != pkg.File(".") { // Prevent unnecessary syscalls
+                                               dirname = pkg.File(".")
                                                if !fileExists(dirname + "/" + includeFile) {
                                                        mkline.Errorf("Cannot read %q.", dirname+"/"+includeFile)
                                                        result = false
@@ -402,17 +423,17 @@ func (pkg *Package) checkfilePackageMake
        if !vars.Defined("PLIST_SRC") &&
                !vars.Defined("GENERATE_PLIST") &&
                !vars.Defined("META_PACKAGE") &&
-               !fileExists(G.CurrentDir+"/"+pkg.Pkgdir+"/PLIST") &&
-               !fileExists(G.CurrentDir+"/"+pkg.Pkgdir+"/PLIST.common") {
+               !fileExists(pkg.File(pkg.Pkgdir+"/PLIST")) &&
+               !fileExists(pkg.File(pkg.Pkgdir+"/PLIST.common")) {
                NewLineWhole(fname).Warnf("Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset.")
        }
 
-       if (vars.Defined("NO_CHECKSUM") || vars.Defined("META_PACKAGE")) && isEmptyDir(G.CurrentDir+"/"+pkg.Patchdir) {
-               if distinfoFile := G.CurrentDir + "/" + pkg.DistinfoFile; fileExists(distinfoFile) {
+       if (vars.Defined("NO_CHECKSUM") || vars.Defined("META_PACKAGE")) && isEmptyDir(pkg.File(pkg.Patchdir)) {
+               if distinfoFile := pkg.File(pkg.DistinfoFile); fileExists(distinfoFile) {
                        NewLineWhole(distinfoFile).Warnf("This file should not exist if NO_CHECKSUM or META_PACKAGE is set.")
                }
        } else {
-               if distinfoFile := G.CurrentDir + "/" + pkg.DistinfoFile; !containsVarRef(distinfoFile) && !fileExists(distinfoFile) {
+               if distinfoFile := pkg.File(pkg.DistinfoFile); !containsVarRef(distinfoFile) && !fileExists(distinfoFile) {
                        NewLineWhole(distinfoFile).Warnf("File not found. Please run \"%s makesum\" or define NO_CHECKSUM=yes in the package Makefile.", confMake)
                }
        }
@@ -901,7 +922,7 @@ func (pkg *Package) CheckInclude(mkline 
                mkline.SetConditionVars(conditionVars)
        }
 
-       if path.Dir(abspath(mkline.Filename)) == abspath(G.CurrentDir) {
+       if path.Dir(abspath(mkline.Filename)) == abspath(pkg.File(".")) {
                includefile := mkline.IncludeFile()
 
                if indentation.IsConditional() {

Index: pkgsrc/pkgtools/pkglint/files/package_test.go
diff -u pkgsrc/pkgtools/pkglint/files/package_test.go:1.26 pkgsrc/pkgtools/pkglint/files/package_test.go:1.27
--- pkgsrc/pkgtools/pkglint/files/package_test.go:1.26  Thu Aug  9 20:21:42 2018
+++ pkgsrc/pkgtools/pkglint/files/package_test.go       Sun Aug 12 16:31:56 2018
@@ -159,9 +159,8 @@ func (s *Suite) Test_Package_varorder_li
                ".include \"../../mk/bsd.pkg.mk\"")
 
        t.SetupVartypes()
-       G.CurrentDir = t.TmpDir()
 
-       G.CheckDirent(t.TmpDir() + "/x11/9term")
+       G.CheckDirent(t.File("x11/9term"))
 
        // Since the error is grave enough, the warning about the correct position is suppressed.
        t.CheckOutputLines(
@@ -281,7 +280,6 @@ func (s *Suite) Test_Package_checkPossib
        t := s.Init(c)
 
        G.Pkg = NewPackage("category/pkgbase")
-       G.CurPkgsrcdir = "../.."
        G.Pkg.EffectivePkgname = "package-1.0nb15"
        G.Pkg.EffectivePkgnameLine = t.NewMkLine("category/pkgbase/Makefile", 5, "PKGNAME=dummy")
        G.Pkgsrc.LastChange = map[string]*Change{
@@ -307,61 +305,44 @@ func (s *Suite) Test_Package_checkPossib
 func (s *Suite) Test_checkdirPackage(c *check.C) {
        t := s.Init(c)
 
-       t.SetupFileLines("Makefile",
+       t.SetupFileLines("category/package/Makefile",
                MkRcsID)
-       G.CurrentDir = t.TmpDir()
 
-       G.checkdirPackage(t.TmpDir())
+       G.checkdirPackage("category/package")
 
        t.CheckOutputLines(
-               "WARN: ~/Makefile: Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset.",
-               "WARN: ~/distinfo: File not found. Please run \""+confMake+" makesum\" or define NO_CHECKSUM=yes in the package Makefile.",
-               "ERROR: ~/Makefile: Each package must define its LICENSE.",
-               "WARN: ~/Makefile: No COMMENT given.")
+               "WARN: ~/category/package/Makefile: Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset.",
+               "WARN: ~/category/package/distinfo: File not found. Please run \""+confMake+" makesum\" or define NO_CHECKSUM=yes in the package Makefile.",
+               "ERROR: ~/category/package/Makefile: Each package must define its LICENSE.",
+               "WARN: ~/category/package/Makefile: No COMMENT given.")
 }
 
-func (s *Suite) Test_checkdirPackage__meta_package_without_license(c *check.C) {
+func (s *Suite) Test_Pkglint_checkdirPackage__meta_package_without_license(c *check.C) {
        t := s.Init(c)
 
-       t.CreateFileLines("Makefile",
+       t.CreateFileLines("category/package/Makefile",
                MkRcsID,
                "",
                "META_PACKAGE=\tyes")
-       G.CurrentDir = t.TmpDir()
        t.SetupVartypes()
 
-       G.checkdirPackage(t.TmpDir())
+       G.checkdirPackage("category/package")
 
        t.CheckOutputLines(
-               "WARN: ~/Makefile: No COMMENT given.") // No error about missing LICENSE.
+               "WARN: ~/category/package/Makefile: No COMMENT given.") // No error about missing LICENSE.
 }
 
 func (s *Suite) Test_Package__varuse_at_load_time(c *check.C) {
        t := s.Init(c)
 
-       t.CreateFileLines("doc/CHANGES-2016",
-               "# dummy")
-       t.CreateFileLines("doc/TODO",
-               "# dummy")
+       t.SetupPkgsrc()
        t.CreateFileLines("licenses/bsd-2",
                "# dummy")
-       t.CreateFileLines("mk/fetch/sites.mk",
-               "# dummy")
-       t.CreateFileLines("mk/bsd.pkg.mk",
-               "# dummy")
-       t.CreateFileLines("mk/defaults/options.description",
-               "option Description")
-       t.CreateFileLines("mk/defaults/mk.conf",
-               "# dummy")
-       t.CreateFileLines("mk/tools/bsd.tools.mk",
-               ".include \"defaults.mk\"")
        t.CreateFileLines("mk/tools/defaults.mk",
                "TOOLS_CREATE+=false",
                "TOOLS_CREATE+=nice",
                "TOOLS_CREATE+=true",
                "_TOOLS_VARNAME.nice=NICE")
-       t.CreateFileLines("mk/bsd.prefs.mk",
-               "# dummy")
 
        t.CreateFileLines("category/pkgbase/Makefile",
                MkRcsID,
@@ -391,7 +372,7 @@ func (s *Suite) Test_Package__varuse_at_
        t.CreateFileLines("category/pkgbase/distinfo",
                RcsID)
 
-       G.Main("pkglint", "-q", "-Wperm", t.TmpDir()+"/category/pkgbase")
+       G.Main("pkglint", "-q", "-Wperm", t.File("category/pkgbase"))
 
        t.CheckOutputLines(
                "WARN: ~/category/pkgbase/Makefile:8: To use the tool \"FALSE\" at load time, bsd.prefs.mk has to be included before.",
@@ -411,11 +392,9 @@ func (s *Suite) Test_Package_loadPackage
                "DISTNAME=distfile_1_67",
                ".include \"../../category/package/Makefile\"")
        pkg := NewPackage("category/package")
-       G.CurrentDir = t.TempFilename("category/package")
-       G.CurPkgsrcdir = "../.."
        G.Pkg = pkg
 
-       pkg.loadPackageMakefile(t.TempFilename("category/package/Makefile"))
+       pkg.loadPackageMakefile()
 
        // Including a package Makefile directly is an error (in the last line),
        // but that is checked later.
@@ -459,8 +438,6 @@ func (s *Suite) Test_Package_conditional
        t.CreateFileLines("sysutils/coreutils/buildlink3.mk", "")
 
        pkg := NewPackage("category/package")
-       G.CurrentDir = t.TmpDir() + "/category/package"
-       G.CurPkgsrcdir = "../.."
        G.Pkg = pkg
 
        G.checkdirPackage("category/package")
@@ -488,8 +465,7 @@ func (s *Suite) Test_Package_includeWith
                "",
                ".include \"../../mk/bsd.pkg.mk\"")
 
-       G.CurrentDir = t.TempFilename("category/package")
-       G.checkdirPackage(G.CurrentDir)
+       G.checkdirPackage("category/package")
 
        t.CheckOutputLines(
                "ERROR: ~/category/package/options.mk: Cannot be read.")
@@ -510,16 +486,14 @@ func (s *Suite) Test_Package_includeAfte
                "",
                ".include \"../../mk/bsd.pkg.mk\"")
 
-       G.CurrentDir = t.TempFilename("category/package")
-       G.checkdirPackage(G.CurrentDir)
+       G.checkdirPackage("category/package")
 
        t.CheckOutputLines(
                "WARN: ~/category/package/Makefile: Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset.",
                "WARN: ~/category/package/distinfo: File not found. Please run \""+confMake+" makesum\" or define NO_CHECKSUM=yes in the package Makefile.",
                "ERROR: ~/category/package/Makefile: Each package must define its LICENSE.",
                "WARN: ~/category/package/Makefile: No COMMENT given.",
-               "ERROR: ~/category/package/Makefile:4: \"options.mk\" does not exist.",
-               "ERROR: ~/category/package/Makefile:7: \"/mk/bsd.pkg.mk\" does not exist.")
+               "ERROR: ~/category/package/Makefile:4: \"options.mk\" does not exist.")
 }
 
 // See https://github.com/rillig/pkglint/issues/1
@@ -537,8 +511,7 @@ func (s *Suite) Test_Package_includeOthe
                "",
                ".include \"../../mk/bsd.pkg.mk\"")
 
-       G.CurrentDir = t.TempFilename("category/package")
-       G.checkdirPackage(G.CurrentDir)
+       G.checkdirPackage("category/package")
 
        t.CheckOutputLines(
                "ERROR: ~/category/package/another.mk: Cannot be read.")
@@ -572,12 +545,9 @@ func (s *Suite) Test_Package__redundant_
                ".include \"../../math/R/Makefile.extension\"",
                ".include \"../../mk/bsd.pkg.mk\"")
 
-       G.CurrentDir = t.TempFilename("math/R-date")
-       G.CurPkgsrcdir = "../.."
-
        // See Package.checkfilePackageMakefile
        // See Scope.uncond
-       G.checkdirPackage(G.CurrentDir)
+       G.checkdirPackage("math/R-date")
 
        t.CheckOutputLines(
                "NOTE: ~/math/R-date/Makefile:6: Definition of MASTER_SITES is redundant because of ../R/Makefile.extension:4.")

Index: pkgsrc/pkgtools/pkglint/files/patches_test.go
diff -u pkgsrc/pkgtools/pkglint/files/patches_test.go:1.17 pkgsrc/pkgtools/pkglint/files/patches_test.go:1.18
--- pkgsrc/pkgtools/pkglint/files/patches_test.go:1.17  Sat Jul 28 18:31:23 2018
+++ pkgsrc/pkgtools/pkglint/files/patches_test.go       Sun Aug 12 16:31:56 2018
@@ -48,7 +48,6 @@ func (s *Suite) Test_ChecklinesPatch__wi
                "SHA1 (some patch) = 49abd735b7e706ea9ed6671628bb54e91f7f5ffb")
 
        t.SetupCommandLine("-Wall", "--autofix")
-       G.CurrentDir = t.TmpDir()
        G.Pkg = &Package{DistinfoFile: "distinfo"}
 
        ChecklinesPatch(patchLines)
@@ -479,3 +478,49 @@ func (s *Suite) Test_ChecklinesPatch__au
 
        t.CheckOutputEmpty()
 }
+
+func (s *Suite) Test_ChecklinesPatch__crlf(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wall", "--autofix")
+       lines := t.SetupFileLines("patch-aa",
+               RcsID,
+               "",
+               "Documentation",
+               "",
+               "--- oldfile",
+               "+++ newfile",
+               "@@ -1,1 +1,1 @@\r",
+               "-old line",
+               "+new line")
+
+       ChecklinesPatch(lines)
+
+       t.CheckOutputLines(
+               "AUTOFIX: ~/patch-aa:7: Replacing \"\\r\\n\" with \"\\n\".")
+}
+
+func (s *Suite) Test_ChecklinesPatch__autogenerated(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wall")
+       lines := t.SetupFileLines("patch-aa",
+               RcsID,
+               "",
+               "Documentation",
+               "",
+               "--- configure.orig",
+               "+++ configure",
+               "@@ -1,1 +1,1 @@",
+               "-old line",
+               "+: Avoid regenerating within pkgsrc")
+
+       ChecklinesPatch(lines)
+
+       t.CheckOutputLines(
+               "ERROR: ~/patch-aa:9: This code must not be included in patches.")
+}
+
+func (s *Suite) Test_FileType_String(c *check.C) {
+       c.Check(ftUnknown.String(), equals, "unknown")
+}

Index: pkgsrc/pkgtools/pkglint/files/pkglint_test.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.21 pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.22
--- pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.21  Thu Aug  9 20:08:12 2018
+++ pkgsrc/pkgtools/pkglint/files/pkglint_test.go       Sun Aug 12 16:31:56 2018
@@ -124,15 +124,11 @@ func (s *Suite) Test_Pkglint_Main__unkno
 func (s *Suite) Test_Pkglint_Main__complete_package(c *check.C) {
        t := s.Init(c)
 
-       // This file is needed to locate the pkgsrc root directory.
-       // See findPkgsrcTopdir.
-       t.CreateFileLines("mk/bsd.pkg.mk",
-               "# dummy")
+       t.SetupPkgsrc()
 
-       // See Pkgsrc.loadDocChanges.
        // FIXME: pkglint should warn that the latest version in this file
        // (1.10) doesn't match the current version in the package (1.11).
-       t.CreateFileLines("doc/CHANGES-2018",
+       t.SetupFileLines("doc/CHANGES-2018",
                RcsID,
                "",
                "Changes to the packages collection and infrastructure in 2018:",
@@ -140,7 +136,7 @@ func (s *Suite) Test_Pkglint_Main__compl
                "\tUpdated sysutils/checkperms to 1.10 [rillig 2018-01-05]")
 
        // See Pkgsrc.loadSuggestedUpdates.
-       t.CreateFileLines("doc/TODO",
+       t.SetupFileLines("doc/TODO",
                RcsID,
                "",
                "Suggested package updates",
@@ -148,47 +144,27 @@ func (s *Suite) Test_Pkglint_Main__compl
                "\to checkperms-1.13 [supports more file formats]")
 
        // The LICENSE in the package Makefile is searched here.
-       t.CreateFileLines("licenses/bsd-2",
+       t.SetupFileLines("licenses/bsd-2",
                "# dummy")
 
        // The MASTER_SITES in the package Makefile are searched here.
        // See Pkgsrc.loadMasterSites.
-       t.CreateFileLines("mk/fetch/sites.mk",
+       t.SetupFileMkLines("mk/fetch/sites.mk",
                MkRcsID,
                "",
                "MASTER_SITE_GITHUB+=\thttps://github.com/";)
 
-       // The options for the PKG_OPTIONS framework must be readable.
-       // See Pkgsrc.loadPkgOptions.
-       t.CreateFileLines("mk/defaults/options.description",
-               "option Description")
-
-       // The user-defined variables are read in to check for missing
-       // BUILD_DEFS declarations in the package Makefile.
-       t.CreateFileLines("mk/defaults/mk.conf",
-               "# dummy")
-
-       // The tool definitions are read in to check for missing
-       // USE_TOOLS declarations in the package Makefile.
-       // They spread over several files from the pkgsrc infrastructure.
-       t.CreateFileLines("mk/tools/bsd.tools.mk",
-               ".include \"defaults.mk\"")
-       t.CreateFileLines("mk/tools/defaults.mk",
-               "# dummy")
-       t.CreateFileLines("mk/bsd.prefs.mk",
-               "# dummy")
-
        // The existence of this file makes the category "sysutils" valid.
        // The category "tools" on the other hand is not valid.
-       t.CreateFileLines("sysutils/Makefile",
-               "# dummy")
+       t.SetupFileMkLines("sysutils/Makefile",
+               MkRcsID)
 
        // The package Makefile is quite simple, containing just the
        // standard variable definitions. The data for checking the variable
        // values is partly defined in the pkgsrc infrastructure files
        // (as defined in the previous lines), and partly in the pkglint
        // code directly. Many details can be found in vartypecheck.go.
-       t.CreateFileLines("sysutils/checkperms/Makefile",
+       t.SetupFileMkLines("sysutils/checkperms/Makefile",
                MkRcsID,
                "",
                "DISTNAME=\tcheckperms-1.11",
@@ -202,7 +178,7 @@ func (s *Suite) Test_Pkglint_Main__compl
                "",
                ".include \"../../mk/bsd.pkg.mk\"")
 
-       t.CreateFileLines("sysutils/checkperms/MESSAGE",
+       t.SetupFileLines("sysutils/checkperms/MESSAGE",
                "===========================================================================",
                RcsID,
                "",
@@ -210,18 +186,18 @@ func (s *Suite) Test_Pkglint_Main__compl
                "",
                "===========================================================================")
 
-       t.CreateFileLines("sysutils/checkperms/PLIST",
+       t.SetupFileLines("sysutils/checkperms/PLIST",
                PlistRcsID,
                "bin/checkperms",
                "man/man1/checkperms.1")
 
-       t.CreateFileLines("sysutils/checkperms/README",
+       t.SetupFileLines("sysutils/checkperms/README",
                "When updating this package, test the pkgsrc bootstrap.")
 
-       t.CreateFileLines("sysutils/checkperms/TODO",
+       t.SetupFileLines("sysutils/checkperms/TODO",
                "Make the package work on MS-DOS")
 
-       t.CreateFileLines("sysutils/checkperms/patches/patch-checkperms.c",
+       t.SetupFileLines("sysutils/checkperms/patches/patch-checkperms.c",
                RcsID,
                "",
                "A simple patch demonstrating that pkglint checks for missing",
@@ -234,7 +210,7 @@ func (s *Suite) Test_Pkglint_Main__compl
                "+// Header 1",
                "+// Header 2",
                "+// Header 3")
-       t.CreateFileLines("sysutils/checkperms/distinfo",
+       t.SetupFileLines("sysutils/checkperms/distinfo",
                RcsID,
                "",
                "SHA1 (checkperms-1.12.tar.gz) = 34c084b4d06bcd7a8bba922ff57677e651eeced5",
@@ -243,7 +219,7 @@ func (s *Suite) Test_Pkglint_Main__compl
                "Size (checkperms-1.12.tar.gz) = 6621 bytes",
                "SHA1 (patch-checkperms.c) = asdfasdf") // Invalid SHA-1 checksum
 
-       G.Main("pkglint", "-Wall", "-Call", t.TempFilename("sysutils/checkperms"))
+       G.Main("pkglint", "-Wall", "-Call", t.File("sysutils/checkperms"))
 
        t.CheckOutputLines(
                "WARN: ~/sysutils/checkperms/Makefile:3: This package should be updated to 1.13 ([supports more file formats]).",
@@ -276,7 +252,7 @@ func (s *Suite) Test_Pkglint_CheckDirent
 
        t.SetupFileLines("empty")
 
-       G.CheckDirent(t.TmpDir())
+       G.CheckDirent(t.File("."))
 
        t.CheckOutputLines(
                "ERROR: ~: Cannot determine the pkgsrc root directory for \"~\".")
@@ -290,22 +266,22 @@ func (s *Suite) Test_Pkglint_CheckDirent
        t.SetupFileLines("category/Makefile")
        t.SetupFileLines("Makefile")
 
-       G.CheckDirent(t.TmpDir())
+       G.CheckDirent(t.File("."))
 
        t.CheckOutputLines(
                "ERROR: ~/Makefile: Must not be empty.")
 
-       G.CheckDirent(t.TempFilename("category"))
+       G.CheckDirent(t.File("category"))
 
        t.CheckOutputLines(
                "ERROR: ~/category/Makefile: Must not be empty.")
 
-       G.CheckDirent(t.TempFilename("category/package"))
+       G.CheckDirent(t.File("category/package"))
 
        t.CheckOutputLines(
                "ERROR: ~/category/package/Makefile: Must not be empty.")
 
-       G.CheckDirent(t.TempFilename("category/package/nonexistent"))
+       G.CheckDirent(t.File("category/package/nonexistent"))
 
        t.CheckOutputLines(
                "ERROR: ~/category/package/nonexistent: No such file or directory.")
@@ -437,9 +413,9 @@ func (s *Suite) Test_ChecklinesMessage__
 func (s *Suite) Test_Pkgsrc_Latest_no_basedir(c *check.C) {
        t := s.Init(c)
 
-       latest1 := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
+       latest := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
 
-       c.Check(latest1, equals, "")
+       c.Check(latest, equals, "")
        t.CheckOutputLines(
                "ERROR: Cannot find latest version of \"^python[0-9]+$\" in \"~/lang\".")
 }
@@ -449,9 +425,9 @@ func (s *Suite) Test_Pkgsrc_Latest_no_su
 
        t.SetupFileLines("lang/Makefile")
 
-       latest2 := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
+       latest := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
 
-       c.Check(latest2, equals, "")
+       c.Check(latest, equals, "")
        t.CheckOutputLines(
                "ERROR: Cannot find latest version of \"^python[0-9]+$\" in \"~/lang\".")
 }
@@ -462,9 +438,9 @@ func (s *Suite) Test_Pkgsrc_Latest_singl
        t.SetupFileLines("lang/Makefile")
        t.SetupFileLines("lang/python27/Makefile")
 
-       latest3 := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
+       latest := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
 
-       c.Check(latest3, equals, "../../lang/python27")
+       c.Check(latest, equals, "../../lang/python27")
 }
 
 func (s *Suite) Test_Pkgsrc_Latest_multi(c *check.C) {
@@ -474,9 +450,9 @@ func (s *Suite) Test_Pkgsrc_Latest_multi
        t.SetupFileLines("lang/python27/Makefile")
        t.SetupFileLines("lang/python35/Makefile")
 
-       latest4 := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
+       latest := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
 
-       c.Check(latest4, equals, "../../lang/python35")
+       c.Check(latest, equals, "../../lang/python35")
 }
 
 func (s *Suite) Test_Pkgsrc_Latest_numeric(c *check.C) {
@@ -491,3 +467,20 @@ func (s *Suite) Test_Pkgsrc_Latest_numer
 
        c.Check(latest, equals, "postgresql104")
 }
+
+// Demonstrates that an ALTERNATIVES file can be tested individually,
+// without any dependencies on a whole package or a PLIST file.
+func (s *Suite) Test_Pkglint_Checkfile__alternatives(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupPkgsrc()
+       lines := t.SetupFileLines("category/package/ALTERNATIVES",
+               "bin/tar @PREFIX@/bin/gnu-tar")
+
+       G.Main("pkglint", lines[0].Filename)
+
+       t.CheckOutputLines(
+               "NOTE: ~/category/package/ALTERNATIVES:1: @PREFIX@/ can be omitted from the file name.",
+               "Looks fine.",
+               "(Run \"pkglint -e\" to show explanations.)")
+}

Index: pkgsrc/pkgtools/pkglint/files/pkgsrc.go
diff -u pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.6 pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.7
--- pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.6 Thu Aug  9 20:08:12 2018
+++ pkgsrc/pkgtools/pkglint/files/pkgsrc.go     Sun Aug 12 16:31:56 2018
@@ -552,7 +552,7 @@ func (src *Pkgsrc) LoadExistingLines(fil
 // Example:
 //  NewPkgsrc("/usr/pkgsrc").File("distfiles") => "/usr/pkgsrc/distfiles"
 func (src *Pkgsrc) File(relativeName string) string {
-       return src.topdir + "/" + relativeName
+       return cleanpath(src.topdir + "/" + relativeName)
 }
 
 // ToRel returns the path of `fileName`, relative to the pkgsrc top directory.

Index: pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go
diff -u pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.4 pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.5
--- pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.4    Thu Aug  9 20:08:12 2018
+++ pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go        Sun Aug 12 16:31:56 2018
@@ -33,7 +33,7 @@ func (s *Suite) Test_Pkgsrc_loadMasterSi
 func (s *Suite) Test_Pkgsrc_InitVartypes(c *check.C) {
        t := s.Init(c)
 
-       src := NewPkgsrc(t.TmpDir())
+       src := NewPkgsrc(t.File("."))
        src.InitVartypes()
 
        c.Check(src.vartypes["BSD_MAKE_ENV"].basicType.name, equals, "ShellWord")
@@ -96,8 +96,6 @@ func (s *Suite) Test_Pkgsrc_loadTools(c 
                "USE_TOOLS+=\tm4:pkgsrc")
        t.SetupFileLines("mk/bsd.pkg.mk",
                "USE_TOOLS+=\tmv")
-       G.CurrentDir = t.TmpDir()
-       G.CurPkgsrcdir = "."
 
        G.Pkgsrc.loadTools()
 
@@ -135,7 +133,7 @@ func (s *Suite) Test_Pkgsrc_loadDocChang
                "\tRemoved category/package successor category/package2 [author6 2018-01-06]",
                "\tDowngraded category/package to 1.2 [author7 2018-01-07]")
 
-       changes := G.Pkgsrc.loadDocChangesFromFile(t.TmpDir() + "/doc/CHANGES-2018")
+       changes := G.Pkgsrc.loadDocChangesFromFile(t.File("doc/CHANGES-2018"))
 
        c.Assert(len(changes), equals, 7)
        c.Check(*changes[0], equals, Change{changes[0].Line, "Added", "category/package", "1.0", "author1", "2015-01-01"})

Index: pkgsrc/pkgtools/pkglint/files/shell.go
diff -u pkgsrc/pkgtools/pkglint/files/shell.go:1.23 pkgsrc/pkgtools/pkglint/files/shell.go:1.24
--- pkgsrc/pkgtools/pkglint/files/shell.go:1.23 Sat Mar 24 14:32:49 2018
+++ pkgsrc/pkgtools/pkglint/files/shell.go      Sun Aug 12 16:31:56 2018
@@ -338,27 +338,25 @@ func (shline *ShellLine) CheckShellComma
        spc := &ShellProgramChecker{shline}
        spc.checkConditionalCd(program)
 
-       (*MkShWalker).Walk(nil, program, func(node interface{}) {
-               if cmd, ok := node.(*MkShSimpleCommand); ok {
-                       scc := NewSimpleCommandChecker(shline, cmd)
-                       scc.Check()
-                       if scc.strcmd.Name == "set" && scc.strcmd.AnyArgMatches(`^-.*e`) {
-                               *pSetE = true
-                       }
-               }
-
-               if cmd, ok := node.(*MkShList); ok {
-                       spc.checkSetE(cmd, pSetE)
-               }
-
-               if cmd, ok := node.(*MkShPipeline); ok {
-                       spc.checkPipeExitcode(line, cmd)
+       callback := NewMkShWalkCallback()
+       callback.SimpleCommand = func(command *MkShSimpleCommand) {
+               scc := NewSimpleCommandChecker(shline, command)
+               scc.Check()
+               if scc.strcmd.Name == "set" && scc.strcmd.AnyArgMatches(`^-.*e`) {
+                       *pSetE = true
                }
+       }
+       callback.List = func(list *MkShList) {
+               spc.checkSetE(list, pSetE)
+       }
+       callback.Pipeline = func(pipeline *MkShPipeline) {
+               spc.checkPipeExitcode(line, pipeline)
+       }
+       callback.Word = func(word *ShToken) {
+               spc.checkWord(word, false)
+       }
 
-               if word, ok := node.(*ShToken); ok {
-                       spc.checkWord(word, false)
-               }
-       })
+       NewMkShWalker().Walk(program, callback)
 }
 
 func (shline *ShellLine) CheckShellCommands(shellcmds string) {
@@ -743,20 +741,29 @@ func (spc *ShellProgramChecker) checkCon
                }
        }
 
-       (*MkShWalker).Walk(nil, list, func(node interface{}) {
-               if cmd, ok := node.(*MkShIfClause); ok {
-                       for _, cond := range cmd.Conds {
-                               if simple := getSimple(cond); simple != nil {
-                                       checkConditionalCd(simple)
-                               }
-                       }
-               }
-               if cmd, ok := node.(*MkShLoopClause); ok {
-                       if simple := getSimple(cmd.Cond); simple != nil {
+       callback := NewMkShWalkCallback()
+       callback.If = func(ifClause *MkShIfClause) {
+               for _, cond := range ifClause.Conds {
+                       if simple := getSimple(cond); simple != nil {
                                checkConditionalCd(simple)
                        }
                }
-       })
+       }
+       callback.Loop = func(loop *MkShLoopClause) {
+               if simple := getSimple(loop.Cond); simple != nil {
+                       checkConditionalCd(simple)
+               }
+       }
+       callback.Pipeline = func(pipeline *MkShPipeline) {
+               if pipeline.Negated {
+                       spc.shline.mkline.Warnf("The Solaris /bin/sh does not support negation of shell commands.")
+                       Explain(
+                               "The GNU Autoconf manual has many more details of what shell",
+                               "features to avoid for portable programs.  It can be read at:",
+                               "https://www.gnu.org/software/autoconf/manual/autoconf.html#Limitations-of-Builtins";)
+               }
+       }
+       NewMkShWalker().Walk(list, callback)
 }
 
 func (spc *ShellProgramChecker) checkWords(words []*ShToken, checkQuoting bool) {

Index: pkgsrc/pkgtools/pkglint/files/shell_test.go
diff -u pkgsrc/pkgtools/pkglint/files/shell_test.go:1.27 pkgsrc/pkgtools/pkglint/files/shell_test.go:1.28
--- pkgsrc/pkgtools/pkglint/files/shell_test.go:1.27    Thu Aug  9 20:08:12 2018
+++ pkgsrc/pkgtools/pkglint/files/shell_test.go Sun Aug 12 16:31:56 2018
@@ -680,3 +680,33 @@ func (s *Suite) Test_ShellLine__variable
                "WARN: dummy.mk:2: Unquoted shell variable \"comment\".",
                "WARN: dummy.mk:2: ECHO should not be evaluated indirectly at load time.")
 }
+
+func (s *Suite) Test_ShellLine_CheckShellCommand__cd_inside_if(c *check.C) {
+       t := s.Init(c)
+
+       mklines := t.NewMkLines("Makefile",
+               MkRcsID,
+               "",
+               "\t${RUN} if cd /bin; then echo \"/bin exists.\"; fi")
+
+       mklines.Check()
+
+       t.CheckOutputLines(
+               "ERROR: Makefile:3: The Solaris /bin/sh cannot handle \"cd\" inside conditionals.",
+               "WARN: Makefile:3: Found absolute pathname: /bin")
+}
+
+func (s *Suite) Test_ShellLine_CheckShellCommand__negated_pipe(c *check.C) {
+       t := s.Init(c)
+
+       mklines := t.NewMkLines("Makefile",
+               MkRcsID,
+               "",
+               "\t${RUN} if ! test -f /etc/passwd; then echo \"passwd is missing.\"; fi")
+
+       mklines.Check()
+
+       t.CheckOutputLines(
+               "WARN: Makefile:3: The Solaris /bin/sh does not support negation of shell commands.",
+               "WARN: Makefile:3: Found absolute pathname: /etc/passwd")
+}

Index: pkgsrc/pkgtools/pkglint/files/util.go
diff -u pkgsrc/pkgtools/pkglint/files/util.go:1.25 pkgsrc/pkgtools/pkglint/files/util.go:1.26
--- pkgsrc/pkgtools/pkglint/files/util.go:1.25  Thu Aug  9 20:08:12 2018
+++ pkgsrc/pkgtools/pkglint/files/util.go       Sun Aug 12 16:31:56 2018
@@ -15,6 +15,18 @@ import (
        "time"
 )
 
+type YesNoUnknown uint8
+
+const (
+       no YesNoUnknown = iota
+       yes
+       unknown
+)
+
+func (ynu YesNoUnknown) String() string {
+       return [...]string{"no", "yes", "unknown"}[ynu]
+}
+
 // Short names for commonly used functions.
 func contains(s, substr string) bool {
        return strings.Contains(s, substr)
@@ -313,7 +325,6 @@ func mkopSubst(s string, left bool, from
 }
 
 // relpath returns the relative path from `from` to `to`.
-// If `to` is not within `from`, it panics.
 func relpath(from, to string) string {
        absFrom, err1 := filepath.Abs(from)
        absTo, err2 := filepath.Abs(to)
@@ -348,6 +359,9 @@ func cleanpath(fname string) string {
        for contains(tmp, "/./") {
                tmp = strings.Replace(tmp, "/./", "/", -1)
        }
+       if len(tmp) > 2 && hasSuffix(tmp, "/.") {
+               tmp = tmp[:len(tmp)-2]
+       }
        for contains(tmp, "//") {
                tmp = strings.Replace(tmp, "//", "/", -1)
        }

Index: pkgsrc/pkgtools/pkglint/files/vartypecheck.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.36 pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.37
--- pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.36  Sat Jul 28 20:44:45 2018
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck.go       Sun Aug 12 16:31:56 2018
@@ -139,7 +139,7 @@ func (cv *VartypeCheck) BuildlinkDepmeth
 }
 
 func (cv *VartypeCheck) Category() {
-       if cv.Value != "wip" && fileExists(G.CurrentDir+"/"+G.CurPkgsrcdir+"/"+cv.Value+"/Makefile") {
+       if cv.Value != "wip" && fileExists(G.Pkgsrc.File(cv.Value+"/Makefile")) {
                return
        }
        switch cv.Value {
@@ -757,7 +757,8 @@ func (cv *VartypeCheck) PkgOptionsVar() 
 // A directory name relative to the top-level pkgsrc directory.
 // Despite its name, it is more similar to RelativePkgDir than to RelativePkgPath.
 func (cv *VartypeCheck) PkgPath() {
-       MkLineChecker{cv.MkLine}.CheckRelativePkgdir(G.CurPkgsrcdir + "/" + cv.Value)
+       pkgsrcdir := relpath(path.Dir(cv.MkLine.Filename), G.Pkgsrc.File("."))
+       MkLineChecker{cv.MkLine}.CheckRelativePkgdir(pkgsrcdir + "/" + cv.Value)
 }
 
 func (cv *VartypeCheck) PkgRevision() {

Index: pkgsrc/pkgtools/pkglint/files/histogram/histogram.go
diff -u pkgsrc/pkgtools/pkglint/files/histogram/histogram.go:1.2 pkgsrc/pkgtools/pkglint/files/histogram/histogram.go:1.3
--- pkgsrc/pkgtools/pkglint/files/histogram/histogram.go:1.2    Sun Jan 28 23:21:16 2018
+++ pkgsrc/pkgtools/pkglint/files/histogram/histogram.go        Sun Aug 12 16:31:56 2018
@@ -40,7 +40,7 @@ func (h *Histogram) PrintStats(caption s
 
        for i, entry := range entries {
                fmt.Fprintf(out, "%s %6d %s\n", caption, entry.count, entry.s)
-               if limit > 0 && i >= limit {
+               if limit >= 0 && i >= limit {
                        break
                }
        }

Index: pkgsrc/pkgtools/pkglint/files/pkgver/vercmp_test.go
diff -u pkgsrc/pkgtools/pkglint/files/pkgver/vercmp_test.go:1.3 pkgsrc/pkgtools/pkglint/files/pkgver/vercmp_test.go:1.4
--- pkgsrc/pkgtools/pkglint/files/pkgver/vercmp_test.go:1.3     Thu Jul 12 16:23:36 2018
+++ pkgsrc/pkgtools/pkglint/files/pkgver/vercmp_test.go Sun Aug 12 16:31:56 2018
@@ -17,6 +17,7 @@ func (s *Suite) Test_newVersion(c *check
        c.Check(newVersion("5.0nb5"), check.DeepEquals, &version{[]int{5, 0, 0}, 5})
        c.Check(newVersion("0.0.1-SNAPSHOT"), check.DeepEquals, &version{[]int{0, 0, 0, 0, 1, 19, 14, 1, 16, 19, 8, 15, 20}, 0})
        c.Check(newVersion("1.0alpha3"), check.DeepEquals, &version{[]int{1, 0, 0, -3, 3}, 0})
+       c.Check(newVersion("1_0alpha3"), check.DeepEquals, &version{[]int{1, 0, 0, -3, 3}, 0})
        c.Check(newVersion("2.5beta"), check.DeepEquals, &version{[]int{2, 0, 5, -2}, 0})
        c.Check(newVersion("20151110"), check.DeepEquals, &version{[]int{20151110}, 0})
        c.Check(newVersion("0"), check.DeepEquals, &version{[]int{0}, 0})
@@ -37,7 +38,7 @@ func (s *Suite) Test_Compare(c *check.C)
                {"1.0alpha3"},
                {"1", "1.0", "1.0.0"},
                {"1.0nb1"},
-               {"1.0nb2"},
+               {"1.0nb2", "1_0nb2"},
                {"1.0.1a", "1.0.a1", "1.0.aa"},
                {"1.0.1z"},
                {"1.0.11", "1.0.k"},
@@ -52,23 +53,25 @@ func (s *Suite) Test_Compare(c *check.C)
                {"20151110"},
        }
 
+       checkVersion := func(i int, iversion string, j int, jversion string) {
+               actual := Compare(iversion, jversion)
+               switch {
+               case i < j && !(actual < 0):
+                       c.Check([]interface{}{i, iversion, j, jversion, actual}, check.DeepEquals, []interface{}{i, iversion, j, jversion, "<0"})
+               case i == j && !(actual == 0):
+                       c.Check([]interface{}{i, iversion, j, jversion, actual}, check.DeepEquals, []interface{}{i, iversion, j, jversion, "==0"})
+               case i > j && !(actual > 0):
+                       c.Check([]interface{}{i, iversion, j, jversion, actual}, check.DeepEquals, []interface{}{i, iversion, j, jversion, ">0"})
+               }
+       }
+
        for i, iversions := range versions {
-               for _, iversion := range iversions {
-                       for j, jversions := range versions {
+               for j, jversions := range versions {
+                       for _, iversion := range iversions {
                                for _, jversion := range jversions {
-                                       actual := Compare(iversion, jversion)
-                                       if i < j && !(actual < 0) {
-                                               c.Check([]interface{}{i, iversion, j, jversion, "<0"}, check.DeepEquals, []interface{}{i, iversion, j, jversion, actual})
-                                       }
-                                       if i == j && !(actual == 0) {
-                                               c.Check([]interface{}{i, iversion, j, jversion, "==0"}, check.DeepEquals, []interface{}{i, iversion, j, jversion, actual})
-                                       }
-                                       if i > j && !(actual > 0) {
-                                               c.Check([]interface{}{i, iversion, j, jversion, ">0"}, check.DeepEquals, []interface{}{i, iversion, j, jversion, actual})
-                                       }
+                                       checkVersion(i, iversion, j, jversion)
                                }
                        }
-
                }
        }
 }

Added files:

Index: pkgsrc/pkgtools/pkglint/files/vardefs_test.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/vardefs_test.go:1.1
--- /dev/null   Sun Aug 12 16:31:56 2018
+++ pkgsrc/pkgtools/pkglint/files/vardefs_test.go       Sun Aug 12 16:31:56 2018
@@ -0,0 +1,21 @@
+package main
+
+import "gopkg.in/check.v1"
+
+func (s *Suite) Test_InitVartypes__enumFrom(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupFileMkLines("editors/emacs/modules.mk",
+               MkRcsID,
+               "",
+               "_EMACS_VERSIONS_ALL=\temacs31",
+               "_EMACS_VERSIONS_ALL=\tignored")
+       mklines := t.SetupFileMkLines("Makefile",
+               MkRcsID,
+               "")
+
+       t.SetupVartypes()
+       vartype := mklines.mklines[1].VariableType("EMACS_VERSIONS_ACCEPTED")
+
+       c.Check(vartype.String(), equals, "ShellList of enum: emacs31 ")
+}



Home | Main Index | Thread Index | Old Index