diff options
Diffstat (limited to 'src/tools/android/java')
17 files changed, 416 insertions, 155 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 5e168414c9..d975051497 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 @@ -19,6 +19,7 @@ import static java.util.stream.Collectors.toList; import com.android.utils.StdLogger; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.devtools.build.android.AndroidResourceMerger.MergingException; import com.google.devtools.build.android.AndroidResourceProcessingAction.Options; @@ -86,13 +87,29 @@ 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")); + Path generatedSources = null; + if (options.srcJarOutput != null || options.rOutput != null || options.symbolsOut != null) { + generatedSources = tmp.resolve("generated_resources"); + } + logger.fine(String.format("Setup finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); + List<DependencyAndroidData> data = + ImmutableSet.<DependencyAndroidData>builder() + .addAll(options.directData) + .addAll(options.transitiveData) + .build() + .asList(); + // Checks for merge conflicts. MergedAndroidData mergedAndroidData = AndroidResourceMerger.mergeData( @@ -125,7 +142,8 @@ public class Aapt2ResourcePackagingAction { CompiledResources compiled = options .primaryData - .processDataBindings(options.dataBindingInfoOut, databindingResourcesRoot) + .processDataBindings(options.dataBindingInfoOut, options.packageForR, + databindingResourcesRoot) .compile(compiler, compiledResources) .processManifest( manifest -> 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 a0549c879b..6e9c9a41ab 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 @@ -244,10 +244,12 @@ public class AaptCommandBuilder { * @throws IOException when the process cannot execute. */ public String execute(String action) throws IOException { - StringBuilder processLog = new StringBuilder(); - final Process process = new ProcessBuilder().command(build()).redirectErrorStream(true).start(); + final StringBuilder processLog = new StringBuilder(); + List<String> command = build(); + + final Process process = new ProcessBuilder().command(command).redirectErrorStream(true).start(); processLog.append("Command: "); - Joiner.on(" ").appendTo(processLog, build()); + Joiner.on("\n\t").appendTo(processLog, command); processLog.append("\n"); final InputStreamReader stdout = new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8); diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java b/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java index f48a7966c6..1f9d50090c 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java @@ -112,7 +112,7 @@ public class AndroidDataDeserializer { } readEntriesSegment(consumers, in, currentFileSystem, header); } catch (IOException e) { - throw new DeserializationException(e); + throw new DeserializationException("Error deserializing " + inPath, e); } finally { logger.fine( String.format("Deserialized in merged in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidManifestProcessor.java b/src/tools/android/java/com/google/devtools/build/android/AndroidManifestProcessor.java index be6738fa3b..cd9e6f8654 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidManifestProcessor.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidManifestProcessor.java @@ -246,13 +246,29 @@ public class AndroidManifestProcessor { Path manifest, Path processedManifest) { - ManifestMerger2.MergeType mergeType = ManifestMerger2.MergeType.APPLICATION; + if (versionCode != -1 || versionName != null || applicationId != null) { + processManifest( + versionCode, versionName, manifest, processedManifest, MergeType.APPLICATION, + applicationId); + return processedManifest; + } + return manifest; + } - String newManifestPackage = applicationId; + /** Processes the manifest for a library and return the manifest Path. */ + public Path processLibraryManifest( + String newManifestPackage, + Path manifest, + Path processedManifest) { - if (versionCode != -1 || versionName != null || newManifestPackage != null) { + if (newManifestPackage != null) { processManifest( - versionCode, versionName, manifest, processedManifest, mergeType, newManifestPackage); + -1 /* versionCode */, + null /* versionName */, + manifest, + processedManifest, + MergeType.LIBRARY, + newManifestPackage); return processedManifest; } return manifest; @@ -377,4 +393,17 @@ public class AndroidManifestProcessor { throw new ManifestProcessingException(e); } } + + public static Path writeDummyManifestForAapt(Path dummyManifest, String packageForR) { + try { + Files.createDirectories(dummyManifest.getParent()); + return Files.write(dummyManifest, String.format( + "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"" + + " package=\"%s\">" + + "</manifest>", packageForR).getBytes(UTF_8)); + } catch (IOException e) { + throw new ManifestProcessingException(e); + } + } } 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 58ca71bd4f..6c8b86b3e1 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 @@ -13,8 +13,6 @@ // limitations under the License. package com.google.devtools.build.android; -import static java.nio.charset.StandardCharsets.UTF_8; - import android.databinding.AndroidDataBinding; import android.databinding.cli.ProcessXmlOptions; import com.android.annotations.NonNull; @@ -689,13 +687,8 @@ public class AndroidResourceProcessor { } } - public void writeDummyManifestForAapt(Path dummyManifest, String packageForR) throws IOException { - Files.createDirectories(dummyManifest.getParent()); - Files.write(dummyManifest, String.format( - "<?xml version=\"1.0\" encoding=\"utf-8\"?>" - + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"" - + " package=\"%s\">" - + "</manifest>", packageForR).getBytes(UTF_8)); + public static void writeDummyManifestForAapt(Path dummyManifest, String packageForR) { + AndroidManifestProcessor.writeDummyManifestForAapt(dummyManifest, packageForR); } /** 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 index a6afad5ad0..7c19745a1c 100644 --- a/src/tools/android/java/com/google/devtools/build/android/CompileLibraryResourcesAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/CompileLibraryResourcesAction.java @@ -14,14 +14,13 @@ 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.Converters.UnvalidatedAndroidDirectoriesConverter; import com.google.devtools.build.android.aapt2.ResourceCompiler; import com.google.devtools.common.options.Option; import com.google.devtools.common.options.OptionDocumentationCategory; @@ -29,13 +28,9 @@ 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. */ @@ -46,14 +41,13 @@ public class CompileLibraryResourcesAction { @Option( documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, - name = "resource", - defaultValue = "", - allowMultiple = true, - converter = ExistingPathConverter.class, + name = "resources", + defaultValue = "null", + converter = UnvalidatedAndroidDirectoriesConverter.class, category = "input", help = "The resources to compile with aapt2." ) - public List<Path> resources; + public UnvalidatedAndroidDirectories resources; @Option( documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, @@ -140,72 +134,25 @@ public class CompileLibraryResourcesAction { Preconditions.checkNotNull(options.output); Preconditions.checkNotNull(options.aapt2); - try (ScopedTemporaryDirectory scopedTmp = - new ScopedTemporaryDirectory("android_resources_tmp")) { + final ListeningExecutorService defaultService = ExecutorServiceCloser.createDefaultService(); + try (Closeable serviceCloser = ExecutorServiceCloser.createWith(defaultService); + 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; + final ResourceCompiler compiler = + ResourceCompiler.create( + defaultService, compiledResources, options.aapt2, options.buildToolsVersion); + options + .resources + .toData(options.manifest) + .processDataBindings( + options.dataBindingInfoOut, options.packagePath, databindingResourcesRoot) + .compile(compiler, compiledResources) + .copyResourcesZipTo(options.output); } - - 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/DependencyAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java index f7ed888690..72f0ab5db6 100644 --- a/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java +++ b/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java @@ -38,7 +38,7 @@ class DependencyAndroidData extends SerializedAndroidData { private static final Pattern VALID_REGEX = Pattern.compile(".*:.*:.+:.+(:.*){0,2}"); public static final String EXPECTED_FORMAT = - "resources[#resources]:assets[#assets]:manifest:r.txt:symbols.bin:static.library.ap_"; + "resources[#resources]:assets[#assets]:manifest:r.txt:static.library.ap_:symbols.bin"; public static DependencyAndroidData valueOf(String text) { return valueOf(text, FileSystems.getDefault()); @@ -55,15 +55,24 @@ class DependencyAndroidData extends SerializedAndroidData { Path rTxt = exists(fileSystem.getPath(parts[3])); ImmutableList<Path> assetDirs = parts[1].length() == 0 ? ImmutableList.<Path>of() : splitPaths(parts[1], fileSystem); + StaticLibrary staticLibrary = null; + Path symbolsBin = null; + + if (parts.length == 6) { // contains symbols bin and static library + staticLibrary = StaticLibrary.from(exists(fileSystem.getPath(parts[4])), rTxt, assetDirs); + symbolsBin = exists(fileSystem.getPath(parts[5])); + } else if (parts.length == 5) { // contains symbols bin + symbolsBin = exists(fileSystem.getPath(parts[4])); + } + + return new DependencyAndroidData( splitPaths(parts[0], fileSystem), assetDirs, exists(fileSystem.getPath(parts[2])), rTxt, - parts.length > 4 ? fileSystem.getPath(parts[4]) : null, - parts.length > 5 - ? StaticLibrary.from(exists(fileSystem.getPath(parts[5])), rTxt, assetDirs) - : null); + symbolsBin, + staticLibrary); } private final Path manifest; diff --git a/src/tools/android/java/com/google/devtools/build/android/DeserializationException.java b/src/tools/android/java/com/google/devtools/build/android/DeserializationException.java index ba001b33f4..34bc707062 100644 --- a/src/tools/android/java/com/google/devtools/build/android/DeserializationException.java +++ b/src/tools/android/java/com/google/devtools/build/android/DeserializationException.java @@ -35,6 +35,11 @@ public class DeserializationException extends RuntimeException { this.isLegacy = false; } + public DeserializationException(String message, IOException e) { + super(message, e); + this.isLegacy = false; + } + public boolean isLegacy() { return isLegacy; } diff --git a/src/tools/android/java/com/google/devtools/build/android/ExecutorServiceCloser.java b/src/tools/android/java/com/google/devtools/build/android/ExecutorServiceCloser.java index cc99342ddf..24d7739782 100644 --- a/src/tools/android/java/com/google/devtools/build/android/ExecutorServiceCloser.java +++ b/src/tools/android/java/com/google/devtools/build/android/ExecutorServiceCloser.java @@ -32,7 +32,8 @@ final class ExecutorServiceCloser implements Closeable { public void close() throws IOException { List<Runnable> unfinishedTasks = executorService.shutdownNow(); if (!unfinishedTasks.isEmpty()) { - throw new IOException("Shutting down the executor with unfinished tasks:" + unfinishedTasks); + throw new IOException( + "Shutting down the executor with unfinished tasks:" + unfinishedTasks.size()); } } diff --git a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java index 74976543e9..736c9d4ce7 100644 --- a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java +++ b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java @@ -13,7 +13,9 @@ // limitations under the License. package com.google.devtools.build.android; +import com.android.builder.core.VariantType; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.devtools.build.android.aapt2.CompiledResources; import com.google.devtools.build.android.aapt2.ResourceCompiler; @@ -21,10 +23,12 @@ import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.regex.Pattern; +import javax.annotation.Nullable; /** * Android data that has yet to be merged and validated, the primary data for the Processor. @@ -45,8 +49,7 @@ class UnvalidatedAndroidData extends UnvalidatedAndroidDirectories { @VisibleForTesting static UnvalidatedAndroidData valueOf(String text, FileSystem fileSystem) { if (!VALID_REGEX.matcher(text).find()) { - throw new IllegalArgumentException( - text + " is not in the format '" + EXPECTED_FORMAT + "'"); + throw new IllegalArgumentException(text + " is not in the format '" + EXPECTED_FORMAT + "'"); } String[] parts = text.split(":"); return new UnvalidatedAndroidData( @@ -111,16 +114,44 @@ class UnvalidatedAndroidData extends UnvalidatedAndroidDirectories { assetDirs); } + /* Processes the resources for databinding annotations if dataBindingOut is defined. */ public UnvalidatedAndroidData processDataBindings( - Path dataBindingInfoOut, Path dataBindingWorkingDirectory) { + @Nullable Path dataBindingOut, String packagePath, Path dataBindingWorkingDirectory) + throws IOException { - return new UnvalidatedAndroidData(resourceDirs, assetDirs, manifest) { + if (dataBindingOut == null) { + return this; + } + + Preconditions.checkNotNull(manifest); + Preconditions.checkNotNull(packagePath); + + final List<Path> processed = new ArrayList<>(); + final Path metadataWorkingDirectory = dataBindingWorkingDirectory.resolve("metadata"); + final Path databindingResourceRoot = dataBindingWorkingDirectory.resolve("resources"); + for (Path resource : resourceDirs) { + processed.add( + AndroidResourceProcessor.processDataBindings( + databindingResourceRoot, + resource, + metadataWorkingDirectory, + VariantType.LIBRARY, + packagePath, + manifest, + false)); + } + + AndroidResourceOutputs.archiveDirectory( + metadataWorkingDirectory, dataBindingOut); + + return new UnvalidatedAndroidData(ImmutableList.copyOf(processed), assetDirs, manifest) { @Override protected CompiledResources archiveCompiledResources( List<Path> resources, Path workingDirectory, Path output) throws IOException { + // Update the archiving to ensure that the resources are correctly placed. return CompiledResources.from( AndroidResourceOutputs.archiveCompiledResources( - output, dataBindingWorkingDirectory, workingDirectory, resources), + output, databindingResourceRoot, workingDirectory, resources), manifest, assetDirs); } diff --git a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java index 5723f6ec61..8be10be0fb 100644 --- a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java +++ b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java @@ -107,4 +107,7 @@ public class UnvalidatedAndroidDirectories { && Objects.equals(other.assetDirs, assetDirs); } + public UnvalidatedAndroidData toData(Path manifest) { + return new UnvalidatedAndroidData(resourceDirs, assetDirs, manifest); + } } diff --git a/src/tools/android/java/com/google/devtools/build/android/ValidateAndLinkResourcesAction.java b/src/tools/android/java/com/google/devtools/build/android/ValidateAndLinkResourcesAction.java index bca1bc9a63..9886be2778 100644 --- a/src/tools/android/java/com/google/devtools/build/android/ValidateAndLinkResourcesAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/ValidateAndLinkResourcesAction.java @@ -14,6 +14,7 @@ // Copyright 2017 The Bazel Authors. All rights reserved. package com.google.devtools.build.android; +import com.google.common.base.Preconditions; import com.google.devtools.build.android.aapt2.Aapt2ConfigOptions; import com.google.devtools.build.android.aapt2.CompiledResources; import com.google.devtools.build.android.aapt2.ResourceLinker; @@ -34,6 +35,34 @@ public class ValidateAndLinkResourcesAction { /** Action configuration options. */ public static class Options extends OptionsBase { @Option( + name = "compiled", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + defaultValue = "null", + converter = Converters.ExistingPathConverter.class, + category = "input", + help = "Compiled resources to link.", + deprecationWarning = "Use --resources." + ) + // TODO(b/64570523): Still used by blaze. Will be removed as part of the command line cleanup. + @Deprecated + public Path compiled; + + @Option( + name = "manifest", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + defaultValue = "null", + converter = Converters.ExistingPathConverter.class, + category = "input", + help = "Manifest for the library.", + deprecationWarning = "Use --resources." + ) + // TODO(b/64570523): Still used by blaze. Will be removed as part of the command line cleanup. + @Deprecated + public Path manifest; + + @Option( name = "resources", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, @@ -69,6 +98,16 @@ public class ValidateAndLinkResourcesAction { public List<StaticLibrary> libraries; @Option( + name = "packageForR", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + defaultValue = "null", + category = "input", + help = "Package for the resources." + ) + public String packageForR; + + @Option( name = "staticLibraryOut", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.UNKNOWN}, @@ -89,6 +128,17 @@ public class ValidateAndLinkResourcesAction { help = "R.txt out." ) public Path rTxtOut; + + @Option( + name = "sourceJarOut", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + converter = Converters.PathConverter.class, + defaultValue = "null", + category = "output", + help = "Generated java classes from the resources." + ) + public Path sourceJarOut; } public static void main(String[] args) throws Exception { @@ -102,12 +152,28 @@ public class ValidateAndLinkResourcesAction { try (ScopedTemporaryDirectory scopedTmp = new ScopedTemporaryDirectory("android_resources_tmp")) { + CompiledResources resources = + // TODO(b/64570523): Remove when the flags are standardized. + Optional.ofNullable(options.resources) + .orElseGet( + () -> + CompiledResources.from( + Preconditions.checkNotNull(options.compiled), + Preconditions.checkNotNull(options.manifest))) + // We need to make the manifest aapt safe (w.r.t., placeholders). For now, just stub + // it out. + .processManifest( + manifest -> + AndroidManifestProcessor.writeDummyManifestForAapt( + scopedTmp.getPath().resolve("manifest-aapt-dummy/AndroidManifest.xml"), + options.packageForR)); ResourceLinker.create(aapt2Options.aapt2, scopedTmp.getPath()) .dependencies(Optional.ofNullable(options.deprecatedLibraries).orElse(options.libraries)) .buildVersion(aapt2Options.buildToolsVersion) - .linkStatically(options.resources) + .linkStatically(resources) .copyLibraryTo(options.staticLibraryOut) + .copySourceJarTo(options.sourceJarOut) .copyRTxtTo(options.rTxtOut); } } diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/Aapt2ConfigOptions.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/Aapt2ConfigOptions.java index ada69bc1a3..0225041418 100644 --- a/src/tools/android/java/com/google/devtools/build/android/aapt2/Aapt2ConfigOptions.java +++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/Aapt2ConfigOptions.java @@ -18,11 +18,14 @@ package com.google.devtools.build.android.aapt2; import com.android.repository.Revision; import com.google.devtools.build.android.Converters.ExistingPathConverter; import com.google.devtools.build.android.Converters.RevisionConverter; +import com.google.devtools.common.options.Converters.CommaSeparatedOptionListConverter; 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.TriState; import java.nio.file.Path; +import java.util.List; /** Aaprt2 specific configuration options. */ public class Aapt2ConfigOptions extends OptionsBase { @@ -58,4 +61,100 @@ public class Aapt2ConfigOptions extends OptionsBase { help = "Path to the android jar for resource packaging and building apks." ) public Path androidJar; + + @Option( + name = "annotationJar", + defaultValue = "null", + converter = ExistingPathConverter.class, + category = "tool", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Path to the android jar for resource packaging and building apks." + ) + public Path annotationJar; + + + @Option( + name = "useAaptCruncher", + defaultValue = "auto", + category = "config", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = + "Use the legacy aapt cruncher, defaults to true for non-LIBRARY packageTypes. " + + " LIBRARY packages do not benefit from the additional processing as the resources" + + " will need to be reprocessed during the generation of the final apk. See" + + " https://code.google.com/p/android/issues/detail?id=67525 for a discussion of the" + + " different png crunching methods." + ) + public TriState useAaptCruncher; + + @Option( + name = "uncompressedExtensions", + defaultValue = "", + converter = CommaSeparatedOptionListConverter.class, + category = "config", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "A list of file extensions not to compress." + ) + public List<String> uncompressedExtensions; + + @Option( + name = "assetsToIgnore", + defaultValue = "", + converter = CommaSeparatedOptionListConverter.class, + category = "config", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "A list of assets extensions to ignore." + ) + public List<String> assetsToIgnore; + + @Option( + name = "debug", + defaultValue = "false", + category = "config", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Indicates if it is a debug build." + ) + public boolean debug; + + @Option( + name = "resourceConfigs", + defaultValue = "", + converter = CommaSeparatedOptionListConverter.class, + category = "config", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "A list of resource config filters to pass to aapt." + ) + public List<String> resourceConfigs; + + private static final String ANDROID_SPLIT_DOCUMENTATION_URL = + "https://developer.android.com/guide/topics/resources/providing-resources.html" + + "#QualifierRules"; + + @Option( + name = "split", + defaultValue = "required but ignored due to allowMultiple", + category = "config", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + allowMultiple = true, + help = + "An individual split configuration to pass to aapt." + + " Each split is a list of configuration filters separated by commas." + + " Configuration filters are lists of configuration qualifiers separated by dashes," + + " as used in resource directory names and described on the Android developer site: " + + ANDROID_SPLIT_DOCUMENTATION_URL + + " For example, a split might be 'en-television,en-xxhdpi', containing English" + + " assets which either are for TV screens or are extra extra high resolution." + + " Multiple splits can be specified by passing this flag multiple times." + + " Each split flag will produce an additional output file, named by replacing the" + + " commas in the split specification with underscores, and appending the result to" + + " the output package name following an underscore." + ) + public List<String> splits; } 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 81d94fb244..aa13dc35ce 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 @@ -15,6 +15,8 @@ package com.google.devtools.build.android.aapt2; import com.google.common.collect.ImmutableList; import com.google.devtools.build.android.ManifestContainer; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Optional; @@ -53,13 +55,21 @@ public class CompiledResources implements ManifestContainer { return resources; } + /** 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); + } + @Override public Path getManifest() { return manifest; } public List<String> getAssetsStrings() { - return assetsDirs.stream().map(Path::toString).collect(Collectors.toList()); + return assetsDirs + .stream() + .map(Path::toString) + .collect(Collectors.toList()); } public CompiledResources processManifest(Function<Path, Path> processManifest) { 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 e13dc613ca..6084f6710f 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 @@ -16,6 +16,7 @@ package com.google.devtools.build.android.aapt2; import com.android.builder.core.VariantType; import com.android.repository.Revision; +import com.google.common.base.Preconditions; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; @@ -55,22 +56,38 @@ 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(); if (type.startsWith("values")) { - filename = filename.substring(0, filename.indexOf('.')) + ".arsc"; + filename = + (filename.indexOf('.') != -1 ? filename.substring(0, filename.indexOf('.')) : filename) + + ".arsc"; } - return compiledResourcesOut.resolve(type + "_" + filename + ".flat"); + + final Path compiledResourcePath = + compiledResourcesOut.resolve(type + "_" + filename + ".flat"); + Preconditions.checkArgument( + Files.exists(compiledResourcePath), + "%s does not exists after aapt2 ran.", + compiledResourcePath); + return compiledResourcePath; + } + + @Override + public String toString() { + return "ResourceCompiler.CompileTask(" + file + ")"; } } @@ -95,7 +112,8 @@ public class ResourceCompiler { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - if (!Files.isDirectory(file)) { + // Ignore directories and "hidden" files that start with . + if (!Files.isDirectory(file) && !file.getFileName().toString().startsWith(".")) { tasks.add( executorService.submit( new CompileTask( 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 fa9edc3044..dc6ab34aff 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 @@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.io.ByteStreams; import com.google.devtools.build.android.AaptCommandBuilder; +import com.google.devtools.build.android.AndroidResourceOutputs; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -40,7 +41,7 @@ public class ResourceLinker { } } - private static final Logger logger = Logger.getLogger(ResourceLinker.class.getName()); + private static Logger logger = Logger.getLogger(ResourceLinker.class.getName()); private final Path aapt2; private final Path workingDirectory; @@ -62,9 +63,10 @@ public class ResourceLinker { return new ResourceLinker(aapt2, workingDirectory); } - /** Dependent static to be linked against. */ - public ResourceLinker dependencies(List<StaticLibrary> linkAgainst) { - this.linkAgainst = linkAgainst; + + /** Dependent static libraries to be linked to. */ + public ResourceLinker dependencies(List<StaticLibrary> libraries) { + this.linkAgainst = libraries; return this; } @@ -86,9 +88,8 @@ public class ResourceLinker { public ResourceLinker filterToDensity(List<String> densitiesToFilter) { if (densitiesToFilter.size() > 1) { - throw new UnsupportedOperationException("Multiple densities not yet supported with aapt2"); - } - if (densitiesToFilter.size() > 0) { + logger.warning("Multiple densities not yet supported with aapt2"); + } else if (densitiesToFilter.size() > 0) { density = Iterables.getOnlyElement(densitiesToFilter); } return this; @@ -102,7 +103,9 @@ public class ResourceLinker { */ public StaticLibrary linkStatically(CompiledResources resources) { final Path outPath = workingDirectory.resolve("lib.ap_"); - Path rTxt = workingDirectory.resolve("R.txt"); + final Path rTxt = workingDirectory.resolve("R.txt"); + final Path sourceJar = workingDirectory.resolve("r.srcjar"); + Path javaSourceDirectory = workingDirectory.resolve("java"); try { logger.fine( @@ -117,12 +120,16 @@ public class ResourceLinker { .thenAdd("--no-version-vectors") .addRepeated("-R", unzipCompiledResources(resources.getZip())) .addRepeated("-I", StaticLibrary.toPathStrings(linkAgainst)) + .add("--java", javaSourceDirectory) .add("--auto-add-overlay") .add("-o", outPath) - .add("--java", workingDirectory.resolve("java")) // java needed to create R.txt + .add("--java", javaSourceDirectory) .add("--output-text-symbols", rTxt) .execute(String.format("Statically linking %s", resources))); - return StaticLibrary.from(outPath, rTxt); + + AndroidResourceOutputs.createSrcJar(javaSourceDirectory, sourceJar, true /* staticIds */); + + return StaticLibrary.from(outPath, rTxt, ImmutableList.of(), sourceJar); } catch (IOException e) { throw new LinkError(e); } @@ -144,7 +151,7 @@ public class ResourceLinker { .whenVersionIsAtLeast(new Revision(23)) .thenAdd("--no-version-vectors") .add("--no-static-lib-packages") - .when(Level.FINE.equals(logger.getLevel())) + .when(logger.getLevel() == Level.FINE) .thenAdd("-v") .add("--manifest", compiled.getManifest()) .add("--auto-add-overlay") 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 fc574f89ff..54ba743224 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 @@ -24,6 +24,7 @@ import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.function.Function; import javax.annotation.Nullable; /** A static library generated by aapt2. */ @@ -32,44 +33,74 @@ public class StaticLibrary { private final Path library; private final Optional<Path> rTxt; private final Optional<List<Path>> assets; + private final Optional<Path> sourceJar; - private StaticLibrary(Path library, Optional<Path> rTxt, Optional<List<Path>> assets) { + private StaticLibrary( + Path library, Optional<Path> rTxt, Optional<List<Path>> assets, Optional<Path> sourceJar) { this.library = library; this.rTxt = rTxt; this.assets = assets; + this.sourceJar = sourceJar; } public static StaticLibrary from(Path library) { - return of(library, Optional.empty(), Optional.empty()); + return of(library, Optional.empty(), Optional.empty(), Optional.empty()); } public static StaticLibrary from(Path library, Path rTxt) { - return of(library, Optional.of(rTxt), Optional.empty()); + return of(library, Optional.of(rTxt), Optional.empty(), Optional.empty()); } - private static StaticLibrary of(Path library, Optional<Path> rTxt, Optional<List<Path>> assets) { - return new StaticLibrary(library, rTxt, assets); + private static StaticLibrary of( + Path library, Optional<Path> rTxt, Optional<List<Path>> assets, Optional<Path> sourceJar) { + return new StaticLibrary(library, rTxt, assets, sourceJar); } - public StaticLibrary copyLibraryTo(Path target) throws IOException { - return of(Files.copy(library, target), rTxt, assets); + public static StaticLibrary from( + Path library, Path rTxt, ImmutableList<Path> assetDirs) { + return of( + library, + Optional.ofNullable(rTxt), + Optional.ofNullable(assetDirs), + Optional.empty()); } - public StaticLibrary copyRTxtTo(@Nullable final Path target) { + public static StaticLibrary from( + Path library, Path rTxt, ImmutableList<Path> assetDirs, Path sourceJar) { return of( library, - rTxt.map( - input -> { - if (target == null) { - return input; - } - try { - return Files.copy(input, target); - } catch (IOException e) { - throw new RuntimeException(e); - } - }), - assets); + Optional.ofNullable(rTxt), + Optional.ofNullable(assetDirs), + Optional.ofNullable(sourceJar)); + } + + public static Collection<String> toPathStrings(List<StaticLibrary> libraries) { + return Lists.transform(libraries, StaticLibrary::asLibraryPathString); + } + + public StaticLibrary copyLibraryTo(Path target) throws IOException { + return of(Files.copy(library, target), rTxt, assets, sourceJar); + } + + public StaticLibrary copyRTxtTo(@Nullable final Path target) { + return of(library, rTxt.map(copyTo(target)), assets, sourceJar); + } + + public StaticLibrary copySourceJarTo(@Nullable final Path target) { + return of(library, rTxt, assets, sourceJar.map(copyTo(target))); + } + + private Function<Path, Path> copyTo(Path target) { + return input -> { + if (target == null) { + return input; + } + try { + return Files.copy(input, target); + } catch (IOException e) { + throw new RuntimeException(e); + } + }; } @VisibleForTesting @@ -99,14 +130,6 @@ public class StaticLibrary { return assets.orElse(ImmutableList.of()); } - public static Collection<String> toPathStrings(List<StaticLibrary> libraries) { - return Lists.transform(libraries, StaticLibrary::asLibraryPathString); - } - - public static StaticLibrary from(Path library, Path rTxt, ImmutableList<Path> assetDirs) { - return of(library, Optional.ofNullable(rTxt), Optional.ofNullable(assetDirs)); - } - public static Collection<String> toAssetPaths(List<StaticLibrary> libraries) { return libraries .stream() |