Merge "Remove stale TODO regarding member rule combination for -if rule."
diff --git a/build.gradle b/build.gradle
index 1ed69a7..da3375e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -532,15 +532,6 @@
baseName 'deps'
}
-task repackageDepsForLib(type: ShadowJar) {
- configurations = [project.configurations.runtimeClasspath]
- mergeServiceFiles(it)
- configureRelocations(it)
- exclude { it.getRelativePath().getPathString() == "module-info.class" }
- exclude { it.getRelativePath().getPathString().startsWith("META-INF/maven/") }
- baseName 'r8lib_deps'
-}
-
task repackageSources(type: ShadowJar) {
from sourceSets.main.output
mergeServiceFiles(it)
@@ -550,16 +541,19 @@
baseName 'sources'
}
-task R8libWithDeps(type: ShadowJar) {
+task r8WithRelocatedDeps(type: ShadowJar) {
from consolidatedLicense.outputs.files
- baseName 'r8lib_with_deps'
+ baseName 'r8_with_relocated_deps'
classifier = null
version = null
manifest {
attributes 'Main-Class': 'com.android.tools.r8.SwissArmyKnife'
}
from repackageSources.outputs.files
- from repackageDepsForLib.outputs.files
+ from repackageDeps.outputs.files
+ doLast {
+ configureRelocations(it)
+ }
}
task R8(type: ShadowJar) {
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index fcba149..48c67fd 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -7,15 +7,16 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graphinfo.GraphConsumer;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.MainDexClasses;
import com.android.tools.r8.shaking.MainDexListBuilder;
-import com.android.tools.r8.shaking.ReasonPrinter;
import com.android.tools.r8.shaking.RootSetBuilder;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
-import com.android.tools.r8.shaking.TreePruner;
+import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -46,7 +47,15 @@
new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense());
RootSet mainDexRootSet =
new RootSetBuilder(appView, application, options.mainDexKeepRules, options).run(executor);
- Enqueuer enqueuer = new Enqueuer(appView, options, true);
+
+ GraphConsumer graphConsumer = options.mainDexKeptGraphConsumer;
+ WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
+ if (!mainDexRootSet.reasonAsked.isEmpty()) {
+ whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(graphConsumer);
+ graphConsumer = whyAreYouKeepingConsumer;
+ }
+
+ Enqueuer enqueuer = new Enqueuer(appView, options, graphConsumer, true);
AppInfoWithLiveness mainDexAppInfo = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
// LiveTypes is the result.
MainDexClasses mainDexClasses =
@@ -63,12 +72,12 @@
}
// 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);
+ if (whyAreYouKeepingConsumer != null) {
+ // TODO(b/120959039): This should be ordered!
+ for (DexDefinition definition : mainDexRootSet.reasonAsked) {
+ whyAreYouKeepingConsumer.printWhyAreYouKeeping(
+ enqueuer.getGraphNode(definition), System.out);
+ }
}
return result;
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index ec7bc53..0b31acf 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -4,6 +4,7 @@
package com.android.tools.r8;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graphinfo.GraphConsumer;
import com.android.tools.r8.origin.CommandLineOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardConfigurationParser;
@@ -25,6 +26,7 @@
private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
private final StringConsumer mainDexListConsumer;
+ private final GraphConsumer mainDexKeptGraphConsumer;
private final DexItemFactory factory;
private final Reporter reporter;
@@ -33,6 +35,7 @@
private final DexItemFactory factory = new DexItemFactory();
private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
private StringConsumer mainDexListConsumer = null;
+ private GraphConsumer mainDexKeptGraphConsumer = null;
private Builder() {
}
@@ -114,7 +117,18 @@
}
return new GenerateMainDexListCommand(
- factory, getAppBuilder().build(), mainDexKeepRules, mainDexListConsumer, getReporter());
+ factory,
+ getAppBuilder().build(),
+ mainDexKeepRules,
+ mainDexListConsumer,
+ mainDexKeptGraphConsumer,
+ getReporter());
+ }
+
+ public GenerateMainDexListCommand.Builder setMainDexKeptGraphConsumer(
+ GraphConsumer graphConsumer) {
+ this.mainDexKeptGraphConsumer = graphConsumer;
+ return self();
}
}
@@ -185,11 +199,13 @@
AndroidApp inputApp,
ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
StringConsumer mainDexListConsumer,
+ GraphConsumer mainDexKeptGraphConsumer,
Reporter reporter) {
super(inputApp);
this.factory = factory;
this.mainDexKeepRules = mainDexKeepRules;
this.mainDexListConsumer = mainDexListConsumer;
+ this.mainDexKeptGraphConsumer = mainDexKeptGraphConsumer;
this.reporter = reporter;
}
@@ -198,6 +214,7 @@
this.factory = new DexItemFactory();
this.mainDexKeepRules = ImmutableList.of();
this.mainDexListConsumer = null;
+ this.mainDexKeptGraphConsumer = null;
this.reporter = new Reporter();
}
@@ -206,6 +223,7 @@
InternalOptions internal = new InternalOptions(factory, reporter);
internal.mainDexKeepRules = mainDexKeepRules;
internal.mainDexListConsumer = mainDexListConsumer;
+ internal.mainDexKeptGraphConsumer = mainDexKeptGraphConsumer;
internal.minimalMainDex = internal.debug;
internal.enableSwitchMapRemoval = false;
internal.enableInlining = false;
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 5714d93..0d8524b 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -89,7 +89,7 @@
new RootSetBuilder(
appView, application, options.proguardConfiguration.getRules(), options)
.run(executor);
- Enqueuer enqueuer = new Enqueuer(appView, options);
+ Enqueuer enqueuer = new Enqueuer(appView, options, null);
AppInfoWithLiveness appInfo =
enqueuer.traceApplication(
rootSet, options.proguardConfiguration.getDontWarnPatterns(), executor, timing);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index fa04ee8..7e4974b 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -15,9 +15,11 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
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.graph.GraphLense;
+import com.android.tools.r8.graphinfo.GraphConsumer;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.optimize.EnumOrdinalMapCollector;
import com.android.tools.r8.ir.optimize.MethodPoolCollection;
@@ -46,12 +48,12 @@
import com.android.tools.r8.shaking.MainDexListBuilder;
import com.android.tools.r8.shaking.ProguardClassFilter;
import com.android.tools.r8.shaking.ProguardConfiguration;
-import com.android.tools.r8.shaking.ReasonPrinter;
import com.android.tools.r8.shaking.RootSetBuilder;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.shaking.StaticClassMerger;
import com.android.tools.r8.shaking.TreePruner;
import com.android.tools.r8.shaking.VerticalClassMerger;
+import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.CfgPrinter;
@@ -287,9 +289,14 @@
appView, application, options.proguardConfiguration.getRules(), options)
.run(executorService);
- Enqueuer enqueuer = new Enqueuer(appView, options, compatibility);
- appView.setAppInfo(enqueuer.traceApplication(
- rootSet, options.proguardConfiguration.getDontWarnPatterns(), executorService, timing));
+ Enqueuer enqueuer = new Enqueuer(appView, options, null, compatibility);
+ appView.setAppInfo(
+ enqueuer.traceApplication(
+ rootSet,
+ options.proguardConfiguration.getDontWarnPatterns(),
+ executorService,
+ timing));
+
if (options.proguardConfiguration.isPrintSeeds()) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
PrintStream out = new PrintStream(bytes);
@@ -452,11 +459,19 @@
MainDexClasses mainDexClasses = MainDexClasses.NONE;
if (!options.mainDexKeepRules.isEmpty()) {
appView.setAppInfo(new AppInfoWithSubtyping(application));
- Enqueuer enqueuer = new Enqueuer(appView, options, true);
// Lets find classes which may have code executed before secondary dex files installation.
RootSet mainDexRootSet =
new RootSetBuilder(appView, application, options.mainDexKeepRules, options)
.run(executorService);
+
+ GraphConsumer mainDexKeptGraphConsumer = options.mainDexKeptGraphConsumer;
+ WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
+ if (!mainDexRootSet.reasonAsked.isEmpty()) {
+ whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(mainDexKeptGraphConsumer);
+ mainDexKeptGraphConsumer = whyAreYouKeepingConsumer;
+ }
+
+ Enqueuer enqueuer = new Enqueuer(appView, options, mainDexKeptGraphConsumer, true);
AppInfoWithLiveness mainDexAppInfo =
enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
@@ -469,13 +484,12 @@
mainDexRootSet, mainDexClasses.getClasses(), 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);
+ if (whyAreYouKeepingConsumer != null) {
+ // TODO(b/120959039): Sort the set so the order is always the same print order!
+ for (DexDefinition dexDefinition : mainDexRootSet.reasonAsked) {
+ whyAreYouKeepingConsumer.printWhyAreYouKeeping(
+ enqueuer.getGraphNode(dexDefinition), System.out);
+ }
}
}
@@ -484,7 +498,18 @@
if (options.enableTreeShaking || options.enableMinification) {
timing.begin("Post optimization code stripping");
try {
- Enqueuer enqueuer = new Enqueuer(appView, options);
+
+ GraphConsumer keptGraphConsumer = null;
+ WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
+ if (options.enableTreeShaking) {
+ keptGraphConsumer = options.keptGraphConsumer;
+ if (!rootSet.reasonAsked.isEmpty()) {
+ whyAreYouKeepingConsumer = new WhyAreYouKeepingConsumer(keptGraphConsumer);
+ keptGraphConsumer = whyAreYouKeepingConsumer;
+ }
+ }
+
+ Enqueuer enqueuer = new Enqueuer(appView, options, keptGraphConsumer);
appView.setAppInfo(
enqueuer.traceApplication(
rootSet,
@@ -501,8 +526,13 @@
.appInfo()
.prunedCopyFrom(application, pruner.getRemovedClasses()));
// Print reasons on the application after pruning, so that we reflect the actual result.
- ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(rootSet.reasonAsked);
- reasonPrinter.run(application);
+ if (whyAreYouKeepingConsumer != null) {
+ // TODO(b/120959039): Sort the set so the order is always the same print order!
+ for (DexDefinition dexDefinition : rootSet.reasonAsked) {
+ whyAreYouKeepingConsumer.printWhyAreYouKeeping(
+ enqueuer.getGraphNode(dexDefinition), System.out);
+ }
+ }
// Remove annotations that refer to types that no longer exist.
new AnnotationRemover(appView.appInfo().withLiveness(), options).run();
if (!mainDexClasses.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 338fb4f..ff74d09 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graphinfo.GraphConsumer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.origin.StandardOutOrigin;
@@ -83,6 +84,8 @@
private boolean disableVerticalClassMerging = false;
private boolean forceProguardCompatibility = false;
private StringConsumer proguardMapConsumer = null;
+ private GraphConsumer keptGraphConsumer = null;
+ private GraphConsumer mainDexKeptGraphConsumer = null;
// Internal compatibility mode for use from CompatProguard tool.
Path proguardCompatibilityRulesOutput = null;
@@ -232,6 +235,26 @@
}
/**
+ * Set a consumer for receiving kept-graph events.
+ *
+ * @param graphConsumer
+ */
+ public Builder setKeptGraphConsumer(GraphConsumer graphConsumer) {
+ this.keptGraphConsumer = graphConsumer;
+ return self();
+ }
+
+ /**
+ * Set a consumer for receiving kept-graph events for the content of the main-dex output.
+ *
+ * @param graphConsumer
+ */
+ public Builder setMainDexKeptGraphConsumer(GraphConsumer graphConsumer) {
+ this.mainDexKeptGraphConsumer = graphConsumer;
+ return self();
+ }
+
+ /**
* Set the output path-and-mode.
*
* <p>Setting the output path-and-mode will override any previous set consumer or any previous
@@ -416,6 +439,8 @@
forceProguardCompatibility,
proguardMapConsumer,
proguardCompatibilityRulesOutput,
+ keptGraphConsumer,
+ mainDexKeptGraphConsumer,
isOptimizeMultidexForLinearAlloc());
return command;
@@ -481,6 +506,8 @@
private final boolean forceProguardCompatibility;
private final StringConsumer proguardMapConsumer;
private final Path proguardCompatibilityRulesOutput;
+ private final GraphConsumer keptGraphConsumer;
+ private final GraphConsumer mainDexKeptGraphConsumer;
/** Get a new {@link R8Command.Builder}. */
public static Builder builder() {
@@ -545,6 +572,8 @@
boolean forceProguardCompatibility,
StringConsumer proguardMapConsumer,
Path proguardCompatibilityRulesOutput,
+ GraphConsumer keptGraphConsumer,
+ GraphConsumer mainDexKeptGraphConsumer,
boolean optimizeMultidexForLinearAlloc) {
super(inputApp, mode, programConsumer, mainDexListConsumer, minApiLevel, reporter,
enableDesugaring, optimizeMultidexForLinearAlloc);
@@ -558,6 +587,8 @@
this.forceProguardCompatibility = forceProguardCompatibility;
this.proguardMapConsumer = proguardMapConsumer;
this.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
+ this.keptGraphConsumer = keptGraphConsumer;
+ this.mainDexKeptGraphConsumer = mainDexKeptGraphConsumer;
}
private R8Command(boolean printHelp, boolean printVersion) {
@@ -570,6 +601,8 @@
forceProguardCompatibility = false;
proguardMapConsumer = null;
proguardCompatibilityRulesOutput = null;
+ keptGraphConsumer = null;
+ mainDexKeptGraphConsumer = null;
}
/** Get the enable-tree-shaking state. */
@@ -671,6 +704,10 @@
internal.proguardMapConsumer = wrappedConsumer;
}
+ // Set the kept-graph consumer if any. It will only be actively used if the enqueuer triggers.
+ internal.keptGraphConsumer = keptGraphConsumer;
+ internal.mainDexKeptGraphConsumer = mainDexKeptGraphConsumer;
+
internal.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
internal.dataResourceConsumer = internal.programConsumer.getDataResourceConsumer();
diff --git a/src/main/java/com/android/tools/r8/graphinfo/AnnotationGraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/AnnotationGraphNode.java
new file mode 100644
index 0000000..2c8482f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graphinfo/AnnotationGraphNode.java
@@ -0,0 +1,44 @@
+// 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.graphinfo;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.graph.DexItem;
+
+@Keep
+public final class AnnotationGraphNode extends GraphNode {
+
+ private final DexItem annotatedItem;
+
+ public AnnotationGraphNode(DexItem annotatedItem) {
+ assert annotatedItem != null;
+ this.annotatedItem = annotatedItem;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o
+ || (o instanceof AnnotationGraphNode
+ && ((AnnotationGraphNode) o).annotatedItem == annotatedItem);
+ }
+
+ @Override
+ public int hashCode() {
+ return annotatedItem.hashCode();
+ }
+
+ public String getDescriptor() {
+ return annotatedItem.toSourceString();
+ }
+
+ /**
+ * Get a unique identity string determining this annotated-item node.
+ *
+ * <p>This is the descriptor of the concrete node type.
+ */
+ @Override
+ public String identity() {
+ return getDescriptor();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/ClassGraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/ClassGraphNode.java
new file mode 100644
index 0000000..96bebdb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graphinfo/ClassGraphNode.java
@@ -0,0 +1,42 @@
+// 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.graphinfo;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.graph.DexType;
+
+@Keep
+public final class ClassGraphNode extends GraphNode {
+
+ private final DexType clazz;
+
+ public ClassGraphNode(DexType clazz) {
+ assert clazz != null;
+ this.clazz = clazz;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o || (o instanceof ClassGraphNode && ((ClassGraphNode) o).clazz == clazz);
+ }
+
+ @Override
+ public int hashCode() {
+ return clazz.hashCode();
+ }
+
+ public String getDescriptor() {
+ return clazz.toDescriptorString();
+ }
+
+ /**
+ * Get a unique identity string determining this clazz node.
+ *
+ * <p>This is just the class descriptor.
+ */
+ @Override
+ public String identity() {
+ return getDescriptor();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/FieldGraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/FieldGraphNode.java
new file mode 100644
index 0000000..bb3b932
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graphinfo/FieldGraphNode.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.graphinfo;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.graph.DexField;
+
+@Keep
+public final class FieldGraphNode extends GraphNode {
+
+ private final DexField field;
+
+ public FieldGraphNode(DexField field) {
+ assert field != null;
+ this.field = field;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o || (o instanceof FieldGraphNode && ((FieldGraphNode) o).field == field);
+ }
+
+ @Override
+ public int hashCode() {
+ return field.hashCode();
+ }
+
+ /**
+ * Get the class descriptor for the field holder as defined by the JVM specification.
+ *
+ * <p>For the field {@code x.y.Z a.b.C.foo}, this would be {@code La/b/C;}.
+ */
+ public String getHolderDescriptor() {
+ return field.clazz.toDescriptorString();
+ }
+
+ /**
+ * Get the field descriptor as defined by the JVM specification.
+ *
+ * <p>For the field {@code x.y.Z a.b.C.foo}, this would be {@code Lx/y/Z;}.
+ */
+ public String getFieldDescriptor() {
+ return field.type.toDescriptorString();
+ }
+
+ /**
+ * Get the (unqualified) field name.
+ *
+ * <p>For the field {@code x.y.Z a.b.C.foo} this would be {@code foo}.
+ */
+ public String getFieldName() {
+ return field.name.toString();
+ }
+
+ /**
+ * Get a unique identity string determining this field node.
+ *
+ * <p>The identity string follows the CF encoding of a field reference: {@code
+ * <holder-descriptor>.<field-name>:<field-descriptor>}, e.g., for {@code x.y.Z a.b.C.foo} this
+ * would be {@code La/b/C;foo:Lx/y/Z;}.
+ */
+ @Override
+ public String identity() {
+ return getHolderDescriptor() + getFieldName() + "\":" + getFieldDescriptor();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/GraphConsumer.java b/src/main/java/com/android/tools/r8/graphinfo/GraphConsumer.java
new file mode 100644
index 0000000..8c124e5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graphinfo/GraphConsumer.java
@@ -0,0 +1,19 @@
+// 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.graphinfo;
+
+import com.android.tools.r8.KeepForSubclassing;
+
+@KeepForSubclassing
+public interface GraphConsumer {
+
+ /**
+ * Registers a directed edge in the graph.
+ *
+ * @param source The source node of the edge.
+ * @param target The target node of the edge.
+ * @param info Additional information about the edge.
+ */
+ void acceptEdge(GraphNode source, GraphNode target, GraphEdgeInfo info);
+}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/GraphEdgeInfo.java b/src/main/java/com/android/tools/r8/graphinfo/GraphEdgeInfo.java
new file mode 100644
index 0000000..c0b3f3b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graphinfo/GraphEdgeInfo.java
@@ -0,0 +1,48 @@
+// 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.graphinfo;
+
+public class GraphEdgeInfo {
+
+ // TODO(b/120959039): Simplify these. Most of the information is present in the source node.
+ public enum EdgeKind {
+ // Prioritized list of edge types.
+ KeepRule,
+ CompatibilityRule,
+ InstantiatedIn,
+ InvokedViaSuper,
+ TargetedBySuper,
+ InvokedFrom,
+ InvokedFromLambdaCreatedIn,
+ ReferencedFrom,
+ ReachableFromLiveType,
+ ReferencedInAnnotation,
+ IsLibraryMethod,
+ }
+
+ private final EdgeKind kind;
+
+ public GraphEdgeInfo(EdgeKind kind) {
+ this.kind = kind;
+ }
+
+ public EdgeKind edgeKind() {
+ return kind;
+ }
+
+ @Override
+ public String toString() {
+ return "{edge-type:" + kind.toString() + "}";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o || (o instanceof GraphEdgeInfo && ((GraphEdgeInfo) o).kind == kind);
+ }
+
+ @Override
+ public int hashCode() {
+ return kind.hashCode();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/GraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/GraphNode.java
new file mode 100644
index 0000000..4f6251b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graphinfo/GraphNode.java
@@ -0,0 +1,23 @@
+// 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.graphinfo;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public abstract class GraphNode {
+
+ public abstract String identity();
+
+ @Override
+ public abstract boolean equals(Object o);
+
+ @Override
+ public abstract int hashCode();
+
+ @Override
+ public String toString() {
+ return identity();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/KeepRuleGraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/KeepRuleGraphNode.java
new file mode 100644
index 0000000..2f4d5e1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graphinfo/KeepRuleGraphNode.java
@@ -0,0 +1,69 @@
+// 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.graphinfo;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.position.TextRange;
+import com.android.tools.r8.shaking.ProguardKeepRule;
+
+@Keep
+public final class KeepRuleGraphNode extends GraphNode {
+
+ private final ProguardKeepRule rule;
+
+ public KeepRuleGraphNode(ProguardKeepRule rule) {
+ assert rule != null;
+ this.rule = rule;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o || (o instanceof KeepRuleGraphNode && ((KeepRuleGraphNode) o).rule == rule);
+ }
+
+ @Override
+ public int hashCode() {
+ return rule.hashCode();
+ }
+
+ public Origin getOrigin() {
+ return rule.getOrigin();
+ }
+
+ public Position getPosition() {
+ return rule.getPosition();
+ }
+
+ public String getContent() {
+ return rule.getSource();
+ }
+
+ /**
+ * Get an identity string determining this keep rule.
+ *
+ * <p>The identity string is typically the source-file (if present) followed by the line number.
+ * {@code <keep-rule-file>:<keep-rule-start-line>:<keep-rule-start-column>}.
+ */
+ @Override
+ public String identity() {
+ return (getOrigin() == Origin.unknown() ? getContent() : getOrigin())
+ + ":"
+ + shortPositionInfo(getPosition());
+ }
+
+ private static String shortPositionInfo(Position position) {
+ if (position instanceof TextRange) {
+ TextPosition start = ((TextRange) position).getStart();
+ return start.getLine() + ":" + start.getColumn();
+ }
+ if (position instanceof TextPosition) {
+ TextPosition start = (TextPosition) position;
+ return start.getLine() + ":" + start.getColumn();
+ }
+ return position.getDescription();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/MethodGraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/MethodGraphNode.java
new file mode 100644
index 0000000..2745518
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graphinfo/MethodGraphNode.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.graphinfo;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.graph.DexMethod;
+
+@Keep
+public final class MethodGraphNode extends GraphNode {
+
+ private final DexMethod method;
+
+ public MethodGraphNode(DexMethod method) {
+ assert method != null;
+ this.method = method;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o || (o instanceof MethodGraphNode && ((MethodGraphNode) o).method == method);
+ }
+
+ @Override
+ public int hashCode() {
+ return method.hashCode();
+ }
+
+ /**
+ * Get the class descriptor for the method holder as defined by the JVM specification.
+ *
+ * <p>For the method {@code void a.b.C.foo(x.y.Z arg)}, this would be {@code La/b/C;}.
+ */
+ public String getHolderDescriptor() {
+ return method.holder.toDescriptorString();
+ }
+
+ /**
+ * Get the method descriptor as defined by the JVM specification.
+ *
+ * <p>For the method {@code void a.b.C.foo(x.y.Z arg)}, this would be {@code (Lx/y/Z;)V}.
+ */
+ public String getMethodDescriptor() {
+ return method.proto.toDescriptorString();
+ }
+
+ /**
+ * Get the (unqualified) method name.
+ *
+ * <p>For the method {@code void a.b.C.foo(x.y.Z arg)} this would be {@code foo}.
+ */
+ public String getMethodName() {
+ return method.name.toString();
+ }
+
+ /**
+ * Get a unique identity string determining this method node.
+ *
+ * <p>The identity string follows the CF encoding of a method reference:
+ * {@code <holder-descriptor><method-name><method-descriptor>}, e.g., for
+ * {@code void a.b.C.foo(x.y.Z arg)} this will be {@code La/b/C;foo(Lx/y/Z;)V}.
+ */
+ @Override
+ public String identity() {
+ return getHolderDescriptor() + getMethodName() + getMethodDescriptor();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 6f7965e..315df31 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -333,7 +333,31 @@
// Returns `true` if at least one method was inlined.
boolean processInlining(IRCode code, Supplier<InliningOracle> defaultOracle) {
replaceUsagesAsUnusedArgument(code);
- boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code, defaultOracle);
+
+ boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code);
+ if (anyInlinedMethods) {
+ // Reset the collections.
+ methodCallsOnInstance.clear();
+ extraMethodCalls.clear();
+ unusedArguments.clear();
+ estimatedCombinedSizeForInlining = 0;
+
+ // Repeat user analysis
+ InstructionOrPhi ineligibleUser = areInstanceUsersEligible(null, defaultOracle);
+ if (ineligibleUser != null) {
+ // We introduced a user that we cannot handle in the class inliner as a result of force
+ // inlining. Abort gracefully from class inlining without removing the instance.
+ //
+ // Alternatively we would need to collect additional information about the behavior of
+ // methods (which is bad for memory), or we would need to analyze the called methods before
+ // inlining them. The latter could be good solution, since we are going to build IR for the
+ // methods that need to be inlined anyway.
+ return true;
+ }
+ assert extraMethodCalls.isEmpty();
+ assert unusedArguments.isEmpty();
+ }
+
anyInlinedMethods |= forceInlineDirectMethodInvocations(code);
removeMiscUsages(code);
removeFieldReads(code);
@@ -359,32 +383,12 @@
unusedArguments.clear();
}
- private boolean forceInlineExtraMethodInvocations(
- IRCode code, Supplier<InliningOracle> defaultOracle) {
+ private boolean forceInlineExtraMethodInvocations(IRCode code) {
if (extraMethodCalls.isEmpty()) {
return false;
}
-
// Inline extra methods.
inliner.performForcedInlining(method, code, extraMethodCalls);
-
- // Reset the collections.
- methodCallsOnInstance.clear();
- extraMethodCalls.clear();
- unusedArguments.clear();
- estimatedCombinedSizeForInlining = 0;
-
- // Repeat user analysis
- InstructionOrPhi ineligibleUser = areInstanceUsersEligible(null, defaultOracle);
- if (ineligibleUser != null) {
- throw new Unreachable(
- "Unexpected ineligible user in method `"
- + method.method.toSourceString()
- + "` after inlining of extra methods: "
- + ineligibleUser);
- }
- assert extraMethodCalls.isEmpty();
- assert unusedArguments.isEmpty();
return true;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index ea62227..c579500 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -36,6 +36,15 @@
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.PresortedComparable;
+import com.android.tools.r8.graphinfo.AnnotationGraphNode;
+import com.android.tools.r8.graphinfo.ClassGraphNode;
+import com.android.tools.r8.graphinfo.FieldGraphNode;
+import com.android.tools.r8.graphinfo.GraphConsumer;
+import com.android.tools.r8.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.graphinfo.GraphEdgeInfo.EdgeKind;
+import com.android.tools.r8.graphinfo.GraphNode;
+import com.android.tools.r8.graphinfo.KeepRuleGraphNode;
+import com.android.tools.r8.graphinfo.MethodGraphNode;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Invoke.Type;
@@ -66,7 +75,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
@@ -79,6 +87,7 @@
import java.util.SortedSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -121,6 +130,14 @@
private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
+ // Canonicalization of external graph-nodes and edge info.
+ private final Map<DexItem, AnnotationGraphNode> annotationNodes = new IdentityHashMap<>();
+ private final Map<DexType, ClassGraphNode> classNodes = new IdentityHashMap<>();
+ private final Map<DexMethod, MethodGraphNode> methodNodes = new IdentityHashMap<>();
+ private final Map<DexField, FieldGraphNode> fieldNodes = new IdentityHashMap<>();
+ private final Map<ProguardKeepRule, KeepRuleGraphNode> ruleNodes = new IdentityHashMap<>();
+ private final Map<EdgeKind, GraphEdgeInfo> reasonInfo = new IdentityHashMap<>();
+
/**
* Set of method signatures used in invoke-super instructions that either cannot be resolved or
* resolve to a private method (leading to an IllegalAccessError).
@@ -154,16 +171,15 @@
* Set of annotation types that are instantiated.
*/
private final Set<DexType> instantiatedAnnotations = Sets.newIdentityHashSet();
- /**
- * Set of types that are actually instantiated. These cannot be abstract.
- */
- private final SetWithReason<DexType> instantiatedTypes = new SetWithReason<>();
+ /** Set of types that are actually instantiated. These cannot be abstract. */
+ private final SetWithReason<DexType> instantiatedTypes = new SetWithReason<>(this::registerType);
/**
* Set of methods that are the immediate target of an invoke. They might not actually be live but
* are required so that invokes can find the method. If a method is only a target but not live,
* its implementation may be removed and it may be marked abstract.
*/
- private final SetWithReason<DexEncodedMethod> targetedMethods = new SetWithReason<>();
+ private final SetWithReason<DexEncodedMethod> targetedMethods =
+ new SetWithReason<>(this::registerMethod);
/**
* Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
*/
@@ -180,19 +196,21 @@
* Set of methods that belong to live classes and can be reached by invokes. These need to be
* kept.
*/
- private final SetWithReason<DexEncodedMethod> liveMethods = new SetWithReason<>();
+ private final SetWithReason<DexEncodedMethod> liveMethods =
+ new SetWithReason<>(this::registerMethod);
/**
- * Set of fields that belong to live classes and can be reached by invokes. These need to be
- * kept.
+ * Set of fields that belong to live classes and can be reached by invokes. These need to be kept.
*/
- private final SetWithReason<DexEncodedField> liveFields = new SetWithReason<>();
+ private final SetWithReason<DexEncodedField> liveFields =
+ new SetWithReason<>(this::registerField);
/**
* Set of interface types for which a lambda expression can be reached. These never have a single
* interface implementation.
*/
- private final SetWithReason<DexType> instantiatedLambdas = new SetWithReason<>();
+ private final SetWithReason<DexType> instantiatedLambdas =
+ new SetWithReason<>(this::registerType);
/**
* A queue of items that need processing. Different items trigger different actions:
@@ -236,33 +254,42 @@
*/
private final ProguardConfiguration.Builder compatibility;
- public Enqueuer(AppView<? extends AppInfoWithSubtyping> appView, InternalOptions options) {
- this(appView, options, options.forceProguardCompatibility, null);
+ private final GraphConsumer keptGraphConsumer;
+
+ public Enqueuer(
+ AppView<? extends AppInfoWithSubtyping> appView,
+ InternalOptions options,
+ GraphConsumer keptGraphConsumer) {
+ this(appView, options, keptGraphConsumer, options.forceProguardCompatibility, null);
}
public Enqueuer(
AppView<? extends AppInfoWithSubtyping> appView,
InternalOptions options,
+ GraphConsumer keptGraphConsumer,
ProguardConfiguration.Builder compatibility) {
- this(appView, options, options.forceProguardCompatibility, compatibility);
+ this(appView, options, keptGraphConsumer, options.forceProguardCompatibility, compatibility);
}
public Enqueuer(
AppView<? extends AppInfoWithSubtyping> appView,
InternalOptions options,
+ GraphConsumer keptGraphConsumer,
boolean forceProguardCompatibility) {
- this(appView, options, forceProguardCompatibility, null);
+ this(appView, options, keptGraphConsumer, forceProguardCompatibility, null);
}
public Enqueuer(
AppView<? extends AppInfoWithSubtyping> appView,
InternalOptions options,
+ GraphConsumer keptGraphConsumer,
boolean forceProguardCompatibility,
ProguardConfiguration.Builder compatibility) {
this.appInfo = appView.appInfo();
this.appView = appView;
this.compatibility = compatibility;
this.forceProguardCompatibility = forceProguardCompatibility;
+ this.keptGraphConsumer = keptGraphConsumer;
this.options = options;
}
@@ -849,9 +876,13 @@
DexClass holder = appInfo.definitionFor(type);
if (holder != null && !holder.isLibraryClass()) {
if (!dontWarnPatterns.matches(context)) {
- Diagnostic message = new StringDiagnostic("Library class " + context.toSourceString()
- + (holder.isInterface() ? " implements " : " extends ")
- + "program class " + type.toSourceString());
+ Diagnostic message =
+ new StringDiagnostic(
+ "Library class "
+ + context.toSourceString()
+ + (holder.isInterface() ? " implements " : " extends ")
+ + "program class "
+ + type.toSourceString());
if (forceProguardCompatibility) {
options.reporter.warning(message);
} else {
@@ -1166,10 +1197,12 @@
if (encodedField.accessFlags.isStatic()) {
markStaticFieldAsLive(encodedField.field, reason);
} else {
- SetWithReason<DexEncodedField> reachable = reachableInstanceFields
- .computeIfAbsent(encodedField.field.clazz, ignore -> new SetWithReason<>());
- if (reachable.add(encodedField, reason) && isInstantiatedOrHasInstantiatedSubtype(
- encodedField.field.clazz)) {
+ SetWithReason<DexEncodedField> reachable =
+ reachableInstanceFields.computeIfAbsent(
+ encodedField.field.clazz, ignore -> new SetWithReason<>((f, r) -> {}));
+ // TODO(b/120959039): The reachable.add test might be hiding other paths to the field.
+ if (reachable.add(encodedField, reason)
+ && isInstantiatedOrHasInstantiatedSubtype(encodedField.field.clazz)) {
// We have at least one live subtype, so mark it as live.
markInstanceFieldAsLive(encodedField, reason);
}
@@ -1212,8 +1245,10 @@
? appInfo.lookupInterfaceTargets(method)
: appInfo.lookupVirtualTargets(method);
for (DexEncodedMethod encodedMethod : targets) {
- SetWithReason<DexEncodedMethod> reachable = reachableVirtualMethods
- .computeIfAbsent(encodedMethod.method.holder, (ignore) -> new SetWithReason<>());
+ // TODO(b/120959039): The reachable.add test might be hiding other paths to the method.
+ SetWithReason<DexEncodedMethod> reachable =
+ reachableVirtualMethods.computeIfAbsent(
+ encodedMethod.method.holder, (ignore) -> new SetWithReason<>((m, r) -> {}));
if (reachable.add(encodedMethod, reason)) {
// Abstract methods cannot be live.
if (!encodedMethod.accessFlags.isAbstract()) {
@@ -1323,23 +1358,6 @@
}
}
- public ReasonPrinter getReasonPrinter(Set<DexDefinition> queriedItems) {
- // If no reason was asked, just return a no-op printer to avoid computing the information.
- // This is the common path.
- if (queriedItems.isEmpty()) {
- return ReasonPrinter.getNoOpPrinter();
- }
- Map<DexDefinition, KeepReason> reachability = new HashMap<>();
- for (SetWithReason<DexEncodedMethod> mappings : reachableVirtualMethods.values()) {
- reachability.putAll(mappings.getReasons());
- }
- for (SetWithReason<DexEncodedField> mappings : reachableInstanceFields.values()) {
- reachability.putAll(mappings.getReasons());
- }
- return new ReasonPrinter(queriedItems, liveFields.getReasons(), liveMethods.getReasons(),
- reachability, instantiatedTypes.getReasons());
- }
-
public AppInfoWithLiveness traceMainDex(
RootSet rootSet, ExecutorService executorService, Timing timing) throws ExecutionException {
this.tracingMainDex = true;
@@ -1355,7 +1373,8 @@
RootSet rootSet,
ProguardClassFilter dontWarnPatterns,
ExecutorService executorService,
- Timing timing) throws ExecutionException {
+ Timing timing)
+ throws ExecutionException {
this.rootSet = rootSet;
this.dontWarnPatterns = dontWarnPatterns;
// Translate the result of root-set computation into enqueuer actions.
@@ -2563,14 +2582,16 @@
private static class SetWithReason<T> {
private final Set<T> items = Sets.newIdentityHashSet();
- private final Map<T, KeepReason> reasons = Maps.newIdentityHashMap();
+
+ private final BiConsumer<T, KeepReason> register;
+
+ public SetWithReason(BiConsumer<T, KeepReason> register) {
+ this.register = register;
+ }
boolean add(T item, KeepReason reason) {
- if (items.add(item)) {
- reasons.put(item, reason);
- return true;
- }
- return false;
+ register.accept(item, reason);
+ return items.add(item);
}
boolean contains(T item) {
@@ -2580,10 +2601,6 @@
Set<T> getItems() {
return ImmutableSet.copyOf(items);
}
-
- Map<T, KeepReason> getReasons() {
- return ImmutableMap.copyOf(reasons);
- }
}
private static final class TargetWithContext<T extends Descriptor<?, T>> {
@@ -2709,4 +2726,78 @@
return false;
}
}
+
+ private void registerType(DexType type, KeepReason reason) {
+ assert getSourceNode(reason) != null;
+ if (keptGraphConsumer == null) {
+ return;
+ }
+ registerEdge(getClassGraphNode(type), reason);
+ }
+
+ private void registerMethod(DexEncodedMethod method, KeepReason reason) {
+ if (reason.edgeKind() == EdgeKind.IsLibraryMethod) {
+ // Don't report edges to actual library methods.
+ // TODO(b/120959039): Make sure we do have edges to methods overwriting library methods!
+ return;
+ }
+ assert getSourceNode(reason) != null;
+ if (keptGraphConsumer == null) {
+ return;
+ }
+ registerEdge(getMethodGraphNode(method.method), reason);
+ }
+
+ private void registerField(DexEncodedField field, KeepReason reason) {
+ assert getSourceNode(reason) != null;
+ if (keptGraphConsumer == null) {
+ return;
+ }
+ registerEdge(getFieldGraphNode(field.field), reason);
+ }
+
+ private void registerEdge(GraphNode target, KeepReason reason) {
+ keptGraphConsumer.acceptEdge(getSourceNode(reason), target, getEdgeInfo(reason));
+ }
+
+ private GraphNode getSourceNode(KeepReason reason) {
+ return reason.getSourceNode(this);
+ }
+
+ public GraphNode getGraphNode(DexDefinition item) {
+ if (item instanceof DexClass) {
+ return getClassGraphNode(((DexClass) item).type);
+ }
+ if (item instanceof DexEncodedMethod) {
+ return getMethodGraphNode(((DexEncodedMethod) item).method);
+ }
+ if (item instanceof DexEncodedField) {
+ return getFieldGraphNode(((DexEncodedField) item).field);
+ }
+ throw new Unreachable();
+ }
+
+ GraphEdgeInfo getEdgeInfo(KeepReason reason) {
+ return reasonInfo.computeIfAbsent(reason.edgeKind(), k -> new GraphEdgeInfo(k));
+ }
+
+ AnnotationGraphNode getAnnotationGraphNode(DexItem type) {
+ return annotationNodes.computeIfAbsent(type, AnnotationGraphNode::new);
+ }
+
+ ClassGraphNode getClassGraphNode(DexType type) {
+ return classNodes.computeIfAbsent(type, ClassGraphNode::new);
+ }
+
+ MethodGraphNode getMethodGraphNode(DexMethod context) {
+ return methodNodes.computeIfAbsent(context, MethodGraphNode::new);
+ }
+
+ FieldGraphNode getFieldGraphNode(DexField context) {
+ return fieldNodes.computeIfAbsent(context, FieldGraphNode::new);
+ }
+
+ KeepRuleGraphNode getKeepRuleGraphNode(ProguardKeepRule rule) {
+ return ruleNodes.computeIfAbsent(rule, KeepRuleGraphNode::new);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index a16c84a..82737a6 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -6,10 +6,16 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.shaking.ReasonPrinter.ReasonFormatter;
+import com.android.tools.r8.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.graphinfo.GraphEdgeInfo.EdgeKind;
+import com.android.tools.r8.graphinfo.GraphNode;
// TODO(herhut): Canonicalize reason objects.
-abstract class KeepReason {
+public abstract class KeepReason {
+
+ public abstract GraphEdgeInfo.EdgeKind edgeKind();
+
+ public abstract GraphNode getSourceNode(Enqueuer enqueuer);
static KeepReason dueToKeepRule(ProguardKeepRule rule) {
return new DueToKeepRule(rule);
@@ -51,8 +57,6 @@
return new ReferencedInAnnotation(holder);
}
- public abstract void print(ReasonFormatter formatter);
-
public boolean isDueToKeepRule() {
return false;
}
@@ -78,6 +82,11 @@
}
@Override
+ public EdgeKind edgeKind() {
+ return EdgeKind.KeepRule;
+ }
+
+ @Override
public boolean isDueToKeepRule() {
return true;
}
@@ -88,18 +97,8 @@
}
@Override
- public void print(ReasonFormatter formatter) {
- formatter.addReason("referenced in keep rule:");
- formatter.addMessage(" " + keepRule.toShortString() + " {");
- int ruleCount = 0;
- for (ProguardMemberRule memberRule : keepRule.getMemberRules()) {
- formatter.addMessage(" " + memberRule + ";");
- if (++ruleCount > 10) {
- formatter.addMessage(" <...>");
- break;
- }
- }
- formatter.addMessage(" };");
+ public GraphNode getSourceNode(Enqueuer enqueuer) {
+ return enqueuer.getKeepRuleGraphNode(keepRule);
}
}
@@ -109,6 +108,11 @@
}
@Override
+ public EdgeKind edgeKind() {
+ return EdgeKind.CompatibilityRule;
+ }
+
+ @Override
public boolean isDueToProguardCompatibility() {
return true;
}
@@ -125,11 +129,9 @@
abstract String getKind();
@Override
- public void print(ReasonFormatter formatter) {
- formatter.addReason("is " + getKind() + " " + method.toSourceString());
- formatter.addMethodReferenceReason(method);
+ public GraphNode getSourceNode(Enqueuer enqueuer) {
+ return enqueuer.getMethodGraphNode(method.method);
}
-
}
private static class InstatiatedIn extends BasedOnOtherMethod {
@@ -139,6 +141,11 @@
}
@Override
+ public EdgeKind edgeKind() {
+ return EdgeKind.InstantiatedIn;
+ }
+
+ @Override
String getKind() {
return "instantiated in";
}
@@ -151,6 +158,11 @@
}
@Override
+ public EdgeKind edgeKind() {
+ return EdgeKind.InvokedViaSuper;
+ }
+
+ @Override
String getKind() {
return "invoked via super from";
}
@@ -163,6 +175,11 @@
}
@Override
+ public EdgeKind edgeKind() {
+ return EdgeKind.TargetedBySuper;
+ }
+
+ @Override
String getKind() {
return "targeted by super from";
}
@@ -175,6 +192,11 @@
}
@Override
+ public EdgeKind edgeKind() {
+ return EdgeKind.InvokedFrom;
+ }
+
+ @Override
String getKind() {
return "invoked from";
}
@@ -187,6 +209,11 @@
}
@Override
+ public EdgeKind edgeKind() {
+ return EdgeKind.InvokedFromLambdaCreatedIn;
+ }
+
+ @Override
String getKind() {
return "invoked from lambda created in";
}
@@ -199,6 +226,11 @@
}
@Override
+ public EdgeKind edgeKind() {
+ return EdgeKind.ReferencedFrom;
+ }
+
+ @Override
String getKind() {
return "referenced from";
}
@@ -213,9 +245,13 @@
}
@Override
- public void print(ReasonFormatter formatter) {
- formatter.addReason("is reachable from type " + type.toSourceString());
- formatter.addTypeLivenessReason(type);
+ public EdgeKind edgeKind() {
+ return EdgeKind.ReachableFromLiveType;
+ }
+
+ @Override
+ public GraphNode getSourceNode(Enqueuer enqueuer) {
+ return enqueuer.getClassGraphNode(type);
}
}
@@ -225,8 +261,13 @@
}
@Override
- public void print(ReasonFormatter formatter) {
- formatter.addReason("is defined in a library.");
+ public EdgeKind edgeKind() {
+ return EdgeKind.IsLibraryMethod;
+ }
+
+ @Override
+ public GraphNode getSourceNode(Enqueuer enqueuer) {
+ return null;
}
}
@@ -239,8 +280,13 @@
}
@Override
- public void print(ReasonFormatter formatter) {
- formatter.addReason("is referenced in annotation on " + holder.toSourceString());
+ public EdgeKind edgeKind() {
+ return EdgeKind.ReferencedInAnnotation;
+ }
+
+ @Override
+ public GraphNode getSourceNode(Enqueuer enqueuer) {
+ return enqueuer.getAnnotationGraphNode(holder);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ReasonPrinter.java b/src/main/java/com/android/tools/r8/shaking/ReasonPrinter.java
deleted file mode 100644
index 1eb43f5..0000000
--- a/src/main/java/com/android/tools/r8/shaking/ReasonPrinter.java
+++ /dev/null
@@ -1,243 +0,0 @@
-// Copyright (c) 2016, 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.shaking;
-
-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.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItem;
-import com.android.tools.r8.graph.DexType;
-import com.google.common.collect.Sets;
-import java.io.PrintStream;
-import java.util.ArrayDeque;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.Map;
-import java.util.Set;
-
-public class ReasonPrinter {
-
- private final Set<DexDefinition> itemsQueried;
- private ReasonFormatter formatter;
-
- private final Map<DexEncodedField, KeepReason> liveFields;
- private final Map<DexEncodedMethod, KeepReason> liveMethods;
- private final Map<DexDefinition, KeepReason> reachablityReasons;
- private final Map<DexType, KeepReason> instantiatedTypes;
-
- ReasonPrinter(Set<DexDefinition> itemsQueried, Map<DexEncodedField, KeepReason> liveFields,
- Map<DexEncodedMethod, KeepReason> liveMethods, Map<DexDefinition, KeepReason> reachablityReasons,
- Map<DexType, KeepReason> instantiatedTypes) {
- this.itemsQueried = itemsQueried;
- this.liveFields = liveFields;
- this.liveMethods = liveMethods;
- this.reachablityReasons = reachablityReasons;
- this.instantiatedTypes = instantiatedTypes;
- }
-
- public void run(DexApplication application) {
- // TODO(herhut): Instead of traversing the app, sort the queried items.
- formatter = new ReasonFormatter();
- for (DexClass clazz : application.classes()) {
- if (itemsQueried.contains(clazz)) {
- printReasonFor(clazz);
- }
- Arrays.stream(clazz.staticFields()).filter(itemsQueried::contains)
- .forEach(this::printReasonFor);
- Arrays.stream(clazz.instanceFields()).filter(itemsQueried::contains)
- .forEach(this::printReasonFor);
- Arrays.stream(clazz.directMethods()).filter(itemsQueried::contains)
- .forEach(this::printReasonFor);
- Arrays.stream(clazz.virtualMethods()).filter(itemsQueried::contains)
- .forEach(this::printReasonFor);
- }
- }
-
- private void printNoIdeaWhy(DexItem item, ReasonFormatter formatter) {
- formatter.startItem(item);
- formatter.pushEmptyPrefix();
- formatter.addReason("is kept for unknown reason.");
- formatter.popPrefix();
- formatter.endItem();
- }
-
- private void printOnlyAbstractShell(DexItem item, ReasonFormatter formatter) {
- formatter.startItem(item);
- KeepReason reachableReason = reachablityReasons.get(item);
- if (reachableReason != null) {
- formatter.pushPrefix(
- "is not kept, only its abstract declaration is needed because it ");
- reachableReason.print(formatter);
- formatter.popPrefix();
- } else {
- formatter.pushEmptyPrefix();
- formatter.addReason("is not kept, only its abstract declaration is.");
- formatter.popPrefix();
- }
- formatter.endItem();
- }
-
- private void printReasonFor(DexClass item) {
- KeepReason reason = instantiatedTypes.get(item.type);
- if (reason == null) {
- if (item.accessFlags.isAbstract()) {
- printOnlyAbstractShell(item, formatter);
- } else {
- printNoIdeaWhy(item, formatter);
- }
- } else {
- formatter.startItem(item);
- formatter.pushIsLivePrefix();
- reason.print(formatter);
- formatter.popPrefix();
- formatter.endItem();
- }
- }
-
- private void printReasonFor(DexEncodedMethod item) {
- KeepReason reasonLive = liveMethods.get(item);
- if (reasonLive == null) {
- if (item.accessFlags.isAbstract()) {
- printOnlyAbstractShell(item, formatter);
- } else {
- printNoIdeaWhy(item.method, formatter);
- }
- } else {
- formatter.addMethodReferenceReason(item);
- }
- }
-
- private void printReasonFor(DexEncodedField item) {
- KeepReason reason = liveFields.get(item);
- if (reason == null) {
- printNoIdeaWhy(item.field, formatter);
- } else {
- formatter.startItem(item.field);
- formatter.pushIsLivePrefix();
- reason.print(formatter);
- formatter.popPrefix();
- reason = reachablityReasons.get(item);
- if (reason != null) {
- formatter.pushIsReachablePrefix();
- reason.print(formatter);
- formatter.popPrefix();
- }
- formatter.endItem();
- }
- }
-
- class ReasonFormatter {
-
- private final Set<DexItem> seen = Sets.newIdentityHashSet();
- private final Deque<String> prefixes = new ArrayDeque<>();
-
- private int indentation = -1;
-
- private PrintStream output = System.out;
-
- void pushIsLivePrefix() {
- prefixes.push("is live because ");
- }
-
- void pushIsReachablePrefix() {
- prefixes.push("is reachable because ");
- }
-
- void pushPrefix(String prefix) {
- prefixes.push(prefix);
- }
-
- void pushEmptyPrefix() {
- prefixes.push("");
- }
-
- void popPrefix() {
- prefixes.pop();
- }
-
- void startItem(DexItem item) {
- indentation++;
- indent();
- output.println(item.toSourceString());
- }
-
- private void indent() {
- for (int i = 0; i < indentation; i++) {
- output.print(" ");
- }
- }
-
- void addReason(String thing) {
- indent();
- output.print("|- ");
- String prefix = prefixes.peek();
- output.print(prefix);
- output.println(thing);
- }
-
- void addMessage(String thing) {
- indent();
- output.print("| ");
- output.println(thing);
- }
-
- void endItem() {
- indentation--;
- }
-
- void addMethodReferenceReason(DexEncodedMethod method) {
- if (!seen.add(method.method)) {
- return;
- }
- startItem(method);
- KeepReason reason = reachablityReasons.get(method);
- if (reason != null) {
- pushIsReachablePrefix();
- reason.print(this);
- popPrefix();
- }
- reason = liveMethods.get(method);
- if (reason != null) {
- pushIsLivePrefix();
- reason.print(this);
- popPrefix();
- }
- endItem();
- }
-
- void addTypeLivenessReason(DexType type) {
- if (!seen.add(type)) {
- return;
- }
- startItem(type);
- pushIsLivePrefix();
- KeepReason reason = instantiatedTypes.get(type);
- if (reason != null) {
- reason.print(this);
- }
- popPrefix();
- endItem();
- }
- }
-
- public static ReasonPrinter getNoOpPrinter() {
- return new NoOpReasonPrinter();
- }
-
- private static class NoOpReasonPrinter extends ReasonPrinter {
-
- NoOpReasonPrinter() {
- super(Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(),
- Collections.emptyMap(), Collections.emptyMap());
- }
-
- @Override
- public void run(DexApplication application) {
- // Intentionally left empty.
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
new file mode 100644
index 0000000..de43dc6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
@@ -0,0 +1,285 @@
+// 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.shaking;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graphinfo.ClassGraphNode;
+import com.android.tools.r8.graphinfo.FieldGraphNode;
+import com.android.tools.r8.graphinfo.GraphConsumer;
+import com.android.tools.r8.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.graphinfo.GraphEdgeInfo.EdgeKind;
+import com.android.tools.r8.graphinfo.GraphNode;
+import com.android.tools.r8.graphinfo.KeepRuleGraphNode;
+import com.android.tools.r8.graphinfo.MethodGraphNode;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.position.TextRange;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class WhyAreYouKeepingConsumer implements GraphConsumer {
+
+ // Single-linked path description when BF searching for a path.
+ private static class GraphPath {
+ final GraphNode node;
+ final GraphPath path;
+
+ public GraphPath(GraphNode node, GraphPath path) {
+ assert node != null;
+ this.node = node;
+ this.path = path;
+ }
+ }
+
+ // Possible sub-consumer that is also inspecting the kept-graph.
+ private final GraphConsumer subConsumer;
+
+ // Directional map backwards from targets to direct sources.
+ private final Map<GraphNode, Map<GraphNode, Set<GraphEdgeInfo>>> target2sources =
+ new IdentityHashMap<>();
+
+ public WhyAreYouKeepingConsumer(GraphConsumer subConsumer) {
+ this.subConsumer = subConsumer;
+ }
+
+ @Override
+ public void acceptEdge(GraphNode source, GraphNode target, GraphEdgeInfo info) {
+ target2sources
+ .computeIfAbsent(target, k -> new IdentityHashMap<>())
+ .computeIfAbsent(source, k -> new HashSet<>())
+ .add(info);
+ if (subConsumer != null) {
+ subConsumer.acceptEdge(source, target, info);
+ }
+ }
+
+ /** Print the shortest path from a root to a node in the graph. */
+ public void printWhyAreYouKeeping(String descriptor, PrintStream out) {
+ assert DescriptorUtils.isClassDescriptor(descriptor);
+ for (GraphNode node : target2sources.keySet()) {
+ if (node.identity().equals(descriptor)) {
+ printWhyAreYouKeeping(node, out);
+ return;
+ }
+ }
+ printNothingKeeping(descriptor, out);
+ }
+
+ public void printWhyAreYouKeeping(GraphNode node, PrintStream out) {
+ Formatter formatter = new Formatter(out);
+ List<Pair<GraphNode, GraphEdgeInfo>> path = findShortestPathTo(node);
+ if (path == null) {
+ printNothingKeeping(node, out);
+ return;
+ }
+ formatter.startItem(getNodeString(node));
+ for (int i = path.size() - 1; i >= 0; i--) {
+ Pair<GraphNode, GraphEdgeInfo> edge = path.get(i);
+ printEdge(edge.getFirst(), edge.getSecond(), formatter);
+ }
+ formatter.endItem();
+ }
+
+ private void printNothingKeeping(GraphNode node, PrintStream out) {
+ out.print("Nothing is keeping ");
+ out.println(getNodeString(node));
+ }
+
+ private void printNothingKeeping(String descriptor, PrintStream out) {
+ out.print("Nothing is keeping ");
+ out.println(DescriptorUtils.descriptorToJavaType(descriptor));
+ }
+
+ private List<Pair<GraphNode, GraphEdgeInfo>> findShortestPathTo(final GraphNode node) {
+ Deque<GraphPath> queue;
+ {
+ Map<GraphNode, Set<GraphEdgeInfo>> sources = target2sources.get(node);
+ if (sources == null) {
+ // The node is not targeted at all (it is not reachable).
+ return null;
+ }
+ queue = new LinkedList<>();
+ for (GraphNode source : sources.keySet()) {
+ queue.addLast(new GraphPath(source, null));
+ }
+ }
+ Map<GraphNode, GraphNode> seen = new IdentityHashMap<>();
+ while (!queue.isEmpty()) {
+ GraphPath path = queue.removeFirst();
+ Map<GraphNode, Set<GraphEdgeInfo>> sources = target2sources.get(path.node);
+ if (sources == null) {
+ return getCanonicalPath(path, node);
+ }
+ for (GraphNode source : sources.keySet()) {
+ if (seen.containsKey(source)) {
+ continue;
+ }
+ seen.put(source, source);
+ queue.addLast(new GraphPath(source, path));
+ }
+ }
+ throw new Unreachable("Failed to find a root from node: " + node);
+ }
+
+ // Convert a internal path representation to the external API and compute the edge reasons.
+ private List<Pair<GraphNode, GraphEdgeInfo>> getCanonicalPath(
+ GraphPath path, GraphNode endTarget) {
+ assert path != null;
+ List<Pair<GraphNode, GraphEdgeInfo>> canonical = new ArrayList<>();
+ while (path.path != null) {
+ GraphNode source = path.node;
+ GraphNode target = path.path.node;
+ Set<GraphEdgeInfo> infos = target2sources.get(target).get(source);
+ canonical.add(new Pair<>(source, getCanonicalInfo(infos)));
+ path = path.path;
+ }
+ Set<GraphEdgeInfo> infos = target2sources.get(endTarget).get(path.node);
+ canonical.add(new Pair<>(path.node, getCanonicalInfo(infos)));
+ return canonical;
+ }
+
+ // Compute the most meaningful edge reason.
+ private GraphEdgeInfo getCanonicalInfo(Set<GraphEdgeInfo> infos) {
+ // TODO(b/120959039): this is pretty bad...
+ for (EdgeKind kind : EdgeKind.values()) {
+ for (GraphEdgeInfo info : infos) {
+ if (info.edgeKind() == kind) {
+ return info;
+ }
+ }
+ }
+ throw new Unreachable("Unexpected empty set of graph edge info");
+ }
+
+ private void printEdge(GraphNode node, GraphEdgeInfo info, Formatter formatter) {
+ formatter.addReason("is " + getInfoPrefix(info) + ":");
+ addNodeMessage(node, formatter);
+ }
+
+ private String getInfoPrefix(GraphEdgeInfo info) {
+ switch (info.edgeKind()) {
+ case KeepRule:
+ case CompatibilityRule:
+ return "referenced in keep rule";
+ case InstantiatedIn:
+ return "instantiated in";
+ case InvokedViaSuper:
+ return "invoked via super from";
+ case TargetedBySuper:
+ return "targeted by super from";
+ case InvokedFrom:
+ return "invoked from";
+ case InvokedFromLambdaCreatedIn:
+ return "invoked from lambda created in";
+ case ReferencedFrom:
+ return "referenced from";
+ case ReachableFromLiveType:
+ return "reachable from";
+ case ReferencedInAnnotation:
+ return "referenced in annotation";
+ case IsLibraryMethod:
+ return "defined in library";
+ default:
+ throw new Unreachable("Unexpected edge kind: " + info.edgeKind());
+ }
+ }
+
+ private String getNodeString(GraphNode node) {
+ if (node instanceof ClassGraphNode) {
+ return DescriptorUtils.descriptorToJavaType(((ClassGraphNode) node).getDescriptor());
+ }
+ if (node instanceof MethodGraphNode) {
+ MethodGraphNode methodNode = (MethodGraphNode) node;
+ MethodSignature signature =
+ MethodSignature.fromSignature(
+ methodNode.getMethodName(), methodNode.getMethodDescriptor());
+ return signature.type
+ + ' '
+ + DescriptorUtils.descriptorToJavaType(methodNode.getHolderDescriptor())
+ + '.'
+ + methodNode.getMethodName()
+ + StringUtils.join(Arrays.asList(signature.parameters), ",", BraceType.PARENS);
+ }
+ if (node instanceof FieldGraphNode) {
+ FieldGraphNode fieldNode = (FieldGraphNode) node;
+ return DescriptorUtils.descriptorToJavaType(fieldNode.getFieldDescriptor())
+ + ' '
+ + DescriptorUtils.descriptorToJavaType(fieldNode.getHolderDescriptor())
+ + '.'
+ + fieldNode.getFieldName();
+ }
+ if (node instanceof KeepRuleGraphNode) {
+ KeepRuleGraphNode keepRuleNode = (KeepRuleGraphNode) node;
+ return keepRuleNode.getOrigin() == Origin.unknown()
+ ? keepRuleNode.getContent()
+ : keepRuleNode.getOrigin() + ":" + shortPositionInfo(keepRuleNode.getPosition());
+ }
+ throw new Unreachable("Unexpected graph node type: " + node);
+ }
+
+ private void addNodeMessage(GraphNode node, Formatter formatter) {
+ for (String line : StringUtils.splitLines(getNodeString(node))) {
+ formatter.addMessage(line);
+ }
+ }
+
+ private static String shortPositionInfo(Position position) {
+ if (position instanceof TextRange) {
+ TextPosition start = ((TextRange) position).getStart();
+ return start.getLine() + ":" + start.getColumn();
+ }
+ return position.getDescription();
+ }
+
+ private static class Formatter {
+ private final PrintStream output;
+ private int indentation = -1;
+
+ public Formatter(PrintStream output) {
+ this.output = output;
+ }
+
+ void startItem(String itemString) {
+ indentation++;
+ indent();
+ output.println(itemString);
+ }
+
+ private void indent() {
+ for (int i = 0; i < indentation; i++) {
+ output.print(" ");
+ }
+ }
+
+ void addReason(String thing) {
+ indent();
+ output.print("|- ");
+ output.println(thing);
+ }
+
+ void addMessage(String thing) {
+ indent();
+ output.print("| ");
+ output.println(thing);
+ }
+
+ void endItem() {
+ indentation--;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index c8486dd..56dc071 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -164,7 +164,8 @@
* @return Java type name
*/
public static String descriptorToJavaType(String descriptor, ClassNameMapper classNameMapper) {
- switch (descriptor.charAt(0)) {
+ char c = descriptor.charAt(0);
+ switch (c) {
case 'L':
assert descriptor.charAt(descriptor.length() - 1) == ';';
String clazz = descriptor.substring(1, descriptor.length() - 1)
@@ -175,6 +176,13 @@
case '[':
return descriptorToJavaType(descriptor.substring(1, descriptor.length()), classNameMapper)
+ "[]";
+ default:
+ return primitiveDescriptorToJavaType(c);
+ }
+ }
+
+ public static String primitiveDescriptorToJavaType(char primitive) {
+ switch (primitive) {
case 'V':
return "void";
case 'Z':
@@ -194,7 +202,7 @@
case 'D':
return "double";
default:
- throw new Unreachable("Unknown type " + descriptor);
+ throw new Unreachable("Unknown type " + primitive);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 065ece4..d9d23b0 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graphinfo.GraphConsumer;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -338,6 +339,16 @@
// If non-null, configuration must be passed to the consumer.
public StringConsumer configurationConsumer = null;
+ // If null, no graph information needs to be provided for the keep/inclusion of classes
+ // in the output. If non-null, each edge pertaining to kept parts of the resulting program
+ // must be reported to the consumer.
+ public GraphConsumer keptGraphConsumer = null;
+
+ // If null, no graph information needs to be provided for the keep/inclusion of classes
+ // in the main-dex output. If non-null, each edge pertaining to kept parts in the main-dex output
+ // of the resulting program must be reported to the consumer.
+ public GraphConsumer mainDexKeptGraphConsumer = null;
+
public Path proguardCompatibilityRulesOutput = null;
public static boolean assertionsEnabled() {
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 07eea98..8c4f682 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.TestBase.R8Mode;
+import com.android.tools.r8.graphinfo.GraphConsumer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
@@ -131,4 +132,14 @@
builder.allowTestProguardOptions();
return self();
}
+
+ public R8TestBuilder setKeptGraphConsumer(GraphConsumer graphConsumer) {
+ builder.setKeptGraphConsumer(graphConsumer);
+ return self();
+ }
+
+ public R8TestBuilder setMainDexKeptGraphConsumer(GraphConsumer graphConsumer) {
+ builder.setMainDexKeptGraphConsumer(graphConsumer);
+ return self();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 9425df9..e9ebd4e 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.base.Suppliers;
import java.io.IOException;
+import java.io.PrintStream;
import java.nio.file.Path;
import java.util.Collection;
import java.util.function.Consumer;
@@ -40,6 +41,7 @@
private StringConsumer mainDexListConsumer;
private AndroidApiLevel defaultMinApiLevel = ToolHelper.getMinApiLevelForDexVm();
private Consumer<InternalOptions> optionsConsumer = DEFAULT_OPTIONS;
+ private PrintStream stdout = null;
TestCompilerBuilder(TestState state, B builder, Backend backend) {
super(state);
@@ -70,7 +72,17 @@
if (backend == Backend.DEX && defaultMinApiLevel != null) {
builder.setMinApiLevel(defaultMinApiLevel.getLevel());
}
- return internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build));
+ PrintStream oldOut = System.out;
+ try {
+ if (stdout != null) {
+ System.setOut(stdout);
+ }
+ return internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build));
+ } finally {
+ if (stdout != null) {
+ System.setOut(oldOut);
+ }
+ }
}
@Override
@@ -151,4 +163,10 @@
builder.setDisableDesugaring(true);
return self();
}
+
+ public T redirectStdOut(PrintStream printStream) {
+ assert stdout == null;
+ stdout = printStream;
+ return self();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 1691232..ae7cb4f 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -49,6 +49,13 @@
return self();
}
+ public T addKeepClassAndMembersRules(Class<?>... classes) {
+ for (Class<?> clazz : classes) {
+ addKeepRules("-keep class " + clazz.getTypeName() + " { *; }");
+ }
+ return self();
+ }
+
public T addKeepPackageRules(Package pkg) {
return addKeepRules("-keep class " + pkg.getName() + ".*");
}
@@ -64,5 +71,4 @@
" public static void main(java.lang.String[]);",
"}"));
}
-
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index c46cef0..eea2ec6 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -112,7 +112,8 @@
private static final String RETRACE = RETRACE6_0_1;
public static final Path R8_JAR = Paths.get(LIBS_DIR, "r8.jar");
- public static final Path R8_LIB_JAR = Paths.get(LIBS_DIR, "r8lib_with_deps.jar");
+ public static final Path R8_WITH_RELOCATED_DEPS_JAR =
+ Paths.get(LIBS_DIR, "r8_with_relocated_deps.jar");
public enum DexVm {
ART_4_0_4_TARGET(Version.V4_0_4, Kind.TARGET),
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationProguardCompatTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationProguardCompatTest.java
new file mode 100644
index 0000000..f6b7b8f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationProguardCompatTest.java
@@ -0,0 +1,83 @@
+// 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.accessrelaxation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPrivate;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import org.junit.Test;
+
+/**
+ * Tests that both R8 and Proguard may change the visibility of a field or method that is explicitly
+ * kept.
+ */
+public class AccessRelaxationProguardCompatTest extends TestBase {
+
+ private static Class<?> clazz = AccessRelaxationProguardCompatTestClass.class;
+ private static Class<?> clazzWithGetter = TestClassWithGetter.class;
+
+ @Test
+ public void r8Test() throws Exception {
+ testForR8(Backend.DEX)
+ .addProgramClasses(clazz, clazzWithGetter)
+ .addKeepMainRule(clazz)
+ .addKeepRules(
+ "-allowaccessmodification",
+ "-keep class " + TestClassWithGetter.class.getTypeName() + " {",
+ " private int field;",
+ "}")
+ .compile()
+ .inspect(AccessRelaxationProguardCompatTest::inspect);
+ }
+
+ @Test
+ public void proguardTest() throws Exception {
+ testForProguard()
+ .addProgramClasses(clazz, clazzWithGetter)
+ .addKeepMainRule(clazz)
+ .addKeepRules(
+ "-allowaccessmodification",
+ "-keep class " + TestClassWithGetter.class.getTypeName() + " {",
+ " private int field;",
+ "}")
+ .compile()
+ .inspect(AccessRelaxationProguardCompatTest::inspect);
+ }
+
+ private static void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(clazzWithGetter);
+ assertThat(classSubject, isPresent());
+
+ FieldSubject fieldSubject = classSubject.uniqueFieldWithName("field");
+ assertThat(fieldSubject, isPresent());
+
+ // Although this field was explicitly kept, it is no longer private.
+ assertThat(fieldSubject, not(isPrivate()));
+ }
+}
+
+class AccessRelaxationProguardCompatTestClass {
+
+ public static void main(String[] args) {
+ TestClassWithGetter obj = new TestClassWithGetter();
+ System.out.println(obj.get());
+ }
+}
+
+class TestClassWithGetter {
+
+ private int field = 0;
+
+ public int get() {
+ System.out.println("In method()");
+ return field;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
index 2cca0f4..a0cd03d 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
@@ -84,8 +84,8 @@
if (testExternal) {
R8Result output =
runExternalR8(
- ToolHelper.R8_LIB_JAR,
- ToolHelper.R8_LIB_JAR,
+ ToolHelper.R8_WITH_RELOCATED_DEPS_JAR,
+ ToolHelper.R8_WITH_RELOCATED_DEPS_JAR,
testFolder.newFolder().toPath(),
MAIN_KEEP,
mode);
@@ -96,7 +96,7 @@
R8Command.builder()
.setMode(CompilationMode.RELEASE)
.addLibraryFiles(runtimeJar(Backend.CF))
- .addProgramFiles(ToolHelper.R8_LIB_JAR)
+ .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
.setOutput(jar, OutputMode.ClassFile)
.build());
}
@@ -125,10 +125,20 @@
private void compareR8(Path program, ProcessResult runResult, String[] keep, String... args)
throws Exception {
R8Result runR8Debug =
- runExternalR8(ToolHelper.R8_LIB_JAR, program, temp.newFolder().toPath(), keep, "--debug");
+ runExternalR8(
+ ToolHelper.R8_WITH_RELOCATED_DEPS_JAR,
+ program,
+ temp.newFolder().toPath(),
+ keep,
+ "--debug");
assertEquals(runResult.toString(), ToolHelper.runJava(runR8Debug.outputJar, args).toString());
R8Result runR8Release =
- runExternalR8(ToolHelper.R8_LIB_JAR, program, temp.newFolder().toPath(), keep, "--release");
+ runExternalR8(
+ ToolHelper.R8_WITH_RELOCATED_DEPS_JAR,
+ program,
+ temp.newFolder().toPath(),
+ keep,
+ "--release");
assertEquals(runResult.toString(), ToolHelper.runJava(runR8Release.outputJar, args).toString());
RunR8AndCheck(r8R8Debug, program, runR8Debug, keep, "--debug");
RunR8AndCheck(r8R8Debug, program, runR8Release, keep, "--release");
diff --git a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
index 66b3b21..a33a5ad 100644
--- a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
+++ b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
@@ -9,6 +9,7 @@
import static org.junit.Assert.fail;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestCompileResult;
import com.android.tools.r8.jasmin.JasminBuilder;
@@ -24,12 +25,12 @@
@BeforeClass
public static void setUp() throws Exception {
JasminBuilder builder = new JasminBuilder();
- JasminBuilder.ClassBuilder clazz = builder.addClass("junit.framework.TestCase");
+ builder.addClass("junit.framework.TestCase");
junitClasses = builder.buildClasses();
}
@Test
- public void testFullModeError() throws Exception {
+ public void testFullModeError() {
try {
testForR8(Backend.DEX)
.setMinApi(AndroidApiLevel.O)
@@ -44,7 +45,7 @@
@Test
public void testCompatibilityModeWarning() throws Exception {
- TestCompileResult result = testForR8Compat(Backend.DEX)
+ TestCompileResult<R8TestRunResult> result = testForR8Compat(Backend.DEX)
.setMinApi(AndroidApiLevel.O)
.addProgramClassFileData(junitClasses)
.addKeepAllClassesRule()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
index 27ceebe..e681c77 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
@@ -36,7 +36,7 @@
appView, dexApplication, TEST_OPTIONS.proguardConfiguration.getRules(), TEST_OPTIONS)
.run(executorService);
Enqueuer enqueuer =
- new Enqueuer(appView, TEST_OPTIONS, TEST_OPTIONS.forceProguardCompatibility);
+ new Enqueuer(appView, TEST_OPTIONS, null, TEST_OPTIONS.forceProguardCompatibility);
return enqueuer.traceApplication(
rootSet, TEST_OPTIONS.proguardConfiguration.getDontWarnPatterns(), executorService, timing);
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithInheritanceTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithInheritanceTest.java
new file mode 100644
index 0000000..c3deab3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithInheritanceTest.java
@@ -0,0 +1,97 @@
+// 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.ir.optimize.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Regression test for b/120182628. */
+@RunWith(Parameterized.class)
+public class BuilderWithInheritanceTest extends TestBase {
+
+ private final Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public BuilderWithInheritanceTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ CodeInspector inspector =
+ testForR8(backend)
+ .addInnerClasses(BuilderWithInheritanceTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .run(TestClass.class)
+ .assertSuccessWithOutput("42")
+ .inspector();
+ assertThat(inspector.clazz(Builder.class), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ A a = new Builder(42).build();
+ System.out.print(a.get());
+ }
+ }
+
+ static class A {
+
+ private final int f;
+
+ public A(int f) {
+ this.f = f;
+ }
+
+ public int get() {
+ return f;
+ }
+ }
+
+ @NeverMerge
+ static class BuilderBase {
+
+ protected int f;
+
+ public BuilderBase(int f) {
+ this.f = f;
+ }
+
+ @NeverInline
+ public static int get(BuilderBase obj) {
+ return obj.f;
+ }
+ }
+
+ static class Builder extends BuilderBase {
+
+ public Builder(int f) {
+ super(f);
+ }
+
+ public A build() {
+ // After force inlining of get() there will be a field-read "this.f", which is not allowed by
+ // the class inliner, because the class inliner only handles reads from fields that are
+ // declared on Builder.
+ return new A(get(this));
+ }
+ }
+}
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 e013b02..0a832c9 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
+import static org.hamcrest.CoreMatchers.containsString;
import com.android.tools.r8.GenerateMainDexList;
import com.android.tools.r8.GenerateMainDexListCommand;
@@ -14,6 +15,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
@@ -59,11 +61,55 @@
Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
AndroidApiLevel.I);
String output = new String(baos.toByteArray(), Charset.defaultCharset());
- Assert.assertTrue(output.contains("is live because referenced in keep rule:"));
+ Assert.assertThat(output, containsString("is referenced in keep rule:"));
System.setOut(stdout);
}
@Test
+ public void traceMainDexList001_whyareyoukeeping_consumer() throws Throwable {
+ WhyAreYouKeepingConsumer graphConsumer = new WhyAreYouKeepingConsumer(null);
+ doTest(
+ "traceMainDexList001_1",
+ "multidex001",
+ EXAMPLE_BUILD_DIR,
+ Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
+ Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
+ Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
+ AndroidApiLevel.I,
+ options -> {
+ options.enableInlining = false;
+ options.mainDexKeptGraphConsumer = graphConsumer;
+ });
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ graphConsumer.printWhyAreYouKeeping("Lmultidex001/MainActivity;", new PrintStream(baos));
+ String output = new String(baos.toByteArray(), Charset.defaultCharset());
+ String expected =
+ StringUtils.lines(
+ "multidex001.MainActivity",
+ "|- is referenced in keep rule:",
+ "| src/test/examples/multidex/main-dex-rules.txt:14:1");
+ Assert.assertEquals(expected, output);
+ }
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ graphConsumer.printWhyAreYouKeeping("Lmultidex001/ClassForMainDex;", new PrintStream(baos));
+ String output = new String(baos.toByteArray(), Charset.defaultCharset());
+ // TODO(b/120951570): We should be able to get the reason for ClassForMainDex too.
+ String expected =
+ true
+ ? StringUtils.lines("Nothing is keeping multidex001.ClassForMainDex")
+ : StringUtils.lines(
+ "multidex001.ClassForMainDex",
+ "|- is direct reference from:",
+ "| multidex001.MainActivity",
+ "|- is referenced in keep rule:",
+ "| src/test/examples/multidex/main-dex-rules.txt:14:1");
+ Assert.assertEquals(expected, output);
+ }
+ }
+
+ @Test
public void traceMainDexList001_1() throws Throwable {
doTest(
"traceMainDexList001_1",
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
index 2713c6d..01265bb 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
@@ -4,21 +4,29 @@
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 static org.junit.Assert.assertEquals;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8Command;
+import com.android.tools.r8.GenerateMainDexList;
+import com.android.tools.r8.GenerateMainDexListCommand;
+import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graphinfo.GraphConsumer;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
-import java.nio.charset.Charset;
+import java.util.List;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
class HelloWorldMain {
public static void main(String[] args) {
@@ -30,38 +38,113 @@
class NonMainDexClass {}
+@RunWith(Parameterized.class)
public class MainDexListWhyAreYouKeeping extends TestBase {
- public String runTest(String whyAreYouKeepingRule) throws Exception {
- R8Command command =
- ToolHelper.prepareR8CommandBuilder(
- readClasses(HelloWorldMain.class, MainDexClass.class, NonMainDexClass.class))
+
+ private static final List<Class<?>> CLASSES =
+ ImmutableList.of(HelloWorldMain.class, MainDexClass.class, NonMainDexClass.class);
+
+ private enum Command {
+ R8,
+ Generator
+ }
+
+ private enum ApiUse {
+ WhyAreYouKeepingRule,
+ KeptGraphConsumer
+ }
+
+ @Parameters(name = "{0} {1}")
+ public static List<Object[]> parameters() {
+ return buildParameters(Command.values(), ApiUse.values());
+ }
+
+ private final Command command;
+ private final ApiUse apiUse;
+
+ public MainDexListWhyAreYouKeeping(Command command, ApiUse apiUse) {
+ this.command = command;
+ this.apiUse = apiUse;
+ }
+
+ public void runTestWithGenerator(GraphConsumer consumer, String rule) throws Exception {
+ GenerateMainDexListCommand.Builder builder =
+ GenerateMainDexListCommand.builder()
+ .addProgramFiles(ListUtils.map(CLASSES, ToolHelper::getClassFileForTestClass))
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
.addMainDexRules(
ImmutableList.of(keepMainProguardConfiguration(HelloWorldMain.class)),
Origin.unknown())
- .addMainDexRules(ImmutableList.of(whyAreYouKeepingRule), Origin.unknown())
- .setOutput(temp.getRoot().toPath(), OutputMode.DexIndexed)
- .setMode(CompilationMode.RELEASE)
- .build();
+ .setMainDexKeptGraphConsumer(consumer);
+ if (rule != null) {
+ builder.addMainDexRules(ImmutableList.of(rule), Origin.unknown());
+ }
+ GenerateMainDexList.run(builder.build());
+ }
+
+ public void runTestWithR8(GraphConsumer consumer, String rule) throws Exception {
+ R8TestBuilder builder =
+ testForR8(Backend.DEX)
+ .setMinApi(AndroidApiLevel.L)
+ .addProgramClasses(CLASSES)
+ .addMainDexRules(keepMainProguardConfiguration(HelloWorldMain.class))
+ .setMainDexKeptGraphConsumer(consumer);
+ if (rule != null) {
+ builder.addMainDexRules(rule);
+ }
+ builder.compile();
+ }
+
+ private String runTest(Class clazz) throws Exception {
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;
+ String rule = null;
+ WhyAreYouKeepingConsumer consumer = null;
+ if (apiUse == ApiUse.KeptGraphConsumer) {
+ consumer = new WhyAreYouKeepingConsumer(null);
+ } else {
+ assert apiUse == ApiUse.WhyAreYouKeepingRule;
+ rule = "-whyareyoukeeping class " + clazz.getTypeName();
+ System.setOut(new PrintStream(baos));
+ }
+ switch (command) {
+ case R8:
+ runTestWithR8(consumer, rule);
+ break;
+ case Generator:
+ runTestWithGenerator(consumer, rule);
+ break;
+ default:
+ throw new Unreachable();
+ }
+ if (consumer != null) {
+ consumer.printWhyAreYouKeeping(
+ DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()), new PrintStream(baos));
+ } else {
+ System.setOut(stdout);
+ }
+ return baos.toString();
}
@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:"));
+ String expected =
+ StringUtils.lines(
+ "com.android.tools.r8.maindexlist.whyareyoukeeping.MainDexClass",
+ "|- is instantiated in:",
+ "| void com.android.tools.r8.maindexlist.whyareyoukeeping.HelloWorldMain.main(java.lang.String[])",
+ "|- is referenced in keep rule:",
+ "| -keep class com.android.tools.r8.maindexlist.whyareyoukeeping.HelloWorldMain {",
+ "| public static void main(java.lang.String[]);",
+ "| }");
+ assertEquals(expected, runTest(MainDexClass.class));
}
@Test
public void testNonMainDexWhyAreYouKeeping() throws Exception {
- String output = runTest("-whyareyoukeeping class " + NonMainDexClass.class.getCanonicalName());
- assertTrue(output.isEmpty());
+ String expected =
+ StringUtils.lines(
+ "Nothing is keeping com.android.tools.r8.maindexlist.whyareyoukeeping.NonMainDexClass");
+ assertEquals(expected, runTest(NonMainDexClass.class));
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index 191559b..3d9edfc 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -82,10 +82,9 @@
new RootSetBuilder(appView, program, configuration.getRules(), options).run(executor);
}
- Enqueuer enqueuer = new Enqueuer(appView, options, options.forceProguardCompatibility);
+ Enqueuer enqueuer = new Enqueuer(appView, options, null, options.forceProguardCompatibility);
AppInfoWithSubtyping appInfo =
- enqueuer.traceApplication(
- rootSet, configuration.getDontWarnPatterns(), executor, timing);
+ enqueuer.traceApplication(rootSet, configuration.getDontWarnPatterns(), executor, timing);
return new Minifier(appInfo.withLiveness(), rootSet, Collections.emptySet(), options)
.run(timing);
}
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index 7157ed5..df6f682 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -115,8 +115,9 @@
buildKeepRuleForClass(Main.class, application.dexItemFactory),
options)
.run(executor);
- appInfo = new Enqueuer(appView, options).traceApplication(
- rootSet, ProguardClassFilter.empty(), executor, timing);
+ appInfo =
+ new Enqueuer(appView, options, null)
+ .traceApplication(rootSet, ProguardClassFilter.empty(), executor, timing);
// We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
// due to liveness.
}
diff --git a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
index 1f06e72..8ae7452 100644
--- a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
@@ -7,36 +7,75 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
-import com.google.common.collect.ImmutableList;
+import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
class A {
}
+@RunWith(Parameterized.class)
public class WhyAreYouKeepingTest extends TestBase {
+
+ public static final String expected =
+ StringUtils.joinLines(
+ "com.android.tools.r8.shaking.whyareyoukeeping.A",
+ "|- is referenced in keep rule:",
+ "| -keep class com.android.tools.r8.shaking.whyareyoukeeping.A { *; }",
+ "");
+
+ @Parameters(name = "{0}")
+ public static Backend[] parameters() {
+ return Backend.values();
+ }
+
+ public final Backend backend;
+
+ public WhyAreYouKeepingTest(Backend backend) {
+ this.backend = backend;
+ }
+
@Test
- public void test() throws Exception {
- String proguardConfig = String.join("\n", ImmutableList.of(
- "-keep class " + A.class.getCanonicalName() + " { *; }",
- "-whyareyoukeeping class " + A.class.getCanonicalName()
- ));
- PrintStream stdout = System.out;
+ public void testWhyAreYouKeepingViaProguardConfig() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- System.setOut(new PrintStream(baos));
- compileWithR8(ImmutableList.of(A.class), proguardConfig);
- String output = new String(baos.toByteArray(), Charset.defaultCharset());
- System.setOut(stdout);
- String expected = String.join(System.lineSeparator(), ImmutableList.of(
- "com.android.tools.r8.shaking.whyareyoukeeping.A",
- "|- is live because referenced in keep rule:",
- "| -keep class com.android.tools.r8.shaking.whyareyoukeeping.A {",
- "| *;",
- "| };",
- ""));
+ testForR8(backend)
+ .addProgramClasses(A.class)
+ .addKeepClassAndMembersRules(A.class)
+ .addKeepRules("-whyareyoukeeping class " + A.class.getTypeName())
+ // Clear the default library and ignore missing classes to avoid processing the library.
+ .addLibraryFiles()
+ .addOptionsModification(o -> o.ignoreMissingClasses = true)
+ // Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
+ .redirectStdOut(new PrintStream(baos))
+ .compile();
+ String output = new String(baos.toByteArray(), StandardCharsets.UTF_8);
+ assertEquals(expected, output);
+ }
+
+ @Test
+ public void testWhyAreYouKeepingViaConsumer() throws Exception {
+ WhyAreYouKeepingConsumer graphConsumer = new WhyAreYouKeepingConsumer(null);
+ testForR8(backend)
+ .addProgramClasses(A.class)
+ .addKeepClassAndMembersRules(A.class)
+ // Clear the default library and ignore missing classes to avoid processing the library.
+ .addLibraryFiles()
+ .addOptionsModification(o -> o.ignoreMissingClasses = true)
+ .setKeptGraphConsumer(graphConsumer)
+ .compile();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ String descriptor = DescriptorUtils.javaTypeToDescriptor(A.class.getTypeName());
+ graphConsumer.printWhyAreYouKeeping(descriptor, new PrintStream(baos));
+ String output = new String(baos.toByteArray(), StandardCharsets.UTF_8);
assertEquals(expected, output);
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
index 336d079..408fc15 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
@@ -17,6 +17,21 @@
}
@Override
+ public boolean isProtected() {
+ throw new Unreachable("Cannot determine if an absent field is protected");
+ }
+
+ @Override
+ public boolean isPrivate() {
+ throw new Unreachable("Cannot determine if an absent field is private");
+ }
+
+ @Override
+ public boolean isPackagePrivate() {
+ throw new Unreachable("Cannot determine if an absent field is package-private");
+ }
+
+ @Override
public boolean isStatic() {
throw new Unreachable("Cannot determine if an absent field is static");
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index e9dd541..5eb2f22 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -26,6 +26,21 @@
}
@Override
+ public boolean isProtected() {
+ throw new Unreachable("Cannot determine if an absent method is protected");
+ }
+
+ @Override
+ public boolean isPrivate() {
+ throw new Unreachable("Cannot determine if an absent method is private");
+ }
+
+ @Override
+ public boolean isPackagePrivate() {
+ throw new Unreachable("Cannot determine if an absent method is package-private");
+ }
+
+ @Override
public boolean isStatic() {
throw new Unreachable("Cannot determine if an absent method is static");
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
index f9a669a..c182f74 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexValue;
public abstract class FieldSubject extends MemberSubject {
+
public abstract boolean hasExplicitStaticValue();
public abstract DexEncodedField getField();
@@ -19,4 +20,14 @@
public abstract String getOriginalSignatureAttribute();
public abstract String getFinalSignatureAttribute();
+
+ @Override
+ public FieldSubject asFieldSubject() {
+ return this;
+ }
+
+ @Override
+ public boolean isFieldSubject() {
+ return true;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index e65a8b0..7efc794 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -29,6 +29,21 @@
}
@Override
+ public boolean isProtected() {
+ return dexField.accessFlags.isProtected();
+ }
+
+ @Override
+ public boolean isPrivate() {
+ return dexField.accessFlags.isPrivate();
+ }
+
+ @Override
+ public boolean isPackagePrivate() {
+ return !isPublic() && !isProtected() && !isPrivate();
+ }
+
+ @Override
public boolean isStatic() {
return dexField.accessFlags.isStatic();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index f4f1660..6ce0496 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -56,6 +56,21 @@
}
@Override
+ public boolean isProtected() {
+ return dexMethod.accessFlags.isProtected();
+ }
+
+ @Override
+ public boolean isPrivate() {
+ return dexMethod.accessFlags.isPrivate();
+ }
+
+ @Override
+ public boolean isPackagePrivate() {
+ return !isPublic() && !isProtected() && !isPrivate();
+ }
+
+ @Override
public boolean isStatic() {
return dexMethod.accessFlags.isStatic();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 7f310ed..a9abba1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.utils.codeinspector;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessFlags;
import com.google.common.collect.ImmutableList;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
@@ -11,6 +13,33 @@
public class Matchers {
+ private enum Visibility {
+ PUBLIC,
+ PROTECTED,
+ PRIVATE,
+ PACKAGE_PRIVATE;
+
+ @Override
+ public String toString() {
+ switch (this) {
+ case PUBLIC:
+ return "public";
+
+ case PROTECTED:
+ return "protected";
+
+ case PRIVATE:
+ return "private";
+
+ case PACKAGE_PRIVATE:
+ return "package-private";
+
+ default:
+ throw new Unreachable("Unexpected visibility");
+ }
+ }
+ }
+
private static String type(Subject subject) {
String type = "<unknown subject type>";
if (subject instanceof ClassSubject) {
@@ -154,22 +183,67 @@
};
}
- public static Matcher<MethodSubject> isPublic() {
- return new TypeSafeMatcher<MethodSubject>() {
+ public static <T extends MemberSubject> Matcher<T> isPrivate() {
+ return hasVisibility(Visibility.PRIVATE);
+ }
+
+ public static <T extends MemberSubject> Matcher<T> isPublic() {
+ return hasVisibility(Visibility.PUBLIC);
+ }
+
+ private static <T extends MemberSubject> Matcher<T> hasVisibility(Visibility visibility) {
+ return new TypeSafeMatcher<T>() {
@Override
- public boolean matchesSafely(final MethodSubject method) {
- return method.isPresent() && method.isPublic();
+ public boolean matchesSafely(final T subject) {
+ if (subject.isPresent()) {
+ switch (visibility) {
+ case PUBLIC:
+ return subject.isPublic();
+
+ case PROTECTED:
+ return subject.isProtected();
+
+ case PRIVATE:
+ return subject.isPrivate();
+
+ case PACKAGE_PRIVATE:
+ return subject.isPackagePrivate();
+
+ default:
+ throw new Unreachable("Unexpected visibility: " + visibility);
+ }
+ }
+ return false;
}
@Override
public void describeTo(final Description description) {
- description.appendText("method public");
+ description.appendText("method " + visibility);
}
@Override
- public void describeMismatchSafely(final MethodSubject method, Description description) {
+ public void describeMismatchSafely(final T subject, Description description) {
description
- .appendText("method ").appendValue(method.getOriginalName()).appendText(" was not");
+ .appendText("method ")
+ .appendValue(subject.getOriginalName())
+ .appendText(" was ");
+ if (subject.isPresent()) {
+ AccessFlags accessFlags =
+ subject.isMethodSubject()
+ ? subject.asMethodSubject().getMethod().accessFlags
+ : subject.asFieldSubject().getField().accessFlags;
+ if (accessFlags.isPublic()) {
+ description.appendText("public");
+ } else if (accessFlags.isProtected()) {
+ description.appendText("protected");
+ } else if (accessFlags.isPrivate()) {
+ description.appendText("private");
+ } else {
+ description.appendText("package-private");
+ }
+ } else {
+ description.appendText(" was absent");
+ }
}
};
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
index 9e90fde..a95b6e6 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -10,6 +10,12 @@
public abstract boolean isPublic();
+ public abstract boolean isProtected();
+
+ public abstract boolean isPrivate();
+
+ public abstract boolean isPackagePrivate();
+
public abstract boolean isStatic();
public abstract boolean isFinal();
@@ -27,4 +33,20 @@
Signature finalSignature = getFinalSignature();
return finalSignature == null ? null : finalSignature.name;
}
+
+ public FieldSubject asFieldSubject() {
+ return null;
+ }
+
+ public boolean isFieldSubject() {
+ return false;
+ }
+
+ public MethodSubject asMethodSubject() {
+ return null;
+ }
+
+ public boolean isMethodSubject() {
+ return false;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 98bb8d0..adcbf31 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -46,4 +46,14 @@
public Stream<InstructionSubject> streamInstructions() {
return Streams.stream(iterateInstructions());
}
+
+ @Override
+ public MethodSubject asMethodSubject() {
+ return this;
+ }
+
+ @Override
+ public boolean isMethodSubject() {
+ return true;
+ }
}
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 47f5a97..7d126b0 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -197,8 +197,6 @@
assert not get_magic_file_exists(READY_FOR_TESTING)
git_hash = utils.get_HEAD_sha1()
put_magic_file(READY_FOR_TESTING, git_hash)
- print("TODO(118647285): Reenable when bug fixed")
- return
begin = time.time()
while True:
if time.time() - begin > BOT_RUN_TIMEOUT:
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 3e7d78c..76fc59b 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -121,6 +121,7 @@
# do Bug: #BUG in the commit message of disabling to ensure re-enabling
DISABLED_PERMUTATIONS = [
# (app, version, type), e.g., ('gmail', '180826.15', 'deploy'),
+ ('youtube', '13.37', 'deploy'), # b/120977564
]
def get_permutations():
@@ -171,6 +172,8 @@
def run_with_options(options, args):
app_provided_pg_conf = False;
extra_args = []
+ # todo(121018500): remove when memory is under control
+ extra_args.append('-Xmx8GB')
if options.golem:
golem.link_third_party()
options.out = os.getcwd()
diff --git a/tools/test.py b/tools/test.py
index 0e4625f..279f58b 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -120,8 +120,8 @@
if utils.is_bot():
gradle.RunGradle(['clean'])
- # Build R8lib with dependencies for bootstrapping tests before adding test sources
- gradle.RunGradle(['r8libwithdeps'])
+ # Build an R8 with dependencies for bootstrapping tests before adding test sources
+ gradle.RunGradle(['r8WithRelocatedDeps'])
gradle_args = ['--stacktrace']
# Set all necessary Gradle properties and options first.
diff --git a/tools/update_prebuilds_in_android.py b/tools/update_prebuilds_in_android.py
index 41881b0..6ef56c7 100755
--- a/tools/update_prebuilds_in_android.py
+++ b/tools/update_prebuilds_in_android.py
@@ -15,12 +15,20 @@
BUILD_ROOT = "http://storage.googleapis.com/r8-releases/raw/"
MASTER_BUILD_ROOT = "%smaster/" % BUILD_ROOT
-JAR_TARGETS = [
- utils.D8,
- utils.R8,
- utils.COMPATDX,
- utils.COMPATPROGUARD,
-]
+JAR_TARGETS_MAP = {
+ 'full': [
+ (utils.D8, 'd8-master'),
+ (utils.R8, 'r8-master'),
+ (utils.COMPATDX, 'compatdx-master'),
+ (utils.COMPATPROGUARD, 'compatproguard-master'),
+ ],
+ 'lib': [
+ (utils.R8LIB, 'r8-master'),
+ (utils.COMPATDXLIB, 'compatdx-master'),
+ (utils.COMPATPROGUARDLIB, 'compatproguard-master'),
+ ],
+}
+
OTHER_TARGETS = ["LICENSE"]
def parse_arguments():
@@ -30,20 +38,36 @@
help='Android checkout root.')
parser.add_argument('--commit_hash', default=None, help='Commit hash')
parser.add_argument('--version', default=None, help='The version to download')
+ parser.add_argument(
+ '--targets',
+ required=True,
+ choices=['full', 'lib'],
+ help="Use 'full' to download the full, non-optimized jars (legacy" +
+ " behaviour) and 'lib' for the R8-processed, optimized jars (this" +
+ " one omits d8.jar)",
+ )
+ parser.add_argument(
+ '--maps',
+ action='store_true',
+ help="Download proguard maps for jars, use only with '--target lib'.",
+ )
return parser.parse_args()
-def copy_targets(root, target_root, srcs, dests):
+def copy_targets(root, target_root, srcs, dests, maps=False):
assert len(srcs) == len(dests)
for i in range(len(srcs)):
src = os.path.join(root, srcs[i])
dest = os.path.join(target_root, 'prebuilts', 'r8', dests[i])
print 'Copying: ' + src + ' -> ' + dest
copyfile(src, dest)
+ if maps:
+ print 'Copying: ' + src + '.map -> ' + dest + '.map'
+ copyfile(src + '.map', dest + '.map')
-def copy_jar_targets(root, target_root):
- srcs = map((lambda t: t + '.jar'), JAR_TARGETS)
- dests = map((lambda t: t + '-master.jar'), JAR_TARGETS)
- copy_targets(root, target_root, srcs, dests)
+def copy_jar_targets(root, target_root, jar_targets, maps):
+ srcs = map((lambda t: t[0] + '.jar'), jar_targets)
+ dests = map((lambda t: t[1] + '.jar'), jar_targets)
+ copy_targets(root, target_root, srcs, dests, maps=maps)
def copy_other_targets(root, target_root):
copy_targets(root, target_root, OTHER_TARGETS, OTHER_TARGETS)
@@ -65,22 +89,29 @@
def Main():
args = parse_arguments()
+ if args.maps and args.targets != 'lib':
+ raise Exception("Use '--maps' only with '--targets lib.")
target_root = args.android_root[0]
+ jar_targets = JAR_TARGETS_MAP[args.targets]
if args.commit_hash == None and args.version == None:
- gradle.RunGradle(JAR_TARGETS)
- copy_jar_targets(utils.LIBS, target_root)
+ gradle.RunGradle(map(lambda t: t[0], jar_targets))
+ copy_jar_targets(utils.LIBS, target_root, jar_targets, args.maps)
copy_other_targets(utils.GENERATED_LICENSE_DIR, target_root)
else:
assert args.commit_hash == None or args.version == None
- targets = map((lambda t: t + '.jar'), JAR_TARGETS) + OTHER_TARGETS
+ targets = map((lambda t: t[0] + '.jar'), jar_targets) + OTHER_TARGETS
with utils.TempDir() as root:
for target in targets:
if args.commit_hash:
download_hash(root, args.commit_hash, target)
+ if args.maps and target not in OTHER_TARGETS:
+ download_hash(root, args.commit_hash, target + '.map')
else:
assert args.version
download_version(root, args.version, target)
- copy_jar_targets(root, target_root)
+ if args.maps and target not in OTHER_TARGETS:
+ download_version(root, args.version, target + '.map')
+ copy_jar_targets(root, target_root, jar_targets, args.maps)
copy_other_targets(root, target_root)
if __name__ == '__main__':