64765aaef9
Synopsis was missing to soong_namespace module. Also added documentation to imports properties. This required the namespaceProperties struct to be pulled out of the NamespaceModule struct in order for the documentation tool to extract the property documentation. Bug: b/128337482 Test: Ran the doc generation command and verified that the synopsis was added to soong_namespace and imports properties is listed too. Change-Id: I519b14629bdbc85f35fbc8fa03e78dc2ad3f655e
417 lines
13 KiB
Go
417 lines
13 KiB
Go
// 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 {
|
|
r := &NameResolver{
|
|
namespacesByDir: sync.Map{},
|
|
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 {
|
|
return namespace.(*Namespace).moduleContainer.Rename(oldName, newName, namespace)
|
|
}
|
|
|
|
// 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 namespaceProperties struct {
|
|
// a list of namespaces that contain modules that will be referenced
|
|
// by modules in this namespace.
|
|
Imports []string `android:"path"`
|
|
}
|
|
|
|
type NamespaceModule struct {
|
|
ModuleBase
|
|
|
|
namespace *Namespace
|
|
resolver *NameResolver
|
|
|
|
properties namespaceProperties
|
|
}
|
|
|
|
func (n *NamespaceModule) GenerateAndroidBuildActions(ctx ModuleContext) {
|
|
}
|
|
|
|
func (n *NamespaceModule) GenerateBuildActions(ctx blueprint.ModuleContext) {
|
|
}
|
|
|
|
func (n *NamespaceModule) Name() (name string) {
|
|
return *n.nameProperties.Name
|
|
}
|
|
|
|
// soong_namespace provides a scope to modules in an Android.bp file to prevent
|
|
// module name conflicts with other defined modules in different Android.bp
|
|
// files. Once soong_namespace has been defined in an Android.bp file, the
|
|
// namespacing is applied to all modules that follow the soong_namespace in
|
|
// the current Android.bp file, as well as modules defined in Android.bp files
|
|
// in subdirectories. An Android.bp file in a subdirectory can define its own
|
|
// soong_namespace which is applied to all its modules and as well as modules
|
|
// defined in subdirectories Android.bp files. Modules in a soong_namespace are
|
|
// visible to Make by listing the namespace path in PRODUCT_SOONG_NAMESPACES
|
|
// make variable in a makefile.
|
|
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)
|
|
}
|
|
}
|