Snap for 9550355 from c5baf11a2d863555db3a0054cc8f0782f86237bd to sdk-release
Change-Id: If6784b2328c8299e71d1550ca013b1073f3ff0d7
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..e0a6e06 100644
--- a/bootstrap/bootstrap.go
+++ b/bootstrap/bootstrap.go
@@ -149,7 +149,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 +693,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 3342bbe..0ddd110 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,60 +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
}
if ctx.BeforePrepareBuildActionsHook != nil {
- err := ctx.BeforePrepareBuildActionsHook()
- if err != nil {
+ if err := ctx.BeforePrepareBuildActionsHook(); err != nil {
fatalErrors([]error{err})
}
}
- extraDeps, errs = ctx.PrepareBuildActions(config)
- if len(errs) > 0 {
+ if buildActionsDeps, errs := ctx.PrepareBuildActions(config); len(errs) > 0 {
fatalErrors(errs)
+ } else {
+ ninjaDeps = append(ninjaDeps, buildActionsDeps...)
}
- ninjaDeps = append(ninjaDeps, extraDeps...)
if stopBefore == StopBeforeWriteNinja {
return ninjaDeps
@@ -138,37 +136,34 @@
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)
+ 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)
}
buf = bufio.NewWriterSize(f, 16*1024*1024)
out = buf
} else {
- out = ioutil.Discard.(io.StringWriter)
+ out = io.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 42de70e..1df808e 100644
--- a/bpmodify/bpmodify.go
+++ b/bpmodify/bpmodify.go
@@ -8,7 +8,6 @@
"bytes"
"flag"
"fmt"
- "github.com/google/blueprint/parser"
"io"
"io/ioutil"
"os"
@@ -17,6 +16,8 @@
"strings"
"syscall"
"unicode"
+
+ "github.com/google/blueprint/parser"
)
var (
@@ -34,6 +35,7 @@
newLocation string
setString *string
addLiteral *string
+ setBool *string
replaceProperty = new(replacements)
)
@@ -43,10 +45,11 @@
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(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
}
@@ -154,6 +157,9 @@
} else if setString != nil {
// We setting a non-existent string property, so we need to create it first.
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,
@@ -278,6 +284,21 @@
}
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 {
@@ -301,7 +322,8 @@
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 {
@@ -344,7 +366,7 @@
return
}
- if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 && setString == nil && addLiteral == nil && !*removeProperty && !*moveProperty && (*replaceProperty).size() == 0 {
+ 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
}
@@ -374,6 +396,7 @@
}
}
}
+
func diff(b1, b2 []byte) (data []byte, err error) {
f1, err := ioutil.TempFile("", "bpfmt")
if err != nil {
@@ -438,7 +461,7 @@
m.oldNameToNewName = make(map[string]string)
for i := 0; i < length; i++ {
- pair := strings.SplitN(pairs[i], ":", 2)
+ pair := strings.SplitN(pairs[i], "=", 2)
if len(pair) != 2 {
return fmt.Errorf("Invalid replacement pair %s", pairs[i])
}
diff --git a/bpmodify/bpmodify_test.go b/bpmodify/bpmodify_test.go
index 4f37251..7bd8b57 100644
--- a/bpmodify/bpmodify_test.go
+++ b/bpmodify/bpmodify_test.go
@@ -14,10 +14,11 @@
package main
import (
- "github.com/google/blueprint/parser"
- "github.com/google/blueprint/proptools"
"strings"
"testing"
+
+ "github.com/google/blueprint/parser"
+ "github.com/google/blueprint/proptools"
)
var testCases = []struct {
@@ -29,6 +30,7 @@
removeSet string
addLiteral *string
setString *string
+ setBool *string
removeProperty bool
replaceProperty string
moveProperty bool
@@ -307,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 {
@@ -373,7 +408,7 @@
],
}
`,
- replaceProperty: "baz:baz_lib,foobar:foobar_lib",
+ replaceProperty: "baz=baz_lib,foobar=foobar_lib",
}, {
name: "replace property multiple modules",
property: "deps,required",
@@ -396,7 +431,7 @@
required: ["foobar_lib"],
}
`,
- replaceProperty: "baz:baz_lib,foobar:foobar_lib",
+ replaceProperty: "baz=baz_lib,foobar=foobar_lib",
}, {
name: "replace property string value",
property: "name",
@@ -416,7 +451,7 @@
required: ["foobar"],
}
`,
- replaceProperty: "foo:foo_lib",
+ replaceProperty: "foo=foo_lib",
}, {
name: "replace property string and list values",
property: "name,deps",
@@ -436,7 +471,7 @@
required: ["foobar"],
}
`,
- replaceProperty: "foo:foo_lib,baz:baz_lib",
+ replaceProperty: "foo=foo_lib,baz=baz_lib",
}, {
name: "move contents of property into non-existing property",
input: `
@@ -476,6 +511,26 @@
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",
},
}
@@ -496,6 +551,7 @@
moveProperty = &testCase.moveProperty
newLocation = testCase.newLocation
setString = testCase.setString
+ setBool = testCase.setBool
addLiteral = testCase.addLiteral
replaceProperty.Set(testCase.replaceProperty)
@@ -536,7 +592,7 @@
}
func TestReplacementsCycleError(t *testing.T) {
- cycleString := "old1:new1,new1:old1"
+ cycleString := "old1=new1,new1=old1"
err := replaceProperty.Set(cycleString)
if err.Error() != "Duplicated replacement name new1" {
@@ -550,7 +606,7 @@
}
func TestReplacementsDuplicatedError(t *testing.T) {
- cycleString := "a:b,a:c"
+ cycleString := "a=b,a=c"
err := replaceProperty.Set(cycleString)
if err.Error() != "Duplicated replacement name a" {
@@ -564,7 +620,7 @@
}
func TestReplacementsMultipleReplacedToSame(t *testing.T) {
- cycleString := "a:c,d:c"
+ cycleString := "a=c,d=c"
err := replaceProperty.Set(cycleString)
if err.Error() != "Duplicated replacement name c" {
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 5448f1e..a7f1013 100644
--- a/context.go
+++ b/context.go
@@ -137,6 +137,31 @@
// 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
+}
+
+// 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
@@ -399,6 +424,7 @@
globs: make(map[globKey]pathtools.GlobResult),
fs: pathtools.OsFs,
finishedMutators: make(map[*mutatorInfo]bool),
+ includeTags: &IncludeTags{},
outDir: nil,
requiredNinjaMajor: 1,
requiredNinjaMinor: 7,
@@ -940,6 +966,33 @@
return c.ParseFileList(baseDir, pathsToParse, config)
}
+// 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) (bool, []error) {
+ for _, def := range file.Defs {
+ switch def := def.(type) {
+ case *parser.Module:
+ 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 true, errs
+ }
+ logicModule, _ := c.cloneLogicModule(module)
+ pi := logicModule.(*PackageIncludes)
+ return pi.MatchesIncludeTags(c), []error{}
+ }
+ }
+ return true, []error{}
+}
+
+
func (c *Context) ParseFileList(rootDir string, filePaths []string,
config interface{}) (deps []string, errs []error) {
@@ -992,6 +1045,15 @@
}
return nil
}
+ shouldVisit, errs := shouldVisitFile(c, file)
+ if len(errs) > 0 {
+ atomic.AddUint32(&numErrs, uint32(len(errs)))
+ errsCh <- errs
+ }
+ if !shouldVisit {
+ // TODO: Write a file that lists the skipped bp files
+ return
+ }
for _, def := range file.Defs {
switch def := def.(type) {
@@ -1798,7 +1860,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 {
@@ -4413,7 +4475,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)
}
@@ -4540,3 +4602,49 @@
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 6b9b599..777cfda 100644
--- a/context_test.go
+++ b/context_test.go
@@ -18,6 +18,7 @@
"bytes"
"errors"
"fmt"
+ "path/filepath"
"reflect"
"strings"
"sync"
@@ -1084,3 +1085,73 @@
}
})
}
+
+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 {
+ 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 != "" {
+ continue // 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))
+ }
+ }
+
+}
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/event_handler.go b/metrics/event_handler.go
index f6bcaf1..3fd0f37 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 {
@@ -100,12 +98,12 @@
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))
}
- ids[event.Id] = true
+ ids[event.Id] = struct{}{}
}
return h.completedEvents
}
diff --git a/ninja_strings.go b/ninja_strings.go
index 51a167d..0b783c3 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)
}
}
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..07b7a9c 100644
--- a/package_ctx.go
+++ b/package_ctx.go
@@ -59,7 +59,7 @@
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
@@ -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/parser.go b/parser/parser.go
index df694c5..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
}
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 4cc4e1b..b393792 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -1139,7 +1139,7 @@
Comments: []*Comment{
&Comment{
Comment: []string{"/* comment3", " comment4 */"},
- Slash: mkpos(32, 5, 3),
+ Slash: mkpos(32, 5, 3),
},
&Comment{
Comment: []string{"// comment5"},
@@ -1214,7 +1214,36 @@
}
}
-// TODO: Test error strings
+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 _, 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)
+ }
+ })
+ }
+}
func TestParserEndPos(t *testing.T) {
in := `
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
}