Use the correct prof file when multiple prebuilt apexes exist

Generating boot image requires a .prof file provided by the ART apex.
When building with prebuilts, this comes via the
prebuilt_bootclasspath_fragment module, which acts as a shim for
prebuilt_apex/apex_set. If we have multiple prebuilt apexes in the tree,
this shim becomes 1:many. This CL prepares dex_bootjars to select the
right .prof file when multiple prebuilts exist.

Implementation details
- Update deps mutator of dex_bootjars to create a dep on
  all_apex_contributions. The latter contains information about which
  apex is selected in a specific release configuration. dex_bootjars
  will create a dependency on the selected apex in a postdeps phase
  mutator.
- All apex module types (apex, prebuilt_apex and apex_set) will set a
  provider that contains info about the location of the .prof file on
  host
- dex_bootjars will access the provider of the selected apex to get the
  location of the .prof file

This CL does not drop the old mechanism to get the .prof file (i.e. by
creating a dep on {prebuilt_}bootclasspath_fragment). Once all mainline
modules have been flagged using apex_contributions, the old mechanism
will be dropped

Bug: 308790457
Test: Added a unit test that checks that the right .prof is selected
when multiple prebuilts exists

Change-Id: I40fdb21416c46bed32f6ff187ce5153711ec2c69
This commit is contained in:
Spandan Das 2023-12-13 00:06:32 +00:00
parent fae468ef14
commit da739a30a6
10 changed files with 297 additions and 22 deletions

View file

@ -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/<apex_name>)
ApexName string
// Path to the image profile file on host (or empty, if profile is not generated).
ProfilePathOnHost Path
}

View file

@ -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/<apex_name>)
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) {
}

View file

@ -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

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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

View file

@ -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",

View file

@ -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

View file

@ -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",

View file

@ -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
}