aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android
diff options
context:
space:
mode:
authorGravatar corysmith <corysmith@google.com>2017-08-10 20:34:02 +0200
committerGravatar Marcel Hlopko <hlopko@google.com>2017-08-11 12:56:18 +0200
commite7e7ba4d6f250899165bb8862fefa4e90a33da57 (patch)
tree675b78d5edc34b3ad6847dfe3fbf62d3c9d5a411 /src/tools/android
parenta664a5118e552504ba7962e1cccfea43b51ef28e (diff)
Adds aapt2.ResourceCompiler and CompileLibraryResourcesAction.
Refactorings: * Change data binding to have configurable archive generation * Extract a ZipBuilder class from the ZipBuilderVisitor to provide a general purpose archiving class. * Small changes to visibility AaptCommandLineBuilder for reuse in the aapt2 code. RELNOTES: None PiperOrigin-RevId: 164880571
Diffstat (limited to 'src/tools/android')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AaptCommandBuilder.java6
-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.java183
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java47
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/BUILD1
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/CompileLibraryResourcesAction.java211
-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/aapt2/ResourceCompiler.java159
8 files changed, 546 insertions, 73 deletions
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 df4f899d34..4bfe5cb10c 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,7 +27,7 @@ import javax.annotation.Nullable;
* Builder for AAPT command lines, with support for making flags conditional on build tools version
* and variant type.
*/
-class AaptCommandBuilder {
+public class AaptCommandBuilder {
private final ImmutableList.Builder<String> flags = new ImmutableList.Builder<>();
private Revision buildToolsVersion;
private VariantType variantType;
@@ -37,7 +37,7 @@ class AaptCommandBuilder {
}
/** Sets the build tools version to be used for {@link #whenVersionIsAtLeast}. */
- AaptCommandBuilder forBuildToolsVersion(@Nullable Revision buildToolsVersion) {
+ public AaptCommandBuilder forBuildToolsVersion(@Nullable Revision buildToolsVersion) {
Preconditions.checkState(
this.buildToolsVersion == null, "A build tools version was already specified.");
this.buildToolsVersion = buildToolsVersion;
@@ -45,7 +45,7 @@ class AaptCommandBuilder {
}
/** Sets the variant type to be used for {@link #whenVariantIs}. */
- AaptCommandBuilder forVariantType(VariantType variantType) {
+ public AaptCommandBuilder forVariantType(VariantType variantType) {
Preconditions.checkNotNull(variantType);
Preconditions.checkState(this.variantType == null, "A variant type was already specified.");
this.variantType = variantType;
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 935ffa0f3a..6840ac8cc4 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
@@ -265,11 +265,13 @@ public class AndroidResourceMergingAction {
if (options.resourcesOutput != null) {
Path resourcesDir =
AndroidResourceProcessor.processDataBindings(
+ tmp.resolve("res_no_binding"),
mergedData.getResourceDir(),
options.dataBindingInfoOut,
packageType,
options.packageForR,
- options.primaryManifest);
+ options.primaryManifest,
+ true);
// 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).
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 608309f556..3cad94c83f 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
@@ -16,9 +16,12 @@ package com.google.devtools.build.android;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
import com.google.common.collect.Ordering;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.FileVisitResult;
@@ -29,6 +32,8 @@ import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.GregorianCalendar;
+import java.util.List;
import java.util.Objects;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
@@ -42,10 +47,71 @@ import java.util.zip.ZipOutputStream;
/** Collects all the functionationality for an action to create the final output artifacts. */
public class AndroidResourceOutputs {
+ static class ZipBuilder implements Closeable {
+ // ZIP timestamps have a resolution of 2 seconds.
+ // see http://www.info-zip.org/FAQ.html#limits
+ private static final long MINIMUM_TIMESTAMP_INCREMENT = 2000L;
+
+ // The earliest date representable in a zip file, 1-1-1980 (the DOS epoch).
+ private static final long ZIP_EPOCH =
+ new GregorianCalendar(1980, 01, 01, 0, 0).getTimeInMillis();
+
+ private final ZipOutputStream zip;
+
+ private ZipBuilder(ZipOutputStream zip) {
+ this.zip = zip;
+ }
+
+ public static ZipBuilder createFor(Path archivePath) throws IOException {
+ return wrap(
+ new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(archivePath))));
+ }
+
+ public static ZipBuilder wrap(ZipOutputStream zip) {
+ return new ZipBuilder(zip);
+ }
+
+ /**
+ * Normalize timestamps for deterministic builds. Stamp .class files to be a bit newer than
+ * .java files. See: {@link
+ * com.google.devtools.build.buildjar.jarhelper.JarHelper#normalizedTimestamp(String)}
+ */
+ protected long normalizeTime(String filename) {
+ if (filename.endsWith(".class")) {
+ return ZIP_EPOCH + MINIMUM_TIMESTAMP_INCREMENT;
+ } else {
+ return ZIP_EPOCH;
+ }
+ }
+
+ protected void addEntry(String rawName, byte[] content, int storageMethod) throws IOException {
+ // Fix the path for windows.
+ String relativeName = rawName.replace('\\', '/');
+ // Make sure the zip entry is not absolute.
+ Preconditions.checkArgument(!relativeName.startsWith("/"));
+ ZipEntry entry = new ZipEntry(relativeName);
+ entry.setMethod(storageMethod);
+ entry.setTime(normalizeTime(relativeName));
+ entry.setSize(content.length);
+ CRC32 crc32 = new CRC32();
+ crc32.update(content);
+ entry.setCrc(crc32.getValue());
+
+ zip.putNextEntry(entry);
+ zip.write(content);
+ zip.closeEntry();
+ }
+
+ @Override
+ public void close() throws IOException {
+ zip.close();
+ }
+ }
+
/** A FileVisitor that will add all R class files to be stored in a zip archive. */
static final class ClassJarBuildingVisitor extends ZipBuilderVisitor {
- ClassJarBuildingVisitor(ZipOutputStream zip, Path root) {
+ ClassJarBuildingVisitor(ZipBuilder zip, Path root) {
super(zip, root, null);
}
@@ -89,8 +155,8 @@ public class AndroidResourceOutputs {
private final boolean staticIds;
- private SymbolFileSrcJarBuildingVisitor(ZipOutputStream zip, Path root, boolean staticIds) {
- super(zip, root, null);
+ private SymbolFileSrcJarBuildingVisitor(ZipBuilder zipBuilder, Path root, boolean staticIds) {
+ super(zipBuilder, root, null);
this.staticIds = staticIds;
}
@@ -139,51 +205,21 @@ public class AndroidResourceOutputs {
/** A FileVisitor that will add all files to be stored in a zip archive. */
static class ZipBuilderVisitor extends SimpleFileVisitor<Path> {
- // ZIP timestamps have a resolution of 2 seconds.
- // see http://www.info-zip.org/FAQ.html#limits
- private static final long MINIMUM_TIMESTAMP_INCREMENT = 2000L;
- // The earliest date representable in a zip file, 1-1-1980 (the DOS epoch).
- private static final long ZIP_EPOCH = 315561600000L;
-
- private final String directoryPrefix;
+ protected final String directoryPrefix;
private final Collection<Path> paths = new ArrayList<>();
protected final Path root;
private int storageMethod = ZipEntry.STORED;
- private final ZipOutputStream zip;
+ private ZipBuilder zipBuilder;
- ZipBuilderVisitor(ZipOutputStream zip, Path root, String directory) {
- this.zip = zip;
+ ZipBuilderVisitor(ZipBuilder zipBuilder, Path root, String directory) {
this.root = root;
- this.directoryPrefix = directory;
+ this.directoryPrefix = directory != null ? (directory + File.separator) : "";
+ this.zipBuilder = zipBuilder;
}
protected void addEntry(Path file, byte[] content) throws IOException {
- String prefix = directoryPrefix != null ? (directoryPrefix + "/") : "";
- String relativeName = root.relativize(file).toString();
- ZipEntry entry = new ZipEntry((prefix + relativeName).replace('\\', '/'));
- entry.setMethod(storageMethod);
- entry.setTime(normalizeTime(relativeName));
- entry.setSize(content.length);
- CRC32 crc32 = new CRC32();
- crc32.update(content);
- entry.setCrc(crc32.getValue());
-
- zip.putNextEntry(entry);
- zip.write(content);
- zip.closeEntry();
- }
-
- /**
- * Normalize timestamps for deterministic builds. Stamp .class files to be a bit newer than
- * .java files. See: {@link
- * com.google.devtools.build.buildjar.jarhelper.JarHelper#normalizedTimestamp(String)}
- */
- protected long normalizeTime(String filename) {
- if (filename.endsWith(".class")) {
- return ZIP_EPOCH + MINIMUM_TIMESTAMP_INCREMENT;
- } else {
- return ZIP_EPOCH;
- }
+ Preconditions.checkArgument(file.startsWith(root), "%s does not start with %s", file, root);
+ zipBuilder.addEntry(directoryPrefix + root.relativize(file), content, storageMethod);
}
public void setCompress(boolean compress) {
@@ -270,10 +306,8 @@ public class AndroidResourceOutputs {
public static void createClassJar(Path generatedClassesRoot, Path classJar) {
try {
Files.createDirectories(classJar.getParent());
- try (final ZipOutputStream zip =
- new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(classJar)))) {
- ClassJarBuildingVisitor visitor =
- new ClassJarBuildingVisitor(zip, generatedClassesRoot);
+ try (final ZipBuilder zip = ZipBuilder.createFor(classJar)) {
+ ClassJarBuildingVisitor visitor = new ClassJarBuildingVisitor(zip, generatedClassesRoot);
Files.walkFileTree(generatedClassesRoot, visitor);
visitor.writeEntries();
visitor.writeManifestContent();
@@ -285,6 +319,23 @@ public class AndroidResourceOutputs {
}
}
+ /** Creates a zip archive from all files under the provided root. */
+ public static void archiveDirectory(Path root, Path archive) {
+ try {
+ Files.createDirectories(archive.getParent());
+ try (final ZipBuilder zip = ZipBuilder.createFor(archive)) {
+ ZipBuilderVisitor visitor = new ZipBuilderVisitor(zip, root, null);
+ visitor.setCompress(false);
+ Files.walkFileTree(root, visitor);
+ visitor.writeEntries();
+ }
+ // Set to the epoch for caching purposes.
+ Files.setLastModifiedTime(archive, FileTime.fromMillis(0L));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
/**
* Creates a zip file containing the provided android resources and assets.
*
@@ -296,18 +347,15 @@ public class AndroidResourceOutputs {
*/
public static void createResourcesZip(
Path resourcesRoot, Path assetsRoot, Path output, boolean compress) throws IOException {
- try (ZipOutputStream zout =
- new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(output)))) {
+ try (final ZipBuilder zip = ZipBuilder.createFor(output)) {
if (Files.exists(resourcesRoot)) {
- ZipBuilderVisitor visitor =
- new ZipBuilderVisitor(zout, resourcesRoot, "res");
+ ZipBuilderVisitor visitor = new ZipBuilderVisitor(zip, resourcesRoot, "res");
visitor.setCompress(compress);
Files.walkFileTree(resourcesRoot, visitor);
visitor.writeEntries();
}
if (Files.exists(assetsRoot)) {
- ZipBuilderVisitor visitor =
- new ZipBuilderVisitor(zout, assetsRoot, "assets");
+ ZipBuilderVisitor visitor = new ZipBuilderVisitor(zip, assetsRoot, "assets");
visitor.setCompress(compress);
Files.walkFileTree(assetsRoot, visitor);
visitor.writeEntries();
@@ -319,11 +367,9 @@ public class AndroidResourceOutputs {
public static void createSrcJar(Path generatedSourcesRoot, Path srcJar, boolean staticIds) {
try {
Files.createDirectories(srcJar.getParent());
- try (final ZipOutputStream zip =
- new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(srcJar)))) {
+ try (final ZipBuilder zip = ZipBuilder.createFor(srcJar)) {
SymbolFileSrcJarBuildingVisitor visitor =
- new SymbolFileSrcJarBuildingVisitor(
- zip, generatedSourcesRoot, staticIds);
+ new SymbolFileSrcJarBuildingVisitor(zip, generatedSourcesRoot, staticIds);
Files.walkFileTree(generatedSourcesRoot, visitor);
visitor.writeEntries();
}
@@ -333,4 +379,33 @@ public class AndroidResourceOutputs {
throw new RuntimeException(e);
}
}
+
+ /** Collects all the compiled resources into an archive, normalizing the paths to the root. */
+ public static void archiveCompiledResources(
+ final Path archiveOut,
+ final Path databindingResourcesRoot,
+ final Path compiledRoot,
+ final List<Path> compiledArtifacts)
+ throws IOException {
+ final Path relativeDatabindingProcessedResources =
+ databindingResourcesRoot.getRoot().relativize(databindingResourcesRoot);
+ try (ZipBuilder builder = ZipBuilder.createFor(archiveOut)) {
+ for (Path artifact : compiledArtifacts) {
+ Path relativeName = artifact;
+ // remove compiled resources prefix
+ if (artifact.startsWith(compiledRoot)) {
+ relativeName = compiledRoot.relativize(relativeName);
+ }
+ // remove databinding prefix
+ if (relativeName.startsWith(relativeDatabindingProcessedResources)) {
+ relativeName =
+ relativeName.subpath(
+ relativeDatabindingProcessedResources.getNameCount(),
+ relativeName.getNameCount());
+ }
+
+ builder.addEntry(relativeName.toString(), Files.readAllBytes(artifact), ZipEntry.STORED);
+ }
+ }
+ }
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java
index 68eed92c55..58ca71bd4f 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java
@@ -307,8 +307,15 @@ public class AndroidResourceProcessor {
@Nullable Path dataBindingInfoOut)
throws IOException, InterruptedException, LoggedErrorException, UnrecognizedSplitsException {
Path androidManifest = primaryData.getManifest();
- final Path resourceDir = processDataBindings(primaryData.getResourceDir(), dataBindingInfoOut,
- variantType, customPackageForR, androidManifest);
+ final Path resourceDir =
+ processDataBindings(
+ primaryData.getResourceDir().resolveSibling("res_no_binding"),
+ primaryData.getResourceDir(),
+ dataBindingInfoOut,
+ variantType,
+ customPackageForR,
+ androidManifest,
+ true /* shouldZipDataBindingInfo */);
final Path assetsDir = primaryData.getAssetDir();
if (publicResourcesOut != null) {
@@ -495,13 +502,19 @@ public class AndroidResourceProcessor {
/**
* If resources exist and a data binding layout info file is requested: processes data binding
* declarations over those resources, populates the output file, and creates a new resources
- * directory with data binding expressions stripped out (so aapt, which doesn't understand
- * data binding, can properly read them).
+ * directory with data binding expressions stripped out (so aapt, which doesn't understand data
+ * binding, can properly read them).
*
* <p>Returns the resources directory that aapt should read.
*/
- static Path processDataBindings(Path resourceDir, Path dataBindingInfoOut,
- VariantType variantType, String packagePath, Path androidManifest)
+ static Path processDataBindings(
+ Path workingDirectory,
+ Path resourceDir,
+ Path dataBindingInfoOut,
+ VariantType variantType,
+ String packagePath,
+ Path androidManifest,
+ boolean shouldZipDataBindingInfo)
throws IOException {
if (dataBindingInfoOut == null) {
@@ -514,15 +527,20 @@ public class AndroidResourceProcessor {
// Strip the file name (the data binding library automatically adds it back in).
// ** The data binding library assumes this file is called "layout-info.zip". **
- dataBindingInfoOut = dataBindingInfoOut.getParent();
- if (Files.notExists(dataBindingInfoOut)) {
- Files.createDirectory(dataBindingInfoOut);
+ if (shouldZipDataBindingInfo) {
+ dataBindingInfoOut = dataBindingInfoOut.getParent();
+ if (Files.notExists(dataBindingInfoOut)) {
+ Files.createDirectory(dataBindingInfoOut);
+ }
}
- Path processedResourceDir = resourceDir.resolveSibling("res_without_databindings");
- if (Files.notExists(processedResourceDir)) {
- Files.createDirectory(processedResourceDir);
- }
+ // Create a directory for the resources, namespaced with the old resource path
+ Path processedResourceDir =
+ Files.createDirectories(
+ workingDirectory.resolve(
+ resourceDir.isAbsolute()
+ ? resourceDir.getRoot().relativize(resourceDir)
+ : resourceDir));
ProcessXmlOptions options = new ProcessXmlOptions();
options.setAppId(packagePath);
@@ -530,7 +548,8 @@ public class AndroidResourceProcessor {
options.setResInput(resourceDir.toFile());
options.setResOutput(processedResourceDir.toFile());
options.setLayoutInfoOutput(dataBindingInfoOut.toFile());
- options.setZipLayoutInfo(true); // Aggregate data-bound .xml files into a single .zip.
+ // Whether or not to aggregate data-bound .xml files into a single .zip.
+ options.setZipLayoutInfo(shouldZipDataBindingInfo);
try {
Object minSdk = AndroidManifest.getMinSdkVersion(new FileWrapper(androidManifest.toFile()));
diff --git a/src/tools/android/java/com/google/devtools/build/android/BUILD b/src/tools/android/java/com/google/devtools/build/android/BUILD
index 1d6aef86e3..834483aa98 100644
--- a/src/tools/android/java/com/google/devtools/build/android/BUILD
+++ b/src/tools/android/java/com/google/devtools/build/android/BUILD
@@ -34,6 +34,7 @@ java_library(
srcs = glob([
"*.java",
"xml/*.java",
+ "aapt2/*.java",
]),
deps = [
"//src/main/java/com/google/devtools/common/options",
diff --git a/src/tools/android/java/com/google/devtools/build/android/CompileLibraryResourcesAction.java b/src/tools/android/java/com/google/devtools/build/android/CompileLibraryResourcesAction.java
new file mode 100644
index 0000000000..a6afad5ad0
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/CompileLibraryResourcesAction.java
@@ -0,0 +1,211 @@
+// 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 com.android.builder.core.VariantType;
+import com.android.repository.Revision;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.devtools.build.android.Converters.ExistingPathConverter;
+import com.google.devtools.build.android.Converters.PathConverter;
+import com.google.devtools.build.android.Converters.RevisionConverter;
+import com.google.devtools.build.android.aapt2.ResourceCompiler;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionDocumentationCategory;
+import com.google.devtools.common.options.OptionEffectTag;
+import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParser;
+import java.io.Closeable;
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.logging.Logger;
+
+/** Compiles resources using aapt2 and archives them to zip. */
+public class CompileLibraryResourcesAction {
+ /** Flag specifications for this action. */
+ public static final class Options extends OptionsBase {
+
+ @Option(
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ name = "resource",
+ defaultValue = "",
+ allowMultiple = true,
+ converter = ExistingPathConverter.class,
+ category = "input",
+ help = "The resources to compile with aapt2."
+ )
+ public List<Path> resources;
+
+ @Option(
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ name = "output",
+ defaultValue = "null",
+ converter = PathConverter.class,
+ category = "output",
+ help = "Path to write the zip of compiled resources."
+ )
+ public Path output;
+
+ @Option(
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ name = "aapt2",
+ defaultValue = "null",
+ converter = ExistingPathConverter.class,
+ category = "tool",
+ help = "Aapt2 tool location for resource compilation."
+ )
+ public Path aapt2;
+
+ @Option(
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ name = "buildToolsVersion",
+ defaultValue = "null",
+ converter = RevisionConverter.class,
+ category = "config",
+ help = "Version of the build tools (e.g. aapt) being used, e.g. 23.0.2"
+ )
+ public Revision buildToolsVersion;
+
+ @Option(
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ name = "packagePath",
+ defaultValue = "null",
+ category = "input",
+ help =
+ "The package path of the library being processed."
+ + " This value is required for processing data binding."
+ )
+ public String packagePath;
+
+ @Option(
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ name = "manifest",
+ defaultValue = "null",
+ category = "input",
+ converter = ExistingPathConverter.class,
+ help =
+ "The manifest of the library being processed."
+ + " This value is required for processing data binding."
+ )
+ public Path manifest;
+
+ @Option(
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ name = "dataBindingInfoOut",
+ defaultValue = "null",
+ category = "output",
+ converter = PathConverter.class,
+ help =
+ "Path for the derived data binding metadata."
+ + " This value is required for processing data binding."
+ )
+ public Path dataBindingInfoOut;
+ }
+
+ static final Logger logger = Logger.getLogger(CompileLibraryResourcesAction.class.getName());
+
+ public static void main(String[] args) throws Exception {
+ OptionsParser optionsParser = OptionsParser.newOptionsParser(Options.class);
+ optionsParser.enableParamsFileSupport(FileSystems.getDefault());
+ optionsParser.parseAndExitUponError(args);
+
+ Options options = optionsParser.getOptions(Options.class);
+
+ Preconditions.checkNotNull(options.resources);
+ Preconditions.checkNotNull(options.output);
+ Preconditions.checkNotNull(options.aapt2);
+
+ try (ScopedTemporaryDirectory scopedTmp =
+ new ScopedTemporaryDirectory("android_resources_tmp")) {
+ final Path tmp = scopedTmp.getPath();
+ 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"));
+ // The reported availableProcessors may be higher than the actual resources
+ // (on a shared system). On the other hand, a lot of the work is I/O, so it's not completely
+ // CPU bound. As a compromise, divide by 2 the reported availableProcessors.
+ int numThreads = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
+ final ListeningExecutorService executorService =
+ MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(numThreads));
+ try (final Closeable closeable = ExecutorServiceCloser.createWith(executorService)) {
+ final ResourceCompiler compiler =
+ ResourceCompiler.create(
+ executorService, compiledResources, options.aapt2, options.buildToolsVersion);
+ for (final Path resource :
+ maybeProcessDataBindings(
+ databindingResourcesRoot,
+ databindingMetaData,
+ options.dataBindingInfoOut,
+ options.manifest,
+ options.packagePath,
+ options.resources)) {
+ compiler.queueDirectoryForCompilation(resource);
+ }
+ AndroidResourceOutputs.archiveCompiledResources(
+ options.output,
+ databindingResourcesRoot,
+ compiledResources,
+ compiler.getCompiledArtifacts());
+ }
+ }
+ }
+
+ private static List<Path> maybeProcessDataBindings(
+ Path resourceRoot,
+ Path databindingMetaData,
+ Path dataBindingInfoOut,
+ Path manifest,
+ String packagePath,
+ List<Path> resources)
+ throws IOException {
+ if (dataBindingInfoOut == null) {
+ return resources;
+ }
+
+ Preconditions.checkNotNull(manifest);
+ Preconditions.checkNotNull(packagePath);
+
+ List<Path> processed = new ArrayList<>();
+ for (Path resource : resources) {
+ processed.add(
+ AndroidResourceProcessor.processDataBindings(
+ resourceRoot,
+ resource,
+ databindingMetaData,
+ VariantType.LIBRARY,
+ packagePath,
+ manifest,
+ false));
+ }
+
+ AndroidResourceOutputs.archiveDirectory(databindingMetaData, dataBindingInfoOut);
+ return processed;
+ }
+}
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 e4a62c8004..5e89dcdec5 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
@@ -108,6 +108,12 @@ public class ResourceProcessorBusyBox {
void call(String[] args) throws Exception {
ManifestMergerAction.main(args);
}
+ },
+ COMPILE_LIBRARY_RESOURCES() {
+ @Override
+ void call(String[] args) throws Exception {
+ CompileLibraryResourcesAction.main(args);
+ }
};
abstract void call(String[] args) throws Exception;
@@ -133,7 +139,7 @@ public class ResourceProcessorBusyBox {
help =
"The processing tool to execute. "
+ "Valid tools: PACKAGE, VALIDATE, GENERATE_BINARY_R, GENERATE_LIBRARY_R, PARSE, "
- + "MERGE, GENERATE_AAR, SHRINK, MERGE_MANIFEST."
+ + "MERGE, GENERATE_AAR, SHRINK, MERGE_MANIFEST, COMPILE_LIBRARY_RESOURCES."
)
public Tool tool;
}
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
new file mode 100644
index 0000000000..240f8e0d04
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java
@@ -0,0 +1,159 @@
+// 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.aapt2;
+
+import com.android.builder.core.VariantType;
+import com.android.repository.Revision;
+import com.google.common.base.Joiner;
+import com.google.common.io.CharStreams;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.devtools.build.android.AaptCommandBuilder;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+
+/** Invokes aapt2 to compile resources. */
+public class ResourceCompiler {
+
+ private final CompilingVisitor compilingVisitor;
+
+ private static class CompileTask implements Callable<Path> {
+
+ private final Path file;
+ private final Path compiledResourcesOut;
+ private final Path aapt2;
+ private final Revision buildToolsVersion;
+
+ public CompileTask(
+ Path file, Path compiledResourcesOut, Path aapt2, Revision buildToolsVersion) {
+ this.file = file;
+ this.compiledResourcesOut = compiledResourcesOut;
+ this.aapt2 = aapt2;
+ this.buildToolsVersion = buildToolsVersion;
+ }
+
+ @Override
+ public Path call() throws Exception {
+ List<String> processLog = new ArrayList<>();
+ AaptCommandBuilder commandBuilder =
+ new AaptCommandBuilder(aapt2)
+ .forBuildToolsVersion(buildToolsVersion)
+ .forVariantType(VariantType.LIBRARY)
+ .add("compile")
+ .add("-v")
+ .add("--legacy")
+ .add("-o", compiledResourcesOut.toString())
+ .add(file.toString());
+ final Process process =
+ new ProcessBuilder().command(commandBuilder.build()).redirectErrorStream(true).start();
+ processLog.add("Command:");
+ processLog.add(commandBuilder.build().toString());
+ final InputStreamReader stdout =
+ new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8);
+ while (process.isAlive()) {
+ processLog.add(CharStreams.toString(stdout));
+ }
+ if (process.exitValue() != 0) {
+ throw new RuntimeException(
+ "Error compiling " + file + "\n" + Joiner.on("\n").join(processLog));
+ }
+ String type = file.getParent().getFileName().toString();
+ String filename = file.getFileName().toString();
+ if (type.startsWith("values")) {
+ filename = filename.substring(0, filename.indexOf('.')) + ".arsc";
+ }
+ return compiledResourcesOut.resolve(type + "_" + filename + ".flat");
+ }
+ }
+
+ private static class CompilingVisitor extends SimpleFileVisitor<Path> {
+
+ private final ListeningExecutorService executorService;
+ private final Path compiledResources;
+ private final List<ListenableFuture<Path>> tasks = new ArrayList<>();
+ private final Path aapt2;
+ private final Revision buildToolsVersion;
+
+ public CompilingVisitor(
+ ListeningExecutorService executorService,
+ Path compiledResources,
+ Path aapt2,
+ Revision buildToolsVersion) {
+ this.executorService = executorService;
+ this.compiledResources = compiledResources;
+ this.aapt2 = aapt2;
+ this.buildToolsVersion = buildToolsVersion;
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ if (!Files.isDirectory(file)) {
+ tasks.add(
+ executorService.submit(
+ new CompileTask(
+ file,
+ // Creates a relative output path based on the input path under the
+ // compiledResources path.
+ Files.createDirectories(
+ compiledResources.resolve(
+ (file.isAbsolute() ? file.getRoot().relativize(file) : file)
+ .getParent()
+ .getParent())),
+ aapt2,
+ buildToolsVersion)));
+ }
+ return super.visitFile(file, attrs);
+ }
+
+ List<Path> getCompiledArtifacts() throws InterruptedException, ExecutionException {
+ return Futures.allAsList(tasks).get();
+ }
+ }
+
+ /** Creates a new {@link ResourceCompiler}. */
+ public static ResourceCompiler create(
+ ListeningExecutorService executorService,
+ Path compiledResources,
+ Path aapt2,
+ Revision buildToolsVersion) {
+ return new ResourceCompiler(
+ new CompilingVisitor(executorService, compiledResources, aapt2, buildToolsVersion));
+ }
+
+ private ResourceCompiler(CompilingVisitor compilingVisitor) {
+ this.compilingVisitor = compilingVisitor;
+ }
+
+ /** Adds a task to compile the directory using aapt2. */
+ public void queueDirectoryForCompilation(Path resource) throws IOException {
+ Files.walkFileTree(resource, compilingVisitor);
+ }
+
+ /** Returns all paths of the aapt2 compiled resources. */
+ public List<Path> getCompiledArtifacts() throws InterruptedException, ExecutionException {
+ return compilingVisitor.getCompiledArtifacts();
+ }
+}