diff --git a/apex/apex.go b/apex/apex.go index 07d1ff2b7..91d646d69 100644 --- a/apex/apex.go +++ b/apex/apex.go @@ -1709,7 +1709,9 @@ func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) { ctx.PropertyErrorf("systemserverclasspath_fragments", "%q is not a systemserverclasspath_fragment module", depName) return false } - filesInfo = append(filesInfo, apexClasspathFragmentProtoFile(ctx, child)) + if af := apexClasspathFragmentProtoFile(ctx, child); af != nil { + filesInfo = append(filesInfo, *af) + } return true } case javaLibTag: @@ -2106,17 +2108,23 @@ func apexBootclasspathFragmentFiles(ctx android.ModuleContext, module blueprint. } // Add classpaths.proto config. - filesToAdd = append(filesToAdd, apexClasspathFragmentProtoFile(ctx, module)) + if af := apexClasspathFragmentProtoFile(ctx, module); af != nil { + filesToAdd = append(filesToAdd, *af) + } return filesToAdd } -// apexClasspathFragmentProtoFile returns apexFile structure defining the classpath.proto config that -// the module contributes to the apex. -func apexClasspathFragmentProtoFile(ctx android.ModuleContext, module blueprint.Module) apexFile { - fragmentInfo := ctx.OtherModuleProvider(module, java.ClasspathFragmentProtoContentInfoProvider).(java.ClasspathFragmentProtoContentInfo) - classpathProtoOutput := fragmentInfo.ClasspathFragmentProtoOutput - return newApexFile(ctx, classpathProtoOutput, classpathProtoOutput.Base(), fragmentInfo.ClasspathFragmentProtoInstallDir.Rel(), etc, nil) +// apexClasspathFragmentProtoFile returns *apexFile structure defining the classpath.proto config that +// the module contributes to the apex; or nil if the proto config was not generated. +func apexClasspathFragmentProtoFile(ctx android.ModuleContext, module blueprint.Module) *apexFile { + info := ctx.OtherModuleProvider(module, java.ClasspathFragmentProtoContentInfoProvider).(java.ClasspathFragmentProtoContentInfo) + if !info.ClasspathFragmentProtoGenerated { + return nil + } + classpathProtoOutput := info.ClasspathFragmentProtoOutput + af := newApexFile(ctx, classpathProtoOutput, classpathProtoOutput.Base(), info.ClasspathFragmentProtoInstallDir.Rel(), etc, nil) + return &af } // apexFileForBootclasspathFragmentContentModule creates an apexFile for a bootclasspath_fragment @@ -2298,16 +2306,30 @@ func (a *apexBundle) checkStaticLinkingToStubLibraries(ctx android.ModuleContext }) } -// Enforce that Java deps of the apex are using stable SDKs to compile +// checkUpdatable enforces APEX and its transitive dep properties to have desired values for updatable APEXes. func (a *apexBundle) checkUpdatable(ctx android.ModuleContext) { if a.Updatable() { if String(a.properties.Min_sdk_version) == "" { ctx.PropertyErrorf("updatable", "updatable APEXes should set min_sdk_version as well") } a.checkJavaStableSdkVersion(ctx) + a.checkClasspathFragments(ctx) } } +// checkClasspathFragments enforces that all classpath fragments in deps generate classpaths.proto config. +func (a *apexBundle) checkClasspathFragments(ctx android.ModuleContext) { + ctx.VisitDirectDeps(func(module android.Module) { + if tag := ctx.OtherModuleDependencyTag(module); tag == bcpfTag || tag == sscpfTag { + info := ctx.OtherModuleProvider(module, java.ClasspathFragmentProtoContentInfoProvider).(java.ClasspathFragmentProtoContentInfo) + if !info.ClasspathFragmentProtoGenerated { + ctx.OtherModuleErrorf(module, "is included in updatable apex %v, it must not set generate_classpaths_proto to false", ctx.ModuleName()) + } + } + }) +} + +// checkJavaStableSdkVersion enforces that all Java deps are using stable SDKs to compile. func (a *apexBundle) checkJavaStableSdkVersion(ctx android.ModuleContext) { // Visit direct deps only. As long as we guarantee top-level deps are using stable SDKs, // java's checkLinkType guarantees correct usage for transitive deps @@ -2326,7 +2348,7 @@ func (a *apexBundle) checkJavaStableSdkVersion(ctx android.ModuleContext) { }) } -// Ensures that the all the dependencies are marked as available for this APEX +// checkApexAvailability ensures that the all the dependencies are marked as available for this APEX. func (a *apexBundle) checkApexAvailability(ctx android.ModuleContext) { // Let's be practical. Availability for test, host, and the VNDK apex isn't important if ctx.Host() || a.testApex || a.vndkApex { diff --git a/apex/apex_test.go b/apex/apex_test.go index 1ba8f5544..2e131a6fa 100644 --- a/apex/apex_test.go +++ b/apex/apex_test.go @@ -6550,6 +6550,47 @@ func TestUpdatableDefault_should_set_min_sdk_version(t *testing.T) { `) } +func TestUpdatable_should_not_set_generate_classpaths_proto(t *testing.T) { + testApexError(t, `"mysystemserverclasspathfragment" .* it must not set generate_classpaths_proto to false`, ` + apex { + name: "myapex", + key: "myapex.key", + systemserverclasspath_fragments: [ + "mysystemserverclasspathfragment", + ], + min_sdk_version: "29", + updatable: true, + } + + apex_key { + name: "myapex.key", + public_key: "testkey.avbpubkey", + private_key: "testkey.pem", + } + + java_library { + name: "foo", + srcs: ["b.java"], + min_sdk_version: "29", + installable: true, + apex_available: [ + "myapex", + ], + } + + systemserverclasspath_fragment { + name: "mysystemserverclasspathfragment", + generate_classpaths_proto: false, + contents: [ + "foo", + ], + apex_available: [ + "myapex", + ], + } + `) +} + func TestNoUpdatableJarsInBootImage(t *testing.T) { // Set the BootJars in dexpreopt.GlobalConfig and productVariables to the same value. This can // result in an invalid configuration as it does not set the ArtApexJars and allows art apex diff --git a/apex/systemserver_classpath_fragment_test.go b/apex/systemserver_classpath_fragment_test.go index 95b6e230d..537f51d95 100644 --- a/apex/systemserver_classpath_fragment_test.go +++ b/apex/systemserver_classpath_fragment_test.go @@ -76,3 +76,54 @@ func TestSystemserverclasspathFragmentContents(t *testing.T) { `mysystemserverclasspathfragment`, }) } + +func TestSystemserverclasspathFragmentNoGeneratedProto(t *testing.T) { + result := android.GroupFixturePreparers( + prepareForTestWithSystemserverclasspathFragment, + prepareForTestWithMyapex, + ).RunTestWithBp(t, ` + apex { + name: "myapex", + key: "myapex.key", + systemserverclasspath_fragments: [ + "mysystemserverclasspathfragment", + ], + 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", + ], + } + + systemserverclasspath_fragment { + name: "mysystemserverclasspathfragment", + generate_classpaths_proto: false, + contents: [ + "foo", + ], + apex_available: [ + "myapex", + ], + } + `) + + ensureExactContents(t, result.TestContext, "myapex", "android_common_myapex_image", []string{ + "javalib/foo.jar", + }) + + java.CheckModuleDependencies(t, result.TestContext, "myapex", "android_common_myapex_image", []string{ + `myapex.key`, + `mysystemserverclasspathfragment`, + }) +} diff --git a/java/classpath_fragment.go b/java/classpath_fragment.go index 4c1e749de..3d7258086 100644 --- a/java/classpath_fragment.go +++ b/java/classpath_fragment.go @@ -19,6 +19,7 @@ package java import ( "fmt" "github.com/google/blueprint" + "github.com/google/blueprint/proptools" "strings" "android/soong/android" @@ -44,6 +45,11 @@ func (c classpathType) String() string { } type classpathFragmentProperties struct { + // Whether to generated classpaths.proto config instance for the fragment. If the config is not + // generated, then relevant boot jars are added to platform classpath, i.e. platform_bootclasspath + // or platform_systemserverclasspath. This is useful for non-updatable APEX boot jars, to keep + // them as part of dexopt on device. Defaults to true. + Generate_classpaths_proto *bool } // classpathFragment interface is implemented by a module that contributes jars to a *CLASSPATH @@ -101,24 +107,28 @@ func configuredJarListToClasspathJars(ctx android.ModuleContext, configuredJars } func (c *ClasspathFragmentBase) generateClasspathProtoBuildActions(ctx android.ModuleContext, jars []classpathJar) { - outputFilename := strings.ToLower(c.classpathType.String()) + ".pb" - c.outputFilepath = android.PathForModuleOut(ctx, outputFilename).OutputPath - c.installDirPath = android.PathForModuleInstall(ctx, "etc", "classpaths") + generateProto := proptools.BoolDefault(c.properties.Generate_classpaths_proto, true) + if generateProto { + outputFilename := strings.ToLower(c.classpathType.String()) + ".pb" + c.outputFilepath = android.PathForModuleOut(ctx, outputFilename).OutputPath + c.installDirPath = android.PathForModuleInstall(ctx, "etc", "classpaths") - generatedJson := android.PathForModuleOut(ctx, outputFilename+".json") - writeClasspathsJson(ctx, generatedJson, jars) + generatedJson := android.PathForModuleOut(ctx, outputFilename+".json") + writeClasspathsJson(ctx, generatedJson, jars) - rule := android.NewRuleBuilder(pctx, ctx) - rule.Command(). - BuiltTool("conv_classpaths_proto"). - Flag("encode"). - Flag("--format=json"). - FlagWithInput("--input=", generatedJson). - FlagWithOutput("--output=", c.outputFilepath) + rule := android.NewRuleBuilder(pctx, ctx) + rule.Command(). + BuiltTool("conv_classpaths_proto"). + Flag("encode"). + Flag("--format=json"). + FlagWithInput("--input=", generatedJson). + FlagWithOutput("--output=", c.outputFilepath) - rule.Build("classpath_fragment", "Compiling "+c.outputFilepath.String()) + rule.Build("classpath_fragment", "Compiling "+c.outputFilepath.String()) + } classpathProtoInfo := ClasspathFragmentProtoContentInfo{ + ClasspathFragmentProtoGenerated: generateProto, ClasspathFragmentProtoInstallDir: c.installDirPath, ClasspathFragmentProtoOutput: c.outputFilepath, } @@ -164,6 +174,9 @@ func (c *ClasspathFragmentBase) androidMkEntries() []android.AndroidMkEntries { var ClasspathFragmentProtoContentInfoProvider = blueprint.NewProvider(ClasspathFragmentProtoContentInfo{}) type ClasspathFragmentProtoContentInfo struct { + // Whether the classpaths.proto config is generated for the fragment. + ClasspathFragmentProtoGenerated bool + // ClasspathFragmentProtoOutput is an output path for the generated classpaths.proto config of this module. // // The file should be copied to a relevant place on device, see ClasspathFragmentProtoInstallDir