diff --git a/apex/Android.bp b/apex/Android.bp index 1890b89c3..7b5240286 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -31,6 +31,7 @@ bootstrap_go_package { testSrcs: [ "apex_test.go", "boot_image_test.go", + "platform_bootclasspath_test.go", "vndk_test.go", ], pluginFor: ["soong_build"], diff --git a/apex/platform_bootclasspath_test.go b/apex/platform_bootclasspath_test.go new file mode 100644 index 000000000..4aed9e00d --- /dev/null +++ b/apex/platform_bootclasspath_test.go @@ -0,0 +1,158 @@ +// Copyright (C) 2021 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 apex + +import ( + "testing" + + "android/soong/android" + "android/soong/dexpreopt" + "android/soong/java" + "github.com/google/blueprint" +) + +// Contains tests for platform_bootclasspath logic from java/platform_bootclasspath.go that requires +// apexes. + +var prepareForTestWithPlatformBootclasspath = android.GroupFixturePreparers( + java.PrepareForTestWithDexpreopt, + PrepareForTestWithApexBuildComponents, +) + +func TestPlatformBootclasspathDependencies(t *testing.T) { + result := android.GroupFixturePreparers( + prepareForTestWithPlatformBootclasspath, + prepareForTestWithArtApex, + prepareForTestWithMyapex, + // Configure some libraries in the art and framework boot images. + dexpreopt.FixtureSetArtBootJars("com.android.art:baz", "com.android.art:quuz"), + dexpreopt.FixtureSetBootJars("platform:foo"), + dexpreopt.FixtureSetUpdatableBootJars("myapex:bar"), + java.PrepareForTestWithJavaSdkLibraryFiles, + java.FixtureWithLastReleaseApis("foo"), + ).RunTestWithBp(t, ` + apex { + name: "com.android.art", + key: "com.android.art.key", + bootclasspath_fragments: [ + "art-bootclasspath-fragment", + ], + updatable: false, + } + + apex_key { + name: "com.android.art.key", + public_key: "com.android.art.avbpubkey", + private_key: "com.android.art.pem", + } + + bootclasspath_fragment { + name: "art-bootclasspath-fragment", + apex_available: [ + "com.android.art", + ], + contents: [ + "baz", + "quuz", + ], + } + + java_library { + name: "baz", + apex_available: [ + "com.android.art", + ], + srcs: ["b.java"], + installable: true, + } + + // Add a java_import that is not preferred and so won't have an appropriate apex variant created + // for it to make sure that the platform_bootclasspath doesn't try and add a dependency onto it. + java_import { + name: "baz", + apex_available: [ + "com.android.art", + ], + jars: ["b.jar"], + } + + java_library { + name: "quuz", + apex_available: [ + "com.android.art", + ], + srcs: ["b.java"], + installable: true, + } + + apex { + name: "myapex", + key: "myapex.key", + java_libs: [ + "bar", + ], + updatable: false, + } + + apex_key { + name: "myapex.key", + public_key: "testkey.avbpubkey", + private_key: "testkey.pem", + } + + java_sdk_library { + name: "foo", + srcs: ["b.java"], + } + + java_library { + name: "bar", + srcs: ["b.java"], + installable: true, + apex_available: ["myapex"], + permitted_packages: ["bar"], + } + + platform_bootclasspath { + name: "myplatform-bootclasspath", + } +`, + ) + + // Make sure that the myplatform-bootclasspath has the correct dependencies. + CheckModuleDependencies(t, result.TestContext, "myplatform-bootclasspath", "android_common", []string{ + `platform:dex2oatd`, + `com.android.art:baz`, + `com.android.art:quuz`, + `platform:foo`, + `myapex:bar`, + }) +} + +// CheckModuleDependencies checks the dependencies of the selected module against the expected list. +// +// The expected list must be a list of strings of the form ":", where is the +// name of the apex, or platform is it is not part of an apex and is the module name. +func CheckModuleDependencies(t *testing.T, ctx *android.TestContext, name, variant string, expected []string) { + t.Helper() + module := ctx.ModuleForTests(name, variant).Module() + modules := []android.Module{} + ctx.VisitDirectDeps(module, func(m blueprint.Module) { + modules = append(modules, m.(android.Module)) + }) + + pairs := java.ApexNamePairsFromModules(ctx, modules) + android.AssertDeepEquals(t, "module dependencies", expected, pairs) +} diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go index 550707754..5272eaf77 100644 --- a/java/platform_bootclasspath.go +++ b/java/platform_bootclasspath.go @@ -17,6 +17,7 @@ package java import ( "android/soong/android" "android/soong/dexpreopt" + "github.com/google/blueprint" ) func init() { @@ -25,10 +26,38 @@ func init() { func registerPlatformBootclasspathBuildComponents(ctx android.RegistrationContext) { ctx.RegisterModuleType("platform_bootclasspath", platformBootclasspathFactory) + + ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) { + ctx.BottomUp("platform_bootclasspath_deps", platformBootclasspathDepsMutator) + }) } +type platformBootclasspathDependencyTag struct { + blueprint.BaseDependencyTag + + name string +} + +// Avoid having to make platform bootclasspath content visible to the platform bootclasspath. +// +// This is a temporary workaround to make it easier to migrate to platform bootclasspath with proper +// dependencies. +// TODO(b/177892522): Remove this and add needed visibility. +func (t platformBootclasspathDependencyTag) ExcludeFromVisibilityEnforcement() { +} + +// The tag used for the dependency between the platform bootclasspath and any configured boot jars. +var platformBootclasspathModuleDepTag = platformBootclasspathDependencyTag{name: "module"} + +var _ android.ExcludeFromVisibilityEnforcementTag = platformBootclasspathDependencyTag{} + type platformBootclasspathModule struct { android.ModuleBase + + // The apex:module pairs obtained from the configured modules. + // + // Currently only for testing. + configuredModules []android.Module } func platformBootclasspathFactory() android.Module { @@ -47,7 +76,71 @@ func (b *platformBootclasspathModule) DepsMutator(ctx android.BottomUpMutatorCon dexpreopt.RegisterToolDeps(ctx) } +func platformBootclasspathDepsMutator(ctx android.BottomUpMutatorContext) { + m := ctx.Module() + if p, ok := m.(*platformBootclasspathModule); ok { + // Add dependencies on all the modules configured in the "art" boot image. + artImageConfig := genBootImageConfigs(ctx)[artBootImageName] + addDependenciesOntoBootImageModules(ctx, artImageConfig.modules) + + // Add dependencies on all the modules configured in the "boot" boot image. That does not + // include modules configured in the "art" boot image. + bootImageConfig := p.getImageConfig(ctx) + addDependenciesOntoBootImageModules(ctx, bootImageConfig.modules) + + // Add dependencies on all the updatable modules. + updatableModules := dexpreopt.GetGlobalConfig(ctx).UpdatableBootJars + addDependenciesOntoBootImageModules(ctx, updatableModules) + } +} + +func addDependencyOntoApexModulePair(ctx android.BottomUpMutatorContext, apex string, name string, tag blueprint.DependencyTag) { + var variations []blueprint.Variation + if apex != "platform" { + // Pick the correct apex variant. + variations = []blueprint.Variation{ + {Mutator: "apex", Variation: apex}, + } + } + + addedDep := false + if ctx.OtherModuleDependencyVariantExists(variations, name) { + ctx.AddFarVariationDependencies(variations, tag, name) + addedDep = true + } + + // Add a dependency on the prebuilt module if it exists. + prebuiltName := android.PrebuiltNameFromSource(name) + if ctx.OtherModuleDependencyVariantExists(variations, prebuiltName) { + ctx.AddVariationDependencies(variations, tag, prebuiltName) + addedDep = true + } + + // If no appropriate variant existing for this, so no dependency could be added, then it is an + // error, unless missing dependencies are allowed. The simplest way to handle that is to add a + // dependency that will not be satisfied and the default behavior will handle it. + if !addedDep { + ctx.AddFarVariationDependencies(variations, tag, name) + } +} + +func addDependenciesOntoBootImageModules(ctx android.BottomUpMutatorContext, modules android.ConfiguredJarList) { + for i := 0; i < modules.Len(); i++ { + apex := modules.Apex(i) + name := modules.Jar(i) + + addDependencyOntoApexModulePair(ctx, apex, name, platformBootclasspathModuleDepTag) + } +} + func (b *platformBootclasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { + ctx.VisitDirectDepsIf(isActiveModule, func(module android.Module) { + tag := ctx.OtherModuleDependencyTag(module) + if tag == platformBootclasspathModuleDepTag { + b.configuredModules = append(b.configuredModules, module) + } + }) + // Nothing to do if skipping the dexpreopt of boot image jars. if SkipDexpreoptBootJars(ctx) { return diff --git a/java/platform_bootclasspath_test.go b/java/platform_bootclasspath_test.go index 1c81cfdc2..ebbe3a5ee 100644 --- a/java/platform_bootclasspath_test.go +++ b/java/platform_bootclasspath_test.go @@ -29,10 +29,105 @@ var prepareForTestWithPlatformBootclasspath = android.GroupFixturePreparers( ) func TestPlatformBootclasspath(t *testing.T) { - prepareForTestWithPlatformBootclasspath. - RunTestWithBp(t, ` + preparer := android.GroupFixturePreparers( + prepareForTestWithPlatformBootclasspath, + dexpreopt.FixtureSetBootJars("platform:foo", "platform:bar"), + android.FixtureWithRootAndroidBp(` platform_bootclasspath { name: "platform-bootclasspath", } - `) + + java_library { + name: "bar", + srcs: ["a.java"], + system_modules: "none", + sdk_version: "none", + compile_dex: true, + } + `), + ) + + var addSourceBootclassPathModule = android.FixtureAddTextFile("source/Android.bp", ` + java_library { + name: "foo", + srcs: ["a.java"], + system_modules: "none", + sdk_version: "none", + compile_dex: true, + } + `) + + var addPrebuiltBootclassPathModule = android.FixtureAddTextFile("prebuilt/Android.bp", ` + java_import { + name: "foo", + jars: ["a.jar"], + compile_dex: true, + prefer: false, + } + `) + + var addPrebuiltPreferredBootclassPathModule = android.FixtureAddTextFile("prebuilt/Android.bp", ` + java_import { + name: "foo", + jars: ["a.jar"], + compile_dex: true, + prefer: true, + } + `) + + t.Run("missing", func(t *testing.T) { + preparer. + ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`"platform-bootclasspath" depends on undefined module "foo"`)). + RunTest(t) + }) + + t.Run("source", func(t *testing.T) { + result := android.GroupFixturePreparers( + preparer, + addSourceBootclassPathModule, + ).RunTest(t) + + CheckPlatformBootclasspathModules(t, result, "platform-bootclasspath", []string{ + "platform:foo", + "platform:bar", + }) + }) + + t.Run("prebuilt", func(t *testing.T) { + result := android.GroupFixturePreparers( + preparer, + addPrebuiltBootclassPathModule, + ).RunTest(t) + + CheckPlatformBootclasspathModules(t, result, "platform-bootclasspath", []string{ + "platform:prebuilt_foo", + "platform:bar", + }) + }) + + t.Run("source+prebuilt - source preferred", func(t *testing.T) { + result := android.GroupFixturePreparers( + preparer, + addSourceBootclassPathModule, + addPrebuiltBootclassPathModule, + ).RunTest(t) + + CheckPlatformBootclasspathModules(t, result, "platform-bootclasspath", []string{ + "platform:foo", + "platform:bar", + }) + }) + + t.Run("source+prebuilt - prebuilt preferred", func(t *testing.T) { + result := android.GroupFixturePreparers( + preparer, + addSourceBootclassPathModule, + addPrebuiltPreferredBootclassPathModule, + ).RunTest(t) + + CheckPlatformBootclasspathModules(t, result, "platform-bootclasspath", []string{ + "platform:prebuilt_foo", + "platform:bar", + }) + }) } diff --git a/java/testing.go b/java/testing.go index 80c107d12..455cca9dd 100644 --- a/java/testing.go +++ b/java/testing.go @@ -300,6 +300,37 @@ func CheckModuleDependencies(t *testing.T, ctx *android.TestContext, name, varia } } +// CheckPlatformBootclasspathModules returns the apex:module pair for the modules depended upon by +// the platform-bootclasspath module. +func CheckPlatformBootclasspathModules(t *testing.T, result *android.TestResult, name string, expected []string) { + t.Helper() + platformBootclasspath := result.Module(name, "android_common").(*platformBootclasspathModule) + pairs := ApexNamePairsFromModules(result.TestContext, platformBootclasspath.configuredModules) + android.AssertDeepEquals(t, fmt.Sprintf("%s modules", "platform-bootclasspath"), expected, pairs) +} + +// ApexNamePairsFromModules returns the apex:module pair for the supplied modules. +func ApexNamePairsFromModules(ctx *android.TestContext, modules []android.Module) []string { + pairs := []string{} + for _, module := range modules { + pairs = append(pairs, apexNamePairFromModule(ctx, module)) + } + return pairs +} + +func apexNamePairFromModule(ctx *android.TestContext, module android.Module) string { + name := module.Name() + var apex string + apexInfo := ctx.ModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo) + if apexInfo.IsForPlatform() { + apex = "platform" + } else { + apex = apexInfo.InApexes[0] + } + + return fmt.Sprintf("%s:%s", apex, name) +} + func CheckHiddenAPIRuleInputs(t *testing.T, expected string, hiddenAPIRule android.TestingBuildParams) { t.Helper() actual := strings.TrimSpace(strings.Join(android.NormalizePathsForTesting(hiddenAPIRule.Implicits), "\n"))