Align boot jar exclusion with apex_contributions

During trunk stable development, it is possible that a new boot jar is
added to the source apex, but not available in prebuilt apex yet.
Thefore we need to conditionally check the `contents` of the respective
bootclasspath_fragment modules. https://r.android.com/2822393 did this
using isActiveModule. This function relies on soong config namespaces to
"hide" the source bootclasspath_fragment module when
<module>.source_build is false.

Soong config namespaces for mainline source vs prebuilt selection is no
longer in use, so this CL replaces `isActiveModule`. The CL cleaves the
implementation
1. For source builds, the check will run in the context of the
   bootclasspath_fragment module. `disableSourceApexVariant` will be
used to skip the check if prebuilts are selected.
2. For prebuilt builds, the prebuilt bootclasspath_fragment module sets
   a provider, and the top-level apex will do the check.

(2) is necessary to handle the edge case of multiple versioned mainline
prebuilts and possible skew in apex boot jars across them. In case of
prebuilt builds, the unselected mainline prebuilts will have
HideFromMake called on them, so the check will only run on the apex that
is actually used during the build.

Bug: 328578801
Test: go test ./apex

Change-Id: I6eec603397eea926f3a481b79ca938245064d809
Merged-In: I6eec603397eea926f3a481b79ca938245064d809
This commit is contained in:
Spandan Das 2024-05-03 21:36:48 +00:00
parent e8cb9178ec
commit 3d0d31a8c6
3 changed files with 166 additions and 2 deletions

View file

