Merge "Make minimal main-dex dependent on compilation mode"
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 4c9b542..2937205 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -205,6 +205,7 @@
InternalOptions internal = new InternalOptions(new DexItemFactory());
assert !internal.debug;
internal.debug = getMode() == CompilationMode.DEBUG;
+ internal.minimalMainDex = internal.debug;
internal.minApiLevel = getMinApiLevel();
internal.intermediate = intermediate;
// Assert and fixup defaults.
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 241ea85..241746b 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -26,7 +26,6 @@
public static class Builder extends BaseCommand.Builder<R8Command, Builder> {
private final List<Path> mainDexRules = new ArrayList<>();
- private boolean minimalMainDex = false;
private Path mainDexListOutput = null;
private final List<Path> proguardConfigFiles = new ArrayList<>();
private Optional<Boolean> treeShaking = Optional.empty();
@@ -78,17 +77,6 @@
return self();
}
- /**
- * Request minimal main dex generated when main dex rules are used.
- *
- * The main purpose of this is to verify that the main dex rules are sufficient
- * for running on a platform without native multi dex support.
- */
- public Builder setMinimalMainDex(boolean value) {
- minimalMainDex = value;
- return self();
- }
-
public Builder setMainDexListOutputPath(Path mainDexListOutputPath) {
mainDexListOutput = mainDexListOutputPath;
return self();
@@ -138,10 +126,6 @@
protected void validate() throws CompilationException {
super.validate();
- if (minimalMainDex && mainDexRules.isEmpty() && !getAppBuilder().hasMainDexList()) {
- throw new CompilationException(
- "Option --minimal-main-dex require --main-dex-rules and/or --main-dex-list");
- }
if (mainDexListOutput != null && mainDexRules.isEmpty() && !getAppBuilder().hasMainDexList()) {
throw new CompilationException(
"Option --main-dex-list-output require --main-dex-rules and/or --main-dex-list");
@@ -192,7 +176,6 @@
getOutputPath(),
getOutputMode(),
mainDexKeepRules,
- minimalMainDex,
mainDexListOutput,
configuration,
getMode(),
@@ -227,14 +210,11 @@
" --main-dex-rules <file> # Proguard keep rules for classes to place in the",
" # primary dex file.",
" --main-dex-list <file> # List of classes to place in the primary dex file.",
- " --minimal-main-dex # Only place classes specified by --main-dex-rules",
- " # in the primary dex file.",
" --main-dex-list-output <file> # Output the full main-dex list in <file>.",
" --version # Print the version of r8.",
" --help # Print this message."));
private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
- private final boolean minimalMainDex;
private final Path mainDexListOutput;
private final ProguardConfiguration proguardConfiguration;
private final boolean useTreeShaking;
@@ -301,8 +281,6 @@
builder.addMainDexRules(Paths.get(args[++i]));
} else if (arg.equals("--main-dex-list")) {
builder.addMainDexListFiles(Paths.get(args[++i]));
- } else if (arg.equals("--minimal-main-dex")) {
- builder.setMinimalMainDex(true);
} else if (arg.equals("--main-dex-list-output")) {
builder.setMainDexListOutputPath(Paths.get(args[++i]));
} else if (arg.equals("--pg-conf")) {
@@ -346,7 +324,6 @@
Path outputPath,
OutputMode outputMode,
ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
- boolean minimalMainDex,
Path mainDexListOutput,
ProguardConfiguration proguardConfiguration,
CompilationMode mode,
@@ -359,7 +336,6 @@
assert mainDexKeepRules != null;
assert getOutputMode() == OutputMode.Indexed : "Only regular mode is supported in R8";
this.mainDexKeepRules = mainDexKeepRules;
- this.minimalMainDex = minimalMainDex;
this.mainDexListOutput = mainDexListOutput;
this.proguardConfiguration = proguardConfiguration;
this.useTreeShaking = useTreeShaking;
@@ -370,7 +346,6 @@
private R8Command(boolean printHelp, boolean printVersion) {
super(printHelp, printVersion);
mainDexKeepRules = ImmutableList.of();
- minimalMainDex = false;
mainDexListOutput = null;
proguardConfiguration = null;
useTreeShaking = false;
@@ -430,7 +405,7 @@
internal.classObfuscationDictionary = proguardConfiguration.getClassObfuscationDictionary();
internal.obfuscationDictionary = proguardConfiguration.getObfuscationDictionary();
internal.mainDexKeepRules = mainDexKeepRules;
- internal.minimalMainDex = minimalMainDex;
+ internal.minimalMainDex = internal.debug;
if (mainDexListOutput != null) {
internal.printMainDexListFile = mainDexListOutput;
}
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 5c5a776..90c22f0 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -13,7 +13,6 @@
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 8064cde..a113003 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -4,12 +4,14 @@
package com.android.tools.r8.maindexlist;
import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.D8Command;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
@@ -64,6 +66,8 @@
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
@@ -232,34 +236,108 @@
MainDexList.parse(mainDexList, factory);
}
+ private Path runD8WithMainDexList(
+ CompilationMode mode, Path input, List<String> mainDexClasses, boolean useFile)
+ throws Exception {
+ Path testDir = temp.newFolder().toPath();
+ Path listFile = testDir.resolve("main-dex-list.txt");
+ if (mainDexClasses != null && useFile) {
+ FileUtils.writeTextFile(
+ listFile,
+ mainDexClasses
+ .stream()
+ .map(clazz -> clazz.replace('.', '/') + ".class")
+ .collect(Collectors.toList()));
+ }
+
+ D8Command.Builder builder = D8Command.builder()
+ .addProgramFiles(input)
+ .setMode(mode)
+ .setOutputPath(testDir);
+ if (mainDexClasses != null) {
+ if (useFile) {
+ builder.addMainDexListFiles(listFile);
+ } else {
+ builder.addMainDexClasses(mainDexClasses);
+ }
+ }
+ ToolHelper.runD8(builder.build());
+ return testDir;
+ }
+
+ private void runDeterministicTest(Path input, List<String> mainDexClasses, boolean allClasses)
+ throws Exception {
+ // Run test in debug and release mode for minimal vs. non-minimal main-dex.
+ for (CompilationMode mode : CompilationMode.values()) {
+
+ // Build with all different main dex lists.
+ List<Path> testDirs = new ArrayList<>();
+ if (allClasses) {
+ // If all classes are passed add a run without a main-dex list as well.
+ testDirs.add(runD8WithMainDexList(mode, input, null, true));
+ }
+ testDirs.add(runD8WithMainDexList(mode, input, mainDexClasses, true));
+ testDirs.add(runD8WithMainDexList(mode, input, mainDexClasses, false));
+ if (mainDexClasses != null) {
+ testDirs.add(runD8WithMainDexList(mode, input, Lists.reverse(mainDexClasses), true));
+ testDirs.add(runD8WithMainDexList(mode, input, Lists.reverse(mainDexClasses), false));
+ }
+
+ byte[] ref = null;
+ byte[] ref2 = null;
+ for (Path testDir : testDirs) {
+ Path primaryDexFile = testDir.resolve(FileUtils.DEFAULT_DEX_FILENAME);
+ Path secondaryDexFile = testDir.resolve("classes2.dex");
+ assertTrue(Files.exists(primaryDexFile));
+ boolean hasSecondaryDexFile = !allClasses && mode == CompilationMode.DEBUG;
+ assertEquals(hasSecondaryDexFile, Files.exists(testDir.resolve(secondaryDexFile)));
+ byte[] content = Files.readAllBytes(primaryDexFile);
+ if (ref == null) {
+ ref = content;
+ } else {
+ assertArrayEquals(ref, content);
+ }
+ if (hasSecondaryDexFile) {
+ content = Files.readAllBytes(primaryDexFile);
+ if (ref2 == null) {
+ ref2 = content;
+ } else {
+ assertArrayEquals(ref2, content);
+ }
+ }
+ }
+ }
+ }
+
@Test
- public void checkDeterminism() throws Exception {
+ public void deterministicTest() throws Exception {
// Synthesize a dex containing a few empty classes including some in the default package.
// Everything can fit easily in a single dex file.
- String[] classes = {
- "A",
- "B",
- "C",
- "D",
- "E",
- "F",
- "A1",
- "A2",
- "A3",
- "A4",
- "A5",
- "maindexlist/A",
- "maindexlist/B",
- "maindexlist/C",
- "maindexlist/D",
- "maindexlist/E",
- "maindexlist/F",
- "maindexlist/A1",
- "maindexlist/A2",
- "maindexlist/A3",
- "maindexlist/A4",
- "maindexlist/A5"
- };
+ ImmutableList<String> classes = new ImmutableList.Builder<String>()
+ .add("A")
+ .add("B")
+ .add("C")
+ .add("D")
+ .add("E")
+ .add("F")
+ .add("A1")
+ .add("A2")
+ .add("A3")
+ .add("A4")
+ .add("A5")
+ .add("maindexlist.A")
+ .add("maindexlist.B")
+ .add("maindexlist.C")
+ .add("maindexlist.D")
+ .add("maindexlist.E")
+ .add("maindexlist.F")
+ .add("maindexlist.A1")
+ .add("maindexlist.A2")
+ .add("maindexlist.A3")
+ .add("maindexlist.A4")
+ .add("maindexlist.A5")
+ .build();
+
JasminBuilder jasminBuilder = new JasminBuilder();
for (String name : classes) {
jasminBuilder.addClass(name);
@@ -267,69 +345,28 @@
Path input = temp.newFolder().toPath().resolve("input.zip");
ToolHelper.runR8(jasminBuilder.build()).writeToZip(input, OutputMode.Indexed);
- // Prepare different main dex lists.
- ArrayList<Path> mainLists = new ArrayList<>();
- // Lets first without a main dex list.
- mainLists.add(null);
+ // Test with empty main dex list.
+ runDeterministicTest(input, null, true);
- // List with all classes.
- List<String> mainList = new ArrayList<>();
- for (int i = 0; i < classes.length; i++) {
- mainList.add(classes[i] + ".class");
- }
- addMainListFile(mainLists, mainList);
+ // Test with main-dex list with all classes.
+ runDeterministicTest(input, classes, true);
- // Full list in reverse order
- addMainListFile(mainLists, Lists.reverse(mainList));
+ // Test with main-dex list with first and second half of the classes.
+ List<List<String>> partitions = Lists.partition(classes, classes.size() / 2);
+ runDeterministicTest(input, partitions.get(0), false);
+ runDeterministicTest(input, partitions.get(1), false);
- // Partial list without first entries (those in default package).
- mainList.clear();
- for (int i = classes.length / 2; i < classes.length; i++) {
- mainList.add(classes[i] + ".class");
- }
- addMainListFile(mainLists, mainList);
-
- // Same in reverse order
- addMainListFile(mainLists, Lists.reverse(mainList));
-
- // Mixed partial list.
- mainList.clear();
- for (int i = 0; i < classes.length; i += 2) {
- mainList.add(classes[i] + ".class");
- }
- addMainListFile(mainLists, mainList);
-
- // Another different mixed partial list.
- mainList.clear();
- for (int i = 1; i < classes.length; i += 2) {
- mainList.add(classes[i] + ".class");
- }
- addMainListFile(mainLists, mainList);
-
- // Build with all main dex lists.
- Path tmp = temp.getRoot().toPath();
- for (int i = 0; i < mainLists.size(); i++) {
- Path out = tmp.resolve(String.valueOf(i));
- Files.createDirectories(out);
- D8Command.Builder builder = D8Command.builder()
- .addProgramFiles(input)
- .setOutputPath(out);
- if (mainLists.get(i) != null) {
- builder.addMainDexListFiles(mainLists.get(i));
- }
- ToolHelper.runD8(builder.build());
- }
-
- // Check: no secondary dex and resulting dex is always the same.
- assertFalse(Files.exists(tmp.resolve(String.valueOf(0)).resolve("classes2.dex")));
- byte[] ref = Files.readAllBytes(
- tmp.resolve(String.valueOf(0)).resolve(FileUtils.DEFAULT_DEX_FILENAME));
- for (int i = 1; i < mainLists.size(); i++) {
- assertFalse(Files.exists(tmp.resolve(String.valueOf(i)).resolve("classes2.dex")));
- byte[] checked = Files.readAllBytes(
- tmp.resolve(String.valueOf(i)).resolve(FileUtils.DEFAULT_DEX_FILENAME));
- assertArrayEquals(ref, checked);
- }
+ // Test with main-dex list with every second of the classes.
+ runDeterministicTest(input,
+ IntStream.range(0, classes.size())
+ .filter(n -> n % 2 == 0)
+ .mapToObj(classes::get)
+ .collect(Collectors.toList()), false);
+ runDeterministicTest(input,
+ IntStream.range(0, classes.size())
+ .filter(n -> n % 2 == 1)
+ .mapToObj(classes::get)
+ .collect(Collectors.toList()), false);
}
@Test
@@ -362,13 +399,6 @@
}
}
- private void addMainListFile(ArrayList<Path> mainLists, List<String> content)
- throws IOException {
- Path listFile = temp.newFile().toPath();
- FileUtils.writeTextFile(listFile, content);
- mainLists.add(listFile);
- }
-
private static String typeToEntry(String type) {
return type.replace(".", "/") + FileUtils.CLASS_EXTENSION;
}
@@ -422,7 +452,8 @@
R8Command.Builder builder =
R8Command.builder()
.addProgramFiles(app)
- .setMinimalMainDex(minimalMainDex && mainDex.size() > 0)
+ .setMode(minimalMainDex && mainDex.size() > 0
+ ? CompilationMode.DEBUG : CompilationMode.RELEASE)
.setOutputPath(outDir)
.setTreeShaking(false)
.setMinification(false);
diff --git a/src/test/java/com/android/tools/r8/utils/R8CommandTest.java b/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
index cb88fb4..fdeb2dc 100644
--- a/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
@@ -121,12 +121,6 @@
}
@Test
- public void minimalMainDex() throws Throwable {
- thrown.expect(CompilationException.class);
- parse("--minimal-main-dex");
- }
-
- @Test
public void mainDexListOutput() throws Throwable {
Path mainDexRules = temp.newFile("main-dex.rules").toPath();
Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
@@ -145,12 +139,6 @@
}
@Test
- public void mainDexRulesWithMinimalMainDex() throws Throwable {
- Path mainDexRules = temp.newFile("main-dex.rules").toPath();
- parse("--main-dex-rules", mainDexRules.toString(), "--minimal-main-dex");
- }
-
- @Test
public void existingOutputDirWithDexFiles() throws Throwable {
Path existingDir = temp.newFolder().toPath();
List<Path> classesFiles = ImmutableList.of(