Ensure that we keep resource table entries for overlapping res entries
This solves the issue that when we have
base: res/xml/foo.xml
and
feature: res/xml/foo.xml
we will keep both files when writing, even if only one of these are
reachable through a resource table entry (in either base or feature)
This ensures that we keep the resource table entry.
Bug: b/384905036
Change-Id: I7d16826ad522664e5d98c000e9e9df64378a0484
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 6cbc8c9..a64a3fd 100644
--- a/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
+++ b/src/resourceshrinker/java/com/android/build/shrinker/r8integration/LegacyResourceShrinker.java
@@ -101,7 +101,7 @@
public Builder addResFolderInput(String path, byte[] bytes) {
PathAndBytes existing = resFolderInputs.get(path);
if (existing != null) {
- assert Arrays.equals(existing.getBytes(), bytes);
+ existing.setDuplicated(true);
} else {
resFolderInputs.put(path, new PathAndBytes(bytes, path));
}
@@ -111,7 +111,7 @@
public Builder addXmlInput(String path, byte[] bytes) {
PathAndBytes existing = xmlInputs.get(path);
if (existing != null) {
- assert Arrays.equals(existing.getBytes(), bytes);
+ existing.setDuplicated(true);
} else {
xmlInputs.put(path, new PathAndBytes(bytes, path));
}
@@ -217,14 +217,20 @@
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<>();
+ ImmutableSet.Builder<String> resEntriesToKeepBuilder = new ImmutableSet.Builder<>();
for (PathAndBytes xmlInput : Iterables.concat(xmlInputs, resFolderInputs)) {
if (ResourceShrinkerImplKt.isJarPathReachable(resourceStore, xmlInput.path.toString())) {
- resEntriesToKeep.add(xmlInput.path.toString());
+ resEntriesToKeepBuilder.add(xmlInput.path.toString());
+ if (xmlInput.duplicated) {
+ // Ensure that we don't remove references to duplicated res folder entries.
+ List<Resource> duplicatedResources =
+ ResourceShrinkerImplKt.getResourcesFor(resourceStore, xmlInput.path.toString());
+ unusedResources.removeAll(duplicatedResources);
+ }
}
}
+ debugReporter.debug(() -> "Unused resources are: ");
+ unusedResources.forEach(unused -> debugReporter.debug(() -> " " + unused));
List<Integer> resourceIdsToRemove = getResourceIdsToRemove(unusedResources);
Map<FeatureSplit, ResourceTable> shrunkenTables = new HashMap<>();
for (Entry<PathAndBytes, FeatureSplit> entry : resourceTables.entrySet()) {
@@ -233,7 +239,7 @@
ResourceTable.parseFrom(entry.getKey().bytes), resourceIdsToRemove);
shrunkenTables.put(entry.getValue(), shrunkenResourceTable);
}
- return new ShrinkerResult(resEntriesToKeep.build(), shrunkenTables);
+ return new ShrinkerResult(resEntriesToKeepBuilder.build(), shrunkenTables);
}
private static List<Integer> getResourceIdsToRemove(List<Resource> unusedResources) {
@@ -331,12 +337,17 @@
private static class PathAndBytes {
private final byte[] bytes;
private final String path;
+ private boolean duplicated;
private PathAndBytes(byte[] bytes, String path) {
this.bytes = bytes;
this.path = path;
}
+ public void setDuplicated(boolean duplicated) {
+ this.duplicated = duplicated;
+ }
+
public String getPathWithoutRes() {
assert path.startsWith("res/");
return path.substring(4);