From 9b9904aa3c97f9f2aa72a23d928770e0a141c983 Mon Sep 17 00:00:00 2001 From: corysmith Date: Mon, 13 Aug 2018 13:03:01 -0700 Subject: Correctly manage the ProtoApk lifecycle by ensuring it is closed when finished. Move finding used resources to ProtoResourceUsageAnalyzer for correctness and memory improvements. New report format: now records the kept resource along with the resource that keep it, as well maintaining the root status for each resource. Example line: {number/foo[isRoot: false] = 0x07f...} => [{array/foos[isRoot: true] = 0x...}...] RELNOTES: None PiperOrigin-RevId: 208529350 --- .../android/aapt2/ProtoResourceUsageAnalyzer.java | 88 ++++++++++++++++------ 1 file changed, 64 insertions(+), 24 deletions(-) (limited to 'src/tools/android/java/com/google/devtools/build/android/aapt2/ProtoResourceUsageAnalyzer.java') diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/ProtoResourceUsageAnalyzer.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/ProtoResourceUsageAnalyzer.java index 171bbf3791..a1cd448827 100644 --- a/src/tools/android/java/com/google/devtools/build/android/aapt2/ProtoResourceUsageAnalyzer.java +++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/ProtoResourceUsageAnalyzer.java @@ -15,6 +15,7 @@ package com.google.devtools.build.android.aapt2; import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; import com.android.build.gradle.tasks.ResourceUsageAnalyzer; import com.android.resources.ResourceType; @@ -24,7 +25,9 @@ import com.android.tools.lint.detector.api.LintUtils; import com.android.utils.XmlUtils; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; +import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; import com.google.devtools.build.android.aapt2.ProtoApk.ManifestVisitor; import com.google.devtools.build.android.aapt2.ProtoApk.ReferenceVisitor; import com.google.devtools.build.android.aapt2.ProtoApk.ResourcePackageVisitor; @@ -34,11 +37,16 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.ArrayDeque; import java.util.Collection; +import java.util.Deque; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Set; import java.util.logging.Logger; +import javax.annotation.CheckReturnValue; +import javax.annotation.Nullable; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Attr; import org.w3c.dom.DOMException; @@ -48,6 +56,8 @@ import org.xml.sax.SAXException; /** A resource usage analyzer tha functions on apks in protocol buffer format. */ public class ProtoResourceUsageAnalyzer extends ResourceUsageAnalyzer { + private static final Logger logger = Logger.getLogger(ProtoResourceUsageAnalyzer.class.getName()); + public ProtoResourceUsageAnalyzer(Set resourcePackages, Path mapping, Path logFile) throws DOMException, ParserConfigurationException { super(resourcePackages, null, null, null, mapping, null, logFile); @@ -72,7 +82,8 @@ public class ProtoResourceUsageAnalyzer extends ResourceUsageAnalyzer { * @param keep A list of resource urls to keep, unused or not. * @param discard A list of resource urls to always discard. */ - public void shrink( + @CheckReturnValue + public ProtoApk shrink( ProtoApk apk, Path classes, Path destination, @@ -107,30 +118,59 @@ public class ProtoResourceUsageAnalyzer extends ResourceUsageAnalyzer { keepPossiblyReferencedResources(); - dumpReferences(); - - // Remove unused. - final ImmutableSet unused = ImmutableSet.copyOf(model().findUnused()); + final List resources = model().getResources(); - // ResourceUsageAnalyzer uses the logger to generate the report. - Logger logger = Logger.getLogger(getClass().getName()); - unused.forEach( - resource -> - logger.fine( - "Deleted unused file " - + ((resource.locations != null && resource.locations.getFile() != null) - ? resource.locations.getFile().toString() - : "" + " for resource " + resource))); + List roots = + resources.stream().filter(r -> r.isKeep() || r.isReachable()).collect(toList()); - apk.copy( + final Set reachable = findReachableResources(roots); + return apk.copy( destination, - (resourceType, name) -> - !unused.contains( - Preconditions.checkNotNull( - model().getResource(resourceType, name), - "%s/%s was not declared but is copied!", - resourceType, - name))); + (resourceType, name) -> reachable.contains(model().getResource(resourceType, name))); + } + + private Set findReachableResources(List roots) { + final Multimap referenceLog = HashMultimap.create(); + Deque queue = new ArrayDeque<>(roots); + final Set reachable = new HashSet<>(); + while (!queue.isEmpty()) { + Resource resource = queue.pop(); + if (resource.references != null) { + resource.references.forEach( + r -> { + referenceLog.put(r, resource); + queue.add(r); + }); + } + // if we see it, it is reachable. + reachable.add(resource); + } + + // dump resource reference map: + final StringBuilder keptResourceLog = new StringBuilder(); + referenceLog + .asMap() + .forEach( + (resource, referencesTo) -> + keptResourceLog + .append(printResource(resource)) + .append(" => [") + .append( + referencesTo + .stream() + .map(ProtoResourceUsageAnalyzer::printResource) + .collect(joining(", "))) + .append("]\n")); + + logger.fine("Kept resource references:\n" + keptResourceLog); + + return reachable; + } + + private static String printResource(Resource res) { + return String.format( + "{%s[isRoot: %s] = %s}", + res.getUrl(), res.isReachable() || res.isKeep(), "0x" + Integer.toHexString(res.value)); } private static final class ResourceDeclarationVisitor implements ResourceVisitor { @@ -138,11 +178,11 @@ public class ProtoResourceUsageAnalyzer extends ResourceUsageAnalyzer { private final ResourceShrinkerUsageModel model; private final Set packageIds = new HashSet<>(); - public ResourceDeclarationVisitor(ResourceShrinkerUsageModel model) { + private ResourceDeclarationVisitor(ResourceShrinkerUsageModel model) { this.model = model; } - @javax.annotation.Nullable + @Nullable @Override public ManifestVisitor enteringManifest() { return null; -- cgit v1.2.3