Merge changes from topic "soong-namespaces"

* changes:
  Make ninja file deterministic even with dup module names
  require namespaces to be declared only in files named Android.bp
  Document Soong namespaces
  Revert "Revert "Soong support for namespaces""
This commit is contained in:
Jeff Gaston
2017-12-05 03:48:20 +00:00
committed by Gerrit Code Review
11 changed files with 1212 additions and 7 deletions

View File

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

View File

@@ -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

View File

@@ -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 == "" {

View File

@@ -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,
})

View File

@@ -86,6 +86,7 @@ func registerArchMutator(ctx RegisterMutatorsContext) {
}
var preDeps = []RegisterMutatorFunc{
RegisterNamespaceMutator,
registerArchMutator,
}

423
android/namespace.go Normal file
View File

@@ -0,0 +1,423 @@
// 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"
"fmt"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"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
}
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"
type NameResolver struct {
rootNamespace *Namespace
// id counter for atomic.AddInt32
nextNamespaceId 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)
return namespace
}
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
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.ModulePath())
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) 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)
_, _, 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(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 {
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", namespaceMutator).Parallel()
}
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)
}
}

703
android/namespace_test.go Normal file
View File

@@ -0,0 +1,703 @@
// 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"
"reflect"
"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/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"]`),
}
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/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)
}
}
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/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"]`),
}
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/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"]`),
}
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/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)
}
}
func TestNamespaceNotAtTopOfFile(t *testing.T) {
_, errs := setupTestExpectErrs(
map[string]string{
"dir1": `
test_module {
name: "a"
}
soong_namespace {
}
`,
},
)
expectedErrors := []error{
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)
}
}
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/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)
}
}
// 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) {
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 files
}
func setupTestFromFiles(bps map[string][]byte) (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(bps)
ctx.RegisterModuleType("test_module", ModuleFactoryAdaptor(newTestModule))
ctx.RegisterModuleType("soong_namespace", ModuleFactoryAdaptor(NamespaceFactory))
ctx.PreDepsMutators(RegisterNamespaceMutator)
ctx.Register()
_, errs = ctx.ParseBlueprintsFiles("Android.bp")
if len(errs) > 0 {
return ctx, errs
}
_, errs = ctx.PrepareBuildActions(config)
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)
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()
}
}

View File

@@ -23,9 +23,19 @@ import (
)
func NewTestContext() *TestContext {
return &TestContext{
Context: blueprint.NewContext(),
namespaceExportFilter := func(namespace *Namespace) bool {
return true
}
nameResolver := NewNameResolver(namespaceExportFilter)
ctx := &TestContext{
Context: blueprint.NewContext(),
NameResolver: nameResolver,
}
ctx.SetNameInterface(nameResolver)
return ctx
}
func NewTestArchContext() *TestContext {
@@ -37,6 +47,7 @@ func NewTestArchContext() *TestContext {
type TestContext struct {
*blueprint.Context
preArch, preDeps, postDeps []RegisterMutatorFunc
NameResolver *NameResolver
}
func (ctx *TestContext) PreArchMutators(f RegisterMutatorFunc) {

View File

@@ -194,6 +194,8 @@ type productVariables struct {
DistDir *string `json:",omitempty"`
ExtraVndkVersions []string `json:",omitempty"`
NamespacesToExport []string `json:",omitempty"`
}
func boolPtr(v bool) *bool {

View File

@@ -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())

View File

@@ -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 {