Snap for 10453563 from bbbced136fa7dc2b3bc0df7c3b08f6206cb72356 to mainline-configinfrastructure-release
Change-Id: Ie625dcbca95b1dad7e48f74c9af734d6d78e1b17
diff --git a/Android.bp b/Android.bp
index 547d610..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",
diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go
index e0a6e06..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",
diff --git a/bootstrap/command.go b/bootstrap/command.go
index 46f6564..3c41cce 100644
--- a/bootstrap/command.go
+++ b/bootstrap/command.go
@@ -139,9 +139,8 @@
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 {
+ 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)
@@ -149,8 +148,6 @@
defer f.Close()
buf = bufio.NewWriterSize(f, 16*1024*1024)
out = buf
- } else {
- out = io.Discard.(io.StringWriter)
}
if err := ctx.WriteBuildFile(out); err != nil {
diff --git a/context.go b/context.go
index 6f27f0e..17daa8a 100644
--- a/context.go
+++ b/context.go
@@ -17,6 +17,8 @@
import (
"bytes"
"context"
+ "crypto/sha256"
+ "encoding/base64"
"encoding/json"
"errors"
"fmt"
@@ -140,6 +142,45 @@
// 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
@@ -425,6 +466,7 @@
fs: pathtools.OsFs,
finishedMutators: make(map[*mutatorInfo]bool),
includeTags: &IncludeTags{},
+ sourceRootDirs: &SourceRootDirs{},
outDir: nil,
requiredNinjaMajor: 1,
requiredNinjaMinor: 7,
@@ -512,7 +554,7 @@
// 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
}
@@ -533,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))
}
}
@@ -555,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))
}
}
@@ -608,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))
}
}
@@ -635,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))
}
}
@@ -966,15 +1008,25 @@
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) (bool, []error) {
+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
}
@@ -982,14 +1034,43 @@
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
+ return shouldVisitFileInfo{
+ shouldVisitFile: true,
+ errs: errs,
+ }
}
logicModule, _ := c.cloneLogicModule(module)
- pi := logicModule.(*PackageIncludes)
- return pi.MatchesIncludeTags(c), []error{}
+ blueprintPackageIncludes = logicModule.(*PackageIncludes)
}
}
- return true, []error{}
+
+ 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,
@@ -1007,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
@@ -1044,12 +1131,17 @@
}
return nil
}
- shouldVisit, errs := shouldVisitFile(c, file)
+ shouldVisitInfo := shouldVisitFile(c, file)
+ errs := shouldVisitInfo.errs
if len(errs) > 0 {
atomic.AddUint32(&numErrs, uint32(len(errs)))
errsCh <- errs
}
- if !shouldVisit {
+ if !shouldVisitInfo.shouldVisitFile {
+ skipCh <- newSkipInfo{
+ file: file.Name,
+ shouldVisitFileInfo: shouldVisitInfo,
+ }
// TODO: Write a file that lists the skipped bp files
return
}
@@ -1106,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,
+ })
+ }
}
}
@@ -2579,6 +2679,7 @@
type jsonModuleName struct {
Name string
+ Variant string
Variations jsonVariations
DependencyVariations jsonVariations
}
@@ -2614,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),
}
@@ -2661,7 +2763,8 @@
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,
@@ -2703,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)
@@ -3488,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,
@@ -3757,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
}
}
@@ -4001,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 {
@@ -4334,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)
}
@@ -4347,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 {
@@ -4373,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
}
}
@@ -4403,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 {
@@ -4472,6 +4587,102 @@
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 {
@@ -4595,7 +4806,7 @@
`
-var moduleHeaderTemplate = `# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+var moduleHeaderTemplate = `# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
Module: {{.name}}
Variant: {{.variant}}
Type: {{.typeName}}
@@ -4603,7 +4814,7 @@
Defined: {{.pos}}
`
-var singletonHeaderTemplate = `# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+var singletonHeaderTemplate = `# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
Singleton: {{.name}}
Factory: {{.goFactory}}
`
diff --git a/context_test.go b/context_test.go
index 777cfda..1a1fb0d 100644
--- a/context_test.go
+++ b/context_test.go
@@ -174,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{
@@ -187,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",
}
@@ -249,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{
@@ -330,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{
@@ -589,7 +589,7 @@
foo_module {
name: "A",
}
-
+
bar_module {
deps: ["A"],
}
@@ -1107,51 +1107,458 @@
"dir1/Android.bp": []byte(dir1_foo_bp),
"dir2/Android.bp": []byte(dir2_foo_bp),
}
- testCases := []struct{
- desc string
+ testCases := []struct {
+ desc string
includeTags []string
expectedDir string
expectedErr string
}{
{
- desc: "use_dir1 is set, use dir1 foo",
+ desc: "use_dir1 is set, use dir1 foo",
includeTags: []string{"use_dir1"},
expectedDir: "dir1",
},
{
- desc: "use_dir2 is set, use dir2 foo",
+ 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",
+ 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))
- }
+ 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/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 3fd0f37..35a6858 100644
--- a/metrics/event_handler.go
+++ b/metrics/event_handler.go
@@ -56,6 +56,9 @@
// 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())
}
@@ -71,7 +74,7 @@
// 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{
@@ -94,14 +97,14 @@
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]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] = struct{}{}
}
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.go b/microfactory/microfactory.go
index a3c50db..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
diff --git a/module_ctx.go b/module_ctx.go
index f2a04c8..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
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 0b783c3..9e83a4d 100644
--- a/ninja_strings.go
+++ b/ninja_strings.go
@@ -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/package_ctx.go b/package_ctx.go
index 07b7a9c..f8a7cc4 100644
--- a/package_ctx.go
+++ b/package_ctx.go
@@ -31,29 +31,29 @@
// 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)
@@ -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
diff --git a/parser/ast.go b/parser/ast.go
index fee2ec2..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
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 f6d9e95..e1d733c 100644
--- a/proptools/unpack.go
+++ b/proptools/unpack.go
@@ -53,9 +53,13 @@
// 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)
//
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"],