Snap for 10453563 from bbbced136fa7dc2b3bc0df7c3b08f6206cb72356 to mainline-os-statsd-release
Change-Id: I8d212fc41dde70e6709081da866144c86b0f643f
diff --git a/Android.bp b/Android.bp
index c84d04a..20fa495 100644
--- a/Android.bp
+++ b/Android.bp
@@ -39,6 +39,7 @@
pkgPath: "github.com/google/blueprint",
srcs: [
"context.go",
+ "levenshtein.go",
"glob.go",
"live_tracker.go",
"mangle.go",
@@ -54,6 +55,7 @@
],
testSrcs: [
"context_test.go",
+ "levenshtein_test.go",
"glob_test.go",
"module_ctx_test.go",
"ninja_strings_test.go",
@@ -184,8 +186,12 @@
blueprint_go_binary {
name: "bpmodify",
- deps: ["blueprint-parser"],
+ deps: [
+ "blueprint-parser",
+ "blueprint-proptools",
+ ],
srcs: ["bpmodify/bpmodify.go"],
+ testSrcs: ["bpmodify/bpmodify_test.go"],
}
blueprint_go_binary {
diff --git a/README.md b/README.md
index 961bc64..22525bf 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,6 @@
Blueprint Build System
======================
-Blueprint is being archived on 2021 May 3.
+Blueprint is part of Soong.
-On 2021 May 3, we will be archiving the Blueprint project. This means it will
-not be possible to file new issues or open new pull requests for this GitHub
-project. As the project is being archived, patches -- including security
-patches -- will not be applied after May 3. The source tree will remain
-available, but changes to Blueprint in AOSP will not be merged here and
-Blueprint's source tree in AOSP will eventually stop being usable outside of
-Android.
-
-Whereas there are no meta-build systems one can use as a drop-in replacement for
-Blueprint, there are a number of build systems that can be used:
-
-* [Bazel](https://bazel.build), Google's multi-language build tool to build and
- test software of any size, quickly and reliably
-* [Soong](https://source.android.com/setup/build), for building the Android
- operating system itself
-* [CMake](https://cmake.org), an open-source, cross-platform family of tools
- designed to build, test and package software
-* [Buck](https://buck.build), a fast build system that encourages the creation
- of small, reusable modules over a variety of platforms and languages
-* The venerable [GNU Make](https://www.gnu.org/software/make/)
+For more information, see `build/soong/README.md` .
diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go
index ceeee19..bf12cd3 100644
--- a/bootstrap/bootstrap.go
+++ b/bootstrap/bootstrap.go
@@ -16,7 +16,6 @@
import (
"fmt"
- "go/build"
"path/filepath"
"runtime"
"strings"
@@ -33,21 +32,15 @@
pluginGenSrcCmd = pctx.StaticVariable("pluginGenSrcCmd", filepath.Join("$ToolDir", "loadplugins"))
parallelCompile = pctx.StaticVariable("parallelCompile", func() string {
- // Parallel compilation is only supported on >= go1.9
- for _, r := range build.Default.ReleaseTags {
- if r == "go1.9" {
- numCpu := runtime.NumCPU()
- // This will cause us to recompile all go programs if the
- // number of cpus changes. We don't get a lot of benefit from
- // higher values, so cap this to make it cheaper to move trees
- // between machines.
- if numCpu > 8 {
- numCpu = 8
- }
- return fmt.Sprintf("-c %d", numCpu)
- }
+ numCpu := runtime.NumCPU()
+ // This will cause us to recompile all go programs if the
+ // number of cpus changes. We don't get a lot of benefit from
+ // higher values, so cap this to make it cheaper to move trees
+ // between machines.
+ if numCpu > 8 {
+ numCpu = 8
}
- return ""
+ return fmt.Sprintf("-c %d", numCpu)
}())
compile = pctx.StaticRule("compile",
@@ -149,7 +142,7 @@
},
"depfile")
- _ = pctx.VariableFunc("ToolDir", func(config interface{}) (string, error) {
+ _ = pctx.VariableFunc("ToolDir", func(ctx blueprint.VariableFuncContext, config interface{}) (string, error) {
return config.(BootstrapConfig).HostToolDir(), nil
})
)
@@ -693,9 +686,10 @@
// Build the main build.ninja
ctx.Build(pctx, blueprint.BuildParams{
- Rule: generateBuildNinja,
- Outputs: i.Outputs,
- Inputs: i.Inputs,
+ Rule: generateBuildNinja,
+ Outputs: i.Outputs,
+ Inputs: i.Inputs,
+ OrderOnly: i.OrderOnlyInputs,
Args: map[string]string{
"builder": primaryBuilderFile,
"env": envAssignments,
diff --git a/bootstrap/command.go b/bootstrap/command.go
index 8c045b4..3c41cce 100644
--- a/bootstrap/command.go
+++ b/bootstrap/command.go
@@ -18,7 +18,6 @@
"bufio"
"fmt"
"io"
- "io/ioutil"
"os"
"path/filepath"
"runtime"
@@ -41,9 +40,10 @@
TraceFile string
}
-// Returns the list of dependencies the emitted Ninja files has. These can be
-// written to the .d file for the output so that it is correctly rebuilt when
-// needed in case Blueprint is itself invoked from Ninja
+// 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 {
runtime.GOMAXPROCS(runtime.NumCPU())
@@ -71,53 +71,58 @@
defer trace.Stop()
}
- srcDir := "."
-
- ninjaDeps := make([]string, 0)
-
- if args.ModuleListFile != "" {
- ctx.SetModuleListFile(args.ModuleListFile)
- ninjaDeps = append(ninjaDeps, args.ModuleListFile)
- } else {
+ if args.ModuleListFile == "" {
fatalf("-l <moduleListFile> is required and must be nonempty")
}
+ ctx.SetModuleListFile(args.ModuleListFile)
+
+ var ninjaDeps []string
+ ninjaDeps = append(ninjaDeps, args.ModuleListFile)
+
ctx.BeginEvent("list_modules")
- filesToParse, err := ctx.ListModulePaths(srcDir)
- ctx.EndEvent("list_modules")
- if err != nil {
+ var filesToParse []string
+ if f, err := ctx.ListModulePaths("."); err != nil {
fatalf("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())
+ blueprint.RegisterPackageIncludesModuleType(ctx)
ctx.BeginEvent("parse_bp")
- blueprintFiles, errs := ctx.ParseFileList(".", filesToParse, config)
- if len(errs) > 0 {
+ if blueprintFiles, errs := ctx.ParseFileList(".", filesToParse, config); len(errs) > 0 {
fatalErrors(errs)
+ } else {
+ ctx.EndEvent("parse_bp")
+ ninjaDeps = append(ninjaDeps, blueprintFiles...)
}
- ctx.EndEvent("parse_bp")
- // Add extra ninja file dependencies
- ninjaDeps = append(ninjaDeps, blueprintFiles...)
-
- extraDeps, errs := ctx.ResolveDependencies(config)
- if len(errs) > 0 {
+ if resolvedDeps, errs := ctx.ResolveDependencies(config); len(errs) > 0 {
fatalErrors(errs)
+ } else {
+ ninjaDeps = append(ninjaDeps, resolvedDeps...)
}
- ninjaDeps = append(ninjaDeps, extraDeps...)
if stopBefore == StopBeforePrepareBuildActions {
return ninjaDeps
}
- extraDeps, errs = ctx.PrepareBuildActions(config)
- if len(errs) > 0 {
- fatalErrors(errs)
+ if ctx.BeforePrepareBuildActionsHook != nil {
+ if err := ctx.BeforePrepareBuildActionsHook(); err != nil {
+ fatalErrors([]error{err})
+ }
}
- ninjaDeps = append(ninjaDeps, extraDeps...)
+
+ if buildActionsDeps, errs := ctx.PrepareBuildActions(config); len(errs) > 0 {
+ fatalErrors(errs)
+ } else {
+ ninjaDeps = append(ninjaDeps, buildActionsDeps...)
+ }
if stopBefore == StopBeforeWriteNinja {
return ninjaDeps
@@ -131,37 +136,32 @@
ctx.BeginEvent("write_files")
defer ctx.EndEvent("write_files")
if args.EmptyNinjaFile {
- if err := ioutil.WriteFile(joinPath(ctx.SrcDir(), args.OutFile), []byte(nil), outFilePermissions); err != nil {
+ if err := os.WriteFile(joinPath(ctx.SrcDir(), args.OutFile), []byte(nil), outFilePermissions); err != nil {
fatalf("error writing empty Ninja file: %s", err)
}
- }
-
- if !args.EmptyNinjaFile {
- f, err = os.OpenFile(joinPath(ctx.SrcDir(), args.OutFile), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, outFilePermissions)
+ 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)
}
+ defer f.Close()
buf = bufio.NewWriterSize(f, 16*1024*1024)
out = buf
- } else {
- out = ioutil.Discard.(io.StringWriter)
}
- err = ctx.WriteBuildFile(out)
- if err != nil {
+ if err := ctx.WriteBuildFile(out); err != nil {
fatalf("error writing Ninja file contents: %s", err)
}
if buf != nil {
- err = buf.Flush()
- if err != nil {
+ if err := buf.Flush(); err != nil {
fatalf("error flushing Ninja file contents: %s", err)
}
}
if f != nil {
- err = f.Close()
- if err != nil {
+ if err := f.Close(); err != nil {
fatalf("error closing Ninja file: %s", err)
}
}
diff --git a/bootstrap/config.go b/bootstrap/config.go
index 9972b5d..1d256ba 100644
--- a/bootstrap/config.go
+++ b/bootstrap/config.go
@@ -25,7 +25,7 @@
)
func bootstrapVariable(name string, value func(BootstrapConfig) string) blueprint.Variable {
- return pctx.VariableFunc(name, func(config interface{}) (string, error) {
+ return pctx.VariableFunc(name, func(ctx blueprint.VariableFuncContext, config interface{}) (string, error) {
c, ok := config.(BootstrapConfig)
if !ok {
panic(fmt.Sprintf("Bootstrap rules were passed a configuration that does not include theirs, config=%q",
@@ -105,10 +105,11 @@
)
type PrimaryBuilderInvocation struct {
- Inputs []string
- Outputs []string
- Args []string
- Console bool
- Description string
- Env map[string]string
+ Inputs []string
+ OrderOnlyInputs []string
+ Outputs []string
+ Args []string
+ Console bool
+ Description string
+ Env map[string]string
}
diff --git a/bootstrap/glob.go b/bootstrap/glob.go
index 70495dc..a766676 100644
--- a/bootstrap/glob.go
+++ b/bootstrap/glob.go
@@ -44,7 +44,7 @@
// in a build failure with a "missing and no known rule to make it" error.
var (
- _ = pctx.VariableFunc("globCmd", func(config interface{}) (string, error) {
+ _ = pctx.VariableFunc("globCmd", func(ctx blueprint.VariableFuncContext, config interface{}) (string, error) {
return filepath.Join(config.(BootstrapConfig).SoongOutDir(), "bpglob"), nil
})
@@ -237,6 +237,8 @@
return nil, errs
}
+ // PrepareBuildActions() will write $OUTDIR/soong/globs/$m/$i files
+ // where $m=bp2build|build and $i=0..numGlobBuckets
extraDeps, errs = ctx.PrepareBuildActions(config)
if len(extraDeps) > 0 {
return nil, []error{fmt.Errorf("shouldn't have extra deps")}
diff --git a/bpmodify/bpmodify.go b/bpmodify/bpmodify.go
index 431eb83..1df808e 100644
--- a/bpmodify/bpmodify.go
+++ b/bpmodify/bpmodify.go
@@ -2,7 +2,6 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-
package main
import (
@@ -15,6 +14,7 @@
"os/exec"
"path/filepath"
"strings"
+ "syscall"
"unicode"
"github.com/google/blueprint/parser"
@@ -22,27 +22,34 @@
var (
// main operation modes
- list = flag.Bool("l", false, "list files that would be modified by bpmodify")
- write = flag.Bool("w", false, "write result to (source) file instead of stdout")
- doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
- sortLists = flag.Bool("s", false, "sort touched lists, even if they were unsorted")
- targetedModules = new(identSet)
- targetedProperty = new(qualifiedProperty)
- addIdents = new(identSet)
- removeIdents = new(identSet)
- removeProperty = flag.Bool("remove-property", false, "remove the property")
- setString *string
- addLiteral *string
+ list = flag.Bool("l", false, "list files that would be modified by bpmodify")
+ write = flag.Bool("w", false, "write result to (source) file instead of stdout")
+ doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
+ sortLists = flag.Bool("s", false, "sort touched lists, even if they were unsorted")
+ targetedModules = new(identSet)
+ targetedProperties = new(qualifiedProperties)
+ addIdents = new(identSet)
+ removeIdents = new(identSet)
+ removeProperty = flag.Bool("remove-property", false, "remove the property")
+ moveProperty = flag.Bool("move-property", false, "moves contents of property into newLocation")
+ newLocation string
+ setString *string
+ addLiteral *string
+ setBool *string
+ replaceProperty = new(replacements)
)
func init() {
flag.Var(targetedModules, "m", "comma or whitespace separated list of modules on which to operate")
- flag.Var(targetedProperty, "parameter", "alias to -property=`name`")
- flag.Var(targetedProperty, "property", "fully qualified `name` of property to modify (default \"deps\")")
+ flag.Var(targetedProperties, "parameter", "alias to -property=`name1[,name2[,... […]")
+ flag.StringVar(&newLocation, "new-location", "", " use with moveProperty to move contents of -property into a property with name -new-location ")
+ flag.Var(targetedProperties, "property", "comma-separated list of fully qualified `name`s of properties to modify (default \"deps\")")
flag.Var(addIdents, "a", "comma or whitespace separated list of identifiers to add")
- flag.Var(stringPtrFlag{&addLiteral}, "add-literal", "a literal to add")
+ flag.Var(stringPtrFlag{&addLiteral}, "add-literal", "a literal to add to a list")
flag.Var(removeIdents, "r", "comma or whitespace separated list of identifiers to remove")
flag.Var(stringPtrFlag{&setString}, "str", "set a string property")
+ flag.Var(replaceProperty, "replace-property", "property names to be replaced, in the form of oldName1=newName1,oldName2=newName2")
+ flag.Var(stringPtrFlag{&setBool}, "set-bool", "a boolean value to set a property with (not a list)")
flag.Usage = usage
}
@@ -68,16 +75,16 @@
return err
}
defer f.Close()
+ if *write {
+ syscall.Flock(int(f.Fd()), syscall.LOCK_EX)
+ }
in = f
}
-
src, err := ioutil.ReadAll(in)
if err != nil {
return err
}
-
r := bytes.NewBuffer(src)
-
file, errs := parser.Parse(filename, r, parser.NewScope(nil))
if len(errs) > 0 {
for _, err := range errs {
@@ -85,7 +92,6 @@
}
return fmt.Errorf("%d parsing errors", len(errs))
}
-
modified, errs := findModules(file)
if len(errs) > 0 {
for _, err := range errs {
@@ -93,13 +99,11 @@
}
fmt.Fprintln(os.Stderr, "continuing...")
}
-
if modified {
res, err := parser.Print(file)
if err != nil {
return err
}
-
if *list {
fmt.Fprintln(out, filename)
}
@@ -117,23 +121,19 @@
fmt.Printf("diff %s bpfmt/%s\n", filename, filename)
out.Write(data)
}
-
if !*list && !*write && !*doDiff {
_, err = out.Write(res)
}
}
-
return err
}
-
func findModules(file *parser.File) (modified bool, errs []error) {
-
for _, def := range file.Defs {
if module, ok := def.(*parser.Module); ok {
for _, prop := range module.Properties {
- if prop.Name == "name" && prop.Value.Type() == parser.StringType {
- if targetedModule(prop.Value.Eval().(*parser.String).Value) {
- m, newErrs := processModule(module, prop.Name, file)
+ if prop.Name == "name" && prop.Value.Type() == parser.StringType && targetedModule(prop.Value.Eval().(*parser.String).Value) {
+ for _, p := range targetedProperties.properties {
+ m, newErrs := processModuleProperty(module, prop.Name, file, p)
errs = append(errs, newErrs...)
modified = modified || m
}
@@ -141,23 +141,25 @@
}
}
}
-
return modified, errs
}
-func processModule(module *parser.Module, moduleName string,
- file *parser.File) (modified bool, errs []error) {
- prop, parent, err := getRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes())
+func processModuleProperty(module *parser.Module, moduleName string,
+ file *parser.File, property qualifiedProperty) (modified bool, errs []error) {
+ prop, parent, err := getRecursiveProperty(module, property.name(), property.prefixes())
if err != nil {
return false, []error{err}
}
if prop == nil {
if len(addIdents.idents) > 0 || addLiteral != nil {
// We are adding something to a non-existing list prop, so we need to create it first.
- prop, modified, err = createRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes(), &parser.List{})
+ prop, modified, err = createRecursiveProperty(module, property.name(), property.prefixes(), &parser.List{})
} else if setString != nil {
// We setting a non-existent string property, so we need to create it first.
- prop, modified, err = createRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes(), &parser.String{})
+ prop, modified, err = createRecursiveProperty(module, property.name(), property.prefixes(), &parser.String{})
+ } else if setBool != nil {
+ // We are setting a non-existent property, so we need to create it first.
+ prop, modified, err = createRecursiveProperty(module, property.name(), property.prefixes(), &parser.Bool{})
} else {
// We cannot find an existing prop, and we aren't adding anything to the prop,
// which means we must be removing something from a non-existing prop,
@@ -171,23 +173,22 @@
} else if *removeProperty {
// remove-property is used solely, so return here.
return parent.RemoveProperty(prop.Name), nil
+ } else if *moveProperty {
+ return parent.MovePropertyContents(prop.Name, newLocation), nil
}
- m, errs := processParameter(prop.Value, targetedProperty.String(), moduleName, file)
+ m, errs := processParameter(prop.Value, property.String(), moduleName, file)
modified = modified || m
return modified, errs
}
-
func getRecursiveProperty(module *parser.Module, name string, prefixes []string) (prop *parser.Property, parent *parser.Map, err error) {
prop, parent, _, err = getOrCreateRecursiveProperty(module, name, prefixes, nil)
return prop, parent, err
}
-
func createRecursiveProperty(module *parser.Module, name string, prefixes []string,
empty parser.Expression) (prop *parser.Property, modified bool, err error) {
prop, _, modified, err = getOrCreateRecursiveProperty(module, name, prefixes, empty)
return prop, modified, err
}
-
func getOrCreateRecursiveProperty(module *parser.Module, name string, prefixes []string,
empty parser.Expression) (prop *parser.Property, parent *parser.Map, modified bool, err error) {
m := &module.Map
@@ -223,38 +224,48 @@
return nil, nil, false, nil
}
}
-
func processParameter(value parser.Expression, paramName, moduleName string,
file *parser.File) (modified bool, errs []error) {
if _, ok := value.(*parser.Variable); ok {
return false, []error{fmt.Errorf("parameter %s in module %s is a variable, unsupported",
paramName, moduleName)}
}
-
if _, ok := value.(*parser.Operator); ok {
return false, []error{fmt.Errorf("parameter %s in module %s is an expression, unsupported",
paramName, moduleName)}
}
+ if (*replaceProperty).size() != 0 {
+ if list, ok := value.Eval().(*parser.List); ok {
+ return parser.ReplaceStringsInList(list, (*replaceProperty).oldNameToNewName), nil
+ } else if str, ok := value.Eval().(*parser.String); ok {
+ oldVal := str.Value
+ replacementValue := (*replaceProperty).oldNameToNewName[oldVal]
+ if replacementValue != "" {
+ str.Value = replacementValue
+ return true, nil
+ } else {
+ return false, nil
+ }
+ }
+ return false, []error{fmt.Errorf("expected parameter %s in module %s to be a list or string, found %s",
+ paramName, moduleName, value.Type().String())}
+ }
if len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 {
list, ok := value.(*parser.List)
if !ok {
return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s",
- paramName, moduleName, value.Type().String())}
+ paramName, moduleName, value.Type())}
}
-
wasSorted := parser.ListIsSorted(list)
-
for _, a := range addIdents.idents {
m := parser.AddStringToList(list, a)
modified = modified || m
}
-
for _, r := range removeIdents.idents {
m := parser.RemoveStringFromList(list, r)
modified = modified || m
}
-
if (wasSorted || *sortLists) && modified {
parser.SortList(file, list)
}
@@ -273,20 +284,32 @@
}
list.Values = append(list.Values, value)
modified = true
+ } else if setBool != nil {
+ res, ok := value.(*parser.Bool)
+ if !ok {
+ return false, []error{fmt.Errorf("expected parameter %s in module %s to be bool, found %s",
+ paramName, moduleName, value.Type().String())}
+ }
+ if *setBool == "true" {
+ res.Value = true
+ } else if *setBool == "false" {
+ res.Value = false
+ } else {
+ return false, []error{fmt.Errorf("expected parameter %s to be true or false, found %s",
+ paramName, *setBool)}
+ }
+ modified = true
} else if setString != nil {
str, ok := value.(*parser.String)
if !ok {
return false, []error{fmt.Errorf("expected parameter %s in module %s to be string, found %s",
paramName, moduleName, value.Type().String())}
}
-
str.Value = *setString
modified = true
}
-
return modified, nil
}
-
func targetedModule(name string) bool {
if targetedModules.all {
return true
@@ -296,12 +319,11 @@
return true
}
}
-
return false
}
-
func visitFile(path string, f os.FileInfo, err error) error {
- if err == nil && f.Name() == "Blueprints" {
+ //TODO(dacek): figure out a better way to target intended .bp files without parsing errors
+ if err == nil && (f.Name() == "Blueprints" || strings.HasSuffix(f.Name(), ".bp")) {
err = processFile(path, nil, os.Stdout)
}
if err != nil {
@@ -309,11 +331,9 @@
}
return nil
}
-
func walkDir(path string) {
filepath.Walk(path, visitFile)
}
-
func main() {
defer func() {
if err := recover(); err != nil {
@@ -321,13 +341,16 @@
}
os.Exit(exitCode)
}()
-
flag.Parse()
- if len(targetedProperty.parts) == 0 {
- targetedProperty.Set("deps")
+ if len(targetedProperties.properties) == 0 && *moveProperty {
+ report(fmt.Errorf("-move-property must specify property"))
+ return
}
+ if len(targetedProperties.properties) == 0 {
+ targetedProperties.Set("deps")
+ }
if flag.NArg() == 0 {
if *write {
report(fmt.Errorf("error: cannot use -w with standard input"))
@@ -338,22 +361,27 @@
}
return
}
-
if len(targetedModules.idents) == 0 {
report(fmt.Errorf("-m parameter is required"))
return
}
- if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 && setString == nil && addLiteral == nil && !*removeProperty {
- report(fmt.Errorf("-a, -add-literal, -r, -remove-property or -str parameter is required"))
+ if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 && setString == nil && addLiteral == nil && !*removeProperty && !*moveProperty && (*replaceProperty).size() == 0 && setBool == nil {
+ report(fmt.Errorf("-a, -add-literal, -r, -remove-property, -move-property, replace-property or -str parameter is required"))
return
}
-
- if *removeProperty && (len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 || setString != nil || addLiteral != nil) {
+ if *removeProperty && (len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 || setString != nil || addLiteral != nil || (*replaceProperty).size() > 0) {
report(fmt.Errorf("-remove-property cannot be used with other parameter(s)"))
return
}
-
+ if *moveProperty && (len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 || setString != nil || addLiteral != nil || (*replaceProperty).size() > 0) {
+ report(fmt.Errorf("-move-property cannot be used with other parameter(s)"))
+ return
+ }
+ if *moveProperty && newLocation == "" {
+ report(fmt.Errorf("-move-property must specify -new-location"))
+ return
+ }
for i := 0; i < flag.NArg(); i++ {
path := flag.Arg(i)
switch dir, err := os.Stat(path); {
@@ -376,17 +404,14 @@
}
defer os.Remove(f1.Name())
defer f1.Close()
-
f2, err := ioutil.TempFile("", "bpfmt")
if err != nil {
return
}
defer os.Remove(f2.Name())
defer f2.Close()
-
f1.Write(b1)
f2.Write(b2)
-
data, err = exec.Command("diff", "-uw", f1.Name(), f2.Name()).CombinedOutput()
if len(data) > 0 {
// diff exits with a non-zero status when the files don't match.
@@ -394,7 +419,6 @@
err = nil
}
return
-
}
type stringPtrFlag struct {
@@ -405,7 +429,6 @@
*f.s = &s
return nil
}
-
func (f stringPtrFlag) String() string {
if f.s == nil || *f.s == nil {
return ""
@@ -413,6 +436,59 @@
return **f.s
}
+type replacements struct {
+ oldNameToNewName map[string]string
+}
+
+func (m *replacements) String() string {
+ ret := ""
+ sep := ""
+ for k, v := range m.oldNameToNewName {
+ ret += sep
+ ret += k
+ ret += ":"
+ ret += v
+ sep = ","
+ }
+ return ret
+}
+
+func (m *replacements) Set(s string) error {
+ usedNames := make(map[string]struct{})
+
+ pairs := strings.Split(s, ",")
+ length := len(pairs)
+ m.oldNameToNewName = make(map[string]string)
+ for i := 0; i < length; i++ {
+
+ pair := strings.SplitN(pairs[i], "=", 2)
+ if len(pair) != 2 {
+ return fmt.Errorf("Invalid replacement pair %s", pairs[i])
+ }
+ oldName := pair[0]
+ newName := pair[1]
+ if _, seen := usedNames[oldName]; seen {
+ return fmt.Errorf("Duplicated replacement name %s", oldName)
+ }
+ if _, seen := usedNames[newName]; seen {
+ return fmt.Errorf("Duplicated replacement name %s", newName)
+ }
+ usedNames[oldName] = struct{}{}
+ usedNames[newName] = struct{}{}
+ m.oldNameToNewName[oldName] = newName
+ }
+ return nil
+}
+
+func (m *replacements) Get() interface{} {
+ //TODO(dacek): Remove Get() method from interface as it seems unused.
+ return m.oldNameToNewName
+}
+
+func (m *replacements) size() (length int) {
+ return len(m.oldNameToNewName)
+}
+
type identSet struct {
idents []string
all bool
@@ -421,7 +497,6 @@
func (m *identSet) String() string {
return strings.Join(m.idents, ",")
}
-
func (m *identSet) Set(s string) error {
m.idents = strings.FieldsFunc(s, func(c rune) bool {
return unicode.IsSpace(c) || c == ','
@@ -431,42 +506,70 @@
}
return nil
}
-
func (m *identSet) Get() interface{} {
return m.idents
}
+type qualifiedProperties struct {
+ properties []qualifiedProperty
+}
+
type qualifiedProperty struct {
parts []string
}
-var _ flag.Getter = (*qualifiedProperty)(nil)
+var _ flag.Getter = (*qualifiedProperties)(nil)
func (p *qualifiedProperty) name() string {
return p.parts[len(p.parts)-1]
}
-
func (p *qualifiedProperty) prefixes() []string {
return p.parts[:len(p.parts)-1]
}
-
func (p *qualifiedProperty) String() string {
return strings.Join(p.parts, ".")
}
-func (p *qualifiedProperty) Set(s string) error {
- p.parts = strings.Split(s, ".")
- if len(p.parts) == 0 {
+func parseQualifiedProperty(s string) (*qualifiedProperty, error) {
+ parts := strings.Split(s, ".")
+ if len(parts) == 0 {
+ return nil, fmt.Errorf("%q is not a valid property name", s)
+ }
+ for _, part := range parts {
+ if part == "" {
+ return nil, fmt.Errorf("%q is not a valid property name", s)
+ }
+ }
+ prop := qualifiedProperty{parts}
+ return &prop, nil
+
+}
+
+func (p *qualifiedProperties) Set(s string) error {
+ properties := strings.Split(s, ",")
+ if len(properties) == 0 {
return fmt.Errorf("%q is not a valid property name", s)
}
- for _, part := range p.parts {
- if part == "" {
- return fmt.Errorf("%q is not a valid property name", s)
+
+ p.properties = make([]qualifiedProperty, len(properties))
+ for i := 0; i < len(properties); i++ {
+ tmp, err := parseQualifiedProperty(properties[i])
+ if err != nil {
+ return err
}
+ p.properties[i] = *tmp
}
return nil
}
-func (p *qualifiedProperty) Get() interface{} {
- return p.parts
+func (p *qualifiedProperties) String() string {
+ arrayLength := len(p.properties)
+ props := make([]string, arrayLength)
+ for i := 0; i < len(p.properties); i++ {
+ props[i] = p.properties[i].String()
+ }
+ return strings.Join(props, ",")
+}
+func (p *qualifiedProperties) Get() interface{} {
+ return p.properties
}
diff --git a/bpmodify/bpmodify_test.go b/bpmodify/bpmodify_test.go
index 4340edb..7bd8b57 100644
--- a/bpmodify/bpmodify_test.go
+++ b/bpmodify/bpmodify_test.go
@@ -4,14 +4,13 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-
package main
import (
@@ -23,15 +22,19 @@
)
var testCases = []struct {
- name string
- input string
- output string
- property string
- addSet string
- removeSet string
- addLiteral *string
- setString *string
- removeProperty bool
+ name string
+ input string
+ output string
+ property string
+ addSet string
+ removeSet string
+ addLiteral *string
+ setString *string
+ setBool *string
+ removeProperty bool
+ replaceProperty string
+ moveProperty bool
+ newLocation string
}{
{
name: "add",
@@ -306,6 +309,39 @@
setString: proptools.StringPtr("bar"),
},
{
+ name: "set bool",
+ input: `
+ cc_foo {
+ name: "foo",
+ }
+ `,
+ output: `
+ cc_foo {
+ name: "foo",
+ foo: true,
+ }
+ `,
+ property: "foo",
+ setBool: proptools.StringPtr("true"),
+ },
+ {
+ name: "set existing bool",
+ input: `
+ cc_foo {
+ name: "foo",
+ foo: true,
+ }
+ `,
+ output: `
+ cc_foo {
+ name: "foo",
+ foo: false,
+ }
+ `,
+ property: "foo",
+ setBool: proptools.StringPtr("false"),
+ },
+ {
name: "remove existing property",
input: `
cc_foo {
@@ -354,6 +390,147 @@
`,
property: "bar",
removeProperty: true,
+ }, {
+ name: "replace property",
+ property: "deps",
+ input: `
+ cc_foo {
+ name: "foo",
+ deps: ["baz", "unchanged"],
+ }
+ `,
+ output: `
+ cc_foo {
+ name: "foo",
+ deps: [
+ "baz_lib",
+ "unchanged",
+ ],
+ }
+ `,
+ replaceProperty: "baz=baz_lib,foobar=foobar_lib",
+ }, {
+ name: "replace property multiple modules",
+ property: "deps,required",
+ input: `
+ cc_foo {
+ name: "foo",
+ deps: ["baz", "unchanged"],
+ unchanged: ["baz"],
+ required: ["foobar"],
+ }
+ `,
+ output: `
+ cc_foo {
+ name: "foo",
+ deps: [
+ "baz_lib",
+ "unchanged",
+ ],
+ unchanged: ["baz"],
+ required: ["foobar_lib"],
+ }
+ `,
+ replaceProperty: "baz=baz_lib,foobar=foobar_lib",
+ }, {
+ name: "replace property string value",
+ property: "name",
+ input: `
+ cc_foo {
+ name: "foo",
+ deps: ["baz"],
+ unchanged: ["baz"],
+ required: ["foobar"],
+ }
+ `,
+ output: `
+ cc_foo {
+ name: "foo_lib",
+ deps: ["baz"],
+ unchanged: ["baz"],
+ required: ["foobar"],
+ }
+ `,
+ replaceProperty: "foo=foo_lib",
+ }, {
+ name: "replace property string and list values",
+ property: "name,deps",
+ input: `
+ cc_foo {
+ name: "foo",
+ deps: ["baz"],
+ unchanged: ["baz"],
+ required: ["foobar"],
+ }
+ `,
+ output: `
+ cc_foo {
+ name: "foo_lib",
+ deps: ["baz_lib"],
+ unchanged: ["baz"],
+ required: ["foobar"],
+ }
+ `,
+ replaceProperty: "foo=foo_lib,baz=baz_lib",
+ }, {
+ name: "move contents of property into non-existing property",
+ input: `
+ cc_foo {
+ name: "foo",
+ bar: ["barContents"],
+ }
+`,
+ output: `
+ cc_foo {
+ name: "foo",
+ baz: ["barContents"],
+ }
+ `,
+ property: "bar",
+ moveProperty: true,
+ newLocation: "baz",
+ }, {
+ name: "move contents of property into existing property",
+ input: `
+ cc_foo {
+ name: "foo",
+ baz: ["bazContents"],
+ bar: ["barContents"],
+ }
+ `,
+ output: `
+ cc_foo {
+ name: "foo",
+ baz: [
+ "bazContents",
+ "barContents",
+ ],
+
+ }
+ `,
+ property: "bar",
+ moveProperty: true,
+ newLocation: "baz",
+ }, {
+ name: "replace nested",
+ input: `
+ cc_foo {
+ name: "foo",
+ foo: {
+ bar: "baz",
+ },
+ }
+ `,
+ output: `
+ cc_foo {
+ name: "foo",
+ foo: {
+ bar: "baz2",
+ },
+ }
+ `,
+ property: "foo.bar",
+ replaceProperty: "baz=baz2",
},
}
@@ -364,16 +541,19 @@
}
return result
}
-
func TestProcessModule(t *testing.T) {
for i, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
- targetedProperty.Set(testCase.property)
+ targetedProperties.Set(testCase.property)
addIdents.Set(testCase.addSet)
removeIdents.Set(testCase.removeSet)
removeProperty = &testCase.removeProperty
+ moveProperty = &testCase.moveProperty
+ newLocation = testCase.newLocation
setString = testCase.setString
+ setBool = testCase.setBool
addLiteral = testCase.addLiteral
+ replaceProperty.Set(testCase.replaceProperty)
inAst, errs := parser.ParseAndEval("", strings.NewReader(testCase.input), parser.NewScope(nil))
if len(errs) > 0 {
@@ -384,16 +564,18 @@
t.Errorf("%+v", testCase)
t.FailNow()
}
-
if inModule, ok := inAst.Defs[0].(*parser.Module); !ok {
t.Fatalf(" input must only contain a single module definition: %s", testCase.input)
} else {
- _, errs := processModule(inModule, "", inAst)
- if len(errs) > 0 {
- t.Errorf("test case %d:", i)
- for _, err := range errs {
- t.Errorf(" %s", err)
+ for _, p := range targetedProperties.properties {
+ _, errs := processModuleProperty(inModule, "", inAst, p)
+ if len(errs) > 0 {
+ t.Errorf("test case %d:", i)
+ for _, err := range errs {
+ t.Errorf(" %s", err)
+ }
}
+
}
inModuleText, _ := parser.Print(inAst)
inModuleString := string(inModuleText)
@@ -407,5 +589,46 @@
}
})
}
+}
+func TestReplacementsCycleError(t *testing.T) {
+ cycleString := "old1=new1,new1=old1"
+ err := replaceProperty.Set(cycleString)
+
+ if err.Error() != "Duplicated replacement name new1" {
+ t.Errorf("Error message did not match")
+ t.Errorf("Expected ")
+ t.Errorf(" Duplicated replacement name new1")
+ t.Errorf("actual error:")
+ t.Errorf(" %s", err.Error())
+ t.FailNow()
+ }
+}
+
+func TestReplacementsDuplicatedError(t *testing.T) {
+ cycleString := "a=b,a=c"
+ err := replaceProperty.Set(cycleString)
+
+ if err.Error() != "Duplicated replacement name a" {
+ t.Errorf("Error message did not match")
+ t.Errorf("Expected ")
+ t.Errorf(" Duplicated replacement name a")
+ t.Errorf("actual error:")
+ t.Errorf(" %s", err.Error())
+ t.FailNow()
+ }
+}
+
+func TestReplacementsMultipleReplacedToSame(t *testing.T) {
+ cycleString := "a=c,d=c"
+ err := replaceProperty.Set(cycleString)
+
+ if err.Error() != "Duplicated replacement name c" {
+ t.Errorf("Error message did not match")
+ t.Errorf("Expected ")
+ t.Errorf(" Duplicated replacement name c")
+ t.Errorf("actual error:")
+ t.Errorf(" %s", err.Error())
+ t.FailNow()
+ }
}
diff --git a/bpmodify/rename_module_and_deps.py b/bpmodify/rename_module_and_deps.py
new file mode 100755
index 0000000..b1e6db9
--- /dev/null
+++ b/bpmodify/rename_module_and_deps.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""A tool to run bpmodify for a given module in order to rename it and all references to it"""
+import os
+import subprocess
+import sys
+
+
+def main():
+ if len(sys.argv) < 2:
+ print("Usage: rename_module_and_deps <pathToModule1,pathToModule2,...>")
+ return
+
+ modulePaths = sys.argv[1].split(",")
+ replacementsList = []
+ colonReplacementsList = []
+
+ for modulePath in modulePaths:
+ moduleName = modulePath.split("/")[-1]
+ replacementsList.append(moduleName + "=" + moduleName + "_lib")
+ # add in the colon replacement
+ colonReplaceString = ":" + moduleName + "=" + ":" + moduleName + "_lib"
+ replacementsList.append(colonReplaceString)
+ colonReplacementsList.append(colonReplaceString)
+
+ replacementsString = ",".join(replacementsList)
+ colonReplacementsString = ",".join(colonReplacementsList)
+ buildTop = os.getenv("ANDROID_BUILD_TOP")
+
+ if not buildTop:
+ raise Exception(
+ "$ANDROID_BUILD_TOP not found in environment. Have you run lunch?")
+
+ rename_deps_cmd = f"{buildTop}/prebuilts/go/linux-x86/bin/go run bpmodify.go -w -m=* -property=static_libs,deps,required,test_suites,name,host,libs,data_bins,data_native_bins,tools,shared_libs,file_contexts,target.not_windows.required,target.android.required,target.platform.required -replace-property={replacementsString} {buildTop}"
+ print(rename_deps_cmd)
+ subprocess.check_output(rename_deps_cmd, shell=True)
+
+ # Some properties (for example, data ), refer to files. Such properties may also refer to a filegroup module by prefixing it with a colon. Replacing these module references must thus be done separately.
+ colon_rename_deps_cmd = f"{buildTop}/prebuilts/go/linux-x86/bin/go run bpmodify.go -w -m=* -property=data -replace-property={colonReplacementsString} {buildTop}"
+ print(colon_rename_deps_cmd)
+ subprocess.check_output(colon_rename_deps_cmd, shell=True)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/context.go b/context.go
index 6496948..17daa8a 100644
--- a/context.go
+++ b/context.go
@@ -17,6 +17,8 @@
import (
"bytes"
"context"
+ "crypto/sha256"
+ "encoding/base64"
"encoding/json"
"errors"
"fmt"
@@ -50,15 +52,15 @@
// through a series of four phases. Each phase corresponds with a some methods
// on the Context object
//
-// Phase Methods
-// ------------ -------------------------------------------
-// 1. Registration RegisterModuleType, RegisterSingletonType
+// Phase Methods
+// ------------ -------------------------------------------
+// 1. Registration RegisterModuleType, RegisterSingletonType
//
-// 2. Parse ParseBlueprintsFiles, Parse
+// 2. Parse ParseBlueprintsFiles, Parse
//
-// 3. Generate ResolveDependencies, PrepareBuildActions
+// 3. Generate ResolveDependencies, PrepareBuildActions
//
-// 4. Write WriteBuildFile
+// 4. Write WriteBuildFile
//
// The registration phase prepares the context to process Blueprints files
// containing various types of modules. The parse phase reads in one or more
@@ -75,6 +77,8 @@
// Used for metrics-related event logging.
EventHandler *metrics.EventHandler
+ BeforePrepareBuildActionsHook func() error
+
moduleFactories map[string]ModuleFactory
nameInterface NameInterface
moduleGroups []*moduleGroup
@@ -135,6 +139,70 @@
// Can be set by tests to avoid invalidating Module values after mutators.
skipCloneModulesAfterMutators bool
+
+ // String values that can be used to gate build graph traversal
+ includeTags *IncludeTags
+
+ sourceRootDirs *SourceRootDirs
+}
+
+// A container for String keys. The keys can be used to gate build graph traversal
+type SourceRootDirs struct {
+ dirs []string
+}
+
+func (dirs *SourceRootDirs) Add(names ...string) {
+ dirs.dirs = append(dirs.dirs, names...)
+}
+
+func (dirs *SourceRootDirs) SourceRootDirAllowed(path string) (bool, string) {
+ sort.Slice(dirs.dirs, func(i, j int) bool {
+ return len(dirs.dirs[i]) < len(dirs.dirs[j])
+ })
+ last := len(dirs.dirs)
+ for i := range dirs.dirs {
+ // iterate from longest paths (most specific)
+ prefix := dirs.dirs[last-i-1]
+ disallowedPrefix := false
+ if len(prefix) >= 1 && prefix[0] == '-' {
+ prefix = prefix[1:]
+ disallowedPrefix = true
+ }
+ if strings.HasPrefix(path, prefix) {
+ if disallowedPrefix {
+ return false, prefix
+ } else {
+ return true, prefix
+ }
+ }
+ }
+ return true, ""
+}
+
+func (c *Context) AddSourceRootDirs(dirs ...string) {
+ c.sourceRootDirs.Add(dirs...)
+}
+
+// A container for String keys. The keys can be used to gate build graph traversal
+type IncludeTags map[string]bool
+
+func (tags *IncludeTags) Add(names ...string) {
+ for _, name := range names {
+ (*tags)[name] = true
+ }
+}
+
+func (tags *IncludeTags) Contains(tag string) bool {
+ _, exists := (*tags)[tag]
+ return exists
+}
+
+func (c *Context) AddIncludeTags(names ...string) {
+ c.includeTags.Add(names...)
+}
+
+func (c *Context) ContainsIncludeTag(name string) bool {
+ return c.includeTags.Contains(name)
}
// An Error describes a problem that was encountered that is related to a
@@ -270,6 +338,11 @@
// set during each runMutator
splitModules modulesOrAliases
+ // Used by TransitionMutator implementations
+ transitionVariations []string
+ currentTransitionMutator string
+ requiredVariationsLock sync.Mutex
+
// set during PrepareBuildActions
actionDefs localBuildActions
@@ -392,6 +465,8 @@
globs: make(map[globKey]pathtools.GlobResult),
fs: pathtools.OsFs,
finishedMutators: make(map[*mutatorInfo]bool),
+ includeTags: &IncludeTags{},
+ sourceRootDirs: &SourceRootDirs{},
outDir: nil,
requiredNinjaMajor: 1,
requiredNinjaMinor: 7,
@@ -448,38 +523,38 @@
//
// As an example, the follow code:
//
-// type myModule struct {
-// properties struct {
-// Foo string
-// Bar []string
-// }
-// }
+// type myModule struct {
+// properties struct {
+// Foo string
+// Bar []string
+// }
+// }
//
-// func NewMyModule() (blueprint.Module, []interface{}) {
-// module := new(myModule)
-// properties := &module.properties
-// return module, []interface{}{properties}
-// }
+// func NewMyModule() (blueprint.Module, []interface{}) {
+// module := new(myModule)
+// properties := &module.properties
+// return module, []interface{}{properties}
+// }
//
-// func main() {
-// ctx := blueprint.NewContext()
-// ctx.RegisterModuleType("my_module", NewMyModule)
-// // ...
-// }
+// func main() {
+// ctx := blueprint.NewContext()
+// ctx.RegisterModuleType("my_module", NewMyModule)
+// // ...
+// }
//
// would support parsing a module defined in a Blueprints file as follows:
//
-// my_module {
-// name: "myName",
-// foo: "my foo string",
-// bar: ["my", "bar", "strings"],
-// }
+// my_module {
+// name: "myName",
+// foo: "my foo string",
+// bar: ["my", "bar", "strings"],
+// }
//
// The factory function may be called from multiple goroutines. Any accesses
// to global variables must be synchronized.
func (c *Context) RegisterModuleType(name string, factory ModuleFactory) {
if _, present := c.moduleFactories[name]; present {
- panic(errors.New("module type name is already registered"))
+ panic(fmt.Errorf("module type %q is already registered", name))
}
c.moduleFactories[name] = factory
}
@@ -500,7 +575,7 @@
func (c *Context) RegisterSingletonType(name string, factory SingletonFactory) {
for _, s := range c.singletonInfo {
if s.name == name {
- panic(errors.New("singleton name is already registered"))
+ panic(fmt.Errorf("singleton %q is already registered", name))
}
}
@@ -522,7 +597,7 @@
func (c *Context) RegisterPreSingletonType(name string, factory SingletonFactory) {
for _, s := range c.preSingletonInfo {
if s.name == name {
- panic(errors.New("presingleton name is already registered"))
+ panic(fmt.Errorf("presingleton %q is already registered", name))
}
}
@@ -575,7 +650,7 @@
func (c *Context) RegisterTopDownMutator(name string, mutator TopDownMutator) MutatorHandle {
for _, m := range c.mutatorInfo {
if m.name == name && m.topDownMutator != nil {
- panic(fmt.Errorf("mutator name %s is already registered", name))
+ panic(fmt.Errorf("mutator %q is already registered", name))
}
}
@@ -602,7 +677,7 @@
func (c *Context) RegisterBottomUpMutator(name string, mutator BottomUpMutator) MutatorHandle {
for _, m := range c.variantMutatorNames {
if m == name {
- panic(fmt.Errorf("mutator name %s is already registered", name))
+ panic(fmt.Errorf("mutator %q is already registered", name))
}
}
@@ -617,6 +692,230 @@
return info
}
+type IncomingTransitionContext interface {
+ // Module returns the target of the dependency edge for which the transition
+ // is being computed
+ Module() Module
+
+ // Config returns the config object that was passed to
+ // Context.PrepareBuildActions.
+ Config() interface{}
+}
+
+type OutgoingTransitionContext interface {
+ // Module returns the target of the dependency edge for which the transition
+ // is being computed
+ Module() Module
+
+ // DepTag() Returns the dependency tag through which this dependency is
+ // reached
+ DepTag() DependencyTag
+}
+
+// Transition mutators implement a top-down mechanism where a module tells its
+// direct dependencies what variation they should be built in but the dependency
+// has the final say.
+//
+// When implementing a transition mutator, one needs to implement four methods:
+// - Split() that tells what variations a module has by itself
+// - OutgoingTransition() where a module tells what it wants from its
+// dependency
+// - IncomingTransition() where a module has the final say about its own
+// variation
+// - Mutate() that changes the state of a module depending on its variation
+//
+// That the effective variation of module B when depended on by module A is the
+// composition the outgoing transition of module A and the incoming transition
+// of module B.
+//
+// the outgoing transition should not take the properties of the dependency into
+// account, only those of the module that depends on it. For this reason, the
+// dependency is not even passed into it as an argument. Likewise, the incoming
+// transition should not take the properties of the depending module into
+// account and is thus not informed about it. This makes for a nice
+// decomposition of the decision logic.
+//
+// A given transition mutator only affects its own variation; other variations
+// stay unchanged along the dependency edges.
+//
+// Soong makes sure that all modules are created in the desired variations and
+// that dependency edges are set up correctly. This ensures that "missing
+// variation" errors do not happen and allows for more flexible changes in the
+// value of the variation among dependency edges (as oppposed to bottom-up
+// mutators where if module A in variation X depends on module B and module B
+// has that variation X, A must depend on variation X of B)
+//
+// The limited power of the context objects passed to individual mutators
+// methods also makes it more difficult to shoot oneself in the foot. Complete
+// safety is not guaranteed because no one prevents individual transition
+// mutators from mutating modules in illegal ways and for e.g. Split() or
+// Mutate() to run their own visitations of the transitive dependency of the
+// module and both of these are bad ideas, but it's better than no guardrails at
+// all.
+//
+// This model is pretty close to Bazel's configuration transitions. The mapping
+// between concepts in Soong and Bazel is as follows:
+// - Module == configured target
+// - Variant == configuration
+// - Variation name == configuration flag
+// - Variation == configuration flag value
+// - Outgoing transition == attribute transition
+// - Incoming transition == rule transition
+//
+// The Split() method does not have a Bazel equivalent and Bazel split
+// transitions do not have a Soong equivalent.
+//
+// Mutate() does not make sense in Bazel due to the different models of the
+// two systems: when creating new variations, Soong clones the old module and
+// thus some way is needed to change it state whereas Bazel creates each
+// configuration of a given configured target anew.
+type TransitionMutator interface {
+ // Returns the set of variations that should be created for a module no matter
+ // who depends on it. Used when Make depends on a particular variation or when
+ // the module knows its variations just based on information given to it in
+ // the Blueprint file. This method should not mutate the module it is called
+ // on.
+ Split(ctx BaseModuleContext) []string
+
+ // Called on a module to determine which variation it wants from its direct
+ // dependencies. The dependency itself can override this decision. This method
+ // should not mutate the module itself.
+ OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string
+
+ // Called on a module to determine which variation it should be in based on
+ // the variation modules that depend on it want. This gives the module a final
+ // say about its own variations. This method should not mutate the module
+ // itself.
+ IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string
+
+ // Called after a module was split into multiple variations on each variation.
+ // It should not split the module any further but adding new dependencies is
+ // fine. Unlike all the other methods on TransitionMutator, this method is
+ // allowed to mutate the module.
+ Mutate(ctx BottomUpMutatorContext, variation string)
+}
+
+type transitionMutatorImpl struct {
+ name string
+ mutator TransitionMutator
+}
+
+// Adds each argument in items to l if it's not already there.
+func addToStringListIfNotPresent(l []string, items ...string) []string {
+OUTER:
+ for _, i := range items {
+ for _, existing := range l {
+ if existing == i {
+ continue OUTER
+ }
+ }
+
+ l = append(l, i)
+ }
+
+ return l
+}
+
+func (t *transitionMutatorImpl) addRequiredVariation(m *moduleInfo, variation string) {
+ m.requiredVariationsLock.Lock()
+ defer m.requiredVariationsLock.Unlock()
+
+ // This is only a consistency check. Leaking the variations of a transition
+ // mutator to another one could well lead to issues that are difficult to
+ // track down.
+ if m.currentTransitionMutator != "" && m.currentTransitionMutator != t.name {
+ panic(fmt.Errorf("transition mutator is %s in mutator %s", m.currentTransitionMutator, t.name))
+ }
+
+ m.currentTransitionMutator = t.name
+ m.transitionVariations = addToStringListIfNotPresent(m.transitionVariations, variation)
+}
+
+func (t *transitionMutatorImpl) topDownMutator(mctx TopDownMutatorContext) {
+ module := mctx.(*mutatorContext).module
+ mutatorSplits := t.mutator.Split(mctx)
+ if mutatorSplits == nil || len(mutatorSplits) == 0 {
+ panic(fmt.Errorf("transition mutator %s returned no splits for module %s", t.name, mctx.ModuleName()))
+ }
+
+ // transitionVariations for given a module can be mutated by the module itself
+ // and modules that directly depend on it. Since this is a top-down mutator,
+ // all modules that directly depend on this module have already been processed
+ // so no locking is necessary.
+ module.transitionVariations = addToStringListIfNotPresent(module.transitionVariations, mutatorSplits...)
+ sort.Strings(module.transitionVariations)
+
+ for _, srcVariation := range module.transitionVariations {
+ for _, dep := range module.directDeps {
+ finalVariation := t.transition(mctx)(mctx.Module(), srcVariation, dep.module.logicModule, dep.tag)
+ t.addRequiredVariation(dep.module, finalVariation)
+ }
+ }
+}
+
+type transitionContextImpl struct {
+ module Module
+ depTag DependencyTag
+ config interface{}
+}
+
+func (c *transitionContextImpl) Module() Module {
+ return c.module
+}
+
+func (c *transitionContextImpl) DepTag() DependencyTag {
+ return c.depTag
+}
+
+func (c *transitionContextImpl) Config() interface{} {
+ return c.config
+}
+
+func (t *transitionMutatorImpl) transition(mctx BaseMutatorContext) Transition {
+ return func(source Module, sourceVariation string, dep Module, depTag DependencyTag) string {
+ tc := &transitionContextImpl{module: dep, depTag: depTag, config: mctx.Config()}
+ outgoingVariation := t.mutator.OutgoingTransition(tc, sourceVariation)
+ finalVariation := t.mutator.IncomingTransition(tc, outgoingVariation)
+ return finalVariation
+ }
+}
+
+func (t *transitionMutatorImpl) bottomUpMutator(mctx BottomUpMutatorContext) {
+ mc := mctx.(*mutatorContext)
+ // Fetch and clean up transition mutator state. No locking needed since the
+ // only time interaction between multiple modules is required is during the
+ // computation of the variations required by a given module.
+ variations := mc.module.transitionVariations
+ mc.module.transitionVariations = nil
+ mc.module.currentTransitionMutator = ""
+
+ if len(variations) < 1 {
+ panic(fmt.Errorf("no variations found for module %s by mutator %s",
+ mctx.ModuleName(), t.name))
+ }
+
+ if len(variations) == 1 && variations[0] == "" {
+ // Module is not split, just apply the transition
+ mc.applyTransition(t.transition(mctx))
+ } else {
+ mc.createVariationsWithTransition(t.transition(mctx), variations...)
+ }
+}
+
+func (t *transitionMutatorImpl) mutateMutator(mctx BottomUpMutatorContext) {
+ module := mctx.(*mutatorContext).module
+ currentVariation := module.variant.variations[t.name]
+ t.mutator.Mutate(mctx, currentVariation)
+}
+
+func (c *Context) RegisterTransitionMutator(name string, mutator TransitionMutator) {
+ impl := &transitionMutatorImpl{name: name, mutator: mutator}
+
+ c.RegisterTopDownMutator(name+"_deps", impl.topDownMutator).Parallel()
+ c.RegisterBottomUpMutator(name, impl.bottomUpMutator).Parallel()
+ c.RegisterBottomUpMutator(name+"_mutate", impl.mutateMutator).Parallel()
+}
+
type MutatorHandle interface {
// Set the mutator to visit modules in parallel while maintaining ordering. Calling any
// method on the mutator context is thread-safe, but the mutator must handle synchronization
@@ -709,6 +1008,71 @@
return c.ParseFileList(baseDir, pathsToParse, config)
}
+type shouldVisitFileInfo struct {
+ shouldVisitFile bool
+ skippedModules []string
+ reasonForSkip string
+ errs []error
+}
+
+// Returns a boolean for whether this file should be analyzed
+// Evaluates to true if the file either
+// 1. does not contain a blueprint_package_includes
+// 2. contains a blueprint_package_includes and all requested tags are set
+// This should be processed before adding any modules to the build graph
+func shouldVisitFile(c *Context, file *parser.File) shouldVisitFileInfo {
+ skippedModules := []string{}
+ var blueprintPackageIncludes *PackageIncludes
+ for _, def := range file.Defs {
+ switch def := def.(type) {
+ case *parser.Module:
+ skippedModules = append(skippedModules, def.Name())
+ if def.Type != "blueprint_package_includes" {
+ continue
+ }
+ module, errs := processModuleDef(def, file.Name, c.moduleFactories, nil, c.ignoreUnknownModuleTypes)
+ if len(errs) > 0 {
+ // This file contains errors in blueprint_package_includes
+ // Visit anyways so that we can report errors on other modules in the file
+ return shouldVisitFileInfo{
+ shouldVisitFile: true,
+ errs: errs,
+ }
+ }
+ logicModule, _ := c.cloneLogicModule(module)
+ blueprintPackageIncludes = logicModule.(*PackageIncludes)
+ }
+ }
+
+ if blueprintPackageIncludes != nil {
+ packageMatches := blueprintPackageIncludes.MatchesIncludeTags(c)
+ if !packageMatches {
+ return shouldVisitFileInfo{
+ shouldVisitFile: false,
+ skippedModules: skippedModules,
+ reasonForSkip: fmt.Sprintf(
+ "module is defined in %q which contains a blueprint_package_includes module with unsatisfied tags",
+ file.Name,
+ ),
+ }
+ }
+ }
+
+ shouldVisit, invalidatingPrefix := c.sourceRootDirs.SourceRootDirAllowed(file.Name)
+ if !shouldVisit {
+ return shouldVisitFileInfo{
+ shouldVisitFile: shouldVisit,
+ skippedModules: skippedModules,
+ reasonForSkip: fmt.Sprintf(
+ "%q is a descendant of %q, and that path prefix was not included in PRODUCT_SOURCE_ROOT_DIRS",
+ file.Name,
+ invalidatingPrefix,
+ ),
+ }
+ }
+ return shouldVisitFileInfo{shouldVisitFile: true}
+}
+
func (c *Context) ParseFileList(rootDir string, filePaths []string,
config interface{}) (deps []string, errs []error) {
@@ -724,9 +1088,15 @@
added chan<- struct{}
}
+ type newSkipInfo struct {
+ shouldVisitFileInfo
+ file string
+ }
+
moduleCh := make(chan newModuleInfo)
errsCh := make(chan []error)
doneCh := make(chan struct{})
+ skipCh := make(chan newSkipInfo)
var numErrs uint32
var numGoroutines int32
@@ -761,6 +1131,20 @@
}
return nil
}
+ shouldVisitInfo := shouldVisitFile(c, file)
+ errs := shouldVisitInfo.errs
+ if len(errs) > 0 {
+ atomic.AddUint32(&numErrs, uint32(len(errs)))
+ errsCh <- errs
+ }
+ if !shouldVisitInfo.shouldVisitFile {
+ skipCh <- newSkipInfo{
+ file: file.Name,
+ shouldVisitFileInfo: shouldVisitInfo,
+ }
+ // TODO: Write a file that lists the skipped bp files
+ return
+ }
for _, def := range file.Defs {
switch def := def.(type) {
@@ -814,6 +1198,14 @@
if n == 0 {
break loop
}
+ case skipped := <-skipCh:
+ nctx := newNamespaceContextFromFilename(skipped.file)
+ for _, name := range skipped.skippedModules {
+ c.nameInterface.NewSkippedModule(nctx, name, SkippedModuleInfo{
+ filename: skipped.file,
+ reason: skipped.reasonForSkip,
+ })
+ }
}
}
@@ -1301,7 +1693,7 @@
}
func (c *Context) createVariations(origModule *moduleInfo, mutatorName string,
- defaultVariationName *string, variationNames []string, local bool) (modulesOrAliases, []error) {
+ depChooser depChooser, variationNames []string, local bool) (modulesOrAliases, []error) {
if len(variationNames) == 0 {
panic(fmt.Errorf("mutator %q passed zero-length variation list for module %q",
@@ -1337,7 +1729,7 @@
newModules = append(newModules, newModule)
- newErrs := c.convertDepsToVariation(newModule, mutatorName, variationName, defaultVariationName)
+ newErrs := c.convertDepsToVariation(newModule, depChooser)
if len(newErrs) > 0 {
errs = append(errs, newErrs...)
}
@@ -1353,31 +1745,79 @@
return newModules, errs
}
-func (c *Context) convertDepsToVariation(module *moduleInfo,
- mutatorName, variationName string, defaultVariationName *string) (errs []error) {
+type depChooser func(source *moduleInfo, dep depInfo) (*moduleInfo, string)
+// This function is called for every dependency edge to determine which
+// variation of the dependency is needed. Its inputs are the depending module,
+// its variation, the dependency and the dependency tag.
+type Transition func(source Module, sourceVariation string, dep Module, depTag DependencyTag) string
+
+func chooseDepByTransition(mutatorName string, transition Transition) depChooser {
+ return func(source *moduleInfo, dep depInfo) (*moduleInfo, string) {
+ sourceVariation := source.variant.variations[mutatorName]
+ depLogicModule := dep.module.logicModule
+ if depLogicModule == nil {
+ // This is really a lie because the original dependency before the split
+ // went away when it was split. We choose an arbitrary split module
+ // instead and hope that whatever information the transition wants from it
+ // is the same as in the original one
+ // TODO(lberki): this can be fixed by calling transition() once and saving
+ // its results somewhere
+ depLogicModule = dep.module.splitModules[0].moduleOrAliasTarget().logicModule
+ }
+
+ desiredVariation := transition(source.logicModule, sourceVariation, depLogicModule, dep.tag)
+ for _, m := range dep.module.splitModules {
+ if m.moduleOrAliasVariant().variations[mutatorName] == desiredVariation {
+ return m.moduleOrAliasTarget(), ""
+ }
+ }
+
+ return nil, desiredVariation
+ }
+}
+
+func chooseDep(candidates modulesOrAliases, mutatorName, variationName string, defaultVariationName *string) (*moduleInfo, string) {
+ for _, m := range candidates {
+ if m.moduleOrAliasVariant().variations[mutatorName] == variationName {
+ return m.moduleOrAliasTarget(), ""
+ }
+ }
+
+ if defaultVariationName != nil {
+ // give it a second chance; match with defaultVariationName
+ for _, m := range candidates {
+ if m.moduleOrAliasVariant().variations[mutatorName] == *defaultVariationName {
+ return m.moduleOrAliasTarget(), ""
+ }
+ }
+ }
+
+ return nil, variationName
+}
+
+func chooseDepExplicit(mutatorName string,
+ variationName string, defaultVariationName *string) depChooser {
+ return func(source *moduleInfo, dep depInfo) (*moduleInfo, string) {
+ return chooseDep(dep.module.splitModules, mutatorName, variationName, defaultVariationName)
+ }
+}
+
+func chooseDepInherit(mutatorName string, defaultVariationName *string) depChooser {
+ return func(source *moduleInfo, dep depInfo) (*moduleInfo, string) {
+ sourceVariation := source.variant.variations[mutatorName]
+ return chooseDep(dep.module.splitModules, mutatorName, sourceVariation, defaultVariationName)
+ }
+}
+
+func (c *Context) convertDepsToVariation(module *moduleInfo, depChooser depChooser) (errs []error) {
for i, dep := range module.directDeps {
if dep.module.logicModule == nil {
- var newDep *moduleInfo
- for _, m := range dep.module.splitModules {
- if m.moduleOrAliasVariant().variations[mutatorName] == variationName {
- newDep = m.moduleOrAliasTarget()
- break
- }
- }
- if newDep == nil && defaultVariationName != nil {
- // give it a second chance; match with defaultVariationName
- for _, m := range dep.module.splitModules {
- if m.moduleOrAliasVariant().variations[mutatorName] == *defaultVariationName {
- newDep = m.moduleOrAliasTarget()
- break
- }
- }
- }
+ newDep, missingVariation := depChooser(module, dep)
if newDep == nil {
errs = append(errs, &BlueprintError{
Err: fmt.Errorf("failed to find variation %q for module %q needed by %q",
- variationName, dep.module.Name(), module.Name()),
+ missingVariation, dep.module.Name(), module.Name()),
Pos: module.pos,
})
continue
@@ -1519,7 +1959,7 @@
pprof.Do(ctx, pprof.Labels("blueprint", "ResolveDependencies"), func(ctx context.Context) {
c.initProviders()
- c.liveGlobals = newLiveTracker(config)
+ c.liveGlobals = newLiveTracker(c, config)
deps, errs = c.generateSingletonBuildActions(config, c.preSingletonInfo, c.liveGlobals)
if len(errs) > 0 {
@@ -2235,12 +2675,13 @@
return
}
-type jsonVariationMap []Variation
+type jsonVariations []Variation
type jsonModuleName struct {
Name string
- Variations jsonVariationMap
- DependencyVariations jsonVariationMap
+ Variant string
+ Variations jsonVariations
+ DependencyVariations jsonVariations
}
type jsonDep struct {
@@ -2253,11 +2694,12 @@
Deps []jsonDep
Type string
Blueprint string
+ CreatedBy *string
Module map[string]interface{}
}
-func toJsonVariationMap(vm variationMap) jsonVariationMap {
- m := make(jsonVariationMap, 0, len(vm))
+func toJsonVariationMap(vm variationMap) jsonVariations {
+ m := make(jsonVariations, 0, len(vm))
for k, v := range vm {
m = append(m, Variation{k, v})
}
@@ -2273,6 +2715,7 @@
func jsonModuleNameFromModuleInfo(m *moduleInfo) *jsonModuleName {
return &jsonModuleName{
Name: m.Name(),
+ Variant: m.variant.name,
Variations: toJsonVariationMap(m.variant.variations),
DependencyVariations: toJsonVariationMap(m.variant.dependencyVariations),
}
@@ -2282,6 +2725,18 @@
AddJSONData(d *map[string]interface{})
}
+// JSONAction contains the action-related info we expose to json module graph
+type JSONAction struct {
+ Inputs []string
+ Outputs []string
+}
+
+// JSONActionSupplier allows JSON representation of additional actions that are not registered in
+// Ninja
+type JSONActionSupplier interface {
+ JSONActions() []JSONAction
+}
+
func jsonModuleFromModuleInfo(m *moduleInfo) *JsonModule {
result := &JsonModule{
jsonModuleName: *jsonModuleNameFromModuleInfo(m),
@@ -2290,6 +2745,10 @@
Blueprint: m.relBlueprintsFile,
Module: make(map[string]interface{}),
}
+ if m.createdBy != nil {
+ n := m.createdBy.Name()
+ result.CreatedBy = &n
+ }
if j, ok := m.logicModule.(JSONDataSupplier); ok {
j.AddJSONData(&result.Module)
}
@@ -2304,24 +2763,35 @@
func jsonModuleWithActionsFromModuleInfo(m *moduleInfo) *JsonModule {
result := &JsonModule{
jsonModuleName: jsonModuleName{
- Name: m.Name(),
+ Name: m.Name(),
+ Variant: m.variant.name,
},
Deps: make([]jsonDep, 0),
Type: m.typeName,
Blueprint: m.relBlueprintsFile,
Module: make(map[string]interface{}),
}
- var actions []map[string]interface{}
+ var actions []JSONAction
for _, bDef := range m.actionDefs.buildDefs {
- actions = append(actions, map[string]interface{}{
- "Inputs": append(
+ actions = append(actions, JSONAction{
+ Inputs: append(
getNinjaStringsWithNilPkgNames(bDef.Inputs),
getNinjaStringsWithNilPkgNames(bDef.Implicits)...),
- "Outputs": append(
+ Outputs: append(
getNinjaStringsWithNilPkgNames(bDef.Outputs),
getNinjaStringsWithNilPkgNames(bDef.ImplicitOutputs)...),
})
}
+
+ if j, ok := m.logicModule.(JSONActionSupplier); ok {
+ actions = append(actions, j.JSONActions()...)
+ }
+ for _, p := range m.providers {
+ if j, ok := p.(JSONActionSupplier); ok {
+ actions = append(actions, j.JSONActions()...)
+ }
+ }
+
result.Module["Actions"] = actions
return result
}
@@ -2336,6 +2806,30 @@
return strs
}
+func (c *Context) GetOutputsFromModuleNames(moduleNames []string) map[string][]string {
+ modulesToOutputs := make(map[string][]string)
+ for _, m := range c.modulesSorted {
+ if inList(m.Name(), moduleNames) {
+ jmWithActions := jsonModuleWithActionsFromModuleInfo(m)
+ for _, a := range jmWithActions.Module["Actions"].([]JSONAction) {
+ modulesToOutputs[m.Name()] = append(modulesToOutputs[m.Name()], a.Outputs...)
+ }
+ // There could be several modules with the same name, so keep looping
+ }
+ }
+
+ return modulesToOutputs
+}
+
+func inList(s string, l []string) bool {
+ for _, element := range l {
+ if s == element {
+ return true
+ }
+ }
+ return false
+}
+
// PrintJSONGraph prints info of modules in a JSON file.
func (c *Context) PrintJSONGraphAndActions(wGraph io.Writer, wActions io.Writer) {
modulesToGraph := make([]*JsonModule, 0)
@@ -2453,6 +2947,8 @@
pprof.Do(ctx, pprof.Labels("blueprint", "runMutators"), func(ctx context.Context) {
for _, mutator := range c.mutatorInfo {
pprof.Do(ctx, pprof.Labels("mutator", mutator.name), func(context.Context) {
+ c.BeginEvent(mutator.name)
+ defer c.EndEvent(mutator.name)
var newDeps []string
if mutator.topDownMutator != nil {
newDeps, errs = c.runMutator(config, mutator, topDownMutator)
@@ -3119,8 +3615,8 @@
}
func (c *Context) missingDependencyError(module *moduleInfo, depName string) (errs error) {
- err := c.nameInterface.MissingDependencyError(module.Name(), module.namespace(), depName)
-
+ guess := namesLike(depName, module.Name(), c.moduleGroups)
+ err := c.nameInterface.MissingDependencyError(module.Name(), module.namespace(), depName, guess)
return &BlueprintError{
Err: err,
Pos: module.pos,
@@ -3388,32 +3884,30 @@
}
targets := map[string]string{}
-
- // Collect all the module build targets.
- for _, module := range c.moduleInfo {
- for _, buildDef := range module.actionDefs.buildDefs {
+ var collectTargets = func(actionDefs localBuildActions) error {
+ for _, buildDef := range actionDefs.buildDefs {
ruleName := buildDef.Rule.fullName(c.pkgNames)
for _, output := range append(buildDef.Outputs, buildDef.ImplicitOutputs...) {
outputValue, err := output.Eval(c.globalVariables)
if err != nil {
- return nil, err
+ return err
}
targets[outputValue] = ruleName
}
}
+ return nil
+ }
+ // Collect all the module build targets.
+ for _, module := range c.moduleInfo {
+ if err := collectTargets(module.actionDefs); err != nil {
+ return nil, err
+ }
}
// Collect all the singleton build targets.
for _, info := range c.singletonInfo {
- for _, buildDef := range info.actionDefs.buildDefs {
- ruleName := buildDef.Rule.fullName(c.pkgNames)
- for _, output := range append(buildDef.Outputs, buildDef.ImplicitOutputs...) {
- outputValue, err := output.Eval(c.globalVariables)
- if err != nil {
- return nil, err
- }
- targets[outputValue] = ruleName
- }
+ if err := collectTargets(info.actionDefs); err != nil {
+ return nil, err
}
}
@@ -3619,7 +4113,7 @@
return ""
}
-// WriteBuildFile writes the Ninja manifeset text for the generated build
+// WriteBuildFile writes the Ninja manifest text for the generated build
// actions to w. If this is called before PrepareBuildActions successfully
// completes then ErrBuildActionsNotReady is returned.
func (c *Context) WriteBuildFile(w io.StringWriter) error {
@@ -3632,59 +4126,46 @@
nw := newNinjaWriter(w)
- err = c.writeBuildFileHeader(nw)
- if err != nil {
+ if err = c.writeBuildFileHeader(nw); err != nil {
return
}
- err = c.writeNinjaRequiredVersion(nw)
- if err != nil {
+ if err = c.writeNinjaRequiredVersion(nw); err != nil {
return
}
- err = c.writeSubninjas(nw)
- if err != nil {
+ if err = c.writeSubninjas(nw); err != nil {
return
}
// TODO: Group the globals by package.
- err = c.writeGlobalVariables(nw)
- if err != nil {
+ if err = c.writeGlobalVariables(nw); err != nil {
return
}
- err = c.writeGlobalPools(nw)
- if err != nil {
+ if err = c.writeGlobalPools(nw); err != nil {
return
}
- err = c.writeBuildDir(nw)
- if err != nil {
+ if err = c.writeBuildDir(nw); err != nil {
return
}
- err = c.writeGlobalRules(nw)
- if err != nil {
+ if err = c.writeGlobalRules(nw); err != nil {
return
}
- err = c.writeAllModuleActions(nw)
- if err != nil {
+ if err = c.writeAllModuleActions(nw); err != nil {
return
}
- err = c.writeAllSingletonActions(nw)
- if err != nil {
+ if err = c.writeAllSingletonActions(nw); err != nil {
return
}
})
- if err != nil {
- return err
- }
-
- return nil
+ return err
}
type pkgAssociation struct {
@@ -3965,9 +4446,10 @@
}
func (c *Context) writeAllModuleActions(nw *ninjaWriter) error {
+ c.BeginEvent("modules")
+ defer c.EndEvent("modules")
headerTemplate := template.New("moduleHeader")
- _, err := headerTemplate.Parse(moduleHeaderTemplate)
- if err != nil {
+ if _, err := headerTemplate.Parse(moduleHeaderTemplate); err != nil {
// This is a programming error.
panic(err)
}
@@ -3978,6 +4460,11 @@
}
sort.Sort(moduleSorter{modules, c.nameInterface})
+ phonys := c.deduplicateOrderOnlyDeps(modules)
+ if err := c.writeLocalBuildActions(nw, phonys); err != nil {
+ return err
+ }
+
buf := bytes.NewBuffer(nil)
for _, module := range modules {
@@ -4004,28 +4491,23 @@
"pos": relPos,
"variant": module.variant.name,
}
- err = headerTemplate.Execute(buf, infoMap)
- if err != nil {
+ if err := headerTemplate.Execute(buf, infoMap); err != nil {
return err
}
- err = nw.Comment(buf.String())
- if err != nil {
+ if err := nw.Comment(buf.String()); err != nil {
return err
}
- err = nw.BlankLine()
- if err != nil {
+ if err := nw.BlankLine(); err != nil {
return err
}
- err = c.writeLocalBuildActions(nw, &module.actionDefs)
- if err != nil {
+ if err := c.writeLocalBuildActions(nw, &module.actionDefs); err != nil {
return err
}
- err = nw.BlankLine()
- if err != nil {
+ if err := nw.BlankLine(); err != nil {
return err
}
}
@@ -4034,6 +4516,8 @@
}
func (c *Context) writeAllSingletonActions(nw *ninjaWriter) error {
+ c.BeginEvent("singletons")
+ defer c.EndEvent("singletons")
headerTemplate := template.New("singletonHeader")
_, err := headerTemplate.Parse(singletonHeaderTemplate)
if err != nil {
@@ -4087,6 +4571,10 @@
return nil
}
+func (c *Context) GetEventHandler() *metrics.EventHandler {
+ return c.EventHandler
+}
+
func (c *Context) BeginEvent(name string) {
c.EventHandler.Begin(name)
}
@@ -4095,6 +4583,106 @@
c.EventHandler.End(name)
}
+func (c *Context) SetBeforePrepareBuildActionsHook(hookFn func() error) {
+ c.BeforePrepareBuildActionsHook = hookFn
+}
+
+// phonyCandidate represents the state of a set of deps that decides its eligibility
+// to be extracted as a phony output
+type phonyCandidate struct {
+ sync.Once
+ phony *buildDef // the phony buildDef that wraps the set
+ first *buildDef // the first buildDef that uses this set
+}
+
+// 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 {
+ hasher := sha256.New()
+ for _, d := range deps {
+ if len(d.Variables()) != 0 {
+ return ""
+ }
+ io.WriteString(hasher, d.Value(nil))
+ }
+ return base64.RawURLEncoding.EncodeToString(hasher.Sum(nil))
+}
+
+// scanBuildDef is called for every known buildDef `b` that has a non-empty `b.OrderOnly`.
+// If `b.OrderOnly` is not present in `candidates`, it gets stored.
+// But if `b.OrderOnly` already exists in `candidates`, then `b.OrderOnly`
+// (and phonyCandidate#first.OrderOnly) will be replaced with phonyCandidate#phony.Outputs
+func scanBuildDef(wg *sync.WaitGroup, candidates *sync.Map, phonyCount *atomic.Uint32, b *buildDef) {
+ defer wg.Done()
+ key := keyForPhonyCandidate(b.OrderOnly)
+ if key == "" {
+ return
+ }
+ if v, loaded := candidates.LoadOrStore(key, &phonyCandidate{
+ first: b,
+ }); loaded {
+ m := v.(*phonyCandidate)
+ m.Do(func() {
+ // this is the second occurrence and hence it makes sense to
+ // extract it as a phony output
+ phonyCount.Add(1)
+ m.phony = &buildDef{
+ Rule: Phony,
+ Outputs: []ninjaString{simpleNinjaString("dedup-" + key)},
+ Inputs: m.first.OrderOnly, //we could also use b.OrderOnly
+ Optional: true,
+ }
+ // the previously recorded build-def, which first had these deps as its
+ // order-only deps, should now use this phony output instead
+ m.first.OrderOnly = m.phony.Outputs
+ m.first = nil
+ })
+ b.OrderOnly = m.phony.Outputs
+ }
+}
+
+// deduplicateOrderOnlyDeps searches for common sets of order-only dependencies across all
+// buildDef instances in the provided moduleInfo instances. Each such
+// common set forms a new buildDef representing a phony output that then becomes
+// the sole order-only dependency of those buildDef instances
+func (c *Context) deduplicateOrderOnlyDeps(infos []*moduleInfo) *localBuildActions {
+ c.BeginEvent("deduplicate_order_only_deps")
+ defer c.EndEvent("deduplicate_order_only_deps")
+
+ candidates := sync.Map{} //used as map[key]*candidate
+ phonyCount := atomic.Uint32{}
+ wg := sync.WaitGroup{}
+ for _, info := range infos {
+ for _, b := range info.actionDefs.buildDefs {
+ if len(b.OrderOnly) > 0 {
+ wg.Add(1)
+ go scanBuildDef(&wg, &candidates, &phonyCount, b)
+ }
+ }
+ }
+ wg.Wait()
+
+ // now collect all created phonys to return
+ phonys := make([]*buildDef, 0, phonyCount.Load())
+ candidates.Range(func(_ any, v any) bool {
+ candidate := v.(*phonyCandidate)
+ if candidate.phony != nil {
+ phonys = append(phonys, candidate.phony)
+ }
+ return true
+ })
+
+ c.EventHandler.Do("sort_phony_builddefs", func() {
+ // sorting for determinism, the phony output names are stable
+ sort.Slice(phonys, func(i int, j int) bool {
+ return phonys[i].Outputs[0].Value(nil) < phonys[j].Outputs[0].Value(nil)
+ })
+ })
+
+ return &localBuildActions{buildDefs: phonys}
+}
+
func (c *Context) writeLocalBuildActions(nw *ninjaWriter,
defs *localBuildActions) error {
@@ -4103,7 +4691,7 @@
// A localVariable doesn't need the package names or config to
// determine its name or value.
name := v.fullName(nil)
- value, err := v.value(nil)
+ value, err := v.value(nil, nil)
if err != nil {
panic(err)
}
@@ -4218,7 +4806,7 @@
`
-var moduleHeaderTemplate = `# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+var moduleHeaderTemplate = `# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
Module: {{.name}}
Variant: {{.variant}}
Type: {{.typeName}}
@@ -4226,7 +4814,53 @@
Defined: {{.pos}}
`
-var singletonHeaderTemplate = `# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+var singletonHeaderTemplate = `# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
Singleton: {{.name}}
Factory: {{.goFactory}}
`
+
+// Blueprint module type that can be used to gate blueprint files beneath this directory
+type PackageIncludes struct {
+ properties struct {
+ // Package will be included if all include tags in this list are set
+ Match_all []string
+ }
+ name *string `blueprint:"mutated"`
+}
+
+func (pi *PackageIncludes) Name() string {
+ return proptools.String(pi.name)
+}
+
+// This module type does not have any build actions
+func (pi *PackageIncludes) GenerateBuildActions(ctx ModuleContext) {
+}
+
+func newPackageIncludesFactory() (Module, []interface{}) {
+ module := &PackageIncludes{}
+ AddLoadHook(module, func(ctx LoadHookContext) {
+ module.name = proptools.StringPtr(ctx.ModuleDir() + "_includes") // Generate a synthetic name
+ })
+ return module, []interface{}{&module.properties}
+}
+
+func RegisterPackageIncludesModuleType(ctx *Context) {
+ ctx.RegisterModuleType("blueprint_package_includes", newPackageIncludesFactory)
+}
+
+func (pi *PackageIncludes) MatchAll() []string {
+ return pi.properties.Match_all
+}
+
+// Returns true if all requested include tags are set in the Context object
+func (pi *PackageIncludes) MatchesIncludeTags(ctx *Context) bool {
+ if len(pi.MatchAll()) == 0 {
+ ctx.ModuleErrorf(pi, "Match_all must be a non-empty list")
+ }
+ for _, includeTag := range pi.MatchAll() {
+ if !ctx.ContainsIncludeTag(includeTag) {
+ return false
+ }
+ }
+ return true
+}
diff --git a/context_test.go b/context_test.go
index 6308ba9..1a1fb0d 100644
--- a/context_test.go
+++ b/context_test.go
@@ -18,6 +18,7 @@
"bytes"
"errors"
"fmt"
+ "path/filepath"
"reflect"
"strings"
"sync"
@@ -173,11 +174,11 @@
}
}
-// |===B---D - represents a non-walkable edge
-// A = represents a walkable edge
-// |===C===E---G
-// | | A should not be visited because it's the root node.
-// |===F===| B, D and E should not be walked.
+// > |===B---D - represents a non-walkable edge
+// > A = represents a walkable edge
+// > |===C===E---G
+// > | | A should not be visited because it's the root node.
+// > |===F===| B, D and E should not be walked.
func TestWalkDeps(t *testing.T) {
ctx := NewContext()
ctx.MockFileSystem(map[string][]byte{
@@ -186,31 +187,31 @@
name: "A",
deps: ["B", "C"],
}
-
+
bar_module {
name: "B",
deps: ["D"],
}
-
+
foo_module {
name: "C",
deps: ["E", "F"],
}
-
+
foo_module {
name: "D",
}
-
+
bar_module {
name: "E",
deps: ["G"],
}
-
+
foo_module {
name: "F",
deps: ["G"],
}
-
+
foo_module {
name: "G",
}
@@ -248,12 +249,12 @@
}
}
-// |===B---D - represents a non-walkable edge
-// A = represents a walkable edge
-// |===C===E===\ A should not be visited because it's the root node.
-// | | B, D should not be walked.
-// |===F===G===H G should be visited multiple times
-// \===/ H should only be visited once
+// > |===B---D - represents a non-walkable edge
+// > A = represents a walkable edge
+// > |===C===E===\ A should not be visited because it's the root node.
+// > | | B, D should not be walked.
+// > |===F===G===H G should be visited multiple times
+// > \===/ H should only be visited once
func TestWalkDepsDuplicates(t *testing.T) {
ctx := NewContext()
ctx.MockFileSystem(map[string][]byte{
@@ -329,11 +330,11 @@
}
}
-// - represents a non-walkable edge
-// A = represents a walkable edge
-// |===B-------\ A should not be visited because it's the root node.
-// | | B -> D should not be walked.
-// |===C===D===E B -> C -> D -> E should be walked
+// > - represents a non-walkable edge
+// > A = represents a walkable edge
+// > |===B-------\ A should not be visited because it's the root node.
+// > | | B -> D should not be walked.
+// > |===C===D===E B -> C -> D -> E should be walked
func TestWalkDepsDuplicates_IgnoreFirstPath(t *testing.T) {
ctx := NewContext()
ctx.MockFileSystem(map[string][]byte{
@@ -461,17 +462,17 @@
Deps []string
}
- ctx.CreateModule(newBarModule, &props{
+ ctx.CreateModule(newBarModule, "new_bar", &props{
Name: "B",
Deps: []string{"D"},
})
- ctx.CreateModule(newBarModule, &props{
+ ctx.CreateModule(newBarModule, "new_bar", &props{
Name: "C",
Deps: []string{"D"},
})
- ctx.CreateModule(newFooModule, &props{
+ ctx.CreateModule(newFooModule, "new_foo", &props{
Name: "D",
})
}
@@ -588,7 +589,7 @@
foo_module {
name: "A",
}
-
+
bar_module {
deps: ["A"],
}
@@ -1084,3 +1085,480 @@
}
})
}
+
+func TestPackageIncludes(t *testing.T) {
+ dir1_foo_bp := `
+ blueprint_package_includes {
+ match_all: ["use_dir1"],
+ }
+ foo_module {
+ name: "foo",
+ }
+ `
+ dir2_foo_bp := `
+ blueprint_package_includes {
+ match_all: ["use_dir2"],
+ }
+ foo_module {
+ name: "foo",
+ }
+ `
+ mockFs := map[string][]byte{
+ "dir1/Android.bp": []byte(dir1_foo_bp),
+ "dir2/Android.bp": []byte(dir2_foo_bp),
+ }
+ testCases := []struct {
+ desc string
+ includeTags []string
+ expectedDir string
+ expectedErr string
+ }{
+ {
+ desc: "use_dir1 is set, use dir1 foo",
+ includeTags: []string{"use_dir1"},
+ expectedDir: "dir1",
+ },
+ {
+ desc: "use_dir2 is set, use dir2 foo",
+ includeTags: []string{"use_dir2"},
+ expectedDir: "dir2",
+ },
+ {
+ desc: "duplicate module error if both use_dir1 and use_dir2 are set",
+ includeTags: []string{"use_dir1", "use_dir2"},
+ expectedDir: "",
+ expectedErr: `module "foo" already defined`,
+ },
+ }
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ ctx := NewContext()
+ // Register mock FS
+ ctx.MockFileSystem(mockFs)
+ // Register module types
+ ctx.RegisterModuleType("foo_module", newFooModule)
+ RegisterPackageIncludesModuleType(ctx)
+ // Add include tags for test case
+ ctx.AddIncludeTags(tc.includeTags...)
+ // Run test
+ _, actualErrs := ctx.ParseFileList(".", []string{"dir1/Android.bp", "dir2/Android.bp"}, nil)
+ // Evaluate
+ if !strings.Contains(fmt.Sprintf("%s", actualErrs), fmt.Sprintf("%s", tc.expectedErr)) {
+ t.Errorf("Expected errors: %s, got errors: %s\n", tc.expectedErr, actualErrs)
+ }
+ if tc.expectedErr != "" {
+ return // expectedDir check not necessary
+ }
+ actualBpFile := ctx.moduleGroupFromName("foo", nil).modules.firstModule().relBlueprintsFile
+ if tc.expectedDir != filepath.Dir(actualBpFile) {
+ t.Errorf("Expected foo from %s, got %s\n", tc.expectedDir, filepath.Dir(actualBpFile))
+ }
+ })
+ }
+
+}
+
+func TestDeduplicateOrderOnlyDeps(t *testing.T) {
+ outputs := func(names ...string) []ninjaString {
+ r := make([]ninjaString, len(names))
+ for i, name := range names {
+ r[i] = literalNinjaString(name)
+ }
+ return r
+ }
+ b := func(output string, inputs []string, orderOnlyDeps []string) *buildDef {
+ return &buildDef{
+ Outputs: outputs(output),
+ Inputs: outputs(inputs...),
+ OrderOnly: outputs(orderOnlyDeps...),
+ }
+ }
+ m := func(bs ...*buildDef) *moduleInfo {
+ return &moduleInfo{actionDefs: localBuildActions{buildDefs: bs}}
+ }
+ type testcase struct {
+ modules []*moduleInfo
+ expectedPhonys []*buildDef
+ conversions map[string][]ninjaString
+ }
+ testCases := []testcase{{
+ modules: []*moduleInfo{
+ m(b("A", nil, []string{"d"})),
+ m(b("B", nil, []string{"d"})),
+ },
+ expectedPhonys: []*buildDef{
+ b("dedup-GKw-c0PwFokMUQ6T-TUmEWnZ4_VlQ2Qpgw-vCTT0-OQ", []string{"d"}, nil),
+ },
+ conversions: map[string][]ninjaString{
+ "A": outputs("dedup-GKw-c0PwFokMUQ6T-TUmEWnZ4_VlQ2Qpgw-vCTT0-OQ"),
+ "B": outputs("dedup-GKw-c0PwFokMUQ6T-TUmEWnZ4_VlQ2Qpgw-vCTT0-OQ"),
+ },
+ }, {
+ modules: []*moduleInfo{
+ m(b("A", nil, []string{"a"})),
+ m(b("B", nil, []string{"b"})),
+ },
+ }, {
+ modules: []*moduleInfo{
+ m(b("A", nil, []string{"a"})),
+ m(b("B", nil, []string{"b"})),
+ m(b("C", nil, []string{"a"})),
+ },
+ expectedPhonys: []*buildDef{b("dedup-ypeBEsobvcr6wjGzmiPcTaeG7_gUfE5yuYB3ha_uSLs", []string{"a"}, nil)},
+ conversions: map[string][]ninjaString{
+ "A": outputs("dedup-ypeBEsobvcr6wjGzmiPcTaeG7_gUfE5yuYB3ha_uSLs"),
+ "B": outputs("b"),
+ "C": outputs("dedup-ypeBEsobvcr6wjGzmiPcTaeG7_gUfE5yuYB3ha_uSLs"),
+ },
+ }, {
+ modules: []*moduleInfo{
+ m(b("A", nil, []string{"a", "b"}),
+ b("B", nil, []string{"a", "b"})),
+ m(b("C", nil, []string{"a", "c"}),
+ b("D", nil, []string{"a", "c"})),
+ },
+ expectedPhonys: []*buildDef{
+ b("dedup--44g_C5MPySMYMOb1lLzwTRymLuXe4tNWQO4UFViBgM", []string{"a", "b"}, nil),
+ b("dedup-9F3lHN7zCZFVHkHogt17VAR5lkigoAdT9E_JZuYVP8E", []string{"a", "c"}, nil)},
+ conversions: map[string][]ninjaString{
+ "A": outputs("dedup--44g_C5MPySMYMOb1lLzwTRymLuXe4tNWQO4UFViBgM"),
+ "B": outputs("dedup--44g_C5MPySMYMOb1lLzwTRymLuXe4tNWQO4UFViBgM"),
+ "C": outputs("dedup-9F3lHN7zCZFVHkHogt17VAR5lkigoAdT9E_JZuYVP8E"),
+ "D": outputs("dedup-9F3lHN7zCZFVHkHogt17VAR5lkigoAdT9E_JZuYVP8E"),
+ },
+ }}
+ for index, tc := range testCases {
+ t.Run(fmt.Sprintf("TestCase-%d", index), func(t *testing.T) {
+ ctx := NewContext()
+ actualPhonys := ctx.deduplicateOrderOnlyDeps(tc.modules)
+ if len(actualPhonys.variables) != 0 {
+ t.Errorf("No variables expected but found %v", actualPhonys.variables)
+ }
+ if len(actualPhonys.rules) != 0 {
+ t.Errorf("No rules expected but found %v", actualPhonys.rules)
+ }
+ if e, a := len(tc.expectedPhonys), len(actualPhonys.buildDefs); e != a {
+ t.Errorf("Expected %d build statements but got %d", e, a)
+ }
+ for i := 0; i < len(tc.expectedPhonys); i++ {
+ a := actualPhonys.buildDefs[i]
+ e := tc.expectedPhonys[i]
+ if !reflect.DeepEqual(e.Outputs, a.Outputs) {
+ t.Errorf("phonys expected %v but actualPhonys %v", e.Outputs, a.Outputs)
+ }
+ if !reflect.DeepEqual(e.Inputs, a.Inputs) {
+ t.Errorf("phonys expected %v but actualPhonys %v", e.Inputs, a.Inputs)
+ }
+ }
+ find := func(k string) *buildDef {
+ for _, m := range tc.modules {
+ for _, b := range m.actionDefs.buildDefs {
+ if reflect.DeepEqual(b.Outputs, outputs(k)) {
+ return b
+ }
+ }
+ }
+ return nil
+ }
+ for k, conversion := range tc.conversions {
+ actual := find(k)
+ if actual == nil {
+ t.Errorf("Couldn't find %s", k)
+ }
+ if !reflect.DeepEqual(actual.OrderOnly, conversion) {
+ t.Errorf("expected %s.OrderOnly = %v but got %v", k, conversion, actual.OrderOnly)
+ }
+ }
+ })
+ }
+}
+
+func TestSourceRootDirAllowed(t *testing.T) {
+ type pathCase struct {
+ path string
+ decidingPrefix string
+ allowed bool
+ }
+ testcases := []struct {
+ desc string
+ rootDirs []string
+ pathCases []pathCase
+ }{
+ {
+ desc: "simple case",
+ rootDirs: []string{
+ "a",
+ "b/c/d",
+ "-c",
+ "-d/c/a",
+ "c/some_single_file",
+ },
+ pathCases: []pathCase{
+ {
+ path: "a",
+ decidingPrefix: "a",
+ allowed: true,
+ },
+ {
+ path: "a/b/c",
+ decidingPrefix: "a",
+ allowed: true,
+ },
+ {
+ path: "b",
+ decidingPrefix: "",
+ allowed: true,
+ },
+ {
+ path: "b/c/d/a",
+ decidingPrefix: "b/c/d",
+ allowed: true,
+ },
+ {
+ path: "c",
+ decidingPrefix: "c",
+ allowed: false,
+ },
+ {
+ path: "c/a/b",
+ decidingPrefix: "c",
+ allowed: false,
+ },
+ {
+ path: "c/some_single_file",
+ decidingPrefix: "c/some_single_file",
+ allowed: true,
+ },
+ {
+ path: "d/c/a/abc",
+ decidingPrefix: "d/c/a",
+ allowed: false,
+ },
+ },
+ },
+ {
+ desc: "root directory order matters",
+ rootDirs: []string{
+ "-a",
+ "a/c/some_allowed_file",
+ "a/b/d/some_allowed_file",
+ "a/b",
+ "a/c",
+ "-a/b/d",
+ },
+ pathCases: []pathCase{
+ {
+ path: "a",
+ decidingPrefix: "a",
+ allowed: false,
+ },
+ {
+ path: "a/some_disallowed_file",
+ decidingPrefix: "a",
+ allowed: false,
+ },
+ {
+ path: "a/c/some_allowed_file",
+ decidingPrefix: "a/c/some_allowed_file",
+ allowed: true,
+ },
+ {
+ path: "a/b/d/some_allowed_file",
+ decidingPrefix: "a/b/d/some_allowed_file",
+ allowed: true,
+ },
+ {
+ path: "a/b/c",
+ decidingPrefix: "a/b",
+ allowed: true,
+ },
+ {
+ path: "a/b/c/some_allowed_file",
+ decidingPrefix: "a/b",
+ allowed: true,
+ },
+ {
+ path: "a/b/d",
+ decidingPrefix: "a/b/d",
+ allowed: false,
+ },
+ },
+ },
+ }
+ for _, tc := range testcases {
+ dirs := SourceRootDirs{}
+ dirs.Add(tc.rootDirs...)
+ for _, pc := range tc.pathCases {
+ t.Run(fmt.Sprintf("%s: %s", tc.desc, pc.path), func(t *testing.T) {
+ allowed, decidingPrefix := dirs.SourceRootDirAllowed(pc.path)
+ if allowed != pc.allowed {
+ if pc.allowed {
+ t.Errorf("expected path %q to be allowed, but was not; root allowlist: %q", pc.path, tc.rootDirs)
+ } else {
+ t.Errorf("path %q was allowed unexpectedly; root allowlist: %q", pc.path, tc.rootDirs)
+ }
+ }
+ if decidingPrefix != pc.decidingPrefix {
+ t.Errorf("expected decidingPrefix to be %q, but got %q", pc.decidingPrefix, decidingPrefix)
+ }
+ })
+ }
+ }
+}
+
+func TestSourceRootDirs(t *testing.T) {
+ root_foo_bp := `
+ foo_module {
+ name: "foo",
+ deps: ["foo_dir1", "foo_dir_ignored_special_case"],
+ }
+ `
+ dir1_foo_bp := `
+ foo_module {
+ name: "foo_dir1",
+ deps: ["foo_dir_ignored"],
+ }
+ `
+ dir_ignored_foo_bp := `
+ foo_module {
+ name: "foo_dir_ignored",
+ }
+ `
+ dir_ignored_special_case_foo_bp := `
+ foo_module {
+ name: "foo_dir_ignored_special_case",
+ }
+ `
+ mockFs := map[string][]byte{
+ "Android.bp": []byte(root_foo_bp),
+ "dir1/Android.bp": []byte(dir1_foo_bp),
+ "dir_ignored/Android.bp": []byte(dir_ignored_foo_bp),
+ "dir_ignored/special_case/Android.bp": []byte(dir_ignored_special_case_foo_bp),
+ }
+ fileList := []string{}
+ for f := range mockFs {
+ fileList = append(fileList, f)
+ }
+ testCases := []struct {
+ sourceRootDirs []string
+ expectedModuleDefs []string
+ unexpectedModuleDefs []string
+ expectedErrs []string
+ }{
+ {
+ sourceRootDirs: []string{},
+ expectedModuleDefs: []string{
+ "foo",
+ "foo_dir1",
+ "foo_dir_ignored",
+ "foo_dir_ignored_special_case",
+ },
+ },
+ {
+ sourceRootDirs: []string{"-", ""},
+ unexpectedModuleDefs: []string{
+ "foo",
+ "foo_dir1",
+ "foo_dir_ignored",
+ "foo_dir_ignored_special_case",
+ },
+ },
+ {
+ sourceRootDirs: []string{"-"},
+ unexpectedModuleDefs: []string{
+ "foo",
+ "foo_dir1",
+ "foo_dir_ignored",
+ "foo_dir_ignored_special_case",
+ },
+ },
+ {
+ sourceRootDirs: []string{"dir1"},
+ expectedModuleDefs: []string{
+ "foo",
+ "foo_dir1",
+ "foo_dir_ignored",
+ "foo_dir_ignored_special_case",
+ },
+ },
+ {
+ sourceRootDirs: []string{"-dir1"},
+ expectedModuleDefs: []string{
+ "foo",
+ "foo_dir_ignored",
+ "foo_dir_ignored_special_case",
+ },
+ unexpectedModuleDefs: []string{
+ "foo_dir1",
+ },
+ expectedErrs: []string{
+ `Android.bp:2:2: module "foo" depends on skipped module "foo_dir1"; "foo_dir1" was defined in files(s) [dir1/Android.bp], but was skipped for reason(s) ["dir1/Android.bp" is a descendant of "dir1", and that path prefix was not included in PRODUCT_SOURCE_ROOT_DIRS]`,
+ },
+ },
+ {
+ sourceRootDirs: []string{"-", "dir1"},
+ expectedModuleDefs: []string{
+ "foo_dir1",
+ },
+ unexpectedModuleDefs: []string{
+ "foo",
+ "foo_dir_ignored",
+ "foo_dir_ignored_special_case",
+ },
+ expectedErrs: []string{
+ `dir1/Android.bp:2:2: module "foo_dir1" depends on skipped module "foo_dir_ignored"; "foo_dir_ignored" was defined in files(s) [dir_ignored/Android.bp], but was skipped for reason(s) ["dir_ignored/Android.bp" is a descendant of "", and that path prefix was not included in PRODUCT_SOURCE_ROOT_DIRS]`,
+ },
+ },
+ {
+ sourceRootDirs: []string{"-", "dir1", "dir_ignored/special_case/Android.bp"},
+ expectedModuleDefs: []string{
+ "foo_dir1",
+ "foo_dir_ignored_special_case",
+ },
+ unexpectedModuleDefs: []string{
+ "foo",
+ "foo_dir_ignored",
+ },
+ expectedErrs: []string{
+ "dir1/Android.bp:2:2: module \"foo_dir1\" depends on skipped module \"foo_dir_ignored\"; \"foo_dir_ignored\" was defined in files(s) [dir_ignored/Android.bp], but was skipped for reason(s) [\"dir_ignored/Android.bp\" is a descendant of \"\", and that path prefix was not included in PRODUCT_SOURCE_ROOT_DIRS]",
+ },
+ },
+ }
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf(`source root dirs are %q`, tc.sourceRootDirs), func(t *testing.T) {
+ ctx := NewContext()
+ ctx.MockFileSystem(mockFs)
+ ctx.RegisterModuleType("foo_module", newFooModule)
+ ctx.RegisterBottomUpMutator("deps", depsMutator)
+ ctx.AddSourceRootDirs(tc.sourceRootDirs...)
+ RegisterPackageIncludesModuleType(ctx)
+ ctx.ParseFileList(".", fileList, nil)
+ _, actualErrs := ctx.ResolveDependencies(nil)
+
+ stringErrs := []string(nil)
+ for _, err := range actualErrs {
+ stringErrs = append(stringErrs, err.Error())
+ }
+ if !reflect.DeepEqual(tc.expectedErrs, stringErrs) {
+ t.Errorf("expected to find errors %v; got %v", tc.expectedErrs, stringErrs)
+ }
+ for _, modName := range tc.expectedModuleDefs {
+ allMods := ctx.moduleGroupFromName(modName, nil)
+ if allMods == nil || len(allMods.modules) != 1 {
+ mods := modulesOrAliases{}
+ if allMods != nil {
+ mods = allMods.modules
+ }
+ t.Errorf("expected to find one definition for module %q, but got %v", modName, mods)
+ }
+ }
+
+ for _, modName := range tc.unexpectedModuleDefs {
+ allMods := ctx.moduleGroupFromName(modName, nil)
+ if allMods != nil {
+ t.Errorf("expected to find no definitions for module %q, but got %v", modName, allMods.modules)
+ }
+ }
+ })
+ }
+}
diff --git a/doc.go b/doc.go
index 4a9dfd7..9e917f6 100644
--- a/doc.go
+++ b/doc.go
@@ -35,17 +35,17 @@
// the module type looks like a function call, and the properties of the module
// look like optional arguments. For example, a simple module might look like:
//
-// cc_library {
-// name: "cmd",
-// srcs: [
-// "main.c",
-// ],
-// deps: [
-// "libc",
-// ],
-// }
+// cc_library {
+// name: "cmd",
+// srcs: [
+// "main.c",
+// ],
+// deps: [
+// "libc",
+// ],
+// }
//
-// subdirs = ["subdir1", "subdir2"]
+// subdirs = ["subdir1", "subdir2"]
//
// The modules from the top level Blueprints file and recursively through any
// subdirectories listed by the "subdirs" variable are read by Blueprint, and
diff --git a/go.mod b/go.mod
index fe96d45..e278f2f 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
module github.com/google/blueprint
-go 1.13
+go 1.18
diff --git a/levenshtein.go b/levenshtein.go
new file mode 100644
index 0000000..de5b75a
--- /dev/null
+++ b/levenshtein.go
@@ -0,0 +1,117 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package blueprint
+
+import (
+ "sort"
+)
+
+func abs(a int) int {
+ if a < 0 {
+ return -a
+ }
+ return a
+}
+
+// This implementation is written to be recursive, because
+// we know Soong names are short, so we shouldn't hit the stack
+// depth. Also, the buffer is indexed this way so that new
+// allocations aren't needed.
+func levenshtein(a, b string, ai, bi, max int, buf [][]int) int {
+ if max == 0 {
+ return 0
+ }
+ if ai >= len(a) {
+ return len(b) - bi
+ }
+ if bi >= len(b) {
+ return len(a) - ai
+ }
+ if buf[bi][ai] != 0 {
+ return buf[bi][ai]
+ }
+ if abs(len(a)-len(b)) >= max {
+ return max
+ }
+ var res = max
+ if a[ai] == b[bi] {
+ res = levenshtein(a, b, ai+1, bi+1, max, buf)
+ } else {
+ if c := levenshtein(a, b, ai+1, bi+1, max-1, buf); c < res {
+ res = c // replace
+ }
+ if c := levenshtein(a, b, ai+1, bi, max-1, buf); c < res {
+ res = c // delete from a
+ }
+ if c := levenshtein(a, b, ai, bi+1, max-1, buf); c < res {
+ res = c // delete from b
+ }
+ res += 1
+ }
+ buf[bi][ai] = res
+ return res
+}
+
+func stringIn(arr []string, str string) bool {
+ for _, a := range arr {
+ if a == str {
+ return true
+ }
+ }
+ return false
+}
+
+func namesLike(name string, unlike string, moduleGroups []*moduleGroup) []string {
+ const kAllowedDifferences = 10
+ buf := make([][]int, len(name)+kAllowedDifferences)
+ for i := range buf {
+ buf[i] = make([]int, len(name))
+ }
+
+ var best []string
+ bestVal := kAllowedDifferences + 1
+
+ for _, group := range moduleGroups {
+ other := group.name
+
+ if other == unlike {
+ continue
+ }
+
+ l := levenshtein(name, other, 0, 0, kAllowedDifferences, buf)
+ // fmt.Printf("levenshtein %q %q %d\n", name, other, l)
+
+ // slightly better to use a min-heap
+ if l == 0 {
+ // these are the same, so it must be in a different namespace
+ // ignore...
+ } else if l < bestVal {
+ bestVal = l
+ best = []string{other}
+ } else if l == bestVal && !stringIn(best, other) {
+ best = append(best, other)
+ }
+
+ // zero buffer once used
+ for _, v := range buf {
+ for j := range v {
+ v[j] = 0
+ }
+ }
+ }
+
+ sort.Strings(best)
+ return best
+}
diff --git a/levenshtein_test.go b/levenshtein_test.go
new file mode 100644
index 0000000..60f0293
--- /dev/null
+++ b/levenshtein_test.go
@@ -0,0 +1,54 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package blueprint
+
+import (
+ "reflect"
+ "testing"
+)
+
+func mods(mods []string) []*moduleGroup {
+ ret := []*moduleGroup{}
+
+ for _, v := range mods {
+ m := moduleGroup{name: v}
+ ret = append(ret, &m)
+ }
+
+ return ret
+}
+
+func assertEqual(t *testing.T, a, b []string) {
+ if len(a) == 0 && len(b) == 0 {
+ return
+ }
+
+ if !reflect.DeepEqual(a, b) {
+ t.Errorf("Expected the following to be equal:\n\t%q\n\t%q", a, b)
+ }
+}
+
+func TestLevenshteinWontGuessUnlike(t *testing.T) {
+ assertEqual(t, namesLike("a", "test", mods([]string{"test"})), []string{})
+}
+func TestLevenshteinInsert(t *testing.T) {
+ assertEqual(t, namesLike("a", "test", mods([]string{"ab", "ac", "not_this"})), []string{"ab", "ac"})
+}
+func TestLevenshteinDelete(t *testing.T) {
+ assertEqual(t, namesLike("ab", "test", mods([]string{"a", "b", "not_this"})), []string{"a", "b"})
+}
+func TestLevenshteinReplace(t *testing.T) {
+ assertEqual(t, namesLike("aa", "test", mods([]string{"ab", "ac", "not_this"})), []string{"ab", "ac"})
+}
diff --git a/live_tracker.go b/live_tracker.go
index 1d48e58..ef9c8a9 100644
--- a/live_tracker.go
+++ b/live_tracker.go
@@ -23,14 +23,16 @@
type liveTracker struct {
sync.Mutex
config interface{} // Used to evaluate variable, rule, and pool values.
+ ctx *Context // Used to evaluate globs
variables map[Variable]ninjaString
pools map[Pool]*poolDef
rules map[Rule]*ruleDef
}
-func newLiveTracker(config interface{}) *liveTracker {
+func newLiveTracker(ctx *Context, config interface{}) *liveTracker {
return &liveTracker{
+ ctx: ctx,
config: config,
variables: make(map[Variable]ninjaString),
pools: make(map[Pool]*poolDef),
@@ -153,7 +155,9 @@
func (l *liveTracker) addVariable(v Variable) error {
_, ok := l.variables[v]
if !ok {
- value, err := v.value(l.config)
+ ctx := &variableFuncContext{l.ctx}
+
+ value, err := v.value(ctx, l.config)
if err == errVariableIsArg {
// This variable is a placeholder for an argument that can be passed
// to a rule. It has no value and thus doesn't reference any other
diff --git a/metrics/Android.bp b/metrics/Android.bp
index 3668668..1e3a6f0 100644
--- a/metrics/Android.bp
+++ b/metrics/Android.bp
@@ -24,4 +24,7 @@
srcs: [
"event_handler.go",
],
+ testSrcs: [
+ "event_handler_test.go",
+ ],
}
diff --git a/metrics/event_handler.go b/metrics/event_handler.go
index c19d039..35a6858 100644
--- a/metrics/event_handler.go
+++ b/metrics/event_handler.go
@@ -31,10 +31,8 @@
scopeStartTimes []time.Time
}
-// _now wraps the time.Now() function. _now is declared for unit testing purpose.
-var _now = func() time.Time {
- return time.Now()
-}
+// _now simply delegates to time.Now() function. _now is declared for unit testing purpose.
+var _now = time.Now
// Event holds the performance metrics data of a single build event.
type Event struct {
@@ -58,15 +56,25 @@
// call to End (though other events may begin and end before this event ends).
// Events within the same scope must have unique names.
func (h *EventHandler) Begin(name string) {
+ if strings.ContainsRune(name, '.') {
+ panic(fmt.Sprintf("illegal event name (avoid dot): %s", name))
+ }
h.scopeIds = append(h.scopeIds, name)
h.scopeStartTimes = append(h.scopeStartTimes, _now())
}
+// Do wraps a function with calls to Begin() and End().
+func (h *EventHandler) Do(name string, f func()) {
+ h.Begin(name)
+ defer h.End(name)
+ f()
+}
+
// End logs the end of an event. All events nested within this event must have
// themselves been marked completed.
func (h *EventHandler) End(name string) {
if len(h.scopeIds) == 0 || name != h.scopeIds[len(h.scopeIds)-1] {
- panic(fmt.Errorf("Unexpected scope end '%s'. Current scope: (%s)",
+ panic(fmt.Errorf("unexpected scope end '%s'. Current scope: (%s)",
name, h.scopeIds))
}
event := Event{
@@ -89,16 +97,16 @@
func (h *EventHandler) CompletedEvents() []Event {
if len(h.scopeIds) > 0 {
panic(fmt.Errorf(
- "Retrieving events before all events have been closed. Current scope: (%s)",
+ "retrieving events before all events have been closed. Current scope: (%s)",
h.scopeIds))
}
// Validate no two events have the same full id.
- ids := map[string]bool{}
+ ids := map[string]struct{}{}
for _, event := range h.completedEvents {
if _, containsId := ids[event.Id]; containsId {
- panic(fmt.Errorf("Duplicate event registered: %s", event.Id))
+ panic(fmt.Errorf("duplicate event registered: %s", event.Id))
}
- ids[event.Id] = true
+ ids[event.Id] = struct{}{}
}
return h.completedEvents
}
diff --git a/metrics/event_handler_test.go b/metrics/event_handler_test.go
new file mode 100644
index 0000000..08a59bd
--- /dev/null
+++ b/metrics/event_handler_test.go
@@ -0,0 +1,100 @@
+// Copyright 2022 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package metrics
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func Map[A any, B any](in []A, f func(A) B) []B {
+ r := make([]B, len(in))
+ for i, a := range in {
+ r[i] = f(a)
+ }
+ return r
+}
+
+func TestEventNameWithDot(t *testing.T) {
+ defer func() {
+ r := fmt.Sprintf("%v", recover())
+ if !strings.HasPrefix(r, "illegal event name") {
+ t.Errorf("The code did not panic in the expected manner: %s", r)
+ }
+ }()
+ eh := EventHandler{}
+ eh.Begin("a.")
+}
+
+func TestEventNesting(t *testing.T) {
+ eh := EventHandler{}
+ eh.Begin("a")
+ eh.Begin("b")
+ eh.End("b")
+ eh.Begin("c")
+ eh.End("c")
+ eh.End("a")
+ expected := []string{"a.b", "a.c", "a"}
+ actual := Map(eh.CompletedEvents(), func(e Event) string {
+ return e.Id
+ })
+ if !reflect.DeepEqual(expected, actual) {
+ t.Errorf("expected: %s actual %s", expected, actual)
+ }
+}
+
+func TestEventOverlap(t *testing.T) {
+ defer func() {
+ r := fmt.Sprintf("%v", recover())
+ if !strings.Contains(r, "unexpected scope end 'a'") {
+ t.Errorf("expected panic but: %s", r)
+ }
+ }()
+ eh := EventHandler{}
+ eh.Begin("a")
+ eh.Begin("b")
+ eh.End("a")
+}
+
+func TestEventDuplication(t *testing.T) {
+ eh := EventHandler{}
+ eh.Begin("a")
+ eh.Begin("b")
+ eh.End("b")
+ eh.Begin("b")
+ eh.End("b")
+ eh.End("a")
+ defer func() {
+ r := fmt.Sprintf("%v", recover())
+ if !strings.HasPrefix(r, "duplicate event") {
+ t.Errorf("expected panic but: %s", r)
+ }
+ }()
+ eh.CompletedEvents()
+}
+
+func TestIncompleteEvent(t *testing.T) {
+ eh := EventHandler{}
+ eh.Begin("a")
+ defer func() {
+ r := fmt.Sprintf("%v", recover())
+ if !strings.HasPrefix(r, "retrieving events before all events have been closed.") {
+ t.Errorf("expected panic but: %s", r)
+ }
+ }()
+ eh.CompletedEvents()
+}
diff --git a/microfactory/microfactory.bash b/microfactory/microfactory.bash
index ded7b33..f037c04 100644
--- a/microfactory/microfactory.bash
+++ b/microfactory/microfactory.bash
@@ -51,6 +51,7 @@
local gen_src_dir="${BUILDDIR}/.microfactory_$(uname)_intermediates/src"
mkdir -p "${gen_src_dir}"
sed "s/^package microfactory/package main/" "${mf_src}/microfactory.go" >"${gen_src_dir}/microfactory.go"
+ printf "\n//for use with go run\nfunc main() { Main() }\n" >>"${gen_src_dir}/microfactory.go"
mf_cmd="${GOROOT}/bin/go run ${gen_src_dir}/microfactory.go"
else
diff --git a/microfactory/microfactory.go b/microfactory/microfactory.go
index a0c9a14..faa0d73 100644
--- a/microfactory/microfactory.go
+++ b/microfactory/microfactory.go
@@ -16,8 +16,8 @@
// to `go install`, but doesn't require a GOPATH. A package->path mapping can
// be specified as command line options:
//
-// -pkg-path android/soong=build/soong
-// -pkg-path github.com/google/blueprint=build/blueprint
+// -pkg-path android/soong=build/soong
+// -pkg-path github.com/google/blueprint=build/blueprint
//
// The paths can be relative to the current working directory, or an absolute
// path. Both packages and paths are compared with full directory names, so the
@@ -617,8 +617,6 @@
return true
}
-// microfactory.bash will make a copy of this file renamed into the main package for use with `go run`
-func main() { Main() }
func Main() {
var output, mybin string
var config Config
diff --git a/module_ctx.go b/module_ctx.go
index 53ee405..a1388b4 100644
--- a/module_ctx.go
+++ b/module_ctx.go
@@ -58,27 +58,27 @@
// that other modules can link against. The library Module might implement the
// following interface:
//
-// type LibraryProducer interface {
-// LibraryFileName() string
-// }
+// type LibraryProducer interface {
+// LibraryFileName() string
+// }
//
-// func IsLibraryProducer(module blueprint.Module) {
-// _, ok := module.(LibraryProducer)
-// return ok
-// }
+// func IsLibraryProducer(module blueprint.Module) {
+// _, ok := module.(LibraryProducer)
+// return ok
+// }
//
// A binary-producing Module that depends on the library Module could then do:
//
-// func (m *myBinaryModule) GenerateBuildActions(ctx blueprint.ModuleContext) {
-// ...
-// var libraryFiles []string
-// ctx.VisitDepsDepthFirstIf(IsLibraryProducer,
-// func(module blueprint.Module) {
-// libProducer := module.(LibraryProducer)
-// libraryFiles = append(libraryFiles, libProducer.LibraryFileName())
-// })
-// ...
-// }
+// func (m *myBinaryModule) GenerateBuildActions(ctx blueprint.ModuleContext) {
+// ...
+// var libraryFiles []string
+// ctx.VisitDepsDepthFirstIf(IsLibraryProducer,
+// func(module blueprint.Module) {
+// libProducer := module.(LibraryProducer)
+// libraryFiles = append(libraryFiles, libProducer.LibraryFileName())
+// })
+// ...
+// }
//
// to build the list of library file names that should be included in its link
// command.
@@ -300,6 +300,9 @@
// There are no guarantees about which variant of the module will be returned.
// Prefer retrieving the module using GetDirectDep or a visit function, when possible, as
// this will guarantee the appropriate module-variant dependency is returned.
+ //
+ // WARNING: This should _only_ be used within the context of bp2build, where variants and
+ // dependencies are not created.
ModuleFromName(name string) (Module, bool)
// OtherModuleDependencyVariantExists returns true if a module with the
@@ -836,7 +839,7 @@
// CreateModule creates a new module by calling the factory method for the specified moduleType, and applies
// the specified property structs to it as if the properties were set in a blueprint file.
- CreateModule(ModuleFactory, ...interface{}) Module
+ CreateModule(ModuleFactory, string, ...interface{}) Module
}
type BottomUpMutatorContext interface {
@@ -991,11 +994,17 @@
}
func (mctx *mutatorContext) CreateVariations(variationNames ...string) []Module {
- return mctx.createVariations(variationNames, false)
+ depChooser := chooseDepInherit(mctx.name, mctx.defaultVariation)
+ return mctx.createVariations(variationNames, depChooser, false)
+}
+
+func (mctx *mutatorContext) createVariationsWithTransition(transition Transition, variationNames ...string) []Module {
+ return mctx.createVariations(variationNames, chooseDepByTransition(mctx.name, transition), false)
}
func (mctx *mutatorContext) CreateLocalVariations(variationNames ...string) []Module {
- return mctx.createVariations(variationNames, true)
+ depChooser := chooseDepInherit(mctx.name, mctx.defaultVariation)
+ return mctx.createVariations(variationNames, depChooser, true)
}
func (mctx *mutatorContext) SetVariationProvider(module Module, provider ProviderKey, value interface{}) {
@@ -1008,9 +1017,9 @@
panic(fmt.Errorf("module %q is not a newly created variant of %q", module, mctx.module))
}
-func (mctx *mutatorContext) createVariations(variationNames []string, local bool) []Module {
+func (mctx *mutatorContext) createVariations(variationNames []string, depChooser depChooser, local bool) []Module {
var ret []Module
- modules, errs := mctx.context.createVariations(mctx.module, mctx.name, mctx.defaultVariation, variationNames, local)
+ modules, errs := mctx.context.createVariations(mctx.module, mctx.name, depChooser, variationNames, local)
if len(errs) > 0 {
mctx.errs = append(mctx.errs, errs...)
}
@@ -1091,8 +1100,13 @@
panic(fmt.Errorf("no %q variation in module variations %q", targetVariationName, foundVariations))
}
+func (mctx *mutatorContext) applyTransition(transition Transition) {
+ mctx.context.convertDepsToVariation(mctx.module, chooseDepByTransition(mctx.name, transition))
+}
+
func (mctx *mutatorContext) SetDependencyVariation(variationName string) {
- mctx.context.convertDepsToVariation(mctx.module, mctx.name, variationName, nil)
+ mctx.context.convertDepsToVariation(mctx.module, chooseDepExplicit(
+ mctx.name, variationName, nil))
}
func (mctx *mutatorContext) SetDefaultDependencyVariation(variationName *string) {
@@ -1201,13 +1215,14 @@
mctx.rename = append(mctx.rename, rename{mctx.module.group, name})
}
-func (mctx *mutatorContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
+func (mctx *mutatorContext) CreateModule(factory ModuleFactory, typeName string, props ...interface{}) Module {
module := newModule(factory)
module.relBlueprintsFile = mctx.module.relBlueprintsFile
module.pos = mctx.module.pos
module.propertyPos = mctx.module.propertyPos
module.createdBy = mctx.module
+ module.typeName = typeName
for _, p := range props {
err := proptools.AppendMatchingProperties(module.properties, p, nil)
diff --git a/name_interface.go b/name_interface.go
index 5e7e16e..db82453 100644
--- a/name_interface.go
+++ b/name_interface.go
@@ -17,6 +17,7 @@
import (
"fmt"
"sort"
+ "strings"
)
// This file exposes the logic of locating a module via a query string, to enable
@@ -54,12 +55,18 @@
// Gets called when a new module is created
NewModule(ctx NamespaceContext, group ModuleGroup, module Module) (namespace Namespace, err []error)
+ // Gets called when a module was pruned from the build tree by SourceRootDirs
+ NewSkippedModule(ctx NamespaceContext, name string, skipInfo SkippedModuleInfo)
+
// Finds the module with the given name
ModuleFromName(moduleName string, namespace Namespace) (group ModuleGroup, found bool)
+ // Finds if the module with the given name was skipped
+ SkippedModuleFromName(moduleName string, namespace Namespace) (skipInfos []SkippedModuleInfo, skipped bool)
+
// Returns an error indicating that the given module could not be found.
// The error contains some diagnostic information about where the dependency can be found.
- MissingDependencyError(depender string, dependerNamespace Namespace, depName string) (err error)
+ MissingDependencyError(depender string, dependerNamespace Namespace, depName string, guess []string) (err error)
// Rename
Rename(oldName string, newName string, namespace Namespace) []error
@@ -88,18 +95,29 @@
return &namespaceContextImpl{moduleInfo.pos.Filename}
}
+func newNamespaceContextFromFilename(filename string) NamespaceContext {
+ return &namespaceContextImpl{filename}
+}
+
func (ctx *namespaceContextImpl) ModulePath() string {
return ctx.modulePath
}
+type SkippedModuleInfo struct {
+ filename string
+ reason string
+}
+
// a SimpleNameInterface just stores all modules in a map based on name
type SimpleNameInterface struct {
- modules map[string]ModuleGroup
+ modules map[string]ModuleGroup
+ skippedModules map[string][]SkippedModuleInfo
}
func NewSimpleNameInterface() *SimpleNameInterface {
return &SimpleNameInterface{
- modules: make(map[string]ModuleGroup),
+ modules: make(map[string]ModuleGroup),
+ skippedModules: make(map[string][]SkippedModuleInfo),
}
}
@@ -118,11 +136,23 @@
return nil, []error{}
}
+func (s *SimpleNameInterface) NewSkippedModule(ctx NamespaceContext, name string, info SkippedModuleInfo) {
+ if name == "" {
+ return
+ }
+ s.skippedModules[name] = append(s.skippedModules[name], info)
+}
+
func (s *SimpleNameInterface) ModuleFromName(moduleName string, namespace Namespace) (group ModuleGroup, found bool) {
group, found = s.modules[moduleName]
return group, found
}
+func (s *SimpleNameInterface) SkippedModuleFromName(moduleName string, namespace Namespace) (skipInfos []SkippedModuleInfo, skipped bool) {
+ skipInfos, skipped = s.skippedModules[moduleName]
+ return
+}
+
func (s *SimpleNameInterface) Rename(oldName string, newName string, namespace Namespace) (errs []error) {
existingGroup, exists := s.modules[newName]
if exists {
@@ -167,8 +197,30 @@
return groups
}
-func (s *SimpleNameInterface) MissingDependencyError(depender string, dependerNamespace Namespace, dependency string) (err error) {
- return fmt.Errorf("%q depends on undefined module %q", depender, dependency)
+func (s *SimpleNameInterface) MissingDependencyError(depender string, dependerNamespace Namespace, dependency string, guess []string) (err error) {
+ skipInfos, skipped := s.SkippedModuleFromName(dependency, dependerNamespace)
+ if skipped {
+ filesFound := make([]string, 0, len(skipInfos))
+ reasons := make([]string, 0, len(skipInfos))
+ for _, info := range skipInfos {
+ filesFound = append(filesFound, info.filename)
+ reasons = append(reasons, info.reason)
+ }
+ return fmt.Errorf(
+ "module %q depends on skipped module %q; %q was defined in files(s) [%v], but was skipped for reason(s) [%v]",
+ depender,
+ dependency,
+ dependency,
+ strings.Join(filesFound, ", "),
+ strings.Join(reasons, "; "),
+ )
+ }
+
+ guessString := ""
+ if len(guess) > 0 {
+ guessString = fmt.Sprintf(" Did you mean %q?", guess)
+ }
+ return fmt.Errorf("%q depends on undefined module %q.%s", depender, dependency, guessString)
}
func (s *SimpleNameInterface) GetNamespace(ctx NamespaceContext) Namespace {
diff --git a/ninja_strings.go b/ninja_strings.go
index 51a167d..9e83a4d 100644
--- a/ninja_strings.go
+++ b/ninja_strings.go
@@ -118,7 +118,7 @@
r := rune(str[i])
state, err = state(parseState, i, r)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("error parsing ninja string %q: %s", str, err)
}
}
@@ -325,11 +325,11 @@
return n.variables
}
-func (l literalNinjaString) Value(pkgNames map[*packageContext]string) string {
+func (l literalNinjaString) Value(_ map[*packageContext]string) string {
return defaultEscaper.Replace(string(l))
}
-func (l literalNinjaString) ValueWithEscaper(w io.StringWriter, pkgNames map[*packageContext]string,
+func (l literalNinjaString) ValueWithEscaper(w io.StringWriter, _ map[*packageContext]string,
escaper *strings.Replacer) {
w.WriteString(escaper.Replace(string(l)))
}
diff --git a/ninja_strings_test.go b/ninja_strings_test.go
index c1e05f7..f400074 100644
--- a/ninja_strings_test.go
+++ b/ninja_strings_test.go
@@ -87,7 +87,7 @@
strs: []string{"$ ", " "},
}, {
input: "foo $ bar",
- err: "invalid character after '$' at byte offset 5",
+ err: `error parsing ninja string "foo $ bar": invalid character after '$' at byte offset 5`,
},
{
input: "foo $",
@@ -95,11 +95,11 @@
},
{
input: "foo ${} bar",
- err: "empty variable name at byte offset 6",
+ err: `error parsing ninja string "foo ${} bar": empty variable name at byte offset 6`,
},
{
input: "foo ${abc!} bar",
- err: "invalid character in variable name at byte offset 9",
+ err: `error parsing ninja string "foo ${abc!} bar": invalid character in variable name at byte offset 9`,
},
{
input: "foo ${abc",
diff --git a/package_ctx.go b/package_ctx.go
index 1eafdb9..f8a7cc4 100644
--- a/package_ctx.go
+++ b/package_ctx.go
@@ -31,35 +31,35 @@
// passed to all calls to define module- or singleton-specific Ninja
// definitions. For example:
//
-// package blah
+// package blah
//
-// import (
-// "blueprint"
-// )
+// import (
+// "blueprint"
+// )
//
-// var (
-// pctx = NewPackageContext("path/to/blah")
+// var (
+// pctx = NewPackageContext("path/to/blah")
//
-// myPrivateVar = pctx.StaticVariable("myPrivateVar", "abcdef")
-// MyExportedVar = pctx.StaticVariable("MyExportedVar", "$myPrivateVar 123456!")
+// myPrivateVar = pctx.StaticVariable("myPrivateVar", "abcdef")
+// MyExportedVar = pctx.StaticVariable("MyExportedVar", "$myPrivateVar 123456!")
//
-// SomeRule = pctx.StaticRule(...)
-// )
+// SomeRule = pctx.StaticRule(...)
+// )
//
-// // ...
+// // ...
//
-// func (m *MyModule) GenerateBuildActions(ctx blueprint.Module) {
-// ctx.Build(pctx, blueprint.BuildParams{
-// Rule: SomeRule,
-// Outputs: []string{"$myPrivateVar"},
-// })
-// }
+// func (m *MyModule) GenerateBuildActions(ctx blueprint.Module) {
+// ctx.Build(pctx, blueprint.BuildParams{
+// Rule: SomeRule,
+// Outputs: []string{"$myPrivateVar"},
+// })
+// }
type PackageContext interface {
Import(pkgPath string)
ImportAs(as, pkgPath string)
StaticVariable(name, value string) Variable
- VariableFunc(name string, f func(config interface{}) (string, error)) Variable
+ VariableFunc(name string, f func(ctx VariableFuncContext, config interface{}) (string, error)) Variable
VariableConfigMethod(name string, method interface{}) Variable
StaticPool(name string, params PoolParams) Pool
@@ -190,25 +190,25 @@
// "${pkg.Variable}", while the imported rules can simply be accessed as
// exported Go variables from the package. For example:
//
-// import (
-// "blueprint"
-// "foo/bar"
-// )
+// import (
+// "blueprint"
+// "foo/bar"
+// )
//
-// var pctx = NewPackagePath("blah")
+// var pctx = NewPackagePath("blah")
//
-// func init() {
-// pctx.Import("foo/bar")
-// }
+// func init() {
+// pctx.Import("foo/bar")
+// }
//
-// ...
+// ...
//
-// func (m *MyModule) GenerateBuildActions(ctx blueprint.Module) {
-// ctx.Build(pctx, blueprint.BuildParams{
-// Rule: bar.SomeRule,
-// Outputs: []string{"${bar.SomeVariable}"},
-// })
-// }
+// func (m *MyModule) GenerateBuildActions(ctx blueprint.Module) {
+// ctx.Build(pctx, blueprint.BuildParams{
+// Rule: bar.SomeRule,
+// Outputs: []string{"${bar.SomeVariable}"},
+// })
+// }
//
// Note that the local name used to refer to the package in Ninja variable names
// is derived from pkgPath by extracting the last path component. This differs
@@ -304,7 +304,7 @@
v.fullName_ = v.fullName(pkgNames)
}
-func (v *staticVariable) value(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)
@@ -320,10 +320,30 @@
type variableFunc struct {
pctx *packageContext
name_ string
- value_ func(interface{}) (string, error)
+ value_ func(VariableFuncContext, interface{}) (string, error)
fullName_ string
}
+// VariableFuncContext is passed to VariableFunc functions.
+type VariableFuncContext interface {
+ // GlobWithDeps returns a list of files and directories that match the
+ // specified pattern but do not match any of the patterns in excludes.
+ // Any directories will have a '/' suffix. It also adds efficient
+ // dependencies to rerun the primary builder whenever a file matching
+ // the pattern as added or removed, without rerunning if a file that
+ // does not match the pattern is added to a searched directory.
+ GlobWithDeps(globPattern string, excludes []string) ([]string, error)
+}
+
+type variableFuncContext struct {
+ context *Context
+}
+
+func (v *variableFuncContext) GlobWithDeps(pattern string,
+ excludes []string) ([]string, error) {
+ return v.context.glob(pattern, excludes)
+}
+
// VariableFunc returns a Variable whose value is determined by a function that
// takes a config object as input and returns either the variable value or an
// error. It may only be called during a Go package's initialization - either
@@ -336,7 +356,7 @@
// reference other Ninja variables that are visible within the calling Go
// package.
func (p *packageContext) VariableFunc(name string,
- f func(config interface{}) (string, error)) Variable {
+ f func(ctx VariableFuncContext, config interface{}) (string, error)) Variable {
checkCalledFromInit()
@@ -382,7 +402,7 @@
methodValue := reflect.ValueOf(method)
validateVariableMethod(name, methodValue)
- fun := func(config interface{}) (string, error) {
+ fun := func(ctx VariableFuncContext, config interface{}) (string, error) {
result := methodValue.Call([]reflect.Value{reflect.ValueOf(config)})
resultStr := result[0].Interface().(string)
return resultStr, nil
@@ -420,8 +440,8 @@
v.fullName_ = v.fullName(pkgNames)
}
-func (v *variableFunc) value(config interface{}) (ninjaString, error) {
- value, err := v.value_(config)
+func (v *variableFunc) value(ctx VariableFuncContext, config interface{}) (ninjaString, error) {
+ value, err := v.value_(ctx, config)
if err != nil {
return nil, err
}
@@ -484,7 +504,7 @@
// Nothing to do, full name is known at initialization.
}
-func (v *argVariable) value(config interface{}) (ninjaString, error) {
+func (v *argVariable) value(ctx VariableFuncContext, config interface{}) (ninjaString, error) {
return nil, errVariableIsArg
}
diff --git a/parser/ast.go b/parser/ast.go
index cb311ee..ea774e6 100644
--- a/parser/ast.go
+++ b/parser/ast.go
@@ -60,6 +60,8 @@
Type string
TypePos scanner.Position
Map
+ //TODO(delmerico) make this a private field once ag/21588220 lands
+ Name__internal_only *string
}
func (m *Module) Copy() *Module {
@@ -86,6 +88,28 @@
func (m *Module) Pos() scanner.Position { return m.TypePos }
func (m *Module) End() scanner.Position { return m.Map.End() }
+func (m *Module) Name() string {
+ if m.Name__internal_only != nil {
+ return *m.Name__internal_only
+ }
+ for _, prop := range m.Properties {
+ if prop.Name == "name" {
+ if stringProp, ok := prop.Value.(*String); ok {
+ name := stringProp.Value
+ m.Name__internal_only = &name
+ } else {
+ name := prop.Value.String()
+ m.Name__internal_only = &name
+ }
+ }
+ }
+ if m.Name__internal_only == nil {
+ name := ""
+ m.Name__internal_only = &name
+ }
+ return *m.Name__internal_only
+}
+
// A Property is a name: value pair within a Map, which may be a top level Module.
type Property struct {
Name string
@@ -107,29 +131,6 @@
func (p *Property) Pos() scanner.Position { return p.NamePos }
func (p *Property) End() scanner.Position { return p.Value.End() }
-// A MapItem is a key: value pair within a Map, corresponding to map type, rather than a struct.
-type MapItem struct {
- ColonPos scanner.Position
- Key *String
- Value Expression
-}
-
-func (m *MapItem) Copy() *MapItem {
- ret := MapItem{
- ColonPos: m.ColonPos,
- Key: m.Key.Copy().(*String),
- Value: m.Value.Copy(),
- }
- return &ret
-}
-
-func (m *MapItem) String() string {
- return fmt.Sprintf("%s@%s: %s", m.Key, m.ColonPos, m.Value)
-}
-
-func (m *MapItem) Pos() scanner.Position { return m.Key.Pos() }
-func (m *MapItem) End() scanner.Position { return m.Value.End() }
-
// An Expression is a Value in a Property or Assignment. It can be a literal (String or Bool), a
// Map, a List, an Operator that combines two expressions of the same type, or a Variable that
// references and Assignment.
@@ -138,7 +139,7 @@
// Copy returns a copy of the Expression that will not affect the original if mutated
Copy() Expression
String() string
- // Type returns the underlying Type enum of the Expression if it were to be evalutated
+ // Type returns the underlying Type enum of the Expression if it were to be evaluated
Type() Type
// Eval returns an expression that is fully evaluated to a simple type (List, Map, String, or
// Bool). It will return the same object for every call to Eval().
@@ -267,7 +268,6 @@
LBracePos scanner.Position
RBracePos scanner.Position
Properties []*Property
- MapItems []*MapItem
}
func (x *Map) Pos() scanner.Position { return x.LBracePos }
@@ -279,36 +279,20 @@
for i := range x.Properties {
ret.Properties[i] = x.Properties[i].Copy()
}
- ret.MapItems = make([]*MapItem, len(x.MapItems))
- for i := range x.MapItems {
- ret.MapItems[i] = x.MapItems[i].Copy()
- }
return &ret
}
func (x *Map) Eval() Expression {
- if len(x.Properties) > 0 && len(x.MapItems) > 0 {
- panic("Cannot support both Properties and MapItems")
- }
return x
}
func (x *Map) String() string {
- var s string
- if len(x.MapItems) > 0 {
- mapStrings := make([]string, len(x.MapItems))
- for i, mapItem := range x.MapItems {
- mapStrings[i] = mapItem.String()
- }
- s = strings.Join(mapStrings, ", ")
- } else {
- propertyStrings := make([]string, len(x.Properties))
- for i, property := range x.Properties {
- propertyStrings[i] = property.String()
- }
- s = strings.Join(propertyStrings, ", ")
+ propertyStrings := make([]string, len(x.Properties))
+ for i, property := range x.Properties {
+ propertyStrings[i] = property.String()
}
- return fmt.Sprintf("@%s-%s{%s}", x.LBracePos, x.RBracePos, s)
+ return fmt.Sprintf("@%s-%s{%s}", x.LBracePos, x.RBracePos,
+ strings.Join(propertyStrings, ", "))
}
func (x *Map) Type() Type { return MapType }
@@ -338,6 +322,42 @@
return found
}
+// MovePropertyContents moves the contents of propertyName into property newLocation
+// If property newLocation doesn't exist, MovePropertyContents renames propertyName as newLocation.
+// Otherwise, MovePropertyContents only supports moving contents that are a List of String.
+func (x *Map) MovePropertyContents(propertyName string, newLocation string) (removed bool) {
+ oldProp, oldFound, _ := x.getPropertyImpl(propertyName)
+ newProp, newFound, _ := x.getPropertyImpl(newLocation)
+
+ // newLoc doesn't exist, simply renaming property
+ if oldFound && !newFound {
+ oldProp.Name = newLocation
+ return oldFound
+ }
+
+ if oldFound {
+ old, oldOk := oldProp.Value.(*List)
+ new, newOk := newProp.Value.(*List)
+ if oldOk && newOk {
+ toBeMoved := make([]string, len(old.Values)) //
+ for i, p := range old.Values {
+ toBeMoved[i] = p.(*String).Value
+ }
+
+ for _, moved := range toBeMoved {
+ RemoveStringFromList(old, moved)
+ AddStringToList(new, moved)
+ }
+ // oldProp should now be empty and needs to be deleted
+ x.RemoveProperty(oldProp.Name)
+ } else {
+ print(`MovePropertyContents currently only supports moving PropertyName
+ with List of Strings into an existing newLocation with List of Strings\n`)
+ }
+ }
+ return oldFound
+}
+
type List struct {
LBracePos scanner.Position
RBracePos scanner.Position
diff --git a/parser/modify.go b/parser/modify.go
index 3051f66..a28fbe6 100644
--- a/parser/modify.go
+++ b/parser/modify.go
@@ -56,6 +56,24 @@
return false
}
+func ReplaceStringsInList(list *List, replacements map[string]string) (replaced bool) {
+ modified := false
+ for i, v := range list.Values {
+ if v.Type() != StringType {
+ panic(fmt.Errorf("expected string in list, got %s", v.Type()))
+ }
+ if sv, ok := v.(*String); ok && replacements[sv.Value] != "" {
+ pos := list.Values[i].Pos()
+ list.Values[i] = &String{
+ LiteralPos: pos,
+ Value: replacements[sv.Value],
+ }
+ modified = true
+ }
+ }
+ return modified
+}
+
// A Patch represents a region of a text buffer to be replaced [Start, End) and its Replacement
type Patch struct {
Start, End int
diff --git a/parser/parser.go b/parser/parser.go
index bb8817e..63a6ac1 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -70,6 +70,7 @@
}
}()
+ p.next()
defs := p.parseDefinitions()
p.accept(scanner.EOF)
errs = p.errors
@@ -100,6 +101,7 @@
func ParseExpression(r io.Reader) (value Expression, errs []error) {
p := newParser(r, NewScope(nil))
+ p.next()
value = p.parseExpression()
p.accept(scanner.EOF)
errs = p.errors
@@ -124,7 +126,6 @@
}
p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanStrings |
scanner.ScanRawStrings | scanner.ScanComments
- p.next()
return p
}
@@ -301,37 +302,6 @@
return
}
-func (p *parser) parseMapItemList() []*MapItem {
- var items []*MapItem
- // this is a map, not a struct, we only know we're at the end if we hit a '}'
- for p.tok != '}' {
- items = append(items, p.parseMapItem())
-
- if p.tok != ',' {
- // There was no comma, so the list is done.
- break
- }
- p.accept(',')
- }
- return items
-}
-
-func (p *parser) parseMapItem() *MapItem {
- keyExpression := p.parseExpression()
- if keyExpression.Type() != StringType {
- p.errorf("only strings are supported as map keys: %s (%s)", keyExpression.Type(), keyExpression.String())
- }
- key := keyExpression.(*String)
- p.accept(':')
- pos := p.scanner.Position
- value := p.parseExpression()
- return &MapItem{
- ColonPos: pos,
- Key: key,
- Value: value,
- }
-}
-
func (p *parser) parseProperty(isModule, compat bool) (property *Property) {
property = new(Property)
@@ -614,15 +584,7 @@
return nil
}
- var properties []*Property
- var mapItems []*MapItem
- // if the next item is an identifier, this is a property
- if p.tok == scanner.Ident {
- properties = p.parsePropertyList(false, false)
- } else {
- // otherwise, we assume that this is a map
- mapItems = p.parseMapItemList()
- }
+ properties := p.parsePropertyList(false, false)
rBracePos := p.scanner.Position
p.accept('}')
@@ -631,7 +593,6 @@
LBracePos: lBracePos,
RBracePos: rBracePos,
Properties: properties,
- MapItems: mapItems,
}
}
diff --git a/parser/parser_test.go b/parser/parser_test.go
index b32581e..b393792 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -194,121 +194,6 @@
nil,
},
- {`
- foo {
- stuff: {
- "key1": 1,
- "key2": 2,
- },
- }
- `,
- []Definition{
- &Module{
- Type: "foo",
- TypePos: mkpos(3, 2, 3),
- Map: Map{
- LBracePos: mkpos(7, 2, 7),
- RBracePos: mkpos(59, 7, 3),
- Properties: []*Property{
- {
- Name: "stuff",
- NamePos: mkpos(12, 3, 4),
- ColonPos: mkpos(17, 3, 9),
- Value: &Map{
- LBracePos: mkpos(19, 3, 11),
- RBracePos: mkpos(54, 6, 4),
- MapItems: []*MapItem{
- &MapItem{
- ColonPos: mkpos(33, 4, 13),
- Key: &String{
- LiteralPos: mkpos(25, 4, 5),
- Value: "key1",
- },
- Value: &Int64{
- LiteralPos: mkpos(33, 4, 13),
- Value: 1,
- Token: "1",
- },
- },
- &MapItem{
- ColonPos: mkpos(48, 5, 13),
- Key: &String{
- LiteralPos: mkpos(40, 5, 5),
- Value: "key2",
- },
- Value: &Int64{
- LiteralPos: mkpos(48, 5, 13),
- Value: 2,
- Token: "2",
- },
- },
- },
- },
- },
- },
- },
- },
- },
- nil,
- },
-
- {`
- foo {
- stuff: {
- "key1": {
- a: "abc",
- },
- },
- }
- `,
- []Definition{
- &Module{
- Type: "foo",
- TypePos: mkpos(3, 2, 3),
- Map: Map{
- LBracePos: mkpos(7, 2, 7),
- RBracePos: mkpos(65, 8, 3),
- Properties: []*Property{
- {
- Name: "stuff",
- NamePos: mkpos(12, 3, 4),
- ColonPos: mkpos(17, 3, 9),
- Value: &Map{
- LBracePos: mkpos(19, 3, 11),
- RBracePos: mkpos(60, 7, 4),
- MapItems: []*MapItem{
- &MapItem{
- ColonPos: mkpos(33, 4, 13),
- Key: &String{
- LiteralPos: mkpos(25, 4, 5),
- Value: "key1",
- },
- Value: &Map{
- LBracePos: mkpos(33, 4, 13),
- RBracePos: mkpos(54, 6, 5),
- Properties: []*Property{
- &Property{
- Name: "a",
- NamePos: mkpos(40, 5, 6),
- ColonPos: mkpos(41, 5, 7),
- Value: &String{
- LiteralPos: mkpos(43, 5, 9),
- Value: "abc",
- },
- },
- },
- },
- },
- },
- },
- },
- },
- },
- },
- },
- nil,
- },
-
{
`
foo {
@@ -1254,7 +1139,7 @@
Comments: []*Comment{
&Comment{
Comment: []string{"/* comment3", " comment4 */"},
- Slash: mkpos(32, 5, 3),
+ Slash: mkpos(32, 5, 3),
},
&Comment{
Comment: []string{"// comment5"},
@@ -1329,27 +1214,34 @@
}
}
-// TODO: Test error strings
-
-func TestMapParserError(t *testing.T) {
- input :=
- `
- foo {
- stuff: {
- 1: "value1",
- 2: "value2",
- },
- }
- `
- expectedErr := `<input>:4:6: only strings are supported as map keys: int64 ('\x01'@<input>:4:5)`
- _, errs := ParseAndEval("", bytes.NewBufferString(input), NewScope(nil))
- if len(errs) == 0 {
- t.Fatalf("Expected errors, got none.")
+func TestParserError(t *testing.T) {
+ testcases := []struct {
+ name string
+ input string
+ err string
+ }{
+ {
+ name: "invalid first token",
+ input: "\x00",
+ err: "invalid character NUL",
+ },
+ // TODO: test more parser errors
}
- for _, err := range errs {
- if expectedErr != err.Error() {
- t.Errorf("Unexpected err: %s", err)
- }
+
+ for _, tt := range testcases {
+ t.Run(tt.name, func(t *testing.T) {
+ r := bytes.NewBufferString(tt.input)
+ _, errs := ParseAndEval("", r, NewScope(nil))
+ if len(errs) == 0 {
+ t.Fatalf("missing expected error")
+ }
+ if g, w := errs[0], tt.err; !strings.Contains(g.Error(), w) {
+ t.Errorf("expected error %q, got %q", w, g)
+ }
+ for _, err := range errs[1:] {
+ t.Errorf("got unexpected extra error %q", err)
+ }
+ })
}
}
diff --git a/pathtools/lists_test.go b/pathtools/lists_test.go
index cce8786..87f24f9 100644
--- a/pathtools/lists_test.go
+++ b/pathtools/lists_test.go
@@ -4,7 +4,7 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/proptools/unpack.go b/proptools/unpack.go
index 28a68b5..e1d733c 100644
--- a/proptools/unpack.go
+++ b/proptools/unpack.go
@@ -27,12 +27,6 @@
const maxUnpackErrors = 10
-var (
- // Hard-coded list of allowlisted property names of type map. This is to limit use of maps to
- // where absolutely necessary.
- validMapProperties = []string{}
-)
-
type UnpackError struct {
Err error
Pos scanner.Position
@@ -51,18 +45,21 @@
// unpackContext keeps compound names and their values in a map. It is initialized from
// parsed properties.
type unpackContext struct {
- propertyMap map[string]*packedProperty
- validMapProperties map[string]bool
- errs []error
+ propertyMap map[string]*packedProperty
+ errs []error
}
// UnpackProperties populates the list of runtime values ("property structs") from the parsed properties.
// If a property a.b.c has a value, a field with the matching name in each runtime value is initialized
// from it. See PropertyNameForField for field and property name matching.
// For instance, if the input contains
-// { foo: "abc", bar: {x: 1},}
+//
+// { foo: "abc", bar: {x: 1},}
+//
// and a runtime value being has been declared as
-// var v struct { Foo string; Bar int }
+//
+// var v struct { Foo string; Bar int }
+//
// then v.Foo will be set to "abc" and v.Bar will be set to 1
// (cf. unpack_test.go for further examples)
//
@@ -74,19 +71,11 @@
// The same property can initialize fields in multiple runtime values. It is an error if any property
// value was not used to initialize at least one field.
func UnpackProperties(properties []*parser.Property, objects ...interface{}) (map[string]*parser.Property, []error) {
- return unpackProperties(properties, validMapProperties, objects...)
-}
-
-func unpackProperties(properties []*parser.Property, validMapProps []string, objects ...interface{}) (map[string]*parser.Property, []error) {
var unpackContext unpackContext
unpackContext.propertyMap = make(map[string]*packedProperty)
if !unpackContext.buildPropertyMap("", properties) {
return nil, unpackContext.errs
}
- unpackContext.validMapProperties = make(map[string]bool, len(validMapProps))
- for _, p := range validMapProps {
- unpackContext.validMapProperties[p] = true
- }
for _, obj := range objects {
valueObject := reflect.ValueOf(obj)
@@ -153,33 +142,7 @@
ctx.propertyMap[name] = &packedProperty{property, false}
switch propValue := property.Value.Eval().(type) {
case *parser.Map:
- // If this is a map and the values are not primitive types, we need to unroll it for further
- // mapping. Keys are limited to string types.
ctx.buildPropertyMap(name, propValue.Properties)
- if len(propValue.MapItems) == 0 {
- continue
- }
- items := propValue.MapItems
- keysType := items[0].Key.Type()
- valsAreBasic := primitiveType(items[0].Value.Type())
- if keysType != parser.StringType {
- ctx.addError(&UnpackError{Err: fmt.Errorf("complex key types are unsupported: %s", keysType)})
- return false
- } else if valsAreBasic {
- continue
- }
- itemProperties := make([]*parser.Property, len(items), len(items))
- for i, item := range items {
- itemProperties[i] = &parser.Property{
- Name: fmt.Sprintf("%s{value:%d}", property.Name, i),
- NamePos: property.NamePos,
- ColonPos: property.ColonPos,
- Value: item.Value,
- }
- }
- if !ctx.buildPropertyMap(prefix, itemProperties) {
- return false
- }
case *parser.List:
// If it is a list, unroll it unless its elements are of primitive type
// (no further mapping will be needed in that case, so we avoid cluttering
@@ -187,7 +150,7 @@
if len(propValue.Values) == 0 {
continue
}
- if primitiveType(propValue.Values[0].Type()) {
+ if t := propValue.Values[0].Type(); t == parser.StringType || t == parser.Int64Type || t == parser.BoolType {
continue
}
@@ -209,11 +172,6 @@
return len(ctx.errs) == nOldErrors
}
-// primitiveType returns whether typ is a primitive type
-func primitiveType(typ parser.Type) bool {
- return typ == parser.StringType || typ == parser.Int64Type || typ == parser.BoolType
-}
-
func fieldPath(prefix, fieldName string) string {
if prefix == "" {
return fieldName
@@ -265,15 +223,6 @@
switch kind := fieldValue.Kind(); kind {
case reflect.Bool, reflect.String, reflect.Struct, reflect.Slice:
// Do nothing
- case reflect.Map:
- // Restrict names of map properties that _can_ be set in bp files
- if _, ok := ctx.validMapProperties[propertyName]; !ok {
- if !HasTag(field, "blueprint", "mutated") {
- ctx.addError(&UnpackError{
- Err: fmt.Errorf("Uses of maps for properties must be allowlisted. %q is an unsupported use case", propertyName),
- })
- }
- }
case reflect.Interface:
if fieldValue.IsNil() {
panic(fmt.Errorf("field %s contains a nil interface", propertyName))
@@ -354,13 +303,6 @@
if len(ctx.errs) >= maxUnpackErrors {
return
}
- } else if fieldValue.Type().Kind() == reflect.Map {
- if unpackedValue, ok := ctx.unpackToMap(propertyName, property, fieldValue.Type()); ok {
- ExtendBasicType(fieldValue, unpackedValue, Append)
- }
- if len(ctx.errs) >= maxUnpackErrors {
- return
- }
} else {
unpackedValue, err := propertyToValue(fieldValue.Type(), property)
@@ -372,61 +314,6 @@
}
}
-// unpackToMap unpacks given parser.property into a go map of type mapType
-func (ctx *unpackContext) unpackToMap(mapName string, property *parser.Property, mapType reflect.Type) (reflect.Value, bool) {
- propValueAsMap, ok := property.Value.Eval().(*parser.Map)
- // Verify this property is a map
- if !ok {
- ctx.addError(&UnpackError{
- fmt.Errorf("can't assign %q value to map property %q", property.Value.Type(), property.Name),
- property.Value.Pos(),
- })
- return reflect.MakeMap(mapType), false
- }
- // And is not a struct
- if len(propValueAsMap.Properties) > 0 {
- ctx.addError(&UnpackError{
- fmt.Errorf("can't assign property to a map (%s) property %q", property.Value.Type(), property.Name),
- property.Value.Pos(),
- })
- return reflect.MakeMap(mapType), false
- }
-
- items := propValueAsMap.MapItems
- m := reflect.MakeMap(mapType)
- if len(items) == 0 {
- return m, true
- }
- keyConstructor := ctx.itemConstructor(items[0].Key.Type())
- keyType := mapType.Key()
- valueConstructor := ctx.itemConstructor(items[0].Value.Type())
- valueType := mapType.Elem()
-
- itemProperty := &parser.Property{NamePos: property.NamePos, ColonPos: property.ColonPos}
- for i, item := range items {
- itemProperty.Name = fmt.Sprintf("%s{key:%d}", mapName, i)
- itemProperty.Value = item.Key
- if packedProperty, ok := ctx.propertyMap[itemProperty.Name]; ok {
- packedProperty.used = true
- }
- keyValue, ok := itemValue(keyConstructor, itemProperty, keyType)
- if !ok {
- continue
- }
- itemProperty.Name = fmt.Sprintf("%s{value:%d}", mapName, i)
- itemProperty.Value = item.Value
- if packedProperty, ok := ctx.propertyMap[itemProperty.Name]; ok {
- packedProperty.used = true
- }
- value, ok := itemValue(valueConstructor, itemProperty, valueType)
- if ok {
- m.SetMapIndex(keyValue, value)
- }
- }
-
- return m, true
-}
-
// unpackSlice creates a value of a given slice type from the property which should be a list
func (ctx *unpackContext) unpackToSlice(
sliceName string, property *parser.Property, sliceType reflect.Type) (reflect.Value, bool) {
@@ -445,50 +332,11 @@
return value, true
}
- itemConstructor := ctx.itemConstructor(exprs[0].Type())
- itemType := sliceType.Elem()
-
- itemProperty := &parser.Property{NamePos: property.NamePos, ColonPos: property.ColonPos}
- for i, expr := range exprs {
- itemProperty.Name = sliceName + "[" + strconv.Itoa(i) + "]"
- itemProperty.Value = expr
- if packedProperty, ok := ctx.propertyMap[itemProperty.Name]; ok {
- packedProperty.used = true
- }
- if itemValue, ok := itemValue(itemConstructor, itemProperty, itemType); ok {
- value = reflect.Append(value, itemValue)
- }
- }
- return value, true
-}
-
-// constructItem is a function to construct a reflect.Value from given parser.Property of reflect.Type
-type constructItem func(*parser.Property, reflect.Type) (reflect.Value, bool)
-
-// itemValue creates a new item of type t with value determined by f
-func itemValue(f constructItem, property *parser.Property, t reflect.Type) (reflect.Value, bool) {
- isPtr := t.Kind() == reflect.Ptr
- if isPtr {
- t = t.Elem()
- }
- val, ok := f(property, t)
- if !ok {
- return val, ok
- }
- if isPtr {
- ptrValue := reflect.New(val.Type())
- ptrValue.Elem().Set(val)
- return ptrValue, true
- }
- return val, true
-}
-
-// itemConstructor returns a function to construct an item of typ
-func (ctx *unpackContext) itemConstructor(typ parser.Type) constructItem {
// The function to construct an item value depends on the type of list elements.
- switch typ {
+ var getItemFunc func(*parser.Property, reflect.Type) (reflect.Value, bool)
+ switch exprs[0].Type() {
case parser.BoolType, parser.StringType, parser.Int64Type:
- return func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
+ getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
value, err := propertyToValue(t, property)
if err != nil {
ctx.addError(err)
@@ -497,26 +345,46 @@
return value, true
}
case parser.ListType:
- return func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
+ getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
return ctx.unpackToSlice(property.Name, property, t)
}
case parser.MapType:
- return func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
- if t.Kind() == reflect.Map {
- return ctx.unpackToMap(property.Name, property, t)
- } else {
- itemValue := reflect.New(t).Elem()
- ctx.unpackToStruct(property.Name, itemValue)
- return itemValue, true
- }
+ getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
+ itemValue := reflect.New(t).Elem()
+ ctx.unpackToStruct(property.Name, itemValue)
+ return itemValue, true
}
case parser.NotEvaluatedType:
- return func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
+ getItemFunc = func(property *parser.Property, t reflect.Type) (reflect.Value, bool) {
return reflect.New(t), false
}
default:
- panic(fmt.Errorf("bizarre property expression type: %v", typ))
+ panic(fmt.Errorf("bizarre property expression type: %v", exprs[0].Type()))
}
+
+ itemProperty := &parser.Property{NamePos: property.NamePos, ColonPos: property.ColonPos}
+ elemType := sliceType.Elem()
+ isPtr := elemType.Kind() == reflect.Ptr
+
+ for i, expr := range exprs {
+ itemProperty.Name = sliceName + "[" + strconv.Itoa(i) + "]"
+ itemProperty.Value = expr
+ if packedProperty, ok := ctx.propertyMap[itemProperty.Name]; ok {
+ packedProperty.used = true
+ }
+ if isPtr {
+ if itemValue, ok := getItemFunc(itemProperty, elemType.Elem()); ok {
+ ptrValue := reflect.New(itemValue.Type())
+ ptrValue.Elem().Set(itemValue)
+ value = reflect.Append(value, ptrValue)
+ }
+ } else {
+ if itemValue, ok := getItemFunc(itemProperty, elemType); ok {
+ value = reflect.Append(value, itemValue)
+ }
+ }
+ }
+ return value, true
}
// propertyToValue creates a value of a given value type from the property.
diff --git a/proptools/unpack_test.go b/proptools/unpack_test.go
index 5c6e3d0..7e2751d 100644
--- a/proptools/unpack_test.go
+++ b/proptools/unpack_test.go
@@ -129,82 +129,6 @@
},
{
- name: "map",
- input: `
- m {
- stuff: { "asdf": "jkl;", "qwert": "uiop"},
- empty: {},
- nested: {
- other_stuff: {},
- },
- }
- `,
- output: []interface{}{
- &struct {
- Stuff map[string]string
- Empty map[string]string
- Nil map[string]string
- NonString map[string]struct{ S string } `blueprint:"mutated"`
- Nested struct {
- Other_stuff map[string]string
- }
- }{
- Stuff: map[string]string{"asdf": "jkl;", "qwert": "uiop"},
- Empty: map[string]string{},
- Nil: nil,
- NonString: nil,
- Nested: struct{ Other_stuff map[string]string }{
- Other_stuff: map[string]string{},
- },
- },
- },
- },
-
- {
- name: "map with slice",
- input: `
- m {
- stuff: { "asdf": ["jkl;"], "qwert": []},
- empty: {},
- }
- `,
- output: []interface{}{
- &struct {
- Stuff map[string][]string
- Empty map[string][]string
- Nil map[string][]string
- NonString map[string]struct{ S string } `blueprint:"mutated"`
- }{
- Stuff: map[string][]string{"asdf": []string{"jkl;"}, "qwert": []string{}},
- Empty: map[string][]string{},
- Nil: nil,
- NonString: nil,
- },
- },
- },
-
- {
- name: "map with struct",
- input: `
- m {
- stuff: { "asdf": {s:"a"}},
- empty: {},
- }
- `,
- output: []interface{}{
- &struct {
- Stuff map[string]struct{ S string }
- Empty map[string]struct{ S string }
- Nil map[string]struct{ S string }
- }{
- Stuff: map[string]struct{ S string }{"asdf": struct{ S string }{"a"}},
- Empty: map[string]struct{ S string }{},
- Nil: nil,
- },
- },
- },
-
- {
name: "double nested",
input: `
m {
@@ -831,7 +755,7 @@
}
}
- _, errs = unpackProperties(module.Properties, []string{"stuff", "empty", "nil", "nested.other_stuff"}, output...)
+ _, errs = UnpackProperties(module.Properties, output...)
if len(errs) != 0 && len(testCase.errs) == 0 {
t.Errorf("test case: %s", testCase.input)
t.Errorf("unexpected unpack errors:")
@@ -1038,37 +962,6 @@
`<input>:3:16: can't assign string value to list property "map_list"`,
},
},
- {
- name: "invalid use of maps",
- input: `
- m {
- map: {"foo": "bar"},
- }
- `,
- output: []interface{}{
- &struct {
- Map map[string]string
- }{},
- },
- errors: []string{
- `<input>: Uses of maps for properties must be allowlisted. "map" is an unsupported use case`,
- },
- },
- {
- name: "invalid use of maps, not used in bp file",
- input: `
- m {
- }
- `,
- output: []interface{}{
- &struct {
- Map map[string]string
- }{},
- },
- errors: []string{
- `<input>: Uses of maps for properties must be allowlisted. "map" is an unsupported use case`,
- },
- },
}
for _, testCase := range testCases {
diff --git a/scope.go b/scope.go
index 3f39eb7..9585559 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(config interface{}) (ninjaString, error)
+ value(ctx VariableFuncContext, config interface{}) (ninjaString, error)
String() string
}
@@ -373,7 +373,7 @@
// Nothing to do, full name is known at initialization.
}
-func (l *localVariable) value(interface{}) (ninjaString, error) {
+func (l *localVariable) value(VariableFuncContext, interface{}) (ninjaString, error) {
return l.value_, nil
}
diff --git a/visit_test.go b/visit_test.go
index 798e289..34e67d1 100644
--- a/visit_test.go
+++ b/visit_test.go
@@ -74,18 +74,18 @@
}
}
-// A
-// |
-// B
-// |\
-// C \
-// \|
-// D
-// |
-// E
-// / \
-// \ /
-// F
+// > A
+// > |
+// > B
+// > |\
+// > C \
+// > \|
+// > D
+// > |
+// > E
+// > / \
+// > \ /
+// > F
func setupVisitTest(t *testing.T) *Context {
ctx := NewContext()
ctx.RegisterModuleType("visit_module", newVisitModule)
@@ -98,22 +98,22 @@
name: "A",
visit: ["B"],
}
-
+
visit_module {
name: "B",
visit: ["C", "D"],
}
-
+
visit_module {
name: "C",
visit: ["D"],
}
-
+
visit_module {
name: "D",
visit: ["E"],
}
-
+
visit_module {
name: "E",
visit: ["F", "F"],