Add resource logging api, and report resource logs
The current implementation is consistent with the old resource shrinker for legacy mode
Bug: b/360284025
Bug: b/360284664
Change-Id: Idfce812bc92f357c04e2b5944d5db43253fd970a
Fixes: 360284025
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index f84fd22..088b1ee 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -114,6 +114,7 @@
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.ResourceShrinkerUtils;
import com.android.tools.r8.utils.SelfRetraceTest;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.StringUtils;
@@ -968,6 +969,9 @@
if (options.androidResourceProguardMapStrings != null) {
resourceShrinkerBuilder.setProguardMapStrings(options.androidResourceProguardMapStrings);
}
+ resourceShrinkerBuilder.setShrinkerDebugReporter(
+ ResourceShrinkerUtils.shrinkerDebugReporterFromStringConsumer(
+ options.resourceShrinkerConfiguration.getDebugConsumer(), reporter));
LegacyResourceShrinker shrinker = resourceShrinkerBuilder.build();
ShrinkerResult shrinkerResult;
if (options.resourceShrinkerConfiguration.isOptimizedShrinking()) {
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinkerConfiguration.java b/src/main/java/com/android/tools/r8/ResourceShrinkerConfiguration.java
index 1a5b781..82137e2 100644
--- a/src/main/java/com/android/tools/r8/ResourceShrinkerConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ResourceShrinkerConfiguration.java
@@ -30,14 +30,17 @@
@KeepForApi
public class ResourceShrinkerConfiguration {
public static ResourceShrinkerConfiguration DEFAULT_CONFIGURATION =
- new ResourceShrinkerConfiguration(false, true);
+ new ResourceShrinkerConfiguration(false, true, null);
private final boolean optimizedShrinking;
private final boolean preciseShrinking;
+ private final StringConsumer debugConsumer;
- private ResourceShrinkerConfiguration(boolean optimizedShrinking, boolean preciseShrinking) {
+ private ResourceShrinkerConfiguration(
+ boolean optimizedShrinking, boolean preciseShrinking, StringConsumer debugConsumer) {
this.optimizedShrinking = optimizedShrinking;
this.preciseShrinking = preciseShrinking;
+ this.debugConsumer = debugConsumer;
}
public static Builder builder(DiagnosticsHandler handler) {
@@ -52,6 +55,10 @@
return preciseShrinking;
}
+ public StringConsumer getDebugConsumer() {
+ return debugConsumer;
+ }
+
/**
* Builder for constructing a ResourceShrinkerConfiguration.
*
@@ -63,6 +70,7 @@
private boolean optimizedShrinking = false;
private boolean preciseShrinking = true;
+ private StringConsumer debugConsumer;
private Builder() {}
@@ -82,6 +90,11 @@
return this;
}
+ public Builder setDebugConsumer(StringConsumer consumer) {
+ this.debugConsumer = consumer;
+ return this;
+ }
+
/**
* Disable precise shrinking.
*
@@ -97,7 +110,7 @@
/** Build and return the {@link ResourceShrinkerConfiguration} */
public ResourceShrinkerConfiguration build() {
- return new ResourceShrinkerConfiguration(optimizedShrinking, preciseShrinking);
+ return new ResourceShrinkerConfiguration(optimizedShrinking, preciseShrinking, debugConsumer);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/ResourceShrinkerUtils.java b/src/main/java/com/android/tools/r8/utils/ResourceShrinkerUtils.java
index 41ad64f..74fbb46 100644
--- a/src/main/java/com/android/tools/r8/utils/ResourceShrinkerUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ResourceShrinkerUtils.java
@@ -3,21 +3,28 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import com.android.build.shrinker.NoDebugReporter;
+import com.android.build.shrinker.ShrinkerDebugReporter;
import com.android.build.shrinker.r8integration.R8ResourceShrinkerState;
import com.android.tools.r8.AndroidResourceInput;
+import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.FeatureSplit;
import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.graph.AppView;
import java.io.InputStream;
import java.util.Collection;
+import java.util.function.Supplier;
public class ResourceShrinkerUtils {
public static R8ResourceShrinkerState createResourceShrinkerState(AppView<?> appView) {
+ InternalOptions options = appView.options();
R8ResourceShrinkerState state =
new R8ResourceShrinkerState(
- exception -> appView.reporter().fatalError(new ExceptionDiagnostic(exception)));
- InternalOptions options = appView.options();
+ exception -> appView.reporter().fatalError(new ExceptionDiagnostic(exception)),
+ shrinkerDebugReporterFromStringConsumer(
+ options.resourceShrinkerConfiguration.getDebugConsumer(), appView.reporter()));
if (options.resourceShrinkerConfiguration.isOptimizedShrinking()
&& options.androidResourceProvider != null) {
try {
@@ -77,6 +84,29 @@
}
}
+ public static ShrinkerDebugReporter shrinkerDebugReporterFromStringConsumer(
+ StringConsumer consumer, DiagnosticsHandler diagnosticsHandler) {
+ if (consumer == null) {
+ return NoDebugReporter.INSTANCE;
+ }
+ return new ShrinkerDebugReporter() {
+ @Override
+ public void debug(Supplier<String> logSupplier) {
+ consumer.accept(logSupplier.get(), diagnosticsHandler);
+ }
+
+ @Override
+ public void info(Supplier<String> logProducer) {
+ consumer.accept(logProducer.get(), diagnosticsHandler);
+ }
+
+ @Override
+ public void close() throws Exception {
+ consumer.finished(diagnosticsHandler);
+ }
+ };
+ }
+
private static InputStream wrapThrowingInputStreamResource(
AppView<?> appView, AndroidResourceInput androidResource) {
try {
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/ShrinkerDebugReporter.kt b/src/resourceshrinker/java/com/android/build/shrinker/ShrinkerDebugReporter.kt
index b592bb8..bcb6af3 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/ShrinkerDebugReporter.kt
+++ b/src/resourceshrinker/java/com/android/build/shrinker/ShrinkerDebugReporter.kt
@@ -18,16 +18,17 @@
import java.io.File
import java.io.PrintWriter
+import java.util.function.Supplier
interface ShrinkerDebugReporter : AutoCloseable {
- fun debug(f: () -> String)
- fun info(f: () -> String)
+ fun debug(f: Supplier<String>)
+ fun info(f: Supplier<String>)
}
object NoDebugReporter : ShrinkerDebugReporter {
- override fun debug(f: () -> String) = Unit
+ override fun debug(f: Supplier<String>) = Unit
- override fun info(f: () -> String) = Unit
+ override fun info(f: Supplier<String>) = Unit
override fun close() = Unit
}
@@ -36,12 +37,12 @@
reportFile: File
) : ShrinkerDebugReporter {
private val writer: PrintWriter = reportFile.let { PrintWriter(it) }
- override fun debug(f: () -> String) {
- writer.println(f())
+ override fun debug(f: Supplier<String>) {
+ writer.println(f.get())
}
- override fun info(f: () -> String) {
- writer.println(f())
+ override fun info(f: Supplier<String>) {
+ writer.println(f.get())
}
override fun close() {
@@ -56,14 +57,14 @@
) : ShrinkerDebugReporter {
private val writer: PrintWriter? = reportFile?.let { PrintWriter(it) }
- override fun debug(f: () -> String) {
- val message = f()
+ override fun debug(f: Supplier<String>) {
+ val message = f.get()
writer?.println(message)
logDebug(message)
}
- override fun info(f: () -> String) {
- val message = f()
+ override fun info(f: Supplier<String>) {
+ val message = f.get()
writer?.println(message)
logInfo(message)
}
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
index 7a82e3f..f1ba15e 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
@@ -10,9 +10,9 @@
import com.android.aapt.Resources.ResourceTable;
import com.android.aapt.Resources.XmlNode;
-import com.android.build.shrinker.NoDebugReporter;
import com.android.build.shrinker.ResourceShrinkerImplKt;
import com.android.build.shrinker.ResourceTableUtilKt;
+import com.android.build.shrinker.ShrinkerDebugReporter;
import com.android.build.shrinker.graph.ProtoResourcesGraphBuilder;
import com.android.build.shrinker.obfuscation.ProguardMappingsRecorder;
import com.android.build.shrinker.r8integration.R8ResourceShrinkerState.R8ResourceShrinkerModel;
@@ -53,6 +53,7 @@
private final Collection<PathAndBytes> resFolderInputs;
private final Collection<PathAndBytes> xmlInputs;
private List<String> proguardMapStrings;
+ private final ShrinkerDebugReporter debugReporter;
private final List<PathAndBytes> manifest;
private final Map<PathAndBytes, FeatureSplit> resourceTables;
@@ -65,6 +66,7 @@
private final List<PathAndBytes> manifests = new ArrayList<>();
private final Map<PathAndBytes, FeatureSplit> resourceTables = new HashMap<>();
private List<String> proguardMapStrings;
+ private ShrinkerDebugReporter debugReporter;
private Builder() {}
@@ -117,12 +119,18 @@
manifests,
resourceTables,
xmlInputs.values(),
- proguardMapStrings);
+ proguardMapStrings,
+ debugReporter);
}
public void setProguardMapStrings(List<String> proguardMapStrings) {
this.proguardMapStrings = proguardMapStrings;
}
+
+ public Builder setShrinkerDebugReporter(ShrinkerDebugReporter debugReporter) {
+ this.debugReporter = debugReporter;
+ return this;
+ }
}
private LegacyResourceShrinker(
@@ -131,13 +139,15 @@
List<PathAndBytes> manifests,
Map<PathAndBytes, FeatureSplit> resourceTables,
Collection<PathAndBytes> xmlInputs,
- List<String> proguardMapStrings) {
+ List<String> proguardMapStrings,
+ ShrinkerDebugReporter debugReporter) {
this.dexInputs = dexInputs;
this.resFolderInputs = resFolderInputs;
this.manifest = manifests;
this.resourceTables = resourceTables;
this.xmlInputs = xmlInputs;
this.proguardMapStrings = proguardMapStrings;
+ this.debugReporter = debugReporter;
}
public static Builder builder() {
@@ -145,7 +155,7 @@
}
public ShrinkerResult run() throws IOException, ParserConfigurationException, SAXException {
- R8ResourceShrinkerModel model = new R8ResourceShrinkerModel(NoDebugReporter.INSTANCE, true);
+ R8ResourceShrinkerModel model = new R8ResourceShrinkerModel(debugReporter, true);
for (PathAndBytes pathAndBytes : resourceTables.keySet()) {
ResourceTable loadedResourceTable = ResourceTable.parseFrom(pathAndBytes.bytes);
model.instantiateFromResourceTable(loadedResourceTable, false);
@@ -156,7 +166,7 @@
}
for (Entry<String, byte[]> entry : dexInputs.entrySet()) {
// The analysis needs an origin for the dex files, synthesize an easy recognizable one.
- Path inMemoryR8 = Paths.get("in_memory_r8_" + entry.getKey() + ".dex");
+ Path inMemoryR8 = Paths.get("in_memory_r8_" + entry.getKey());
R8ResourceShrinker.runResourceShrinkerAnalysis(
entry.getValue(), inMemoryR8, new DexFileAnalysisCallback(inMemoryR8, model));
}
@@ -188,11 +198,19 @@
ResourceStore resourceStore = model.getResourceStore();
resourceStore.processToolsAttributes();
model.keepPossiblyReferencedResources();
+ debugReporter.debug(model.getResourceStore()::dumpResourceModel);
// Transitively mark the reachable resources in the model.
// Finds unused resources in provided resources collection.
// Marks all used resources as 'reachable' in original collection.
List<Resource> unusedResources =
- ResourcesUtil.findUnusedResources(model.getResourceStore().getResources(), x -> {});
+ ResourcesUtil.findUnusedResources(
+ model.getResourceStore().getResources(),
+ roots -> {
+ debugReporter.debug(() -> "The root reachable resources are:");
+ roots.forEach(root -> debugReporter.debug(() -> " " + root));
+ });
+ debugReporter.debug(() -> "Unused resources are: ");
+ unusedResources.forEach(unused -> debugReporter.debug(() -> " " + unused));
ImmutableSet.Builder<String> resEntriesToKeep = new ImmutableSet.Builder<>();
for (PathAndBytes xmlInput : Iterables.concat(xmlInputs, resFolderInputs)) {
if (ResourceShrinkerImplKt.isJarPathReachable(resourceStore, xmlInput.path.toString())) {
diff --git a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
index 49f3546..2dd02e8 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -16,7 +16,6 @@
import com.android.aapt.Resources.XmlAttribute;
import com.android.aapt.Resources.XmlElement;
import com.android.aapt.Resources.XmlNode;
-import com.android.build.shrinker.NoDebugReporter;
import com.android.build.shrinker.ResourceShrinkerImplKt;
import com.android.build.shrinker.ResourceShrinkerModel;
import com.android.build.shrinker.ResourceTableUtilKt;
@@ -69,8 +68,10 @@
boolean tryClass(String possibleClass, Origin xmlFileOrigin);
}
- public R8ResourceShrinkerState(Function<Exception, RuntimeException> errorHandler) {
- r8ResourceShrinkerModel = new R8ResourceShrinkerModel(NoDebugReporter.INSTANCE, true);
+ public R8ResourceShrinkerState(
+ Function<Exception, RuntimeException> errorHandler,
+ ShrinkerDebugReporter shrinkerDebugReporter) {
+ r8ResourceShrinkerModel = new R8ResourceShrinkerModel(shrinkerDebugReporter, true);
this.errorHandler = errorHandler;
}
diff --git a/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkerLoggingTest.java b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkerLoggingTest.java
new file mode 100644
index 0000000..5ed2e09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/ResourceShrinkerLoggingTest.java
@@ -0,0 +1,190 @@
+// Copyright (c) 2024, 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.androidresources;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ResourceShrinkerLoggingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean optimized;
+
+ @Parameters(name = "{0}, optimized: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDefaultDexRuntime().withAllApiLevels().build(),
+ BooleanUtils.values());
+ }
+
+ public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+ return new AndroidTestResourceBuilder()
+ .withSimpleManifestAndAppNameString()
+ .addRClassInitializeWithDefaultValues(R.string.class, R.drawable.class)
+ .build(temp);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ StringBuilder log = new StringBuilder();
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addProgramClasses(FooBar.class)
+ .apply(
+ b ->
+ b.getBuilder()
+ .setResourceShrinkerConfiguration(
+ configurationBuilder -> {
+ if (optimized) {
+ configurationBuilder.enableOptimizedShrinkingWithR8();
+ }
+ configurationBuilder.setDebugConsumer(
+ (string, handler) -> log.append(string + "\n"));
+ return configurationBuilder.build();
+ }))
+ .addAndroidResources(getTestResources(temp))
+ .addKeepMainRule(FooBar.class)
+ .compile()
+ .inspectShrunkenResources(
+ resourceTableInspector -> {
+ resourceTableInspector.assertContainsResourceWithName("string", "bar");
+ resourceTableInspector.assertContainsResourceWithName("string", "foo");
+ resourceTableInspector.assertContainsResourceWithName("drawable", "foobar");
+ resourceTableInspector.assertDoesNotContainResourceWithName(
+ "string", "unused_string");
+ resourceTableInspector.assertDoesNotContainResourceWithName(
+ "drawable", "unused_drawable");
+ })
+ .run(parameters.getRuntime(), FooBar.class)
+ .assertSuccess();
+ // TODO(b/360284664): Add (non compatible) logging for optimized shrinking
+ if (!optimized) {
+ // Consistent with the old AGP embedded shrinker
+ List<String> strings = StringUtils.splitLines(log.toString());
+ // string:bar reachable from code
+ for (String dexReachableString : ImmutableList.of("bar", "foo")) {
+ ensureDexReachableResourcesState(strings, "string", dexReachableString, true);
+ ensureResourceReachabilityState(strings, "string", dexReachableString, true);
+ ensureRootResourceState(strings, "string", dexReachableString, true);
+ ensureUnusedState(strings, "string", dexReachableString, false);
+ }
+ // The app name is only reachable from the manifest, not dex
+ ensureDexReachableResourcesState(strings, "string", "app_name", false);
+ ensureResourceReachabilityState(strings, "string", "app_name", true);
+ ensureRootResourceState(strings, "string", "app_name", true);
+ ensureUnusedState(strings, "string", "app_name", false);
+
+ ensureDexReachableResourcesState(strings, "drawable", "unused_drawable", false);
+ ensureResourceReachabilityState(strings, "drawable", "unused_drawable", false);
+ ensureRootResourceState(strings, "drawable", "unused_drawable", false);
+ ensureUnusedState(strings, "drawable", "unused_drawable", true);
+
+ ensureDexReachableResourcesState(strings, "drawable", "foobar", true);
+ ensureResourceReachabilityState(strings, "drawable", "foobar", true);
+ ensureRootResourceState(strings, "drawable", "foobar", true);
+ ensureUnusedState(strings, "drawable", "foobar", false);
+ }
+ }
+
+ private void ensureDexReachableResourcesState(
+ List<String> logStrings, String type, String name, boolean reachable) {
+ // Example line:
+ // Marking drawable:foobar:2130771968 reachable: referenced from classes.dex
+ assertEquals(
+ logStrings.stream()
+ .anyMatch(
+ s ->
+ s.contains("Marking " + type + ":" + name)
+ && s.contains("reachable: referenced from")),
+ reachable);
+ }
+
+ private void ensureResourceReachabilityState(
+ List<String> logStrings, String type, String name, boolean reachable) {
+ // Example line:
+ // @packagename:string/bar : reachable=true
+ assertTrue(
+ logStrings.stream()
+ .anyMatch(s -> s.contains(type + "/" + name + " : reachable=" + reachable)));
+ }
+
+ private void ensureRootResourceState(
+ List<String> logStrings, String type, String name, boolean isRoot) {
+ assertEquals(isInSection(logStrings, type, name, "The root reachable resources are:"), isRoot);
+ }
+
+ private void ensureUnusedState(
+ List<String> logStrings, String type, String name, boolean isUnused) {
+ assertEquals(isInSection(logStrings, type, name, "Unused resources are: "), isUnused);
+ }
+
+ private static boolean isInSection(
+ List<String> logStrings, String type, String name, String sectionHeader) {
+ // Example for roots
+ // "The root reachable resources are:"
+ // " drawable:foobar:2130771968"
+ boolean isInSection = false;
+ for (String logString : logStrings) {
+ if (logString.equals(sectionHeader)) {
+ isInSection = true;
+ continue;
+ }
+ if (isInSection) {
+ if (!logString.startsWith(" ")) {
+ return false;
+ }
+ if (logString.startsWith(" " + type + ":" + name)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static class FooBar {
+
+ public static void main(String[] args) {
+ if (System.currentTimeMillis() == 0) {
+ System.out.println(R.drawable.foobar);
+ System.out.println(R.string.bar);
+ System.out.println(R.string.foo);
+ }
+ }
+ }
+
+ public static class R {
+
+ public static class string {
+
+ public static int bar;
+ public static int foo;
+ public static int unused_string;
+ }
+
+ public static class drawable {
+
+ public static int foobar;
+ public static int unused_drawable;
+ }
+ }
+}