Add traced main dex classes to set prior to repackaging

Bug: 176477869
Change-Id: I81a742b5e6396b366fae87e3a649c45cfbdc7d12
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 1dc771e..0a41c48 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -810,6 +810,11 @@
           MemberRebindingIdentityLensFactory.create(appView, executorService);
       appView.setGraphLens(memberRebindingLens);
 
+      // Add automatic main dex classes to an eventual manual list of classes.
+      if (!options.mainDexKeepRules.isEmpty()) {
+        appView.appInfo().getMainDexClasses().addAll(mainDexTracingResult);
+      }
+
       // Perform repackaging.
       if (options.isRepackagingEnabled()) {
         DirectMappedDexApplication.Builder appBuilder =
@@ -828,11 +833,6 @@
       }
       assert Repackaging.verifyIdentityRepackaging(appView);
 
-      // Add automatic main dex classes to an eventual manual list of classes.
-      if (!options.mainDexKeepRules.isEmpty()) {
-        appView.appInfo().getMainDexClasses().addAll(mainDexTracingResult);
-      }
-
       SyntheticFinalization.Result result =
           appView.getSyntheticItems().computeFinalSynthetics(appView);
       if (result != null) {
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java b/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java
index 9a1ad72..f1439ad 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java
@@ -80,7 +80,13 @@
         if (!classSubject.isPresent()) {
           return false;
         }
-        return getActualPackage(classSubject).equals(getExpectedPackage(clazz));
+        String actualPackage = getActualPackage(classSubject);
+        String expectedPackage = getExpectedPackageForEligibleClass();
+        if (!eligibleForRepackaging) {
+          // The package may have been changed by minification.
+          return !actualPackage.startsWith(expectedPackage);
+        }
+        return actualPackage.equals(expectedPackage);
       }
 
       @Override
@@ -113,12 +119,6 @@
         return classSubject.getDexProgramClass().getType().getPackageName();
       }
 
-      private String getExpectedPackage(Class<?> clazz) {
-        return eligibleForRepackaging
-            ? getExpectedPackageForEligibleClass()
-            : clazz.getPackage().getName();
-      }
-
       private String getExpectedPackageForEligibleClass() {
         List<String> expectedPackageNames = new ArrayList<>();
         expectedPackageNames.add(getRepackagePackage());
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
index 4fc28f5..d322cba 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
@@ -7,12 +7,18 @@
 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 com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
@@ -42,6 +48,7 @@
 
   @Test
   public void test() throws Exception {
+    Box<String> r8MainDexList = new Box<>();
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         // -keep,allowobfuscation does not prohibit repackaging.
@@ -51,40 +58,48 @@
         // Add a class that will be repackaged to the main dex list.
         .addMainDexListClasses(TestClass.class)
         .apply(this::configureRepackaging)
+        .setMainDexListConsumer(ToolHelper.consumeString(r8MainDexList::set))
         // Debug mode to enable minimal main dex.
         .debug()
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .apply(this::checkCompileResult)
+        .apply(result -> checkCompileResult(result, r8MainDexList.get()))
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello world!");
   }
 
-  private void checkCompileResult(R8TestCompileResult compileResult) throws Exception {
+  private void checkCompileResult(R8TestCompileResult compileResult, String mainDexList)
+      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()));
+    inspectMainDex(new CodeInspector(classes, compileResult.getProguardMap()), mainDexList);
     inspectSecondaryDex(new CodeInspector(classes2, compileResult.getProguardMap()));
   }
 
-  private void inspectMainDex(CodeInspector inspector) {
-    assertThat(inspector.clazz(TestClass.class), isPresent());
+  private void inspectMainDex(CodeInspector inspector, String mainDexList) {
+    ClassSubject testClass = inspector.clazz(TestClass.class);
+    assertThat(testClass, isPresentAndRenamed());
+    assertThat(TestClass.class, isRepackaged(inspector));
+    List<String> mainDexTypeNames = StringUtils.splitLines(mainDexList);
+    assertEquals(1, mainDexTypeNames.size());
+    assertEquals(testClass.getFinalBinaryName(), mainDexTypeNames.get(0).replace(".class", ""));
     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());
+    assertThat(OtherTestClass.class, isRepackaged(inspector));
   }
 
