diff --git a/dexpreopt/config.go b/dexpreopt/config.go index 888466a2f..908ba715b 100644 --- a/dexpreopt/config.go +++ b/dexpreopt/config.go @@ -33,6 +33,8 @@ type GlobalConfig struct { OnlyPreoptBootImageAndSystemServer bool // only preopt jars in the boot image or system server + PreoptWithUpdatableBcp bool // If updatable boot jars are included in dexpreopt or not. + UseArtImage bool // use the art image (use other boot class path dex files without image) HasSystemOther bool // store odex files that match PatternsOnSystemOther on the system_other partition diff --git a/dexpreopt/testing.go b/dexpreopt/testing.go index 8e90295b1..7705e8507 100644 --- a/dexpreopt/testing.go +++ b/dexpreopt/testing.go @@ -78,3 +78,17 @@ func FixtureSetBootJars(bootJars ...string) android.FixturePreparer { dexpreoptConfig.BootJars = android.CreateTestConfiguredJarList(bootJars) }) } + +// FixtureSetUpdatableBootJars sets the UpdatableBootJars property in the global config. +func FixtureSetUpdatableBootJars(bootJars ...string) android.FixturePreparer { + return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) { + dexpreoptConfig.UpdatableBootJars = android.CreateTestConfiguredJarList(bootJars) + }) +} + +// FixtureSetPreoptWithUpdatableBcp sets the PreoptWithUpdatableBcp property in the global config. +func FixtureSetPreoptWithUpdatableBcp(value bool) android.FixturePreparer { + return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) { + dexpreoptConfig.PreoptWithUpdatableBcp = value + }) +} diff --git a/java/app_test.go b/java/app_test.go index 252353364..825ad203a 100644 --- a/java/app_test.go +++ b/java/app_test.go @@ -26,6 +26,7 @@ import ( "android/soong/android" "android/soong/cc" + "android/soong/dexpreopt" "android/soong/genrule" ) @@ -2422,6 +2423,66 @@ func TestUsesLibraries(t *testing.T) { `#PCL[/system/framework/android.test.mock.jar] `) } +func TestDexpreoptBcp(t *testing.T) { + bp := ` + java_sdk_library { + name: "foo", + srcs: ["a.java"], + api_packages: ["foo"], + sdk_version: "current", + } + + java_sdk_library { + name: "bar", + srcs: ["a.java"], + api_packages: ["bar"], + permitted_packages: ["bar"], + sdk_version: "current", + } + + android_app { + name: "app", + srcs: ["a.java"], + sdk_version: "current", + } + ` + + testCases := []struct { + name string + with bool + expect string + }{ + { + name: "with updatable bcp", + with: true, + expect: "/system/framework/foo.jar:/system/framework/bar.jar", + }, + { + name: "without updatable bcp", + with: false, + expect: "/system/framework/foo.jar", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + result := android.GroupFixturePreparers( + prepareForJavaTest, + PrepareForTestWithJavaSdkLibraryFiles, + FixtureWithLastReleaseApis("runtime-library", "foo", "bar"), + dexpreopt.FixtureSetBootJars("platform:foo"), + dexpreopt.FixtureSetUpdatableBootJars("platform:bar"), + dexpreopt.FixtureSetPreoptWithUpdatableBcp(test.with), + ).RunTestWithBp(t, bp) + + app := result.ModuleForTests("app", "android_common") + cmd := app.Rule("dexpreopt").RuleParams.Command + bcp := " -Xbootclasspath-locations:" + test.expect + " " // space at the end matters + android.AssertStringDoesContain(t, "dexpreopt app bcp", cmd, bcp) + }) + } +} + func TestCodelessApp(t *testing.T) { testCases := []struct { name string diff --git a/java/dexpreopt.go b/java/dexpreopt.go index a2961c29e..b4cf012af 100644 --- a/java/dexpreopt.go +++ b/java/dexpreopt.go @@ -160,14 +160,17 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Wr globalSoong := dexpreopt.GetGlobalSoongConfig(ctx) global := dexpreopt.GetGlobalConfig(ctx) + + isSystemServerJar := inList(ctx.ModuleName(), global.SystemServerJars) + bootImage := defaultBootImageConfig(ctx) - dexFiles := bootImage.dexPathsDeps.Paths() - // The dex locations for all Android variants are identical. - dexLocations := bootImage.getAnyAndroidVariant().dexLocationsDeps if global.UseArtImage { bootImage = artBootImageConfig(ctx) } + // System server jars are an exception: they are dexpreopted without updatable bootclasspath. + dexFiles, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp && !isSystemServerJar) + targets := ctx.MultiTargets() if len(targets) == 0 { // assume this is a java library, dexpreopt for all arches for now @@ -176,7 +179,7 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Wr targets = append(targets, target) } } - if inList(ctx.ModuleName(), global.SystemServerJars) && !d.isSDKLibrary { + if isSystemServerJar && !d.isSDKLibrary { // If the module is not an SDK library and it's a system server jar, only preopt the primary arch. targets = targets[:1] } @@ -237,7 +240,7 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Wr DexPreoptImagesDeps: imagesDeps, DexPreoptImageLocations: imageLocations, - PreoptBootClassPathDexFiles: dexFiles, + PreoptBootClassPathDexFiles: dexFiles.Paths(), PreoptBootClassPathDexLocations: dexLocations, PreoptExtractedApk: false, diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go index 17499ee7d..6dc408b83 100644 --- a/java/dexpreopt_bootjars.go +++ b/java/dexpreopt_bootjars.go @@ -439,6 +439,8 @@ func (d *dexpreoptBootJars) GenerateSingletonBuildActions(ctx android.SingletonC // Create boot image for the ART apex (build artifacts are accessed via the global boot image config). d.otherImages = append(d.otherImages, buildBootImage(ctx, artBootImageConfig(ctx))) + copyUpdatableBootJars(ctx) + dumpOatRules(ctx, d.defaultBootImage) } @@ -630,6 +632,21 @@ func buildBootImage(ctx android.SingletonContext, image *bootImageConfig) *bootI return image } +// Generate commands that will copy updatable boot jars to predefined paths in the global config. +func copyUpdatableBootJars(ctx android.SingletonContext) { + config := GetUpdatableBootConfig(ctx) + getBootJarFunc := func(module android.Module) (int, android.Path) { + index, jar, _ := getBootJar(ctx, config.modules, module, "configured in updatable boot jars ") + return index, jar + } + missingDeps := findAndCopyBootJars(ctx, config.modules, config.dexPaths, getBootJarFunc) + // Ignoring missing dependencies here. Ideally they should be added to the dexpreopt rule, but + // that is not possible as this rule is created after dexpreopt rules (it's in a singleton + // context, and they are in a module context). The true fix is to add dependencies from the + // dexpreopted modules on updatable boot jars and avoid this copying altogether. + _ = missingDeps +} + // Generate boot image build rules for a specific target. func buildBootImageVariant(ctx android.SingletonContext, image *bootImageVariant, profile android.Path, missingDeps []string) android.WritablePaths { @@ -997,8 +1014,11 @@ func (d *dexpreoptBootJars) MakeVars(ctx android.MakeVarsContext) { image := d.defaultBootImage if image != nil { ctx.Strict("DEXPREOPT_IMAGE_PROFILE_BUILT_INSTALLED", image.profileInstalls.String()) - ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_FILES", strings.Join(image.dexPathsDeps.Strings(), " ")) - ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(image.getAnyAndroidVariant().dexLocationsDeps, " ")) + + global := dexpreopt.GetGlobalConfig(ctx) + dexPaths, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp) + ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_FILES", strings.Join(dexPaths.Strings(), " ")) + ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(dexLocations, " ")) var imageNames []string // TODO: the primary ART boot image should not be exposed to Make, as it is installed in a diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go index 282e9364e..64b265640 100644 --- a/java/dexpreopt_config.go +++ b/java/dexpreopt_config.go @@ -176,6 +176,57 @@ func defaultBootclasspath(ctx android.PathContext) []string { }) } +// Updatable boot config allows to access build/install paths of updatable boot jars without going +// through the usual trouble of registering dependencies on those modules and extracting build paths +// from those dependencies. +type updatableBootConfig struct { + // A list of updatable boot jars. + modules android.ConfiguredJarList + + // A list of predefined build paths to updatable boot jars. They are configured very early, + // before the modules for these jars are processed and the actual paths are generated, and + // later on a singleton adds commands to copy actual jars to the predefined paths. + dexPaths android.WritablePaths + + // A list of dex locations (a.k.a. on-device paths) to the boot jars. + dexLocations []string +} + +var updatableBootConfigKey = android.NewOnceKey("updatableBootConfig") + +// Returns updatable boot config. +func GetUpdatableBootConfig(ctx android.PathContext) updatableBootConfig { + return ctx.Config().Once(updatableBootConfigKey, func() interface{} { + updatableBootJars := dexpreopt.GetGlobalConfig(ctx).UpdatableBootJars + + dir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "updatable_bootjars") + dexPaths := updatableBootJars.BuildPaths(ctx, dir) + + dexLocations := updatableBootJars.DevicePaths(ctx.Config(), android.Android) + + return updatableBootConfig{updatableBootJars, dexPaths, dexLocations} + }).(updatableBootConfig) +} + +// Returns a list of paths and a list of locations for the boot jars used in dexpreopt (to be +// passed in -Xbootclasspath and -Xbootclasspath-locations arguments for dex2oat). +func bcpForDexpreopt(ctx android.PathContext, withUpdatable bool) (android.WritablePaths, []string) { + // Non-updatable boot jars (they are used both in the boot image and in dexpreopt). + bootImage := defaultBootImageConfig(ctx) + dexPaths := bootImage.dexPathsDeps + // The dex locations for all Android variants are identical. + dexLocations := bootImage.getAnyAndroidVariant().dexLocationsDeps + + if withUpdatable { + // Updatable boot jars (they are used only in dexpreopt, but not in the boot image). + updBootConfig := GetUpdatableBootConfig(ctx) + dexPaths = append(dexPaths, updBootConfig.dexPaths...) + dexLocations = append(dexLocations, updBootConfig.dexLocations...) + } + + return dexPaths, dexLocations +} + var defaultBootclasspathKey = android.NewOnceKey("defaultBootclasspath") var copyOf = android.CopyOf