From 54c86b4c6f29f4b0d52e1db702d30c10f3ac8b56 Mon Sep 17 00:00:00 2001 From: corysmith Date: Fri, 22 Sep 2017 02:02:35 +0200 Subject: Action for resource shrinking with aapt2 Introduces the ResourcesZip class to more easily handle processing merged resources. RELNOTES: None PiperOrigin-RevId: 169622715 --- .../devtools/build/android/ResourcesZip.java | 184 +++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 src/tools/android/java/com/google/devtools/build/android/ResourcesZip.java (limited to 'src/tools/android/java/com/google/devtools/build/android/ResourcesZip.java') diff --git a/src/tools/android/java/com/google/devtools/build/android/ResourcesZip.java b/src/tools/android/java/com/google/devtools/build/android/ResourcesZip.java new file mode 100644 index 0000000000..e5436ad388 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/ResourcesZip.java @@ -0,0 +1,184 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.android; + +import static com.google.common.base.Predicates.not; + +import com.android.build.gradle.tasks.ResourceUsageAnalyzer; +import com.google.common.collect.ImmutableList; +import com.google.common.io.ByteStreams; +import com.google.devtools.build.android.AndroidResourceOutputs.ZipBuilder; +import com.google.devtools.build.android.AndroidResourceOutputs.ZipBuilderVisitor; +import com.google.devtools.build.android.aapt2.CompiledResources; +import com.google.devtools.build.android.aapt2.ResourceCompiler; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import javax.annotation.Nullable; +import javax.xml.parsers.ParserConfigurationException; +import org.xml.sax.SAXException; + +/** Represents a collection of raw, merged resources with an optional id list. */ +public class ResourcesZip { + + private final Path resourcesRoot; + private final Path assetsRoot; + private final Optional ids; + + private ResourcesZip(Path resourcesRoot, Path assetsRoot, Optional ids) { + this.resourcesRoot = resourcesRoot; + this.assetsRoot = assetsRoot; + this.ids = ids; + } + + /** + * @param resourcesRoot The root of the raw resources. + * @param assetsRoot The root of the raw assets. + */ + public static ResourcesZip from(Path resourcesRoot, Path assetsRoot) { + return new ResourcesZip(resourcesRoot, assetsRoot, Optional.empty()); + } + + /** + * @param resourcesRoot The root of the raw resources. + * @param assetsRoot The root of the raw assets. + * @param resourceIds Optional path to a file containing the resource ids. + */ + public static ResourcesZip from(Path resourcesRoot, Path assetsRoot, Path resourceIds) { + return new ResourcesZip( + resourcesRoot, assetsRoot, Optional.of(resourceIds).filter(Files::exists)); + } + + /** Creates a ResourcesZip from an archive by expanding into the workingDirectory. */ + public static ResourcesZip createFrom(Path resourcesZip, Path workingDirectory) + throws IOException { + // Expand resource files zip into working directory. + final ZipFile zipFile = new ZipFile(resourcesZip.toFile()); + + zipFile + .stream() + .filter(not(ZipEntry::isDirectory)) + .forEach( + entry -> { + Path output = workingDirectory.resolve(entry.getName()); + try { + Files.createDirectories(output.getParent()); + try (FileOutputStream fos = new FileOutputStream(output.toFile())) { + ByteStreams.copy(zipFile.getInputStream(entry), fos); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + return from( + Files.createDirectories(workingDirectory.resolve("res")), + Files.createDirectories(workingDirectory.resolve("assets")), + workingDirectory.resolve("ids.txt")); + } + + /** + * Creates a zip file containing the provided android resources and assets. + * + * @param output The path to write the zip file + * @param compress Whether or not to compress the content + * @throws IOException + */ + public void writeTo(Path output, boolean compress) throws IOException { + try (final ZipBuilder zip = ZipBuilder.createFor(output)) { + if (Files.exists(resourcesRoot)) { + ZipBuilderVisitor visitor = new ZipBuilderVisitor(zip, resourcesRoot, "res"); + visitor.setCompress(compress); + Files.walkFileTree(resourcesRoot, visitor); + if (!Files.exists(resourcesRoot.resolve("values/public.xml"))) { + // add an empty public xml, if one doesn't exist. The ResourceUsageAnalyzer expects one. + visitor.addEntry( + resourcesRoot.resolve("values").resolve("public.xml"), + "".getBytes(StandardCharsets.UTF_8)); + } + visitor.writeEntries(); + } + if (Files.exists(assetsRoot)) { + ZipBuilderVisitor visitor = new ZipBuilderVisitor(zip, assetsRoot, "assets"); + visitor.setCompress(compress); + Files.walkFileTree(assetsRoot, visitor); + visitor.writeEntries(); + } + + ids.ifPresent( + p -> { + try { + zip.addEntry("ids.txt", Files.readAllBytes(p), ZipEntry.STORED); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + } + + /** Removes unused resources from the archived resources. */ + public ShrunkResources shrink( + Set packages, + Path rTxt, + Path classJar, + Path manifest, + @Nullable Path proguardMapping, + Path logFile, + Path workingDirectory) + throws ParserConfigurationException, IOException, SAXException { + + new ResourceUsageAnalyzer( + packages, rTxt, classJar, manifest, proguardMapping, resourcesRoot, logFile) + .shrink(workingDirectory); + return ShrunkResources.of( + new ResourcesZip(workingDirectory, assetsRoot, ids), + new UnvalidatedAndroidData( + ImmutableList.of(workingDirectory), ImmutableList.of(assetsRoot), manifest)); + } + + static class ShrunkResources { + + private ResourcesZip resourcesZip; + private UnvalidatedAndroidData unvalidatedAndroidData; + + private ShrunkResources( + ResourcesZip resourcesZip, UnvalidatedAndroidData unvalidatedAndroidData) { + this.resourcesZip = resourcesZip; + this.unvalidatedAndroidData = unvalidatedAndroidData; + } + + public static ShrunkResources of( + ResourcesZip resourcesZip, UnvalidatedAndroidData unvalidatedAndroidData) { + return new ShrunkResources(resourcesZip, unvalidatedAndroidData); + } + + public ShrunkResources writeArchiveTo(Path archivePath, boolean compress) throws IOException { + resourcesZip.writeTo(archivePath, compress); + return this; + } + + public CompiledResources compile(ResourceCompiler compiler, Path workingDirectory) + throws InterruptedException, ExecutionException, IOException { + return unvalidatedAndroidData + .compile(compiler, workingDirectory) + .addStableIds(resourcesZip.ids); + } + } +} -- cgit v1.2.3