360 lines
9.3 KiB
Go
360 lines
9.3 KiB
Go
package main
|
||
|
||
import (
|
||
"path"
|
||
"strings"
|
||
)
|
||
|
||
// MkLines contains data for the Makefile (or *.mk) that is currently checked.
|
||
type MkLines struct {
|
||
mklines []*MkLine
|
||
lines []*Line
|
||
forVars map[string]bool // The variables currently used in .for loops
|
||
target string // Current make(1) target
|
||
vardef map[string]*MkLine // varname => line; for all variables that are defined in the current file
|
||
varuse map[string]*MkLine // varname => line; for all variables that are used in the current file
|
||
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.
|
||
SeenBsdPrefsMk bool
|
||
indentation Indentation // Indentation depth of preprocessing directives
|
||
}
|
||
|
||
func NewMkLines(lines []*Line) *MkLines {
|
||
mklines := make([]*MkLine, len(lines))
|
||
for i, line := range lines {
|
||
mklines[i] = NewMkLine(line)
|
||
}
|
||
tools := make(map[string]bool)
|
||
for toolname, tool := range G.globalData.Tools.byName {
|
||
if tool.Predefined {
|
||
tools[toolname] = true
|
||
}
|
||
}
|
||
|
||
return &MkLines{
|
||
mklines,
|
||
lines,
|
||
make(map[string]bool),
|
||
"",
|
||
make(map[string]*MkLine),
|
||
make(map[string]*MkLine),
|
||
make(map[string]bool),
|
||
make(map[string]bool),
|
||
tools,
|
||
NewToolRegistry(),
|
||
false,
|
||
Indentation{}}
|
||
}
|
||
|
||
func (mklines *MkLines) DefineVar(mkline *MkLine, varname string) {
|
||
if mklines.vardef[varname] == nil {
|
||
mklines.vardef[varname] = mkline
|
||
}
|
||
varcanon := varnameCanon(varname)
|
||
if mklines.vardef[varcanon] == nil {
|
||
mklines.vardef[varcanon] = mkline
|
||
}
|
||
}
|
||
|
||
func (mklines *MkLines) UseVar(mkline *MkLine, varname string) {
|
||
varcanon := varnameCanon(varname)
|
||
mklines.varuse[varname] = mkline
|
||
mklines.varuse[varcanon] = mkline
|
||
if G.Pkg != nil {
|
||
G.Pkg.varuse[varname] = mkline
|
||
G.Pkg.varuse[varcanon] = mkline
|
||
}
|
||
}
|
||
|
||
func (mklines *MkLines) VarValue(varname string) (value string, found bool) {
|
||
if mkline := mklines.vardef[varname]; mkline != nil {
|
||
return mkline.Value(), true
|
||
}
|
||
return "", false
|
||
}
|
||
|
||
func (mklines *MkLines) Check() {
|
||
if G.opts.Debug {
|
||
defer tracecall1(mklines.lines[0].Fname)()
|
||
}
|
||
|
||
G.Mk = mklines
|
||
defer func() { G.Mk = nil }()
|
||
|
||
allowedTargets := make(map[string]bool)
|
||
prefixes := [...]string{"pre", "do", "post"}
|
||
actions := [...]string{"fetch", "extract", "patch", "tools", "wrapper", "configure", "build", "test", "install", "package", "clean"}
|
||
for _, prefix := range prefixes {
|
||
for _, action := range actions {
|
||
allowedTargets[prefix+"-"+action] = true
|
||
}
|
||
}
|
||
|
||
// In the first pass, all additions to BUILD_DEFS and USE_TOOLS
|
||
// are collected to make the order of the definitions irrelevant.
|
||
mklines.DetermineUsedVariables()
|
||
mklines.determineDefinedVariables()
|
||
|
||
// In the second pass, the actual checks are done.
|
||
|
||
mklines.lines[0].CheckRcsid(`#\s+`, "# ")
|
||
|
||
var substcontext SubstContext
|
||
var varalign VaralignBlock
|
||
indentation := &mklines.indentation
|
||
indentation.Push(0)
|
||
for _, mkline := range mklines.mklines {
|
||
mkline.Check()
|
||
varalign.Check(mkline)
|
||
|
||
switch {
|
||
case mkline.IsEmpty():
|
||
substcontext.Finish(mkline)
|
||
|
||
case mkline.IsVarassign():
|
||
mklines.target = ""
|
||
substcontext.Varassign(mkline)
|
||
|
||
case mkline.IsInclude():
|
||
mklines.target = ""
|
||
switch path.Base(mkline.Includefile()) {
|
||
case "bsd.prefs.mk", "bsd.fast.prefs.mk", "bsd.builtin.mk":
|
||
mklines.setSeenBsdPrefsMk()
|
||
}
|
||
|
||
case mkline.IsCond():
|
||
mkline.checkCond(mklines.forVars)
|
||
|
||
case mkline.IsDependency():
|
||
mkline.checkDependencyRule(allowedTargets)
|
||
mklines.target = mkline.Targets()
|
||
}
|
||
}
|
||
lastMkline := mklines.mklines[len(mklines.mklines)-1]
|
||
substcontext.Finish(lastMkline)
|
||
varalign.Finish()
|
||
|
||
ChecklinesTrailingEmptyLines(mklines.lines)
|
||
|
||
if indentation.Len() != 1 && indentation.Depth() != 0 {
|
||
lastMkline.Line.Errorf("Directive indentation is not 0, but %d.", indentation.Depth())
|
||
}
|
||
|
||
SaveAutofixChanges(mklines.lines)
|
||
}
|
||
|
||
func (mklines *MkLines) determineDefinedVariables() {
|
||
if G.opts.Debug {
|
||
defer tracecall0()()
|
||
}
|
||
|
||
for _, mkline := range mklines.mklines {
|
||
if !mkline.IsVarassign() {
|
||
continue
|
||
}
|
||
|
||
varcanon := mkline.Varcanon()
|
||
switch varcanon {
|
||
case "BUILD_DEFS", "PKG_GROUPS_VARS", "PKG_USERS_VARS":
|
||
for _, varname := range splitOnSpace(mkline.Value()) {
|
||
mklines.buildDefs[varname] = true
|
||
if G.opts.Debug {
|
||
traceStep1("%q is added to BUILD_DEFS.", varname)
|
||
}
|
||
}
|
||
|
||
case "PLIST_VARS":
|
||
for _, id := range splitOnSpace(mkline.Value()) {
|
||
mklines.plistVars["PLIST."+id] = true
|
||
if G.opts.Debug {
|
||
traceStep1("PLIST.%s is added to PLIST_VARS.", id)
|
||
}
|
||
mklines.UseVar(mkline, "PLIST."+id)
|
||
}
|
||
|
||
case "USE_TOOLS":
|
||
tools := mkline.Value()
|
||
if matches(tools, `\bautoconf213\b`) {
|
||
tools += " autoconf autoheader-2.13 autom4te-2.13 autoreconf-2.13 autoscan-2.13 autoupdate-2.13 ifnames-2.13"
|
||
}
|
||
if matches(tools, `\bautoconf\b`) {
|
||
tools += " autoheader autom4te autoreconf autoscan autoupdate ifnames"
|
||
}
|
||
for _, tool := range splitOnSpace(tools) {
|
||
tool = strings.Split(tool, ":")[0]
|
||
mklines.tools[tool] = true
|
||
if G.opts.Debug {
|
||
traceStep1("%s is added to USE_TOOLS.", tool)
|
||
}
|
||
}
|
||
|
||
case "SUBST_VARS.*":
|
||
for _, svar := range splitOnSpace(mkline.Value()) {
|
||
mklines.UseVar(mkline, varnameCanon(svar))
|
||
if G.opts.Debug {
|
||
traceStep1("varuse %s", svar)
|
||
}
|
||
}
|
||
|
||
case "OPSYSVARS":
|
||
for _, osvar := range splitOnSpace(mkline.Value()) {
|
||
mklines.UseVar(mkline, osvar+".*")
|
||
defineVar(mkline, osvar)
|
||
}
|
||
}
|
||
|
||
mklines.toolRegistry.ParseToolLine(mkline.Line)
|
||
}
|
||
}
|
||
|
||
func (mklines *MkLines) DetermineUsedVariables() {
|
||
for _, mkline := range mklines.mklines {
|
||
varnames := mkline.determineUsedVariables()
|
||
for _, varname := range varnames {
|
||
mklines.UseVar(mkline, varname)
|
||
}
|
||
}
|
||
}
|
||
|
||
func (mklines *MkLines) setSeenBsdPrefsMk() {
|
||
if !mklines.SeenBsdPrefsMk {
|
||
mklines.SeenBsdPrefsMk = true
|
||
if G.opts.Debug {
|
||
traceStep("Mk.setSeenBsdPrefsMk")
|
||
}
|
||
}
|
||
}
|
||
|
||
type VaralignBlock struct {
|
||
info []struct {
|
||
mkline *MkLine
|
||
prefix string
|
||
align string
|
||
}
|
||
skip bool
|
||
differ bool
|
||
maxPrefixWidth int
|
||
maxSpaceWidth int
|
||
maxTabWidth int
|
||
}
|
||
|
||
func (va *VaralignBlock) Check(mkline *MkLine) {
|
||
if !G.opts.WarnSpace || mkline.Line.IsMultiline() || mkline.IsComment() || mkline.IsCond() {
|
||
return
|
||
}
|
||
if mkline.IsEmpty() {
|
||
va.Finish()
|
||
return
|
||
}
|
||
if !mkline.IsVarassign() {
|
||
va.skip = true
|
||
return
|
||
}
|
||
|
||
valueAlign := mkline.ValueAlign()
|
||
prefix := strings.TrimRight(valueAlign, " \t")
|
||
align := valueAlign[len(prefix):]
|
||
|
||
va.info = append(va.info, struct {
|
||
mkline *MkLine
|
||
prefix string
|
||
align string
|
||
}{mkline, prefix, align})
|
||
|
||
alignedWidth := tabLength(valueAlign)
|
||
if contains(align, " ") {
|
||
if va.maxSpaceWidth != 0 && alignedWidth != va.maxSpaceWidth {
|
||
va.differ = true
|
||
}
|
||
if alignedWidth > va.maxSpaceWidth {
|
||
va.maxSpaceWidth = alignedWidth
|
||
}
|
||
} else {
|
||
if va.maxTabWidth != 0 && alignedWidth != va.maxTabWidth {
|
||
va.differ = true
|
||
}
|
||
if alignedWidth > va.maxTabWidth {
|
||
va.maxTabWidth = alignedWidth
|
||
}
|
||
}
|
||
|
||
va.maxPrefixWidth = imax(va.maxPrefixWidth, tabLength(prefix))
|
||
}
|
||
|
||
func (va *VaralignBlock) Finish() {
|
||
if !va.skip {
|
||
for _, info := range va.info {
|
||
if !info.mkline.Line.IsMultiline() {
|
||
va.fixalign(info.mkline, info.prefix, info.align)
|
||
}
|
||
}
|
||
}
|
||
*va = VaralignBlock{}
|
||
}
|
||
|
||
func (va *VaralignBlock) fixalign(mkline *MkLine, prefix, oldalign string) {
|
||
if mkline.Value() == "" && mkline.VarassignComment() == "" {
|
||
return
|
||
}
|
||
|
||
hasSpace := contains(oldalign, " ")
|
||
if hasSpace &&
|
||
va.maxTabWidth != 0 &&
|
||
va.maxSpaceWidth > va.maxTabWidth &&
|
||
tabLength(prefix+oldalign) == va.maxSpaceWidth {
|
||
return
|
||
}
|
||
|
||
// Don’t warn about procedure parameters
|
||
if mkline.Op() == opAssignEval && matches(mkline.Varname(), `^[a-z]`) {
|
||
return
|
||
}
|
||
|
||
goodWidth := va.maxTabWidth
|
||
if goodWidth == 0 && va.differ {
|
||
goodWidth = va.maxSpaceWidth
|
||
}
|
||
minWidth := va.maxPrefixWidth + 1
|
||
if goodWidth == 0 || minWidth < goodWidth && va.differ {
|
||
goodWidth = minWidth
|
||
}
|
||
goodWidth = (goodWidth + 7) & -8
|
||
|
||
newalign := ""
|
||
for tabLength(prefix+newalign) < goodWidth {
|
||
newalign += "\t"
|
||
}
|
||
if newalign == oldalign {
|
||
return
|
||
}
|
||
|
||
if !mkline.Line.AutofixReplace(prefix+oldalign, prefix+newalign) {
|
||
wrongColumn := tabLength(prefix+oldalign) != tabLength(prefix+newalign)
|
||
switch {
|
||
case hasSpace && wrongColumn:
|
||
mkline.Line.Notef("This variable value should be aligned with tabs, not spaces, to column %d.", goodWidth+1)
|
||
case hasSpace:
|
||
mkline.Line.Notef("Variable values should be aligned with tabs, not spaces.")
|
||
case wrongColumn:
|
||
mkline.Line.Notef("This variable value should be aligned to column %d.", goodWidth+1)
|
||
}
|
||
if wrongColumn {
|
||
Explain(
|
||
"Normally, all variable values in a block should start at the same",
|
||
"column. There are some exceptions to this rule:",
|
||
"",
|
||
"Definitions for long variable names may be indented with a single",
|
||
"space instead of tabs, but only if they appear in a block that is",
|
||
"otherwise indented using tabs.",
|
||
"",
|
||
"Variable definitions that span multiple lines are not checked for",
|
||
"alignment at all.",
|
||
"",
|
||
"When the block contains something else than variable definitions,",
|
||
"it is not checked at all.")
|
||
}
|
||
}
|
||
}
|