Ensure tracing of config specific xml files
The current version only searches the last added configuration.
Change-Id: Icd8e8b517dfe52f409fabf594660552f242b2012
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 7874b5c..7a82e3f 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
@@ -37,6 +37,8 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -48,8 +50,8 @@
public class LegacyResourceShrinker {
private final Map<String, byte[]> dexInputs;
- private final List<PathAndBytes> resFolderInputs;
- private final List<PathAndBytes> xmlInputs;
+ private final Collection<PathAndBytes> resFolderInputs;
+ private final Collection<PathAndBytes> xmlInputs;
private List<String> proguardMapStrings;
private final List<PathAndBytes> manifest;
private final Map<PathAndBytes, FeatureSplit> resourceTables;
@@ -57,8 +59,8 @@
public static class Builder {
private final Map<String, byte[]> dexInputs = new HashMap<>();
- private final List<PathAndBytes> resFolderInputs = new ArrayList<>();
- private final List<PathAndBytes> xmlInputs = new ArrayList<>();
+ private final Map<Path, PathAndBytes> resFolderInputs = new HashMap<>();
+ private final Map<Path, PathAndBytes> xmlInputs = new HashMap<>();
private final List<PathAndBytes> manifests = new ArrayList<>();
private final Map<PathAndBytes, FeatureSplit> resourceTables = new HashMap<>();
@@ -88,19 +90,34 @@
}
public Builder addResFolderInput(Path path, byte[] bytes) {
- resFolderInputs.add(new PathAndBytes(bytes, path));
+ PathAndBytes existing = resFolderInputs.get(path);
+ if (existing != null) {
+ assert Arrays.equals(existing.getBytes(), bytes);
+ } else {
+ resFolderInputs.put(path, new PathAndBytes(bytes, path));
+ }
return this;
}
public Builder addXmlInput(Path path, byte[] bytes) {
- xmlInputs.add(new PathAndBytes(bytes, path));
+ PathAndBytes existing = xmlInputs.get(path);
+ if (existing != null) {
+ assert Arrays.equals(existing.getBytes(), bytes);
+ } else {
+ xmlInputs.put(path, new PathAndBytes(bytes, path));
+ }
return this;
}
public LegacyResourceShrinker build() {
assert manifests != null && resourceTables != null;
return new LegacyResourceShrinker(
- dexInputs, resFolderInputs, manifests, resourceTables, xmlInputs, proguardMapStrings);
+ dexInputs,
+ resFolderInputs.values(),
+ manifests,
+ resourceTables,
+ xmlInputs.values(),
+ proguardMapStrings);
}
public void setProguardMapStrings(List<String> proguardMapStrings) {
@@ -110,10 +127,10 @@
private LegacyResourceShrinker(
Map<String, byte[]> dexInputs,
- List<PathAndBytes> resFolderInputs,
+ Collection<PathAndBytes> resFolderInputs,
List<PathAndBytes> manifests,
Map<PathAndBytes, FeatureSplit> resourceTables,
- List<PathAndBytes> xmlInputs,
+ Collection<PathAndBytes> xmlInputs,
List<String> proguardMapStrings) {
this.dexInputs = dexInputs;
this.resFolderInputs = resFolderInputs;
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 f289319..49f3546 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/R8ResourceShrinkerState.java
@@ -59,7 +59,7 @@
private final Map<String, Supplier<InputStream>> resfileProviders = new HashMap<>();
private final Map<ResourceTable, FeatureSplit> resourceTables = new HashMap<>();
private ClassReferenceCallback enqueuerCallback;
- private Map<Integer, String> resourceIdToXmlFiles;
+ private Map<Integer, List<String>> resourceIdToXmlFiles;
private Set<String> packageNames;
private final Set<String> seenNoneClassValues = new HashSet<>();
private final Set<Integer> seenResourceIds = new HashSet<>();
@@ -208,10 +208,12 @@
}
private void traceXmlForResourceId(int id) {
- String xmlFile = getResourceIdToXmlFiles().get(id);
- if (xmlFile != null) {
- InputStream inputStream = xmlFileProviders.get(xmlFile).get();
- traceXml(xmlFile, inputStream);
+ List<String> xmlFiles = getResourceIdToXmlFiles().get(id);
+ if (xmlFiles != null) {
+ for (String xmlFile : xmlFiles) {
+ InputStream inputStream = xmlFileProviders.get(xmlFile).get();
+ traceXml(xmlFile, inputStream);
+ }
}
}
@@ -255,7 +257,7 @@
element.getChildList().forEach(e -> visitNode(e, xmlName));
}
- public Map<Integer, String> getResourceIdToXmlFiles() {
+ public Map<Integer, List<String>> getResourceIdToXmlFiles() {
if (resourceIdToXmlFiles == null) {
resourceIdToXmlFiles = new HashMap<>();
for (ResourceTable resourceTable : resourceTables.keySet()) {
@@ -271,7 +273,9 @@
FileReference file = item.getFile();
if (file.getType() == FileReference.Type.PROTO_XML) {
int id = ResourceTableUtilKt.toIdentifier(packageEntry, type, entry);
- resourceIdToXmlFiles.put(id, file.getPath());
+ resourceIdToXmlFiles
+ .computeIfAbsent(id, unused -> new ArrayList<>())
+ .add(file.getPath());
}
}
}
diff --git a/src/test/java/com/android/tools/r8/androidresources/MultiConfigXmlFilesTest.java b/src/test/java/com/android/tools/r8/androidresources/MultiConfigXmlFilesTest.java
new file mode 100644
index 0000000..c8ebee4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidresources/MultiConfigXmlFilesTest.java
@@ -0,0 +1,90 @@
+// 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.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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 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 MultiConfigXmlFilesTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withDefaultDexRuntime().withAllApiLevels().build();
+ }
+
+ public static String VIEW_WITH_CLASS_ATTRIBUTE_REFERENCE =
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" class=\"%s\"/>\n";
+
+ public AndroidTestResource getTestResources(TemporaryFolder temp) throws Exception {
+ return new AndroidTestResourceBuilder()
+ .withSimpleManifestAndAppNameString()
+ .addRClassInitializeWithDefaultValues(R.xml.class)
+ // We add two configurations of the xml file, one for default and one for -v24.
+ // They reference different classes, both of which we should keep.
+ .addXml(
+ "xml_with_class_reference.xml",
+ String.format(VIEW_WITH_CLASS_ATTRIBUTE_REFERENCE, Foo.class.getTypeName()))
+ .addApiSpecificXml(
+ "xml_with_class_reference.xml",
+ String.format(VIEW_WITH_CLASS_ATTRIBUTE_REFERENCE, Bar.class.getTypeName()))
+ .build(temp);
+ }
+
+ @Test
+ public void testClassReferences() throws Exception {
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters)
+ .addProgramClasses(TestClass.class, Foo.class, Bar.class)
+ .addAndroidResources(getTestResources(temp))
+ .addKeepMainRule(TestClass.class)
+ .enableOptimizedShrinking()
+ .compile()
+ .inspectShrunkenResources(
+ resourceTableInspector -> {
+ resourceTableInspector.assertContainsResourceWithName(
+ "xml", "xml_with_class_reference");
+ })
+ .run(parameters.getRuntime(), TestClass.class)
+ .inspect(
+ codeInspector -> {
+ assertThat(codeInspector.clazz(Foo.class), isPresent());
+ assertThat(codeInspector.clazz(Bar.class), isPresent());
+ })
+ .assertSuccess();
+ }
+
+ public static class TestClass {
+ public static void main(String[] args) {
+ System.out.println(R.xml.xml_with_class_reference);
+ }
+ }
+
+ // Only referenced from XML file
+ public static class Bar {}
+
+ // Only referenced from XML file
+ public static class Foo {}
+
+ public static class R {
+ public static class xml {
+ public static int xml_with_class_reference;
+ }
+ }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java b/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
index eb5b8e2..80333c3 100644
--- a/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
+++ b/src/test/testbase/java/com/android/tools/r8/androidresources/AndroidResourceTestingUtils.java
@@ -293,6 +293,8 @@
private final Map<String, Integer> styleables = new TreeMap<>();
private final Map<String, byte[]> drawables = new TreeMap<>();
private final Map<String, String> xmlFiles = new TreeMap<>();
+ // Used for xml files that we hard code to -v24
+ private final Map<String, String> apiSpecificXmlFiles = new TreeMap<>();
private final Map<String, String> rawXmlFiles = new TreeMap<>();
private final List<Class<?>> classesToRemap = new ArrayList<>();
private int packageId = 0x7f;
@@ -304,8 +306,15 @@
// These R classes will be used to rewrite the namespace and class names on the aapt2
// generated names.
public AndroidTestResourceBuilder addRClassInitializeWithDefaultValues(Class<?>... rClasses) {
+ return addRClassInitializeWithDefaultValues(true, rClasses);
+ }
+
+ public AndroidTestResourceBuilder addRClassInitializeWithDefaultValues(
+ boolean addRClass, Class<?>... rClasses) {
for (Class<?> rClass : rClasses) {
- classesToRemap.add(rClass);
+ if (addRClass) {
+ classesToRemap.add(rClass);
+ }
RClassType rClassType = RClassType.fromClass(rClass);
for (Field declaredField : rClass.getDeclaredFields()) {
String name = declaredField.getName();
@@ -364,6 +373,11 @@
return this;
}
+ public AndroidTestResourceBuilder addApiSpecificXml(String name, String content) {
+ apiSpecificXmlFiles.put(name, content);
+ return this;
+ }
+
public AndroidTestResourceBuilder addKeepXmlFor(String... resourceReferences) {
addRawXml("keep.xml", String.format(KEEP_XML, String.join(",", resourceReferences)));
return this;
@@ -435,6 +449,13 @@
}
}
+ if (apiSpecificXmlFiles.size() > 0) {
+ File xmlFolder = temp.newFolder("res", "xml-v24");
+ for (Entry<String, String> entry : apiSpecificXmlFiles.entrySet()) {
+ FileUtils.writeTextFile(xmlFolder.toPath().resolve(entry.getKey()), entry.getValue());
+ }
+ }
+
if (rawXmlFiles.size() > 0) {
File rawXmlFolder = temp.newFolder("res", "raw");
for (Entry<String, String> entry : rawXmlFiles.entrySet()) {