summaryrefslogtreecommitdiff
path: root/pkgtools/pkglint/files/mkline.go
diff options
context:
space:
mode:
authorrillig <rillig@pkgsrc.org>2016-06-05 11:24:32 +0000
committerrillig <rillig@pkgsrc.org>2016-06-05 11:24:32 +0000
commitbec7f522d057796813bb23512b253c7ad4e41c89 (patch)
tree09d09b9e63a8be86da2b8da29e0779e275052eb5 /pkgtools/pkglint/files/mkline.go
parent18c8e185e87c5012e9079d3a9eb731637a763c66 (diff)
downloadpkgsrc-bec7f522d057796813bb23512b253c7ad4e41c89.tar.gz
Updated pkglint to 5.4.0.
Changes since 5.3.7: * Replaced the -D... debug options with a single -d * Omitted duplicate diagnostics * Marked the :Q operator unnecessary for some variables * Improved detection of whether bsd.prefs.mk has been included, which fixed unwarranted warnings about load time evaluation of some variables like ${ECHO} and ${SED} * Improved detection for $(VAR) with round parentheses * Fixed allowed locations for several variables * Improved detection for used variables (still not perfect) * Added warning that MASTER_SITES should not be used in HOMEPAGE * Fixed warning about manual patches not being in distinfo * Added a check for missing MASTER_SITE_* variables * Added a check for unfinished url2pkg work * Fixed several wrong warnings
Diffstat (limited to 'pkgtools/pkglint/files/mkline.go')
-rw-r--r--pkgtools/pkglint/files/mkline.go838
1 files changed, 611 insertions, 227 deletions
diff --git a/pkgtools/pkglint/files/mkline.go b/pkgtools/pkglint/files/mkline.go
index 96677eb7b21..0953797f768 100644
--- a/pkgtools/pkglint/files/mkline.go
+++ b/pkgtools/pkglint/files/mkline.go
@@ -5,6 +5,7 @@ package main
import (
"fmt"
"os"
+ "path"
"strconv"
"strings"
)
@@ -23,14 +24,12 @@ type MkLine struct {
xcomment string
}
-func (mkline *MkLine) Error1(format, arg1 string) { mkline.Line.Error1(format, arg1) }
-func (mkline *MkLine) Warn0(format string) { mkline.Line.Warn0(format) }
-func (mkline *MkLine) Warn1(format, arg1 string) { mkline.Line.Warn1(format, arg1) }
-func (mkline *MkLine) Warn2(format, arg1, arg2 string) { mkline.Line.Warn2(format, arg1, arg2) }
-func (mkline *MkLine) Note0(format string) { mkline.Line.Note0(format) }
-func (mkline *MkLine) Note2(format, arg1, arg2 string) { mkline.Line.Note2(format, arg1, arg2) }
-func (mkline *MkLine) Debug1(format, arg1 string) { mkline.Line.Debug1(format, arg1) }
-func (mkline *MkLine) Debug2(format, arg1, arg2 string) { mkline.Line.Debug2(format, arg1, arg2) }
+func (mkline *MkLine) Error1(format, arg1 string) { mkline.Line.Error1(format, arg1) }
+func (mkline *MkLine) Warn0(format string) { mkline.Line.Warn0(format) }
+func (mkline *MkLine) Warn1(format, arg1 string) { mkline.Line.Warn1(format, arg1) }
+func (mkline *MkLine) Warn2(format, arg1, arg2 string) { mkline.Line.Warn2(format, arg1, arg2) }
+func (mkline *MkLine) Note0(format string) { mkline.Line.Note0(format) }
+func (mkline *MkLine) Note2(format, arg1, arg2 string) { mkline.Line.Note2(format, arg1, arg2) }
func NewMkLine(line *Line) (mkline *MkLine) {
mkline = &MkLine{Line: line}
@@ -119,6 +118,9 @@ func NewMkLine(line *Line) (mkline *MkLine) {
return mkline
}
+func (mkline *MkLine) String() string {
+ return fmt.Sprintf("%s:%s", mkline.Line.Fname, mkline.Line.linenos())
+}
func (mkline *MkLine) IsVarassign() bool { return mkline.xtype == 1 }
func (mkline *MkLine) Varname() string { return mkline.xs1 }
func (mkline *MkLine) Varcanon() string { return mkline.xs2 }
@@ -143,32 +145,228 @@ func (mkline *MkLine) IsDependency() bool { return mkline.xtype == 8 }
func (mkline *MkLine) Targets() string { return mkline.xs1 }
func (mkline *MkLine) Sources() string { return mkline.xs2 }
-func (mkline *MkLine) Tokenize(s string) {
- p := NewParser(mkline.Line, s)
- p.MkTokens()
- if p.Rest() != "" {
- mkline.Error1("Invalid Makefile syntax at %q.", p.Rest())
+func (mkline *MkLine) Check() {
+ mkline.Line.CheckTrailingWhitespace()
+ mkline.Line.CheckValidCharacters(`[\t -~]`)
+
+ switch {
+ case mkline.IsVarassign():
+ mkline.checkVarassign()
+
+ case mkline.IsShellcmd():
+ shellcmd := mkline.Shellcmd()
+ mkline.checkText(shellcmd)
+ NewShellLine(mkline).CheckShellCommandLine(shellcmd)
+
+ case mkline.IsComment():
+ if hasPrefix(mkline.Line.Text, "# url2pkg-marker") {
+ mkline.Line.Error0("This comment indicates unfinished work (url2pkg).")
+ }
+
+ case mkline.IsInclude():
+ mkline.checkInclude()
}
}
-func (mkline *MkLine) CheckVardef(varname string, op MkOperator) {
- if G.opts.DebugTrace {
- defer tracecall(varname, op)()
+func (mkline *MkLine) checkInclude() {
+ if G.opts.Debug {
+ defer tracecall0()()
}
- defineVar(mkline, varname)
- mkline.CheckVardefPermissions(varname, op)
+ includefile := mkline.Includefile()
+ mustExist := mkline.MustExist()
+ if G.opts.Debug {
+ traceStep1("includefile=%s", includefile)
+ }
+ mkline.CheckRelativePath(includefile, mustExist)
+
+ switch {
+ case hasSuffix(includefile, "/Makefile"):
+ mkline.Line.Error0("Other Makefiles must not be included directly.")
+ Explain4(
+ "If you want to include portions of another Makefile, extract",
+ "the common parts and put them into a Makefile.common. After",
+ "that, both this one and the other package should include the",
+ "Makefile.common.")
+
+ case includefile == "../../mk/bsd.prefs.mk":
+ if path.Base(mkline.Line.Fname) == "buildlink3.mk" {
+ mkline.Note0("For efficiency reasons, please include bsd.fast.prefs.mk instead of bsd.prefs.mk.")
+ }
+ if G.Pkg != nil {
+ G.Pkg.setSeenBsdPrefsMk()
+ }
+
+ case includefile == "../../mk/bsd.fast.prefs.mk", includefile == "../../mk/buildlink3/bsd.builtin.mk":
+ if G.Pkg != nil {
+ G.Pkg.setSeenBsdPrefsMk()
+ }
+
+ case hasSuffix(includefile, "/x11-links/buildlink3.mk"):
+ mkline.Error1("%s must not be included directly. Include \"../../mk/x11.buildlink3.mk\" instead.", includefile)
+
+ case hasSuffix(includefile, "/jpeg/buildlink3.mk"):
+ mkline.Error1("%s must not be included directly. Include \"../../mk/jpeg.buildlink3.mk\" instead.", includefile)
+
+ case hasSuffix(includefile, "/intltool/buildlink3.mk"):
+ mkline.Warn0("Please write \"USE_TOOLS+= intltool\" instead of this line.")
+
+ case hasSuffix(includefile, "/builtin.mk"):
+ mkline.Line.Error2("%s must not be included directly. Include \"%s/buildlink3.mk\" instead.", includefile, path.Dir(includefile))
+ }
+}
+
+func (mkline *MkLine) checkCond(indentation *Indentation, forVars map[string]bool) {
+ indent, directive, args := mkline.Indent(), mkline.Directive(), mkline.Args()
+
+ switch directive {
+ case "endif", "endfor", "elif", "else":
+ if indentation.Len() > 1 {
+ indentation.Pop()
+ } else {
+ mkline.Error1("Unmatched .%s.", directive)
+ }
+ }
+
+ // Check the indentation
+ if expected := strings.Repeat(" ", indentation.Depth()); indent != expected {
+ if G.opts.WarnSpace && !mkline.Line.AutofixReplace("."+indent, "."+expected) {
+ mkline.Line.Notef("This directive should be indented by %d spaces.", indentation.Depth())
+ }
+ }
+
+ if directive == "if" && matches(args, `^!defined\([\w]+_MK\)$`) {
+ indentation.Push(indentation.Depth())
+
+ } else if matches(directive, `^(?:if|ifdef|ifndef|for|elif|else)$`) {
+ indentation.Push(indentation.Depth() + 2)
+ }
+
+ reDirectivesWithArgs := `^(?:if|ifdef|ifndef|elif|for|undef)$`
+ if matches(directive, reDirectivesWithArgs) && args == "" {
+ mkline.Error1("\".%s\" requires arguments.", directive)
+
+ } else if !matches(directive, reDirectivesWithArgs) && args != "" {
+ mkline.Error1("\".%s\" does not take arguments.", directive)
+
+ if directive == "else" {
+ mkline.Note0("If you meant \"else if\", use \".elif\".")
+ }
+
+ } else if directive == "if" || directive == "elif" {
+ mkline.CheckCond()
+
+ } else if directive == "ifdef" || directive == "ifndef" {
+ if matches(args, `\s`) {
+ mkline.Error1("The \".%s\" directive can only handle _one_ argument.", directive)
+ } else {
+ mkline.Line.Warnf("The \".%s\" directive is deprecated. Please use \".if %sdefined(%s)\" instead.",
+ directive, ifelseStr(directive == "ifdef", "", "!"), args)
+ }
+
+ } else if directive == "for" {
+ if m, vars, values := match2(args, `^(\S+(?:\s*\S+)*?)\s+in\s+(.*)$`); m {
+ for _, forvar := range splitOnSpace(vars) {
+ if !G.Infrastructure && hasPrefix(forvar, "_") {
+ mkline.Warn1("Variable names starting with an underscore (%s) are reserved for internal pkgsrc use.", forvar)
+ }
+
+ if matches(forvar, `^[_a-z][_a-z0-9]*$`) {
+ // Fine.
+ } else if matches(forvar, `[A-Z]`) {
+ mkline.Warn0(".for variable names should not contain uppercase letters.")
+ } else {
+ mkline.Error1("Invalid variable name %q.", forvar)
+ }
+
+ forVars[forvar] = true
+ }
+
+ // Check if any of the value's types is not guessed.
+ guessed := true
+ for _, value := range splitOnSpace(values) {
+ if m, vname := match1(value, `^\$\{(.*)\}`); m {
+ vartype := mkline.getVariableType(vname)
+ if vartype != nil && !vartype.guessed {
+ guessed = false
+ }
+ }
+ }
+
+ forLoopType := &Vartype{lkSpace, CheckvarUnchecked, []AclEntry{{"*", aclpAllRead}}, guessed}
+ forLoopContext := &VarUseContext{forLoopType, vucTimeParse, vucQuotFor, vucExtentWord}
+ for _, forLoopVar := range mkline.extractUsedVariables(values) {
+ mkline.CheckVaruse(&MkVarUse{forLoopVar, nil}, forLoopContext)
+ }
+ }
+
+ } else if directive == "undef" && args != "" {
+ for _, uvar := range splitOnSpace(args) {
+ if forVars[uvar] {
+ mkline.Note0("Using \".undef\" after a \".for\" loop is unnecessary.")
+ }
+ }
+ }
}
-func (mkline *MkLine) CheckVardefPermissions(varname string, op MkOperator) {
+func (mkline *MkLine) checkDependencyRule(allowedTargets map[string]bool) {
+ targets := splitOnSpace(mkline.Targets())
+ sources := splitOnSpace(mkline.Sources())
+
+ for _, source := range sources {
+ if source == ".PHONY" {
+ for _, target := range targets {
+ allowedTargets[target] = true
+ }
+ }
+ }
+
+ for _, target := range targets {
+ if target == ".PHONY" {
+ for _, dep := range sources {
+ allowedTargets[dep] = true
+ }
+
+ } else if target == ".ORDER" {
+ // TODO: Check for spelling mistakes.
+
+ } else if !allowedTargets[target] {
+ mkline.Warn1("Unusual target %q.", target)
+ Explain3(
+ "If you want to define your own targets, you can \"declare\"",
+ "them by inserting a \".PHONY: my-target\" line before this line. This",
+ "will tell make(1) to not interpret this target's name as a filename.")
+ }
+ }
+}
+
+func (mkline *MkLine) Tokenize(s string) []*MkToken {
+ if G.opts.Debug {
+ defer tracecall(mkline, s)()
+ }
+
+ p := NewMkParser(mkline.Line, s, true)
+ tokens := p.MkTokens()
+ if p.Rest() != "" {
+ mkline.Warn1("Pkglint parse error in MkLine.Tokenize at %q.", p.Rest())
+ }
+ return tokens
+}
+
+func (mkline *MkLine) checkVarassignDefPermissions() {
if !G.opts.WarnPerm {
return
}
+ if G.opts.Debug {
+ defer tracecall()()
+ }
+ varname := mkline.Varname()
+ op := mkline.Op()
vartype := mkline.getVariableType(varname)
if vartype == nil {
- if G.opts.DebugMisc {
- mkline.Debug1("No type definition found for %q.", varname)
+ if G.opts.Debug {
+ traceStep1("No type definition found for %q.", varname)
}
return
}
@@ -188,8 +386,8 @@ func (mkline *MkLine) CheckVardefPermissions(varname string, op MkOperator) {
case perms.Contains(needed):
break
case perms == aclpUnknown:
- if G.opts.DebugUnchecked {
- mkline.Line.Debug1("Unknown permissions for %q.", varname)
+ if G.opts.Debug {
+ traceStep1("Unknown permissions for %q.", varname)
}
default:
alternativeActions := perms & aclpAllWrite
@@ -216,33 +414,39 @@ func (mkline *MkLine) CheckVardefPermissions(varname string, op MkOperator) {
}
}
-func (mkline *MkLine) CheckVaruse(varname string, mod string, vuc *VarUseContext) {
- if G.opts.DebugTrace {
- defer tracecall(mkline, varname, mod, *vuc)()
+func (mkline *MkLine) CheckVaruse(varuse *MkVarUse, vuc *VarUseContext) {
+ if G.opts.Debug {
+ defer tracecall(mkline, varuse, vuc)()
}
+ if varuse.IsExpression() {
+ return
+ }
+
+ varname := varuse.varname
vartype := mkline.getVariableType(varname)
if G.opts.WarnExtra &&
(vartype == nil || vartype.guessed) &&
!varIsUsed(varname) &&
- !(G.Mk != nil && G.Mk.forVars[varname]) {
+ !(G.Mk != nil && G.Mk.forVars[varname]) &&
+ !hasPrefix(varname, "${") {
mkline.Warn1("%s is used but not defined. Spelling mistake?", varname)
}
- mkline.CheckVarusePermissions(varname, vuc)
+ mkline.CheckVarusePermissions(varname, vartype, vuc)
if varname == "LOCALBASE" && !G.Infrastructure {
mkline.WarnVaruseLocalbase()
}
- needsQuoting := mkline.variableNeedsQuoting(varname, vuc)
+ needsQuoting := mkline.variableNeedsQuoting(varname, vartype, vuc)
if vuc.quoting == vucQuotFor {
mkline.checkVaruseFor(varname, vartype, needsQuoting)
}
if G.opts.WarnQuoting && vuc.quoting != vucQuotUnknown && needsQuoting != nqDontKnow {
- mkline.CheckVaruseShellword(varname, vartype, vuc, mod, needsQuoting)
+ mkline.CheckVaruseShellword(varname, vartype, vuc, varuse.Mod(), needsQuoting)
}
if G.globalData.UserDefinedVars[varname] != nil && !G.globalData.SystemBuildDefs[varname] && !G.Mk.buildDefs[varname] {
@@ -256,19 +460,21 @@ func (mkline *MkLine) CheckVaruse(varname string, mod string, vuc *VarUseContext
}
}
-func (mkline *MkLine) CheckVarusePermissions(varname string, vuc *VarUseContext) {
+func (mkline *MkLine) CheckVarusePermissions(varname string, vartype *Vartype, vuc *VarUseContext) {
if !G.opts.WarnPerm {
return
}
+ if G.opts.Debug {
+ defer tracecall(varname, vuc)()
+ }
// This is the type of the variable that is being used. Not to
// be confused with vuc.vartype, which is the type of the
// context in which the variable is used (often a ShellCommand
// or, in an assignment, the type of the left hand side variable).
- vartype := mkline.getVariableType(varname)
if vartype == nil {
- if G.opts.DebugMisc {
- mkline.Debug1("No type definition found for %q.", varname)
+ if G.opts.Debug {
+ traceStep1("No type definition found for %q.", varname)
}
return
}
@@ -293,31 +499,71 @@ func (mkline *MkLine) CheckVarusePermissions(varname string, vuc *VarUseContext)
isIndirect = true
}
- if isLoadTime && !isIndirect {
+ done := false
+ tool := G.globalData.Tools.byVarname[varname]
+
+ if isLoadTime && tool != nil {
+ done = tool.Predefined && (G.Mk == nil || G.Mk.SeenBsdPrefsMk || G.Pkg == nil || G.Pkg.SeenBsdPrefsMk)
+
+ if !done && G.Pkg != nil && !G.Pkg.SeenBsdPrefsMk && G.Mk != nil && !G.Mk.SeenBsdPrefsMk {
+ mkline.Warn1("To use the tool %q at load time, bsd.prefs.mk has to be included before.", varname)
+ done = true
+ }
+
+ if !done && G.Pkg != nil {
+ usable, defined := G.Pkg.loadTimeTools[tool.Name]
+ if usable {
+ done = true
+ }
+ if defined && !usable {
+ mkline.Warn1("To use the tool %q at load time, it has to be added to USE_TOOLS before including bsd.prefs.mk.", varname)
+ done = true
+ }
+ }
+ }
+
+ if !done && isLoadTime && !isIndirect {
mkline.Warn1("%s should not be evaluated at load time.", varname)
Explain(
"Many variables, especially lists of something, get their values",
"incrementally. Therefore it is generally unsafe to rely on their",
"value until it is clear that it will never change again. This",
"point is reached when the whole package Makefile is loaded and",
- "execution of the shell commands starts, in some cases earlier.",
+ "execution of the shell commands starts; in some cases earlier.",
"",
"Additionally, when using the \":=\" operator, each $$ is replaced",
"with a single $, so variables that have references to shell",
"variables or regular expressions are modified in a subtle way.")
+ done = true
}
- if isLoadTime && isIndirect {
+ if !done && isLoadTime && isIndirect {
mkline.Warn1("%s should not be evaluated indirectly at load time.", varname)
Explain4(
"The variable on the left-hand side may be evaluated at load time,",
"but the variable on the right-hand side may not. Because of the",
"assignment in this line, the variable might be used indirectly",
"at load time, before it is guaranteed to be properly initialized.")
+ done = true
}
if !perms.Contains(aclpUseLoadtime) && !perms.Contains(aclpUse) {
- mkline.Warn1("%s may not be used in this file.", varname)
+ needed := aclpUse
+ if isLoadTime {
+ needed = aclpUseLoadtime
+ }
+ alternativeFiles := vartype.AllowedFiles(needed)
+ if alternativeFiles != "" {
+ mkline.Warn2("%s may not be used in this file; it would be ok in %s.",
+ varname, alternativeFiles)
+ } else {
+ mkline.Warn1("%s may not be used in any file; it is a write-only variable.", varname)
+ }
+ Explain4(
+ "The allowed actions for a variable are determined based on the file",
+ "name in which the variable is used or defined. The exact rules are",
+ "hard-coded into pkglint. If they seem to be incorrect, please ask",
+ "on the tech-pkg@NetBSD.org mailing list.")
}
}
@@ -353,7 +599,7 @@ func (mkline *MkLine) WarnVaruseLocalbase() {
}
func (mkline *MkLine) checkVaruseFor(varname string, vartype *Vartype, needsQuoting NeedsQuoting) {
- if G.opts.DebugTrace {
+ if G.opts.Debug {
defer tracecall(varname, vartype, needsQuoting)()
}
@@ -371,6 +617,9 @@ func (mkline *MkLine) checkVaruseFor(varname string, vartype *Vartype, needsQuot
}
func (mkline *MkLine) CheckVaruseShellword(varname string, vartype *Vartype, vuc *VarUseContext, mod string, needsQuoting NeedsQuoting) {
+ if G.opts.Debug {
+ defer tracecall(varname, vartype, vuc, mod, needsQuoting)()
+ }
// In GNU configure scripts, a few variables need to be
// passed through the :M* operator before they reach the
@@ -390,7 +639,27 @@ func (mkline *MkLine) CheckVaruseShellword(varname string, vartype *Vartype, vuc
} else if needsQuoting == nqYes {
correctMod := strippedMod + ifelseStr(needMstar, ":M*:Q", ":Q")
- if mod != correctMod {
+ if correctMod == mod+":Q" && vuc.extent == vucExtentWordpart && !vartype.IsShell() {
+ mkline.Line.Warnf("The list variable %s should not be embedded in a word.", varname)
+ Explain(
+ "When a list variable has multiple elements, this expression expands",
+ "to something unexpected:",
+ "",
+ "Example: ${MASTER_SITE_SOURCEFORGE}directory/ expands to",
+ "",
+ "\thttps://mirror1.sf.net/ https://mirror2.sf.net/directory/",
+ "",
+ "The first URL is missing the directory. To fix this, write",
+ "\t${MASTER_SITE_SOURCEFORGE:=directory/}.",
+ "",
+ "Example: -l${LIBS} expands to",
+ "",
+ "\t-llib1 lib2",
+ "",
+ "The second library is missing the -l. To fix this, write",
+ "${LIBS:@lib@-l${lib}@}.")
+
+ } else if mod != correctMod {
if vuc.quoting == vucQuotPlain {
if !mkline.Line.AutofixReplace("${"+varname+mod+"}", "${"+varname+correctMod+"}") {
mkline.Line.Warnf("Please use ${%s%s} instead of ${%s%s}.", varname, correctMod, varname, mod)
@@ -401,6 +670,18 @@ func (mkline *MkLine) CheckVaruseShellword(varname string, vartype *Vartype, vuc
}
Explain1(
"See the pkgsrc guide, section \"quoting guideline\", for details.")
+
+ } else if vuc.quoting != vucQuotPlain {
+ mkline.Line.Warnf("Please move ${%s%s} outside of any quoting characters.", varname, mod)
+ Explain(
+ "The :Q modifier only works reliably when it is used outside of any",
+ "quoting characters.",
+ "",
+ "Examples:",
+ "Instead of CFLAGS=\"${CFLAGS:Q}\",",
+ " write CFLAGS=${CFLAGS:Q}.",
+ "Instead of 's,@CFLAGS@,${CFLAGS:Q},',",
+ " write 's,@CFLAGS@,'${CFLAGS:Q}','.")
}
}
@@ -432,8 +713,8 @@ func (mkline *MkLine) CheckVaruseShellword(varname string, vartype *Vartype, vuc
}
}
-func (mkline *MkLine) CheckDecreasingOrder(varname, value string) {
- if G.opts.DebugTrace {
+func (mkline *MkLine) checkVarassignPythonVersions(varname, value string) {
+ if G.opts.Debug {
defer tracecall2(varname, value)()
}
@@ -458,27 +739,28 @@ func (mkline *MkLine) CheckDecreasingOrder(varname, value string) {
}
}
-func (mkline *MkLine) CheckVarassign() {
- if G.opts.DebugTrace {
- defer tracecall0()()
- }
-
+func (mkline *MkLine) checkVarassign() {
varname := mkline.Varname()
op := mkline.Op()
value := mkline.Value()
comment := mkline.Comment()
varcanon := varnameCanon(varname)
- mkline.CheckVardef(varname, op)
- mkline.CheckVarassignBsdPrefs()
+ if G.opts.Debug {
+ defer tracecall(varname, op, value)()
+ }
+
+ defineVar(mkline, varname)
+ mkline.checkVarassignDefPermissions()
+ mkline.checkVarassignBsdPrefs()
- mkline.CheckText(value)
+ mkline.checkText(value)
mkline.CheckVartype(varname, op, value, comment)
// If the variable is not used and is untyped, it may be a spelling mistake.
if op == opAssignEval && varname == strings.ToLower(varname) {
- if G.opts.DebugUnchecked {
- mkline.Debug1("%s might be unused unless it is an argument to a procedure file.", varname)
+ if G.opts.Debug {
+ traceStep1("%s might be unused unless it is an argument to a procedure file.", varname)
}
} else if !varIsUsed(varname) {
@@ -491,7 +773,127 @@ func (mkline *MkLine) CheckVarassign() {
}
}
- if matches(value, `/etc/rc\.d`) {
+ mkline.checkVarassignSpecific()
+
+ if varname == "EVAL_PREFIX" {
+ if m, evalVarname := match1(value, `^([\w_]+)=`); m {
+
+ // The variables mentioned in EVAL_PREFIX will later be
+ // defined by find-prefix.mk. Therefore, they are marked
+ // as known in the current file.
+ G.Mk.vardef[evalVarname] = mkline
+ }
+ }
+
+ if varname == "USE_TOOLS" {
+ for _, fullToolname := range splitOnSpace(value) {
+ toolname := strings.Split(fullToolname, ":")[0]
+ if G.Pkg != nil {
+ if !G.Pkg.SeenBsdPrefsMk {
+ G.Pkg.loadTimeTools[toolname] = true
+ if G.opts.Debug {
+ traceStep1("loadTimeTool %q", toolname)
+ }
+ } else if !G.Pkg.loadTimeTools[toolname] {
+ G.Pkg.loadTimeTools[toolname] = false
+ if G.opts.Debug {
+ traceStep1("too late for loadTimeTool %q", toolname)
+ }
+ }
+ }
+ }
+ }
+
+ if fix := G.globalData.Deprecated[varname]; fix != "" {
+ mkline.Warn2("Definition of %s is deprecated. %s", varname, fix)
+ } else if fix := G.globalData.Deprecated[varcanon]; fix != "" {
+ mkline.Warn2("Definition of %s is deprecated. %s", varname, fix)
+ }
+
+ mkline.checkVarassignPlistComment(varname, value)
+ mkline.checkVarassignVaruse()
+}
+
+func (mkline *MkLine) checkVarassignVaruse() {
+ if G.opts.Debug {
+ defer tracecall()()
+ }
+
+ op := mkline.Op()
+
+ time := vucTimeRun
+ if op == opAssignEval || op == opAssignShell {
+ time = vucTimeParse
+ }
+
+ vartype := mkline.getVariableType(mkline.Varname())
+ if op == opAssignShell {
+ vartype = shellcommandsContextType
+ }
+
+ if vartype != nil && vartype.IsShell() {
+ mkline.checkVarassignVaruseShell(vartype, time)
+ } else {
+ mkline.checkVarassignVaruseMk(vartype, time)
+ }
+}
+
+func (mkline *MkLine) checkVarassignVaruseMk(vartype *Vartype, time vucTime) {
+ if G.opts.Debug {
+ defer tracecall(vartype, time)()
+ }
+ tokens := NewMkParser(mkline.Line, mkline.Value(), false).MkTokens()
+ for i, token := range tokens {
+ if token.Varuse != nil {
+ spaceLeft := i-1 < 0 || matches(tokens[i-1].Text, `\s$`)
+ spaceRight := i+1 >= len(tokens) || matches(tokens[i+1].Text, `^\s`)
+ extent := vucExtentWordpart
+ if spaceLeft && spaceRight {
+ extent = vucExtentWord
+ }
+
+ vuc := &VarUseContext{vartype, time, vucQuotPlain, extent}
+ mkline.CheckVaruse(token.Varuse, vuc)
+ }
+ }
+}
+
+func (mkline *MkLine) checkVarassignVaruseShell(vartype *Vartype, time vucTime) {
+ if G.opts.Debug {
+ defer tracecall(vartype, time)()
+ }
+
+ extent := func(tokens []*ShAtom, i int) vucExtent {
+ if i-1 >= 0 {
+ typ := tokens[i-1].Type
+ if typ != shtSpace && typ != shtSemicolon && typ != shtParenOpen && typ != shtParenClose {
+ return vucExtentWordpart
+ }
+ }
+ if i+1 < len(tokens) {
+ typ := tokens[i+1].Type
+ if typ != shtSpace && typ != shtSemicolon && typ != shtParenOpen && typ != shtParenClose {
+ return vucExtentWordpart
+ }
+ }
+ return vucExtentWord
+ }
+
+ atoms := NewShTokenizer(mkline.Line, mkline.Value(), false).ShAtoms()
+ for i, atom := range atoms {
+ if atom.Type == shtVaruse {
+ extent := extent(atoms, i)
+ vuc := &VarUseContext{vartype, time, atom.Quoting.ToVarUseContext(), extent}
+ mkline.CheckVaruse(atom.Data.(*MkVarUse), vuc)
+ }
+ }
+}
+
+func (mkline *MkLine) checkVarassignSpecific() {
+ varname := mkline.Varname()
+ value := mkline.Value()
+
+ if contains(value, "/etc/rc.d") {
mkline.Warn0("Please use the RCD_SCRIPTS mechanism to install rc.d scripts automatically to ${RCD_SCRIPTS_EXAMPLEDIR}.")
}
@@ -510,7 +912,7 @@ func (mkline *MkLine) CheckVarassign() {
}
}
- if varname == "CONFIGURE_ARGS" && matches(value, `=\$\{PREFIX\}/share/kde`) {
+ if varname == "CONFIGURE_ARGS" && contains(value, "=${PREFIX}/share/kde") {
mkline.Note0("Please .include \"../../meta-pkgs/kde3/kde3.mk\" instead of this line.")
Explain3(
"That file does many things automatically and consistently that this",
@@ -518,21 +920,11 @@ func (mkline *MkLine) CheckVarassign() {
"out some explicit dependencies.")
}
- if varname == "EVAL_PREFIX" {
- if m, evalVarname := match1(value, `^([\w_]+)=`); m {
-
- // The variables mentioned in EVAL_PREFIX will later be
- // defined by find-prefix.mk. Therefore, they are marked
- // as known in the current file.
- G.Mk.vardef[evalVarname] = mkline
- }
- }
-
if varname == "PYTHON_VERSIONS_ACCEPTED" {
- mkline.CheckDecreasingOrder(varname, value)
+ mkline.checkVarassignPythonVersions(varname, value)
}
- if comment == "# defined" && !hasSuffix(varname, "_MK") && !hasSuffix(varname, "_COMMON") {
+ if mkline.Comment() == "# defined" && !hasSuffix(varname, "_MK") && !hasSuffix(varname, "_COMMON") {
mkline.Note0("Please use \"# empty\", \"# none\" or \"yes\" instead of \"# defined\".")
Explain(
"The value #defined says something about the state of the variable,",
@@ -548,31 +940,12 @@ func (mkline *MkLine) CheckVarassign() {
}
}
- if fix := G.globalData.Deprecated[varname]; fix != "" {
- mkline.Warn2("Definition of %s is deprecated. %s", varname, fix)
- } else if fix := G.globalData.Deprecated[varcanon]; fix != "" {
- mkline.Warn2("Definition of %s is deprecated. %s", varname, fix)
- }
-
if hasPrefix(varname, "SITES_") {
mkline.Warn0("SITES_* is deprecated. Please use SITES.* instead.")
}
-
- mkline.CheckVarassignPlistComment(varname, value)
-
- time := vucTimeRun
- if op == opAssignEval || op == opAssignShell {
- time = vucTimeParse
- }
-
- usedVars := mkline.extractUsedVariables(value)
- vuc := &VarUseContext{mkline.getVariableType(varname), time, vucQuotUnknown, vucExtentUnknown}
- for _, usedVar := range usedVars {
- mkline.CheckVaruse(usedVar, "", vuc)
- }
}
-func (mkline *MkLine) CheckVarassignBsdPrefs() {
+func (mkline *MkLine) checkVarassignBsdPrefs() {
if G.opts.WarnExtra && mkline.Op() == opAssignDefault && G.Pkg != nil && !G.Pkg.SeenBsdPrefsMk {
switch mkline.Varcanon() {
case "BUILDLINK_PKGSRCDIR.*", "BUILDLINK_DEPMETHOD.*", "BUILDLINK_ABI_DEPENDS.*":
@@ -593,7 +966,7 @@ func (mkline *MkLine) CheckVarassignBsdPrefs() {
}
}
-func (mkline *MkLine) CheckVarassignPlistComment(varname, value string) {
+func (mkline *MkLine) checkVarassignPlistComment(varname, value string) {
if false && // This is currently neither correct nor helpful
contains(value, "@comment") && !matches(value, `="@comment "`) {
mkline.Warn1("Please don't use @comment in %s.", varname)
@@ -621,63 +994,63 @@ func (mkline *MkLine) CheckVarassignPlistComment(varname, value string) {
}
}
-const reVarnamePlural = "^(?:" +
- ".*[Ss]" +
- "|.*LIST" +
- "|.*_AWK" +
- "|.*_ENV" +
- "|.*_OVERRIDE" +
- "|.*_PREREQ" +
- "|.*_REQD" +
- "|.*_SED" +
- "|.*_SKIP" +
- "|.*_SRC" +
- "|.*_SUBST" +
- "|.*_TARGET" +
- "|.*_TMPL" +
- "|BROKEN_EXCEPT_ON_PLATFORM" +
- "|BROKEN_ON_PLATFORM" +
- "|BUILDLINK_DEPMETHOD" +
- "|BUILDLINK_LDADD" +
- "|BUILDLINK_TRANSFORM" +
- "|COMMENT" +
- "|CRYPTO" +
- "|DEINSTALL_TEMPLATE" +
- "|EVAL_PREFIX" +
- "|EXTRACT_ONLY" +
- "|FETCH_MESSAGE" +
- "|FIX_RPATH" +
- "|GENERATE_PLIST" +
- "|INSTALL_TEMPLATE" +
- "|INTERACTIVE_STAGE" +
- "|LICENSE" +
- "|MASTER_SITE_.*" +
- "|MASTER_SORT_REGEX" +
- "|NOT_FOR_COMPILER" +
- "|NOT_FOR_PLATFORM" +
- "|ONLY_FOR_COMPILER" +
- "|ONLY_FOR_PLATFORM" +
- "|PERL5_PACKLIST" +
- "|PLIST_CAT" +
- "|PLIST_PRE" +
- "|PKG_FAIL_REASON" +
- "|PKG_SKIP_REASON" +
- "|PREPEND_PATH" +
- "|PYTHON_VERSIONS_INCOMPATIBLE" +
- "|REPLACE_INTERPRETER" +
- "|REPLACE_PERL" +
- "|REPLACE_RUBY" +
- "|RESTRICTED" +
- "|SITES_.*" +
- "|TOOLS_ALIASES\\.*" +
- "|TOOLS_BROKEN" +
- "|TOOLS_CREATE" +
- "|TOOLS_GNU_MISSING" +
- "|TOOLS_NOOP" +
- ")$"
+const reVarnamePlural = `^(?:` +
+ `.*[Ss]` +
+ `|.*LIST` +
+ `|.*_AWK` +
+ `|.*_ENV` +
+ `|.*_OVERRIDE` +
+ `|.*_PREREQ` +
+ `|.*_REQD` +
+ `|.*_SED` +
+ `|.*_SKIP` +
+ `|.*_SRC` +
+ `|.*_SUBST` +
+ `|.*_TARGET` +
+ `|.*_TMPL` +
+ `|BROKEN_EXCEPT_ON_PLATFORM` +
+ `|BROKEN_ON_PLATFORM` +
+ `|BUILDLINK_DEPMETHOD` +
+ `|BUILDLINK_LDADD` +
+ `|BUILDLINK_TRANSFORM` +
+ `|COMMENT` +
+ `|CRYPTO` +
+ `|DEINSTALL_TEMPLATE` +
+ `|EVAL_PREFIX` +
+ `|EXTRACT_ONLY` +
+ `|FETCH_MESSAGE` +
+ `|FIX_RPATH` +
+ `|GENERATE_PLIST` +
+ `|INSTALL_TEMPLATE` +
+ `|INTERACTIVE_STAGE` +
+ `|LICENSE` +
+ `|MASTER_SITE_.*` +
+ `|MASTER_SORT_REGEX` +
+ `|NOT_FOR_COMPILER` +
+ `|NOT_FOR_PLATFORM` +
+ `|ONLY_FOR_COMPILER` +
+ `|ONLY_FOR_PLATFORM` +
+ `|PERL5_PACKLIST` +
+ `|PLIST_CAT` +
+ `|PLIST_PRE` +
+ `|PKG_FAIL_REASON` +
+ `|PKG_SKIP_REASON` +
+ `|PREPEND_PATH` +
+ `|PYTHON_VERSIONS_INCOMPATIBLE` +
+ `|REPLACE_INTERPRETER` +
+ `|REPLACE_PERL` +
+ `|REPLACE_RUBY` +
+ `|RESTRICTED` +
+ `|SITES_.+` +
+ `|TOOLS_ALIASES\..+` +
+ `|TOOLS_BROKEN` +
+ `|TOOLS_CREATE` +
+ `|TOOLS_GNU_MISSING` +
+ `|TOOLS_NOOP` +
+ `)$`
func (mkline *MkLine) CheckVartype(varname string, op MkOperator, value, comment string) {
- if G.opts.DebugTrace {
+ if G.opts.Debug {
defer tracecall(varname, op, value, comment)()
}
@@ -701,29 +1074,30 @@ func (mkline *MkLine) CheckVartype(varname string, op MkOperator, value, comment
switch {
case vartype == nil:
// Cannot check anything if the type is not known.
- if G.opts.DebugUnchecked {
- mkline.Debug1("Unchecked variable assignment for %s.", varname)
+ if G.opts.Debug {
+ traceStep1("Unchecked variable assignment for %s.", varname)
}
case op == opAssignShell:
- if G.opts.DebugMisc {
- mkline.Debug1("Use of !=: %q", value)
+ if G.opts.Debug {
+ traceStep1("Unchecked use of !=: %q", value)
}
case vartype.kindOfList == lkNone:
mkline.CheckVartypePrimitive(varname, vartype.checker, op, value, comment, vartype.IsConsideredList(), vartype.guessed)
+ case value == "":
+ break
+
case vartype.kindOfList == lkSpace:
for _, word := range splitOnSpace(value) {
mkline.CheckVartypePrimitive(varname, vartype.checker, op, word, comment, true, vartype.guessed)
}
case vartype.kindOfList == lkShell:
- shline := NewShellLine(mkline)
- words, _ := splitIntoShellWords(mkline.Line, value)
+ words, _ := splitIntoMkWords(mkline.Line, value)
for _, word := range words {
mkline.CheckVartypePrimitive(varname, vartype.checker, op, word, comment, true, vartype.guessed)
- shline.CheckToken(word, true)
}
}
}
@@ -731,13 +1105,12 @@ func (mkline *MkLine) CheckVartype(varname string, op MkOperator, value, comment
// For some variables (like `BuildlinkDepth`), `op` influences the valid values.
// The `comment` parameter comes from a variable assignment, when a part of the line is commented out.
func (mkline *MkLine) CheckVartypePrimitive(varname string, checker *VarChecker, op MkOperator, value, comment string, isList bool, guessed bool) {
- if G.opts.DebugTrace {
- defer tracecall(varname, op, value, comment, isList, guessed)()
+ if G.opts.Debug {
+ defer tracecall(varname, checker.name, op, value, comment, isList, guessed)()
}
- ctx := &VartypeCheck{mkline, mkline.Line, varname, op, value, "", comment, isList, guessed}
- ctx.valueNovar = mkline.withoutMakeVariables(value, isList)
-
+ valueNoVar := mkline.withoutMakeVariables(value, isList)
+ ctx := &VartypeCheck{mkline, mkline.Line, varname, op, value, valueNoVar, comment, isList, guessed}
checker.checker(ctx)
}
@@ -756,19 +1129,11 @@ func (mkline *MkLine) withoutMakeVariables(value string, qModifierAllowed bool)
}
}
-func (mkline *MkLine) CheckText(text string) {
- if G.opts.DebugTrace {
+func (mkline *MkLine) checkText(text string) {
+ if G.opts.Debug {
defer tracecall1(text)()
}
- if m, varname := match1(text, `^(?:[^#]*[^\$])?\$(\w+)`); m {
- mkline.Warn1("$%[1]s is ambiguous. Use ${%[1]s} if you mean a Makefile variable or $$%[1]s if you mean a shell variable.", varname)
- }
-
- if mkline.Line.firstLine == 1 {
- mkline.Line.CheckRcsid(`# `, "# ")
- }
-
if contains(text, "${WRKSRC}/..") {
mkline.Warn0("Building the package should take place entirely inside ${WRKSRC}, not \"${WRKSRC}/..\".")
Explain(
@@ -812,11 +1177,11 @@ func (mkline *MkLine) CheckText(text string) {
}
func (mkline *MkLine) CheckCond() {
- if G.opts.DebugTrace {
- defer tracecall0()()
+ if G.opts.Debug {
+ defer tracecall1(mkline.Args())()
}
- p := NewParser(mkline.Line, mkline.Args())
+ p := NewMkParser(mkline.Line, mkline.Args(), false)
cond := p.MkCond()
if !p.EOF() {
mkline.Warn1("Invalid conditional %q.", mkline.Args())
@@ -981,46 +1346,20 @@ const (
nqDontKnow
)
-func (mkline *MkLine) variableNeedsQuoting(varname string, vuc *VarUseContext) (needsQuoting NeedsQuoting) {
- if G.opts.DebugTrace {
- defer tracecall(varname, *vuc, "=>", needsQuoting)()
+func (nq NeedsQuoting) String() string {
+ return [...]string{"no", "yes", "doesn't matter", "don't known"}[nq]
+}
+
+func (mkline *MkLine) variableNeedsQuoting(varname string, vartype *Vartype, vuc *VarUseContext) (needsQuoting NeedsQuoting) {
+ if G.opts.Debug {
+ defer tracecall(varname, vartype, vuc, "=>", &needsQuoting)()
}
- vartype := mkline.getVariableType(varname)
if vartype == nil || vuc.vartype == nil {
return nqDontKnow
}
- isPlainWord := vartype.checker.IsEnum()
- switch vartype.checker {
- case CheckvarBuildlinkDepmethod,
- CheckvarCategory,
- CheckvarDistSuffix,
- CheckvarEmulPlatform,
- CheckvarFileMode,
- CheckvarFilename,
- CheckvarIdentifier,
- CheckvarInteger,
- CheckvarOption,
- CheckvarPathname,
- CheckvarPerl5Packlist,
- CheckvarPkgName,
- CheckvarPkgOptionsVar,
- CheckvarPkgPath,
- CheckvarPkgRevision,
- CheckvarPrefixPathname,
- CheckvarPythonDependency,
- CheckvarRelativePkgDir,
- CheckvarRelativePkgPath,
- CheckvarStage,
- CheckvarUserGroupName,
- CheckvarVersion,
- CheckvarWrkdirSubdirectory,
- CheckvarYesNo,
- CheckvarYesNoIndirectly:
- isPlainWord = true
- }
- if isPlainWord {
+ if vartype.checker.IsEnum() || vartype.IsBasicSafe() {
if vartype.kindOfList == lkNone {
return nqDoesntMatter
}
@@ -1037,12 +1376,10 @@ func (mkline *MkLine) variableNeedsQuoting(varname string, vuc *VarUseContext) (
}
// Determine whether the context expects a list of shell words or not.
- wantList := vuc.vartype.IsConsideredList() && (vuc.quoting == vucQuotBackt || vuc.extent != vucExtentWordpart)
+ wantList := vuc.vartype.IsConsideredList()
haveList := vartype.IsConsideredList()
-
- if G.opts.DebugQuoting {
- mkline.Line.Debugf("variableNeedsQuoting: varname=%q, context=%v, type=%v, wantList=%v, haveList=%v",
- varname, vuc, vartype, wantList, haveList)
+ if G.opts.Debug {
+ traceStep("wantList=%v, haveList=%v", wantList, haveList)
}
// A shell word may appear as part of a shell word, for example COMPILER_RPATH_FLAG.
@@ -1052,9 +1389,18 @@ func (mkline *MkLine) variableNeedsQuoting(varname string, vuc *VarUseContext) (
}
}
+ // Both of these can be correct, depending on the situation:
+ // 1. echo ${PERL5:Q}
+ // 2. xargs ${PERL5}
+ if vuc.extent == vucExtentWord && vuc.quoting == vucQuotPlain {
+ if wantList && haveList {
+ return nqDontKnow
+ }
+ }
+
// Assuming the tool definitions don't include very special characters,
// so they can safely be used inside any quotes.
- if G.globalData.VarnameToToolname[varname] != "" {
+ if G.globalData.Tools.byVarname[varname] != nil {
switch vuc.quoting {
case vucQuotPlain:
if vuc.extent != vucExtentWordpart {
@@ -1071,23 +1417,42 @@ func (mkline *MkLine) variableNeedsQuoting(varname string, vuc *VarUseContext) (
// to be quoted. An exception is in the case of backticks,
// because the whole backticks expression is parsed as a single
// shell word by pkglint.
- if vuc.extent == vucExtentWordpart && vuc.quoting != vucQuotBackt {
+ if vuc.extent == vucExtentWordpart && vuc.vartype != nil && vuc.vartype.IsShell() && vuc.quoting != vucQuotBackt {
return nqYes
}
+ // SUBST_MESSAGE.perl= Replacing in ${REPLACE_PERL}
+ if vuc.vartype != nil && vuc.vartype.IsPlainString() {
+ return nqNo
+ }
+
// Assigning lists to lists does not require any quoting, though
// there may be cases like "CONFIGURE_ARGS+= -libs ${LDFLAGS:Q}"
// where quoting is necessary.
- if wantList && haveList {
+ if wantList && haveList && vuc.extent != vucExtentWordpart {
return nqDoesntMatter
}
if wantList != haveList {
+ if vuc.vartype != nil && vartype != nil {
+ if vuc.vartype.checker == CheckvarFetchURL && vartype.checker == CheckvarHomepage {
+ return nqNo
+ }
+ if vuc.vartype.checker == CheckvarHomepage && vartype.checker == CheckvarFetchURL {
+ return nqNo // Just for HOMEPAGE=${MASTER_SITE_*:=subdir/}.
+ }
+ }
+ return nqYes
+ }
+
+ // Bad: LDADD += -l${LIBS}
+ // Good: LDADD += ${LIBS:@lib@-l${lib} @}
+ if wantList && haveList && vuc.extent == vucExtentWordpart {
return nqYes
}
- if G.opts.DebugQuoting {
- mkline.Line.Debug1("Don't know whether :Q is needed for %q", varname)
+ if G.opts.Debug {
+ traceStep1("Don't know whether :Q is needed for %q", varname)
}
return nqDontKnow
}
@@ -1095,6 +1460,10 @@ func (mkline *MkLine) variableNeedsQuoting(varname string, vuc *VarUseContext) (
// Returns the type of the variable (maybe guessed based on the variable name),
// or nil if the type cannot even be guessed.
func (mkline *MkLine) getVariableType(varname string) *Vartype {
+ if G.opts.Debug {
+ defer tracecall1(varname)()
+ }
+
if vartype := G.globalData.vartypes[varname]; vartype != nil {
return vartype
}
@@ -1102,11 +1471,20 @@ func (mkline *MkLine) getVariableType(varname string) *Vartype {
return vartype
}
- if G.globalData.VarnameToToolname[varname] != "" {
- return &Vartype{lkNone, CheckvarShellCommand, []AclEntry{{"*", aclpUse}}, false}
+ if tool := G.globalData.Tools.byVarname[varname]; tool != nil {
+ perms := aclpUse
+ if G.opts.Debug {
+ traceStep("Use of tool %+v", tool)
+ }
+ if tool.UsableAtLoadtime {
+ if G.Pkg == nil || G.Pkg.SeenBsdPrefsMk || G.Pkg.loadTimeTools[tool.Name] {
+ perms |= aclpUseLoadtime
+ }
+ }
+ return &Vartype{lkNone, CheckvarShellCommand, []AclEntry{{"*", perms}}, false}
}
- if m, toolvarname := match1(varname, `^TOOLS_(.*)`); m && G.globalData.VarnameToToolname[toolvarname] != "" {
+ if m, toolvarname := match1(varname, `^TOOLS_(.*)`); m && G.globalData.Tools.byVarname[toolvarname] != nil {
return &Vartype{lkNone, CheckvarPathname, []AclEntry{{"*", aclpUse}}, false}
}
@@ -1149,11 +1527,11 @@ func (mkline *MkLine) getVariableType(varname string) *Vartype {
gtype = &Vartype{lkNone, CheckvarYes, allowAll, true}
}
- if G.opts.DebugVartypes {
+ if G.opts.Debug {
if gtype != nil {
- mkline.Line.Debug2("The guessed type of %q is %q.", varname, gtype.String())
+ traceStep2("The guessed type of %q is %q.", varname, gtype.String())
} else {
- mkline.Line.Debug1("No type definition found for %q.", varname)
+ traceStep1("No type definition found for %q.", varname)
}
}
return gtype
@@ -1176,8 +1554,8 @@ func (mkline *MkLine) extractUsedVariables(text string) []string {
}
}
- if rest != "" && G.opts.DebugMisc {
- mkline.Debug1("extractUsedVariables: rest=%q", rest)
+ if G.opts.Debug && rest != "" {
+ traceStep1("extractUsedVariables: rest=%q", rest)
}
return result
}
@@ -1212,7 +1590,7 @@ func (mkline *MkLine) determineUsedVariables() (varnames []string) {
}
rest = rest[min:]
- m := regcomp(`(?:\$\{|\$\(|defined\(|empty\()([0-9+.A-Z_a-z]+)[:})]`).FindStringSubmatchIndex(rest)
+ m := regcomp(`(?:\$\{|\$\(|defined\(|empty\()([*+\-.0-9A-Z_a-z]+)[:})]`).FindStringSubmatchIndex(rest)
if m == nil {
return
}
@@ -1286,19 +1664,25 @@ func (q vucQuoting) String() string {
type vucExtent uint8
const (
- vucExtentUnknown vucExtent = iota
- vucExtentWord // Example: echo ${LOCALBASE}
- vucExtentWordpart // Example: echo LOCALBASE=${LOCALBASE}
+ vucExtentWord vucExtent = iota // Example: echo ${LOCALBASE}
+ vucExtentWordpart // Example: echo LOCALBASE=${LOCALBASE}
)
-func (e vucExtent) String() string {
- return [...]string{"unknown", "word", "wordpart"}[e]
-}
+func (e vucExtent) String() string { return [...]string{"word", "wordpart"}[e] }
func (vuc *VarUseContext) String() string {
typename := "no-type"
if vuc.vartype != nil {
typename = vuc.vartype.String()
}
- return fmt.Sprintf("(%s %s %s %s)", vuc.time, typename, vuc.quoting, vuc.extent)
+ return fmt.Sprintf("(%s time:%s quoting:%s extent:%s)", typename, vuc.time, vuc.quoting, vuc.extent)
}
+
+type Indentation struct {
+ data []int
+}
+
+func (ind *Indentation) Len() int { return len(ind.data) }
+func (ind *Indentation) Depth() int { return ind.data[len(ind.data)-1] }
+func (ind *Indentation) Pop() { ind.data = ind.data[:len(ind.data)-1] }
+func (ind *Indentation) Push(indent int) { ind.data = append(ind.data, indent) }