[BACK]Return to vargroups.go CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / pkgsrc / pkgtools / pkglint / files

File: [cvs.NetBSD.org] / pkgsrc / pkgtools / pkglint / files / vargroups.go (download)

Revision 1.6, Sun Dec 8 22:03:38 2019 UTC (2 months, 2 weeks ago) by rillig
Branch: MAIN
CVS Tags: pkgsrc-2019Q4-base, pkgsrc-2019Q4
Changes since 1.5: +1 -1 lines

pkgtools/pkglint: update pkglint to 19.3.15

Changes since 19.3.14:

Invalid lines in PLIST files are now reported as errors instead of
warnings. If pkglint doesn't know about it, it must be an error.

In PLIST files, all paths are validated to be canonical. That is, no
dotdot components, no absolute paths, no extra slashes, no intermediate
dot components.

Fewer notes for unexpanded variable expressions in DESCR files. Before,
the text $@ was reported as possible Makefile variable even though it
was just a Perl expression.

README files are allowed again in pkgsrc package directories. There was
no convincing argument why these should be forbidden.

A few diagnostics have been changed from NOTE to WARNING or from WARNING
to ERROR, to match their wording.

When pkglint suggests to replace :M with ==, the wording is now "can be
made" instead of "should".

package pkglint

import (
	"path"
	"strings"
)

// VargroupsChecker checks that the _VARGROUPS section of an infrastructure
// file matches the rest of the file content:
//
// - All variables that are used in the file should also be declared as used.
//
// - All variables that are declared to be used should actually be used.
//
// See mk/misc/show.mk, keyword _VARGROUPS.
type VargroupsChecker struct {
	mklines *MkLines
	skip    bool

	registered map[string]*MkLine
	userVars   map[string]*MkLine
	pkgVars    map[string]*MkLine
	sysVars    map[string]*MkLine
	defVars    map[string]*MkLine
	useVars    map[string]*MkLine
	ignVars    map[string]*MkLine
	sortedVars map[string]*MkLine
	listedVars map[string]*MkLine

	undefinedVars map[string]*MkLine
	unusedVars    map[string]*MkLine
}

func NewVargroupsChecker(mklines *MkLines) *VargroupsChecker {
	ck := VargroupsChecker{mklines: mklines}
	ck.init()
	return &ck
}

func (ck *VargroupsChecker) init() {
	mklines := ck.mklines
	scope := mklines.allVars
	if !scope.IsDefined("_VARGROUPS") {
		ck.skip = true
		return
	}

	ck.registered = make(map[string]*MkLine)
	ck.userVars = make(map[string]*MkLine)
	ck.pkgVars = make(map[string]*MkLine)
	ck.sysVars = make(map[string]*MkLine)
	ck.defVars = make(map[string]*MkLine)
	ck.useVars = make(map[string]*MkLine)
	ck.ignVars = make(map[string]*MkLine)
	ck.sortedVars = make(map[string]*MkLine)
	ck.listedVars = make(map[string]*MkLine)
	userPrivate := ""
	pkgPrivate := ""
	sysPrivate := ""
	defPrivate := ""
	usePrivate := ""

	group := ""

	checkGroupName := func(mkline *MkLine) {
		varname := mkline.Varname()
		if varnameParam(varname) != group {
			mkline.Warnf("Expected %s.%s, but found %q.",
				varnameBase(varname), group, varnameParam(varname))
		}
	}

	appendTo := func(vars map[string]*MkLine, mkline *MkLine, publicGroup bool, firstPrivate *string) {
		checkGroupName(mkline)

		for _, varname := range mkline.ValueFields(mkline.Value()) {
			if containsVarRef(varname) {
				continue
			}

			private := hasPrefix(varname, "_")
			if publicGroup && private {
				mkline.Warnf("%s should list only variables that start with a letter, not %q.",
					mkline.Varname(), varname)
			}

			if firstPrivate != nil {
				if *firstPrivate != "" && !private {
					mkline.Warnf("The public variable %s should be listed before the private variable %s.",
						varname, *firstPrivate)
				}
				if private && *firstPrivate == "" {
					*firstPrivate = varname
				}
			}

			if ck.registered[varname] != nil {
				mkline.Warnf("Duplicate variable name %s, already appeared in %s.",
					varname, mkline.RelMkLine(ck.registered[varname]))
			} else {
				ck.registered[varname] = mkline
			}

			vars[varname] = mkline
		}
	}

	appendToStyle := func(vars map[string]*MkLine, mkline *MkLine) {
		checkGroupName(mkline)

		for _, varname := range mkline.ValueFields(mkline.Value()) {
			if !containsVarRef(varname) {
				vars[varname] = mkline
			}
		}
	}

	mklines.ForEach(func(mkline *MkLine) {
		if mkline.IsVarassign() {
			switch varnameCanon(mkline.Varname()) {
			case "_VARGROUPS":
				group = mkline.Value()
			case "_USER_VARS.*":
				appendTo(ck.userVars, mkline, true, &userPrivate)
			case "_PKG_VARS.*":
				appendTo(ck.pkgVars, mkline, true, &pkgPrivate)
			case "_SYS_VARS.*":
				appendTo(ck.sysVars, mkline, true, &sysPrivate)
			case "_DEF_VARS.*":
				appendTo(ck.defVars, mkline, false, &defPrivate)
			case "_USE_VARS.*":
				appendTo(ck.useVars, mkline, false, &usePrivate)
			case "_IGN_VARS.*":
				appendTo(ck.ignVars, mkline, false, nil)
			case "_SORTED_VARS.*":
				appendToStyle(ck.sortedVars, mkline)
			case "_LISTED_VARS.*":
				appendToStyle(ck.listedVars, mkline)
			}
		}
	})

	ck.undefinedVars = copyStringMkLine(ck.defVars)
	ck.unusedVars = copyStringMkLine(ck.useVars)
}

