From 76b0852a481d3a0c81464e3470294e030b78c897 Mon Sep 17 00:00:00 2001 From: Ulya Trafimovich Date: Thu, 14 Jan 2021 17:52:43 +0000 Subject: [PATCH] Write module dexpreopt.config for Make. This is needed for Java libraries that are dependencies of Java libraries and apps defined as Make modules. Each dexpreopted module in Make generates a dexpreopt.config file, which incorporates information from its dependencies' dexpreopt.config files. For dependencies that are Make modules their dexpreopt.config files are generated by Make, and for Soong modules they are generated by Soong. Since Soong doesn't know which libraries are used by Make, it generates build rules for a superset of the necessary libraries. Bug: 132357300 Test: lunch aosp_cf_x86_phone-userdebug && m Change-Id: I325b1037658736ee3c02450b08c00eca1a175962 --- dexpreopt/class_loader_context.go | 23 +++++++++++++++ dexpreopt/config.go | 37 ++++++++++++++++++++++++ java/androidmk.go | 4 +++ java/app.go | 1 + java/app_import.go | 1 + java/dexpreopt.go | 47 ++++++++++++++++++++++++++++--- java/dexpreopt_test.go | 2 +- 7 files changed, 110 insertions(+), 5 deletions(-) diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go index bf610ef6a..ec62eb3d8 100644 --- a/dexpreopt/class_loader_context.go +++ b/dexpreopt/class_loader_context.go @@ -534,3 +534,26 @@ func fromJsonClassLoaderContextRec(ctx android.PathContext, jClcs map[string]*js } return clcs } + +// Convert Soong CLC map to JSON representation for Make. +func toJsonClassLoaderContext(clcMap ClassLoaderContextMap) jsonClassLoaderContextMap { + jClcMap := make(jsonClassLoaderContextMap) + for sdkVer, clcs := range clcMap { + sdkVerStr := fmt.Sprintf("%d", sdkVer) + jClcMap[sdkVerStr] = toJsonClassLoaderContextRec(clcs) + } + return jClcMap +} + +// Recursive helper for toJsonClassLoaderContext. +func toJsonClassLoaderContextRec(clcs []*ClassLoaderContext) map[string]*jsonClassLoaderContext { + jClcs := make(map[string]*jsonClassLoaderContext, len(clcs)) + for _, clc := range clcs { + jClcs[clc.Name] = &jsonClassLoaderContext{ + Host: clc.Host.String(), + Device: clc.Device, + Subcontexts: toJsonClassLoaderContextRec(clc.Subcontexts), + } + } + return jClcs +} diff --git a/dexpreopt/config.go b/dexpreopt/config.go index 867ece62b..d55204b09 100644 --- a/dexpreopt/config.go +++ b/dexpreopt/config.go @@ -114,6 +114,7 @@ type ModuleConfig struct { ProfileBootListing android.OptionalPath EnforceUsesLibraries bool + ProvidesUsesLibrary string // the name of the (usually the same as its module) ClassLoaderContexts ClassLoaderContextMap Archs []android.ArchType @@ -290,6 +291,42 @@ func ParseModuleConfig(ctx android.PathContext, data []byte) (*ModuleConfig, err return config.ModuleConfig, nil } +// WriteSlimModuleConfigForMake serializes a subset of ModuleConfig into a per-module +// dexpreopt.config JSON file. It is a way to pass dexpreopt information about Soong modules to +// Make, which is needed when a Make module has a dependency on a Soong module. +func WriteSlimModuleConfigForMake(ctx android.ModuleContext, config *ModuleConfig, path android.WritablePath) { + if path == nil { + return + } + + // JSON representation of the slim module dexpreopt.config. + type slimModuleJSONConfig struct { + Name string + DexLocation string + BuildPath string + EnforceUsesLibraries bool + ProvidesUsesLibrary string + ClassLoaderContexts jsonClassLoaderContextMap + } + + jsonConfig := &slimModuleJSONConfig{ + Name: config.Name, + DexLocation: config.DexLocation, + BuildPath: config.BuildPath.String(), + EnforceUsesLibraries: config.EnforceUsesLibraries, + ProvidesUsesLibrary: config.ProvidesUsesLibrary, + ClassLoaderContexts: toJsonClassLoaderContext(config.ClassLoaderContexts), + } + + data, err := json.MarshalIndent(jsonConfig, "", " ") + if err != nil { + ctx.ModuleErrorf("failed to JSON marshal module dexpreopt.config: %v", err) + return + } + + android.WriteFileRule(ctx, path, string(data)) +} + // dex2oatModuleName returns the name of the module to use for the dex2oat host // tool. It should be a binary module with public visibility that is compiled // and installed for host. diff --git a/java/androidmk.go b/java/androidmk.go index cc454b03d..21f3012a4 100644 --- a/java/androidmk.go +++ b/java/androidmk.go @@ -125,6 +125,10 @@ func (library *Library) AndroidMkEntries() []android.AndroidMkEntries { entries.SetString("LOCAL_MODULE_STEM", library.Stem()) entries.SetOptionalPaths("LOCAL_SOONG_LINT_REPORTS", library.linter.reports) + + if library.dexpreopter.configPath != nil { + entries.SetPath("LOCAL_SOONG_DEXPREOPT_CONFIG", library.dexpreopter.configPath) + } }, }, }) diff --git a/java/app.go b/java/app.go index e6c9a2d98..ce89e9bb6 100755 --- a/java/app.go +++ b/java/app.go @@ -455,6 +455,7 @@ func (a *AndroidApp) installPath(ctx android.ModuleContext) android.InstallPath func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) android.Path { a.dexpreopter.installPath = a.installPath(ctx) + a.dexpreopter.isApp = true if a.dexProperties.Uncompress_dex == nil { // If the value was not force-set by the user, use reasonable default based on the module. a.dexProperties.Uncompress_dex = proptools.BoolPtr(a.shouldUncompressDex(ctx)) diff --git a/java/app_import.go b/java/app_import.go index df940f1ff..6f21bfbbf 100644 --- a/java/app_import.go +++ b/java/app_import.go @@ -255,6 +255,7 @@ func (a *AndroidAppImport) generateAndroidBuildActions(ctx android.ModuleContext installDir = android.PathForModuleInstall(ctx, "app", a.BaseModuleName()) } + a.dexpreopter.isApp = true a.dexpreopter.installPath = installDir.Join(ctx, a.BaseModuleName()+".apk") a.dexpreopter.isPresignedPrebuilt = Bool(a.properties.Presigned) a.dexpreopter.uncompressedDex = a.shouldUncompressDex(ctx) diff --git a/java/dexpreopt.go b/java/dexpreopt.go index b5830c744..ac00592a7 100644 --- a/java/dexpreopt.go +++ b/java/dexpreopt.go @@ -30,6 +30,7 @@ type dexpreopter struct { installPath android.InstallPath uncompressedDex bool isSDKLibrary bool + isApp bool isTest bool isPresignedPrebuilt bool @@ -38,6 +39,11 @@ type dexpreopter struct { classLoaderContexts dexpreopt.ClassLoaderContextMap builtInstalled string + + // A path to a dexpreopt.config file generated by Soong for libraries that may be used as a + // by Make modules. The path is passed to Make via LOCAL_SOONG_DEXPREOPT_CONFIG + // variable. If the path is nil, no config is generated (which is the case for apps and tests). + configPath android.WritablePath } type DexpreoptProperties struct { @@ -117,7 +123,40 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Mo // 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 // disabled, even if installable is true. - if d.dexpreoptDisabled(ctx) || d.installPath.Base() == "." { + if d.installPath.Base() == "." { + return + } + + dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath) + + buildPath := android.PathForModuleOut(ctx, "dexpreopt", ctx.ModuleName()+".jar").OutputPath + + providesUsesLib := ctx.ModuleName() + if ulib, ok := ctx.Module().(ProvidesUsesLib); ok { + name := ulib.ProvidesUsesLib() + if name != nil { + providesUsesLib = *name + } + } + + if !d.isApp && !d.isTest { + // Slim dexpreopt config is serialized to dexpreopt.config files and used by + // dex_preopt_config_merger.py to get information about dependencies. + // Note that it might be needed even if dexpreopt is disabled for this module. + slimDexpreoptConfig := &dexpreopt.ModuleConfig{ + Name: ctx.ModuleName(), + DexLocation: dexLocation, + BuildPath: buildPath, + EnforceUsesLibraries: d.enforceUsesLibs, + ProvidesUsesLibrary: providesUsesLib, + ClassLoaderContexts: d.classLoaderContexts, + // The rest of the fields are not needed. + } + d.configPath = android.PathForModuleOut(ctx, "dexpreopt", "dexpreopt.config") + dexpreopt.WriteSlimModuleConfigForMake(ctx, slimDexpreoptConfig, d.configPath) + } + + if d.dexpreoptDisabled(ctx) { return } @@ -157,8 +196,6 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Mo // The image locations for all Android variants are identical. imageLocations := bootImage.getAnyAndroidVariant().imageLocations() - dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath) - var profileClassListing android.OptionalPath var profileBootListing android.OptionalPath profileIsTextListing := false @@ -177,10 +214,11 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Mo } } + // Full dexpreopt config, used to create dexpreopt build rules. dexpreoptConfig := &dexpreopt.ModuleConfig{ Name: ctx.ModuleName(), DexLocation: dexLocation, - BuildPath: android.PathForModuleOut(ctx, "dexpreopt", ctx.ModuleName()+".jar").OutputPath, + BuildPath: buildPath, DexPath: dexJarFile, ManifestPath: d.manifestFile, UncompressedDex: d.uncompressedDex, @@ -192,6 +230,7 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Mo ProfileBootListing: profileBootListing, EnforceUsesLibraries: d.enforceUsesLibs, + ProvidesUsesLibrary: providesUsesLib, ClassLoaderContexts: d.classLoaderContexts, Archs: archs, diff --git a/java/dexpreopt_test.go b/java/dexpreopt_test.go index 5550a4c17..a9e0773b7 100644 --- a/java/dexpreopt_test.go +++ b/java/dexpreopt_test.go @@ -148,7 +148,7 @@ func TestDexpreoptEnabled(t *testing.T) { t.Run(test.name, func(t *testing.T) { ctx, _ := testJava(t, test.bp) - dexpreopt := ctx.ModuleForTests("foo", "android_common").MaybeDescription("dexpreopt") + dexpreopt := ctx.ModuleForTests("foo", "android_common").MaybeRule("dexpreopt") enabled := dexpreopt.Rule != nil if enabled != test.enabled {