-  static class TestClass {
+  public static class TestClass {
 
     public static void main(String[] args) {
       System.out.println("Hello world!");
     }
   }
 
-  static class OtherTestClass {}
+  public static class OtherTestClass {}
 }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexNoRepackageTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexNoRepackageTest.java
new file mode 100644
index 0000000..832629b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexNoRepackageTest.java
@@ -0,0 +1,93 @@
+// 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 com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8TestCompileResult;
+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 RepackageWithMainDexNoRepackageTest extends RepackageTestBase {
+
+  @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 RepackageWithMainDexNoRepackageTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, 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>; }")
+        // Add a class that will be repackaged to the main dex list.
+        .addMainDexListClasses(TestClass.class)
+        .apply(this::configureRepackaging)
+        // 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), isPresentAndRenamed());
+    assertThat(inspector.clazz(OtherTestClass.class), not(isPresent()));
+    assertThat(TestClass.class, isNotRepackaged(inspector));
+  }
+
+  private void inspectSecondaryDex(CodeInspector inspector) {
+    assertThat(inspector.clazz(TestClass.class), not(isPresent()));
+    assertThat(inspector.clazz(OtherTestClass.class), isPresent());
+    assertThat(OtherTestClass.class, isNotRepackaged(inspector));
+  }
+
+  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/repackage/RepackageWithMainDexTracingTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexTracingTest.java
new file mode 100644
index 0000000..b258458
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexTracingTest.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2021, 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 com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.StringUtils;
+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 RepackageWithMainDexTracingTest extends RepackageTestBase {
+
+  @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 RepackageWithMainDexTracingTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Box<String> r8MainDexList = new Box<>();
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassRulesWithAllowObfuscation(Other.class)
+        .addMainDexClassRules(Main.class)
+        .addOptionsModification(options -> options.minimalMainDex = true)
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .setMainDexListConsumer(ToolHelper.consumeString(r8MainDexList::set))
+        .apply(this::configureRepackaging)
+        .compile()
+        .apply(result -> checkCompileResult(result, r8MainDexList.get()))
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("main dex");
+  }
+
+  private void checkCompileResult(R8TestCompileResult compileResult, String mainDexList)
+      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()), mainDexList);
+    inspectSecondaryDex(new CodeInspector(classes2, compileResult.getProguardMap()));
+  }
+
+  private void inspectMainDex(CodeInspector inspector, String mainDexList) {
+    assertThat(inspector.clazz(Main.class), isPresentAndNotRenamed());
+    assertThat(Main.class, isNotRepackaged(inspector));
+    assertThat(inspector.clazz(A.class), isPresentAndRenamed());
+    assertThat(A.class, isRepackaged(inspector));
+    List<String> mainDexTypeNames = StringUtils.splitLines(mainDexList);
+    assertEquals(2, mainDexTypeNames.size());
+    assertEquals(
+        inspector.clazz(Main.class).getFinalBinaryName(),
+        mainDexTypeNames.get(0).replace(".class", ""));
+    assertEquals(
+        inspector.clazz(A.class).getFinalBinaryName(),
+        mainDexTypeNames.get(1).replace(".class", ""));
+    assertThat(inspector.clazz(Other.class), not(isPresent()));
+  }
+
+  private void inspectSecondaryDex(CodeInspector inspector) {
+    assertThat(inspector.clazz(Main.class), not(isPresent()));
+    assertThat(inspector.clazz(A.class), not(isPresent()));
+    assertThat(inspector.clazz(Other.class), isPresentAndRenamed());
+    assertThat(Other.class, isRepackaged(inspector));
+  }
+
+  @NoHorizontalClassMerging
+  public static class Other {}
+
+  @NeverClassInline
+  public static class A {
+    public A() {
+      System.out.println("main dex");
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      A a = new A();
+    }
+  }
+}