From 233807ec94189c4a322c203322163a154d90aa8f Mon Sep 17 00:00:00 2001 From: Googler Date: Mon, 8 Jan 2018 15:15:58 -0800 Subject: Serialize and package xml attributes from resources xml tags in values folders for aapt2. RELNOTES: none PiperOrigin-RevId: 181226483 --- .../android/AndroidCompiledDataDeserializer.java | 81 +++++++++++++----- .../build/android/aapt2/ResourceCompiler.java | 97 ++++++++++++++++++++-- 2 files changed, 151 insertions(+), 27 deletions(-) (limited to 'src/tools') 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 6ca066d06d..265d938fea 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 @@ -27,11 +27,15 @@ 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.SerializeFormat; +import com.google.devtools.build.android.proto.SerializeFormat.Header; +import com.google.devtools.build.android.xml.ResourcesAttribute.AttributeType; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -177,6 +181,38 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer } } + private void readAttributesFile( + InputStream resourceFileStream, + FileSystem fileSystem, + KeyValueConsumers consumers) throws IOException { + + Header header = Header.parseDelimitedFrom(resourceFileStream); + List fullyQualifiedNames = new ArrayList<>(); + for (int i = 0; i < header.getEntryCount(); i++) { + SerializeFormat.DataKey protoKey = + SerializeFormat.DataKey.parseDelimitedFrom(resourceFileStream); + fullyQualifiedNames.add(FullyQualifiedName.fromProto(protoKey)); + } + + DataSourceTable sourceTable = DataSourceTable.read(resourceFileStream, fileSystem, header); + + for (DataKey fullyQualifiedName : fullyQualifiedNames) { + SerializeFormat.DataValue protoValue = + SerializeFormat.DataValue.parseDelimitedFrom(resourceFileStream); + DataSource source = sourceTable.sourceFromId(protoValue.getSourceId()); + DataResourceXml dataResourceXml = + (DataResourceXml) DataResourceXml.from(protoValue, source); + AttributeType attributeType = + AttributeType.valueOf(protoValue.getXmlValue().getValueType()); + + if (attributeType.isCombining()) { + consumers.combiningConsumer.accept(fullyQualifiedName, dataResourceXml); + } else { + consumers.overwritingConsumer.accept(fullyQualifiedName, dataResourceXml); + } + } + } + @Override public void read(Path inPath, KeyValueConsumers consumers) { Stopwatch timer = Stopwatch.createStarted(); @@ -185,8 +221,6 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer while (resourceFiles.hasMoreElements()) { ZipEntry resourceFile = resourceFiles.nextElement(); - InputStream resourceFileStream = zipFile.getInputStream(resourceFile); - String fileZipPath = resourceFile.getName(); int resourceSubdirectoryIndex = fileZipPath.indexOf('_', fileZipPath.lastIndexOf('/')); Path filePath = @@ -206,24 +240,31 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer continue; } - final String[] dirNameAndQualifiers = - filePath.getParent().getFileName().toString().split(SdkConstants.RES_QUALIFIER_SEP); - Factory fqnFactory = Factory.fromDirectoryName(dirNameAndQualifiers); - - LittleEndianDataInputStream dataInputStream = - new LittleEndianDataInputStream(resourceFileStream); - - // Magic number (4 bytes), Format version (4 bytes), Number of entries (4 bytes). - Preconditions.checkArgument(dataInputStream.skipBytes(12) == 12); - - int resourceType = dataInputStream.readInt(); - if (resourceType == 0) { // 0 is a resource table - readResourceTable(dataInputStream, consumers, fqnFactory); - } else if (resourceType == 1) { // 1 is a resource file - readCompiledFile(dataInputStream, consumers, fqnFactory); - } else { - throw new RuntimeException( - String.format("Invalid resource type enum: %s from %s", resourceType, fileZipPath)); + try (InputStream resourceFileStream = zipFile.getInputStream(resourceFile)) { + final String[] dirNameAndQualifiers = + filePath.getParent().getFileName().toString().split(SdkConstants.RES_QUALIFIER_SEP); + Factory fqnFactory = Factory.fromDirectoryName(dirNameAndQualifiers); + + if (fileZipPath.endsWith(".attributes")) { + readAttributesFile(resourceFileStream, inPath.getFileSystem(), consumers); + } else { + LittleEndianDataInputStream dataInputStream = + new LittleEndianDataInputStream(resourceFileStream); + + // Magic number (4 bytes), Format version (4 bytes), Number of entries (4 bytes). + Preconditions.checkArgument(dataInputStream.skipBytes(12) == 12); + + int resourceType = dataInputStream.readInt(); + if (resourceType == 0) { // 0 is a resource table + readResourceTable(dataInputStream, consumers, fqnFactory); + } else if (resourceType == 1) { // 1 is a resource file + readCompiledFile(dataInputStream, consumers, fqnFactory); + } else { + throw new RuntimeException( + String.format( + "Invalid resource type enum: %s from %s", resourceType, fileZipPath)); + } + } } } } catch (IOException e) { diff --git a/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java b/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java index e51192ce1e..50c5ff5b13 100644 --- a/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java +++ b/src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java @@ -14,6 +14,7 @@ package com.google.devtools.build.android.aapt2; +import com.android.SdkConstants; import com.android.builder.core.VariantType; import com.android.repository.Revision; import com.google.common.base.Preconditions; @@ -22,6 +23,15 @@ import com.google.common.collect.ImmutableList.Builder; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.devtools.build.android.AaptCommandBuilder; +import com.google.devtools.build.android.AndroidDataSerializer; +import com.google.devtools.build.android.DataResourceXml; +import com.google.devtools.build.android.FullyQualifiedName; +import com.google.devtools.build.android.FullyQualifiedName.Factory; +import com.google.devtools.build.android.FullyQualifiedName.VirtualType; +import com.google.devtools.build.android.XmlResourceValues; +import com.google.devtools.build.android.xml.Namespaces; +import com.google.devtools.build.android.xml.ResourcesAttribute; +import java.io.FileInputStream; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; @@ -29,11 +39,17 @@ import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.logging.Logger; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.StartElement; /** Invokes aapt2 to compile resources. */ public class ResourceCompiler { @@ -58,7 +74,7 @@ public class ResourceCompiler { private final CompilingVisitor compilingVisitor; - private static class CompileTask implements Callable { + private static class CompileTask implements Callable> { private final Path file; private final Path compiledResourcesOut; @@ -66,7 +82,10 @@ public class ResourceCompiler { private final Revision buildToolsVersion; private CompileTask( - Path file, Path compiledResourcesOut, Path aapt2, Revision buildToolsVersion) { + Path file, + Path compiledResourcesOut, + Path aapt2, + Revision buildToolsVersion) { this.file = file; this.compiledResourcesOut = compiledResourcesOut; this.aapt2 = aapt2; @@ -74,7 +93,7 @@ public class ResourceCompiler { } @Override - public Path call() throws Exception { + public List call() throws Exception { logger.fine( new AaptCommandBuilder(aapt2) .forBuildToolsVersion(buildToolsVersion) @@ -88,10 +107,34 @@ public class ResourceCompiler { String type = file.getParent().getFileName().toString(); String filename = file.getFileName().toString(); + + List results = new ArrayList<>(); if (type.startsWith("values")) { filename = (filename.indexOf('.') != -1 ? filename.substring(0, filename.indexOf('.')) : filename) + ".arsc"; + + XMLEventReader xmlEventReader = null; + try { + // aapt2 compile strips out namespaces and attributes from the resources tag. + // Read them here separately and package them with the other flat files. + xmlEventReader = + XMLInputFactory.newInstance() + .createXMLEventReader(new FileInputStream(file.toString())); + + StartElement rootElement = xmlEventReader.nextTag().asStartElement(); + Iterator attributeIterator = + XmlResourceValues.iterateAttributesFrom(rootElement); + + if (attributeIterator.hasNext()) { + results.add( + createAttributesProto(type, filename, attributeIterator)); + } + } finally { + if (xmlEventReader != null) { + xmlEventReader.close(); + } + } } final Path compiledResourcePath = @@ -100,7 +143,47 @@ public class ResourceCompiler { Files.exists(compiledResourcePath), "%s does not exists after aapt2 ran.", compiledResourcePath); - return compiledResourcePath; + results.add(compiledResourcePath); + return results; + } + + private Path createAttributesProto( + String type, + String filename, + Iterator attributeIterator) + throws IOException { + + AndroidDataSerializer serializer = AndroidDataSerializer.create(); + final Path resourcesAttributesPath = + compiledResourcesOut.resolve(type + "_" + filename + ".attributes"); + + while (attributeIterator.hasNext()) { + Attribute attribute = attributeIterator.next(); + String namespaceUri = attribute.getName().getNamespaceURI(); + String localPart = attribute.getName().getLocalPart(); + String prefix = attribute.getName().getPrefix(); + QName qName = new QName(namespaceUri, localPart, prefix); + + Namespaces namespaces = Namespaces.from(qName); + String attributeName = + namespaceUri.isEmpty() + ? localPart + : prefix + ":" + localPart; + + final String[] dirNameAndQualifiers = type.split(SdkConstants.RES_QUALIFIER_SEP); + Factory fqnFactory = Factory.fromDirectoryName(dirNameAndQualifiers); + FullyQualifiedName fqn = + fqnFactory.create(VirtualType.RESOURCES_ATTRIBUTE, qName.toString()); + ResourcesAttribute resourceAttribute = + ResourcesAttribute.of(fqn, attributeName, attribute.getValue()); + DataResourceXml resource = + DataResourceXml.createWithNamespaces(file, resourceAttribute, namespaces); + + serializer.queueForSerialization(fqn, resource); + } + + serializer.flushTo(resourcesAttributesPath); + return resourcesAttributesPath; } @Override @@ -113,7 +196,7 @@ public class ResourceCompiler { private final ListeningExecutorService executorService; private final Path compiledResources; - private final List> tasks = new ArrayList<>(); + private final List>> tasks = new ArrayList<>(); private final Path aapt2; private final Revision buildToolsVersion; @@ -152,9 +235,9 @@ public class ResourceCompiler { List getCompiledArtifacts() throws InterruptedException, ExecutionException { Builder builder = ImmutableList.builder(); List compilationErrors = new ArrayList<>(); - for (ListenableFuture task : tasks) { + for (ListenableFuture> task : tasks) { try { - builder.add(task.get()); + builder.addAll(task.get()); } catch (InterruptedException | ExecutionException e) { compilationErrors.add(Optional.ofNullable(e.getCause()).orElse(e)); } -- cgit v1.2.3