@ -795,3 +795,127 @@ func TestNonBootJarInFragment(t *testing.T) {
}
`)
}
// Source and prebuilt apex provide different set of boot jars
func TestNonBootJarMissingInPrebuiltFragment(t *testing.T) {
bp := `
apex {
name: "myapex",
key: "myapex.key",
bootclasspath_fragments: ["apex-fragment"],
updatable: false,
}
apex_key {
name: "myapex.key",
public_key: "testkey.avbpubkey",
private_key: "testkey.pem",
}
java_library {
name: "foo",
srcs: ["b.java"],
installable: true,
apex_available: ["myapex"],
permitted_packages: ["foo"],
}
java_library {
name: "bar",
srcs: ["b.java"],
installable: true,
apex_available: ["myapex"],
permitted_packages: ["bar"],
}
bootclasspath_fragment {
name: "apex-fragment",
contents: ["foo", "bar"],
apex_available:[ "myapex" ],
hidden_api: {
split_packages: ["*"],
},
}
prebuilt_apex {
name: "com.google.android.myapex", // mainline prebuilt selection logic in soong relies on the naming convention com.google.android
apex_name: "myapex",
source_apex_name: "myapex",
src: "myapex.apex",
exported_bootclasspath_fragments: ["apex-fragment"],
}
java_import {
name: "foo",
jars: ["foo.jar"],
apex_available: ["myapex"],
permitted_packages: ["foo"],
}
prebuilt_bootclasspath_fragment {
name: "apex-fragment",
contents: ["foo"], // Unlike the source fragment, this is missing bar
apex_available:[ "myapex" ],
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_contributions {
name: "my_apex_contributions",
api_domain: "myapex",
contents: [%v],
}
`
testCases := []struct {
desc string
configuredBootJars []string
apexContributionContents string
errorExpected bool
}{
{
desc: "Source apex is selected, and APEX_BOOT_JARS is correctly configured for source apex builds",
configuredBootJars: []string{"myapex:foo", "myapex:bar"},
},
{
desc: "Source apex is selected, and APEX_BOOT_JARS is missing bar",
configuredBootJars: []string{"myapex:foo"},
errorExpected: true,
},
{
desc: "Prebuilt apex is selected, and APEX_BOOT_JARS is correctly configured for prebuilt apex build",
configuredBootJars: []string{"myapex:foo"},
apexContributionContents: `"prebuilt_com.google.android.myapex"`,
},
{
desc: "Prebuilt apex is selected, and APEX_BOOT_JARS is missing foo",
configuredBootJars: []string{"myapex:bar"},
apexContributionContents: `"prebuilt_com.google.android.myapex"`,
errorExpected: true,
},
}
for _, tc := range testCases {
fixture := android.GroupFixturePreparers(
prepareForTestWithPlatformBootclasspath,
PrepareForTestWithApexBuildComponents,
prepareForTestWithMyapex,
java.FixtureConfigureApexBootJars(tc.configuredBootJars...),
android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
variables.BuildFlags = map[string]string{
"RELEASE_APEX_CONTRIBUTIONS_ART": "my_apex_contributions",
}
}),
)
if tc.errorExpected {
fixture = fixture.ExtendWithErrorHandler(
android.FixtureExpectsAtLeastOneErrorMatchingPattern(`in contents.*must also be declared in PRODUCT_APEX_BOOT_JARS`),
)
}
fixture.RunTestWithBp(t, fmt.Sprintf(bp, tc.apexContributionContents))
}
}

View file

@ -835,7 +835,21 @@ func (p *prebuiltCommon) providePrebuiltInfo(ctx android.ModuleContext) {
android.SetProvider(ctx, android.PrebuiltInfoProvider, info)
}
// Uses an object provided by its deps to validate that the contents of bcpf have been added to the global
// PRODUCT_APEX_BOOT_JARS
// This validation will only run on the apex which is active for this product/release_config
func validateApexClasspathFragments(ctx android.ModuleContext) {
ctx.VisitDirectDeps(func(m android.Module) {
if info, exists := android.OtherModuleProvider(ctx, m, java.ClasspathFragmentValidationInfoProvider); exists {
ctx.ModuleErrorf("%s in contents of %s must also be declared in PRODUCT_APEX_BOOT_JARS", info.UnknownJars, info.ClasspathFragmentModuleName)
}
})
}
func (p *Prebuilt) GenerateAndroidBuildActions(ctx android.ModuleContext) {
// Validate contents of classpath fragments
validateApexClasspathFragments(ctx)
p.apexKeysPath = writeApexKeys(ctx, p)
// TODO(jungjw): Check the key validity.
p.inputApex = android.OptionalPathForModuleSrc(ctx, p.prebuiltCommonProperties.Selected_apex).Path()
@ -1059,6 +1073,9 @@ func (a *ApexSet) ApexInfoMutator(mctx android.TopDownMutatorContext) {
}
func (a *ApexSet) GenerateAndroidBuildActions(ctx android.ModuleContext) {
// Validate contents of classpath fragments
validateApexClasspathFragments(ctx)
a.apexKeysPath = writeApexKeys(ctx, a)
a.installFilename = a.InstallFilename()
if !strings.HasSuffix(a.installFilename, imageApexSuffix) && !strings.HasSuffix(a.installFilename, imageCapexSuffix) {

View file

@ -590,13 +590,36 @@ func (b *BootclasspathFragmentModule) configuredJars(ctx android.ModuleContext)
// So ignore it even if it is not in PRODUCT_APEX_BOOT_JARS.
// TODO(b/202896428): Add better way to handle this.
_, unknown = android.RemoveFromList("android.car-module", unknown)
if isActiveModule(ctx, ctx.Module()) && len(unknown) > 0 {
ctx.ModuleErrorf("%s in contents must also be declared in PRODUCT_APEX_BOOT_JARS", unknown)
if isApexVariant(ctx) && len(unknown) > 0 {
if android.IsModulePrebuilt(ctx.Module()) {
// prebuilt bcpf. the validation of this will be done at the top-level apex
providerClasspathFragmentValidationInfoProvider(ctx, unknown)
} else if !disableSourceApexVariant(ctx) {
// source bcpf, and prebuilt apex are not selected.
ctx.ModuleErrorf("%s in contents must also be declared in PRODUCT_APEX_BOOT_JARS", unknown)
}
}
}
return jars
}
var ClasspathFragmentValidationInfoProvider = blueprint.NewProvider[ClasspathFragmentValidationInfo]()
type ClasspathFragmentValidationInfo struct {
ClasspathFragmentModuleName string
UnknownJars []string
}
// Set a provider with the list of jars that have not been added to PRODUCT_APEX_BOOT_JARS
// The validation will be done in the ctx of the top-level _selected_ apex
func providerClasspathFragmentValidationInfoProvider(ctx android.ModuleContext, unknown []string) {
info := ClasspathFragmentValidationInfo{
ClasspathFragmentModuleName: ctx.ModuleName(),
UnknownJars: unknown,
}
android.SetProvider(ctx, ClasspathFragmentValidationInfoProvider, info)
}
// generateHiddenAPIBuildActions generates all the hidden API related build rules.
func (b *BootclasspathFragmentModule) generateHiddenAPIBuildActions(ctx android.ModuleContext, contents []android.Module, fragments []android.Module) *HiddenAPIOutput {