From 519c5c82e596959b683570d387aba43bae8fd65a Mon Sep 17 00:00:00 2001 From: Jiakai Zhang Date: Thu, 16 Sep 2021 06:15:39 +0000 Subject: [PATCH] Revert^2 "Preopt APEX system server jars." This reverts commit 92346c483249726164f4bd140413d60391121763. Reason for revert: Fixed build error. The build error is fixed by ag/15841934. This CL remains unchanged. This CL will be submitted AFTER ag/15841934 is submitted. Bug: 200024131 Test: 1. Patch this CL and ag/15841934 into internal master. 2. sudo vendor/google/build/build_test.bash Change-Id: I5f2b8357846fc7dda56e25ebe6ffb095e8047ec8 --- android/config.go | 14 +++ apex/apex.go | 5 + dexpreopt/dexpreopt.go | 54 +++++---- dexpreopt/dexpreopt_test.go | 49 +++++++- java/androidmk.go | 8 +- java/dexpreopt.go | 160 ++++++++++++++++++++++--- java/dexpreopt_test.go | 227 +++++++++++++++++++++++++++++++++++- java/java.go | 13 ++- java/testing.go | 42 +++++++ 9 files changed, 518 insertions(+), 54 deletions(-) diff --git a/android/config.go b/android/config.go index 3e41fbb4f..993aaa779 100644 --- a/android/config.go +++ b/android/config.go @@ -1659,6 +1659,20 @@ func (l *ConfiguredJarList) Append(apex string, jar string) ConfiguredJarList { return ConfiguredJarList{apexes, jars} } +// Append a list of (apex, jar) pairs to the list. +func (l *ConfiguredJarList) AppendList(other ConfiguredJarList) ConfiguredJarList { + apexes := make([]string, 0, l.Len()+other.Len()) + jars := make([]string, 0, l.Len()+other.Len()) + + apexes = append(apexes, l.apexes...) + jars = append(jars, l.jars...) + + apexes = append(apexes, other.apexes...) + jars = append(jars, other.jars...) + + return ConfiguredJarList{apexes, jars} +} + // RemoveList filters out a list of (apex, jar) pairs from the receiving list of pairs. func (l *ConfiguredJarList) RemoveList(list ConfiguredJarList) ConfiguredJarList { apexes := make([]string, 0, l.Len()) diff --git a/apex/apex.go b/apex/apex.go index e3edc681d..2d153e2c0 100644 --- a/apex/apex.go +++ b/apex/apex.go @@ -1569,6 +1569,11 @@ func apexFileForJavaModuleWithFile(ctx android.BaseModuleContext, module javaMod af.jacocoReportClassesFile = module.JacocoReportClassesFile() af.lintDepSets = module.LintDepSets() af.customStem = module.Stem() + ".jar" + if dexpreopter, ok := module.(java.DexpreopterInterface); ok { + for _, install := range dexpreopter.DexpreoptBuiltInstalledForApex() { + af.requiredModuleNames = append(af.requiredModuleNames, install.FullModuleName()) + } + } return af } diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go index 1401c75d9..7733c1b43 100644 --- a/dexpreopt/dexpreopt.go +++ b/dexpreopt/dexpreopt.go @@ -110,17 +110,12 @@ func dexpreoptDisabled(ctx android.PathContext, global *GlobalConfig, module *Mo return true } - // Don't preopt system server jars that are updatable. - if global.ApexSystemServerJars.ContainsJar(module.Name) { - return true - } - // If OnlyPreoptBootImageAndSystemServer=true and module is not in boot class path skip // Also preopt system server jars since selinux prevents system server from loading anything from // /data. If we don't do this they will need to be extracted which is not favorable for RAM usage // or performance. If PreoptExtractedApk is true, we ignore the only preopt boot image options. if global.OnlyPreoptBootImageAndSystemServer && !global.BootJars.ContainsJar(module.Name) && - !global.SystemServerJars.ContainsJar(module.Name) && !module.PreoptExtractedApk { + !AllSystemServerJars(ctx, global).ContainsJar(module.Name) && !module.PreoptExtractedApk { return true } @@ -201,6 +196,14 @@ func bootProfileCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, return profilePath } +// Returns the dex location of a system server java library. +func GetSystemServerDexLocation(global *GlobalConfig, lib string) string { + if apex := global.ApexSystemServerJars.ApexOfJar(lib); apex != "" { + return fmt.Sprintf("/apex/%s/javalib/%s.jar", apex, lib) + } + return fmt.Sprintf("/system/framework/%s.jar", lib) +} + func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, global *GlobalConfig, module *ModuleConfig, rule *android.RuleBuilder, archIdx int, profile android.WritablePath, appImage bool, generateDM bool) { @@ -216,6 +219,13 @@ func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, g } toOdexPath := func(path string) string { + if global.ApexSystemServerJars.ContainsJar(module.Name) { + return filepath.Join( + "/system/framework/oat", + arch.String(), + strings.ReplaceAll(path[1:], "/", "@")+"@classes.odex") + } + return filepath.Join( filepath.Dir(path), "oat", @@ -234,20 +244,21 @@ func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, g invocationPath := odexPath.ReplaceExtension(ctx, "invocation") - systemServerJars := NonApexSystemServerJars(ctx, global) + systemServerJars := AllSystemServerJars(ctx, global) rule.Command().FlagWithArg("mkdir -p ", filepath.Dir(odexPath.String())) rule.Command().FlagWithOutput("rm -f ", odexPath) - if jarIndex := android.IndexList(module.Name, systemServerJars); jarIndex >= 0 { + if jarIndex := systemServerJars.IndexOfJar(module.Name); jarIndex >= 0 { // System server jars should be dexpreopted together: class loader context of each jar // should include all preceding jars on the system server classpath. var clcHost android.Paths var clcTarget []string - for _, lib := range systemServerJars[:jarIndex] { + for i := 0; i < jarIndex; i++ { + lib := systemServerJars.Jar(i) clcHost = append(clcHost, SystemServerDexJarHostPath(ctx, lib)) - clcTarget = append(clcTarget, filepath.Join("/system/framework", lib+".jar")) + clcTarget = append(clcTarget, GetSystemServerDexLocation(global, lib)) } // Copy the system server jar to a predefined location where dex2oat will find it. @@ -362,7 +373,7 @@ func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, g if !android.PrefixInList(preoptFlags, "--compiler-filter=") { var compilerFilter string - if global.SystemServerJars.ContainsJar(module.Name) { + if systemServerJars.ContainsJar(module.Name) { // Jars of system server, use the product option if it is set, speed otherwise. if global.SystemServerCompilerFilter != "" { compilerFilter = global.SystemServerCompilerFilter @@ -416,7 +427,7 @@ func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, g // PRODUCT_SYSTEM_SERVER_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO. // PRODUCT_OTHER_JAVA_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO. - if global.SystemServerJars.ContainsJar(module.Name) { + if systemServerJars.ContainsJar(module.Name) { if global.AlwaysSystemServerDebugInfo { debugInfo = true } else if global.NeverSystemServerDebugInfo { @@ -518,14 +529,15 @@ func makefileMatch(pattern, s string) bool { } } -var nonApexSystemServerJarsKey = android.NewOnceKey("nonApexSystemServerJars") +var allSystemServerJarsKey = android.NewOnceKey("allSystemServerJars") // TODO: eliminate the superficial global config parameter by moving global config definition // from java subpackage to dexpreopt. -func NonApexSystemServerJars(ctx android.PathContext, global *GlobalConfig) []string { - return ctx.Config().Once(nonApexSystemServerJarsKey, func() interface{} { - return android.RemoveListFromList(global.SystemServerJars.CopyOfJars(), global.ApexSystemServerJars.CopyOfJars()) - }).([]string) +func AllSystemServerJars(ctx android.PathContext, global *GlobalConfig) *android.ConfiguredJarList { + return ctx.Config().Once(allSystemServerJarsKey, func() interface{} { + allSystemServerJars := global.SystemServerJars.AppendList(global.ApexSystemServerJars) + return &allSystemServerJars + }).(*android.ConfiguredJarList) } // A predefined location for the system server dex jars. This is needed in order to generate @@ -551,12 +563,12 @@ func checkSystemServerOrder(ctx android.PathContext, jarIndex int) { mctx, isModule := ctx.(android.ModuleContext) if isModule { config := GetGlobalConfig(ctx) - jars := NonApexSystemServerJars(ctx, config) + jars := AllSystemServerJars(ctx, config) mctx.WalkDeps(func(dep android.Module, parent android.Module) bool { - depIndex := android.IndexList(dep.Name(), jars) + depIndex := jars.IndexOfJar(dep.Name()) if jarIndex < depIndex && !config.BrokenSuboptimalOrderOfSystemServerJars { - jar := jars[jarIndex] - dep := jars[depIndex] + jar := jars.Jar(jarIndex) + dep := jars.Jar(depIndex) mctx.ModuleErrorf("non-optimal order of jars on the system server classpath:"+ " '%s' precedes its dependency '%s', so dexpreopt is unable to resolve any"+ " references from '%s' to '%s'.\n", jar, dep, jar, dep) diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go index 4ee61b6b0..798d77604 100644 --- a/dexpreopt/dexpreopt_test.go +++ b/dexpreopt/dexpreopt_test.go @@ -33,17 +33,35 @@ func testProductModuleConfig(ctx android.PathContext, name string) *ModuleConfig } func testModuleConfig(ctx android.PathContext, name, partition string) *ModuleConfig { + return createTestModuleConfig( + name, + fmt.Sprintf("/%s/app/test/%s.apk", partition, name), + android.PathForOutput(ctx, fmt.Sprintf("%s/%s.apk", name, name)), + android.PathForOutput(ctx, fmt.Sprintf("%s/dex/%s.jar", name, name)), + android.PathForOutput(ctx, fmt.Sprintf("%s/enforce_uses_libraries.status", name))) +} + +func testApexModuleConfig(ctx android.PathContext, name, apexName string) *ModuleConfig { + return createTestModuleConfig( + name, + fmt.Sprintf("/apex/%s/javalib/%s.jar", apexName, name), + android.PathForOutput(ctx, fmt.Sprintf("%s/dexpreopt/%s.jar", name, name)), + android.PathForOutput(ctx, fmt.Sprintf("%s/aligned/%s.jar", name, name)), + android.PathForOutput(ctx, fmt.Sprintf("%s/enforce_uses_libraries.status", name))) +} + +func createTestModuleConfig(name, dexLocation string, buildPath, dexPath, enforceUsesLibrariesStatusFile android.OutputPath) *ModuleConfig { return &ModuleConfig{ Name: name, - DexLocation: fmt.Sprintf("/%s/app/test/%s.apk", partition, name), - BuildPath: android.PathForOutput(ctx, fmt.Sprintf("%s/%s.apk", name, name)), - DexPath: android.PathForOutput(ctx, fmt.Sprintf("%s/dex/%s.jar", name, name)), + DexLocation: dexLocation, + BuildPath: buildPath, + DexPath: dexPath, UncompressedDex: false, HasApkLibraries: false, PreoptFlags: nil, ProfileClassListing: android.OptionalPath{}, ProfileIsTextListing: false, - EnforceUsesLibrariesStatusFile: android.PathForOutput(ctx, fmt.Sprintf("%s/enforce_uses_libraries.status", name)), + EnforceUsesLibrariesStatusFile: enforceUsesLibrariesStatusFile, EnforceUsesLibraries: false, ClassLoaderContexts: nil, Archs: []android.ArchType{android.Arm}, @@ -140,6 +158,29 @@ func TestDexPreoptSystemOther(t *testing.T) { } +func TestDexPreoptApexSystemServerJars(t *testing.T) { + config := android.TestConfig("out", nil, "", nil) + ctx := android.BuilderContextForTesting(config) + globalSoong := globalSoongConfigForTests() + global := GlobalConfigForTests(ctx) + module := testApexModuleConfig(ctx, "service-A", "com.android.apex1") + + global.ApexSystemServerJars = android.CreateTestConfiguredJarList( + []string{"com.android.apex1:service-A"}) + + rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module) + if err != nil { + t.Fatal(err) + } + + wantInstalls := android.RuleBuilderInstalls{ + {android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.odex"), "/system/framework/oat/arm/apex@com.android.apex1@javalib@service-A.jar@classes.odex"}, + {android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.vdex"), "/system/framework/oat/arm/apex@com.android.apex1@javalib@service-A.jar@classes.vdex"}, + } + + android.AssertStringEquals(t, "installs", wantInstalls.String(), rule.Installs().String()) +} + func TestDexPreoptProfile(t *testing.T) { config := android.TestConfig("out", nil, "", nil) ctx := android.BuilderContextForTesting(config) diff --git a/java/androidmk.go b/java/androidmk.go index 68ccd82e9..71370c9b1 100644 --- a/java/androidmk.go +++ b/java/androidmk.go @@ -61,7 +61,13 @@ func (library *Library) AndroidMkEntries() []android.AndroidMkEntries { var entriesList []android.AndroidMkEntries if library.hideApexVariantFromMake { - // For a java library built for an APEX we don't need Make module + // For a java library built for an APEX, we don't need a Make module for itself. Otherwise, it + // will conflict with the platform variant because they have the same module name in the + // makefile. However, we need to add its dexpreopt outputs as sub-modules, if it is preopted. + dexpreoptEntries := library.dexpreopter.AndroidMkEntriesForApex() + if len(dexpreoptEntries) > 0 { + entriesList = append(entriesList, dexpreoptEntries...) + } entriesList = append(entriesList, android.AndroidMkEntries{Disabled: true}) } else if !library.ApexModuleBase.AvailableFor(android.AvailableToPlatform) { // Platform variant. If not available for the platform, we don't need Make module. diff --git a/java/dexpreopt.go b/java/dexpreopt.go index 0faae36ba..cdd42ed1a 100644 --- a/java/dexpreopt.go +++ b/java/dexpreopt.go @@ -15,13 +15,46 @@ package java import ( + "path/filepath" + "strings" + "android/soong/android" "android/soong/dexpreopt" ) -type dexpreopterInterface interface { +type DexpreopterInterface interface { IsInstallable() bool // Structs that embed dexpreopter must implement this. dexpreoptDisabled(ctx android.BaseModuleContext) bool + DexpreoptBuiltInstalledForApex() []dexpreopterInstall + AndroidMkEntriesForApex() []android.AndroidMkEntries +} + +type dexpreopterInstall struct { + // A unique name to distinguish an output from others for the same java library module. Usually in + // the form of `-.odex/vdex/art`. + name string + + // The name of the input java module. + moduleName string + + // The path to the dexpreopt output on host. + outputPathOnHost android.Path + + // The directory on the device for the output to install to. + installDirOnDevice android.InstallPath + + // The basename (the last segment of the path) for the output to install as. + installFileOnDevice string +} + +// The full module name of the output in the makefile. +func (install *dexpreopterInstall) FullModuleName() string { + return install.moduleName + install.SubModuleName() +} + +// The sub-module name of the output in the makefile (the name excluding the java module name). +func (install *dexpreopterInstall) SubModuleName() string { + return "-dexpreopt-" + install.name } type dexpreopter struct { @@ -39,7 +72,9 @@ type dexpreopter struct { enforceUsesLibs bool classLoaderContexts dexpreopt.ClassLoaderContextMap - builtInstalled string + // See the `dexpreopt` function for details. + builtInstalled string + builtInstalledForApex []dexpreopterInstall // The config is used for two purposes: // - Passing dexpreopt information about libraries from Soong to Make. This is needed when @@ -74,6 +109,17 @@ func init() { dexpreopt.DexpreoptRunningInSoong = true } +func isApexVariant(ctx android.BaseModuleContext) bool { + apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo) + return !apexInfo.IsForPlatform() +} + +func moduleName(ctx android.BaseModuleContext) string { + // Remove the "prebuilt_" prefix if the module is from a prebuilt because the prefix is not + // expected by dexpreopter. + return android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName()) +} + func (d *dexpreopter) dexpreoptDisabled(ctx android.BaseModuleContext) bool { global := dexpreopt.GetGlobalConfig(ctx) @@ -81,7 +127,7 @@ func (d *dexpreopter) dexpreoptDisabled(ctx android.BaseModuleContext) bool { return true } - if inList(ctx.ModuleName(), global.DisablePreoptModules) { + if inList(moduleName(ctx), global.DisablePreoptModules) { return true } @@ -93,7 +139,7 @@ func (d *dexpreopter) dexpreoptDisabled(ctx android.BaseModuleContext) bool { return true } - if !ctx.Module().(dexpreopterInterface).IsInstallable() { + if !ctx.Module().(DexpreopterInterface).IsInstallable() { return true } @@ -101,9 +147,17 @@ func (d *dexpreopter) dexpreoptDisabled(ctx android.BaseModuleContext) bool { return true } - // Don't preopt APEX variant module - if apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo); !apexInfo.IsForPlatform() { - return true + if isApexVariant(ctx) { + // Don't preopt APEX variant module unless the module is an APEX system server jar and we are + // building the entire system image. + if !global.ApexSystemServerJars.ContainsJar(moduleName(ctx)) || ctx.Config().UnbundledBuild() { + return true + } + } else { + // Don't preopt the platform variant of an APEX system server jar to avoid conflicts. + if global.ApexSystemServerJars.ContainsJar(moduleName(ctx)) { + return true + } } // TODO: contains no java code @@ -112,17 +166,40 @@ func (d *dexpreopter) dexpreoptDisabled(ctx android.BaseModuleContext) bool { } func dexpreoptToolDepsMutator(ctx android.BottomUpMutatorContext) { - if d, ok := ctx.Module().(dexpreopterInterface); !ok || d.dexpreoptDisabled(ctx) { + if d, ok := ctx.Module().(DexpreopterInterface); !ok || d.dexpreoptDisabled(ctx) { return } dexpreopt.RegisterToolDeps(ctx) } -func odexOnSystemOther(ctx android.ModuleContext, installPath android.InstallPath) bool { - return dexpreopt.OdexOnSystemOtherByName(ctx.ModuleName(), android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx)) +func (d *dexpreopter) odexOnSystemOther(ctx android.ModuleContext, installPath android.InstallPath) bool { + return dexpreopt.OdexOnSystemOtherByName(moduleName(ctx), android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx)) +} + +// Returns the install path of the dex jar of a module. +// +// Do not rely on `ApexInfo.ApexVariationName` because it can be something like "apex1000", rather +// than the `name` in the path `/apex/` as suggested in its comment. +// +// This function is on a best-effort basis. It cannot handle the case where an APEX jar is not a +// system server jar, which is fine because we currently only preopt system server jars for APEXes. +func (d *dexpreopter) getInstallPath( + ctx android.ModuleContext, defaultInstallPath android.InstallPath) android.InstallPath { + global := dexpreopt.GetGlobalConfig(ctx) + if global.ApexSystemServerJars.ContainsJar(moduleName(ctx)) { + dexLocation := dexpreopt.GetSystemServerDexLocation(global, moduleName(ctx)) + return android.PathForModuleInPartitionInstall(ctx, "", strings.TrimPrefix(dexLocation, "/")) + } + if !d.dexpreoptDisabled(ctx) && isApexVariant(ctx) && + filepath.Base(defaultInstallPath.PartitionDir()) != "apex" { + ctx.ModuleErrorf("unable to get the install path of the dex jar for dexpreopt") + } + return defaultInstallPath } func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.WritablePath) { + global := dexpreopt.GetGlobalConfig(ctx) + // TODO(b/148690468): The check on d.installPath is to bail out in cases where // the dexpreopter struct hasn't been fully initialized before we're called, // e.g. in aar.go. This keeps the behaviour that dexpreopting is effectively @@ -133,7 +210,7 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Wr dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath) - providesUsesLib := ctx.ModuleName() + providesUsesLib := moduleName(ctx) if ulib, ok := ctx.Module().(ProvidesUsesLib); ok { name := ulib.ProvidesUsesLib() if name != nil { @@ -147,9 +224,8 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Wr return } - global := dexpreopt.GetGlobalConfig(ctx) - - isSystemServerJar := global.SystemServerJars.ContainsJar(ctx.ModuleName()) + isSystemServerJar := global.SystemServerJars.ContainsJar(moduleName(ctx)) || + global.ApexSystemServerJars.ContainsJar(moduleName(ctx)) bootImage := defaultBootImageConfig(ctx) if global.UseArtImage { @@ -199,15 +275,15 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Wr profileIsTextListing = true } else if global.ProfileDir != "" { profileClassListing = android.ExistentPathForSource(ctx, - global.ProfileDir, ctx.ModuleName()+".prof") + global.ProfileDir, moduleName(ctx)+".prof") } } // Full dexpreopt config, used to create dexpreopt build rules. dexpreoptConfig := &dexpreopt.ModuleConfig{ - Name: ctx.ModuleName(), + Name: moduleName(ctx), DexLocation: dexLocation, - BuildPath: android.PathForModuleOut(ctx, "dexpreopt", ctx.ModuleName()+".jar").OutputPath, + BuildPath: android.PathForModuleOut(ctx, "dexpreopt", moduleName(ctx)+".jar").OutputPath, DexPath: dexJarFile, ManifestPath: android.OptionalPathForPath(d.manifestFile), UncompressedDex: d.uncompressedDex, @@ -256,5 +332,53 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Wr dexpreoptRule.Build("dexpreopt", "dexpreopt") - d.builtInstalled = dexpreoptRule.Installs().String() + if global.ApexSystemServerJars.ContainsJar(moduleName(ctx)) { + // APEX variants of java libraries are hidden from Make, so their dexpreopt outputs need special + // handling. Currently, for APEX variants of java libraries, only those in the system server + // classpath are handled here. Preopting of boot classpath jars in the ART APEX are handled in + // java/dexpreopt_bootjars.go, and other APEX jars are not preopted. + for _, install := range dexpreoptRule.Installs() { + // Remove the "/" prefix because the path should be relative to $ANDROID_PRODUCT_OUT. + installDir := strings.TrimPrefix(filepath.Dir(install.To), "/") + installBase := filepath.Base(install.To) + arch := filepath.Base(installDir) + installPath := android.PathForModuleInPartitionInstall(ctx, "", installDir) + // The installs will be handled by Make as sub-modules of the java library. + d.builtInstalledForApex = append(d.builtInstalledForApex, dexpreopterInstall{ + name: arch + "-" + installBase, + moduleName: moduleName(ctx), + outputPathOnHost: install.From, + installDirOnDevice: installPath, + installFileOnDevice: installBase, + }) + } + } else { + // The installs will be handled by Make as LOCAL_SOONG_BUILT_INSTALLED of the java library + // module. + d.builtInstalled = dexpreoptRule.Installs().String() + } +} + +func (d *dexpreopter) DexpreoptBuiltInstalledForApex() []dexpreopterInstall { + return d.builtInstalledForApex +} + +func (d *dexpreopter) AndroidMkEntriesForApex() []android.AndroidMkEntries { + var entries []android.AndroidMkEntries + for _, install := range d.builtInstalledForApex { + install := install + entries = append(entries, android.AndroidMkEntries{ + Class: "ETC", + SubName: install.SubModuleName(), + OutputFile: android.OptionalPathForPath(install.outputPathOnHost), + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) { + entries.SetString("LOCAL_MODULE_PATH", install.installDirOnDevice.ToMakePath().String()) + entries.SetString("LOCAL_INSTALLED_MODULE_STEM", install.installFileOnDevice) + entries.SetString("LOCAL_NOT_AVAILABLE_FOR_PLATFORM", "false") + }, + }, + }) + } + return entries } diff --git a/java/dexpreopt_test.go b/java/dexpreopt_test.go index 8dc7b798a..1c1070add 100644 --- a/java/dexpreopt_test.go +++ b/java/dexpreopt_test.go @@ -17,6 +17,7 @@ package java import ( "fmt" "runtime" + "strings" "testing" "android/soong/android" @@ -24,11 +25,17 @@ import ( "android/soong/dexpreopt" ) +func init() { + RegisterFakeRuntimeApexMutator() +} + func TestDexpreoptEnabled(t *testing.T) { tests := []struct { - name string - bp string - enabled bool + name string + bp string + moduleName string + apexVariant bool + enabled bool }{ { name: "app", @@ -148,13 +155,81 @@ func TestDexpreoptEnabled(t *testing.T) { }`, enabled: true, }, + { + name: "apex variant", + bp: ` + java_library { + name: "foo", + installable: true, + srcs: ["a.java"], + apex_available: ["com.android.apex1"], + }`, + apexVariant: true, + enabled: false, + }, + { + name: "apex variant of apex system server jar", + bp: ` + java_library { + name: "service-foo", + installable: true, + srcs: ["a.java"], + apex_available: ["com.android.apex1"], + }`, + moduleName: "service-foo", + apexVariant: true, + enabled: true, + }, + { + name: "apex variant of prebuilt apex system server jar", + bp: ` + java_library { + name: "prebuilt_service-foo", + installable: true, + srcs: ["a.java"], + apex_available: ["com.android.apex1"], + }`, + moduleName: "prebuilt_service-foo", + apexVariant: true, + enabled: true, + }, + { + name: "platform variant of apex system server jar", + bp: ` + java_library { + name: "service-foo", + installable: true, + srcs: ["a.java"], + apex_available: ["com.android.apex1"], + }`, + moduleName: "service-foo", + apexVariant: false, + enabled: false, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - ctx, _ := testJava(t, test.bp) + preparers := android.GroupFixturePreparers( + PrepareForTestWithJavaDefaultModules, + PrepareForTestWithFakeApexMutator, + dexpreopt.FixtureSetApexSystemServerJars("com.android.apex1:service-foo"), + ) - dexpreopt := ctx.ModuleForTests("foo", "android_common").MaybeRule("dexpreopt") + result := preparers.RunTestWithBp(t, test.bp) + ctx := result.TestContext + + moduleName := "foo" + if test.moduleName != "" { + moduleName = test.moduleName + } + + variant := "android_common" + if test.apexVariant { + variant += "_apex1000" + } + + dexpreopt := ctx.ModuleForTests(moduleName, variant).MaybeRule("dexpreopt") enabled := dexpreopt.Rule != nil if enabled != test.enabled { @@ -220,3 +295,145 @@ func TestDex2oatToolDeps(t *testing.T) { testDex2oatToolDep(true, true, true, prebuiltDex2oatPath) testDex2oatToolDep(false, true, false, prebuiltDex2oatPath) } + +func TestDexpreoptBuiltInstalledForApex(t *testing.T) { + preparers := android.GroupFixturePreparers( + PrepareForTestWithJavaDefaultModules, + PrepareForTestWithFakeApexMutator, + dexpreopt.FixtureSetApexSystemServerJars("com.android.apex1:service-foo"), + ) + + // An APEX system server jar. + result := preparers.RunTestWithBp(t, ` + java_library { + name: "service-foo", + installable: true, + srcs: ["a.java"], + apex_available: ["com.android.apex1"], + }`) + ctx := result.TestContext + module := ctx.ModuleForTests("service-foo", "android_common_apex1000") + library := module.Module().(*Library) + + installs := library.dexpreopter.DexpreoptBuiltInstalledForApex() + + android.AssertIntEquals(t, "install count", 2, len(installs)) + + android.AssertStringEquals(t, "installs[0] FullModuleName", + "service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.odex", + installs[0].FullModuleName()) + + android.AssertStringEquals(t, "installs[0] SubModuleName", + "-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.odex", + installs[0].SubModuleName()) + + android.AssertStringEquals(t, "installs[1] FullModuleName", + "service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.vdex", + installs[1].FullModuleName()) + + android.AssertStringEquals(t, "installs[1] SubModuleName", + "-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.vdex", + installs[1].SubModuleName()) + + // Not an APEX system server jar. + result = preparers.RunTestWithBp(t, ` + java_library { + name: "foo", + installable: true, + srcs: ["a.java"], + }`) + ctx = result.TestContext + module = ctx.ModuleForTests("foo", "android_common") + library = module.Module().(*Library) + + installs = library.dexpreopter.DexpreoptBuiltInstalledForApex() + + android.AssertIntEquals(t, "install count", 0, len(installs)) +} + +func filterDexpreoptEntriesList(entriesList []android.AndroidMkEntries) []android.AndroidMkEntries { + var results []android.AndroidMkEntries + for _, entries := range entriesList { + if strings.Contains(entries.EntryMap["LOCAL_MODULE"][0], "-dexpreopt-") { + results = append(results, entries) + } + } + return results +} + +func verifyEntries(t *testing.T, message string, expectedModule string, + expectedPrebuiltModuleFile string, expectedModulePath string, expectedInstalledModuleStem string, + entries android.AndroidMkEntries) { + android.AssertStringEquals(t, message+" LOCAL_MODULE", expectedModule, + entries.EntryMap["LOCAL_MODULE"][0]) + + android.AssertStringEquals(t, message+" LOCAL_MODULE_CLASS", "ETC", + entries.EntryMap["LOCAL_MODULE_CLASS"][0]) + + android.AssertStringDoesContain(t, message+" LOCAL_PREBUILT_MODULE_FILE", + entries.EntryMap["LOCAL_PREBUILT_MODULE_FILE"][0], expectedPrebuiltModuleFile) + + android.AssertStringDoesContain(t, message+" LOCAL_MODULE_PATH", + entries.EntryMap["LOCAL_MODULE_PATH"][0], expectedModulePath) + + android.AssertStringEquals(t, message+" LOCAL_INSTALLED_MODULE_STEM", + expectedInstalledModuleStem, entries.EntryMap["LOCAL_INSTALLED_MODULE_STEM"][0]) + + android.AssertStringEquals(t, message+" LOCAL_NOT_AVAILABLE_FOR_PLATFORM", + "false", entries.EntryMap["LOCAL_NOT_AVAILABLE_FOR_PLATFORM"][0]) +} + +func TestAndroidMkEntriesForApex(t *testing.T) { + preparers := android.GroupFixturePreparers( + PrepareForTestWithJavaDefaultModules, + PrepareForTestWithFakeApexMutator, + dexpreopt.FixtureSetApexSystemServerJars("com.android.apex1:service-foo"), + ) + + // An APEX system server jar. + result := preparers.RunTestWithBp(t, ` + java_library { + name: "service-foo", + installable: true, + srcs: ["a.java"], + apex_available: ["com.android.apex1"], + }`) + ctx := result.TestContext + module := ctx.ModuleForTests("service-foo", "android_common_apex1000") + + entriesList := android.AndroidMkEntriesForTest(t, ctx, module.Module()) + entriesList = filterDexpreoptEntriesList(entriesList) + + android.AssertIntEquals(t, "entries count", 2, len(entriesList)) + + verifyEntries(t, + "entriesList[0]", + "service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.odex", + "/dexpreopt/oat/arm64/javalib.odex", + "/system/framework/oat/arm64", + "apex@com.android.apex1@javalib@service-foo.jar@classes.odex", + entriesList[0]) + + verifyEntries(t, + "entriesList[1]", + "service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.vdex", + "/dexpreopt/oat/arm64/javalib.vdex", + "/system/framework/oat/arm64", + "apex@com.android.apex1@javalib@service-foo.jar@classes.vdex", + entriesList[1]) + + // Not an APEX system server jar. + result = preparers.RunTestWithBp(t, ` + java_library { + name: "foo", + installable: true, + srcs: ["a.java"], + }`) + ctx = result.TestContext + module = ctx.ModuleForTests("foo", "android_common") + + entriesList = android.AndroidMkEntriesForTest(t, ctx, module.Module()) + entriesList = filterDexpreoptEntriesList(entriesList) + + android.AssertIntEquals(t, "entries count", 0, len(entriesList)) +} diff --git a/java/java.go b/java/java.go index 1a052b432..e2665ef04 100644 --- a/java/java.go +++ b/java/java.go @@ -487,7 +487,7 @@ func shouldUncompressDex(ctx android.ModuleContext, dexpreopter *dexpreopter) bo } // Store uncompressed dex files that are preopted on /system. - if !dexpreopter.dexpreoptDisabled(ctx) && (ctx.Host() || !odexOnSystemOther(ctx, dexpreopter.installPath)) { + if !dexpreopter.dexpreoptDisabled(ctx) && (ctx.Host() || !dexpreopter.odexOnSystemOther(ctx, dexpreopter.installPath)) { return true } if ctx.Config().UncompressPrivAppDex() && @@ -508,7 +508,8 @@ func (j *Library) GenerateAndroidBuildActions(ctx android.ModuleContext) { } j.checkSdkVersions(ctx) - j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar") + j.dexpreopter.installPath = j.dexpreopter.getInstallPath( + ctx, android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar")) j.dexpreopter.isSDKLibrary = j.deviceProperties.IsSDKLibrary if j.dexProperties.Uncompress_dex == nil { // If the value was not force-set by the user, use reasonable default based on the module. @@ -1368,7 +1369,8 @@ func (j *Import) GenerateAndroidBuildActions(ctx android.ModuleContext) { // Dex compilation - j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", jarName) + j.dexpreopter.installPath = j.dexpreopter.getInstallPath( + ctx, android.PathForModuleInstall(ctx, "framework", jarName)) if j.dexProperties.Uncompress_dex == nil { // If the value was not force-set by the user, use reasonable default based on the module. j.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, &j.dexpreopter)) @@ -1509,7 +1511,7 @@ func (j *Import) IsInstallable() bool { return Bool(j.properties.Installable) } -var _ dexpreopterInterface = (*Import)(nil) +var _ DexpreopterInterface = (*Import)(nil) // java_import imports one or more `.jar` files into the build graph as if they were built by a java_library module. // @@ -1622,7 +1624,8 @@ func (j *DexImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { j.hideApexVariantFromMake = true } - j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar") + j.dexpreopter.installPath = j.dexpreopter.getInstallPath( + ctx, android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar")) j.dexpreopter.uncompressedDex = shouldUncompressDex(ctx, &j.dexpreopter) inputJar := ctx.ExpandSource(j.properties.Jars[0], "jars") diff --git a/java/testing.go b/java/testing.go index 8860b45fa..d8a77cf37 100644 --- a/java/testing.go +++ b/java/testing.go @@ -431,3 +431,45 @@ func CheckMergedCompatConfigInputs(t *testing.T, result *android.TestResult, mes output := sourceGlobalCompatConfig.Output(allOutputs[0]) android.AssertPathsRelativeToTopEquals(t, message+": inputs", expectedPaths, output.Implicits) } + +// Register the fake APEX mutator to `android.InitRegistrationContext` as if the real mutator exists +// at runtime. This must be called in `init()` of a test if the test is going to use the fake APEX +// mutator. Otherwise, we will be missing the runtime mutator because "soong-apex" is not a +// dependency, which will cause an inconsistency between testing and runtime mutators. +func RegisterFakeRuntimeApexMutator() { + registerFakeApexMutator(android.InitRegistrationContext) +} + +var PrepareForTestWithFakeApexMutator = android.GroupFixturePreparers( + android.FixtureRegisterWithContext(registerFakeApexMutator), +) + +func registerFakeApexMutator(ctx android.RegistrationContext) { + ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) { + ctx.BottomUp("apex", fakeApexMutator).Parallel() + }) +} + +type apexModuleBase interface { + ApexAvailable() []string +} + +var _ apexModuleBase = (*Library)(nil) +var _ apexModuleBase = (*SdkLibrary)(nil) + +// A fake APEX mutator that creates a platform variant and an APEX variant for modules with +// `apex_available`. It helps us avoid a dependency on the real mutator defined in "soong-apex", +// which will cause a cyclic dependency, and it provides an easy way to create an APEX variant for +// testing without dealing with all the complexities in the real mutator. +func fakeApexMutator(mctx android.BottomUpMutatorContext) { + switch mctx.Module().(type) { + case *Library, *SdkLibrary: + if len(mctx.Module().(apexModuleBase).ApexAvailable()) > 0 { + modules := mctx.CreateVariations("", "apex1000") + apexInfo := android.ApexInfo{ + ApexVariationName: "apex1000", + } + mctx.SetVariationProvider(modules[1], android.ApexInfoProvider, apexInfo) + } + } +}