diff --git a/java/Android.bp b/java/Android.bp index e9319d1b1..4450c4275 100644 --- a/java/Android.bp +++ b/java/Android.bp @@ -66,6 +66,7 @@ bootstrap_go_package { "plugin.go", "prebuilt_apis.go", "proto.go", + "resourceshrinker.go", "robolectric.go", "rro.go", "sdk.go", @@ -105,6 +106,7 @@ bootstrap_go_package { "plugin_test.go", "prebuilt_apis_test.go", "proto_test.go", + "resourceshrinker_test.go", "rro_test.go", "sdk_test.go", "sdk_library_test.go", diff --git a/java/app.go b/java/app.go index 6350a1924..7b9e6bb14 100755 --- a/java/app.go +++ b/java/app.go @@ -532,7 +532,7 @@ func (a *AndroidApp) installPath(ctx android.ModuleContext) android.InstallPath return android.PathForModuleInstall(ctx, installDir, a.installApkName+".apk") } -func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) (android.Path, android.Path) { +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 { @@ -545,15 +545,7 @@ func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) (android.Path, a a.dexpreopter.manifestFile = a.mergedManifestFile a.dexpreopter.preventInstall = a.appProperties.PreventInstall - var packageResources = a.exportPackage - if ctx.ModuleName() != "framework-res" { - if Bool(a.dexProperties.Optimize.Shrink_resources) { - protoFile := android.PathForModuleOut(ctx, packageResources.Base()+".proto.apk") - aapt2Convert(ctx, protoFile, packageResources, "proto") - a.dexer.resourcesInput = android.OptionalPathForPath(protoFile) - } - var extraSrcJars android.Paths var extraClasspathJars android.Paths var extraCombinedJars android.Paths @@ -571,14 +563,9 @@ func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) (android.Path, a } a.Module.compile(ctx, extraSrcJars, extraClasspathJars, extraCombinedJars) - if Bool(a.dexProperties.Optimize.Shrink_resources) { - binaryResources := android.PathForModuleOut(ctx, packageResources.Base()+".binary.out.apk") - aapt2Convert(ctx, binaryResources, a.dexer.resourcesOutput.Path(), "binary") - packageResources = binaryResources - } } - return a.dexJarFile.PathOrNil(), packageResources + return a.dexJarFile.PathOrNil() } func (a *AndroidApp) jniBuildActions(jniLibs []jniLib, prebuiltJniPackages android.Paths, ctx android.ModuleContext) android.WritablePath { @@ -763,6 +750,7 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { // Process all building blocks, from AAPT to certificates. a.aaptBuildActions(ctx) + // The decision to enforce checks is made before adding implicit SDK libraries. a.usesLibrary.freezeEnforceUsesLibraries() @@ -788,7 +776,7 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { a.linter.resources = a.aapt.resourceFiles a.linter.buildModuleReportZip = ctx.Config().UnbundledBuildApps() - dexJarFile, packageResources := a.dexBuildActions(ctx) + dexJarFile := a.dexBuildActions(ctx) jniLibs, prebuiltJniPackages, certificates := collectAppDeps(ctx, a, a.shouldEmbedJnis(ctx), !Bool(a.appProperties.Jni_uses_platform_apis)) jniJarFile := a.jniBuildActions(jniLibs, prebuiltJniPackages, ctx) @@ -812,7 +800,7 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { } rotationMinSdkVersion := String(a.overridableAppProperties.RotationMinSdkVersion) - CreateAndSignAppPackage(ctx, packageFile, packageResources, jniJarFile, dexJarFile, certificates, apkDeps, v4SignatureFile, lineageFile, rotationMinSdkVersion) + CreateAndSignAppPackage(ctx, packageFile, a.exportPackage, jniJarFile, dexJarFile, certificates, apkDeps, v4SignatureFile, lineageFile, rotationMinSdkVersion, Bool(a.dexProperties.Optimize.Shrink_resources)) a.outputFile = packageFile if v4SigningRequested { a.extraOutputFiles = append(a.extraOutputFiles, v4SignatureFile) @@ -841,7 +829,7 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { if v4SigningRequested { v4SignatureFile = android.PathForModuleOut(ctx, a.installApkName+"_"+split.suffix+".apk.idsig") } - CreateAndSignAppPackage(ctx, packageFile, split.path, nil, nil, certificates, apkDeps, v4SignatureFile, lineageFile, rotationMinSdkVersion) + CreateAndSignAppPackage(ctx, packageFile, split.path, nil, nil, certificates, apkDeps, v4SignatureFile, lineageFile, rotationMinSdkVersion, false) a.extraOutputFiles = append(a.extraOutputFiles, packageFile) if v4SigningRequested { a.extraOutputFiles = append(a.extraOutputFiles, v4SignatureFile) diff --git a/java/app_builder.go b/java/app_builder.go index 943ce317b..d397ff7f5 100644 --- a/java/app_builder.go +++ b/java/app_builder.go @@ -52,7 +52,7 @@ var combineApk = pctx.AndroidStaticRule("combineApk", }) func CreateAndSignAppPackage(ctx android.ModuleContext, outputFile android.WritablePath, - packageFile, jniJarFile, dexJarFile android.Path, certificates []Certificate, deps android.Paths, v4SignatureFile android.WritablePath, lineageFile android.Path, rotationMinSdkVersion string) { + packageFile, jniJarFile, dexJarFile android.Path, certificates []Certificate, deps android.Paths, v4SignatureFile android.WritablePath, lineageFile android.Path, rotationMinSdkVersion string, shrinkResources bool) { unsignedApkName := strings.TrimSuffix(outputFile.Base(), ".apk") + "-unsigned.apk" unsignedApk := android.PathForModuleOut(ctx, unsignedApkName) @@ -71,6 +71,12 @@ func CreateAndSignAppPackage(ctx android.ModuleContext, outputFile android.Writa Output: unsignedApk, Implicits: deps, }) + + if shrinkResources { + shrunkenApk := android.PathForModuleOut(ctx, "resource-shrunken", unsignedApk.Base()) + ShrinkResources(ctx, unsignedApk, shrunkenApk) + unsignedApk = shrunkenApk + } SignAppPackage(ctx, outputFile, unsignedApk, certificates, v4SignatureFile, lineageFile, rotationMinSdkVersion) } diff --git a/java/dex.go b/java/dex.go index ee28643e9..aa017834d 100644 --- a/java/dex.go +++ b/java/dex.go @@ -95,8 +95,6 @@ type dexer struct { proguardDictionary android.OptionalPath proguardConfiguration android.OptionalPath proguardUsageZip android.OptionalPath - resourcesInput android.OptionalPath - resourcesOutput android.OptionalPath providesTransitiveHeaderJars } @@ -163,7 +161,7 @@ var r8, r8RE = pctx.MultiCommandRemoteStaticRules("r8", "$r8Template": &remoteexec.REParams{ Labels: map[string]string{"type": "compile", "compiler": "r8"}, Inputs: []string{"$implicits", "${config.R8Jar}"}, - OutputFiles: []string{"${outUsage}", "${outConfig}", "${outDict}", "${resourcesOutput}"}, + OutputFiles: []string{"${outUsage}", "${outConfig}", "${outDict}"}, ExecStrategy: "${config.RER8ExecStrategy}", ToolchainInputs: []string{"${config.JavaCmd}"}, Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, @@ -183,7 +181,7 @@ var r8, r8RE = pctx.MultiCommandRemoteStaticRules("r8", Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, }, }, []string{"outDir", "outDict", "outConfig", "outUsage", "outUsageZip", "outUsageDir", - "r8Flags", "zipFlags", "mergeZipsFlags", "resourcesOutput"}, []string{"implicits"}) + "r8Flags", "zipFlags", "mergeZipsFlags"}, []string{"implicits"}) func (d *dexer) dexCommonFlags(ctx android.ModuleContext, dexParams *compileDexParams) (flags []string, deps android.Paths) { @@ -352,12 +350,6 @@ func (d *dexer) r8Flags(ctx android.ModuleContext, flags javaBuilderFlags) (r8Fl r8Flags = append(r8Flags, "-ignorewarnings") } - if d.resourcesInput.Valid() { - r8Flags = append(r8Flags, "--resource-input", d.resourcesInput.Path().String()) - r8Deps = append(r8Deps, d.resourcesInput.Path()) - r8Flags = append(r8Flags, "--resource-output", d.resourcesOutput.Path().String()) - } - return r8Flags, r8Deps } @@ -399,8 +391,6 @@ func (d *dexer) compileDex(ctx android.ModuleContext, dexParams *compileDexParam android.ModuleNameWithPossibleOverride(ctx), "unused.txt") proguardUsageZip := android.PathForModuleOut(ctx, "proguard_usage.zip") d.proguardUsageZip = android.OptionalPathForPath(proguardUsageZip) - resourcesOutput := android.PathForModuleOut(ctx, "package-res-shrunken.apk") - d.resourcesOutput = android.OptionalPathForPath(resourcesOutput) r8Flags, r8Deps := d.r8Flags(ctx, dexParams.flags) r8Deps = append(r8Deps, commonDeps...) rule := r8 @@ -419,22 +409,17 @@ func (d *dexer) compileDex(ctx android.ModuleContext, dexParams *compileDexParam rule = r8RE args["implicits"] = strings.Join(r8Deps.Strings(), ",") } - implicitOutputs := android.WritablePaths{ - proguardDictionary, - proguardUsageZip, - proguardConfiguration} - if d.resourcesInput.Valid() { - implicitOutputs = append(implicitOutputs, resourcesOutput) - args["resourcesOutput"] = resourcesOutput.String() - } ctx.Build(pctx, android.BuildParams{ - Rule: rule, - Description: "r8", - Output: javalibJar, - ImplicitOutputs: implicitOutputs, - Input: dexParams.classesJar, - Implicits: r8Deps, - Args: args, + Rule: rule, + Description: "r8", + Output: javalibJar, + ImplicitOutputs: android.WritablePaths{ + proguardDictionary, + proguardUsageZip, + proguardConfiguration}, + Input: dexParams.classesJar, + Implicits: r8Deps, + Args: args, }) } else { d8Flags, d8Deps := d8Flags(dexParams.flags) diff --git a/java/resourceshrinker.go b/java/resourceshrinker.go new file mode 100644 index 000000000..bf1b04d94 --- /dev/null +++ b/java/resourceshrinker.go @@ -0,0 +1,44 @@ +// Copyright 2022 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 ( + "android/soong/android" + + "github.com/google/blueprint" +) + +var shrinkResources = pctx.AndroidStaticRule("shrinkResources", + blueprint.RuleParams{ + // Note that we suppress stdout to avoid successful log confirmations. + Command: `${config.ResourceShrinkerCmd} --output $out --input $in --raw_resources $raw_resources >/dev/null`, + CommandDeps: []string{"${config.ResourceShrinkerCmd}"}, + }, "raw_resources") + +func ShrinkResources(ctx android.ModuleContext, apk android.Path, outputFile android.WritablePath) { + protoFile := android.PathForModuleOut(ctx, apk.Base()+".proto.apk") + aapt2Convert(ctx, protoFile, apk, "proto") + strictModeFile := android.PathForSource(ctx, "prebuilts/cmdline-tools/shrinker.xml") + protoOut := android.PathForModuleOut(ctx, apk.Base()+".proto.out.apk") + ctx.Build(pctx, android.BuildParams{ + Rule: shrinkResources, + Input: protoFile, + Output: protoOut, + Args: map[string]string{ + "raw_resources": strictModeFile.String(), + }, + }) + aapt2Convert(ctx, outputFile, protoOut, "binary") +} diff --git a/java/resourceshrinker_test.go b/java/resourceshrinker_test.go new file mode 100644 index 000000000..3bbf11670 --- /dev/null +++ b/java/resourceshrinker_test.go @@ -0,0 +1,53 @@ +// Copyright 2022 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 ( + "testing" + + "android/soong/android" +) + +func TestShrinkResourcesArgs(t *testing.T) { + result := android.GroupFixturePreparers( + PrepareForTestWithJavaDefaultModules, + ).RunTestWithBp(t, ` + android_app { + name: "app_shrink", + platform_apis: true, + optimize: { + shrink_resources: true, + } + } + + android_app { + name: "app_no_shrink", + platform_apis: true, + optimize: { + shrink_resources: false, + } + } + `) + + appShrink := result.ModuleForTests("app_shrink", "android_common") + appShrinkResources := appShrink.Rule("shrinkResources") + android.AssertStringDoesContain(t, "expected shrinker.xml in app_shrink resource shrinker flags", + appShrinkResources.Args["raw_resources"], "shrinker.xml") + + appNoShrink := result.ModuleForTests("app_no_shrink", "android_common") + if appNoShrink.MaybeRule("shrinkResources").Rule != nil { + t.Errorf("unexpected shrinkResources rule for app_no_shrink") + } +}