Merge "Support require_root in auto-gen test configs"
diff --git a/Android.bp b/Android.bp
index 614e71f..fff17ef 100644
--- a/Android.bp
+++ b/Android.bp
@@ -296,6 +296,7 @@
"java/jdeps_test.go",
"java/kotlin_test.go",
"java/plugin_test.go",
+ "java/robolectric_test.go",
"java/sdk_test.go",
],
pluginFor: ["soong_build"],
diff --git a/android/namespace.go b/android/namespace.go
index 78d7f3c..27ec163 100644
--- a/android/namespace.go
+++ b/android/namespace.go
@@ -222,6 +222,11 @@
}
func (r *NameResolver) getNamespacesToSearchForModule(sourceNamespace *Namespace) (searchOrder []*Namespace) {
+ if sourceNamespace.visibleNamespaces == nil {
+ // When handling dependencies before namespaceMutator, assume they are non-Soong Blueprint modules and give
+ // access to all namespaces.
+ return r.sortedNamespaces.sortedItems()
+ }
return sourceNamespace.visibleNamespaces
}
diff --git a/android/namespace_test.go b/android/namespace_test.go
index 51a0af2..20241fe 100644
--- a/android/namespace_test.go
+++ b/android/namespace_test.go
@@ -91,6 +91,28 @@
// setupTest will report any errors
}
+func TestDependingOnBlueprintModuleInRootNamespace(t *testing.T) {
+ _ = setupTest(t,
+ map[string]string{
+ ".": `
+ blueprint_test_module {
+ name: "a",
+ }
+ `,
+ "dir1": `
+ soong_namespace {
+ }
+ blueprint_test_module {
+ name: "b",
+ deps: ["a"],
+ }
+ `,
+ },
+ )
+
+ // setupTest will report any errors
+}
+
func TestDependingOnModuleInImportedNamespace(t *testing.T) {
ctx := setupTest(t,
map[string]string{
@@ -617,6 +639,7 @@
ctx.MockFileSystem(bps)
ctx.RegisterModuleType("test_module", ModuleFactoryAdaptor(newTestModule))
ctx.RegisterModuleType("soong_namespace", ModuleFactoryAdaptor(NamespaceFactory))
+ ctx.RegisterModuleType("blueprint_test_module", newBlueprintTestModule)
ctx.PreArchMutators(RegisterNamespaceMutator)
ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
ctx.BottomUp("rename", renameMutator)
@@ -641,6 +664,7 @@
}
func setupTest(t *testing.T, bps map[string]string) (ctx *TestContext) {
+ t.Helper()
ctx, errs := setupTestExpectErrs(bps)
FailIfErrored(t, errs)
return ctx
@@ -718,3 +742,22 @@
InitAndroidModule(m)
return m
}
+
+type blueprintTestModule struct {
+ blueprint.SimpleName
+ properties struct {
+ Deps []string
+ }
+}
+
+func (b *blueprintTestModule) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
+ return b.properties.Deps
+}
+
+func (b *blueprintTestModule) GenerateBuildActions(blueprint.ModuleContext) {
+}
+
+func newBlueprintTestModule() (blueprint.Module, []interface{}) {
+ m := &blueprintTestModule{}
+ return m, []interface{}{&m.properties, &m.SimpleName.Properties}
+}
diff --git a/android/neverallow.go b/android/neverallow.go
index ecff62d..f751389 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -108,6 +108,9 @@
neverallow().
notIn(coreLibraryProjects...).
with("no_standard_libs", "true"),
+ neverallow().
+ notIn(coreLibraryProjects...).
+ with("sdk_version", "none"),
}
return rules
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index 9c43d53..62c5142 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -178,6 +178,37 @@
}`),
},
},
+ {
+ name: "sdk_version: \"none\" inside core libraries",
+ fs: map[string][]byte{
+ "libcore/Blueprints": []byte(`
+ java_library {
+ name: "inside_core_libraries",
+ sdk_version: "none",
+ }`),
+ },
+ },
+ {
+ name: "sdk_version: \"none\" outside core libraries",
+ fs: map[string][]byte{
+ "Blueprints": []byte(`
+ java_library {
+ name: "outside_core_libraries",
+ sdk_version: "none",
+ }`),
+ },
+ expectedError: "module \"outside_core_libraries\": violates neverallow",
+ },
+ {
+ name: "sdk_version: \"current\"",
+ fs: map[string][]byte{
+ "Blueprints": []byte(`
+ java_library {
+ name: "outside_core_libraries",
+ sdk_version: "current",
+ }`),
+ },
+ },
// java_library_host rule tests
{
name: "java_library_host with no_standard_libs: true",
@@ -266,6 +297,7 @@
type mockJavaLibraryProperties struct {
Libs []string
No_standard_libs *bool
+ Sdk_version *string
}
type mockJavaLibraryModule struct {
diff --git a/cc/pgo.go b/cc/pgo.go
index 7334ea2..4e915ff 100644
--- a/cc/pgo.go
+++ b/cc/pgo.go
@@ -27,10 +27,9 @@
var (
// Add flags to ignore warnings that profiles are old or missing for
- // some functions, and turn on the experimental new pass manager.
+ // some functions.
profileUseOtherFlags = []string{
"-Wno-backend-plugin",
- "-fexperimental-new-pass-manager",
}
globalPgoProfileProjects = []string{
diff --git a/java/aar.go b/java/aar.go
index 1e8e6d8..adea87f 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -188,8 +188,7 @@
return linkFlags, linkDeps, resDirs, overlayDirs, rroDirs, resourceZips
}
-func (a *aapt) deps(ctx android.BottomUpMutatorContext, sdkContext sdkContext) {
- sdkDep := decodeSdkDep(ctx, sdkContext)
+func (a *aapt) deps(ctx android.BottomUpMutatorContext, sdkDep sdkDep) {
if sdkDep.frameworkResModule != "" {
ctx.AddVariationDependencies(nil, frameworkResTag, sdkDep.frameworkResModule)
}
@@ -401,8 +400,9 @@
func (a *AndroidLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
a.Module.deps(ctx)
- if !Bool(a.properties.No_framework_libs) && !Bool(a.properties.No_standard_libs) {
- a.aapt.deps(ctx, sdkContext(a))
+ sdkDep := decodeSdkDep(ctx, sdkContext(a))
+ if sdkDep.hasFrameworkLibs() {
+ a.aapt.deps(ctx, sdkDep)
}
}
@@ -513,6 +513,14 @@
return a.sdkVersion()
}
+func (a *AARImport) noFrameworkLibs() bool {
+ return false
+}
+
+func (a *AARImport) noStandardLibs() bool {
+ return false
+}
+
var _ AndroidLibraryDependency = (*AARImport)(nil)
func (a *AARImport) ExportPackage() android.Path {
diff --git a/java/app.go b/java/app.go
index 78e0501..cab97de 100644
--- a/java/app.go
+++ b/java/app.go
@@ -159,8 +159,9 @@
ctx.PropertyErrorf("stl", "sdk_version must be set in order to use c++_shared")
}
- if !Bool(a.properties.No_framework_libs) && !Bool(a.properties.No_standard_libs) {
- a.aapt.deps(ctx, sdkContext(a))
+ sdkDep := decodeSdkDep(ctx, sdkContext(a))
+ if sdkDep.hasFrameworkLibs() {
+ a.aapt.deps(ctx, sdkDep)
}
embedJni := a.shouldEmbedJnis(ctx)
@@ -180,7 +181,7 @@
}
}
- a.usesLibrary.deps(ctx, Bool(a.properties.No_framework_libs))
+ a.usesLibrary.deps(ctx, sdkDep.hasFrameworkLibs())
}
func (a *AndroidApp) OverridablePropertiesDepsMutator(ctx android.BottomUpMutatorContext) {
@@ -783,7 +784,7 @@
ctx.AddDependency(ctx.Module(), certificateTag, cert)
}
- a.usesLibrary.deps(ctx, false)
+ a.usesLibrary.deps(ctx, true)
}
func (a *AndroidAppImport) uncompressEmbeddedJniLibs(
@@ -937,11 +938,14 @@
usesLibraryProperties UsesLibraryProperties
}
-func (u *usesLibrary) deps(ctx android.BottomUpMutatorContext, noFrameworkLibs bool) {
+func (u *usesLibrary) deps(ctx android.BottomUpMutatorContext, hasFrameworkLibs bool) {
if !ctx.Config().UnbundledBuild() {
ctx.AddVariationDependencies(nil, usesLibTag, u.usesLibraryProperties.Uses_libs...)
ctx.AddVariationDependencies(nil, usesLibTag, u.presentOptionalUsesLibs(ctx)...)
- if !noFrameworkLibs {
+ // Only add these extra dependencies if the module depends on framework libs. This avoids
+ // creating a cyclic dependency:
+ // e.g. framework-res -> org.apache.http.legacy -> ... -> framework-res.
+ if hasFrameworkLibs {
// dexpreopt/dexpreopt.go needs the paths to the dex jars of these libraries in case construct_context.sh needs
// to pass them to dex2oat. Add them as a dependency so we can determine the path to the dex jar of each
// library to dexpreopt.
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 992c8b5..e1476a2 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -538,16 +538,24 @@
return j.sdkVersion()
}
+func (j *Javadoc) noFrameworkLibs() bool {
+ return Bool(j.properties.No_framework_libs)
+}
+
+func (j *Javadoc) noStandardLibs() bool {
+ return Bool(j.properties.No_standard_libs)
+}
+
func (j *Javadoc) addDeps(ctx android.BottomUpMutatorContext) {
if ctx.Device() {
- if !Bool(j.properties.No_standard_libs) {
- sdkDep := decodeSdkDep(ctx, sdkContext(j))
+ sdkDep := decodeSdkDep(ctx, sdkContext(j))
+ if sdkDep.hasStandardLibs() {
if sdkDep.useDefaultLibs {
ctx.AddVariationDependencies(nil, bootClasspathTag, config.DefaultBootclasspathLibraries...)
if ctx.Config().TargetOpenJDK9() {
ctx.AddVariationDependencies(nil, systemModulesTag, config.DefaultSystemModules)
}
- if !Bool(j.properties.No_framework_libs) {
+ if sdkDep.hasFrameworkLibs() {
ctx.AddVariationDependencies(nil, libTag, config.DefaultLibraries...)
}
} else if sdkDep.useModule {
diff --git a/java/java.go b/java/java.go
index a1addb3..c42ca28 100644
--- a/java/java.go
+++ b/java/java.go
@@ -440,6 +440,16 @@
jars android.Paths
aidl android.OptionalPath
+
+ noStandardLibs, noFrameworksLibs bool
+}
+
+func (s sdkDep) hasStandardLibs() bool {
+ return !s.noStandardLibs
+}
+
+func (s sdkDep) hasFrameworkLibs() bool {
+ return !s.noStandardLibs && !s.noFrameworksLibs
}
type jniLib struct {
@@ -476,14 +486,22 @@
return j.sdkVersion()
}
+func (j *Module) noFrameworkLibs() bool {
+ return Bool(j.properties.No_framework_libs)
+}
+
+func (j *Module) noStandardLibs() bool {
+ return Bool(j.properties.No_standard_libs)
+}
+
func (j *Module) deps(ctx android.BottomUpMutatorContext) {
if ctx.Device() {
- if !Bool(j.properties.No_standard_libs) {
- sdkDep := decodeSdkDep(ctx, sdkContext(j))
+ sdkDep := decodeSdkDep(ctx, sdkContext(j))
+ if sdkDep.hasStandardLibs() {
if sdkDep.useDefaultLibs {
ctx.AddVariationDependencies(nil, bootClasspathTag, config.DefaultBootclasspathLibraries...)
ctx.AddVariationDependencies(nil, systemModulesTag, config.DefaultSystemModules)
- if !Bool(j.properties.No_framework_libs) {
+ if sdkDep.hasFrameworkLibs() {
ctx.AddVariationDependencies(nil, libTag, config.DefaultLibraries...)
}
} else if sdkDep.useModule {
@@ -664,7 +682,7 @@
return javaSdk, true
case ver == "current":
return javaSdk, false
- case ver == "":
+ case ver == "" || ver == "none":
return javaPlatform, false
default:
if _, err := strconv.Atoi(ver); err != nil {
@@ -842,7 +860,7 @@
var ret string
v := sdkContext.sdkVersion()
// For PDK builds, use the latest SDK version instead of "current"
- if ctx.Config().IsPdkBuild() && (v == "" || v == "current") {
+ if ctx.Config().IsPdkBuild() && (v == "" || v == "none" || v == "current") {
sdkVersions := ctx.Config().Get(sdkVersionsKey).([]int)
latestSdkVersion := 0
if len(sdkVersions) > 0 {
@@ -861,7 +879,7 @@
ret = "1.7"
} else if ctx.Device() && sdk <= 29 || !ctx.Config().TargetOpenJDK9() {
ret = "1.8"
- } else if ctx.Device() && sdkContext.sdkVersion() != "" && sdk == android.FutureApiLevel {
+ } else if ctx.Device() && sdkContext.sdkVersion() != "" && sdkContext.sdkVersion() != "none" && sdk == android.FutureApiLevel {
// TODO(ccross): once we generate stubs we should be able to use 1.9 for sdk_version: "current"
ret = "1.8"
} else {
@@ -913,7 +931,7 @@
flags.processor = strings.Join(deps.processorClasses, ",")
if len(flags.bootClasspath) == 0 && ctx.Host() && flags.javaVersion != "1.9" &&
- !Bool(j.properties.No_standard_libs) &&
+ decodeSdkDep(ctx, sdkContext(j)).hasStandardLibs() &&
inList(flags.javaVersion, []string{"1.6", "1.7", "1.8"}) {
// Give host-side tools a version of OpenJDK's standard libraries
// close to what they're targeting. As of Dec 2017, AOSP is only
diff --git a/java/java_test.go b/java/java_test.go
index 4c8367b..cecc8da 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -842,6 +842,19 @@
}
}
+func TestJavaLibrary(t *testing.T) {
+ config := testConfig(nil)
+ ctx := testContext(config, "", map[string][]byte{
+ "libcore/Android.bp": []byte(`
+ java_library {
+ name: "core",
+ sdk_version: "none",
+ system_modules: "none",
+ }`),
+ })
+ run(t, ctx, config)
+}
+
func TestJavaSdkLibrary(t *testing.T) {
ctx := testJava(t, `
droiddoc_template {
diff --git a/java/robolectric.go b/java/robolectric.go
index b87ee0d..1de56a5 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -17,6 +17,7 @@
import (
"fmt"
"io"
+ "strconv"
"strings"
"android/soong/android"
@@ -40,6 +41,9 @@
Test_options struct {
// Timeout in seconds when running the tests.
Timeout *int64
+
+ // Number of shards to use when running the tests.
+ Shards *int64
}
}
@@ -48,7 +52,8 @@
robolectricProperties robolectricProperties
- libs []string
+ libs []string
+ tests []string
}
func (r *robolectricTest) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -69,6 +74,39 @@
for _, dep := range ctx.GetDirectDepsWithTag(libTag) {
r.libs = append(r.libs, ctx.OtherModuleName(dep))
}
+
+ // TODO: this could all be removed if tradefed was used as the test runner, it will find everything
+ // annotated as a test and run it.
+ for _, src := range r.compiledJavaSrcs {
+ s := src.Rel()
+ if !strings.HasSuffix(s, "Test.java") {
+ continue
+ } else if strings.HasSuffix(s, "/BaseRobolectricTest.java") {
+ continue
+ } else if strings.HasPrefix(s, "src/") {
+ s = strings.TrimPrefix(s, "src/")
+ }
+ r.tests = append(r.tests, s)
+ }
+}
+
+func shardTests(paths []string, shards int) [][]string {
+ if shards > len(paths) {
+ shards = len(paths)
+ }
+ if shards == 0 {
+ return nil
+ }
+ ret := make([][]string, 0, shards)
+ shardSize := (len(paths) + shards - 1) / shards
+ for len(paths) > shardSize {
+ ret = append(ret, paths[0:shardSize])
+ paths = paths[shardSize:]
+ }
+ if len(paths) > 0 {
+ ret = append(ret, paths)
+ }
+ return ret
}
func (r *robolectricTest) AndroidMk() android.AndroidMkData {
@@ -77,24 +115,50 @@
data.Custom = func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
android.WriteAndroidMkData(w, data)
- fmt.Fprintln(w, "")
- fmt.Fprintln(w, "include $(CLEAR_VARS)")
- fmt.Fprintln(w, "LOCAL_MODULE := Run"+name)
- fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES :=", name)
- fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES += ", strings.Join(r.libs, " "))
- fmt.Fprintln(w, "LOCAL_TEST_PACKAGE :=", String(r.robolectricProperties.Instrumentation_for))
- if t := r.robolectricProperties.Test_options.Timeout; t != nil {
- fmt.Fprintln(w, "LOCAL_ROBOTEST_TIMEOUT :=", *t)
+ if s := r.robolectricProperties.Test_options.Shards; s != nil && *s > 1 {
+ shards := shardTests(r.tests, int(*s))
+ for i, shard := range shards {
+ r.writeTestRunner(w, name, "Run"+name+strconv.Itoa(i), shard)
+ }
+
+ // TODO: add rules to dist the outputs of the individual tests, or combine them together?
+ fmt.Fprintln(w, "")
+ fmt.Fprintln(w, ".PHONY:", "Run"+name)
+ fmt.Fprintln(w, "Run"+name, ": \\")
+ for i := range shards {
+ fmt.Fprintln(w, " ", "Run"+name+strconv.Itoa(i), "\\")
+ }
+ fmt.Fprintln(w, "")
+ } else {
+ r.writeTestRunner(w, name, "Run"+name, r.tests)
}
- fmt.Fprintln(w, "-include external/robolectric-shadows/run_robotests.mk")
}
return data
}
+func (r *robolectricTest) writeTestRunner(w io.Writer, module, name string, tests []string) {
+ fmt.Fprintln(w, "")
+ fmt.Fprintln(w, "include $(CLEAR_VARS)")
+ fmt.Fprintln(w, "LOCAL_MODULE :=", name)
+ fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES :=", module)
+ fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES += ", strings.Join(r.libs, " "))
+ fmt.Fprintln(w, "LOCAL_TEST_PACKAGE :=", String(r.robolectricProperties.Instrumentation_for))
+ fmt.Fprintln(w, "LOCAL_ROBOTEST_FILES :=", strings.Join(tests, " "))
+ if t := r.robolectricProperties.Test_options.Timeout; t != nil {
+ fmt.Fprintln(w, "LOCAL_ROBOTEST_TIMEOUT :=", *t)
+ }
+ fmt.Fprintln(w, "-include external/robolectric-shadows/run_robotests.mk")
+
+}
+
// An android_robolectric_test module compiles tests against the Robolectric framework that can run on the local host
// instead of on a device. It also generates a rule with the name of the module prefixed with "Run" that can be
// used to run the tests. Running the tests with build rule will eventually be deprecated and replaced with atest.
+//
+// The test runner considers any file listed in srcs whose name ends with Test.java to be a test class, unless
+// it is named BaseRobolectricTest.java. The path to the each source file must exactly match the package
+// name, or match the package name when the prefix "src/" is removed.
func RobolectricTestFactory() android.Module {
module := &robolectricTest{}
diff --git a/java/robolectric_test.go b/java/robolectric_test.go
new file mode 100644
index 0000000..e89c6e7
--- /dev/null
+++ b/java/robolectric_test.go
@@ -0,0 +1,88 @@
+// Copyright 2019 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 (
+ "reflect"
+ "testing"
+)
+
+func Test_shardTests(t *testing.T) {
+ type args struct {
+ paths []string
+ shards int
+ }
+ tests := []struct {
+ name string
+ args args
+ want [][]string
+ }{
+ {
+ name: "empty",
+ args: args{
+ paths: nil,
+ shards: 1,
+ },
+ want: [][]string(nil),
+ },
+ {
+ name: "too many shards",
+ args: args{
+ paths: []string{"a", "b"},
+ shards: 3,
+ },
+ want: [][]string{{"a"}, {"b"}},
+ },
+ {
+ name: "single shard",
+ args: args{
+ paths: []string{"a", "b"},
+ shards: 1,
+ },
+ want: [][]string{{"a", "b"}},
+ },
+ {
+ name: "shard per input",
+ args: args{
+ paths: []string{"a", "b", "c"},
+ shards: 3,
+ },
+ want: [][]string{{"a"}, {"b"}, {"c"}},
+ },
+ {
+ name: "balanced shards",
+ args: args{
+ paths: []string{"a", "b", "c", "d"},
+ shards: 2,
+ },
+ want: [][]string{{"a", "b"}, {"c", "d"}},
+ },
+ {
+ name: "unbalanced shards",
+ args: args{
+ paths: []string{"a", "b", "c"},
+ shards: 2,
+ },
+ want: [][]string{{"a", "b"}, {"c"}},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := shardTests(tt.args.paths, tt.args.shards); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("shardTests() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/java/sdk.go b/java/sdk.go
index 90b8fac..9dfb38b 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -38,17 +38,23 @@
var apiFingerprintPathKey = android.NewOnceKey("apiFingerprintPathKey")
type sdkContext interface {
- // sdkVersion eturns the sdk_version property of the current module, or an empty string if it is not set.
+ // sdkVersion returns the sdk_version property of the current module, or an empty string if it is not set.
sdkVersion() string
// minSdkVersion returns the min_sdk_version property of the current module, or sdkVersion() if it is not set.
minSdkVersion() string
// targetSdkVersion returns the target_sdk_version property of the current module, or sdkVersion() if it is not set.
targetSdkVersion() string
+
+ // Temporarily provide access to the no_standard_libs property (where present).
+ noStandardLibs() bool
+
+ // Temporarily provide access to the no_frameworks_libs property (where present).
+ noFrameworkLibs() bool
}
func sdkVersionOrDefault(ctx android.BaseModuleContext, v string) string {
switch v {
- case "", "current", "system_current", "test_current", "core_current":
+ case "", "none", "current", "system_current", "test_current", "core_current":
return ctx.Config().DefaultAppTargetSdk()
default:
return v
@@ -59,7 +65,7 @@
// it returns android.FutureApiLevel (10000).
func sdkVersionToNumber(ctx android.BaseModuleContext, v string) (int, error) {
switch v {
- case "", "current", "test_current", "system_current", "core_current":
+ case "", "none", "current", "test_current", "system_current", "core_current":
return ctx.Config().DefaultAppTargetSdkInt(), nil
default:
n := android.GetNumericSdkVersion(v)
@@ -138,6 +144,10 @@
useFiles: true,
jars: android.Paths{jarPath.Path(), lambdaStubsPath},
aidl: android.OptionalPathForPath(aidlPath.Path()),
+
+ // Pass values straight through for now to match previous behavior.
+ noStandardLibs: sdkContext.noStandardLibs(),
+ noFrameworksLibs: sdkContext.noFrameworkLibs(),
}
}
@@ -148,6 +158,10 @@
systemModules: m + "_system_modules",
frameworkResModule: r,
aidl: android.OptionalPathForPath(aidl),
+
+ // Pass values straight through for now to match previous behavior.
+ noStandardLibs: sdkContext.noStandardLibs(),
+ noFrameworksLibs: sdkContext.noFrameworkLibs(),
}
if m == "core.current.stubs" {
@@ -173,7 +187,7 @@
}
}
- if ctx.Config().UnbundledBuildUsePrebuiltSdks() && v != "" {
+ if ctx.Config().UnbundledBuildUsePrebuiltSdks() && v != "" && v != "none" {
return toPrebuilt(v)
}
@@ -182,6 +196,14 @@
return sdkDep{
useDefaultLibs: true,
frameworkResModule: "framework-res",
+
+ // Pass values straight through for now to match previous behavior.
+ noStandardLibs: sdkContext.noStandardLibs(),
+ noFrameworksLibs: sdkContext.noFrameworkLibs(),
+ }
+ case "none":
+ return sdkDep{
+ noStandardLibs: true,
}
case "current":
return toModule("android_stubs_current", "framework-res", sdkFrameworkAidlPath(ctx))
diff --git a/java/sdk_library.go b/java/sdk_library.go
index e383533..01531c5 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -159,7 +159,8 @@
ctx.AddVariationDependencies(nil, publicApiStubsTag, module.stubsName(apiScopePublic))
ctx.AddVariationDependencies(nil, publicApiFileTag, module.docsName(apiScopePublic))
- if !Bool(module.properties.No_standard_libs) {
+ sdkDep := decodeSdkDep(ctx, sdkContext(&module.Library))
+ if sdkDep.hasStandardLibs() {
ctx.AddVariationDependencies(nil, systemApiStubsTag, module.stubsName(apiScopeSystem))
ctx.AddVariationDependencies(nil, systemApiFileTag, module.docsName(apiScopeSystem))
ctx.AddVariationDependencies(nil, testApiFileTag, module.docsName(apiScopeTest))
@@ -401,17 +402,22 @@
}
}{}
+ sdkVersion := module.sdkVersion(apiScope)
+ sdkDep := decodeSdkDep(mctx, sdkContext(&module.Library))
+ if !sdkDep.hasStandardLibs() {
+ sdkVersion = "none"
+ }
+
props.Name = proptools.StringPtr(module.stubsName(apiScope))
// sources are generated from the droiddoc
props.Srcs = []string{":" + module.docsName(apiScope)}
- props.Sdk_version = proptools.StringPtr(module.sdkVersion(apiScope))
+ props.Sdk_version = proptools.StringPtr(sdkVersion)
props.Libs = module.sdkLibraryProperties.Stub_only_libs
// Unbundled apps will use the prebult one from /prebuilts/sdk
if mctx.Config().UnbundledBuildUsePrebuiltSdks() {
props.Product_variables.Unbundled_build.Enabled = proptools.BoolPtr(false)
}
props.Product_variables.Pdk.Enabled = proptools.BoolPtr(false)
- props.No_standard_libs = module.Library.Module.properties.No_standard_libs
props.System_modules = module.Library.Module.deviceProperties.System_modules
props.Openjdk9.Srcs = module.Library.Module.properties.Openjdk9.Srcs
props.Openjdk9.Javacflags = module.Library.Module.properties.Openjdk9.Javacflags
@@ -441,13 +447,13 @@
Srcs_lib *string
Srcs_lib_whitelist_dirs []string
Srcs_lib_whitelist_pkgs []string
+ Sdk_version *string
Libs []string
Arg_files []string
Args *string
Api_tag_name *string
Api_filename *string
Removed_api_filename *string
- No_standard_libs *bool
Java_version *string
Merge_annotations_dirs []string
Merge_inclusion_annotations_dirs []string
@@ -462,9 +468,16 @@
}
}{}
+ sdkDep := decodeSdkDep(mctx, sdkContext(&module.Library))
+ sdkVersion := ""
+ if !sdkDep.hasStandardLibs() {
+ sdkVersion = "none"
+ }
+
props.Name = proptools.StringPtr(module.docsName(apiScope))
props.Srcs = append(props.Srcs, module.Library.Module.properties.Srcs...)
props.Srcs = append(props.Srcs, module.sdkLibraryProperties.Api_srcs...)
+ props.Sdk_version = proptools.StringPtr(sdkVersion)
props.Installable = proptools.BoolPtr(false)
// A droiddoc module has only one Libs property and doesn't distinguish between
// shared libs and static libs. So we need to add both of these libs to Libs property.
@@ -472,7 +485,6 @@
props.Libs = append(props.Libs, module.Library.Module.properties.Static_libs...)
props.Aidl.Include_dirs = module.Library.Module.deviceProperties.Aidl.Include_dirs
props.Aidl.Local_include_dirs = module.Library.Module.deviceProperties.Aidl.Local_include_dirs
- props.No_standard_libs = module.Library.Module.properties.No_standard_libs
props.Java_version = module.Library.Module.properties.Java_version
props.Merge_annotations_dirs = module.sdkLibraryProperties.Merge_annotations_dirs
@@ -593,7 +605,7 @@
func (module *SdkLibrary) PrebuiltJars(ctx android.BaseModuleContext, sdkVersion string) android.Paths {
var api, v string
- if sdkVersion == "" {
+ if sdkVersion == "" || sdkVersion == "none" {
api = "system"
v = "current"
} else if strings.Contains(sdkVersion, "_") {
@@ -701,7 +713,8 @@
module.createStubsLibrary(mctx, apiScopePublic)
module.createDocs(mctx, apiScopePublic)
- if !Bool(module.properties.No_standard_libs) {
+ sdkDep := decodeSdkDep(mctx, sdkContext(&module.Library))
+ if sdkDep.hasStandardLibs() {
// for system API stubs
module.createStubsLibrary(mctx, apiScopeSystem)
module.createDocs(mctx, apiScopeSystem)
diff --git a/java/sdk_test.go b/java/sdk_test.go
index 23d7a98..1efe83b 100644
--- a/java/sdk_test.go
+++ b/java/sdk_test.go
@@ -113,7 +113,7 @@
},
{
- name: "nostdlib",
+ name: "nostdlib - no_standard_libs: true",
properties: `no_standard_libs: true, system_modules: "none"`,
system: "none",
bootclasspath: []string{`""`},
@@ -121,7 +121,15 @@
},
{
- name: "nostdlib system_modules",
+ name: "nostdlib",
+ properties: `sdk_version: "none", system_modules: "none"`,
+ system: "none",
+ bootclasspath: []string{`""`},
+ classpath: []string{},
+ },
+ {
+
+ name: "nostdlib system_modules - no_standard_libs: true",
properties: `no_standard_libs: true, system_modules: "core-platform-api-stubs-system-modules"`,
system: "core-platform-api-stubs-system-modules",
bootclasspath: []string{`""`},
@@ -129,6 +137,14 @@
},
{
+ name: "nostdlib system_modules",
+ properties: `sdk_version: "none", system_modules: "core-platform-api-stubs-system-modules"`,
+ system: "core-platform-api-stubs-system-modules",
+ bootclasspath: []string{`""`},
+ classpath: []string{},
+ },
+ {
+
name: "host default",
moduleType: "java_library_host",
properties: ``,
@@ -145,12 +161,18 @@
bootclasspath: []string{"jdk8/jre/lib/jce.jar", "jdk8/jre/lib/rt.jar"},
},
{
- name: "host supported nostdlib",
+ name: "host supported nostdlib - no_standard_libs: true",
host: android.Host,
properties: `host_supported: true, no_standard_libs: true, system_modules: "none"`,
classpath: []string{},
},
{
+ name: "host supported nostdlib",
+ host: android.Host,
+ properties: `host_supported: true, sdk_version: "none", system_modules: "none"`,
+ classpath: []string{},
+ },
+ {
name: "unbundled sdk v25",
unbundled: true,
diff --git a/java/testing.go b/java/testing.go
index a9d4670..7cd1871 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -54,7 +54,7 @@
java_library {
name: "%s",
srcs: ["a.java"],
- no_standard_libs: true,
+ sdk_version: "none",
system_modules: "core-platform-api-stubs-system-modules",
}
`, extra)
diff --git a/ui/build/config.go b/ui/build/config.go
index c298f00..6df9529 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -61,6 +61,28 @@
const srcDirFileCheck = "build/soong/root.bp"
+type BuildAction uint
+
+const (
+ // Builds all of the modules and their dependencies of a specified directory, relative to the root
+ // directory of the source tree.
+ BUILD_MODULES_IN_A_DIRECTORY BuildAction = iota
+
+ // Builds all of the modules and their dependencies of a list of specified directories. All specified
+ // directories are relative to the root directory of the source tree.
+ BUILD_MODULES_IN_DIRECTORIES
+)
+
+// checkTopDir validates that the current directory is at the root directory of the source tree.
+func checkTopDir(ctx Context) {
+ if _, err := os.Stat(srcDirFileCheck); err != nil {
+ if os.IsNotExist(err) {
+ ctx.Fatalf("Current working directory must be the source tree. %q not found.", srcDirFileCheck)
+ }
+ ctx.Fatalln("Error verifying tree state:", err)
+ }
+}
+
func NewConfig(ctx Context, args ...string) Config {
ret := &configImpl{
environ: OsEnvironment(),
@@ -154,12 +176,7 @@
ret.environ.Set("TMPDIR", absPath(ctx, ret.TempDir()))
// Precondition: the current directory is the top of the source tree
- if _, err := os.Stat(srcDirFileCheck); err != nil {
- if os.IsNotExist(err) {
- log.Fatalf("Current working directory must be the source tree. %q not found", srcDirFileCheck)
- }
- log.Fatalln("Error verifying tree state:", err)
- }
+ checkTopDir(ctx)
if srcDir := absPath(ctx, "."); strings.ContainsRune(srcDir, ' ') {
log.Println("You are building in a directory whose absolute path contains a space character:")
@@ -229,6 +246,203 @@
return Config{ret}
}
+// NewBuildActionConfig returns a build configuration based on the build action. The arguments are
+// processed based on the build action and extracts any arguments that belongs to the build action.
+func NewBuildActionConfig(action BuildAction, dir string, buildDependencies bool, ctx Context, args ...string) Config {
+ return NewConfig(ctx, getConfigArgs(action, dir, buildDependencies, ctx, args)...)
+}
+
+// getConfigArgs processes the command arguments based on the build action and creates a set of new
+// arguments to be accepted by Config.
+func getConfigArgs(action BuildAction, dir string, buildDependencies bool, ctx Context, args []string) []string {
+ // The next block of code verifies that the current directory is the root directory of the source
+ // tree. It then finds the relative path of dir based on the root directory of the source tree
+ // and verify that dir is inside of the source tree.
+ checkTopDir(ctx)
+ topDir, err := os.Getwd()
+ if err != nil {
+ ctx.Fatalf("Error retrieving top directory: %v", err)
+ }
+ dir, err = filepath.Abs(dir)
+ if err != nil {
+ ctx.Fatalf("Unable to find absolute path %s: %v", dir, err)
+ }
+ relDir, err := filepath.Rel(topDir, dir)
+ if err != nil {
+ ctx.Fatalf("Unable to find relative path %s of %s: %v", relDir, topDir, err)
+ }
+ // If there are ".." in the path, it's not in the source tree.
+ if strings.Contains(relDir, "..") {
+ ctx.Fatalf("Directory %s is not under the source tree %s", dir, topDir)
+ }
+
+ configArgs := args[:]
+
+ // If the arguments contains GET-INSTALL-PATH, change the target name prefix from MODULES-IN- to
+ // GET-INSTALL-PATH-IN- to extract the installation path instead of building the modules.
+ targetNamePrefix := "MODULES-IN-"
+ if inList("GET-INSTALL-PATH", configArgs) {
+ targetNamePrefix = "GET-INSTALL-PATH-IN-"
+ configArgs = removeFromList("GET-INSTALL-PATH", configArgs)
+ }
+
+ var buildFiles []string
+ var targets []string
+
+ switch action {
+ case BUILD_MODULES_IN_A_DIRECTORY:
+ // If dir is the root source tree, all the modules are built of the source tree are built so
+ // no need to find the build file.
+ if topDir == dir {
+ break
+ }
+ // Find the build file from the directory where the build action was triggered by traversing up
+ // the source tree. If a blank build filename is returned, simply use the directory where the build
+ // action was invoked.
+ buildFile := findBuildFile(ctx, relDir)
+ if buildFile == "" {
+ buildFile = filepath.Join(relDir, "Android.mk")
+ }
+ buildFiles = []string{buildFile}
+ targets = []string{convertToTarget(filepath.Dir(buildFile), targetNamePrefix)}
+ case BUILD_MODULES_IN_DIRECTORIES:
+ newConfigArgs, dirs := splitArgs(configArgs)
+ configArgs = newConfigArgs
+ targets, buildFiles = getTargetsFromDirs(ctx, relDir, dirs, targetNamePrefix)
+ }
+
+ // This is to support building modules without building their dependencies. Soon, this will be
+ // deprecated.
+ if !buildDependencies && len(buildFiles) > 0 {
+ if err := os.Setenv("ONE_SHOT_MAKEFILE", strings.Join(buildFiles, " ")); err != nil {
+ ctx.Fatalf("Unable to set ONE_SHOT_MAKEFILE environment variable: %v", err)
+ }
+ }
+
+ // Tidy only override all other specified targets.
+ tidyOnly := os.Getenv("WITH_TIDY_ONLY")
+ if tidyOnly == "true" || tidyOnly == "1" {
+ configArgs = append(configArgs, "tidy_only")
+ } else {
+ configArgs = append(configArgs, targets...)
+ }
+
+ return configArgs
+}
+
+// convertToTarget replaces "/" to "-" in dir and pre-append the targetNamePrefix to the target name.
+func convertToTarget(dir string, targetNamePrefix string) string {
+ return targetNamePrefix + strings.ReplaceAll(dir, "/", "-")
+}
+
+// findBuildFile finds a build file (makefile or blueprint file) by looking at dir first. If not
+// found, go up one level and repeat again until one is found and the path of that build file
+// relative to the root directory of the source tree is returned. The returned filename of build
+// file is "Android.mk". If one was not found, a blank string is returned.
+func findBuildFile(ctx Context, dir string) string {
+ // If the string is empty, assume it is top directory of the source tree.
+ if dir == "" {
+ return ""
+ }
+
+ for ; dir != "."; dir = filepath.Dir(dir) {
+ for _, buildFile := range []string{"Android.bp", "Android.mk"} {
+ _, err := os.Stat(filepath.Join(dir, buildFile))
+ if err == nil {
+ // Returning the filename Android.mk as it might be used for ONE_SHOT_MAKEFILE variable.
+ return filepath.Join(dir, "Android.mk")
+ }
+ if !os.IsNotExist(err) {
+ ctx.Fatalf("Error retrieving the build file stats: %v", err)
+ }
+ }
+ }
+
+ return ""
+}
+
+// splitArgs iterates over the arguments list and splits into two lists: arguments and directories.
+func splitArgs(args []string) (newArgs []string, dirs []string) {
+ specialArgs := map[string]bool{
+ "showcommands": true,
+ "snod": true,
+ "dist": true,
+ "checkbuild": true,
+ }
+
+ newArgs = []string{}
+ dirs = []string{}
+
+ for _, arg := range args {
+ // It's a dash argument if it starts with "-" or it's a key=value pair, it's not a directory.
+ if strings.IndexRune(arg, '-') == 0 || strings.IndexRune(arg, '=') != -1 {
+ newArgs = append(newArgs, arg)
+ continue
+ }
+
+ if _, ok := specialArgs[arg]; ok {
+ newArgs = append(newArgs, arg)
+ continue
+ }
+
+ dirs = append(dirs, arg)
+ }
+
+ return newArgs, dirs
+}
+
+// getTargetsFromDirs iterates over the dirs list and creates a list of targets to build. If a
+// directory from the dirs list does not exist, a fatal error is raised. relDir is related to the
+// source root tree where the build action command was invoked. Each directory is validated if the
+// build file can be found and follows the format "dir1:target1,target2,...". Target is optional.
+func getTargetsFromDirs(ctx Context, relDir string, dirs []string, targetNamePrefix string) (targets []string, buildFiles []string) {
+ for _, dir := range dirs {
+ // The directory may have specified specific modules to build. ":" is the separator to separate
+ // the directory and the list of modules.
+ s := strings.Split(dir, ":")
+ l := len(s)
+ if l > 2 { // more than one ":" was specified.
+ ctx.Fatalf("%s not in proper directory:target1,target2,... format (\":\" was specified more than once)", dir)
+ }
+
+ dir = filepath.Join(relDir, s[0])
+ if _, err := os.Stat(dir); err != nil {
+ ctx.Fatalf("couldn't find directory %s", dir)
+ }
+
+ // Verify that if there are any targets specified after ":". Each target is separated by ",".
+ var newTargets []string
+ if l == 2 && s[1] != "" {
+ newTargets = strings.Split(s[1], ",")
+ if inList("", newTargets) {
+ ctx.Fatalf("%s not in proper directory:target1,target2,... format", dir)
+ }
+ }
+
+ buildFile := findBuildFile(ctx, dir)
+ if buildFile == "" {
+ ctx.Fatalf("Build file not found for %s directory", dir)
+ }
+ buildFileDir := filepath.Dir(buildFile)
+
+ // If there are specified targets, find the build file in the directory. If dir does not
+ // contain the build file, bail out as it is required for one shot build. If there are no
+ // target specified, build all the modules in dir (or the closest one in the dir path).
+ if len(newTargets) > 0 {
+ if buildFileDir != dir {
+ ctx.Fatalf("Couldn't locate a build file from %s directory", dir)
+ }
+ } else {
+ newTargets = []string{convertToTarget(buildFileDir, targetNamePrefix)}
+ }
+
+ buildFiles = append(buildFiles, buildFile)
+ targets = append(targets, newTargets...)
+ }
+
+ return targets, buildFiles
+}
+
func (c *configImpl) parseArgs(ctx Context, args []string) {
for i := 0; i < len(args); i++ {
arg := strings.TrimSpace(args[i])
diff --git a/ui/build/config_test.go b/ui/build/config_test.go
index 1d23fec..1ef5456 100644
--- a/ui/build/config_test.go
+++ b/ui/build/config_test.go
@@ -17,6 +17,10 @@
import (
"bytes"
"context"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
"reflect"
"strings"
"testing"
@@ -172,3 +176,877 @@
})
}
}
+
+func TestConfigCheckTopDir(t *testing.T) {
+ ctx := testContext()
+ buildRootDir := filepath.Dir(srcDirFileCheck)
+ expectedErrStr := fmt.Sprintf("Current working directory must be the source tree. %q not found.", srcDirFileCheck)
+
+ tests := []struct {
+ // ********* Setup *********
+ // Test description.
+ description string
+
+ // ********* Action *********
+ // If set to true, the build root file is created.
+ rootBuildFile bool
+
+ // The current path where Soong is being executed.
+ path string
+
+ // ********* Validation *********
+ // Expecting error and validate the error string against expectedErrStr.
+ wantErr bool
+ }{{
+ description: "current directory is the root source tree",
+ rootBuildFile: true,
+ path: ".",
+ wantErr: false,
+ }, {
+ description: "one level deep in the source tree",
+ rootBuildFile: true,
+ path: "1",
+ wantErr: true,
+ }, {
+ description: "very deep in the source tree",
+ rootBuildFile: true,
+ path: "1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7/8/9/1/2/3/4/5/6/7",
+ wantErr: true,
+ }, {
+ description: "outside of source tree",
+ rootBuildFile: false,
+ path: "1/2/3/4/5",
+ wantErr: true,
+ }}
+
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ defer logger.Recover(func(err error) {
+ if !tt.wantErr {
+ t.Fatalf("Got unexpected error: %v", err)
+ }
+ if expectedErrStr != err.Error() {
+ t.Fatalf("expected %s, got %s", expectedErrStr, err.Error())
+ }
+ })
+
+ // Create the root source tree.
+ rootDir, err := ioutil.TempDir("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(rootDir)
+
+ // Create the build root file. This is to test if topDir returns an error if the build root
+ // file does not exist.
+ if tt.rootBuildFile {
+ dir := filepath.Join(rootDir, buildRootDir)
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ t.Errorf("failed to create %s directory: %v", dir, err)
+ }
+ f := filepath.Join(rootDir, srcDirFileCheck)
+ if err := ioutil.WriteFile(f, []byte{}, 0644); err != nil {
+ t.Errorf("failed to create file %s: %v", f, err)
+ }
+ }
+
+ // Next block of code is to set the current directory.
+ dir := rootDir
+ if tt.path != "" {
+ dir = filepath.Join(dir, tt.path)
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ t.Errorf("failed to create %s directory: %v", dir, err)
+ }
+ }
+ curDir, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("failed to get the current directory: %v", err)
+ }
+ defer func() { os.Chdir(curDir) }()
+
+ if err := os.Chdir(dir); err != nil {
+ t.Fatalf("failed to change directory to %s: %v", dir, err)
+ }
+
+ checkTopDir(ctx)
+ })
+ }
+}
+
+func TestConfigConvertToTarget(t *testing.T) {
+ tests := []struct {
+ // ********* Setup *********
+ // Test description.
+ description string
+
+ // ********* Action *********
+ // The current directory where Soong is being executed.
+ dir string
+
+ // The current prefix string to be pre-appended to the target.
+ prefix string
+
+ // ********* Validation *********
+ // The expected target to be invoked in ninja.
+ expectedTarget string
+ }{{
+ description: "one level directory in source tree",
+ dir: "test1",
+ prefix: "MODULES-IN-",
+ expectedTarget: "MODULES-IN-test1",
+ }, {
+ description: "multiple level directories in source tree",
+ dir: "test1/test2/test3/test4",
+ prefix: "GET-INSTALL-PATH-IN-",
+ expectedTarget: "GET-INSTALL-PATH-IN-test1-test2-test3-test4",
+ }}
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ target := convertToTarget(tt.dir, tt.prefix)
+ if target != tt.expectedTarget {
+ t.Errorf("expected %s, got %s for target", tt.expectedTarget, target)
+ }
+ })
+ }
+}
+
+func setTop(t *testing.T, dir string) func() {
+ curDir, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("failed to get current directory: %v", err)
+ }
+ if err := os.Chdir(dir); err != nil {
+ t.Fatalf("failed to change directory to top dir %s: %v", dir, err)
+ }
+ return func() { os.Chdir(curDir) }
+}
+
+func createBuildFiles(t *testing.T, topDir string, buildFiles []string) {
+ for _, buildFile := range buildFiles {
+ buildFile = filepath.Join(topDir, buildFile)
+ if err := ioutil.WriteFile(buildFile, []byte{}, 0644); err != nil {
+ t.Errorf("failed to create file %s: %v", buildFile, err)
+ }
+ }
+}
+
+func createDirectories(t *testing.T, topDir string, dirs []string) {
+ for _, dir := range dirs {
+ dir = filepath.Join(topDir, dir)
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ t.Errorf("failed to create %s directory: %v", dir, err)
+ }
+ }
+}
+
+func TestConfigGetTargets(t *testing.T) {
+ ctx := testContext()
+ tests := []struct {
+ // ********* Setup *********
+ // Test description.
+ description string
+
+ // Directories that exist in the source tree.
+ dirsInTrees []string
+
+ // Build files that exists in the source tree.
+ buildFiles []string
+
+ // ********* Action *********
+ // Directories passed in to soong_ui.
+ dirs []string
+
+ // Current directory that the user executed the build action command.
+ curDir string
+
+ // ********* Validation *********
+ // Expected targets from the function.
+ expectedTargets []string
+
+ // Expected build from the build system.
+ expectedBuildFiles []string
+
+ // Expecting error from running test case.
+ errStr string
+ }{{
+ description: "one target dir specified",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.bp"},
+ dirs: []string{"1/2/3"},
+ curDir: "0",
+ expectedTargets: []string{"MODULES-IN-0-1-2-3"},
+ expectedBuildFiles: []string{"0/1/2/3/Android.mk"},
+ }, {
+ description: "one target dir specified, build file does not exist",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{},
+ dirs: []string{"1/2/3"},
+ curDir: "0",
+ errStr: "Build file not found for 0/1/2/3 directory",
+ }, {
+ description: "one target dir specified, invalid targets specified",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{},
+ dirs: []string{"1/2/3:t1:t2"},
+ curDir: "0",
+ errStr: "1/2/3:t1:t2 not in proper directory:target1,target2,... format (\":\" was specified more than once)",
+ }, {
+ description: "one target dir specified, no targets specified but has colon",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.bp"},
+ dirs: []string{"1/2/3:"},
+ curDir: "0",
+ expectedTargets: []string{"MODULES-IN-0-1-2-3"},
+ expectedBuildFiles: []string{"0/1/2/3/Android.mk"},
+ }, {
+ description: "one target dir specified, two targets specified",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.bp"},
+ dirs: []string{"1/2/3:t1,t2"},
+ curDir: "0",
+ expectedTargets: []string{"t1", "t2"},
+ expectedBuildFiles: []string{"0/1/2/3/Android.mk"},
+ }, {
+ description: "one target dir specified, no targets and has a comma",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.bp"},
+ dirs: []string{"1/2/3:,"},
+ curDir: "0",
+ errStr: "0/1/2/3 not in proper directory:target1,target2,... format",
+ }, {
+ description: "one target dir specified, improper targets defined",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.bp"},
+ dirs: []string{"1/2/3:,t1"},
+ curDir: "0",
+ errStr: "0/1/2/3 not in proper directory:target1,target2,... format",
+ }, {
+ description: "one target dir specified, blank target",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.bp"},
+ dirs: []string{"1/2/3:t1,"},
+ curDir: "0",
+ errStr: "0/1/2/3 not in proper directory:target1,target2,... format",
+ }, {
+ description: "one target dir specified, many targets specified",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.bp"},
+ dirs: []string{"1/2/3:t1,t2,t3,t4,t5,t6,t7,t8,t9,t10"},
+ curDir: "0",
+ expectedTargets: []string{"t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10"},
+ expectedBuildFiles: []string{"0/1/2/3/Android.mk"},
+ }, {
+ description: "one target dir specified, one target specified, build file does not exist",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{},
+ dirs: []string{"1/2/3:t1"},
+ curDir: "0",
+ errStr: "Build file not found for 0/1/2/3 directory",
+ }, {
+ description: "one target dir specified, one target specified, build file not in target dir",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/Android.mk"},
+ dirs: []string{"1/2/3:t1"},
+ curDir: "0",
+ errStr: "Couldn't locate a build file from 0/1/2/3 directory",
+ }, {
+ description: "one target dir specified, build file not in target dir",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/Android.mk"},
+ dirs: []string{"1/2/3"},
+ curDir: "0",
+ expectedTargets: []string{"MODULES-IN-0-1-2"},
+ expectedBuildFiles: []string{"0/1/2/Android.mk"},
+ }, {
+ description: "multiple targets dir specified, targets specified",
+ dirsInTrees: []string{"0/1/2/3", "0/3/4"},
+ buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"},
+ dirs: []string{"1/2/3:t1,t2", "3/4:t3,t4,t5"},
+ curDir: "0",
+ expectedTargets: []string{"t1", "t2", "t3", "t4", "t5"},
+ expectedBuildFiles: []string{"0/1/2/3/Android.mk", "0/3/4/Android.mk"},
+ }, {
+ description: "multiple targets dir specified, one directory has targets specified",
+ dirsInTrees: []string{"0/1/2/3", "0/3/4"},
+ buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"},
+ dirs: []string{"1/2/3:t1,t2", "3/4"},
+ curDir: "0",
+ expectedTargets: []string{"t1", "t2", "MODULES-IN-0-3-4"},
+ expectedBuildFiles: []string{"0/1/2/3/Android.mk", "0/3/4/Android.mk"},
+ }, {
+ description: "two dirs specified, only one dir exist",
+ dirsInTrees: []string{"0/1/2/3"},
+ buildFiles: []string{"0/1/2/3/Android.mk"},
+ dirs: []string{"1/2/3:t1", "3/4"},
+ curDir: "0",
+ errStr: "couldn't find directory 0/3/4",
+ }, {
+ description: "multiple targets dirs specified at root source tree",
+ dirsInTrees: []string{"0/1/2/3", "0/3/4"},
+ buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"},
+ dirs: []string{"0/1/2/3:t1,t2", "0/3/4"},
+ curDir: ".",
+ expectedTargets: []string{"t1", "t2", "MODULES-IN-0-3-4"},
+ expectedBuildFiles: []string{"0/1/2/3/Android.mk", "0/3/4/Android.mk"},
+ }, {
+ description: "no directories specified",
+ dirsInTrees: []string{"0/1/2/3", "0/3/4"},
+ buildFiles: []string{"0/1/2/3/Android.bp", "0/3/4/Android.mk"},
+ dirs: []string{},
+ curDir: ".",
+ }}
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ defer logger.Recover(func(err error) {
+ if tt.errStr == "" {
+ t.Fatalf("Got unexpected error: %v", err)
+ }
+ if tt.errStr != err.Error() {
+ t.Errorf("expected %s, got %s", tt.errStr, err.Error())
+ }
+ })
+
+ // Create the root source tree.
+ topDir, err := ioutil.TempDir("", "")
+ if err != nil {
+ t.Fatalf("failed to create temp dir: %v", err)
+ }
+ defer os.RemoveAll(topDir)
+
+ createDirectories(t, topDir, tt.dirsInTrees)
+ createBuildFiles(t, topDir, tt.buildFiles)
+ r := setTop(t, topDir)
+ defer r()
+
+ targets, buildFiles := getTargetsFromDirs(ctx, tt.curDir, tt.dirs, "MODULES-IN-")
+ if !reflect.DeepEqual(targets, tt.expectedTargets) {
+ t.Errorf("expected %v, got %v for targets", tt.expectedTargets, targets)
+ }
+ if !reflect.DeepEqual(buildFiles, tt.expectedBuildFiles) {
+ t.Errorf("expected %v, got %v for build files", tt.expectedBuildFiles, buildFiles)
+ }
+
+ // If the execution reached here and there was an expected error code, the unit test case failed.
+ if tt.errStr != "" {
+ t.Errorf("expecting error %s", tt.errStr)
+ }
+ })
+ }
+}
+
+func TestConfigFindBuildFile(t *testing.T) {
+ ctx := testContext()
+
+ tests := []struct {
+ // ********* Setup *********
+ // Test description.
+ description string
+
+ // Array of build files to create in dir.
+ buildFiles []string
+
+ // ********* Action *********
+ // Directory to create, also the base directory is where findBuildFile is invoked.
+ dir string
+
+ // ********* Validation *********
+ // Expected build file path to find.
+ expectedBuildFile string
+ }{{
+ description: "build file exists at leaf directory",
+ buildFiles: []string{"1/2/3/Android.bp"},
+ dir: "1/2/3",
+ expectedBuildFile: "1/2/3/Android.mk",
+ }, {
+ description: "build file exists in all directory paths",
+ buildFiles: []string{"1/Android.mk", "1/2/Android.mk", "1/2/3/Android.mk"},
+ dir: "1/2/3",
+ expectedBuildFile: "1/2/3/Android.mk",
+ }, {
+ description: "build file does not exist in all directory paths",
+ buildFiles: []string{},
+ dir: "1/2/3",
+ expectedBuildFile: "",
+ }, {
+ description: "build file exists only at top directory",
+ buildFiles: []string{"Android.bp"},
+ dir: "1/2/3",
+ expectedBuildFile: "",
+ }, {
+ description: "build file exist in a subdirectory",
+ buildFiles: []string{"1/2/Android.bp"},
+ dir: "1/2/3",
+ expectedBuildFile: "1/2/Android.mk",
+ }, {
+ description: "build file exists in a subdirectory",
+ buildFiles: []string{"1/Android.mk"},
+ dir: "1/2/3",
+ expectedBuildFile: "1/Android.mk",
+ }, {
+ description: "top directory",
+ buildFiles: []string{"Android.bp"},
+ dir: ".",
+ expectedBuildFile: "",
+ }}
+
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ defer logger.Recover(func(err error) {
+ t.Fatalf("Got unexpected error: %v", err)
+ })
+
+ topDir, err := ioutil.TempDir("", "")
+ if err != nil {
+ t.Fatalf("failed to create temp dir: %v", err)
+ }
+ defer os.RemoveAll(topDir)
+
+ if tt.dir != "" {
+ createDirectories(t, topDir, []string{tt.dir})
+ }
+
+ createBuildFiles(t, topDir, tt.buildFiles)
+
+ curDir, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("Could not get working directory: %v", err)
+ }
+ defer func() { os.Chdir(curDir) }()
+ if err := os.Chdir(topDir); err != nil {
+ t.Fatalf("Could not change top dir to %s: %v", topDir, err)
+ }
+
+ buildFile := findBuildFile(ctx, tt.dir)
+ if buildFile != tt.expectedBuildFile {
+ t.Errorf("expected %q, got %q for build file", tt.expectedBuildFile, buildFile)
+ }
+ })
+ }
+}
+
+func TestConfigSplitArgs(t *testing.T) {
+ tests := []struct {
+ // ********* Setup *********
+ // Test description.
+ description string
+
+ // ********* Action *********
+ // Arguments passed in to soong_ui.
+ args []string
+
+ // ********* Validation *********
+ // Expected newArgs list after extracting the directories.
+ expectedNewArgs []string
+
+ // Expected directories
+ expectedDirs []string
+ }{{
+ description: "flags but no directories specified",
+ args: []string{"showcommands", "-j", "-k"},
+ expectedNewArgs: []string{"showcommands", "-j", "-k"},
+ expectedDirs: []string{},
+ }, {
+ description: "flags and one directory specified",
+ args: []string{"snod", "-j", "dir:target1,target2"},
+ expectedNewArgs: []string{"snod", "-j"},
+ expectedDirs: []string{"dir:target1,target2"},
+ }, {
+ description: "flags and directories specified",
+ args: []string{"dist", "-k", "dir1", "dir2:target1,target2"},
+ expectedNewArgs: []string{"dist", "-k"},
+ expectedDirs: []string{"dir1", "dir2:target1,target2"},
+ }, {
+ description: "only directories specified",
+ args: []string{"dir1", "dir2", "dir3:target1,target2"},
+ expectedNewArgs: []string{},
+ expectedDirs: []string{"dir1", "dir2", "dir3:target1,target2"},
+ }}
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ args, dirs := splitArgs(tt.args)
+ if !reflect.DeepEqual(tt.expectedNewArgs, args) {
+ t.Errorf("expected %v, got %v for arguments", tt.expectedNewArgs, args)
+ }
+ if !reflect.DeepEqual(tt.expectedDirs, dirs) {
+ t.Errorf("expected %v, got %v for directories", tt.expectedDirs, dirs)
+ }
+ })
+ }
+}
+
+type envVar struct {
+ name string
+ value string
+}
+
+type buildActionTestCase struct {
+ // ********* Setup *********
+ // Test description.
+ description string
+
+ // Directories that exist in the source tree.
+ dirsInTrees []string
+
+ // Build files that exists in the source tree.
+ buildFiles []string
+
+ // ********* Action *********
+ // Arguments passed in to soong_ui.
+ args []string
+
+ // Directory where the build action was invoked.
+ curDir string
+
+ // WITH_TIDY_ONLY environment variable specified.
+ tidyOnly string
+
+ // ********* Validation *********
+ // Expected arguments to be in Config instance.
+ expectedArgs []string
+
+ // Expected environment variables to be set.
+ expectedEnvVars []envVar
+}
+
+func testGetConfigArgs(t *testing.T, tt buildActionTestCase, action BuildAction, buildDependencies bool) {
+ ctx := testContext()
+
+ // Environment variables to set it to blank on every test case run.
+ resetEnvVars := []string{
+ "ONE_SHOT_MAKEFILE",
+ "WITH_TIDY_ONLY",
+ }
+
+ for _, name := range resetEnvVars {
+ if err := os.Unsetenv(name); err != nil {
+ t.Fatalf("failed to unset environment variable %s: %v", name, err)
+ }
+ }
+ if tt.tidyOnly != "" {
+ if err := os.Setenv("WITH_TIDY_ONLY", tt.tidyOnly); err != nil {
+ t.Errorf("failed to set WITH_TIDY_ONLY to %s: %v", tt.tidyOnly, err)
+ }
+ }
+
+ // Create the root source tree.
+ topDir, err := ioutil.TempDir("", "")
+ if err != nil {
+ t.Fatalf("failed to create temp dir: %v", err)
+ }
+ defer os.RemoveAll(topDir)
+
+ createDirectories(t, topDir, tt.dirsInTrees)
+ createBuildFiles(t, topDir, tt.buildFiles)
+
+ r := setTop(t, topDir)
+ defer r()
+
+ // The next block is to create the root build file.
+ rootBuildFileDir := filepath.Dir(srcDirFileCheck)
+ if err := os.MkdirAll(rootBuildFileDir, 0755); err != nil {
+ t.Fatalf("Failed to create %s directory: %v", rootBuildFileDir, err)
+ }
+
+ if err := ioutil.WriteFile(srcDirFileCheck, []byte{}, 0644); err != nil {
+ t.Fatalf("failed to create %s file: %v", srcDirFileCheck, err)
+ }
+
+ args := getConfigArgs(action, tt.curDir, buildDependencies, ctx, tt.args)
+ if !reflect.DeepEqual(tt.expectedArgs, args) {
+ t.Fatalf("expected %v, got %v for config arguments", tt.expectedArgs, args)
+ }
+
+ for _, env := range tt.expectedEnvVars {
+ if val := os.Getenv(env.name); val != env.value {
+ t.Errorf("expecting %s, got %s for environment variable %s", env.value, val, env.name)
+ }
+ }
+}
+
+// TODO: Remove this test case once mm shell build command has been deprecated.
+func TestGetConfigArgsBuildModulesInDirecotoryNoDeps(t *testing.T) {
+ tests := []buildActionTestCase{{
+ description: "normal execution in a directory",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/2/Android.mk"},
+ args: []string{"-j", "-k", "showcommands", "fake-module"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"-j", "-k", "showcommands", "fake-module", "MODULES-IN-0-1-2"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/2/Android.mk"}},
+ }, {
+ description: "makefile in parent directory",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/Android.mk"},
+ args: []string{},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0-1"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/Android.mk"}},
+ }, {
+ description: "build file not found",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{},
+ args: []string{},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0-1-2"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/2/Android.mk"}},
+ }, {
+ description: "build action executed at root directory",
+ dirsInTrees: []string{},
+ buildFiles: []string{},
+ args: []string{},
+ curDir: ".",
+ tidyOnly: "",
+ expectedArgs: []string{},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: ""}},
+ }, {
+ description: "GET-INSTALL-PATH specified,",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/Android.mk"},
+ args: []string{"GET-INSTALL-PATH"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"GET-INSTALL-PATH-IN-0-1"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/Android.mk"}},
+ }, {
+ description: "tidy only environment variable specified,",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/Android.mk"},
+ args: []string{"GET-INSTALL-PATH"},
+ curDir: "0/1/2",
+ tidyOnly: "true",
+ expectedArgs: []string{"tidy_only"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/Android.mk"}},
+ }}
+ for _, tt := range tests {
+ t.Run("build action BUILD_MODULES_IN_DIR without their dependencies, "+tt.description, func(t *testing.T) {
+ testGetConfigArgs(t, tt, BUILD_MODULES_IN_A_DIRECTORY, false)
+ })
+ }
+}
+
+func TestGetConfigArgsBuildModulesInDirectory(t *testing.T) {
+ tests := []buildActionTestCase{{
+ description: "normal execution in a directory",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/2/Android.mk"},
+ args: []string{"fake-module"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"fake-module", "MODULES-IN-0-1-2"},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "build file in parent directory",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/Android.mk"},
+ args: []string{},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0-1"},
+ expectedEnvVars: []envVar{},
+ },
+ {
+ description: "build file in parent directory, multiple module names passed in",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/Android.mk"},
+ args: []string{"fake-module1", "fake-module2", "fake-module3"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"fake-module1", "fake-module2", "fake-module3", "MODULES-IN-0-1"},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "build file in 2nd level parent directory",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/Android.bp"},
+ args: []string{},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0"},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "build action executed at root directory",
+ dirsInTrees: []string{},
+ buildFiles: []string{},
+ args: []string{},
+ curDir: ".",
+ tidyOnly: "",
+ expectedArgs: []string{},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "build file not found - no error is expected to return",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{},
+ args: []string{},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0-1-2"},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "GET-INSTALL-PATH specified,",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/Android.mk"},
+ args: []string{"GET-INSTALL-PATH", "-j", "-k", "GET-INSTALL-PATH"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"-j", "-k", "GET-INSTALL-PATH-IN-0-1"},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "tidy only environment variable specified,",
+ dirsInTrees: []string{"0/1/2"},
+ buildFiles: []string{"0/1/Android.mk"},
+ args: []string{"GET-INSTALL-PATH"},
+ curDir: "0/1/2",
+ tidyOnly: "true",
+ expectedArgs: []string{"tidy_only"},
+ expectedEnvVars: []envVar{},
+ }, {
+ description: "normal execution in root directory with args",
+ dirsInTrees: []string{},
+ buildFiles: []string{},
+ args: []string{"-j", "-k", "fake_module"},
+ curDir: "",
+ tidyOnly: "",
+ expectedArgs: []string{"-j", "-k", "fake_module"},
+ expectedEnvVars: []envVar{},
+ }}
+ for _, tt := range tests {
+ t.Run("build action BUILD_MODULES_IN_DIR, "+tt.description, func(t *testing.T) {
+ testGetConfigArgs(t, tt, BUILD_MODULES_IN_A_DIRECTORY, true)
+ })
+ }
+}
+
+// TODO: Remove this test case once mmm shell build command has been deprecated.
+func TestGetConfigArgsBuildModulesInDirectoriesNoDeps(t *testing.T) {
+ tests := []buildActionTestCase{{
+ description: "normal execution in a directory",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
+ args: []string{"3.1/:t1,t2", "3.2/:t3,t4", "3.3/:t5,t6"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"t1", "t2", "t3", "t4", "t5", "t6"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}},
+ }, {
+ description: "GET-INSTALL-PATH specified",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
+ args: []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3/:t6"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"GET-INSTALL-PATH-IN-0-1-2-3.1", "GET-INSTALL-PATH-IN-0-1-2-3.2", "t6"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}},
+ }, {
+ description: "tidy only environment variable specified",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
+ args: []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3/:t6"},
+ curDir: "0/1/2",
+ tidyOnly: "1",
+ expectedArgs: []string{"tidy_only"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}},
+ }, {
+ description: "normal execution from top dir directory",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
+ args: []string{"0/1/2/3.1", "0/1/2/3.2/:t3,t4", "0/1/2/3.3/:t5,t6"},
+ curDir: ".",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "t3", "t4", "t5", "t6"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: "0/1/2/3.1/Android.mk 0/1/2/3.2/Android.mk 0/1/2/3.3/Android.mk"}},
+ }}
+ for _, tt := range tests {
+ t.Run("build action BUILD_MODULES_IN_DIRS_NO_DEPS, "+tt.description, func(t *testing.T) {
+ testGetConfigArgs(t, tt, BUILD_MODULES_IN_DIRECTORIES, false)
+ })
+ }
+}
+
+func TestGetConfigArgsBuildModulesInDirectories(t *testing.T) {
+ tests := []buildActionTestCase{{
+ description: "normal execution in a directory",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
+ args: []string{"3.1/", "3.2/", "3.3/"},
+ curDir: "0/1/2",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-2-3.3"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: ""}},
+ }, {
+ description: "GET-INSTALL-PATH specified",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/Android.bp"},
+ args: []string{"GET-INSTALL-PATH", "2/3.1/", "2/3.2", "3"},
+ curDir: "0/1",
+ tidyOnly: "",
+ expectedArgs: []string{"GET-INSTALL-PATH-IN-0-1-2-3.1", "GET-INSTALL-PATH-IN-0-1-2-3.2", "GET-INSTALL-PATH-IN-0-1"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: ""}},
+ }, {
+ description: "tidy only environment variable specified",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/2/3.3"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/2/3.3/Android.bp"},
+ args: []string{"GET-INSTALL-PATH", "3.1/", "3.2/", "3.3"},
+ curDir: "0/1/2",
+ tidyOnly: "1",
+ expectedArgs: []string{"tidy_only"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: ""}},
+ }, {
+ description: "normal execution from top dir directory",
+ dirsInTrees: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
+ buildFiles: []string{"0/1/2/3.1/Android.bp", "0/1/2/3.2/Android.bp", "0/1/3/Android.bp", "0/2/Android.bp"},
+ args: []string{"0/1/2/3.1", "0/1/2/3.2", "0/1/3", "0/2"},
+ curDir: ".",
+ tidyOnly: "",
+ expectedArgs: []string{"MODULES-IN-0-1-2-3.1", "MODULES-IN-0-1-2-3.2", "MODULES-IN-0-1-3", "MODULES-IN-0-2"},
+ expectedEnvVars: []envVar{
+ envVar{
+ name: "ONE_SHOT_MAKEFILE",
+ value: ""}},
+ }}
+ for _, tt := range tests {
+ t.Run("build action BUILD_MODULES_IN_DIRS, "+tt.description, func(t *testing.T) {
+ testGetConfigArgs(t, tt, BUILD_MODULES_IN_DIRECTORIES, true)
+ })
+ }
+}
diff --git a/ui/build/util.go b/ui/build/util.go
index 0676a86..75e6753 100644
--- a/ui/build/util.go
+++ b/ui/build/util.go
@@ -44,6 +44,17 @@
return indexList(s, list) != -1
}
+// removeFromlist removes all occurrences of the string in list.
+func removeFromList(s string, list []string) []string {
+ filteredList := make([]string, 0, len(list))
+ for _, ls := range list {
+ if s != ls {
+ filteredList = append(filteredList, ls)
+ }
+ }
+ return filteredList
+}
+
// ensureDirectoriesExist is a shortcut to os.MkdirAll, sending errors to the ctx logger.
func ensureDirectoriesExist(ctx Context, dirs ...string) {
for _, dir := range dirs {