diff options
author | corysmith <corysmith@google.com> | 2017-09-22 02:02:35 +0200 |
---|---|---|
committer | Damien Martin-Guillerez <dmarting@google.com> | 2017-09-22 12:16:02 +0200 |
commit | 54c86b4c6f29f4b0d52e1db702d30c10f3ac8b56 (patch) | |
tree | 08141e8298a2eadae818e7d8cce1198280e3c1be /src/tools/android/java/com/google/devtools/build | |
parent | 87c70eef9cae2c2bb652ecc0baa16adb90a21eab (diff) |
Action for resource shrinking with aapt2
Introduces the ResourcesZip class to more easily handle processing merged resources.
RELNOTES: None
PiperOrigin-RevId: 169622715
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build')
15 files changed, 493 insertions, 109 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java b/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java index 4b53f88423..2ddcdc9acc 100644 --- a/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java @@ -23,6 +23,7 @@ import com.google.devtools.build.android.AndroidResourceMerger.MergingException; import com.google.devtools.build.android.AndroidResourceProcessingAction.Options; import com.google.devtools.build.android.aapt2.Aapt2ConfigOptions; import com.google.devtools.build.android.aapt2.CompiledResources; +import com.google.devtools.build.android.aapt2.PackagedResources; import com.google.devtools.build.android.aapt2.ResourceCompiler; import com.google.devtools.build.android.aapt2.ResourceLinker; import com.google.devtools.build.android.aapt2.StaticLibrary; @@ -84,13 +85,9 @@ public class Aapt2ResourcePackagingAction { final Path densityManifest = tmp.resolve("manifest-filtered/AndroidManifest.xml"); final Path processedManifest = tmp.resolve("manifest-processed/AndroidManifest.xml"); - final Path dummyManifest = tmp.resolve("manifest-aapt-dummy/AndroidManifest.xml"); final Path databindingResourcesRoot = Files.createDirectories(tmp.resolve("android_data_binding_resources")); - final Path databindingMetaData = - Files.createDirectories(tmp.resolve("android_data_binding_metadata")); final Path compiledResources = Files.createDirectories(tmp.resolve("compiled")); - final Path staticLinkedOut = Files.createDirectories(tmp.resolve("static-linked")); final Path linkedOut = Files.createDirectories(tmp.resolve("linked")); profiler.recordEndOf("setup").startTask("merging"); @@ -157,30 +154,31 @@ public class Aapt2ResourcePackagingAction { .map(DependencyAndroidData::getStaticLibrary) .collect(toList()); - ResourceLinker.create(aaptConfigOptions.aapt2, linkedOut) - .profileUsing(profiler) - .dependencies(ImmutableList.of(StaticLibrary.from(aaptConfigOptions.androidJar))) - .include(dependencies) - .buildVersion(aaptConfigOptions.buildToolsVersion) - .filterToDensity(densitiesToFilter) - .link(compiled) - .copyPackageTo(options.packagePath) - .copyProguardTo(options.proguardOutput) - .copyMainDexProguardTo(options.mainDexProguardOutput) - .createSourceJar(options.srcJarOutput) - .copyRTxtTo(options.rOutput); + final PackagedResources packagedResources = + ResourceLinker.create(aaptConfigOptions.aapt2, linkedOut) + .profileUsing(profiler) + .dependencies(ImmutableList.of(StaticLibrary.from(aaptConfigOptions.androidJar))) + .include(dependencies) + .buildVersion(aaptConfigOptions.buildToolsVersion) + .filterToDensity(densitiesToFilter) + .link(compiled) + .copyPackageTo(options.packagePath) + .copyProguardTo(options.proguardOutput) + .copyMainDexProguardTo(options.mainDexProguardOutput) + .createSourceJar(options.srcJarOutput) + .copyRTxtTo(options.rOutput); profiler.recordEndOf("link"); - } - if (options.resourcesOutput != null) { - profiler.startTask("package"); - // The compiled resources and the merged resources should be the same. - // TODO(corysmith): Decompile or otherwise provide the exact resources in the apk. - AndroidResourceOutputs.createResourcesZip( - mergedAndroidData.getResourceDir(), - mergedAndroidData.getAssetDir(), - options.resourcesOutput, - false /* compress */); - profiler.recordEndOf("package"); + if (options.resourcesOutput != null) { + profiler.startTask("package"); + // The compiled resources and the merged resources should be the same. + // TODO(corysmith): Decompile or otherwise provide the exact resources in the apk. + ResourcesZip.from( + mergedAndroidData.getResourceDir(), + mergedAndroidData.getAssetDir(), + packagedResources.resourceIds()) + .writeTo(options.resourcesOutput, false /* compress */); + profiler.recordEndOf("package"); + } } } catch (MergingException e) { logger.severe("Merging exception: " + e.getMessage()); diff --git a/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourceShrinkingAction.java b/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourceShrinkingAction.java new file mode 100644 index 0000000000..2fbae70f40 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourceShrinkingAction.java @@ -0,0 +1,128 @@ +// 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 java.util.stream.Collectors.toSet; + +import com.android.builder.core.VariantConfiguration; +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.devtools.build.android.ResourceShrinkerAction.Options; +import com.google.devtools.build.android.aapt2.Aapt2ConfigOptions; +import com.google.devtools.build.android.aapt2.CompiledResources; +import com.google.devtools.build.android.aapt2.ResourceCompiler; +import com.google.devtools.build.android.aapt2.ResourceLinker; +import com.google.devtools.build.android.aapt2.StaticLibrary; +import com.google.devtools.common.options.OptionsParser; +import java.io.Closeable; +import java.io.File; +import java.nio.file.Path; +import java.util.concurrent.ExecutionException; +import java.util.function.Function; + +/** + * An action to perform resource shrinking using the Gradle resource shrinker. + * + * <pre> + * Example Usage: + * java/com/google/build/android/ResourceShrinkerAction + * --aapt2 path to sdk/aapt2 + * --annotationJar path to sdk/annotationJar + * --androidJar path to sdk/androidJar + * --shrunkJar path to proguard dead code removal jar + * --resources path to processed resources zip + * --rTxt path to processed resources R.txt + * --primaryManifest path to processed resources AndroidManifest.xml + * --dependencyManifest path to dependency library manifest (repeated flag) + * --shrunkResourceApk path to write shrunk ap_ + * --shrunkResources path to write shrunk resources zip + * </pre> + */ +public class Aapt2ResourceShrinkingAction { + + public static void main(String[] args) throws Exception { + final Profiler profiler = LoggingProfiler.createAndStart("shrink").startTask("flags"); + // Parse arguments. + OptionsParser optionsParser = + OptionsParser.newOptionsParser(Options.class, Aapt2ConfigOptions.class); + optionsParser.parseAndExitUponError(args); + Aapt2ConfigOptions aapt2ConfigOptions = optionsParser.getOptions(Aapt2ConfigOptions.class); + Options options = optionsParser.getOptions(Options.class); + options.dependencyManifests = + Converters.concatLists(options.dependencyManifests, options.deprecatedDependencyManifests); + profiler.recordEndOf("flags").startTask("setup"); + + final ListeningExecutorService executorService = ExecutorServiceCloser.createDefaultService(); + try (ScopedTemporaryDirectory scopedTmp = + new ScopedTemporaryDirectory("android_resources_tmp"); + Closeable closer = ExecutorServiceCloser.createWith(executorService)) { + + Path workingResourcesDirectory = scopedTmp.subDirectoryOf("resources"); + final ResourceCompiler resourceCompiler = + ResourceCompiler.create( + executorService, + workingResourcesDirectory, + aapt2ConfigOptions.aapt2, + aapt2ConfigOptions.buildToolsVersion); + profiler.recordEndOf("setup").startTask("compile"); + + final ResourcesZip resourcesZip = + ResourcesZip.createFrom( + options.resourcesZip, scopedTmp.subDirectoryOf("merged-resources")); + final CompiledResources compiled = + resourcesZip + .shrink( + options + .dependencyManifests + .stream() + .map(Path::toFile) + .map(manifestToPackageUsing(executorService)) + .map(futureToString()) + .collect(toSet()), + options.rTxt, + options.shrunkJar, + options.primaryManifest, + options.proguardMapping, + options.log, + scopedTmp.subDirectoryOf("shrunk-resources")) + .writeArchiveTo(options.shrunkResources, false) + .compile(resourceCompiler, workingResourcesDirectory); + profiler.recordEndOf("compile"); + + ResourceLinker.create(aapt2ConfigOptions.aapt2, scopedTmp.subDirectoryOf("linking")) + .profileUsing(profiler) + .dependencies(ImmutableList.of(StaticLibrary.from(aapt2ConfigOptions.androidJar))) + .link(compiled) + .copyPackageTo(options.shrunkApk) + .copyRTxtTo(options.rTxtOutput); + profiler.recordEndOf("shrink"); + } + } + + static Function<File, ListenableFuture<String>> manifestToPackageUsing( + ListeningExecutorService executor) { + return f -> executor.submit(() -> VariantConfiguration.getManifestPackage(f)); + } + + static Function<ListenableFuture<String>, String> futureToString() { + return f -> { + try { + return f.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + }; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/AaptCommandBuilder.java b/src/tools/android/java/com/google/devtools/build/android/AaptCommandBuilder.java index ad645a6fdd..f4b70fc930 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AaptCommandBuilder.java +++ b/src/tools/android/java/com/google/devtools/build/android/AaptCommandBuilder.java @@ -27,6 +27,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -164,6 +165,13 @@ public class AaptCommandBuilder { return flags.build(); } + public AaptCommandBuilder add(String flag, Optional<Path> optionalPath) { + Preconditions.checkNotNull(flag); + Preconditions.checkNotNull(optionalPath); + optionalPath.map(p -> add(flag, p)); + return this; + } + /** Wrapper for potentially adding flags to an AaptCommandBuilder based on a conditional. */ public interface ConditionalAaptCommandBuilder { /** @@ -190,6 +198,14 @@ public class AaptCommandBuilder { AaptCommandBuilder thenAdd(String flag, @Nullable Path value); /** + * Adds a single flag and associated path value to the builder if the value is non-null and the + * condition was true. + * + * @see AaptCommandBuilder#add(String,Optional) + */ + AaptCommandBuilder thenAdd(String flag, Optional<Path> value); + + /** * Adds the values in the collection to the builder, each preceded by the given flag, if the * collection was non-empty and the condition was true. * @@ -224,6 +240,11 @@ public class AaptCommandBuilder { } @Override + public AaptCommandBuilder thenAdd(String flag, @Nullable Optional<Path> value) { + return originalCommandBuilder.add(flag, value); + } + + @Override public AaptCommandBuilder thenAddRepeated(String flag, Collection<String> values) { return originalCommandBuilder.addRepeated(flag, values); } @@ -256,6 +277,13 @@ public class AaptCommandBuilder { } @Override + public AaptCommandBuilder thenAdd(String flag, Optional<Path> value) { + Preconditions.checkNotNull(flag); + Preconditions.checkNotNull(value); + return originalCommandBuilder; + } + + @Override public AaptCommandBuilder thenAddRepeated(String flag, Collection<String> values) { Preconditions.checkNotNull(flag); Preconditions.checkNotNull(values); diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java index 7beac4760a..78393dcc3b 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java @@ -280,8 +280,8 @@ public class AndroidResourceMergingAction { // For now, try compressing the library resources that we pass to the validator. This takes // extra CPU resources to pack and unpack (~2x), but can reduce the zip size (~4x). - AndroidResourceOutputs.createResourcesZip( - resourcesDir, mergedData.getAssetDir(), options.resourcesOutput, true /* compress */); + ResourcesZip.from(resourcesDir, mergedData.getAssetDir()) + .writeTo(options.resourcesOutput, true /* compress */); logger.fine( String.format( "Create resources.zip finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceOutputs.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceOutputs.java index 97c261610e..d6558ed437 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceOutputs.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceOutputs.java @@ -338,33 +338,6 @@ public class AndroidResourceOutputs { } } - /** - * Creates a zip file containing the provided android resources and assets. - * - * @param resourcesRoot The root containing android resources to be written. - * @param assetsRoot The root containing android assets to be written. - * @param output The path to write the zip file - * @param compress Whether or not to compress the content - * @throws IOException - */ - public static void createResourcesZip( - Path resourcesRoot, Path assetsRoot, 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); - visitor.writeEntries(); - } - if (Files.exists(assetsRoot)) { - ZipBuilderVisitor visitor = new ZipBuilderVisitor(zip, assetsRoot, "assets"); - visitor.setCompress(compress); - Files.walkFileTree(assetsRoot, visitor); - visitor.writeEntries(); - } - } - } - /** Creates a zip archive from all found R.java files. */ public static void createSrcJar(Path generatedSourcesRoot, Path srcJar, boolean staticIds) { try { diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java index 4380a0b69b..4ec9ebcf92 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java @@ -437,7 +437,7 @@ public class AndroidResourceProcessingAction { } if (options.packageType == VariantType.LIBRARY) { - resourceProcessor.writeDummyManifestForAapt(dummyManifest, options.packageForR); + AndroidResourceProcessor.writeDummyManifestForAapt(dummyManifest, options.packageForR); processedData = new MergedAndroidData( processedData.getResourceDir(), processedData.getAssetDir(), dummyManifest); @@ -488,11 +488,8 @@ public class AndroidResourceProcessingAction { generatedSources, options.rOutput, VariantType.LIBRARY == options.packageType); } if (options.resourcesOutput != null) { - AndroidResourceOutputs.createResourcesZip( - processedData.getResourceDir(), - processedData.getAssetDir(), - options.resourcesOutput, - false /* compress */); + ResourcesZip.from(processedData.getResourceDir(), processedData.getAssetDir()) + .writeTo(options.resourcesOutput, false /* compress */); } logger.fine( String.format("Packaging finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); diff --git a/src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java b/src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java index a5f9271696..317599989f 100644 --- a/src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java +++ b/src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java @@ -126,6 +126,12 @@ public class ResourceProcessorBusyBox { void call(String[] args) throws Exception { Aapt2ResourcePackagingAction.main(args); } + }, + SHRINK_AAPT2() { + @Override + void call(String[] args) throws Exception { + Aapt2ResourceShrinkingAction.main(args); + } }; abstract void call(String[] args) throws Exception; @@ -152,7 +158,7 @@ public class ResourceProcessorBusyBox { "The processing tool to execute. " + "Valid tools: PACKAGE, VALIDATE, GENERATE_BINARY_R, GENERATE_LIBRARY_R, PARSE, " + "MERGE, GENERATE_AAR, SHRINK, MERGE_MANIFEST, COMPILE_LIBRARY_RESOURCES, " - + "LINK_STATIC_LIBRARY, AAPT2_PACKAGE." + + "LINK_STATIC_LIBRARY, AAPT2_PACKAGE, SHRINK_AAPT2." ) public Tool tool; } diff --git a/src/tools/android/java/com/google/devtools/build/android/ResourceShrinkerAction.java b/src/tools/android/java/com/google/devtools/build/android/ResourceShrinkerAction.java index f8cd48722e..8e0a5995d3 100644 --- a/src/tools/android/java/com/google/devtools/build/android/ResourceShrinkerAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/ResourceShrinkerAction.java @@ -327,11 +327,8 @@ public class ResourceShrinkerAction { null /* publicResourcesOut */, null /* dataBindingInfoOut */); if (options.shrunkResources != null) { - AndroidResourceOutputs.createResourcesZip( - shrunkResources, - resourceFiles.resolve("assets"), - options.shrunkResources, - false /* compress */); + ResourcesZip.from(shrunkResources, resourceFiles.resolve("assets")) + .writeTo(options.shrunkResources, false /* compress */); } if (options.rTxtOutput != null) { AndroidResourceOutputs.copyRToOutput( 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<Path> ids; + + private ResourcesZip(Path resourcesRoot, Path assetsRoot, Optional<Path> 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"), + "<resources></resources>".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<String> 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); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/ScopedTemporaryDirectory.java b/src/tools/android/java/com/google/devtools/build/android/ScopedTemporaryDirectory.java index f7bcc88dfb..e9c1ca6a48 100644 --- a/src/tools/android/java/com/google/devtools/build/android/ScopedTemporaryDirectory.java +++ b/src/tools/android/java/com/google/devtools/build/android/ScopedTemporaryDirectory.java @@ -47,6 +47,14 @@ final class ScopedTemporaryDirectory extends SimpleFileVisitor<Path> implements return this.path; } + public Path subDirectoryOf(String... directories) throws IOException { + Path sub = this.path; + for (String directory : directories) { + sub = sub.resolve(directory); + } + return Files.createDirectories(sub); + } + private void makeWritable(Path file) throws IOException { FileStore fileStore = Files.getFileStore(file); if (IS_WINDOWS && fileStore.supportsFileAttributeView(DosFileAttributeView.class)) { diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/CompiledResources.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/CompiledResources.java index aa13dc35ce..21eb59dd15 100644 --- a/src/tools/android/java/com/google/devtools/build/android/aapt2/CompiledResources.java +++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/CompiledResources.java @@ -13,6 +13,7 @@ // limitations under the License. package com.google.devtools.build.android.aapt2; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.devtools.build.android.ManifestContainer; import java.io.IOException; @@ -34,21 +35,24 @@ public class CompiledResources implements ManifestContainer { private final Path resources; private final Path manifest; private final List<Path> assetsDirs; + private final Optional<Path> stableIds; - private CompiledResources(Path resources, Path manifest, List<Path> assetsDirs) { + private CompiledResources( + Path resources, Path manifest, List<Path> assetsDirs, Optional<Path> stableIds) { this.resources = resources; this.manifest = manifest; this.assetsDirs = assetsDirs; + this.stableIds = stableIds; } public static CompiledResources from(Path resources, Path manifest) { - return from(resources, manifest, null); + return from(resources, manifest, ImmutableList.of()); } public static CompiledResources from( Path resources, Path manifest, @Nullable List<Path> assetDirs) { return new CompiledResources( - resources, manifest, Optional.ofNullable(assetDirs).orElseGet(ImmutableList::of)); + resources, manifest, assetDirs != null ? assetDirs : ImmutableList.of(), Optional.empty()); } public Path getZip() { @@ -57,22 +61,37 @@ public class CompiledResources implements ManifestContainer { /** Copies resources archive to a path and returns the new {@link CompiledResources} */ public CompiledResources copyResourcesZipTo(Path destination) throws IOException { - return new CompiledResources(Files.copy(resources, destination), manifest, assetsDirs); + return new CompiledResources( + Files.copy(resources, destination), manifest, assetsDirs, stableIds); } @Override public Path getManifest() { + Preconditions.checkState(Files.exists(manifest)); return manifest; } public List<String> getAssetsStrings() { return assetsDirs .stream() + .map( + p -> { + Preconditions.checkArgument(Files.exists(p), "does not exist %s", p); + return p; + }) .map(Path::toString) .collect(Collectors.toList()); } public CompiledResources processManifest(Function<Path, Path> processManifest) { - return new CompiledResources(resources, processManifest.apply(manifest), assetsDirs); + return new CompiledResources(resources, processManifest.apply(manifest), assetsDirs, stableIds); + } + + public CompiledResources addStableIds(Optional<Path> stableIds) { + return new CompiledResources(resources, manifest, assetsDirs, stableIds); + } + + public Optional<Path> getStableIds() { + return stableIds; } } diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/PackagedResources.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/PackagedResources.java index 77c9c66cc6..6f77b5966e 100644 --- a/src/tools/android/java/com/google/devtools/build/android/aapt2/PackagedResources.java +++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/PackagedResources.java @@ -17,6 +17,7 @@ import com.google.devtools.build.android.AndroidResourceOutputs; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import javax.annotation.Nullable; /** Represents the packaged, flattened resources. */ public class PackagedResources { @@ -26,19 +27,43 @@ public class PackagedResources { private final Path proguardConfig; private final Path mainDexProguard; private final Path javaSourceDirectory; + private final Path resourceIds; private PackagedResources( - Path apk, Path rTxt, Path proguardConfig, Path mainDexProguard, Path javaSourceDirectory) { + Path apk, + Path rTxt, + Path proguardConfig, + Path mainDexProguard, + Path javaSourceDirectory, + Path resourceIds) { this.apk = apk; this.rTxt = rTxt; this.proguardConfig = proguardConfig; this.mainDexProguard = mainDexProguard; this.javaSourceDirectory = javaSourceDirectory; + this.resourceIds = resourceIds; } - public PackagedResources copyPackageTo(Path packagePath) throws IOException { + public static PackagedResources of( + Path outPath, + Path rTxt, + Path proguardConfig, + Path mainDexProguard, + Path javaSourceDirectory, + Path resourceIds) + throws IOException { return new PackagedResources( - copy(apk, packagePath), rTxt, proguardConfig, mainDexProguard, javaSourceDirectory); + outPath, rTxt, proguardConfig, mainDexProguard, javaSourceDirectory, resourceIds); + } + + public PackagedResources copyPackageTo(Path packagePath) throws IOException { + return of( + copy(apk, packagePath), + rTxt, + proguardConfig, + mainDexProguard, + javaSourceDirectory, + resourceIds); } public PackagedResources copyRTxtTo(Path rOutput) throws IOException { @@ -46,7 +71,12 @@ public class PackagedResources { return this; } return new PackagedResources( - apk, copy(rTxt, rOutput), proguardConfig, mainDexProguard, javaSourceDirectory); + apk, + copy(rTxt, rOutput), + proguardConfig, + mainDexProguard, + javaSourceDirectory, + resourceIds); } private Path copy(Path from, Path out) throws IOException { @@ -59,8 +89,13 @@ public class PackagedResources { if (proguardOut == null) { return this; } - return new PackagedResources( - apk, rTxt, copy(proguardConfig, proguardOut), mainDexProguard, javaSourceDirectory); + return of( + apk, + rTxt, + copy(proguardConfig, proguardOut), + mainDexProguard, + javaSourceDirectory, + resourceIds); } public PackagedResources copyMainDexProguardTo(Path mainDexProguardOut) throws IOException { @@ -68,21 +103,23 @@ public class PackagedResources { return this; } return of( - apk, rTxt, proguardConfig, copy(mainDexProguard, mainDexProguardOut), javaSourceDirectory); + apk, + rTxt, + proguardConfig, + copy(mainDexProguard, mainDexProguardOut), + javaSourceDirectory, + resourceIds); } - public PackagedResources createSourceJar(Path sourceJarPath) throws IOException { + public PackagedResources createSourceJar(@Nullable Path sourceJarPath) throws IOException { if (sourceJarPath == null) { return this; } AndroidResourceOutputs.createSrcJar(javaSourceDirectory, sourceJarPath, false); - return of(apk, rTxt, proguardConfig, mainDexProguard, sourceJarPath); + return of(apk, rTxt, proguardConfig, mainDexProguard, sourceJarPath, resourceIds); } - public static PackagedResources of( - Path outPath, Path rTxt, Path proguardConfig, Path mainDexProguard, Path javaSourceDirectory) - throws IOException { - return new PackagedResources( - outPath, rTxt, proguardConfig, mainDexProguard, javaSourceDirectory); + public Path resourceIds() { + return resourceIds; } } diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java index 6084f6710f..5f8b842859 100644 --- a/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java +++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java @@ -46,7 +46,7 @@ public class ResourceCompiler { private final Path aapt2; private final Revision buildToolsVersion; - public CompileTask( + private CompileTask( Path file, Path compiledResourcesOut, Path aapt2, Revision buildToolsVersion) { this.file = file; this.compiledResourcesOut = compiledResourcesOut; @@ -56,17 +56,16 @@ public class ResourceCompiler { @Override public Path call() throws Exception { - logger.fine( - new AaptCommandBuilder(aapt2) - .forBuildToolsVersion(buildToolsVersion) - .forVariantType(VariantType.LIBRARY) - .add("compile") - .add("-v") - .add("--legacy") - .add("-o", compiledResourcesOut.toString()) - .add(file.toString()) - .execute("Compiling " + file)); - + logger.fine( + new AaptCommandBuilder(aapt2) + .forBuildToolsVersion(buildToolsVersion) + .forVariantType(VariantType.LIBRARY) + .add("compile") + .add("-v") + .add("--legacy") + .add("-o", compiledResourcesOut.toString()) + .add(file.toString()) + .execute("Compiling " + file)); String type = file.getParent().getFileName().toString(); String filename = file.getFileName().toString(); diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceLinker.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceLinker.java index 8c26b5b8b8..e15ee6670f 100644 --- a/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceLinker.java +++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceLinker.java @@ -17,6 +17,7 @@ import com.android.builder.core.VariantType; import com.android.repository.Revision; import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; import com.google.devtools.build.android.AaptCommandBuilder; @@ -50,14 +51,15 @@ public class ResourceLinker { private final Path workingDirectory; private List<StaticLibrary> linkAgainst = ImmutableList.of(); + private Revision buildToolsVersion; - private List<String> densities; + private List<String> densities = ImmutableList.of(); private Path androidJar; private Profiler profiler = Profiler.empty(); private List<String> uncompressedExtensions = ImmutableList.of(); private List<String> resourceConfigs = ImmutableList.of(); private Path baseApk; - private List<StaticLibrary> include; + private List<StaticLibrary> include = ImmutableList.of(); private ResourceLinker(Path aapt2, Path workingDirectory) { this.aapt2 = aapt2; @@ -65,6 +67,7 @@ public class ResourceLinker { } public static ResourceLinker create(Path aapt2, Path workingDirectory) { + Preconditions.checkArgument(Files.exists(workingDirectory)); return new ResourceLinker(aapt2, workingDirectory); } @@ -142,13 +145,14 @@ public class ResourceLinker { } public PackagedResources link(CompiledResources compiled) { - final Path outPath = workingDirectory.resolve("bin.apk"); - Path rTxt = workingDirectory.resolve("R.txt"); - Path proguardConfig = workingDirectory.resolve("proguard.cfg"); - Path mainDexProguard = workingDirectory.resolve("proguard.maindex.cfg"); - Path javaSourceDirectory = workingDirectory.resolve("java"); - try { + final Path outPath = workingDirectory.resolve("bin.apk"); + Path rTxt = workingDirectory.resolve("R.txt"); + Path proguardConfig = workingDirectory.resolve("proguard.cfg"); + Path mainDexProguard = workingDirectory.resolve("proguard.maindex.cfg"); + Path javaSourceDirectory = Files.createDirectories(workingDirectory.resolve("java")); + Path resourceIds = workingDirectory.resolve("ids.txt"); + profiler.startTask("fulllink"); logger.finer( new AaptCommandBuilder(aapt2) @@ -157,13 +161,16 @@ public class ResourceLinker { .add("link") .whenVersionIsAtLeast(new Revision(23)) .thenAdd("--no-version-vectors") + // Turn off namespaced resources .add("--no-static-lib-packages") .when(Objects.equals(logger.getLevel(), Level.FINE)) .thenAdd("-v") .add("--manifest", compiled.getManifest()) + // Enables resource redefinition and merging .add("--auto-add-overlay") .when(densities.size() == 1) .thenAddRepeated("--preferred-density", densities) + .add("--stable-ids", compiled.getStableIds()) .addRepeated("-A", compiled.getAssetsStrings()) .addRepeated("-I", StaticLibrary.toPathStrings(linkAgainst)) .addRepeated("-R", StaticLibrary.toPathStrings(include)) @@ -178,6 +185,7 @@ public class ResourceLinker { .when(!resourceConfigs.isEmpty()) .thenAdd("-c", Joiner.on(',').join(resourceConfigs)) .add("--output-text-symbols", rTxt) + .add("--emit-ids", resourceIds) .add("--java", javaSourceDirectory) .add("--proguard", proguardConfig) .add("--proguard-main-dex", mainDexProguard) @@ -187,7 +195,7 @@ public class ResourceLinker { profiler.startTask("optimize"); if (densities.size() < 2) { return PackagedResources.of( - outPath, rTxt, proguardConfig, mainDexProguard, javaSourceDirectory); + outPath, rTxt, proguardConfig, mainDexProguard, javaSourceDirectory, resourceIds); } final Path optimized = workingDirectory.resolve("optimized.apk"); logger.finer( @@ -201,7 +209,7 @@ public class ResourceLinker { .execute(String.format("Optimizing %s", compiled.getManifest()))); profiler.recordEndOf("optimize"); return PackagedResources.of( - optimized, rTxt, proguardConfig, mainDexProguard, javaSourceDirectory); + optimized, rTxt, proguardConfig, mainDexProguard, javaSourceDirectory, resourceIds); } catch (IOException e) { throw new LinkError(e); } diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/StaticLibrary.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/StaticLibrary.java index 54ba743224..142d7fefb1 100644 --- a/src/tools/android/java/com/google/devtools/build/android/aapt2/StaticLibrary.java +++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/StaticLibrary.java @@ -16,6 +16,7 @@ package com.google.devtools.build.android.aapt2; import static com.google.common.collect.ImmutableList.toImmutableList; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import java.io.IOException; @@ -134,6 +135,7 @@ public class StaticLibrary { return libraries .stream() .map(StaticLibrary::asAssetPathStrings) + .filter(Predicates.isNull()) .flatMap(List::stream) .map(Object::toString) .collect(toImmutableList()); |