diff options
50 files changed, 1772 insertions, 573 deletions
diff --git a/android/Android.bp b/android/Android.bp index a8fa53a41..f17a8a047 100644 --- a/android/Android.bp +++ b/android/Android.bp @@ -12,7 +12,6 @@ bootstrap_go_package { "soong", "soong-android-soongconfig", "soong-bazel", - "soong-env", "soong-shared", "soong-ui-metrics_proto", ], @@ -34,6 +33,7 @@ bootstrap_go_package { "deptag.go", "expand.go", "filegroup.go", + "fixture.go", "hooks.go", "image.go", "license.go", @@ -87,6 +87,7 @@ bootstrap_go_package { "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 46b705468..68cb70543 100644 --- a/android/android_test.go +++ b/android/android_test.go @@ -44,3 +44,5 @@ func TestMain(m *testing.M) { os.Exit(run()) } + +var emptyTestFixtureFactory = NewFixtureFactory(&buildDir) diff --git a/android/arch.go b/android/arch.go index f719ddcd9..20b4ab07d 100644 --- a/android/arch.go +++ b/android/arch.go @@ -1615,3 +1615,97 @@ func decodeMultilibTargets(multilib string, targets []Target, prefer32 bool) ([] 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/config.go b/android/config.go index f0bba81ce..ef5eadfb9 100644 --- a/android/config.go +++ b/android/config.go @@ -305,10 +305,7 @@ func TestArchConfigFuchsia(buildDir string, env map[string]string, bp string, fs 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 @@ func TestArchConfig(buildDir string, env map[string]string, bp string, fs map[st 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 c2a09aab8..a8c7777ad 100644 --- a/android/env.go +++ b/android/env.go @@ -21,7 +21,7 @@ import ( "strings" "syscall" - "android/soong/env" + "android/soong/shared" ) // This file supports dependencies on environment variables. During build manifest generation, @@ -113,7 +113,7 @@ func (c *envSingleton) GenerateBuildActions(ctx SingletonContext) { 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 593e4707b..2eb474187 100644 --- a/android/filegroup.go +++ b/android/filegroup.go @@ -24,6 +24,10 @@ func init() { 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 000000000..0efe329cb --- /dev/null +++ b/android/fixture.go @@ -0,0 +1,604 @@ +// 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 + + // Run the test, expecting no errors, 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), + } +} + +// 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} +} + +// Fixture defines the test environment. +type Fixture interface { + // Run the test, expecting no errors, 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 +} + +func (f *fixtureFactory) Extend(preparers ...FixturePreparer) FixtureFactory { + all := append(f.preparers, dedupAndFlattenPreparers(f.preparers, preparers)...) + return &fixtureFactory{ + buildDirSupplier: f.buildDirSupplier, + preparers: all, + } +} + +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), + } + + for _, preparer := range f.preparers { + preparer.function(fixture) + } + + for _, preparer := range dedupAndFlattenPreparers(f.preparers, preparers) { + preparer.function(fixture) + } + + return fixture +} + +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 { + factory *fixtureFactory + t *testing.T + config Config + ctx *TestContext + mockFS MockFS +} + +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") + FailIfErrored(f.t, errs) + _, errs = ctx.PrepareBuildActions(f.config) + FailIfErrored(f.t, errs) + + result := &TestResult{ + TestHelper: TestHelper{T: f.t}, + testContext: testContext{ctx}, + fixture: f, + Config: f.config, + } + 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 000000000..7bc033be7 --- /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 53422465a..f0e17bab8 100644 --- a/android/module.go +++ b/android/module.go @@ -443,6 +443,7 @@ type Module interface { Disable() Enabled() bool Target() Target + MultiTargets() []Target Owner() string InstallInData() bool InstallInTestcases() bool @@ -1123,8 +1124,15 @@ type ModuleBase struct { 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/prebuilt.go b/android/prebuilt.go index 39d30c5ec..04864a1d0 100644 --- a/android/prebuilt.go +++ b/android/prebuilt.go @@ -100,7 +100,7 @@ func (p *Prebuilt) Prefer() bool { // 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 @@ func (p *Prebuilt) UsePrebuilt() bool { // 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 @@ func InitPrebuiltModule(module PrebuiltInterface, srcs *[]string) { 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 @@ func InitSingleSourcePrebuiltModule(module PrebuiltInterface, srcProps interface 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 @@ func PrebuiltSelectModuleMutator(ctx TopDownMutatorContext) { 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 @@ func PrebuiltPostDepsMutator(ctx BottomUpMutatorContext) { // 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 9ac38750c..164b1bc59 100644 --- a/android/prebuilt_test.go +++ b/android/prebuilt_test.go @@ -262,7 +262,7 @@ var prebuiltsTests = []struct { } func TestPrebuilts(t *testing.T) { - fs := map[string][]byte{ + fs := MockFS{ "prebuilt_file": nil, "source_file": nil, } @@ -277,32 +277,33 @@ func TestPrebuilts(t *testing.T) { 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() - _, errs := ctx.ParseBlueprintsFiles("Android.bp") - FailIfErrored(t, errs) - _, errs = ctx.PrepareBuildActions(config) - FailIfErrored(t, errs) + 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) - 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,18 @@ func TestPrebuilts(t *testing.T) { } func registerTestPrebuiltBuildComponents(ctx RegistrationContext) { - ctx.RegisterModuleType("prebuilt", newPrebuiltModule) - ctx.RegisterModuleType("source", newSourceModule) - ctx.RegisterModuleType("override_source", newOverrideSourceModule) + registerTestPrebuiltModules(ctx) RegisterPrebuiltMutators(ctx) ctx.PostDepsMutators(RegisterOverridePostDepsMutators) } +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 1b1feb774..583279656 100644 --- a/android/testing.go +++ b/android/testing.go @@ -48,6 +48,43 @@ func NewTestContext(config Config) *TestContext { 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/androidmk/androidmk/androidmk.go b/androidmk/androidmk/androidmk.go index 2d1bbb4a8..b8316a361 100644 --- a/androidmk/androidmk/androidmk.go +++ b/androidmk/androidmk/androidmk.go @@ -48,6 +48,22 @@ var invalidVariableStringToReplacement = map[string]string{ "-": "_dash_", } +// Fix steps that should only run in the androidmk tool, i.e. should only be applied to +// newly-converted Android.bp files. +var fixSteps = bpfix.FixStepsExtension{ + Name: "androidmk", + Steps: []bpfix.FixStep{ + { + Name: "RewriteRuntimeResourceOverlay", + Fix: bpfix.RewriteRuntimeResourceOverlay, + }, + }, +} + +func init() { + bpfix.RegisterFixStepExtension(&fixSteps) +} + func (f *bpFile) insertComment(s string) { f.comments = append(f.comments, &bpparser.CommentGroup{ Comments: []*bpparser.Comment{ diff --git a/apex/apex.go b/apex/apex.go index 662bbbd0b..a4bdc639b 100644 --- a/apex/apex.go +++ b/apex/apex.go @@ -54,8 +54,6 @@ func init() { 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 3f5604741..a2992df2a 100644 --- a/apex/apex_test.go +++ b/apex/apex_test.go @@ -19,6 +19,7 @@ import ( "io/ioutil" "os" "path" + "path/filepath" "reflect" "regexp" "sort" @@ -191,6 +192,7 @@ func testApexContext(_ *testing.T, bp string, handlers ...testCustomizer) (*andr "AppSet.apks": nil, "foo.rs": nil, "libfoo.jar": nil, + "libbar.jar": nil, } cc.GatherRequiredFilesForTest(fs) @@ -970,8 +972,8 @@ func TestApexWithStubsWithMinSdkVersion(t *testing.T) { 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") @@ -1055,11 +1057,11 @@ func TestApex_PlatformUsesLatestStubFromApex(t *testing.T) { 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"] @@ -1357,18 +1359,18 @@ func TestApexDependsOnLLNDKTransitively(t *testing.T) { 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 { @@ -1530,8 +1532,8 @@ func TestApexWithSystemLibsStubs(t *testing.T) { } 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, ` @@ -1605,9 +1607,9 @@ func TestApexMinSdkVersion_NativeModulesShouldBeBuiltAgainstStubs(t *testing.T) } // 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") @@ -1825,41 +1827,6 @@ func TestQTargetApexUsesStaticUnwinder(t *testing.T) { 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 { @@ -2171,7 +2138,7 @@ func TestApexMinSdkVersion_OkayEvenWhenDepIsNewer_IfItSatisfiesApexMinSdkVersion 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", @@ -2205,7 +2172,7 @@ func TestApexMinSdkVersion_OkayEvenWhenDepIsNewer_IfItSatisfiesApexMinSdkVersion 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") } @@ -2274,7 +2241,7 @@ func TestApexMinSdkVersion_WorksWithActiveCodenames(t *testing.T) { // 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) { @@ -4366,14 +4333,15 @@ func TestPrebuiltExportDexImplementationJars(t *testing.T) { // 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) } } @@ -4390,19 +4358,42 @@ func TestPrebuiltExportDexImplementationJars(t *testing.T) { 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) { @@ -4418,7 +4409,7 @@ func TestPrebuiltExportDexImplementationJars(t *testing.T) { src: "myapex-arm.apex", }, }, - exported_java_libs: ["libfoo"], + exported_java_libs: ["libfoo", "libbar"], } java_import { @@ -4429,13 +4420,29 @@ func TestPrebuiltExportDexImplementationJars(t *testing.T) { 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) { @@ -4450,7 +4457,7 @@ func TestPrebuiltExportDexImplementationJars(t *testing.T) { src: "myapex-arm.apex", }, }, - exported_java_libs: ["libfoo"], + exported_java_libs: ["libfoo", "libbar"], } java_import { @@ -4462,26 +4469,45 @@ func TestPrebuiltExportDexImplementationJars(t *testing.T) { 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) @@ -4496,6 +4522,7 @@ func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) { } 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) @@ -4513,7 +4540,7 @@ func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) { src: "myapex-arm.apex", }, }, - exported_java_libs: ["libfoo"], + exported_java_libs: ["libfoo", "libbar"], } java_import { @@ -4521,13 +4548,23 @@ func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) { 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 `) }) @@ -4544,7 +4581,7 @@ func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) { src: "myapex-arm.apex", }, }, - exported_java_libs: ["libfoo"], + exported_java_libs: ["libfoo", "libbar"], } java_import { @@ -4558,6 +4595,21 @@ func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) { 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 @@ -4580,7 +4632,7 @@ func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) { src: "myapex-arm.apex", }, }, - exported_java_libs: ["libfoo"], + exported_java_libs: ["libfoo", "libbar"], } java_import { @@ -4595,13 +4647,31 @@ func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) { 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 `) }) @@ -4611,7 +4681,7 @@ func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) { apex { name: "myapex", key: "myapex.key", - java_libs: ["libfoo"], + java_libs: ["libfoo", "libbar"], } apex_key { @@ -4630,7 +4700,7 @@ func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) { src: "myapex-arm.apex", }, }, - exported_java_libs: ["libfoo"], + exported_java_libs: ["libfoo", "libbar"], } java_import { @@ -4644,13 +4714,30 @@ func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) { 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 `) }) @@ -4680,7 +4767,7 @@ func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) { src: "myapex-arm.apex", }, }, - exported_java_libs: ["libfoo"], + exported_java_libs: ["libfoo", "libbar"], } java_import { @@ -4695,13 +4782,31 @@ func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) { 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 `) }) @@ -6319,6 +6424,9 @@ func testDexpreoptWithApexes(t *testing.T, bp, errmsg string, transformDexpreopt } cc.GatherRequiredFilesForTest(fs) + for k, v := range filesForSdkLibrary { + fs[k] = v + } config := android.TestArchConfig(buildDir, nil, bp, fs) ctx := android.NewTestArchContext(config) @@ -6327,6 +6435,7 @@ func testDexpreoptWithApexes(t *testing.T, bp, errmsg string, transformDexpreopt 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 8f4a28569..46ce41f4d 100644 --- a/apex/deapexer.go +++ b/apex/deapexer.go @@ -65,7 +65,7 @@ func privateDeapexerFactory() android.Module { &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 @@ func (p *Deapexer) Name() string { 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 ec7f25336..3280cd8e5 100644 --- a/apex/prebuilt.go +++ b/apex/prebuilt.go @@ -109,8 +109,10 @@ type Prebuilt struct { 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 @@ type ApexFileProperties struct { } } -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 @@ func (p *ApexFileProperties) selectSource(ctx android.BottomUpMutatorContext) er 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 (p *Prebuilt) Name() string { 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 @@ func prebuiltApexExportedModuleName(ctx android.BottomUpMutatorContext, name str 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 @@ func apexSetFactory() android.Module { 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/properties.go b/bazel/properties.go index a4df4bc83..a5ffa55cb 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 @@ func (ll *LabelList) Append(other LabelList) { 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 7d748ec98..8deb5a217 100644 --- a/bp2build/Android.bp +++ b/bp2build/Android.bp @@ -10,6 +10,7 @@ bootstrap_go_package { "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 a4c4a0dd3..7fa499683 100644 --- a/bp2build/build_conversion.go +++ b/bp2build/build_conversion.go @@ -354,11 +354,42 @@ func prettyPrint(propertyValue reflect.Value, indent int) (string, error) { 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 f6558422f..1d4e32221 100644 --- a/bp2build/cc_object_conversion_test.go +++ b/bp2build/cc_object_conversion_test.go @@ -99,15 +99,6 @@ func TestCcObjectBp2Build(t *testing.T) { 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 @@ cc_object { } } } + +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 000000000..47cf3c612 --- /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/bpfix/bpfix/bpfix.go b/bpfix/bpfix/bpfix.go index 94b825208..fae610189 100644 --- a/bpfix/bpfix/bpfix.go +++ b/bpfix/bpfix/bpfix.go @@ -670,6 +670,26 @@ func rewriteAndroidAppImport(f *Fixer) error { return nil } +func RewriteRuntimeResourceOverlay(f *Fixer) error { + for _, def := range f.tree.Defs { + mod, ok := def.(*parser.Module) + if !(ok && mod.Type == "runtime_resource_overlay") { + continue + } + // runtime_resource_overlays are always product specific in Make. + if _, ok := mod.GetProperty("product_specific"); !ok { + prop := &parser.Property{ + Name: "product_specific", + Value: &parser.Bool{ + Value: true, + }, + } + mod.Properties = append(mod.Properties, prop) + } + } + return nil +} + // Removes library dependencies which are empty (and restricted from usage in Soong) func removeEmptyLibDependencies(f *Fixer) error { emptyLibraries := []string{ diff --git a/bpfix/bpfix/bpfix_test.go b/bpfix/bpfix/bpfix_test.go index ef9814fb8..61dfe1af4 100644 --- a/bpfix/bpfix/bpfix_test.go +++ b/bpfix/bpfix/bpfix_test.go @@ -1056,3 +1056,71 @@ func TestRemovePdkProperty(t *testing.T) { }) } } + +func TestRewriteRuntimeResourceOverlay(t *testing.T) { + tests := []struct { + name string + in string + out string + }{ + { + name: "product_specific runtime_resource_overlay", + in: ` + runtime_resource_overlay { + name: "foo", + resource_dirs: ["res"], + product_specific: true, + } + `, + out: ` + runtime_resource_overlay { + name: "foo", + resource_dirs: ["res"], + product_specific: true, + } + `, + }, + { + // It's probably wrong for runtime_resource_overlay not to be product specific, but let's not + // debate it here. + name: "non-product_specific runtime_resource_overlay", + in: ` + runtime_resource_overlay { + name: "foo", + resource_dirs: ["res"], + product_specific: false, + } + `, + out: ` + runtime_resource_overlay { + name: "foo", + resource_dirs: ["res"], + product_specific: false, + } + `, + }, + { + name: "runtime_resource_overlay without product_specific value", + in: ` + runtime_resource_overlay { + name: "foo", + resource_dirs: ["res"], + } + `, + out: ` + runtime_resource_overlay { + name: "foo", + resource_dirs: ["res"], + product_specific: true, + } + `, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + runPass(t, test.in, test.out, func(fixer *Fixer) error { + return RewriteRuntimeResourceOverlay(fixer) + }) + }) + } +} @@ -2420,36 +2420,6 @@ func checkDoubleLoadableLibraries(ctx android.TopDownMutatorContext) { } } -// 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 +2603,12 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { 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/object.go b/cc/object.go index 140a066af..32347b81b 100644 --- a/cc/object.go +++ b/cc/object.go @@ -93,7 +93,7 @@ func ObjectFactory() android.Module { type bazelObjectAttributes struct { Srcs bazel.LabelList Deps bazel.LabelList - Copts []string + Copts bazel.StringListAttribute Local_include_dirs []string } @@ -133,13 +133,14 @@ func ObjectBp2Build(ctx android.TopDownMutatorContext) { 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 +155,13 @@ func ObjectBp2Build(ctx android.TopDownMutatorContext) { } } + 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 2cd18cb99..6b9a3d529 100644 --- a/cc/prebuilt.go +++ b/cc/prebuilt.go @@ -246,7 +246,7 @@ func NewPrebuiltLibrary(hod android.HostOrDeviceSupported) (*Module, *libraryDec 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 251c67b07..616cf8ab4 100644 --- a/cc/tidy.go +++ b/cc/tidy.go @@ -42,6 +42,21 @@ type tidyFeature struct { 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 @@ func (tidy *tidyFeature) flags(ctx ModuleContext, flags Flags) Flags { 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 @@ func (tidy *tidyFeature) flags(ctx ModuleContext, flags Flags) Flags { 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 @@ func (tidy *tidyFeature) flags(ctx ModuleContext, flags Flags) Flags { 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 6a0a87bc6..9f0922449 100644 --- a/cmd/soong_build/Android.bp +++ b/cmd/soong_build/Android.bp @@ -25,7 +25,6 @@ bootstrap_go_binary { "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 ad717d0b1..000000000 --- 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 8020b17a0..000000000 --- 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/env/Android.bp b/env/Android.bp deleted file mode 100644 index c6a97b1e6..000000000 --- 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/java/Android.bp b/java/Android.bp index 9bfd009c6..461b16d58 100644 --- a/java/Android.bp +++ b/java/Android.bp @@ -58,7 +58,6 @@ bootstrap_go_package { "sdk_library.go", "sdk_library_external.go", "support_libraries.go", - "sysprop.go", "system_modules.go", "testing.go", "tradefed.go", diff --git a/java/androidmk.go b/java/androidmk.go index 9bdb70cb9..3d3eae530 100644 --- a/java/androidmk.go +++ b/java/androidmk.go @@ -220,7 +220,7 @@ func (prebuilt *DexImport) AndroidMkEntries() []android.AndroidMkEntries { } return []android.AndroidMkEntries{android.AndroidMkEntries{ Class: "JAVA_LIBRARIES", - OutputFile: android.OptionalPathForPath(prebuilt.maybeStrippedDexJarFile), + OutputFile: android.OptionalPathForPath(prebuilt.dexJarFile), Include: "$(BUILD_SYSTEM)/soong_java_prebuilt.mk", ExtraEntries: []android.AndroidMkExtraEntriesFunc{ func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) { diff --git a/java/app.go b/java/app.go index e81b25d73..2d918e942 100755 --- a/java/app.go +++ b/java/app.go @@ -469,7 +469,7 @@ func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) android.Path { a.Module.compile(ctx, a.aaptSrcJar) } - return a.maybeStrippedDexJarFile + return a.dexJarFile } func (a *AndroidApp) jniBuildActions(jniLibs []jniLib, ctx android.ModuleContext) android.WritablePath { diff --git a/java/app_test.go b/java/app_test.go index de06c294c..c5a618c95 100644 --- a/java/app_test.go +++ b/java/app_test.go @@ -685,6 +685,51 @@ func TestLibraryAssets(t *testing.T) { } } +func TestAppJavaResources(t *testing.T) { + bp := ` + android_app { + name: "foo", + sdk_version: "current", + java_resources: ["resources/a"], + srcs: ["a.java"], + } + + android_app { + name: "bar", + sdk_version: "current", + java_resources: ["resources/a"], + } + ` + + ctx := testApp(t, bp) + + foo := ctx.ModuleForTests("foo", "android_common") + fooResources := foo.Output("res/foo.jar") + fooDexJar := foo.Output("dex-withres/foo.jar") + fooDexJarAligned := foo.Output("dex-withres-aligned/foo.jar") + fooApk := foo.Rule("combineApk") + + if g, w := fooDexJar.Inputs.Strings(), fooResources.Output.String(); !android.InList(w, g) { + t.Errorf("expected resource jar %q in foo dex jar inputs %q", w, g) + } + + if g, w := fooDexJarAligned.Input.String(), fooDexJar.Output.String(); g != w { + t.Errorf("expected dex jar %q in foo aligned dex jar inputs %q", w, g) + } + + if g, w := fooApk.Inputs.Strings(), fooDexJarAligned.Output.String(); !android.InList(w, g) { + t.Errorf("expected aligned dex jar %q in foo apk inputs %q", w, g) + } + + bar := ctx.ModuleForTests("bar", "android_common") + barResources := bar.Output("res/bar.jar") + barApk := bar.Rule("combineApk") + + if g, w := barApk.Inputs.Strings(), barResources.Output.String(); !android.InList(w, g) { + t.Errorf("expected resources jar %q in bar apk inputs %q", w, g) + } +} + func TestAndroidResources(t *testing.T) { testCases := []struct { name string diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go index 86b189558..e94b20c55 100644 --- a/java/dexpreopt_bootjars.go +++ b/java/dexpreopt_bootjars.go @@ -210,6 +210,15 @@ import ( // 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 @@ func getBootImageJar(ctx android.SingletonContext, image *bootImageConfig, modul 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 da2c48f0c..208ced769 100644 --- a/java/hiddenapi.go +++ b/java/hiddenapi.go @@ -148,7 +148,18 @@ func (h *hiddenAPI) initHiddenAPI(ctx android.BaseModuleContext, configurationNa 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 c0f0e381c..fb63820a8 100644 --- a/java/hiddenapi_singleton_test.go +++ b/java/hiddenapi_singleton_test.go @@ -126,6 +126,29 @@ func TestHiddenAPIIndexSingleton(t *testing.T) { `, 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.go b/java/java.go index 78cd362d8..e471f0de6 100644 --- a/java/java.go +++ b/java/java.go @@ -370,6 +370,10 @@ type CompilerDeviceProperties struct { // If true, generate the signature file of APK Signing Scheme V4, along side the signed APK file. // Defaults to false. V4_signature *bool + + // Only for libraries created by a sysprop_library module, SyspropPublicStub is the name of the + // public stubs library. + SyspropPublicStub string `blueprint:"mutated"` } // Functionality common to Module and Import @@ -434,9 +438,6 @@ type Module struct { // output file containing classes.dex and resources dexJarFile android.Path - // output file that contains classes.dex if it should be in the output file - maybeStrippedDexJarFile android.Path - // output file containing uninstrumented classes that will be instrumented by jacoco jacocoReportClassesFile android.Path @@ -583,6 +584,16 @@ type JavaInfo struct { var JavaInfoProvider = blueprint.NewProvider(JavaInfo{}) +// SyspropPublicStubInfo contains info about the sysprop public stub library that corresponds to +// the sysprop implementation library. +type SyspropPublicStubInfo struct { + // JavaInfo is the JavaInfoProvider of the sysprop public stub library that corresponds to + // the sysprop implementation library. + JavaInfo JavaInfo +} + +var SyspropPublicStubInfoProvider = blueprint.NewProvider(SyspropPublicStubInfo{}) + // Methods that need to be implemented for a module that is added to apex java_libs property. type ApexDependency interface { HeaderJars() android.Paths @@ -652,29 +663,30 @@ func IsJniDepTag(depTag blueprint.DependencyTag) bool { } var ( - dataNativeBinsTag = dependencyTag{name: "dataNativeBins"} - staticLibTag = dependencyTag{name: "staticlib"} - libTag = dependencyTag{name: "javalib"} - java9LibTag = dependencyTag{name: "java9lib"} - pluginTag = dependencyTag{name: "plugin"} - errorpronePluginTag = dependencyTag{name: "errorprone-plugin"} - exportedPluginTag = dependencyTag{name: "exported-plugin"} - bootClasspathTag = dependencyTag{name: "bootclasspath"} - systemModulesTag = dependencyTag{name: "system modules"} - frameworkResTag = dependencyTag{name: "framework-res"} - kotlinStdlibTag = dependencyTag{name: "kotlin-stdlib"} - kotlinAnnotationsTag = dependencyTag{name: "kotlin-annotations"} - proguardRaiseTag = dependencyTag{name: "proguard-raise"} - certificateTag = dependencyTag{name: "certificate"} - instrumentationForTag = dependencyTag{name: "instrumentation_for"} - extraLintCheckTag = dependencyTag{name: "extra-lint-check"} - jniLibTag = dependencyTag{name: "jnilib"} - jniInstallTag = installDependencyTag{name: "jni install"} - binaryInstallTag = installDependencyTag{name: "binary install"} - usesLibTag = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion) - usesLibCompat28Tag = makeUsesLibraryDependencyTag(28) - usesLibCompat29Tag = makeUsesLibraryDependencyTag(29) - usesLibCompat30Tag = makeUsesLibraryDependencyTag(30) + dataNativeBinsTag = dependencyTag{name: "dataNativeBins"} + staticLibTag = dependencyTag{name: "staticlib"} + libTag = dependencyTag{name: "javalib"} + java9LibTag = dependencyTag{name: "java9lib"} + pluginTag = dependencyTag{name: "plugin"} + errorpronePluginTag = dependencyTag{name: "errorprone-plugin"} + exportedPluginTag = dependencyTag{name: "exported-plugin"} + bootClasspathTag = dependencyTag{name: "bootclasspath"} + systemModulesTag = dependencyTag{name: "system modules"} + frameworkResTag = dependencyTag{name: "framework-res"} + kotlinStdlibTag = dependencyTag{name: "kotlin-stdlib"} + kotlinAnnotationsTag = dependencyTag{name: "kotlin-annotations"} + proguardRaiseTag = dependencyTag{name: "proguard-raise"} + certificateTag = dependencyTag{name: "certificate"} + instrumentationForTag = dependencyTag{name: "instrumentation_for"} + extraLintCheckTag = dependencyTag{name: "extra-lint-check"} + jniLibTag = dependencyTag{name: "jnilib"} + syspropPublicStubDepTag = dependencyTag{name: "sysprop public stub"} + jniInstallTag = installDependencyTag{name: "jni install"} + binaryInstallTag = installDependencyTag{name: "binary install"} + usesLibTag = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion) + usesLibCompat28Tag = makeUsesLibraryDependencyTag(28) + usesLibCompat29Tag = makeUsesLibraryDependencyTag(29) + usesLibCompat30Tag = makeUsesLibraryDependencyTag(30) ) func IsLibDepTag(depTag blueprint.DependencyTag) bool { @@ -813,35 +825,17 @@ func (j *Module) deps(ctx android.BottomUpMutatorContext) { j.linter.deps(ctx) sdkDeps(ctx, sdkContext(j), j.dexer) - } - - syspropPublicStubs := syspropPublicStubs(ctx.Config()) - - // rewriteSyspropLibs validates if a java module can link against platform's sysprop_library, - // and redirects dependency to public stub depending on the link type. - rewriteSyspropLibs := func(libs []string, prop string) []string { - // make a copy - ret := android.CopyOf(libs) - - for idx, lib := range libs { - stub, ok := syspropPublicStubs[lib] - - if !ok { - continue - } - linkType, _ := j.getLinkType(ctx.ModuleName()) - // only platform modules can use internal props - if linkType != javaPlatform { - ret[idx] = stub - } + if j.deviceProperties.SyspropPublicStub != "" { + // This is a sysprop implementation library that has a corresponding sysprop public + // stubs library, and a dependency on it so that dependencies on the implementation can + // be forwarded to the public stubs library when necessary. + ctx.AddVariationDependencies(nil, syspropPublicStubDepTag, j.deviceProperties.SyspropPublicStub) } - - return ret } - libDeps := ctx.AddVariationDependencies(nil, libTag, rewriteSyspropLibs(j.properties.Libs, "libs")...) - ctx.AddVariationDependencies(nil, staticLibTag, rewriteSyspropLibs(j.properties.Static_libs, "static_libs")...) + libDeps := ctx.AddVariationDependencies(nil, libTag, j.properties.Libs...) + ctx.AddVariationDependencies(nil, staticLibTag, j.properties.Static_libs...) // Add dependency on libraries that provide additional hidden api annotations. ctx.AddVariationDependencies(nil, hiddenApiAnnotationsTag, j.properties.Hiddenapi_additional_annotations...) @@ -856,15 +850,11 @@ func (j *Module) deps(ctx android.BottomUpMutatorContext) { // if true, enable enforcement // PRODUCT_INTER_PARTITION_JAVA_LIBRARY_ALLOWLIST // exception list of java_library names to allow inter-partition dependency - for idx, lib := range j.properties.Libs { + for idx := range j.properties.Libs { if libDeps[idx] == nil { continue } - if _, ok := syspropPublicStubs[lib]; ok { - continue - } - if javaDep, ok := libDeps[idx].(javaSdkLibraryEnforceContext); ok { // java_sdk_library is always allowed at inter-partition dependency. // So, skip check. @@ -1134,6 +1124,8 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { } } + linkType, _ := j.getLinkType(ctx.ModuleName()) + ctx.VisitDirectDeps(func(module android.Module) { otherName := ctx.OtherModuleName(module) tag := ctx.OtherModuleDependencyTag(module) @@ -1156,6 +1148,14 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { } } else if ctx.OtherModuleHasProvider(module, JavaInfoProvider) { dep := ctx.OtherModuleProvider(module, JavaInfoProvider).(JavaInfo) + if linkType != javaPlatform && + ctx.OtherModuleHasProvider(module, SyspropPublicStubInfoProvider) { + // dep is a sysprop implementation library, but this module is not linking against + // the platform, so it gets the sysprop public stubs library instead. Replace + // dep with the JavaInfo from the SyspropPublicStubInfoProvider. + syspropDep := ctx.OtherModuleProvider(module, SyspropPublicStubInfoProvider).(SyspropPublicStubInfo) + dep = syspropDep.JavaInfo + } switch tag { case bootClasspathTag: deps.bootClasspath = append(deps.bootClasspath, dep.HeaderJars...) @@ -1214,6 +1214,12 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { deps.kotlinStdlib = append(deps.kotlinStdlib, dep.HeaderJars...) case kotlinAnnotationsTag: deps.kotlinAnnotations = dep.HeaderJars + case syspropPublicStubDepTag: + // This is a sysprop implementation library, forward the JavaInfoProvider from + // the corresponding sysprop public stub library as SyspropPublicStubInfoProvider. + ctx.SetProvider(SyspropPublicStubInfoProvider, SyspropPublicStubInfo{ + JavaInfo: dep, + }) } } else if dep, ok := module.(android.SourceFileProducer); ok { switch tag { @@ -1818,46 +1824,50 @@ func (j *Module) compile(ctx android.ModuleContext, aaptSrcJar android.Path) { } } - if ctx.Device() && j.hasCode(ctx) && - (Bool(j.properties.Installable) || Bool(j.dexProperties.Compile_dex)) { - if j.shouldInstrumentStatic(ctx) { - j.dexer.extraProguardFlagFiles = append(j.dexer.extraProguardFlagFiles, - android.PathForSource(ctx, "build/make/core/proguard.jacoco.flags")) - } - // Dex compilation - var dexOutputFile android.OutputPath - dexOutputFile = j.dexer.compileDex(ctx, flags, j.minSdkVersion(), outputFile, jarName) - if ctx.Failed() { - return - } + if ctx.Device() && (Bool(j.properties.Installable) || Bool(j.dexProperties.Compile_dex)) { + if j.hasCode(ctx) { + if j.shouldInstrumentStatic(ctx) { + j.dexer.extraProguardFlagFiles = append(j.dexer.extraProguardFlagFiles, + android.PathForSource(ctx, "build/make/core/proguard.jacoco.flags")) + } + // Dex compilation + var dexOutputFile android.OutputPath + dexOutputFile = j.dexer.compileDex(ctx, flags, j.minSdkVersion(), outputFile, jarName) + if ctx.Failed() { + return + } - // Hidden API CSV generation and dex encoding - dexOutputFile = j.hiddenAPIExtractAndEncode(ctx, dexOutputFile, j.implementationJarFile, - proptools.Bool(j.dexProperties.Uncompress_dex)) + // Hidden API CSV generation and dex encoding + dexOutputFile = j.hiddenAPIExtractAndEncode(ctx, dexOutputFile, j.implementationJarFile, + proptools.Bool(j.dexProperties.Uncompress_dex)) - // merge dex jar with resources if necessary - if j.resourceJar != nil { - jars := android.Paths{dexOutputFile, j.resourceJar} - combinedJar := android.PathForModuleOut(ctx, "dex-withres", jarName).OutputPath - TransformJarsToJar(ctx, combinedJar, "for dex resources", jars, android.OptionalPath{}, - false, nil, nil) - if *j.dexProperties.Uncompress_dex { - combinedAlignedJar := android.PathForModuleOut(ctx, "dex-withres-aligned", jarName).OutputPath - TransformZipAlign(ctx, combinedAlignedJar, combinedJar) - dexOutputFile = combinedAlignedJar - } else { - dexOutputFile = combinedJar + // merge dex jar with resources if necessary + if j.resourceJar != nil { + jars := android.Paths{dexOutputFile, j.resourceJar} + combinedJar := android.PathForModuleOut(ctx, "dex-withres", jarName).OutputPath + TransformJarsToJar(ctx, combinedJar, "for dex resources", jars, android.OptionalPath{}, + false, nil, nil) + if *j.dexProperties.Uncompress_dex { + combinedAlignedJar := android.PathForModuleOut(ctx, "dex-withres-aligned", jarName).OutputPath + TransformZipAlign(ctx, combinedAlignedJar, combinedJar) + dexOutputFile = combinedAlignedJar + } else { + dexOutputFile = combinedJar + } } - } - - j.dexJarFile = dexOutputFile - // Dexpreopting - j.dexpreopt(ctx, dexOutputFile) + j.dexJarFile = dexOutputFile - j.maybeStrippedDexJarFile = dexOutputFile + // Dexpreopting + j.dexpreopt(ctx, dexOutputFile) - outputFile = dexOutputFile + outputFile = dexOutputFile + } else { + // There is no code to compile into a dex jar, make sure the resources are propagated + // to the APK if this is an app. + outputFile = implementationAndResourcesJar + j.dexJarFile = j.resourceJar + } if ctx.Failed() { return @@ -3183,8 +3193,7 @@ type DexImport struct { properties DexImportProperties - dexJarFile android.Path - maybeStrippedDexJarFile android.Path + dexJarFile android.Path dexpreopter @@ -3271,8 +3280,6 @@ func (j *DexImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { j.dexpreopt(ctx, dexOutputFile) - j.maybeStrippedDexJarFile = dexOutputFile - if apexInfo.IsForPlatform() { ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"), j.Stem()+".jar", dexOutputFile) diff --git a/java/java_test.go b/java/java_test.go index 8407f2462..bb51ebc3d 100644 --- a/java/java_test.go +++ b/java/java_test.go @@ -114,20 +114,26 @@ func testJavaErrorWithConfig(t *testing.T, pattern string, config android.Config 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 90823a0c6..30d120d5c 100644 --- a/java/sdk_library.go +++ b/java/sdk_library.go @@ -1772,6 +1772,8 @@ type SdkLibraryImport struct { android.ApexModuleBase android.SdkBase + hiddenAPI + properties sdkLibraryImportProperties // Map from api scope to the scope specific property structure. @@ -1786,6 +1788,9 @@ type SdkLibraryImport struct { // 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) OutputFiles(tag string) (android.Paths, error) { 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 @@ func (module *SdkLibraryImport) GenerateAndroidBuildActions(ctx android.ModuleCo 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 @@ func (module *SdkLibraryImport) GenerateAndroidBuildActions(ctx android.ModuleCo 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 @@ func (module *SdkLibraryImport) SdkImplementationJars(ctx android.BaseModuleCont // 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/sdk_library_external.go b/java/sdk_library_external.go index 293493685..0acaa13b2 100644 --- a/java/sdk_library_external.go +++ b/java/sdk_library_external.go @@ -75,10 +75,15 @@ func (j *Module) allowListedInterPartitionJavaLibrary(ctx android.EarlyModuleCon return inList(j.Name(), ctx.Config().InterPartitionJavaLibraryAllowList()) } +func (j *Module) syspropWithPublicStubs() bool { + return j.deviceProperties.SyspropPublicStub != "" +} + type javaSdkLibraryEnforceContext interface { Name() string allowListedInterPartitionJavaLibrary(ctx android.EarlyModuleContext) bool partitionGroup(ctx android.EarlyModuleContext) partitionGroup + syspropWithPublicStubs() bool } var _ javaSdkLibraryEnforceContext = (*Module)(nil) @@ -88,6 +93,10 @@ func (j *Module) checkPartitionsForJavaDependency(ctx android.EarlyModuleContext return } + if dep.syspropWithPublicStubs() { + return + } + // If product interface is not enforced, skip check between system and product partition. // But still need to check between product and vendor partition because product interface flag // just represents enforcement between product and system, and vendor interface enforcement diff --git a/java/sysprop.go b/java/sysprop.go deleted file mode 100644 index e41aef68a..000000000 --- a/java/sysprop.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package java - -// This file contains a map to redirect dependencies towards sysprop_library. If a sysprop_library -// is owned by Platform, and the client module links against system API, the public stub of the -// sysprop_library should be used. The map will contain public stub names of sysprop_libraries. - -import ( - "sync" - - "android/soong/android" -) - -type syspropLibraryInterface interface { - BaseModuleName() string - Owner() string - HasPublicStub() bool - JavaPublicStubName() string -} - -var ( - syspropPublicStubsKey = android.NewOnceKey("syspropPublicStubsJava") - syspropPublicStubsLock sync.Mutex -) - -func init() { - android.PreDepsMutators(func(ctx android.RegisterMutatorsContext) { - ctx.BottomUp("sysprop_java", SyspropMutator).Parallel() - }) -} - -func syspropPublicStubs(config android.Config) map[string]string { - return config.Once(syspropPublicStubsKey, func() interface{} { - return make(map[string]string) - }).(map[string]string) -} - -// gather list of sysprop libraries owned by platform. -func SyspropMutator(mctx android.BottomUpMutatorContext) { - if m, ok := mctx.Module().(syspropLibraryInterface); ok { - if m.Owner() != "Platform" || !m.HasPublicStub() { - return - } - - syspropPublicStubs := syspropPublicStubs(mctx.Config()) - syspropPublicStubsLock.Lock() - defer syspropPublicStubsLock.Unlock() - - syspropPublicStubs[m.BaseModuleName()] = m.JavaPublicStubName() - } -} diff --git a/java/testing.go b/java/testing.go index 781106ff2..bfa1e6b2a 100644 --- a/java/testing.go +++ b/java/testing.go @@ -240,6 +240,7 @@ func CheckModuleDependencies(t *testing.T, ctx *android.TestContext, name, varia } 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 5aa9d54f7..c79bc2b39 100644 --- a/shared/Android.bp +++ b/shared/Android.bp @@ -6,6 +6,7 @@ bootstrap_go_package { name: "soong-shared", pkgPath: "android/soong/shared", srcs: [ + "env.go", "paths.go", ], deps: [ diff --git a/env/env.go b/shared/env.go index 735a38aa4..7900daa88 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 @@ func EnvFileContents(envDeps map[string]string) ([]byte, error) { // 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 @@ func StaleEnvFile(filepath string) (bool, error) { 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/sysprop/sysprop_library.go b/sysprop/sysprop_library.go index 2ccce1524..4ad68eadc 100644 --- a/sysprop/sysprop_library.go +++ b/sysprop/sysprop_library.go @@ -37,9 +37,10 @@ type dependencyTag struct { } type syspropGenProperties struct { - Srcs []string `android:"path"` - Scope string - Name *string + Srcs []string `android:"path"` + Scope string + Name *string + Check_api *string } type syspropJavaGenRule struct { @@ -68,10 +69,6 @@ var ( func init() { pctx.HostBinToolVariable("soongZipCmd", "soong_zip") pctx.HostBinToolVariable("syspropJavaCmd", "sysprop_java") - - android.PreArchMutators(func(ctx android.RegisterMutatorsContext) { - ctx.BottomUp("sysprop_deps", syspropDepsMutator).Parallel() - }) } // syspropJavaGenRule module generates srcjar containing generated java APIs. @@ -103,6 +100,12 @@ func (g *syspropJavaGenRule) GenerateAndroidBuildActions(ctx android.ModuleConte } } +func (g *syspropJavaGenRule) DepsMutator(ctx android.BottomUpMutatorContext) { + // Add a dependency from the stubs to sysprop library so that the generator rule can depend on + // the check API rule of the sysprop library. + ctx.AddFarVariationDependencies(nil, nil, proptools.String(g.properties.Check_api)) +} + func (g *syspropJavaGenRule) OutputFiles(tag string) (android.Paths, error) { switch tag { case "": @@ -157,9 +160,6 @@ type syspropLibraryProperties struct { // If set to true, build a variant of the module for the host. Defaults to false. Host_supported *bool - // Whether public stub exists or not. - Public_stub *bool `blueprint:"mutated"` - Cpp struct { // Minimum sdk version that the artifact should support when it runs as part of mainline modules(APEX). // Forwarded to cc_library.min_sdk_version @@ -202,11 +202,8 @@ func (m *syspropLibrary) CcImplementationModuleName() string { return "lib" + m.BaseModuleName() } -func (m *syspropLibrary) JavaPublicStubName() string { - if proptools.Bool(m.properties.Public_stub) { - return m.BaseModuleName() + "_public" - } - return "" +func (m *syspropLibrary) javaPublicStubName() string { + return m.BaseModuleName() + "_public" } func (m *syspropLibrary) javaGenModuleName() string { @@ -221,10 +218,6 @@ func (m *syspropLibrary) BaseModuleName() string { return m.ModuleBase.Name() } -func (m *syspropLibrary) HasPublicStub() bool { - return proptools.Bool(m.properties.Public_stub) -} - func (m *syspropLibrary) CurrentSyspropApiFile() android.OptionalPath { return m.currentApiFile } @@ -399,16 +392,17 @@ type ccLibraryProperties struct { } type javaLibraryProperties struct { - Name *string - Srcs []string - Soc_specific *bool - Device_specific *bool - Product_specific *bool - Required []string - Sdk_version *string - Installable *bool - Libs []string - Stem *string + Name *string + Srcs []string + Soc_specific *bool + Device_specific *bool + Product_specific *bool + Required []string + Sdk_version *string + Installable *bool + Libs []string + Stem *string + SyspropPublicStub string } func syspropLibraryHook(ctx android.LoadHookContext, m *syspropLibrary) { @@ -490,35 +484,42 @@ func syspropLibraryHook(ctx android.LoadHookContext, m *syspropLibrary) { // Contrast to C++, syspropJavaGenRule module will generate srcjar and the srcjar will be fed // to Java implementation library. ctx.CreateModule(syspropJavaGenFactory, &syspropGenProperties{ - Srcs: m.properties.Srcs, - Scope: scope, - Name: proptools.StringPtr(m.javaGenModuleName()), - }) - - ctx.CreateModule(java.LibraryFactory, &javaLibraryProperties{ - Name: proptools.StringPtr(m.BaseModuleName()), - Srcs: []string{":" + m.javaGenModuleName()}, - Soc_specific: proptools.BoolPtr(ctx.SocSpecific()), - Device_specific: proptools.BoolPtr(ctx.DeviceSpecific()), - Product_specific: proptools.BoolPtr(ctx.ProductSpecific()), - Installable: m.properties.Installable, - Sdk_version: proptools.StringPtr("core_current"), - Libs: []string{javaSyspropStub}, + Srcs: m.properties.Srcs, + Scope: scope, + Name: proptools.StringPtr(m.javaGenModuleName()), + Check_api: proptools.StringPtr(ctx.ModuleName()), }) // if platform sysprop_library is installed in /system or /system-ext, we regard it as an API // and allow any modules (even from different partition) to link against the sysprop_library. // To do that, we create a public stub and expose it to modules with sdk_version: system_*. + var publicStub string if isOwnerPlatform && installedInSystem { - m.properties.Public_stub = proptools.BoolPtr(true) + publicStub = m.javaPublicStubName() + } + + ctx.CreateModule(java.LibraryFactory, &javaLibraryProperties{ + Name: proptools.StringPtr(m.BaseModuleName()), + Srcs: []string{":" + m.javaGenModuleName()}, + Soc_specific: proptools.BoolPtr(ctx.SocSpecific()), + Device_specific: proptools.BoolPtr(ctx.DeviceSpecific()), + Product_specific: proptools.BoolPtr(ctx.ProductSpecific()), + Installable: m.properties.Installable, + Sdk_version: proptools.StringPtr("core_current"), + Libs: []string{javaSyspropStub}, + SyspropPublicStub: publicStub, + }) + + if publicStub != "" { ctx.CreateModule(syspropJavaGenFactory, &syspropGenProperties{ - Srcs: m.properties.Srcs, - Scope: "public", - Name: proptools.StringPtr(m.javaGenPublicStubName()), + Srcs: m.properties.Srcs, + Scope: "public", + Name: proptools.StringPtr(m.javaGenPublicStubName()), + Check_api: proptools.StringPtr(ctx.ModuleName()), }) ctx.CreateModule(java.LibraryFactory, &javaLibraryProperties{ - Name: proptools.StringPtr(m.JavaPublicStubName()), + Name: proptools.StringPtr(publicStub), Srcs: []string{":" + m.javaGenPublicStubName()}, Installable: proptools.BoolPtr(false), Sdk_version: proptools.StringPtr("core_current"), @@ -537,15 +538,3 @@ func syspropLibraryHook(ctx android.LoadHookContext, m *syspropLibrary) { *libraries = append(*libraries, "//"+ctx.ModuleDir()+":"+ctx.ModuleName()) } } - -// syspropDepsMutator adds dependencies from java implementation library to sysprop library. -// java implementation library then depends on check API rule of sysprop library. -func syspropDepsMutator(ctx android.BottomUpMutatorContext) { - if m, ok := ctx.Module().(*syspropLibrary); ok { - ctx.AddReverseDependency(m, nil, m.javaGenModuleName()) - - if proptools.Bool(m.properties.Public_stub) { - ctx.AddReverseDependency(m, nil, m.javaGenPublicStubName()) - } - } -} diff --git a/sysprop/sysprop_test.go b/sysprop/sysprop_test.go index 5cb9e64ee..9d914e317 100644 --- a/sysprop/sysprop_test.go +++ b/sysprop/sysprop_test.go @@ -26,7 +26,6 @@ import ( "strings" "testing" - "github.com/google/blueprint" "github.com/google/blueprint/proptools" ) @@ -61,16 +60,10 @@ func testContext(config android.Config) *android.TestContext { java.RegisterRequiredBuildComponentsForTest(ctx) ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators) - ctx.PreArchMutators(func(ctx android.RegisterMutatorsContext) { - ctx.BottomUp("sysprop_deps", syspropDepsMutator).Parallel() - }) android.RegisterPrebuiltMutators(ctx) cc.RegisterRequiredBuildComponentsForTest(ctx) - ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) { - ctx.BottomUp("sysprop_java", java.SyspropMutator).Parallel() - }) ctx.RegisterModuleType("sysprop_library", syspropLibraryFactory) @@ -392,15 +385,9 @@ func TestSyspropLibrary(t *testing.T) { } // Java modules linking against system API should use public stub - javaSystemApiClient := ctx.ModuleForTests("java-platform", "android_common") - publicStubFound := false - ctx.VisitDirectDeps(javaSystemApiClient.Module(), func(dep blueprint.Module) { - if dep.Name() == "sysprop-platform_public" { - publicStubFound = true - } - }) - if !publicStubFound { - t.Errorf("system api client should use public stub") + javaSystemApiClient := ctx.ModuleForTests("java-platform", "android_common").Rule("javac") + syspropPlatformPublic := ctx.ModuleForTests("sysprop-platform_public", "android_common").Description("for turbine") + if g, w := javaSystemApiClient.Implicits.Strings(), syspropPlatformPublic.Output.String(); !android.InList(w, g) { + t.Errorf("system api client should use public stub %q, got %q", w, g) } - } diff --git a/ui/build/soong.go b/ui/build/soong.go index 899ab5da5..125dbcc11 100644 --- a/ui/build/soong.go +++ b/ui/build/soong.go @@ -19,8 +19,8 @@ import ( "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 @@ func runSoong(ctx Context, config Config) { 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) } }() |