aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build
diff options
context:
space:
mode:
authorGravatar Googler <noreply@google.com>2017-11-15 11:55:15 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2017-11-15 11:57:13 -0800
commit7925d5b265249466bff385602e94509a05de6870 (patch)
tree9543038c23171dd93261a792a6f0571591e3cb09 /src/tools/android/java/com/google/devtools/build
parent1e0b7cb49b5d22f72d9e32018d15972a9f28878c (diff)
Create merge action and data deserializer for aapt2 compiled resources.
RELNOTES: None PiperOrigin-RevId: 175858467
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java279
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidCompiledResourceMergingAction.java121
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java200
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidDataMerger.java4
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java215
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceMerger.java46
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/BUILD2
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java59
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java6
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/GenerateRobolectricResourceSymbolsAction.java2
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/LibraryRClassGeneratorAction.java2
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java8
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/aapt2/Aapt2ConfigOptions.java10
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/proto/BUILD21
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/proto/Resources.proto687
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/proto/format.proto43
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java27
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java64
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java27
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java61
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java39
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java19
22 files changed, 1738 insertions, 204 deletions
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
new file mode 100644
index 0000000000..50ba095cf6
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java
@@ -0,0 +1,279 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android;
+
+import com.android.SdkConstants;
+import com.android.aapt.Resources;
+import com.android.aapt.Resources.ConfigValue;
+import com.android.aapt.Resources.Package;
+import com.android.aapt.Resources.ResourceTable;
+import com.android.aapt.Resources.Type;
+import com.android.aapt.Resources.Value;
+import com.android.resources.ResourceType;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
+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.proto.Format.CompiledFile;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/** Deserializes {@link DataKey}, {@link DataValue} entries from compiled resource files. */
+public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer{
+ private static final Logger logger =
+ Logger.getLogger(AndroidCompiledDataDeserializer.class.getName());
+
+ private final ImmutableSet<String> filteredResources;
+
+ /**
+ * @param filteredResources resources that were filtered out of this target and should be ignored
+ * if they are referenced in symbols files.
+ */
+ public static AndroidCompiledDataDeserializer withFilteredResources(
+ Collection<String> filteredResources) {
+ return new AndroidCompiledDataDeserializer(ImmutableSet.copyOf(filteredResources));
+ }
+
+ public static AndroidCompiledDataDeserializer create() {
+ return new AndroidCompiledDataDeserializer(ImmutableSet.of());
+ }
+
+ private AndroidCompiledDataDeserializer(ImmutableSet<String> filteredResources) {
+ this.filteredResources = filteredResources;
+ }
+
+ private void readResourceTable(
+ InputStream resourceTableStream,
+ KeyValueConsumers consumers,
+ Factory fqnFactory) throws IOException {
+ ResourceTable resourceTable = ResourceTable.parseFrom(resourceTableStream);
+
+ List<String> sourcePool =
+ decodeSourcePool(resourceTable.getSourcePool().getData().toByteArray());
+
+ Map<String, Entry<FullyQualifiedName, Boolean>> fullyQualifiedNames = new HashMap<>();
+
+ for (int i = resourceTable.getPackageCount() - 1; i >= 0; i--) {
+ Package resourceTablePackage = resourceTable.getPackage(i);
+
+ String packageName = "";
+ if (!resourceTablePackage.getPackageName().isEmpty()) {
+ packageName = resourceTablePackage.getPackageName() + ":";
+ }
+
+ for (Type resourceFormatType : resourceTablePackage.getTypeList()) {
+ ResourceType resourceType = ResourceType.getEnum(resourceFormatType.getName());
+
+ for (Resources.Entry resource : resourceFormatType.getEntryList()) {
+ Value resourceValue = resource.getConfigValue(0).getValue();
+ String resourceName = packageName + resource.getName();
+ List<ConfigValue> configValues = resource.getConfigValueList();
+
+ Preconditions.checkArgument(configValues.size() == 1);
+ int sourceIndex =
+ configValues.get(0)
+ .getValue()
+ .getSource()
+ .getPathIdx();
+
+ String source = sourcePool.get(sourceIndex);
+
+ DataSource dataSource = DataSource.of(Paths.get(source));
+ FullyQualifiedName fqn = fqnFactory.create(resourceType, resourceName);
+ fullyQualifiedNames.put(
+ packageName + resourceType + "/" + resource.getName(),
+ new SimpleEntry<FullyQualifiedName, Boolean>(fqn, packageName.isEmpty()));
+
+ if (packageName.isEmpty()) {
+ DataResourceXml dataResourceXml = DataResourceXml
+ .from(resourceValue, dataSource, resourceType, fullyQualifiedNames);
+ if (resourceType == ResourceType.ID
+ || resourceType == ResourceType.PUBLIC
+ || resourceType == ResourceType.STYLEABLE) {
+ consumers.combiningConsumer.accept(fqn, dataResourceXml);
+ } else {
+ consumers.overwritingConsumer.accept(fqn, dataResourceXml);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Reads compiled resource data files and adds them to consumers
+ * @param compiledFileStream First byte is number of compiled files represented in this file.
+ * Next 8 bytes is a long indicating the length of the metadata describing the compiled file.
+ * Next N bytes is the metadata describing the compiled file.
+ * The remaining bytes are the actual original file.
+ * @param consumers
+ * @param fqnFactory
+ * @throws IOException
+ */
+ private void readCompiledFile(
+ InputStream compiledFileStream,
+ KeyValueConsumers consumers,
+ Factory fqnFactory) throws IOException {
+ LittleEndianDataInputStream dataInputStream =
+ new LittleEndianDataInputStream(compiledFileStream);
+
+ int numberOfCompiledFiles = dataInputStream.readInt();
+ if (numberOfCompiledFiles != 1) {
+ logger.warning("Compiled resource file has "
+ + numberOfCompiledFiles + " files. Expected 1 compiled file.");
+ }
+
+ long length = dataInputStream.readLong();
+ byte[] file = new byte[(int) length];
+ dataInputStream.read(file, 0, (int) length);
+ CompiledFile compiledFile = CompiledFile.parseFrom(file);
+
+ Path sourcePath = Paths.get(compiledFile.getSourcePath());
+ FullyQualifiedName fqn = fqnFactory.parse(sourcePath);
+ DataSource dataSource = DataSource.of(sourcePath);
+
+ if (consumers != null) {
+ consumers.overwritingConsumer.accept(fqn, DataValueFile.of(dataSource));
+ }
+
+ for (CompiledFile.Symbol exportedSymbol : compiledFile.getExportedSymbolsList()) {
+ FullyQualifiedName symbolFqn =
+ fqnFactory.create(
+ ResourceType.ID, exportedSymbol.getResourceName().replaceFirst("id/", ""));
+
+ DataResourceXml dataResourceXml =
+ DataResourceXml.from(null, dataSource, ResourceType.ID, null);
+ consumers.combiningConsumer.accept(symbolFqn, dataResourceXml);
+ }
+ }
+
+ @Override
+ public void read(Path inPath, KeyValueConsumers consumers){
+ Stopwatch timer = Stopwatch.createStarted();
+ try (ZipFile zipFile = new ZipFile(inPath.toFile())) {
+ Enumeration<? extends ZipEntry> resourceFiles = zipFile.entries();
+
+ while (resourceFiles.hasMoreElements()) {
+ ZipEntry resourceFile = resourceFiles.nextElement();
+ InputStream resourceFileStream = zipFile.getInputStream(resourceFile);
+
+ String fileZipPath = resourceFile.getName();
+ int resourceSubdirectoryIndex = fileZipPath.indexOf('_', fileZipPath.lastIndexOf('/'));
+ Path filePath = Paths.get(String.format("%s%c%s",
+ fileZipPath.substring(0, resourceSubdirectoryIndex),
+ '/',
+ fileZipPath.substring(resourceSubdirectoryIndex + 1)));
+
+ String shortPath = filePath.getParent().getFileName() + "/" + filePath.getFileName();
+
+ if (filteredResources.contains(shortPath) && !Files.exists(filePath)) {
+ // Skip files that were filtered out during analysis.
+ // TODO(asteinb): Properly filter out these files from android_library symbol files during
+ // analysis instead, and remove this list.
+ continue;
+ }
+
+ final String[] dirNameAndQualifiers = filePath.getParent().getFileName().toString()
+ .split(SdkConstants.RES_QUALIFIER_SEP);
+ Factory fqnFactory = Factory.fromDirectoryName(dirNameAndQualifiers);
+
+ if (fileZipPath.endsWith(".arsc.flat")) {
+ readResourceTable(resourceFileStream, consumers, fqnFactory);
+ } else {
+ readCompiledFile(resourceFileStream, consumers, fqnFactory);
+ }
+ }
+ } catch (IOException e) {
+ throw new DeserializationException("Error deserializing " + inPath, e);
+ } finally {
+ logger.fine(
+ String.format(
+ "Deserialized in compiled merged in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+ }
+ }
+
+ private static List<String> decodeSourcePool(byte[] bytes) throws UnsupportedEncodingException {
+ ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+
+ int stringCount = byteBuffer.getInt(8);
+ boolean isUtf8 = (byteBuffer.getInt(16) & (1 << 8)) != 0;
+ int stringsStart = byteBuffer.getInt(20);
+ //Position the ByteBuffer after the metadata
+ byteBuffer.position(28);
+
+ List<String> strings = new ArrayList<>();
+
+ for (int i = 0; i < stringCount; i++) {
+ int stringOffset = stringsStart + byteBuffer.getInt();
+
+ if (isUtf8) {
+ int characterCount = byteBuffer.get(stringOffset) & 0xFF;
+ if ((characterCount & 0x80) != 0) {
+ characterCount =
+ ((characterCount & 0x7F) << 8) | (byteBuffer.get(stringOffset + 1) & 0xFF);
+ }
+
+ 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);
+
+ strings.add(new String(bytes, stringOffset, length, "UTF8"));
+ } else {
+ int characterCount = byteBuffer.get(stringOffset) & 0xFFFF;
+ if ((characterCount & 0x8000) != 0) {
+ characterCount =
+ ((characterCount & 0x7FFF) << 16) | (byteBuffer.get(stringOffset + 2) & 0xFFFF);
+ }
+
+ 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);
+
+ strings.add(new String(bytes, stringOffset, length, "UTF16"));
+ }
+ }
+
+ return strings;
+ }
+
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledResourceMergingAction.java b/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledResourceMergingAction.java
new file mode 100644
index 0000000000..88ae2be56b
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledResourceMergingAction.java
@@ -0,0 +1,121 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android;
+
+import com.android.builder.core.VariantConfiguration;
+import com.android.utils.StdLogger;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Strings;
+import com.google.devtools.build.android.AndroidDataMerger.MergeConflictException;
+import com.google.devtools.build.android.AndroidResourceMerger.MergingException;
+import com.google.devtools.build.android.AndroidResourceMergingAction.Options;
+import com.google.devtools.build.android.AndroidResourceProcessor.AaptConfigOptions;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Provides an entry point for the compiled resource merging action.
+ *
+ * <p>This action merges compiled intermediate resource files from aapt2
+ * and reports merge conflicts. It also provides a merged manifest file
+ * to {@link ValidateAndLinkResourcesAction} and builds the resource class
+ * jar for the lib jar
+ */
+public class AndroidCompiledResourceMergingAction {
+
+ private static final StdLogger stdLogger = new StdLogger(StdLogger.Level.WARNING);
+
+ private static final Logger logger =
+ Logger.getLogger(AndroidCompiledResourceMergingAction.class.getName());
+
+ public static void main(String[] args) throws Exception {
+ final Stopwatch timer = Stopwatch.createStarted();
+ OptionsParser optionsParser =
+ OptionsParser.newOptionsParser(Options.class, AaptConfigOptions.class);
+ optionsParser.enableParamsFileSupport(
+ new ShellQuotedParamsFilePreProcessor(FileSystems.getDefault()));
+ optionsParser.parseAndExitUponError(args);
+ AaptConfigOptions aaptConfigOptions = optionsParser.getOptions(AaptConfigOptions.class);
+ Options options = optionsParser.getOptions(Options.class);
+
+ Preconditions.checkNotNull(options.primaryData);
+ Preconditions.checkNotNull(options.primaryManifest);
+ Preconditions.checkNotNull(options.manifestOutput);
+ Preconditions.checkNotNull(options.classJarOutput);
+
+ try (ScopedTemporaryDirectory scopedTmp =
+ new ScopedTemporaryDirectory("android_resource_merge_tmp")) {
+ Path tmp = scopedTmp.getPath();
+ Path generatedSources = tmp.resolve("generated_resources");
+ Path processedManifest = tmp.resolve("manifest-processed/AndroidManifest.xml");
+
+ logger.fine(String.format("Setup finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+
+ String packageForR = options.packageForR;
+ if (packageForR == null) {
+ packageForR =
+ Strings.nullToEmpty(
+ VariantConfiguration.getManifestPackage(options.primaryManifest.toFile()));
+ }
+ AndroidResourceClassWriter resourceClassWriter =
+ AndroidResourceClassWriter.createWith(
+ aaptConfigOptions.androidJar, generatedSources, packageForR);
+ resourceClassWriter.setIncludeClassFile(true);
+ resourceClassWriter.setIncludeJavaFile(false);
+
+ AndroidResourceMerger.mergeCompiledData(
+ options.primaryData,
+ options.primaryManifest,
+ options.directData,
+ options.transitiveData,
+ resourceClassWriter,
+ options.throwOnResourceConflict);
+ logger.fine(String.format("Merging finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+
+ AndroidResourceOutputs.createClassJar(generatedSources, options.classJarOutput);
+ logger.fine(
+ String.format(
+ "Create classJar finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+
+ // Until enough users with manifest placeholders migrate to the new manifest merger,
+ // we need to replace ${applicationId} and ${packageName} with options.packageForR to make
+ // the manifests compatible with the old manifest merger.
+ processedManifest = AndroidManifestProcessor.with(stdLogger)
+ .processLibraryManifest(options.packageForR, options.primaryManifest, processedManifest);
+
+ Files.createDirectories(options.manifestOutput.getParent());
+ Files.copy(processedManifest, options.manifestOutput);
+ } catch (MergeConflictException e) {
+ logger.log(Level.SEVERE, e.getMessage());
+ System.exit(1);
+ } catch (MergingException e) {
+ logger.log(Level.SEVERE, "Error during merging resources", e);
+ throw e;
+ } catch (AndroidManifestProcessor.ManifestProcessingException e) {
+ System.exit(1);
+ } catch (Exception e) {
+ logger.log(Level.SEVERE, "Unexpected", e);
+ throw e;
+ }
+ logger.fine(String.format("Resources merged in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+ }
+
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java b/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java
index 62756c9ebc..60212bdb20 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java
@@ -13,202 +13,12 @@
// limitations under the License.
package com.google.devtools.build.android;
-import com.google.common.base.Stopwatch;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-import com.google.devtools.build.android.AndroidResourceMerger.MergingException;
-import com.google.devtools.build.android.ParsedAndroidData.Builder;
-import com.google.devtools.build.android.ParsedAndroidData.KeyValueConsumer;
-import com.google.devtools.build.android.proto.SerializeFormat;
-import com.google.devtools.build.android.proto.SerializeFormat.Header;
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.FileSystem;
-import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.logging.Logger;
-/** Deserializes {@link DataKey}, {@link DataValue} entries from a binary file. */
-public class AndroidDataDeserializer {
- /** Task to deserialize resources from a path. */
- static final class Deserialize implements Callable<Boolean> {
+/** Represents a deserializer to deserialize {@link DataKey}
+ * and {@link DataValue} from a path and feed to it consumers.
+ */
+public interface AndroidDataDeserializer {
- private final Path symbolPath;
-
- private final Builder finalDataBuilder;
- private final AndroidDataDeserializer deserializer;
-
- private Deserialize(
- AndroidDataDeserializer deserializer, Path symbolPath, Builder finalDataBuilder) {
- this.deserializer = deserializer;
- this.symbolPath = symbolPath;
- this.finalDataBuilder = finalDataBuilder;
- }
-
- @Override
- public Boolean call() throws Exception {
- final Builder parsedDataBuilder = ParsedAndroidData.Builder.newBuilder();
- deserializer.read(symbolPath, parsedDataBuilder.consumers());
- // The builder isn't threadsafe, so synchronize the copyTo call.
- synchronized (finalDataBuilder) {
- // All the resources are sorted before writing, so they can be aggregated in
- // whatever order here.
- parsedDataBuilder.copyTo(finalDataBuilder);
- }
- return Boolean.TRUE;
- }
- }
-
- private static final Logger logger = Logger.getLogger(AndroidDataDeserializer.class.getName());
-
- private final ImmutableSet<String> filteredResources;
-
- /**
- * @param filteredResources resources that were filtered out of this target and should be ignored
- * if they are referenced in symbols files.
- */
- public static AndroidDataDeserializer withFilteredResources(
- Collection<String> filteredResources) {
- return new AndroidDataDeserializer(ImmutableSet.copyOf(filteredResources));
- }
-
- public static AndroidDataDeserializer create() {
- return new AndroidDataDeserializer(ImmutableSet.<String>of());
- }
-
- private AndroidDataDeserializer(ImmutableSet<String> filteredResources) {
- this.filteredResources = filteredResources;
- }
-
- /**
- * Reads the serialized {@link DataKey} and {@link DataValue} to the {@link KeyValueConsumers}.
- *
- * @param inPath The path to the serialized protocol buffer.
- * @param consumers The {@link KeyValueConsumers} for the entries {@link DataKey} -&gt;
- * {@link DataValue}.
- * @throws DeserializationException Raised for an IOException or when the inPath is not a valid
- * proto buffer.
- */
- public void read(Path inPath, KeyValueConsumers consumers) {
- Stopwatch timer = Stopwatch.createStarted();
- try (InputStream in = Files.newInputStream(inPath, StandardOpenOption.READ)) {
- FileSystem currentFileSystem = inPath.getFileSystem();
- Header header = Header.parseDelimitedFrom(in);
- if (header == null) {
- throw new DeserializationException("No Header found in " + inPath);
- }
- readEntriesSegment(consumers, in, currentFileSystem, header);
- } catch (IOException e) {
- throw new DeserializationException("Error deserializing " + inPath, e);
- } finally {
- logger.fine(
- String.format("Deserialized in merged in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
- }
- }
-
- private void readEntriesSegment(
- KeyValueConsumers consumers,
- InputStream in,
- FileSystem currentFileSystem,
- Header header)
- throws IOException {
- int numberOfEntries = header.getEntryCount();
- Map<DataKey, KeyValueConsumer<DataKey, ? extends DataValue>> keys =
- Maps.newLinkedHashMapWithExpectedSize(numberOfEntries);
- for (int i = 0; i < numberOfEntries; i++) {
- SerializeFormat.DataKey protoKey = SerializeFormat.DataKey.parseDelimitedFrom(in);
- if (protoKey.hasResourceType()) {
- FullyQualifiedName resourceName = FullyQualifiedName.fromProto(protoKey);
- keys.put(
- resourceName,
- resourceName.isOverwritable()
- ? consumers.overwritingConsumer
- : consumers.combiningConsumer);
- } else {
- keys.put(RelativeAssetPath.fromProto(protoKey, currentFileSystem), consumers.assetConsumer);
- }
- }
-
- // Read back the sources table.
- DataSourceTable sourceTable = DataSourceTable.read(in, currentFileSystem, header);
-
- // TODO(corysmith): Make this a lazy read of the values.
- for (Entry<DataKey, KeyValueConsumer<DataKey, ?>> entry : keys.entrySet()) {
- SerializeFormat.DataValue protoValue = SerializeFormat.DataValue.parseDelimitedFrom(in);
- DataSource source = sourceTable.sourceFromId(protoValue.getSourceId());
- // Compose the `shortPath` manually to ensure it uses a forward slash.
- // Using Path.subpath would return a backslash-using path on Windows.
- String shortPath =
- source.getPath().getParent().getFileName() + "/" + source.getPath().getFileName();
- if (filteredResources.contains(shortPath) && !Files.exists(source.getPath())) {
- // Skip files that were filtered out during analysis.
- // TODO(asteinb): Properly filter out these files from android_library symbol files during
- // analysis instead, and remove this list.
- continue;
- }
- if (protoValue.hasXmlValue()) {
- // TODO(corysmith): Figure out why the generics are wrong.
- // If I use Map<DataKey, KeyValueConsumer<DataKey, ? extends DataValue>>, I can put
- // consumers into the map, but I can't call accept.
- // If I use Map<DataKey, KeyValueConsumer<DataKey, ? super DataValue>>, I can consume
- // but I can't put.
- // Same for below.
- @SuppressWarnings("unchecked")
- KeyValueConsumer<DataKey, DataValue> value =
- (KeyValueConsumer<DataKey, DataValue>) entry.getValue();
- value.accept(entry.getKey(), DataResourceXml.from(protoValue, source));
- } else {
- @SuppressWarnings("unchecked")
- KeyValueConsumer<DataKey, DataValue> value =
- (KeyValueConsumer<DataKey, DataValue>) entry.getValue();
- value.accept(entry.getKey(), DataValueFile.of(source));
- }
- }
- }
-
- /** Deserializes a list of serialized resource paths to a {@link ParsedAndroidData}. */
- public static ParsedAndroidData deserializeSymbolsToData(List<Path> symbolPaths)
- throws IOException, MergingException {
- AndroidDataDeserializer deserializer = create();
- final ListeningExecutorService executorService =
- MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(15));
- final Builder deserializedDataBuilder = ParsedAndroidData.Builder.newBuilder();
- try (Closeable closeable = ExecutorServiceCloser.createWith(executorService)) {
- List<ListenableFuture<Boolean>> deserializing = new ArrayList<>();
- for (final Path symbolPath : symbolPaths) {
- deserializing.add(
- executorService.submit(
- new AndroidDataDeserializer.Deserialize(
- deserializer, symbolPath, deserializedDataBuilder)));
- }
- FailedFutureAggregator<MergingException> aggregator =
- FailedFutureAggregator.createForMergingExceptionWithMessage(
- "Failure(s) during dependency parsing");
- aggregator.aggregateAndMaybeThrow(deserializing);
- }
- return deserializedDataBuilder.build();
- }
-
- public static ParsedAndroidData deserializeSingleAndroidData(SerializedAndroidData data)
- throws MergingException {
- final ParsedAndroidData.Builder builder = ParsedAndroidData.Builder.newBuilder();
- final AndroidDataDeserializer deserializer = create();
- data.deserialize(deserializer, builder.consumers());
- return builder.build();
- }
+ void read(Path inPath, KeyValueConsumers consumers);
}
-
-
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidDataMerger.java b/src/tools/android/java/com/google/devtools/build/android/AndroidDataMerger.java
index 8e550a2d60..16b43b848e 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidDataMerger.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidDataMerger.java
@@ -119,7 +119,9 @@ class AndroidDataMerger {
@VisibleForTesting
static AndroidDataMerger createWithDefaultThreadPool(SourceChecker deDuplicator) {
return new AndroidDataMerger(
- deDuplicator, MoreExecutors.newDirectExecutorService(), AndroidDataDeserializer.create());
+ deDuplicator,
+ MoreExecutors.newDirectExecutorService(),
+ AndroidParsedDataDeserializer.create());
}
/** Creates a merger with a file contents hashing deduplicator. */
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java b/src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java
new file mode 100644
index 0000000000..71787b4b39
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java
@@ -0,0 +1,215 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.devtools.build.android.AndroidResourceMerger.MergingException;
+import com.google.devtools.build.android.ParsedAndroidData.Builder;
+import com.google.devtools.build.android.ParsedAndroidData.KeyValueConsumer;
+import com.google.devtools.build.android.proto.SerializeFormat;
+import com.google.devtools.build.android.proto.SerializeFormat.Header;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+/** Deserializes {@link DataKey}, {@link DataValue} entries from a binary file. */
+public class AndroidParsedDataDeserializer implements AndroidDataDeserializer {
+ /** Task to deserialize resources from a path. */
+ static final class Deserialize implements Callable<Boolean> {
+
+ private final Path symbolPath;
+
+ private final Builder finalDataBuilder;
+ private final AndroidParsedDataDeserializer deserializer;
+
+ private Deserialize(
+ AndroidParsedDataDeserializer deserializer, Path symbolPath, Builder finalDataBuilder) {
+ this.deserializer = deserializer;
+ this.symbolPath = symbolPath;
+ this.finalDataBuilder = finalDataBuilder;
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ final Builder parsedDataBuilder = ParsedAndroidData.Builder.newBuilder();
+ deserializer.read(symbolPath, parsedDataBuilder.consumers());
+ // The builder isn't threadsafe, so synchronize the copyTo call.
+ synchronized (finalDataBuilder) {
+ // All the resources are sorted before writing, so they can be aggregated in
+ // whatever order here.
+ parsedDataBuilder.copyTo(finalDataBuilder);
+ }
+ return Boolean.TRUE;
+ }
+ }
+
+ private static final Logger logger =
+ Logger.getLogger(AndroidParsedDataDeserializer.class.getName());
+
+ private final ImmutableSet<String> filteredResources;
+
+ /**
+ * @param filteredResources resources that were filtered out of this target and should be ignored
+ * if they are referenced in symbols files.
+ */
+ public static AndroidParsedDataDeserializer withFilteredResources(
+ Collection<String> filteredResources) {
+ return new AndroidParsedDataDeserializer(ImmutableSet.copyOf(filteredResources));
+ }
+
+ public static AndroidParsedDataDeserializer create() {
+ return new AndroidParsedDataDeserializer(ImmutableSet.<String>of());
+ }
+
+ private AndroidParsedDataDeserializer(ImmutableSet<String> filteredResources) {
+ this.filteredResources = filteredResources;
+ }
+
+ /**
+ * Reads the serialized {@link DataKey} and {@link DataValue} to the {@link KeyValueConsumers}.
+ *
+ * @param inPath The path to the serialized protocol buffer.
+ * @param consumers The {@link KeyValueConsumers} for the entries {@link DataKey} -&gt; {@link
+ * DataValue}.
+ * @throws DeserializationException Raised for an IOException or when the inPath is not a valid
+ * proto buffer.
+ */
+ @Override
+ public void read(Path inPath, KeyValueConsumers consumers) {
+ Stopwatch timer = Stopwatch.createStarted();
+ try (InputStream in = Files.newInputStream(inPath, StandardOpenOption.READ)) {
+ FileSystem currentFileSystem = inPath.getFileSystem();
+ Header header = Header.parseDelimitedFrom(in);
+ if (header == null) {
+ throw new DeserializationException("No Header found in " + inPath);
+ }
+ readEntriesSegment(consumers, in, currentFileSystem, header);
+ } catch (IOException e) {
+ throw new DeserializationException("Error deserializing " + inPath, e);
+ } finally {
+ logger.fine(
+ String.format("Deserialized in merged in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+ }
+ }
+
+ private void readEntriesSegment(
+ KeyValueConsumers consumers,
+ InputStream in,
+ FileSystem currentFileSystem,
+ Header header)
+ throws IOException {
+ int numberOfEntries = header.getEntryCount();
+ Map<DataKey, KeyValueConsumer<DataKey, ? extends DataValue>> keys =
+ Maps.newLinkedHashMapWithExpectedSize(numberOfEntries);
+ for (int i = 0; i < numberOfEntries; i++) {
+ SerializeFormat.DataKey protoKey = SerializeFormat.DataKey.parseDelimitedFrom(in);
+ if (protoKey.hasResourceType()) {
+ FullyQualifiedName resourceName = FullyQualifiedName.fromProto(protoKey);
+ keys.put(
+ resourceName,
+ resourceName.isOverwritable()
+ ? consumers.overwritingConsumer
+ : consumers.combiningConsumer);
+ } else {
+ keys.put(RelativeAssetPath.fromProto(protoKey, currentFileSystem), consumers.assetConsumer);
+ }
+ }
+
+ // Read back the sources table.
+ DataSourceTable sourceTable = DataSourceTable.read(in, currentFileSystem, header);
+
+ // TODO(corysmith): Make this a lazy read of the values.
+ for (Entry<DataKey, KeyValueConsumer<DataKey, ?>> entry : keys.entrySet()) {
+ SerializeFormat.DataValue protoValue = SerializeFormat.DataValue.parseDelimitedFrom(in);
+ DataSource source = sourceTable.sourceFromId(protoValue.getSourceId());
+ // Compose the `shortPath` manually to ensure it uses a forward slash.
+ // Using Path.subpath would return a backslash-using path on Windows.
+ String shortPath =
+ source.getPath().getParent().getFileName() + "/" + source.getPath().getFileName();
+ if (filteredResources.contains(shortPath) && !Files.exists(source.getPath())) {
+ // Skip files that were filtered out during analysis.
+ // TODO(asteinb): Properly filter out these files from android_library symbol files during
+ // analysis instead, and remove this list.
+ continue;
+ }
+ if (protoValue.hasXmlValue()) {
+ // TODO(corysmith): Figure out why the generics are wrong.
+ // If I use Map<DataKey, KeyValueConsumer<DataKey, ? extends DataValue>>, I can put
+ // consumers into the map, but I can't call accept.
+ // If I use Map<DataKey, KeyValueConsumer<DataKey, ? super DataValue>>, I can consume
+ // but I can't put.
+ // Same for below.
+ @SuppressWarnings("unchecked")
+ KeyValueConsumer<DataKey, DataValue> value =
+ (KeyValueConsumer<DataKey, DataValue>) entry.getValue();
+ value.accept(entry.getKey(), DataResourceXml.from(protoValue, source));
+ } else {
+ @SuppressWarnings("unchecked")
+ KeyValueConsumer<DataKey, DataValue> value =
+ (KeyValueConsumer<DataKey, DataValue>) entry.getValue();
+ value.accept(entry.getKey(), DataValueFile.of(source));
+ }
+ }
+ }
+
+ /** Deserializes a list of serialized resource paths to a {@link ParsedAndroidData}. */
+ public static ParsedAndroidData deserializeSymbolsToData(List<Path> symbolPaths)
+ throws IOException {
+ AndroidParsedDataDeserializer deserializer = create();
+ final ListeningExecutorService executorService =
+ MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(15));
+ final Builder deserializedDataBuilder = ParsedAndroidData.Builder.newBuilder();
+ try (Closeable closeable = ExecutorServiceCloser.createWith(executorService)) {
+ List<ListenableFuture<Boolean>> deserializing = new ArrayList<>();
+ for (final Path symbolPath : symbolPaths) {
+ deserializing.add(
+ executorService.submit(
+ new AndroidParsedDataDeserializer.Deserialize(
+ deserializer, symbolPath, deserializedDataBuilder)));
+ }
+ FailedFutureAggregator<MergingException> aggregator =
+ FailedFutureAggregator.createForMergingExceptionWithMessage(
+ "Failure(s) during dependency parsing");
+ aggregator.aggregateAndMaybeThrow(deserializing);
+ }
+ return deserializedDataBuilder.build();
+ }
+
+ public static ParsedAndroidData deserializeSingleAndroidData(SerializedAndroidData data) {
+ final ParsedAndroidData.Builder builder = ParsedAndroidData.Builder.newBuilder();
+ final AndroidParsedDataDeserializer deserializer = create();
+ data.deserialize(deserializer, builder.consumers());
+ return builder.build();
+ }
+}
+
+
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMerger.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMerger.java
index eb7fd70711..bc9bf6d408 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMerger.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMerger.java
@@ -161,7 +161,7 @@ public class AndroidResourceMerger {
type,
symbolsOut,
null /* rclassWriter */,
- AndroidDataDeserializer.withFilteredResources(filteredResources),
+ AndroidParsedDataDeserializer.withFilteredResources(filteredResources),
throwOnResourceConflict);
} catch (IOException e) {
throw MergingException.wrapException(e);
@@ -185,7 +185,7 @@ public class AndroidResourceMerger {
@Nullable final AndroidResourceClassWriter rclassWriter,
boolean throwOnResourceConflict) {
final ParsedAndroidData.Builder primaryBuilder = ParsedAndroidData.Builder.newBuilder();
- final AndroidDataDeserializer deserializer = AndroidDataDeserializer.create();
+ final AndroidDataDeserializer deserializer = AndroidParsedDataDeserializer.create();
primary.deserialize(deserializer, primaryBuilder.consumers());
ParsedAndroidData primaryData = primaryBuilder.build();
return mergeData(
@@ -202,6 +202,48 @@ public class AndroidResourceMerger {
deserializer,
throwOnResourceConflict);
}
+
+ /**
+ * Merges all secondary compiled resources with the primary compiled resources,
+ * given that the primary resources have been separately compiled
+ */
+ public static void mergeCompiledData(
+ final SerializedAndroidData primary,
+ final Path primaryManifest,
+ final List<? extends SerializedAndroidData> direct,
+ final List<? extends SerializedAndroidData> transitive,
+ @Nullable final AndroidResourceClassWriter rclassWriter,
+ boolean throwOnResourceConflict) {
+ final ParsedAndroidData.Builder primaryBuilder = ParsedAndroidData.Builder.newBuilder();
+ final AndroidDataDeserializer deserializer = AndroidCompiledDataDeserializer.create();
+ primary.deserialize(deserializer, primaryBuilder.consumers());
+ ParsedAndroidData primaryData = primaryBuilder.build();
+
+ Stopwatch timer = Stopwatch.createStarted();
+ final ListeningExecutorService executorService = ExecutorServiceCloser.createDefaultService();
+ try (Closeable closeable = ExecutorServiceCloser.createWith(executorService)) {
+ UnwrittenMergedAndroidData merged =
+ mergeData(
+ executorService,
+ transitive,
+ direct,
+ primaryData,
+ primaryManifest,
+ false,
+ deserializer,
+ throwOnResourceConflict);
+ timer.reset().start();
+ merged.writeResourceClass(rclassWriter);
+ logger.fine(
+ String.format("write classes finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+ timer.reset().start();
+ } catch (IOException e) {
+ throw MergingException.wrapException(e);
+ } finally {
+ logger.fine(
+ String.format("write merge finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+ }
+ }
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/BUILD b/src/tools/android/java/com/google/devtools/build/android/BUILD
index d563c772df..74080c9960 100644
--- a/src/tools/android/java/com/google/devtools/build/android/BUILD
+++ b/src/tools/android/java/com/google/devtools/build/android/BUILD
@@ -61,6 +61,8 @@ java_library(
"//src/java_tools/singlejar/java/com/google/devtools/build/zip",
"//src/main/java/com/google/devtools/common/options",
"//src/tools/android/java/com/google/devtools/build/android/junctions",
+ "//src/tools/android/java/com/google/devtools/build/android/proto:format_java_pb",
+ "//src/tools/android/java/com/google/devtools/build/android/proto:resources_java_proto",
"//src/tools/android/java/com/google/devtools/build/android/proto:serialize_format_java_pb",
"//src/tools/android/java/com/google/devtools/build/android/resources",
"//third_party:android_common_25_0_0",
diff --git a/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java b/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java
index 51b0688f10..145725f941 100644
--- a/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java
+++ b/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java
@@ -17,6 +17,7 @@ import static com.android.resources.ResourceType.DECLARE_STYLEABLE;
import static com.android.resources.ResourceType.ID;
import static com.android.resources.ResourceType.PUBLIC;
+import com.android.aapt.Resources.Value;
import com.android.resources.ResourceType;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
@@ -44,6 +45,8 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLEventReader;
@@ -201,6 +204,62 @@ public class DataResourceXml implements DataResource {
}
}
+ public static DataResourceXml from(
+ Value protoValue,
+ DataSource source,
+ ResourceType resourceType,
+ Map<String, Entry<FullyQualifiedName, Boolean>> fullyQualifiedNames)
+ throws InvalidProtocolBufferException {
+ DataResourceXml dataResourceXml = createWithNamespaces(
+ source,
+ valueFromProto(protoValue, resourceType, fullyQualifiedNames),
+ Namespaces.empty());
+ return dataResourceXml;
+ }
+
+ private static XmlResourceValue valueFromProto(
+ Value proto,
+ ResourceType resourceType,
+ Map<String, Entry<FullyQualifiedName, Boolean>> fullyQualifiedNames)
+ throws InvalidProtocolBufferException {
+ switch (resourceType) {
+ case STYLE:
+ return StyleXmlResourceValue.from(proto);
+ case ARRAY:
+ return ArrayXmlResourceValue.from(proto);
+ case PLURALS:
+ return PluralXmlResourceValue.from(proto);
+ case ATTR:
+ return AttrXmlResourceValue.from(proto);
+ case PUBLIC:
+ throw new RuntimeException();
+ case STYLEABLE:
+ return StyleableXmlResourceValue.from(proto, fullyQualifiedNames);
+ case ID:
+ return IdXmlResourceValue.of();
+ case DIMEN:
+ case LAYOUT:
+ case STRING:
+ case BOOL:
+ case COLOR:
+ case FRACTION:
+ case INTEGER:
+ case DRAWABLE:
+ case ANIM:
+ case ANIMATOR:
+ case DECLARE_STYLEABLE:
+ case INTERPOLATOR:
+ case MENU:
+ case MIPMAP:
+ case RAW:
+ case TRANSITION:
+ case XML:
+ return SimpleXmlResourceValue.from(proto, resourceType);
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
private static XmlResourceValue parseXmlElements(
ResourceType resourceType,
XMLEventReader eventReader,
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 d2c72cd644..f59267d23e 100644
--- a/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java
+++ b/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java
@@ -38,7 +38,7 @@ class DependencyAndroidData extends SerializedAndroidData {
private static final Pattern VALID_REGEX = Pattern.compile(".*:.*:.+:.+(:.*){0,2}");
public static final String EXPECTED_FORMAT =
- "resources[#resources]:assets[#assets]:manifest:r.txt:static.library.ap_:symbols.bin";
+ "resources[#resources]:assets[#assets]:manifest:r.txt(:symbols.zip?):symbols.bin";
public static DependencyAndroidData valueOf(String text) {
return valueOf(text, FileSystems.getDefault());
@@ -61,7 +61,9 @@ class DependencyAndroidData extends SerializedAndroidData {
if (parts.length == 6) { // contains symbols bin and compiled symbols
compiledSymbols = CompiledResources.from(exists(fileSystem.getPath(parts[4])));
symbolsBin = exists(fileSystem.getPath(parts[5]));
- } else if (parts.length == 5) { // contains symbols bin
+ } else if (parts.length == 5) {
+ //This is either symbols bin or compiled symbols depending on "useCompiledResourcesForMerge"
+ compiledSymbols = CompiledResources.from(exists(fileSystem.getPath(parts[4])));
symbolsBin = exists(fileSystem.getPath(parts[4]));
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/GenerateRobolectricResourceSymbolsAction.java b/src/tools/android/java/com/google/devtools/build/android/GenerateRobolectricResourceSymbolsAction.java
index 3cd3d9c6e5..fa71b5f3e1 100644
--- a/src/tools/android/java/com/google/devtools/build/android/GenerateRobolectricResourceSymbolsAction.java
+++ b/src/tools/android/java/com/google/devtools/build/android/GenerateRobolectricResourceSymbolsAction.java
@@ -136,7 +136,7 @@ public class GenerateRobolectricResourceSymbolsAction {
final PlaceholderIdFieldInitializerBuilder robolectricIds =
PlaceholderIdFieldInitializerBuilder.from(aaptConfigOptions.androidJar);
ParsedAndroidData.loadedFrom(
- options.data, executorService, AndroidDataDeserializer.create())
+ options.data, executorService, AndroidParsedDataDeserializer.create())
.writeResourcesTo(
new AndroidResourceSymbolSink() {
diff --git a/src/tools/android/java/com/google/devtools/build/android/LibraryRClassGeneratorAction.java b/src/tools/android/java/com/google/devtools/build/android/LibraryRClassGeneratorAction.java
index 9adc2fa55c..2cce77e3cf 100644
--- a/src/tools/android/java/com/google/devtools/build/android/LibraryRClassGeneratorAction.java
+++ b/src/tools/android/java/com/google/devtools/build/android/LibraryRClassGeneratorAction.java
@@ -122,7 +122,7 @@ public class LibraryRClassGeneratorAction {
logger.fine(String.format("Setup finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
final ParsedAndroidData data =
- AndroidDataDeserializer.deserializeSymbolsToData(options.symbols);
+ AndroidParsedDataDeserializer.deserializeSymbolsToData(options.symbols);
logger.fine(
String.format("Deserialization finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
diff --git a/src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java b/src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java
index 40de32f0c2..f1da34a520 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
@@ -92,6 +92,12 @@ public class ResourceProcessorBusyBox {
AndroidResourceMergingAction.main(args);
}
},
+ MERGE_COMPILED() {
+ @Override
+ void call(String[] args) throws Exception {
+ AndroidCompiledResourceMergingAction.main(args);
+ }
+ },
GENERATE_AAR() {
@Override
void call(String[] args) throws Exception {
@@ -159,7 +165,7 @@ public class ResourceProcessorBusyBox {
"The processing tool to execute. "
+ "Valid tools: PACKAGE, VALIDATE, GENERATE_BINARY_R, GENERATE_LIBRARY_R, PARSE, "
+ "MERGE, GENERATE_AAR, SHRINK, MERGE_MANIFEST, COMPILE_LIBRARY_RESOURCES, "
- + "LINK_STATIC_LIBRARY, AAPT2_PACKAGE, SHRINK_AAPT2."
+ + "LINK_STATIC_LIBRARY, AAPT2_PACKAGE, SHRINK_AAPT2, MERGE_COMPILED."
)
public Tool tool;
}
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 0225041418..aa02712957 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
@@ -157,4 +157,14 @@ public class Aapt2ConfigOptions extends OptionsBase {
+ " the output package name following an underscore."
)
public List<String> splits;
+
+ @Option(
+ name = "useCompiledResourcesForMerge",
+ defaultValue = "false",
+ category = "config",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "Use compiled resources for merging rather than parsed symbols binary."
+ )
+ public boolean useCompiledResourcesForMerge;
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/proto/BUILD b/src/tools/android/java/com/google/devtools/build/android/proto/BUILD
index 8b28e76d02..b64629645a 100644
--- a/src/tools/android/java/com/google/devtools/build/android/proto/BUILD
+++ b/src/tools/android/java/com/google/devtools/build/android/proto/BUILD
@@ -12,6 +12,27 @@ proto_library(
srcs = ["serialize_format.proto"],
)
+java_proto_library(
+ name = "format_java_pb",
+ deps = [":format_proto"],
+)
+
+proto_library(
+ name = "format_proto",
+ srcs = ["format.proto"],
+)
+
+java_proto_library(
+ name = "resources_java_proto",
+ strict_deps = 0,
+ deps = [":resources_proto"],
+)
+
+proto_library(
+ name = "resources_proto",
+ srcs = ["Resources.proto"],
+)
+
filegroup(
name = "srcs",
srcs = glob(["**"]),
diff --git a/src/tools/android/java/com/google/devtools/build/android/proto/Resources.proto b/src/tools/android/java/com/google/devtools/build/android/proto/Resources.proto
new file mode 100644
index 0000000000..27789a3162
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/proto/Resources.proto
@@ -0,0 +1,687 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package aapt.pb;
+
+option java_package = "com.android.aapt";
+
+// A string pool that wraps the binary form of the C++ class
+// android::ResStringPool.
+message StringPool {
+ bytes data = 1;
+}
+
+// The position of a declared entity within a file.
+message SourcePosition {
+ uint32 line_number = 1;
+ uint32 column_number = 2;
+}
+
+// Developer friendly source file information for an entity in the resource
+// table.
+message Source {
+ // The index of the string path within the source string pool of a
+ // ResourceTable.
+ uint32 path_idx = 1;
+ SourcePosition position = 2;
+}
+
+// Top level message representing a resource table.
+message ResourceTable {
+ // The string pool containing source paths referenced throughout the resource
+ // table. This does not end up in the final binary ARSC file.
+ StringPool source_pool = 1;
+
+ // Resource definitions corresponding to an Android package.
+ repeated Package package = 2;
+}
+
+// A package ID in the range [0x00, 0xff].
+message PackageId {
+ uint32 id = 1;
+}
+
+// Defines resources for an Android package.
+message Package {
+ // The package ID of this package, in the range [0x00, 0xff].
+ // - ID 0x00 is reserved for shared libraries, or when the ID is assigned at
+ // run-time.
+ // - ID 0x01 is reserved for the 'android' package (framework).
+ // - ID range [0x02, 0x7f) is reserved for auto-assignment to shared libraries
+ // at run-time.
+ // - ID 0x7f is reserved for the application package.
+ // - IDs > 0x7f are reserved for the application as well and are treated as
+ // feature splits. This may not be set if no ID was assigned.
+ PackageId package_id = 1;
+
+ // The Java compatible Android package name of the app.
+ string package_name = 2;
+
+ // The series of types defined by the package.
+ repeated Type type = 3;
+}
+
+// A type ID in the range [0x01, 0xff].
+message TypeId {
+ uint32 id = 1;
+}
+
+// A set of resources grouped under a common type. Such types include string,
+// layout, xml, dimen, attr, etc. This maps to the second part of a resource
+// identifier in Java (R.type.entry).
+message Type {
+ // The ID of the type. This may not be set if no ID was assigned.
+ TypeId id = 1;
+
+ // The name of the type. This corresponds to the 'type' part of a full
+ // resource name of the form package:type/entry. The set of legal type names
+ // is listed in Resource.cpp.
+ string name = 2;
+
+ // The entries defined for this type.
+ repeated Entry entry = 3;
+}
+
+// The status of a symbol/entry. This contains information like visibility
+// (public/private), comments, and whether the entry can be overridden.
+message SymbolStatus {
+ // The visibility of the resource outside of its package.
+ enum Visibility {
+ // No visibility was explicitly specified. This is typically treated as
+ // private. The distinction is important when two separate R.java files are
+ // generated: a public and private one. An unknown visibility, in this case,
+ // would cause the resource to be omitted from either R.java.
+ UNKNOWN = 0;
+
+ // A resource was explicitly marked as private. This means the resource can
+ // not be accessed outside of its package unless the @*package:type/entry
+ // notation is used (the asterisk being the private accessor). If two R.java
+ // files are generated (private + public), the resource will only be emitted
+ // to the private R.java file.
+ PRIVATE = 1;
+
+ // A resource was explicitly marked as public. This means the resource can
+ // be accessed from any package, and is emitted into all R.java files,
+ // public and private.
+ PUBLIC = 2;
+ }
+
+ Visibility visibility = 1;
+
+ // The path at which this entry's visibility was defined (eg. public.xml).
+ Source source = 2;
+
+ // The comment associated with the <public> tag.
+ string comment = 3;
+
+ // Whether the symbol can be merged into another resource table without there
+ // being an existing definition to override. Used for overlays and set to true
+ // when <add-resource> is specified.
+ bool allow_new = 4;
+}
+
+// An entry ID in the range [0x0000, 0xffff].
+message EntryId {
+ uint32 id = 1;
+}
+
+// An entry declaration. An entry has a full resource ID that is the combination
+// of package ID, type ID, and its own entry ID. An entry on its own has no
+// value, but values are defined for various configurations/variants.
+message Entry {
+ // The ID of this entry. Together with the package ID and type ID, this forms
+ // a full resource ID of the form 0xPPTTEEEE, where PP is the package ID, TT
+ // is the type ID, and EEEE is the entry ID. This may not be set if no ID was
+ // assigned.
+ EntryId id = 1;
+
+ // The name of this entry. This corresponds to the 'entry' part of a full
+ // resource name of the form package:type/entry.
+ string name = 2;
+
+ // The symbol status of this entry, which includes visibility information.
+ SymbolStatus symbol_status = 3;
+
+ // The set of values defined for this entry, each corresponding to a different
+ // configuration/variant.
+ repeated ConfigValue config_value = 4;
+}
+
+// A Configuration/Value pair.
+message ConfigValue {
+ Configuration config = 1;
+ Value value = 2;
+}
+
+// The generic meta-data for every value in a resource table.
+message Value {
+ // Where the value was defined.
+ Source source = 1;
+
+ // Any comment associated with the value.
+ string comment = 2;
+
+ // Whether the value can be overridden.
+ bool weak = 3;
+
+ // The value is either an Item or a CompoundValue.
+ oneof value {
+ Item item = 4;
+ CompoundValue compound_value = 5;
+ }
+}
+
+// An Item is an abstract type. It represents a value that can appear inline in
+// many places, such as XML attribute values or on the right hand side of style
+// attribute definitions. The concrete type is one of the types below. Only one
+// can be set.
+message Item {
+ oneof value {
+ Reference ref = 1;
+ String str = 2;
+ RawString raw_str = 3;
+ StyledString styled_str = 4;
+ FileReference file = 5;
+ Id id = 6;
+ Primitive prim = 7;
+ }
+}
+
+// A CompoundValue is an abstract type. It represents a value that is a made of
+// other values. These can only usually appear as top-level resources. The
+// concrete type is one of the types below. Only one can be set.
+message CompoundValue {
+ oneof value {
+ Attribute attr = 1;
+ Style style = 2;
+ Styleable styleable = 3;
+ Array array = 4;
+ Plural plural = 5;
+ }
+}
+
+// A value that is a reference to another resource. This reference can be by
+// name or resource ID.
+message Reference {
+ enum Type {
+ // A plain reference (@package:type/entry).
+ REFERENCE = 0;
+
+ // A reference to a theme attribute (?package:type/entry).
+ ATTRIBUTE = 1;
+ }
+
+ Type type = 1;
+
+ // The resource ID (0xPPTTEEEE) of the resource being referred. This is
+ // optional.
+ uint32 id = 2;
+
+ // The name of the resource being referred. This is optional if the resource
+ // ID is set.
+ string name = 3;
+
+ // Whether this reference is referencing a private resource
+ // (@*package:type/entry).
+ bool private = 4;
+}
+
+// A value that represents an ID. This is just a placeholder, as ID values are
+// used to occupy a resource ID (0xPPTTEEEE) as a unique identifier. Their value
+// is unimportant.
+message Id {
+}
+
+// A value that is a string.
+message String {
+ string value = 1;
+}
+
+// A value that is a raw string, which is unescaped/uninterpreted. This is
+// typically used to represent the value of a style attribute before the
+// attribute is compiled and the set of allowed values is known.
+message RawString {
+ string value = 1;
+}
+
+// A string with styling information, like html tags that specify boldness,
+// italics, etc.
+message StyledString {
+ // The raw text of the string.
+ string value = 1;
+
+ // A Span marks a region of the string text that is styled.
+ message Span {
+ // The name of the tag, and its attributes, encoded as follows:
+ // tag_name;attr1=value1;attr2=value2;[...]
+ string tag = 1;
+
+ // The first character position this span applies to, in UTF-16 offset.
+ uint32 first_char = 2;
+
+ // The last character position this span applies to, in UTF-16 offset.
+ uint32 last_char = 3;
+ }
+
+ repeated Span span = 2;
+}
+
+// A value that is a reference to an external entity, like an XML file or a PNG.
+message FileReference {
+ // Path to a file within the APK (typically res/type-config/entry.ext).
+ string path = 1;
+}
+
+// A value that represents a primitive data type (float, int, boolean, etc.).
+// Corresponds to the fields (type/data) of the C struct android::Res_value.
+message Primitive {
+ uint32 type = 1;
+ uint32 data = 2;
+}
+
+// A value that represents an XML attribute and what values it accepts.
+message Attribute {
+ // A Symbol used to represent an enum or a flag.
+ message Symbol {
+ // Where the enum/flag item was defined.
+ Source source = 1;
+
+ // Any comments associated with the enum or flag.
+ string comment = 2;
+
+ // The name of the enum/flag as a reference. Enums/flag items are generated
+ // as ID resource values.
+ Reference name = 3;
+
+ // The value of the enum/flag.
+ uint32 value = 4;
+ }
+
+ // Bitmask of formats allowed for an attribute.
+ enum FormatFlags {
+ NONE = 0x0; // Proto3 requires a default of 0.
+ ANY = 0x0000ffff; // Allows any type except ENUM and FLAGS.
+ REFERENCE = 0x01; // Allows Reference values.
+ STRING = 0x02; // Allows String/StyledString values.
+ INTEGER = 0x04; // Allows any integer BinaryPrimitive values.
+ BOOLEAN = 0x08; // Allows any boolean BinaryPrimitive values.
+ COLOR = 0x010; // Allows any color BinaryPrimitive values.
+ FLOAT = 0x020; // Allows any float BinaryPrimitive values.
+ DIMENSION = 0x040; // Allows any dimension BinaryPrimitive values.
+ FRACTION = 0x080; // Allows any fraction BinaryPrimitive values.
+ ENUM = 0x00010000; // Allows enums that are defined in the Attribute's
+ // symbols.
+ // ENUM and FLAGS cannot BOTH be set.
+ FLAGS = 0x00020000; // Allows flags that are defined in the Attribute's
+ // symbols.
+ // ENUM and FLAGS cannot BOTH be set.
+ }
+
+ // A bitmask of types that this XML attribute accepts. Corresponds to the
+ // flags in the enum FormatFlags.
+ uint32 format_flags = 1;
+
+ // The smallest integer allowed for this XML attribute. Only makes sense if
+ // the format includes FormatFlags::INTEGER.
+ int32 min_int = 2;
+
+ // The largest integer allowed for this XML attribute. Only makes sense if the
+ // format includes FormatFlags::INTEGER.
+ int32 max_int = 3;
+
+ // The set of enums/flags defined in this attribute. Only makes sense if the
+ // format includes either FormatFlags::ENUM or FormatFlags::FLAGS. Having both
+ // is an error.
+ repeated Symbol symbol = 4;
+}
+
+// A value that represents a style.
+message Style {
+ // An XML attribute/value pair defined in the style.
+ message Entry {
+ // Where the entry was defined.
+ Source source = 1;
+
+ // Any comments associated with the entry.
+ string comment = 2;
+
+ // A reference to the XML attribute.
+ Reference key = 3;
+
+ // The Item defined for this XML attribute.
+ Item item = 4;
+ }
+
+ // The optinal style from which this style inherits attributes.
+ Reference parent = 1;
+
+ // The source file information of the parent inheritance declaration.
+ Source parent_source = 2;
+
+ // The set of XML attribute/value pairs for this style.
+ repeated Entry entry = 3;
+}
+
+// A value that represents a <declare-styleable> XML resource. These are not
+// real resources and only end up as Java fields in the generated R.java. They
+// do not end up in the binary ARSC file.
+message Styleable {
+ // An attribute defined for this styleable.
+ message Entry {
+ // Where the attribute was defined within the <declare-styleable> block.
+ Source source = 1;
+
+ // Any comments associated with the declaration.
+ string comment = 2;
+
+ // The reference to the attribute.
+ Reference attr = 3;
+ }
+
+ // The set of attribute declarations.
+ repeated Entry entry = 1;
+}
+
+// A value that represents an array of resource values.
+message Array {
+ // A single element of the array.
+ message Element {
+ // Where the element was defined.
+ Source source = 1;
+
+ // Any comments associated with the element.
+ string comment = 2;
+
+ // The value assigned to this element.
+ Item item = 3;
+ }
+
+ // The list of array elements.
+ repeated Element element = 1;
+}
+
+// A value that represents a string and its many variations based on plurality.
+message Plural {
+ // The arity of the plural.
+ enum Arity {
+ ZERO = 0;
+ ONE = 1;
+ TWO = 2;
+ FEW = 3;
+ MANY = 4;
+ OTHER = 5;
+ }
+
+ // The plural value for a given arity.
+ message Entry {
+ // Where the plural was defined.
+ Source source = 1;
+
+ // Any comments associated with the plural.
+ string comment = 2;
+
+ // The arity of the plural.
+ Arity arity = 3;
+
+ // The value assigned to this plural.
+ Item item = 4;
+ }
+
+ // The set of arity/plural mappings.
+ repeated Entry entry = 1;
+}
+
+// Defines an abstract XmlNode that must be either an XmlElement, or
+// a text node represented by a string.
+message XmlNode {
+ oneof node {
+ XmlElement element = 1;
+ string text = 2;
+ }
+
+ // Source line and column info.
+ SourcePosition source = 3;
+}
+
+// An <element> in an XML document.
+message XmlElement {
+ // Namespaces defined on this element.
+ repeated XmlNamespace namespace_declaration = 1;
+
+ // The namespace URI of this element.
+ string namespace_uri = 2;
+
+ // The name of this element.
+ string name = 3;
+
+ // The attributes of this element.
+ repeated XmlAttribute attribute = 4;
+
+ // The children of this element.
+ repeated XmlNode child = 5;
+}
+
+// A namespace declaration on an XmlElement (xmlns:android="http://...").
+message XmlNamespace {
+ string prefix = 1;
+ string uri = 2;
+
+ // Source line and column info.
+ SourcePosition source = 3;
+}
+
+// An attribute defined on an XmlElement (android:text="...").
+message XmlAttribute {
+ string namespace_uri = 1;
+ string name = 2;
+ string value = 3;
+
+ // Source line and column info.
+ SourcePosition source = 4;
+
+ // The optional resource ID (0xPPTTEEEE) of the attribute.
+ uint32 resource_id = 5;
+
+ // The optional interpreted/compiled version of the `value` string.
+ Item compiled_item = 6;
+}
+
+// A description of the requirements a device must have in order for a
+// resource to be matched and selected.
+message Configuration {
+ enum LayoutDirection {
+ LAYOUT_DIRECTION_UNSET = 0;
+ LAYOUT_DIRECTION_LTR = 1;
+ LAYOUT_DIRECTION_RTL = 2;
+ }
+
+ enum ScreenLayoutSize {
+ SCREEN_LAYOUT_SIZE_UNSET = 0;
+ SCREEN_LAYOUT_SIZE_SMALL = 1;
+ SCREEN_LAYOUT_SIZE_NORMAL = 2;
+ SCREEN_LAYOUT_SIZE_LARGE = 3;
+ SCREEN_LAYOUT_SIZE_XLARGE = 4;
+ }
+
+ enum ScreenLayoutLong {
+ SCREEN_LAYOUT_LONG_UNSET = 0;
+ SCREEN_LAYOUT_LONG_LONG = 1;
+ SCREEN_LAYOUT_LONG_NOTLONG = 2;
+ }
+
+ enum ScreenRound {
+ SCREEN_ROUND_UNSET = 0;
+ SCREEN_ROUND_ROUND = 1;
+ SCREEN_ROUND_NOTROUND = 2;
+ }
+
+ enum WideColorGamut {
+ WIDE_COLOR_GAMUT_UNSET = 0;
+ WIDE_COLOR_GAMUT_WIDECG = 1;
+ WIDE_COLOR_GAMUT_NOWIDECG = 2;
+ }
+
+ enum Hdr {
+ HDR_UNSET = 0;
+ HDR_HIGHDR = 1;
+ HDR_LOWDR = 2;
+ }
+
+ enum Orientation {
+ ORIENTATION_UNSET = 0;
+ ORIENTATION_PORT = 1;
+ ORIENTATION_LAND = 2;
+ ORIENTATION_SQUARE = 3;
+ }
+
+ enum UiModeType {
+ UI_MODE_TYPE_UNSET = 0;
+ UI_MODE_TYPE_NORMAL = 1;
+ UI_MODE_TYPE_DESK = 2;
+ UI_MODE_TYPE_CAR = 3;
+ UI_MODE_TYPE_TELEVISION = 4;
+ UI_MODE_TYPE_APPLIANCE = 5;
+ UI_MODE_TYPE_WATCH = 6;
+ UI_MODE_TYPE_VRHEADSET = 7;
+ }
+
+ enum UiModeNight {
+ UI_MODE_NIGHT_UNSET = 0;
+ UI_MODE_NIGHT_NIGHT = 1;
+ UI_MODE_NIGHT_NOTNIGHT = 2;
+ }
+
+ enum Touchscreen {
+ TOUCHSCREEN_UNSET = 0;
+ TOUCHSCREEN_NOTOUCH = 1;
+ TOUCHSCREEN_STYLUS = 2;
+ TOUCHSCREEN_FINGER = 3;
+ }
+
+ enum KeysHidden {
+ KEYS_HIDDEN_UNSET = 0;
+ KEYS_HIDDEN_KEYSEXPOSED = 1;
+ KEYS_HIDDEN_KEYSHIDDEN = 2;
+ KEYS_HIDDEN_KEYSSOFT = 3;
+ }
+
+ enum Keyboard {
+ KEYBOARD_UNSET = 0;
+ KEYBOARD_NOKEYS = 1;
+ KEYBOARD_QWERTY = 2;
+ KEYBOARD_TWELVEKEY = 3;
+ }
+
+ enum NavHidden {
+ NAV_HIDDEN_UNSET = 0;
+ NAV_HIDDEN_NAVEXPOSED = 1;
+ NAV_HIDDEN_NAVHIDDEN = 2;
+ }
+
+ enum Navigation {
+ NAVIGATION_UNSET = 0;
+ NAVIGATION_NONAV = 1;
+ NAVIGATION_DPAD = 2;
+ NAVIGATION_TRACKBALL = 3;
+ NAVIGATION_WHEEL = 4;
+ }
+
+ //
+ // Axis/dimensions that are understood by the runtime.
+ //
+
+ // Mobile country code.
+ uint32 mcc = 1;
+
+ // Mobile network code.
+ uint32 mnc = 2;
+
+ // BCP-47 locale tag.
+ string locale = 3;
+
+ // Left-to-right, right-to-left...
+ LayoutDirection layout_direction = 4;
+
+ // Screen width in pixels. Prefer screen_width_dp.
+ uint32 screen_width = 5;
+
+ // Screen height in pixels. Prefer screen_height_dp.
+ uint32 screen_height = 6;
+
+ // Screen width in density independent pixels (dp).
+ uint32 screen_width_dp = 7;
+
+ // Screen height in density independent pixels (dp).
+ uint32 screen_height_dp = 8;
+
+ // The smallest screen dimension, regardless of orientation, in dp.
+ uint32 smallest_screen_width_dp = 9;
+
+ // Whether the device screen is classified as small, normal, large, xlarge.
+ ScreenLayoutSize screen_layout_size = 10;
+
+ // Whether the device screen is long.
+ ScreenLayoutLong screen_layout_long = 11;
+
+ // Whether the screen is round (Android Wear).
+ ScreenRound screen_round = 12;
+
+ // Whether the screen supports wide color gamut.
+ WideColorGamut wide_color_gamut = 13;
+
+ // Whether the screen has high dynamic range.
+ Hdr hdr = 14;
+
+ // Which orientation the device is in (portrait, landscape).
+ Orientation orientation = 15;
+
+ // Which type of UI mode the device is in (television, car, etc.).
+ UiModeType ui_mode_type = 16;
+
+ // Whether the device is in night mode.
+ UiModeNight ui_mode_night = 17;
+
+ // The device's screen density in dots-per-inch (dpi).
+ uint32 density = 18;
+
+ // Whether a touchscreen exists, supports a stylus, or finger.
+ Touchscreen touchscreen = 19;
+
+ // Whether the keyboard hardware keys are currently hidden, exposed, or
+ // if the keyboard is a software keyboard.
+ KeysHidden keys_hidden = 20;
+
+ // The type of keyboard present (none, QWERTY, 12-key).
+ Keyboard keyboard = 21;
+
+ // Whether the navigation is exposed or hidden.
+ NavHidden nav_hidden = 22;
+
+ // The type of navigation present on the device
+ // (trackball, wheel, dpad, etc.).
+ Navigation navigation = 23;
+
+ // The minimum SDK version of the device.
+ uint32 sdk_version = 24;
+
+ //
+ // Build-time only dimensions.
+ //
+
+ string product = 25;
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/proto/format.proto b/src/tools/android/java/com/google/devtools/build/android/proto/format.proto
new file mode 100644
index 0000000000..6caeedb368
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/proto/format.proto
@@ -0,0 +1,43 @@
+// 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.
+
+syntax = "proto2";
+
+
+// option java_api_version = 2;
+option optimize_for = LITE_RUNTIME;
+
+package aapt.pb;
+option java_package = "com.google.devtools.build.android.proto";
+
+message ConfigDescription {
+ optional bytes data = 1;
+ optional string product = 2;
+}
+
+message StringPool {
+ optional bytes data = 1;
+}
+
+message CompiledFile {
+ message Symbol {
+ optional string resource_name = 1;
+ optional uint32 line_no = 2;
+ }
+
+ optional string resource_name = 1;
+ optional ConfigDescription config = 2;
+ optional string source_path = 3;
+ repeated Symbol exported_symbols = 4;
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java
index e3f5571c7d..fb74fd2208 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java
@@ -13,6 +13,10 @@
// limitations under the License.
package com.google.devtools.build.android.xml;
+import com.android.aapt.Resources.Array;
+import com.android.aapt.Resources.Array.Element;
+import com.android.aapt.Resources.Item;
+import com.android.aapt.Resources.Value;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
@@ -117,6 +121,29 @@ public class ArrayXmlResourceValue implements XmlResourceValue {
ImmutableMap.copyOf(proto.getAttribute()));
}
+ public static XmlResourceValue from(Value proto) {
+ Array array = proto.getCompoundValue().getArray();
+ List<String> items = new ArrayList<>();
+
+ for (Element entry : array.getElementList()) {
+ Item item = entry.getItem();
+
+ if (item.hasPrim()) {
+ String stringValue = "#" + Integer.toHexString(item.getPrim().getData());
+ items.add(stringValue);
+ } else if (item.hasRef()) {
+ items.add("@" + item.getRef().getName());
+ } else if (item.hasStr()) {
+ items.add(item.getStr().getValue());
+ }
+ }
+
+ return of(
+ ArrayType.ARRAY,
+ items,
+ ImmutableMap.of());
+ }
+
@Override
public void write(
FullyQualifiedName key, DataSource source, AndroidDataWritingVisitor mergedDataWriter) {
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java
index 661e5ddf98..f2645ac0a3 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java
@@ -16,6 +16,9 @@ package com.google.devtools.build.android.xml;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.not;
+import com.android.aapt.Resources.Attribute;
+import com.android.aapt.Resources.Attribute.Symbol;
+import com.android.aapt.Resources.Value;
import com.android.resources.ResourceType;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
@@ -39,6 +42,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
@@ -195,6 +199,66 @@ public class AttrXmlResourceValue implements XmlResourceValue {
return of(formats.build());
}
+ public static XmlResourceValue from(Value proto) throws InvalidProtocolBufferException {
+ Builder<String, ResourceXmlAttrValue> formats =
+ ImmutableMap.builder();
+
+ Attribute attribute = proto.getCompoundValue().getAttr();
+ int formatFlags = attribute.getFormatFlags();
+
+ if (formatFlags != 0xFFFF) {
+ //These flags are defined in AOSP in ResourceTypes.h:ResTable_map
+ if ((formatFlags & 1 << 0) != 0) {
+ formats.put("reference", ReferenceResourceXmlAttrValue.of());
+ }
+ if ((formatFlags & 1 << 1) != 0) {
+ formats.put("string", StringResourceXmlAttrValue.of());
+ }
+ if ((formatFlags & 1 << 2) != 0) {
+ formats.put("integer", IntegerResourceXmlAttrValue.of());
+ }
+ if ((formatFlags & 1 << 3) != 0) {
+ formats.put("boolean", BooleanResourceXmlAttrValue.of());
+ }
+ if ((formatFlags & 1 << 4) != 0) {
+ formats.put("color", ColorResourceXmlAttrValue.of());
+ }
+ if ((formatFlags & 1 << 5) != 0) {
+ formats.put("float", FloatResourceXmlAttrValue.of());
+ }
+ if ((formatFlags & 1 << 6) != 0) {
+ formats.put("dimension", DimensionResourceXmlAttrValue.of());
+ }
+ if ((formatFlags & 1 << 7) != 0) {
+ formats.put("fraction", FractionResourceXmlAttrValue.of());
+ }
+ if ((formatFlags & 1 << 16) != 0) {
+ Map<String, String> enums = new HashMap<>();
+
+ for (Symbol attrSymbol : attribute.getSymbolList()) {
+ String name = attrSymbol.getName().getName().replaceFirst("id/", "");
+ enums.put(name, Integer.toString(attrSymbol.getValue()));
+ }
+
+ formats.put("enum", EnumResourceXmlAttrValue.of(enums));
+ }
+ if ((formatFlags & 1 << 17) != 0) {
+ Map<String, String> flags = new HashMap<>();
+ for (Symbol attrSymbol : attribute.getSymbolList()) {
+ String name = attrSymbol.getName().getName().replaceFirst("id/", "");
+ flags.put(name, Integer.toString(attrSymbol.getValue()));
+ }
+
+ formats.put("flags", FlagResourceXmlAttrValue.of(flags));
+ }
+ if ((formatFlags & 0xFFFCFF00) != 0) {
+ throw new InvalidProtocolBufferException(
+ "Unexpected format flags: " + formatFlags);
+ }
+ }
+ return of(formats.build());
+ }
+
/**
* Creates a new {@link AttrXmlResourceValue}. Returns null if there are no formats.
*/
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java
index 1ff1578eab..6e1fba18c7 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java
@@ -13,6 +13,8 @@
// limitations under the License.
package com.google.devtools.build.android.xml;
+import com.android.aapt.Resources.Plural;
+import com.android.aapt.Resources.Value;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.android.AndroidDataWritingVisitor;
@@ -27,6 +29,8 @@ import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.XmlT
import com.google.protobuf.CodedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import javax.annotation.concurrent.Immutable;
@@ -131,6 +135,29 @@ public class PluralXmlResourceValue implements XmlResourceValue {
ImmutableMap.copyOf(proto.getMappedStringValue()));
}
+ public static XmlResourceValue from(Value proto) {
+ Plural plural = proto.getCompoundValue().getPlural();
+
+ Map<String, String> items = new HashMap<>();
+
+ for (Plural.Entry entry : plural.getEntryList()) {
+ String name = entry.getArity().toString().toLowerCase();
+ String value =
+ entry
+ .getItem()
+ .getStr()
+ .toString()
+ .replace("value: \"", "")
+ .replace("\"", "")
+ .replace('\n', ' ');
+ items.put(name, value);
+ }
+
+ return createWithAttributesAndValues(
+ ImmutableMap.of(),
+ ImmutableMap.copyOf(items));
+ }
+
@Override
public int serializeTo(int sourceId, Namespaces namespaces, OutputStream output)
throws IOException {
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java
index 923c163ad3..4006d4d7a4 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java
@@ -13,6 +13,10 @@
// limitations under the License.
package com.google.devtools.build.android.xml;
+import com.android.aapt.Resources.Item;
+import com.android.aapt.Resources.StyledString;
+import com.android.aapt.Resources.StyledString.Span;
+import com.android.aapt.Resources.Value;
import com.android.resources.ResourceType;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
@@ -54,6 +58,8 @@ public class SimpleXmlResourceValue implements XmlResourceValue {
static final QName TAG_FRACTION = QName.valueOf("fraction");
static final QName TAG_INTEGER = QName.valueOf("integer");
static final QName TAG_ITEM = QName.valueOf("item");
+ static final QName TAG_LAYOUT = QName.valueOf("layout");
+ static final QName TAG_MIPMAP = QName.valueOf("mipmap");
static final QName TAG_PUBLIC = QName.valueOf("public");
static final QName TAG_STRING = QName.valueOf("string");
@@ -108,6 +114,20 @@ public class SimpleXmlResourceValue implements XmlResourceValue {
return true;
}
},
+ LAYOUT(TAG_LAYOUT) {
+ @Override
+ public boolean validate(String value) {
+ // TODO(corysmith): Validate the item type.
+ return true;
+ }
+ },
+ MIPMAP(TAG_MIPMAP) {
+ @Override
+ public boolean validate(String value) {
+ // TODO(corysmith): Validate the item type.
+ return true;
+ }
+ },
PUBLIC(TAG_PUBLIC) {
@Override
public boolean validate(String value) {
@@ -121,7 +141,7 @@ public class SimpleXmlResourceValue implements XmlResourceValue {
return true;
}
};
- private QName tagName;
+ private final QName tagName;
Type(QName tagName) {
this.tagName = tagName;
@@ -211,6 +231,45 @@ public class SimpleXmlResourceValue implements XmlResourceValue {
proto.hasValue() ? proto.getValue() : null);
}
+ public static XmlResourceValue from(Value proto, ResourceType resourceType) {
+ Item item = proto.getItem();
+ String stringValue = null;
+
+ if (item.hasStr()) {
+ stringValue = item.getStr().toString();
+ } else if (item.hasRef()) {
+ stringValue = "@" + item.getRef().getName();
+ } else if (item.hasStyledStr()) {
+ StyledString styledString = item.getStyledStr();
+ StringBuilder stringBuilder = new StringBuilder(styledString.getValue());
+
+ for (Span span : styledString.getSpanList()) {
+ stringBuilder.append(
+ String.format(";%s,%d,%d", span.getTag(), span.getFirstChar(), span.getLastChar()));
+ }
+ stringValue = stringBuilder.toString();
+ } else if ((resourceType == ResourceType.COLOR
+ || resourceType == ResourceType.DRAWABLE) && item.hasPrim()) {
+ stringValue =
+ String.format("#%1$8s", Integer.toHexString(item.getPrim().getData())).replace(' ', '0');
+ } else if (resourceType == ResourceType.INTEGER && item.hasPrim()){
+ stringValue = Integer.toString(item.getPrim().getData());
+ } else if (resourceType == ResourceType.BOOL && item.hasPrim()) {
+ stringValue = item.getPrim().getData() == 0 ? "false" : "true";
+ } else if (resourceType == ResourceType.FRACTION
+ || resourceType == ResourceType.DIMEN) {
+ stringValue = Integer.toString(item.getPrim().getData());
+ } else {
+ throw new IllegalArgumentException(
+ String.format("'%s' is not a valid resource type.", resourceType));
+ }
+
+ return of(
+ Type.valueOf(resourceType.toString().toUpperCase()),
+ ImmutableMap.of(),
+ stringValue);
+ }
+
@Override
public void writeResourceToClass(FullyQualifiedName key, AndroidResourceSymbolSink sink) {
sink.acceptSimpleResource(key.type(), key.name());
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java
index ccf52b6480..55c3d00fdd 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java
@@ -13,6 +13,8 @@
// limitations under the License.
package com.google.devtools.build.android.xml;
+import com.android.aapt.Resources.Style;
+import com.android.aapt.Resources.Value;
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
@@ -27,6 +29,7 @@ import com.google.devtools.build.android.proto.SerializeFormat;
import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.XmlType;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
@@ -77,6 +80,42 @@ public class StyleXmlResourceValue implements XmlResourceValue {
this.values = values;
}
+ public static XmlResourceValue from(Value proto) {
+ Style style = proto.getCompoundValue().getStyle();
+ String parent = "";
+
+ if (style.hasParent()) {
+ parent = proto.getCompoundValue().getStyle().getParent().getName();
+ }
+
+ Map<String, String> items = itemMapFromProto(style);
+
+ return of(parent, items);
+ }
+
+ private static Map<String, String> itemMapFromProto(Style style) {
+ Map<String, String> result = new HashMap<>();
+
+ for (Style.Entry styleEntry : style.getEntryList()) {
+ String itemName = styleEntry.getKey().getName().replace("attr/", "");
+ String itemValue;
+
+ if (styleEntry.getItem().hasRawStr()) {
+ itemValue = styleEntry.getItem().getRawStr().getValue();
+ } else if (styleEntry.getItem().hasRef()) {
+ itemValue = "@" + styleEntry.getItem().getRef().getName();
+ if (itemValue.equals("@")) {
+ itemValue = "@null";
+ }
+ } else {
+ throw new IllegalArgumentException("Could not parse item value from Style resource.");
+ }
+ result.put(itemName, itemValue);
+ }
+
+ return result;
+ }
+
@Override
public void write(
FullyQualifiedName key, DataSource source, AndroidDataWritingVisitor mergedDataWriter) {
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java
index 8e55e089d3..c9661690cf 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java
@@ -13,6 +13,8 @@
// limitations under the License.
package com.google.devtools.build.android.xml;
+import com.android.aapt.Resources.Styleable;
+import com.android.aapt.Resources.Value;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
@@ -31,6 +33,7 @@ import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.XmlT
import java.io.IOException;
import java.io.OutputStream;
import java.util.AbstractMap.SimpleEntry;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -161,6 +164,22 @@ public class StyleableXmlResourceValue implements XmlResourceValue {
Iterables.transform(proto.getReferencesList(), DATA_KEY_TO_FULLY_QUALIFIED_NAME)));
}
+ public static XmlResourceValue from(
+ Value proto, Map<String, Entry<FullyQualifiedName, Boolean>> fullyQualifiedNames) {
+ Map<FullyQualifiedName, Boolean> attributes = new HashMap<>();
+
+ Styleable styleable = proto.getCompoundValue().getStyleable();
+ for (Styleable.Entry entry : styleable.getEntryList()) {
+ String attrName = entry.getAttr().getName();
+
+ Entry<FullyQualifiedName, Boolean> fqnEntry = fullyQualifiedNames.get(attrName);
+ attributes.put(fqnEntry.getKey(), fqnEntry.getValue());
+ fqnEntry.setValue(false);
+ }
+
+ return of(ImmutableMap.copyOf(attributes));
+ }
+
@Override
public int hashCode() {
return attrs.hashCode();