aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build
diff options
context:
space:
mode:
authorGravatar corysmith <corysmith@google.com>2017-09-22 02:02:35 +0200
committerGravatar Damien Martin-Guillerez <dmarting@google.com>2017-09-22 12:16:02 +0200
commit54c86b4c6f29f4b0d52e1db702d30c10f3ac8b56 (patch)
tree08141e8298a2eadae818e7d8cce1198280e3c1be /src/tools/android/java/com/google/devtools/build
parent87c70eef9cae2c2bb652ecc0baa16adb90a21eab (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')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java52
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/Aapt2ResourceShrinkingAction.java128
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AaptCommandBuilder.java28
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java4
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceOutputs.java27
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java9
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java8
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ResourceShrinkerAction.java7
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ResourcesZip.java184
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ScopedTemporaryDirectory.java8
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/aapt2/CompiledResources.java29
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/aapt2/PackagedResources.java65
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java23
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceLinker.java28
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/aapt2/StaticLibrary.java2
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());