diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java index 2e7258018909b4..a0772c085e8844 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java @@ -25,6 +25,7 @@ import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.actions.Artifact; @@ -445,6 +446,9 @@ public static RuleConfiguredTargetBuilder createAndroidBinary( proguardDeps); boolean hasProguardSpecs = !proguardSpecs.isEmpty(); + // Only use R8 if we are actually proguarding, otherwise fallback to old behavior (D8) + boolean useR8 = AndroidCommon.getAndroidConfig(ruleContext).enableR8() && hasProguardSpecs; + // TODO(bazel-team): Verify that proguard spec files don't contain -printmapping directions // which this -printmapping command line flag will override. Artifact proguardOutputMap = null; @@ -527,20 +531,23 @@ public static RuleConfiguredTargetBuilder createAndroidBinary( resourceApk = maybeOptimizeResources(dataContext, resourceApk, hasProguardSpecs); + // R8 takes care of dexing so only dex if r8 is not enabled Artifact jarToDex = proguardOutput.getOutputJar(); - DexingOutput dexingOutput = - dex( - ruleContext, - androidSemantics, - binaryJar, - jarToDex, - isBinaryJarFiltered, - androidCommon, - resourceApk.getMainDexProguardConfig(), - resourceClasses, - derivedJarFunction, - proguardOutputMap); - + DexingOutput dexingOutput = null; + if (!useR8) { + dexingOutput = + dex( + ruleContext, + androidSemantics, + binaryJar, + jarToDex, + isBinaryJarFiltered, + androidCommon, + resourceApk.getMainDexProguardConfig(), + resourceClasses, + derivedJarFunction, + proguardOutputMap); + } // Collect all native shared libraries across split transitions. Some AARs contain shared // libraries across multiple architectures, e.g. x86 and armeabi-v7a, and need to be packed // into the APK. @@ -556,79 +563,85 @@ public static RuleConfiguredTargetBuilder createAndroidBinary( } NestedSet nativeLibsAar = transitiveNativeLibs.build(); - DexPostprocessingOutput dexPostprocessingOutput = + DexPostprocessingOutput dexPostprocessingOutput = null; + if (!useR8) { + dexPostprocessingOutput = androidSemantics.postprocessClassesDexZip( ruleContext, filesBuilder, dexingOutput.classesDexZip, proguardOutput, postProcessingOutputMap); + } // Compute the final DEX files by appending Java 8 legacy .dex if used. - Artifact finalClassesDex; + Artifact finalClassesDex = null; + ImmutableList finalShardDexZips = null; Java8LegacyDexOutput java8LegacyDexOutput; - ImmutableList finalShardDexZips = dexingOutput.shardDexZips; - if (AndroidCommon.getAndroidConfig(ruleContext).desugarJava8Libs() - && dexPostprocessingOutput.classesDexZip().getFilename().endsWith(".zip")) { - if (binaryJar.equals(jarToDex)) { - // No shrinking: use canned Java 8 legacy .dex file - java8LegacyDexOutput = Java8LegacyDexOutput.getCanned(ruleContext); - } else { - // Shrinking is used: build custom Java 8 legacy .dex file - java8LegacyDexOutput = buildJava8LegacyDex(ruleContext, jarToDex); - - // Merge the mapping files from shrinking the program and Java 8 legacy .dex file. - if (finalProguardOutputMap != null) { - ruleContext.registerAction( - createSpawnActionBuilder(ruleContext) - .useDefaultShellEnvironment() - .setExecutable(ruleContext.getExecutablePrerequisite("$merge_proguard_maps")) - .addInput(dexPostprocessingOutput.proguardMap()) - .addInput(java8LegacyDexOutput.getMap()) - .addOutput(finalProguardOutputMap) - .addCommandLine( - CustomCommandLine.builder() - .addExecPath("--pg-map", dexPostprocessingOutput.proguardMap()) - .addExecPath("--pg-map", java8LegacyDexOutput.getMap()) - .addExecPath("--pg-map-output", finalProguardOutputMap) - .build()) - .setMnemonic("MergeProguardMaps") - .setProgressMessage( - "Merging app and desugared library Proguard maps for %{label}") - .build(ruleContext)); + if (!useR8) { + finalShardDexZips = dexingOutput.shardDexZips; + if (AndroidCommon.getAndroidConfig(ruleContext).desugarJava8Libs() + && dexPostprocessingOutput.classesDexZip().getFilename().endsWith(".zip")) { + if (binaryJar.equals(jarToDex)) { + // No shrinking: use canned Java 8 legacy .dex file + java8LegacyDexOutput = Java8LegacyDexOutput.getCanned(ruleContext); + } else { + // Shrinking is used: build custom Java 8 legacy .dex file + java8LegacyDexOutput = buildJava8LegacyDex(ruleContext, jarToDex); + + // Merge the mapping files from shrinking the program and Java 8 legacy .dex file. + if (finalProguardOutputMap != null) { + ruleContext.registerAction( + createSpawnActionBuilder(ruleContext) + .useDefaultShellEnvironment() + .setExecutable(ruleContext.getExecutablePrerequisite("$merge_proguard_maps")) + .addInput(dexPostprocessingOutput.proguardMap()) + .addInput(java8LegacyDexOutput.getMap()) + .addOutput(finalProguardOutputMap) + .addCommandLine( + CustomCommandLine.builder() + .addExecPath("--pg-map", dexPostprocessingOutput.proguardMap()) + .addExecPath("--pg-map", java8LegacyDexOutput.getMap()) + .addExecPath("--pg-map-output", finalProguardOutputMap) + .build()) + .setMnemonic("MergeProguardMaps") + .setProgressMessage( + "Merging app and desugared library Proguard maps for %{label}") + .build(ruleContext)); + } } - } - // Append legacy .dex library to app's .dex files - finalClassesDex = getDxArtifact(ruleContext, "_final_classes.dex.zip"); - ruleContext.registerAction( - createSpawnActionBuilder(ruleContext) - .useDefaultShellEnvironment() - .setMnemonic("AppendJava8LegacyDex") - .setProgressMessage("Adding Java 8 legacy library for %s", ruleContext.getLabel()) - .setExecutable(ruleContext.getExecutablePrerequisite("$merge_dexzips")) - .addInput(dexPostprocessingOutput.classesDexZip()) - .addInput(java8LegacyDexOutput.getDex()) - .addOutput(finalClassesDex) - // Order matters here: we want java8LegacyDex to be the highest-numbered classesN.dex - .addCommandLine( - CustomCommandLine.builder() - .addExecPath("--input_zip", dexPostprocessingOutput.classesDexZip()) - .addExecPath("--input_zip", java8LegacyDexOutput.getDex()) - .addExecPath("--output_zip", finalClassesDex) - .build()) - .build(ruleContext)); - finalShardDexZips = - ImmutableList.builder() - .addAll(finalShardDexZips) - .add(java8LegacyDexOutput.getDex()) - .build(); + // Append legacy .dex library to app's .dex files + finalClassesDex = getDxArtifact(ruleContext, "_final_classes.dex.zip"); + ruleContext.registerAction( + createSpawnActionBuilder(ruleContext) + .useDefaultShellEnvironment() + .setMnemonic("AppendJava8LegacyDex") + .setProgressMessage("Adding Java 8 legacy library for %s", ruleContext.getLabel()) + .setExecutable(ruleContext.getExecutablePrerequisite("$merge_dexzips")) + .addInput(dexPostprocessingOutput.classesDexZip()) + .addInput(java8LegacyDexOutput.getDex()) + .addOutput(finalClassesDex) + // Order matters here: we want java8LegacyDex to be the highest-numbered classesN.dex + .addCommandLine( + CustomCommandLine.builder() + .addExecPath("--input_zip", dexPostprocessingOutput.classesDexZip()) + .addExecPath("--input_zip", java8LegacyDexOutput.getDex()) + .addExecPath("--output_zip", finalClassesDex) + .build()) + .build(ruleContext)); + finalShardDexZips = + ImmutableList.builder() + .addAll(finalShardDexZips) + .add(java8LegacyDexOutput.getDex()) + .build(); - } else { - finalClassesDex = dexPostprocessingOutput.classesDexZip(); + } else { + finalClassesDex = dexPostprocessingOutput.classesDexZip(); + } } - if (hasProguardSpecs) { + if (!useR8 && hasProguardSpecs) { proguardOutput.addAllToSet(filesBuilder, finalProguardOutputMap); } @@ -647,10 +660,8 @@ public static RuleConfiguredTargetBuilder createAndroidBinary( FilesToRunProvider resourceExtractor = ruleContext.getExecutablePrerequisite("$resource_extractor"); - ApkActionsBuilder.create("apk") - .setClassesDex(finalClassesDex) + ApkActionsBuilder apkBuilder = ApkActionsBuilder.create("apk") .addInputZip(resourceApk.getArtifact()) - .setJavaResourceZip(dexingOutput.javaResourceJar, resourceExtractor) .addInputZips(nativeLibsAar.toList()) .setNativeLibs(nativeLibs) .setUnsignedApk(unsignedApk) @@ -660,8 +671,16 @@ public static RuleConfiguredTargetBuilder createAndroidBinary( .setSigningKeyRotationMinSdk(keyRotationMinSdk) .setV4Signature(v4Signature) .setZipalignApk(true) - .setDeterministicSigning(androidSemantics.deterministicSigning()) - .registerActions(ruleContext); + .setDeterministicSigning(androidSemantics.deterministicSigning()); + + // R8 outputs a single jar with dexed classes and resources + if (useR8) { + apkBuilder.setJavaResourceZip(proguardOutput.getOutputJar(), resourceExtractor); + } else { + apkBuilder.setClassesDex(finalClassesDex); + apkBuilder.setJavaResourceZip(dexingOutput.javaResourceJar, resourceExtractor); + } + apkBuilder.registerActions(ruleContext); filesBuilder.add(binaryJar); filesBuilder.add(unsignedApk); @@ -736,16 +755,19 @@ public static RuleConfiguredTargetBuilder createAndroidBinary( androidCommon.isNeverLink(), /* isLibrary = */ false); - if (dexPostprocessingOutput.proguardMap() != null) { - builder.addNativeDeclaredProvider( - new ProguardMappingProvider(dexPostprocessingOutput.proguardMap())); + if (!useR8) { + if (dexPostprocessingOutput.proguardMap() != null) { + builder.addNativeDeclaredProvider( + new ProguardMappingProvider(dexPostprocessingOutput.proguardMap())); + } } if (oneVersionEnforcementArtifact != null) { builder.addOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL, oneVersionEnforcementArtifact); } - if (mobileInstallResourceApks != null) { + // Mobile-Install relies on sharding implementation when manually dexing jars. Disable when using R8. + if (!useR8 && mobileInstallResourceApks != null) { AndroidBinaryMobileInstall.addMobileInstall( ruleContext, builder, @@ -967,25 +989,31 @@ private static ProguardOutput applyProguard( } libraryJars.addTransitive(common.getTransitiveNeverLinkLibraries()); - Artifact proguardSeeds = + Artifact proguardSeeds = null; + Artifact proguardUsage = null; + if (!AndroidCommon.getAndroidConfig(ruleContext).enableR8()) { + proguardSeeds = ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_PROGUARD_SEEDS); - Artifact proguardUsage = + proguardUsage = ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_PROGUARD_USAGE); + } Artifact proguardDictionary = ruleContext.getPrerequisiteArtifact("proguard_apply_dictionary"); - return ProguardHelper.createOptimizationActions( - ruleContext, - sdk.getProguard(), - deployJarArtifact, - proguardSpecs, - proguardSeeds, - proguardUsage, - proguardMapping, - proguardDictionary, - libraryJars.build(), - proguardOutputJar, - javaSemantics, - getProguardOptimizationPasses(ruleContext), - proguardOutputMap); + ProguardOutput result = + ProguardHelper.createOptimizationActions( + ruleContext, + sdk.getProguard(), + deployJarArtifact, + proguardSpecs, + proguardSeeds, + proguardUsage, + proguardMapping, + proguardDictionary, + libraryJars.build(), + proguardOutputJar, + javaSemantics, + getProguardOptimizationPasses(ruleContext), + proguardOutputMap); + return result; } @Nullable diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java index 0b0188bcf937f0..1fc0be69dcda4d 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java @@ -427,6 +427,21 @@ public static class Options extends FragmentOptions { + "Use incremental_dexing attribute to override default for a particular " + "android_binary.") public boolean incrementalDexingAfterProguardByDefault; + + @Option( + name = "experimental_enable_r8", + defaultValue = "false", + metadataTags = {OptionMetadataTag.EXPERIMENTAL}, + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS}, + help = + "Whether to use R8 instead of proguarding. " + + "This only has effect if proguard_specs are specified. " + + " Legacy multidex and mobile-install are not supported with R8.") + public boolean enableR8; + + + // TODO(b/31711689): Remove this flag when this optimization is proven to work globally. @Option( @@ -982,6 +997,7 @@ public FragmentOptions getHost() { host.incrementalDexingShardsAfterProguard = incrementalDexingShardsAfterProguard; host.incrementalDexingUseDexSharder = incrementalDexingUseDexSharder; host.incrementalDexingAfterProguardByDefault = incrementalDexingAfterProguardByDefault; + host.enableR8 = enableR8; host.assumeMinSdkVersion = assumeMinSdkVersion; host.nonIncrementalPerTargetDexopts = nonIncrementalPerTargetDexopts; host.dexoptsSupportedInIncrementalDexing = dexoptsSupportedInIncrementalDexing; @@ -1008,6 +1024,7 @@ public FragmentOptions getHost() { private final int incrementalDexingShardsAfterProguard; private final boolean incrementalDexingUseDexSharder; private final boolean incrementalDexingAfterProguardByDefault; + private final boolean enableR8; private final boolean assumeMinSdkVersion; private final ImmutableList dexoptsSupportedInIncrementalDexing; private final ImmutableList targetDexoptsThatPreventIncrementalDexing; @@ -1060,6 +1077,7 @@ public AndroidConfiguration(BuildOptions buildOptions) throws InvalidConfigurati this.incrementalDexingShardsAfterProguard = options.incrementalDexingShardsAfterProguard; this.incrementalDexingUseDexSharder = options.incrementalDexingUseDexSharder; this.incrementalDexingAfterProguardByDefault = options.incrementalDexingAfterProguardByDefault; + this.enableR8 = options.enableR8; this.assumeMinSdkVersion = options.assumeMinSdkVersion; this.dexoptsSupportedInIncrementalDexing = ImmutableList.copyOf(options.dexoptsSupportedInIncrementalDexing); @@ -1165,6 +1183,12 @@ public boolean incrementalDexingAfterProguardByDefault() { return incrementalDexingAfterProguardByDefault; } + /** Whether to dex class files using the r8 binary. */ + @Override + public boolean enableR8() { + return enableR8; + } + /** * Returns true if an -assumevalues should be generated for Proguard based on the minSdkVersion of * the merged AndroidManifest. diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/android/AndroidConfigurationApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/android/AndroidConfigurationApi.java index 197d3c16a4e5a9..9314b3988095b5 100644 --- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/android/AndroidConfigurationApi.java +++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/android/AndroidConfigurationApi.java @@ -61,6 +61,14 @@ public interface AndroidConfigurationApi extends StarlarkValue { documented = false) boolean incrementalDexingAfterProguardByDefault(); + @StarlarkMethod( + name = "experimental_enable_r8", + structField = true, + doc = "When true R8 is used for dexing and proguarding. " + + "Only in effect when proguard_specs are specified.", + documented = false) + boolean enableR8(); + @StarlarkMethod(name = "apk_signing_method_v1", structField = true, doc = "", documented = false) boolean apkSigningMethodV1();