Add D8 support for main-dex rules.

Bug: 176880642
Change-Id: I2c9708c6ac6f1ae8c5b6a827a82827f39cae6cbf
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index bc8d186..4def5d0 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -183,6 +183,12 @@
       AppView<AppInfo> appView = readApp(inputApp, options, executor, timing);
       SyntheticItems.collectSyntheticInputs(appView);
 
+      if (!options.mainDexKeepRules.isEmpty()) {
+        new GenerateMainDexList(options)
+            .traceMainDex(
+                executor, appView.appInfo().app(), appView.appInfo().getMainDexClasses()::addAll);
+      }
+
       final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
       if (AssertionsRewriter.isEnabled(options)) {
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index f720ee9..047df78 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -13,6 +13,11 @@
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.ProguardConfigurationParser;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.ProguardConfigurationSource;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
@@ -21,7 +26,11 @@
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.function.BiPredicate;
@@ -75,6 +84,7 @@
     private boolean enableMainDexListCheck = true;
     private boolean minimalMainDex = false;
     private boolean skipDump = false;
+    private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
 
     private Builder() {
       this(new DefaultD8DiagnosticsHandler());
@@ -203,6 +213,26 @@
       return self();
     }
 
+    /** Add proguard configuration files with rules for automatic main-dex-list calculation. */
+    public Builder addMainDexRulesFiles(Path... paths) {
+      return addMainDexRulesFiles(Arrays.asList(paths));
+    }
+
+    /** Add proguard configuration files with rules for automatic main-dex-list calculation. */
+    public Builder addMainDexRulesFiles(Collection<Path> paths) {
+      guard(() -> paths.forEach(p -> mainDexRules.add(new ProguardConfigurationSourceFile(p))));
+      return self();
+    }
+
+    /** Add proguard rules for automatic main-dex-list calculation. */
+    public Builder addMainDexRules(List<String> lines, Origin origin) {
+      guard(
+          () ->
+              mainDexRules.add(
+                  new ProguardConfigurationSourceStrings(lines, Paths.get("."), origin)));
+      return self();
+    }
+
     @Override
     void validate() {
       if (isPrintHelp()) {
@@ -219,8 +249,20 @@
         if (getProgramConsumer() instanceof DexFilePerClassFileConsumer) {
           reporter.error("Option --main-dex-list cannot be used with --file-per-class");
         }
-      } else if (getMainDexListConsumer() != null) {
-        reporter.error("Option --main-dex-list-output require --main-dex-list");
+      }
+      if (!mainDexRules.isEmpty()) {
+        if (intermediate) {
+          reporter.error("Option --main-dex-rules cannot be used with --intermediate");
+        }
+        if (getProgramConsumer() instanceof DexFilePerClassFileConsumer) {
+          reporter.error("Option --main-dex-rules cannot be used with --file-per-class");
+        }
+      }
+      if (getMainDexListConsumer() != null
+          && mainDexRules.isEmpty()
+          && !getAppBuilder().hasMainDexList()) {
+        reporter.error(
+            "Option --main-dex-list-output requires --main-dex-rules and/or --main-dex-list");
       }
       if (getMinApiLevel() >= AndroidApiLevel.L.getLevel()) {
         if (getMainDexListConsumer() != null || getAppBuilder().hasMainDexList()) {
@@ -248,6 +290,9 @@
       DesugaredLibraryConfiguration libraryConfiguration =
           getDesugaredLibraryConfiguration(factory, false);
 
+      ImmutableList<ProguardConfigurationRule> mainDexKeepRules =
+          ProguardConfigurationParser.parse(mainDexRules, factory, getReporter());
+
       return new D8Command(
           getAppBuilder().build(),
           getMode(),
@@ -269,6 +314,7 @@
           skipDump,
           enableMainDexListCheck,
           minimalMainDex,
+          mainDexKeepRules,
           getThreadCount(),
           factory);
     }
@@ -284,6 +330,7 @@
   private final boolean skipDump;
   private final boolean enableMainDexListCheck;
   private final boolean minimalMainDex;
+  private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
   private final DexItemFactory factory;
 
   public static Builder builder() {
@@ -347,6 +394,7 @@
       boolean skipDump,
       boolean enableMainDexListCheck,
       boolean minimalMainDex,
+      ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
       int threadCount,
       DexItemFactory factory) {
     super(
@@ -371,6 +419,7 @@
     this.skipDump = skipDump;
     this.enableMainDexListCheck = enableMainDexListCheck;
     this.minimalMainDex = minimalMainDex;
+    this.mainDexKeepRules = mainDexKeepRules;
     this.factory = factory;
   }
 
@@ -384,6 +433,7 @@
     skipDump = false;
     enableMainDexListCheck = true;
     minimalMainDex = false;
+    mainDexKeepRules = null;
     factory = null;
   }
 
@@ -403,6 +453,7 @@
     internal.intermediate = intermediate;
     internal.readCompileTimeAnnotations = intermediate;
     internal.desugarGraphConsumer = desugarGraphConsumer;
+    internal.mainDexKeepRules = mainDexKeepRules;
 
     // Assert and fixup defaults.
     assert !internal.isShrinking();
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 98a6582..8f90ade 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -31,6 +31,7 @@
           "--classpath",
           "--pg-map",
           MIN_API_FLAG,
+          "--main-dex-rules",
           "--main-dex-list",
           "--main-dex-list-output",
           "--desugared-lib",
@@ -139,6 +140,8 @@
                   "  --no-desugaring         # Force disable desugaring.",
                   "  --desugared-lib <file>  # Specify desugared library configuration.",
                   "                          # <file> is a desugared library configuration (json).",
+                  "  --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.",
                   "  --main-dex-list-output <file>",
                   "                          # Output resulting main dex list in <file>."),
@@ -252,6 +255,8 @@
         } catch (IOException e) {
           builder.error(new ExceptionDiagnostic(e, new PathOrigin(file)));
         }
+      } else if (arg.equals("--main-dex-rules")) {
+        builder.addMainDexRulesFiles(Paths.get(nextArg));
       } else if (arg.equals("--main-dex-list")) {
         builder.addMainDexListFiles(Paths.get(nextArg));
       } else if (arg.equals("--main-dex-list-output")) {
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 04479b1..d260bcb 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -10,9 +10,9 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppServices;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
@@ -33,6 +33,7 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 @Keep
@@ -40,78 +41,91 @@
   private final Timing timing = new Timing("maindex");
   private final InternalOptions options;
 
-  private GenerateMainDexList(InternalOptions options) {
+  private List<String> result = null;
+
+  public GenerateMainDexList(InternalOptions options) {
     this.options = options;
   }
 
   private List<String> run(AndroidApp app, ExecutorService executor)
       throws IOException {
     try {
-      DirectMappedDexApplication application =
-          new ApplicationReader(app, options, timing).read(executor).toDirect();
-      AppView<? extends AppInfoWithClassHierarchy> appView = AppView.createForR8(application);
-      appView.setAppServices(AppServices.builder(appView).build());
+      DexApplication application = new ApplicationReader(app, options, timing).read(executor);
+      traceMainDex(
+          executor,
+          application,
+          mainDexTracingResult -> {
+            result =
+                mainDexTracingResult.getClasses().stream()
+                    .map(c -> c.toSourceString().replace('.', '/') + ".class")
+                    .sorted()
+                    .collect(Collectors.toList());
 
-      MainDexListBuilder.checkForAssumedLibraryTypes(appView.appInfo());
-
-      SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
-
-      RootSet mainDexRootSet =
-          new RootSetBuilder(appView, subtypingInfo, options.mainDexKeepRules).run(executor);
-
-      GraphConsumer graphConsumer = options.mainDexKeptGraphConsumer;
-      WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
-      if (!mainDexRootSet.reasonAsked.isEmpty()) {
-        whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(graphConsumer);
-        graphConsumer = whyAreYouKeepingConsumer;
-      }
-
-      Enqueuer enqueuer =
-          EnqueuerFactory.createForMainDexTracing(appView, subtypingInfo, graphConsumer);
-      Set<DexProgramClass> liveTypes = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
-      // LiveTypes is the result.
-      MainDexTracingResult mainDexTracingResult = new MainDexListBuilder(liveTypes, appView).run();
-
-      List<String> result =
-          mainDexTracingResult.getClasses().stream()
-              .map(c -> c.toSourceString().replace('.', '/') + ".class")
-              .sorted()
-              .collect(Collectors.toList());
-
-      if (options.mainDexListConsumer != null) {
-        options.mainDexListConsumer.accept(String.join("\n", result), options.reporter);
-        options.mainDexListConsumer.finished(options.reporter);
-      }
-
-      R8.processWhyAreYouKeepingAndCheckDiscarded(
-          mainDexRootSet,
-          () -> {
-            ArrayList<DexProgramClass> classes = new ArrayList<>();
-            // TODO(b/131668850): This is not a deterministic order!
-            mainDexTracingResult
-                .getClasses()
-                .forEach(
-                    type -> {
-                      DexClass clazz = appView.definitionFor(type);
-                      assert clazz.isProgramClass();
-                      classes.add(clazz.asProgramClass());
-                    });
-            return classes;
-          },
-          whyAreYouKeepingConsumer,
-          appView,
-          enqueuer,
-          true,
-          options,
-          timing,
-          executor);
-
+            if (options.mainDexListConsumer != null) {
+              options.mainDexListConsumer.accept(String.join("\n", result), options.reporter);
+              options.mainDexListConsumer.finished(options.reporter);
+            }
+          });
       return result;
     } catch (ExecutionException e) {
       throw unwrapExecutionException(e);
     }
   }
 
+  public void traceMainDex(
+      ExecutorService executor,
+      DexApplication application,
+      Consumer<MainDexTracingResult> resultConsumer)
+      throws ExecutionException {
+    AppView<? extends AppInfoWithClassHierarchy> appView =
+        AppView.createForR8(application.toDirect());
+    appView.setAppServices(AppServices.builder(appView).build());
+
+    MainDexListBuilder.checkForAssumedLibraryTypes(appView.appInfo());
+
+    SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
+
+    RootSet mainDexRootSet =
+        new RootSetBuilder(appView, subtypingInfo, options.mainDexKeepRules).run(executor);
+
+    GraphConsumer graphConsumer = options.mainDexKeptGraphConsumer;
+    WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
+    if (!mainDexRootSet.reasonAsked.isEmpty()) {
+      whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(graphConsumer);
+      graphConsumer = whyAreYouKeepingConsumer;
+    }
+
+    Enqueuer enqueuer =
+        EnqueuerFactory.createForMainDexTracing(appView, subtypingInfo, graphConsumer);
+    Set<DexProgramClass> liveTypes = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
+    // LiveTypes is the result.
+    MainDexTracingResult mainDexTracingResult = new MainDexListBuilder(liveTypes, appView).run();
+    resultConsumer.accept(mainDexTracingResult);
+
+    R8.processWhyAreYouKeepingAndCheckDiscarded(
+        mainDexRootSet,
+        () -> {
+          ArrayList<DexProgramClass> classes = new ArrayList<>();
+          // TODO(b/131668850): This is not a deterministic order!
+          mainDexTracingResult
+              .getClasses()
+              .forEach(
+                  type -> {
+                    DexClass clazz = appView.definitionFor(type);
+                    assert clazz.isProgramClass();
+                    classes.add(clazz.asProgramClass());
+                  });
+          return classes;
+        },
+        whyAreYouKeepingConsumer,
+        appView,
+        enqueuer,
+        true,
+        options,
+        timing,
+        executor);
+  }
+
   /**
    * Main API entry for computing the main-dex list.
    *
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index 07569a7..b08a738 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -108,15 +108,8 @@
         return new GenerateMainDexListCommand(isPrintHelp(), isPrintVersion());
       }
 
-      List<ProguardConfigurationRule> mainDexKeepRules;
-      if (this.mainDexRules.isEmpty()) {
-        mainDexKeepRules = ImmutableList.of();
-      } else {
-        ProguardConfigurationParser parser =
-            new ProguardConfigurationParser(factory, getReporter());
-        parser.parse(mainDexRules);
-        mainDexKeepRules = parser.getConfig().getRules();
-      }
+      List<ProguardConfigurationRule> mainDexKeepRules =
+          ProguardConfigurationParser.parse(mainDexRules, factory, getReporter());
 
       return new GenerateMainDexListCommand(
           factory,
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 29112ed..d6ab3a6 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -40,6 +40,7 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
@@ -182,28 +183,21 @@
 
     /** Add proguard configuration files with rules for automatic main-dex-list calculation. */
     public Builder addMainDexRulesFiles(Path... paths) {
-      guard(() -> {
-        for (Path path : paths) {
-          mainDexRules.add(new ProguardConfigurationSourceFile(path));
-        }
-      });
-      return self();
+      return addMainDexRulesFiles(Arrays.asList(paths));
     }
 
     /** Add proguard configuration files with rules for automatic main-dex-list calculation. */
     public Builder addMainDexRulesFiles(Collection<Path> paths) {
-      guard(() -> {
-        for (Path path : paths) {
-          mainDexRules.add(new ProguardConfigurationSourceFile(path));
-        }
-      });
+      guard(() -> paths.forEach(p -> mainDexRules.add(new ProguardConfigurationSourceFile(p))));
       return self();
     }
 
     /** Add proguard rules for automatic main-dex-list calculation. */
     public Builder addMainDexRules(List<String> lines, Origin origin) {
-      guard(() -> mainDexRules.add(
-          new ProguardConfigurationSourceStrings(lines, Paths.get("."), origin)));
+      guard(
+          () ->
+              mainDexRules.add(
+                  new ProguardConfigurationSourceStrings(lines, Paths.get("."), origin)));
       return self();
     }
 
@@ -432,7 +426,7 @@
           && mainDexRules.isEmpty()
           && !getAppBuilder().hasMainDexList()) {
         reporter.error(
-            "Option --main-dex-list-output require --main-dex-rules and/or --main-dex-list");
+            "Option --main-dex-list-output requires --main-dex-rules and/or --main-dex-list");
       }
       if (!(getProgramConsumer() instanceof ClassFileConsumer)
           && getMinApiLevel() >= AndroidApiLevel.L.getLevel()) {
@@ -479,15 +473,8 @@
     private R8Command makeR8Command() {
       Reporter reporter = getReporter();
       DexItemFactory factory = new DexItemFactory();
-      List<ProguardConfigurationRule> mainDexKeepRules;
-      if (this.mainDexRules.isEmpty()) {
-        mainDexKeepRules = ImmutableList.of();
-      } else {
-        ProguardConfigurationParser parser =
-            new ProguardConfigurationParser(factory, reporter);
-        parser.parse(mainDexRules);
-        mainDexKeepRules = parser.getConfig().getRules();
-      }
+      List<ProguardConfigurationRule> mainDexKeepRules =
+          ProguardConfigurationParser.parse(mainDexRules, factory, reporter);
 
       DesugaredLibraryConfiguration libraryConfiguration =
           getDesugaredLibraryConfiguration(factory, false);
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 9706eab..762617e 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -86,7 +86,8 @@
         assert !DexAnnotation.isMemberClassesAnnotation(annotation, dexItemFactory);
         assert !DexAnnotation.isEnclosingMethodAnnotation(annotation, dexItemFactory);
         assert !DexAnnotation.isEnclosingClassAnnotation(annotation, dexItemFactory);
-        assert !DexAnnotation.isSignatureAnnotation(annotation, dexItemFactory);
+        assert appView.options().passthroughDexCode
+            || !DexAnnotation.isSignatureAnnotation(annotation, dexItemFactory);
         if (config.exceptions && DexAnnotation.isThrowingAnnotation(annotation, dexItemFactory)) {
           return true;
         }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 8294be0..47ac1e3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -104,6 +104,16 @@
   private static final List<String> UNSUPPORTED_FLAG_OPTIONS =
       ImmutableList.of("skipnonpubliclibraryclasses");
 
+  public static ImmutableList<ProguardConfigurationRule> parse(
+      List<ProguardConfigurationSource> sources, DexItemFactory factory, Reporter reporter) {
+    if (sources.isEmpty()) {
+      return ImmutableList.of();
+    }
+    ProguardConfigurationParser parser = new ProguardConfigurationParser(factory, reporter);
+    parser.parse(sources);
+    return ImmutableList.copyOf(parser.getConfig().getRules());
+  }
+
   public ProguardConfigurationParser(
       DexItemFactory dexItemFactory, Reporter reporter) {
     this(dexItemFactory, reporter, false);
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 9b80b80..3bcb3a7 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -255,6 +255,21 @@
   }
 
   @Test
+  public void mainDexRules() throws Throwable {
+    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(expected = CompilationFailedException.class)
+  public void nonExistingMainDexRules() throws Throwable {
+    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();
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 44fe743..4c88d91 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -81,4 +81,9 @@
     }
     return self();
   }
+
+  public D8TestBuilder addMainDexRulesFiles(Path... mainDexRuleFiles) {
+    builder.addMainDexRulesFiles(mainDexRuleFiles);
+    return self();
+  }
 }
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 6ef399e..52b60fe 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -358,6 +358,26 @@
             .sorted()
             .collect(Collectors.toList());
 
+    // Build main-dex list using D8 & rules.
+    List<String> mainDexListFromD8;
+    {
+      final Box<String> mainDexListOutputFromD8 = new Box<>();
+      testForD8(Backend.DEX)
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
+          .addProgramFiles(inputJar)
+          .addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
+          .addMainDexRulesFiles(mainDexRules)
+          .setMainDexListConsumer(ToolHelper.consumeString(mainDexListOutputFromD8::set))
+          .setMinApi(minSdk)
+          .allowStdoutMessages()
+          .compile();
+      mainDexListFromD8 =
+          StringUtils.splitLines(mainDexListOutputFromD8.get()).stream()
+              .map(this::mainDexStringToDescriptor)
+              .sorted()
+              .collect(Collectors.toList());
+    }
+
     // Build main-dex list using R8.
     final Box<String> r8MainDexListOutput = new Box<>();
     testForR8(Backend.DEX)
@@ -402,6 +422,13 @@
     }
     String[] refList = new String(Files.readAllBytes(
         expectedMainDexList), StandardCharsets.UTF_8).split("\n");
+    for (int i = 0; i < refList.length; i++) {
+      String reference = refList[i].trim();
+      if (mainDexListFromD8.size() <= i) {
+        fail("D8 main-dex list is missing '" + reference + "'");
+      }
+      checkSameMainDexEntry(reference, mainDexListFromD8.get(i));
+    }
     int nonLambdaOffset = 0;
     for (int i = 0; i < refList.length; i++) {
       String reference = refList[i].trim();