From 088e29ed38757e288ad26c7365e3eb6ee92a6f35 Mon Sep 17 00:00:00 2001 From: Jeff Gaston Date: Wed, 29 Nov 2017 16:47:17 -0800 Subject: [PATCH 1/4] Revert "Revert "Soong support for namespaces"" This mostly reverts commit 178d5fefc0cea9d0f031c0bdee125b9d960f32c3 and mostly reapplies change I6d3e52ef62c4cabe85b9a135a54de0e1a6aab29c . Bug: 65683273 Test: build/soong/scripts/diff_build_graphs.sh \ --products=aosp_arm \ 'build/blueprint:work^ build/soong:work^' \ 'build/blueprint:work build/soong:work' # and see that the only changes were: # 1. adding some new files # 2. changing some line numbers Test: m -j nothing # which runs unit tests Change-Id: I32baae00277a547fdcdd1c2219fe6625ee0e45d7 --- Android.bp | 2 + android/androidmk.go | 6 + android/module.go | 15 +- android/mutator.go | 1 + android/namespace.go | 398 +++++++++++++++++++++++ android/namespace_test.go | 652 ++++++++++++++++++++++++++++++++++++++ android/testing.go | 9 +- android/variable.go | 2 + cmd/soong_build/main.go | 19 +- ui/build/dumpvars.go | 1 + 10 files changed, 1099 insertions(+), 6 deletions(-) create mode 100644 android/namespace.go create mode 100644 android/namespace_test.go diff --git a/Android.bp b/Android.bp index a296da1a7..3e09add4a 100644 --- a/Android.bp +++ b/Android.bp @@ -50,6 +50,7 @@ bootstrap_go_package { "android/makevars.go", "android/module.go", "android/mutator.go", + "android/namespace.go", "android/onceper.go", "android/package_ctx.go", "android/paths.go", @@ -67,6 +68,7 @@ bootstrap_go_package { testSrcs: [ "android/config_test.go", "android/expand_test.go", + "android/namespace_test.go", "android/paths_test.go", "android/prebuilt_test.go", "android/util_test.go", diff --git a/android/androidmk.go b/android/androidmk.go index d88ba8fa5..12aa5fa8a 100644 --- a/android/androidmk.go +++ b/android/androidmk.go @@ -157,6 +157,12 @@ func translateAndroidMkModule(ctx SingletonContext, w io.Writer, mod blueprint.M return nil } + if !amod.commonProperties.NamespaceExportedToMake { + // TODO(jeffrygaston) do we want to validate that there are no modules being + // exported to Kati that depend on this module? + return nil + } + data := provider.AndroidMk() if data.Include == "" { diff --git a/android/module.go b/android/module.go index c72848753..e6766a346 100644 --- a/android/module.go +++ b/android/module.go @@ -151,6 +151,7 @@ type ModuleContext interface { VisitAllModuleVariants(visit func(Module)) GetMissingDependencies() []string + Namespace() blueprint.Namespace } type Module interface { @@ -235,6 +236,8 @@ type commonProperties struct { ArchSpecific bool `blueprint:"mutated"` SkipInstall bool `blueprint:"mutated"` + + NamespaceExportedToMake bool `blueprint:"mutated"` } type hostAndDeviceProperties struct { @@ -500,8 +503,13 @@ func (a *ModuleBase) generateModuleTarget(ctx ModuleContext) { var deps Paths + namespacePrefix := ctx.Namespace().(*Namespace).id + if namespacePrefix != "" { + namespacePrefix = namespacePrefix + "-" + } + if len(allInstalledFiles) > 0 { - name := PathForPhony(ctx, ctx.ModuleName()+"-install") + name := PathForPhony(ctx, namespacePrefix+ctx.ModuleName()+"-install") ctx.Build(pctx, BuildParams{ Rule: blueprint.Phony, Output: name, @@ -513,7 +521,7 @@ func (a *ModuleBase) generateModuleTarget(ctx ModuleContext) { } if len(allCheckbuildFiles) > 0 { - name := PathForPhony(ctx, ctx.ModuleName()+"-checkbuild") + name := PathForPhony(ctx, namespacePrefix+ctx.ModuleName()+"-checkbuild") ctx.Build(pctx, BuildParams{ Rule: blueprint.Phony, Output: name, @@ -529,9 +537,10 @@ func (a *ModuleBase) generateModuleTarget(ctx ModuleContext) { suffix = "-soong" } + name := PathForPhony(ctx, namespacePrefix+ctx.ModuleName()+suffix) ctx.Build(pctx, BuildParams{ Rule: blueprint.Phony, - Output: PathForPhony(ctx, ctx.ModuleName()+suffix), + Outputs: []WritablePath{name}, Implicits: deps, }) diff --git a/android/mutator.go b/android/mutator.go index db3eaa376..876d16176 100644 --- a/android/mutator.go +++ b/android/mutator.go @@ -86,6 +86,7 @@ func registerArchMutator(ctx RegisterMutatorsContext) { } var preDeps = []RegisterMutatorFunc{ + RegisterNamespaceMutator, registerArchMutator, } diff --git a/android/namespace.go b/android/namespace.go new file mode 100644 index 000000000..a2ff1a692 --- /dev/null +++ b/android/namespace.go @@ -0,0 +1,398 @@ +// Copyright 2017 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 android + +import ( + "fmt" + "path/filepath" + "sort" + "strconv" + "strings" + "sync" + "sync/atomic" + + "github.com/google/blueprint" +) + +// This file implements namespaces +const ( + namespacePrefix = "//" + modulePrefix = ":" +) + +func init() { + RegisterModuleType("soong_namespace", NamespaceFactory) +} + +// threadsafe sorted list +type sortedNamespaces struct { + lock sync.Mutex + items []*Namespace + sorted bool +} + +func (s *sortedNamespaces) add(namespace *Namespace) { + s.lock.Lock() + defer s.lock.Unlock() + if s.sorted { + panic("It is not supported to call sortedNamespaces.add() after sortedNamespaces.sortedItems()") + } + s.items = append(s.items, namespace) +} + +func (s *sortedNamespaces) sortedItems() []*Namespace { + s.lock.Lock() + defer s.lock.Unlock() + if !s.sorted { + less := func(i int, j int) bool { + return s.items[i].Path < s.items[j].Path + } + sort.Slice(s.items, less) + s.sorted = true + } + return s.items +} + +// A NameResolver implements blueprint.NameInterface, and implements the logic to +// find a module from namespaces based on a query string. +// A query string can be a module name or can be be "//namespace_path:module_path" +type NameResolver struct { + rootNamespace *Namespace + + // id counter for atomic.AddInt32 + numNamespaces int32 + + // All namespaces, without duplicates. + sortedNamespaces sortedNamespaces + + // Map from dir to namespace. Will have duplicates if two dirs are part of the same namespace. + namespacesByDir sync.Map // if generics were supported, this would be sync.Map[string]*Namespace + + // func telling whether to export a namespace to Kati + namespaceExportFilter func(*Namespace) bool +} + +func NewNameResolver(namespaceExportFilter func(*Namespace) bool) *NameResolver { + namespacesByDir := sync.Map{} + + r := &NameResolver{ + namespacesByDir: namespacesByDir, + namespaceExportFilter: namespaceExportFilter, + } + r.rootNamespace = r.newNamespace(".") + r.rootNamespace.visibleNamespaces = []*Namespace{r.rootNamespace} + r.addNamespace(r.rootNamespace) + + return r +} + +func (r *NameResolver) newNamespace(path string) *Namespace { + namespace := NewNamespace(path) + + namespace.exportToKati = r.namespaceExportFilter(namespace) + + nextId := atomic.AddInt32(&r.numNamespaces, 1) + id := nextId - 1 + stringId := "" + if id > 0 { + stringId = strconv.Itoa(int(id)) + } + namespace.id = stringId + + return namespace +} + +func (r *NameResolver) addNewNamespaceForModule(module *NamespaceModule, dir string) error { + namespace := r.newNamespace(dir) + module.namespace = namespace + module.resolver = r + namespace.importedNamespaceNames = module.properties.Imports + return r.addNamespace(namespace) +} + +func (r *NameResolver) addNamespace(namespace *Namespace) (err error) { + existingNamespace, exists := r.namespaceAt(namespace.Path) + if exists { + if existingNamespace.Path == namespace.Path { + return fmt.Errorf("namespace %v already exists", namespace.Path) + } else { + // It would probably confuse readers if namespaces were declared anywhere but + // the top of the file, so we forbid declaring namespaces after anything else. + return fmt.Errorf("a namespace must be the first module in the file") + } + } + r.sortedNamespaces.add(namespace) + + r.namespacesByDir.Store(namespace.Path, namespace) + return nil +} + +// non-recursive check for namespace +func (r *NameResolver) namespaceAt(path string) (namespace *Namespace, found bool) { + mapVal, found := r.namespacesByDir.Load(path) + if !found { + return nil, false + } + return mapVal.(*Namespace), true +} + +// recursive search upward for a namespace +func (r *NameResolver) findNamespace(path string) (namespace *Namespace) { + namespace, found := r.namespaceAt(path) + if found { + return namespace + } + parentDir := filepath.Dir(path) + if parentDir == path { + return nil + } + namespace = r.findNamespace(parentDir) + r.namespacesByDir.Store(path, namespace) + return namespace +} + +func (r *NameResolver) NewModule(ctx blueprint.NamespaceContext, moduleGroup blueprint.ModuleGroup, module blueprint.Module) (namespace blueprint.Namespace, errs []error) { + // if this module is a namespace, then save it to our list of namespaces + newNamespace, ok := module.(*NamespaceModule) + if ok { + err := r.addNewNamespaceForModule(newNamespace, ctx.ModuleDir()) + if err != nil { + return nil, []error{err} + } + return nil, nil + } + + // if this module is not a namespace, then save it into the appropriate namespace + ns := r.findNamespaceFromCtx(ctx) + + _, errs = ns.moduleContainer.NewModule(ctx, moduleGroup, module) + if len(errs) > 0 { + return nil, errs + } + + amod, ok := module.(Module) + if ok { + // inform the module whether its namespace is one that we want to export to Make + amod.base().commonProperties.NamespaceExportedToMake = ns.exportToKati + } + + return ns, nil +} + +func (r *NameResolver) AllModules() []blueprint.ModuleGroup { + childLists := [][]blueprint.ModuleGroup{} + totalCount := 0 + for _, namespace := range r.sortedNamespaces.sortedItems() { + newModules := namespace.moduleContainer.AllModules() + totalCount += len(newModules) + childLists = append(childLists, newModules) + } + + allModules := make([]blueprint.ModuleGroup, 0, totalCount) + for _, childList := range childLists { + allModules = append(allModules, childList...) + } + return allModules +} + +// parses a fully-qualified path (like "//namespace_path:module_name") into a namespace name and a +// module name +func (r *NameResolver) parseFullyQualifiedName(name string) (namespaceName string, moduleName string, ok bool) { + if !strings.HasPrefix(name, namespacePrefix) { + return "", "", false + } + name = strings.TrimPrefix(name, namespacePrefix) + components := strings.Split(name, modulePrefix) + if len(components) != 2 { + return "", "", false + } + return components[0], components[1], true + +} + +func (r *NameResolver) getNamespacesToSearchForModule(sourceNamespace *Namespace) (searchOrder []*Namespace) { + return sourceNamespace.visibleNamespaces +} + +func (r *NameResolver) ModuleFromName(name string, namespace blueprint.Namespace) (group blueprint.ModuleGroup, found bool) { + // handle fully qualified references like "//namespace_path:module_name" + nsName, moduleName, isAbs := r.parseFullyQualifiedName(name) + if isAbs { + namespace, found := r.namespaceAt(nsName) + if !found { + return blueprint.ModuleGroup{}, false + } + container := namespace.moduleContainer + return container.ModuleFromName(moduleName, nil) + } + for _, candidate := range r.getNamespacesToSearchForModule(namespace.(*Namespace)) { + group, found = candidate.moduleContainer.ModuleFromName(name, nil) + if found { + return group, true + } + } + return blueprint.ModuleGroup{}, false + +} + +func (r *NameResolver) Rename(oldName string, newName string, namespace blueprint.Namespace) []error { + oldNs := r.findNamespace(oldName) + newNs := r.findNamespace(newName) + if oldNs != newNs { + return []error{fmt.Errorf("cannot rename %v to %v because the destination is outside namespace %v", oldName, newName, oldNs.Path)} + } + + oldName, err := filepath.Rel(oldNs.Path, oldName) + if err != nil { + panic(err) + } + newName, err = filepath.Rel(newNs.Path, newName) + if err != nil { + panic(err) + } + + return oldNs.moduleContainer.Rename(oldName, newName, nil) +} + +// resolve each element of namespace.importedNamespaceNames and put the result in namespace.visibleNamespaces +func (r *NameResolver) FindNamespaceImports(namespace *Namespace) (err error) { + namespace.visibleNamespaces = make([]*Namespace, 0, 2+len(namespace.importedNamespaceNames)) + // search itself first + namespace.visibleNamespaces = append(namespace.visibleNamespaces, namespace) + // search its imports next + for _, name := range namespace.importedNamespaceNames { + imp, ok := r.namespaceAt(name) + if !ok { + return fmt.Errorf("namespace %v does not exist", name) + } + namespace.visibleNamespaces = append(namespace.visibleNamespaces, imp) + } + // search the root namespace last + namespace.visibleNamespaces = append(namespace.visibleNamespaces, r.rootNamespace) + return nil +} + +func (r *NameResolver) MissingDependencyError(depender string, dependerNamespace blueprint.Namespace, depName string) (err error) { + text := fmt.Sprintf("%q depends on undefined module %q", depender, depName) + + _, _, isAbs := r.parseFullyQualifiedName(depName) + if isAbs { + // if the user gave a fully-qualified name, we don't need to look for other + // modules that they might have been referring to + return fmt.Errorf(text) + } + + // determine which namespaces the module can be found in + foundInNamespaces := []string{} + for _, namespace := range r.sortedNamespaces.sortedItems() { + _, found := namespace.moduleContainer.ModuleFromName(depName, nil) + if found { + foundInNamespaces = append(foundInNamespaces, namespace.Path) + } + } + if len(foundInNamespaces) > 0 { + // determine which namespaces are visible to dependerNamespace + dependerNs := dependerNamespace.(*Namespace) + searched := r.getNamespacesToSearchForModule(dependerNs) + importedNames := []string{} + for _, ns := range searched { + importedNames = append(importedNames, ns.Path) + } + text += fmt.Sprintf("\nModule %q is defined in namespace %q which can read these %v namespaces: %q", depender, dependerNs.Path, len(importedNames), importedNames) + text += fmt.Sprintf("\nModule %q can be found in these namespaces: %q", depName, foundInNamespaces) + } + + return fmt.Errorf(text) +} + +func (r *NameResolver) GetNamespace(ctx blueprint.NamespaceContext) blueprint.Namespace { + return r.findNamespaceFromCtx(ctx) +} + +func (r *NameResolver) findNamespaceFromCtx(ctx blueprint.NamespaceContext) *Namespace { + return r.findNamespace(ctx.ModuleDir()) +} + +var _ blueprint.NameInterface = (*NameResolver)(nil) + +type Namespace struct { + blueprint.NamespaceMarker + Path string + + // names of namespaces listed as imports by this namespace + importedNamespaceNames []string + // all namespaces that should be searched when a module in this namespace declares a dependency + visibleNamespaces []*Namespace + + id string + + exportToKati bool + + moduleContainer blueprint.NameInterface +} + +func NewNamespace(path string) *Namespace { + return &Namespace{Path: path, moduleContainer: blueprint.NewSimpleNameInterface()} +} + +var _ blueprint.Namespace = (*Namespace)(nil) + +type NamespaceModule struct { + ModuleBase + + namespace *Namespace + resolver *NameResolver + + properties struct { + Imports []string + } +} + +func (n *NamespaceModule) DepsMutator(context BottomUpMutatorContext) { +} + +func (n *NamespaceModule) GenerateAndroidBuildActions(ctx ModuleContext) { +} + +func (n *NamespaceModule) GenerateBuildActions(ctx blueprint.ModuleContext) { +} + +func (n *NamespaceModule) Name() (name string) { + return *n.nameProperties.Name +} + +func NamespaceFactory() Module { + module := &NamespaceModule{} + + name := "soong_namespace" + module.nameProperties.Name = &name + + module.AddProperties(&module.properties) + return module +} + +func RegisterNamespaceMutator(ctx RegisterMutatorsContext) { + ctx.BottomUp("namespace_deps", namespaceDeps) +} + +func namespaceDeps(ctx BottomUpMutatorContext) { + module, ok := ctx.Module().(*NamespaceModule) + if ok { + err := module.resolver.FindNamespaceImports(module.namespace) + if err != nil { + ctx.ModuleErrorf(err.Error()) + } + } +} diff --git a/android/namespace_test.go b/android/namespace_test.go new file mode 100644 index 000000000..b10b5287e --- /dev/null +++ b/android/namespace_test.go @@ -0,0 +1,652 @@ +// Copyright 2017 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 android + +import ( + "errors" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/google/blueprint" +) + +func TestDependingOnModuleInSameNamespace(t *testing.T) { + ctx := setupTest(t, + map[string]string{ + "dir1": ` + soong_namespace { + } + test_module { + name: "a", + } + test_module { + name: "b", + deps: ["a"], + } + `, + }, + ) + + a := getModule(ctx, "a") + b := getModule(ctx, "b") + if !dependsOn(ctx, b, a) { + t.Errorf("module b does not depend on module a in the same namespace") + } +} + +func TestDependingOnModuleInRootNamespace(t *testing.T) { + ctx := setupTest(t, + map[string]string{ + ".": ` + test_module { + name: "b", + deps: ["a"], + } + test_module { + name: "a", + } + `, + }, + ) + + a := getModule(ctx, "a") + b := getModule(ctx, "b") + if !dependsOn(ctx, b, a) { + t.Errorf("module b in root namespace does not depend on module a in the root namespace") + } +} + +func TestImplicitlyImportRootNamespace(t *testing.T) { + _ = setupTest(t, + map[string]string{ + ".": ` + test_module { + name: "a", + } + `, + "dir1": ` + soong_namespace { + } + test_module { + name: "b", + deps: ["a"], + } + `, + }, + ) + + // setupTest will report any errors +} + +func TestDependingOnModuleInImportedNamespace(t *testing.T) { + ctx := setupTest(t, + map[string]string{ + "dir1": ` + soong_namespace { + } + test_module { + name: "a", + } + `, + "dir2": ` + soong_namespace { + imports: ["dir1"], + } + test_module { + name: "b", + deps: ["a"], + } + `, + }, + ) + + a := getModule(ctx, "a") + b := getModule(ctx, "b") + if !dependsOn(ctx, b, a) { + t.Errorf("module b does not depend on module a in the same namespace") + } +} + +func TestDependingOnModuleInNonImportedNamespace(t *testing.T) { + _, errs := setupTestExpectErrs( + map[string]string{ + "dir1": ` + soong_namespace { + } + test_module { + name: "a", + } + `, + "dir2": ` + soong_namespace { + } + test_module { + name: "a", + } + `, + "dir3": ` + soong_namespace { + } + test_module { + name: "b", + deps: ["a"], + } + `, + }, + ) + + expectedErrors := []error{ + errors.New( + `dir3/Blueprints:4:4: "b" depends on undefined module "a" +Module "b" is defined in namespace "dir3" which can read these 2 namespaces: ["dir3" "."] +Module "a" can be found in these namespaces: ["dir1" "dir2"]`), + } + + if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { + t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) + } +} + +func TestDependingOnModuleByFullyQualifiedReference(t *testing.T) { + ctx := setupTest(t, + map[string]string{ + "dir1": ` + soong_namespace { + } + test_module { + name: "a", + } + `, + "dir2": ` + soong_namespace { + } + test_module { + name: "b", + deps: ["//dir1:a"], + } + `, + }, + ) + a := getModule(ctx, "a") + b := getModule(ctx, "b") + if !dependsOn(ctx, b, a) { + t.Errorf("module b does not depend on module a") + } +} + +func TestSameNameInTwoNamespaces(t *testing.T) { + ctx := setupTest(t, + map[string]string{ + "dir1": ` + soong_namespace { + } + test_module { + name: "a", + id: "1", + } + test_module { + name: "b", + deps: ["a"], + id: "2", + } + `, + "dir2": ` + soong_namespace { + } + test_module { + name: "a", + id:"3", + } + test_module { + name: "b", + deps: ["a"], + id:"4", + } + `, + }, + ) + + one := findModuleById(ctx, "1") + two := findModuleById(ctx, "2") + three := findModuleById(ctx, "3") + four := findModuleById(ctx, "4") + if !dependsOn(ctx, two, one) { + t.Fatalf("Module 2 does not depend on module 1 in its namespace") + } + if dependsOn(ctx, two, three) { + t.Fatalf("Module 2 depends on module 3 in another namespace") + } + if !dependsOn(ctx, four, three) { + t.Fatalf("Module 4 does not depend on module 3 in its namespace") + } + if dependsOn(ctx, four, one) { + t.Fatalf("Module 4 depends on module 1 in another namespace") + } +} + +func TestSearchOrder(t *testing.T) { + ctx := setupTest(t, + map[string]string{ + "dir1": ` + soong_namespace { + } + test_module { + name: "a", + id: "1", + } + `, + "dir2": ` + soong_namespace { + } + test_module { + name: "a", + id:"2", + } + test_module { + name: "b", + id:"3", + } + `, + "dir3": ` + soong_namespace { + } + test_module { + name: "a", + id:"4", + } + test_module { + name: "b", + id:"5", + } + test_module { + name: "c", + id:"6", + } + `, + ".": ` + test_module { + name: "a", + id: "7", + } + test_module { + name: "b", + id: "8", + } + test_module { + name: "c", + id: "9", + } + test_module { + name: "d", + id: "10", + } + `, + "dir4": ` + soong_namespace { + imports: ["dir1", "dir2", "dir3"] + } + test_module { + name: "test_me", + id:"0", + deps: ["a", "b", "c", "d"], + } + `, + }, + ) + + testMe := findModuleById(ctx, "0") + if !dependsOn(ctx, testMe, findModuleById(ctx, "1")) { + t.Errorf("test_me doesn't depend on id 1") + } + if !dependsOn(ctx, testMe, findModuleById(ctx, "3")) { + t.Errorf("test_me doesn't depend on id 3") + } + if !dependsOn(ctx, testMe, findModuleById(ctx, "6")) { + t.Errorf("test_me doesn't depend on id 6") + } + if !dependsOn(ctx, testMe, findModuleById(ctx, "10")) { + t.Errorf("test_me doesn't depend on id 10") + } + if numDeps(ctx, testMe) != 4 { + t.Errorf("num dependencies of test_me = %v, not 4\n", numDeps(ctx, testMe)) + } +} + +func TestTwoNamespacesCanImportEachOther(t *testing.T) { + _ = setupTest(t, + map[string]string{ + "dir1": ` + soong_namespace { + imports: ["dir2"] + } + test_module { + name: "a", + } + test_module { + name: "c", + deps: ["b"], + } + `, + "dir2": ` + soong_namespace { + imports: ["dir1"], + } + test_module { + name: "b", + deps: ["a"], + } + `, + }, + ) + + // setupTest will report any errors +} + +func TestImportingNonexistentNamespace(t *testing.T) { + _, errs := setupTestExpectErrs( + map[string]string{ + "dir1": ` + soong_namespace { + imports: ["a_nonexistent_namespace"] + } + test_module { + name: "a", + deps: ["a_nonexistent_module"] + } + `, + }, + ) + + // should complain about the missing namespace and not complain about the unresolvable dependency + expectedErrors := []error{ + errors.New(`dir1/Blueprints:2:4: module "soong_namespace": namespace a_nonexistent_namespace does not exist`), + } + if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { + t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) + } +} + +func TestNamespacesDontInheritParentNamespaces(t *testing.T) { + _, errs := setupTestExpectErrs( + map[string]string{ + "dir1": ` + soong_namespace { + } + test_module { + name: "a", + } + `, + "dir1/subdir1": ` + soong_namespace { + } + test_module { + name: "b", + deps: ["a"], + } + `, + }, + ) + + expectedErrors := []error{ + errors.New(`dir1/subdir1/Blueprints:4:4: "b" depends on undefined module "a" +Module "b" is defined in namespace "dir1/subdir1" which can read these 2 namespaces: ["dir1/subdir1" "."] +Module "a" can be found in these namespaces: ["dir1"]`), + } + if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { + t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) + } +} + +func TestModulesDoReceiveParentNamespace(t *testing.T) { + _ = setupTest(t, + map[string]string{ + "dir1": ` + soong_namespace { + } + test_module { + name: "a", + } + `, + "dir1/subdir": ` + test_module { + name: "b", + deps: ["a"], + } + `, + }, + ) + + // setupTest will report any errors +} + +func TestNamespaceImportsNotTransitive(t *testing.T) { + _, errs := setupTestExpectErrs( + map[string]string{ + "dir1": ` + soong_namespace { + } + test_module { + name: "a", + } + `, + "dir2": ` + soong_namespace { + imports: ["dir1"], + } + test_module { + name: "b", + deps: ["a"], + } + `, + "dir3": ` + soong_namespace { + imports: ["dir2"], + } + test_module { + name: "c", + deps: ["a"], + } + `, + }, + ) + + expectedErrors := []error{ + errors.New(`dir3/Blueprints:5:4: "c" depends on undefined module "a" +Module "c" is defined in namespace "dir3" which can read these 3 namespaces: ["dir3" "dir2" "."] +Module "a" can be found in these namespaces: ["dir1"]`), + } + if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { + t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) + } +} + +func TestTwoNamepacesInSameDir(t *testing.T) { + _, errs := setupTestExpectErrs( + map[string]string{ + "dir1": ` + soong_namespace { + } + soong_namespace { + } + `, + }, + ) + + expectedErrors := []error{ + errors.New(`dir1/Blueprints:4:4: namespace dir1 already exists`), + } + if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { + t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) + } +} + +func TestNamespaceNotAtTopOfFile(t *testing.T) { + _, errs := setupTestExpectErrs( + map[string]string{ + "dir1": ` + test_module { + name: "a" + } + soong_namespace { + } + `, + }, + ) + + expectedErrors := []error{ + errors.New(`dir1/Blueprints:5:4: a namespace must be the first module in the file`), + } + if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { + t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) + } +} + +func TestTwoModulesWithSameNameInSameNamespace(t *testing.T) { + _, errs := setupTestExpectErrs( + map[string]string{ + "dir1": ` + soong_namespace { + } + test_module { + name: "a" + } + test_module { + name: "a" + } + `, + }, + ) + + expectedErrors := []error{ + errors.New(`dir1/Blueprints:7:4: module "a" already defined + dir1/Blueprints:4:4 <-- previous definition here`), + } + if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { + t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) + } +} + +// some utils to support the tests + +func mockFiles(bps map[string]string) (files map[string][]byte) { + files = make(map[string][]byte, len(bps)) + files["Blueprints"] = []byte("") + for dir, text := range bps { + files[filepath.Join(dir, "Blueprints")] = []byte(text) + } + return files +} + +func setupTestExpectErrs(bps map[string]string) (ctx *TestContext, errs []error) { + buildDir, err := ioutil.TempDir("", "soong_namespace_test") + if err != nil { + return nil, []error{err} + } + defer os.RemoveAll(buildDir) + + config := TestConfig(buildDir, nil) + + ctx = NewTestContext() + ctx.MockFileSystem(mockFiles(bps)) + ctx.RegisterModuleType("test_module", ModuleFactoryAdaptor(newTestModule)) + ctx.RegisterModuleType("soong_namespace", ModuleFactoryAdaptor(NamespaceFactory)) + ctx.PreDepsMutators(RegisterNamespaceMutator) + ctx.Register() + + _, errs = ctx.ParseBlueprintsFiles("Blueprints") + if len(errs) > 0 { + return ctx, errs + } + _, errs = ctx.PrepareBuildActions(config) + return ctx, errs +} + +func setupTest(t *testing.T, bps map[string]string) (ctx *TestContext) { + ctx, errs := setupTestExpectErrs(bps) + failIfErrored(t, errs) + return ctx +} + +func dependsOn(ctx *TestContext, module TestingModule, possibleDependency TestingModule) bool { + depends := false + visit := func(dependency blueprint.Module) { + if dependency == possibleDependency.module { + depends = true + } + } + ctx.VisitDirectDeps(module.module, visit) + return depends +} + +func numDeps(ctx *TestContext, module TestingModule) int { + count := 0 + visit := func(dependency blueprint.Module) { + count++ + } + ctx.VisitDirectDeps(module.module, visit) + return count +} + +func getModule(ctx *TestContext, moduleName string) TestingModule { + return ctx.ModuleForTests(moduleName, "") +} + +func findModuleById(ctx *TestContext, id string) (module TestingModule) { + visit := func(candidate blueprint.Module) { + testModule, ok := candidate.(*testModule) + if ok { + if testModule.properties.Id == id { + module = TestingModule{testModule} + } + } + } + ctx.VisitAllModules(visit) + return module +} + +type testModule struct { + ModuleBase + properties struct { + Deps []string + Id string + } +} + +func (m *testModule) DepsMutator(ctx BottomUpMutatorContext) { + for _, d := range m.properties.Deps { + ctx.AddDependency(ctx.Module(), nil, d) + } +} + +func (m *testModule) GenerateAndroidBuildActions(ModuleContext) { +} + +func newTestModule() Module { + m := &testModule{} + m.AddProperties(&m.properties) + InitAndroidModule(m) + return m +} + +func failIfErrored(t *testing.T, errs []error) { + if len(errs) > 0 { + for _, err := range errs { + t.Error(err) + } + t.FailNow() + } +} diff --git a/android/testing.go b/android/testing.go index fc58cecf1..4f2a2da4f 100644 --- a/android/testing.go +++ b/android/testing.go @@ -23,9 +23,16 @@ import ( ) func NewTestContext() *TestContext { - return &TestContext{ + ctx := &TestContext{ Context: blueprint.NewContext(), } + + namespaceExportFilter := func(namespace *Namespace) bool { + return true + } + ctx.SetNameInterface(NewNameResolver(namespaceExportFilter)) + + return ctx } func NewTestArchContext() *TestContext { diff --git a/android/variable.go b/android/variable.go index a3920a1cb..ab8103aeb 100644 --- a/android/variable.go +++ b/android/variable.go @@ -194,6 +194,8 @@ type productVariables struct { DistDir *string `json:",omitempty"` ExtraVndkVersions []string `json:",omitempty"` + + NamespacesToExport []string `json:",omitempty"` } func boolPtr(v bool) *bool { diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go index e15a6bdd1..ddde1c595 100644 --- a/cmd/soong_build/main.go +++ b/cmd/soong_build/main.go @@ -25,6 +25,22 @@ import ( "android/soong/android" ) +func newNameResolver(config android.Config) *android.NameResolver { + namespacePathsToExport := make(map[string]bool) + + for _, namespaceName := range config.ProductVariables.NamespacesToExport { + namespacePathsToExport[namespaceName] = true + } + + namespacePathsToExport["."] = true // always export the root namespace + + exportFilter := func(namespace *android.Namespace) bool { + return namespacePathsToExport[namespace.Path] + } + + return android.NewNameResolver(exportFilter) +} + func main() { flag.Parse() @@ -40,8 +56,7 @@ func main() { os.Exit(1) } - // Temporary hack - //ctx.SetIgnoreUnknownModuleTypes(true) + ctx.SetNameInterface(newNameResolver(configuration)) ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies()) diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go index fb20d63bc..96f2274f9 100644 --- a/ui/build/dumpvars.go +++ b/ui/build/dumpvars.go @@ -106,6 +106,7 @@ var BannerVars = []string{ "AUX_OS_VARIANT_LIST", "TARGET_BUILD_PDK", "PDK_FUSION_PLATFORM_ZIP", + "PRODUCT_SOONG_NAMESPACES", } func Banner(make_vars map[string]string) string { From 44c0cd85435d7c90cbf6d5f930a64edac15b42ee Mon Sep 17 00:00:00 2001 From: Jeff Gaston Date: Wed, 29 Nov 2017 19:51:52 -0800 Subject: [PATCH 2/4] Document Soong namespaces Bug: 65683273 Test: Read README.md Change-Id: I179c085e123858ae425515ac6bbf4b272c245a2e --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index 3d24e75ad..4013a2afa 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,38 @@ cc_binary { } ``` +### Name resolution + +Soong provides the ability for modules in different directories to specify +the same name, as long as each module is declared within a separate namespace. +A namespace can be declared like this: + +``` +soong_namespace { + imports: ["path/to/otherNamespace1", "path/to/otherNamespace2"], +} +``` + +Each Soong module is assigned a namespace based on its location in the tree. +Each Soong module is considered to be in the namespace defined by the +soong_namespace found in an Android.bp in the current directory or closest +ancestor directory, unless no such soong_namespace module is found, in which +case the module is considered to be in the implicit root namespace. + +When Soong attempts to resolve dependency D declared my module M in namespace +N which imports namespaces I1, I2, I3..., then if D is a fully-qualified name +of the form "//namespace:module", only the specified namespace will be searched +for the specified module name. Otherwise, Soong will first look for a module +named D declared in namespace N. If that module does not exist, Soong will look +for a module named D in namespaces I1, I2, I3... Lastly, Soong will look in the +root namespace. + +Until we have fully converted from Make to Soong, it will be necessary for the +Make product config to specify a value of PRODUCT_SOONG_NAMESPACES. Its value +should be a space-separated list of namespaces that Soong export to Make to be +built by the `m` command. After we have fully converted from Make to Soong, the +details of enabling namespaces could potentially change. + ### Formatter Soong includes a canonical formatter for blueprint files, similar to From 5c3886de5a1eabb81e7d166a86c29ca324dabf49 Mon Sep 17 00:00:00 2001 From: Jeff Gaston Date: Thu, 30 Nov 2017 16:46:47 -0800 Subject: [PATCH 3/4] require namespaces to be declared only in files named Android.bp Bug: 65683273 Test: m -j nothing # which runs unit tests Change-Id: I5edf0e0482809f5ac9fb9dfff342fb404e1c52da --- android/namespace.go | 13 ++++++--- android/namespace_test.go | 57 ++++++++++++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/android/namespace.go b/android/namespace.go index a2ff1a692..b3e718af3 100644 --- a/android/namespace.go +++ b/android/namespace.go @@ -15,6 +15,7 @@ package android import ( + "errors" "fmt" "path/filepath" "sort" @@ -114,7 +115,13 @@ func (r *NameResolver) newNamespace(path string) *Namespace { return namespace } -func (r *NameResolver) addNewNamespaceForModule(module *NamespaceModule, dir string) error { +func (r *NameResolver) addNewNamespaceForModule(module *NamespaceModule, path string) error { + fileName := filepath.Base(path) + if fileName != "Android.bp" { + return errors.New("A namespace may only be declared in a file named Android.bp") + } + dir := filepath.Dir(path) + namespace := r.newNamespace(dir) module.namespace = namespace module.resolver = r @@ -167,7 +174,7 @@ func (r *NameResolver) NewModule(ctx blueprint.NamespaceContext, moduleGroup blu // if this module is a namespace, then save it to our list of namespaces newNamespace, ok := module.(*NamespaceModule) if ok { - err := r.addNewNamespaceForModule(newNamespace, ctx.ModuleDir()) + err := r.addNewNamespaceForModule(newNamespace, ctx.ModulePath()) if err != nil { return nil, []error{err} } @@ -322,7 +329,7 @@ func (r *NameResolver) GetNamespace(ctx blueprint.NamespaceContext) blueprint.Na } func (r *NameResolver) findNamespaceFromCtx(ctx blueprint.NamespaceContext) *Namespace { - return r.findNamespace(ctx.ModuleDir()) + return r.findNamespace(filepath.Dir(ctx.ModulePath())) } var _ blueprint.NameInterface = (*NameResolver)(nil) diff --git a/android/namespace_test.go b/android/namespace_test.go index b10b5287e..13da88b42 100644 --- a/android/namespace_test.go +++ b/android/namespace_test.go @@ -151,7 +151,7 @@ func TestDependingOnModuleInNonImportedNamespace(t *testing.T) { expectedErrors := []error{ errors.New( - `dir3/Blueprints:4:4: "b" depends on undefined module "a" + `dir3/Android.bp:4:4: "b" depends on undefined module "a" Module "b" is defined in namespace "dir3" which can read these 2 namespaces: ["dir3" "."] Module "a" can be found in these namespaces: ["dir1" "dir2"]`), } @@ -373,7 +373,7 @@ func TestImportingNonexistentNamespace(t *testing.T) { // should complain about the missing namespace and not complain about the unresolvable dependency expectedErrors := []error{ - errors.New(`dir1/Blueprints:2:4: module "soong_namespace": namespace a_nonexistent_namespace does not exist`), + errors.New(`dir1/Android.bp:2:4: module "soong_namespace": namespace a_nonexistent_namespace does not exist`), } if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) @@ -402,7 +402,7 @@ func TestNamespacesDontInheritParentNamespaces(t *testing.T) { ) expectedErrors := []error{ - errors.New(`dir1/subdir1/Blueprints:4:4: "b" depends on undefined module "a" + errors.New(`dir1/subdir1/Android.bp:4:4: "b" depends on undefined module "a" Module "b" is defined in namespace "dir1/subdir1" which can read these 2 namespaces: ["dir1/subdir1" "."] Module "a" can be found in these namespaces: ["dir1"]`), } @@ -465,7 +465,7 @@ func TestNamespaceImportsNotTransitive(t *testing.T) { ) expectedErrors := []error{ - errors.New(`dir3/Blueprints:5:4: "c" depends on undefined module "a" + errors.New(`dir3/Android.bp:5:4: "c" depends on undefined module "a" Module "c" is defined in namespace "dir3" which can read these 3 namespaces: ["dir3" "dir2" "."] Module "a" can be found in these namespaces: ["dir1"]`), } @@ -487,7 +487,7 @@ func TestTwoNamepacesInSameDir(t *testing.T) { ) expectedErrors := []error{ - errors.New(`dir1/Blueprints:4:4: namespace dir1 already exists`), + errors.New(`dir1/Android.bp:4:4: namespace dir1 already exists`), } if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) @@ -508,7 +508,7 @@ func TestNamespaceNotAtTopOfFile(t *testing.T) { ) expectedErrors := []error{ - errors.New(`dir1/Blueprints:5:4: a namespace must be the first module in the file`), + errors.New(`dir1/Android.bp:5:4: a namespace must be the first module in the file`), } if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) @@ -532,26 +532,48 @@ func TestTwoModulesWithSameNameInSameNamespace(t *testing.T) { ) expectedErrors := []error{ - errors.New(`dir1/Blueprints:7:4: module "a" already defined - dir1/Blueprints:4:4 <-- previous definition here`), + errors.New(`dir1/Android.bp:7:4: module "a" already defined + dir1/Android.bp:4:4 <-- previous definition here`), } if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) } } +func TestDeclaringNamespaceInNonAndroidBpFile(t *testing.T) { + _, errs := setupTestFromFiles( + map[string][]byte{ + "Android.bp": []byte(` + build = ["include.bp"] + `), + "include.bp": []byte(` + soong_namespace { + } + `), + }, + ) + + expectedErrors := []error{ + errors.New(`include.bp:2:5: A namespace may only be declared in a file named Android.bp`), + } + + if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() { + t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs) + } +} + // some utils to support the tests func mockFiles(bps map[string]string) (files map[string][]byte) { files = make(map[string][]byte, len(bps)) - files["Blueprints"] = []byte("") + files["Android.bp"] = []byte("") for dir, text := range bps { - files[filepath.Join(dir, "Blueprints")] = []byte(text) + files[filepath.Join(dir, "Android.bp")] = []byte(text) } return files } -func setupTestExpectErrs(bps map[string]string) (ctx *TestContext, errs []error) { +func setupTestFromFiles(bps map[string][]byte) (ctx *TestContext, errs []error) { buildDir, err := ioutil.TempDir("", "soong_namespace_test") if err != nil { return nil, []error{err} @@ -561,13 +583,13 @@ func setupTestExpectErrs(bps map[string]string) (ctx *TestContext, errs []error) config := TestConfig(buildDir, nil) ctx = NewTestContext() - ctx.MockFileSystem(mockFiles(bps)) + ctx.MockFileSystem(bps) ctx.RegisterModuleType("test_module", ModuleFactoryAdaptor(newTestModule)) ctx.RegisterModuleType("soong_namespace", ModuleFactoryAdaptor(NamespaceFactory)) ctx.PreDepsMutators(RegisterNamespaceMutator) ctx.Register() - _, errs = ctx.ParseBlueprintsFiles("Blueprints") + _, errs = ctx.ParseBlueprintsFiles("Android.bp") if len(errs) > 0 { return ctx, errs } @@ -575,6 +597,15 @@ func setupTestExpectErrs(bps map[string]string) (ctx *TestContext, errs []error) return ctx, errs } +func setupTestExpectErrs(bps map[string]string) (ctx *TestContext, errs []error) { + files := make(map[string][]byte, len(bps)) + files["Android.bp"] = []byte("") + for dir, text := range bps { + files[filepath.Join(dir, "Android.bp")] = []byte(text) + } + return setupTestFromFiles(files) +} + func setupTest(t *testing.T, bps map[string]string) (ctx *TestContext) { ctx, errs := setupTestExpectErrs(bps) failIfErrored(t, errs) From b274ed3252dfadfd5a7edf70e470b61cf074c0de Mon Sep 17 00:00:00 2001 From: Jeff Gaston Date: Fri, 1 Dec 2017 17:10:33 -0800 Subject: [PATCH 4/4] Make ninja file deterministic even with dup module names Bug: 65683273 Test: ./build/soong/scripts/diff_build_graphs.sh \ --products=aosp_arm '' '' Change-Id: Ie01ff579d69e94b12363f53aec5d25810211c451 --- android/namespace.go | 42 ++++++++++++++++++++++++++++----------- android/namespace_test.go | 20 +++++++++++++++++++ android/testing.go | 14 ++++++++----- 3 files changed, 59 insertions(+), 17 deletions(-) diff --git a/android/namespace.go b/android/namespace.go index b3e718af3..1f8ef5a66 100644 --- a/android/namespace.go +++ b/android/namespace.go @@ -22,7 +22,6 @@ import ( "strconv" "strings" "sync" - "sync/atomic" "github.com/google/blueprint" ) @@ -66,6 +65,15 @@ func (s *sortedNamespaces) sortedItems() []*Namespace { return s.items } +func (s *sortedNamespaces) index(namespace *Namespace) int { + for i, candidate := range s.sortedItems() { + if namespace == candidate { + return i + } + } + return -1 +} + // A NameResolver implements blueprint.NameInterface, and implements the logic to // find a module from namespaces based on a query string. // A query string can be a module name or can be be "//namespace_path:module_path" @@ -73,7 +81,7 @@ type NameResolver struct { rootNamespace *Namespace // id counter for atomic.AddInt32 - numNamespaces int32 + nextNamespaceId int32 // All namespaces, without duplicates. sortedNamespaces sortedNamespaces @@ -104,14 +112,6 @@ func (r *NameResolver) newNamespace(path string) *Namespace { namespace.exportToKati = r.namespaceExportFilter(namespace) - nextId := atomic.AddInt32(&r.numNamespaces, 1) - id := nextId - 1 - stringId := "" - if id > 0 { - stringId = strconv.Itoa(int(id)) - } - namespace.id = stringId - return namespace } @@ -291,6 +291,14 @@ func (r *NameResolver) FindNamespaceImports(namespace *Namespace) (err error) { return nil } +func (r *NameResolver) chooseId(namespace *Namespace) { + id := r.sortedNamespaces.index(namespace) + if id < 0 { + panic(fmt.Sprintf("Namespace not found: %v\n", namespace.id)) + } + namespace.id = strconv.Itoa(id) +} + func (r *NameResolver) MissingDependencyError(depender string, dependerNamespace blueprint.Namespace, depName string) (err error) { text := fmt.Sprintf("%q depends on undefined module %q", depender, depName) @@ -332,6 +340,14 @@ func (r *NameResolver) findNamespaceFromCtx(ctx blueprint.NamespaceContext) *Nam return r.findNamespace(filepath.Dir(ctx.ModulePath())) } +func (r *NameResolver) UniqueName(ctx blueprint.NamespaceContext, name string) (unique string) { + prefix := r.findNamespaceFromCtx(ctx).id + if prefix != "" { + prefix = prefix + "-" + } + return prefix + name +} + var _ blueprint.NameInterface = (*NameResolver)(nil) type Namespace struct { @@ -391,15 +407,17 @@ func NamespaceFactory() Module { } func RegisterNamespaceMutator(ctx RegisterMutatorsContext) { - ctx.BottomUp("namespace_deps", namespaceDeps) + ctx.BottomUp("namespace_deps", namespaceMutator).Parallel() } -func namespaceDeps(ctx BottomUpMutatorContext) { +func namespaceMutator(ctx BottomUpMutatorContext) { module, ok := ctx.Module().(*NamespaceModule) if ok { err := module.resolver.FindNamespaceImports(module.namespace) if err != nil { ctx.ModuleErrorf(err.Error()) } + + module.resolver.chooseId(module.namespace) } } diff --git a/android/namespace_test.go b/android/namespace_test.go index 13da88b42..9ab186b00 100644 --- a/android/namespace_test.go +++ b/android/namespace_test.go @@ -19,6 +19,7 @@ import ( "io/ioutil" "os" "path/filepath" + "reflect" "testing" "github.com/google/blueprint" @@ -562,6 +563,25 @@ func TestDeclaringNamespaceInNonAndroidBpFile(t *testing.T) { } } +// so that the generated .ninja file will have consistent names +func TestConsistentNamespaceNames(t *testing.T) { + ctx := setupTest(t, + map[string]string{ + "dir1": "soong_namespace{}", + "dir2": "soong_namespace{}", + "dir3": "soong_namespace{}", + }) + + ns1, _ := ctx.NameResolver.namespaceAt("dir1") + ns2, _ := ctx.NameResolver.namespaceAt("dir2") + ns3, _ := ctx.NameResolver.namespaceAt("dir3") + actualIds := []string{ns1.id, ns2.id, ns3.id} + expectedIds := []string{"1", "2", "3"} + if !reflect.DeepEqual(actualIds, expectedIds) { + t.Errorf("Incorrect namespace ids.\nactual: %s\nexpected: %s\n", actualIds, expectedIds) + } +} + // some utils to support the tests func mockFiles(bps map[string]string) (files map[string][]byte) { diff --git a/android/testing.go b/android/testing.go index 4f2a2da4f..1c0fac10d 100644 --- a/android/testing.go +++ b/android/testing.go @@ -23,14 +23,17 @@ import ( ) func NewTestContext() *TestContext { - ctx := &TestContext{ - Context: blueprint.NewContext(), - } - namespaceExportFilter := func(namespace *Namespace) bool { return true } - ctx.SetNameInterface(NewNameResolver(namespaceExportFilter)) + + nameResolver := NewNameResolver(namespaceExportFilter) + ctx := &TestContext{ + Context: blueprint.NewContext(), + NameResolver: nameResolver, + } + + ctx.SetNameInterface(nameResolver) return ctx } @@ -44,6 +47,7 @@ func NewTestArchContext() *TestContext { type TestContext struct { *blueprint.Context preArch, preDeps, postDeps []RegisterMutatorFunc + NameResolver *NameResolver } func (ctx *TestContext) PreArchMutators(f RegisterMutatorFunc) {