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 87func init() { 88 pctx.HostBinToolVariable("runWithTimeoutCmd", "run_with_timeout") 89 pctx.SourcePathVariable("jstackCmd", "${config.JavaToolchain}/jstack") 90} 91 92var d8, d8RE = pctx.MultiCommandRemoteStaticRules("d8", 93 blueprint.RuleParams{ 94 Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` + 95 `$d8Template${config.D8Cmd} ${config.DexFlags} --output $outDir $d8Flags $in && ` + 96 `$zipTemplate${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` + 97 `${config.MergeZipsCmd} -D -stripFile "**/*.class" $out $outDir/classes.dex.jar $in`, 98 CommandDeps: []string{ 99 "${config.D8Cmd}", 100 "${config.SoongZipCmd}", 101 "${config.MergeZipsCmd}", 102 }, 103 }, map[string]*remoteexec.REParams{ 104 "$d8Template": &remoteexec.REParams{ 105 Labels: map[string]string{"type": "compile", "compiler": "d8"}, 106 Inputs: []string{"${config.D8Jar}"}, 107 ExecStrategy: "${config.RED8ExecStrategy}", 108 ToolchainInputs: []string{"${config.JavaCmd}"}, 109 Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, 110 }, 111 "$zipTemplate": &remoteexec.REParams{ 112 Labels: map[string]string{"type": "tool", "name": "soong_zip"}, 113 Inputs: []string{"${config.SoongZipCmd}", "$outDir"}, 114 OutputFiles: []string{"$outDir/classes.dex.jar"}, 115 ExecStrategy: "${config.RED8ExecStrategy}", 116 Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, 117 }, 118 }, []string{"outDir", "d8Flags", "zipFlags"}, nil) 119 120var r8, r8RE = pctx.MultiCommandRemoteStaticRules("r8", 121 blueprint.RuleParams{ 122 Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` + 123 `rm -f "$outDict" && rm -rf "${outUsageDir}" && ` + 124 `mkdir -p $$(dirname ${outUsage}) && ` + 125 // TODO(b/181095653): remove R8 timeout and go back to config.R8Cmd. 126 `${runWithTimeoutCmd} -timeout 30m -on_timeout '${jstackCmd} $$PID' -- ` + 127 `$r8Template${config.JavaCmd} ${config.DexJavaFlags} -cp ${config.R8Jar} ` + 128 `com.android.tools.r8.compatproguard.CompatProguard -injars $in --output $outDir ` + 129 `--no-data-resources ` + 130 `-printmapping ${outDict} ` + 131 `-printusage ${outUsage} ` + 132 `$r8Flags && ` + 133 `touch "${outDict}" "${outUsage}" && ` + 134 `${config.SoongZipCmd} -o ${outUsageZip} -C ${outUsageDir} -f ${outUsage} && ` + 135 `rm -rf ${outUsageDir} && ` + 136 `$zipTemplate${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` + 137 `${config.MergeZipsCmd} -D -stripFile "**/*.class" $out $outDir/classes.dex.jar $in`, 138 CommandDeps: []string{ 139 "${config.R8Jar}", 140 "${config.SoongZipCmd}", 141 "${config.MergeZipsCmd}", 142 "${runWithTimeoutCmd}", 143 }, 144 }, map[string]*remoteexec.REParams{ 145 "$r8Template": &remoteexec.REParams{ 146 Labels: map[string]string{"type": "compile", "compiler": "r8"}, 147 Inputs: []string{"$implicits", "${config.R8Jar}"}, 148 OutputFiles: []string{"${outUsage}"}, 149 ExecStrategy: "${config.RER8ExecStrategy}", 150 ToolchainInputs: []string{"${config.JavaCmd}"}, 151 Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, 152 }, 153 "$zipTemplate": &remoteexec.REParams{ 154 Labels: map[string]string{"type": "tool", "name": "soong_zip"}, 155 Inputs: []string{"${config.SoongZipCmd}", "$outDir"}, 156 OutputFiles: []string{"$outDir/classes.dex.jar"}, 157 ExecStrategy: "${config.RER8ExecStrategy}", 158 Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, 159 }, 160 "$zipUsageTemplate": &remoteexec.REParams{ 161 Labels: map[string]string{"type": "tool", "name": "soong_zip"}, 162 Inputs: []string{"${config.SoongZipCmd}", "${outUsage}"}, 163 OutputFiles: []string{"${outUsageZip}"}, 164 ExecStrategy: "${config.RER8ExecStrategy}", 165 Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, 166 }, 167 }, []string{"outDir", "outDict", "outUsage", "outUsageZip", "outUsageDir", 168 "r8Flags", "zipFlags"}, []string{"implicits"}) 169 170func (d *dexer) dexCommonFlags(ctx android.ModuleContext, minSdkVersion android.SdkSpec) []string { 171 flags := d.dexProperties.Dxflags 172 // Translate all the DX flags to D8 ones until all the build files have been migrated 173 // to D8 flags. See: b/69377755 174 flags = android.RemoveListFromList(flags, 175 []string{"--core-library", "--dex", "--multi-dex"}) 176 177 if ctx.Config().Getenv("NO_OPTIMIZE_DX") != "" { 178 flags = append(flags, "--debug") 179 } 180 181 if ctx.Config().Getenv("GENERATE_DEX_DEBUG") != "" { 182 flags = append(flags, 183 "--debug", 184 "--verbose") 185 } 186 187 effectiveVersion, err := minSdkVersion.EffectiveVersion(ctx) 188 if err != nil { 189 ctx.PropertyErrorf("min_sdk_version", "%s", err) 190 } 191 192 flags = append(flags, "--min-api "+strconv.Itoa(effectiveVersion.FinalOrFutureInt())) 193 return flags 194} 195 196func d8Flags(flags javaBuilderFlags) (d8Flags []string, d8Deps android.Paths) { 197 d8Flags = append(d8Flags, flags.bootClasspath.FormRepeatedClassPath("--lib ")...) 198 d8Flags = append(d8Flags, flags.classpath.FormRepeatedClassPath("--lib ")...) 199 200 d8Deps = append(d8Deps, flags.bootClasspath...) 201 d8Deps = append(d8Deps, flags.classpath...) 202 203 return d8Flags, d8Deps 204} 205 206func (d *dexer) r8Flags(ctx android.ModuleContext, flags javaBuilderFlags) (r8Flags []string, r8Deps android.Paths) { 207 opt := d.dexProperties.Optimize 208 209 // When an app contains references to APIs that are not in the SDK specified by 210 // its LOCAL_SDK_VERSION for example added by support library or by runtime 211 // classes added by desugaring, we artifically raise the "SDK version" "linked" by 212 // ProGuard, to 213 // - suppress ProGuard warnings of referencing symbols unknown to the lower SDK version. 214 // - prevent ProGuard stripping subclass in the support library that extends class added in the higher SDK version. 215 // See b/20667396 216 var proguardRaiseDeps classpath 217 ctx.VisitDirectDepsWithTag(proguardRaiseTag, func(m android.Module) { 218 dep := ctx.OtherModuleProvider(m, JavaInfoProvider).(JavaInfo) 219 proguardRaiseDeps = append(proguardRaiseDeps, dep.HeaderJars...) 220 }) 221 222 r8Flags = append(r8Flags, proguardRaiseDeps.FormJavaClassPath("-libraryjars")) 223 r8Flags = append(r8Flags, flags.bootClasspath.FormJavaClassPath("-libraryjars")) 224 r8Flags = append(r8Flags, flags.classpath.FormJavaClassPath("-libraryjars")) 225 226 r8Deps = append(r8Deps, proguardRaiseDeps...) 227 r8Deps = append(r8Deps, flags.bootClasspath...) 228 r8Deps = append(r8Deps, flags.classpath...) 229 230 flagFiles := android.Paths{ 231 android.PathForSource(ctx, "build/make/core/proguard.flags"), 232 } 233 234 flagFiles = append(flagFiles, d.extraProguardFlagFiles...) 235 // TODO(ccross): static android library proguard files 236 237 flagFiles = append(flagFiles, android.PathsForModuleSrc(ctx, opt.Proguard_flags_files)...) 238 239 r8Flags = append(r8Flags, android.JoinWithPrefix(flagFiles.Strings(), "-include ")) 240 r8Deps = append(r8Deps, flagFiles...) 241 242 // TODO(b/70942988): This is included from build/make/core/proguard.flags 243 r8Deps = append(r8Deps, android.PathForSource(ctx, 244 "build/make/core/proguard_basic_keeps.flags")) 245 246 r8Flags = append(r8Flags, opt.Proguard_flags...) 247 248 if BoolDefault(opt.Proguard_compatibility, true) { 249 r8Flags = append(r8Flags, "--force-proguard-compatibility") 250 } 251 252 // TODO(ccross): Don't shrink app instrumentation tests by default. 253 if !Bool(opt.Shrink) { 254 r8Flags = append(r8Flags, "-dontshrink") 255 } 256 257 if !Bool(opt.Optimize) { 258 r8Flags = append(r8Flags, "-dontoptimize") 259 } 260 261 // TODO(ccross): error if obufscation + app instrumentation test. 262 if !Bool(opt.Obfuscate) { 263 r8Flags = append(r8Flags, "-dontobfuscate") 264 } 265 // TODO(ccross): if this is an instrumentation test of an obfuscated app, use the 266 // dictionary of the app and move the app from libraryjars to injars. 267 268 // Don't strip out debug information for eng builds. 269 if ctx.Config().Eng() { 270 r8Flags = append(r8Flags, "--debug") 271 } 272 273 // TODO(b/180878971): missing classes should be added to the relevant builds. 274 r8Flags = append(r8Flags, "-ignorewarnings") 275 276 return r8Flags, r8Deps 277} 278 279func (d *dexer) compileDex(ctx android.ModuleContext, flags javaBuilderFlags, minSdkVersion android.SdkSpec, 280 classesJar android.Path, jarName string) android.OutputPath { 281 282 // Compile classes.jar into classes.dex and then javalib.jar 283 javalibJar := android.PathForModuleOut(ctx, "dex", jarName).OutputPath 284 outDir := android.PathForModuleOut(ctx, "dex") 285 286 zipFlags := "--ignore_missing_files" 287 if proptools.Bool(d.dexProperties.Uncompress_dex) { 288 zipFlags += " -L 0" 289 } 290 291 commonFlags := d.dexCommonFlags(ctx, minSdkVersion) 292 293 useR8 := d.effectiveOptimizeEnabled() 294 if useR8 { 295 proguardDictionary := android.PathForModuleOut(ctx, "proguard_dictionary") 296 d.proguardDictionary = android.OptionalPathForPath(proguardDictionary) 297 proguardUsageDir := android.PathForModuleOut(ctx, "proguard_usage") 298 proguardUsage := proguardUsageDir.Join(ctx, ctx.Namespace().Path, 299 android.ModuleNameWithPossibleOverride(ctx), "unused.txt") 300 proguardUsageZip := android.PathForModuleOut(ctx, "proguard_usage.zip") 301 d.proguardUsageZip = android.OptionalPathForPath(proguardUsageZip) 302 r8Flags, r8Deps := d.r8Flags(ctx, flags) 303 rule := r8 304 args := map[string]string{ 305 "r8Flags": strings.Join(append(commonFlags, r8Flags...), " "), 306 "zipFlags": zipFlags, 307 "outDict": proguardDictionary.String(), 308 "outUsageDir": proguardUsageDir.String(), 309 "outUsage": proguardUsage.String(), 310 "outUsageZip": proguardUsageZip.String(), 311 "outDir": outDir.String(), 312 } 313 if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_R8") { 314 rule = r8RE 315 args["implicits"] = strings.Join(r8Deps.Strings(), ",") 316 } 317 ctx.Build(pctx, android.BuildParams{ 318 Rule: rule, 319 Description: "r8", 320 Output: javalibJar, 321 ImplicitOutputs: android.WritablePaths{proguardDictionary, proguardUsageZip}, 322 Input: classesJar, 323 Implicits: r8Deps, 324 Args: args, 325 }) 326 } else { 327 d8Flags, d8Deps := d8Flags(flags) 328 rule := d8 329 if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_D8") { 330 rule = d8RE 331 } 332 ctx.Build(pctx, android.BuildParams{ 333 Rule: rule, 334 Description: "d8", 335 Output: javalibJar, 336 Input: classesJar, 337 Implicits: d8Deps, 338 Args: map[string]string{ 339 "d8Flags": strings.Join(append(commonFlags, d8Flags...), " "), 340 "zipFlags": zipFlags, 341 "outDir": outDir.String(), 342 }, 343 }) 344 } 345 if proptools.Bool(d.dexProperties.Uncompress_dex) { 346 alignedJavalibJar := android.PathForModuleOut(ctx, "aligned", jarName).OutputPath 347 TransformZipAlign(ctx, alignedJavalibJar, javalibJar) 348 javalibJar = alignedJavalibJar 349 } 350 351 return javalibJar 352} 353