Trace AndroidManifest.xml in the enqueuer
Bug: 326008763
Bug: 287398085
Change-Id: Id853f29929a6750381a2c3513e5567165a3b9dd2
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 0dfda11..e52bb2e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3771,6 +3771,7 @@
enqueueAllIfNotShrinking();
timing.end();
timing.begin("Trace");
+ traceManifests(timing);
trace(executorService, timing);
timing.end();
options.reporter.failIfPendingErrors();
@@ -3804,6 +3805,14 @@
return result;
}
+ private void traceManifests(Timing timing) {
+ if (options.isOptimizedResourceShrinking()) {
+ timing.begin("Trace AndroidManifest.xml files");
+ appView.getResourceShrinkerState().traceManifests();
+ timing.end();
+ }
+ }
+
private void includeMinimumKeepInfo(RootSetBase rootSet) {
rootSet
.getDependentMinimumKeepInfo()
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 2d4e961..41ad64f 100644
--- a/src/main/java/com/android/tools/r8/utils/ResourceShrinkerUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ResourceShrinkerUtils.java
@@ -55,7 +55,7 @@
for (AndroidResourceInput androidResource : androidResources) {
switch (androidResource.getKind()) {
case MANIFEST:
- state.setManifestProvider(
+ state.addManifestProvider(
() -> wrapThrowingInputStreamResource(appView, androidResource));
break;
case RESOURCE_TABLE:
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 d8c98c7..11bb767 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -38,6 +38,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
@@ -54,7 +55,7 @@
private final R8ResourceShrinkerModel r8ResourceShrinkerModel;
private final Map<String, Supplier<InputStream>> xmlFileProviders = new HashMap<>();
- private Supplier<InputStream> manifestProvider;
+ private final List<Supplier<InputStream>> manifestProviders = new ArrayList<>();
private final Map<String, Supplier<InputStream>> resfileProviders = new HashMap<>();
private final Map<ResourceTable, FeatureSplit> resourceTables = new HashMap<>();
private ClassReferenceCallback enqueuerCallback;
@@ -82,7 +83,7 @@
return;
}
ResourceUsageModel.markReachable(resource);
- traceXml(id);
+ traceXmlForResourceId(id);
if (resource.references != null) {
for (Resource reference : resource.references) {
if (!reference.isReachable()) {
@@ -92,6 +93,12 @@
}
}
+ public void traceManifests() {
+ for (Supplier<InputStream> manifestProvider : manifestProviders) {
+ traceXml("AndroidManifest.xml", manifestProvider.get());
+ }
+ }
+
public void setEnqueuerCallback(ClassReferenceCallback enqueuerCallback) {
assert this.enqueuerCallback == null;
this.enqueuerCallback = enqueuerCallback;
@@ -111,8 +118,8 @@
return packageNames;
}
- public void setManifestProvider(Supplier<InputStream> manifestProvider) {
- this.manifestProvider = manifestProvider;
+ public void addManifestProvider(Supplier<InputStream> manifestProvider) {
+ this.manifestProviders.add(manifestProvider);
}
public void addXmlFileProvider(Supplier<InputStream> inputStreamSupplier, String location) {
@@ -185,19 +192,24 @@
return resEntriesToKeep.build();
}
- private void traceXml(int id) {
+ private void traceXmlForResourceId(int id) {
String xmlFile = getResourceIdToXmlFiles().get(id);
if (xmlFile != null) {
InputStream inputStream = xmlFileProviders.get(xmlFile).get();
- try {
- XmlNode xmlNode = XmlNode.parseFrom(inputStream);
- visitNode(xmlNode, xmlFile);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ traceXml(xmlFile, inputStream);
}
}
+ private void traceXml(String xmlFile, InputStream inputStream) {
+ try {
+ XmlNode xmlNode = XmlNode.parseFrom(inputStream);
+ visitNode(xmlNode, xmlFile);
+ } catch (IOException e) {
+ errorHandler.apply(e);
+ }
+ }
+
+
private void tryEnqueuerOnString(String possibleClass, String xmlName) {
// There are a lot of xml tags and attributes that are evaluated over and over, if it is
// not a class, ignore it.
@@ -264,11 +276,10 @@
// Temporary to support updating the reachable entries from the manifest, we need to instead
// trace these in the enqueuer.
public void updateModelWithManifestReferences() throws IOException {
- if (manifestProvider == null) {
- return;
+ for (Supplier<InputStream> manifestProvider : manifestProviders) {
+ ProtoAndroidManifestUsageRecorderKt.recordUsagesFromNode(
+ XmlNode.parseFrom(manifestProvider.get()), r8ResourceShrinkerModel);
}
- ProtoAndroidManifestUsageRecorderKt.recordUsagesFromNode(
- XmlNode.parseFrom(manifestProvider.get()), r8ResourceShrinkerModel);
}
public void updateModelWithKeepXmlReferences() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/androidresources/AndroidManifestWithCodeReferences.java b/src/test/java/com/android/tools/r8/androidresources/AndroidManifestWithCodeReferences.java
new file mode 100644
index 0000000..03f1663
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/AndroidManifestWithCodeReferences.java
@@ -0,0 +1,107 @@
+// 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 com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResource;
+import com.android.tools.r8.androidresources.AndroidResourceTestingUtils.AndroidTestResourceBuilder;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 AndroidManifestWithCodeReferences extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withDefaultDexRuntime().withAllApiLevels().build();
+ }
+
+ public static String MANIFEST_WITH_CLASS_REFERENCE =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\""
+ + " package=\"com.android.tools.r8\""
+ + ">\n"
+ + " <application\n"
+ + " android:label=\"@string/app_name\">\n"
+ + " <activity\n"
+ + " android:name=\""
+ + Bar.class.getTypeName()
+ + "\"\n"
+ + " android:exported=\"true\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + "\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " </application>\n"
+ + "</manifest>";
+
+ public static AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+ return new AndroidTestResourceBuilder()
+ .withManifest(MANIFEST_WITH_CLASS_REFERENCE)
+ .addStringValue("app_name", "The one and only.")
+ .build(temp);
+ }
+
+ @Test
+ public void testManifestReferences() throws Exception {
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addProgramClasses(TestClass.class, Bar.class)
+ .addAndroidResources(getTestResources(temp))
+ .addKeepMainRule(TestClass.class)
+ .enableOptimizedShrinking()
+ .compile()
+ .inspectShrunkenResources(
+ resourceTableInspector -> {
+ resourceTableInspector.assertContainsResourceWithName("string", "app_name");
+ })
+ .run(parameters.getRuntime(), TestClass.class)
+ .inspect(
+ codeInspector -> {
+ ClassSubject barClass = codeInspector.clazz(Bar.class);
+ assertThat(barClass, isPresentAndNotRenamed());
+ // We should have two and only two methods, the two constructors.
+ assertEquals(barClass.allMethods(MethodSubject::isInstanceInitializer).size(), 2);
+ assertEquals(barClass.allMethods().size(), 2);
+ })
+ .assertSuccess();
+ }
+
+ public static class TestClass {
+ public static void main(String[] args) {}
+ }
+
+ // Only referenced from Manifest file
+ public static class Bar {
+ public Bar() {
+ System.out.println("init");
+ }
+
+ public Bar(String x) {
+ System.out.println("init with string");
+ }
+
+ public void bar() {
+ System.out.println("never kept");
+ }
+ }
+}