From e3784eccef5ff6b1db35186e763a2b3d1dd3ef54 Mon Sep 17 00:00:00 2001 From: corysmith Date: Tue, 8 May 2018 08:25:10 -0700 Subject: Generate a separate psuedo locale .flat file only for default locale on values resources. RELNOTES:None PiperOrigin-RevId: 195821595 --- .../build/android/aapt2/ResourceCompiler.java | 246 +++++++++++++-------- 1 file changed, 150 insertions(+), 96 deletions(-) (limited to 'src/tools/android/java/com/google/devtools/build/android/aapt2/ResourceCompiler.java') 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 355fcbf4d0..68e12e146e 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,8 +14,10 @@ package com.google.devtools.build.android.aapt2; +import com.android.SdkConstants; import com.android.builder.core.VariantType; import com.android.repository.Revision; +import com.android.resources.ResourceFolderType; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.ListenableFuture; @@ -25,6 +27,7 @@ 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.Qualifiers; import com.google.devtools.build.android.FullyQualifiedName.VirtualType; import com.google.devtools.build.android.XmlResourceValues; import com.google.devtools.build.android.xml.Namespaces; @@ -38,11 +41,10 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Iterator; -import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; +import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.logging.Logger; @@ -85,96 +87,125 @@ public class ResourceCompiler { private final Path compiledResourcesOut; private final Path aapt2; private final Revision buildToolsVersion; - private final boolean generatePseudoLocale; + private final Optional generatedResourcesOut; private CompileTask( Path file, Path compiledResourcesOut, Path aapt2, Revision buildToolsVersion, - boolean generatePseudoLocale) { + Optional generatedResourcesOut) { this.file = file; this.compiledResourcesOut = compiledResourcesOut; this.aapt2 = aapt2; this.buildToolsVersion = buildToolsVersion; - this.generatePseudoLocale = generatePseudoLocale; + this.generatedResourcesOut = generatedResourcesOut; } @Override public List call() throws Exception { - logger.fine( - new AaptCommandBuilder(aapt2) - .forBuildToolsVersion(buildToolsVersion) - .forVariantType(VariantType.LIBRARY) - .add("compile") - .add("-v") - .add("--legacy") - .when(generatePseudoLocale) - .thenAdd("--pseudo-localize") - .add("-o", compiledResourcesOut.toString()) - .add(file.toString()) - .execute("Compiling " + file)); - - String type = file.getParent().getFileName().toString(); - String filename = file.getFileName().toString(); + final String directoryName = file.getParent().getFileName().toString(); + Qualifiers qualifiers = Qualifiers.parseFrom(directoryName); + String filename = interpolateAapt2Filename(qualifiers, file.getFileName().toString()); List results = new ArrayList<>(); - if (type.startsWith("values")) { - filename = - (filename.indexOf('.') != -1 ? filename.substring(0, filename.indexOf('.')) : filename) - + ".arsc"; + compile(directoryName, filename, results, compiledResourcesOut, file, false); + if (qualifiers.asFolderType().equals(ResourceFolderType.VALUES)) { + extractAttributes(directoryName, filename, results); + + if (qualifiers.containDefaultLocale()) { + // aapt2 only generates pseudo locales for the default locale. + generatedResourcesOut.ifPresent( + out -> compile(directoryName, filename, results, out, file, true)); + } + } + return results; + } - 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())); - - // Iterate through the XML until we find a start element. - // This should mimic xmlEventReader.nextTag() except that it also skips DTD elements. - StartElement rootElement = null; - while (xmlEventReader.hasNext()) { - XMLEvent event = xmlEventReader.nextEvent(); - if (event.getEventType() != XMLStreamConstants.COMMENT - && event.getEventType() != XMLStreamConstants.DTD - && event.getEventType() != XMLStreamConstants.PROCESSING_INSTRUCTION - && event.getEventType() != XMLStreamConstants.SPACE - && event.getEventType() != XMLStreamConstants.START_DOCUMENT) { - - // If the event should not be skipped, try parsing it as a start element here. - // If the event is not a start element, an appropriate exception will be thrown. - rootElement = event.asStartElement(); - break; - } - } + static String interpolateAapt2Filename(Qualifiers qualifiers, String filename) { + return qualifiers.asFolderType().equals(ResourceFolderType.VALUES) + ? (filename.indexOf('.') != -1 ? filename.substring(0, filename.indexOf('.')) : filename) + + ".arsc" + : filename; + } + + private void compile( + String type, + String filename, + List results, + Path compileOutRoot, + Path file, + boolean generatePseudoLocale) { + try { + Path destination = CompilingVisitor.destinationPath(file, compileOutRoot); + final Path compiledResourcePath = destination.resolve(type + "_" + filename + ".flat"); + + logger.fine( + new AaptCommandBuilder(aapt2) + .forBuildToolsVersion(buildToolsVersion) + .forVariantType(VariantType.LIBRARY) + .add("compile") + .add("-v") + .add("--legacy") + .when(generatePseudoLocale) + .thenAdd("--pseudo-localize") + .add("-o", destination.toString()) + .add(file.toString()) + .execute("Compiling " + file)); + + Preconditions.checkArgument( + Files.exists(compiledResourcePath), + "%s does not exists after aapt2 ran.", + compiledResourcePath); + results.add(compiledResourcePath); + } catch (IOException e) { + throw new CompileError(e); + } + } - if (rootElement == null) { - throw new Exception("No start element found in resource XML file: " + file.toString()); + private void extractAttributes(String type, String filename, List results) + throws Exception { + 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())); + + // Iterate through the XML until we find a start element. + // This should mimic xmlEventReader.nextTag() except that it also skips DTD elements. + StartElement rootElement = null; + while (xmlEventReader.hasNext()) { + XMLEvent event = xmlEventReader.nextEvent(); + if (event.getEventType() != XMLStreamConstants.COMMENT + && event.getEventType() != XMLStreamConstants.DTD + && event.getEventType() != XMLStreamConstants.PROCESSING_INSTRUCTION + && event.getEventType() != XMLStreamConstants.SPACE + && event.getEventType() != XMLStreamConstants.START_DOCUMENT) { + + // If the event should not be skipped, try parsing it as a start element here. + // If the event is not a start element, an appropriate exception will be thrown. + rootElement = event.asStartElement(); + break; } + } + + if (rootElement == null) { + throw new Exception("No start element found in resource XML file: " + file.toString()); + } - Iterator attributeIterator = - XmlResourceValues.iterateAttributesFrom(rootElement); + Iterator attributeIterator = + XmlResourceValues.iterateAttributesFrom(rootElement); - if (attributeIterator.hasNext()) { - results.add(createAttributesProto(type, filename, attributeIterator)); - } - } finally { - if (xmlEventReader != null) { - xmlEventReader.close(); - } + if (attributeIterator.hasNext()) { + results.add(createAttributesProto(type, filename, attributeIterator)); + } + } finally { + if (xmlEventReader != null) { + xmlEventReader.close(); } } - - final Path compiledResourcePath = - compiledResourcesOut.resolve(type + "_" + filename + ".flat"); - Preconditions.checkArgument( - Files.exists(compiledResourcePath), - "%s does not exists after aapt2 ran.", - compiledResourcePath); - results.add(compiledResourcePath); - return results; } private Path createAttributesProto( @@ -194,7 +225,8 @@ public class ResourceCompiler { Namespaces namespaces = Namespaces.from(qName); String attributeName = namespaceUri.isEmpty() ? localPart : prefix + ":" + localPart; - Factory fqnFactory = Factory.fromDirectoryName(type); + 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 = @@ -218,23 +250,23 @@ public class ResourceCompiler { private static class CompilingVisitor extends SimpleFileVisitor { private final ListeningExecutorService executorService; - private final Path compiledResources; - private final Map pathToProcessed = new LinkedHashMap<>(); + private final Path compiledResourcesOut; + private final Set pathToProcessed = new LinkedHashSet<>(); private final Path aapt2; private final Revision buildToolsVersion; - private final boolean generatePseudoLocale; + private final Optional generatedResourcesOut; public CompilingVisitor( ListeningExecutorService executorService, - Path compiledResources, + Path compiledResourcesOut, Path aapt2, Revision buildToolsVersion, - boolean generatePseudoLocale) { + Optional generatedResourcesOut) { this.executorService = executorService; - this.compiledResources = compiledResources; + this.compiledResourcesOut = compiledResourcesOut; this.aapt2 = aapt2; this.buildToolsVersion = buildToolsVersion; - this.generatePseudoLocale = generatePseudoLocale; + this.generatedResourcesOut = generatedResourcesOut; } static final Pattern REGION_PATTERN = @@ -244,14 +276,7 @@ public class ResourceCompiler { public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { // Ignore directories and "hidden" files that start with . if (!Files.isDirectory(file) && !file.getFileName().toString().startsWith(".")) { - // Creates a relative output path based on the input path under the - // compiledResources path. - Path outputDirectory = - Files.createDirectories( - compiledResources.resolve( - (file.isAbsolute() ? file.getRoot().relativize(file) : file) - .getParent() - .getParent())); + Path outputDirectory = destinationPath(file, compiledResourcesOut); Path maybeFixedPath = file.getParent() @@ -263,18 +288,17 @@ public class ResourceCompiler { if (!Files.exists(maybeFixedPath)) { logger.severe( String.format( - "The locale identifier in %s is not supported by aapt2. Converting to %s. " + "The locale identifier in %s is not supported by aapt2. Converting to %s. " + "This will be an error in the future.", file, maybeFixedPath)); // Only use the processed path if doesn't exist. If it exists, there are is already // resources for that region. - pathToProcessed.put( + pathToProcessed.add( Files.copy( file, Files.createDirectories( outputDirectory.resolve(maybeFixedPath.getParent().getFileName())) - .resolve(file.getFileName())), - outputDirectory); + .resolve(file.getFileName()))); } else { logger.severe( String.format( @@ -284,12 +308,26 @@ public class ResourceCompiler { file, maybeFixedPath)); } } else { - pathToProcessed.put(file, outputDirectory); + pathToProcessed.add(file); } } return super.visitFile(file, attrs); } + public static Path destinationPath(Path file, Path compiledResourcesOut) { + // Creates a relative output path based on the input path under the + // compiledResources path. + try { + return Files.createDirectories( + compiledResourcesOut.resolve( + (file.isAbsolute() ? file.getRoot().relativize(file) : file) + .getParent() + .getParent())); + } catch (IOException e) { + throw new CompileError(e); + } + } + /** Aapt cannot interpret these regions so we rename them to get them to compile. */ static Path maybeFixRegion(Path p) { Matcher matcher = REGION_PATTERN.matcher(p.toString()); @@ -303,16 +341,25 @@ public class ResourceCompiler { } List getCompiledArtifacts() { + generatedResourcesOut.ifPresent( + out -> { + try { + Files.createDirectories(out); + } catch (IOException e) { + throw new CompileError(e); + } + }); + List>> tasks = new ArrayList<>(); - for (Entry entry : pathToProcessed.entrySet()) { + for (Path uncompiled : pathToProcessed) { tasks.add( executorService.submit( new CompileTask( - entry.getKey(), - entry.getValue(), + uncompiled, + compiledResourcesOut, aapt2, buildToolsVersion, - generatePseudoLocale))); + generatedResourcesOut))); } ImmutableList.Builder builder = ImmutableList.builder(); @@ -338,9 +385,16 @@ public class ResourceCompiler { Path aapt2, Revision buildToolsVersion, boolean generatePseudoLocale) { + return new ResourceCompiler( new CompilingVisitor( - executorService, compiledResources, aapt2, buildToolsVersion, generatePseudoLocale)); + executorService, + compiledResources, + aapt2, + buildToolsVersion, + generatePseudoLocale + ? Optional.of(compiledResources.resolve("generated")) + : Optional.empty())); } private ResourceCompiler(CompilingVisitor compilingVisitor) { -- cgit v1.2.3