aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Gaston <jeffrygaston@google.com>2017-11-29 16:47:17 -0800
committerJeff Gaston <jeffrygaston@google.com>2017-12-04 17:44:29 -0800
commit088e29ed38757e288ad26c7365e3eb6ee92a6f35 (patch)
tree1ae73ad0fd698fe5978733e98093e1fcceb54dee
parent7154928c93e062775c1d3885ed59a5b61c48e168 (diff)
downloadbuild_soong-088e29ed38757e288ad26c7365e3eb6ee92a6f35.tar.gz
build_soong-088e29ed38757e288ad26c7365e3eb6ee92a6f35.tar.bz2
build_soong-088e29ed38757e288ad26c7365e3eb6ee92a6f35.zip
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
-rw-r--r--Android.bp2
-rw-r--r--android/androidmk.go6
-rw-r--r--android/module.go15
-rw-r--r--android/mutator.go1
-rw-r--r--android/namespace.go398
-rw-r--r--android/namespace_test.go652
-rw-r--r--android/testing.go9
-rw-r--r--android/variable.go2
-rw-r--r--cmd/soong_build/main.go19
-rw-r--r--ui/build/dumpvars.go1
10 files changed, 1099 insertions, 6 deletions
diff --git a/Android.bp b/Android.bp
index a296da1a..3e09add4 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 d88ba8fa..12aa5fa8 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 c7284875..e6766a34 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 db3eaa37..876d1617 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 00000000..a2ff1a69
--- /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 00000000..b10b5287
--- /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 fc58cecf..4f2a2da4 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 a3920a1c..ab8103ae 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 e15a6bdd..ddde1c59 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 fb20d63b..96f2274f 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 {