Move name memoization out of variables

memoizeFullName was added to variables, rules and pools as an
optimization to prevent recomputing the full name repeatedly,
but the storage of variables, rules and pools are generally global
and not tied to the Context.  When running multiple tests in
parallel there will be multiple Context objects all trying to
update the memoized names on the global variables, causing a data
race.

Package names were previously memoized via a pkgNames map stored
on the Context.  Expand pkgNames to a nameTracker object that
contains maps for packages, variables, rules and pools, and replace
calls to fullName with calls through nameTracker.

Test: context_test.go
Change-Id: I15040b85a6d1dab9ab3cff44f227b22985acee18
This commit is contained in:
Colin Cross 2024-01-18 12:00:20 -08:00
parent 2f0f395cc9
commit 6bc984abca
7 changed files with 141 additions and 185 deletions

View file

@ -16,6 +16,7 @@ package blueprint
import ( import (
"bytes" "bytes"
"cmp"
"context" "context"
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
@ -29,6 +30,7 @@ import (
"reflect" "reflect"
"runtime" "runtime"
"runtime/pprof" "runtime/pprof"
"slices"
"sort" "sort"
"strings" "strings"
"sync" "sync"
@ -100,7 +102,7 @@ type Context struct {
allowMissingDependencies bool allowMissingDependencies bool
// set during PrepareBuildActions // set during PrepareBuildActions
pkgNames map[*packageContext]string nameTracker *nameTracker
liveGlobals *liveTracker liveGlobals *liveTracker
globalVariables map[Variable]*ninjaString globalVariables map[Variable]*ninjaString
globalPools map[Pool]*poolDef globalPools map[Pool]*poolDef
@ -2741,7 +2743,7 @@ func jsonModuleFromModuleInfo(m *moduleInfo) *JsonModule {
return result return result
} }
func jsonModuleWithActionsFromModuleInfo(m *moduleInfo) *JsonModule { func jsonModuleWithActionsFromModuleInfo(m *moduleInfo, nameTracker *nameTracker) *JsonModule {
result := &JsonModule{ result := &JsonModule{
jsonModuleName: jsonModuleName{ jsonModuleName: jsonModuleName{
Name: m.Name(), Name: m.Name(),
@ -2758,17 +2760,17 @@ func jsonModuleWithActionsFromModuleInfo(m *moduleInfo) *JsonModule {
Inputs: append(append(append( Inputs: append(append(append(
bDef.InputStrings, bDef.InputStrings,
bDef.ImplicitStrings...), bDef.ImplicitStrings...),
getNinjaStringsWithNilPkgNames(bDef.Inputs)...), getNinjaStrings(bDef.Inputs, nameTracker)...),
getNinjaStringsWithNilPkgNames(bDef.Implicits)...), getNinjaStrings(bDef.Implicits, nameTracker)...),
Outputs: append(append(append( Outputs: append(append(append(
bDef.OutputStrings, bDef.OutputStrings,
bDef.ImplicitOutputStrings...), bDef.ImplicitOutputStrings...),
getNinjaStringsWithNilPkgNames(bDef.Outputs)...), getNinjaStrings(bDef.Outputs, nameTracker)...),
getNinjaStringsWithNilPkgNames(bDef.ImplicitOutputs)...), getNinjaStrings(bDef.ImplicitOutputs, nameTracker)...),
} }
if d, ok := bDef.Variables["description"]; ok { if d, ok := bDef.Variables["description"]; ok {
a.Desc = d.Value(nil) a.Desc = d.Value(nameTracker)
} }
actions = append(actions, a) actions = append(actions, a)
} }
@ -2786,12 +2788,11 @@ func jsonModuleWithActionsFromModuleInfo(m *moduleInfo) *JsonModule {
return result return result
} }
// Gets a list of strings from the given list of ninjaStrings by invoking ninjaString.Value with // Gets a list of strings from the given list of ninjaStrings by invoking ninjaString.Value on each.
// nil pkgNames on each of the input ninjaStrings. func getNinjaStrings(nStrs []*ninjaString, nameTracker *nameTracker) []string {
func getNinjaStringsWithNilPkgNames(nStrs []*ninjaString) []string {
var strs []string var strs []string
for _, nstr := range nStrs { for _, nstr := range nStrs {
strs = append(strs, nstr.Value(nil)) strs = append(strs, nstr.Value(nameTracker))
} }
return strs return strs
} }
@ -2799,7 +2800,7 @@ func getNinjaStringsWithNilPkgNames(nStrs []*ninjaString) []string {
func (c *Context) GetWeightedOutputsFromPredicate(predicate func(*JsonModule) (bool, int)) map[string]int { func (c *Context) GetWeightedOutputsFromPredicate(predicate func(*JsonModule) (bool, int)) map[string]int {
outputToWeight := make(map[string]int) outputToWeight := make(map[string]int)
for _, m := range c.modulesSorted { for _, m := range c.modulesSorted {
jmWithActions := jsonModuleWithActionsFromModuleInfo(m) jmWithActions := jsonModuleWithActionsFromModuleInfo(m, c.nameTracker)
if ok, weight := predicate(jmWithActions); ok { if ok, weight := predicate(jmWithActions); ok {
for _, a := range jmWithActions.Module["Actions"].([]JSONAction) { for _, a := range jmWithActions.Module["Actions"].([]JSONAction) {
for _, o := range a.Outputs { for _, o := range a.Outputs {
@ -2831,7 +2832,7 @@ func (c *Context) PrintJSONGraphAndActions(wGraph io.Writer, wActions io.Writer)
modulesToActions := make([]*JsonModule, 0) modulesToActions := make([]*JsonModule, 0)
for _, m := range c.modulesSorted { for _, m := range c.modulesSorted {
jm := jsonModuleFromModuleInfo(m) jm := jsonModuleFromModuleInfo(m)
jmWithActions := jsonModuleWithActionsFromModuleInfo(m) jmWithActions := jsonModuleWithActionsFromModuleInfo(m, c.nameTracker)
for _, d := range m.directDeps { for _, d := range m.directDeps {
jm.Deps = append(jm.Deps, jsonDep{ jm.Deps = append(jm.Deps, jsonDep{
jsonModuleName: *jsonModuleNameFromModuleInfo(d.module), jsonModuleName: *jsonModuleNameFromModuleInfo(d.module),
@ -2918,12 +2919,12 @@ func (c *Context) PrepareBuildActions(config interface{}) (deps []string, errs [
deps = append(deps, depsPackages...) deps = append(deps, depsPackages...)
c.memoizeFullNames(c.liveGlobals, pkgNames) nameTracker := c.memoizeFullNames(c.liveGlobals, pkgNames)
// This will panic if it finds a problem since it's a programming error. // This will panic if it finds a problem since it's a programming error.
c.checkForVariableReferenceCycles(c.liveGlobals.variables, pkgNames) c.checkForVariableReferenceCycles(c.liveGlobals.variables, nameTracker)
c.pkgNames = pkgNames c.nameTracker = nameTracker
c.globalVariables = c.liveGlobals.variables c.globalVariables = c.liveGlobals.variables
c.globalPools = c.liveGlobals.pools c.globalPools = c.liveGlobals.pools
c.globalRules = c.liveGlobals.rules c.globalRules = c.liveGlobals.rules
@ -3862,20 +3863,27 @@ func (c *Context) makeUniquePackageNames(
// memoizeFullNames stores the full name of each live global variable, rule and pool since each is // memoizeFullNames stores the full name of each live global variable, rule and pool since each is
// guaranteed to be used at least twice, once in the definition and once for each usage, and many // guaranteed to be used at least twice, once in the definition and once for each usage, and many
// are used much more than once. // are used much more than once.
func (c *Context) memoizeFullNames(liveGlobals *liveTracker, pkgNames map[*packageContext]string) { func (c *Context) memoizeFullNames(liveGlobals *liveTracker, pkgNames map[*packageContext]string) *nameTracker {
nameTracker := &nameTracker{
pkgNames: pkgNames,
variables: make(map[Variable]string),
rules: make(map[Rule]string),
pools: make(map[Pool]string),
}
for v := range liveGlobals.variables { for v := range liveGlobals.variables {
v.memoizeFullName(pkgNames) nameTracker.variables[v] = v.fullName(pkgNames)
} }
for r := range liveGlobals.rules { for r := range liveGlobals.rules {
r.memoizeFullName(pkgNames) nameTracker.rules[r] = r.fullName(pkgNames)
} }
for p := range liveGlobals.pools { for p := range liveGlobals.pools {
p.memoizeFullName(pkgNames) nameTracker.pools[p] = p.fullName(pkgNames)
} }
return nameTracker
} }
func (c *Context) checkForVariableReferenceCycles( func (c *Context) checkForVariableReferenceCycles(
variables map[Variable]*ninjaString, pkgNames map[*packageContext]string) { variables map[Variable]*ninjaString, nameTracker *nameTracker) {
visited := make(map[Variable]bool) // variables that were already checked visited := make(map[Variable]bool) // variables that were already checked
checking := make(map[Variable]bool) // variables actively being checked checking := make(map[Variable]bool) // variables actively being checked
@ -3905,12 +3913,12 @@ func (c *Context) checkForVariableReferenceCycles(
msgs := []string{"detected variable reference cycle:"} msgs := []string{"detected variable reference cycle:"}
// Iterate backwards through the cycle list. // Iterate backwards through the cycle list.
curName := v.fullName(pkgNames) curName := nameTracker.Variable(v)
curValue := value.Value(pkgNames) curValue := value.Value(nameTracker)
for i := len(cycle) - 1; i >= 0; i-- { for i := len(cycle) - 1; i >= 0; i-- {
next := cycle[i] next := cycle[i]
nextName := next.fullName(pkgNames) nextName := nameTracker.Variable(next)
nextValue := variables[next].Value(pkgNames) nextValue := variables[next].Value(nameTracker)
msgs = append(msgs, fmt.Sprintf( msgs = append(msgs, fmt.Sprintf(
" %q depends on %q", curName, nextName)) " %q depends on %q", curName, nextName))
@ -3958,7 +3966,7 @@ func (c *Context) AllTargets() (map[string]string, error) {
targets := map[string]string{} targets := map[string]string{}
var collectTargets = func(actionDefs localBuildActions) error { var collectTargets = func(actionDefs localBuildActions) error {
for _, buildDef := range actionDefs.buildDefs { for _, buildDef := range actionDefs.buildDefs {
ruleName := buildDef.Rule.fullName(c.pkgNames) ruleName := c.nameTracker.Rule(buildDef.Rule)
for _, output := range append(buildDef.Outputs, buildDef.ImplicitOutputs...) { for _, output := range append(buildDef.Outputs, buildDef.ImplicitOutputs...) {
outputValue, err := output.Eval(c.globalVariables) outputValue, err := output.Eval(c.globalVariables)
if err != nil { if err != nil {
@ -4266,7 +4274,7 @@ func (c *Context) writeBuildFileHeader(nw *ninjaWriter) error {
var pkgs []pkgAssociation var pkgs []pkgAssociation
maxNameLen := 0 maxNameLen := 0
for pkg, name := range c.pkgNames { for pkg, name := range c.nameTracker.pkgNames {
pkgs = append(pkgs, pkgAssociation{ pkgs = append(pkgs, pkgAssociation{
PkgName: name, PkgName: name,
PkgPath: pkg.pkgPath, PkgPath: pkg.pkgPath,
@ -4319,7 +4327,7 @@ func (c *Context) writeSubninjas(nw *ninjaWriter) error {
func (c *Context) writeBuildDir(nw *ninjaWriter) error { func (c *Context) writeBuildDir(nw *ninjaWriter) error {
if c.outDir != nil { if c.outDir != nil {
err := nw.Assign("builddir", c.outDir.Value(c.pkgNames)) err := nw.Assign("builddir", c.outDir.Value(c.nameTracker))
if err != nil { if err != nil {
return err return err
} }
@ -4332,29 +4340,6 @@ func (c *Context) writeBuildDir(nw *ninjaWriter) error {
return nil return nil
} }
type globalEntity interface {
fullName(pkgNames map[*packageContext]string) string
}
type globalEntitySorter struct {
pkgNames map[*packageContext]string
entities []globalEntity
}
func (s *globalEntitySorter) Len() int {
return len(s.entities)
}
func (s *globalEntitySorter) Less(i, j int) bool {
iName := s.entities[i].fullName(s.pkgNames)
jName := s.entities[j].fullName(s.pkgNames)
return iName < jName
}
func (s *globalEntitySorter) Swap(i, j int) {
s.entities[i], s.entities[j] = s.entities[j], s.entities[i]
}
func (c *Context) writeGlobalVariables(nw *ninjaWriter) error { func (c *Context) writeGlobalVariables(nw *ninjaWriter) error {
visited := make(map[Variable]bool) visited := make(map[Variable]bool)
@ -4373,7 +4358,7 @@ func (c *Context) writeGlobalVariables(nw *ninjaWriter) error {
} }
} }
err := nw.Assign(v.fullName(c.pkgNames), value.Value(c.pkgNames)) err := nw.Assign(c.nameTracker.Variable(v), value.Value(c.nameTracker))
if err != nil { if err != nil {
return err return err
} }
@ -4386,15 +4371,16 @@ func (c *Context) writeGlobalVariables(nw *ninjaWriter) error {
return nil return nil
} }
globalVariables := make([]globalEntity, 0, len(c.globalVariables)) globalVariables := make([]Variable, 0, len(c.globalVariables))
for variable := range c.globalVariables { for variable := range c.globalVariables {
globalVariables = append(globalVariables, variable) globalVariables = append(globalVariables, variable)
} }
sort.Sort(&globalEntitySorter{c.pkgNames, globalVariables}) slices.SortFunc(globalVariables, func(a, b Variable) int {
return cmp.Compare(c.nameTracker.Variable(a), c.nameTracker.Variable(b))
})
for _, entity := range globalVariables { for _, v := range globalVariables {
v := entity.(Variable)
if !visited[v] { if !visited[v] {
err := walk(v) err := walk(v)
if err != nil { if err != nil {
@ -4407,16 +4393,17 @@ func (c *Context) writeGlobalVariables(nw *ninjaWriter) error {
} }
func (c *Context) writeGlobalPools(nw *ninjaWriter) error { func (c *Context) writeGlobalPools(nw *ninjaWriter) error {
globalPools := make([]globalEntity, 0, len(c.globalPools)) globalPools := make([]Pool, 0, len(c.globalPools))
for pool := range c.globalPools { for pool := range c.globalPools {
globalPools = append(globalPools, pool) globalPools = append(globalPools, pool)
} }
sort.Sort(&globalEntitySorter{c.pkgNames, globalPools}) slices.SortFunc(globalPools, func(a, b Pool) int {
return cmp.Compare(c.nameTracker.Pool(a), c.nameTracker.Pool(b))
})
for _, entity := range globalPools { for _, pool := range globalPools {
pool := entity.(Pool) name := c.nameTracker.Pool(pool)
name := pool.fullName(c.pkgNames)
def := c.globalPools[pool] def := c.globalPools[pool]
err := def.WriteTo(nw, name) err := def.WriteTo(nw, name)
if err != nil { if err != nil {
@ -4433,18 +4420,19 @@ func (c *Context) writeGlobalPools(nw *ninjaWriter) error {
} }
func (c *Context) writeGlobalRules(nw *ninjaWriter) error { func (c *Context) writeGlobalRules(nw *ninjaWriter) error {
globalRules := make([]globalEntity, 0, len(c.globalRules)) globalRules := make([]Rule, 0, len(c.globalRules))
for rule := range c.globalRules { for rule := range c.globalRules {
globalRules = append(globalRules, rule) globalRules = append(globalRules, rule)
} }
sort.Sort(&globalEntitySorter{c.pkgNames, globalRules}) slices.SortFunc(globalRules, func(a, b Rule) int {
return cmp.Compare(c.nameTracker.Rule(a), c.nameTracker.Rule(b))
})
for _, entity := range globalRules { for _, rule := range globalRules {
rule := entity.(Rule) name := c.nameTracker.Rule(rule)
name := rule.fullName(c.pkgNames)
def := c.globalRules[rule] def := c.globalRules[rule]
err := def.WriteTo(nw, name, c.pkgNames) err := def.WriteTo(nw, name, c.nameTracker)
if err != nil { if err != nil {
return err return err
} }
@ -4766,7 +4754,7 @@ func (c *Context) writeLocalBuildActions(nw *ninjaWriter,
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = nw.Assign(name, value.Value(c.pkgNames)) err = nw.Assign(name, value.Value(c.nameTracker))
if err != nil { if err != nil {
return err return err
} }
@ -4789,7 +4777,7 @@ func (c *Context) writeLocalBuildActions(nw *ninjaWriter,
panic(err) panic(err)
} }
err = def.WriteTo(nw, name, c.pkgNames) err = def.WriteTo(nw, name, c.nameTracker)
if err != nil { if err != nil {
return err return err
} }
@ -4802,7 +4790,7 @@ func (c *Context) writeLocalBuildActions(nw *ninjaWriter,
// Write the build definitions. // Write the build definitions.
for _, buildDef := range defs.buildDefs { for _, buildDef := range defs.buildDefs {
err := buildDef.WriteTo(nw, c.pkgNames) err := buildDef.WriteTo(nw, c.nameTracker)
if err != nil { if err != nil {
return err return err
} }

View file

@ -229,8 +229,7 @@ func parseRuleParams(scope scope, params *RuleParams) (*ruleDef,
return r, nil return r, nil
} }
func (r *ruleDef) WriteTo(nw *ninjaWriter, name string, func (r *ruleDef) WriteTo(nw *ninjaWriter, name string, nameTracker *nameTracker) error {
pkgNames map[*packageContext]string) error {
if r.Comment != "" { if r.Comment != "" {
err := nw.Comment(r.Comment) err := nw.Comment(r.Comment)
@ -245,13 +244,13 @@ func (r *ruleDef) WriteTo(nw *ninjaWriter, name string,
} }
if r.Pool != nil { if r.Pool != nil {
err = nw.ScopedAssign("pool", r.Pool.fullName(pkgNames)) err = nw.ScopedAssign("pool", nameTracker.Pool(r.Pool))
if err != nil { if err != nil {
return err return err
} }
} }
err = writeVariables(nw, r.Variables, pkgNames) err = writeVariables(nw, r.Variables, nameTracker)
if err != nil { if err != nil {
return err return err
} }
@ -415,10 +414,10 @@ func parseBuildParams(scope scope, params *BuildParams,
return b, nil return b, nil
} }
func (b *buildDef) WriteTo(nw *ninjaWriter, pkgNames map[*packageContext]string) error { func (b *buildDef) WriteTo(nw *ninjaWriter, nameTracker *nameTracker) error {
var ( var (
comment = b.Comment comment = b.Comment
rule = b.Rule.fullName(pkgNames) rule = nameTracker.Rule(b.Rule)
outputs = b.Outputs outputs = b.Outputs
implicitOuts = b.ImplicitOutputs implicitOuts = b.ImplicitOutputs
explicitDeps = b.Inputs explicitDeps = b.Inputs
@ -441,12 +440,12 @@ func (b *buildDef) WriteTo(nw *ninjaWriter, pkgNames map[*packageContext]string)
err := nw.Build(comment, rule, outputs, implicitOuts, explicitDeps, implicitDeps, orderOnlyDeps, validations, err := nw.Build(comment, rule, outputs, implicitOuts, explicitDeps, implicitDeps, orderOnlyDeps, validations,
outputStrings, implicitOutStrings, explicitDepStrings, outputStrings, implicitOutStrings, explicitDepStrings,
implicitDepStrings, orderOnlyDepStrings, validationStrings, implicitDepStrings, orderOnlyDepStrings, validationStrings,
pkgNames) nameTracker)
if err != nil { if err != nil {
return err return err
} }
err = writeVariables(nw, b.Variables, pkgNames) err = writeVariables(nw, b.Variables, nameTracker)
if err != nil { if err != nil {
return err return err
} }
@ -458,8 +457,8 @@ func (b *buildDef) WriteTo(nw *ninjaWriter, pkgNames map[*packageContext]string)
args := make([]nameValuePair, 0, len(b.Args)) args := make([]nameValuePair, 0, len(b.Args))
for argVar, value := range b.Args { for argVar, value := range b.Args {
fullName := argVar.fullName(pkgNames) fullName := nameTracker.Variable(argVar)
args = append(args, nameValuePair{fullName, value.Value(pkgNames)}) args = append(args, nameValuePair{fullName, value.Value(nameTracker)})
} }
sort.Slice(args, func(i, j int) bool { return args[i].name < args[j].name }) sort.Slice(args, func(i, j int) bool { return args[i].name < args[j].name })
@ -471,7 +470,7 @@ func (b *buildDef) WriteTo(nw *ninjaWriter, pkgNames map[*packageContext]string)
} }
if !b.Optional { if !b.Optional {
err = nw.Default(pkgNames, outputs, outputStrings) err = nw.Default(nameTracker, outputs, outputStrings)
if err != nil { if err != nil {
return err return err
} }
@ -480,8 +479,7 @@ func (b *buildDef) WriteTo(nw *ninjaWriter, pkgNames map[*packageContext]string)
return nw.BlankLine() return nw.BlankLine()
} }
func writeVariables(nw *ninjaWriter, variables map[string]*ninjaString, func writeVariables(nw *ninjaWriter, variables map[string]*ninjaString, nameTracker *nameTracker) error {
pkgNames map[*packageContext]string) error {
var keys []string var keys []string
for k := range variables { for k := range variables {
keys = append(keys, k) keys = append(keys, k)
@ -489,7 +487,7 @@ func writeVariables(nw *ninjaWriter, variables map[string]*ninjaString,
sort.Strings(keys) sort.Strings(keys)
for _, name := range keys { for _, name := range keys {
err := nw.ScopedAssign(name, variables[name].Value(pkgNames)) err := nw.ScopedAssign(name, variables[name].Value(nameTracker))
if err != nil { if err != nil {
return err return err
} }

View file

@ -324,17 +324,16 @@ func parseNinjaOrSimpleStrings(scope scope, strs []string) ([]*ninjaString, []st
return ninjaStrings, simpleStrings, nil return ninjaStrings, simpleStrings, nil
} }
func (n *ninjaString) Value(pkgNames map[*packageContext]string) string { func (n *ninjaString) Value(nameTracker *nameTracker) string {
if n.variables == nil || len(*n.variables) == 0 { if n.variables == nil || len(*n.variables) == 0 {
return defaultEscaper.Replace(n.str) return defaultEscaper.Replace(n.str)
} }
str := &strings.Builder{} str := &strings.Builder{}
n.ValueWithEscaper(str, pkgNames, defaultEscaper) n.ValueWithEscaper(str, nameTracker, defaultEscaper)
return str.String() return str.String()
} }
func (n *ninjaString) ValueWithEscaper(w io.StringWriter, pkgNames map[*packageContext]string, func (n *ninjaString) ValueWithEscaper(w io.StringWriter, nameTracker *nameTracker, escaper *strings.Replacer) {
escaper *strings.Replacer) {
if n.variables == nil || len(*n.variables) == 0 { if n.variables == nil || len(*n.variables) == 0 {
w.WriteString(escaper.Replace(n.str)) w.WriteString(escaper.Replace(n.str))
@ -348,7 +347,7 @@ func (n *ninjaString) ValueWithEscaper(w io.StringWriter, pkgNames map[*packageC
w.WriteString("$ ") w.WriteString("$ ")
} else { } else {
w.WriteString("${") w.WriteString("${")
w.WriteString(v.variable.fullName(pkgNames)) w.WriteString(nameTracker.Variable(v.variable))
w.WriteString("}") w.WriteString("}")
} }
i = int(v.end) i = int(v.end)

View file

@ -164,7 +164,7 @@ func TestParseNinjaString(t *testing.T) {
output, err := parseNinjaString(scope, testCase.input) output, err := parseNinjaString(scope, testCase.input)
if err == nil { if err == nil {
if g, w := output.Value(nil), testCase.value; g != w { if g, w := output.Value(&nameTracker{}), testCase.value; g != w {
t.Errorf("incorrect Value output, want %q, got %q", w, g) t.Errorf("incorrect Value output, want %q, got %q", w, g)
} }
@ -191,7 +191,11 @@ func TestParseNinjaString(t *testing.T) {
} }
func TestParseNinjaStringWithImportedVar(t *testing.T) { func TestParseNinjaStringWithImportedVar(t *testing.T) {
ImpVar := &staticVariable{name_: "ImpVar", fullName_: "g.impPkg.ImpVar"} pctx := &packageContext{}
pkgNames := map[*packageContext]string{
pctx: "impPkg",
}
ImpVar := &staticVariable{pctx: pctx, name_: "ImpVar"}
impScope := newScope(nil) impScope := newScope(nil)
impScope.AddVariable(ImpVar) impScope.AddVariable(ImpVar)
scope := newScope(nil) scope := newScope(nil)
@ -211,7 +215,7 @@ func TestParseNinjaStringWithImportedVar(t *testing.T) {
t.Errorf(" got: %#v", *output.variables) t.Errorf(" got: %#v", *output.variables)
} }
if g, w := output.Value(nil), "abc def ${g.impPkg.ImpVar} ghi"; g != w { if g, w := output.Value(&nameTracker{pkgNames: pkgNames}), "abc def ${g.impPkg.ImpVar} ghi"; g != w {
t.Errorf("incorrect Value output, want %q got %q", w, g) t.Errorf("incorrect Value output, want %q got %q", w, g)
} }
} }
@ -289,7 +293,7 @@ func Test_parseNinjaOrSimpleStrings(t *testing.T) {
if gotNinjaStrings != nil { if gotNinjaStrings != nil {
evaluatedNinjaStrings = make([]string, 0, len(gotNinjaStrings)) evaluatedNinjaStrings = make([]string, 0, len(gotNinjaStrings))
for _, ns := range gotNinjaStrings { for _, ns := range gotNinjaStrings {
evaluatedNinjaStrings = append(evaluatedNinjaStrings, ns.Value(nil)) evaluatedNinjaStrings = append(evaluatedNinjaStrings, ns.Value(&nameTracker{}))
} }
} }
@ -364,7 +368,7 @@ func BenchmarkNinjaString_Value(b *testing.B) {
b.Run(strconv.Itoa(l), func(b *testing.B) { b.Run(strconv.Itoa(l), func(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
ns.Value(nil) ns.Value(&nameTracker{})
} }
}) })
} }
@ -377,7 +381,7 @@ func BenchmarkNinjaString_Value(b *testing.B) {
b.Run(strconv.Itoa(l), func(b *testing.B) { b.Run(strconv.Itoa(l), func(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
ns.Value(nil) ns.Value(&nameTracker{})
} }
}) })
} }
@ -394,7 +398,7 @@ func BenchmarkNinjaString_Value(b *testing.B) {
b.Run(strconv.Itoa(l), func(b *testing.B) { b.Run(strconv.Itoa(l), func(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
ns.Value(nil) ns.Value(&nameTracker{})
} }
}) })
} }

View file

@ -117,7 +117,7 @@ func (n *ninjaWriter) Build(comment string, rule string, outputs, implicitOuts,
explicitDeps, implicitDeps, orderOnlyDeps, validations []*ninjaString, explicitDeps, implicitDeps, orderOnlyDeps, validations []*ninjaString,
outputStrings, implicitOutStrings, explicitDepStrings, outputStrings, implicitOutStrings, explicitDepStrings,
implicitDepStrings, orderOnlyDepStrings, validationStrings []string, implicitDepStrings, orderOnlyDepStrings, validationStrings []string,
pkgNames map[*packageContext]string) error { nameTracker *nameTracker) error {
n.justDidBlankLine = false n.justDidBlankLine = false
@ -144,7 +144,7 @@ func (n *ninjaWriter) Build(comment string, rule string, outputs, implicitOuts,
} }
for _, output := range outputs { for _, output := range outputs {
wrapper.Space() wrapper.Space()
output.ValueWithEscaper(wrapper, pkgNames, outputEscaper) output.ValueWithEscaper(wrapper, nameTracker, outputEscaper)
} }
if len(implicitOuts) > 0 || len(implicitOutStrings) > 0 { if len(implicitOuts) > 0 || len(implicitOutStrings) > 0 {
@ -156,7 +156,7 @@ func (n *ninjaWriter) Build(comment string, rule string, outputs, implicitOuts,
} }
for _, out := range implicitOuts { for _, out := range implicitOuts {
wrapper.Space() wrapper.Space()
out.ValueWithEscaper(wrapper, pkgNames, outputEscaper) out.ValueWithEscaper(wrapper, nameTracker, outputEscaper)
} }
} }
@ -170,7 +170,7 @@ func (n *ninjaWriter) Build(comment string, rule string, outputs, implicitOuts,
} }
for _, dep := range explicitDeps { for _, dep := range explicitDeps {
wrapper.Space() wrapper.Space()
dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper) dep.ValueWithEscaper(wrapper, nameTracker, inputEscaper)
} }
if len(implicitDeps) > 0 || len(implicitDepStrings) > 0 { if len(implicitDeps) > 0 || len(implicitDepStrings) > 0 {
@ -182,7 +182,7 @@ func (n *ninjaWriter) Build(comment string, rule string, outputs, implicitOuts,
} }
for _, dep := range implicitDeps { for _, dep := range implicitDeps {
wrapper.Space() wrapper.Space()
dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper) dep.ValueWithEscaper(wrapper, nameTracker, inputEscaper)
} }
} }
@ -195,7 +195,7 @@ func (n *ninjaWriter) Build(comment string, rule string, outputs, implicitOuts,
} }
for _, dep := range orderOnlyDeps { for _, dep := range orderOnlyDeps {
wrapper.Space() wrapper.Space()
dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper) dep.ValueWithEscaper(wrapper, nameTracker, inputEscaper)
} }
} }
@ -208,7 +208,7 @@ func (n *ninjaWriter) Build(comment string, rule string, outputs, implicitOuts,
} }
for _, dep := range validations { for _, dep := range validations {
wrapper.Space() wrapper.Space()
dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper) dep.ValueWithEscaper(wrapper, nameTracker, inputEscaper)
} }
} }
@ -261,7 +261,7 @@ func (n *ninjaWriter) ScopedAssign(name, value string) error {
return nil return nil
} }
func (n *ninjaWriter) Default(pkgNames map[*packageContext]string, targets []*ninjaString, targetStrings []string) error { func (n *ninjaWriter) Default(nameTracker *nameTracker, targets []*ninjaString, targetStrings []string) error {
n.justDidBlankLine = false n.justDidBlankLine = false
const lineWrapLen = len(" $") const lineWrapLen = len(" $")
@ -280,7 +280,7 @@ func (n *ninjaWriter) Default(pkgNames map[*packageContext]string, targets []*ni
} }
for _, target := range targets { for _, target := range targets {
wrapper.Space() wrapper.Space()
target.ValueWithEscaper(wrapper, pkgNames, outputEscaper) target.ValueWithEscaper(wrapper, nameTracker, outputEscaper)
} }
return wrapper.Flush() return wrapper.Flush()

View file

@ -250,10 +250,9 @@ func (p *packageContext) ImportAs(as, pkgPath string) {
} }
type staticVariable struct { type staticVariable struct {
pctx *packageContext pctx *packageContext
name_ string name_ string
value_ string value_ string
fullName_ string
} }
// StaticVariable returns a Variable whose value does not depend on any // StaticVariable returns a Variable whose value does not depend on any
@ -294,16 +293,9 @@ func (v *staticVariable) name() string {
} }
func (v *staticVariable) fullName(pkgNames map[*packageContext]string) string { func (v *staticVariable) fullName(pkgNames map[*packageContext]string) string {
if v.fullName_ != "" {
return v.fullName_
}
return packageNamespacePrefix(pkgNames[v.pctx]) + v.name_ return packageNamespacePrefix(pkgNames[v.pctx]) + v.name_
} }
func (v *staticVariable) memoizeFullName(pkgNames map[*packageContext]string) {
v.fullName_ = v.fullName(pkgNames)
}
func (v *staticVariable) value(VariableFuncContext, interface{}) (*ninjaString, error) { func (v *staticVariable) value(VariableFuncContext, interface{}) (*ninjaString, error) {
ninjaStr, err := parseNinjaString(v.pctx.scope, v.value_) ninjaStr, err := parseNinjaString(v.pctx.scope, v.value_)
if err != nil { if err != nil {
@ -318,10 +310,9 @@ func (v *staticVariable) String() string {
} }
type variableFunc struct { type variableFunc struct {
pctx *packageContext pctx *packageContext
name_ string name_ string
value_ func(VariableFuncContext, interface{}) (string, error) value_ func(VariableFuncContext, interface{}) (string, error)
fullName_ string
} }
// VariableFuncContext is passed to VariableFunc functions. // VariableFuncContext is passed to VariableFunc functions.
@ -430,16 +421,9 @@ func (v *variableFunc) name() string {
} }
func (v *variableFunc) fullName(pkgNames map[*packageContext]string) string { func (v *variableFunc) fullName(pkgNames map[*packageContext]string) string {
if v.fullName_ != "" {
return v.fullName_
}
return packageNamespacePrefix(pkgNames[v.pctx]) + v.name_ return packageNamespacePrefix(pkgNames[v.pctx]) + v.name_
} }
func (v *variableFunc) memoizeFullName(pkgNames map[*packageContext]string) {
v.fullName_ = v.fullName(pkgNames)
}
func (v *variableFunc) value(ctx VariableFuncContext, config interface{}) (*ninjaString, error) { func (v *variableFunc) value(ctx VariableFuncContext, config interface{}) (*ninjaString, error) {
value, err := v.value_(ctx, config) value, err := v.value_(ctx, config)
if err != nil { if err != nil {
@ -500,10 +484,6 @@ func (v *argVariable) fullName(pkgNames map[*packageContext]string) string {
return v.name_ return v.name_
} }
func (v *argVariable) memoizeFullName(pkgNames map[*packageContext]string) {
// Nothing to do, full name is known at initialization.
}
func (v *argVariable) value(ctx VariableFuncContext, config interface{}) (*ninjaString, error) { func (v *argVariable) value(ctx VariableFuncContext, config interface{}) (*ninjaString, error) {
return nil, errVariableIsArg return nil, errVariableIsArg
} }
@ -513,10 +493,9 @@ func (v *argVariable) String() string {
} }
type staticPool struct { type staticPool struct {
pctx *packageContext pctx *packageContext
name_ string name_ string
params PoolParams params PoolParams
fullName_ string
} }
// StaticPool returns a Pool whose value does not depend on any configuration // StaticPool returns a Pool whose value does not depend on any configuration
@ -558,16 +537,9 @@ func (p *staticPool) name() string {
} }
func (p *staticPool) fullName(pkgNames map[*packageContext]string) string { func (p *staticPool) fullName(pkgNames map[*packageContext]string) string {
if p.fullName_ != "" {
return p.fullName_
}
return packageNamespacePrefix(pkgNames[p.pctx]) + p.name_ return packageNamespacePrefix(pkgNames[p.pctx]) + p.name_
} }
func (p *staticPool) memoizeFullName(pkgNames map[*packageContext]string) {
p.fullName_ = p.fullName(pkgNames)
}
func (p *staticPool) def(config interface{}) (*poolDef, error) { func (p *staticPool) def(config interface{}) (*poolDef, error) {
def, err := parsePoolParams(p.pctx.scope, &p.params) def, err := parsePoolParams(p.pctx.scope, &p.params)
if err != nil { if err != nil {
@ -584,7 +556,6 @@ type poolFunc struct {
pctx *packageContext pctx *packageContext
name_ string name_ string
paramsFunc func(interface{}) (PoolParams, error) paramsFunc func(interface{}) (PoolParams, error)
fullName_ string
} }
// PoolFunc returns a Pool whose value is determined by a function that takes a // PoolFunc returns a Pool whose value is determined by a function that takes a
@ -629,16 +600,9 @@ func (p *poolFunc) name() string {
} }
func (p *poolFunc) fullName(pkgNames map[*packageContext]string) string { func (p *poolFunc) fullName(pkgNames map[*packageContext]string) string {
if p.fullName_ != "" {
return p.fullName_
}
return packageNamespacePrefix(pkgNames[p.pctx]) + p.name_ return packageNamespacePrefix(pkgNames[p.pctx]) + p.name_
} }
func (p *poolFunc) memoizeFullName(pkgNames map[*packageContext]string) {
p.fullName_ = p.fullName(pkgNames)
}
func (p *poolFunc) def(config interface{}) (*poolDef, error) { func (p *poolFunc) def(config interface{}) (*poolDef, error) {
params, err := p.paramsFunc(config) params, err := p.paramsFunc(config)
if err != nil { if err != nil {
@ -671,10 +635,6 @@ func (p *builtinPool) fullName(pkgNames map[*packageContext]string) string {
return p.name_ return p.name_
} }
func (p *builtinPool) memoizeFullName(pkgNames map[*packageContext]string) {
// Nothing to do, full name is known at initialization.
}
func (p *builtinPool) def(config interface{}) (*poolDef, error) { func (p *builtinPool) def(config interface{}) (*poolDef, error) {
return nil, errPoolIsBuiltin return nil, errPoolIsBuiltin
} }
@ -696,7 +656,6 @@ type staticRule struct {
params RuleParams params RuleParams
argNames map[string]bool argNames map[string]bool
scope_ *basicScope scope_ *basicScope
fullName_ string
sync.Mutex // protects scope_ during lazy creation sync.Mutex // protects scope_ during lazy creation
} }
@ -764,16 +723,9 @@ func (r *staticRule) name() string {
} }
func (r *staticRule) fullName(pkgNames map[*packageContext]string) string { func (r *staticRule) fullName(pkgNames map[*packageContext]string) string {
if r.fullName_ != "" {
return r.fullName_
}
return packageNamespacePrefix(pkgNames[r.pctx]) + r.name_ return packageNamespacePrefix(pkgNames[r.pctx]) + r.name_
} }
func (r *staticRule) memoizeFullName(pkgNames map[*packageContext]string) {
r.fullName_ = r.fullName(pkgNames)
}
func (r *staticRule) def(interface{}) (*ruleDef, error) { func (r *staticRule) def(interface{}) (*ruleDef, error) {
def, err := parseRuleParams(r.scope(), &r.params) def, err := parseRuleParams(r.scope(), &r.params)
if err != nil { if err != nil {
@ -809,7 +761,6 @@ type ruleFunc struct {
paramsFunc func(interface{}) (RuleParams, error) paramsFunc func(interface{}) (RuleParams, error)
argNames map[string]bool argNames map[string]bool
scope_ *basicScope scope_ *basicScope
fullName_ string
sync.Mutex // protects scope_ during lazy creation sync.Mutex // protects scope_ during lazy creation
} }
@ -878,16 +829,9 @@ func (r *ruleFunc) name() string {
} }
func (r *ruleFunc) fullName(pkgNames map[*packageContext]string) string { func (r *ruleFunc) fullName(pkgNames map[*packageContext]string) string {
if r.fullName_ != "" {
return r.fullName_
}
return packageNamespacePrefix(pkgNames[r.pctx]) + r.name_ return packageNamespacePrefix(pkgNames[r.pctx]) + r.name_
} }
func (r *ruleFunc) memoizeFullName(pkgNames map[*packageContext]string) {
r.fullName_ = r.fullName(pkgNames)
}
func (r *ruleFunc) def(config interface{}) (*ruleDef, error) { func (r *ruleFunc) def(config interface{}) (*ruleDef, error) {
params, err := r.paramsFunc(config) params, err := r.paramsFunc(config)
if err != nil { if err != nil {
@ -939,10 +883,6 @@ func (r *builtinRule) fullName(pkgNames map[*packageContext]string) string {
return r.name_ return r.name_
} }
func (r *builtinRule) memoizeFullName(pkgNames map[*packageContext]string) {
// Nothing to do, full name is known at initialization.
}
func (r *builtinRule) def(config interface{}) (*ruleDef, error) { func (r *builtinRule) def(config interface{}) (*ruleDef, error) {
return nil, errRuleIsBuiltin return nil, errRuleIsBuiltin
} }

View file

@ -28,7 +28,6 @@ type Variable interface {
packageContext() *packageContext packageContext() *packageContext
name() string // "foo" name() string // "foo"
fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo" fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
memoizeFullName(pkgNames map[*packageContext]string) // precompute fullName if desired
value(ctx VariableFuncContext, config interface{}) (*ninjaString, error) value(ctx VariableFuncContext, config interface{}) (*ninjaString, error)
String() string String() string
} }
@ -39,7 +38,6 @@ type Pool interface {
packageContext() *packageContext packageContext() *packageContext
name() string // "foo" name() string // "foo"
fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo" fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
memoizeFullName(pkgNames map[*packageContext]string) // precompute fullName if desired
def(config interface{}) (*poolDef, error) def(config interface{}) (*poolDef, error)
String() string String() string
} }
@ -50,7 +48,6 @@ type Rule interface {
packageContext() *packageContext packageContext() *packageContext
name() string // "foo" name() string // "foo"
fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo" fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
memoizeFullName(pkgNames map[*packageContext]string) // precompute fullName if desired
def(config interface{}) (*ruleDef, error) def(config interface{}) (*ruleDef, error)
scope() *basicScope scope() *basicScope
isArg(argName string) bool isArg(argName string) bool
@ -369,10 +366,6 @@ func (l *localVariable) fullName(pkgNames map[*packageContext]string) string {
return l.fullName_ return l.fullName_
} }
func (l *localVariable) memoizeFullName(pkgNames map[*packageContext]string) {
// Nothing to do, full name is known at initialization.
}
func (l *localVariable) value(VariableFuncContext, interface{}) (*ninjaString, error) { func (l *localVariable) value(VariableFuncContext, interface{}) (*ninjaString, error) {
return l.value_, nil return l.value_, nil
} }
@ -401,10 +394,6 @@ func (l *localRule) fullName(pkgNames map[*packageContext]string) string {
return l.fullName_ return l.fullName_
} }
func (l *localRule) memoizeFullName(pkgNames map[*packageContext]string) {
// Nothing to do, full name is known at initialization.
}
func (l *localRule) def(interface{}) (*ruleDef, error) { func (l *localRule) def(interface{}) (*ruleDef, error) {
return l.def_, nil return l.def_, nil
} }
@ -420,3 +409,41 @@ func (r *localRule) isArg(argName string) bool {
func (r *localRule) String() string { func (r *localRule) String() string {
return "<local rule>:" + r.fullName_ return "<local rule>:" + r.fullName_
} }
type nameTracker struct {
variables map[Variable]string
rules map[Rule]string
pools map[Pool]string
pkgNames map[*packageContext]string
}
func (m *nameTracker) Variable(v Variable) string {
if m == nil {
return v.fullName(nil)
}
if name, ok := m.variables[v]; ok {
return name
}
return v.fullName(m.pkgNames)
}
func (m *nameTracker) Rule(r Rule) string {
if m == nil {
return r.fullName(nil)
}
if name, ok := m.rules[r]; ok {
return name
}
return r.fullName(m.pkgNames)
}
func (m *nameTracker) Pool(p Pool) string {
if m == nil {
return p.fullName(nil)
}
if name, ok := m.pools[p]; ok {
return name
}
return p.fullName(m.pkgNames)
}