Better error handling for GenerateMainDexList
Unwrap ExecutionException, and report all errors on the provided
DiagnosticsHandler same way as the D8, R8 and DexSplitter.
Bug: 112679440
Change-Id: I6b803f0e6585c1461eeacfcac1845b363508ef69
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 475d34f..76e7277 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -201,8 +201,7 @@
.write(executor);
options.printWarnings();
} catch (ExecutionException e) {
- R8.unwrapExecutionException(e);
- throw new AssertionError(e); // unwrapping method should have thrown
+ throw R8.unwrapExecutionException(e);
} finally {
options.signalFinishedToConsumers();
// Dump timings.
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index a26924e..d7183c8 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -105,8 +105,7 @@
writer.write(executor);
options.printWarnings();
} catch (ExecutionException e) {
- R8.unwrapExecutionException(e);
- throw new AssertionError(e); // unwrapping method should have thrown
+ throw R8.unwrapExecutionException(e);
} finally {
options.signalFinishedToConsumers();
}
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index 68dc702..163c46f 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -109,8 +109,7 @@
}
}
} catch (ExecutionException e) {
- R8.unwrapExecutionException(e);
- throw new AssertionError(e); // unwrapping method should have thrown
+ throw R8.unwrapExecutionException(e);
} catch (FeatureMappingException e) {
options.reporter.error(e.getMessage());
} finally {
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index fe946da..43989ff 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.shaking.TreePruner;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
@@ -36,37 +37,41 @@
}
private List<String> run(AndroidApp app, ExecutorService executor)
- throws IOException, ExecutionException {
- DexApplication application =
- new ApplicationReader(app, options, timing).read(executor).toDirect();
- AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
- RootSet mainDexRootSet =
- new RootSetBuilder(appInfo, application, options.mainDexKeepRules, options).run(executor);
- Enqueuer enqueuer = new Enqueuer(appInfo, GraphLense.getIdentityLense(), options, true);
- AppInfoWithLiveness mainDexAppInfo = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
- // LiveTypes is the result.
- Set<DexType> mainDexClasses =
- new MainDexListBuilder(new HashSet<>(mainDexAppInfo.liveTypes), application).run();
+ throws IOException {
+ try {
+ DexApplication application =
+ new ApplicationReader(app, options, timing).read(executor).toDirect();
+ AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
+ RootSet mainDexRootSet =
+ new RootSetBuilder(appInfo, application, options.mainDexKeepRules, options).run(executor);
+ Enqueuer enqueuer = new Enqueuer(appInfo, GraphLense.getIdentityLense(), options, true);
+ AppInfoWithLiveness mainDexAppInfo = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
+ // LiveTypes is the result.
+ Set<DexType> mainDexClasses =
+ new MainDexListBuilder(new HashSet<>(mainDexAppInfo.liveTypes), application).run();
- List<String> result = mainDexClasses.stream()
- .map(c -> c.toSourceString().replace('.', '/') + ".class")
- .sorted()
- .collect(Collectors.toList());
+ List<String> result = mainDexClasses.stream()
+ .map(c -> c.toSourceString().replace('.', '/') + ".class")
+ .sorted()
+ .collect(Collectors.toList());
- if (options.mainDexListConsumer != null) {
- options.mainDexListConsumer.accept(String.join("\n", result), options.reporter);
+ if (options.mainDexListConsumer != null) {
+ options.mainDexListConsumer.accept(String.join("\n", result), options.reporter);
+ }
+
+ // Print -whyareyoukeeping results if any.
+ if (mainDexRootSet.reasonAsked.size() > 0) {
+ // Print reasons on the application after pruning, so that we reflect the actual result.
+ TreePruner pruner = new TreePruner(application, mainDexAppInfo.withLiveness(), options);
+ application = pruner.run();
+ ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(mainDexRootSet.reasonAsked);
+ reasonPrinter.run(application);
+ }
+
+ return result;
+ } catch (ExecutionException e) {
+ throw R8.unwrapExecutionException(e);
}
-
- // Print -whyareyoukeeping results if any.
- if (mainDexRootSet.reasonAsked.size() > 0) {
- // Print reasons on the application after pruning, so that we reflect the actual result.
- TreePruner pruner = new TreePruner(application, mainDexAppInfo.withLiveness(), options);
- application = pruner.run();
- ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(mainDexRootSet.reasonAsked);
- reasonPrinter.run(application);
- }
-
- return result;
}
/**
@@ -82,7 +87,7 @@
* @return classes to keep in the primary dex file.
*/
public static List<String> run(GenerateMainDexListCommand command)
- throws IOException, ExecutionException {
+ throws CompilationFailedException {
ExecutorService executorService = ThreadUtils.getExecutorService(command.getInternalOptions());
try {
return run(command, executorService);
@@ -105,14 +110,28 @@
* @return classes to keep in the primary dex file.
*/
public static List<String> run(GenerateMainDexListCommand command, ExecutorService executor)
- throws IOException, ExecutionException {
+ throws CompilationFailedException {
AndroidApp app = command.getInputApp();
InternalOptions options = command.getInternalOptions();
- return new GenerateMainDexList(options).run(app, executor);
+ ResultBox result = new ResultBox();
+
+ ExceptionUtils.withMainDexListHandler(
+ command.getReporter(),
+ () -> {
+ try {
+ result.content = new GenerateMainDexList(options).run(app, executor);
+ } finally {
+ executor.shutdown();
+ }
+ });
+ return result.content;
}
- public static void main(String[] args)
- throws IOException, ExecutionException, CompilationFailedException {
+ private static class ResultBox {
+ List<String> content;
+ }
+
+ public static void main(String[] args) throws CompilationFailedException {
GenerateMainDexListCommand.Builder builder = GenerateMainDexListCommand.parse(args);
GenerateMainDexListCommand command = builder.build();
if (command.isPrintHelp()) {
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index f9e0ebf..2d06da8 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -150,6 +150,10 @@
return mainDexListConsumer;
}
+ Reporter getReporter() {
+ return reporter;
+ }
+
private static void parse(String[] args, GenerateMainDexListCommand.Builder builder) {
for (int i = 0; i < args.length; i++) {
String arg = args[i].trim();
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 4410d8a..78cb75a 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -92,8 +92,7 @@
System.out,
type -> descriptors.contains(type.toDescriptorString()));
} catch (ExecutionException e) {
- R8.unwrapExecutionException(e);
- throw new AssertionError(e); // unwrapping method should have thrown
+ throw R8.unwrapExecutionException(e);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 9738c8d..1bfe039 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -525,8 +525,7 @@
options.printWarnings();
} catch (ExecutionException e) {
- unwrapExecutionException(e);
- throw new AssertionError(e); // unwrapping method should have thrown
+ throw unwrapExecutionException(e);
} finally {
options.signalFinishedToConsumers();
// Dump timings.
@@ -545,7 +544,7 @@
}
}
- static void unwrapExecutionException(ExecutionException executionException) {
+ static RuntimeException unwrapExecutionException(ExecutionException executionException) {
Throwable cause = executionException.getCause();
if (cause instanceof CompilationError) {
// add original exception as suppressed exception to provide the original stack trace
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index fe8adfa..1ff2664 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -48,6 +48,11 @@
withCompilationHandler(reporter, action);
}
+ public static void withMainDexListHandler(
+ Reporter reporter, CompileAction action) throws CompilationFailedException {
+ withCompilationHandler(reporter, action);
+ }
+
public static void withCompilationHandler(Reporter reporter, CompileAction action)
throws CompilationFailedException {
try {
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 374d58f..ddb0e7d 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -27,7 +27,6 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
-import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.junit.Assert;
@@ -218,92 +217,89 @@
Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
Path inputJar = Paths.get(buildDir, packageName + JAR_EXTENSION);
- try {
- // Build main-dex list using GenerateMainDexList and test the output from run.
- GenerateMainDexListCommand.Builder mdlCommandBuilder = GenerateMainDexListCommand.builder();
- GenerateMainDexListCommand mdlCommand = mdlCommandBuilder
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
- .addProgramFiles(inputJar)
- .addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
- .addMainDexRulesFiles(mainDexRules)
- .build();
- List<String> mainDexGeneratorMainDexList =
- GenerateMainDexList.run(mdlCommand).stream()
- .map(this::mainDexStringToDescriptor)
- .sorted()
- .collect(Collectors.toList());
+ // Build main-dex list using GenerateMainDexList and test the output from run.
+ GenerateMainDexListCommand.Builder mdlCommandBuilder = GenerateMainDexListCommand.builder();
+ GenerateMainDexListCommand mdlCommand = mdlCommandBuilder
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
+ .addProgramFiles(inputJar)
+ .addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
+ .addMainDexRulesFiles(mainDexRules)
+ .build();
+ List<String> mainDexGeneratorMainDexList =
+ GenerateMainDexList.run(mdlCommand).stream()
+ .map(this::mainDexStringToDescriptor)
+ .sorted()
+ .collect(Collectors.toList());
- class Box {
- String content;
+ class Box {
+
+ String content;
+ }
+
+ // Build main-dex list using GenerateMainDexList and test the output from a consumer.
+ final Box mainDexListOutput = new Box();
+ mdlCommandBuilder = GenerateMainDexListCommand.builder();
+ mdlCommand = mdlCommandBuilder
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
+ .addProgramFiles(inputJar)
+ .addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
+ .addMainDexRulesFiles(mainDexRules)
+ .setMainDexListConsumer((string, handler) -> mainDexListOutput.content = string)
+ .build();
+ GenerateMainDexList.run(mdlCommand);
+ List<String> mainDexGeneratorMainDexListFromConsumer =
+ StringUtils.splitLines(mainDexListOutput.content).stream()
+ .map(this::mainDexStringToDescriptor)
+ .sorted()
+ .collect(Collectors.toList());
+
+ // Build main-dex list using R8.
+ final Box r8MainDexListOutput = new Box();
+ R8Command.Builder r8CommandBuilder = R8Command.builder();
+ R8Command command =
+ r8CommandBuilder
+ .setMinApiLevel(minSdk.getLevel())
+ .addProgramFiles(inputJar)
+ .addProgramFiles(
+ Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
+ .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
+ .setOutput(out, OutputMode.DexIndexed)
+ .addMainDexRulesFiles(mainDexRules)
+ .setMainDexListConsumer((string, handler) -> r8MainDexListOutput.content = string)
+ .build();
+ ToolHelper.runR8WithFullResult(command, optionsConsumer);
+ List<String> r8MainDexList =
+ StringUtils.splitLines(r8MainDexListOutput.content).stream()
+ .map(this::mainDexStringToDescriptor)
+ .sorted()
+ .collect(Collectors.toList());
+ // Check that generated lists are the same as the reference list, except for lambda
+ // classes which are only produced when running R8.
+ String[] r8RefList = new String(Files.readAllBytes(
+ expectedR8MainDexList), StandardCharsets.UTF_8).split("\n");
+ for (int i = 0; i < r8RefList.length; i++) {
+ String reference = r8RefList[i].trim();
+ if (r8MainDexList.size() <= i) {
+ Assert.fail("R8 main dex list is missing '" + reference + "'");
}
-
- // Build main-dex list using GenerateMainDexList and test the output from a consumer.
- final Box mainDexListOutput = new Box();
- mdlCommandBuilder = GenerateMainDexListCommand.builder();
- mdlCommand = mdlCommandBuilder
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
- .addProgramFiles(inputJar)
- .addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
- .addMainDexRulesFiles(mainDexRules)
- .setMainDexListConsumer((string, handler) -> mainDexListOutput.content = string)
- .build();
- GenerateMainDexList.run(mdlCommand);
- List<String> mainDexGeneratorMainDexListFromConsumer =
- StringUtils.splitLines(mainDexListOutput.content).stream()
- .map(this::mainDexStringToDescriptor)
- .sorted()
- .collect(Collectors.toList());
-
- // Build main-dex list using R8.
- final Box r8MainDexListOutput = new Box();
- R8Command.Builder r8CommandBuilder = R8Command.builder();
- R8Command command =
- r8CommandBuilder
- .setMinApiLevel(minSdk.getLevel())
- .addProgramFiles(inputJar)
- .addProgramFiles(
- Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
- .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
- .setOutput(out, OutputMode.DexIndexed)
- .addMainDexRulesFiles(mainDexRules)
- .setMainDexListConsumer((string, handler) -> r8MainDexListOutput.content = string)
- .build();
- ToolHelper.runR8WithFullResult(command, optionsConsumer);
- List<String> r8MainDexList =
- StringUtils.splitLines(r8MainDexListOutput.content).stream()
- .map(this::mainDexStringToDescriptor)
- .sorted()
- .collect(Collectors.toList());
- // Check that generated lists are the same as the reference list, except for lambda
- // classes which are only produced when running R8.
- String[] r8RefList = new String(Files.readAllBytes(
- expectedR8MainDexList), StandardCharsets.UTF_8).split("\n");
- for (int i = 0; i < r8RefList.length; i++) {
- String reference = r8RefList[i].trim();
- if (r8MainDexList.size() <= i) {
- Assert.fail("R8 main dex list is missing '" + reference + "'");
+ checkSameMainDexEntry(reference, r8MainDexList.get(i));
+ }
+ String[] refList = new String(Files.readAllBytes(
+ expectedMainDexList), StandardCharsets.UTF_8).split("\n");
+ int nonLambdaOffset = 0;
+ for (int i = 0; i < refList.length; i++) {
+ String reference = refList[i].trim();
+ // The main dex list generator does not do any lambda desugaring.
+ if (!isLambda(reference)) {
+ if (mainDexGeneratorMainDexList.size() <= i - nonLambdaOffset) {
+ Assert.fail("Main dex list generator is missing '" + reference + "'");
}
- checkSameMainDexEntry(reference, r8MainDexList.get(i));
+ checkSameMainDexEntry(reference, mainDexGeneratorMainDexList.get(i - nonLambdaOffset));
+ checkSameMainDexEntry(
+ reference, mainDexGeneratorMainDexListFromConsumer.get(i - nonLambdaOffset));
+ } else {
+ nonLambdaOffset++;
}
- String[] refList = new String(Files.readAllBytes(
- expectedMainDexList), StandardCharsets.UTF_8).split("\n");
- int nonLambdaOffset = 0;
- for (int i = 0; i < refList.length; i++) {
- String reference = refList[i].trim();
- // The main dex list generator does not do any lambda desugaring.
- if (!isLambda(reference)) {
- if (mainDexGeneratorMainDexList.size() <= i - nonLambdaOffset) {
- Assert.fail("Main dex list generator is missing '" + reference + "'");
- }
- checkSameMainDexEntry(reference, mainDexGeneratorMainDexList.get(i - nonLambdaOffset));
- checkSameMainDexEntry(
- reference, mainDexGeneratorMainDexListFromConsumer.get(i - nonLambdaOffset));
- } else {
- nonLambdaOffset++;
- }
- }
- } catch (ExecutionException e) {
- throw e.getCause();
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
index fb4fe70..c80b72e 100644
--- a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
@@ -3,14 +3,20 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import static com.android.tools.r8.ToolHelper.EXAMPLES_BUILD_DIR;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsChecker;
+import com.android.tools.r8.GenerateMainDexList;
import com.android.tools.r8.GenerateMainDexListCommand;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
@@ -154,6 +160,53 @@
parse("--main-dex-list", mainDexList1.toString(), "--main-dex-list", mainDexList2.toString());
}
+ @Test(expected = CompilationFailedException.class)
+ public void missingAndroidJar() throws Throwable {
+ DiagnosticsChecker.checkErrorsContains(
+ "Tracing for legacy multi dex is not possible without all classpath libraries",
+ handler ->
+ GenerateMainDexList.run(
+ GenerateMainDexListCommand.builder(handler).build()));
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void duplicateProgramClasses() throws Throwable {
+ Path input = Paths.get(EXAMPLES_BUILD_DIR, "arithmetic.jar");
+ DiagnosticsChecker.checkErrorsContains(
+ "Program type already present",
+ handler ->
+ GenerateMainDexList.run(
+ GenerateMainDexListCommand.builder(handler)
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .addProgramFiles(input)
+ .addProgramFiles(input)
+ .build()));
+ }
+
+ @Test
+ public void emptyMainDex() throws Throwable {
+ Path input = Paths.get(EXAMPLES_BUILD_DIR, "arithmetic.jar");
+ List<String> result = GenerateMainDexList.run(
+ GenerateMainDexListCommand.builder()
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .addProgramFiles(input)
+ .build());
+ assertEquals(0, result.size());
+ }
+
+ @Test
+ public void nonEmptyMainDex() throws Throwable {
+ Path input = Paths.get(EXAMPLES_BUILD_DIR, "arithmetic.jar");
+ List<String> result = GenerateMainDexList.run(
+ GenerateMainDexListCommand.builder()
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .addProgramFiles(input)
+ .addMainDexRules(
+ ImmutableList.of("-keep class arithmetic.Arithmetic"), Origin.unknown())
+ .build());
+ assertEquals(1, result.size());
+ }
+
private GenerateMainDexListCommand parse(String... args) throws Throwable {
return GenerateMainDexListCommand.parse(args).build();
}