Update the main-dex handling for D8 and R8
* Both D8 and R8 can now take several main-dex files.
* In the API main-dex classes can be specified using strings as well
as through files.
* R8 have an explicit option for outputting the computed main-dex list.
Added the APIs
BaseCommand.addMainDexListFiles(Path... files)
BaseCommand.addMainDexListFiles(Collection<Path> files)
BaseCommand.addMainDexClass(String clazz)
R8Command.setMainDexListOutputPath(Path mainDexListOutputPath)
Removed the API
BaseCommand.setMainDexListFile(Path file)
D8 and R8 can take several --main-dex-list options
R8 have the new option --main-dex-list-output
Change-Id: Ibca32c74a193c6961ad3980d92f61b0561d02272
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index 5eb67c4..430624b 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -219,9 +219,53 @@
return self();
}
- /** Set the main-dex list file. */
- public B setMainDexListFile(Path file) {
- app.setMainDexListFile(file);
+ /**
+ * Add main-dex list files.
+ *
+ * Each line in each of the files specifies one class to keep in the primary dex file
+ * (<code>classes.dex</code>).
+ *
+ * A class is specified using the following format: "com/example/MyClass.class". That is
+ * "/" as separator between package components, and a trailing ".class".
+ */
+ public B addMainDexListFiles(Path... files) throws IOException {
+ app.addMainDexListFiles(files);
+ return self();
+ }
+
+ /**
+ * Add main-dex list files.
+ *
+ * @see #addMainDexListFiles(Path...)
+ */
+ public B addMainDexListFiles(Collection<Path> files) throws IOException {
+ app.addMainDexListFiles(files);
+ return self();
+ }
+
+ /**
+ * Add main-dex classes.
+ *
+ * Add classes to keep in the primary dex file (<code>classes.dex</code>).
+ *
+ * NOTE: The name of the classes is specified using the Java fully qualified names format
+ * (e.g. "com.example.MyClass"), and <i>not</i> the format used by the main-dex list file.
+ */
+ public B addMainDexClasses(String... classes) {
+ app.addMainDexClasses(classes);
+ return self();
+ }
+
+ /**
+ * Add main-dex classes.
+ *
+ * Add classes to keep in the primary dex file (<code>classes.dex</code>).
+ *
+ * NOTE: The name of the classes is specified using the Java fully qualified names format
+ * (e.g. "com.example.MyClass"), and <i>not</i> the format used by the main-dex list file.
+ */
+ public B addMainDexClasses(Collection<String> classes) {
+ app.addMainDexClasses(classes);
return self();
}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index b856556..4c9b542 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -131,7 +131,6 @@
public static Builder parse(String[] args) throws CompilationException, IOException {
CompilationMode modeSet = null;
Path outputPath = null;
- String mainDexList = null;
Builder builder = builder();
try {
for (int i = 0; i < args.length; i++) {
@@ -168,11 +167,7 @@
} else if (arg.equals("--classpath")) {
builder.addClasspathFiles(Paths.get(args[++i]));
} else if (arg.equals("--main-dex-list")) {
- if (mainDexList != null) {
- throw new CompilationException("Only one --main-dex-list supported");
- }
- mainDexList = args[++i];
- builder.setMainDexListFile(Paths.get(mainDexList));
+ builder.addMainDexListFiles(Paths.get(args[++i]));
} else if (arg.equals("--min-api")) {
builder.setMinApiLevel(Integer.valueOf(args[++i]));
} else if (arg.equals("--intermediate")) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 50f79dc..5f56d79 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -432,7 +432,7 @@
outputApp.writeProguardSeeds(closer, seedsOut);
}
}
- if (options.printMainDexList && outputApp.hasMainDexList()) {
+ if (outputApp.hasMainDexListOutput()) {
try (Closer closer = Closer.create()) {
OutputStream mainDexOut =
FileUtils.openPathWithDefault(
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 8d3d937..241ea85 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -27,6 +27,7 @@
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();
private Optional<Boolean> minification = Optional.empty();
@@ -88,6 +89,11 @@
return self();
}
+ public Builder setMainDexListOutputPath(Path mainDexListOutputPath) {
+ mainDexListOutput = mainDexListOutputPath;
+ return self();
+ }
+
/**
* Add proguard configuration file resources.
*/
@@ -134,7 +140,11 @@
super.validate();
if (minimalMainDex && mainDexRules.isEmpty() && !getAppBuilder().hasMainDexList()) {
throw new CompilationException(
- "Option --minimal-main-dex require --main-dex-rules");
+ "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");
}
}
@@ -183,6 +193,7 @@
getOutputMode(),
mainDexKeepRules,
minimalMainDex,
+ mainDexListOutput,
configuration,
getMode(),
getMinApiLevel(),
@@ -215,13 +226,16 @@
" --no-minification # Force disable minification of names.",
" --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;
private final boolean useMinification;
@@ -285,8 +299,12 @@
builder.setMinification(false);
} else if (arg.equals("--main-dex-rules")) {
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")) {
builder.addProguardConfigurationFiles(Paths.get(args[++i]));
} else if (arg.equals("--pg-map")) {
@@ -329,6 +347,7 @@
OutputMode outputMode,
ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
boolean minimalMainDex,
+ Path mainDexListOutput,
ProguardConfiguration proguardConfiguration,
CompilationMode mode,
int minApiLevel,
@@ -341,6 +360,7 @@
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;
this.useMinification = useMinification;
@@ -351,6 +371,7 @@
super(printHelp, printVersion);
mainDexKeepRules = ImmutableList.of();
minimalMainDex = false;
+ mainDexListOutput = null;
proguardConfiguration = null;
useTreeShaking = false;
useMinification = false;
@@ -410,6 +431,9 @@
internal.obfuscationDictionary = proguardConfiguration.getObfuscationDictionary();
internal.mainDexKeepRules = mainDexKeepRules;
internal.minimalMainDex = minimalMainDex;
+ if (mainDexListOutput != null) {
+ internal.printMainDexListFile = mainDexListOutput;
+ }
internal.keepRules = proguardConfiguration.getRules();
internal.dontWarnPatterns = proguardConfiguration.getDontWarnPatterns();
internal.outputMode = getOutputMode();
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index 6bbb406..6750f4c 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -450,13 +450,15 @@
ExecutorService executor = ThreadUtils.getExecutorService(numberOfThreads);
D8Output result;
try {
- result = D8.run(
- D8Command.builder()
- .addProgramFiles(inputs)
- .setMode(mode)
- .setMinApiLevel(dexArgs.minApiLevel)
- .setMainDexListFile(mainDexList)
- .build());
+ D8Command.Builder builder = D8Command.builder();
+ builder
+ .addProgramFiles(inputs)
+ .setMode(mode)
+ .setMinApiLevel(dexArgs.minApiLevel);
+ if (mainDexList != null) {
+ builder.addMainDexListFiles(mainDexList);
+ }
+ result = D8.run(builder.build());
} finally {
executor.shutdown();
}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 355080f..8820b4d 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ClassProvider;
import com.android.tools.r8.utils.ClasspathClassCollection;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LibraryClassCollection;
import com.android.tools.r8.utils.MainDexList;
@@ -45,6 +46,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import java.util.stream.Collectors;
public class ApplicationReader {
@@ -149,13 +151,20 @@
if (inputApp.hasMainDexList()) {
futures.add(executorService.submit(() -> {
try {
- InputStream input = inputApp.getMainDexList(closer);
- builder.addToMainDexList(MainDexList.parse(input, itemFactory));
+ for (Resource resource : inputApp.getMainDexListResources()) {
+ InputStream input = closer.register(resource.getStream());
+ builder.addToMainDexList(MainDexList.parse(input, itemFactory));
+ }
} catch (IOException e) {
throw new RuntimeException(e);
}
}));
}
+ builder.addToMainDexList(
+ inputApp.getMainDexClasses()
+ .stream()
+ .map(clazz -> itemFactory.createType(DescriptorUtils.javaTypeToDescriptor(clazz)))
+ .collect(Collectors.toList()));
}
private final class ClassReader {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 5613169..1cb0fb0 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -186,7 +186,7 @@
}
byte[] mainDexList = writeMainDexList();
if (mainDexList != null) {
- builder.setMainDexListData(mainDexList);
+ builder.setMainDexListOutputData(mainDexList);
}
return builder.build();
} finally {
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index b328895..a297c10 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -52,7 +52,9 @@
private final Resource proguardMap;
private final Resource proguardSeeds;
private final Resource packageDistribution;
- private final Resource mainDexList;
+ private final List<Resource> mainDexListResources;
+ private final List<String> mainDexClasses;
+ private final Resource mainDexListOutput;
// See factory methods and AndroidApp.Builder below.
private AndroidApp(
@@ -64,7 +66,9 @@
Resource proguardMap,
Resource proguardSeeds,
Resource packageDistribution,
- Resource mainDexList) {
+ List<Resource> mainDexListResources,
+ List<String> mainDexClasses,
+ Resource mainDexListOutput) {
this.programResources = programResources;
this.programFileArchiveReaders = programFileArchiveReaders;
this.classpathResourceProviders = classpathResourceProviders;
@@ -73,7 +77,9 @@
this.proguardMap = proguardMap;
this.proguardSeeds = proguardSeeds;
this.packageDistribution = packageDistribution;
- this.mainDexList = mainDexList;
+ this.mainDexListResources = mainDexListResources;
+ this.mainDexClasses = mainDexClasses;
+ this.mainDexListOutput = mainDexListOutput;
}
/**
@@ -242,14 +248,35 @@
* True if the main dex list resource exists.
*/
public boolean hasMainDexList() {
- return mainDexList != null;
+ return !mainDexListResources.isEmpty();
}
/**
- * Get the input stream of the main dex list resource if it exists.
+ * Get the main dex list resources if any.
*/
- public InputStream getMainDexList(Closer closer) throws IOException {
- return mainDexList == null ? null : closer.register(mainDexList.getStream());
+ public List<Resource> getMainDexListResources() {
+ return mainDexListResources;
+ }
+
+ /**
+ * Get the main dex classes if any.
+ */
+ public List<String> getMainDexClasses() {
+ return mainDexClasses;
+ }
+
+ /**
+ * True if the main dex list resource exists.
+ */
+ public boolean hasMainDexListOutput() {
+ return mainDexListOutput != null;
+ }
+
+ /**
+ * Get the main dex list output resources if any.
+ */
+ public InputStream getMainDexListOutput(Closer closer) throws IOException {
+ return mainDexListOutput == null ? null : closer.register(mainDexListOutput.getStream());
}
/**
@@ -361,7 +388,7 @@
}
public void writeMainDexList(Closer closer, OutputStream out) throws IOException {
- InputStream input = getMainDexList(closer);
+ InputStream input = getMainDexListOutput(closer);
assert input != null;
out.write(ByteStreams.toByteArray(input));
}
@@ -385,7 +412,9 @@
private Resource proguardMap;
private Resource proguardSeeds;
private Resource packageDistribution;
- private Resource mainDexList;
+ private List<Resource> mainDexListResources = new ArrayList<>();
+ private List<String> mainDexListClasses = new ArrayList<>();
+ private Resource mainDexListOutput;
// See AndroidApp::builder().
private Builder() {
@@ -401,7 +430,9 @@
proguardMap = app.proguardMap;
proguardSeeds = app.proguardSeeds;
packageDistribution = app.packageDistribution;
- mainDexList = app.mainDexList;
+ mainDexListResources = app.mainDexListResources;
+ mainDexListClasses = app.mainDexClasses;
+ mainDexListOutput = app.mainDexListOutput;
}
/**
@@ -585,22 +616,49 @@
}
/**
- * Set the main-dex list file.
+ * Add a main-dex list file.
*/
- public Builder setMainDexListFile(Path file) {
- mainDexList = file == null ? null : Resource.fromFile(null, file);
+ public Builder addMainDexListFiles(Path... files) throws IOException {
+ return addMainDexListFiles(Arrays.asList(files));
+ }
+
+ public Builder addMainDexListFiles(Collection<Path> files) throws IOException {
+ for (Path file : files) {
+ if (!Files.exists(file)) {
+ throw new FileNotFoundException("Non-existent input file: " + file);
+ }
+ // TODO(sgjesse): Should we just read the file here? This will sacrifice the parallelism
+ // in ApplicationReader where all input resources are read in parallel.
+ mainDexListResources.add(Resource.fromFile(null, file));
+ }
+ return this;
+ }
+
+
+ /**
+ * Add main-dex classes.
+ */
+ public Builder addMainDexClasses(String... classes) {
+ return addMainDexClasses(Arrays.asList(classes));
+ }
+
+ /**
+ * Add main-dex classes.
+ */
+ public Builder addMainDexClasses(Collection<String> classes) {
+ mainDexListClasses.addAll(classes);
return this;
}
public boolean hasMainDexList() {
- return mainDexList != null;
+ return !(mainDexListResources.isEmpty() && mainDexListClasses.isEmpty());
}
/**
- * Set the main-dex list data.
+ * Set the main-dex list output data.
*/
- public Builder setMainDexListData(byte[] content) {
- mainDexList = content == null ? null : Resource.fromBytes(null, content);
+ public Builder setMainDexListOutputData(byte[] content) {
+ mainDexListOutput = content == null ? null : Resource.fromBytes(null, content);
return this;
}
@@ -617,7 +675,9 @@
proguardMap,
proguardSeeds,
packageDistribution,
- mainDexList);
+ mainDexListResources,
+ mainDexListClasses,
+ mainDexListOutput);
}
private void addProgramFile(Path file) throws IOException {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 9b5e007..02dd7be 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -69,7 +69,6 @@
public Path seedsFile;
public boolean printMapping;
public Path printMappingFile;
- public boolean printMainDexList;
public Path printMainDexListFile;
public boolean ignoreMissingClasses = false;
public boolean skipMinification = false;
diff --git a/src/main/java/com/android/tools/r8/utils/MainDexList.java b/src/main/java/com/android/tools/r8/utils/MainDexList.java
index b219314..826901a 100644
--- a/src/main/java/com/android/tools/r8/utils/MainDexList.java
+++ b/src/main/java/com/android/tools/r8/utils/MainDexList.java
@@ -28,6 +28,18 @@
}
}
+ public static DexType parse(String clazz, DexItemFactory itemFactory) {
+ if (!clazz.endsWith(CLASS_EXTENSION)) {
+ throw new CompilationError("Illegal main-dex-list entry '" + clazz + "'.");
+ }
+ String name = clazz.substring(0, clazz.length() - CLASS_EXTENSION.length());
+ if (name.contains("" + JAVA_PACKAGE_SEPARATOR)) {
+ throw new CompilationError("Illegal main-dex-list entry '" + clazz + "'.");
+ }
+ String descriptor = "L" + name + ";";
+ return itemFactory.createType(descriptor);
+ }
+
public static Set<DexType> parse(InputStream input, DexItemFactory itemFactory) {
Set<DexType> result = Sets.newIdentityHashSet();
try {
@@ -35,15 +47,7 @@
new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
String line;
while ((line = file.readLine()) != null) {
- if (!line.endsWith(CLASS_EXTENSION)) {
- throw new CompilationError("Illegal main-dex-list entry '" + line + "'.");
- }
- String name = line.substring(0, line.length() - CLASS_EXTENSION.length());
- if (name.contains("" + JAVA_PACKAGE_SEPARATOR)) {
- throw new CompilationError("Illegal main-dex-list entry '" + line + "'.");
- }
- String descriptor = "L" + name + ";";
- result.add(itemFactory.createType(descriptor));
+ result.add(parse(line, itemFactory));
}
} catch (IOException e) {
throw new CompilationError("Cannot load main-dex-list.");
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index f8ca2a8..dfc20f9 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -6,43 +6,45 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.CompilationException;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.FileUtils;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
public class MainDexListOutputTest extends TestBase {
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
@Test
public void testNoMainDex() throws Exception {
- Path mainDexList = temp.newFile().toPath();
- compileWithR8(ImmutableList.of(HelloWorldMain.class),
- options -> {
- options.printMainDexList = true;
- options.printMainDexListFile = mainDexList;
- });
- // Empty main dex list.
- assertEquals(0, FileUtils.readTextFile(mainDexList).size());
+ thrown.expect(CompilationException.class);
+ Path mainDexListOutput = temp.getRoot().toPath().resolve("main-dex-output.txt");
+ R8Command command =
+ ToolHelper.prepareR8CommandBuilder(readClasses(HelloWorldMain.class))
+ .setMainDexListOutputPath(mainDexListOutput)
+ .build();
+ ToolHelper.runR8(command);
}
@Test
public void testWithMainDex() throws Exception {
Path mainDexRules = writeTextToTempFile(keepMainProguardConfiguration(HelloWorldMain.class));
+ Path mainDexListOutput = temp.getRoot().toPath().resolve("main-dex-output.txt");
R8Command command =
ToolHelper.prepareR8CommandBuilder(readClasses(HelloWorldMain.class))
.addMainDexRules(mainDexRules)
+ .setMainDexListOutputPath(mainDexListOutput)
.build();
- Path mainDexList = temp.newFile().toPath();
- ToolHelper.runR8(command,
- options -> {
- options.printMainDexList = true;
- options.printMainDexListFile = mainDexList;
- });
+ ToolHelper.runR8(command);
// Main dex list with the single class.
assertEquals(
ImmutableList.of(HelloWorldMain.class.getTypeName().replace('.', '/') + ".class"),
- FileUtils.readTextFile(mainDexList));
+ FileUtils.readTextFile(mainDexListOutput));
}
}
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 714792d..95878ea 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -5,6 +5,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -216,7 +217,9 @@
FileUtils.writeTextFile(mainDexList, list);
Set<DexType> types = MainDexList.parse(mainDexList, factory);
for (String entry : list) {
- assertTrue(types.contains(factory.createType("L" + entry.replace(".class", "") + ";")));
+ DexType type = factory.createType("L" + entry.replace(".class", "") + ";");
+ assertTrue(types.contains(type));
+ assertSame(type, MainDexList.parse(entry, factory));
}
}
@@ -312,7 +315,7 @@
.addProgramFiles(input)
.setOutputPath(out);
if (mainLists.get(i) != null) {
- builder.setMainDexListFile(mainLists.get(i));
+ builder.addMainDexListFiles(mainLists.get(i));
}
ToolHelper.runD8(builder.build());
}
@@ -398,8 +401,16 @@
}
}
+ private enum MultiDexTestMode {
+ SINGLE_FILE,
+ MULTIPLE_FILES,
+ STRINGS,
+ FILES_AND_STRINGS
+ }
+
private void doVerifyMainDexContains(
- List<String> mainDex, Path app, boolean singleDexApp, boolean minimalMainDex)
+ List<String> mainDex, Path app, boolean singleDexApp, boolean minimalMainDex,
+ MultiDexTestMode testMode)
throws IOException, CompilationException, ExecutionException, ProguardRuleParserException {
AndroidApp originalApp = AndroidApp.fromProgramFiles(app);
DexInspector originalInspector = new DexInspector(originalApp);
@@ -408,18 +419,57 @@
originalInspector.clazz(clazz).isPresent());
}
Path outDir = temp.newFolder().toPath();
- Path mainDexList = temp.newFile().toPath();
- FileUtils.writeTextFile(mainDexList, ListUtils.map(mainDex, MainDexListTests::typeToEntry));
- R8Command command =
+ R8Command.Builder builder =
R8Command.builder()
.addProgramFiles(app)
- .setMainDexListFile(mainDexList)
.setMinimalMainDex(minimalMainDex && mainDex.size() > 0)
.setOutputPath(outDir)
.setTreeShaking(false)
- .setMinification(false)
- .build();
- ToolHelper.runR8(command);
+ .setMinification(false);
+
+ switch (testMode) {
+ case SINGLE_FILE:
+ Path mainDexList = temp.newFile().toPath();
+ FileUtils.writeTextFile(mainDexList, ListUtils.map(mainDex, MainDexListTests::typeToEntry));
+ builder.addMainDexListFiles(mainDexList);
+ break;
+ case MULTIPLE_FILES: {
+ // Partion the main dex list into several files.
+ List<List<String>> partitions = Lists.partition(mainDex, Math.max(mainDex.size() / 3, 1));
+ List<Path> mainDexListFiles = new ArrayList<>();
+ for (List<String> partition : partitions) {
+ Path partialMainDexList = temp.newFile().toPath();
+ FileUtils.writeTextFile(partialMainDexList,
+ ListUtils.map(partition, MainDexListTests::typeToEntry));
+ mainDexListFiles.add(partialMainDexList);
+ }
+ builder.addMainDexListFiles(mainDexListFiles);
+ break;
+ }
+ case STRINGS:
+ builder.addMainDexClasses(mainDex);
+ break;
+ case FILES_AND_STRINGS: {
+ // Partion the main dex list add some parts through files and the other parts using strings.
+ List<List<String>> partitions = Lists.partition(mainDex, Math.max(mainDex.size() / 3, 1));
+ List<Path> mainDexListFiles = new ArrayList<>();
+ for (int i = 0; i < partitions.size(); i++) {
+ List<String> partition = partitions.get(i);
+ if (i % 2 == 0) {
+ Path partialMainDexList = temp.newFile().toPath();
+ FileUtils.writeTextFile(partialMainDexList,
+ ListUtils.map(partition, MainDexListTests::typeToEntry));
+ mainDexListFiles.add(partialMainDexList);
+ } else {
+ builder.addMainDexClasses(mainDex);
+ }
+ }
+ builder.addMainDexListFiles(mainDexListFiles);
+ break;
+ }
+ }
+
+ ToolHelper.runR8(builder.build());
if (!singleDexApp && !minimalMainDex) {
assertTrue("Output run only produced one dex file.",
1 < Files.list(outDir).filter(FileUtils::isDexFile).count());
@@ -438,8 +488,10 @@
private void verifyMainDexContains(List<String> mainDex, Path app, boolean singleDexApp)
throws Throwable {
- doVerifyMainDexContains(mainDex, app, singleDexApp, false);
- doVerifyMainDexContains(mainDex, app, singleDexApp, true);
+ for (MultiDexTestMode multiDexTestMode : MultiDexTestMode.values()) {
+ doVerifyMainDexContains(mainDex, app, singleDexApp, false, multiDexTestMode);
+ doVerifyMainDexContains(mainDex, app, singleDexApp, true, multiDexTestMode);
+ }
}
public static AndroidApp generateApplication(List<String> classes, int minApi, int methodCount)
diff --git a/src/test/java/com/android/tools/r8/utils/D8CommandTest.java b/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
index 443e257..9cc7eda 100644
--- a/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.google.common.collect.ImmutableList;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -157,32 +158,37 @@
@Test
public void mainDexList() throws Throwable {
- Path mailDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
- D8Command command = parse("--main-dex-list", mailDexList.toString());
+ Path mainDexList1 = temp.newFile("main-dex-list-1.txt").toPath();
+ Path mainDexList2 = temp.newFile("main-dex-list-2.txt").toPath();
+
+ D8Command command = parse("--main-dex-list", mainDexList1.toString());
+ assertTrue(ToolHelper.getApp(command).hasMainDexList());
+
+ command = parse(
+ "--main-dex-list", mainDexList1.toString(), "--main-dex-list", mainDexList2.toString());
assertTrue(ToolHelper.getApp(command).hasMainDexList());
}
@Test
- public void multipleMainDexList() throws Throwable {
- thrown.expect(CompilationException.class);
- Path mailDexList1 = temp.getRoot().toPath().resolve("main-dex-list-1.txt");
- Path mailDexList2 = temp.getRoot().toPath().resolve("main-dex-list-2.txt");
- parse("--main-dex-list", mailDexList1.toString(), "--main-dex-list", mailDexList2.toString());
+ public void nonExistingMainDexList() throws Throwable {
+ thrown.expect(FileNotFoundException.class);
+ Path mainDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
+ parse("--main-dex-list", mainDexList.toString());
}
@Test
public void mainDexListWithFilePerClass() throws Throwable {
thrown.expect(CompilationException.class);
- Path mailDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
- D8Command command = parse("--main-dex-list", mailDexList.toString(), "--file-per-class");
+ Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
+ D8Command command = parse("--main-dex-list", mainDexList.toString(), "--file-per-class");
assertTrue(ToolHelper.getApp(command).hasMainDexList());
}
@Test
public void mainDexListWithIntermediate() throws Throwable {
thrown.expect(CompilationException.class);
- Path mailDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
- D8Command command = parse("--main-dex-list", mailDexList.toString(), "--intermediate");
+ Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
+ D8Command command = parse("--main-dex-list", mainDexList.toString(), "--intermediate");
assertTrue(ToolHelper.getApp(command).hasMainDexList());
}
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 a1e57ac..cb2085f 100644
--- a/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/R8CommandTest.java
@@ -16,8 +16,10 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.google.common.collect.ImmutableList;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@@ -90,8 +92,32 @@
@Test
public void mainDexRules() throws Throwable {
- Path mailDexRules = temp.newFile("main-dex.rules").toPath();
- parse("--main-dex-rules", mailDexRules.toString());
+ Path mainDexRules1 = temp.newFile("main-dex-1.rules").toPath();
+ Path mainDexRules2 = temp.newFile("main-dex-2.rules").toPath();
+ parse("--main-dex-rules", mainDexRules1.toString());
+ parse("--main-dex-rules", mainDexRules1.toString(), "--main-dex-rules", mainDexRules2.toString());
+ }
+
+ @Test
+ public void nonExistingMainDexRules() throws Throwable {
+ thrown.expect(NoSuchFileException.class);
+ Path mainDexRules = temp.getRoot().toPath().resolve("main-dex.rules");
+ parse("--main-dex-rules", mainDexRules.toString());
+ }
+
+ @Test
+ public void mainDexList() throws Throwable {
+ Path mainDexList1 = temp.newFile("main-dex-list-1.txt").toPath();
+ Path mainDexList2 = temp.newFile("main-dex-list-2.txt").toPath();
+ parse("--main-dex-list", mainDexList1.toString());
+ parse("--main-dex-list", mainDexList1.toString(), "--main-dex-list", mainDexList2.toString());
+ }
+
+ @Test
+ public void nonExistingMainDexList() throws Throwable {
+ thrown.expect(FileNotFoundException.class);
+ Path mainDexList = temp.getRoot().toPath().resolve("main-dex-list.txt");
+ parse("--main-dex-list", mainDexList.toString());
}
@Test
@@ -101,9 +127,27 @@
}
@Test
+ public void mainDexListOutput() throws Throwable {
+ Path mainDexRules = temp.newFile("main-dex.rules").toPath();
+ Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
+ Path mainDexListOutput = temp.newFile("main-dex-out.txt").toPath();
+ parse("--main-dex-rules", mainDexRules.toString(),
+ "--main-dex-list-output", mainDexListOutput.toString());
+ parse("--main-dex-list", mainDexList.toString(),
+ "--main-dex-list-output", mainDexListOutput.toString());
+ }
+
+ @Test
+ public void mainDexListOutputWithoutAnyMainDexSpecification() throws Throwable {
+ thrown.expect(CompilationException.class);
+ Path mainDexListOutput = temp.newFile("main-dex-out.txt").toPath();
+ parse("--main-dex-list-output", mainDexListOutput.toString());
+ }
+
+ @Test
public void mainDexRulesWithMinimalMainDex() throws Throwable {
- Path mailDexRules = temp.newFile("main-dex.rules").toPath();
- parse("--main-dex-rules", mailDexRules.toString(), "--minimal-main-dex");
+ Path mainDexRules = temp.newFile("main-dex.rules").toPath();
+ parse("--main-dex-rules", mainDexRules.toString(), "--minimal-main-dex");
}
@Test