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 {