Merge changes I3e6bc9b5,If9f8fb10
* changes:
Disable TestSendLog on the buildbots
Remove global state from VNDK apexes
diff --git a/android/Android.bp b/android/Android.bp
index a8fa53a..f17a8a0 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -12,7 +12,6 @@
"soong",
"soong-android-soongconfig",
"soong-bazel",
- "soong-env",
"soong-shared",
"soong-ui-metrics_proto",
],
@@ -34,6 +33,7 @@
"deptag.go",
"expand.go",
"filegroup.go",
+ "fixture.go",
"hooks.go",
"image.go",
"license.go",
@@ -87,6 +87,7 @@
"depset_test.go",
"deptag_test.go",
"expand_test.go",
+ "fixture_test.go",
"license_kind_test.go",
"license_test.go",
"licenses_test.go",
diff --git a/android/android_test.go b/android/android_test.go
index 46b7054..68cb705 100644
--- a/android/android_test.go
+++ b/android/android_test.go
@@ -44,3 +44,5 @@
os.Exit(run())
}
+
+var emptyTestFixtureFactory = NewFixtureFactory(&buildDir)
diff --git a/android/arch.go b/android/arch.go
index f719ddc..20b4ab0 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -1615,3 +1615,97 @@
return buildTargets, nil
}
+
+// GetArchProperties returns a map of architectures to the values of the
+// properties of the 'dst' struct that are specific to that architecture.
+//
+// For example, passing a struct { Foo bool, Bar string } will return an
+// interface{} that can be type asserted back into the same struct, containing
+// the arch specific property value specified by the module if defined.
+func (m *ModuleBase) GetArchProperties(dst interface{}) map[ArchType]interface{} {
+ // Return value of the arch types to the prop values for that arch.
+ archToProp := map[ArchType]interface{}{}
+
+ // Nothing to do for non-arch-specific modules.
+ if !m.ArchSpecific() {
+ return archToProp
+ }
+
+ // archProperties has the type of [][]interface{}. Looks complicated, so let's
+ // explain this step by step.
+ //
+ // Loop over the outer index, which determines the property struct that
+ // contains a matching set of properties in dst that we're interested in.
+ // For example, BaseCompilerProperties or BaseLinkerProperties.
+ for i := range m.archProperties {
+ if m.archProperties[i] == nil {
+ // Skip over nil arch props
+ continue
+ }
+
+ // Non-nil arch prop, let's see if the props match up.
+ for _, arch := range ArchTypeList() {
+ // e.g X86, Arm
+ field := arch.Field
+
+ // If it's not nil, loop over the inner index, which determines the arch variant
+ // of the prop type. In an Android.bp file, this is like looping over:
+ //
+ // arch: { arm: { key: value, ... }, x86: { key: value, ... } }
+ for _, archProperties := range m.archProperties[i] {
+ archPropValues := reflect.ValueOf(archProperties).Elem()
+
+ // This is the archPropRoot struct. Traverse into the Arch nested struct.
+ src := archPropValues.FieldByName("Arch").Elem()
+
+ // Step into non-nil pointers to structs in the src value.
+ if src.Kind() == reflect.Ptr {
+ if src.IsNil() {
+ // Ignore nil pointers.
+ continue
+ }
+ src = src.Elem()
+ }
+
+ // Find the requested field (e.g. x86, x86_64) in the src struct.
+ src = src.FieldByName(field)
+ if !src.IsValid() {
+ continue
+ }
+
+ // We only care about structs. These are not the droids you are looking for.
+ if src.Kind() != reflect.Struct {
+ continue
+ }
+
+ // If the value of the field is a struct then step into the
+ // BlueprintEmbed field. The special "BlueprintEmbed" name is
+ // used by createArchPropTypeDesc to embed the arch properties
+ // in the parent struct, so the src arch prop should be in this
+ // field.
+ //
+ // See createArchPropTypeDesc for more details on how Arch-specific
+ // module properties are processed from the nested props and written
+ // into the module's archProperties.
+ src = src.FieldByName("BlueprintEmbed")
+
+ // Clone the destination prop, since we want a unique prop struct per arch.
+ dstClone := reflect.New(reflect.ValueOf(dst).Elem().Type()).Interface()
+
+ // Copy the located property struct into the cloned destination property struct.
+ err := proptools.ExtendMatchingProperties([]interface{}{dstClone}, src.Interface(), nil, proptools.OrderReplace)
+ if err != nil {
+ // This is fine, it just means the src struct doesn't match.
+ continue
+ }
+
+ // Found the prop for the arch, you have.
+ archToProp[arch] = dstClone
+
+ // Go to the next prop.
+ break
+ }
+ }
+ }
+ return archToProp
+}
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index a5c4bed..415f00e 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -36,12 +36,14 @@
const (
getAllFiles CqueryRequestType = iota
+ getCcObjectFiles
)
// Map key to describe bazel cquery requests.
type cqueryKey struct {
label string
requestType CqueryRequestType
+ archType ArchType
}
type BazelContext interface {
@@ -50,7 +52,11 @@
// has been queued to be run later.
// Returns result files built by building the given bazel target label.
- GetAllFiles(label string) ([]string, bool)
+ GetAllFiles(label string, archType ArchType) ([]string, bool)
+
+ // Returns object files produced by compiling the given cc-related target.
+ // Retrieves these files from Bazel's CcInfo provider.
+ GetCcObjectFiles(label string, archType ArchType) ([]string, bool)
// TODO(cparsons): Other cquery-related methods should be added here.
// ** End cquery methods
@@ -100,7 +106,12 @@
AllFiles map[string][]string
}
-func (m MockBazelContext) GetAllFiles(label string) ([]string, bool) {
+func (m MockBazelContext) GetAllFiles(label string, archType ArchType) ([]string, bool) {
+ result, ok := m.AllFiles[label]
+ return result, ok
+}
+
+func (m MockBazelContext) GetCcObjectFiles(label string, archType ArchType) ([]string, bool) {
result, ok := m.AllFiles[label]
return result, ok
}
@@ -123,8 +134,8 @@
var _ BazelContext = MockBazelContext{}
-func (bazelCtx *bazelContext) GetAllFiles(label string) ([]string, bool) {
- result, ok := bazelCtx.cquery(label, getAllFiles)
+func (bazelCtx *bazelContext) GetAllFiles(label string, archType ArchType) ([]string, bool) {
+ result, ok := bazelCtx.cquery(label, getAllFiles, archType)
if ok {
bazelOutput := strings.TrimSpace(result)
return strings.Split(bazelOutput, ", "), true
@@ -133,7 +144,21 @@
}
}
-func (n noopBazelContext) GetAllFiles(label string) ([]string, bool) {
+func (bazelCtx *bazelContext) GetCcObjectFiles(label string, archType ArchType) ([]string, bool) {
+ result, ok := bazelCtx.cquery(label, getCcObjectFiles, archType)
+ if ok {
+ bazelOutput := strings.TrimSpace(result)
+ return strings.Split(bazelOutput, ", "), true
+ } else {
+ return nil, false
+ }
+}
+
+func (n noopBazelContext) GetAllFiles(label string, archType ArchType) ([]string, bool) {
+ panic("unimplemented")
+}
+
+func (n noopBazelContext) GetCcObjectFiles(label string, archType ArchType) ([]string, bool) {
panic("unimplemented")
}
@@ -207,8 +232,9 @@
// If the given request was already made (and the results are available), then
// returns (result, true). If the request is queued but no results are available,
// then returns ("", false).
-func (context *bazelContext) cquery(label string, requestType CqueryRequestType) (string, bool) {
- key := cqueryKey{label, requestType}
+func (context *bazelContext) cquery(label string, requestType CqueryRequestType,
+ archType ArchType) (string, bool) {
+ key := cqueryKey{label, requestType, archType}
if result, ok := context.results[key]; ok {
return result, true
} else {
@@ -241,17 +267,21 @@
fmt.Sprintf("--platforms=%s", canonicalizeLabel("//build/bazel/platforms:generic_x86_64")))
cmdFlags = append(cmdFlags,
fmt.Sprintf("--extra_toolchains=%s", canonicalizeLabel("//prebuilts/clang/host/linux-x86:all")))
+ // Explicitly disable downloading rules (such as canonical C++ and Java rules) from the network.
+ cmdFlags = append(cmdFlags, "--experimental_repository_disable_download")
cmdFlags = append(cmdFlags, extraFlags...)
bazelCmd := exec.Command(context.bazelPath, cmdFlags...)
bazelCmd.Dir = context.workspaceDir
- bazelCmd.Env = append(os.Environ(), "HOME="+context.homeDir, pwdPrefix())
-
+ bazelCmd.Env = append(os.Environ(), "HOME="+context.homeDir, pwdPrefix(),
+ // Disables local host detection of gcc; toolchain information is defined
+ // explicitly in BUILD files.
+ "BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1")
stderr := &bytes.Buffer{}
bazelCmd.Stderr = stderr
if output, err := bazelCmd.Output(); err != nil {
- return "", fmt.Errorf("bazel command failed. command: [%s], error [%s]", bazelCmd, stderr)
+ return "", fmt.Errorf("bazel command failed. command: [%s], env: [%s], error [%s]", bazelCmd, bazelCmd.Env, stderr)
} else {
return string(output), nil
}
@@ -273,20 +303,81 @@
}
func (context *bazelContext) mainBzlFileContents() []byte {
+ // TODO(cparsons): Define configuration transitions programmatically based
+ // on available archs.
contents := `
#####################################################
# This file is generated by soong_build. Do not edit.
#####################################################
+def _x86_64_transition_impl(settings, attr):
+ return {
+ "//command_line_option:platforms": "@sourceroot//build/bazel/platforms:generic_x86_64",
+ }
+
+def _x86_transition_impl(settings, attr):
+ return {
+ "//command_line_option:platforms": "@sourceroot//build/bazel/platforms:generic_x86",
+ }
+
+def _arm64_transition_impl(settings, attr):
+ return {
+ "//command_line_option:platforms": "@sourceroot//build/bazel/platforms:generic_arm64",
+ }
+
+def _arm_transition_impl(settings, attr):
+ return {
+ "//command_line_option:platforms": "@sourceroot//build/bazel/platforms:generic_arm",
+ }
+
+x86_64_transition = transition(
+ implementation = _x86_64_transition_impl,
+ inputs = [],
+ outputs = [
+ "//command_line_option:platforms",
+ ],
+)
+
+x86_transition = transition(
+ implementation = _x86_transition_impl,
+ inputs = [],
+ outputs = [
+ "//command_line_option:platforms",
+ ],
+)
+
+arm64_transition = transition(
+ implementation = _arm64_transition_impl,
+ inputs = [],
+ outputs = [
+ "//command_line_option:platforms",
+ ],
+)
+
+arm_transition = transition(
+ implementation = _arm_transition_impl,
+ inputs = [],
+ outputs = [
+ "//command_line_option:platforms",
+ ],
+)
+
def _mixed_build_root_impl(ctx):
- return [DefaultInfo(files = depset(ctx.files.deps))]
+ all_files = ctx.files.deps_x86_64 + ctx.files.deps_x86 + ctx.files.deps_arm64 + ctx.files.deps_arm
+ return [DefaultInfo(files = depset(all_files))]
# Rule representing the root of the build, to depend on all Bazel targets that
# are required for the build. Building this target will build the entire Bazel
# build tree.
mixed_build_root = rule(
implementation = _mixed_build_root_impl,
- attrs = {"deps" : attr.label_list()},
+ attrs = {
+ "deps_x86_64" : attr.label_list(cfg = x86_64_transition),
+ "deps_x86" : attr.label_list(cfg = x86_transition),
+ "deps_arm64" : attr.label_list(cfg = arm64_transition),
+ "deps_arm" : attr.label_list(cfg = arm_transition),
+ "_allowlist_function_transition": attr.label(default = "@bazel_tools//tools/allowlists/function_transition_allowlist"),
+ },
)
def _phony_root_impl(ctx):
@@ -317,25 +408,48 @@
}
func (context *bazelContext) mainBuildFileContents() []byte {
+ // TODO(cparsons): Map label to attribute programmatically; don't use hard-coded
+ // architecture mapping.
formatString := `
# This file is generated by soong_build. Do not edit.
load(":main.bzl", "mixed_build_root", "phony_root")
mixed_build_root(name = "buildroot",
- deps = [%s],
+ deps_x86_64 = [%s],
+ deps_x86 = [%s],
+ deps_arm64 = [%s],
+ deps_arm = [%s],
)
phony_root(name = "phonyroot",
deps = [":buildroot"],
)
`
- var buildRootDeps []string = nil
+ var deps_x86_64 []string = nil
+ var deps_x86 []string = nil
+ var deps_arm64 []string = nil
+ var deps_arm []string = nil
for val, _ := range context.requests {
- buildRootDeps = append(buildRootDeps, fmt.Sprintf("\"%s\"", canonicalizeLabel(val.label)))
+ labelString := fmt.Sprintf("\"%s\"", canonicalizeLabel(val.label))
+ switch getArchString(val) {
+ case "x86_64":
+ deps_x86_64 = append(deps_x86_64, labelString)
+ case "x86":
+ deps_x86 = append(deps_x86, labelString)
+ case "arm64":
+ deps_arm64 = append(deps_arm64, labelString)
+ case "arm":
+ deps_arm = append(deps_arm, labelString)
+ default:
+ panic(fmt.Sprintf("unhandled architecture %s for %s", getArchString(val), val))
+ }
}
- buildRootDepsString := strings.Join(buildRootDeps, ",\n ")
- return []byte(fmt.Sprintf(formatString, buildRootDepsString))
+ return []byte(fmt.Sprintf(formatString,
+ strings.Join(deps_x86_64, ",\n "),
+ strings.Join(deps_x86, ",\n "),
+ strings.Join(deps_arm64, ",\n "),
+ strings.Join(deps_arm, ",\n ")))
}
func (context *bazelContext) cqueryStarlarkFileContents() []byte {
@@ -345,23 +459,64 @@
%s
}
+getCcObjectFilesLabels = {
+ %s
+}
+
+def get_cc_object_files(target):
+ result = []
+ linker_inputs = providers(target)["CcInfo"].linking_context.linker_inputs.to_list()
+
+ for linker_input in linker_inputs:
+ for library in linker_input.libraries:
+ for object in library.objects:
+ result += [object.path]
+ return result
+
+def get_arch(target):
+ buildoptions = build_options(target)
+ platforms = build_options(target)["//command_line_option:platforms"]
+ if len(platforms) != 1:
+ # An individual configured target should have only one platform architecture.
+ # Note that it's fine for there to be multiple architectures for the same label,
+ # but each is its own configured target.
+ fail("expected exactly 1 platform for " + str(target.label) + " but got " + str(platforms))
+ platform_name = build_options(target)["//command_line_option:platforms"][0].name
+ if platform_name == "host":
+ return "HOST"
+ elif not platform_name.startswith("generic_"):
+ fail("expected platform name of the form 'generic_<arch>', but was " + str(platforms))
+ return "UNKNOWN"
+ return platform_name[len("generic_"):]
+
def format(target):
- if str(target.label) in getAllFilesLabels:
- return str(target.label) + ">>" + ', '.join([f.path for f in target.files.to_list()])
+ id_string = str(target.label) + "|" + get_arch(target)
+ if id_string in getAllFilesLabels:
+ return id_string + ">>" + ', '.join([f.path for f in target.files.to_list()])
+ elif id_string in getCcObjectFilesLabels:
+ return id_string + ">>" + ', '.join(get_cc_object_files(target))
else:
# This target was not requested via cquery, and thus must be a dependency
# of a requested target.
- return ""
+ return id_string + ">>NONE"
`
- var buildRootDeps []string = nil
- // TODO(cparsons): Sort by request type instead of assuming all requests
- // are of GetAllFiles type.
- for val, _ := range context.requests {
- buildRootDeps = append(buildRootDeps, fmt.Sprintf("\"%s\" : True", canonicalizeLabel(val.label)))
- }
- buildRootDepsString := strings.Join(buildRootDeps, ",\n ")
+ var getAllFilesDeps []string = nil
+ var getCcObjectFilesDeps []string = nil
- return []byte(fmt.Sprintf(formatString, buildRootDepsString))
+ for val, _ := range context.requests {
+ labelWithArch := getCqueryId(val)
+ mapEntryString := fmt.Sprintf("%q : True", labelWithArch)
+ switch val.requestType {
+ case getAllFiles:
+ getAllFilesDeps = append(getAllFilesDeps, mapEntryString)
+ case getCcObjectFiles:
+ getCcObjectFilesDeps = append(getCcObjectFilesDeps, mapEntryString)
+ }
+ }
+ getAllFilesDepsString := strings.Join(getAllFilesDeps, ",\n ")
+ getCcObjectFilesDepsString := strings.Join(getCcObjectFilesDeps, ",\n ")
+
+ return []byte(fmt.Sprintf(formatString, getAllFilesDepsString, getCcObjectFilesDepsString))
}
// Returns a workspace-relative path containing build-related metadata required
@@ -414,9 +569,15 @@
}
buildrootLabel := "//:buildroot"
cqueryOutput, err = context.issueBazelCommand(bazel.CqueryBuildRootRunName, "cquery",
- []string{fmt.Sprintf("deps(%s)", buildrootLabel)},
+ []string{fmt.Sprintf("kind(rule, deps(%s))", buildrootLabel)},
"--output=starlark",
"--starlark:file="+cqueryFileRelpath)
+ err = ioutil.WriteFile(
+ absolutePath(filepath.Join(context.intermediatesDir(), "cquery.out")),
+ []byte(cqueryOutput), 0666)
+ if err != nil {
+ return err
+ }
if err != nil {
return err
@@ -431,10 +592,10 @@
}
for val, _ := range context.requests {
- if cqueryResult, ok := cqueryResults[canonicalizeLabel(val.label)]; ok {
+ if cqueryResult, ok := cqueryResults[getCqueryId(val)]; ok {
context.results[val] = string(cqueryResult)
} else {
- return fmt.Errorf("missing result for bazel target %s", val.label)
+ return fmt.Errorf("missing result for bazel target %s. query output: [%s]", getCqueryId(val), cqueryOutput)
}
}
@@ -510,6 +671,9 @@
// Register bazel-owned build statements (obtained from the aquery invocation).
for index, buildStatement := range ctx.Config().BazelContext.BuildStatementsToRegister() {
+ if len(buildStatement.Command) < 1 {
+ panic(fmt.Sprintf("unhandled build statement: %s", buildStatement))
+ }
rule := NewRuleBuilder(pctx, ctx)
cmd := rule.Command()
cmd.Text(fmt.Sprintf("cd %s/execroot/__main__ && %s",
@@ -531,3 +695,16 @@
rule.Build(fmt.Sprintf("bazel %d", index), buildStatement.Mnemonic)
}
}
+
+func getCqueryId(key cqueryKey) string {
+ return canonicalizeLabel(key.label) + "|" + getArchString(key)
+}
+
+func getArchString(key cqueryKey) string {
+ arch := key.archType.Name
+ if len(arch) > 0 {
+ return arch
+ } else {
+ return "x86_64"
+ }
+}
diff --git a/android/config.go b/android/config.go
index f0bba81..ef5eadf 100644
--- a/android/config.go
+++ b/android/config.go
@@ -305,10 +305,7 @@
return testConfig
}
-// TestArchConfig returns a Config object suitable for using for tests that
-// need to run the arch mutator.
-func TestArchConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
- testConfig := TestConfig(buildDir, env, bp, fs)
+func modifyTestConfigToSupportArchMutator(testConfig Config) {
config := testConfig.config
config.Targets = map[OsType][]Target{
@@ -334,7 +331,13 @@
config.TestProductVariables.DeviceArchVariant = proptools.StringPtr("armv8-a")
config.TestProductVariables.DeviceSecondaryArch = proptools.StringPtr("arm")
config.TestProductVariables.DeviceSecondaryArchVariant = proptools.StringPtr("armv7-a-neon")
+}
+// TestArchConfig returns a Config object suitable for using for tests that
+// need to run the arch mutator.
+func TestArchConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
+ testConfig := TestConfig(buildDir, env, bp, fs)
+ modifyTestConfigToSupportArchMutator(testConfig)
return testConfig
}
diff --git a/android/env.go b/android/env.go
index c2a09aa..a8c7777 100644
--- a/android/env.go
+++ b/android/env.go
@@ -21,7 +21,7 @@
"strings"
"syscall"
- "android/soong/env"
+ "android/soong/shared"
)
// This file supports dependencies on environment variables. During build manifest generation,
@@ -113,7 +113,7 @@
return
}
- data, err := env.EnvFileContents(envDeps)
+ data, err := shared.EnvFileContents(envDeps)
if err != nil {
ctx.Errorf(err.Error())
}
diff --git a/android/filegroup.go b/android/filegroup.go
index 593e470..2eb4741 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -24,6 +24,10 @@
RegisterBp2BuildMutator("filegroup", FilegroupBp2Build)
}
+var PrepareForTestWithFilegroup = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.RegisterModuleType("filegroup", FileGroupFactory)
+})
+
// https://docs.bazel.build/versions/master/be/general.html#filegroup
type bazelFilegroupAttributes struct {
Srcs bazel.LabelList
diff --git a/android/fixture.go b/android/fixture.go
new file mode 100644
index 0000000..ae24b91
--- /dev/null
+++ b/android/fixture.go
@@ -0,0 +1,712 @@
+// Copyright 2021 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 (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+// Provides support for creating test fixtures on which tests can be run. Reduces duplication
+// of test setup by allow tests to easily reuse setup code.
+//
+// Fixture
+// =======
+// These determine the environment within which a test can be run. Fixtures are mutable and are
+// created by FixtureFactory instances and mutated by FixturePreparer instances. They are created by
+// first creating a base Fixture (which is essentially empty) and then applying FixturePreparer
+// instances to it to modify the environment.
+//
+// FixtureFactory
+// ==============
+// These are responsible for creating fixtures. Factories are immutable and are intended to be
+// initialized once and reused to create multiple fixtures. Each factory has a list of fixture
+// preparers that prepare a fixture for running a test. Factories can also be used to create other
+// factories by extending them with additional fixture preparers.
+//
+// FixturePreparer
+// ===============
+// These are responsible for modifying a Fixture in preparation for it to run a test. Preparers are
+// intended to be immutable and able to prepare multiple Fixture objects simultaneously without
+// them sharing any data.
+//
+// FixturePreparers are only ever invoked once per test fixture. Prior to invocation the list of
+// FixturePreparers are flattened and deduped while preserving the order they first appear in the
+// list. This makes it easy to reuse, group and combine FixturePreparers together.
+//
+// Each small self contained piece of test setup should be their own FixturePreparer. e.g.
+// * A group of related modules.
+// * A group of related mutators.
+// * A combination of both.
+// * Configuration.
+//
+// They should not overlap, e.g. the same module type should not be registered by different
+// FixturePreparers as using them both would cause a build error. In that case the preparer should
+// be split into separate parts and combined together using FixturePreparers(...).
+//
+// e.g. attempting to use AllPreparers in preparing a Fixture would break as it would attempt to
+// register module bar twice:
+// var Preparer1 = FixtureRegisterWithContext(RegisterModuleFooAndBar)
+// var Preparer2 = FixtureRegisterWithContext(RegisterModuleBarAndBaz)
+// var AllPreparers = FixturePreparers(Preparer1, Preparer2)
+//
+// However, when restructured like this it would work fine:
+// var PreparerFoo = FixtureRegisterWithContext(RegisterModuleFoo)
+// var PreparerBar = FixtureRegisterWithContext(RegisterModuleBar)
+// var PreparerBaz = FixtureRegisterWithContext(RegisterModuleBaz)
+// var Preparer1 = FixturePreparers(RegisterModuleFoo, RegisterModuleBar)
+// var Preparer2 = FixturePreparers(RegisterModuleBar, RegisterModuleBaz)
+// var AllPreparers = FixturePreparers(Preparer1, Preparer2)
+//
+// As after deduping and flattening AllPreparers would result in the following preparers being
+// applied:
+// 1. PreparerFoo
+// 2. PreparerBar
+// 3. PreparerBaz
+//
+// Preparers can be used for both integration and unit tests.
+//
+// Integration tests typically use all the module types, mutators and singletons that are available
+// for that package to try and replicate the behavior of the runtime build as closely as possible.
+// However, that realism comes at a cost of increased fragility (as they can be broken by changes in
+// many different parts of the build) and also increased runtime, especially if they use lots of
+// singletons and mutators.
+//
+// Unit tests on the other hand try and minimize the amount of code being tested which makes them
+// less susceptible to changes elsewhere in the build and quick to run but at a cost of potentially
+// not testing realistic scenarios.
+//
+// Supporting unit tests effectively require that preparers are available at the lowest granularity
+// possible. Supporting integration tests effectively require that the preparers are organized into
+// groups that provide all the functionality available.
+//
+// At least in terms of tests that check the behavior of build components via processing
+// `Android.bp` there is no clear separation between a unit test and an integration test. Instead
+// they vary from one end that tests a single module (e.g. filegroup) to the other end that tests a
+// whole system of modules, mutators and singletons (e.g. apex + hiddenapi).
+//
+// TestResult
+// ==========
+// These are created by running tests in a Fixture and provide access to the Config and TestContext
+// in which the tests were run.
+//
+// Example
+// =======
+//
+// An exported preparer for use by other packages that need to use java modules.
+//
+// package java
+// var PrepareForIntegrationTestWithJava = FixturePreparers(
+// android.PrepareForIntegrationTestWithAndroid,
+// FixtureRegisterWithContext(RegisterAGroupOfRelatedModulesMutatorsAndSingletons),
+// FixtureRegisterWithContext(RegisterAnotherGroupOfRelatedModulesMutatorsAndSingletons),
+// ...
+// )
+//
+// Some files to use in tests in the java package.
+//
+// var javaMockFS = android.MockFS{
+// "api/current.txt": nil,
+// "api/removed.txt": nil,
+// ...
+// }
+//
+// A package private factory for use for testing java within the java package.
+//
+// var javaFixtureFactory = NewFixtureFactory(
+// PrepareForIntegrationTestWithJava,
+// FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+// ctx.RegisterModuleType("test_module", testModule)
+// }),
+// javaMockFS.AddToFixture(),
+// ...
+// }
+//
+// func TestJavaStuff(t *testing.T) {
+// result := javaFixtureFactory.RunTest(t,
+// android.FixtureWithRootAndroidBp(`java_library {....}`),
+// android.MockFS{...}.AddToFixture(),
+// )
+// ... test result ...
+// }
+//
+// package cc
+// var PrepareForTestWithCC = FixturePreparers(
+// android.PrepareForArchMutator,
+// android.prepareForPrebuilts,
+// FixtureRegisterWithContext(RegisterRequiredBuildComponentsForTest),
+// ...
+// )
+//
+// package apex
+//
+// var PrepareForApex = FixturePreparers(
+// ...
+// )
+//
+// Use modules and mutators from java, cc and apex. Any duplicate preparers (like
+// android.PrepareForArchMutator) will be automatically deduped.
+//
+// var apexFixtureFactory = android.NewFixtureFactory(
+// PrepareForJava,
+// PrepareForCC,
+// PrepareForApex,
+// )
+
+// Factory for Fixture objects.
+//
+// This is configured with a set of FixturePreparer objects that are used to
+// initialize each Fixture instance this creates.
+type FixtureFactory interface {
+
+ // Creates a copy of this instance and adds some additional preparers.
+ //
+ // Before the preparers are used they are combined with the preparers provided when the factory
+ // was created, any groups of preparers are flattened, and the list is deduped so that each
+ // preparer is only used once. See the file documentation in android/fixture.go for more details.
+ Extend(preparers ...FixturePreparer) FixtureFactory
+
+ // Create a Fixture.
+ Fixture(t *testing.T, preparers ...FixturePreparer) Fixture
+
+ // Set the error handler that will be used to check any errors reported by the test.
+ //
+ // The default handlers is FixtureExpectsNoErrors which will fail the go test immediately if any
+ // errors are reported.
+ SetErrorHandler(errorHandler FixtureErrorHandler) FixtureFactory
+
+ // Run the test, checking any errors reported and returning a TestResult instance.
+ //
+ // Shorthand for Fixture(t, preparers...).RunTest()
+ RunTest(t *testing.T, preparers ...FixturePreparer) *TestResult
+
+ // Run the test with the supplied Android.bp file.
+ //
+ // Shorthand for RunTest(t, android.FixtureWithRootAndroidBp(bp))
+ RunTestWithBp(t *testing.T, bp string) *TestResult
+}
+
+// Create a new FixtureFactory that will apply the supplied preparers.
+//
+// The buildDirSupplier is a pointer to the package level buildDir variable that is initialized by
+// the package level setUp method. It has to be a pointer to the variable as the variable will not
+// have been initialized at the time the factory is created.
+func NewFixtureFactory(buildDirSupplier *string, preparers ...FixturePreparer) FixtureFactory {
+ return &fixtureFactory{
+ buildDirSupplier: buildDirSupplier,
+ preparers: dedupAndFlattenPreparers(nil, preparers),
+
+ // Set the default error handler.
+ errorHandler: FixtureExpectsNoErrors,
+ }
+}
+
+// A set of mock files to add to the mock file system.
+type MockFS map[string][]byte
+
+func (fs MockFS) Merge(extra map[string][]byte) {
+ for p, c := range extra {
+ fs[p] = c
+ }
+}
+
+func (fs MockFS) AddToFixture() FixturePreparer {
+ return FixtureMergeMockFs(fs)
+}
+
+// Modify the config
+func FixtureModifyConfig(mutator func(config Config)) FixturePreparer {
+ return newSimpleFixturePreparer(func(f *fixture) {
+ mutator(f.config)
+ })
+}
+
+// Modify the config and context
+func FixtureModifyConfigAndContext(mutator func(config Config, ctx *TestContext)) FixturePreparer {
+ return newSimpleFixturePreparer(func(f *fixture) {
+ mutator(f.config, f.ctx)
+ })
+}
+
+// Modify the context
+func FixtureModifyContext(mutator func(ctx *TestContext)) FixturePreparer {
+ return newSimpleFixturePreparer(func(f *fixture) {
+ mutator(f.ctx)
+ })
+}
+
+func FixtureRegisterWithContext(registeringFunc func(ctx RegistrationContext)) FixturePreparer {
+ return FixtureModifyContext(func(ctx *TestContext) { registeringFunc(ctx) })
+}
+
+// Modify the mock filesystem
+func FixtureModifyMockFS(mutator func(fs MockFS)) FixturePreparer {
+ return newSimpleFixturePreparer(func(f *fixture) {
+ mutator(f.mockFS)
+ })
+}
+
+// Merge the supplied file system into the mock filesystem.
+//
+// Paths that already exist in the mock file system are overridden.
+func FixtureMergeMockFs(mockFS MockFS) FixturePreparer {
+ return FixtureModifyMockFS(func(fs MockFS) {
+ fs.Merge(mockFS)
+ })
+}
+
+// Add a file to the mock filesystem
+func FixtureAddFile(path string, contents []byte) FixturePreparer {
+ return FixtureModifyMockFS(func(fs MockFS) {
+ fs[path] = contents
+ })
+}
+
+// Add a text file to the mock filesystem
+func FixtureAddTextFile(path string, contents string) FixturePreparer {
+ return FixtureAddFile(path, []byte(contents))
+}
+
+// Add the root Android.bp file with the supplied contents.
+func FixtureWithRootAndroidBp(contents string) FixturePreparer {
+ return FixtureAddTextFile("Android.bp", contents)
+}
+
+// Create a composite FixturePreparer that is equivalent to applying each of the supplied
+// FixturePreparer instances in order.
+func FixturePreparers(preparers ...FixturePreparer) FixturePreparer {
+ return &compositeFixturePreparer{dedupAndFlattenPreparers(nil, preparers)}
+}
+
+type simpleFixturePreparerVisitor func(preparer *simpleFixturePreparer)
+
+// FixturePreparer is an opaque interface that can change a fixture.
+type FixturePreparer interface {
+ // visit calls the supplied visitor with each *simpleFixturePreparer instances in this preparer,
+ visit(simpleFixturePreparerVisitor)
+}
+
+type fixturePreparers []FixturePreparer
+
+func (f fixturePreparers) visit(visitor simpleFixturePreparerVisitor) {
+ for _, p := range f {
+ p.visit(visitor)
+ }
+}
+
+// dedupAndFlattenPreparers removes any duplicates and flattens any composite FixturePreparer
+// instances.
+//
+// base - a list of already flattened and deduped preparers that will be applied first before
+// the list of additional preparers. Any duplicates of these in the additional preparers
+// will be ignored.
+//
+// preparers - a list of additional unflattened, undeduped preparers that will be applied after the
+// base preparers.
+//
+// Returns a deduped and flattened list of the preparers minus any that exist in the base preparers.
+func dedupAndFlattenPreparers(base []*simpleFixturePreparer, preparers fixturePreparers) []*simpleFixturePreparer {
+ var list []*simpleFixturePreparer
+ visited := make(map[*simpleFixturePreparer]struct{})
+
+ // Mark the already flattened and deduped preparers, if any, as having been seen so that
+ // duplicates of these in the additional preparers will be discarded.
+ for _, s := range base {
+ visited[s] = struct{}{}
+ }
+
+ preparers.visit(func(preparer *simpleFixturePreparer) {
+ if _, seen := visited[preparer]; !seen {
+ visited[preparer] = struct{}{}
+ list = append(list, preparer)
+ }
+ })
+ return list
+}
+
+// compositeFixturePreparer is a FixturePreparer created from a list of fixture preparers.
+type compositeFixturePreparer struct {
+ preparers []*simpleFixturePreparer
+}
+
+func (c *compositeFixturePreparer) visit(visitor simpleFixturePreparerVisitor) {
+ for _, p := range c.preparers {
+ p.visit(visitor)
+ }
+}
+
+// simpleFixturePreparer is a FixturePreparer that applies a function to a fixture.
+type simpleFixturePreparer struct {
+ function func(fixture *fixture)
+}
+
+func (s *simpleFixturePreparer) visit(visitor simpleFixturePreparerVisitor) {
+ visitor(s)
+}
+
+func newSimpleFixturePreparer(preparer func(fixture *fixture)) FixturePreparer {
+ return &simpleFixturePreparer{function: preparer}
+}
+
+// FixtureErrorHandler determines how to respond to errors reported by the code under test.
+//
+// Some possible responses:
+// * Fail the test if any errors are reported, see FixtureExpectsNoErrors.
+// * Fail the test if at least one error that matches a pattern is not reported see
+// FixtureExpectsAtLeastOneErrorMatchingPattern
+// * Fail the test if any unexpected errors are reported.
+//
+// Although at the moment all the error handlers are implemented as simply a wrapper around a
+// function this is defined as an interface to allow future enhancements, e.g. provide different
+// ways other than patterns to match an error and to combine handlers together.
+type FixtureErrorHandler interface {
+ // CheckErrors checks the errors reported.
+ //
+ // The supplied result can be used to access the state of the code under test just as the main
+ // body of the test would but if any errors other than ones expected are reported the state may
+ // be indeterminate.
+ CheckErrors(result *TestResult, errs []error)
+}
+
+type simpleErrorHandler struct {
+ function func(result *TestResult, errs []error)
+}
+
+func (h simpleErrorHandler) CheckErrors(result *TestResult, errs []error) {
+ h.function(result, errs)
+}
+
+// The default fixture error handler.
+//
+// Will fail the test immediately if any errors are reported.
+var FixtureExpectsNoErrors = FixtureCustomErrorHandler(
+ func(result *TestResult, errs []error) {
+ FailIfErrored(result.T, errs)
+ },
+)
+
+// FixtureExpectsAtLeastOneMatchingError returns an error handler that will cause the test to fail
+// if at least one error that matches the regular expression is not found.
+//
+// The test will be failed if:
+// * No errors are reported.
+// * One or more errors are reported but none match the pattern.
+//
+// The test will not fail if:
+// * Multiple errors are reported that do not match the pattern as long as one does match.
+func FixtureExpectsAtLeastOneErrorMatchingPattern(pattern string) FixtureErrorHandler {
+ return FixtureCustomErrorHandler(func(result *TestResult, errs []error) {
+ FailIfNoMatchingErrors(result.T, pattern, errs)
+ })
+}
+
+// FixtureExpectsOneErrorToMatchPerPattern returns an error handler that will cause the test to fail
+// if there are any unexpected errors.
+//
+// The test will be failed if:
+// * The number of errors reported does not exactly match the patterns.
+// * One or more of the reported errors do not match a pattern.
+// * No patterns are provided and one or more errors are reported.
+//
+// The test will not fail if:
+// * One or more of the patterns does not match an error.
+func FixtureExpectsAllErrorsToMatchAPattern(patterns []string) FixtureErrorHandler {
+ return FixtureCustomErrorHandler(func(result *TestResult, errs []error) {
+ CheckErrorsAgainstExpectations(result.T, errs, patterns)
+ })
+}
+
+// FixtureCustomErrorHandler creates a custom error handler
+func FixtureCustomErrorHandler(function func(result *TestResult, errs []error)) FixtureErrorHandler {
+ return simpleErrorHandler{
+ function: function,
+ }
+}
+
+// Fixture defines the test environment.
+type Fixture interface {
+ // Run the test, checking any errors reported and returning a TestResult instance.
+ RunTest() *TestResult
+}
+
+// Provides general test support.
+type TestHelper struct {
+ *testing.T
+}
+
+// AssertBoolEquals checks if the expected and actual values are equal and if they are not then it
+// reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertBoolEquals(message string, expected bool, actual bool) {
+ h.Helper()
+ if actual != expected {
+ h.Errorf("%s: expected %t, actual %t", message, expected, actual)
+ }
+}
+
+// AssertStringEquals checks if the expected and actual values are equal and if they are not then
+// it reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertStringEquals(message string, expected string, actual string) {
+ h.Helper()
+ if actual != expected {
+ h.Errorf("%s: expected %s, actual %s", message, expected, actual)
+ }
+}
+
+// AssertTrimmedStringEquals checks if the expected and actual values are the same after trimming
+// leading and trailing spaces from them both. If they are not then it reports an error prefixed
+// with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertTrimmedStringEquals(message string, expected string, actual string) {
+ h.Helper()
+ h.AssertStringEquals(message, strings.TrimSpace(expected), strings.TrimSpace(actual))
+}
+
+// AssertStringDoesContain checks if the string contains the expected substring. If it does not
+// then it reports an error prefixed with the supplied message and including a reason for why it
+// failed.
+func (h *TestHelper) AssertStringDoesContain(message string, s string, expectedSubstring string) {
+ h.Helper()
+ if !strings.Contains(s, expectedSubstring) {
+ h.Errorf("%s: could not find %q within %q", message, expectedSubstring, s)
+ }
+}
+
+// AssertStringDoesNotContain checks if the string contains the expected substring. If it does then
+// it reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertStringDoesNotContain(message string, s string, unexpectedSubstring string) {
+ h.Helper()
+ if strings.Contains(s, unexpectedSubstring) {
+ h.Errorf("%s: unexpectedly found %q within %q", message, unexpectedSubstring, s)
+ }
+}
+
+// AssertArrayString checks if the expected and actual values are equal and if they are not then it
+// reports an error prefixed with the supplied message and including a reason for why it failed.
+func (h *TestHelper) AssertArrayString(message string, expected, actual []string) {
+ h.Helper()
+ if len(actual) != len(expected) {
+ h.Errorf("%s: expected %d (%q), actual (%d) %q", message, len(expected), expected, len(actual), actual)
+ return
+ }
+ for i := range actual {
+ if actual[i] != expected[i] {
+ h.Errorf("%s: expected %d-th, %q (%q), actual %q (%q)",
+ message, i, expected[i], expected, actual[i], actual)
+ return
+ }
+ }
+}
+
+// AssertArrayString checks if the expected and actual values are equal using reflect.DeepEqual and
+// if they are not then it reports an error prefixed with the supplied message and including a
+// reason for why it failed.
+func (h *TestHelper) AssertDeepEquals(message string, expected interface{}, actual interface{}) {
+ h.Helper()
+ if !reflect.DeepEqual(actual, expected) {
+ h.Errorf("%s: expected:\n %#v\n got:\n %#v", message, expected, actual)
+ }
+}
+
+// Struct to allow TestResult to embed a *TestContext and allow call forwarding to its methods.
+type testContext struct {
+ *TestContext
+}
+
+// The result of running a test.
+type TestResult struct {
+ TestHelper
+ testContext
+
+ fixture *fixture
+ Config Config
+}
+
+var _ FixtureFactory = (*fixtureFactory)(nil)
+
+type fixtureFactory struct {
+ buildDirSupplier *string
+ preparers []*simpleFixturePreparer
+ errorHandler FixtureErrorHandler
+}
+
+func (f *fixtureFactory) Extend(preparers ...FixturePreparer) FixtureFactory {
+ all := append(f.preparers, dedupAndFlattenPreparers(f.preparers, preparers)...)
+ // Copy the existing factory.
+ extendedFactory := &fixtureFactory{}
+ *extendedFactory = *f
+ // Use the extended list of preparers.
+ extendedFactory.preparers = all
+ return extendedFactory
+}
+
+func (f *fixtureFactory) Fixture(t *testing.T, preparers ...FixturePreparer) Fixture {
+ config := TestConfig(*f.buildDirSupplier, nil, "", nil)
+ ctx := NewTestContext(config)
+ fixture := &fixture{
+ factory: f,
+ t: t,
+ config: config,
+ ctx: ctx,
+ mockFS: make(MockFS),
+ errorHandler: f.errorHandler,
+ }
+
+ for _, preparer := range f.preparers {
+ preparer.function(fixture)
+ }
+
+ for _, preparer := range dedupAndFlattenPreparers(f.preparers, preparers) {
+ preparer.function(fixture)
+ }
+
+ return fixture
+}
+
+func (f *fixtureFactory) SetErrorHandler(errorHandler FixtureErrorHandler) FixtureFactory {
+ f.errorHandler = errorHandler
+ return f
+}
+
+func (f *fixtureFactory) RunTest(t *testing.T, preparers ...FixturePreparer) *TestResult {
+ t.Helper()
+ fixture := f.Fixture(t, preparers...)
+ return fixture.RunTest()
+}
+
+func (f *fixtureFactory) RunTestWithBp(t *testing.T, bp string) *TestResult {
+ t.Helper()
+ return f.RunTest(t, FixtureWithRootAndroidBp(bp))
+}
+
+type fixture struct {
+ // The factory used to create this fixture.
+ factory *fixtureFactory
+
+ // The gotest state of the go test within which this was created.
+ t *testing.T
+
+ // The configuration prepared for this fixture.
+ config Config
+
+ // The test context prepared for this fixture.
+ ctx *TestContext
+
+ // The mock filesystem prepared for this fixture.
+ mockFS MockFS
+
+ // The error handler used to check the errors, if any, that are reported.
+ errorHandler FixtureErrorHandler
+}
+
+func (f *fixture) RunTest() *TestResult {
+ f.t.Helper()
+
+ ctx := f.ctx
+
+ // The TestConfig() method assumes that the mock filesystem is available when creating so creates
+ // the mock file system immediately. Similarly, the NewTestContext(Config) method assumes that the
+ // supplied Config's FileSystem has been properly initialized before it is called and so it takes
+ // its own reference to the filesystem. However, fixtures create the Config and TestContext early
+ // so they can be modified by preparers at which time the mockFS has not been populated (because
+ // it too is modified by preparers). So, this reinitializes the Config and TestContext's
+ // FileSystem using the now populated mockFS.
+ f.config.mockFileSystem("", f.mockFS)
+ ctx.SetFs(ctx.config.fs)
+ if ctx.config.mockBpList != "" {
+ ctx.SetModuleListFile(ctx.config.mockBpList)
+ }
+
+ ctx.Register()
+ _, errs := ctx.ParseBlueprintsFiles("ignored")
+ if len(errs) == 0 {
+ _, errs = ctx.PrepareBuildActions(f.config)
+ }
+
+ result := &TestResult{
+ TestHelper: TestHelper{T: f.t},
+ testContext: testContext{ctx},
+ fixture: f,
+ Config: f.config,
+ }
+
+ f.errorHandler.CheckErrors(result, errs)
+
+ return result
+}
+
+// NormalizePathForTesting removes the test invocation specific build directory from the supplied
+// path.
+//
+// If the path is within the build directory (e.g. an OutputPath) then this returns the relative
+// path to avoid tests having to deal with the dynamically generated build directory.
+//
+// Otherwise, this returns the supplied path as it is almost certainly a source path that is
+// relative to the root of the source tree.
+//
+// Even though some information is removed from some paths and not others it should be possible to
+// differentiate between them by the paths themselves, e.g. output paths will likely include
+// ".intermediates" but source paths won't.
+func (r *TestResult) NormalizePathForTesting(path Path) string {
+ pathContext := PathContextForTesting(r.Config)
+ pathAsString := path.String()
+ if rel, isRel := MaybeRel(pathContext, r.Config.BuildDir(), pathAsString); isRel {
+ return rel
+ }
+ return pathAsString
+}
+
+// NormalizePathsForTesting normalizes each path in the supplied list and returns their normalized
+// forms.
+func (r *TestResult) NormalizePathsForTesting(paths Paths) []string {
+ var result []string
+ for _, path := range paths {
+ result = append(result, r.NormalizePathForTesting(path))
+ }
+ return result
+}
+
+// NewFixture creates a new test fixture that is based on the one that created this result. It is
+// intended to test the output of module types that generate content to be processed by the build,
+// e.g. sdk snapshots.
+func (r *TestResult) NewFixture(preparers ...FixturePreparer) Fixture {
+ return r.fixture.factory.Fixture(r.T, preparers...)
+}
+
+// RunTest is shorthand for NewFixture(preparers...).RunTest().
+func (r *TestResult) RunTest(preparers ...FixturePreparer) *TestResult {
+ r.Helper()
+ return r.fixture.factory.Fixture(r.T, preparers...).RunTest()
+}
+
+// Module returns the module with the specific name and of the specified variant.
+func (r *TestResult) Module(name string, variant string) Module {
+ return r.ModuleForTests(name, variant).Module()
+}
+
+// Create a *TestResult object suitable for use within a subtest.
+//
+// This ensures that any errors reported by the TestResult, e.g. from within one of its
+// Assert... methods, will be associated with the sub test and not the main test.
+//
+// result := ....RunTest()
+// t.Run("subtest", func(t *testing.T) {
+// subResult := result.ResultForSubTest(t)
+// subResult.AssertStringEquals("something", ....)
+// })
+func (r *TestResult) ResultForSubTest(t *testing.T) *TestResult {
+ subTestResult := *r
+ r.T = t
+ return &subTestResult
+}
diff --git a/android/fixture_test.go b/android/fixture_test.go
new file mode 100644
index 0000000..7bc033b
--- /dev/null
+++ b/android/fixture_test.go
@@ -0,0 +1,49 @@
+// Copyright 2021 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 "testing"
+
+// Make sure that FixturePreparer instances are only called once per fixture and in the order in
+// which they were added.
+func TestFixtureDedup(t *testing.T) {
+ list := []string{}
+
+ appendToList := func(s string) FixturePreparer {
+ return FixtureModifyConfig(func(_ Config) {
+ list = append(list, s)
+ })
+ }
+
+ preparer1 := appendToList("preparer1")
+ preparer2 := appendToList("preparer2")
+ preparer3 := appendToList("preparer3")
+ preparer4 := appendToList("preparer4")
+
+ preparer1Then2 := FixturePreparers(preparer1, preparer2)
+
+ preparer2Then1 := FixturePreparers(preparer2, preparer1)
+
+ buildDir := "build"
+ factory := NewFixtureFactory(&buildDir, preparer1, preparer2, preparer1, preparer1Then2)
+
+ extension := factory.Extend(preparer4, preparer2)
+
+ extension.Fixture(t, preparer1, preparer2, preparer2Then1, preparer3)
+
+ h := TestHelper{t}
+ h.AssertDeepEquals("preparers called in wrong order",
+ []string{"preparer1", "preparer2", "preparer4", "preparer3"}, list)
+}
diff --git a/android/module.go b/android/module.go
index 5342246..f0e17ba 100644
--- a/android/module.go
+++ b/android/module.go
@@ -443,6 +443,7 @@
Disable()
Enabled() bool
Target() Target
+ MultiTargets() []Target
Owner() string
InstallInData() bool
InstallInTestcases() bool
@@ -1123,8 +1124,15 @@
variableProperties interface{}
hostAndDeviceProperties hostAndDeviceProperties
generalProperties []interface{}
- archProperties [][]interface{}
- customizableProperties []interface{}
+
+ // Arch specific versions of structs in generalProperties. The outer index
+ // has the same order as generalProperties as initialized in
+ // InitAndroidArchModule, and the inner index chooses the props specific to
+ // the architecture. The interface{} value is an archPropRoot that is
+ // filled with arch specific values by the arch mutator.
+ archProperties [][]interface{}
+
+ customizableProperties []interface{}
// Properties specific to the Blueprint to BUILD migration.
bazelTargetModuleProperties bazel.BazelTargetModuleProperties
diff --git a/android/package.go b/android/package.go
index 7012fc7..878e4c4 100644
--- a/android/package.go
+++ b/android/package.go
@@ -23,6 +23,8 @@
RegisterPackageBuildComponents(InitRegistrationContext)
}
+var PrepareForTestWithPackageModule = FixtureRegisterWithContext(RegisterPackageBuildComponents)
+
// Register the package module type.
func RegisterPackageBuildComponents(ctx RegistrationContext) {
ctx.RegisterModuleType("package", PackageFactory)
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 39d30c5..04864a1 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -100,7 +100,7 @@
// more modules like this.
func (p *Prebuilt) SingleSourcePath(ctx ModuleContext) Path {
if p.srcsSupplier != nil {
- srcs := p.srcsSupplier(ctx)
+ srcs := p.srcsSupplier(ctx, ctx.Module())
if len(srcs) == 0 {
ctx.PropertyErrorf(p.srcsPropertyName, "missing prebuilt source file")
@@ -128,8 +128,11 @@
// Called to provide the srcs value for the prebuilt module.
//
+// This can be called with a context for any module not just the prebuilt one itself. It can also be
+// called concurrently.
+//
// Return the src value or nil if it is not available.
-type PrebuiltSrcsSupplier func(ctx BaseModuleContext) []string
+type PrebuiltSrcsSupplier func(ctx BaseModuleContext, prebuilt Module) []string
// Initialize the module as a prebuilt module that uses the provided supplier to access the
// prebuilt sources of the module.
@@ -163,7 +166,7 @@
panic(fmt.Errorf("srcs must not be nil"))
}
- srcsSupplier := func(ctx BaseModuleContext) []string {
+ srcsSupplier := func(ctx BaseModuleContext, _ Module) []string {
return *srcs
}
@@ -184,7 +187,7 @@
srcFieldIndex := srcStructField.Index
srcPropertyName := proptools.PropertyNameForField(srcField)
- srcsSupplier := func(ctx BaseModuleContext) []string {
+ srcsSupplier := func(ctx BaseModuleContext, _ Module) []string {
if !module.Enabled() {
return nil
}
@@ -256,12 +259,12 @@
panic(fmt.Errorf("prebuilt module did not have InitPrebuiltModule called on it"))
}
if !p.properties.SourceExists {
- p.properties.UsePrebuilt = p.usePrebuilt(ctx, nil)
+ p.properties.UsePrebuilt = p.usePrebuilt(ctx, nil, m)
}
} else if s, ok := ctx.Module().(Module); ok {
ctx.VisitDirectDepsWithTag(PrebuiltDepTag, func(m Module) {
p := m.(PrebuiltInterface).Prebuilt()
- if p.usePrebuilt(ctx, s) {
+ if p.usePrebuilt(ctx, s, m) {
p.properties.UsePrebuilt = true
s.ReplacedByPrebuilt()
}
@@ -296,8 +299,8 @@
// usePrebuilt returns true if a prebuilt should be used instead of the source module. The prebuilt
// will be used if it is marked "prefer" or if the source module is disabled.
-func (p *Prebuilt) usePrebuilt(ctx TopDownMutatorContext, source Module) bool {
- if p.srcsSupplier != nil && len(p.srcsSupplier(ctx)) == 0 {
+func (p *Prebuilt) usePrebuilt(ctx TopDownMutatorContext, source Module, prebuilt Module) bool {
+ if p.srcsSupplier != nil && len(p.srcsSupplier(ctx, prebuilt)) == 0 {
return false
}
diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go
index 9ac3875..32af5df 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -262,7 +262,7 @@
}
func TestPrebuilts(t *testing.T) {
- fs := map[string][]byte{
+ fs := MockFS{
"prebuilt_file": nil,
"source_file": nil,
}
@@ -277,32 +277,33 @@
deps: [":bar"],
}`
}
- config := TestArchConfig(buildDir, nil, bp, fs)
// Add windows to the target list to test the logic when a variant is
// disabled by default.
if !Windows.DefaultDisabled {
t.Errorf("windows is assumed to be disabled by default")
}
- config.config.Targets[Windows] = []Target{
- {Windows, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", "", true},
- }
- ctx := NewTestArchContext(config)
- registerTestPrebuiltBuildComponents(ctx)
- ctx.RegisterModuleType("filegroup", FileGroupFactory)
- ctx.Register()
+ result := emptyTestFixtureFactory.Extend(
+ PrepareForTestWithArchMutator,
+ PrepareForTestWithPrebuilts,
+ PrepareForTestWithOverrides,
+ PrepareForTestWithFilegroup,
+ // Add a Windows target to the configuration.
+ FixtureModifyConfig(func(config Config) {
+ config.Targets[Windows] = []Target{
+ {Windows, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", "", true},
+ }
+ }),
+ fs.AddToFixture(),
+ FixtureRegisterWithContext(registerTestPrebuiltModules),
+ ).RunTestWithBp(t, bp)
- _, errs := ctx.ParseBlueprintsFiles("Android.bp")
- FailIfErrored(t, errs)
- _, errs = ctx.PrepareBuildActions(config)
- FailIfErrored(t, errs)
-
- for _, variant := range ctx.ModuleVariantsForTests("foo") {
- foo := ctx.ModuleForTests("foo", variant)
+ for _, variant := range result.ModuleVariantsForTests("foo") {
+ foo := result.ModuleForTests("foo", variant)
t.Run(foo.Module().Target().Os.String(), func(t *testing.T) {
var dependsOnSourceModule, dependsOnPrebuiltModule bool
- ctx.VisitDirectDeps(foo.Module(), func(m blueprint.Module) {
+ result.VisitDirectDeps(foo.Module(), func(m blueprint.Module) {
if _, ok := m.(*sourceModule); ok {
dependsOnSourceModule = true
}
@@ -381,14 +382,20 @@
}
func registerTestPrebuiltBuildComponents(ctx RegistrationContext) {
- ctx.RegisterModuleType("prebuilt", newPrebuiltModule)
- ctx.RegisterModuleType("source", newSourceModule)
- ctx.RegisterModuleType("override_source", newOverrideSourceModule)
+ registerTestPrebuiltModules(ctx)
RegisterPrebuiltMutators(ctx)
ctx.PostDepsMutators(RegisterOverridePostDepsMutators)
}
+var prepareForTestWithFakePrebuiltModules = FixtureRegisterWithContext(registerTestPrebuiltModules)
+
+func registerTestPrebuiltModules(ctx RegistrationContext) {
+ ctx.RegisterModuleType("prebuilt", newPrebuiltModule)
+ ctx.RegisterModuleType("source", newSourceModule)
+ ctx.RegisterModuleType("override_source", newOverrideSourceModule)
+}
+
type prebuiltModule struct {
ModuleBase
prebuilt Prebuilt
diff --git a/android/testing.go b/android/testing.go
index 1b1feb7..5832796 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -48,6 +48,43 @@
return ctx
}
+var PrepareForTestWithArchMutator = FixturePreparers(
+ // Configure architecture targets in the fixture config.
+ FixtureModifyConfig(modifyTestConfigToSupportArchMutator),
+
+ // Add the arch mutator to the context.
+ FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.PreDepsMutators(registerArchMutator)
+ }),
+)
+
+var PrepareForTestWithDefaults = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
+})
+
+var PrepareForTestWithComponentsMutator = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.PreArchMutators(RegisterComponentsMutator)
+})
+
+var PrepareForTestWithPrebuilts = FixtureRegisterWithContext(RegisterPrebuiltMutators)
+
+var PrepareForTestWithOverrides = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.PostDepsMutators(RegisterOverridePostDepsMutators)
+})
+
+// Prepares an integration test with build components from the android package.
+var PrepareForIntegrationTestWithAndroid = FixturePreparers(
+ // Mutators. Must match order in mutator.go.
+ PrepareForTestWithArchMutator,
+ PrepareForTestWithDefaults,
+ PrepareForTestWithComponentsMutator,
+ PrepareForTestWithPrebuilts,
+ PrepareForTestWithOverrides,
+
+ // Modules
+ PrepareForTestWithFilegroup,
+)
+
func NewTestArchContext(config Config) *TestContext {
ctx := NewTestContext(config)
ctx.preDeps = append(ctx.preDeps, registerArchMutator)
diff --git a/android/visibility.go b/android/visibility.go
index 7eac471..631e88f 100644
--- a/android/visibility.go
+++ b/android/visibility.go
@@ -202,6 +202,18 @@
ExcludeFromVisibilityEnforcement()
}
+var PrepareForTestWithVisibilityRuleChecker = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.PreArchMutators(RegisterVisibilityRuleChecker)
+})
+
+var PrepareForTestWithVisibilityRuleGatherer = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.PreArchMutators(RegisterVisibilityRuleGatherer)
+})
+
+var PrepareForTestWithVisibilityRuleEnforcer = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.PostDepsMutators(RegisterVisibilityRuleEnforcer)
+})
+
// The rule checker needs to be registered before defaults expansion to correctly check that
// //visibility:xxx isn't combined with other packages in the same list in any one module.
func RegisterVisibilityRuleChecker(ctx RegisterMutatorsContext) {
diff --git a/android/visibility_test.go b/android/visibility_test.go
index 87a295e..30cdcbf 100644
--- a/android/visibility_test.go
+++ b/android/visibility_test.go
@@ -9,13 +9,13 @@
var visibilityTests = []struct {
name string
- fs map[string][]byte
+ fs MockFS
expectedErrors []string
effectiveVisibility map[qualifiedModuleName][]string
}{
{
name: "invalid visibility: empty list",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -26,7 +26,7 @@
},
{
name: "invalid visibility: empty rule",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -37,7 +37,7 @@
},
{
name: "invalid visibility: unqualified",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -48,7 +48,7 @@
},
{
name: "invalid visibility: empty namespace",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -59,7 +59,7 @@
},
{
name: "invalid visibility: empty module",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -70,7 +70,7 @@
},
{
name: "invalid visibility: empty namespace and module",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -81,7 +81,7 @@
},
{
name: "//visibility:unknown",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -92,7 +92,7 @@
},
{
name: "//visibility:xxx mixed",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -113,7 +113,7 @@
},
{
name: "//visibility:legacy_public",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -129,7 +129,7 @@
// Verify that //visibility:public will allow the module to be referenced from anywhere, e.g.
// the current directory, a nested directory and a directory in a separate tree.
name: "//visibility:public",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -156,7 +156,7 @@
// Verify that //visibility:private allows the module to be referenced from the current
// directory only.
name: "//visibility:private",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -188,7 +188,7 @@
{
// Verify that :__pkg__ allows the module to be referenced from the current directory only.
name: ":__pkg__",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -221,7 +221,7 @@
// Verify that //top/nested allows the module to be referenced from the current directory and
// the top/nested directory only, not a subdirectory of top/nested and not peak directory.
name: "//top/nested",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -259,7 +259,7 @@
// Verify that :__subpackages__ allows the module to be referenced from the current directory
// and sub directories but nowhere else.
name: ":__subpackages__",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -290,7 +290,7 @@
// Verify that //top/nested:__subpackages__ allows the module to be referenced from the current
// directory and sub directories but nowhere else.
name: "//top/nested:__subpackages__",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -321,7 +321,7 @@
// Verify that ["//top/nested", "//peak:__subpackages"] allows the module to be referenced from
// the current directory, top/nested and peak and all its subpackages.
name: `["//top/nested", "//peak:__subpackages__"]`,
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -347,7 +347,7 @@
{
// Verify that //vendor... cannot be used outside vendor apart from //vendor:__subpackages__
name: `//vendor`,
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -381,7 +381,7 @@
{
// Check that visibility is the union of the defaults modules.
name: "defaults union, basic",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
@@ -419,7 +419,7 @@
},
{
name: "defaults union, multiple defaults",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults_1",
@@ -460,7 +460,7 @@
},
{
name: "//visibility:public mixed with other in defaults",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
@@ -478,7 +478,7 @@
},
{
name: "//visibility:public overriding defaults",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
@@ -501,7 +501,7 @@
},
{
name: "//visibility:public mixed with other from different defaults 1",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults_1",
@@ -524,7 +524,7 @@
},
{
name: "//visibility:public mixed with other from different defaults 2",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults_1",
@@ -547,7 +547,7 @@
},
{
name: "//visibility:private in defaults",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
@@ -581,7 +581,7 @@
},
{
name: "//visibility:private mixed with other in defaults",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
@@ -599,7 +599,7 @@
},
{
name: "//visibility:private overriding defaults",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
@@ -618,7 +618,7 @@
},
{
name: "//visibility:private in defaults overridden",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
@@ -637,7 +637,7 @@
},
{
name: "//visibility:private override //visibility:public",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
@@ -655,7 +655,7 @@
},
{
name: "//visibility:public override //visibility:private",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
@@ -673,7 +673,7 @@
},
{
name: "//visibility:override must be first in the list",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_library {
name: "libexample",
@@ -686,7 +686,7 @@
},
{
name: "//visibility:override discards //visibility:private",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
@@ -707,7 +707,7 @@
},
{
name: "//visibility:override discards //visibility:public",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
@@ -736,7 +736,7 @@
},
{
name: "//visibility:override discards defaults supplied rules",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
@@ -765,7 +765,7 @@
},
{
name: "//visibility:override can override //visibility:public with //visibility:private",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
@@ -788,7 +788,7 @@
},
{
name: "//visibility:override can override //visibility:private with //visibility:public",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults",
@@ -808,7 +808,7 @@
},
{
name: "//visibility:private mixed with itself",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "libexample_defaults_1",
@@ -838,7 +838,7 @@
// Defaults module's defaults_visibility tests
{
name: "defaults_visibility invalid",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_defaults {
name: "top_defaults",
@@ -851,7 +851,7 @@
},
{
name: "defaults_visibility overrides package default",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
package {
default_visibility: ["//visibility:private"],
@@ -871,7 +871,7 @@
// Package default_visibility tests
{
name: "package default_visibility property is checked",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
package {
default_visibility: ["//visibility:invalid"],
@@ -882,7 +882,7 @@
{
// This test relies on the default visibility being legacy_public.
name: "package default_visibility property used when no visibility specified",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
package {
default_visibility: ["//visibility:private"],
@@ -904,7 +904,7 @@
},
{
name: "package default_visibility public does not override visibility private",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
package {
default_visibility: ["//visibility:public"],
@@ -927,7 +927,7 @@
},
{
name: "package default_visibility private does not override visibility public",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
package {
default_visibility: ["//visibility:private"],
@@ -946,7 +946,7 @@
},
{
name: "package default_visibility :__subpackages__",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
package {
default_visibility: [":__subpackages__"],
@@ -973,7 +973,7 @@
},
{
name: "package default_visibility inherited to subpackages",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
package {
default_visibility: ["//outsider"],
@@ -1001,7 +1001,7 @@
},
{
name: "package default_visibility inherited to subpackages",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
package {
default_visibility: ["//visibility:private"],
@@ -1031,7 +1031,7 @@
},
{
name: "verify that prebuilt dependencies are ignored for visibility reasons (not preferred)",
- fs: map[string][]byte{
+ fs: MockFS{
"prebuilts/Blueprints": []byte(`
prebuilt {
name: "module",
@@ -1053,7 +1053,7 @@
},
{
name: "verify that prebuilt dependencies are ignored for visibility reasons (preferred)",
- fs: map[string][]byte{
+ fs: MockFS{
"prebuilts/Blueprints": []byte(`
prebuilt {
name: "module",
@@ -1076,7 +1076,7 @@
},
{
name: "ensure visibility properties are checked for correctness",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_parent {
name: "parent",
@@ -1093,7 +1093,7 @@
},
{
name: "invalid visibility added to child detected during gather phase",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_parent {
name: "parent",
@@ -1115,7 +1115,7 @@
},
{
name: "automatic visibility inheritance enabled",
- fs: map[string][]byte{
+ fs: MockFS{
"top/Blueprints": []byte(`
mock_parent {
name: "parent",
@@ -1142,55 +1142,43 @@
func TestVisibility(t *testing.T) {
for _, test := range visibilityTests {
t.Run(test.name, func(t *testing.T) {
- ctx, errs := testVisibility(buildDir, test.fs)
-
- CheckErrorsAgainstExpectations(t, errs, test.expectedErrors)
+ result := emptyTestFixtureFactory.Extend(
+ FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.RegisterModuleType("mock_library", newMockLibraryModule)
+ ctx.RegisterModuleType("mock_parent", newMockParentFactory)
+ ctx.RegisterModuleType("mock_defaults", defaultsFactory)
+ }),
+ prepareForTestWithFakePrebuiltModules,
+ PrepareForTestWithPackageModule,
+ // Order of the following method calls is significant as they register mutators.
+ PrepareForTestWithArchMutator,
+ PrepareForTestWithPrebuilts,
+ PrepareForTestWithOverrides,
+ PrepareForTestWithVisibilityRuleChecker,
+ PrepareForTestWithDefaults,
+ PrepareForTestWithVisibilityRuleGatherer,
+ PrepareForTestWithVisibilityRuleEnforcer,
+ // Add additional files to the mock filesystem
+ test.fs.AddToFixture(),
+ ).
+ SetErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(test.expectedErrors)).
+ RunTest(t)
if test.effectiveVisibility != nil {
- checkEffectiveVisibility(t, ctx, test.effectiveVisibility)
+ checkEffectiveVisibility(result, test.effectiveVisibility)
}
})
}
}
-func checkEffectiveVisibility(t *testing.T, ctx *TestContext, effectiveVisibility map[qualifiedModuleName][]string) {
+func checkEffectiveVisibility(result *TestResult, effectiveVisibility map[qualifiedModuleName][]string) {
for moduleName, expectedRules := range effectiveVisibility {
- rule := effectiveVisibilityRules(ctx.config, moduleName)
+ rule := effectiveVisibilityRules(result.Config, moduleName)
stringRules := rule.Strings()
- if !reflect.DeepEqual(expectedRules, stringRules) {
- t.Errorf("effective rules mismatch: expected %q, found %q", expectedRules, stringRules)
- }
+ result.AssertDeepEquals("effective rules mismatch", expectedRules, stringRules)
}
}
-func testVisibility(buildDir string, fs map[string][]byte) (*TestContext, []error) {
-
- // Create a new config per test as visibility information is stored in the config.
- config := TestArchConfig(buildDir, nil, "", fs)
-
- ctx := NewTestArchContext(config)
- ctx.RegisterModuleType("mock_library", newMockLibraryModule)
- ctx.RegisterModuleType("mock_parent", newMockParentFactory)
- ctx.RegisterModuleType("mock_defaults", defaultsFactory)
-
- // Order of the following method calls is significant.
- RegisterPackageBuildComponents(ctx)
- registerTestPrebuiltBuildComponents(ctx)
- ctx.PreArchMutators(RegisterVisibilityRuleChecker)
- ctx.PreArchMutators(RegisterDefaultsPreArchMutators)
- ctx.PreArchMutators(RegisterVisibilityRuleGatherer)
- ctx.PostDepsMutators(RegisterVisibilityRuleEnforcer)
- ctx.Register()
-
- _, errs := ctx.ParseBlueprintsFiles(".")
- if len(errs) > 0 {
- return ctx, errs
- }
-
- _, errs = ctx.PrepareBuildActions(config)
- return ctx, errs
-}
-
type mockLibraryProperties struct {
Deps []string
}
diff --git a/apex/allowed_deps.txt b/apex/allowed_deps.txt
index 47ea239..78b84f0 100644
--- a/apex/allowed_deps.txt
+++ b/apex/allowed_deps.txt
@@ -477,6 +477,7 @@
MediaProvider(minSdkVersion:30)
mediaswcodec(minSdkVersion:29)
metrics-constants-protos(minSdkVersion:29)
+modules-annotation-minsdk(minSdkVersion:29)
modules-utils-build(minSdkVersion:29)
modules-utils-build_system(minSdkVersion:29)
modules-utils-os(minSdkVersion:30)
diff --git a/apex/apex.go b/apex/apex.go
index 662bbbd..a4bdc63 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -54,8 +54,6 @@
func RegisterPreDepsMutators(ctx android.RegisterMutatorsContext) {
ctx.TopDown("apex_vndk", apexVndkMutator).Parallel()
ctx.BottomUp("apex_vndk_deps", apexVndkDepsMutator).Parallel()
- ctx.BottomUp("prebuilt_apex_select_source", prebuiltSelectSourceMutator).Parallel()
- ctx.BottomUp("deapexer_select_source", deapexerSelectSourceMutator).Parallel()
}
func RegisterPostDepsMutators(ctx android.RegisterMutatorsContext) {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index c27eed6..af5fe06 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -19,6 +19,7 @@
"io/ioutil"
"os"
"path"
+ "path/filepath"
"reflect"
"regexp"
"sort"
@@ -192,6 +193,7 @@
"AppSet.apks": nil,
"foo.rs": nil,
"libfoo.jar": nil,
+ "libbar.jar": nil,
}
cc.GatherRequiredFilesForTest(fs)
@@ -971,8 +973,8 @@
mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex29").Rule("ld").Args["libFlags"]
- // Ensure that mylib is linking with the version 29 stubs for mylib2
- ensureContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_29/mylib2.so")
+ // Ensure that mylib is linking with the latest version of stub for mylib2
+ ensureContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_current/mylib2.so")
// ... and not linking to the non-stub (impl) variant of mylib2
ensureNotContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared/mylib2.so")
@@ -1056,11 +1058,11 @@
config.TestProductVariables.Platform_version_active_codenames = []string{"Z"}
})
- // Ensure that mylib from myapex is built against "min_sdk_version" stub ("Z"), which is non-final
+ // Ensure that mylib from myapex is built against the latest stub (current)
mylibCflags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_apex10000").Rule("cc").Args["cFlags"]
- ensureContains(t, mylibCflags, "-D__LIBSTUB_API__=9000 ")
+ ensureContains(t, mylibCflags, "-D__LIBSTUB_API__=10000 ")
mylibLdflags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
- ensureContains(t, mylibLdflags, "libstub/android_arm64_armv8-a_shared_Z/libstub.so ")
+ ensureContains(t, mylibLdflags, "libstub/android_arm64_armv8-a_shared_current/libstub.so ")
// Ensure that libplatform is built against latest stub ("current") of mylib3 from the apex
libplatformCflags := ctx.ModuleForTests("libplatform", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
@@ -1358,18 +1360,18 @@
shouldNotLink []string
}{
{
- name: "should link to the latest",
+ name: "unspecified version links to the latest",
minSdkVersion: "",
apexVariant: "apex10000",
shouldLink: "30",
shouldNotLink: []string{"29"},
},
{
- name: "should link to llndk#29",
+ name: "always use the latest",
minSdkVersion: "min_sdk_version: \"29\",",
apexVariant: "apex29",
- shouldLink: "29",
- shouldNotLink: []string{"30"},
+ shouldLink: "30",
+ shouldNotLink: []string{"29"},
},
}
for _, tc := range testcases {
@@ -1531,8 +1533,8 @@
}
func TestApexMinSdkVersion_NativeModulesShouldBeBuiltAgainstStubs(t *testing.T) {
- // there are three links between liba --> libz
- // 1) myapex -> libx -> liba -> libz : this should be #29 link, but fallback to #28
+ // there are three links between liba --> libz.
+ // 1) myapex -> libx -> liba -> libz : this should be #30 link
// 2) otherapex -> liby -> liba -> libz : this should be #30 link
// 3) (platform) -> liba -> libz : this should be non-stub link
ctx, _ := testApex(t, `
@@ -1606,9 +1608,9 @@
}
// platform liba is linked to non-stub version
expectLink("liba", "shared", "libz", "shared")
- // liba in myapex is linked to #28
- expectLink("liba", "shared_apex29", "libz", "shared_28")
- expectNoLink("liba", "shared_apex29", "libz", "shared_30")
+ // liba in myapex is linked to #30
+ expectLink("liba", "shared_apex29", "libz", "shared_30")
+ expectNoLink("liba", "shared_apex29", "libz", "shared_28")
expectNoLink("liba", "shared_apex29", "libz", "shared")
// liba in otherapex is linked to #30
expectLink("liba", "shared_apex30", "libz", "shared_30")
@@ -1826,41 +1828,6 @@
ensureListNotContains(t, cm.Properties.AndroidMkStaticLibs, "libunwind")
}
-func TestApexMinSdkVersion_ErrorIfIncompatibleStubs(t *testing.T) {
- testApexError(t, `"libz" .*: not found a version\(<=29\)`, `
- apex {
- name: "myapex",
- key: "myapex.key",
- native_shared_libs: ["libx"],
- min_sdk_version: "29",
- }
-
- apex_key {
- name: "myapex.key",
- public_key: "testkey.avbpubkey",
- private_key: "testkey.pem",
- }
-
- cc_library {
- name: "libx",
- shared_libs: ["libz"],
- system_shared_libs: [],
- stl: "none",
- apex_available: [ "myapex" ],
- min_sdk_version: "29",
- }
-
- cc_library {
- name: "libz",
- system_shared_libs: [],
- stl: "none",
- stubs: {
- versions: ["30"],
- },
- }
- `)
-}
-
func TestApexMinSdkVersion_ErrorIfIncompatibleVersion(t *testing.T) {
testApexError(t, `module "mylib".*: should support min_sdk_version\(29\)`, `
apex {
@@ -2172,7 +2139,7 @@
private_key: "testkey.pem",
}
- // mylib in myapex will link to mylib2#29
+ // mylib in myapex will link to mylib2#30
// mylib in otherapex will link to mylib2(non-stub) in otherapex as well
cc_library {
name: "mylib",
@@ -2206,7 +2173,7 @@
libFlags := ld.Args["libFlags"]
ensureContains(t, libFlags, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
}
- expectLink("mylib", "shared_apex29", "mylib2", "shared_29")
+ expectLink("mylib", "shared_apex29", "mylib2", "shared_30")
expectLink("mylib", "shared_apex30", "mylib2", "shared_apex30")
}
@@ -2275,7 +2242,7 @@
// ensure libfoo is linked with "S" version of libbar stub
libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared_apex10000")
libFlags := libfoo.Rule("ld").Args["libFlags"]
- ensureContains(t, libFlags, "android_arm64_armv8-a_shared_S/libbar.so")
+ ensureContains(t, libFlags, "android_arm64_armv8-a_shared_T/libbar.so")
}
func TestFilesInSubDir(t *testing.T) {
@@ -4319,14 +4286,15 @@
// Make sure the import has been given the correct path to the dex jar.
p := ctx.ModuleForTests(name, "android_common_myapex").Module().(java.UsesLibraryDependency)
dexJarBuildPath := p.DexJarBuildPath()
- if expected, actual := ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar", android.NormalizePathForTesting(dexJarBuildPath); actual != expected {
+ stem := android.RemoveOptionalPrebuiltPrefix(name)
+ if expected, actual := ".intermediates/myapex.deapexer/android_common/deapexer/javalib/"+stem+".jar", android.NormalizePathForTesting(dexJarBuildPath); actual != expected {
t.Errorf("Incorrect DexJarBuildPath value '%s', expected '%s'", actual, expected)
}
}
- ensureNoSourceVariant := func(t *testing.T, ctx *android.TestContext) {
+ ensureNoSourceVariant := func(t *testing.T, ctx *android.TestContext, name string) {
// Make sure that an apex variant is not created for the source module.
- if expected, actual := []string{"android_common"}, ctx.ModuleVariantsForTests("libfoo"); !reflect.DeepEqual(expected, actual) {
+ if expected, actual := []string{"android_common"}, ctx.ModuleVariantsForTests(name); !reflect.DeepEqual(expected, actual) {
t.Errorf("invalid set of variants for %q: expected %q, found %q", "libfoo", expected, actual)
}
}
@@ -4343,19 +4311,42 @@
src: "myapex-arm.apex",
},
},
- exported_java_libs: ["libfoo"],
+ exported_java_libs: ["libfoo", "libbar"],
}
java_import {
name: "libfoo",
jars: ["libfoo.jar"],
}
+
+ java_sdk_library_import {
+ name: "libbar",
+ public: {
+ jars: ["libbar.jar"],
+ },
+ }
`
// Make sure that dexpreopt can access dex implementation files from the prebuilt.
ctx := testDexpreoptWithApexes(t, bp, "", transform)
+ // Make sure that the deapexer has the correct input APEX.
+ deapexer := ctx.ModuleForTests("myapex.deapexer", "android_common")
+ rule := deapexer.Rule("deapexer")
+ if expected, actual := []string{"myapex-arm64.apex"}, android.NormalizePathsForTesting(rule.Implicits); !reflect.DeepEqual(expected, actual) {
+ t.Errorf("expected: %q, found: %q", expected, actual)
+ }
+
+ // Make sure that the prebuilt_apex has the correct input APEX.
+ prebuiltApex := ctx.ModuleForTests("myapex", "android_common")
+ rule = prebuiltApex.Rule("android/soong/android.Cp")
+ if expected, actual := "myapex-arm64.apex", android.NormalizePathForTesting(rule.Input); !reflect.DeepEqual(expected, actual) {
+ t.Errorf("expected: %q, found: %q", expected, actual)
+ }
+
checkDexJarBuildPath(t, ctx, "libfoo")
+
+ checkDexJarBuildPath(t, ctx, "libbar")
})
t.Run("prebuilt with source preferred", func(t *testing.T) {
@@ -4371,7 +4362,7 @@
src: "myapex-arm.apex",
},
},
- exported_java_libs: ["libfoo"],
+ exported_java_libs: ["libfoo", "libbar"],
}
java_import {
@@ -4382,13 +4373,29 @@
java_library {
name: "libfoo",
}
+
+ java_sdk_library_import {
+ name: "libbar",
+ public: {
+ jars: ["libbar.jar"],
+ },
+ }
+
+ java_sdk_library {
+ name: "libbar",
+ srcs: ["foo/bar/MyClass.java"],
+ unsafe_ignore_missing_latest_api: true,
+ }
`
// Make sure that dexpreopt can access dex implementation files from the prebuilt.
ctx := testDexpreoptWithApexes(t, bp, "", transform)
checkDexJarBuildPath(t, ctx, "prebuilt_libfoo")
- ensureNoSourceVariant(t, ctx)
+ ensureNoSourceVariant(t, ctx, "libfoo")
+
+ checkDexJarBuildPath(t, ctx, "prebuilt_libbar")
+ ensureNoSourceVariant(t, ctx, "libbar")
})
t.Run("prebuilt preferred with source", func(t *testing.T) {
@@ -4403,7 +4410,7 @@
src: "myapex-arm.apex",
},
},
- exported_java_libs: ["libfoo"],
+ exported_java_libs: ["libfoo", "libbar"],
}
java_import {
@@ -4415,26 +4422,45 @@
java_library {
name: "libfoo",
}
+
+ java_sdk_library_import {
+ name: "libbar",
+ prefer: true,
+ public: {
+ jars: ["libbar.jar"],
+ },
+ }
+
+ java_sdk_library {
+ name: "libbar",
+ srcs: ["foo/bar/MyClass.java"],
+ unsafe_ignore_missing_latest_api: true,
+ }
`
// Make sure that dexpreopt can access dex implementation files from the prebuilt.
ctx := testDexpreoptWithApexes(t, bp, "", transform)
checkDexJarBuildPath(t, ctx, "prebuilt_libfoo")
- ensureNoSourceVariant(t, ctx)
+ ensureNoSourceVariant(t, ctx, "libfoo")
+
+ checkDexJarBuildPath(t, ctx, "prebuilt_libbar")
+ ensureNoSourceVariant(t, ctx, "libbar")
})
}
func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) {
transform := func(config *dexpreopt.GlobalConfig) {
- config.BootJars = android.CreateTestConfiguredJarList([]string{"myapex:libfoo"})
+ config.BootJars = android.CreateTestConfiguredJarList([]string{"myapex:libfoo", "myapex:libbar"})
}
- checkBootDexJarPath := func(t *testing.T, ctx *android.TestContext, bootDexJarPath string) {
+ checkBootDexJarPath := func(t *testing.T, ctx *android.TestContext, stem string, bootDexJarPath string) {
+ t.Helper()
s := ctx.SingletonForTests("dex_bootjars")
foundLibfooJar := false
+ base := stem + ".jar"
for _, output := range s.AllOutputs() {
- if strings.HasSuffix(output, "/libfoo.jar") {
+ if filepath.Base(output) == base {
foundLibfooJar = true
buildRule := s.Output(output)
actual := android.NormalizePathForTesting(buildRule.Input)
@@ -4449,6 +4475,7 @@
}
checkHiddenAPIIndexInputs := func(t *testing.T, ctx *android.TestContext, expectedInputs string) {
+ t.Helper()
hiddenAPIIndex := ctx.SingletonForTests("hiddenapi_index")
indexRule := hiddenAPIIndex.Rule("singleton-merged-hiddenapi-index")
java.CheckHiddenAPIRuleInputs(t, expectedInputs, indexRule)
@@ -4466,7 +4493,7 @@
src: "myapex-arm.apex",
},
},
- exported_java_libs: ["libfoo"],
+ exported_java_libs: ["libfoo", "libbar"],
}
java_import {
@@ -4474,13 +4501,23 @@
jars: ["libfoo.jar"],
apex_available: ["myapex"],
}
+
+ java_sdk_library_import {
+ name: "libbar",
+ public: {
+ jars: ["libbar.jar"],
+ },
+ apex_available: ["myapex"],
+ }
`
ctx := testDexpreoptWithApexes(t, bp, "", transform)
- checkBootDexJarPath(t, ctx, ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+ checkBootDexJarPath(t, ctx, "libfoo", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+ checkBootDexJarPath(t, ctx, "libbar", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
// Make sure that the dex file from the prebuilt_apex contributes to the hiddenapi index file.
checkHiddenAPIIndexInputs(t, ctx, `
+.intermediates/libbar/android_common_myapex/hiddenapi/index.csv
.intermediates/libfoo/android_common_myapex/hiddenapi/index.csv
`)
})
@@ -4497,7 +4534,7 @@
src: "myapex-arm.apex",
},
},
- exported_java_libs: ["libfoo"],
+ exported_java_libs: ["libfoo", "libbar"],
}
java_import {
@@ -4511,6 +4548,21 @@
srcs: ["foo/bar/MyClass.java"],
apex_available: ["myapex"],
}
+
+ java_sdk_library_import {
+ name: "libbar",
+ public: {
+ jars: ["libbar.jar"],
+ },
+ apex_available: ["myapex"],
+ }
+
+ java_sdk_library {
+ name: "libbar",
+ srcs: ["foo/bar/MyClass.java"],
+ unsafe_ignore_missing_latest_api: true,
+ apex_available: ["myapex"],
+ }
`
// In this test the source (java_library) libfoo is active since the
@@ -4533,7 +4585,7 @@
src: "myapex-arm.apex",
},
},
- exported_java_libs: ["libfoo"],
+ exported_java_libs: ["libfoo", "libbar"],
}
java_import {
@@ -4548,13 +4600,31 @@
srcs: ["foo/bar/MyClass.java"],
apex_available: ["myapex"],
}
+
+ java_sdk_library_import {
+ name: "libbar",
+ prefer: true,
+ public: {
+ jars: ["libbar.jar"],
+ },
+ apex_available: ["myapex"],
+ }
+
+ java_sdk_library {
+ name: "libbar",
+ srcs: ["foo/bar/MyClass.java"],
+ unsafe_ignore_missing_latest_api: true,
+ apex_available: ["myapex"],
+ }
`
ctx := testDexpreoptWithApexes(t, bp, "", transform)
- checkBootDexJarPath(t, ctx, ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+ checkBootDexJarPath(t, ctx, "libfoo", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+ checkBootDexJarPath(t, ctx, "libbar", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
// Make sure that the dex file from the prebuilt_apex contributes to the hiddenapi index file.
checkHiddenAPIIndexInputs(t, ctx, `
+.intermediates/prebuilt_libbar/android_common_myapex/hiddenapi/index.csv
.intermediates/prebuilt_libfoo/android_common_myapex/hiddenapi/index.csv
`)
})
@@ -4564,7 +4634,7 @@
apex {
name: "myapex",
key: "myapex.key",
- java_libs: ["libfoo"],
+ java_libs: ["libfoo", "libbar"],
}
apex_key {
@@ -4583,7 +4653,7 @@
src: "myapex-arm.apex",
},
},
- exported_java_libs: ["libfoo"],
+ exported_java_libs: ["libfoo", "libbar"],
}
java_import {
@@ -4597,13 +4667,30 @@
srcs: ["foo/bar/MyClass.java"],
apex_available: ["myapex"],
}
+
+ java_sdk_library_import {
+ name: "libbar",
+ public: {
+ jars: ["libbar.jar"],
+ },
+ apex_available: ["myapex"],
+ }
+
+ java_sdk_library {
+ name: "libbar",
+ srcs: ["foo/bar/MyClass.java"],
+ unsafe_ignore_missing_latest_api: true,
+ apex_available: ["myapex"],
+ }
`
ctx := testDexpreoptWithApexes(t, bp, "", transform)
- checkBootDexJarPath(t, ctx, ".intermediates/libfoo/android_common_apex10000/hiddenapi/libfoo.jar")
+ checkBootDexJarPath(t, ctx, "libfoo", ".intermediates/libfoo/android_common_apex10000/hiddenapi/libfoo.jar")
+ checkBootDexJarPath(t, ctx, "libbar", ".intermediates/libbar/android_common_myapex/hiddenapi/libbar.jar")
// Make sure that the dex file from the prebuilt_apex contributes to the hiddenapi index file.
checkHiddenAPIIndexInputs(t, ctx, `
+.intermediates/libbar/android_common_myapex/hiddenapi/index.csv
.intermediates/libfoo/android_common_apex10000/hiddenapi/index.csv
`)
})
@@ -4633,7 +4720,7 @@
src: "myapex-arm.apex",
},
},
- exported_java_libs: ["libfoo"],
+ exported_java_libs: ["libfoo", "libbar"],
}
java_import {
@@ -4648,13 +4735,31 @@
srcs: ["foo/bar/MyClass.java"],
apex_available: ["myapex"],
}
+
+ java_sdk_library_import {
+ name: "libbar",
+ prefer: true,
+ public: {
+ jars: ["libbar.jar"],
+ },
+ apex_available: ["myapex"],
+ }
+
+ java_sdk_library {
+ name: "libbar",
+ srcs: ["foo/bar/MyClass.java"],
+ unsafe_ignore_missing_latest_api: true,
+ apex_available: ["myapex"],
+ }
`
ctx := testDexpreoptWithApexes(t, bp, "", transform)
- checkBootDexJarPath(t, ctx, ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+ checkBootDexJarPath(t, ctx, "libfoo", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+ checkBootDexJarPath(t, ctx, "libbar", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
// Make sure that the dex file from the prebuilt_apex contributes to the hiddenapi index file.
checkHiddenAPIIndexInputs(t, ctx, `
+.intermediates/prebuilt_libbar/android_common_prebuilt_myapex/hiddenapi/index.csv
.intermediates/prebuilt_libfoo/android_common_prebuilt_myapex/hiddenapi/index.csv
`)
})
@@ -6272,6 +6377,9 @@
}
cc.GatherRequiredFilesForTest(fs)
+ for k, v := range filesForSdkLibrary {
+ fs[k] = v
+ }
config := android.TestArchConfig(buildDir, nil, bp, fs)
ctx := android.NewTestArchContext(config)
@@ -6280,6 +6388,7 @@
ctx.RegisterModuleType("prebuilt_apex", PrebuiltFactory)
ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
+ ctx.PreArchMutators(android.RegisterComponentsMutator)
android.RegisterPrebuiltMutators(ctx)
cc.RegisterRequiredBuildComponentsForTest(ctx)
java.RegisterRequiredBuildComponentsForTest(ctx)
diff --git a/apex/deapexer.go b/apex/deapexer.go
index 8f4a285..46ce41f 100644
--- a/apex/deapexer.go
+++ b/apex/deapexer.go
@@ -65,7 +65,7 @@
&module.properties,
&module.apexFileProperties,
)
- android.InitSingleSourcePrebuiltModule(module, &module.apexFileProperties, "Source")
+ android.InitPrebuiltModuleWithSrcSupplier(module, module.apexFileProperties.prebuiltApexSelector, "src")
android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
return module
}
@@ -78,16 +78,6 @@
return p.prebuilt.Name(p.ModuleBase.Name())
}
-func deapexerSelectSourceMutator(ctx android.BottomUpMutatorContext) {
- p, ok := ctx.Module().(*Deapexer)
- if !ok {
- return
- }
- if err := p.apexFileProperties.selectSource(ctx); err != nil {
- ctx.ModuleErrorf("%s", err)
- }
-}
-
func (p *Deapexer) DepsMutator(ctx android.BottomUpMutatorContext) {
// Add dependencies from the java modules to which this exports files from the `.apex` file onto
// this module so that they can access the `DeapexerInfo` object that this provides.
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index ec7f253..3280cd8 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -109,8 +109,10 @@
type ApexFileProperties struct {
// the path to the prebuilt .apex file to import.
- Source string `blueprint:"mutated"`
-
+ //
+ // This cannot be marked as `android:"arch_variant"` because the `prebuilt_apex` is only mutated
+ // for android_common. That is so that it will have the same arch variant as, and so be compatible
+ // with, the source `apex` module type that it replaces.
Src *string
Arch struct {
Arm struct {
@@ -128,15 +130,20 @@
}
}
-func (p *ApexFileProperties) selectSource(ctx android.BottomUpMutatorContext) error {
- // This is called before prebuilt_select and prebuilt_postdeps mutators
- // The mutators requires that src to be set correctly for each arch so that
- // arch variants are disabled when src is not provided for the arch.
- if len(ctx.MultiTargets()) != 1 {
- return fmt.Errorf("compile_multilib shouldn't be \"both\" for prebuilt_apex")
+// prebuiltApexSelector selects the correct prebuilt APEX file for the build target.
+//
+// The ctx parameter can be for any module not just the prebuilt module so care must be taken not
+// to use methods on it that are specific to the current module.
+//
+// See the ApexFileProperties.Src property.
+func (p *ApexFileProperties) prebuiltApexSelector(ctx android.BaseModuleContext, prebuilt android.Module) []string {
+ multiTargets := prebuilt.MultiTargets()
+ if len(multiTargets) != 1 {
+ ctx.OtherModuleErrorf(prebuilt, "compile_multilib shouldn't be \"both\" for prebuilt_apex")
+ return nil
}
var src string
- switch ctx.MultiTargets()[0].Arch.ArchType {
+ switch multiTargets[0].Arch.ArchType {
case android.Arm:
src = String(p.Arch.Arm.Src)
case android.Arm64:
@@ -146,14 +153,14 @@
case android.X86_64:
src = String(p.Arch.X86_64.Src)
default:
- return fmt.Errorf("prebuilt_apex does not support %q", ctx.MultiTargets()[0].Arch.String())
+ ctx.OtherModuleErrorf(prebuilt, "prebuilt_apex does not support %q", multiTargets[0].Arch.String())
+ return nil
}
if src == "" {
src = String(p.Src)
}
- p.Source = src
- return nil
+ return []string{src}
}
type PrebuiltProperties struct {
@@ -217,7 +224,7 @@
func PrebuiltFactory() android.Module {
module := &Prebuilt{}
module.AddProperties(&module.properties)
- android.InitSingleSourcePrebuiltModule(module, &module.properties, "Source")
+ android.InitPrebuiltModuleWithSrcSupplier(module, module.properties.prebuiltApexSelector, "src")
android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
android.AddLoadHook(module, func(ctx android.LoadHookContext) {
@@ -250,16 +257,6 @@
return name
}
-func prebuiltSelectSourceMutator(ctx android.BottomUpMutatorContext) {
- p, ok := ctx.Module().(*Prebuilt)
- if !ok {
- return
- }
- if err := p.properties.selectSource(ctx); err != nil {
- ctx.ModuleErrorf("%s", err)
- }
-}
-
type exportedDependencyTag struct {
blueprint.BaseDependencyTag
name string
@@ -535,7 +532,7 @@
module := &ApexSet{}
module.AddProperties(&module.properties)
- srcsSupplier := func(ctx android.BaseModuleContext) []string {
+ srcsSupplier := func(ctx android.BaseModuleContext, _ android.Module) []string {
return module.prebuiltSrcs(ctx)
}
diff --git a/bazel/aquery.go b/bazel/aquery.go
index eb4bdfe..c82b464 100644
--- a/bazel/aquery.go
+++ b/bazel/aquery.go
@@ -115,7 +115,23 @@
// may be an expensive operation.
depsetIdToArtifactIdsCache := map[int][]int{}
+ // Do a pass through all actions to identify which artifacts are middleman artifacts.
+ // These will be omitted from the inputs of other actions.
+ // TODO(b/180945500): Handle middleman actions; without proper handling, depending on generated
+ // headers may cause build failures.
+ middlemanArtifactIds := map[int]bool{}
for _, actionEntry := range aqueryResult.Actions {
+ if actionEntry.Mnemonic == "Middleman" {
+ for _, outputId := range actionEntry.OutputIds {
+ middlemanArtifactIds[outputId] = true
+ }
+ }
+ }
+
+ for _, actionEntry := range aqueryResult.Actions {
+ if shouldSkipAction(actionEntry) {
+ continue
+ }
outputPaths := []string{}
for _, outputId := range actionEntry.OutputIds {
outputPath, exists := artifactIdToPath[outputId]
@@ -132,6 +148,10 @@
return nil, err
}
for _, inputId := range inputArtifacts {
+ if _, isMiddlemanArtifact := middlemanArtifactIds[inputId]; isMiddlemanArtifact {
+ // Omit middleman artifacts.
+ continue
+ }
inputPath, exists := artifactIdToPath[inputId]
if !exists {
return nil, fmt.Errorf("undefined input artifactId %d", inputId)
@@ -145,12 +165,38 @@
InputPaths: inputPaths,
Env: actionEntry.EnvironmentVariables,
Mnemonic: actionEntry.Mnemonic}
+ if len(actionEntry.Arguments) < 1 {
+ return nil, fmt.Errorf("received action with no command: [%s]", buildStatement)
+ continue
+ }
buildStatements = append(buildStatements, buildStatement)
}
return buildStatements, nil
}
+func shouldSkipAction(a action) bool {
+ // TODO(b/180945121): Handle symlink actions.
+ if a.Mnemonic == "Symlink" || a.Mnemonic == "SourceSymlinkManifest" || a.Mnemonic == "SymlinkTree" {
+ return true
+ }
+ // TODO(b/180945500): Handle middleman actions; without proper handling, depending on generated
+ // headers may cause build failures.
+ if a.Mnemonic == "Middleman" {
+ return true
+ }
+ // Skip "Fail" actions, which are placeholder actions designed to always fail.
+ if a.Mnemonic == "Fail" {
+ return true
+ }
+ // TODO(b/180946980): Handle FileWrite. The aquery proto currently contains no information
+ // about the contents that are written.
+ if a.Mnemonic == "FileWrite" {
+ return true
+ }
+ return false
+}
+
func artifactIdsFromDepsetId(depsetIdToDepset map[int]depSetOfFiles,
depsetIdToArtifactIdsCache map[int][]int, depsetId int) ([]int, error) {
if result, exists := depsetIdToArtifactIdsCache[depsetId]; exists {
diff --git a/bazel/properties.go b/bazel/properties.go
index a4df4bc..a5ffa55 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -14,6 +14,8 @@
package bazel
+import "fmt"
+
type bazelModuleProperties struct {
// The label of the Bazel target replacing this Soong module.
Label string
@@ -63,3 +65,72 @@
ll.Excludes = append(other.Excludes, other.Excludes...)
}
}
+
+// StringListAttribute corresponds to the string_list Bazel attribute type with
+// support for additional metadata, like configurations.
+type StringListAttribute struct {
+ // The base value of the string list attribute.
+ Value []string
+
+ // Optional additive set of list values to the base value.
+ ArchValues stringListArchValues
+}
+
+// Arch-specific string_list typed Bazel attribute values. This should correspond
+// to the types of architectures supported for compilation in arch.go.
+type stringListArchValues struct {
+ X86 []string
+ X86_64 []string
+ Arm []string
+ Arm64 []string
+ Default []string
+ // TODO(b/181299724): this is currently missing the "common" arch, which
+ // doesn't have an equivalent platform() definition yet.
+}
+
+// HasArchSpecificValues returns true if the attribute contains
+// architecture-specific string_list values.
+func (attrs *StringListAttribute) HasArchSpecificValues() bool {
+ for _, arch := range []string{"x86", "x86_64", "arm", "arm64", "default"} {
+ if len(attrs.GetValueForArch(arch)) > 0 {
+ return true
+ }
+ }
+ return false
+}
+
+// GetValueForArch returns the string_list attribute value for an architecture.
+func (attrs *StringListAttribute) GetValueForArch(arch string) []string {
+ switch arch {
+ case "x86":
+ return attrs.ArchValues.X86
+ case "x86_64":
+ return attrs.ArchValues.X86_64
+ case "arm":
+ return attrs.ArchValues.Arm
+ case "arm64":
+ return attrs.ArchValues.Arm64
+ case "default":
+ return attrs.ArchValues.Default
+ default:
+ panic(fmt.Errorf("Unknown arch: %s", arch))
+ }
+}
+
+// SetValueForArch sets the string_list attribute value for an architecture.
+func (attrs *StringListAttribute) SetValueForArch(arch string, value []string) {
+ switch arch {
+ case "x86":
+ attrs.ArchValues.X86 = value
+ case "x86_64":
+ attrs.ArchValues.X86_64 = value
+ case "arm":
+ attrs.ArchValues.Arm = value
+ case "arm64":
+ attrs.ArchValues.Arm64 = value
+ case "default":
+ attrs.ArchValues.Default = value
+ default:
+ panic(fmt.Errorf("Unknown arch: %s", arch))
+ }
+}
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index 7d748ec..8deb5a2 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -10,6 +10,7 @@
"bp2build.go",
"build_conversion.go",
"bzl_conversion.go",
+ "configurability.go",
"conversion.go",
"metrics.go",
],
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index a4c4a0d..7fa4996 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -354,11 +354,42 @@
ret += makeIndent(indent)
ret += "]"
case reflect.Struct:
+ // Special cases where the bp2build sends additional information to the codegenerator
+ // by wrapping the attributes in a custom struct type.
if labels, ok := propertyValue.Interface().(bazel.LabelList); ok {
// TODO(b/165114590): convert glob syntax
return prettyPrint(reflect.ValueOf(labels.Includes), indent)
} else if label, ok := propertyValue.Interface().(bazel.Label); ok {
return fmt.Sprintf("%q", label.Label), nil
+ } else if stringList, ok := propertyValue.Interface().(bazel.StringListAttribute); ok {
+ // A Bazel string_list attribute that may contain a select statement.
+ ret, err := prettyPrint(reflect.ValueOf(stringList.Value), indent)
+ if err != nil {
+ return ret, err
+ }
+
+ if !stringList.HasArchSpecificValues() {
+ // Select statement not needed.
+ return ret, nil
+ }
+
+ ret += " + " + "select({\n"
+ for _, arch := range android.ArchTypeList() {
+ value := stringList.GetValueForArch(arch.Name)
+ if len(value) > 0 {
+ ret += makeIndent(indent + 1)
+ list, _ := prettyPrint(reflect.ValueOf(value), indent+1)
+ ret += fmt.Sprintf("\"%s\": %s,\n", platformArchMap[arch], list)
+ }
+ }
+
+ ret += makeIndent(indent + 1)
+ list, _ := prettyPrint(reflect.ValueOf(stringList.GetValueForArch("default")), indent+1)
+ ret += fmt.Sprintf("\"%s\": %s,\n", "//conditions:default", list)
+
+ ret += makeIndent(indent)
+ ret += "})"
+ return ret, err
}
ret = "{\n"
diff --git a/bp2build/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
index f655842..1d4e322 100644
--- a/bp2build/cc_object_conversion_test.go
+++ b/bp2build/cc_object_conversion_test.go
@@ -99,15 +99,6 @@
cc_defaults {
name: "foo_defaults",
defaults: ["foo_bar_defaults"],
- // TODO(b/178130668): handle configurable attributes that depend on the platform
- arch: {
- x86: {
- cflags: ["-fPIC"],
- },
- x86_64: {
- cflags: ["-fPIC"],
- },
- },
}
cc_defaults {
@@ -233,3 +224,137 @@
}
}
}
+
+func TestCcObjectConfigurableAttributesBp2Build(t *testing.T) {
+ testCases := []struct {
+ description string
+ moduleTypeUnderTest string
+ moduleTypeUnderTestFactory android.ModuleFactory
+ moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
+ blueprint string
+ expectedBazelTargets []string
+ filesystem map[string]string
+ }{
+ {
+ description: "cc_object setting cflags for one arch",
+ moduleTypeUnderTest: "cc_object",
+ moduleTypeUnderTestFactory: cc.ObjectFactory,
+ moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+ blueprint: `cc_object {
+ name: "foo",
+ arch: {
+ x86: {
+ cflags: ["-fPIC"],
+ },
+ },
+ bazel_module: { bp2build_available: true },
+}
+`,
+ expectedBazelTargets: []string{
+ `cc_object(
+ name = "foo",
+ copts = [
+ "-fno-addrsig",
+ ] + select({
+ "@bazel_tools//platforms:x86_32": [
+ "-fPIC",
+ ],
+ "//conditions:default": [
+ ],
+ }),
+)`,
+ },
+ },
+ {
+ description: "cc_object setting cflags for 4 architectures",
+ moduleTypeUnderTest: "cc_object",
+ moduleTypeUnderTestFactory: cc.ObjectFactory,
+ moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+ blueprint: `cc_object {
+ name: "foo",
+ arch: {
+ x86: {
+ cflags: ["-fPIC"],
+ },
+ x86_64: {
+ cflags: ["-fPIC"],
+ },
+ arm: {
+ cflags: ["-Wall"],
+ },
+ arm64: {
+ cflags: ["-Wall"],
+ },
+ },
+ bazel_module: { bp2build_available: true },
+}
+`,
+ expectedBazelTargets: []string{
+ `cc_object(
+ name = "foo",
+ copts = [
+ "-fno-addrsig",
+ ] + select({
+ "@bazel_tools//platforms:arm": [
+ "-Wall",
+ ],
+ "@bazel_tools//platforms:aarch64": [
+ "-Wall",
+ ],
+ "@bazel_tools//platforms:x86_32": [
+ "-fPIC",
+ ],
+ "@bazel_tools//platforms:x86_64": [
+ "-fPIC",
+ ],
+ "//conditions:default": [
+ ],
+ }),
+)`,
+ },
+ },
+ }
+
+ dir := "."
+ for _, testCase := range testCases {
+ filesystem := make(map[string][]byte)
+ toParse := []string{
+ "Android.bp",
+ }
+ config := android.TestConfig(buildDir, nil, testCase.blueprint, filesystem)
+ ctx := android.NewTestContext(config)
+ // Always register cc_defaults module factory
+ ctx.RegisterModuleType("cc_defaults", func() android.Module { return cc.DefaultsFactory() })
+
+ ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
+ ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
+ ctx.RegisterForBazelConversion()
+
+ _, errs := ctx.ParseFileList(dir, toParse)
+ if Errored(t, testCase.description, errs) {
+ continue
+ }
+ _, errs = ctx.ResolveDependencies(config)
+ if Errored(t, testCase.description, errs) {
+ continue
+ }
+
+ codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+ bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
+ if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
+ fmt.Println(bazelTargets)
+ t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
+ } else {
+ for i, target := range bazelTargets {
+ if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
+ t.Errorf(
+ "%s: Expected generated Bazel target to be '%s', got '%s'",
+ testCase.description,
+ w,
+ g,
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/bp2build/configurability.go b/bp2build/configurability.go
new file mode 100644
index 0000000..47cf3c6
--- /dev/null
+++ b/bp2build/configurability.go
@@ -0,0 +1,15 @@
+package bp2build
+
+import "android/soong/android"
+
+// Configurability support for bp2build.
+
+var (
+ // A map of architectures to the Bazel label of the constraint_value.
+ platformArchMap = map[android.ArchType]string{
+ android.Arm: "@bazel_tools//platforms:arm",
+ android.Arm64: "@bazel_tools//platforms:aarch64",
+ android.X86: "@bazel_tools//platforms:x86_32",
+ android.X86_64: "@bazel_tools//platforms:x86_64",
+ }
+)
diff --git a/cc/cc.go b/cc/cc.go
index 11d8718..c335dac 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -577,6 +577,17 @@
makeUninstallable(mod *Module)
}
+// bazelHandler is the interface for a helper object related to deferring to Bazel for
+// processing a module (during Bazel mixed builds). Individual module types should define
+// their own bazel handler if they support deferring to Bazel.
+type bazelHandler interface {
+ // Issue query to Bazel to retrieve information about Bazel's view of the current module.
+ // If Bazel returns this information, set module properties on the current module to reflect
+ // the returned information.
+ // Returns true if information was available from Bazel, false if bazel invocation still needs to occur.
+ generateBazelBuildActions(ctx android.ModuleContext, label string) bool
+}
+
type xref interface {
XrefCcFiles() android.Paths
}
@@ -779,9 +790,10 @@
// type-specific logic. These members may reference different objects or the same object.
// Functions of these decorators will be invoked to initialize and register type-specific
// build statements.
- compiler compiler
- linker linker
- installer installer
+ compiler compiler
+ linker linker
+ installer installer
+ bazelHandler bazelHandler
features []feature
stl *stl
@@ -1559,24 +1571,7 @@
return nameSuffix
}
-func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
- // Handle the case of a test module split by `test_per_src` mutator.
- //
- // The `test_per_src` mutator adds an extra variation named "", depending on all the other
- // `test_per_src` variations of the test module. Set `outputFile` to an empty path for this
- // module and return early, as this module does not produce an output file per se.
- if c.IsTestPerSrcAllTestsVariation() {
- c.outputFile = android.OptionalPath{}
- return
- }
-
- apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
- if !apexInfo.IsForPlatform() {
- c.hideApexVariantFromMake = true
- }
-
- c.makeLinkType = GetMakeLinkType(actx, c)
-
+func (c *Module) setSubnameProperty(actx android.ModuleContext) {
c.Properties.SubName = ""
if c.Target().NativeBridge == android.NativeBridgeEnabled {
@@ -1606,6 +1601,43 @@
c.Properties.SubName += "." + c.SdkVersion()
}
}
+}
+
+// Returns true if Bazel was successfully used for the analysis of this module.
+func (c *Module) maybeGenerateBazelActions(actx android.ModuleContext) bool {
+ bazelModuleLabel := c.GetBazelLabel()
+ bazelActionsUsed := false
+ if c.bazelHandler != nil && actx.Config().BazelContext.BazelEnabled() && len(bazelModuleLabel) > 0 {
+ bazelActionsUsed = c.bazelHandler.generateBazelBuildActions(actx, bazelModuleLabel)
+ }
+ return bazelActionsUsed
+}
+
+func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
+ // TODO(cparsons): Any logic in this method occurring prior to querying Bazel should be
+ // requested from Bazel instead.
+
+ // Handle the case of a test module split by `test_per_src` mutator.
+ //
+ // The `test_per_src` mutator adds an extra variation named "", depending on all the other
+ // `test_per_src` variations of the test module. Set `outputFile` to an empty path for this
+ // module and return early, as this module does not produce an output file per se.
+ if c.IsTestPerSrcAllTestsVariation() {
+ c.outputFile = android.OptionalPath{}
+ return
+ }
+
+ c.setSubnameProperty(actx)
+ apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+ if !apexInfo.IsForPlatform() {
+ c.hideApexVariantFromMake = true
+ }
+
+ if c.maybeGenerateBazelActions(actx) {
+ return
+ }
+
+ c.makeLinkType = GetMakeLinkType(actx, c)
ctx := &moduleContext{
ModuleContext: actx,
@@ -2420,36 +2452,6 @@
}
}
-// Returns the highest version which is <= maxSdkVersion.
-// For example, with maxSdkVersion is 10 and versionList is [9,11]
-// it returns 9 as string. The list of stubs must be in order from
-// oldest to newest.
-func (c *Module) chooseSdkVersion(ctx android.PathContext, stubsInfo []SharedStubLibrary,
- maxSdkVersion android.ApiLevel) (SharedStubLibrary, error) {
-
- for i := range stubsInfo {
- stubInfo := stubsInfo[len(stubsInfo)-i-1]
- var ver android.ApiLevel
- if stubInfo.Version == "" {
- ver = android.FutureApiLevel
- } else {
- var err error
- ver, err = android.ApiLevelFromUser(ctx, stubInfo.Version)
- if err != nil {
- return SharedStubLibrary{}, err
- }
- }
- if ver.LessThanOrEqualTo(maxSdkVersion) {
- return stubInfo, nil
- }
- }
- var versionList []string
- for _, stubInfo := range stubsInfo {
- versionList = append(versionList, stubInfo.Version)
- }
- return SharedStubLibrary{}, fmt.Errorf("not found a version(<=%s) in versionList: %v", maxSdkVersion.String(), versionList)
-}
-
// Convert dependencies to paths. Returns a PathDeps containing paths
func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps {
var depPaths PathDeps
@@ -2633,16 +2635,12 @@
useStubs = !android.DirectlyInAllApexes(apexInfo, depName)
}
- // when to use (unspecified) stubs, check min_sdk_version and choose the right one
+ // when to use (unspecified) stubs, use the latest one.
if useStubs {
- sharedLibraryStubsInfo, err :=
- c.chooseSdkVersion(ctx, sharedLibraryStubsInfo.SharedStubLibraries, c.apexSdkVersion)
- if err != nil {
- ctx.OtherModuleErrorf(dep, err.Error())
- return
- }
- sharedLibraryInfo = sharedLibraryStubsInfo.SharedLibraryInfo
- depExporterInfo = sharedLibraryStubsInfo.FlagExporterInfo
+ stubs := sharedLibraryStubsInfo.SharedStubLibraries
+ toUse := stubs[len(stubs)-1]
+ sharedLibraryInfo = toUse.SharedLibraryInfo
+ depExporterInfo = toUse.FlagExporterInfo
}
}
diff --git a/cc/config/tidy.go b/cc/config/tidy.go
index 7c20dd5..c4563e2 100644
--- a/cc/config/tidy.go
+++ b/cc/config/tidy.go
@@ -31,7 +31,6 @@
}
checks := strings.Join([]string{
"-*",
- "abseil-*",
"android-*",
"bugprone-*",
"cert-*",
diff --git a/cc/object.go b/cc/object.go
index 140a066..126bd65 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -46,6 +46,26 @@
Properties ObjectLinkerProperties
}
+type objectBazelHandler struct {
+ bazelHandler
+
+ module *Module
+}
+
+func (handler *objectBazelHandler) generateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+ bazelCtx := ctx.Config().BazelContext
+ objPaths, ok := bazelCtx.GetCcObjectFiles(label, ctx.Arch().ArchType)
+ if ok {
+ if len(objPaths) != 1 {
+ ctx.ModuleErrorf("expected exactly one object file for '%s', but got %s", label, objPaths)
+ return false
+ }
+
+ handler.module.outputFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, objPaths[0]))
+ }
+ return ok
+}
+
type ObjectLinkerProperties struct {
// list of modules that should only provide headers for this module.
Header_libs []string `android:"arch_variant,variant_prepend"`
@@ -80,6 +100,7 @@
baseLinker: NewBaseLinker(module.sanitize),
}
module.compiler = NewBaseCompiler()
+ module.bazelHandler = &objectBazelHandler{module: module}
// Clang's address-significance tables are incompatible with ld -r.
module.compiler.appendCflags([]string{"-fno-addrsig"})
@@ -93,7 +114,7 @@
type bazelObjectAttributes struct {
Srcs bazel.LabelList
Deps bazel.LabelList
- Copts []string
+ Copts bazel.StringListAttribute
Local_include_dirs []string
}
@@ -133,13 +154,14 @@
ctx.ModuleErrorf("compiler must not be nil for a cc_object module")
}
- var copts []string
+ // Set arch-specific configurable attributes
+ var copts bazel.StringListAttribute
var srcs []string
var excludeSrcs []string
var localIncludeDirs []string
for _, props := range m.compiler.compilerProps() {
if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
- copts = baseCompilerProps.Cflags
+ copts.Value = baseCompilerProps.Cflags
srcs = baseCompilerProps.Srcs
excludeSrcs = baseCompilerProps.Exclude_srcs
localIncludeDirs = baseCompilerProps.Local_include_dirs
@@ -154,6 +176,13 @@
}
}
+ for arch, p := range m.GetArchProperties(&BaseCompilerProperties{}) {
+ if cProps, ok := p.(*BaseCompilerProperties); ok {
+ copts.SetValueForArch(arch.Name, cProps.Cflags)
+ }
+ }
+ copts.SetValueForArch("default", []string{})
+
attrs := &bazelObjectAttributes{
Srcs: android.BazelLabelForModuleSrcExcludes(ctx, srcs, excludeSrcs),
Deps: deps,
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index 2cd18cb..6b9a3d5 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -246,7 +246,7 @@
module.AddProperties(&prebuilt.properties)
- srcsSupplier := func(ctx android.BaseModuleContext) []string {
+ srcsSupplier := func(ctx android.BaseModuleContext, _ android.Module) []string {
return prebuilt.prebuiltSrcs(ctx)
}
diff --git a/cc/tidy.go b/cc/tidy.go
index 251c67b..616cf8a 100644
--- a/cc/tidy.go
+++ b/cc/tidy.go
@@ -42,6 +42,21 @@
Properties TidyProperties
}
+var quotedFlagRegexp, _ = regexp.Compile(`^-?-[^=]+=('|").*('|")$`)
+
+// When passing flag -name=value, if user add quotes around 'value',
+// the quotation marks will be preserved by NinjaAndShellEscapeList
+// and the 'value' string with quotes won't work like the intended value.
+// So here we report an error if -*='*' is found.
+func checkNinjaAndShellEscapeList(ctx ModuleContext, prop string, slice []string) []string {
+ for _, s := range slice {
+ if quotedFlagRegexp.MatchString(s) {
+ ctx.PropertyErrorf(prop, "Extra quotes in: %s", s)
+ }
+ }
+ return proptools.NinjaAndShellEscapeList(slice)
+}
+
func (tidy *tidyFeature) props() []interface{} {
return []interface{}{&tidy.Properties}
}
@@ -74,8 +89,8 @@
if len(withTidyFlags) > 0 {
flags.TidyFlags = append(flags.TidyFlags, withTidyFlags)
}
- esc := proptools.NinjaAndShellEscapeList
- flags.TidyFlags = append(flags.TidyFlags, esc(tidy.Properties.Tidy_flags)...)
+ esc := checkNinjaAndShellEscapeList
+ flags.TidyFlags = append(flags.TidyFlags, esc(ctx, "tidy_flags", tidy.Properties.Tidy_flags)...)
// If TidyFlags does not contain -header-filter, add default header filter.
// Find the substring because the flag could also appear as --header-filter=...
// and with or without single or double quotes.
@@ -119,7 +134,7 @@
tidyChecks += config.TidyChecksForDir(ctx.ModuleDir())
}
if len(tidy.Properties.Tidy_checks) > 0 {
- tidyChecks = tidyChecks + "," + strings.Join(esc(
+ tidyChecks = tidyChecks + "," + strings.Join(esc(ctx, "tidy_checks",
config.ClangRewriteTidyChecks(tidy.Properties.Tidy_checks)), ",")
}
if ctx.Windows() {
@@ -165,7 +180,7 @@
flags.TidyFlags = append(flags.TidyFlags, "-warnings-as-errors=-*")
}
} else if len(tidy.Properties.Tidy_checks_as_errors) > 0 {
- tidyChecksAsErrors := "-warnings-as-errors=" + strings.Join(esc(tidy.Properties.Tidy_checks_as_errors), ",")
+ tidyChecksAsErrors := "-warnings-as-errors=" + strings.Join(esc(ctx, "tidy_checks_as_errors", tidy.Properties.Tidy_checks_as_errors), ",")
flags.TidyFlags = append(flags.TidyFlags, tidyChecksAsErrors)
}
return flags
diff --git a/cmd/soong_build/Android.bp b/cmd/soong_build/Android.bp
index 6a0a87b..9f09224 100644
--- a/cmd/soong_build/Android.bp
+++ b/cmd/soong_build/Android.bp
@@ -25,7 +25,6 @@
"soong",
"soong-android",
"soong-bp2build",
- "soong-env",
"soong-ui-metrics_proto",
],
srcs: [
diff --git a/cmd/soong_env/Android.bp b/cmd/soong_env/Android.bp
deleted file mode 100644
index ad717d0..0000000
--- a/cmd/soong_env/Android.bp
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2015 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 {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-bootstrap_go_binary {
- name: "soong_env",
- deps: [
- "soong-env",
- ],
- srcs: [
- "soong_env.go",
- ],
- default: true,
-}
diff --git a/cmd/soong_env/soong_env.go b/cmd/soong_env/soong_env.go
deleted file mode 100644
index 8020b17..0000000
--- a/cmd/soong_env/soong_env.go
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2015 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.
-
-// soong_env determines if the given soong environment file (usually ".soong.environment") is stale
-// by comparing its contents to the current corresponding environment variable values.
-// It fails if the file cannot be opened or corrupted, or its contents differ from the current
-// values.
-
-package main
-
-import (
- "flag"
- "fmt"
- "os"
-
- "android/soong/env"
-)
-
-func usage() {
- fmt.Fprintf(os.Stderr, "usage: soong_env env_file\n")
- fmt.Fprintf(os.Stderr, "exits with success if the environment varibles in env_file match\n")
- fmt.Fprintf(os.Stderr, "the current environment\n")
- flag.PrintDefaults()
- os.Exit(2)
-}
-
-// This is a simple executable packaging, and the real work happens in env.StaleEnvFile.
-func main() {
- flag.Parse()
-
- if flag.NArg() != 1 {
- usage()
- }
-
- stale, err := env.StaleEnvFile(flag.Arg(0))
- if err != nil {
- fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
- os.Exit(1)
- }
-
- if stale {
- os.Exit(1)
- }
-
- os.Exit(0)
-}
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 36a5e2a..888466a 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -85,7 +85,7 @@
Dex2oatImageXmx string // max heap size for dex2oat for the boot image
Dex2oatImageXms string // initial heap size for dex2oat for the boot image
- // If true, downgrade the compiler filter of dexpreopt to "extract" when verify_uses_libraries
+ // If true, downgrade the compiler filter of dexpreopt to "verify" when verify_uses_libraries
// check fails, instead of failing the build. This will disable any AOT-compilation.
//
// The intended use case for this flag is to have a smoother migration path for the Java
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 6e0fe01..737773f 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -369,11 +369,11 @@
}
if module.EnforceUsesLibraries {
// If the verify_uses_libraries check failed (in this case status file contains a
- // non-empty error message), then use "extract" compiler filter to avoid compiling any
+ // non-empty error message), then use "verify" compiler filter to avoid compiling any
// code (it would be rejected on device because of a class loader context mismatch).
cmd.Text("--compiler-filter=$(if test -s ").
Input(module.EnforceUsesLibrariesStatusFile).
- Text(" ; then echo extract ; else echo " + compilerFilter + " ; fi)")
+ Text(" ; then echo verify ; else echo " + compilerFilter + " ; fi)")
} else {
cmd.FlagWithArg("--compiler-filter=", compilerFilter)
}
diff --git a/env/Android.bp b/env/Android.bp
deleted file mode 100644
index c6a97b1..0000000
--- a/env/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-bootstrap_go_package {
- name: "soong-env",
- pkgPath: "android/soong/env",
- srcs: [
- "env.go",
- ],
-}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index b4303a6..50c77cf 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -206,7 +206,7 @@
// Returns true if information was available from Bazel, false if bazel invocation still needs to occur.
func (c *Module) generateBazelBuildActions(ctx android.ModuleContext, label string) bool {
bazelCtx := ctx.Config().BazelContext
- filePaths, ok := bazelCtx.GetAllFiles(label)
+ filePaths, ok := bazelCtx.GetAllFiles(label, ctx.Arch().ArchType)
if ok {
var bazelOutputFiles android.Paths
for _, bazelOutputFile := range filePaths {
diff --git a/java/app.go b/java/app.go
index 39f06d0..2d918e9 100755
--- a/java/app.go
+++ b/java/app.go
@@ -1217,6 +1217,15 @@
return optionalUsesLibs
}
+// Helper function to replace string in a list.
+func replaceInList(list []string, oldstr, newstr string) {
+ for i, str := range list {
+ if str == oldstr {
+ list[i] = newstr
+ }
+ }
+}
+
// Returns a map of module names of shared library dependencies to the paths
// to their dex jars on host and on device.
func (u *usesLibrary) classLoaderContextForUsesLibDeps(ctx android.ModuleContext) dexpreopt.ClassLoaderContextMap {
@@ -1227,7 +1236,16 @@
if tag, ok := ctx.OtherModuleDependencyTag(m).(usesLibraryDependencyTag); ok {
dep := ctx.OtherModuleName(m)
if lib, ok := m.(UsesLibraryDependency); ok {
- clcMap.AddContext(ctx, tag.sdkVersion, dep,
+ libName := dep
+ if ulib, ok := m.(ProvidesUsesLib); ok && ulib.ProvidesUsesLib() != nil {
+ libName = *ulib.ProvidesUsesLib()
+ // Replace module name with library name in `uses_libs`/`optional_uses_libs`
+ // in order to pass verify_uses_libraries check (which compares these
+ // properties against library names written in the manifest).
+ replaceInList(u.usesLibraryProperties.Uses_libs, dep, libName)
+ replaceInList(u.usesLibraryProperties.Optional_uses_libs, dep, libName)
+ }
+ clcMap.AddContext(ctx, tag.sdkVersion, libName,
lib.DexJarBuildPath(), lib.DexJarInstallPath(), lib.ClassLoaderContexts())
} else if ctx.Config().AllowMissingDependencies() {
ctx.AddMissingDependencies([]string{dep})
diff --git a/java/app_import.go b/java/app_import.go
index 59eb10a..d69dd10 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -244,10 +244,6 @@
srcApk := a.prebuilt.SingleSourcePath(ctx)
- if a.usesLibrary.enforceUsesLibraries() {
- srcApk = a.usesLibrary.verifyUsesLibrariesAPK(ctx, srcApk)
- }
-
// TODO: Install or embed JNI libraries
// Uncompress JNI libraries in the apk
@@ -276,6 +272,10 @@
a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries()
a.dexpreopter.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
+ if a.usesLibrary.enforceUsesLibraries() {
+ srcApk = a.usesLibrary.verifyUsesLibrariesAPK(ctx, srcApk)
+ }
+
a.dexpreopter.dexpreopt(ctx, jnisUncompressed)
if a.dexpreopter.uncompressedDex {
dexUncompressed := android.PathForModuleOut(ctx, "dex-uncompressed", ctx.ModuleName()+".apk")
diff --git a/java/app_test.go b/java/app_test.go
index 349579e..f41047a 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -2290,17 +2290,33 @@
sdk_version: "current",
}
+ // A library that has to use "provides_uses_lib", because:
+ // - it is not an SDK library
+ // - its library name is different from its module name
+ java_library {
+ name: "non-sdk-lib",
+ provides_uses_lib: "com.non.sdk.lib",
+ installable: true,
+ srcs: ["a.java"],
+ }
+
android_app {
name: "app",
srcs: ["a.java"],
- libs: ["qux", "quuz.stubs"],
+ libs: [
+ "qux",
+ "quuz.stubs"
+ ],
static_libs: [
"static-runtime-helper",
// statically linked component libraries should not pull their SDK libraries,
// so "fred" should not be added to class loader context
"fred.stubs",
],
- uses_libs: ["foo"],
+ uses_libs: [
+ "foo",
+ "non-sdk-lib"
+ ],
sdk_version: "current",
optional_uses_libs: [
"bar",
@@ -2312,7 +2328,11 @@
name: "prebuilt",
apk: "prebuilts/apk/app.apk",
certificate: "platform",
- uses_libs: ["foo", "android.test.runner"],
+ uses_libs: [
+ "foo",
+ "non-sdk-lib",
+ "android.test.runner"
+ ],
optional_uses_libs: [
"bar",
"baz",
@@ -2331,39 +2351,51 @@
prebuilt := ctx.ModuleForTests("prebuilt", "android_common")
// Test that implicit dependencies on java_sdk_library instances are passed to the manifest.
- manifestFixerArgs := app.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
- for _, w := range []string{"qux", "quuz", "runtime-library"} {
- if !strings.Contains(manifestFixerArgs, "--uses-library "+w) {
- t.Errorf("unexpected manifest_fixer args: wanted %q in %q", w, manifestFixerArgs)
- }
+ // This should not include explicit `uses_libs`/`optional_uses_libs` entries.
+ actualManifestFixerArgs := app.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
+ expectManifestFixerArgs := `--extract-native-libs=true ` +
+ `--uses-library qux ` +
+ `--uses-library quuz ` +
+ `--uses-library foo ` + // TODO(b/132357300): "foo" should not be passed to manifest_fixer
+ `--uses-library com.non.sdk.lib ` + // TODO(b/132357300): "com.non.sdk.lib" should not be passed to manifest_fixer
+ `--uses-library bar ` + // TODO(b/132357300): "bar" should not be passed to manifest_fixer
+ `--uses-library runtime-library`
+ if actualManifestFixerArgs != expectManifestFixerArgs {
+ t.Errorf("unexpected manifest_fixer args:\n\texpect: %q\n\tactual: %q",
+ expectManifestFixerArgs, actualManifestFixerArgs)
}
- // Test that all libraries are verified
- cmd := app.Rule("verify_uses_libraries").RuleParams.Command
- if w := "--uses-library foo"; !strings.Contains(cmd, w) {
- t.Errorf("wanted %q in %q", w, cmd)
+ // Test that all libraries are verified (library order matters).
+ verifyCmd := app.Rule("verify_uses_libraries").RuleParams.Command
+ verifyArgs := `--uses-library foo ` +
+ `--uses-library com.non.sdk.lib ` +
+ `--uses-library qux ` +
+ `--uses-library quuz ` +
+ `--uses-library runtime-library ` +
+ `--optional-uses-library bar ` +
+ `--optional-uses-library baz `
+ if !strings.Contains(verifyCmd, verifyArgs) {
+ t.Errorf("wanted %q in %q", verifyArgs, verifyCmd)
}
- if w := "--optional-uses-library bar --optional-uses-library baz"; !strings.Contains(cmd, w) {
- t.Errorf("wanted %q in %q", w, cmd)
+ // Test that all libraries are verified for an APK (library order matters).
+ verifyApkCmd := prebuilt.Rule("verify_uses_libraries").RuleParams.Command
+ verifyApkReqLibs := `uses_library_names="foo com.non.sdk.lib android.test.runner"`
+ verifyApkOptLibs := `optional_uses_library_names="bar baz"`
+ if !strings.Contains(verifyApkCmd, verifyApkReqLibs) {
+ t.Errorf("wanted %q in %q", verifyApkReqLibs, verifyApkCmd)
}
-
- cmd = prebuilt.Rule("verify_uses_libraries").RuleParams.Command
-
- if w := `uses_library_names="foo android.test.runner"`; !strings.Contains(cmd, w) {
- t.Errorf("wanted %q in %q", w, cmd)
- }
-
- if w := `optional_uses_library_names="bar baz"`; !strings.Contains(cmd, w) {
- t.Errorf("wanted %q in %q", w, cmd)
+ if !strings.Contains(verifyApkCmd, verifyApkOptLibs) {
+ t.Errorf("wanted %q in %q", verifyApkOptLibs, verifyApkCmd)
}
// Test that all present libraries are preopted, including implicit SDK dependencies, possibly stubs
- cmd = app.Rule("dexpreopt").RuleParams.Command
+ cmd := app.Rule("dexpreopt").RuleParams.Command
w := `--target-context-for-sdk any ` +
`PCL[/system/framework/qux.jar]#` +
`PCL[/system/framework/quuz.jar]#` +
`PCL[/system/framework/foo.jar]#` +
+ `PCL[/system/framework/non-sdk-lib.jar]#` +
`PCL[/system/framework/bar.jar]#` +
`PCL[/system/framework/runtime-library.jar]`
if !strings.Contains(cmd, w) {
@@ -2393,6 +2425,7 @@
cmd = prebuilt.Rule("dexpreopt").RuleParams.Command
if w := `--target-context-for-sdk any` +
` PCL[/system/framework/foo.jar]` +
+ `#PCL[/system/framework/non-sdk-lib.jar]` +
`#PCL[/system/framework/android.test.runner.jar]` +
`#PCL[/system/framework/bar.jar] `; !strings.Contains(cmd, w) {
t.Errorf("wanted %q in %q", w, cmd)
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 86b1895..e94b20c 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -210,6 +210,15 @@
// apps instead of the Framework boot image extension (see DEXPREOPT_USE_ART_IMAGE and UseArtImage).
//
+var artApexNames = []string{
+ "com.android.art",
+ "com.android.art.debug",
+ "com.android.art,testing",
+ "com.google.android.art",
+ "com.google.android.art.debug",
+ "com.google.android.art.testing",
+}
+
func init() {
RegisterDexpreoptBootJarsComponents(android.InitRegistrationContext)
}
@@ -485,7 +494,14 @@
switch image.name {
case artBootImageName:
- if apexInfo.InApexByBaseName("com.android.art") || apexInfo.InApexByBaseName("com.android.art.debug") || apexInfo.InApexByBaseName("com.android.art,testing") {
+ inArtApex := false
+ for _, n := range artApexNames {
+ if apexInfo.InApexByBaseName(n) {
+ inArtApex = true
+ break
+ }
+ }
+ if inArtApex {
// ok: found the jar in the ART apex
} else if name == "jacocoagent" && ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
// exception (skip and continue): Jacoco platform variant for a coverage build
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index da2c48f..208ced7 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -148,7 +148,18 @@
primary = configurationName == ctx.ModuleName()
// A source module that has been replaced by a prebuilt can never be the primary module.
- primary = primary && !module.IsReplacedByPrebuilt()
+ if module.IsReplacedByPrebuilt() {
+ ctx.VisitDirectDepsWithTag(android.PrebuiltDepTag, func(prebuilt android.Module) {
+ if h, ok := prebuilt.(hiddenAPIIntf); ok && h.bootDexJar() != nil {
+ primary = false
+ } else {
+ ctx.ModuleErrorf(
+ "hiddenapi has determined that the source module %q should be ignored as it has been"+
+ " replaced by the prebuilt module %q but unfortunately it does not provide a"+
+ " suitable boot dex jar", ctx.ModuleName(), ctx.OtherModuleName(prebuilt))
+ }
+ })
+ }
}
h.primary = primary
}
diff --git a/java/hiddenapi_singleton_test.go b/java/hiddenapi_singleton_test.go
index c0f0e38..fb63820 100644
--- a/java/hiddenapi_singleton_test.go
+++ b/java/hiddenapi_singleton_test.go
@@ -126,6 +126,29 @@
`, indexParams)
}
+func TestHiddenAPISingletonWithSourceAndPrebuiltPreferredButNoDex(t *testing.T) {
+ config := testConfigWithBootJars(`
+ java_library {
+ name: "foo",
+ srcs: ["a.java"],
+ compile_dex: true,
+ }
+
+ java_import {
+ name: "foo",
+ jars: ["a.jar"],
+ prefer: true,
+ }
+ `, []string{"platform:foo"}, nil)
+
+ ctx := testContextWithHiddenAPI(config)
+
+ runWithErrors(t, ctx, config,
+ "hiddenapi has determined that the source module \"foo\" should be ignored as it has been"+
+ " replaced by the prebuilt module \"prebuilt_foo\" but unfortunately it does not provide a"+
+ " suitable boot dex jar")
+}
+
func TestHiddenAPISingletonWithPrebuilt(t *testing.T) {
ctx, _ := testHiddenAPIBootJars(t, `
java_import {
diff --git a/java/java_test.go b/java/java_test.go
index 8407f24..bb51ebc 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -114,20 +114,26 @@
pathCtx := android.PathContextForTesting(config)
dexpreopt.SetTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
+ runWithErrors(t, ctx, config, pattern)
+
+ return ctx, config
+}
+
+func runWithErrors(t *testing.T, ctx *android.TestContext, config android.Config, pattern string) {
ctx.Register()
_, errs := ctx.ParseBlueprintsFiles("Android.bp")
if len(errs) > 0 {
android.FailIfNoMatchingErrors(t, pattern, errs)
- return ctx, config
+ return
}
_, errs = ctx.PrepareBuildActions(config)
if len(errs) > 0 {
android.FailIfNoMatchingErrors(t, pattern, errs)
- return ctx, config
+ return
}
t.Fatalf("missing expected error %q (0 errors are returned)", pattern)
- return ctx, config
+ return
}
func testJavaWithFS(t *testing.T, bp string, fs map[string][]byte) (*android.TestContext, android.Config) {
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 90823a0..30d120d 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -1772,6 +1772,8 @@
android.ApexModuleBase
android.SdkBase
+ hiddenAPI
+
properties sdkLibraryImportProperties
// Map from api scope to the scope specific property structure.
@@ -1786,6 +1788,9 @@
// The reference to the xml permissions module created by the source module.
// Is nil if the source module does not exist.
xmlPermissionsFileModule *sdkLibraryXml
+
+ // Path to the dex implementation jar obtained from the prebuilt_apex, if any.
+ dexJarFile android.Path
}
var _ SdkLibraryDependency = (*SdkLibraryImport)(nil)
@@ -1982,6 +1987,8 @@
func (module *SdkLibraryImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
module.generateCommonBuildActions(ctx)
+ var deapexerModule android.Module
+
// Record the paths to the prebuilt stubs library and stubs source.
ctx.VisitDirectDeps(func(to android.Module) {
tag := ctx.OtherModuleDependencyTag(to)
@@ -2007,6 +2014,11 @@
ctx.ModuleErrorf("xml permissions file module must be of type *sdkLibraryXml but was %T", to)
}
}
+
+ // Save away the `deapexer` module on which this depends, if any.
+ if tag == android.DeapexerTag {
+ deapexerModule = to
+ }
})
// Populate the scope paths with information from the properties.
@@ -2019,6 +2031,32 @@
paths.currentApiFilePath = android.OptionalPathForModuleSrc(ctx, scopeProperties.Current_api)
paths.removedApiFilePath = android.OptionalPathForModuleSrc(ctx, scopeProperties.Removed_api)
}
+
+ if ctx.Device() {
+ // If this is a variant created for a prebuilt_apex then use the dex implementation jar
+ // obtained from the associated deapexer module.
+ ai := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+ if ai.ForPrebuiltApex {
+ if deapexerModule == nil {
+ // This should never happen as a variant for a prebuilt_apex is only created if the
+ // deapxer module has been configured to export the dex implementation jar for this module.
+ ctx.ModuleErrorf("internal error: module %q does not depend on a `deapexer` module for prebuilt_apex %q",
+ module.Name(), ai.ApexVariationName)
+ }
+
+ // Get the path of the dex implementation jar from the `deapexer` module.
+ di := ctx.OtherModuleProvider(deapexerModule, android.DeapexerProvider).(android.DeapexerInfo)
+ if dexOutputPath := di.PrebuiltExportPath(module.BaseModuleName(), ".dexjar"); dexOutputPath != nil {
+ module.dexJarFile = dexOutputPath
+ module.initHiddenAPI(ctx, module.configurationName)
+ module.hiddenAPIExtractInformation(ctx, dexOutputPath, module.findScopePaths(apiScopePublic).stubsImplPath[0])
+ } else {
+ // This should never happen as a variant for a prebuilt_apex is only created if the
+ // prebuilt_apex has been configured to export the java library dex file.
+ ctx.ModuleErrorf("internal error: no dex implementation jar available from prebuilt_apex %q", deapexerModule.Name())
+ }
+ }
+ }
}
func (module *SdkLibraryImport) sdkJars(ctx android.BaseModuleContext, sdkVersion sdkSpec, headerJars bool) android.Paths {
@@ -2051,6 +2089,11 @@
// to satisfy UsesLibraryDependency interface
func (module *SdkLibraryImport) DexJarBuildPath() android.Path {
+ // The dex implementation jar extracted from the .apex file should be used in preference to the
+ // source.
+ if module.dexJarFile != nil {
+ return module.dexJarFile
+ }
if module.implLibraryModule == nil {
return nil
} else {
diff --git a/java/testing.go b/java/testing.go
index 781106f..bfa1e6b 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -240,6 +240,7 @@
}
func CheckHiddenAPIRuleInputs(t *testing.T, expected string, hiddenAPIRule android.TestingBuildParams) {
+ t.Helper()
actual := strings.TrimSpace(strings.Join(android.NormalizePathsForTesting(hiddenAPIRule.Implicits), "\n"))
expected = strings.TrimSpace(expected)
if actual != expected {
diff --git a/shared/Android.bp b/shared/Android.bp
index 5aa9d54..c79bc2b 100644
--- a/shared/Android.bp
+++ b/shared/Android.bp
@@ -6,6 +6,7 @@
name: "soong-shared",
pkgPath: "android/soong/shared",
srcs: [
+ "env.go",
"paths.go",
],
deps: [
diff --git a/env/env.go b/shared/env.go
similarity index 89%
rename from env/env.go
rename to shared/env.go
index 735a38a..7900daa 100644
--- a/env/env.go
+++ b/shared/env.go
@@ -12,15 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// env implements the environment JSON file handling for the soong_env command line tool run before
-// the builder and for the env writer in the builder.
-package env
+// Implements the environment JSON file handling for serializing the
+// environment variables that were used in soong_build so that soong_ui can
+// check whether they have changed
+package shared
import (
"encoding/json"
"fmt"
"io/ioutil"
- "os"
"sort"
)
@@ -57,7 +57,7 @@
// Reads and deserializes a Soong environment file located at the given file path to determine its
// staleness. If any environment variable values have changed, it prints them out and returns true.
// Failing to read or parse the file also causes it to return true.
-func StaleEnvFile(filepath string) (bool, error) {
+func StaleEnvFile(filepath string, getenv func(string) string) (bool, error) {
data, err := ioutil.ReadFile(filepath)
if err != nil {
return true, err
@@ -74,7 +74,7 @@
for _, entry := range contents {
key := entry.Key
old := entry.Value
- cur := os.Getenv(key)
+ cur := getenv(key)
if old != cur {
changed = append(changed, fmt.Sprintf("%s (%q -> %q)", key, old, cur))
}
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 899ab5d..125dbcc 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -19,8 +19,8 @@
"os"
"path/filepath"
"strconv"
- "strings"
+ "android/soong/shared"
soong_metrics_proto "android/soong/ui/metrics/metrics_proto"
"github.com/golang/protobuf/proto"
@@ -79,29 +79,12 @@
defer ctx.EndTrace()
envFile := filepath.Join(config.SoongOutDir(), ".soong.environment")
- envTool := filepath.Join(config.SoongOutDir(), ".bootstrap/bin/soong_env")
- if _, err := os.Stat(envFile); err == nil {
- if _, err := os.Stat(envTool); err == nil {
- cmd := Command(ctx, config, "soong_env", envTool, envFile)
- cmd.Sandbox = soongSandbox
-
- var buf strings.Builder
- cmd.Stdout = &buf
- cmd.Stderr = &buf
- if err := cmd.Run(); err != nil {
- ctx.Verboseln("soong_env failed, forcing manifest regeneration")
- os.Remove(envFile)
- }
-
- if buf.Len() > 0 {
- ctx.Verboseln(buf.String())
- }
- } else {
- ctx.Verboseln("Missing soong_env tool, forcing manifest regeneration")
- os.Remove(envFile)
- }
- } else if !os.IsNotExist(err) {
- ctx.Fatalf("Failed to stat %f: %v", envFile, err)
+ getenv := func(k string) string {
+ v, _ := config.Environment().Get(k)
+ return v
+ }
+ if stale, _ := shared.StaleEnvFile(envFile, getenv); stale {
+ os.Remove(envFile)
}
}()