Skip to content

Commit

Permalink
Remove support for multidex=off
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 453318216
Change-Id: I6270f61ea08e7cd4b2712ead2f86a34a8330b40e
  • Loading branch information
cushon authored and copybara-github committed Jun 7, 2022
1 parent 46e4a2f commit 7de4fab
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 264 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -178,26 +178,13 @@ private static void validateRuleContext(RuleContext ruleContext, AndroidDataCont
ruleContext.throwWithAttributeError("shrink_resources", "This attribute is not supported");
}

if (Allowlist.hasAllowlist(ruleContext, "android_multidex_off_allowlist")
&& !Allowlist.isAvailable(ruleContext, "android_multidex_off_allowlist")
&& AndroidBinary.getMultidexMode(ruleContext) == MultidexMode.OFF) {
ruleContext.attributeError("multidex", "Multidex must be enabled");
}

if (ruleContext.attributes().isAttributeValueExplicitlySpecified("min_sdk_version")
&& Allowlist.hasAllowlist(ruleContext, "allow_min_sdk_version")
&& !Allowlist.isAvailable(ruleContext, "allow_min_sdk_version")) {
ruleContext.attributeError(
"min_sdk_version", "Target is not permitted to set min_sdk_version");
}

if (AndroidCommon.getAndroidConfig(ruleContext).desugarJava8Libs()
&& getMultidexMode(ruleContext) == MultidexMode.OFF) {
// Multidex is required so we can include legacy libs as a separate .dex file.
ruleContext.throwWithAttributeError(
"multidex", "Support for Java 8 libraries on legacy devices requires multidex");
}

