aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools
diff options
context:
space:
mode:
authorGravatar corysmith <corysmith@google.com>2017-08-23 15:24:31 +0200
committerGravatar Damien Martin-Guillerez <dmarting@google.com>2017-08-23 15:46:23 +0200
commite516a101bb615f064d6622a5d4add541617b8c1f (patch)
tree93b6a3989b048a1cd35d9c1b37f411f77ec556bc /src/tools/android/java/com/google/devtools
parent296cb428546a7e966065b03137eca362efb9f34c (diff)
Adds compile action for aapt2: Aapt2ResourcePackagingAction
Adds a ManifestContainer interface to centralize the types that provide manifests. Adds PackagedResources to represent a linked dexless resource apk RELNOTES: None PiperOrigin-RevId: 166193049
Diffstat (limited to 'src/tools/android/java/com/google/devtools')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java192
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidManifestProcessor.java180
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java3
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceOutputs.java16
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java17
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/Converters.java7
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/DensitySpecificManifestProcessor.java11
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java42
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ExecutorServiceCloser.java16
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ManifestContainer.java21
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java3
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/MergedAndroidData.java9
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java9
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/SerializedAndroidData.java2
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidData.java46
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/aapt2/Aapt2ConfigOptions.java11
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/aapt2/CompiledResources.java34
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/aapt2/PackagedResources.java88
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java25
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceLinker.java190
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/aapt2/StaticLibrary.java77
21 files changed, 821 insertions, 178 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
new file mode 100644
index 0000000000..5e168414c9
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java
@@ -0,0 +1,192 @@
+// Copyright 2015 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.collect.Streams.concat;
+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.util.concurrent.ListeningExecutorService;
+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.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.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+/**
+ * Provides an entry point for the resource processing using the AOSP build tools.
+ *
+ * <pre>
+ * Example Usage:
+ * java/com/google/build/android/Aapt2ResourcePackagingAction\
+ * --sdkRoot path/to/sdk\
+ * --aapt path/to/sdk/aapt\
+ * --annotationJar path/to/sdk/annotationJar\
+ * --adb path/to/sdk/adb\
+ * --zipAlign path/to/sdk/zipAlign\
+ * --androidJar path/to/sdk/androidJar\
+ * --manifestOutput path/to/manifest\
+ * --primaryData path/to/resources:path/to/assets:path/to/manifest\
+ * --data p/t/res1:p/t/assets1:p/t/1/AndroidManifest.xml:p/t/1/R.txt:symbols,\
+ * p/t/res2:p/t/assets2:p/t/2/AndroidManifest.xml:p/t/2/R.txt:symbols\
+ * --packagePath path/to/write/archive.ap_\
+ * --srcJarOutput path/to/write/archive.srcjar
+ * </pre>
+ */
+public class Aapt2ResourcePackagingAction {
+
+ private static final StdLogger STD_LOGGER = new StdLogger(StdLogger.Level.WARNING);
+
+ private static final Logger logger =
+ Logger.getLogger(Aapt2ResourcePackagingAction.class.getName());
+
+ private static Aapt2ConfigOptions aaptConfigOptions;
+ private static Options options;
+
+ public static void main(String[] args) throws Exception {
+ final Stopwatch timer = Stopwatch.createStarted();
+ OptionsParser optionsParser =
+ OptionsParser.newOptionsParser(Options.class, Aapt2ConfigOptions.class);
+ optionsParser.enableParamsFileSupport(FileSystems.getDefault());
+ optionsParser.parseAndExitUponError(args);
+ aaptConfigOptions = optionsParser.getOptions(Aapt2ConfigOptions.class);
+ options = optionsParser.getOptions(Options.class);
+
+ try (ScopedTemporaryDirectory scopedTmp =
+ new ScopedTemporaryDirectory("android_resources_tmp")) {
+ final Path tmp = scopedTmp.getPath();
+ final Path mergedAssets = tmp.resolve("merged_assets");
+ final Path mergedResources = tmp.resolve("merged_resources");
+
+ final Path densityManifest = tmp.resolve("manifest-filtered/AndroidManifest.xml");
+
+ final Path processedManifest = tmp.resolve("manifest-processed/AndroidManifest.xml");
+ final Path databindingResourcesRoot =
+ Files.createDirectories(tmp.resolve("android_data_binding_resources"));
+ final Path compiledResources = Files.createDirectories(tmp.resolve("compiled"));
+ final Path linkedOut = Files.createDirectories(tmp.resolve("linked"));
+
+ logger.fine(String.format("Setup finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+
+ // Checks for merge conflicts.
+ MergedAndroidData mergedAndroidData =
+ AndroidResourceMerger.mergeData(
+ options.primaryData,
+ options.directData,
+ options.transitiveData,
+ mergedResources,
+ mergedAssets,
+ null /* cruncher. Aapt2 automatically chooses to crunch or not. */,
+ options.packageType,
+ options.symbolsOut,
+ options.prefilteredResources,
+ false /* throwOnResourceConflict */);
+
+ logger.fine(String.format("Merging finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+
+ final List<String> densitiesToFilter =
+ options.prefilteredResources.isEmpty()
+ ? options.densities
+ : Collections.<String>emptyList();
+ final ListeningExecutorService executorService = ExecutorServiceCloser.createDefaultService();
+ try (final Closeable closeable = ExecutorServiceCloser.createWith(executorService)) {
+ final ResourceCompiler compiler =
+ ResourceCompiler.create(
+ executorService,
+ compiledResources,
+ aaptConfigOptions.aapt2,
+ aaptConfigOptions.buildToolsVersion);
+
+ CompiledResources compiled =
+ options
+ .primaryData
+ .processDataBindings(options.dataBindingInfoOut, databindingResourcesRoot)
+ .compile(compiler, compiledResources)
+ .processManifest(
+ manifest ->
+ AndroidManifestProcessor.with(STD_LOGGER)
+ .processManifest(
+ options.applicationId,
+ options.versionCode,
+ options.versionName,
+ manifest,
+ processedManifest))
+ .processManifest(
+ manifest ->
+ new DensitySpecificManifestProcessor(options.densities, densityManifest)
+ .process(manifest));
+
+ // Write manifestOutput now before the dummy manifest is created.
+ if (options.manifestOutput != null) {
+ AndroidResourceOutputs.copyManifestToOutput(compiled, options.manifestOutput);
+ }
+
+ List<StaticLibrary> dependencies =
+ // Last defined dependencies will overwrite previous one, so always place direct
+ // after transitive.
+ concat(options.transitiveData.stream(), options.directData.stream())
+ .map(DependencyAndroidData::getStaticLibrary)
+ .collect(toList());
+
+ ResourceLinker.create(aaptConfigOptions.aapt2, linkedOut)
+ .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);
+ logger.fine(String.format("aapt2 finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+ }
+ if (options.resourcesOutput != null) {
+ // 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 */);
+ }
+ logger.fine(
+ String.format("Packaging finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+ } catch (MergingException e) {
+ logger.log(java.util.logging.Level.SEVERE, "Error during merging resources", e);
+ throw e;
+ } catch (IOException e) {
+ logger.log(java.util.logging.Level.SEVERE, "Error during processing resources", e);
+ throw e;
+ } catch (Exception e) {
+ logger.log(java.util.logging.Level.SEVERE, "Unexpected", e);
+ throw e;
+ }
+ logger.fine(String.format("Resources processed 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 93364a6fd7..be6738fa3b 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
@@ -57,6 +57,18 @@ import javax.xml.stream.events.XMLEvent;
/** Provides manifest processing oriented tools. */
public class AndroidManifestProcessor {
+
+ /** Wrapper exception for errors thrown during manifest processing. */
+ public static class ManifestProcessingException extends RuntimeException {
+ public ManifestProcessingException(Throwable e) {
+ super(e);
+ }
+
+ public ManifestProcessingException(String message) {
+ super(message);
+ }
+ }
+
private static final ImmutableMap<SystemProperty, String> SYSTEM_PROPERTY_NAMES =
Maps.toMap(
Arrays.asList(SystemProperty.values()),
@@ -109,7 +121,7 @@ public class AndroidManifestProcessor {
* @param logFile The path to write the merger log to.
* @return The path of the resultant manifest, either {@code output}, or {@code manifest} if no
* merging was required.
- * @throws MergeErrorException if a merge error was encountered during merging.
+ * @throws ManifestProcessingException if there was a problem writing the merged manifest.
*/
// TODO(corysmith): Extract manifest processing.
public Path mergeManifest(
@@ -118,7 +130,8 @@ public class AndroidManifestProcessor {
MergeType mergeType,
Map<String, String> values,
Path output,
- Path logFile) throws MergeErrorException {
+ Path logFile)
+ throws ManifestProcessingException {
if (mergeeManifests.isEmpty() && values.isEmpty()) {
return manifest;
}
@@ -141,8 +154,7 @@ public class AndroidManifestProcessor {
placeholders.putAll(values);
for (SystemProperty property : SystemProperty.values()) {
if (values.containsKey(SYSTEM_PROPERTY_NAMES.get(property))) {
- manifestMerger.setOverride(
- property, values.get(SYSTEM_PROPERTY_NAMES.get(property)));
+ manifestMerger.setOverride(property, values.get(SYSTEM_PROPERTY_NAMES.get(property)));
// The manifest merger does not allow explicitly specifying either applicationId or
// packageName as placeholders if SystemProperty.PACKAGE is specified. It forces these
@@ -181,31 +193,19 @@ public class AndroidManifestProcessor {
break;
case ERROR:
mergingReport.log(stdLogger);
- throw new MergeErrorException(mergingReport);
+ throw new ManifestProcessingException(mergingReport.getReportString());
default:
- throw new RuntimeException("Unhandled result type : " + mergingReport.getResult());
+ throw new ManifestProcessingException(
+ "Unhandled result type : " + mergingReport.getResult());
}
- } catch (IOException | MergeFailureException e) {
- throw new RuntimeException(e);
+ } catch (MergeFailureException | IOException e) {
+ throw new ManifestProcessingException(e);
}
return output;
}
- /**
- * Stamp specific properties into the manifest tag of the given manifest.
- *
- * @param variantType The type of rule the manifest belongs to, determining the stamping behavior.
- * @param customPackageForR The package attribute to stamp if not a binary manifest.
- * @param applicationId The package attribute for a binary manifest.
- * @param versionCode The android:versionCode attribute to stamp.
- * @param versionName The android:versionName attribute to stamp.
- * @param primaryData The {@link MergedAndroidData} that contains the manifest to modify.
- * @param processedManifest The path to write the modified manifest.
- * @return A {@link MergedAndroidData} containing the modified manifest, or {@code primaryData} if
- * no modification was required.
- * @throws MergeErrorException if a merge error was encountered during merging.
- */
+ /** Process a manifest for a library or a binary and return the merged android data. */
public MergedAndroidData processManifest(
VariantType variantType,
String customPackageForR,
@@ -214,7 +214,7 @@ public class AndroidManifestProcessor {
String versionName,
MergedAndroidData primaryData,
Path processedManifest)
- throws MergeErrorException {
+ throws IOException {
ManifestMerger2.MergeType mergeType =
variantType == VariantType.DEFAULT
@@ -225,55 +225,91 @@ public class AndroidManifestProcessor {
variantType == VariantType.DEFAULT ? applicationId : customPackageForR;
if (versionCode != -1 || versionName != null || newManifestPackage != null) {
- try {
- Files.createDirectories(processedManifest.getParent());
-
- // The generics on Invoker don't make sense, so ignore them.
- @SuppressWarnings("unchecked")
- Invoker<?> manifestMergerInvoker =
- ManifestMerger2.newMerger(primaryData.getManifest().toFile(), stdLogger, mergeType);
- // Stamp new package
- if (newManifestPackage != null) {
- manifestMergerInvoker.setOverride(SystemProperty.PACKAGE, newManifestPackage);
- }
- // Stamp version and applicationId (if provided) into the manifest
- if (versionCode > 0) {
- manifestMergerInvoker.setOverride(
- SystemProperty.VERSION_CODE, String.valueOf(versionCode));
- }
- if (versionName != null) {
- manifestMergerInvoker.setOverride(SystemProperty.VERSION_NAME, versionName);
- }
-
- MergedManifestKind mergedManifestKind = MergedManifestKind.MERGED;
- if (mergeType == ManifestMerger2.MergeType.APPLICATION) {
- manifestMergerInvoker.withFeatures(Invoker.Feature.REMOVE_TOOLS_DECLARATIONS);
- }
-
- MergingReport mergingReport = manifestMergerInvoker.merge();
- switch (mergingReport.getResult()) {
- case WARNING:
- mergingReport.log(stdLogger);
- writeMergedManifest(mergedManifestKind, mergingReport, processedManifest);
- break;
- case SUCCESS:
- writeMergedManifest(mergedManifestKind, mergingReport, processedManifest);
- break;
- case ERROR:
- mergingReport.log(stdLogger);
- throw new MergeErrorException(mergingReport);
- default:
- throw new RuntimeException("Unhandled result type : " + mergingReport.getResult());
- }
- } catch (IOException | MergeFailureException e) {
- throw new RuntimeException(e);
- }
+ processManifest(
+ versionCode,
+ versionName,
+ primaryData.getManifest(),
+ processedManifest,
+ mergeType,
+ newManifestPackage);
return new MergedAndroidData(
primaryData.getResourceDir(), primaryData.getAssetDir(), processedManifest);
}
return primaryData;
}
+ /** Processes the manifest for a binary and return the manifest Path. */
+ public Path processManifest(
+ String applicationId,
+ int versionCode,
+ String versionName,
+ Path manifest,
+ Path processedManifest) {
+
+ ManifestMerger2.MergeType mergeType = ManifestMerger2.MergeType.APPLICATION;
+
+ String newManifestPackage = applicationId;
+
+ if (versionCode != -1 || versionName != null || newManifestPackage != null) {
+ processManifest(
+ versionCode, versionName, manifest, processedManifest, mergeType, newManifestPackage);
+ return processedManifest;
+ }
+ return manifest;
+ }
+
+ private void processManifest(
+ int versionCode,
+ String versionName,
+ Path primaryManifest,
+ Path processedManifest,
+ MergeType mergeType,
+ String newManifestPackage) {
+ try {
+ Files.createDirectories(processedManifest.getParent());
+
+ // The generics on Invoker don't make sense, so ignore them.
+ @SuppressWarnings("unchecked")
+ Invoker<?> manifestMergerInvoker =
+ ManifestMerger2.newMerger(primaryManifest.toFile(), stdLogger, mergeType);
+ // Stamp new package
+ if (newManifestPackage != null) {
+ manifestMergerInvoker.setOverride(SystemProperty.PACKAGE, newManifestPackage);
+ }
+ // Stamp version and applicationId (if provided) into the manifest
+ if (versionCode > 0) {
+ manifestMergerInvoker.setOverride(SystemProperty.VERSION_CODE, String.valueOf(versionCode));
+ }
+ if (versionName != null) {
+ manifestMergerInvoker.setOverride(SystemProperty.VERSION_NAME, versionName);
+ }
+
+ MergedManifestKind mergedManifestKind = MergedManifestKind.MERGED;
+ if (mergeType == MergeType.APPLICATION) {
+ manifestMergerInvoker.withFeatures(Feature.REMOVE_TOOLS_DECLARATIONS);
+ }
+
+ MergingReport mergingReport = manifestMergerInvoker.merge();
+ switch (mergingReport.getResult()) {
+ case WARNING:
+ mergingReport.log(stdLogger);
+ writeMergedManifest(mergedManifestKind, mergingReport, processedManifest);
+ break;
+ case SUCCESS:
+ writeMergedManifest(mergedManifestKind, mergingReport, processedManifest);
+ break;
+ case ERROR:
+ mergingReport.log(stdLogger);
+ throw new ManifestProcessingException(mergingReport.getReportString());
+ default:
+ throw new ManifestProcessingException(
+ "Unhandled result type : " + mergingReport.getResult());
+ }
+ } catch (IOException | MergeFailureException e) {
+ throw new ManifestProcessingException(e);
+ }
+ }
+
/**
* Overwrite the package attribute of {@code <manifest>} in an AndroidManifest.xml file.
*
@@ -323,18 +359,22 @@ public class AndroidManifestProcessor {
}
writer.flush();
} catch (XMLStreamException | FactoryConfigurationError | IOException e) {
- throw new RuntimeException(e);
+ throw new ManifestProcessingException(e);
}
return output;
}
- private void writeMergedManifest(
+ public void writeMergedManifest(
MergedManifestKind mergedManifestKind, MergingReport mergingReport, Path manifestOut)
- throws IOException {
+ throws ManifestProcessingException {
String manifestContents = mergingReport.getMergedDocument(mergedManifestKind);
String annotatedDocument = mergingReport.getMergedDocument(MergedManifestKind.BLAME);
stdLogger.verbose(annotatedDocument);
- Files.write(manifestOut, manifestContents.getBytes(UTF_8));
+ try {
+ Files.write(manifestOut, manifestContents.getBytes(UTF_8));
+ } catch (IOException e) {
+ throw new ManifestProcessingException(e);
+ }
}
}
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 6840ac8cc4..1fb31561ed 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
@@ -22,7 +22,6 @@ import com.google.common.base.Stopwatch;
import com.google.common.base.Strings;
import com.google.common.io.Files;
import com.google.devtools.build.android.AndroidDataMerger.MergeConflictException;
-import com.google.devtools.build.android.AndroidManifestProcessor.MergeErrorException;
import com.google.devtools.build.android.AndroidResourceMerger.MergingException;
import com.google.devtools.build.android.AndroidResourceProcessor.AaptConfigOptions;
import com.google.devtools.build.android.Converters.ExistingPathConverter;
@@ -287,7 +286,7 @@ public class AndroidResourceMergingAction {
} catch (MergingException e) {
logger.log(Level.SEVERE, "Error during merging resources", e);
throw e;
- } catch (MergeErrorException e) {
+ } catch (AndroidManifestProcessor.ManifestProcessingException e) {
System.exit(1);
} catch (Exception e) {
logger.log(Level.SEVERE, "Unexpected", e);
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 3cad94c83f..97c261610e 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
@@ -15,6 +15,7 @@ package com.google.devtools.build.android;
import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Ordering;
@@ -47,6 +48,7 @@ import java.util.zip.ZipOutputStream;
/** Collects all the functionationality for an action to create the final output artifacts. */
public class AndroidResourceOutputs {
+ @VisibleForTesting
static class ZipBuilder implements Closeable {
// ZIP timestamps have a resolution of 2 seconds.
// see http://www.info-zip.org/FAQ.html#limits
@@ -88,7 +90,8 @@ public class AndroidResourceOutputs {
// Fix the path for windows.
String relativeName = rawName.replace('\\', '/');
// Make sure the zip entry is not absolute.
- Preconditions.checkArgument(!relativeName.startsWith("/"));
+ Preconditions.checkArgument(
+ !relativeName.startsWith("/"), "Cannot add absolute resources %s", relativeName);
ZipEntry entry = new ZipEntry(relativeName);
entry.setMethod(storageMethod);
entry.setTime(normalizeTime(relativeName));
@@ -254,14 +257,13 @@ public class AndroidResourceOutputs {
/**
* Copies the AndroidManifest.xml to the specified output location.
*
- * @param androidData The MergedAndroidData which contains the manifest to be written to
- * manifestOut.
+ * @param provider The MergedAndroidData which contains the manifest to be written to manifestOut.
* @param manifestOut The Path to write the AndroidManifest.xml.
*/
- public static void copyManifestToOutput(MergedAndroidData androidData, Path manifestOut) {
+ public static void copyManifestToOutput(ManifestContainer provider, Path manifestOut) {
try {
Files.createDirectories(manifestOut.getParent());
- Files.copy(androidData.getManifest(), manifestOut);
+ Files.copy(provider.getManifest(), manifestOut);
// Set to the epoch for caching purposes.
Files.setLastModifiedTime(manifestOut, FileTime.fromMillis(0L));
} catch (IOException e) {
@@ -381,7 +383,7 @@ public class AndroidResourceOutputs {
}
/** Collects all the compiled resources into an archive, normalizing the paths to the root. */
- public static void archiveCompiledResources(
+ public static Path archiveCompiledResources(
final Path archiveOut,
final Path databindingResourcesRoot,
final Path compiledRoot,
@@ -389,6 +391,7 @@ public class AndroidResourceOutputs {
throws IOException {
final Path relativeDatabindingProcessedResources =
databindingResourcesRoot.getRoot().relativize(databindingResourcesRoot);
+
try (ZipBuilder builder = ZipBuilder.createFor(archiveOut)) {
for (Path artifact : compiledArtifacts) {
Path relativeName = artifact;
@@ -407,5 +410,6 @@ public class AndroidResourceOutputs {
builder.addEntry(relativeName.toString(), Files.readAllBytes(artifact), ZipEntry.STORED);
}
}
+ return archiveOut;
}
}
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 18b89c998d..cfee8a88e0 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
@@ -23,7 +23,6 @@ import com.android.utils.StdLogger;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.android.AndroidDataMerger.MergeConflictException;
-import com.google.devtools.build.android.AndroidManifestProcessor.MergeErrorException;
import com.google.devtools.build.android.AndroidResourceMerger.MergingException;
import com.google.devtools.build.android.AndroidResourceProcessor.AaptConfigOptions;
import com.google.devtools.build.android.AndroidResourceProcessor.FlagAaptOptions;
@@ -298,12 +297,14 @@ public class AndroidResourceProcessingAction {
)
public List<String> prefilteredResources;
- @Option(name = "throwOnResourceConflict",
- defaultValue = "false",
- category = "config",
- documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
- effectTags = {OptionEffectTag.UNKNOWN},
- help = "If passed, resource merge conflicts will be treated as errors instead of warnings")
+ @Option(
+ name = "throwOnResourceConflict",
+ defaultValue = "false",
+ category = "config",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "If passed, resource merge conflicts will be treated as errors instead of warnings"
+ )
public boolean throwOnResourceConflict;
}
@@ -452,7 +453,7 @@ public class AndroidResourceProcessingAction {
| UnrecognizedSplitsException e) {
logger.log(java.util.logging.Level.SEVERE, "Error during processing resources", e);
throw e;
- } catch (MergeErrorException e) {
+ } catch (AndroidManifestProcessor.ManifestProcessingException e) {
System.exit(1);
} catch (Exception e) {
logger.log(java.util.logging.Level.SEVERE, "Unexpected", e);
diff --git a/src/tools/android/java/com/google/devtools/build/android/Converters.java b/src/tools/android/java/com/google/devtools/build/android/Converters.java
index db88ade4d1..26d918b278 100644
--- a/src/tools/android/java/com/google/devtools/build/android/Converters.java
+++ b/src/tools/android/java/com/google/devtools/build/android/Converters.java
@@ -465,15 +465,15 @@ public final class Converters {
/** Converts a list of static library strings into paths. */
@Deprecated
public static class StaticLibraryListConverter implements Converter<List<StaticLibrary>> {
- static final Splitter SPLITTER = Splitter.on(File.pathSeparator);
+ static final Splitter SPLITTER = Splitter.on(File.pathSeparatorChar);
static final StaticLibraryConverter libraryConverter = new StaticLibraryConverter();
@Override
public List<StaticLibrary> convert(String input) throws OptionsParsingException {
final Builder<StaticLibrary> builder = ImmutableList.<StaticLibrary>builder();
- for (String unused : SPLITTER.splitToList(input)) {
- builder.add(libraryConverter.convert(input));
+ for (String path : SPLITTER.splitToList(input)) {
+ builder.add(libraryConverter.convert(path));
}
return builder.build();
}
@@ -486,7 +486,6 @@ public final class Converters {
/** Converts a static library string into path. */
public static class StaticLibraryConverter implements Converter<StaticLibrary> {
- static final Splitter SPLITTER = Splitter.on(File.pathSeparator);
static final PathConverter pathConverter = new PathConverter(true);
diff --git a/src/tools/android/java/com/google/devtools/build/android/DensitySpecificManifestProcessor.java b/src/tools/android/java/com/google/devtools/build/android/DensitySpecificManifestProcessor.java
index 5616a2adb5..8a08978f94 100644
--- a/src/tools/android/java/com/google/devtools/build/android/DensitySpecificManifestProcessor.java
+++ b/src/tools/android/java/com/google/devtools/build/android/DensitySpecificManifestProcessor.java
@@ -101,7 +101,7 @@ public class DensitySpecificManifestProcessor {
*
* @throws ManifestProcessingException when the manifest cannot be properly modified.
*/
- public Path process(Path manifest) throws ManifestProcessingException {
+ public Path process(Path manifest) {
if (densities.isEmpty()) {
return manifest;
}
@@ -111,9 +111,10 @@ public class DensitySpecificManifestProcessor {
NodeList manifestElements = doc.getElementsByTagName("manifest");
if (manifestElements.getLength() != 1) {
- throw new ManifestProcessingException(
- String.format("Manifest %s does not contain exactly one <manifest> tag. "
- + "It contains %d.", manifest, manifestElements.getLength()));
+ throw new AndroidManifestProcessor.ManifestProcessingException(
+ String.format(
+ "Manifest %s does not contain exactly one <manifest> tag. " + "It contains %d.",
+ manifest, manifestElements.getLength()));
}
Node manifestElement = manifestElements.item(0);
@@ -170,7 +171,7 @@ public class DensitySpecificManifestProcessor {
return out;
} catch (ParserConfigurationException | SAXException | IOException | TransformerException e) {
- throw new ManifestProcessingException(e.getMessage());
+ throw new AndroidManifestProcessor.ManifestProcessingException(e.getMessage());
}
}
}
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 be7977ff60..f7ed888690 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
@@ -16,6 +16,7 @@ package com.google.devtools.build.android;
import com.android.builder.dependency.SymbolFileProvider;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.android.aapt2.StaticLibrary;
import java.io.File;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
@@ -26,19 +27,18 @@ import java.util.regex.Pattern;
/**
* Contains the assets, resources, manifest and resource symbols for an android_library dependency.
*
- * <p>
- * This class serves the role of both a processed MergedAndroidData and a dependency exported from
- * another invocation of the AndroidResourcesProcessorAction. Since it's presumed to be cheaper to
- * only pass the derived artifact (rTxt) rather that the entirety of the processed dependencies (png
- * crunching and resource processing should be saved for the final AndroidResourcesProcessorAction
- * invocation) AndroidData can have multiple roots for resources and assets.
- * </p>
+ * <p>This class serves the role of both a processed MergedAndroidData and a dependency exported
+ * from another invocation of the AndroidResourcesProcessorAction. Since it's presumed to be cheaper
+ * to only pass the derived artifact (rTxt) rather that the entirety of the processed dependencies
+ * (png crunching and resource processing should be saved for the final
+ * AndroidResourcesProcessorAction invocation) AndroidData can have multiple roots for resources and
+ * assets.
*/
class DependencyAndroidData extends SerializedAndroidData {
- private static final Pattern VALID_REGEX = Pattern.compile(".*:.*:.+:.+(:.*)?");
+ 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";
+ "resources[#resources]:assets[#assets]:manifest:r.txt:symbols.bin:static.library.ap_";
public static DependencyAndroidData valueOf(String text) {
return valueOf(text, FileSystems.getDefault());
@@ -47,33 +47,41 @@ class DependencyAndroidData extends SerializedAndroidData {
@VisibleForTesting
static DependencyAndroidData 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(":");
// TODO(bazel-team): Handle the symbols.bin file.
// The local symbols.bin is optional -- if it is missing, we'll use the full R.txt
+ Path rTxt = exists(fileSystem.getPath(parts[3]));
+ ImmutableList<Path> assetDirs =
+ parts[1].length() == 0 ? ImmutableList.<Path>of() : splitPaths(parts[1], fileSystem);
return new DependencyAndroidData(
splitPaths(parts[0], fileSystem),
- parts[1].length() == 0 ? ImmutableList.<Path>of() : splitPaths(parts[1], fileSystem),
+ assetDirs,
exists(fileSystem.getPath(parts[2])),
- exists(fileSystem.getPath(parts[3])),
- parts.length == 5 ? fileSystem.getPath(parts[4]) : null);
+ rTxt,
+ parts.length > 4 ? fileSystem.getPath(parts[4]) : null,
+ parts.length > 5
+ ? StaticLibrary.from(exists(fileSystem.getPath(parts[5])), rTxt, assetDirs)
+ : null);
}
private final Path manifest;
private final Path rTxt;
+ private final StaticLibrary staticLibrary;
public DependencyAndroidData(
ImmutableList<Path> resourceDirs,
ImmutableList<Path> assetDirs,
Path manifest,
Path rTxt,
- Path symbols) {
+ Path symbols,
+ StaticLibrary staticLibrary) {
// Use the manifest as a label for now.
super(resourceDirs, assetDirs, manifest.toString(), symbols);
this.manifest = manifest;
this.rTxt = rTxt;
+ this.staticLibrary = staticLibrary;
}
public SymbolFileProvider asSymbolFileProvider() {
@@ -110,6 +118,10 @@ class DependencyAndroidData extends SerializedAndroidData {
};
}
+ public StaticLibrary getStaticLibrary() {
+ return staticLibrary;
+ }
+
@Override
public String toString() {
return String.format(
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 374c8a5db0..cc99342ddf 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
@@ -14,9 +14,11 @@
package com.google.devtools.build.android;
import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
import java.io.Closeable;
import java.io.IOException;
import java.util.List;
+import java.util.concurrent.Executors;
/** Shutdowns and verifies that no tasks are running in the executor service. */
final class ExecutorServiceCloser implements Closeable {
@@ -37,4 +39,18 @@ final class ExecutorServiceCloser implements Closeable {
public static Closeable createWith(ListeningExecutorService executorService) {
return new ExecutorServiceCloser(executorService);
}
+
+ /**
+ * Creates a {@link ListeningExecutorService} with a sane sized thread pool based on our current
+ * metrics.
+ */
+ public static ListeningExecutorService createDefaultService() {
+ // 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));
+ return executorService;
+ }
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/ManifestContainer.java b/src/tools/android/java/com/google/devtools/build/android/ManifestContainer.java
new file mode 100644
index 0000000000..7b672be37d
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/ManifestContainer.java
@@ -0,0 +1,21 @@
+// 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 java.nio.file.Path;
+
+/** Represents a container that can be converted to a manifest. */
+public interface ManifestContainer {
+ Path getManifest();
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java b/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java
index 2bd9a7756b..5f0681e8df 100644
--- a/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java
+++ b/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java
@@ -18,7 +18,6 @@ import static java.util.logging.Level.SEVERE;
import com.android.manifmerger.ManifestMerger2.MergeType;
import com.android.utils.StdLogger;
import com.google.common.collect.ImmutableMap;
-import com.google.devtools.build.android.AndroidManifestProcessor.MergeErrorException;
import com.google.devtools.build.android.Converters.ExistingPathConverter;
import com.google.devtools.build.android.Converters.ExistingPathStringDictionaryConverter;
import com.google.devtools.build.android.Converters.MergeTypeConverter;
@@ -224,7 +223,7 @@ public class ManifestMergerAction {
// Set to the epoch for caching purposes.
Files.setLastModifiedTime(options.manifestOutput, FileTime.fromMillis(0L));
- } catch (MergeErrorException e) {
+ } catch (AndroidManifestProcessor.ManifestProcessingException e) {
System.exit(1);
} catch (Exception e) {
logger.log(SEVERE, "Error during merging manifests", e);
diff --git a/src/tools/android/java/com/google/devtools/build/android/MergedAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/MergedAndroidData.java
index a69b02d71b..431d205e12 100644
--- a/src/tools/android/java/com/google/devtools/build/android/MergedAndroidData.java
+++ b/src/tools/android/java/com/google/devtools/build/android/MergedAndroidData.java
@@ -17,15 +17,15 @@ import java.nio.file.Path;
/**
* Represents the AndroidData before processing, after merging.
- *
- * <p>
- * The life cycle of AndroidData goes:
+ *
+ * <p>The life cycle of AndroidData goes:
+ *
* <pre>
* UnvalidatedAndroidData -> MergedAndroidData -> DensityFilteredAndroidData
* -> DependencyAndroidData
* </pre>
*/
-class MergedAndroidData {
+class MergedAndroidData implements ManifestContainer {
private Path resourceDir;
private Path assetDir;
@@ -45,6 +45,7 @@ class MergedAndroidData {
return assetDir;
}
+ @Override
public Path getManifest() {
return manifest;
}
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 5e89dcdec5..991a5d5bf7 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
@@ -114,6 +114,12 @@ public class ResourceProcessorBusyBox {
void call(String[] args) throws Exception {
CompileLibraryResourcesAction.main(args);
}
+ },
+ AAPT2_PACKAGE() {
+ @Override
+ void call(String[] args) throws Exception {
+ Aapt2ResourcePackagingAction.main(args);
+ }
};
abstract void call(String[] args) throws Exception;
@@ -139,7 +145,8 @@ 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, COMPILE_LIBRARY_RESOURCES."
+ + "MERGE, GENERATE_AAR, SHRINK, MERGE_MANIFEST, COMPILE_LIBRARY_RESOURCES, "
+ + "AAPT2_PACKAGE."
)
public Tool tool;
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/SerializedAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/SerializedAndroidData.java
index ea744c53e5..b2a81d345d 100644
--- a/src/tools/android/java/com/google/devtools/build/android/SerializedAndroidData.java
+++ b/src/tools/android/java/com/google/devtools/build/android/SerializedAndroidData.java
@@ -47,7 +47,7 @@ public class SerializedAndroidData {
splitPaths(parts[0], fileSystem),
splitPaths(parts[1], fileSystem),
parts[2],
- parts.length > 3 ? fileSystem.getPath(parts[3]) : null);
+ parts.length > 3 ? exists(fileSystem.getPath(parts[3])) : null);
}
protected static ImmutableList<Path> splitPaths(String pathsString, FileSystem fileSystem) {
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 20717d6005..74976543e9 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
@@ -15,20 +15,25 @@ package com.google.devtools.build.android;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.android.aapt2.CompiledResources;
+import com.google.devtools.build.android.aapt2.ResourceCompiler;
+import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
+import java.util.List;
import java.util.Objects;
+import java.util.concurrent.ExecutionException;
import java.util.regex.Pattern;
/**
* Android data that has yet to be merged and validated, the primary data for the Processor.
*
- * <p>The life cycle of AndroidData goes:
- * {@link UnvalidatedAndroidData} -> {@link MergedAndroidData} -> {@link DensityFilteredAndroidData}
- * -> {@link DependencyAndroidData}
+ * <p>The life cycle of AndroidData goes: {@link UnvalidatedAndroidData} -> {@link
+ * MergedAndroidData} -> {@link DensityFilteredAndroidData} -> {@link DependencyAndroidData}
*/
class UnvalidatedAndroidData extends UnvalidatedAndroidDirectories {
+
private static final Pattern VALID_REGEX = Pattern.compile(".*:.*:.+");
public static final String EXPECTED_FORMAT = "resources[#resources]:assets[#assets]:manifest";
@@ -86,4 +91,39 @@ class UnvalidatedAndroidData extends UnvalidatedAndroidDirectories {
&& Objects.equals(other.manifest, manifest);
}
+ public CompiledResources compile(ResourceCompiler compiler, Path workingDirectory)
+ throws IOException, ExecutionException, InterruptedException {
+ for (Path resourceDir : resourceDirs) {
+ compiler.queueDirectoryForCompilation(resourceDir);
+ }
+ return archiveCompiledResources(
+ compiler.getCompiledArtifacts(),
+ workingDirectory,
+ workingDirectory.resolve("compiled.zip"));
+ }
+
+ protected CompiledResources archiveCompiledResources(
+ List<Path> resources, Path workingDirectory, Path output) throws IOException {
+ return CompiledResources.from(
+ AndroidResourceOutputs.archiveCompiledResources(
+ output, workingDirectory, workingDirectory, resources),
+ manifest,
+ assetDirs);
+ }
+
+ public UnvalidatedAndroidData processDataBindings(
+ Path dataBindingInfoOut, Path dataBindingWorkingDirectory) {
+
+ return new UnvalidatedAndroidData(resourceDirs, assetDirs, manifest) {
+ @Override
+ protected CompiledResources archiveCompiledResources(
+ List<Path> resources, Path workingDirectory, Path output) throws IOException {
+ return CompiledResources.from(
+ AndroidResourceOutputs.archiveCompiledResources(
+ output, dataBindingWorkingDirectory, workingDirectory, resources),
+ manifest,
+ assetDirs);
+ }
+ };
+ }
}
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 bfe57af3a1..ada69bc1a3 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
@@ -47,4 +47,15 @@ public class Aapt2ConfigOptions extends OptionsBase {
help = "Version of the build tools (e.g. aapt) being used, e.g. 23.0.2"
)
public Revision buildToolsVersion;
+
+ @Option(
+ name = "androidJar",
+ 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 androidJar;
}
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 65f9c1a9a6..81d94fb244 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,32 +13,56 @@
// limitations under the License.
package com.google.devtools.build.android.aapt2;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.android.ManifestContainer;
import java.nio.file.Path;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
/**
* Contains reference to the aapt2 generated .flat file archive and a manifest.
*
* <p>This represents the state between the aapt2 compile and link actions.
*/
-public class CompiledResources {
+public class CompiledResources implements ManifestContainer {
private final Path resources;
private final Path manifest;
+ private final List<Path> assetsDirs;
- private CompiledResources(Path resources, Path manifest) {
+ private CompiledResources(Path resources, Path manifest, List<Path> assetsDirs) {
this.resources = resources;
this.manifest = manifest;
+ this.assetsDirs = assetsDirs;
}
public static CompiledResources from(Path resources, Path manifest) {
- return new CompiledResources(resources, manifest);
+ return from(resources, manifest, null);
}
- public Path asZip() {
+ public static CompiledResources from(
+ Path resources, Path manifest, @Nullable List<Path> assetDirs) {
+ return new CompiledResources(
+ resources, manifest, Optional.ofNullable(assetDirs).orElseGet(ImmutableList::of));
+ }
+
+ public Path getZip() {
return resources;
}
- public Path asManifest() {
+ @Override
+ public Path getManifest() {
return manifest;
}
+
+ public List<String> getAssetsStrings() {
+ return assetsDirs.stream().map(Path::toString).collect(Collectors.toList());
+ }
+
+ public CompiledResources processManifest(Function<Path, Path> processManifest) {
+ return new CompiledResources(resources, processManifest.apply(manifest), assetsDirs);
+ }
}
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
new file mode 100644
index 0000000000..77c9c66cc6
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/PackagedResources.java
@@ -0,0 +1,88 @@
+// 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.google.devtools.build.android.AndroidResourceOutputs;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/** Represents the packaged, flattened resources. */
+public class PackagedResources {
+
+ private final Path apk;
+ private final Path rTxt;
+ private final Path proguardConfig;
+ private final Path mainDexProguard;
+ private final Path javaSourceDirectory;
+
+ private PackagedResources(
+ Path apk, Path rTxt, Path proguardConfig, Path mainDexProguard, Path javaSourceDirectory) {
+ this.apk = apk;
+ this.rTxt = rTxt;
+ this.proguardConfig = proguardConfig;
+ this.mainDexProguard = mainDexProguard;
+ this.javaSourceDirectory = javaSourceDirectory;
+ }
+
+ public PackagedResources copyPackageTo(Path packagePath) throws IOException {
+ return new PackagedResources(
+ copy(apk, packagePath), rTxt, proguardConfig, mainDexProguard, javaSourceDirectory);
+ }
+
+ public PackagedResources copyRTxtTo(Path rOutput) throws IOException {
+ if (rOutput == null) {
+ return this;
+ }
+ return new PackagedResources(
+ apk, copy(rTxt, rOutput), proguardConfig, mainDexProguard, javaSourceDirectory);
+ }
+
+ private Path copy(Path from, Path out) throws IOException {
+ Files.createDirectories(out.getParent());
+ Files.copy(from, out);
+ return out;
+ }
+
+ public PackagedResources copyProguardTo(Path proguardOut) throws IOException {
+ if (proguardOut == null) {
+ return this;
+ }
+ return new PackagedResources(
+ apk, rTxt, copy(proguardConfig, proguardOut), mainDexProguard, javaSourceDirectory);
+ }
+
+ public PackagedResources copyMainDexProguardTo(Path mainDexProguardOut) throws IOException {
+ if (mainDexProguardOut == null) {
+ return this;
+ }
+ return of(
+ apk, rTxt, proguardConfig, copy(mainDexProguard, mainDexProguardOut), javaSourceDirectory);
+ }
+
+ public PackagedResources createSourceJar(Path sourceJarPath) throws IOException {
+ if (sourceJarPath == null) {
+ return this;
+ }
+ AndroidResourceOutputs.createSrcJar(javaSourceDirectory, sourceJarPath, false);
+ return of(apk, rTxt, proguardConfig, mainDexProguard, sourceJarPath);
+ }
+
+ public static PackagedResources of(
+ Path outPath, Path rTxt, Path proguardConfig, Path mainDexProguard, Path javaSourceDirectory)
+ throws IOException {
+ return new PackagedResources(
+ outPath, rTxt, proguardConfig, mainDexProguard, javaSourceDirectory);
+ }
+}
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 240f8e0d04..e13dc613ca 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,15 +16,11 @@ 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;
@@ -34,9 +30,11 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
+import java.util.logging.Logger;
/** Invokes aapt2 to compile resources. */
public class ResourceCompiler {
+ private static final Logger logger = Logger.getLogger(ResourceCompiler.class.getName());
private final CompilingVisitor compilingVisitor;
@@ -57,8 +55,7 @@ public class ResourceCompiler {
@Override
public Path call() throws Exception {
- List<String> processLog = new ArrayList<>();
- AaptCommandBuilder commandBuilder =
+ logger.fine(
new AaptCommandBuilder(aapt2)
.forBuildToolsVersion(buildToolsVersion)
.forVariantType(VariantType.LIBRARY)
@@ -66,20 +63,8 @@ public class ResourceCompiler {
.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));
- }
+ .add(file.toString())
+ .execute("Compiling " + file));
String type = file.getParent().getFileName().toString();
String filename = file.getFileName().toString();
if (type.startsWith("values")) {
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 eb17f2dc9f..fa9edc3044 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
@@ -15,18 +15,43 @@ 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.base.MoreObjects;
+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 java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.zip.ZipFile;
/** Performs linking of {@link CompiledResources} using aapt2. */
public class ResourceLinker {
+ /** Represents errors thrown during linking. */
+ public static class LinkError extends RuntimeException {
+ public LinkError(Throwable e) {
+ super(e);
+ }
+ }
+
+ private static final Logger logger = Logger.getLogger(ResourceLinker.class.getName());
+
private final Path aapt2;
- private List<StaticLibrary> libraries;
- private Revision buildToolsVersion;
private final Path workingDirectory;
+ private List<StaticLibrary> linkAgainst = ImmutableList.of();
+ private Revision buildToolsVersion;
+ private String density;
+ private Path androidJar;
+ private List<String> uncompressedExtensions = ImmutableList.of();
+ private List<String> resourceConfigs = ImmutableList.of();
+ private Path baseApk;
+ private List<StaticLibrary> include;
private ResourceLinker(Path aapt2, Path workingDirectory) {
this.aapt2 = aapt2;
@@ -37,9 +62,15 @@ public class ResourceLinker {
return new ResourceLinker(aapt2, workingDirectory);
}
- /** Dependent static libraries to be linked to. */
- public ResourceLinker dependencies(List<StaticLibrary> libraries) {
- this.libraries = libraries;
+ /** Dependent static to be linked against. */
+ public ResourceLinker dependencies(List<StaticLibrary> linkAgainst) {
+ this.linkAgainst = linkAgainst;
+ return this;
+ }
+
+ /** Dependent static libraries to be included in the binary. */
+ public ResourceLinker include(List<StaticLibrary> include) {
+ this.include = include;
return this;
}
@@ -48,26 +79,147 @@ public class ResourceLinker {
return this;
}
+ public ResourceLinker baseApkToLinkAgainst(Path baseApk) {
+ this.baseApk = baseApk;
+ return this;
+ }
+
+ public ResourceLinker filterToDensity(List<String> densitiesToFilter) {
+ if (densitiesToFilter.size() > 1) {
+ throw new UnsupportedOperationException("Multiple densities not yet supported with aapt2");
+ }
+ if (densitiesToFilter.size() > 0) {
+ density = Iterables.getOnlyElement(densitiesToFilter);
+ }
+ return this;
+ }
+
/**
* Statically links the {@link CompiledResources} with the dependencies to produce a {@link
* StaticLibrary}.
*
* @throws IOException
*/
- public StaticLibrary linkStatically(CompiledResources resources) throws IOException {
+ public StaticLibrary linkStatically(CompiledResources resources) {
final Path outPath = workingDirectory.resolve("lib.ap_");
- new AaptCommandBuilder(aapt2)
- .forBuildToolsVersion(buildToolsVersion)
- .forVariantType(VariantType.LIBRARY)
- .add("link")
- .add("--manifest", resources.asManifest())
- .add("--static-lib")
- .add("--output-text-symbols", workingDirectory)
- .add("-o", outPath)
- .add("--auto-add-overlay")
- .addRepeated("-I", StaticLibrary.toPathStrings(libraries))
- .add("-R", resources.asZip())
- .execute(String.format("Linking %s", resources));
- return StaticLibrary.from(outPath);
+ Path rTxt = workingDirectory.resolve("R.txt");
+
+ try {
+ logger.fine(
+ new AaptCommandBuilder(aapt2)
+ .forBuildToolsVersion(buildToolsVersion)
+ .forVariantType(VariantType.LIBRARY)
+ .add("link")
+ .add("--manifest", resources.getManifest())
+ .add("--static-lib")
+ .add("--no-static-lib-packages")
+ .whenVersionIsAtLeast(new Revision(23))
+ .thenAdd("--no-version-vectors")
+ .addRepeated("-R", unzipCompiledResources(resources.getZip()))
+ .addRepeated("-I", StaticLibrary.toPathStrings(linkAgainst))
+ .add("--auto-add-overlay")
+ .add("-o", outPath)
+ .add("--java", workingDirectory.resolve("java")) // java needed to create R.txt
+ .add("--output-text-symbols", rTxt)
+ .execute(String.format("Statically linking %s", resources)));
+ return StaticLibrary.from(outPath, rTxt);
+ } catch (IOException e) {
+ throw new LinkError(e);
+ }
+ }
+
+ public PackagedResources link(CompiledResources compiled) {
+ final Path outPath = workingDirectory.resolve("bin.ap_");
+ 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 {
+ logger.fine(
+ new AaptCommandBuilder(aapt2)
+ .forBuildToolsVersion(buildToolsVersion)
+ .forVariantType(VariantType.DEFAULT)
+ .add("link")
+ .whenVersionIsAtLeast(new Revision(23))
+ .thenAdd("--no-version-vectors")
+ .add("--no-static-lib-packages")
+ .when(Level.FINE.equals(logger.getLevel()))
+ .thenAdd("-v")
+ .add("--manifest", compiled.getManifest())
+ .add("--auto-add-overlay")
+ .addRepeated("-A", compiled.getAssetsStrings())
+ .addRepeated("-I", StaticLibrary.toPathStrings(linkAgainst))
+ .addRepeated("-R", StaticLibrary.toPathStrings(include))
+ .addRepeated("-R", unzipCompiledResources(compiled.getZip()))
+ // Never compress apks.
+ .add("-0", "apk")
+ // Add custom no-compress extensions.
+ .addRepeated("-0", uncompressedExtensions)
+ .addRepeated("-A", StaticLibrary.toAssetPaths(include))
+ .when(density != null)
+ .thenAdd("--preferred-density", density)
+ // Filter by resource configuration type.
+ .when(!resourceConfigs.isEmpty())
+ .thenAdd("-c", Joiner.on(',').join(resourceConfigs))
+ .add("--output-text-symbols", rTxt)
+ .add("--java", javaSourceDirectory)
+ .add("--proguard", proguardConfig)
+ .add("--proguard-main-dex", mainDexProguard)
+ .add("-o", outPath)
+ .execute(String.format("Linking %s", compiled.getManifest())));
+ return PackagedResources.of(
+ outPath, rTxt, proguardConfig, mainDexProguard, javaSourceDirectory);
+ } catch (IOException e) {
+ throw new LinkError(e);
+ }
+ }
+
+ private List<String> unzipCompiledResources(Path resourceZip) throws IOException {
+ final ZipFile zipFile = new ZipFile(resourceZip.toFile());
+ return zipFile
+ .stream()
+ .map(
+ entry -> {
+ final Path resolve = workingDirectory.resolve(entry.getName());
+ try {
+ Files.createDirectories(resolve.getParent());
+ return Files.write(resolve, ByteStreams.toByteArray(zipFile.getInputStream(entry)));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ })
+ .map(Path::toString)
+ .collect(Collectors.toList());
+ }
+
+ public ResourceLinker storeUncompressed(List<String> uncompressedExtensions) {
+ this.uncompressedExtensions = uncompressedExtensions;
+ return this;
+ }
+
+ public ResourceLinker includeOnlyConfigs(List<String> resourceConfigs) {
+ this.resourceConfigs = resourceConfigs;
+ return this;
+ }
+
+ public ResourceLinker using(Path androidJar) {
+ this.androidJar = androidJar;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("aapt2", aapt2)
+ .add("linkAgainst", linkAgainst)
+ .add("buildToolsVersion", buildToolsVersion)
+ .add("workingDirectory", workingDirectory)
+ .add("density", density)
+ .add("androidJar", androidJar)
+ .add("uncompressedExtensions", uncompressedExtensions)
+ .add("resourceConfigs", resourceConfigs)
+ .add("baseApk", baseApk)
+ .toString();
}
}
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 8828e87042..fc574f89ff 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
@@ -13,7 +13,10 @@
// limitations under the License.
package com.google.devtools.build.android.aapt2;
-import com.google.common.base.Function;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.nio.file.Files;
@@ -21,47 +24,95 @@ import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
+import javax.annotation.Nullable;
-/** A library generated by aapt2. */
+/** A static library generated by aapt2. */
public class StaticLibrary {
private final Path library;
private final Optional<Path> rTxt;
+ private final Optional<List<Path>> assets;
- private static final Function<StaticLibrary, String> LIBRARY_TO_STRING =
- input -> input.library.toString();
-
- private StaticLibrary(Path library, Optional<Path> rTxt) {
+ private StaticLibrary(Path library, Optional<Path> rTxt, Optional<List<Path>> assets) {
this.library = library;
this.rTxt = rTxt;
+ this.assets = assets;
}
public static StaticLibrary from(Path library) {
- return new StaticLibrary(library, Optional.empty());
+ return of(library, Optional.empty(), Optional.empty());
}
public static StaticLibrary from(Path library, Path rTxt) {
- return new StaticLibrary(library, Optional.of(rTxt));
+ return of(library, Optional.of(rTxt), Optional.empty());
+ }
+
+ private static StaticLibrary of(Path library, Optional<Path> rTxt, Optional<List<Path>> assets) {
+ return new StaticLibrary(library, rTxt, assets);
}
public StaticLibrary copyLibraryTo(Path target) throws IOException {
- return new StaticLibrary(Files.copy(library, target), rTxt);
+ return of(Files.copy(library, target), rTxt, assets);
}
- public StaticLibrary copyRTxtTo(final Path target) {
- return new StaticLibrary(
+ public StaticLibrary copyRTxtTo(@Nullable final Path target) {
+ 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);
+ }
+
+ @VisibleForTesting
+ public String asLibraryPathString() {
+ return fixForAapt2(library).toString();
+ }
+
+ // TODO(b/64140845): aapt2 doesn't recognize .ap_ as a valid library.
+ private static Path fixForAapt2(Path library) {
+ if (library.getFileName().toString().endsWith(".ap_")) {
+ Path fixed = library.resolveSibling(library.getFileName().toString().replace(".ap_", ".apk"));
+ // The file has already been "fixed"
+ if (Files.exists(fixed)) {
+ return fixed;
+ }
+ try {
+
+ return Files.copy(library, fixed);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return library;
+ }
+
+ private List<Path> asAssetPathStrings() {
+ return assets.orElse(ImmutableList.of());
}
public static Collection<String> toPathStrings(List<StaticLibrary> libraries) {
- return Lists.transform(libraries, LIBRARY_TO_STRING);
+ 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()
+ .map(StaticLibrary::asAssetPathStrings)
+ .flatMap(List::stream)
+ .map(Object::toString)
+ .collect(toImmutableList());
}
}