aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools
diff options
context:
space:
mode:
authorGravatar corysmith <corysmith@google.com>2018-07-31 10:39:15 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-07-31 10:41:13 -0700
commit3852edd46cb777191728c42e62873e39fd9fe4c4 (patch)
tree8acda6f07d4ff6d0e65337c0bbffd5040220fec0 /src/tools
parentab8a844240cf6ccb4ee46e6bb8795fcc2b3fe6be (diff)
Automated rollback of commit 0a635c5236ce30ea84b765ce752267992733a649.
*** Reason for rollback *** Rolling forward with the correct attribute handling. *** Original change description *** Automated rollback of commit 8fe0f45852a620a078013310989396caed273342. *** Reason for rollback *** Breaks a couple of builds due to a bad merge. *** Original change description *** Add apk converted to proto and all attributes from CompiledResources to ResourcesZip. Add new proto format for tool attributes stored in the AndroidDataXml for storing them in the resources.zip. RELNOTES:None PiperOrigin-RevId: 206786645
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java85
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/Aapt2ResourceShrinkingAction.java127
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java76
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ResourcesZip.java96
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/aapt2/PackagedResources.java45
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceLinker.java171
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/proto/serialize_format.proto8
7 files changed, 484 insertions, 124 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 6fc5e76419..ac89b36ee3 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
@@ -119,10 +119,7 @@ public class Aapt2ResourcePackagingAction {
options.densities, filteredResources, mergedResources),
new DensitySpecificManifestProcessor(options.densities, densityManifest));
- profiler.recordEndOf("merging");
-
-
- profiler.startTask("compile");
+ profiler.recordEndOf("merging").startTask("compile");
final ResourceCompiler compiler =
ResourceCompiler.create(
executorService,
@@ -131,37 +128,37 @@ public class Aapt2ResourcePackagingAction {
aaptConfigOptions.buildToolsVersion,
aaptConfigOptions.generatePseudoLocale);
- CompiledResources compiled =
- options
- .primaryData
- .processDataBindings(
- options.dataBindingInfoOut, options.packageForR, 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));
- profiler.recordEndOf("compile").startTask("link");
- // Write manifestOutput now before the dummy manifest is created.
- if (options.manifestOutput != null) {
- AndroidResourceOutputs.copyManifestToOutput(compiled, options.manifestOutput);
- }
-
- List<CompiledResources> compiledResourceDeps =
- // Last defined dependencies will overwrite previous one, so always place direct
- // after transitive.
- concat(options.transitiveData.stream(), options.directData.stream())
- .map(DependencyAndroidData::getCompiledSymbols)
- .collect(toList());
+ CompiledResources compiled =
+ options
+ .primaryData
+ .processDataBindings(
+ options.dataBindingInfoOut, options.packageForR, 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));
+ profiler.recordEndOf("compile").startTask("link");
+ // Write manifestOutput now before the dummy manifest is created.
+ if (options.manifestOutput != null) {
+ AndroidResourceOutputs.copyManifestToOutput(compiled, options.manifestOutput);
+ }
+
+ List<CompiledResources> compiledResourceDeps =
+ // Last defined dependencies will overwrite previous one, so always place direct
+ // after transitive.
+ concat(options.transitiveData.stream(), options.directData.stream())
+ .map(DependencyAndroidData::getCompiledSymbols)
+ .collect(toList());
List<Path> assetDirs =
concat(
@@ -171,7 +168,7 @@ public class Aapt2ResourcePackagingAction {
options.directAssets.stream())
.flatMap(dep -> dep.assetDirs.stream())
.collect(toList());
- assetDirs.addAll(options.primaryData.assetDirs);
+ assetDirs.addAll(options.primaryData.assetDirs);
final PackagedResources packagedResources =
ResourceLinker.create(aaptConfigOptions.aapt2, executorService, linkedOut)
@@ -193,18 +190,14 @@ public class Aapt2ResourcePackagingAction {
.copyMainDexProguardTo(options.mainDexProguardOutput)
.createSourceJar(options.srcJarOutput)
.copyRTxtTo(options.rOutput);
- profiler.recordEndOf("link");
- if (options.resourcesOutput != null) {
- profiler.startTask("package");
+ profiler.recordEndOf("link");
+ 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.
- ResourcesZip.fromApk(
- mergedAndroidData.getResourceDir(),
- packagedResources.getApk(),
- packagedResources.getResourceIds())
- .writeTo(options.resourcesOutput, /* compress= */ false);
- profiler.recordEndOf("package");
- }
+ packagedResources
+ .packageWith(mergedAndroidData.getResourceDir())
+ .writeTo(options.resourcesOutput, false);
}
}
}
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourceShrinkingAction.java b/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourceShrinkingAction.java
index af2a98ab2f..8d1be26dab 100644
--- a/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourceShrinkingAction.java
+++ b/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourceShrinkingAction.java
@@ -25,12 +25,16 @@ 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.Option;
+import com.google.devtools.common.options.OptionDocumentationCategory;
+import com.google.devtools.common.options.OptionEffectTag;
+import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParser;
import com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor;
-import java.io.Closeable;
import java.io.File;
import java.nio.file.FileSystems;
import java.nio.file.Path;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
@@ -53,68 +57,101 @@ import java.util.function.Function;
*/
public class Aapt2ResourceShrinkingAction {
+ /** Aapt2 shrinking specific options */
+ public static final class Aapt2ShrinkOptions extends OptionsBase {
+ @Option(
+ name = "useProtoApk",
+ defaultValue = "false",
+ category = "config",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "Path to the shrunk jar from a Proguard run with shrinking enabled.")
+ public boolean useProtoApk;
+ }
+
public static void main(String[] args) throws Exception {
final Profiler profiler = LoggingProfiler.createAndStart("shrink").startTask("flags");
// Parse arguments.
OptionsParser optionsParser =
- OptionsParser.newOptionsParser(Options.class, Aapt2ConfigOptions.class);
+ OptionsParser.newOptionsParser(
+ ImmutableList.of(Options.class, Aapt2ConfigOptions.class, Aapt2ShrinkOptions.class));
optionsParser.enableParamsFileSupport(
new ShellQuotedParamsFilePreProcessor(FileSystems.getDefault()));
optionsParser.parseAndExitUponError(args);
Aapt2ConfigOptions aapt2ConfigOptions = optionsParser.getOptions(Aapt2ConfigOptions.class);
Options options = optionsParser.getOptions(Options.class);
+ Aapt2ShrinkOptions aapt2ShrinkOptions = optionsParser.getOptions(Aapt2ShrinkOptions.class);
profiler.recordEndOf("flags").startTask("setup");
- final ListeningExecutorService executorService = ExecutorServiceCloser.createDefaultService();
try (ScopedTemporaryDirectory scopedTmp =
new ScopedTemporaryDirectory("android_resources_tmp");
- Closeable closer = ExecutorServiceCloser.createWith(executorService)) {
-
- Path workingResourcesDirectory = scopedTmp.subDirectoryOf("resources");
- final ResourceCompiler resourceCompiler =
- ResourceCompiler.create(
- executorService,
- workingResourcesDirectory,
- aapt2ConfigOptions.aapt2,
- aapt2ConfigOptions.buildToolsVersion,
- aapt2ConfigOptions.generatePseudoLocale);
- profiler.recordEndOf("setup").startTask("compile");
+ ExecutorServiceCloser executorService = ExecutorServiceCloser.createWithFixedPoolOf(15)) {
final ResourcesZip resourcesZip =
ResourcesZip.createFrom(
options.resourcesZip, scopedTmp.subDirectoryOf("merged-resources"));
- final CompiledResources compiled =
- resourcesZip
- .shrink(
- options
- .dependencyManifests
- .stream()
- .map(Path::toFile)
- .map(manifestToPackageUsing(executorService))
- .map(futureToString())
- .collect(toSet()),
- options.rTxt,
- options.shrunkJar,
- options.primaryManifest,
- options.proguardMapping,
- options.log,
- scopedTmp.subDirectoryOf("shrunk-resources"))
- .writeArchiveTo(options.shrunkResources, false)
- .compile(resourceCompiler, workingResourcesDirectory);
- profiler.recordEndOf("compile");
+ Path workingResourcesDirectory = scopedTmp.subDirectoryOf("resources");
+ final ResourceLinker linker =
+ ResourceLinker.create(
+ aapt2ConfigOptions.aapt2, executorService, scopedTmp.subDirectoryOf("linking"))
+ .profileUsing(profiler);
+
+ final Set<String> packages =
+ options
+ .dependencyManifests
+ .stream()
+ .map(Path::toFile)
+ .map(manifestToPackageUsing(executorService))
+ .map(futureToString())
+ .collect(toSet());
- ResourceLinker.create(
- aapt2ConfigOptions.aapt2, executorService, scopedTmp.subDirectoryOf("linking"))
- .profileUsing(profiler)
- .dependencies(ImmutableList.of(StaticLibrary.from(aapt2ConfigOptions.androidJar)))
- .profileUsing(profiler)
- .outputAsProto(aapt2ConfigOptions.resourceTableAsProto)
- .buildVersion(aapt2ConfigOptions.buildToolsVersion)
- .includeOnlyConfigs(aapt2ConfigOptions.resourceConfigs)
- .debug(aapt2ConfigOptions.debug)
- .link(compiled)
- .copyPackageTo(options.shrunkApk)
- .copyRTxtTo(options.rTxtOutput);
+ if (aapt2ShrinkOptions.useProtoApk) {
+ resourcesZip
+ .shrinkUsingProto(
+ packages,
+ options.rTxt,
+ options.shrunkJar,
+ options.primaryManifest,
+ options.proguardMapping,
+ options.log,
+ scopedTmp.subDirectoryOf("shrunk-resources"))
+ .writeBinaryTo(linker, options.shrunkApk)
+ .writeReportTo(options.log)
+ .writeResourceToZip(options.shrunkResources);
+ } else {
+ final ResourceCompiler resourceCompiler =
+ ResourceCompiler.create(
+ executorService,
+ workingResourcesDirectory,
+ aapt2ConfigOptions.aapt2,
+ aapt2ConfigOptions.buildToolsVersion,
+ aapt2ConfigOptions.generatePseudoLocale);
+ profiler.recordEndOf("setup").startTask("compile");
+
+ final CompiledResources compiled =
+ resourcesZip
+ .shrink(
+ packages,
+ options.rTxt,
+ options.shrunkJar,
+ options.primaryManifest,
+ options.proguardMapping,
+ options.log,
+ scopedTmp.subDirectoryOf("shrunk-resources"))
+ .writeArchiveTo(options.shrunkResources, false)
+ .compile(resourceCompiler, workingResourcesDirectory);
+ profiler.recordEndOf("compile");
+ linker
+ .dependencies(ImmutableList.of(StaticLibrary.from(aapt2ConfigOptions.androidJar)))
+ .profileUsing(profiler)
+ .outputAsProto(aapt2ConfigOptions.resourceTableAsProto)
+ .buildVersion(aapt2ConfigOptions.buildToolsVersion)
+ .includeOnlyConfigs(aapt2ConfigOptions.resourceConfigs)
+ .debug(aapt2ConfigOptions.debug)
+ .link(compiled)
+ .copyPackageTo(options.shrunkApk)
+ .copyRTxtTo(options.rTxtOutput);
+ }
profiler.recordEndOf("shrink");
}
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java b/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java
index b4ba3761fe..711a8bb098 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java
@@ -15,7 +15,9 @@ package com.google.devtools.build.android;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.not;
+import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
import android.aapt.pb.internal.ResourcesInternal.CompiledFile;
import com.android.SdkConstants;
@@ -78,6 +80,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.devtools.build.android.FullyQualifiedName.Factory;
+import com.google.devtools.build.android.aapt2.CompiledResources;
import com.google.devtools.build.android.proto.SerializeFormat;
import com.google.devtools.build.android.proto.SerializeFormat.Header;
import com.google.devtools.build.android.xml.ResourcesAttribute.AttributeType;
@@ -88,9 +91,11 @@ import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -99,9 +104,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
import java.util.logging.Logger;
+import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.annotation.concurrent.NotThreadSafe;
@@ -574,11 +582,14 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer
}
private void readAttributesFile(
- InputStream resourceFileStream, FileSystem fileSystem, KeyValueConsumers consumers)
+ InputStream resourceFileStream,
+ FileSystem fileSystem,
+ BiConsumer<DataKey, DataResource> combine,
+ BiConsumer<DataKey, DataResource> overwrite)
throws IOException {
Header header = Header.parseDelimitedFrom(resourceFileStream);
- List<DataKey> fullyQualifiedNames = new ArrayList<>();
+ List<FullyQualifiedName> fullyQualifiedNames = new ArrayList<>();
for (int i = 0; i < header.getEntryCount(); i++) {
SerializeFormat.DataKey protoKey =
SerializeFormat.DataKey.parseDelimitedFrom(resourceFileStream);
@@ -587,7 +598,7 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer
DataSourceTable sourceTable = DataSourceTable.read(resourceFileStream, fileSystem, header);
- for (DataKey fullyQualifiedName : fullyQualifiedNames) {
+ for (FullyQualifiedName fullyQualifiedName : fullyQualifiedNames) {
SerializeFormat.DataValue protoValue =
SerializeFormat.DataValue.parseDelimitedFrom(resourceFileStream);
DataSource source = sourceTable.sourceFromId(protoValue.getSourceId());
@@ -595,13 +606,54 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer
AttributeType attributeType = AttributeType.valueOf(protoValue.getXmlValue().getValueType());
if (attributeType.isCombining()) {
- consumers.combiningConsumer.accept(fullyQualifiedName, dataResourceXml);
+ combine.accept(fullyQualifiedName, dataResourceXml);
} else {
- consumers.overwritingConsumer.accept(fullyQualifiedName, dataResourceXml);
+ overwrite.accept(fullyQualifiedName, dataResourceXml);
}
}
}
+ public Map<DataKey, DataResource> readAttributes(CompiledResources resources) {
+ try (ZipFile zipFile = new ZipFile(resources.getZip().toFile())) {
+ return zipFile
+ .stream()
+ .filter(e -> e.getName().endsWith(".attributes"))
+ .flatMap(
+ entry -> {
+ try {
+ final Stream.Builder<Entry<DataKey, DataResource>> builder = Stream.builder();
+ final BiConsumer<DataKey, DataResource> consumeToStream =
+ (k, v) -> builder.add(new SimpleImmutableEntry<>(k, v));
+ readAttributesFile(
+ zipFile.getInputStream(entry),
+ FileSystems.getDefault(),
+ consumeToStream,
+ consumeToStream);
+ return builder.build();
+ } catch (IOException e) {
+ throw new DeserializationException(e);
+ }
+ })
+ .collect(toImmutableSetMultimap(Entry::getKey, Entry::getValue))
+ .asMap()
+ .entrySet()
+ .stream()
+ .collect(
+ toMap(
+ Entry::getKey,
+ e ->
+ e.getValue()
+ .stream()
+ .reduce(
+ ((FullyQualifiedName) e.getKey()).isOverwritable()
+ ? DataResource::overwrite
+ : DataResource::combineWith)
+ .orElseThrow(IllegalStateException::new)));
+ } catch (IOException e) {
+ throw new DeserializationException(e);
+ }
+ }
+
public void readTable(InputStream in, KeyValueConsumers consumers) throws IOException {
final ResourceTable resourceTable = ResourceTable.parseFrom(in);
readPackages(consumers, resourceTable);
@@ -640,7 +692,11 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer
Factory fqnFactory = Factory.fromDirectoryName(dirNameAndQualifiers);
if (fileZipPath.endsWith(".attributes")) {
- readAttributesFile(resourceFileStream, inPath.getFileSystem(), consumers);
+ readAttributesFile(
+ resourceFileStream,
+ inPath.getFileSystem(),
+ consumers.combiningConsumer,
+ consumers.overwritingConsumer);
} else {
LittleEndianDataInputStream dataInputStream =
new LittleEndianDataInputStream(resourceFileStream);
@@ -699,14 +755,14 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer
((characterCount & 0x7F) << 8) | (byteBuffer.get(stringOffset + 1) & 0xFF);
}
- stringOffset += (characterCount >= (0x80) ? 2 : 1);
+ stringOffset += (characterCount >= 0x80 ? 2 : 1);
int length = byteBuffer.get(stringOffset) & 0xFF;
if ((length & 0x80) != 0) {
length = ((length & 0x7F) << 8) | (byteBuffer.get(stringOffset + 1) & 0xFF);
}
- stringOffset += (length >= (0x80) ? 2 : 1);
+ stringOffset += (length >= 0x80 ? 2 : 1);
strings.add(new String(bytes, stringOffset, length, "UTF8"));
} else {
@@ -716,14 +772,14 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer
((characterCount & 0x7FFF) << 16) | (byteBuffer.get(stringOffset + 2) & 0xFFFF);
}
- stringOffset += 2 * (characterCount >= (0x8000) ? 2 : 1);
+ stringOffset += 2 * (characterCount >= 0x8000 ? 2 : 1);
int length = byteBuffer.get(stringOffset) & 0xFFFF;
if ((length & 0x8000) != 0) {
length = ((length & 0x7FFF) << 16) | (byteBuffer.get(stringOffset + 2) & 0xFFFF);
}
- stringOffset += 2 * (length >= (0x8000) ? 2 : 1);
+ stringOffset += 2 * (length >= 0x8000 ? 2 : 1);
strings.add(new String(bytes, stringOffset, length, "UTF16"));
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/ResourcesZip.java b/src/tools/android/java/com/google/devtools/build/android/ResourcesZip.java
index dd3cb099f3..cf482ff030 100644
--- a/src/tools/android/java/com/google/devtools/build/android/ResourcesZip.java
+++ b/src/tools/android/java/com/google/devtools/build/android/ResourcesZip.java
@@ -22,13 +22,16 @@ import com.google.devtools.build.android.AndroidResourceOutputs.ZipBuilder;
import com.google.devtools.build.android.AndroidResourceOutputs.ZipBuilderVisitorWithDirectories;
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 java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
import java.util.Set;
import java.util.concurrent.ExecutionException;
+import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.annotation.Nullable;
@@ -38,17 +41,28 @@ import org.xml.sax.SAXException;
/** Represents a collection of raw, merged resources with an optional id list. */
public class ResourcesZip {
- private final Path resourcesRoot;
- private final Path assetsRoot;
+ static final Logger logger = Logger.getLogger(ResourcesZip.class.toString());
+
+ @Nullable private final Path resourcesRoot;
+ @Nullable private final Path assetsRoot;
@Nullable private final Path apkWithAssets;
+ @Nullable private final Path proto;
+ @Nullable private final Path attributes;
@Nullable private final Path ids;
private ResourcesZip(
- Path resourcesRoot, Path assetsRoot, @Nullable Path ids, @Nullable Path apkWithAssets) {
+ @Nullable Path resourcesRoot,
+ @Nullable Path assetsRoot,
+ @Nullable Path ids,
+ @Nullable Path apkWithAssets,
+ @Nullable Path proto,
+ @Nullable Path attributes) {
this.resourcesRoot = resourcesRoot;
this.assetsRoot = assetsRoot;
this.ids = ids;
this.apkWithAssets = apkWithAssets;
+ this.proto = proto;
+ this.attributes = attributes;
}
/**
@@ -56,7 +70,7 @@ public class ResourcesZip {
* @param assetsRoot The root of the raw assets.
*/
public static ResourcesZip from(Path resourcesRoot, Path assetsRoot) {
- return new ResourcesZip(resourcesRoot, assetsRoot, null, null);
+ return new ResourcesZip(resourcesRoot, assetsRoot, null, null, null, null);
}
/**
@@ -69,6 +83,8 @@ public class ResourcesZip {
resourcesRoot,
assetsRoot,
resourceIds != null && Files.exists(resourceIds) ? resourceIds : null,
+ null,
+ null,
null);
}
@@ -82,7 +98,27 @@ public class ResourcesZip {
resourcesRoot,
/* assetsRoot= */ null,
resourceIds != null && Files.exists(resourceIds) ? resourceIds : null,
- apkWithAssets);
+ apkWithAssets,
+ null,
+ null);
+ }
+
+ /**
+ * @param proto apk in proto format.
+ * @param attributes Tooling attributes.
+ * @param resourcesRoot The root of the raw resources.
+ * @param apkWithAssets The apk containing assets.
+ * @param resourceIds Optional path to a file containing the resource ids.
+ */
+ public static ResourcesZip fromApkWithProto(
+ Path proto, Path attributes, Path resourcesRoot, Path apkWithAssets, Path resourceIds) {
+ return new ResourcesZip(
+ resourcesRoot,
+ /* assetsRoot= */ null,
+ resourceIds != null && Files.exists(resourceIds) ? resourceIds : null,
+ apkWithAssets,
+ proto,
+ attributes);
}
/** Creates a ResourcesZip from an archive by expanding into the workingDirectory. */
@@ -160,6 +196,15 @@ public class ResourcesZip {
if (ids != null) {
zip.addEntry("ids.txt", Files.readAllBytes(ids), ZipEntry.STORED);
}
+
+ if (proto != null && Files.exists(proto)) {
+ zip.addEntry("apk.pb", Files.readAllBytes(proto), ZipEntry.STORED);
+ }
+
+ if (attributes != null && Files.exists(attributes)) {
+ zip.addEntry("tools.attributes.pb", Files.readAllBytes(attributes), ZipEntry.STORED);
+ }
+
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -181,11 +226,50 @@ public class ResourcesZip {
packages, rTxt, classJar, manifest, proguardMapping, resourcesRoot, logFile)
.shrink(workingDirectory);
return ShrunkResources.of(
- new ResourcesZip(workingDirectory, assetsRoot, ids, null),
+ new ResourcesZip(workingDirectory, assetsRoot, ids, null, null, attributes),
new UnvalidatedAndroidData(
ImmutableList.of(workingDirectory), ImmutableList.of(assetsRoot), manifest));
}
+ public ShrunkProtoApk shrinkUsingProto(
+ Set<String> packages,
+ Path rTxt,
+ Path classJar,
+ Path primaryManifest,
+ Path proguardMapping,
+ Path logFile,
+ Path workingDirectory)
+ throws ParserConfigurationException {
+ throw new UnsupportedOperationException();
+ }
+
+ static class ShrunkProtoApk {
+ private final Path apk;
+ private final Path report;
+
+ ShrunkProtoApk(Path apk, Path report) {
+ this.apk = apk;
+ this.report = report;
+ }
+
+ ShrunkProtoApk writeBinaryTo(ResourceLinker linker, Path binaryOut) throws IOException {
+ Files.copy(linker.convertToBinary(apk), binaryOut, StandardCopyOption.REPLACE_EXISTING);
+ return this;
+ }
+
+ ShrunkProtoApk writeReportTo(Path reportOut) throws IOException {
+ Files.copy(report, reportOut);
+ return this;
+ }
+
+ ShrunkProtoApk writeResourceToZip(Path resourcesZip) throws IOException {
+ try (final ZipBuilder zip = ZipBuilder.createFor(resourcesZip)) {
+ zip.addEntry("apk.pb", Files.readAllBytes(apk), ZipEntry.STORED);
+ }
+ return this;
+ }
+ }
+
static class ShrunkResources {
private ResourcesZip resourcesZip;
diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/PackagedResources.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/PackagedResources.java
index faad2cd868..2d92b4e071 100644
--- a/src/tools/android/java/com/google/devtools/build/android/aapt2/PackagedResources.java
+++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/PackagedResources.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.android.aapt2;
import com.google.devtools.build.android.AndroidResourceOutputs;
+import com.google.devtools.build.android.ResourcesZip;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -23,47 +24,64 @@ import javax.annotation.Nullable;
public class PackagedResources {
private final Path apk;
+ private final Path proto;
private final Path rTxt;
private final Path proguardConfig;
private final Path mainDexProguard;
private final Path javaSourceDirectory;
private final Path resourceIds;
+ private final Path attributes;
private PackagedResources(
Path apk,
+ Path proto,
Path rTxt,
Path proguardConfig,
Path mainDexProguard,
Path javaSourceDirectory,
- Path resourceIds) {
+ Path resourceIds,
+ Path attributes) {
this.apk = apk;
+ this.proto = proto;
this.rTxt = rTxt;
this.proguardConfig = proguardConfig;
this.mainDexProguard = mainDexProguard;
this.javaSourceDirectory = javaSourceDirectory;
this.resourceIds = resourceIds;
+ this.attributes = attributes;
}
public static PackagedResources of(
Path outPath,
+ Path protoPath,
Path rTxt,
Path proguardConfig,
Path mainDexProguard,
Path javaSourceDirectory,
- Path resourceIds)
+ Path resourceIds,
+ Path attributes)
throws IOException {
return new PackagedResources(
- outPath, rTxt, proguardConfig, mainDexProguard, javaSourceDirectory, resourceIds);
+ outPath,
+ protoPath,
+ rTxt,
+ proguardConfig,
+ mainDexProguard,
+ javaSourceDirectory,
+ resourceIds,
+ attributes);
}
public PackagedResources copyPackageTo(Path packagePath) throws IOException {
return of(
copy(apk, packagePath),
+ proto,
rTxt,
proguardConfig,
mainDexProguard,
javaSourceDirectory,
- resourceIds);
+ resourceIds,
+ attributes);
}
public PackagedResources copyRTxtTo(Path rOutput) throws IOException {
@@ -72,11 +90,13 @@ public class PackagedResources {
}
return new PackagedResources(
apk,
+ proto,
copy(rTxt, rOutput),
proguardConfig,
mainDexProguard,
javaSourceDirectory,
- resourceIds);
+ resourceIds,
+ attributes);
}
private Path copy(Path from, Path out) throws IOException {
@@ -91,11 +111,13 @@ public class PackagedResources {
}
return of(
apk,
+ proto,
rTxt,
copy(proguardConfig, proguardOut),
mainDexProguard,
javaSourceDirectory,
- resourceIds);
+ resourceIds,
+ attributes);
}
public PackagedResources copyMainDexProguardTo(Path mainDexProguardOut) throws IOException {
@@ -104,11 +126,13 @@ public class PackagedResources {
}
return of(
apk,
+ proto,
rTxt,
proguardConfig,
copy(mainDexProguard, mainDexProguardOut),
javaSourceDirectory,
- resourceIds);
+ resourceIds,
+ attributes);
}
public PackagedResources createSourceJar(@Nullable Path sourceJarPath) throws IOException {
@@ -116,7 +140,12 @@ public class PackagedResources {
return this;
}
AndroidResourceOutputs.createSrcJar(javaSourceDirectory, sourceJarPath, false);
- return of(apk, rTxt, proguardConfig, mainDexProguard, sourceJarPath, resourceIds);
+ return of(
+ apk, proto, rTxt, proguardConfig, mainDexProguard, sourceJarPath, resourceIds, attributes);
+ }
+
+ public ResourcesZip packageWith(Path resourceRoot) {
+ return ResourcesZip.fromApkWithProto(proto, attributes, resourceRoot, apk, resourceIds);
}
public Path getResourceIds() {
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 3839a5d94b..9ea3703d42 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,26 +21,39 @@ import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
+import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
import com.google.common.collect.Streams;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.devtools.build.android.AaptCommandBuilder;
+import com.google.devtools.build.android.AndroidCompiledDataDeserializer;
+import com.google.devtools.build.android.AndroidDataWritingVisitor;
+import com.google.devtools.build.android.AndroidResourceMerger.MergingException;
import com.google.devtools.build.android.AndroidResourceOutputs;
+import com.google.devtools.build.android.FullyQualifiedName;
import com.google.devtools.build.android.Profiler;
import com.google.devtools.build.android.aapt2.ResourceCompiler.CompiledType;
+import com.google.devtools.build.android.proto.SerializeFormat.ToolAttributes;
+import com.google.devtools.build.android.xml.Namespaces;
import com.google.devtools.build.android.ziputils.DirectoryEntry;
import com.google.devtools.build.android.ziputils.ZipIn;
import com.google.devtools.build.android.ziputils.ZipOut;
+import java.io.BufferedOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Function;
@@ -263,7 +276,7 @@ public class ResourceLinker {
}
private List<String> compiledResourcesToPaths(
- CompiledResources compiled, Predicate<DirectoryEntry> shouldKeep) throws IOException {
+ CompiledResources compiled, Predicate<DirectoryEntry> shouldKeep) {
// Using sequential streams to maintain the overlay order for aapt2.
return Stream.concat(include.stream(), Stream.of(compiled))
.sequential()
@@ -323,9 +336,61 @@ public class ResourceLinker {
R apply(T arg) throws Throwable;
}
+ private String replaceExtension(String fileName, String newExtension) {
+ int lastIndex = fileName.lastIndexOf('.');
+ if (lastIndex == -1) {
+ return fileName.concat(".").concat(newExtension);
+ }
+ return fileName.substring(0, lastIndex).concat(".").concat(newExtension);
+ }
+
+ public Path convertToBinary(Path protoApk) {
+ try {
+ profiler.startTask("convertToBinary");
+ final Path outPath =
+ workingDirectory.resolveSibling(
+ replaceExtension(protoApk.getFileName().toString(), "apk"));
+ logger.fine(
+ new AaptCommandBuilder(aapt2)
+ .add("convert")
+ .add("-o", outPath)
+ .add("--output-format", "binary")
+ .add(protoApk.toString())
+ .execute("Converting " + protoApk));
+ profiler.recordEndOf("convertToBinary");
+ return outPath;
+ } catch (IOException e) {
+ throw new LinkError(e);
+ }
+ }
+
+ public Path optimizeApk(Path apk) {
+ try {
+ profiler.startTask("optimizeApk");
+ final Path outPath =
+ workingDirectory.resolveSibling(
+ replaceExtension(apk.getFileName().toString(), ".optimized.apk"));
+ logger.fine(
+ new AaptCommandBuilder(aapt2)
+ .forBuildToolsVersion(buildToolsVersion)
+ .forVariantType(VariantType.DEFAULT)
+ .add("optimize")
+ .when(Objects.equals(logger.getLevel(), Level.FINE))
+ .thenAdd("-v")
+ .add("-o", outPath)
+ .add(apk.toString())
+ .execute(String.format("Optimizing %s", apk)));
+ return outPath;
+ } catch (IOException e) {
+ throw new LinkError(e);
+ } finally {
+ profiler.recordEndOf("optimizeApk");
+ }
+ }
+
public PackagedResources link(CompiledResources compiled) {
try {
- final Path outPath = workingDirectory.resolve("bin.apk");
+ final Path outPath = workingDirectory.resolve("bin.pb");
Path rTxt = workingDirectory.resolve("R.txt");
Path proguardConfig = workingDirectory.resolve("proguard.cfg");
Path mainDexProguard = workingDirectory.resolve("proguard.maindex.cfg");
@@ -342,8 +407,7 @@ public class ResourceLinker {
.thenAdd("--no-version-vectors")
// Turn off namespaced resources
.add("--no-static-lib-packages")
- .when(outputAsProto)
- .thenAdd("--proto-format")
+ .add("--proto-format")
.when(Objects.equals(logger.getLevel(), Level.FINE))
.thenAdd("-v")
.add("--manifest", compiled.getManifest())
@@ -387,13 +451,35 @@ public class ResourceLinker {
.thenAdd("--proguard-conditional-keep-rules")
.add("-o", outPath)
.execute(String.format("Linking %s", compiled.getManifest())));
- profiler.recordEndOf("fulllink");
- profiler.startTask("optimize");
+ profiler.recordEndOf("fulllink").startTask("attributes");
+
+ final Path attributes = workingDirectory.resolve("tool.attributes");
+ // extract tool annotations from the compile resources.
+ final ToolProtoWriter writer = new ToolProtoWriter(attributes);
+ Stream.concat(include.stream(), Stream.of(compiled))
+ .parallel()
+ .map(AndroidCompiledDataDeserializer.create()::readAttributes)
+ .map(Map::entrySet)
+ .flatMap(Set::stream)
+ .distinct()
+ .forEach(e -> e.getValue().writeResource((FullyQualifiedName) e.getKey(), writer));
+ writer.flush();
+
+ profiler.recordEndOf("attributes");
if (densities.size() < 2) {
return PackagedResources.of(
- outPath, rTxt, proguardConfig, mainDexProguard, javaSourceDirectory, resourceIds);
+ outputAsProto ? outPath : convertToBinary(outPath), // convert proto to apk
+ outPath,
+ rTxt,
+ proguardConfig,
+ mainDexProguard,
+ javaSourceDirectory,
+ resourceIds,
+ attributes);
}
- final Path optimized = workingDirectory.resolve("optimized.apk");
+
+ profiler.startTask("optimize");
+ final Path optimized = workingDirectory.resolve("optimized.pb");
logger.fine(
new AaptCommandBuilder(aapt2)
.forBuildToolsVersion(buildToolsVersion)
@@ -406,8 +492,16 @@ public class ResourceLinker {
.add(outPath.toString())
.execute(String.format("Optimizing %s", compiled.getManifest())));
profiler.recordEndOf("optimize");
+
return PackagedResources.of(
- optimized, rTxt, proguardConfig, mainDexProguard, javaSourceDirectory, resourceIds);
+ outputAsProto ? optimized : convertToBinary(optimized),
+ optimized,
+ rTxt,
+ proguardConfig,
+ mainDexProguard,
+ javaSourceDirectory,
+ resourceIds,
+ attributes);
} catch (IOException e) {
throw new LinkError(e);
}
@@ -442,4 +536,63 @@ public class ResourceLinker {
.add("baseApk", baseApk)
.toString();
}
+
+ private static class ToolProtoWriter implements AndroidDataWritingVisitor {
+
+ final Multimap<String, String> attributes = HashMultimap.create();
+ private final Path out;
+
+ ToolProtoWriter(Path out) {
+ this.out = out;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ ToolAttributes.Builder builder = ToolAttributes.newBuilder();
+ for (Entry<String, Collection<String>> entry : attributes.asMap().entrySet()) {
+ builder.putAttributes(
+ entry.getKey(),
+ ToolAttributes.ToolAttributeValues.newBuilder().addAllValues(entry.getValue()).build());
+ }
+ try (OutputStream stream = new BufferedOutputStream(Files.newOutputStream(out))) {
+ builder.build().writeTo(stream);
+ }
+ }
+
+ @Override
+ public Path copyManifest(Path sourceManifest) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void copyAsset(Path source, String relativeDestinationPath) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void copyResource(Path source, String relativeDestinationPath) throws MergingException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void defineAttribute(FullyQualifiedName fqn, String name, String value) {
+ attributes.put(removeNamespace(name), value);
+ }
+
+ private String removeNamespace(String qualifiedName) {
+ int indexColon = qualifiedName.indexOf(':');
+ if (indexColon == -1) {
+ return qualifiedName;
+ }
+ return qualifiedName.substring(indexColon);
+ }
+
+ @Override
+ public void defineNamespacesFor(FullyQualifiedName fqn, Namespaces namespaces) {}
+
+ @Override
+ public ValueResourceDefinitionMetadata define(FullyQualifiedName fqn) {
+ throw new UnsupportedOperationException();
+ }
+ }
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/proto/serialize_format.proto b/src/tools/android/java/com/google/devtools/build/android/proto/serialize_format.proto
index b87d5fee83..a7f913057d 100644
--- a/src/tools/android/java/com/google/devtools/build/android/proto/serialize_format.proto
+++ b/src/tools/android/java/com/google/devtools/build/android/proto/serialize_format.proto
@@ -87,3 +87,11 @@ message DataValueXml {
map<string, string> attribute = 8;
map<string, string> namespace = 9;
}
+
+// Container for serialized attributes.
+message ToolAttributes {
+ message ToolAttributeValues {
+ repeated string values = 1;
+ }
+ map<string, ToolAttributeValues> attributes = 1;
+}