1// Copyright 2017 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package java 16 17import ( 18 "strconv" 19 "strings" 20 21 "github.com/google/blueprint" 22 "github.com/google/blueprint/proptools" 23 24 "android/soong/android" 25 "android/soong/remoteexec" 26) 27 28type DexProperties struct { 29 // If set to true, compile dex regardless of installable. Defaults to false. 30 Compile_dex *bool 31 32 // list of module-specific flags that will be used for dex compiles 33 Dxflags []string `android:"arch_variant"` 34 35 Optimize struct { 36 // If false, disable all optimization. Defaults to true for android_app and android_test 37 // modules, false for java_library and java_test modules. 38 Enabled *bool 39 // True if the module containing this has it set by default. 40 EnabledByDefault bool `blueprint:"mutated"` 41 42 // If true, runs R8 in Proguard compatibility mode (default). 43 // Otherwise, runs R8 in full mode. 44 Proguard_compatibility *bool 45 46 // If true, optimize for size by removing unused code. Defaults to true for apps, 47 // false for libraries and tests. 48 Shrink *bool 49 50 // If true, optimize bytecode. Defaults to false. 51 Optimize *bool 52 53 // If true, obfuscate bytecode. Defaults to false. 54 Obfuscate *bool 55 56 // If true, do not use the flag files generated by aapt that automatically keep 57 // classes referenced by the app manifest. Defaults to false. 58 No_aapt_flags *bool 59 60 // Flags to pass to proguard. 61 Proguard_flags []string 62 63 // Specifies the locations of files containing proguard flags. 64 Proguard_flags_files []string `android:"path"` 65 } 66 67 // Keep the data uncompressed. We always need uncompressed dex for execution, 68 // so this might actually save space by avoiding storing the same data twice. 69 // This defaults to reasonable value based on module and should not be set. 70 // It exists only to support ART tests. 71 Uncompress_dex *bool 72} 73 74type dexer struct { 75 dexProperties DexProperties 76 77 // list of extra proguard flag files 78 extraProguardFlagFiles android.Paths 79 proguardDictionary android.OptionalPath 80 proguardUsageZip android.OptionalPath 81} 82 83func (d *dexer) effectiveOptimizeEnabled() bool { 84 return BoolDefault(d.dexProperties.Optimize.Enabled, d.dexProperties.Optimize.EnabledByDefault) 85} 86 87var d8, d8RE = pctx.MultiCommandRemoteStaticRules("d8", 88 blueprint.RuleParams{ 89 Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` + 90 `$d8Template${config.D8Cmd} ${config.DexFlags} --output $outDir $d8Flags $in && ` + 91 `$zipTemplate${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` + 92 `${config.MergeZipsCmd} -D -stripFile "**/*.class" $out $outDir/classes.dex.jar $in`, 93 CommandDeps: []string{ 94 "${config.D8Cmd}", 95 "${config.SoongZipCmd}", 96 "${config.MergeZipsCmd}", 97 }, 98 }, map[string]*remoteexec.REParams{ 99 "$d8Template": &remoteexec.REParams{ 100 Labels: map[string]string{"type": "compile", "compiler": "d8"}, 101 Inputs: []string{"${config.D8Jar}"}, 102 ExecStrategy: "${config.RED8ExecStrategy}", 103 ToolchainInputs: []string{"${config.JavaCmd}"}, 104 Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, 105 }, 106 "$zipTemplate": &remoteexec.REParams{ 107 Labels: map[string]string{"type": "tool", "name": "soong_zip"}, 108 Inputs: []string{"${config.SoongZipCmd}", "$outDir"}, 109 OutputFiles: []string{"$outDir/classes.dex.jar"}, 110 ExecStrategy: "${config.RED8ExecStrategy}", 111 Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, 112 }, 113 }, []string{"outDir", "d8Flags", "zipFlags"}, nil) 114 115var r8, r8RE = pctx.MultiCommandRemoteStaticRules("r8", 116 blueprint.RuleParams{ 117 Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` + 118 `rm -f "$outDict" && rm -rf "${outUsageDir}" && ` + 119 `mkdir -p $$(dirname ${outUsage}) && ` + 120 `$r8Template${config.R8Cmd} ${config.DexFlags} -injars $in --output $outDir ` + 121 `--no-data-resources ` + 122 `-printmapping ${outDict} ` + 123 `-printusage ${outUsage} ` + 124 `$r8Flags && ` + 125 `touch "${outDict}" "${outUsage}" && ` + 126 `${config.SoongZipCmd} -o ${outUsageZip} -C ${outUsageDir} -f ${outUsage} && ` + 127 `rm -rf ${outUsageDir} && ` + 128 `$zipTemplate${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` + 129 `${config.MergeZipsCmd} -D -stripFile "**/*.class" $out $outDir/classes.dex.jar $in`, 130 CommandDeps: []string{ 131 "${config.R8Cmd}", 132 "${config.SoongZipCmd}", 133 "${config.MergeZipsCmd}", 134 }, 135 }, map[string]*remoteexec.REParams{ 136 "$r8Template": &remoteexec.REParams{ 137 Labels: map[string]string{"type": "compile", "compiler": "r8"}, 138 Inputs: []string{"$implicits", "${config.R8Jar}"}, 139 OutputFiles: []string{"${outUsage}"}, 140 ExecStrategy: "${config.RER8ExecStrategy}", 141 ToolchainInputs: []string{"${config.JavaCmd}"}, 142 Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, 143 }, 144 "$zipTemplate": &remoteexec.REParams{ 145 Labels: map[string]string{"type": "tool", "name": "soong_zip"}, 146 Inputs: []string{"${config.SoongZipCmd}", "$outDir"}, 147 OutputFiles: []string{"$outDir/classes.dex.jar"}, 148 ExecStrategy: "${config.RER8ExecStrategy}", 149 Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, 150 }, 151 "$zipUsageTemplate": &remoteexec.REParams{ 152 Labels: map[string]string{"type": "tool", "name": "soong_zip"}, 153 Inputs: []string{"${config.SoongZipCmd}", "${outUsage}"}, 154 OutputFiles: []string{"${outUsageZip}"}, 155 ExecStrategy: "${config.RER8ExecStrategy}", 156 Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, 157 }, 158 }, []string{"outDir", "outDict", "outUsage", "outUsageZip", "outUsageDir", 159 "r8Flags", "zipFlags"}, []string{"implicits"}) 160 161func (d *dexer) dexCommonFlags(ctx android.ModuleContext, minSdkVersion android.SdkSpec) []string { 162 flags := d.dexProperties.Dxflags 163 // Translate all the DX flags to D8 ones until all the build files have been migrated 164 // to D8 flags. See: b/69377755 165 flags = android.RemoveListFromList(flags, 166 []string{"--core-library", "--dex", "--multi-dex"}) 167 168 if ctx.Config().Getenv("NO_OPTIMIZE_DX") != "" { 169 flags = append(flags, "--debug") 170 } 171 172 if ctx.Config().Getenv("GENERATE_DEX_DEBUG") != "" { 173 flags = append(flags, 174 "--debug", 175 "--verbose") 176 } 177 178 effectiveVersion, err := minSdkVersion.EffectiveVersion(ctx) 179 if err != nil { 180 ctx.PropertyErrorf("min_sdk_version", "%s", err) 181 } 182 183 flags = append(flags, "--min-api "+strconv.Itoa(effectiveVersion.FinalOrFutureInt())) 184 return flags 185} 186 187func d8Flags(flags javaBuilderFlags) (d8Flags []string, d8Deps android.Paths) { 188 d8Flags = append(d8Flags, flags.bootClasspath.FormRepeatedClassPath("--lib ")...) 189 d8Flags = append(d8Flags, flags.classpath.FormRepeatedClassPath("--lib ")...) 190 191 d8Deps = append(d8Deps, flags.bootClasspath...) 192 d8Deps = append(d8Deps, flags.classpath...) 193 194 return d8Flags, d8Deps 195} 196 197func (d *dexer) r8Flags(ctx android.ModuleContext, flags javaBuilderFlags) (r8Flags []string, r8Deps android.Paths) { 198 opt := d.dexProperties.Optimize 199 200 // When an app contains references to APIs that are not in the SDK specified by 201 // its LOCAL_SDK_VERSION for example added by support library or by runtime 202 // classes added by desugaring, we artifically raise the "SDK version" "linked" by 203 // ProGuard, to 204 // - suppress ProGuard warnings of referencing symbols unknown to the lower SDK version. 205 // - prevent ProGuard stripping subclass in the support library that extends class added in the higher SDK version. 206 // See b/20667396 207 var proguardRaiseDeps classpath 208 ctx.VisitDirectDepsWithTag(proguardRaiseTag, func(m android.Module) { 209 dep := ctx.OtherModuleProvider(m, JavaInfoProvider).(JavaInfo) 210 proguardRaiseDeps = append(proguardRaiseDeps, dep.HeaderJars...) 211 }) 212 213 r8Flags = append(r8Flags, proguardRaiseDeps.FormJavaClassPath("-libraryjars")) 214 r8Flags = append(r8Flags, flags.bootClasspath.FormJavaClassPath("-libraryjars")) 215 r8Flags = append(r8Flags, flags.classpath.FormJavaClassPath("-libraryjars")) 216 217 r8Deps = append(r8Deps, proguardRaiseDeps...) 218 r8Deps = append(r8Deps, flags.bootClasspath...) 219 r8Deps = append(r8Deps, flags.classpath...) 220 221 flagFiles := android.Paths{ 222 android.PathForSource(ctx, "build/make/core/proguard.flags"), 223 } 224 225 flagFiles = append(flagFiles, d.extraProguardFlagFiles...) 226 // TODO(ccross): static android library proguard files 227 228 flagFiles = append(flagFiles, android.PathsForModuleSrc(ctx, opt.Proguard_flags_files)...) 229 230 r8Flags = append(r8Flags, android.JoinWithPrefix(flagFiles.Strings(), "-include ")) 231 r8Deps = append(r8Deps, flagFiles...) 232 233 // TODO(b/70942988): This is included from build/make/core/proguard.flags 234 r8Deps = append(r8Deps, android.PathForSource(ctx, 235 "build/make/core/proguard_basic_keeps.flags")) 236 237 r8Flags = append(r8Flags, opt.Proguard_flags...) 238 239 if BoolDefault(opt.Proguard_compatibility, true) { 240 r8Flags = append(r8Flags, "--force-proguard-compatibility") 241 } 242 243 // TODO(ccross): Don't shrink app instrumentation tests by default. 244 if !Bool(opt.Shrink) { 245 r8Flags = append(r8Flags, "-dontshrink") 246 } 247 248 if !Bool(opt.Optimize) { 249 r8Flags = append(r8Flags, "-dontoptimize") 250 } 251 252 // TODO(ccross): error if obufscation + app instrumentation test. 253 if !Bool(opt.Obfuscate) { 254 r8Flags = append(r8Flags, "-dontobfuscate") 255 } 256 // TODO(ccross): if this is an instrumentation test of an obfuscated app, use the 257 // dictionary of the app and move the app from libraryjars to injars. 258 259 // Don't strip out debug information for eng builds. 260 if ctx.Config().Eng() { 261 r8Flags = append(r8Flags, "--debug") 262 } 263 264 // TODO(b/180878971): missing classes should be added to the relevant builds. 265 r8Flags = append(r8Flags, "-ignorewarnings") 266 267 return r8Flags, r8Deps 268} 269 270func (d *dexer) compileDex(ctx android.ModuleContext, flags javaBuilderFlags, minSdkVersion android.SdkSpec, 271 classesJar android.Path, jarName string) android.OutputPath { 272 273 // Compile classes.jar into classes.dex and then javalib.jar 274 javalibJar := android.PathForModuleOut(ctx, "dex", jarName).OutputPath 275 outDir := android.PathForModuleOut(ctx, "dex") 276 277 zipFlags := "--ignore_missing_files" 278 if proptools.Bool(d.dexProperties.Uncompress_dex) { 279 zipFlags += " -L 0" 280 } 281 282 commonFlags := d.dexCommonFlags(ctx, minSdkVersion) 283 284 useR8 := d.effectiveOptimizeEnabled() 285 if useR8 { 286 proguardDictionary := android.PathForModuleOut(ctx, "proguard_dictionary") 287 d.proguardDictionary = android.OptionalPathForPath(proguardDictionary) 288 proguardUsageDir := android.PathForModuleOut(ctx, "proguard_usage") 289 proguardUsage := proguardUsageDir.Join(ctx, ctx.Namespace().Path, 290 android.ModuleNameWithPossibleOverride(ctx), "unused.txt") 291 proguardUsageZip := android.PathForModuleOut(ctx, "proguard_usage.zip") 292 d.proguardUsageZip = android.OptionalPathForPath(proguardUsageZip) 293 r8Flags, r8Deps := d.r8Flags(ctx, flags) 294 rule := r8 295 args := map[string]string{ 296 "r8Flags": strings.Join(append(commonFlags, r8Flags...), " "), 297 "zipFlags": zipFlags, 298 "outDict": proguardDictionary.String(), 299 "outUsageDir": proguardUsageDir.String(), 300 "outUsage": proguardUsage.String(), 301 "outUsageZip": proguardUsageZip.String(), 302 "outDir": outDir.String(), 303 } 304 if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_R8") { 305 rule = r8RE 306 args["implicits"] = strings.Join(r8Deps.Strings(), ",") 307 } 308 ctx.Build(pctx, android.BuildParams{ 309 Rule: rule, 310 Description: "r8", 311 Output: javalibJar, 312 ImplicitOutputs: android.WritablePaths{proguardDictionary, proguardUsageZip}, 313 Input: classesJar, 314 Implicits: r8Deps, 315 Args: args, 316 }) 317 } else { 318 d8Flags, d8Deps := d8Flags(flags) 319 rule := d8 320 if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_D8") { 321 rule = d8RE 322 } 323 ctx.Build(pctx, android.BuildParams{ 324 Rule: rule, 325 Description: "d8", 326 Output: javalibJar, 327 Input: classesJar, 328 Implicits: d8Deps, 329 Args: map[string]string{ 330 "d8Flags": strings.Join(append(commonFlags, d8Flags...), " "), 331 "zipFlags": zipFlags, 332 "outDir": outDir.String(), 333 }, 334 }) 335 } 336 if proptools.Bool(d.dexProperties.Uncompress_dex) { 337 alignedJavalibJar := android.PathForModuleOut(ctx, "aligned", jarName).OutputPath 338 TransformZipAlign(ctx, alignedJavalibJar, javalibJar) 339 javalibJar = alignedJavalibJar 340 } 341 342 return javalibJar 343} 344