if (ruleContext.getFragment(JavaConfiguration.class).enforceProguardFileExtension()
&& ruleContext.attributes().has(ProguardHelper.PROGUARD_SPECS)) {
List<PathFragment> pathsWithUnexpectedExtension =
Expand Down Expand Up @@ -1294,10 +1281,6 @@ private static DexingOutput dex(

int dexShards = ruleContext.attributes().get("dex_shards", Type.INTEGER).toIntUnchecked();
if (dexShards > 1) {
if (multidexMode == MultidexMode.OFF) {
ruleContext.throwWithRuleError(".dex sharding is only available in multidex mode");
}

if (multidexMode == MultidexMode.MANUAL_MAIN_DEX) {
ruleContext.throwWithRuleError(".dex sharding is not available in manual multidex mode");
}
Expand All @@ -1320,11 +1303,90 @@ private static DexingOutput dex(
AndroidCommon.getAndroidConfig(ruleContext).getJavaResourcesFromOptimizedJar()
? proguardedJar
: binaryJar;
if (multidexMode == MultidexMode.OFF) {
// Single dex mode: generate classes.dex directly from the input jar.
Artifact classesDex;

// Multidex mode: generate classes.dex.zip, where the zip contains [classes.dex,
// classes2.dex, ... classesN.dex].

if (multidexMode == MultidexMode.LEGACY) {
// For legacy multidex, we need to generate a list for the dexer's --main-dex-list flag.
mainDexList =
createMainDexListAction(
ruleContext, androidSemantics, proguardedJar, mainDexProguardSpec, proguardOutputMap);
} else if (multidexMode == MultidexMode.MANUAL_MAIN_DEX) {
mainDexList =
transformDexListThroughProguardMapAction(ruleContext, proguardOutputMap, mainDexList);
}

Artifact classesDex = getDxArtifact(ruleContext, "classes.dex.zip");
if (dexShards > 1) {
ImmutableList<Artifact> shards =
makeShardArtifacts(ruleContext, dexShards, usesDexArchives ? ".jar.dex.zip" : ".jar");

Artifact javaResourceJar =
createShuffleJarActions(
ruleContext,
usesDexArchives,
singleJarToDex,
shards,
common,
inclusionFilterJar,
dexopts,
minSdkVersion,
androidSemantics,
attributes,
derivedJarFunction,
mainDexList);

ImmutableList.Builder<Artifact> shardDexesBuilder = ImmutableList.builder();
for (int i = 1; i <= dexShards; i++) {
Artifact shard = shards.get(i - 1);
Artifact shardDex = getDxArtifact(ruleContext, "shard" + i + ".dex.zip");
shardDexesBuilder.add(shardDex);
if (usesDexArchives) {
// If there's a main dex list then the first shard contains exactly those files.
// To work with devices that lack native multi-dex support we need to make sure that
// the main dex list becomes one dex file if at all possible.
// Note shard here (mostly) contains of .class.dex files from shuffled dex archives,
// instead of being a conventional Jar file with .class files.
createDexMergerAction(
ruleContext,
mainDexList != null && i == 1 ? "minimal" : "best_effort",
ImmutableList.of(shard),
shardDex,
/*mainDexList=*/ null,
dexopts);
} else {
AndroidCommon.createDexAction(
ruleContext, shard, shardDex, dexopts, minSdkVersion, /*mainDexList=*/ null);
}
}
ImmutableList<Artifact> shardDexes = shardDexesBuilder.build();

CommandLine mergeCommandLine =
CustomCommandLine.builder()
.addExecPaths(VectorArg.addBefore("--input_zip").each(shardDexes))
.addExecPath("--output_zip", classesDex)
.build();
ruleContext.registerAction(
createSpawnActionBuilder(ruleContext)
.useDefaultShellEnvironment()
.setMnemonic("MergeDexZips")
.setProgressMessage("Merging dex shards for %s", ruleContext.getLabel())
.setExecutable(ruleContext.getExecutablePrerequisite("$merge_dexzips"))
.addInputs(shardDexes)
.addOutput(classesDex)
.addCommandLine(mergeCommandLine)
.build(ruleContext));
if (usesDexArchives) {
// Using the deploy jar for java resources gives better "bazel mobile-install" performance
// with incremental dexing b/c bazel can create the "incremental" and "split resource"
// APKs earlier (b/c these APKs don't depend on code being dexed here). This is also done
// for other multidex modes.
javaResourceJar = javaResourceSourceJar;
}
return new DexingOutput(classesDex, javaResourceJar, shardDexes);
} else {
if (usesDexArchives) {
classesDex = getDxArtifact(ruleContext, "classes.dex.zip");
createIncrementalDexingActions(
ruleContext,
singleJarToDex,
Expand All @@ -1335,156 +1397,30 @@ private static DexingOutput dex(
androidSemantics,
attributes,
derivedJarFunction,
/*multidex=*/ false,
/*mainDexList=*/ null,
mainDexList,
classesDex);
} else {
// By *not* writing a zip we get dx to drop resources on the floor.
classesDex = getDxArtifact(ruleContext, "classes.dex");
// Because the dexer also places resources into this zip, we also need to create a cleanup
// action that removes all non-.dex files before staging for apk building.
// Create an artifact for the intermediate zip output that includes non-.dex files.
Artifact classesDexIntermediate =
AndroidBinary.getDxArtifact(ruleContext, "intermediate_classes.dex.zip");
// Have the dexer generate the intermediate file and the "cleaner" action consume this to
// generate the final archive with only .dex files.
AndroidCommon.createDexAction(
ruleContext,
proguardedJar,
classesDex,
classesDexIntermediate,
dexopts,
minSdkVersion,
/*multidex=*/ false,
/*mainDexList=*/ null);
mainDexList);
createCleanDexZipAction(ruleContext, classesDexIntermediate, classesDex);
}
return new DexingOutput(classesDex, javaResourceSourceJar, ImmutableList.of(classesDex));
} else {
// Multidex mode: generate classes.dex.zip, where the zip contains [classes.dex,
// classes2.dex, ... classesN.dex].

if (multidexMode == MultidexMode.LEGACY) {
// For legacy multidex, we need to generate a list for the dexer's --main-dex-list flag.
mainDexList =
createMainDexListAction(
ruleContext,
androidSemantics,
proguardedJar,
mainDexProguardSpec,
proguardOutputMap);
} else if (multidexMode == MultidexMode.MANUAL_MAIN_DEX) {
mainDexList =
transformDexListThroughProguardMapAction(ruleContext, proguardOutputMap, mainDexList);
}

Artifact classesDex = getDxArtifact(ruleContext, "classes.dex.zip");
if (dexShards > 1) {
ImmutableList<Artifact> shards =
makeShardArtifacts(ruleContext, dexShards, usesDexArchives ? ".jar.dex.zip" : ".jar");

Artifact javaResourceJar =
createShuffleJarActions(
ruleContext,
usesDexArchives,
singleJarToDex,
shards,
common,
inclusionFilterJar,
dexopts,
minSdkVersion,
androidSemantics,
attributes,
derivedJarFunction,
mainDexList);

ImmutableList.Builder<Artifact> shardDexesBuilder = ImmutableList.builder();
for (int i = 1; i <= dexShards; i++) {
Artifact shard = shards.get(i - 1);
Artifact shardDex = getDxArtifact(ruleContext, "shard" + i + ".dex.zip");
shardDexesBuilder.add(shardDex);
if (usesDexArchives) {
// If there's a main dex list then the first shard contains exactly those files.
// To work with devices that lack native multi-dex support we need to make sure that
// the main dex list becomes one dex file if at all possible.
// Note shard here (mostly) contains of .class.dex files from shuffled dex archives,
// instead of being a conventional Jar file with .class files.
createDexMergerAction(
ruleContext,
mainDexList != null && i == 1 ? "minimal" : "best_effort",
ImmutableList.of(shard),
shardDex,
/*mainDexList=*/ null,
dexopts);
} else {
AndroidCommon.createDexAction(
ruleContext,
shard,
shardDex,
dexopts,
minSdkVersion,
/*multidex=*/ true,
/*mainDexList=*/ null);
}
}
ImmutableList<Artifact> shardDexes = shardDexesBuilder.build();

CommandLine mergeCommandLine =
CustomCommandLine.builder()
.addExecPaths(VectorArg.addBefore("--input_zip").each(shardDexes))
.addExecPath("--output_zip", classesDex)
.build();
ruleContext.registerAction(
createSpawnActionBuilder(ruleContext)
.useDefaultShellEnvironment()
.setMnemonic("MergeDexZips")
.setProgressMessage("Merging dex shards for %s", ruleContext.getLabel())
.setExecutable(ruleContext.getExecutablePrerequisite("$merge_dexzips"))
.addInputs(shardDexes)
.addOutput(classesDex)
.addCommandLine(mergeCommandLine)
.build(ruleContext));
if (usesDexArchives) {
// Using the deploy jar for java resources gives better "bazel mobile-install" performance
// with incremental dexing b/c bazel can create the "incremental" and "split resource"
// APKs earlier (b/c these APKs don't depend on code being dexed here). This is also done
// for other multidex modes.
javaResourceJar = javaResourceSourceJar;
}
return new DexingOutput(classesDex, javaResourceJar, shardDexes);
} else {
if (usesDexArchives) {
createIncrementalDexingActions(
ruleContext,
singleJarToDex,
common,
inclusionFilterJar,
dexopts,
minSdkVersion,
androidSemantics,
attributes,
derivedJarFunction,
/*multidex=*/ true,
mainDexList,
classesDex);
} else {
// Because the dexer also places resources into this zip, we also need to create a cleanup
// action that removes all non-.dex files before staging for apk building.
// Create an artifact for the intermediate zip output that includes non-.dex files.
Artifact classesDexIntermediate =
AndroidBinary.getDxArtifact(ruleContext, "intermediate_classes.dex.zip");
// Have the dexer generate the intermediate file and the "cleaner" action consume this to
// generate the final archive with only .dex files.
AndroidCommon.createDexAction(
ruleContext,
proguardedJar,
classesDexIntermediate,
dexopts,
minSdkVersion,
/*multidex=*/ true,
mainDexList);
createCleanDexZipAction(ruleContext, classesDexIntermediate, classesDex);
}
return new DexingOutput(classesDex, javaResourceSourceJar, ImmutableList.of(classesDex));
}
}
}

/**
* Helper that sets up dexbuilder/dexmerger actions when dex_shards attribute is not set, for use
* with or without multidex.
*/
/** Helper that sets up dexbuilder/dexmerger actions when dex_shards attribute is not set. */
private static void createIncrementalDexingActions(
RuleContext ruleContext,
@Nullable Artifact proguardedJar,
Expand All @@ -1495,13 +1431,11 @@ private static void createIncrementalDexingActions(
AndroidSemantics androidSemantics,
JavaTargetAttributes attributes,
Function<Artifact, Artifact> derivedJarFunction,
boolean multidex,
@Nullable Artifact mainDexList,
Artifact classesDex)
throws InterruptedException, RuleErrorException {
ImmutableList<Artifact> dexArchives;
if (proguardedJar == null
&& (multidex || inclusionFilterJar == null)
&& AndroidCommon.getAndroidConfig(ruleContext).incrementalDexingUseDexSharder()) {
dexArchives =
toDexedClasspath(
Expand Down Expand Up @@ -1555,10 +1489,9 @@ private static void createIncrementalDexingActions(
}
}

if (dexArchives.size() == 1 || !multidex) {
if (dexArchives.size() == 1) {
checkState(inclusionFilterJar == null);
createDexMergerAction(
ruleContext, multidex ? "minimal" : "off", dexArchives, classesDex, mainDexList, dexopts);
createDexMergerAction(ruleContext, "minimal", dexArchives, classesDex, mainDexList, dexopts);
} else {
SpecialArtifact shardsToMerge =
createSharderAction(ruleContext, dexArchives, mainDexList, dexopts, inclusionFilterJar);
Expand Down Expand Up @@ -2220,12 +2153,8 @@ public static Artifact createMainDexProguardSpec(Label label, ActionConstruction

/** Returns the multidex mode to apply to this target. */
public static MultidexMode getMultidexMode(RuleContext ruleContext) {
if (ruleContext.getRule().isAttrDefined("multidex", Type.STRING)) {
return Preconditions.checkNotNull(
MultidexMode.fromValue(ruleContext.attributes().get("multidex", Type.STRING)));
} else {
return MultidexMode.OFF;
}
}

private static int getMinSdkVersion(RuleContext ruleContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,9 +367,9 @@ private static Artifact getStubDex(
Artifact stubDex =
getMobileInstallArtifact(
ruleContext,
split ? "split_stub_application/classes.dex" : "stub_application/classes.dex");
split ? "split_stub_application/classes.dex.zip" : "stub_application/classes.dex.zip");
AndroidCommon.createDexAction(
ruleContext, stubDeployJar, stubDex, ImmutableList.<String>of(), 0, false, null);
ruleContext, stubDeployJar, stubDex, ImmutableList.<String>of(), 0, null);

return stubDex;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,28 +188,17 @@ public static void createDexAction(
Artifact classesDex,
List<String> dexOptions,
int minSdkVersion,
boolean multidex,
Artifact mainDexList) {
CustomCommandLine.Builder commandLine = CustomCommandLine.builder();
commandLine.add("--dex");

// Multithreaded dex does not work when using --multi-dex.
if (!multidex) {
// Multithreaded dex tends to run faster, but only up to about 5 threads (at which point the
// law of diminishing returns kicks in). This was determined experimentally, with 5-thread dex
// performing about 25% faster than 1-thread dex.
commandLine.add("--num-threads=" + DEX_THREADS);
}

commandLine.addAll(dexOptions);
if (minSdkVersion > 0) {
commandLine.add("--min_sdk_version", Integer.toString(minSdkVersion));
}
if (multidex) {
commandLine.add("--multi-dex");
if (mainDexList != null) {
commandLine.addPrefixedExecPath("--main-dex-list=", mainDexList);
}
commandLine.add("--multi-dex");
if (mainDexList != null) {
commandLine.addPrefixedExecPath("--main-dex-list=", mainDexList);
}
commandLine.addPrefixedExecPath("--output=", classesDex);
commandLine.addExecPath(jarToDex);
Expand Down Expand Up @@ -687,7 +676,7 @@ private void initJava(

filesToBuild = filesBuilder.build();

if ( attributes.hasSources() && jar != null) {
if (attributes.hasSources() && jar != null) {
iJar = helper.createCompileTimeJarAction(jar, javaArtifactsBuilder);
}

Expand Down
Loading

0 comments on commit 7de4fab

Please sign in to comment.