diff --git a/apex/platform_bootclasspath_test.go b/apex/platform_bootclasspath_test.go index 2be9c10e1..9f1e1e16f 100644 --- a/apex/platform_bootclasspath_test.go +++ b/apex/platform_bootclasspath_test.go @@ -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)) + } +} diff --git a/apex/prebuilt.go b/apex/prebuilt.go index 72a9e5219..b2afa3933 100644 --- a/apex/prebuilt.go +++ b/apex/prebuilt.go @@ -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) { diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go index 82a34ca9d..4d3d794d8 100644 --- a/java/bootclasspath_fragment.go +++ b/java/bootclasspath_fragment.go @@ -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 {