diff --git a/android/apex.go b/android/apex.go index c0907a758..b4bb67c78 100644 --- a/android/apex.go +++ b/android/apex.go @@ -954,3 +954,15 @@ type ApexTestInterface interface { // Return true if the apex bundle is an apex_test IsTestApex() bool } + +var ApexExportsInfoProvider = blueprint.NewProvider[ApexExportsInfo]() + +// ApexExportsInfo contains information about the artifacts provided by apexes to dexpreopt and hiddenapi +type ApexExportsInfo struct { + // Canonical name of this APEX. Used to determine the path to the activated APEX on + // device (/apex/) + ApexName string + + // Path to the image profile file on host (or empty, if profile is not generated). + ProfilePathOnHost Path +} diff --git a/android/apex_contributions.go b/android/apex_contributions.go index 34941c091..a30964080 100644 --- a/android/apex_contributions.go +++ b/android/apex_contributions.go @@ -164,6 +164,18 @@ func (p *PrebuiltSelectionInfoMap) IsSelected(baseModuleName, name string) bool } } +// Return the list of soong modules selected for this api domain +// In the case of apexes, it is the canonical name of the apex on device (/apex/) +func (p *PrebuiltSelectionInfoMap) GetSelectedModulesForApiDomain(apiDomain string) []string { + selected := []string{} + for _, entry := range *p { + if entry.apiDomain == apiDomain { + selected = append(selected, entry.selectedModuleName) + } + } + return selected +} + // This module type does not have any build actions. func (a *allApexContributions) GenerateAndroidBuildActions(ctx ModuleContext) { } diff --git a/apex/apex.go b/apex/apex.go index 56559b16b..b70484cbe 100644 --- a/apex/apex.go +++ b/apex/apex.go @@ -2371,6 +2371,24 @@ func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) { a.buildApex(ctx) a.buildApexDependencyInfo(ctx) a.buildLintReports(ctx) + + // Set a provider for dexpreopt of bootjars + a.provideApexExportsInfo(ctx) +} + +// Set a provider containing information about the jars and .prof provided by the apex +// Apexes built from source retrieve this information by visiting `bootclasspath_fragments` +// Used by dex_bootjars to generate the boot image +func (a *apexBundle) provideApexExportsInfo(ctx android.ModuleContext) { + ctx.VisitDirectDepsWithTag(bcpfTag, func(child android.Module) { + if info, ok := android.OtherModuleProvider(ctx, child, java.BootclasspathFragmentApexContentInfoProvider); ok { + exports := android.ApexExportsInfo{ + ApexName: a.ApexVariationName(), + ProfilePathOnHost: info.ProfilePathOnHost(), + } + ctx.SetProvider(android.ApexExportsInfoProvider, exports) + } + }) } // apexBootclasspathFragmentFiles returns the list of apexFile structures defining the files that diff --git a/apex/dexpreopt_bootjars_test.go b/apex/dexpreopt_bootjars_test.go index 2e828cade..9d745193d 100644 --- a/apex/dexpreopt_bootjars_test.go +++ b/apex/dexpreopt_bootjars_test.go @@ -252,3 +252,153 @@ func TestDexpreoptBootZip(t *testing.T) { testDexpreoptBoot(t, ruleFile, expectedInputs, expectedOutputs, false) } + +// Multiple ART apexes might exist in the tree. +// The profile should correspond to the apex selected using release build flags +func TestDexpreoptProfileWithMultiplePrebuiltArtApexes(t *testing.T) { + ruleFile := "out/soong/dexpreopt_arm64/dex_bootjars/android/system/framework/arm64/boot.art" + bp := ` + // Platform. + + platform_bootclasspath { + name: "platform-bootclasspath", + fragments: [ + { + apex: "com.android.art", + module: "art-bootclasspath-fragment", + }, + ], + } + + // Source ART APEX. + + java_library { + name: "core-oj", + srcs: ["core-oj.java"], + installable: true, + apex_available: [ + "com.android.art", + ], + } + + bootclasspath_fragment { + name: "art-bootclasspath-fragment", + image_name: "art", + contents: ["core-oj"], + apex_available: [ + "com.android.art", + ], + hidden_api: { + split_packages: ["*"], + }, + } + + apex_key { + name: "com.android.art.key", + public_key: "com.android.art.avbpubkey", + private_key: "com.android.art.pem", + } + + apex { + name: "com.android.art", + key: "com.android.art.key", + bootclasspath_fragments: ["art-bootclasspath-fragment"], + updatable: false, + } + + // Prebuilt ART APEX. + + prebuilt_bootclasspath_fragment { + name: "art-bootclasspath-fragment", + image_name: "art", + hidden_api: { + annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv", + metadata: "my-bootclasspath-fragment/metadata.csv", + index: "my-bootclasspath-fragment/index.csv", + stub_flags: "my-bootclasspath-fragment/stub-flags.csv", + all_flags: "my-bootclasspath-fragment/all-flags.csv", + }, + apex_available: [ + "com.android.art", + ], + } + + prebuilt_apex { + name: "com.android.art", + apex_name: "com.android.art", + src: "com.android.art-arm.apex", + exported_bootclasspath_fragments: ["art-bootclasspath-fragment"], + } + + // Another Prebuilt ART APEX + prebuilt_apex { + name: "com.android.art.v2", + apex_name: "com.android.art", // Used to determine the API domain + src: "com.android.art-arm.apex", + exported_bootclasspath_fragments: ["art-bootclasspath-fragment"], + } + + // APEX contribution modules + + apex_contributions { + name: "art.source.contributions", + api_domain: "com.android.art", + contents: ["com.android.art"], + } + + apex_contributions { + name: "art.prebuilt.contributions", + api_domain: "com.android.art", + contents: ["prebuilt_com.android.art"], + } + + apex_contributions { + name: "art.prebuilt.v2.contributions", + api_domain: "com.android.art", + contents: ["com.android.art.v2"], // prebuilt_ prefix is missing because of prebuilt_rename mutator + } + + ` + + testCases := []struct { + desc string + selectedArtApexContributions string + expectedProfile string + }{ + { + desc: "Source apex com.android.art is selected, profile should come from source java library", + selectedArtApexContributions: "art.source.contributions", + expectedProfile: "out/soong/.intermediates/art-bootclasspath-fragment/android_common_apex10000/art-bootclasspath-fragment/boot.prof", + }, + { + desc: "Prebuilt apex prebuilt_com.android.art is selected, profile should come from .prof deapexed from the prebuilt", + selectedArtApexContributions: "art.prebuilt.contributions", + expectedProfile: "out/soong/.intermediates/com.android.art.deapexer/android_common/deapexer/etc/boot-image.prof", + }, + { + desc: "Prebuilt apex prebuilt_com.android.art.v2 is selected, profile should come from .prof deapexed from the prebuilt", + selectedArtApexContributions: "art.prebuilt.v2.contributions", + expectedProfile: "out/soong/.intermediates/com.android.art.v2.deapexer/android_common/deapexer/etc/boot-image.prof", + }, + } + for _, tc := range testCases { + result := android.GroupFixturePreparers( + java.PrepareForTestWithDexpreopt, + java.PrepareForTestWithJavaSdkLibraryFiles, + java.FixtureConfigureBootJars("com.android.art:core-oj"), + PrepareForTestWithApexBuildComponents, + prepareForTestWithArtApex, + android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { + variables.BuildFlags = map[string]string{ + "RELEASE_APEX_CONTRIBUTIONS_ART": tc.selectedArtApexContributions, + } + }), + ).RunTestWithBp(t, bp) + + dexBootJars := result.ModuleForTests("dex_bootjars", "android_common") + rule := dexBootJars.Output(ruleFile) + + inputs := rule.Implicits.Strings() + android.AssertStringListContains(t, tc.desc, inputs, tc.expectedProfile) + } +} diff --git a/apex/prebuilt.go b/apex/prebuilt.go index b13ecc2fd..37a9ff5a1 100644 --- a/apex/prebuilt.go +++ b/apex/prebuilt.go @@ -769,6 +769,25 @@ func (p *Prebuilt) ApexInfoMutator(mctx android.TopDownMutatorContext) { p.apexInfoMutator(mctx) } +// Set a provider containing information about the jars and .prof provided by the apex +// Apexes built from prebuilts retrieve this information by visiting its internal deapexer module +// Used by dex_bootjars to generate the boot image +func (p *prebuiltCommon) provideApexExportsInfo(ctx android.ModuleContext) { + if !p.hasExportedDeps() { + // nothing to do + return + } + if di, err := android.FindDeapexerProviderForModule(ctx); err == nil { + exports := android.ApexExportsInfo{ + ApexName: p.ApexVariationName(), + ProfilePathOnHost: di.PrebuiltExportPath(java.ProfileInstallPathInApex), + } + ctx.SetProvider(android.ApexExportsInfoProvider, exports) + } else { + ctx.ModuleErrorf(err.Error()) + } +} + func (p *Prebuilt) GenerateAndroidBuildActions(ctx android.ModuleContext) { p.apexKeysPath = writeApexKeys(ctx, p) // TODO(jungjw): Check the key validity. @@ -793,6 +812,9 @@ func (p *Prebuilt) GenerateAndroidBuildActions(ctx android.ModuleContext) { // dexpreopt any system server jars if present p.dexpreoptSystemServerJars(ctx) + // provide info used for generating the boot image + p.provideApexExportsInfo(ctx) + // Save the files that need to be made available to Make. p.initApexFilesForAndroidMk(ctx) @@ -1012,6 +1034,9 @@ func (a *ApexSet) GenerateAndroidBuildActions(ctx android.ModuleContext) { // dexpreopt any system server jars if present a.dexpreoptSystemServerJars(ctx) + // provide info used for generating the boot image + a.provideApexExportsInfo(ctx) + // Save the files that need to be made available to Make. a.initApexFilesForAndroidMk(ctx) diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go index 83030b51e..010dbec8f 100644 --- a/java/bootclasspath_fragment.go +++ b/java/bootclasspath_fragment.go @@ -534,7 +534,7 @@ func (b *BootclasspathFragmentModule) provideApexContentInfo(ctx android.ModuleC if profile != nil { info.profilePathOnHost = profile - info.profileInstallPathInApex = profileInstallPathInApex + info.profileInstallPathInApex = ProfileInstallPathInApex } // Make the apex content info available for other modules. @@ -1074,7 +1074,7 @@ func (module *PrebuiltBootclasspathFragmentModule) produceBootImageProfile(ctx a return nil // An error has been reported by FindDeapexerProviderForModule. } - return di.PrebuiltExportPath(profileInstallPathInApex) + return di.PrebuiltExportPath(ProfileInstallPathInApex) } func (b *PrebuiltBootclasspathFragmentModule) getProfilePath() android.Path { @@ -1094,7 +1094,7 @@ var _ commonBootclasspathFragment = (*PrebuiltBootclasspathFragmentModule)(nil) func (module *PrebuiltBootclasspathFragmentModule) RequiredFilesFromPrebuiltApex(ctx android.BaseModuleContext) []string { for _, apex := range module.ApexProperties.Apex_available { if isProfileProviderApex(ctx, apex) { - return []string{profileInstallPathInApex} + return []string{ProfileInstallPathInApex} } } return nil diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go index 5a19945b4..e158ed362 100644 --- a/java/dexpreopt_bootjars.go +++ b/java/dexpreopt_bootjars.go @@ -21,6 +21,7 @@ import ( "android/soong/android" "android/soong/dexpreopt" + "github.com/google/blueprint" "github.com/google/blueprint/proptools" ) @@ -224,8 +225,9 @@ var artApexNames = []string{ } var ( - dexpreoptBootJarDepTag = bootclasspathDependencyTag{name: "dexpreopt-boot-jar"} - dexBootJarsFragmentsKey = android.NewOnceKey("dexBootJarsFragments") + dexpreoptBootJarDepTag = bootclasspathDependencyTag{name: "dexpreopt-boot-jar"} + dexBootJarsFragmentsKey = android.NewOnceKey("dexBootJarsFragments") + apexContributionsMetadataDepTag = dependencyTag{name: "all_apex_contributions"} ) func init() { @@ -502,6 +504,11 @@ type dexpreoptBootJars struct { dexpreoptConfigForMake android.WritablePath } +func (dbj *dexpreoptBootJars) DepsMutator(ctx android.BottomUpMutatorContext) { + // Create a dependency on all_apex_contributions to determine the selected mainline module + ctx.AddDependency(ctx.Module(), apexContributionsMetadataDepTag, "all_apex_contributions") +} + func DexpreoptBootJarsMutator(ctx android.BottomUpMutatorContext) { if _, ok := ctx.Module().(*dexpreoptBootJars); !ok { return @@ -520,6 +527,14 @@ func DexpreoptBootJarsMutator(ctx android.BottomUpMutatorContext) { } // For accessing the boot jars. addDependenciesOntoBootImageModules(ctx, config.modules, dexpreoptBootJarDepTag) + // Create a dependency on the apex selected using RELEASE_APEX_CONTRIBUTIONS_* + // TODO: b/308174306 - Remove the direct depedendency edge to the java_library (source/prebuilt) once all mainline modules + // have been flagged using RELEASE_APEX_CONTRIBUTIONS_* + apexes := []string{} + for i := 0; i < config.modules.Len(); i++ { + apexes = append(apexes, config.modules.Apex(i)) + } + addDependenciesOntoSelectedBootImageApexes(ctx, android.FirstUniqueStrings(apexes)...) } if ctx.OtherModuleExists("platform-bootclasspath") { @@ -532,6 +547,28 @@ func DexpreoptBootJarsMutator(ctx android.BottomUpMutatorContext) { } } +// Create a dependency from dex_bootjars to the specific apexes selected using all_apex_contributions +// This dependency will be used to get the path to the deapexed dex boot jars and profile (via a provider) +func addDependenciesOntoSelectedBootImageApexes(ctx android.BottomUpMutatorContext, apexes ...string) { + psi := android.PrebuiltSelectionInfoMap{} + ctx.VisitDirectDepsWithTag(apexContributionsMetadataDepTag, func(am android.Module) { + if ctx.OtherModuleHasProvider(am, android.PrebuiltSelectionInfoProvider) { + psi = ctx.OtherModuleProvider(am, android.PrebuiltSelectionInfoProvider).(android.PrebuiltSelectionInfoMap) + } + }) + for _, apex := range apexes { + for _, selected := range psi.GetSelectedModulesForApiDomain(apex) { + // We need to add a dep on only the apex listed in `contents` of the selected apex_contributions module + // This is not available in a structured format in `apex_contributions`, so this hack adds a dep on all `contents` + // (some modules like art.module.public.api do not have an apex variation since it is a pure stub module that does not get installed) + apexVariationOfSelected := append(ctx.Target().Variations(), blueprint.Variation{Mutator: "apex", Variation: apex}) + if ctx.OtherModuleDependencyVariantExists(apexVariationOfSelected, selected) { + ctx.AddFarVariationDependencies(apexVariationOfSelected, dexpreoptBootJarDepTag, selected) + } + } + } +} + func gatherBootclasspathFragments(ctx android.ModuleContext) map[string]android.Module { return ctx.Config().Once(dexBootJarsFragmentsKey, func() interface{} { fragments := make(map[string]android.Module) @@ -823,6 +860,27 @@ type bootImageVariantOutputs struct { config *bootImageVariant } +// Returns the profile file for an apex +// This information can come from two mechanisms +// 1. New: Direct deps to _selected_ apexes. The apexes return a BootclasspathFragmentApexContentInfo +// 2. Legacy: An edge to bootclasspath_fragment module. For prebuilt apexes, this serves as a hook and is populated by deapexers of prebuilt apxes +// TODO: b/308174306 - Once all mainline modules have been flagged, drop (2) +func getProfilePathForApex(ctx android.ModuleContext, apexName string, apexNameToBcpInfoMap map[string]android.ApexExportsInfo) android.Path { + if info, exists := apexNameToBcpInfoMap[apexName]; exists { + return info.ProfilePathOnHost + } + // TODO: b/308174306 - Remove the legacy mechanism + fragment := getBootclasspathFragmentByApex(ctx, apexName) + if fragment == nil { + ctx.ModuleErrorf("Boot image config imports profile from '%[2]s', but a "+ + "bootclasspath_fragment for APEX '%[2]s' doesn't exist or is not added as a "+ + "dependency of dex_bootjars", + apexName) + return nil + } + return fragment.(commonBootclasspathFragment).getProfilePath() +} + // Generate boot image build rules for a specific target. func buildBootImageVariant(ctx android.ModuleContext, image *bootImageVariant, profile android.Path) bootImageVariantOutputs { @@ -865,6 +923,13 @@ func buildBootImageVariant(ctx android.ModuleContext, image *bootImageVariant, p invocationPath := outputPath.ReplaceExtension(ctx, "invocation") + apexNameToBcpInfoMap := map[string]android.ApexExportsInfo{} + ctx.VisitDirectDepsWithTag(dexpreoptBootJarDepTag, func(am android.Module) { + if info, exists := android.OtherModuleProvider(ctx, am, android.ApexExportsInfoProvider); exists { + apexNameToBcpInfoMap[info.ApexName] = info + } + }) + cmd.Tool(globalSoong.Dex2oat). Flag("--avoid-storing-invocation"). FlagWithOutput("--write-invocation-to=", invocationPath).ImplicitOutput(invocationPath). @@ -877,16 +942,7 @@ func buildBootImageVariant(ctx android.ModuleContext, image *bootImageVariant, p } for _, apex := range image.profileImports { - fragment := getBootclasspathFragmentByApex(ctx, apex) - if fragment == nil { - ctx.ModuleErrorf("Boot image config '%[1]s' imports profile from '%[2]s', but a "+ - "bootclasspath_fragment for APEX '%[2]s' doesn't exist or is not added as a "+ - "dependency of dex_bootjars", - image.name, - apex) - return bootImageVariantOutputs{} - } - importedProfile := fragment.(commonBootclasspathFragment).getProfilePath() + importedProfile := getProfilePathForApex(ctx, apex, apexNameToBcpInfoMap) if importedProfile == nil { ctx.ModuleErrorf("Boot image config '%[1]s' imports profile from '%[2]s', but '%[2]s' "+ "doesn't provide a profile", diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go index 2bd696c3d..254b2c1b0 100644 --- a/java/dexpreopt_config.go +++ b/java/dexpreopt_config.go @@ -45,7 +45,7 @@ var ( frameworkBootImageName = "boot" mainlineBootImageName = "mainline" bootImageStem = "boot" - profileInstallPathInApex = "etc/boot-image.prof" + ProfileInstallPathInApex = "etc/boot-image.prof" ) // getImageNames returns an ordered list of image names. The order doesn't matter but needs to be diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go index 0965fc2c7..63419d6b1 100644 --- a/java/sdk_library_test.go +++ b/java/sdk_library_test.go @@ -950,6 +950,7 @@ func TestJavaSdkLibraryImport_WithSource(t *testing.T) { }) CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{ + `all_apex_contributions`, `prebuilt_sdklib.stubs`, `sdklib.impl`, // This should be prebuilt_sdklib.stubs but is set to sdklib.stubs because the @@ -1022,6 +1023,7 @@ func testJavaSdkLibraryImport_Preferred(t *testing.T, prefer string, preparer an }) CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{ + `all_apex_contributions`, `dex2oatd`, `prebuilt_sdklib.stubs`, `prebuilt_sdklib.stubs.source`, @@ -1085,9 +1087,6 @@ func TestSdkLibraryImport_MetadataModuleSupersedesPreferred(t *testing.T) { "prebuilt_sdklib.source_preferred_using_legacy_flags", ], } - all_apex_contributions { - name: "all_apex_contributions", - } java_sdk_library { name: "sdklib.prebuilt_preferred_using_legacy_flags", srcs: ["a.java"], @@ -1169,9 +1168,6 @@ func TestSdkLibraryImport_MetadataModuleSupersedesPreferred(t *testing.T) { prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis("sdklib.source_preferred_using_legacy_flags", "sdklib.prebuilt_preferred_using_legacy_flags"), - android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) { - android.RegisterApexContributionsBuildComponents(ctx) - }), android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { variables.BuildFlags = map[string]string{ "RELEASE_APEX_CONTRIBUTIONS_ADSERVICES": "my_mainline_module_contributions", diff --git a/java/testing.go b/java/testing.go index d55cffc40..5959c49e4 100644 --- a/java/testing.go +++ b/java/testing.go @@ -383,6 +383,7 @@ func registerRequiredBuildComponentsForTest(ctx android.RegistrationContext) { RegisterSystemModulesBuildComponents(ctx) registerSystemserverClasspathBuildComponents(ctx) registerLintBuildComponents(ctx) + android.RegisterApexContributionsBuildComponents(ctx) } // gatherRequiredDepsForTest gathers the module definitions used by @@ -570,6 +571,11 @@ func gatherRequiredDepsForTest() string { } ` + bp += ` + all_apex_contributions { + name: "all_apex_contributions", + } +` return bp }