Preopt APEX system server jars.
The path to the artifacts will in the form of
/system/framework/oat/<arch>/<encoded-jar-path>@classes.{odex,vdex,art},
where <encoded-jar-path> is the path to the jar file with "/" replaced
by "@". For example,
/system/framework/oat/x86_64/apex@com.android.art@javalib@service-art.jar@classes.odex
There will be a follow-up CL to update ART runtime to recognize
artifacts in that path.
Test: m com.android.art
Bug: 194150908
Change-Id: Ic89fd63c4b1cd565684cead83fc91dae3bc97a4c
diff --git a/java/androidmk.go b/java/androidmk.go
index 68ccd82..71370c9 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -61,7 +61,13 @@
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 0faae36..cdd42ed 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 `<arch>-<encoded-path>.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 @@
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 @@
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 @@
return true
}
- if inList(ctx.ModuleName(), global.DisablePreoptModules) {
+ if inList(moduleName(ctx), global.DisablePreoptModules) {
return true
}
@@ -93,7 +139,7 @@
return true
}
- if !ctx.Module().(dexpreopterInterface).IsInstallable() {
+ if !ctx.Module().(DexpreopterInterface).IsInstallable() {
return true
}
@@ -101,9 +147,17 @@
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 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/<name>` 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 @@
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 @@
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 @@
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 @@
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 8dc7b79..1c1070a 100644
--- a/java/dexpreopt_test.go
+++ b/java/dexpreopt_test.go
@@ -17,6 +17,7 @@
import (
"fmt"
"runtime"
+ "strings"
"testing"
"android/soong/android"
@@ -24,11 +25,17 @@
"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 @@
}`,
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 @@
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 1a052b4..e2665ef 100644
--- a/java/java.go
+++ b/java/java.go
@@ -487,7 +487,7 @@
}
// 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 @@
}
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 @@
// 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 @@
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 @@
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 8860b45..d8a77cf 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -431,3 +431,45 @@
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)
+ }
+ }
+}