aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google
diff options
context:
space:
mode:
authorGravatar Googler <noreply@google.com>2016-07-01 14:25:13 +0000
committerGravatar Lukacs Berki <lberki@google.com>2016-07-04 07:17:51 +0000
commit6382bfd45ee39f97809a185216a183686e1a8f1b (patch)
tree29c106fd8e7d458e6474e27240bb799a01dfdc66 /src/tools/android/java/com/google
parentf47fd83ad044bb786883662380e4cf1f53c3cc63 (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')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidDataMerger.java10
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/DataValueFileWithIds.java85
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java52
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/XmlResourceValues.java2
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();
}