Better multi-dex error message.
It informs the user of:
1) whether s/he can try using multi-dex
or it exceeds the main-dex list limit; and
2) what exceeds the limit: num of either methods or fields.
Bug: 63170711
Change-Id: Id6ceccbdeae3005b0c8da7172925712f25b12d43
diff --git a/src/main/java/com/android/tools/r8/dex/DexFile.java b/src/main/java/com/android/tools/r8/dex/DexFile.java
index a2c65d3..3441346 100644
--- a/src/main/java/com/android/tools/r8/dex/DexFile.java
+++ b/src/main/java/com/android/tools/r8/dex/DexFile.java
@@ -62,13 +62,13 @@
int version;
switch (versionByte) {
case '8':
- version = 38;
+ version = Constants.ANDROID_O_DEX_VERSION;
break;
case '7':
- version = 37;
+ version = Constants.ANDROID_N_DEX_VERSION;
break;
case '5':
- version = 35;
+ version = Constants.ANDROID_PRE_N_DEX_VERSION;
break;
default:
throw new CompilationError("Dex file has invalid version number: " + name);
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index f336a3d..f309670 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -174,6 +174,35 @@
return isFull(transaction.getNumberOfMethods(), transaction.getNumberOfFields(), MAX_ENTRIES);
}
+ void throwIfFull(boolean multiDexEnabled) {
+ if (!isFull()) {
+ return;
+ }
+ StringBuilder messageBuilder = new StringBuilder();
+ // General message: Cannot fit.
+ messageBuilder.append("Cannot fit requested classes in ");
+ messageBuilder.append(multiDexEnabled ? "the main-" : "a single ");
+ messageBuilder.append("dex file.\n");
+ // Suggest supplying the main-dex list or explicitly mention that main-dex list is too large.
+ if (multiDexEnabled) {
+ messageBuilder.append("The list of classes for the main-dex list is too large.\n");
+ } else {
+ messageBuilder.append("Try supplying a main-dex list.\n");
+ }
+ // Show the numbers of methods and/or fields that exceed the limit.
+ if (transaction.getNumberOfMethods() > MAX_ENTRIES) {
+ messageBuilder.append("# methods: ");
+ messageBuilder.append(transaction.getNumberOfMethods());
+ messageBuilder.append(" > ").append(MAX_ENTRIES).append('\n');
+ }
+ if (transaction.getNumberOfFields() > MAX_ENTRIES) {
+ messageBuilder.append("# fields: ");
+ messageBuilder.append(transaction.getNumberOfFields());
+ messageBuilder.append(" > ").append(MAX_ENTRIES).append('\n');
+ }
+ throw new CompilationError(messageBuilder.toString());
+ }
+
private boolean isFilledEnough(FillStrategy fillStrategy) {
return isFull(
transaction.getNumberOfMethods(),
@@ -202,7 +231,7 @@
protected final ApplicationWriter writer;
protected final Map<Integer, VirtualFile> nameToFileMap = new HashMap<>();
- public Distributor(ApplicationWriter writer) {
+ Distributor(ApplicationWriter writer) {
this.application = writer.application;
this.writer = writer;
}
@@ -212,7 +241,7 @@
public static class FilePerClassDistributor extends Distributor {
- public FilePerClassDistributor(ApplicationWriter writer) {
+ FilePerClassDistributor(ApplicationWriter writer) {
super(writer);
}
@@ -232,7 +261,7 @@
protected Set<DexProgramClass> classes;
protected Map<DexProgramClass, String> originalNames;
- public DistributorBase(ApplicationWriter writer) {
+ DistributorBase(ApplicationWriter writer) {
super(writer);
classes = Sets.newHashSet(application.classes());
@@ -247,9 +276,7 @@
if (clazz != null && clazz.isProgramClass()) {
DexProgramClass programClass = (DexProgramClass) clazz;
mainDexFile.addClass(programClass);
- if (mainDexFile.isFull()) {
- throw new CompilationError("Cannot fit requested classes in main-dex file.");
- }
+ mainDexFile.throwIfFull(true);
classes.remove(programClass);
} else {
System.out.println(
@@ -298,7 +325,7 @@
public static class FillFilesDistributor extends DistributorBase {
private final FillStrategy fillStrategy;
- public FillFilesDistributor(ApplicationWriter writer, boolean minimalMainDex) {
+ FillFilesDistributor(ApplicationWriter writer, boolean minimalMainDex) {
super(writer);
this.fillStrategy = minimalMainDex ? FillStrategy.MINIMAL_MAIN_DEX : FillStrategy.FILL_MAX;
}
@@ -326,7 +353,7 @@
}
public static class MonoDexDistributor extends DistributorBase {
- public MonoDexDistributor(ApplicationWriter writer) {
+ MonoDexDistributor(ApplicationWriter writer) {
super(writer);
}
@@ -337,9 +364,7 @@
for (DexProgramClass programClass : classes) {
mainDexFile.addClass(programClass);
- if (mainDexFile.isFull()) {
- throw new CompilationError("Cannot fit all classes in a single dex file.");
- }
+ mainDexFile.throwIfFull(false);
}
mainDexFile.commitTransaction();
return nameToFileMap;
@@ -350,7 +375,7 @@
private final PackageDistribution packageDistribution;
private final ExecutorService executorService;
- public PackageMapDistributor(
+ PackageMapDistributor(
ApplicationWriter writer,
PackageDistribution packageDistribution,
ExecutorService executorService) {
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 69fdd65..a5e2309 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -150,8 +150,15 @@
@Test
public void cannotFitBothIntoMainDex() throws Throwable {
- thrown.expect(CompilationError.class);
- verifyMainDexContains(TWO_LARGE_CLASSES, getTwoLargeClassesAppPath(), false);
+ try {
+ verifyMainDexContains(TWO_LARGE_CLASSES, getTwoLargeClassesAppPath(), false);
+ fail("Expect to fail, for there are too many classes for the main-dex list.");
+ } catch (CompilationError e) {
+ // Make sure {@link MonoDexDistributor} was _not_ used.
+ assertFalse(e.getMessage().contains("single dex file"));
+ // Make sure what exceeds the limit is the number of methods.
+ assertTrue(e.getMessage().contains("# methods"));
+ }
}
@Test
@@ -183,8 +190,15 @@
@Test
public void cannotFitAllIntoMainDex() throws Throwable {
- thrown.expect(CompilationError.class);
- verifyMainDexContains(MANY_CLASSES, getManyClassesMultiDexAppPath(), false);
+ try {
+ verifyMainDexContains(MANY_CLASSES, getManyClassesMultiDexAppPath(), false);
+ fail("Expect to fail, for there are too many classes for the main-dex list.");
+ } catch (CompilationError e) {
+ // Make sure {@link MonoDexDistributor} was _not_ used.
+ assertFalse(e.getMessage().contains("single dex file"));
+ // Make sure what exceeds the limit is the number of methods.
+ assertTrue(e.getMessage().contains("# methods"));
+ }
}
@Test
@@ -334,7 +348,10 @@
MANY_CLASSES, Constants.ANDROID_K_API, false, MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS);
fail("Expect to fail, for there are many classes while multidex is not enabled.");
} catch (CompilationError e) {
- assertTrue(e.getMessage().contains("Cannot fit all classes in a single dex file."));
+ // Make sure {@link MonoDexDistributor} was used.
+ assertTrue(e.getMessage().contains("single dex file"));
+ // Make sure what exceeds the limit is the number of methods.
+ assertTrue(e.getMessage().contains("# methods"));
}
}
@@ -476,8 +493,8 @@
}
DexApplication application = builder.build();
AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
- ApplicationWriter writer =
- new ApplicationWriter(application, appInfo, options, null, NamingLens.getIdentityLens(), null);
+ ApplicationWriter writer = new ApplicationWriter(
+ application, appInfo, options, null, NamingLens.getIdentityLens(), null);
ExecutorService executor = ThreadUtils.getExecutorService(options);
try {
return writer.write(null, executor);