Add support for -checkdiscard and -whyareyoukeeping for main dex rules
Bug: 116774422
Change-Id: Ic97a0303c202e414a32f54e7c28f2747faf749a3
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 6436654..8be6306 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -424,14 +424,23 @@
AppInfoWithLiveness mainDexAppInfo =
enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
- // LiveTypes is the result.
+ // LiveTypes is the tracing result.
Set<DexType> mainDexBaseClasses = new HashSet<>(mainDexAppInfo.liveTypes);
-
// Calculate the automatic main dex list according to legacy multidex constraints.
- // Add those classes to an eventual manual list of classes.
- application = application.builder()
- .addToMainDexList(new MainDexListBuilder(mainDexBaseClasses, application).run())
- .build();
+ Set<DexType> mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
+ if (!mainDexRootSet.checkDiscarded.isEmpty()) {
+ new DiscardedChecker(mainDexRootSet, mainDexClasses, appView.appInfo(), options).run();
+ }
+ if (!mainDexRootSet.reasonAsked.isEmpty()) {
+ // If the main dex rules have -whyareyoukeeping rules build an application
+ // pruned with the main dex tracing result to reflect only on main dex content.
+ TreePruner mainDexPruner = new TreePruner(application, mainDexAppInfo, options);
+ DexApplication mainDexApplication = mainDexPruner.run();
+ ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(mainDexRootSet.reasonAsked);
+ reasonPrinter.run(mainDexApplication);
+ }
+ // Add automatic main dex classes to an eventual manual list of classes.
+ application = application.builder().addToMainDexList(mainDexClasses).build();
}
appView.setAppInfo(new AppInfoWithSubtyping(application));
diff --git a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
index e3348a6..b28a1a7 100644
--- a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
+++ b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
@@ -4,29 +4,47 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringDiagnostic;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
public class DiscardedChecker {
private final Set<DexDefinition> checkDiscarded;
- private final DexApplication application;
+ private final List<DexProgramClass> classes;
private boolean fail = false;
private final InternalOptions options;
public DiscardedChecker(RootSet rootSet, DexApplication application, InternalOptions options) {
this.checkDiscarded = rootSet.checkDiscarded;
- this.application = application;
+ this.classes = application.classes();
+ this.options = options;
+ }
+
+ public DiscardedChecker(
+ RootSet rootSet, Set<DexType> types, AppInfo appInfo, InternalOptions options) {
+ this.checkDiscarded = rootSet.checkDiscarded;
+ this.classes = new ArrayList<>();
+ types.forEach(
+ type -> {
+ DexClass clazz = appInfo.definitionFor(type);
+ assert clazz.isProgramClass();
+ this.classes.add(clazz.asProgramClass());
+ });
this.options = options;
}
public void run() {
- for (DexProgramClass clazz : application.classes()) {
+ for (DexProgramClass clazz : classes) {
checkItem(clazz);
clazz.forEachMethod(this::checkItem);
clazz.forEachField(this::checkItem);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java b/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java
new file mode 100644
index 0000000..a68ae3b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2018, 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.maindexlist.checkdiscard;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableList;
+import org.junit.Assert;
+import org.junit.Test;
+
+class HelloWorldMain {
+ public static void main(String[] args) {
+ System.out.println(new MainDexClass());
+ }
+}
+
+class MainDexClass {}
+
+class NonMainDexClass {}
+
+public class MainDexListCheckDiscard extends TestBase {
+ public void runTest(String checkDiscardRule, boolean shouldFail) throws Exception {
+ R8Command command =
+ ToolHelper.prepareR8CommandBuilder(
+ readClasses(HelloWorldMain.class, MainDexClass.class, NonMainDexClass.class))
+ .addMainDexRules(
+ ImmutableList.of(keepMainProguardConfiguration(HelloWorldMain.class)),
+ Origin.unknown())
+ .addMainDexRules(ImmutableList.of(checkDiscardRule), Origin.unknown())
+ .setOutput(temp.getRoot().toPath(), OutputMode.DexIndexed)
+ .setMode(CompilationMode.RELEASE)
+ .build();
+ try {
+ ToolHelper.runR8(command);
+ } catch (CompilationFailedException e) {
+ Assert.assertTrue(shouldFail);
+ return;
+ }
+ Assert.assertFalse(shouldFail);
+ }
+
+ @Test
+ public void testMainDexClassNotDiscarded() throws Exception {
+ runTest("-checkdiscard class " + MainDexClass.class.getCanonicalName(), true);
+ }
+
+ @Test
+ public void testNonMainDexClassDiscarded() throws Exception {
+ runTest("-checkdiscard class " + NonMainDexClass.class.getCanonicalName(), false);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java b/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
new file mode 100644
index 0000000..2713c6d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2018, 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.maindexlist.whyareyoukeeping;
+
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import org.junit.Test;
+
+class HelloWorldMain {
+ public static void main(String[] args) {
+ System.out.println(new MainDexClass());
+ }
+}
+
+class MainDexClass {}
+
+class NonMainDexClass {}
+
+public class MainDexListWhyAreYouKeeping extends TestBase {
+ public String runTest(String whyAreYouKeepingRule) throws Exception {
+ R8Command command =
+ ToolHelper.prepareR8CommandBuilder(
+ readClasses(HelloWorldMain.class, MainDexClass.class, NonMainDexClass.class))
+ .addMainDexRules(
+ ImmutableList.of(keepMainProguardConfiguration(HelloWorldMain.class)),
+ Origin.unknown())
+ .addMainDexRules(ImmutableList.of(whyAreYouKeepingRule), Origin.unknown())
+ .setOutput(temp.getRoot().toPath(), OutputMode.DexIndexed)
+ .setMode(CompilationMode.RELEASE)
+ .build();
+ PrintStream stdout = System.out;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(baos));
+ ToolHelper.runR8(command);
+ String output = new String(baos.toByteArray(), Charset.defaultCharset());
+ System.setOut(stdout);
+ return output;
+ }
+
+ @Test
+ public void testMainDexClassWhyAreYouKeeping() throws Exception {
+ String output = runTest("-whyareyoukeeping class " + MainDexClass.class.getCanonicalName());
+ assertThat(
+ output, containsString("com.android.tools.r8.maindexlist.whyareyoukeeping.MainDexClass"));
+ assertThat(output, containsString("- is live because referenced in keep rule:"));
+ }
+
+ @Test
+ public void testNonMainDexWhyAreYouKeeping() throws Exception {
+ String output = runTest("-whyareyoukeeping class " + NonMainDexClass.class.getCanonicalName());
+ assertTrue(output.isEmpty());
+ }
+}