// CheckVargroups checks that each variable that is used or defined
// somewhere in the file is also registered in the _VARGROUPS section,
// in order to make it discoverable by "bmake show-all".
//
// This check is intended mainly for infrastructure files and similar
// support files, such as lang/*/module.mk.
func (ck *VargroupsChecker) Check(mkline *MkLine) {
	if ck.skip {
		return
	}

	ck.checkDef(mkline)
	ck.checkUse(mkline)
}

func (ck *VargroupsChecker) checkDef(mkline *MkLine) {
	if !mkline.IsVarassignMaybeCommented() {
		return
	}

	varname := mkline.Varname()
	delete(ck.undefinedVars, varname)
	if ck.ignore(varname) {
		return
	}

	if ck.mklines.once.FirstTimeSlice("_VARGROUPS", "def", varname) {
		mkline.Warnf("Variable %s is defined but not mentioned in the _VARGROUPS section.", varname)
	}
}

func (ck *VargroupsChecker) checkUse(mkline *MkLine) {
	mkline.ForEachUsed(func(varUse *MkVarUse, _ VucTime) { ck.checkUseVar(mkline, varUse) })
}

func (ck *VargroupsChecker) checkUseVar(mkline *MkLine, varUse *MkVarUse) {
	varname := varUse.varname
	delete(ck.unusedVars, varname)

	if ck.ignore(varname) {
		return
	}

	if ck.mklines.once.FirstTimeSlice("_VARGROUPS", "use", varname) {
		mkline.Warnf("Variable %s is used but not mentioned in the _VARGROUPS section.", varname)
	}
}

func (ck *VargroupsChecker) ignore(varname string) bool {
	switch {
	case containsVarRef(varname),
		hasSuffix(varname, "_MK"),
		ck.registered[varname] != nil,
		G.Pkgsrc.Tools.ExistsVar(varname),
		ck.isVargroups(varname),
		varname == strings.ToLower(varname),
		ck.isShellCommand(varname),
		varname == ".TARGET",
		varname == "BUILD_DEFS",
		varname == "BUILD_DEFS_EFFECTS",
		varname == "PKG_FAIL_REASON",
		varname == "TOUCH_FLAGS":
		return true
	}

	for pattern := range ck.ignVars {
		matched, err := path.Match(pattern, varname)
		if err == nil && matched {
			return true
		}
	}

	return false
}

func (ck *VargroupsChecker) isShellCommand(varname string) bool {
	vartype := G.Pkgsrc.VariableType(ck.mklines, varname)
	return vartype != nil && vartype.basicType == BtShellCommand
}

func (ck *VargroupsChecker) isVargroups(varname string) bool {
	if !hasPrefix(varname, "_") {
		return false
	}

	switch varnameCanon(varname) {
	case "_VARGROUPS",
		"_USER_VARS.*",
		"_PKG_VARS.*",
		"_SYS_VARS.*",
		"_DEF_VARS.*",
		"_USE_VARS.*",
		"_IGN_VARS.*",
		"_LISTED_VARS.*",
		"_SORTED_VARS.*":
		return true
	}
	return false
}

func (ck *VargroupsChecker) Finish(mkline *MkLine) {
	if ck.skip {
		return
	}

	forEachStringMkLine(ck.undefinedVars, func(varname string, mkline *MkLine) {
		mkline.Warnf("The variable %s is not actually defined in this file.", varname)
	})
	forEachStringMkLine(ck.unusedVars, func(varname string, mkline *MkLine) {
		mkline.Warnf("The variable %s is not actually used in this file.", varname)
	})
}