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"],