Rewrite the main dex classes using the graph lens

Bug: 167939768
Change-Id: I5cbb7fe3c3d899e1745d464be6284ad9e4694bb0
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 9af31ab..6c6bab5 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -990,8 +990,7 @@
     DexDefinitionSupplier definitionSupplier = application.getDefinitionsSupplier(committedItems);
     return new AppInfoWithLiveness(
         application,
-        // TODO(b/167939768): Rewrite the main dex classes.
-        getMainDexClasses(),
+        getMainDexClasses().rewrittenWithLens(lens),
         committedItems,
         deadProtoTypes,
         missingTypes,
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java b/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
index 991b7cf..cfadae32 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
 import com.google.common.collect.Sets;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -69,6 +70,15 @@
     return mainDexClasses.isEmpty();
   }
 
+  public MainDexClasses rewrittenWithLens(GraphLens lens) {
+    MainDexClasses rewrittenMainDexClasses = createEmptyMainDexClasses();
+    for (DexType mainDexClass : mainDexClasses) {
+      DexType rewrittenMainDexClass = lens.lookupType(mainDexClass);
+      rewrittenMainDexClasses.mainDexClasses.add(rewrittenMainDexClass);
+    }
+    return rewrittenMainDexClasses;
+  }
+
   public int size() {
     return mainDexClasses.size();
   }
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 1b51a73..6b5604e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -51,6 +51,9 @@
   private final Reporter reporter;
   private final boolean allowTestOptions;
 
+  public static final String FLATTEN_PACKAGE_HIERARCHY = "flattenpackagehierarchy";
+  public static final String REPACKAGE_CLASSES = "repackageclasses";
+
   private static final List<String> IGNORED_SINGLE_ARG_OPTIONS = ImmutableList.of(
       "protomapping",
       "target",
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 0d09fcd..342920c 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1603,6 +1603,10 @@
     return AndroidApiLevel.O;
   }
 
+  public static AndroidApiLevel apiLevelWithNativeMultiDexSupport() {
+    return AndroidApiLevel.L;
+  }
+
   public Path compileToZip(
       TestParameters parameters, Collection<Class<?>> classPath, Class<?>... compilationUnit)
       throws Exception {
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
index f503359..0ef3bdf 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
@@ -369,7 +369,7 @@
 
     // Ensure that the Class1 is actually in the correct split. Note that Class2 would have been
     // shaken away.
-    CodeInspector inspector = new CodeInspector(base, proguardMap.toString());
+    CodeInspector inspector = new CodeInspector(base, proguardMap);
     ClassSubject subject = inspector.clazz("dexsplitsample.Class1");
     assertTrue(subject.isPresent());
     assertTrue(subject.isRenamed());
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
index 97b4db0..bbddb47 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1419TreeShakeJarVerificationTest.java
@@ -69,8 +69,7 @@
           new ProtoApplicationStats(dexItemFactory, compileResult.inspector(), original);
       ProtoApplicationStats baseline =
           new ProtoApplicationStats(
-              dexItemFactory,
-              new CodeInspector(getReleaseApk(), getReleaseProguardMap().toString()));
+              dexItemFactory, new CodeInspector(getReleaseApk(), getReleaseProguardMap()));
       System.out.println(actual.getStats(baseline));
     }
 
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
index b2498b5..0a304b1 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
@@ -78,8 +78,7 @@
           new ProtoApplicationStats(dexItemFactory, compileResult.inspector(), original);
       ProtoApplicationStats baseline =
           new ProtoApplicationStats(
-              dexItemFactory,
-              new CodeInspector(getReleaseApk(), getReleaseProguardMap().toString()));
+              dexItemFactory, new CodeInspector(getReleaseApk(), getReleaseProguardMap()));
       System.out.println(actual.getStats(baseline));
     }
 
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
index b48bec3..79e7eb1 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
@@ -78,8 +78,7 @@
           new ProtoApplicationStats(dexItemFactory, compileResult.inspector(), original);
       ProtoApplicationStats baseline =
           new ProtoApplicationStats(
-              dexItemFactory,
-              new CodeInspector(getReleaseApk(), getReleaseProguardMap().toString()));
+              dexItemFactory, new CodeInspector(getReleaseApk(), getReleaseProguardMap()));
       System.out.println(actual.getStats(baseline));
     }
 
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
index 632210e..09293ac 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1533TreeShakeJarVerificationTest.java
@@ -78,8 +78,7 @@
           new ProtoApplicationStats(dexItemFactory, compileResult.inspector(), original);
       ProtoApplicationStats baseline =
           new ProtoApplicationStats(
-              dexItemFactory,
-              new CodeInspector(getReleaseApk(), getReleaseProguardMap().toString()));
+              dexItemFactory, new CodeInspector(getReleaseApk(), getReleaseProguardMap()));
       System.out.println(actual.getStats(baseline));
     }
 
