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
diff --git a/android/namespace.go b/android/namespace.go
new file mode 100644
index 0000000..a2ff1a6
--- /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())
+ }
+ }
+}