Snap for 10399941 from 797fe25cb2f2cfcb712f8de314361d662c4b8c3c to sdk-release
Change-Id: Iffa8ae7c1efbf2d29737d518d834f0a7727a5529
diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go
index bf12cd3..ead22d0 100644
--- a/bootstrap/bootstrap.go
+++ b/bootstrap/bootstrap.go
@@ -155,7 +155,7 @@
}
func pluginDeps(ctx blueprint.BottomUpMutatorContext) {
- if pkg, ok := ctx.Module().(*goPackage); ok {
+ if pkg, ok := ctx.Module().(*GoPackage); ok {
if ctx.PrimaryModule() == ctx.Module() {
for _, plugin := range pkg.properties.PluginFor {
ctx.AddReverseDependency(ctx.Module(), nil, plugin)
@@ -190,18 +190,18 @@
}
func IsBootstrapModule(module blueprint.Module) bool {
- _, isPackage := module.(*goPackage)
- _, isBinary := module.(*goBinary)
+ _, isPackage := module.(*GoPackage)
+ _, isBinary := module.(*GoBinary)
return isPackage || isBinary
}
func isBootstrapBinaryModule(module blueprint.Module) bool {
- _, isBinary := module.(*goBinary)
+ _, isBinary := module.(*GoBinary)
return isBinary
}
-// A goPackage is a module for building Go packages.
-type goPackage struct {
+// A GoPackage is a module for building Go packages.
+type GoPackage struct {
blueprint.SimpleName
properties struct {
Deps []string
@@ -231,39 +231,39 @@
testResultFile []string
}
-var _ goPackageProducer = (*goPackage)(nil)
+var _ goPackageProducer = (*GoPackage)(nil)
func newGoPackageModuleFactory() func() (blueprint.Module, []interface{}) {
return func() (blueprint.Module, []interface{}) {
- module := &goPackage{}
+ module := &GoPackage{}
return module, []interface{}{&module.properties, &module.SimpleName.Properties}
}
}
-func (g *goPackage) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
+func (g *GoPackage) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
if ctx.Module() != ctx.PrimaryModule() {
return nil
}
return g.properties.Deps
}
-func (g *goPackage) GoPkgPath() string {
+func (g *GoPackage) GoPkgPath() string {
return g.properties.PkgPath
}
-func (g *goPackage) GoPkgRoot() string {
+func (g *GoPackage) GoPkgRoot() string {
return g.pkgRoot
}
-func (g *goPackage) GoPackageTarget() string {
+func (g *GoPackage) GoPackageTarget() string {
return g.archiveFile
}
-func (g *goPackage) GoTestTargets() []string {
+func (g *GoPackage) GoTestTargets() []string {
return g.testResultFile
}
-func (g *goPackage) IsPluginFor(name string) bool {
+func (g *GoPackage) IsPluginFor(name string) bool {
for _, plugin := range g.properties.PluginFor {
if plugin == name {
return true
@@ -272,11 +272,11 @@
return false
}
-func (g *goPackage) GenerateBuildActions(ctx blueprint.ModuleContext) {
+func (g *GoPackage) GenerateBuildActions(ctx blueprint.ModuleContext) {
// Allow the primary builder to create multiple variants. Any variants after the first
// will copy outputs from the first.
if ctx.Module() != ctx.PrimaryModule() {
- primary := ctx.PrimaryModule().(*goPackage)
+ primary := ctx.PrimaryModule().(*GoPackage)
g.pkgRoot = primary.pkgRoot
g.archiveFile = primary.archiveFile
g.testResultFile = primary.testResultFile
@@ -340,8 +340,36 @@
srcs, genSrcs)
}
-// A goBinary is a module for building executable binaries from Go sources.
-type goBinary struct {
+func (g *GoPackage) Srcs() []string {
+ return g.properties.Srcs
+}
+
+func (g *GoPackage) LinuxSrcs() []string {
+ return g.properties.Linux.Srcs
+}
+
+func (g *GoPackage) DarwinSrcs() []string {
+ return g.properties.Darwin.Srcs
+}
+
+func (g *GoPackage) TestSrcs() []string {
+ return g.properties.TestSrcs
+}
+
+func (g *GoPackage) LinuxTestSrcs() []string {
+ return g.properties.Linux.TestSrcs
+}
+
+func (g *GoPackage) DarwinTestSrcs() []string {
+ return g.properties.Darwin.TestSrcs
+}
+
+func (g *GoPackage) Deps() []string {
+ return g.properties.Deps
+}
+
+// A GoBinary is a module for building executable binaries from Go sources.
+type GoBinary struct {
blueprint.SimpleName
properties struct {
Deps []string
@@ -363,32 +391,60 @@
installPath string
}
-var _ GoBinaryTool = (*goBinary)(nil)
+var _ GoBinaryTool = (*GoBinary)(nil)
func newGoBinaryModuleFactory() func() (blueprint.Module, []interface{}) {
return func() (blueprint.Module, []interface{}) {
- module := &goBinary{}
+ module := &GoBinary{}
return module, []interface{}{&module.properties, &module.SimpleName.Properties}
}
}
-func (g *goBinary) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
+func (g *GoBinary) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
if ctx.Module() != ctx.PrimaryModule() {
return nil
}
return g.properties.Deps
}
-func (g *goBinary) isGoBinary() {}
-func (g *goBinary) InstallPath() string {
+func (g *GoBinary) isGoBinary() {}
+func (g *GoBinary) InstallPath() string {
return g.installPath
}
-func (g *goBinary) GenerateBuildActions(ctx blueprint.ModuleContext) {
+func (g *GoBinary) Srcs() []string {
+ return g.properties.Srcs
+}
+
+func (g *GoBinary) LinuxSrcs() []string {
+ return g.properties.Linux.Srcs
+}
+
+func (g *GoBinary) DarwinSrcs() []string {
+ return g.properties.Darwin.Srcs
+}
+
+func (g *GoBinary) TestSrcs() []string {
+ return g.properties.TestSrcs
+}
+
+func (g *GoBinary) LinuxTestSrcs() []string {
+ return g.properties.Linux.TestSrcs
+}
+
+func (g *GoBinary) DarwinTestSrcs() []string {
+ return g.properties.Darwin.TestSrcs
+}
+
+func (g *GoBinary) Deps() []string {
+ return g.properties.Deps
+}
+
+func (g *GoBinary) GenerateBuildActions(ctx blueprint.ModuleContext) {
// Allow the primary builder to create multiple variants. Any variants after the first
// will copy outputs from the first.
if ctx.Module() != ctx.PrimaryModule() {
- primary := ctx.PrimaryModule().(*goBinary)
+ primary := ctx.PrimaryModule().(*GoBinary)
g.installPath = primary.installPath
return
}
@@ -619,7 +675,7 @@
// Find the module that's marked as the "primary builder", which means it's
// creating the binary that we'll use to generate the non-bootstrap
// build.ninja file.
- var primaryBuilders []*goBinary
+ var primaryBuilders []*GoBinary
// blueprintTools contains blueprint go binaries that will be built in StageMain
var blueprintTools []string
// blueprintGoPackages contains all blueprint go packages that can be built in StageMain
@@ -627,14 +683,14 @@
ctx.VisitAllModulesIf(IsBootstrapModule,
func(module blueprint.Module) {
if ctx.PrimaryModule(module) == module {
- if binaryModule, ok := module.(*goBinary); ok {
+ if binaryModule, ok := module.(*GoBinary); ok {
blueprintTools = append(blueprintTools, binaryModule.InstallPath())
if binaryModule.properties.PrimaryBuilder {
primaryBuilders = append(primaryBuilders, binaryModule)
}
}
- if packageModule, ok := module.(*goPackage); ok {
+ if packageModule, ok := module.(*GoPackage); ok {
blueprintGoPackages = append(blueprintGoPackages,
packageModule.GoPackageTarget())
blueprintGoPackages = append(blueprintGoPackages,
diff --git a/bootstrap/command.go b/bootstrap/command.go
index 08a0e43..08b6a84 100644
--- a/bootstrap/command.go
+++ b/bootstrap/command.go
@@ -16,6 +16,7 @@
import (
"bufio"
+ "errors"
"fmt"
"io"
"os"
@@ -40,11 +41,17 @@
TraceFile string
}
+// RegisterGoModuleTypes adds module types to build tools written in golang
+func RegisterGoModuleTypes(ctx *blueprint.Context) {
+ ctx.RegisterModuleType("bootstrap_go_package", newGoPackageModuleFactory())
+ ctx.RegisterModuleType("blueprint_go_binary", newGoBinaryModuleFactory())
+}
+
// RunBlueprint emits `args.OutFile` (a Ninja file) and returns the list of
// its dependencies. These can be written to a `${args.OutFile}.d` file
// so that it is correctly rebuilt when needed in case Blueprint is itself
// invoked from Ninja
-func RunBlueprint(args Args, stopBefore StopBefore, ctx *blueprint.Context, config interface{}) []string {
+func RunBlueprint(args Args, stopBefore StopBefore, ctx *blueprint.Context, config interface{}) ([]string, error) {
runtime.GOMAXPROCS(runtime.NumCPU())
if args.NoGC {
@@ -54,7 +61,7 @@
if args.Cpuprofile != "" {
f, err := os.Create(joinPath(ctx.SrcDir(), args.Cpuprofile))
if err != nil {
- fatalf("error opening cpuprofile: %s", err)
+ return nil, fmt.Errorf("error opening cpuprofile: %s", err)
}
pprof.StartCPUProfile(f)
defer f.Close()
@@ -64,7 +71,7 @@
if args.TraceFile != "" {
f, err := os.Create(joinPath(ctx.SrcDir(), args.TraceFile))
if err != nil {
- fatalf("error opening trace: %s", err)
+ return nil, fmt.Errorf("error opening trace: %s", err)
}
trace.Start(f)
defer f.Close()
@@ -72,7 +79,7 @@
}
if args.ModuleListFile == "" {
- fatalf("-l <moduleListFile> is required and must be nonempty")
+ return nil, fmt.Errorf("-l <moduleListFile> is required and must be nonempty")
}
ctx.SetModuleListFile(args.ModuleListFile)
@@ -82,50 +89,49 @@
ctx.BeginEvent("list_modules")
var filesToParse []string
if f, err := ctx.ListModulePaths("."); err != nil {
- fatalf("could not enumerate files: %v\n", err.Error())
+ return nil, fmt.Errorf("could not enumerate files: %v\n", err.Error())
} else {
filesToParse = f
}
ctx.EndEvent("list_modules")
ctx.RegisterBottomUpMutator("bootstrap_plugin_deps", pluginDeps)
- ctx.RegisterModuleType("bootstrap_go_package", newGoPackageModuleFactory())
- ctx.RegisterModuleType("blueprint_go_binary", newGoBinaryModuleFactory())
ctx.RegisterSingletonType("bootstrap", newSingletonFactory(), false)
+ RegisterGoModuleTypes(ctx)
blueprint.RegisterPackageIncludesModuleType(ctx)
ctx.BeginEvent("parse_bp")
if blueprintFiles, errs := ctx.ParseFileList(".", filesToParse, config); len(errs) > 0 {
- fatalErrors(errs)
+ return nil, fatalErrors(errs)
} else {
ctx.EndEvent("parse_bp")
ninjaDeps = append(ninjaDeps, blueprintFiles...)
}
if resolvedDeps, errs := ctx.ResolveDependencies(config); len(errs) > 0 {
- fatalErrors(errs)
+ return nil, fatalErrors(errs)
} else {
ninjaDeps = append(ninjaDeps, resolvedDeps...)
}
if stopBefore == StopBeforePrepareBuildActions {
- return ninjaDeps
+ return ninjaDeps, nil
}
if ctx.BeforePrepareBuildActionsHook != nil {
if err := ctx.BeforePrepareBuildActionsHook(); err != nil {
- fatalErrors([]error{err})
+ return nil, fatalErrors([]error{err})
}
}
if buildActionsDeps, errs := ctx.PrepareBuildActions(config); len(errs) > 0 {
- fatalErrors(errs)
+ return nil, fatalErrors(errs)
} else {
ninjaDeps = append(ninjaDeps, buildActionsDeps...)
}
if stopBefore == StopBeforeWriteNinja {
- return ninjaDeps
+ return ninjaDeps, nil
}
const outFilePermissions = 0666
@@ -137,13 +143,13 @@
defer ctx.EndEvent("write_files")
if args.EmptyNinjaFile {
if err := os.WriteFile(joinPath(ctx.SrcDir(), args.OutFile), []byte(nil), outFilePermissions); err != nil {
- fatalf("error writing empty Ninja file: %s", err)
+ return nil, fmt.Errorf("error writing empty Ninja file: %s", err)
}
out = io.Discard.(io.StringWriter)
} else {
f, err := os.OpenFile(joinPath(ctx.SrcDir(), args.OutFile), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, outFilePermissions)
if err != nil {
- fatalf("error opening Ninja file: %s", err)
+ return nil, fmt.Errorf("error opening Ninja file: %s", err)
}
defer f.Close()
buf = bufio.NewWriterSize(f, 16*1024*1024)
@@ -151,40 +157,34 @@
}
if err := ctx.WriteBuildFile(out); err != nil {
- fatalf("error writing Ninja file contents: %s", err)
+ return nil, fmt.Errorf("error writing Ninja file contents: %s", err)
}
if buf != nil {
if err := buf.Flush(); err != nil {
- fatalf("error flushing Ninja file contents: %s", err)
+ return nil, fmt.Errorf("error flushing Ninja file contents: %s", err)
}
}
if f != nil {
if err := f.Close(); err != nil {
- fatalf("error closing Ninja file: %s", err)
+ return nil, fmt.Errorf("error closing Ninja file: %s", err)
}
}
if args.Memprofile != "" {
f, err := os.Create(joinPath(ctx.SrcDir(), args.Memprofile))
if err != nil {
- fatalf("error opening memprofile: %s", err)
+ return nil, fmt.Errorf("error opening memprofile: %s", err)
}
defer f.Close()
pprof.WriteHeapProfile(f)
}
- return ninjaDeps
+ return ninjaDeps, nil
}
-func fatalf(format string, args ...interface{}) {
- fmt.Printf(format, args...)
- fmt.Print("\n")
- os.Exit(1)
-}
-
-func fatalErrors(errs []error) {
+func fatalErrors(errs []error) error {
red := "\x1b[31m"
unred := "\x1b[0m"
@@ -198,7 +198,8 @@
fmt.Printf("%sinternal error:%s %s\n", red, unred, err)
}
}
- os.Exit(1)
+
+ return errors.New("fatal errors encountered")
}
func joinPath(base, path string) string {
diff --git a/bootstrap/glob.go b/bootstrap/glob.go
index ef68090..b2c3a2d 100644
--- a/bootstrap/glob.go
+++ b/bootstrap/glob.go
@@ -210,18 +210,21 @@
// Writes a .ninja file that contains instructions for regenerating the glob
// files that contain the results of every glob that was run. The list of files
// is available as the result of GlobFileListFiles().
-func WriteBuildGlobsNinjaFile(glob *GlobSingleton, config interface{}) {
+func WriteBuildGlobsNinjaFile(glob *GlobSingleton, config interface{}) error {
buffer, errs := generateGlobNinjaFile(glob, config)
if len(errs) > 0 {
- fatalErrors(errs)
+ return fatalErrors(errs)
}
const outFilePermissions = 0666
err := ioutil.WriteFile(joinPath(glob.SrcDir, glob.GlobFile), buffer, outFilePermissions)
if err != nil {
- fatalf("error writing %s: %s", glob.GlobFile, err)
+ return fmt.Errorf("error writing %s: %s", glob.GlobFile, err)
}
+
+ return nil
}
+
func generateGlobNinjaFile(glob *GlobSingleton, config interface{}) ([]byte, []error) {
ctx := blueprint.NewContext()
diff --git a/bootstrap/writedocs.go b/bootstrap/writedocs.go
index f7314f7..d172f70 100644
--- a/bootstrap/writedocs.go
+++ b/bootstrap/writedocs.go
@@ -16,16 +16,16 @@
// Find the module that's marked as the "primary builder", which means it's
// creating the binary that we'll use to generate the non-bootstrap
// build.ninja file.
- var primaryBuilders []*goBinary
+ var primaryBuilders []*GoBinary
ctx.VisitAllModulesIf(isBootstrapBinaryModule,
func(module blueprint.Module) {
- binaryModule := module.(*goBinary)
+ binaryModule := module.(*GoBinary)
if binaryModule.properties.PrimaryBuilder {
primaryBuilders = append(primaryBuilders, binaryModule)
}
})
- var primaryBuilder *goBinary
+ var primaryBuilder *GoBinary
switch len(primaryBuilders) {
case 0:
return nil, fmt.Errorf("no primary builder module present")
@@ -40,7 +40,7 @@
pkgFiles := make(map[string][]string)
ctx.VisitDepsDepthFirst(primaryBuilder, func(module blueprint.Module) {
switch m := module.(type) {
- case (*goPackage):
+ case (*GoPackage):
pkgFiles[m.properties.PkgPath] = pathtools.PrefixPaths(m.properties.Srcs,
filepath.Join(ctx.SrcDir(), ctx.ModuleDir(m)))
default:
diff --git a/context.go b/context.go
index ae35dac..75ea2d6 100644
--- a/context.go
+++ b/context.go
@@ -103,15 +103,15 @@
// set during PrepareBuildActions
pkgNames map[*packageContext]string
liveGlobals *liveTracker
- globalVariables map[Variable]ninjaString
+ globalVariables map[Variable]*ninjaString
globalPools map[Pool]*poolDef
globalRules map[Rule]*ruleDef
// set during PrepareBuildActions
- outDir ninjaString // The builddir special Ninja variable
- requiredNinjaMajor int // For the ninja_required_version variable
- requiredNinjaMinor int // For the ninja_required_version variable
- requiredNinjaMicro int // For the ninja_required_version variable
+ outDir *ninjaString // The builddir special Ninja variable
+ requiredNinjaMajor int // For the ninja_required_version variable
+ requiredNinjaMinor int // For the ninja_required_version variable
+ requiredNinjaMicro int // For the ninja_required_version variable
subninjas []string
@@ -137,8 +137,12 @@
// True for any mutators that have already run over all modules
finishedMutators map[*mutatorInfo]bool
- // Can be set by tests to avoid invalidating Module values after mutators.
- skipCloneModulesAfterMutators bool
+ // If true, RunBlueprint will skip cloning modules at the end of RunBlueprint.
+ // Cloning modules intentionally invalidates some Module values after
+ // mutators run (to ensure that mutators don't set such Module values in a way
+ // which ruins the integrity of the graph). However, keeping Module values
+ // changed by mutators may be a desirable outcome (such as for tooling or tests).
+ SkipCloneModulesAfterMutators bool
// String values that can be used to gate build graph traversal
includeTags *IncludeTags
@@ -1983,9 +1987,11 @@
}
deps = append(deps, mutatorDeps...)
- if !c.skipCloneModulesAfterMutators {
+ c.BeginEvent("clone_modules")
+ if !c.SkipCloneModulesAfterMutators {
c.cloneModules()
}
+ defer c.EndEvent("clone_modules")
c.dependenciesReady = true
})
@@ -2734,6 +2740,7 @@
type JSONAction struct {
Inputs []string
Outputs []string
+ Desc string
}
// JSONActionSupplier allows JSON representation of additional actions that are not registered in
@@ -2778,14 +2785,18 @@
}
var actions []JSONAction
for _, bDef := range m.actionDefs.buildDefs {
- actions = append(actions, JSONAction{
+ a := JSONAction{
Inputs: append(
getNinjaStringsWithNilPkgNames(bDef.Inputs),
getNinjaStringsWithNilPkgNames(bDef.Implicits)...),
Outputs: append(
getNinjaStringsWithNilPkgNames(bDef.Outputs),
getNinjaStringsWithNilPkgNames(bDef.ImplicitOutputs)...),
- })
+ }
+ if d, ok := bDef.Variables["description"]; ok {
+ a.Desc = d.Value(nil)
+ }
+ actions = append(actions, a)
}
if j, ok := m.logicModule.(JSONActionSupplier); ok {
@@ -2803,7 +2814,7 @@
// Gets a list of strings from the given list of ninjaStrings by invoking ninjaString.Value with
// nil pkgNames on each of the input ninjaStrings.
-func getNinjaStringsWithNilPkgNames(nStrs []ninjaString) []string {
+func getNinjaStringsWithNilPkgNames(nStrs []*ninjaString) []string {
var strs []string
for _, nstr := range nStrs {
strs = append(strs, nstr.Value(nil))
@@ -3809,7 +3820,7 @@
}
}
-func (c *Context) setOutDir(value ninjaString) {
+func (c *Context) setOutDir(value *ninjaString) {
if c.outDir == nil {
c.outDir = value
}
@@ -3890,7 +3901,7 @@
}
func (c *Context) checkForVariableReferenceCycles(
- variables map[Variable]ninjaString, pkgNames map[*packageContext]string) {
+ variables map[Variable]*ninjaString, pkgNames map[*packageContext]string) {
visited := make(map[Variable]bool) // variables that were already checked
checking := make(map[Variable]bool) // variables actively being checked
@@ -4685,7 +4696,7 @@
// keyForPhonyCandidate gives a unique identifier for a set of deps.
// If any of the deps use a variable, we return an empty string to signal
// that this set of deps is ineligible for extraction.
-func keyForPhonyCandidate(deps []ninjaString) string {
+func keyForPhonyCandidate(deps []*ninjaString) string {
hasher := sha256.New()
for _, d := range deps {
if len(d.Variables()) != 0 {
@@ -4716,7 +4727,7 @@
phonyCount.Add(1)
m.phony = &buildDef{
Rule: Phony,
- Outputs: []ninjaString{simpleNinjaString("dedup-" + key)},
+ Outputs: []*ninjaString{simpleNinjaString("dedup-" + key)},
Inputs: m.first.OrderOnly, //we could also use b.OrderOnly
Optional: true,
}
diff --git a/context_test.go b/context_test.go
index 1a1fb0d..a10f681 100644
--- a/context_test.go
+++ b/context_test.go
@@ -1159,10 +1159,10 @@
}
func TestDeduplicateOrderOnlyDeps(t *testing.T) {
- outputs := func(names ...string) []ninjaString {
- r := make([]ninjaString, len(names))
+ outputs := func(names ...string) []*ninjaString {
+ r := make([]*ninjaString, len(names))
for i, name := range names {
- r[i] = literalNinjaString(name)
+ r[i] = simpleNinjaString(name)
}
return r
}
@@ -1179,7 +1179,7 @@
type testcase struct {
modules []*moduleInfo
expectedPhonys []*buildDef
- conversions map[string][]ninjaString
+ conversions map[string][]*ninjaString
}
testCases := []testcase{{
modules: []*moduleInfo{
@@ -1189,7 +1189,7 @@
expectedPhonys: []*buildDef{
b("dedup-GKw-c0PwFokMUQ6T-TUmEWnZ4_VlQ2Qpgw-vCTT0-OQ", []string{"d"}, nil),
},
- conversions: map[string][]ninjaString{
+ conversions: map[string][]*ninjaString{
"A": outputs("dedup-GKw-c0PwFokMUQ6T-TUmEWnZ4_VlQ2Qpgw-vCTT0-OQ"),
"B": outputs("dedup-GKw-c0PwFokMUQ6T-TUmEWnZ4_VlQ2Qpgw-vCTT0-OQ"),
},
@@ -1205,7 +1205,7 @@
m(b("C", nil, []string{"a"})),
},
expectedPhonys: []*buildDef{b("dedup-ypeBEsobvcr6wjGzmiPcTaeG7_gUfE5yuYB3ha_uSLs", []string{"a"}, nil)},
- conversions: map[string][]ninjaString{
+ conversions: map[string][]*ninjaString{
"A": outputs("dedup-ypeBEsobvcr6wjGzmiPcTaeG7_gUfE5yuYB3ha_uSLs"),
"B": outputs("b"),
"C": outputs("dedup-ypeBEsobvcr6wjGzmiPcTaeG7_gUfE5yuYB3ha_uSLs"),
@@ -1220,7 +1220,7 @@
expectedPhonys: []*buildDef{
b("dedup--44g_C5MPySMYMOb1lLzwTRymLuXe4tNWQO4UFViBgM", []string{"a", "b"}, nil),
b("dedup-9F3lHN7zCZFVHkHogt17VAR5lkigoAdT9E_JZuYVP8E", []string{"a", "c"}, nil)},
- conversions: map[string][]ninjaString{
+ conversions: map[string][]*ninjaString{
"A": outputs("dedup--44g_C5MPySMYMOb1lLzwTRymLuXe4tNWQO4UFViBgM"),
"B": outputs("dedup--44g_C5MPySMYMOb1lLzwTRymLuXe4tNWQO4UFViBgM"),
"C": outputs("dedup-9F3lHN7zCZFVHkHogt17VAR5lkigoAdT9E_JZuYVP8E"),
diff --git a/live_tracker.go b/live_tracker.go
index 9d4f599..e2c4057 100644
--- a/live_tracker.go
+++ b/live_tracker.go
@@ -25,7 +25,7 @@
config interface{} // Used to evaluate variable, rule, and pool values.
ctx *Context // Used to evaluate globs
- variables map[Variable]ninjaString
+ variables map[Variable]*ninjaString
pools map[Pool]*poolDef
rules map[Rule]*ruleDef
}
@@ -34,7 +34,7 @@
return &liveTracker{
ctx: ctx,
config: config,
- variables: make(map[Variable]ninjaString),
+ variables: make(map[Variable]*ninjaString),
pools: make(map[Pool]*poolDef),
rules: make(map[Rule]*ruleDef),
}
@@ -197,13 +197,13 @@
return nil
}
-func (l *liveTracker) addNinjaStringListDeps(list []ninjaString) error {
+func (l *liveTracker) addNinjaStringListDeps(list []*ninjaString) error {
l.Lock()
defer l.Unlock()
return l.innerAddNinjaStringListDeps(list)
}
-func (l *liveTracker) innerAddNinjaStringListDeps(list []ninjaString) error {
+func (l *liveTracker) innerAddNinjaStringListDeps(list []*ninjaString) error {
for _, str := range list {
err := l.innerAddNinjaStringDeps(str)
if err != nil {
@@ -213,13 +213,13 @@
return nil
}
-func (l *liveTracker) addNinjaStringDeps(str ninjaString) error {
+func (l *liveTracker) addNinjaStringDeps(str *ninjaString) error {
l.Lock()
defer l.Unlock()
return l.innerAddNinjaStringDeps(str)
}
-func (l *liveTracker) innerAddNinjaStringDeps(str ninjaString) error {
+func (l *liveTracker) innerAddNinjaStringDeps(str *ninjaString) error {
for _, v := range str.Variables() {
err := l.innerAddVariable(v)
if err != nil {
diff --git a/module_ctx.go b/module_ctx.go
index a43deed..ed03789 100644
--- a/module_ctx.go
+++ b/module_ctx.go
@@ -139,6 +139,14 @@
// Context.RegisterModuleType().
ModuleType() string
+ // ModuleTags returns the tags for this module that should be passed to
+ // ninja for analysis. For example:
+ // [
+ // "module_name": "libfoo",
+ // "module_type": "cc_library",
+ // ]
+ ModuleTags() map[string]string
+
// BlueprintsFile returns the name of the blueprint file that contains the definition of this
// module.
BlueprintsFile() string
@@ -406,6 +414,13 @@
return d.module.typeName
}
+func (d *baseModuleContext) ModuleTags() map[string]string {
+ return map[string]string{
+ "module_name": d.ModuleName(),
+ "module_type": d.ModuleType(),
+ }
+}
+
func (d *baseModuleContext) ContainsProperty(name string) bool {
_, ok := d.module.propertyPos[name]
return ok
@@ -796,7 +811,7 @@
func (m *moduleContext) Build(pctx PackageContext, params BuildParams) {
m.scope.ReparentTo(pctx)
- def, err := parseBuildParams(m.scope, ¶ms)
+ def, err := parseBuildParams(m.scope, ¶ms, m.ModuleTags())
if err != nil {
panic(err)
}
diff --git a/ninja_defs.go b/ninja_defs.go
index 69233c2..482c80c 100644
--- a/ninja_defs.go
+++ b/ninja_defs.go
@@ -131,11 +131,11 @@
// A ruleDef describes a rule definition. It does not include the name of the
// rule.
type ruleDef struct {
- CommandDeps []ninjaString
- CommandOrderOnly []ninjaString
+ CommandDeps []*ninjaString
+ CommandOrderOnly []*ninjaString
Comment string
Pool Pool
- Variables map[string]ninjaString
+ Variables map[string]*ninjaString
}
func parseRuleParams(scope scope, params *RuleParams) (*ruleDef,
@@ -144,7 +144,7 @@
r := &ruleDef{
Comment: params.Comment,
Pool: params.Pool,
- Variables: make(map[string]ninjaString),
+ Variables: make(map[string]*ninjaString),
}
if params.Command == "" {
@@ -264,19 +264,36 @@
Comment string
Rule Rule
RuleDef *ruleDef
- Outputs []ninjaString
- ImplicitOutputs []ninjaString
- Inputs []ninjaString
- Implicits []ninjaString
- OrderOnly []ninjaString
- Validations []ninjaString
- Args map[Variable]ninjaString
- Variables map[string]ninjaString
+ Outputs []*ninjaString
+ ImplicitOutputs []*ninjaString
+ Inputs []*ninjaString
+ Implicits []*ninjaString
+ OrderOnly []*ninjaString
+ Validations []*ninjaString
+ Args map[Variable]*ninjaString
+ Variables map[string]*ninjaString
Optional bool
}
-func parseBuildParams(scope scope, params *BuildParams) (*buildDef,
- error) {
+func formatTags(tags map[string]string, rule Rule) string {
+ // Maps in golang do not have a guaranteed iteration order, nor is there an
+ // ordered map type in the stdlib, but we need to deterministically generate
+ // the ninja file.
+ keys := make([]string, 0, len(tags))
+ for k := range tags {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ pairs := make([]string, 0, len(keys))
+ for _, k := range keys {
+ pairs = append(pairs, k+"="+tags[k])
+ }
+ pairs = append(pairs, "rule_name="+rule.name())
+ return strings.Join(pairs, ";")
+}
+
+func parseBuildParams(scope scope, params *BuildParams,
+ tags map[string]string) (*buildDef, error) {
comment := params.Comment
rule := params.Rule
@@ -286,9 +303,9 @@
Rule: rule,
}
- setVariable := func(name string, value ninjaString) {
+ setVariable := func(name string, value *ninjaString) {
if b.Variables == nil {
- b.Variables = make(map[string]ninjaString)
+ b.Variables = make(map[string]*ninjaString)
}
b.Variables[name] = value
}
@@ -360,10 +377,14 @@
simpleNinjaString(strings.Join(params.SymlinkOutputs, " ")))
}
+ if len(tags) > 0 {
+ setVariable("tags", simpleNinjaString(formatTags(tags, rule)))
+ }
+
argNameScope := rule.scope()
if len(params.Args) > 0 {
- b.Args = make(map[Variable]ninjaString)
+ b.Args = make(map[Variable]*ninjaString)
for name, value := range params.Args {
if !rule.isArg(name) {
return nil, fmt.Errorf("unknown argument %q", name)
@@ -444,7 +465,7 @@
return nw.BlankLine()
}
-func writeVariables(nw *ninjaWriter, variables map[string]ninjaString,
+func writeVariables(nw *ninjaWriter, variables map[string]*ninjaString,
pkgNames map[*packageContext]string) error {
var keys []string
for k := range variables {
diff --git a/ninja_strings.go b/ninja_strings.go
index 9e83a4d..0e565e2 100644
--- a/ninja_strings.go
+++ b/ninja_strings.go
@@ -35,19 +35,28 @@
":", "$:")
)
-type ninjaString interface {
- Value(pkgNames map[*packageContext]string) string
- ValueWithEscaper(w io.StringWriter, pkgNames map[*packageContext]string, escaper *strings.Replacer)
- Eval(variables map[Variable]ninjaString) (string, error)
- Variables() []Variable
+// ninjaString contains the parsed result of a string that can contain references to variables (e.g. $cflags) that will
+// be propagated to the build.ninja file. For literal strings with no variable references, the variables field will be
+// nil. For strings with variable references str contains the original, unparsed string, and variables contains a
+// pointer to a list of references, each with a span of bytes they should replace and a Variable interface.
+type ninjaString struct {
+ str string
+ variables *[]variableReference
}
-type varNinjaString struct {
- strings []string
- variables []Variable
-}
+// variableReference contains information about a single reference to a variable (e.g. $cflags) inside a parsed
+// ninjaString. start and end are int32 to reduce memory usage. A nil variable is a special case of an inserted '$'
+// at the beginning of the string to handle leading whitespace that must not be stripped by ninja.
+type variableReference struct {
+ // start is the offset of the '$' character from the beginning of the unparsed string.
+ start int32
-type literalNinjaString string
+ // end is the offset of the character _after_ the final character of the variable name (or '}' if using the
+ //'${}' syntax)
+ end int32
+
+ variable Variable
+}
type scope interface {
LookupVariable(name string) (Variable, error)
@@ -55,36 +64,24 @@
IsPoolVisible(pool Pool) bool
}
-func simpleNinjaString(str string) ninjaString {
- return literalNinjaString(str)
+func simpleNinjaString(str string) *ninjaString {
+ return &ninjaString{str: str}
}
type parseState struct {
- scope scope
- str string
- pendingStr string
- stringStart int
- varStart int
- result *varNinjaString
+ scope scope
+ str string
+ varStart int
+ varNameStart int
+ result *ninjaString
}
-func (ps *parseState) pushVariable(v Variable) {
- if len(ps.result.variables) == len(ps.result.strings) {
- // Last push was a variable, we need a blank string separator
- ps.result.strings = append(ps.result.strings, "")
+func (ps *parseState) pushVariable(start, end int, v Variable) {
+ if ps.result.variables == nil {
+ ps.result.variables = &[]variableReference{{start: int32(start), end: int32(end), variable: v}}
+ } else {
+ *ps.result.variables = append(*ps.result.variables, variableReference{start: int32(start), end: int32(end), variable: v})
}
- if ps.pendingStr != "" {
- panic("oops, pushed variable with pending string")
- }
- ps.result.variables = append(ps.result.variables, v)
-}
-
-func (ps *parseState) pushString(s string) {
- if len(ps.result.strings) != len(ps.result.variables) {
- panic("oops, pushed string after string")
- }
- ps.result.strings = append(ps.result.strings, ps.pendingStr+s)
- ps.pendingStr = ""
}
type stateFunc func(*parseState, int, rune) (stateFunc, error)
@@ -92,18 +89,19 @@
// parseNinjaString parses an unescaped ninja string (i.e. all $<something>
// occurrences are expected to be variables or $$) and returns a list of the
// variable names that the string references.
-func parseNinjaString(scope scope, str string) (ninjaString, error) {
- // naively pre-allocate slices by counting $ signs
+func parseNinjaString(scope scope, str string) (*ninjaString, error) {
+ // naively pre-allocate slice by counting $ signs
n := strings.Count(str, "$")
if n == 0 {
- if strings.HasPrefix(str, " ") {
+ if len(str) > 0 && str[0] == ' ' {
str = "$" + str
}
- return literalNinjaString(str), nil
+ return simpleNinjaString(str), nil
}
- result := &varNinjaString{
- strings: make([]string, 0, n+1),
- variables: make([]Variable, 0, n),
+ variableReferences := make([]variableReference, 0, n)
+ result := &ninjaString{
+ str: str,
+ variables: &variableReferences,
}
parseState := &parseState{
@@ -127,12 +125,18 @@
return nil, err
}
+ // All the '$' characters counted initially could have been "$$" escapes, leaving no
+ // variable references. Deallocate the variables slice if so.
+ if len(*result.variables) == 0 {
+ result.variables = nil
+ }
+
return result, nil
}
func parseFirstRuneState(state *parseState, i int, r rune) (stateFunc, error) {
if r == ' ' {
- state.pendingStr += "$"
+ state.pushVariable(0, 1, nil)
}
return parseStringState(state, i, r)
}
@@ -140,11 +144,10 @@
func parseStringState(state *parseState, i int, r rune) (stateFunc, error) {
switch {
case r == '$':
- state.varStart = i + 1
+ state.varStart = i
return parseDollarStartState, nil
case r == eof:
- state.pushString(state.str[state.stringStart:i])
return nil, nil
default:
@@ -156,21 +159,17 @@
switch {
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
r >= '0' && r <= '9', r == '_', r == '-':
- // The beginning of a of the variable name. Output the string and
- // keep going.
- state.pushString(state.str[state.stringStart : i-1])
+ // The beginning of a of the variable name.
+ state.varNameStart = i
return parseDollarState, nil
case r == '$':
- // Just a "$$". Go back to parseStringState without changing
- // state.stringStart.
+ // Just a "$$". Go back to parseStringState.
return parseStringState, nil
case r == '{':
- // This is a bracketted variable name (e.g. "${blah.blah}"). Output
- // the string and keep going.
- state.pushString(state.str[state.stringStart : i-1])
- state.varStart = i + 1
+ // This is a bracketted variable name (e.g. "${blah.blah}").
+ state.varNameStart = i + 1
return parseBracketsState, nil
case r == eof:
@@ -190,45 +189,26 @@
r >= '0' && r <= '9', r == '_', r == '-':
// A part of the variable name. Keep going.
return parseDollarState, nil
+ }
+ // The variable name has ended, output what we have.
+ v, err := state.scope.LookupVariable(state.str[state.varNameStart:i])
+ if err != nil {
+ return nil, err
+ }
+
+ state.pushVariable(state.varStart, i, v)
+
+ switch {
case r == '$':
- // A dollar after the variable name (e.g. "$blah$"). Output the
- // variable we have and start a new one.
- v, err := state.scope.LookupVariable(state.str[state.varStart:i])
- if err != nil {
- return nil, err
- }
-
- state.pushVariable(v)
- state.varStart = i + 1
- state.stringStart = i
-
+ // A dollar after the variable name (e.g. "$blah$"). Start a new one.
+ state.varStart = i
return parseDollarStartState, nil
case r == eof:
- // This is the end of the variable name.
- v, err := state.scope.LookupVariable(state.str[state.varStart:i])
- if err != nil {
- return nil, err
- }
-
- state.pushVariable(v)
-
- // We always end with a string, even if it's an empty one.
- state.pushString("")
-
return nil, nil
default:
- // We've just gone past the end of the variable name, so record what
- // we have.
- v, err := state.scope.LookupVariable(state.str[state.varStart:i])
- if err != nil {
- return nil, err
- }
-
- state.pushVariable(v)
- state.stringStart = i
return parseStringState, nil
}
}
@@ -241,20 +221,19 @@
return parseBracketsState, nil
case r == '}':
- if state.varStart == i {
+ if state.varNameStart == i {
// The brackets were immediately closed. That's no good.
return nil, fmt.Errorf("empty variable name at byte offset %d",
i)
}
// This is the end of the variable name.
- v, err := state.scope.LookupVariable(state.str[state.varStart:i])
+ v, err := state.scope.LookupVariable(state.str[state.varNameStart:i])
if err != nil {
return nil, err
}
- state.pushVariable(v)
- state.stringStart = i + 1
+ state.pushVariable(state.varStart, i+1, v)
return parseStringState, nil
case r == eof:
@@ -267,13 +246,13 @@
}
}
-func parseNinjaStrings(scope scope, strs []string) ([]ninjaString,
+func parseNinjaStrings(scope scope, strs []string) ([]*ninjaString,
error) {
if len(strs) == 0 {
return nil, nil
}
- result := make([]ninjaString, len(strs))
+ result := make([]*ninjaString, len(strs))
for i, str := range strs {
ninjaStr, err := parseNinjaString(scope, str)
if err != nil {
@@ -284,62 +263,78 @@
return result, nil
}
-func (n varNinjaString) Value(pkgNames map[*packageContext]string) string {
- if len(n.strings) == 1 {
- return defaultEscaper.Replace(n.strings[0])
+func (n *ninjaString) Value(pkgNames map[*packageContext]string) string {
+ if n.variables == nil || len(*n.variables) == 0 {
+ return defaultEscaper.Replace(n.str)
}
str := &strings.Builder{}
n.ValueWithEscaper(str, pkgNames, defaultEscaper)
return str.String()
}
-func (n varNinjaString) ValueWithEscaper(w io.StringWriter, pkgNames map[*packageContext]string,
+func (n *ninjaString) ValueWithEscaper(w io.StringWriter, pkgNames map[*packageContext]string,
escaper *strings.Replacer) {
- w.WriteString(escaper.Replace(n.strings[0]))
- for i, v := range n.variables {
- w.WriteString("${")
- w.WriteString(v.fullName(pkgNames))
- w.WriteString("}")
- w.WriteString(escaper.Replace(n.strings[i+1]))
+ if n.variables == nil || len(*n.variables) == 0 {
+ w.WriteString(escaper.Replace(n.str))
+ return
}
-}
-func (n varNinjaString) Eval(variables map[Variable]ninjaString) (string, error) {
- str := n.strings[0]
- for i, v := range n.variables {
- variable, ok := variables[v]
- if !ok {
- return "", fmt.Errorf("no such global variable: %s", v)
+ i := 0
+ for _, v := range *n.variables {
+ w.WriteString(escaper.Replace(n.str[i:v.start]))
+ if v.variable == nil {
+ w.WriteString("$ ")
+ } else {
+ w.WriteString("${")
+ w.WriteString(v.variable.fullName(pkgNames))
+ w.WriteString("}")
}
- value, err := variable.Eval(variables)
- if err != nil {
- return "", err
- }
- str += value + n.strings[i+1]
+ i = int(v.end)
}
- return str, nil
+ w.WriteString(escaper.Replace(n.str[i:len(n.str)]))
}
-func (n varNinjaString) Variables() []Variable {
- return n.variables
+func (n *ninjaString) Eval(variables map[Variable]*ninjaString) (string, error) {
+ if n.variables == nil || len(*n.variables) == 0 {
+ return n.str, nil
+ }
+
+ w := &strings.Builder{}
+ i := 0
+ for _, v := range *n.variables {
+ w.WriteString(n.str[i:v.start])
+ if v.variable == nil {
+ w.WriteString(" ")
+ } else {
+ variable, ok := variables[v.variable]
+ if !ok {
+ return "", fmt.Errorf("no such global variable: %s", v.variable)
+ }
+ value, err := variable.Eval(variables)
+ if err != nil {
+ return "", err
+ }
+ w.WriteString(value)
+ }
+ i = int(v.end)
+ }
+ w.WriteString(n.str[i:len(n.str)])
+ return w.String(), nil
}
-func (l literalNinjaString) Value(_ map[*packageContext]string) string {
- return defaultEscaper.Replace(string(l))
-}
+func (n *ninjaString) Variables() []Variable {
+ if n.variables == nil || len(*n.variables) == 0 {
+ return nil
+ }
-func (l literalNinjaString) ValueWithEscaper(w io.StringWriter, _ map[*packageContext]string,
- escaper *strings.Replacer) {
- w.WriteString(escaper.Replace(string(l)))
-}
-
-func (l literalNinjaString) Eval(variables map[Variable]ninjaString) (string, error) {
- return string(l), nil
-}
-
-func (l literalNinjaString) Variables() []Variable {
- return nil
+ variables := make([]Variable, 0, len(*n.variables))
+ for _, v := range *n.variables {
+ if v.variable != nil {
+ variables = append(variables, v.variable)
+ }
+ }
+ return variables
}
func validateNinjaName(name string) error {
@@ -399,6 +394,10 @@
return fmt.Errorf("%q contains a '.' character", argName)
}
+ if argName == "tags" {
+ return fmt.Errorf("\"tags\" is a reserved argument name")
+ }
+
for _, builtin := range builtinRuleArgs {
if argName == builtin {
return fmt.Errorf("%q conflicts with Ninja built-in", argName)
diff --git a/ninja_strings_test.go b/ninja_strings_test.go
index f400074..2fa4937 100644
--- a/ninja_strings_test.go
+++ b/ninja_strings_test.go
@@ -21,143 +21,176 @@
"testing"
)
-var ninjaParseTestCases = []struct {
- input string
- vars []string
- strs []string
- literal bool
- err string
-}{
- {
- input: "abc def $ghi jkl",
- vars: []string{"ghi"},
- strs: []string{"abc def ", " jkl"},
- },
- {
- input: "abc def $ghi$jkl",
- vars: []string{"ghi", "jkl"},
- strs: []string{"abc def ", "", ""},
- },
- {
- input: "foo $012_-345xyz_! bar",
- vars: []string{"012_-345xyz_"},
- strs: []string{"foo ", "! bar"},
- },
- {
- input: "foo ${012_-345xyz_} bar",
- vars: []string{"012_-345xyz_"},
- strs: []string{"foo ", " bar"},
- },
- {
- input: "foo ${012_-345xyz_} bar",
- vars: []string{"012_-345xyz_"},
- strs: []string{"foo ", " bar"},
- },
- {
- input: "foo $$ bar",
- vars: nil,
- strs: []string{"foo $$ bar"},
- // this is technically a literal, but not recognized as such due to the $$
- },
- {
- input: "$foo${bar}",
- vars: []string{"foo", "bar"},
- strs: []string{"", "", ""},
- },
- {
- input: "$foo$$",
- vars: []string{"foo"},
- strs: []string{"", "$$"},
- },
- {
- input: "foo bar",
- vars: nil,
- strs: []string{"foo bar"},
- literal: true,
- },
- {
- input: " foo ",
- vars: nil,
- strs: []string{"$ foo "},
- literal: true,
- },
- {
- input: " $foo ",
- vars: []string{"foo"},
- strs: []string{"$ ", " "},
- }, {
- input: "foo $ bar",
- err: `error parsing ninja string "foo $ bar": invalid character after '$' at byte offset 5`,
- },
- {
- input: "foo $",
- err: "unexpected end of string after '$'",
- },
- {
- input: "foo ${} bar",
- err: `error parsing ninja string "foo ${} bar": empty variable name at byte offset 6`,
- },
- {
- input: "foo ${abc!} bar",
- err: `error parsing ninja string "foo ${abc!} bar": invalid character in variable name at byte offset 9`,
- },
- {
- input: "foo ${abc",
- err: "unexpected end of string in variable name",
- },
+type testVariableRef struct {
+ start, end int
+ name string
}
func TestParseNinjaString(t *testing.T) {
- for _, testCase := range ninjaParseTestCases {
- scope := newLocalScope(nil, "namespace")
- expectedVars := []Variable{}
- for _, varName := range testCase.vars {
- v, err := scope.LookupVariable(varName)
- if err != nil {
- v, err = scope.AddLocalVariable(varName, "")
+ testCases := []struct {
+ input string
+ vars []string
+ value string
+ eval string
+ err string
+ }{
+ {
+ input: "abc def $ghi jkl",
+ vars: []string{"ghi"},
+ value: "abc def ${namespace.ghi} jkl",
+ eval: "abc def GHI jkl",
+ },
+ {
+ input: "abc def $ghi$jkl",
+ vars: []string{"ghi", "jkl"},
+ value: "abc def ${namespace.ghi}${namespace.jkl}",
+ eval: "abc def GHIJKL",
+ },
+ {
+ input: "foo $012_-345xyz_! bar",
+ vars: []string{"012_-345xyz_"},
+ value: "foo ${namespace.012_-345xyz_}! bar",
+ eval: "foo 012_-345XYZ_! bar",
+ },
+ {
+ input: "foo ${012_-345xyz_} bar",
+ vars: []string{"012_-345xyz_"},
+ value: "foo ${namespace.012_-345xyz_} bar",
+ eval: "foo 012_-345XYZ_ bar",
+ },
+ {
+ input: "foo ${012_-345xyz_} bar",
+ vars: []string{"012_-345xyz_"},
+ value: "foo ${namespace.012_-345xyz_} bar",
+ eval: "foo 012_-345XYZ_ bar",
+ },
+ {
+ input: "foo $$ bar",
+ vars: nil,
+ value: "foo $$ bar",
+ eval: "foo $$ bar",
+ },
+ {
+ input: "$foo${bar}",
+ vars: []string{"foo", "bar"},
+ value: "${namespace.foo}${namespace.bar}",
+ eval: "FOOBAR",
+ },
+ {
+ input: "$foo$$",
+ vars: []string{"foo"},
+ value: "${namespace.foo}$$",
+ eval: "FOO$$",
+ },
+ {
+ input: "foo bar",
+ vars: nil,
+ value: "foo bar",
+ eval: "foo bar",
+ },
+ {
+ input: " foo ",
+ vars: nil,
+ value: "$ foo ",
+ eval: "$ foo ",
+ },
+ {
+ input: "\tfoo ",
+ vars: nil,
+ value: "\tfoo ",
+ eval: "\tfoo ",
+ },
+ {
+ input: "\nfoo ",
+ vars: nil,
+ value: "$\nfoo ",
+ eval: "\nfoo ",
+ },
+ {
+ input: " $foo ",
+ vars: []string{"foo"},
+ value: "$ ${namespace.foo} ",
+ eval: " FOO ",
+ },
+ {
+ input: "\t$foo ",
+ vars: []string{"foo"},
+ value: "\t${namespace.foo} ",
+ eval: "\tFOO ",
+ },
+ {
+ input: "\n$foo ",
+ vars: []string{"foo"},
+ value: "$\n${namespace.foo} ",
+ eval: "\nFOO ",
+ },
+ {
+ input: "foo $ bar",
+ err: `error parsing ninja string "foo $ bar": invalid character after '$' at byte offset 5`,
+ },
+ {
+ input: "foo $",
+ err: "unexpected end of string after '$'",
+ },
+ {
+ input: "foo ${} bar",
+ err: `error parsing ninja string "foo ${} bar": empty variable name at byte offset 6`,
+ },
+ {
+ input: "foo ${abc!} bar",
+ err: `error parsing ninja string "foo ${abc!} bar": invalid character in variable name at byte offset 9`,
+ },
+ {
+ input: "foo ${abc",
+ err: "unexpected end of string in variable name",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.input, func(t *testing.T) {
+ scope := newLocalScope(nil, "namespace.")
+ variablesMap := map[Variable]*ninjaString{}
+ for _, varName := range testCase.vars {
+ _, err := scope.LookupVariable(varName)
if err != nil {
- t.Fatalf("error creating scope: %s", err)
+ v, err := scope.AddLocalVariable(varName, strings.ToUpper(varName))
+ if err != nil {
+ t.Fatalf("error creating scope: %s", err)
+ }
+ variablesMap[v] = simpleNinjaString(strings.ToUpper(varName))
}
}
- expectedVars = append(expectedVars, v)
- }
- var expected ninjaString
- if len(testCase.strs) > 0 {
- if testCase.literal {
- expected = literalNinjaString(testCase.strs[0])
- } else {
- expected = &varNinjaString{
- strings: testCase.strs,
- variables: expectedVars,
+ output, err := parseNinjaString(scope, testCase.input)
+ if err == nil {
+ if g, w := output.Value(nil), testCase.value; g != w {
+ t.Errorf("incorrect Value output, want %q, got %q", w, g)
+ }
+
+ eval, err := output.Eval(variablesMap)
+ if err != nil {
+ t.Errorf("unexpected error in Eval: %s", err)
+ }
+ if g, w := eval, testCase.eval; g != w {
+ t.Errorf("incorrect Eval output, want %q, got %q", w, g)
}
}
- }
-
- output, err := parseNinjaString(scope, testCase.input)
- if err == nil {
- if !reflect.DeepEqual(output, expected) {
- t.Errorf("incorrect ninja string:")
+ var errStr string
+ if err != nil {
+ errStr = err.Error()
+ }
+ if err != nil && err.Error() != testCase.err {
+ t.Errorf("unexpected error:")
t.Errorf(" input: %q", testCase.input)
- t.Errorf(" expected: %#v", expected)
- t.Errorf(" got: %#v", output)
+ t.Errorf(" expected: %q", testCase.err)
+ t.Errorf(" got: %q", errStr)
}
- }
- var errStr string
- if err != nil {
- errStr = err.Error()
- }
- if err != nil && err.Error() != testCase.err {
- t.Errorf("unexpected error:")
- t.Errorf(" input: %q", testCase.input)
- t.Errorf(" expected: %q", testCase.err)
- t.Errorf(" got: %q", errStr)
- }
+ })
}
}
func TestParseNinjaStringWithImportedVar(t *testing.T) {
- ImpVar := &staticVariable{name_: "ImpVar"}
+ ImpVar := &staticVariable{name_: "ImpVar", fullName_: "g.impPkg.ImpVar"}
impScope := newScope(nil)
impScope.AddVariable(ImpVar)
scope := newScope(nil)
@@ -169,13 +202,59 @@
t.Fatalf("unexpected error: %s", err)
}
- expect := []Variable{ImpVar}
- if !reflect.DeepEqual(output.(*varNinjaString).variables, expect) {
+ expect := []variableReference{{8, 24, ImpVar}}
+ if !reflect.DeepEqual(*output.variables, expect) {
t.Errorf("incorrect output:")
t.Errorf(" input: %q", input)
t.Errorf(" expected: %#v", expect)
- t.Errorf(" got: %#v", output)
+ t.Errorf(" got: %#v", *output.variables)
}
+
+ if g, w := output.Value(nil), "abc def ${g.impPkg.ImpVar} ghi"; g != w {
+ t.Errorf("incorrect Value output, want %q got %q", w, g)
+ }
+}
+
+func Benchmark_parseNinjaString(b *testing.B) {
+ b.Run("constant", func(b *testing.B) {
+ for _, l := range []int{1, 10, 100, 1000} {
+ b.Run(strconv.Itoa(l), func(b *testing.B) {
+ b.ReportAllocs()
+ for n := 0; n < b.N; n++ {
+ _ = simpleNinjaString(strings.Repeat("a", l))
+ }
+ })
+ }
+ })
+ b.Run("variable", func(b *testing.B) {
+ for _, l := range []int{1, 10, 100, 1000} {
+ scope := newLocalScope(nil, "")
+ scope.AddLocalVariable("a", strings.Repeat("b", l/3))
+ b.Run(strconv.Itoa(l), func(b *testing.B) {
+ b.ReportAllocs()
+ for n := 0; n < b.N; n++ {
+ _, _ = parseNinjaString(scope, strings.Repeat("a", l/3)+"${a}"+strings.Repeat("a", l/3))
+ }
+ })
+ }
+ })
+ b.Run("variables", func(b *testing.B) {
+ for _, l := range []int{1, 2, 3, 4, 5, 10, 100, 1000} {
+ scope := newLocalScope(nil, "")
+ str := strings.Repeat("a", 10)
+ for i := 0; i < l; i++ {
+ scope.AddLocalVariable("a"+strconv.Itoa(i), strings.Repeat("b", 10))
+ str += "${a" + strconv.Itoa(i) + "}"
+ }
+ b.Run(strconv.Itoa(l), func(b *testing.B) {
+ b.ReportAllocs()
+ for n := 0; n < b.N; n++ {
+ _, _ = parseNinjaString(scope, str)
+ }
+ })
+ }
+ })
+
}
func BenchmarkNinjaString_Value(b *testing.B) {
@@ -183,6 +262,7 @@
for _, l := range []int{1, 10, 100, 1000} {
ns := simpleNinjaString(strings.Repeat("a", l))
b.Run(strconv.Itoa(l), func(b *testing.B) {
+ b.ReportAllocs()
for n := 0; n < b.N; n++ {
ns.Value(nil)
}
@@ -195,6 +275,7 @@
scope.AddLocalVariable("a", strings.Repeat("b", l/3))
ns, _ := parseNinjaString(scope, strings.Repeat("a", l/3)+"${a}"+strings.Repeat("a", l/3))
b.Run(strconv.Itoa(l), func(b *testing.B) {
+ b.ReportAllocs()
for n := 0; n < b.N; n++ {
ns.Value(nil)
}
@@ -211,6 +292,7 @@
}
ns, _ := parseNinjaString(scope, str)
b.Run(strconv.Itoa(l), func(b *testing.B) {
+ b.ReportAllocs()
for n := 0; n < b.N; n++ {
ns.Value(nil)
}
diff --git a/ninja_writer.go b/ninja_writer.go
index f9951b4..fbbfb9e 100644
--- a/ninja_writer.go
+++ b/ninja_writer.go
@@ -114,7 +114,7 @@
}
func (n *ninjaWriter) Build(comment string, rule string, outputs, implicitOuts,
- explicitDeps, implicitDeps, orderOnlyDeps, validations []ninjaString,
+ explicitDeps, implicitDeps, orderOnlyDeps, validations []*ninjaString,
pkgNames map[*packageContext]string) error {
n.justDidBlankLine = false
@@ -235,7 +235,7 @@
return nil
}
-func (n *ninjaWriter) Default(pkgNames map[*packageContext]string, targets ...ninjaString) error {
+func (n *ninjaWriter) Default(pkgNames map[*packageContext]string, targets ...*ninjaString) error {
n.justDidBlankLine = false
const lineWrapLen = len(" $")
diff --git a/ninja_writer_test.go b/ninja_writer_test.go
index 82eeee5..c70d5df 100644
--- a/ninja_writer_test.go
+++ b/ninja_writer_test.go
@@ -139,7 +139,7 @@
}
}
-func testNinjaStrings(s ...string) []ninjaString {
+func testNinjaStrings(s ...string) []*ninjaString {
ret, _ := parseNinjaStrings(nil, s)
return ret
}
diff --git a/package_ctx.go b/package_ctx.go
index f8a7cc4..4b48337 100644
--- a/package_ctx.go
+++ b/package_ctx.go
@@ -304,7 +304,7 @@
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_)
if err != nil {
err = fmt.Errorf("error parsing variable %s value: %s", v, err)
@@ -440,7 +440,7 @@
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)
if err != nil {
return nil, err
@@ -504,7 +504,7 @@
// 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
}
diff --git a/provider_test.go b/provider_test.go
index 942dd31..4038eae 100644
--- a/provider_test.go
+++ b/provider_test.go
@@ -283,7 +283,7 @@
ctx.RegisterTopDownMutator("parent", invalidProviderUsageParentMutator)
// Don't invalidate the parent pointer and before GenerateBuildActions.
- ctx.skipCloneModulesAfterMutators = true
+ ctx.SkipCloneModulesAfterMutators = true
var parentBP, moduleUnderTestBP, childBP string
diff --git a/scope.go b/scope.go
index 9585559..0ca3464 100644
--- a/scope.go
+++ b/scope.go
@@ -29,7 +29,7 @@
name() string // "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
}
@@ -354,7 +354,7 @@
type localVariable struct {
fullName_ string
name_ string
- value_ ninjaString
+ value_ *ninjaString
}
func (l *localVariable) packageContext() *packageContext {
@@ -373,7 +373,7 @@
// 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
}
diff --git a/singleton_ctx.go b/singleton_ctx.go
index d579b8e..95c29c4 100644
--- a/singleton_ctx.go
+++ b/singleton_ctx.go
@@ -265,7 +265,10 @@
func (s *singletonContext) Build(pctx PackageContext, params BuildParams) {
s.scope.ReparentTo(pctx)
- def, err := parseBuildParams(s.scope, ¶ms)
+ def, err := parseBuildParams(s.scope, ¶ms, map[string]string{
+ "module_name": s.name,
+ "module_type": "singleton",
+ })
if err != nil {
panic(err)
}