diff --git a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
index 08a4ae9..0d96a65 100644
--- a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
+++ b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
@@ -162,10 +162,10 @@
         annotationElement[0].value.asDexValueString().value.toSourceString());
   }
 
-  private String getGeneratedProguardMap() throws IOException {
+  private Path getGeneratedProguardMap() throws IOException {
     Path mapFile = Paths.get(tmpOutputDir.getRoot().getCanonicalPath(), DEFAULT_MAP_FILENAME);
     if (Files.exists(mapFile)) {
-      return mapFile.toAbsolutePath().toString();
+      return mapFile.toAbsolutePath();
     }
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
new file mode 100644
index 0000000..00dd079
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.repackage;
+
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RepackageWithMainDexListTest extends TestBase {
+
+  private static final String REPACKAGE_DIR = "foo";
+
+  private final String flattenPackageHierarchyOrRepackageClasses;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{1}, kind: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters()
+            .withDexRuntimes()
+            .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+            .build());
+  }
+
+  public RepackageWithMainDexListTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        // -keep,allowobfuscation does not prohibit repackaging.
+        .addKeepClassRulesWithAllowObfuscation(TestClass.class, OtherTestClass.class)
+        .addKeepRules(
+            "-keepclassmembers class " + TestClass.class.getTypeName() + " { <methods>; }")
+        .addKeepRules(
+            "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + REPACKAGE_DIR + "\"")
+        // Add a class that will be repackaged to the main dex list.
+        .addMainDexListClasses(TestClass.class)
+        .addOptionsModification(
+            options -> {
+              assertFalse(options.testing.enableExperimentalRepackaging);
+              options.testing.enableExperimentalRepackaging = true;
+            })
+        // Debug mode to enable minimal main dex.
+        .debug()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .apply(this::checkCompileResult)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  private void checkCompileResult(R8TestCompileResult compileResult) throws Exception {
+    Path out = temp.newFolder().toPath();
+    compileResult.app.writeToDirectory(out, OutputMode.DexIndexed);
+    Path classes = out.resolve("classes.dex");
+    Path classes2 = out.resolve("classes2.dex");
+    inspectMainDex(new CodeInspector(classes, compileResult.getProguardMap()));
+    inspectSecondaryDex(new CodeInspector(classes2, compileResult.getProguardMap()));
+  }
+
+  private void inspectMainDex(CodeInspector inspector) {
+    assertThat(inspector.clazz(TestClass.class), isPresent());
+    assertThat(inspector.clazz(OtherTestClass.class), not(isPresent()));
+  }
+
+  private void inspectSecondaryDex(CodeInspector inspector) {
+    assertThat(inspector.clazz(TestClass.class), not(isPresent()));
+    assertThat(inspector.clazz(OtherTestClass.class), isPresent());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println("Hello world!");
+    }
+  }
+
+  static class OtherTestClass {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 8365345..6f7c644 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -220,12 +220,7 @@
         Assert.assertEquals(resultInput.toString(), resultOutput.toString());
       }
       if (inspection != null) {
-        CodeInspector inspector =
-            new CodeInspector(
-                out,
-                minify.isMinify()
-                    ? proguardMap.toString()
-                    : null);
+        CodeInspector inspector = new CodeInspector(out, minify.isMinify() ? proguardMap : null);
         inspection.accept(inspector);
       }
       return;
@@ -252,9 +247,7 @@
 
       if (dexComparator != null) {
         CodeInspector ref = new CodeInspector(Paths.get(originalDex));
-        CodeInspector inspector = new CodeInspector(out,
-            minify.isMinify() ? proguardMap.toString()
-                : null);
+        CodeInspector inspector = new CodeInspector(out, minify.isMinify() ? proguardMap : null);
         dexComparator.accept(ref, inspector);
       }
     } else {
@@ -265,8 +258,7 @@
     }
 
     if (inspection != null) {
-      CodeInspector inspector =
-          new CodeInspector(out, minify.isMinify() ? proguardMap.toString() : null);
+      CodeInspector inspector = new CodeInspector(out, minify.isMinify() ? proguardMap : null);
       inspection.accept(inspector);
     }
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 6d48d92..8672caa 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -77,8 +77,12 @@
     this(Paths.get(path));
   }
 
-  public CodeInspector(Path file, String mappingFile) throws IOException {
-    this(Collections.singletonList(file), mappingFile, null);
+  public CodeInspector(Path file, String proguardMapContent) throws IOException {
+    this(AndroidApp.builder().addProgramFile(file).build(), proguardMapContent);
+  }
+
+  public CodeInspector(Path file, Path mappingFile) throws IOException {
+    this(Collections.singletonList(file), mappingFile.toString(), null);
   }
 
   public CodeInspector(Path file) throws IOException {