Merge "Revert "Default to building with OpenJDK 9 -target 1.8.""
diff --git a/Android.bp b/Android.bp
index a296da1..643e8df 100644
--- a/Android.bp
+++ b/Android.bp
@@ -50,6 +50,7 @@
"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 @@
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",
@@ -216,6 +218,7 @@
"java/app.go",
"java/builder.go",
"java/gen.go",
+ "java/genrule.go",
"java/jacoco.go",
"java/java.go",
"java/proto.go",
diff --git a/README.md b/README.md
index 3d24e75..4013a2a 100644
--- a/README.md
+++ b/README.md
@@ -95,6 +95,38 @@
}
```
+### 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
diff --git a/android/androidmk.go b/android/androidmk.go
index d88ba8f..12aa5fa 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -157,6 +157,12 @@
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/arch.go b/android/arch.go
index 7f9abc6..af3919c 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -1072,19 +1072,30 @@
return ret
}
+func preferTargets(targets []Target, filters ...string) []Target {
+ for _, filter := range filters {
+ buildTargets := filterMultilibTargets(targets, filter)
+ if len(buildTargets) > 0 {
+ return buildTargets
+ }
+ }
+ return nil
+}
+
// Use the module multilib setting to select one or more targets from a target list
func decodeMultilib(multilib string, targets []Target, prefer32 bool) ([]Target, error) {
buildTargets := []Target{}
- if multilib == "first" {
- if prefer32 {
- multilib = "prefer32"
- } else {
- multilib = "prefer64"
- }
- }
+
switch multilib {
case "common":
- buildTargets = append(buildTargets, getCommonTargets(targets)...)
+ buildTargets = getCommonTargets(targets)
+ case "common_first":
+ buildTargets = getCommonTargets(targets)
+ if prefer32 {
+ buildTargets = append(buildTargets, preferTargets(targets, "lib32", "lib64")...)
+ } else {
+ buildTargets = append(buildTargets, preferTargets(targets, "lib64", "lib32")...)
+ }
case "both":
if prefer32 {
buildTargets = append(buildTargets, filterMultilibTargets(targets, "lib32")...)
@@ -1097,16 +1108,14 @@
buildTargets = filterMultilibTargets(targets, "lib32")
case "64":
buildTargets = filterMultilibTargets(targets, "lib64")
+ case "first":
+ if prefer32 {
+ buildTargets = preferTargets(targets, "lib32", "lib64")
+ } else {
+ buildTargets = preferTargets(targets, "lib64", "lib32")
+ }
case "prefer32":
- buildTargets = filterMultilibTargets(targets, "lib32")
- if len(buildTargets) == 0 {
- buildTargets = filterMultilibTargets(targets, "lib64")
- }
- case "prefer64":
- buildTargets = filterMultilibTargets(targets, "lib64")
- if len(buildTargets) == 0 {
- buildTargets = filterMultilibTargets(targets, "lib32")
- }
+ buildTargets = preferTargets(targets, "lib32", "lib64")
default:
return nil, fmt.Errorf(`compile_multilib must be "both", "first", "32", "64", or "prefer32" found %q`,
multilib)
diff --git a/android/module.go b/android/module.go
index c728487..865764f 100644
--- a/android/module.go
+++ b/android/module.go
@@ -151,6 +151,7 @@
VisitAllModuleVariants(visit func(Module))
GetMissingDependencies() []string
+ Namespace() blueprint.Namespace
}
type Module interface {
@@ -235,6 +236,8 @@
ArchSpecific bool `blueprint:"mutated"`
SkipInstall bool `blueprint:"mutated"`
+
+ NamespaceExportedToMake bool `blueprint:"mutated"`
}
type hostAndDeviceProperties struct {
@@ -245,10 +248,11 @@
type Multilib string
const (
- MultilibBoth Multilib = "both"
- MultilibFirst Multilib = "first"
- MultilibCommon Multilib = "common"
- MultilibDefault Multilib = ""
+ MultilibBoth Multilib = "both"
+ MultilibFirst Multilib = "first"
+ MultilibCommon Multilib = "common"
+ MultilibCommonFirst Multilib = "common_first"
+ MultilibDefault Multilib = ""
)
type HostOrDeviceSupported int
@@ -500,8 +504,13 @@
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 +522,7 @@
}
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 +538,10 @@
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 db3eaa3..876d161 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -86,6 +86,7 @@
}
var preDeps = []RegisterMutatorFunc{
+ RegisterNamespaceMutator,
registerArchMutator,
}
diff --git a/android/namespace.go b/android/namespace.go
new file mode 100644
index 0000000..1f8ef5a
--- /dev/null
+++ b/android/namespace.go
@@ -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)
+ }
+}
diff --git a/android/namespace_test.go b/android/namespace_test.go
new file mode 100644
index 0000000..9ab186b
--- /dev/null
+++ b/android/namespace_test.go
@@ -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()
+ }
+}
diff --git a/android/paths.go b/android/paths.go
index e0cbd21..80863c9 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -92,7 +92,7 @@
// Rel returns the portion of the path relative to the directory it was created from. For
// example, Rel on a PathsForModuleSrc would return the path relative to the module source
- // directory.
+ // directory, and OutputPath.Join("foo").Rel() would return "foo".
Rel() string
}
@@ -456,6 +456,12 @@
return p.path
}
+func (p basePath) withRel(rel string) basePath {
+ p.path = filepath.Join(p.path, rel)
+ p.rel = rel
+ return p
+}
+
// SourcePath is a Path representing a file path rooted from SrcDir
type SourcePath struct {
basePath
@@ -463,6 +469,11 @@
var _ Path = SourcePath{}
+func (p SourcePath) withRel(rel string) SourcePath {
+ p.basePath = p.basePath.withRel(rel)
+ return p
+}
+
// safePathForSource is for paths that we expect are safe -- only for use by go
// code that is embedding ninja variables in paths
func safePathForSource(ctx PathContext, path string) SourcePath {
@@ -589,7 +600,7 @@
// provided paths... may not use '..' to escape from the current path.
func (p SourcePath) Join(ctx PathContext, paths ...string) SourcePath {
path := validatePath(ctx, paths...)
- return PathForSource(ctx, p.path, path)
+ return p.withRel(path)
}
// OverlayPath returns the overlay for `path' if it exists. This assumes that the
@@ -631,8 +642,7 @@
}
func (p OutputPath) withRel(rel string) OutputPath {
- p.basePath.path = filepath.Join(p.basePath.path, rel)
- p.basePath.rel = rel
+ p.basePath = p.basePath.withRel(rel)
return p
}
@@ -660,7 +670,7 @@
// provided paths... may not use '..' to escape from the current path.
func (p OutputPath) Join(ctx PathContext, paths ...string) OutputPath {
path := validatePath(ctx, paths...)
- return PathForOutput(ctx, p.path, path)
+ return p.withRel(path)
}
// PathForIntermediates returns an OutputPath representing the top-level
diff --git a/android/testing.go b/android/testing.go
index fc58cec..ae012b0 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -23,9 +23,19 @@
)
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 @@
type TestContext struct {
*blueprint.Context
preArch, preDeps, postDeps []RegisterMutatorFunc
+ NameResolver *NameResolver
}
func (ctx *TestContext) PreArchMutators(f RegisterMutatorFunc) {
@@ -125,6 +136,7 @@
}
func (m TestingModule) Output(file string) BuildParams {
+ var searchedOutputs []string
for _, p := range m.module.BuildParamsForTests() {
outputs := append(WritablePaths(nil), p.Outputs...)
if p.Output != nil {
@@ -134,7 +146,9 @@
if f.String() == file || f.Rel() == file {
return p
}
+ searchedOutputs = append(searchedOutputs, f.Rel())
}
}
- panic(fmt.Errorf("couldn't find output %q", file))
+ panic(fmt.Errorf("couldn't find output %q.\nall outputs: %v",
+ file, searchedOutputs))
}
diff --git a/android/variable.go b/android/variable.go
index a3920a1..ab8103a 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -194,6 +194,8 @@
DistDir *string `json:",omitempty"`
ExtraVndkVersions []string `json:",omitempty"`
+
+ NamespacesToExport []string `json:",omitempty"`
}
func boolPtr(v bool) *bool {
diff --git a/cc/config/global.go b/cc/config/global.go
index fb71e6a..a24b60c 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -131,7 +131,6 @@
"frameworks/native/libs/vr/libbufferhubqueue/",
"frameworks/native/libs/vr/libdvr/tests/",
"frameworks/native/services/surfaceflinger/tests/",
- "frameworks/native/services/vr/",
"vendor/",
}
@@ -139,20 +138,15 @@
WarningAllowedOldProjects = []string{
"cts/hostsidetests/security/securityPatch/",
"cts/tests/tests/permission/jni/",
- "development/tutorials/ReverseDebug/",
"frameworks/av/drm/mediacas/plugins/",
"frameworks/av/services/mediaextractor/",
- "frameworks/base/core/tests/webkit/apk_with_native_libs/jni/",
"frameworks/base/tests/backup/",
"frameworks/native/cmds/cmd/",
"frameworks/webview/chromium/",
"hardware/libhardware/modules/",
- "hardware/libhardware/tests/",
"hardware/qcom/",
"sdk/emulator/mksdcard/",
"system/vold/tests/",
- "test/vts-testcase/kernel/api/qtaguid/",
- "test/vts-testcase/security/poc/target/",
"tools/adt/idea/android/ultimate/get_modification_time/jni/",
}
)
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index e15a6bd..ddde1c5 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -25,6 +25,22 @@
"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 @@
os.Exit(1)
}
- // Temporary hack
- //ctx.SetIgnoreUnknownModuleTypes(true)
+ ctx.SetNameInterface(newNameResolver(configuration))
ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
diff --git a/java/androidmk.go b/java/androidmk.go
index 2e67639..f52d5e9 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -95,28 +95,35 @@
}
func (binary *Binary) AndroidMk() android.AndroidMkData {
- return android.AndroidMkData{
- Class: "JAVA_LIBRARIES",
- OutputFile: android.OptionalPathForPath(binary.implementationJarFile),
- Include: "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
- Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
- android.WriteAndroidMkData(w, data)
- fmt.Fprintln(w, "jar_installed_module := $(LOCAL_INSTALLED_MODULE)")
- fmt.Fprintln(w, "include $(CLEAR_VARS)")
- fmt.Fprintln(w, "LOCAL_MODULE := "+name)
- fmt.Fprintln(w, "LOCAL_MODULE_CLASS := EXECUTABLES")
- if strings.Contains(prefix, "HOST_") {
- fmt.Fprintln(w, "LOCAL_IS_HOST_MODULE := true")
- }
- fmt.Fprintln(w, "LOCAL_STRIP_MODULE := false")
- fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", binary.wrapperFile.String())
- fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
+ if !binary.isWrapperVariant {
+ return android.AndroidMkData{
+ Class: "JAVA_LIBRARIES",
+ OutputFile: android.OptionalPathForPath(binary.implementationJarFile),
+ Include: "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
+ Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
+ android.WriteAndroidMkData(w, data)
- // Ensure that the wrapper script timestamp is always updated when the jar is updated
- fmt.Fprintln(w, "$(LOCAL_INSTALLED_MODULE): $(jar_installed_module)")
- fmt.Fprintln(w, "jar_installed_module :=")
- },
+ fmt.Fprintln(w, "jar_installed_module := $(LOCAL_INSTALLED_MODULE)")
+ },
+ }
+ } else {
+ return android.AndroidMkData{
+ Class: "EXECUTABLES",
+ OutputFile: android.OptionalPathForPath(binary.wrapperFile),
+ Extra: []android.AndroidMkExtraFunc{
+ func(w io.Writer, outputFile android.Path) {
+ fmt.Fprintln(w, "LOCAL_STRIP_MODULE := false")
+ },
+ },
+ Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
+ android.WriteAndroidMkData(w, data)
+
+ // Ensure that the wrapper script timestamp is always updated when the jar is updated
+ fmt.Fprintln(w, "$(LOCAL_INSTALLED_MODULE): $(jar_installed_module)")
+ fmt.Fprintln(w, "jar_installed_module :=")
+ },
+ }
}
}
diff --git a/java/genrule.go b/java/genrule.go
new file mode 100644
index 0000000..80b7030
--- /dev/null
+++ b/java/genrule.go
@@ -0,0 +1,35 @@
+// 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 java
+
+import (
+ "android/soong/android"
+ "android/soong/genrule"
+)
+
+func init() {
+ android.RegisterModuleType("java_genrule", genRuleFactory)
+}
+
+// java_genrule is a genrule that can depend on other java_* objects.
+// The cmd may be run multiple times, once for each of the different host/device
+// variations.
+func genRuleFactory() android.Module {
+ module := genrule.NewGenRule()
+
+ android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon)
+
+ return module
+}
diff --git a/java/java.go b/java/java.go
index 4355200..0e54e3c 100644
--- a/java/java.go
+++ b/java/java.go
@@ -211,6 +211,12 @@
compiledSrcJars android.Paths
}
+func (j *Module) Srcs() android.Paths {
+ return android.Paths{j.implementationJarFile}
+}
+
+var _ android.SourceFileProducer = (*Module)(nil)
+
type Dependency interface {
HeaderJars() android.Paths
ImplementationJars() android.Paths
@@ -446,6 +452,15 @@
kotlinStdlib android.Paths
}
+func checkProducesJars(ctx android.ModuleContext, dep android.SourceFileProducer) {
+ for _, f := range dep.Srcs() {
+ if f.Ext() != ".jar" {
+ ctx.ModuleErrorf("genrule %q must generate files ending with .jar to be used as a libs or static_libs dependency",
+ ctx.OtherModuleName(dep.(blueprint.Module)))
+ }
+ }
+}
+
func (j *Module) collectDeps(ctx android.ModuleContext) deps {
var deps deps
@@ -462,8 +477,46 @@
otherName := ctx.OtherModuleName(module)
tag := ctx.OtherModuleDependencyTag(module)
- dep, _ := module.(Dependency)
- if dep == nil {
+ switch dep := module.(type) {
+ case Dependency:
+ switch tag {
+ case bootClasspathTag:
+ deps.bootClasspath = append(deps.bootClasspath, dep.HeaderJars()...)
+ case libTag:
+ deps.classpath = append(deps.classpath, dep.HeaderJars()...)
+ case staticLibTag:
+ deps.classpath = append(deps.classpath, dep.HeaderJars()...)
+ deps.staticJars = append(deps.staticJars, dep.ImplementationJars()...)
+ deps.staticHeaderJars = append(deps.staticHeaderJars, dep.HeaderJars()...)
+ case frameworkResTag:
+ if ctx.ModuleName() == "framework" {
+ // framework.jar has a one-off dependency on the R.java and Manifest.java files
+ // generated by framework-res.apk
+ deps.srcJars = append(deps.srcJars, dep.(*AndroidApp).aaptSrcJar)
+ }
+ case kotlinStdlibTag:
+ deps.kotlinStdlib = dep.HeaderJars()
+ default:
+ panic(fmt.Errorf("unknown dependency %q for %q", otherName, ctx.ModuleName()))
+ }
+
+ deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
+ case android.SourceFileProducer:
+ switch tag {
+ case libTag:
+ checkProducesJars(ctx, dep)
+ deps.classpath = append(deps.classpath, dep.Srcs()...)
+ case staticLibTag:
+ checkProducesJars(ctx, dep)
+ deps.classpath = append(deps.classpath, dep.Srcs()...)
+ deps.staticJars = append(deps.staticJars, dep.Srcs()...)
+ deps.staticHeaderJars = append(deps.staticHeaderJars, dep.Srcs()...)
+ case android.DefaultsDepTag, android.SourceDepTag:
+ // Nothing to do
+ default:
+ ctx.ModuleErrorf("dependency on genrule %q may only be in srcs, libs, or static_libs", otherName)
+ }
+ default:
switch tag {
case android.DefaultsDepTag, android.SourceDepTag:
// Nothing to do
@@ -479,31 +532,7 @@
default:
ctx.ModuleErrorf("depends on non-java module %q", otherName)
}
- return
}
-
- switch tag {
- case bootClasspathTag:
- deps.bootClasspath = append(deps.bootClasspath, dep.HeaderJars()...)
- case libTag:
- deps.classpath = append(deps.classpath, dep.HeaderJars()...)
- case staticLibTag:
- deps.classpath = append(deps.classpath, dep.HeaderJars()...)
- deps.staticJars = append(deps.staticJars, dep.ImplementationJars()...)
- deps.staticHeaderJars = append(deps.staticHeaderJars, dep.HeaderJars()...)
- case frameworkResTag:
- if ctx.ModuleName() == "framework" {
- // framework.jar has a one-off dependency on the R.java and Manifest.java files
- // generated by framework-res.apk
- deps.srcJars = append(deps.srcJars, dep.(*AndroidApp).aaptSrcJar)
- }
- case kotlinStdlibTag:
- deps.kotlinStdlib = dep.HeaderJars()
- default:
- panic(fmt.Errorf("unknown dependency %q for %q", otherName, ctx.ModuleName()))
- }
-
- deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs()...)
})
return deps
@@ -985,6 +1014,8 @@
binaryProperties binaryProperties
+ isWrapperVariant bool
+
wrapperFile android.SourcePath
binaryFile android.OutputPath
}
@@ -994,21 +1025,32 @@
}
func (j *Binary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
- j.Library.GenerateAndroidBuildActions(ctx)
-
- // Depend on the installed jar (j.installFile) so that the wrapper doesn't get executed by
- // another build rule before the jar has been installed.
- if String(j.binaryProperties.Wrapper) != "" {
- j.wrapperFile = android.PathForModuleSrc(ctx, String(j.binaryProperties.Wrapper)).SourcePath
+ if ctx.Arch().ArchType == android.Common {
+ // Compile the jar
+ j.Library.GenerateAndroidBuildActions(ctx)
} else {
- j.wrapperFile = android.PathForSource(ctx, "build/soong/scripts/jar-wrapper.sh")
+ // Handle the binary wrapper
+ j.isWrapperVariant = true
+
+ if String(j.binaryProperties.Wrapper) != "" {
+ j.wrapperFile = android.PathForModuleSrc(ctx, String(j.binaryProperties.Wrapper)).SourcePath
+ } else {
+ j.wrapperFile = android.PathForSource(ctx, "build/soong/scripts/jar-wrapper.sh")
+ }
+
+ // Depend on the installed jar so that the wrapper doesn't get executed by
+ // another build rule before the jar has been installed.
+ jarFile := ctx.PrimaryModule().(*Binary).installFile
+
+ j.binaryFile = ctx.InstallExecutable(android.PathForModuleInstall(ctx, "bin"),
+ ctx.ModuleName(), j.wrapperFile, jarFile)
}
- j.binaryFile = ctx.InstallExecutable(android.PathForModuleInstall(ctx, "bin"),
- ctx.ModuleName(), j.wrapperFile, j.installFile)
}
func (j *Binary) DepsMutator(ctx android.BottomUpMutatorContext) {
- j.deps(ctx)
+ if ctx.Arch().ArchType == android.Common {
+ j.deps(ctx)
+ }
}
func BinaryFactory() android.Module {
@@ -1020,7 +1062,8 @@
&module.Module.protoProperties,
&module.binaryProperties)
- InitJavaModule(module, android.HostAndDeviceSupported)
+ android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommonFirst)
+ android.InitDefaultableModule(module)
return module
}
@@ -1033,7 +1076,8 @@
&module.Module.protoProperties,
&module.binaryProperties)
- InitJavaModule(module, android.HostSupported)
+ android.InitAndroidArchModule(module, android.HostSupported, android.MultilibCommonFirst)
+ android.InitDefaultableModule(module)
return module
}
diff --git a/java/java_test.go b/java/java_test.go
index dbecc70..78fbd41 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -62,11 +62,13 @@
ctx := android.NewTestArchContext()
ctx.RegisterModuleType("android_app", android.ModuleFactoryAdaptor(AndroidAppFactory))
+ ctx.RegisterModuleType("java_binary_host", android.ModuleFactoryAdaptor(BinaryHostFactory))
ctx.RegisterModuleType("java_library", android.ModuleFactoryAdaptor(LibraryFactory(true)))
ctx.RegisterModuleType("java_library_host", android.ModuleFactoryAdaptor(LibraryHostFactory))
ctx.RegisterModuleType("java_import", android.ModuleFactoryAdaptor(ImportFactory))
ctx.RegisterModuleType("java_defaults", android.ModuleFactoryAdaptor(defaultsFactory))
ctx.RegisterModuleType("java_system_modules", android.ModuleFactoryAdaptor(SystemModulesFactory))
+ ctx.RegisterModuleType("java_genrule", android.ModuleFactoryAdaptor(genRuleFactory))
ctx.RegisterModuleType("filegroup", android.ModuleFactoryAdaptor(genrule.FileGroupFactory))
ctx.RegisterModuleType("genrule", android.ModuleFactoryAdaptor(genrule.GenRuleFactory))
ctx.PreArchMutators(android.RegisterPrebuiltsPreArchMutators)
@@ -146,6 +148,8 @@
// For framework-res, which is an implicit dependency for framework
"AndroidManifest.xml": nil,
"build/target/product/security/testkey": nil,
+
+ "build/soong/scripts/jar-wrapper.sh": nil,
}
for k, v := range fs {
@@ -158,6 +162,7 @@
}
func run(t *testing.T, ctx *android.TestContext, config android.Config) {
+ t.Helper()
_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
fail(t, errs)
_, errs = ctx.PrepareBuildActions(config)
@@ -165,6 +170,7 @@
}
func testJava(t *testing.T, bp string) *android.TestContext {
+ t.Helper()
config := testConfig(nil)
ctx := testContext(config, bp, nil)
run(t, ctx, config)
@@ -249,6 +255,35 @@
}
}
+func TestBinary(t *testing.T) {
+ ctx := testJava(t, `
+ java_library_host {
+ name: "foo",
+ srcs: ["a.java"],
+ }
+
+ java_binary_host {
+ name: "bar",
+ srcs: ["b.java"],
+ static_libs: ["foo"],
+ }
+ `)
+
+ buildOS := android.BuildOs.String()
+
+ bar := ctx.ModuleForTests("bar", buildOS+"_common")
+ barJar := bar.Output("bar.jar").Output.String()
+ barWrapper := ctx.ModuleForTests("bar", buildOS+"_x86_64")
+ barWrapperDeps := barWrapper.Output("bar").Implicits.Strings()
+
+ // Test that the install binary wrapper depends on the installed jar file
+ if len(barWrapperDeps) != 1 || barWrapperDeps[0] != barJar {
+ t.Errorf("expected binary wrapper implicits [%q], got %v",
+ barJar, barWrapperDeps)
+ }
+
+}
+
var classpathTestcases = []struct {
name string
moduleType string
@@ -775,7 +810,62 @@
}
}
+func TestJarGenrules(t *testing.T) {
+ ctx := testJava(t, `
+ java_library {
+ name: "foo",
+ srcs: ["a.java"],
+ }
+
+ java_genrule {
+ name: "jargen",
+ tool_files: ["b.java"],
+ cmd: "$(location b.java) $(in) $(out)",
+ out: ["jargen.jar"],
+ srcs: [":foo"],
+ }
+
+ java_library {
+ name: "bar",
+ static_libs: ["jargen"],
+ srcs: ["c.java"],
+ }
+
+ java_library {
+ name: "baz",
+ libs: ["jargen"],
+ srcs: ["c.java"],
+ }
+ `)
+
+ foo := ctx.ModuleForTests("foo", "android_common").Output("javac/foo.jar")
+ jargen := ctx.ModuleForTests("jargen", "android_common").Output("jargen.jar")
+ bar := ctx.ModuleForTests("bar", "android_common").Output("javac/bar.jar")
+ baz := ctx.ModuleForTests("baz", "android_common").Output("javac/baz.jar")
+ barCombined := ctx.ModuleForTests("bar", "android_common").Output("combined/bar.jar")
+
+ if len(jargen.Inputs) != 1 || jargen.Inputs[0].String() != foo.Output.String() {
+ t.Errorf("expected jargen inputs [%q], got %q", foo.Output.String(), jargen.Inputs.Strings())
+ }
+
+ if !strings.Contains(bar.Args["classpath"], jargen.Output.String()) {
+ t.Errorf("bar classpath %v does not contain %q", bar.Args["classpath"], jargen.Output.String())
+ }
+
+ if !strings.Contains(baz.Args["classpath"], jargen.Output.String()) {
+ t.Errorf("baz classpath %v does not contain %q", baz.Args["classpath"], jargen.Output.String())
+ }
+
+ if len(barCombined.Inputs) != 2 ||
+ barCombined.Inputs[0].String() != bar.Output.String() ||
+ barCombined.Inputs[1].String() != jargen.Output.String() {
+ t.Errorf("bar combined jar inputs %v is not [%q, %q]",
+ barCombined.Inputs.Strings(), bar.Output.String(), jargen.Output.String())
+ }
+}
+
func fail(t *testing.T, errs []error) {
+ t.Helper()
if len(errs) > 0 {
for _, err := range errs {
t.Error(err)
diff --git a/scripts/diff_build_graphs.sh b/scripts/diff_build_graphs.sh
index e7d8749..81010f3 100755
--- a/scripts/diff_build_graphs.sh
+++ b/scripts/diff_build_graphs.sh
@@ -120,7 +120,7 @@
diff -r "$unzipped1" "$unzipped2" -x build_date.txt -x build_number.txt -x '\.*' -x '*.log' -x build_fingerprint.txt -x build.ninja.d -x '*.zip' > $diffFile || true
if [[ -s "$diffFile" ]]; then
# outputs are different, so remove the unzipped versions but keep the zipped versions
- echo "Some differences for product $product:"
+ echo "First few differences (total diff linecount=$(wc -l $diffFile)) for product $product:"
cat "$diffFile" | head -n 10
echo "End of differences for product $product"
rm -rf "$unzipped1" "$unzipped2"
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index fb20d63..96f2274 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -106,6 +106,7 @@
"AUX_OS_VARIANT_LIST",
"TARGET_BUILD_PDK",
"PDK_FUSION_PLATFORM_ZIP",
+ "PRODUCT_SOONG_NAMESPACES",
}
func Banner(make_vars map[string]string) string {