diff options
author | Googler <noreply@google.com> | 2016-07-01 14:25:13 +0000 |
---|---|---|
committer | Lukacs Berki <lberki@google.com> | 2016-07-04 07:17:51 +0000 |
commit | 6382bfd45ee39f97809a185216a183686e1a8f1b (patch) | |
tree | 29c106fd8e7d458e6474e27240bb799a01dfdc66 /src/tools/android/java/com/google | |
parent | f47fd83ad044bb786883662380e4cf1f53c3cc63 (diff) |
Parse layout/menu/drawable, etc XML files for R.id
To be used by a later CL where we use this more
complete picture of all the resource items to
generate our own R.java/class without invoking
AAPT (and split out AAPT to a separate action
that is off the java builder critical path).
Technically aapt creates R.id.foo for attr
<enum name="foo" ... /> and flag as well, but
I haven't seen code that actually relies on that.
Currently off-by-default (only exercised by tests).
--
MOS_MIGRATED_REVID=126407838
Diffstat (limited to 'src/tools/android/java/com/google')
4 files changed, 143 insertions, 6 deletions
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 32c9a23155..3f74e81d9a 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 @@ -48,7 +48,7 @@ public class AndroidDataMerger { private static final Logger logger = Logger.getLogger(AndroidDataMerger.class.getCanonicalName()); - private final class ParseDependencyDataTask implements Callable<Boolean> { + private static final class ParseDependencyDataTask implements Callable<Boolean> { private final AndroidDataSerializer serializer; @@ -157,6 +157,7 @@ public class AndroidDataMerger { private final SourceChecker deDuplicator; private final ListeningExecutorService executorService; + private boolean parseIds; /** Creates a merger with no path deduplication and a default {@link ExecutorService}. */ public static AndroidDataMerger createWithDefaults() { @@ -186,6 +187,10 @@ public class AndroidDataMerger { this.executorService = executorService; } + void enableIdParsing() { + this.parseIds = true; + } + /** * Merges a list of {@link DependencyAndroidData} with a {@link UnvalidatedAndroidData}. * @@ -299,7 +304,8 @@ public class AndroidDataMerger { try { // Extract the primary resources. - ParsedAndroidData parsedPrimary = ParsedAndroidData.from(primaryData); + ParsedAndroidData parsedPrimary = parseIds ? ParsedAndroidData.parseWithIds(primaryData) + : ParsedAndroidData.from(primaryData); // Create the builders for the final parsed data. final ParsedAndroidData.Builder primaryBuilder = ParsedAndroidData.Builder.newBuilder(); diff --git a/src/tools/android/java/com/google/devtools/build/android/DataValueFileWithIds.java b/src/tools/android/java/com/google/devtools/build/android/DataValueFileWithIds.java new file mode 100644 index 0000000000..372e9dd989 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/DataValueFileWithIds.java @@ -0,0 +1,85 @@ +// Copyright 2016 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.collect.ImmutableSet; +import com.google.devtools.build.android.ParsedAndroidData.KeyValueConsumer; +import com.google.devtools.build.android.xml.IdXmlResourceValue; + +import com.android.SdkConstants; +import com.android.resources.ResourceType; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Iterator; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; + +/** + * Parses an XML file for "@+id/foo" and creates {@link IdXmlResourceValue} from parsed IDs. + * This can be a layout file, menu, drawable, etc. + */ +public class DataValueFileWithIds { + + public static void parse( + XMLInputFactory xmlInputFactory, + Path source, + FullyQualifiedName fileKey, + FullyQualifiedName.Factory fqnFactory, + KeyValueConsumer<DataKey, DataResource> overwritingConsumer, + KeyValueConsumer<DataKey, DataResource> combiningConsumer) + throws IOException, XMLStreamException { + ImmutableSet.Builder<String> newIds = ImmutableSet.builder(); + try (BufferedInputStream inStream = new BufferedInputStream(Files.newInputStream(source))) { + XMLEventReader eventReader = + xmlInputFactory.createXMLEventReader(inStream, StandardCharsets.UTF_8.toString()); + // Go through every start tag and look at the values of all attributes (the attribute does + // not need to be android:id, e.g., android:layout_above="@+id/UpcomingView"). + // If the any attribute's value begins with @+id/, then track it, since aapt seems to be + // forgiving and allow even non-android namespaced attributes to define a new ID. + while (eventReader.hasNext()) { + XMLEvent event = eventReader.nextEvent(); + if (event.isStartElement()) { + StartElement start = event.asStartElement(); + Iterator<Attribute> attributes = XmlResourceValues.iterateAttributesFrom(start); + while (attributes.hasNext()) { + Attribute attribute = attributes.next(); + String value = attribute.getValue(); + if (value.startsWith(SdkConstants.NEW_ID_PREFIX)) { + String idName = value.substring(SdkConstants.NEW_ID_PREFIX.length()); + newIds.add(idName); + } + } + } + } + eventReader.close(); + } + ImmutableSet<String> idResources = newIds.build(); + overwritingConsumer.consume(fileKey, DataValueFile.of(source)); + for (String id : idResources) { + combiningConsumer.consume( + fqnFactory.create(ResourceType.ID, id), + DataResourceXml.of(source, IdXmlResourceValue.of())); + } + } + +} diff --git a/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java index 15c551f6a5..abe1dfd395 100644 --- a/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java +++ b/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java @@ -20,7 +20,9 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.devtools.build.android.xml.StyleableXmlResourceValue; +import com.android.SdkConstants; import com.android.ide.common.res2.MergingException; +import com.android.resources.FolderTypeRelationship; import com.android.resources.ResourceFolderType; import java.io.IOException; @@ -31,6 +33,7 @@ import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; +import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -63,6 +66,7 @@ public class ParsedAndroidData { private final Map<DataKey, DataAsset> assets; private final Set<MergeConflict> conflicts; private final List<Exception> errors = new ArrayList<>(); + private boolean parseIds; public Builder( Map<DataKey, DataResource> overwritingResources, @@ -83,6 +87,11 @@ public class ParsedAndroidData { return new Builder(overwritingResources, combiningResources, assets, conflicts); } + Builder enableIdParsing() { + this.parseIds = true; + return this; + } + private void checkForErrors() throws MergingException { if (!errors.isEmpty()) { MergingException mergingException = new MergingException("Parse Error(s)"); @@ -121,7 +130,8 @@ public class ParsedAndroidData { return new ResourceFileVisitor( new OverwritableConsumer<>(overwritingResources, conflicts), new CombiningConsumer(combiningResources), - errors); + errors, + parseIds); } AssetFileVisitor assetVisitorFor(Path path) { @@ -248,14 +258,28 @@ public class ParsedAndroidData { private ResourceFolderType folderType; private FullyQualifiedName.Factory fqnFactory; private final XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory(); + private final boolean parseIds; + + /** + * Resource folders with XML files that may contain "@+id". + * See android_ide_common's {@link FolderTypeRelationship}. + */ + private static final EnumSet<ResourceFolderType> ID_PROVIDING_RESOURCE_TYPES = EnumSet.of( + ResourceFolderType.DRAWABLE, + ResourceFolderType.LAYOUT, + ResourceFolderType.MENU, + ResourceFolderType.TRANSITION, + ResourceFolderType.XML); ResourceFileVisitor( KeyValueConsumer<DataKey, DataResource> overwritingConsumer, KeyValueConsumer<DataKey, DataResource> combiningResources, - List<Exception> errors) { + List<Exception> errors, + boolean parseIds) { this.overwritingConsumer = overwritingConsumer; this.combiningResources = combiningResources; this.errors = errors; + this.parseIds = parseIds; } private static String deriveRawFullyQualifiedName(Path path) { @@ -303,7 +327,14 @@ public class ParsedAndroidData { } else if (folderType != null) { String rawFqn = deriveRawFullyQualifiedName(path); FullyQualifiedName key = fqnFactory.parse(rawFqn); - overwritingConsumer.consume(key, DataValueFile.of(path)); + if (parseIds + && ID_PROVIDING_RESOURCE_TYPES.contains(folderType) + && path.getFileName().toString().endsWith(SdkConstants.DOT_XML)) { + DataValueFileWithIds.parse( + xmlInputFactory, path, key, fqnFactory, overwritingConsumer, combiningResources); + } else { + overwritingConsumer.consume(key, DataValueFile.of(path)); + } } } } catch (IllegalArgumentException | XMLStreamException e) { @@ -361,6 +392,21 @@ public class ParsedAndroidData { return pathWalker.createParsedAndroidData(); } + /** + * Parses resource symbols including "@+id/resourceName" (optional for now). + * + * @see ParsedAndroidData#from(UnvalidatedAndroidData) + */ + @VisibleForTesting + static ParsedAndroidData parseWithIds(UnvalidatedAndroidData primary) + throws IOException, MergingException { + Builder builder = Builder.newBuilder().enableIdParsing(); + final ParsedAndroidDataBuildingPathWalker pathWalker = + ParsedAndroidDataBuildingPathWalker.create(builder); + primary.walk(pathWalker); + return pathWalker.createParsedAndroidData(); + } + private final ImmutableSet<MergeConflict> conflicts; private final ImmutableMap<DataKey, DataResource> overwritingResources; private final ImmutableMap<DataKey, DataResource> combiningResources; diff --git a/src/tools/android/java/com/google/devtools/build/android/XmlResourceValues.java b/src/tools/android/java/com/google/devtools/build/android/XmlResourceValues.java index 4321b9ad96..5876966239 100644 --- a/src/tools/android/java/com/google/devtools/build/android/XmlResourceValues.java +++ b/src/tools/android/java/com/google/devtools/build/android/XmlResourceValues.java @@ -273,7 +273,7 @@ public class XmlResourceValues { "cast", "unchecked" }) // The interface returns Iterator, force casting based on documentation. - private static Iterator<Attribute> iterateAttributesFrom(StartElement start) { + static Iterator<Attribute> iterateAttributesFrom(StartElement start) { return (Iterator<Attribute>) start.getAttributes(); } |