diff options
-rw-r--r-- | Android.bp | 2 | ||||
-rw-r--r-- | android/config.go | 24 | ||||
-rw-r--r-- | android/paths.go | 94 | ||||
-rw-r--r-- | android/paths_test.go | 57 | ||||
-rw-r--r-- | android/variable.go | 9 | ||||
-rw-r--r-- | dexpreopt/Android.bp | 15 | ||||
-rw-r--r-- | dexpreopt/config.go | 141 | ||||
-rw-r--r-- | dexpreopt/dexpreopt.go | 541 | ||||
-rw-r--r-- | dexpreopt/dexpreopt_gen/Android.bp | 11 | ||||
-rw-r--r-- | dexpreopt/dexpreopt_gen/dexpreopt_gen.go | 190 | ||||
-rw-r--r-- | dexpreopt/dexpreopt_test.go | 208 | ||||
-rw-r--r-- | dexpreopt/script.go | 173 | ||||
-rw-r--r-- | java/aar.go | 1 | ||||
-rw-r--r-- | java/androidmk.go | 29 | ||||
-rw-r--r-- | java/app.go | 72 | ||||
-rw-r--r-- | java/builder.go | 20 | ||||
-rw-r--r-- | java/config/config.go | 3 | ||||
-rw-r--r-- | java/config/makevars.go | 1 | ||||
-rw-r--r-- | java/dexpreopt.go | 250 | ||||
-rw-r--r-- | java/java.go | 45 | ||||
-rw-r--r-- | java/sdk_library.go | 13 |
21 files changed, 1776 insertions, 123 deletions
@@ -224,6 +224,7 @@ bootstrap_go_package { "soong", "soong-android", "soong-cc", + "soong-dexpreopt", "soong-genrule", "soong-java-config", "soong-tradefed", @@ -238,6 +239,7 @@ bootstrap_go_package { "java/app.go", "java/builder.go", "java/dex.go", + "java/dexpreopt.go", "java/droiddoc.go", "java/gen.go", "java/genrule.go", diff --git a/android/config.go b/android/config.go index 54c9da8b..a0954b67 100644 --- a/android/config.go +++ b/android/config.go @@ -732,14 +732,18 @@ func (c *config) ModulesLoadedByPrivilegedModules() []string { return c.productVariables.ModulesLoadedByPrivilegedModules } -func (c *config) DefaultStripDex() bool { - return Bool(c.productVariables.DefaultStripDex) -} - func (c *config) DisableDexPreopt(name string) bool { return Bool(c.productVariables.DisableDexPreopt) || InList(name, c.productVariables.DisableDexPreoptModules) } +func (c *config) DexpreoptGlobalConfig() string { + return String(c.productVariables.DexpreoptGlobalConfig) +} + +func (c *config) DexPreoptProfileDir() string { + return String(c.productVariables.DexPreoptProfileDir) +} + func (c *deviceConfig) Arches() []Arch { var arches []Arch for _, target := range c.config.Targets[Android] { @@ -854,6 +858,18 @@ func (c *deviceConfig) PlatPrivateSepolicyDirs() []string { return c.config.productVariables.BoardPlatPrivateSepolicyDirs } +func (c *config) SecondArchIsTranslated() bool { + deviceTargets := c.Targets[Android] + if len(deviceTargets) < 2 { + return false + } + + arch := deviceTargets[0].Arch + + return (arch.ArchType == X86 || arch.ArchType == X86_64) && + (hasArmAbi(arch) || hasArmAndroidArch(deviceTargets)) +} + func (c *config) IntegerOverflowDisabledForPath(path string) bool { if c.productVariables.IntegerOverflowExcludePaths == nil { return false diff --git a/android/paths.go b/android/paths.go index b22e3c7d..13b31c78 100644 --- a/android/paths.go +++ b/android/paths.go @@ -659,11 +659,7 @@ func (p SourcePath) OverlayPath(ctx ModuleContext, path Path) OptionalPath { if len(paths) == 0 { return OptionalPath{} } - relPath, err := filepath.Rel(p.config.srcDir, paths[0]) - if err != nil { - reportPathError(ctx, err) - return OptionalPath{} - } + relPath := Rel(ctx, p.config.srcDir, paths[0]) return OptionalPathForPath(PathForSource(ctx, relPath)) } @@ -788,13 +784,7 @@ func (p ModuleSrcPath) resPathWithName(ctx ModuleContext, name string) ModuleRes func (p ModuleSrcPath) WithSubDir(ctx ModuleContext, subdir string) ModuleSrcPath { subdir = PathForModuleSrc(ctx, subdir).String() - var err error - rel, err := filepath.Rel(subdir, p.path) - if err != nil { - ctx.ModuleErrorf("source file %q is not under path %q", p.path, subdir) - return p - } - p.rel = rel + p.rel = Rel(ctx, subdir, p.path) return p } @@ -932,27 +922,7 @@ func PathForModuleRes(ctx ModuleContext, pathComponents ...string) ModuleResPath func PathForModuleInstall(ctx ModuleInstallPathContext, pathComponents ...string) OutputPath { var outPaths []string if ctx.Device() { - var partition string - if ctx.InstallInData() { - partition = "data" - } else if ctx.InstallInRecovery() { - // the layout of recovery partion is the same as that of system partition - partition = "recovery/root/system" - } else if ctx.SocSpecific() { - partition = ctx.DeviceConfig().VendorPath() - } else if ctx.DeviceSpecific() { - partition = ctx.DeviceConfig().OdmPath() - } else if ctx.ProductSpecific() { - partition = ctx.DeviceConfig().ProductPath() - } else if ctx.ProductServicesSpecific() { - partition = ctx.DeviceConfig().ProductServicesPath() - } else { - partition = "system" - } - - if ctx.InstallInSanitizerDir() { - partition = "data/asan/" + partition - } + partition := modulePartition(ctx) outPaths = []string{"target", "product", ctx.Config().DeviceName(), partition} } else { switch ctx.Os() { @@ -972,6 +942,36 @@ func PathForModuleInstall(ctx ModuleInstallPathContext, pathComponents ...string return PathForOutput(ctx, outPaths...) } +func InstallPathToOnDevicePath(ctx PathContext, path OutputPath) string { + rel := Rel(ctx, PathForOutput(ctx, "target", "product", ctx.Config().DeviceName()).String(), path.String()) + + return "/" + rel +} + +func modulePartition(ctx ModuleInstallPathContext) string { + var partition string + if ctx.InstallInData() { + partition = "data" + } else if ctx.InstallInRecovery() { + // the layout of recovery partion is the same as that of system partition + partition = "recovery/root/system" + } else if ctx.SocSpecific() { + partition = ctx.DeviceConfig().VendorPath() + } else if ctx.DeviceSpecific() { + partition = ctx.DeviceConfig().OdmPath() + } else if ctx.ProductSpecific() { + partition = ctx.DeviceConfig().ProductPath() + } else if ctx.ProductServicesSpecific() { + partition = ctx.DeviceConfig().ProductServicesPath() + } else { + partition = "system" + } + if ctx.InstallInSanitizerDir() { + partition = "data/asan/" + partition + } + return partition +} + // validateSafePath validates a path that we trust (may contain ninja variables). // Ensures that each path component does not attempt to leave its component. func validateSafePath(pathComponents ...string) (string, error) { @@ -1039,3 +1039,31 @@ func PathsForTesting(strs []string) Paths { return p } + +// Rel performs the same function as filepath.Rel, but reports errors to a PathContext, and reports an error if +// targetPath is not inside basePath. +func Rel(ctx PathContext, basePath string, targetPath string) string { + rel, isRel := MaybeRel(ctx, basePath, targetPath) + if !isRel { + reportPathErrorf(ctx, "path %q is not under path %q", targetPath, basePath) + return "" + } + return rel +} + +// MaybeRel performs the same function as filepath.Rel, but reports errors to a PathContext, and returns false if +// targetPath is not inside basePath. +func MaybeRel(ctx PathContext, basePath string, targetPath string) (string, bool) { + // filepath.Rel returns an error if one path is absolute and the other is not, handle that case first. + if filepath.IsAbs(basePath) != filepath.IsAbs(targetPath) { + return "", false + } + rel, err := filepath.Rel(basePath, targetPath) + if err != nil { + reportPathError(ctx, err) + return "", false + } else if rel == ".." || strings.HasPrefix(rel, "../") || strings.HasPrefix(rel, "/") { + return "", false + } + return rel, true +} diff --git a/android/paths_test.go b/android/paths_test.go index fbeccb1c..c4332d26 100644 --- a/android/paths_test.go +++ b/android/paths_test.go @@ -573,3 +573,60 @@ func TestDirectorySortedPaths(t *testing.T) { t.Errorf("FilesInDirectory(b):\n %#v\n != \n %#v", inA.Strings(), expectedA) } } + +func TestMaybeRel(t *testing.T) { + testCases := []struct { + name string + base string + target string + out string + isRel bool + }{ + { + name: "normal", + base: "a/b/c", + target: "a/b/c/d", + out: "d", + isRel: true, + }, + { + name: "parent", + base: "a/b/c/d", + target: "a/b/c", + isRel: false, + }, + { + name: "not relative", + base: "a/b", + target: "c/d", + isRel: false, + }, + { + name: "abs1", + base: "/a", + target: "a", + isRel: false, + }, + { + name: "abs2", + base: "a", + target: "/a", + isRel: false, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + ctx := &configErrorWrapper{} + out, isRel := MaybeRel(ctx, testCase.base, testCase.target) + if len(ctx.errors) > 0 { + t.Errorf("MaybeRel(..., %s, %s) reported unexpected errors %v", + testCase.base, testCase.target, ctx.errors) + } + if isRel != testCase.isRel || out != testCase.out { + t.Errorf("MaybeRel(..., %s, %s) want %v, %v got %v, %v", + testCase.base, testCase.target, testCase.out, testCase.isRel, out, isRel) + } + }) + } +} diff --git a/android/variable.go b/android/variable.go index f4960086..85937e3b 100644 --- a/android/variable.go +++ b/android/variable.go @@ -196,9 +196,10 @@ type productVariables struct { UncompressPrivAppDex *bool `json:",omitempty"` ModulesLoadedByPrivilegedModules []string `json:",omitempty"` - DefaultStripDex *bool `json:",omitempty"` - DisableDexPreopt *bool `json:",omitempty"` - DisableDexPreoptModules []string `json:",omitempty"` + + DisableDexPreopt *bool `json:",omitempty"` + DisableDexPreoptModules []string `json:",omitempty"` + DexPreoptProfileDir *string `json:",omitempty"` IntegerOverflowExcludePaths *[]string `json:",omitempty"` @@ -257,6 +258,8 @@ type productVariables struct { Exclude_draft_ndk_apis *bool `json:",omitempty"` FlattenApex *bool `json:",omitempty"` + + DexpreoptGlobalConfig *string `json:",omitempty"` } func boolPtr(v bool) *bool { diff --git a/dexpreopt/Android.bp b/dexpreopt/Android.bp new file mode 100644 index 00000000..b8325298 --- /dev/null +++ b/dexpreopt/Android.bp @@ -0,0 +1,15 @@ +bootstrap_go_package { + name: "soong-dexpreopt", + pkgPath: "android/soong/dexpreopt", + srcs: [ + "config.go", + "dexpreopt.go", + "script.go", + ], + testSrcs: [ + "dexpreopt_test.go", + ], + deps: [ + "blueprint-pathtools", + ], +}
\ No newline at end of file diff --git a/dexpreopt/config.go b/dexpreopt/config.go new file mode 100644 index 00000000..6a4fd4a0 --- /dev/null +++ b/dexpreopt/config.go @@ -0,0 +1,141 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dexpreopt + +import ( + "encoding/json" + "io/ioutil" +) + +// GlobalConfig stores the configuration for dex preopting set by the product +type GlobalConfig struct { + DefaultNoStripping bool // don't strip dex files by default + + DisablePreoptModules []string // modules with preopt disabled by product-specific config + + OnlyPreoptBootImageAndSystemServer bool // only preopt jars in the boot image or system server + + HasSystemOther bool // store odex files that match PatternsOnSystemOther on the system_other partition + PatternsOnSystemOther []string // patterns (using '%' to denote a prefix match) to put odex on the system_other partition + + DisableGenerateProfile bool // don't generate profiles + + BootJars []string // jars that form the boot image + SystemServerJars []string // jars that form the system server + SystemServerApps []string // apps that are loaded into system server + SpeedApps []string // apps that should be speed optimized + + PreoptFlags []string // global dex2oat flags that should be used if no module-specific dex2oat flags are specified + + DefaultCompilerFilter string // default compiler filter to pass to dex2oat, overridden by --compiler-filter= in module-specific dex2oat flags + SystemServerCompilerFilter string // default compiler filter to pass to dex2oat for system server jars + + GenerateDMFiles bool // generate Dex Metadata files + + NoDebugInfo bool // don't generate debug info by default + AlwaysSystemServerDebugInfo bool // always generate mini debug info for system server modules (overrides NoDebugInfo=true) + NeverSystemServerDebugInfo bool // never generate mini debug info for system server modules (overrides NoDebugInfo=false) + AlwaysOtherDebugInfo bool // always generate mini debug info for non-system server modules (overrides NoDebugInfo=true) + NeverOtherDebugInfo bool // never generate mini debug info for non-system server modules (overrides NoDebugInfo=true) + + MissingUsesLibraries []string // libraries that may be listed in OptionalUsesLibraries but will not be installed by the product + + IsEng bool // build is a eng variant + SanitizeLite bool // build is the second phase of a SANITIZE_LITE build + + DefaultAppImages bool // build app images (TODO: .art files?) by default + + Dex2oatXmx string // max heap size + Dex2oatXms string // initial heap size + + EmptyDirectory string // path to an empty directory + + DefaultDexPreoptImageLocation map[string]string // default boot image location for each architecture + CpuVariant map[string]string // cpu variant for each architecture + InstructionSetFeatures map[string]string // instruction set for each architecture + + Tools Tools // paths to tools possibly used by the generated commands +} + +// Tools contains paths to tools possibly used by the generated commands. If you add a new tool here you MUST add it +// to the order-only dependency list in DEXPREOPT_GEN_DEPS. +type Tools struct { + Profman string + Dex2oat string + Aapt string + SoongZip string + Zip2zip string + + VerifyUsesLibraries string + ConstructContext string +} + +type ModuleConfig struct { + Name string + DexLocation string // dex location on device + BuildPath string + DexPath string + PreferIntegrity bool + UncompressedDex bool + HasApkLibraries bool + PreoptFlags []string + + ProfileClassListing string + ProfileIsTextListing bool + + EnforceUsesLibraries bool + OptionalUsesLibraries []string + UsesLibraries []string + LibraryPaths map[string]string + + Archs []string + DexPreoptImageLocation string + + PreoptExtractedApk bool // Overrides OnlyPreoptModules + + NoCreateAppImage bool + ForceCreateAppImage bool + + PresignedPrebuilt bool + + StripInputPath string + StripOutputPath string +} + +func LoadGlobalConfig(path string) (GlobalConfig, error) { + config := GlobalConfig{} + err := loadConfig(path, &config) + return config, err +} + +func LoadModuleConfig(path string) (ModuleConfig, error) { + config := ModuleConfig{} + err := loadConfig(path, &config) + return config, err +} + +func loadConfig(path string, config interface{}) error { + data, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + err = json.Unmarshal(data, config) + if err != nil { + return err + } + + return nil +} diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go new file mode 100644 index 00000000..0c2df6e5 --- /dev/null +++ b/dexpreopt/dexpreopt.go @@ -0,0 +1,541 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The dexpreopt package converts a global dexpreopt config and a module dexpreopt config into rules to perform +// dexpreopting and to strip the dex files from the APK or JAR. +// +// It is used in two places; in the dexpeopt_gen binary for modules defined in Make, and directly linked into Soong. +// +// For Make modules it is built into the dexpreopt_gen binary, which is executed as a Make rule using global config and +// module config specified in JSON files. The binary writes out two shell scripts, only updating them if they have +// changed. One script takes an APK or JAR as an input and produces a zip file containing any outputs of preopting, +// in the location they should be on the device. The Make build rules will unzip the zip file into $(PRODUCT_OUT) when +// installing the APK, which will install the preopt outputs into $(PRODUCT_OUT)/system or $(PRODUCT_OUT)/system_other +// as necessary. The zip file may be empty if preopting was disabled for any reason. The second script takes an APK or +// JAR as an input and strips the dex files in it as necessary. +// +// The intermediate shell scripts allow changes to this package or to the global config to regenerate the shell scripts +// but only require re-executing preopting if the script has changed. +// +// For Soong modules this package is linked directly into Soong and run from the java package. It generates the same +// commands as for make, using athe same global config JSON file used by make, but using a module config structure +// provided by Soong. The generated commands are then converted into Soong rule and written directly to the ninja file, +// with no extra shell scripts involved. +package dexpreopt + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/google/blueprint/pathtools" +) + +const SystemPartition = "/system/" +const SystemOtherPartition = "/system_other/" + +// GenerateStripRule generates a set of commands that will take an APK or JAR as an input and strip the dex files if +// they are no longer necessary after preopting. +func GenerateStripRule(global GlobalConfig, module ModuleConfig) (rule *Rule, err error) { + defer func() { + if r := recover(); r != nil { + if e, ok := r.(error); ok { + err = e + rule = nil + } else { + panic(r) + } + } + }() + + tools := global.Tools + + rule = &Rule{} + + strip := shouldStripDex(module, global) + + if strip { + // Only strips if the dex files are not already uncompressed + rule.Command(). + Textf(`if (zipinfo %s '*.dex' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, module.StripInputPath). + Tool(tools.Zip2zip).FlagWithInput("-i ", module.StripInputPath).FlagWithOutput("-o ", module.StripOutputPath). + FlagWithArg("-x ", `"classes*.dex"`). + Textf(`; else cp -f %s %s; fi`, module.StripInputPath, module.StripOutputPath) + } else { + rule.Command().Text("cp -f").Input(module.StripInputPath).Output(module.StripOutputPath) + } + + return rule, nil +} + +// GenerateDexpreoptRule generates a set of commands that will preopt a module based on a GlobalConfig and a +// ModuleConfig. The produced files and their install locations will be available through rule.Installs(). +func GenerateDexpreoptRule(global GlobalConfig, module ModuleConfig) (rule *Rule, err error) { + defer func() { + if r := recover(); r != nil { + if e, ok := r.(error); ok { + err = e + rule = nil + } else { + panic(r) + } + } + }() + + rule = &Rule{} + + dexpreoptDisabled := contains(global.DisablePreoptModules, module.Name) + + if contains(global.BootJars, module.Name) { + // Don't preopt individual boot jars, they will be preopted together + dexpreoptDisabled = 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 && !contains(global.BootJars, module.Name) && + !contains(global.SystemServerJars, module.Name) && !module.PreoptExtractedApk { + dexpreoptDisabled = true + } + + generateProfile := module.ProfileClassListing != "" && !global.DisableGenerateProfile + + var profile string + if generateProfile { + profile = profileCommand(global, module, rule) + } + + if !dexpreoptDisabled { + appImage := (generateProfile || module.ForceCreateAppImage || global.DefaultAppImages) && + !module.NoCreateAppImage + + generateDM := shouldGenerateDM(module, global) + + for _, arch := range module.Archs { + imageLocation := module.DexPreoptImageLocation + if imageLocation == "" { + imageLocation = global.DefaultDexPreoptImageLocation[arch] + } + dexpreoptCommand(global, module, rule, profile, arch, imageLocation, appImage, generateDM) + } + } + + return rule, nil +} + +func profileCommand(global GlobalConfig, module ModuleConfig, rule *Rule) string { + profilePath := filepath.Join(filepath.Dir(module.BuildPath), "profile.prof") + profileInstalledPath := module.DexLocation + ".prof" + + if !module.ProfileIsTextListing { + rule.Command().FlagWithOutput("touch ", profilePath) + } + + cmd := rule.Command(). + Text(`ANDROID_LOG_TAGS="*:e"`). + Tool(global.Tools.Profman) + + if module.ProfileIsTextListing { + // The profile is a test listing of classes (used for framework jars). + // We need to generate the actual binary profile before being able to compile. + cmd.FlagWithInput("--create-profile-from=", module.ProfileClassListing) + } else { + // The profile is binary profile (used for apps). Run it through profman to + // ensure the profile keys match the apk. + cmd. + Flag("--copy-and-update-profile-key"). + FlagWithInput("--profile-file=", module.ProfileClassListing) + } + + cmd. + FlagWithInput("--apk=", module.DexPath). + Flag("--dex-location="+module.DexLocation). + FlagWithOutput("--reference-profile-file=", profilePath) + + if !module.ProfileIsTextListing { + cmd.Text(fmt.Sprintf(`|| echo "Profile out of date for %s"`, module.DexPath)) + } + rule.Install(profilePath, profileInstalledPath) + + return profilePath +} + +func dexpreoptCommand(global GlobalConfig, module ModuleConfig, rule *Rule, profile, arch, bootImageLocation string, + appImage, generateDM bool) { + + // HACK: make soname in Soong-generated .odex files match Make. + base := filepath.Base(module.DexLocation) + if filepath.Ext(base) == ".jar" { + base = "javalib.jar" + } else if filepath.Ext(base) == ".apk" { + base = "package.apk" + } + + toOdexPath := func(path string) string { + return filepath.Join( + filepath.Dir(path), + "oat", + arch, + pathtools.ReplaceExtension(filepath.Base(path), "odex")) + } + + odexPath := toOdexPath(filepath.Join(filepath.Dir(module.BuildPath), base)) + odexInstallPath := toOdexPath(module.DexLocation) + if odexOnSystemOther(module, global) { + odexInstallPath = strings.Replace(odexInstallPath, SystemPartition, SystemOtherPartition, 1) + } + + vdexPath := pathtools.ReplaceExtension(odexPath, "vdex") + vdexInstallPath := pathtools.ReplaceExtension(odexInstallPath, "vdex") + + // bootImageLocation is $OUT/dex_bootjars/system/framework/boot.art, but dex2oat actually reads + // $OUT/dex_bootjars/system/framework/arm64/boot.art + var bootImagePath string + if bootImageLocation != "" { + bootImagePath = filepath.Join(filepath.Dir(bootImageLocation), arch, filepath.Base(bootImageLocation)) + } + + // Lists of used and optional libraries from the build config to be verified against the manifest in the APK + var verifyUsesLibs []string + var verifyOptionalUsesLibs []string + + // Lists of used and optional libraries from the build config, with optional libraries that are known to not + // be present in the current product removed. + var filteredUsesLibs []string + var filteredOptionalUsesLibs []string + + // The class loader context using paths in the build + var classLoaderContextHost []string + + // The class loader context using paths as they will be on the device + var classLoaderContextTarget []string + + // Extra paths that will be appended to the class loader if the APK manifest has targetSdkVersion < 28 + var conditionalClassLoaderContextHost []string + var conditionalClassLoaderContextTarget []string + + if module.EnforceUsesLibraries { + verifyUsesLibs = copyOf(module.UsesLibraries) + verifyOptionalUsesLibs = copyOf(module.OptionalUsesLibraries) + + filteredOptionalUsesLibs = filterOut(global.MissingUsesLibraries, module.OptionalUsesLibraries) + filteredUsesLibs = append(copyOf(module.UsesLibraries), filteredOptionalUsesLibs...) + + // Create class loader context for dex2oat from uses libraries and filtered optional libraries + for _, l := range filteredUsesLibs { + + classLoaderContextHost = append(classLoaderContextHost, + pathForLibrary(module, l)) + classLoaderContextTarget = append(classLoaderContextTarget, + filepath.Join("/system/framework", l+".jar")) + } + + const httpLegacy = "org.apache.http.legacy" + const httpLegacyImpl = "org.apache.http.legacy.impl" + + // Fix up org.apache.http.legacy.impl since it should be org.apache.http.legacy in the manifest. + replace(verifyUsesLibs, httpLegacyImpl, httpLegacy) + replace(verifyOptionalUsesLibs, httpLegacyImpl, httpLegacy) + + if !contains(verifyUsesLibs, httpLegacy) && !contains(verifyOptionalUsesLibs, httpLegacy) { + conditionalClassLoaderContextHost = append(conditionalClassLoaderContextHost, + pathForLibrary(module, httpLegacyImpl)) + conditionalClassLoaderContextTarget = append(conditionalClassLoaderContextTarget, + filepath.Join("/system/framework", httpLegacyImpl+".jar")) + } + } else { + // Pass special class loader context to skip the classpath and collision check. + // This will get removed once LOCAL_USES_LIBRARIES is enforced. + // Right now LOCAL_USES_LIBRARIES is opt in, for the case where it's not specified we still default + // to the &. + classLoaderContextHost = []string{`\&`} + } + + rule.Command().FlagWithArg("mkdir -p ", filepath.Dir(odexPath)) + rule.Command().FlagWithOutput("rm -f ", odexPath) + // Set values in the environment of the rule. These may be modified by construct_context.sh. + rule.Command().FlagWithArg("class_loader_context_arg=--class-loader-context=", + strings.Join(classLoaderContextHost, ":")) + rule.Command().Text(`stored_class_loader_context_arg=""`) + + if module.EnforceUsesLibraries { + rule.Command().FlagWithList("stored_class_loader_context_libs=", classLoaderContextTarget, ":") + rule.Command().FlagWithInputList("class_loader_context=", classLoaderContextHost, ":") + rule.Command().Textf(`uses_library_names="%s"`, strings.Join(verifyUsesLibs, " ")) + rule.Command().Textf(`optional_uses_library_names="%s"`, strings.Join(verifyOptionalUsesLibs, " ")) + rule.Command().Textf(`aapt_binary="%s"`, global.Tools.Aapt) + rule.Command().Text("source").Tool(global.Tools.VerifyUsesLibraries).Input(module.DexPath) + rule.Command().Text("source").Tool(global.Tools.ConstructContext). + Textf(`"%s"`, strings.Join(conditionalClassLoaderContextHost, ":")). + Textf(`"%s"`, strings.Join(conditionalClassLoaderContextTarget, ":")) + } + + cmd := rule.Command(). + Text(`ANDROID_LOG_TAGS="*:e"`). + Tool(global.Tools.Dex2oat). + Flag("--avoid-storing-invocation"). + Flag("--runtime-arg").FlagWithArg("-Xms", global.Dex2oatXms). + Flag("--runtime-arg").FlagWithArg("-Xmx", global.Dex2oatXmx). + Flag("${class_loader_context_arg}"). + Flag("${stored_class_loader_context_arg}"). + FlagWithArg("--boot-image=", bootImageLocation).Implicit(bootImagePath). + FlagWithInput("--dex-file=", module.DexPath). + FlagWithArg("--dex-location=", module.DexLocation). + FlagWithOutput("--oat-file=", odexPath).ImplicitOutput(vdexPath). + // Pass an empty directory, dex2oat shouldn't be reading arbitrary files + FlagWithArg("--android-root=", global.EmptyDirectory). + FlagWithArg("--instruction-set=", arch). + FlagWithArg("--instruction-set-variant=", global.CpuVariant[arch]). + FlagWithArg("--instruction-set-features=", global.InstructionSetFeatures[arch]). + Flag("--no-generate-debug-info"). + Flag("--generate-build-id"). + Flag("--abort-on-hard-verifier-error"). + Flag("--force-determinism"). + FlagWithArg("--no-inline-from=", "core-oj.jar") + + var preoptFlags []string + if len(module.PreoptFlags) > 0 { + preoptFlags = module.PreoptFlags + } else if len(global.PreoptFlags) > 0 { + preoptFlags = global.PreoptFlags + } + + if len(preoptFlags) > 0 { + cmd.Text(strings.Join(preoptFlags, " ")) + } + + if module.UncompressedDex { + cmd.FlagWithArg("--copy-dex-files=", "false") + } + + if !anyHavePrefix(preoptFlags, "--compiler-filter=") { + var compilerFilter string + if contains(global.SystemServerJars, module.Name) { + // Jars of system server, use the product option if it is set, speed otherwise. + if global.SystemServerCompilerFilter != "" { + compilerFilter = global.SystemServerCompilerFilter + } else { + compilerFilter = "speed" + } + } else if contains(global.SpeedApps, module.Name) || contains(global.SystemServerApps, module.Name) { + // Apps loaded into system server, and apps the product default to being compiled with the + // 'speed' compiler filter. + compilerFilter = "speed" + } else if profile != "" { + // For non system server jars, use speed-profile when we have a profile. + compilerFilter = "speed-profile" + } else if global.DefaultCompilerFilter != "" { + compilerFilter = global.DefaultCompilerFilter + } else { + compilerFilter = "quicken" + } + cmd.FlagWithArg("--compiler-filter=", compilerFilter) + } + + if generateDM { + cmd.FlagWithArg("--copy-dex-files=", "false") + dmPath := filepath.Join(filepath.Dir(module.BuildPath), "generated.dm") + dmInstalledPath := pathtools.ReplaceExtension(module.DexLocation, "dm") + tmpPath := filepath.Join(filepath.Dir(module.BuildPath), "primary.vdex") + rule.Command().Text("cp -f").Input(vdexPath).Output(tmpPath) + rule.Command().Tool(global.Tools.SoongZip). + FlagWithArg("-L", "9"). + FlagWithOutput("-o", dmPath). + Flag("-j"). + Input(tmpPath) + rule.Install(dmPath, dmInstalledPath) + } + + // By default, emit debug info. + debugInfo := true + if global.NoDebugInfo { + // If the global setting suppresses mini-debug-info, disable it. + debugInfo = false + } + + // PRODUCT_SYSTEM_SERVER_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO. + // PRODUCT_OTHER_JAVA_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO. + if contains(global.SystemServerJars, module.Name) { + if global.AlwaysSystemServerDebugInfo { + debugInfo = true + } else if global.NeverSystemServerDebugInfo { + debugInfo = false + } + } else { + if global.AlwaysOtherDebugInfo { + debugInfo = true + } else if global.NeverOtherDebugInfo { + debugInfo = false + } + } + + // Never enable on eng. + if global.IsEng { + debugInfo = false + } + + if debugInfo { + cmd.Flag("--generate-mini-debug-info") + } else { + cmd.Flag("--no-generate-mini-debug-info") + } + + // Set the compiler reason to 'prebuilt' to identify the oat files produced + // during the build, as opposed to compiled on the device. + cmd.FlagWithArg("--compilation-reason=", "prebuilt") + + if appImage { + appImagePath := pathtools.ReplaceExtension(odexPath, "art") + appImageInstallPath := pathtools.ReplaceExtension(odexInstallPath, "art") + cmd.FlagWithOutput("--app-image-file=", appImagePath). + FlagWithArg("--image-format=", "lz4") + rule.Install(appImagePath, appImageInstallPath) + } + + if profile != "" { + cmd.FlagWithArg("--profile-file=", profile) + } + + rule.Install(odexPath, odexInstallPath) + rule.Install(vdexPath, vdexInstallPath) +} + +// Return if the dex file in the APK should be stripped. If an APK is found to contain uncompressed dex files at +// dex2oat time it will not be stripped even if strip=true. +func shouldStripDex(module ModuleConfig, global GlobalConfig) bool { + strip := !global.DefaultNoStripping + + // Don't strip modules that are not on the system partition in case the oat/vdex version in system ROM + // doesn't match the one in other partitions. It needs to be able to fall back to the APK for that case. + if !strings.HasPrefix(module.DexLocation, SystemPartition) { + strip = false + } + + // system_other isn't there for an OTA, so don't strip if module is on system, and odex is on system_other. + if odexOnSystemOther(module, global) { + strip = false + } + + if module.HasApkLibraries { + strip = false + } + + // Don't strip with dex files we explicitly uncompress (dexopt will not store the dex code). + if module.UncompressedDex { + strip = false + } + + if shouldGenerateDM(module, global) { + strip = false + } + + if module.PresignedPrebuilt { + // Only strip out files if we can re-sign the package. + strip = false + } + + return strip +} + +func shouldGenerateDM(module ModuleConfig, global GlobalConfig) bool { + // Generating DM files only makes sense for verify, avoid doing for non verify compiler filter APKs. + // No reason to use a dm file if the dex is already uncompressed. + return global.GenerateDMFiles && !module.UncompressedDex && + contains(module.PreoptFlags, "--compiler-filter=verify") +} + +func odexOnSystemOther(module ModuleConfig, global GlobalConfig) bool { + if !global.HasSystemOther { + return false + } + + if global.SanitizeLite { + return false + } + + if contains(global.SpeedApps, module.Name) || contains(global.SystemServerApps, module.Name) { + return false + } + + for _, f := range global.PatternsOnSystemOther { + if makefileMatch(filepath.Join(SystemPartition, f), module.DexLocation) { + return true + } + } + + return false +} + +func pathForLibrary(module ModuleConfig, lib string) string { + path := module.LibraryPaths[lib] + if path == "" { + panic(fmt.Errorf("unknown library path for %q", lib)) + } + return path +} + +func makefileMatch(pattern, s string) bool { + percent := strings.IndexByte(pattern, '%') + switch percent { + case -1: + return pattern == s + case len(pattern) - 1: + return strings.HasPrefix(s, pattern[:len(pattern)-1]) + default: + panic(fmt.Errorf("unsupported makefile pattern %q", pattern)) + } +} + +func contains(l []string, s string) bool { + for _, e := range l { + if e == s { + return true + } + } + return false +} + +// remove all elements in a from b, returning a new slice +func filterOut(a []string, b []string) []string { + var ret []string + for _, x := range b { + if !contains(a, x) { + ret = append(ret, x) + } + } + return ret +} + +func replace(l []string, from, to string) { + for i := range l { + if l[i] == from { + l[i] = to + } + } +} + +func copyOf(l []string) []string { + return append([]string(nil), l...) +} + +func anyHavePrefix(l []string, prefix string) bool { + for _, x := range l { + if strings.HasPrefix(x, prefix) { + return true + } + } + return false +} diff --git a/dexpreopt/dexpreopt_gen/Android.bp b/dexpreopt/dexpreopt_gen/Android.bp new file mode 100644 index 00000000..07903910 --- /dev/null +++ b/dexpreopt/dexpreopt_gen/Android.bp @@ -0,0 +1,11 @@ +blueprint_go_binary { + name: "dexpreopt_gen", + srcs: [ + "dexpreopt_gen.go", + ], + deps: [ + "soong-dexpreopt", + "blueprint-pathtools", + "blueprint-proptools", + ], +}
\ No newline at end of file diff --git a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go new file mode 100644 index 00000000..c010056a --- /dev/null +++ b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go @@ -0,0 +1,190 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bytes" + "flag" + "fmt" + "os" + "path/filepath" + "runtime" + + "android/soong/dexpreopt" + + "github.com/google/blueprint/pathtools" +) + +var ( + dexpreoptScriptPath = flag.String("dexpreopt_script", "", "path to output dexpreopt script") + stripScriptPath = flag.String("strip_script", "", "path to output strip script") + globalConfigPath = flag.String("global", "", "path to global configuration file") + moduleConfigPath = flag.String("module", "", "path to module configuration file") +) + +func main() { + flag.Parse() + + usage := func(err string) { + if err != "" { + fmt.Println(err) + flag.Usage() + os.Exit(1) + } + } + + if flag.NArg() > 0 { + usage("unrecognized argument " + flag.Arg(0)) + } + + if *dexpreoptScriptPath == "" { + usage("path to output dexpreopt script is required") + } + + if *stripScriptPath == "" { + usage("path to output strip script is required") + } + + if *globalConfigPath == "" { + usage("path to global configuration file is required") + } + + if *moduleConfigPath == "" { + usage("path to module configuration file is required") + } + + globalConfig, err := dexpreopt.LoadGlobalConfig(*globalConfigPath) + if err != nil { + fmt.Fprintf(os.Stderr, "error loading global config %q: %s\n", *globalConfigPath, err) + os.Exit(2) + } + + moduleConfig, err := dexpreopt.LoadModuleConfig(*moduleConfigPath) + if err != nil { + fmt.Fprintf(os.Stderr, "error loading module config %q: %s\n", *moduleConfigPath, err) + os.Exit(2) + } + + defer func() { + if r := recover(); r != nil { + switch x := r.(type) { + case runtime.Error: + panic(x) + case error: + fmt.Fprintln(os.Stderr, "error:", r) + os.Exit(3) + default: + panic(x) + } + } + }() + + writeScripts(globalConfig, moduleConfig, *dexpreoptScriptPath, *stripScriptPath) +} + +func writeScripts(global dexpreopt.GlobalConfig, module dexpreopt.ModuleConfig, + dexpreoptScriptPath, stripScriptPath string) { + dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(global, module) + if err != nil { + panic(err) + } + + installDir := filepath.Join(filepath.Dir(module.BuildPath), "dexpreopt_install") + + dexpreoptRule.Command().FlagWithArg("rm -rf ", installDir) + + for _, install := range dexpreoptRule.Installs() { + installPath := filepath.Join(installDir, install.To) + dexpreoptRule.Command().Text("mkdir -p").Flag(filepath.Dir(installPath)) + dexpreoptRule.Command().Text("cp -f").Input(install.From).Output(installPath) + } + dexpreoptRule.Command().Tool(global.Tools.SoongZip). + FlagWithOutput("-o ", "$2"). + FlagWithArg("-C ", installDir). + FlagWithArg("-D ", installDir) + + stripRule, err := dexpreopt.GenerateStripRule(global, module) + if err != nil { + panic(err) + } + + write := func(rule *dexpreopt.Rule, file string) { + script := &bytes.Buffer{} + script.WriteString(scriptHeader) + for _, c := range rule.Commands() { + script.WriteString(c) + script.WriteString("\n\n") + } + + depFile := &bytes.Buffer{} + + fmt.Fprint(depFile, `: \`+"\n") + for _, tool := range dexpreoptRule.Tools() { + fmt.Fprintf(depFile, ` %s \`+"\n", tool) + } + for _, input := range dexpreoptRule.Inputs() { + // Assume the rule that ran the script already has a dependency on the input file passed on the + // command line. + if input != "$1" { + fmt.Fprintf(depFile, ` %s \`+"\n", input) + } + } + depFile.WriteString("\n") + + fmt.Fprintln(script, "rm -f $2.d") + // Write the output path unescaped so the $2 gets expanded + fmt.Fprintln(script, `echo -n $2 > $2.d`) + // Write the rest of the depsfile using cat <<'EOF', which will not do any shell expansion on + // the contents to preserve backslashes and special characters in filenames. + fmt.Fprintf(script, "cat >> $2.d <<'EOF'\n%sEOF\n", depFile.String()) + + err := pathtools.WriteFileIfChanged(file, script.Bytes(), 0755) + if err != nil { + panic(err) + } + } + + // The written scripts will assume the input is $1 and the output is $2 + if module.DexPath != "$1" { + panic(fmt.Errorf("module.DexPath must be '$1', was %q", module.DexPath)) + } + if module.StripInputPath != "$1" { + panic(fmt.Errorf("module.StripInputPath must be '$1', was %q", module.StripInputPath)) + } + if module.StripOutputPath != "$2" { + panic(fmt.Errorf("module.StripOutputPath must be '$2', was %q", module.StripOutputPath)) + } + + write(dexpreoptRule, dexpreoptScriptPath) + write(stripRule, stripScriptPath) +} + +const scriptHeader = `#!/bin/bash + +err() { + errno=$? + echo "error: $0:$1 exited with status $errno" >&2 + echo "error in command:" >&2 + sed -n -e "$1p" $0 >&2 + if [ "$errno" -ne 0 ]; then + exit $errno + else + exit 1 + fi +} + +trap 'err $LINENO' ERR + +` diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go new file mode 100644 index 00000000..52652481 --- /dev/null +++ b/dexpreopt/dexpreopt_test.go @@ -0,0 +1,208 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dexpreopt + +import ( + "reflect" + "strings" + "testing" +) + +var testGlobalConfig = GlobalConfig{ + DefaultNoStripping: false, + DisablePreoptModules: nil, + OnlyPreoptBootImageAndSystemServer: false, + HasSystemOther: false, + PatternsOnSystemOther: nil, + DisableGenerateProfile: false, + BootJars: nil, + SystemServerJars: nil, + SystemServerApps: nil, + SpeedApps: nil, + PreoptFlags: nil, + DefaultCompilerFilter: "", + SystemServerCompilerFilter: "", + GenerateDMFiles: false, + NoDebugInfo: false, + AlwaysSystemServerDebugInfo: false, + NeverSystemServerDebugInfo: false, + AlwaysOtherDebugInfo: false, + NeverOtherDebugInfo: false, + MissingUsesLibraries: nil, + IsEng: false, + SanitizeLite: false, + DefaultAppImages: false, + Dex2oatXmx: "", + Dex2oatXms: "", + EmptyDirectory: "", + DefaultDexPreoptImageLocation: nil, + CpuVariant: nil, + InstructionSetFeatures: nil, + Tools: Tools{ + Profman: "profman", + Dex2oat: "dex2oat", + Aapt: "aapt", + SoongZip: "soong_zip", + Zip2zip: "zip2zip", + VerifyUsesLibraries: "verify_uses_libraries.sh", + ConstructContext: "construct_context.sh", + }, +} + +var testModuleConfig = ModuleConfig{ + Name: "", + DexLocation: "", + BuildPath: "", + DexPath: "", + PreferIntegrity: false, + UncompressedDex: false, + HasApkLibraries: false, + PreoptFlags: nil, + ProfileClassListing: "", + ProfileIsTextListing: false, + EnforceUsesLibraries: false, + OptionalUsesLibraries: nil, + UsesLibraries: nil, + LibraryPaths: nil, + Archs: nil, + DexPreoptImageLocation: "", + PreoptExtractedApk: false, + NoCreateAppImage: false, + ForceCreateAppImage: false, + PresignedPrebuilt: false, + StripInputPath: "", + StripOutputPath: "", +} + +func TestDexPreopt(t *testing.T) { + global, module := testGlobalConfig, testModuleConfig + + module.Name = "test" + module.DexLocation = "/system/app/test/test.apk" + module.BuildPath = "out/test/test.apk" + module.Archs = []string{"arm"} + + rule, err := GenerateDexpreoptRule(global, module) + if err != nil { + t.Error(err) + } + + wantInstalls := []Install{ + {"out/test/oat/arm/package.odex", "/system/app/test/oat/arm/test.odex"}, + {"out/test/oat/arm/package.vdex", "/system/app/test/oat/arm/test.vdex"}, + } + + if !reflect.DeepEqual(rule.Installs(), wantInstalls) { + t.Errorf("\nwant installs:\n %v\ngot:\n %v", wantInstalls, rule.Installs()) + } +} + +func TestDexPreoptSystemOther(t *testing.T) { + global, module := testGlobalConfig, testModuleConfig + + global.HasSystemOther = true + global.PatternsOnSystemOther = []string{"app/%"} + + module.Name = "test" + module.DexLocation = "/system/app/test/test.apk" + module.BuildPath = "out/test/test.apk" + module.Archs = []string{"arm"} + + rule, err := GenerateDexpreoptRule(global, module) + if err != nil { + t.Error(err) + } + + wantInstalls := []Install{ + {"out/test/oat/arm/package.odex", "/system_other/app/test/oat/arm/test.odex"}, + {"out/test/oat/arm/package.vdex", "/system_other/app/test/oat/arm/test.vdex"}, + } + + if !reflect.DeepEqual(rule.Installs(), wantInstalls) { + t.Errorf("\nwant installs:\n %v\ngot:\n %v", wantInstalls, rule.Installs()) + } +} + +func TestDexPreoptProfile(t *testing.T) { + global, module := testGlobalConfig, testModuleConfig + + module.Name = "test" + module.DexLocation = "/system/app/test/test.apk" + module.BuildPath = "out/test/test.apk" + module.ProfileClassListing = "profile" + module.Archs = []string{"arm"} + + rule, err := GenerateDexpreoptRule(global, module) + if err != nil { + t.Error(err) + } + + wantInstalls := []Install{ + {"out/test/profile.prof", "/system/app/test/test.apk.prof"}, + {"out/test/oat/arm/package.art", "/system/app/test/oat/arm/test.art"}, + {"out/test/oat/arm/package.odex", "/system/app/test/oat/arm/test.odex"}, + {"out/test/oat/arm/package.vdex", "/system/app/test/oat/arm/test.vdex"}, + } + + if !reflect.DeepEqual(rule.Installs(), wantInstalls) { + t.Errorf("\nwant installs:\n %v\ngot:\n %v", wantInstalls, rule.Installs()) + } +} + +func TestStripDex(t *testing.T) { + global, module := testGlobalConfig, testModuleConfig + + module.Name = "test" + module.DexLocation = "/system/app/test/test.apk" + module.BuildPath = "out/test/test.apk" + module.Archs = []string{"arm"} + module.StripInputPath = "$1" + module.StripOutputPath = "$2" + + rule, err := GenerateStripRule(global, module) + if err != nil { + t.Error(err) + } + + want := `zip2zip -i $1 -o $2 -x "classes*.dex"` + if len(rule.Commands()) < 1 || !strings.Contains(rule.Commands()[0], want) { + t.Errorf("\nwant commands[0] to have:\n %v\ngot:\n %v", want, rule.Commands()[0]) + } +} + +func TestNoStripDex(t *testing.T) { + global, module := testGlobalConfig, testModuleConfig + + global.DefaultNoStripping = true + + module.Name = "test" + module.DexLocation = "/system/app/test/test.apk" + module.BuildPath = "out/test/test.apk" + module.Archs = []string{"arm"} + module.StripInputPath = "$1" + module.StripOutputPath = "$2" + + rule, err := GenerateStripRule(global, module) + if err != nil { + t.Error(err) + } + + wantCommands := []string{ + "cp -f $1 $2", + } + if !reflect.DeepEqual(rule.Commands(), wantCommands) { + t.Errorf("\nwant commands:\n %v\ngot:\n %v", wantCommands, rule.Commands()) + } +} diff --git a/dexpreopt/script.go b/dexpreopt/script.go new file mode 100644 index 00000000..fd4cf823 --- /dev/null +++ b/dexpreopt/script.go @@ -0,0 +1,173 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dexpreopt + +import ( + "fmt" + "sort" + "strings" +) + +type Install struct { + From, To string +} + +type Rule struct { + commands []*Command + installs []Install +} + +func (r *Rule) Install(from, to string) { + r.installs = append(r.installs, Install{from, to}) +} + +func (r *Rule) Command() *Command { + command := &Command{} + r.commands = append(r.commands, command) + return command +} + +func (r *Rule) Inputs() []string { + outputs := r.outputSet() + + inputs := make(map[string]bool) + for _, c := range r.commands { + for _, input := range c.inputs { + if !outputs[input] { + inputs[input] = true + } + } + } + + var inputList []string + for input := range inputs { + inputList = append(inputList, input) + } + sort.Strings(inputList) + + return inputList +} + +func (r *Rule) outputSet() map[string]bool { + outputs := make(map[string]bool) + for _, c := range r.commands { + for _, output := range c.outputs { + outputs[output] = true + } + } + return outputs +} + +func (r *Rule) Outputs() []string { + outputs := r.outputSet() + + var outputList []string + for output := range outputs { + outputList = append(outputList, output) + } + sort.Strings(outputList) + return outputList +} + +func (r *Rule) Installs() []Install { + return append([]Install(nil), r.installs...) +} + +func (r *Rule) Tools() []string { + var tools []string + for _, c := range r.commands { + tools = append(tools, c.tools...) + } + return tools +} + +func (r *Rule) Commands() []string { + var commands []string + for _, c := range r.commands { + commands = append(commands, string(c.buf)) + } + return commands +} + +type Command struct { + buf []byte + inputs []string + outputs []string + tools []string +} + +func (c *Command) Text(text string) *Command { + if len(c.buf) > 0 { + c.buf = append(c.buf, ' ') + } + c.buf = append(c.buf, text...) + return c +} + +func (c *Command) Textf(format string, a ...interface{}) *Command { + return c.Text(fmt.Sprintf(format, a...)) +} + +func (c *Command) Flag(flag string) *Command { + return c.Text(flag) +} + +func (c *Command) FlagWithArg(flag, arg string) *Command { + return c.Text(flag + arg) +} + +func (c *Command) FlagWithList(flag string, list []string, sep string) *Command { + return c.Text(flag + strings.Join(list, sep)) +} + +func (c *Command) Tool(path string) *Command { + c.tools = append(c.tools, path) + return c.Text(path) +} + +func (c *Command) Input(path string) *Command { + c.inputs = append(c.inputs, path) + return c.Text(path) +} + +func (c *Command) Implicit(path string) *Command { + c.inputs = append(c.inputs, path) + return c +} + +func (c *Command) Output(path string) *Command { + c.outputs = append(c.outputs, path) + return c.Text(path) +} + +func (c *Command) ImplicitOutput(path string) *Command { + c.outputs = append(c.outputs, path) + return c +} + +func (c *Command) FlagWithInput(flag, path string) *Command { + c.inputs = append(c.inputs, path) + return c.Text(flag + path) +} + +func (c *Command) FlagWithInputList(flag string, paths []string, sep string) *Command { + c.inputs = append(c.inputs, paths...) + return c.FlagWithList(flag, paths, sep) +} + +func (c *Command) FlagWithOutput(flag, path string) *Command { + c.outputs = append(c.outputs, path) + return c.Text(flag + path) +} diff --git a/java/aar.go b/java/aar.go index 99e9136d..89517fed 100644 --- a/java/aar.go +++ b/java/aar.go @@ -357,6 +357,7 @@ func AndroidLibraryFactory() android.Module { module.AddProperties( &module.Module.properties, &module.Module.deviceProperties, + &module.Module.dexpreoptProperties, &module.Module.protoProperties, &module.aaptProperties, &module.androidLibraryProperties) diff --git a/java/androidmk.go b/java/androidmk.go index 0700b587..70d0f7f9 100644 --- a/java/androidmk.go +++ b/java/androidmk.go @@ -25,7 +25,7 @@ import ( func (library *Library) AndroidMk() android.AndroidMkData { return android.AndroidMkData{ Class: "JAVA_LIBRARIES", - OutputFile: android.OptionalPathForPath(library.implementationAndResourcesJar), + OutputFile: android.OptionalPathForPath(library.outputFile), Include: "$(BUILD_SYSTEM)/soong_java_prebuilt.mk", Extra: []android.AndroidMkExtraFunc{ func(w io.Writer, outputFile android.Path) { @@ -42,21 +42,12 @@ func (library *Library) AndroidMk() android.AndroidMkData { } if library.dexJarFile != nil { fmt.Fprintln(w, "LOCAL_SOONG_DEX_JAR :=", library.dexJarFile.String()) - if library.deviceProperties.Dex_preopt.Enabled != nil { - fmt.Fprintln(w, "LOCAL_DEX_PREOPT :=", *library.deviceProperties.Dex_preopt.Enabled) - } - if library.deviceProperties.Dex_preopt.App_image != nil { - fmt.Fprintln(w, "LOCAL_DEX_PREOPT_APP_IMAGE :=", *library.deviceProperties.Dex_preopt.App_image) - } - if library.deviceProperties.Dex_preopt.Profile_guided != nil { - fmt.Fprintln(w, "LOCAL_DEX_PREOPT_GENERATE_PROFILE :=", *library.deviceProperties.Dex_preopt.Profile_guided) - } - if library.deviceProperties.Dex_preopt.Profile != nil { - fmt.Fprintln(w, "LOCAL_DEX_PREOPT_GENERATE_PROFILE := true") - fmt.Fprintln(w, "LOCAL_DEX_PREOPT_PROFILE_CLASS_LISTING := $(LOCAL_PATH)/"+*library.deviceProperties.Dex_preopt.Profile) - } + } + if len(library.dexpreopter.builtInstalled) > 0 { + fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED :=", strings.Join(library.dexpreopter.builtInstalled, " ")) } fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", library.sdkVersion()) + fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", library.implementationAndResourcesJar.String()) fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", library.headerJarFile.String()) if library.jacocoReportClassesFile != nil { @@ -84,7 +75,6 @@ func (library *Library) AndroidMk() android.AndroidMkData { fmt.Fprintln(w, "LOCAL_MODULE := "+name+"-hostdex") fmt.Fprintln(w, "LOCAL_IS_HOST_MODULE := true") fmt.Fprintln(w, "LOCAL_MODULE_CLASS := JAVA_LIBRARIES") - fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", library.implementationAndResourcesJar.String()) if library.installFile == nil { fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true") } @@ -92,6 +82,7 @@ func (library *Library) AndroidMk() android.AndroidMkData { fmt.Fprintln(w, "LOCAL_SOONG_DEX_JAR :=", library.dexJarFile.String()) } fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", library.headerJarFile.String()) + fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", library.implementationAndResourcesJar.String()) fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES := "+strings.Join(data.Required, " ")) fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_java_prebuilt.mk") } @@ -128,6 +119,7 @@ func (prebuilt *Import) AndroidMk() android.AndroidMkData { func(w io.Writer, outputFile android.Path) { fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := ", !Bool(prebuilt.properties.Installable)) fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", prebuilt.combinedClasspathFile.String()) + fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", prebuilt.combinedClasspathFile.String()) fmt.Fprintln(w, "LOCAL_SDK_VERSION :=", prebuilt.sdkVersion()) }, }, @@ -142,8 +134,8 @@ func (prebuilt *AARImport) AndroidMk() android.AndroidMkData { Extra: []android.AndroidMkExtraFunc{ func(w io.Writer, outputFile android.Path) { fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true") - fmt.Fprintln(w, "LOCAL_DEX_PREOPT := false") fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", prebuilt.classpathFile.String()) + fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", prebuilt.classpathFile.String()) fmt.Fprintln(w, "LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE :=", prebuilt.exportPackage.String()) fmt.Fprintln(w, "LOCAL_SOONG_EXPORT_PROGUARD_FLAGS :=", prebuilt.proguardFlags.String()) fmt.Fprintln(w, "LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES :=", prebuilt.extraAaptPackagesFile.String()) @@ -164,6 +156,7 @@ func (binary *Binary) AndroidMk() android.AndroidMkData { Extra: []android.AndroidMkExtraFunc{ func(w io.Writer, outputFile android.Path) { fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", binary.headerJarFile.String()) + fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", binary.implementationAndResourcesJar.String()) }, }, Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { @@ -250,6 +243,9 @@ func (app *AndroidApp) AndroidMk() android.AndroidMkData { for _, jniLib := range app.installJniLibs { fmt.Fprintln(w, "LOCAL_SOONG_JNI_LIBS_"+jniLib.target.Arch.ArchType.String(), "+=", jniLib.name) } + if len(app.dexpreopter.builtInstalled) > 0 { + fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED :=", strings.Join(app.dexpreopter.builtInstalled, " ")) + } }, }, } @@ -313,7 +309,6 @@ func (a *AndroidLibrary) AndroidMk() android.AndroidMkData { fmt.Fprintln(w, "LOCAL_SOONG_EXPORT_PROGUARD_FLAGS :=", strings.Join(a.exportedProguardFlagFiles.Strings(), " ")) fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true") - fmt.Fprintln(w, "LOCAL_DEX_PREOPT := false") }) return data diff --git a/java/app.go b/java/app.go index 392ad3fc..a037ef35 100644 --- a/java/app.go +++ b/java/app.go @@ -17,6 +17,7 @@ package java // This file contains the module types for compiling Android apps. import ( + "path/filepath" "strings" "github.com/google/blueprint" @@ -67,9 +68,7 @@ type appProperties struct { // list of native libraries that will be provided in or alongside the resulting jar Jni_libs []string `android:"arch_variant"` - AllowDexPreopt bool `blueprint:"mutated"` - EmbedJNI bool `blueprint:"mutated"` - StripDex bool `blueprint:"mutated"` + EmbedJNI bool `blueprint:"mutated"` } type AndroidApp struct { @@ -143,42 +142,16 @@ func (a *AndroidApp) GenerateAndroidBuildActions(ctx android.ModuleContext) { a.generateAndroidBuildActions(ctx) } -// Returns whether this module should have the dex file stored uncompressed in the APK, or stripped completely. If -// stripped, the code will still be present on the device in the dexpreopted files. -// This is only necessary for APKs, and not jars, because APKs are signed and the dex file should not be uncompressed -// or removed after the signature has been generated. For jars, which are not signed, the dex file is uncompressed -// or removed at installation time in Make. -func (a *AndroidApp) uncompressOrStripDex(ctx android.ModuleContext) (uncompress, strip bool) { +// Returns whether this module should have the dex file stored uncompressed in the APK. +func (a *AndroidApp) shouldUncompressDex(ctx android.ModuleContext) bool { if ctx.Config().UnbundledBuild() { - return false, false + return false } - strip = ctx.Config().DefaultStripDex() - // TODO(ccross): don't strip dex installed on partitions that may be updated separately (like vendor) - // TODO(ccross): don't strip dex on modules with LOCAL_APK_LIBRARIES equivalent - // Uncompress dex in APKs of privileged apps, and modules used by privileged apps. - if ctx.Config().UncompressPrivAppDex() && + return ctx.Config().UncompressPrivAppDex() && (Bool(a.appProperties.Privileged) || - inList(ctx.ModuleName(), ctx.Config().ModulesLoadedByPrivilegedModules())) { - - uncompress = true - // If the dex files is store uncompressed, don't strip it, we will reuse the uncompressed dex from the APK - // instead of copying it into the odex file. - strip = false - } - - // If dexpreopt is disabled, don't strip the dex file - if !a.appProperties.AllowDexPreopt || - !BoolDefault(a.deviceProperties.Dex_preopt.Enabled, true) || - ctx.Config().DisableDexPreopt(ctx.ModuleName()) { - strip = false - } - - // TODO(ccross): strip dexpropted modules that are not propted to system_other - strip = false - - return uncompress, strip + inList(ctx.ModuleName(), ctx.Config().ModulesLoadedByPrivilegedModules())) } func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { @@ -228,16 +201,25 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { a.Module.extraProguardFlagFiles = append(a.Module.extraProguardFlagFiles, staticLibProguardFlagFiles...) a.Module.extraProguardFlagFiles = append(a.Module.extraProguardFlagFiles, a.proguardOptionsFile) - a.deviceProperties.UncompressDex, a.appProperties.StripDex = a.uncompressOrStripDex(ctx) + a.deviceProperties.UncompressDex = a.shouldUncompressDex(ctx) + + var installDir string + if ctx.ModuleName() == "framework-res" { + // framework-res.apk is installed as system/framework/framework-res.apk + installDir = "framework" + } else if Bool(a.appProperties.Privileged) { + installDir = filepath.Join("priv-app", ctx.ModuleName()) + } else { + installDir = filepath.Join("app", ctx.ModuleName()) + } + a.dexpreopter.installPath = android.PathForModuleInstall(ctx, installDir, ctx.ModuleName()+".apk") + a.dexpreopter.isPrivApp = Bool(a.appProperties.Privileged) if ctx.ModuleName() != "framework-res" { a.Module.compile(ctx, a.aaptSrcJar) } - dexJarFile := a.dexJarFile - if a.appProperties.StripDex { - dexJarFile = nil - } + dexJarFile := a.maybeStrippedDexJarFile var certificates []Certificate @@ -287,9 +269,9 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { // framework-res.apk is installed as system/framework/framework-res.apk ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"), ctx.ModuleName()+".apk", a.outputFile) } else if Bool(a.appProperties.Privileged) { - ctx.InstallFile(android.PathForModuleInstall(ctx, "priv-app"), ctx.ModuleName()+".apk", a.outputFile) + ctx.InstallFile(android.PathForModuleInstall(ctx, "priv-app", ctx.ModuleName()), ctx.ModuleName()+".apk", a.outputFile) } else { - ctx.InstallFile(android.PathForModuleInstall(ctx, "app"), ctx.ModuleName()+".apk", a.outputFile) + ctx.InstallFile(android.PathForModuleInstall(ctx, "app", ctx.ModuleName()), ctx.ModuleName()+".apk", a.outputFile) } } @@ -337,11 +319,11 @@ func AndroidAppFactory() android.Module { module.Module.properties.Instrument = true module.Module.properties.Installable = proptools.BoolPtr(true) - module.appProperties.AllowDexPreopt = true module.AddProperties( &module.Module.properties, &module.Module.deviceProperties, + &module.Module.dexpreoptProperties, &module.Module.protoProperties, &module.aaptProperties, &module.appProperties) @@ -399,11 +381,12 @@ func AndroidTestFactory() android.Module { module.Module.properties.Instrument = true module.Module.properties.Installable = proptools.BoolPtr(true) module.appProperties.EmbedJNI = true - module.appProperties.AllowDexPreopt = false + module.Module.dexpreopter.isTest = true module.AddProperties( &module.Module.properties, &module.Module.deviceProperties, + &module.Module.dexpreoptProperties, &module.Module.protoProperties, &module.aaptProperties, &module.appProperties, @@ -434,11 +417,12 @@ func AndroidTestHelperAppFactory() android.Module { module.Module.properties.Installable = proptools.BoolPtr(true) module.appProperties.EmbedJNI = true - module.appProperties.AllowDexPreopt = false + module.Module.dexpreopter.isTest = true module.AddProperties( &module.Module.properties, &module.Module.deviceProperties, + &module.Module.dexpreoptProperties, &module.Module.protoProperties, &module.aaptProperties, &module.appProperties, diff --git a/java/builder.go b/java/builder.go index cefb916d..86156649 100644 --- a/java/builder.go +++ b/java/builder.go @@ -138,6 +138,17 @@ var ( CommandDeps: []string{"${config.JavaCmd}", "${config.JetifierJar}"}, }, ) + + zipalign = pctx.AndroidStaticRule("zipalign", + blueprint.RuleParams{ + Command: "if ! ${config.ZipAlign} -c 4 $in > /dev/null; then " + + "${config.ZipAlign} -f 4 $in $out; " + + "else " + + "cp -f $in $out; " + + "fi", + CommandDeps: []string{"${config.ZipAlign}"}, + }, + ) ) func init() { @@ -410,6 +421,15 @@ func GenerateMainClassManifest(ctx android.ModuleContext, outputFile android.Wri }) } +func TransformZipAlign(ctx android.ModuleContext, outputFile android.WritablePath, inputFile android.Path) { + ctx.Build(pctx, android.BuildParams{ + Rule: zipalign, + Description: "align", + Input: inputFile, + Output: outputFile, + }) +} + type classpath []android.Path func (x *classpath) FormJavaClassPath(optName string) string { diff --git a/java/config/config.go b/java/config/config.go index d2a8c467..da4eed7b 100644 --- a/java/config/config.go +++ b/java/config/config.go @@ -126,6 +126,7 @@ func init() { pctx.HostJavaToolVariable("JetifierJar", "jetifier.jar") pctx.HostBinToolVariable("SoongJavacWrapper", "soong_javac_wrapper") + pctx.HostBinToolVariable("DexpreoptGen", "dexpreopt_gen") pctx.VariableFunc("JavacWrapper", func(ctx android.PackageVarContext) string { if override := ctx.Config().Getenv("JAVAC_WRAPPER"); override != "" { @@ -152,4 +153,6 @@ func init() { pctx.SourcePathsVariable("ManifestMergerJars", " ", ManifestMergerClasspath...) pctx.SourcePathsVariable("ManifestMergerClasspath", ":", ManifestMergerClasspath...) + + pctx.HostBinToolVariable("ZipAlign", "zipalign") } diff --git a/java/config/makevars.go b/java/config/makevars.go index 275f4966..01adaa7d 100644 --- a/java/config/makevars.go +++ b/java/config/makevars.go @@ -65,6 +65,7 @@ func makeVarsProvider(ctx android.MakeVarsContext) { ctx.Strict("JMOD", "${JmodCmd}") ctx.Strict("SOONG_JAVAC_WRAPPER", "${SoongJavacWrapper}") + ctx.Strict("DEXPREOPT_GEN", "${DexpreoptGen}") ctx.Strict("ZIPSYNC", "${ZipSyncCmd}") ctx.Strict("JACOCO_CLI_JAR", "${JacocoCLIJar}") diff --git a/java/dexpreopt.go b/java/dexpreopt.go new file mode 100644 index 00000000..ae8d3695 --- /dev/null +++ b/java/dexpreopt.go @@ -0,0 +1,250 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package java + +import ( + "path/filepath" + "strings" + + "github.com/google/blueprint" + "github.com/google/blueprint/proptools" + + "android/soong/android" + "android/soong/dexpreopt" +) + +type dexpreopter struct { + dexpreoptProperties DexpreoptProperties + + installPath android.OutputPath + isPrivApp bool + isSDKLibrary bool + isTest bool + + builtInstalled []string +} + +type DexpreoptProperties struct { + Dex_preopt struct { + // If false, prevent dexpreopting and stripping the dex file from the final jar. Defaults to + // true. + Enabled *bool + + // If true, generate an app image (.art file) for this module. + App_image *bool + + // If true, use a checked-in profile to guide optimization. Defaults to false unless + // a matching profile is set or a profile is found in PRODUCT_DEX_PREOPT_PROFILE_DIR + // that matches the name of this module, in which case it is defaulted to true. + Profile_guided *bool + + // If set, provides the path to profile relative to the Android.bp file. If not set, + // defaults to searching for a file that matches the name of this module in the default + // profile location set by PRODUCT_DEX_PREOPT_PROFILE_DIR, or empty if not found. + Profile *string + } +} + +func (d *dexpreopter) dexpreoptDisabled(ctx android.ModuleContext) bool { + if ctx.Config().DisableDexPreopt(ctx.ModuleName()) { + return true + } + + if ctx.Config().UnbundledBuild() { + return true + } + + if d.isTest { + return true + } + + if !BoolDefault(d.dexpreoptProperties.Dex_preopt.Enabled, true) { + return true + } + + // TODO: contains no java code + + return false +} + +func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.ModuleOutPath) android.ModuleOutPath { + if d.dexpreoptDisabled(ctx) { + return dexJarFile + } + + globalConfig := ctx.Config().Once("DexpreoptGlobalConfig", func() interface{} { + if f := ctx.Config().DexpreoptGlobalConfig(); f != "" { + ctx.AddNinjaFileDeps(f) + globalConfig, err := dexpreopt.LoadGlobalConfig(f) + if err != nil { + panic(err) + } + return globalConfig + } + return dexpreopt.GlobalConfig{} + }).(dexpreopt.GlobalConfig) + + var archs []string + for _, a := range ctx.MultiTargets() { + archs = append(archs, a.Arch.ArchType.String()) + } + if len(archs) == 0 { + // assume this is a java library, dexpreopt for all arches for now + for _, target := range ctx.Config().Targets[android.Android] { + archs = append(archs, target.Arch.ArchType.String()) + } + if inList(ctx.ModuleName(), globalConfig.SystemServerJars) && !d.isSDKLibrary { + // If the module is not an SDK library and it's a system server jar, only preopt the primary arch. + archs = archs[:1] + } + } + if ctx.Config().SecondArchIsTranslated() { + // Only preopt primary arch for translated arch since there is only an image there. + archs = archs[:1] + } + + dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath) + + strippedDexJarFile := android.PathForModuleOut(ctx, "dexpreopt", dexJarFile.Base()) + + deps := android.Paths{dexJarFile} + + var profileClassListing android.OptionalPath + profileIsTextListing := false + if BoolDefault(d.dexpreoptProperties.Dex_preopt.Profile_guided, true) { + // If dex_preopt.profile_guided is not set, default it based on the existence of the + // dexprepot.profile option or the profile class listing. + if String(d.dexpreoptProperties.Dex_preopt.Profile) != "" { + profileClassListing = android.OptionalPathForPath( + android.PathForModuleSrc(ctx, String(d.dexpreoptProperties.Dex_preopt.Profile))) + profileIsTextListing = true + } else { + profileClassListing = android.ExistentPathForSource(ctx, + ctx.Config().DexPreoptProfileDir(), ctx.ModuleName()+".prof") + } + } + + if profileClassListing.Valid() { + deps = append(deps, profileClassListing.Path()) + } + + uncompressedDex := false + if ctx.Config().UncompressPrivAppDex() && + (d.isPrivApp || inList(ctx.ModuleName(), ctx.Config().ModulesLoadedByPrivilegedModules())) { + uncompressedDex = true + } + + dexpreoptConfig := dexpreopt.ModuleConfig{ + Name: ctx.ModuleName(), + DexLocation: dexLocation, + BuildPath: android.PathForModuleOut(ctx, "dexpreopt", ctx.ModuleName()+".jar").String(), + DexPath: dexJarFile.String(), + PreferIntegrity: false, + UncompressedDex: uncompressedDex, + HasApkLibraries: false, + PreoptFlags: nil, + + ProfileClassListing: profileClassListing.String(), + ProfileIsTextListing: profileIsTextListing, + + EnforceUsesLibraries: false, + OptionalUsesLibraries: nil, + UsesLibraries: nil, + LibraryPaths: nil, + + Archs: archs, + DexPreoptImageLocation: "", + + PreoptExtractedApk: false, + + NoCreateAppImage: !BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, true), + ForceCreateAppImage: BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, false), + + StripInputPath: dexJarFile.String(), + StripOutputPath: strippedDexJarFile.String(), + } + + dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(globalConfig, dexpreoptConfig) + if err != nil { + ctx.ModuleErrorf("error generating dexpreopt rule: %s", err.Error()) + return dexJarFile + } + + var inputs android.Paths + for _, input := range dexpreoptRule.Inputs() { + if input == "" { + // Tests sometimes have empty configuration values that lead to empty inputs + continue + } + rel, isRel := android.MaybeRel(ctx, android.PathForModuleOut(ctx).String(), input) + if isRel { + inputs = append(inputs, android.PathForModuleOut(ctx, rel)) + } else { + // TODO: use PathForOutput once boot image is moved to where PathForOutput can find it. + inputs = append(inputs, &bootImagePath{input}) + } + } + + var outputs android.WritablePaths + for _, output := range dexpreoptRule.Outputs() { + rel := android.Rel(ctx, android.PathForModuleOut(ctx).String(), output) + outputs = append(outputs, android.PathForModuleOut(ctx, rel)) + } + + for _, install := range dexpreoptRule.Installs() { + d.builtInstalled = append(d.builtInstalled, install.From+":"+install.To) + } + + if len(dexpreoptRule.Commands()) > 0 { + ctx.Build(pctx, android.BuildParams{ + Rule: ctx.Rule(pctx, "dexpreopt", blueprint.RuleParams{ + Command: strings.Join(proptools.NinjaEscape(dexpreoptRule.Commands()), " && "), + CommandDeps: dexpreoptRule.Tools(), + }), + Implicits: inputs, + Outputs: outputs, + Description: "dexpreopt", + }) + } + + stripRule, err := dexpreopt.GenerateStripRule(globalConfig, dexpreoptConfig) + if err != nil { + ctx.ModuleErrorf("error generating dexpreopt strip rule: %s", err.Error()) + return dexJarFile + } + + ctx.Build(pctx, android.BuildParams{ + Rule: ctx.Rule(pctx, "dexpreopt_strip", blueprint.RuleParams{ + Command: strings.Join(proptools.NinjaEscape(stripRule.Commands()), " && "), + CommandDeps: stripRule.Tools(), + }), + Input: dexJarFile, + Output: strippedDexJarFile, + Description: "dexpreopt strip", + }) + + return strippedDexJarFile +} + +type bootImagePath struct { + path string +} + +var _ android.Path = (*bootImagePath)(nil) + +func (p *bootImagePath) String() string { return p.path } +func (p *bootImagePath) Ext() string { return filepath.Ext(p.path) } +func (p *bootImagePath) Base() string { return filepath.Base(p.path) } +func (p *bootImagePath) Rel() string { return p.path } diff --git a/java/java.go b/java/java.go index 5ed99f7e..aea1ef90 100644 --- a/java/java.go +++ b/java/java.go @@ -217,25 +217,6 @@ type CompilerDeviceProperties struct { // If set to true, compile dex regardless of installable. Defaults to false. Compile_dex *bool - Dex_preopt struct { - // If false, prevent dexpreopting and stripping the dex file from the final jar. Defaults to - // true. - Enabled *bool - - // If true, generate an app image (.art file) for this module. - App_image *bool - - // If true, use a checked-in profile to guide optimization. Defaults to false unless - // a matching profile is set or a profile is found in PRODUCT_DEX_PREOPT_PROFILE_DIR - // that matches the name of this module, in which case it is defaulted to true. - Profile_guided *bool - - // If set, provides the path to profile relative to the Android.bp file. If not set, - // defaults to searching for a file that matches the name of this module in the default - // profile location set by PRODUCT_DEX_PREOPT_PROFILE_DIR, or empty if not found. - Profile *string - } - Optimize struct { // If false, disable all optimization. Defaults to true for android_app and android_test // modules, false for java_library and java_test modules. @@ -266,6 +247,7 @@ type CompilerDeviceProperties struct { System_modules *string UncompressDex bool `blueprint:"mutated"` + IsSDKLibrary bool `blueprint:"mutated"` } // Module contains the properties and members used by all java module types @@ -295,6 +277,9 @@ type Module struct { // output file containing classes.dex and resources dexJarFile android.Path + // output file that contains classes.dex if it should be in the output file + maybeStrippedDexJarFile android.Path + // output file containing uninstrumented classes that will be instrumented by jacoco jacocoReportClassesFile android.Path @@ -327,6 +312,8 @@ type Module struct { // list of source files, collected from compiledJavaSrcs and compiledSrcJars // filter out Exclude_srcs, will be used by android.IDEInfo struct expandIDEInfoCompiledSrcs []string + + dexpreopter } func (j *Module) Srcs() android.Paths { @@ -1332,7 +1319,15 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path j.dexJarFile = dexOutputFile + dexOutputFile = j.dexpreopt(ctx, dexOutputFile) + + j.maybeStrippedDexJarFile = dexOutputFile + outputFile = dexOutputFile + + if ctx.Failed() { + return + } } else { outputFile = implementationAndResourcesJar } @@ -1486,9 +1481,17 @@ type Library struct { } func (j *Library) GenerateAndroidBuildActions(ctx android.ModuleContext) { + j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", ctx.ModuleName()+".jar") + j.dexpreopter.isSDKLibrary = j.deviceProperties.IsSDKLibrary j.compile(ctx) if Bool(j.properties.Installable) || ctx.Host() { + if j.deviceProperties.UncompressDex { + alignedOutputFile := android.PathForModuleOut(ctx, "aligned", ctx.ModuleName()+".jar") + TransformZipAlign(ctx, alignedOutputFile, j.outputFile) + j.outputFile = alignedOutputFile + } + j.installFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"), ctx.ModuleName()+".jar", j.outputFile) } @@ -1504,6 +1507,7 @@ func LibraryFactory() android.Module { module.AddProperties( &module.Module.properties, &module.Module.deviceProperties, + &module.Module.dexpreoptProperties, &module.Module.protoProperties) InitJavaModule(module, android.HostAndDeviceSupported) @@ -1574,6 +1578,7 @@ func TestFactory() android.Module { module.AddProperties( &module.Module.properties, &module.Module.deviceProperties, + &module.Module.dexpreoptProperties, &module.Module.protoProperties, &module.testProperties) @@ -1670,6 +1675,7 @@ func BinaryFactory() android.Module { module.AddProperties( &module.Module.properties, &module.Module.deviceProperties, + &module.Module.dexpreoptProperties, &module.Module.protoProperties, &module.binaryProperties) @@ -1889,6 +1895,7 @@ func DefaultsFactory(props ...interface{}) android.Module { module.AddProperties( &CompilerProperties{}, &CompilerDeviceProperties{}, + &DexpreoptProperties{}, &android.ProtoProperties{}, &aaptProperties{}, &androidLibraryProperties{}, diff --git a/java/sdk_library.go b/java/sdk_library.go index fdbf19d2..877abe48 100644 --- a/java/sdk_library.go +++ b/java/sdk_library.go @@ -141,8 +141,9 @@ type sdkLibrary struct { android.ModuleBase android.DefaultableModuleBase - properties sdkLibraryProperties - deviceProperties CompilerDeviceProperties + properties sdkLibraryProperties + deviceProperties CompilerDeviceProperties + dexpreoptProperties DexpreoptProperties publicApiStubsPath android.Paths systemApiStubsPath android.Paths @@ -564,6 +565,7 @@ func (module *sdkLibrary) createImplLibrary(mctx android.TopDownMutatorContext) Errorprone struct { Javacflags []string } + IsSDKLibrary bool }{} props.Name = proptools.StringPtr(module.implName()) @@ -574,6 +576,7 @@ func (module *sdkLibrary) createImplLibrary(mctx android.TopDownMutatorContext) // XML file is installed along with the impl lib props.Required = []string{module.xmlFileName()} props.Errorprone.Javacflags = module.properties.Errorprone.Javacflags + props.IsSDKLibrary = true if module.SocSpecific() { props.Soc_specific = proptools.BoolPtr(true) @@ -583,7 +586,10 @@ func (module *sdkLibrary) createImplLibrary(mctx android.TopDownMutatorContext) props.Product_specific = proptools.BoolPtr(true) } - mctx.CreateModule(android.ModuleFactoryAdaptor(LibraryFactory), &props, &module.deviceProperties) + mctx.CreateModule(android.ModuleFactoryAdaptor(LibraryFactory), + &props, + &module.deviceProperties, + &module.dexpreoptProperties) } // Creates the xml file that publicizes the runtime library @@ -716,6 +722,7 @@ func sdkLibraryFactory() android.Module { module := &sdkLibrary{} module.AddProperties(&module.properties) module.AddProperties(&module.deviceProperties) + module.AddProperties(&module.dexpreoptProperties) InitJavaModule(module, android.DeviceSupported) return module } |