Augment ResourceProcessorBusybox with capability to auto-resolve unambiguous attr resource conflicts.
When a merge conflict has been detected between a strong attr resource and a weak attr resource which doesn't specify format, this is acceptable. Select the strong attr and keep the conflict from being reported as a conflict needing manual resolution.
RELNOTES: Android resource conflicts will no longer be reported between a strong attr resource and a weak attr resource, if the weak attr does not have format specified.
PiperOrigin-RevId: 243344206
diff --git a/src/test/java/com/google/devtools/build/android/AttributeResourcesAndroidDataMergerTest.java b/src/test/java/com/google/devtools/build/android/AttributeResourcesAndroidDataMergerTest.java
new file mode 100644
index 0000000..a306e06
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/AttributeResourcesAndroidDataMergerTest.java
@@ -0,0 +1,536 @@
+// Copyright 2019 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 static com.google.common.truth.Truth.assertAbout;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.android.ParsedAndroidDataBuilder.xml;
+
+import com.android.aapt.Resources.Attribute;
+import com.android.aapt.Resources.CompoundValue;
+import com.android.aapt.Resources.Value;
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.jimfs.Jimfs;
+import com.google.common.truth.Subject;
+import com.google.devtools.build.android.xml.AttrXmlResourceValue;
+import com.google.devtools.build.android.xml.AttrXmlResourceValue.ReferenceResourceXmlAttrValue;
+import com.google.devtools.build.android.xml.AttrXmlResourceValue.StringResourceXmlAttrValue;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Parameterized tests for {@link AndroidDataMerger} focusing on merging attribute resoutces. */
+@RunWith(Parameterized.class)
+public final class AttributeResourcesAndroidDataMergerTest {
+
+ /** For test data readability, represent strength as a 2-state enum rather than a boolean. */
+ enum Strength {
+ STRONG,
+ WEAK
+ }
+
+ @Parameters(name = "{index}: {0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(
+ new Object[][] {
+ {
+ TestParameters.builder()
+ .set1(Strength.STRONG, 1)
+ .set2(Strength.STRONG, 0xFFFF)
+ .setExpectedMergeConflict(
+ ctx ->
+ MergeConflict.of(
+ ctx.fqnFactory.parse("attr/ambiguousName"),
+ DataResourceXml.createWithNoNamespace(
+ ctx.transitiveRoot1.resolve("res/values/attrs.xml"),
+ AttrXmlResourceValue.fromFormatEntries(
+ ReferenceResourceXmlAttrValue.asEntry())),
+ DataResourceXml.createWithNoNamespace(
+ ctx.transitiveRoot2.resolve("res/values/attrs.xml"),
+ AttrXmlResourceValue.fromFormatEntries())))
+ .build()
+ },
+ {
+ TestParameters.builder()
+ .set1(Strength.WEAK, 1)
+ .set2(Strength.WEAK, 0xFFFF)
+ .setExpectedMergeConflict(
+ ctx ->
+ MergeConflict.of(
+ ctx.fqnFactory.parse("attr/ambiguousName"),
+ DataResourceXml.createWithNoNamespace(
+ ctx.transitiveRoot1.resolve("res/values/attrs.xml"),
+ AttrXmlResourceValue.weakFromFormatEntries(
+ ReferenceResourceXmlAttrValue.asEntry())),
+ DataResourceXml.createWithNoNamespace(
+ ctx.transitiveRoot2.resolve("res/values/attrs.xml"),
+ AttrXmlResourceValue.weakFromFormatEntries())))
+ .build()
+ },
+ {
+ TestParameters.builder()
+ .set1(Strength.STRONG, 1)
+ .set2(Strength.STRONG, 1)
+ .setExpectedMergedAndroidData(
+ ctx ->
+ UnwrittenMergedAndroidData.of(
+ ctx.primary.getManifest(),
+ ParsedAndroidDataBuilder.empty(),
+ ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
+ .overwritable(
+ xml("attr/ambiguousName")
+ .root(ctx.primaryRoot)
+ .source(ctx.transitiveAttr2.overwrite(ctx.transitiveAttr1))
+ .value(
+ AttrXmlResourceValue.fromFormatEntries(
+ ReferenceResourceXmlAttrValue.asEntry())))
+ .build()))
+ .build()
+ },
+ {
+ TestParameters.builder()
+ .set1(Strength.WEAK, 1)
+ .set2(Strength.WEAK, 1)
+ .setExpectedMergedAndroidData(
+ ctx ->
+ UnwrittenMergedAndroidData.of(
+ ctx.primary.getManifest(),
+ ParsedAndroidDataBuilder.empty(),
+ ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
+ .overwritable(
+ xml("attr/ambiguousName")
+ .root(ctx.primaryRoot)
+ .source(ctx.transitiveAttr2.overwrite(ctx.transitiveAttr1))
+ .value(
+ AttrXmlResourceValue.weakFromFormatEntries(
+ ReferenceResourceXmlAttrValue.asEntry())))
+ .build()))
+ .build()
+ },
+ {
+ TestParameters.builder()
+ .set1(Strength.STRONG, 1)
+ .set2(Strength.WEAK, 0xFFFF)
+ .setExpectedMergedAndroidData(
+ ctx ->
+ UnwrittenMergedAndroidData.of(
+ ctx.primary.getManifest(),
+ ParsedAndroidDataBuilder.empty(),
+ ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
+ .overwritable(
+ xml("attr/ambiguousName")
+ .root(ctx.primaryRoot)
+ .source(ctx.transitiveAttr1.overwrite(ctx.transitiveAttr2))
+ .value(
+ AttrXmlResourceValue.fromFormatEntries(
+ ReferenceResourceXmlAttrValue.asEntry())))
+ .build()))
+ .build()
+ },
+ {
+ TestParameters.builder()
+ .set1(Strength.WEAK, 0xFFFF)
+ .set2(Strength.STRONG, 2)
+ .setExpectedMergedAndroidData(
+ ctx ->
+ UnwrittenMergedAndroidData.of(
+ ctx.primary.getManifest(),
+ ParsedAndroidDataBuilder.empty(),
+ ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
+ .overwritable(
+ xml("attr/ambiguousName")
+ .root(ctx.primaryRoot)
+ .source(ctx.transitiveAttr2.overwrite(ctx.transitiveAttr1))
+ .value(
+ AttrXmlResourceValue.fromFormatEntries(
+ StringResourceXmlAttrValue.asEntry())))
+ .build()))
+ .build()
+ },
+ {
+ TestParameters.builder()
+ .set1(Strength.STRONG, 1)
+ .set2(Strength.WEAK, 2)
+ .setExpectedMergeConflict(
+ ctx ->
+ MergeConflict.of(
+ ctx.fqnFactory.parse("attr/ambiguousName"),
+ DataResourceXml.createWithNoNamespace(
+ ctx.transitiveRoot1.resolve("res/values/attrs.xml"),
+ AttrXmlResourceValue.fromFormatEntries(
+ ReferenceResourceXmlAttrValue.asEntry())),
+ DataResourceXml.createWithNoNamespace(
+ ctx.transitiveRoot2.resolve("res/values/attrs.xml"),
+ AttrXmlResourceValue.weakFromFormatEntries(
+ StringResourceXmlAttrValue.asEntry()))))
+ .build()
+ },
+ {
+ TestParameters.builder()
+ .set1(Strength.WEAK, 1)
+ .set2(Strength.STRONG, 2)
+ .setExpectedMergeConflict(
+ ctx ->
+ MergeConflict.of(
+ ctx.fqnFactory.parse("attr/ambiguousName"),
+ DataResourceXml.createWithNoNamespace(
+ ctx.transitiveRoot1.resolve("res/values/attrs.xml"),
+ AttrXmlResourceValue.weakFromFormatEntries(
+ ReferenceResourceXmlAttrValue.asEntry())),
+ DataResourceXml.createWithNoNamespace(
+ ctx.transitiveRoot2.resolve("res/values/attrs.xml"),
+ AttrXmlResourceValue.fromFormatEntries(
+ StringResourceXmlAttrValue.asEntry()))))
+ .build()
+ },
+ {
+ TestParameters.builder()
+ .set1(Strength.STRONG, 1)
+ .set2(Strength.WEAK, 1)
+ .setExpectedMergedAndroidData(
+ ctx ->
+ UnwrittenMergedAndroidData.of(
+ ctx.primary.getManifest(),
+ ParsedAndroidDataBuilder.empty(),
+ ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
+ .overwritable(
+ xml("attr/ambiguousName")
+ .root(ctx.primaryRoot)
+ .source(ctx.transitiveAttr1.overwrite(ctx.transitiveAttr2))
+ .value(
+ AttrXmlResourceValue.fromFormatEntries(
+ ReferenceResourceXmlAttrValue.asEntry())))
+ .build()))
+ .build()
+ },
+ {
+ TestParameters.builder()
+ .set1(Strength.WEAK, 1)
+ .set2(Strength.STRONG, 1)
+ .setExpectedMergedAndroidData(
+ ctx ->
+ UnwrittenMergedAndroidData.of(
+ ctx.primary.getManifest(),
+ ParsedAndroidDataBuilder.empty(),
+ ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
+ .overwritable(
+ xml("attr/ambiguousName")
+ .root(ctx.primaryRoot)
+ .source(ctx.transitiveAttr2.overwrite(ctx.transitiveAttr1))
+ .value(
+ AttrXmlResourceValue.fromFormatEntries(
+ ReferenceResourceXmlAttrValue.asEntry())))
+ .build()))
+ .build()
+ },
+ {
+ TestParameters.builder()
+ .set1(Strength.STRONG, 0xFFFF)
+ .set2(Strength.STRONG, 0xFFFF)
+ .setExpectedMergedAndroidData(
+ ctx ->
+ UnwrittenMergedAndroidData.of(
+ ctx.primary.getManifest(),
+ ParsedAndroidDataBuilder.empty(),
+ ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
+ .overwritable(
+ xml("attr/ambiguousName")
+ .root(ctx.primaryRoot)
+ .source(ctx.transitiveAttr2.overwrite(ctx.transitiveAttr1))
+ .value(AttrXmlResourceValue.fromFormatEntries()))
+ .build()))
+ .build()
+ },
+ {
+ TestParameters.builder()
+ .set1(Strength.WEAK, 0xFFFF)
+ .set2(Strength.WEAK, 0xFFFF)
+ .setExpectedMergedAndroidData(
+ ctx ->
+ UnwrittenMergedAndroidData.of(
+ ctx.primary.getManifest(),
+ ParsedAndroidDataBuilder.empty(),
+ ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
+ .overwritable(
+ xml("attr/ambiguousName")
+ .root(ctx.primaryRoot)
+ .source(ctx.transitiveAttr2.overwrite(ctx.transitiveAttr1))
+ .value(AttrXmlResourceValue.weakFromFormatEntries()))
+ .build()))
+ .build()
+ },
+ {
+ TestParameters.builder()
+ .set1(Strength.STRONG, 0xFFFF)
+ .set2(Strength.WEAK, 0xFFFF)
+ .setExpectedMergedAndroidData(
+ ctx ->
+ UnwrittenMergedAndroidData.of(
+ ctx.primary.getManifest(),
+ ParsedAndroidDataBuilder.empty(),
+ ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
+ .overwritable(
+ xml("attr/ambiguousName")
+ .root(ctx.primaryRoot)
+ .source(ctx.transitiveAttr1.overwrite(ctx.transitiveAttr2))
+ .value(AttrXmlResourceValue.fromFormatEntries()))
+ .build()))
+ .build()
+ },
+ {
+ TestParameters.builder()
+ .set1(Strength.WEAK, 0xFFFF)
+ .set2(Strength.STRONG, 0xFFFF)
+ .setExpectedMergedAndroidData(
+ ctx ->
+ UnwrittenMergedAndroidData.of(
+ ctx.primary.getManifest(),
+ ParsedAndroidDataBuilder.empty(),
+ ParsedAndroidDataBuilder.buildOn(ctx.fqnFactory)
+ .overwritable(
+ xml("attr/ambiguousName")
+ .root(ctx.primaryRoot)
+ .source(ctx.transitiveAttr2.overwrite(ctx.transitiveAttr1))
+ .value(AttrXmlResourceValue.fromFormatEntries()))
+ .build()))
+ .build()
+ },
+ {
+ TestParameters.builder()
+ .set1(Strength.STRONG, 0xFFFF)
+ .set2(Strength.WEAK, 1)
+ .setExpectedMergeConflict(
+ ctx ->
+ MergeConflict.of(
+ ctx.fqnFactory.parse("attr/ambiguousName"),
+ DataResourceXml.createWithNoNamespace(
+ ctx.transitiveRoot1.resolve("res/values/attrs.xml"),
+ AttrXmlResourceValue.fromFormatEntries()),
+ DataResourceXml.createWithNoNamespace(
+ ctx.transitiveRoot2.resolve("res/values/attrs.xml"),
+ AttrXmlResourceValue.weakFromFormatEntries(
+ ReferenceResourceXmlAttrValue.asEntry()))))
+ .build()
+ },
+ {
+ TestParameters.builder()
+ .set1(Strength.WEAK, 1)
+ .set2(Strength.STRONG, 0xFFFF)
+ .setExpectedMergeConflict(
+ ctx ->
+ MergeConflict.of(
+ ctx.fqnFactory.parse("attr/ambiguousName"),
+ DataResourceXml.createWithNoNamespace(
+ ctx.transitiveRoot1.resolve("res/values/attrs.xml"),
+ AttrXmlResourceValue.weakFromFormatEntries(
+ ReferenceResourceXmlAttrValue.asEntry())),
+ DataResourceXml.createWithNoNamespace(
+ ctx.transitiveRoot2.resolve("res/values/attrs.xml"),
+ AttrXmlResourceValue.fromFormatEntries())))
+ .build()
+ }
+ });
+ }
+
+ @Parameter public TestParameters testParameters;
+
+ private FullyQualifiedName.Factory fqnFactory;
+ private TestLoggingHandler loggingHandler;
+ private Logger mergerLogger;
+ private Path primaryRoot;
+ private Path transitiveRoot1;
+ private Path transitiveRoot2;
+ private DataSource transitiveAttr1;
+ private DataSource transitiveAttr2;
+ private UnvalidatedAndroidData primary;
+
+ @Before
+ public void setUp() throws Exception {
+ FileSystem fileSystem = Jimfs.newFileSystem();
+ fqnFactory = FullyQualifiedName.Factory.from(ImmutableList.of());
+ mergerLogger = Logger.getLogger(AndroidDataMerger.class.getCanonicalName());
+ loggingHandler = new TestLoggingHandler();
+ mergerLogger.addHandler(loggingHandler);
+ primaryRoot = fileSystem.getPath("primary");
+ transitiveRoot1 = fileSystem.getPath("transitive1");
+ transitiveRoot2 = fileSystem.getPath("transitive2");
+ transitiveAttr1 = DataSource.of(transitiveRoot1.resolve("res").resolve("values/attrs.xml"));
+ transitiveAttr2 = DataSource.of(transitiveRoot2.resolve("res").resolve("values/attrs.xml"));
+ primary =
+ AndroidDataBuilder.of(primaryRoot)
+ .createManifest("AndroidManifest.xml", "com.google.mergetest")
+ .buildUnvalidated();
+ }
+
+ @After
+ public void removeLoggingHandler() {
+ mergerLogger.removeHandler(loggingHandler);
+ }
+
+ @Test
+ public void test() throws Exception {
+ ParsedAndroidData transitiveDependency =
+ ParsedAndroidDataBuilder.buildOn(fqnFactory)
+ .overwritable(
+ xml("attr/ambiguousName")
+ .root(transitiveRoot1)
+ .source("values/attrs.xml")
+ .value(
+ AttrXmlResourceValue.from(
+ Value.newBuilder()
+ .setCompoundValue(
+ CompoundValue.newBuilder()
+ .setAttr(
+ Attribute.newBuilder()
+ .setFormatFlags(testParameters.formatFlags1())))
+ .setWeak(testParameters.strength1() == Strength.WEAK)
+ .build())))
+ .overwritable(
+ xml("attr/ambiguousName")
+ .root(transitiveRoot2)
+ .source("values/attrs.xml")
+ .value(
+ AttrXmlResourceValue.from(
+ Value.newBuilder()
+ .setCompoundValue(
+ CompoundValue.newBuilder()
+ .setAttr(
+ Attribute.newBuilder()
+ .setFormatFlags(testParameters.formatFlags2())))
+ .setWeak(testParameters.strength2() == Strength.WEAK)
+ .build())))
+ .build();
+
+ ParsedAndroidData directDependency = ParsedAndroidDataBuilder.empty();
+
+ AndroidDataMerger merger = AndroidDataMerger.createWithDefaults();
+
+ UnwrittenMergedAndroidData data =
+ merger.merge(transitiveDependency, directDependency, primary, false, false);
+
+ if (testParameters.expectedMergedAndroidData().isPresent()) {
+ assertAbout(unwrittenMergedAndroidData)
+ .that(data)
+ .isEqualTo(testParameters.expectedMergedAndroidData().get().apply(this));
+ }
+
+ if (testParameters.expectedMergeConflict().isPresent()) {
+ assertThat(loggingHandler.warnings)
+ .containsExactly(
+ testParameters.expectedMergeConflict().get().apply(this).toConflictMessage());
+ } else {
+ assertThat(loggingHandler.warnings).isEmpty();
+ }
+ }
+
+ private final Subject.Factory<UnwrittenMergedAndroidDataSubject, UnwrittenMergedAndroidData>
+ unwrittenMergedAndroidData = UnwrittenMergedAndroidDataSubject::new;
+
+ private static final class TestLoggingHandler extends Handler {
+ public final List<String> warnings = new ArrayList<>();
+
+ @Override
+ public void publish(LogRecord record) {
+ if (record.getLevel().equals(Level.WARNING)) {
+ warnings.add(record.getMessage());
+ }
+ }
+
+ @Override
+ public void flush() {}
+
+ @Override
+ public void close() {}
+ }
+
+ @AutoValue
+ abstract static class TestParameters {
+ abstract Strength strength1();
+
+ abstract int formatFlags1();
+
+ abstract Strength strength2();
+
+ abstract int formatFlags2();
+
+ abstract Optional<Function<AttributeResourcesAndroidDataMergerTest, UnwrittenMergedAndroidData>>
+ expectedMergedAndroidData();
+
+ abstract Optional<Function<AttributeResourcesAndroidDataMergerTest, MergeConflict>>
+ expectedMergeConflict();
+
+ @Override
+ public final String toString() {
+ return Joiner.on(", ")
+ .join(
+ strength1(),
+ formatFlags1(),
+ strength2(),
+ formatFlags2(),
+ expectedMergeConflict().isPresent()
+ ? "conflict expected"
+ : "successful merge expected");
+ }
+
+ static Builder builder() {
+ return new AutoValue_AttributeResourcesAndroidDataMergerTest_TestParameters.Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+
+ Builder set1(Strength strength, int formatFlags) {
+ return setStrength1(strength).setFormatFlags1(formatFlags);
+ }
+
+ Builder set2(Strength strength, int formatFlags) {
+ return setStrength2(strength).setFormatFlags2(formatFlags);
+ }
+
+ abstract Builder setStrength1(Strength value);
+
+ abstract Builder setFormatFlags1(int value);
+
+ abstract Builder setStrength2(Strength value);
+
+ abstract Builder setFormatFlags2(int value);
+
+ abstract Builder setExpectedMergedAndroidData(
+ Function<AttributeResourcesAndroidDataMergerTest, UnwrittenMergedAndroidData> value);
+
+ abstract Builder setExpectedMergeConflict(
+ Function<AttributeResourcesAndroidDataMergerTest, MergeConflict> value);
+
+ abstract TestParameters build();
+ }
+ }
+}
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 39aec42..ae0322c 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
@@ -432,4 +432,17 @@
DataResourceXml other = (DataResourceXml) value;
return Objects.equals(xml, other.xml);
}
+
+ @Override
+ public int compareMergePriorityTo(DataValue value) {
+ Preconditions.checkNotNull(value);
+ if (!(value instanceof DataResourceXml)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Can only compare priority with another %s, but was given a %s",
+ DataResourceXml.class.getSimpleName(), value.getClass().getSimpleName()));
+ }
+ DataResourceXml other = (DataResourceXml) value;
+ return xml.compareMergePriorityTo(other.xml);
+ }
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/DataValue.java b/src/tools/android/java/com/google/devtools/build/android/DataValue.java
index de95745..6c327b7 100644
--- a/src/tools/android/java/com/google/devtools/build/android/DataValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/DataValue.java
@@ -42,4 +42,11 @@
* equivalent to another given DataValue object
*/
boolean valueEquals(DataValue value);
+
+ /**
+ * Compares priority in hopes of auto-resolving a merge conflict. Returns 1 if the value
+ * properties are higher in priority than those in another given DataValue object, -1 if lower
+ * priority, 0 if same priority (i.e. in conflict).
+ */
+ int compareMergePriorityTo(DataValue value);
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java b/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java
index 3270e3c..140f343 100644
--- a/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java
+++ b/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java
@@ -134,4 +134,9 @@
public boolean valueEquals(DataValue value) {
return equals(value);
}
+
+ @Override
+ public int compareMergePriorityTo(DataValue value) {
+ return 0;
+ }
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/MergeConflict.java b/src/tools/android/java/com/google/devtools/build/android/MergeConflict.java
index 5a98086..d1077f0 100644
--- a/src/tools/android/java/com/google/devtools/build/android/MergeConflict.java
+++ b/src/tools/android/java/com/google/devtools/build/android/MergeConflict.java
@@ -88,6 +88,7 @@
boolean isValidWith(SourceChecker checker) throws IOException {
return !primary.valueEquals(overwritten)
+ && primary.compareMergePriorityTo(overwritten) == 0
&& !checker.checkEquality(primary.source(), overwritten.source());
}
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 9d7380e..5252767 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
@@ -193,7 +193,11 @@
} else if (value.source().hasOveridden(other.source())) {
target.put(key, value);
} else {
- target.put(key, overwrite(key, value, other));
+ target.put(
+ key,
+ value.compareMergePriorityTo(other) >= 0
+ ? overwrite(key, value, other)
+ : overwrite(key, other, value));
}
} else {
target.put(key, value);
diff --git a/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java
index f24d6b2..49e38c3 100644
--- a/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java
@@ -41,6 +41,12 @@
XmlResourceValue combineWith(XmlResourceValue value);
/**
+ * Returns 1 if the xml value is higher priority than the given value, -1 if lower priority, and 0
+ * if equal priority.
+ */
+ int compareMergePriorityTo(XmlResourceValue value);
+
+ /**
* Queue up writing the resource to the given {@link AndroidResourceClassWriter}. Each resource
* can generate one or more (in the case of styleable) fields and inner classes in the R class.
*
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 fb74fd2..7be5681 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
@@ -210,6 +210,11 @@
}
@Override
+ public int compareMergePriorityTo(XmlResourceValue value) {
+ return 0;
+ }
+
+ @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/AttrXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java
index e7f9e09..6c924de 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
@@ -90,9 +90,11 @@
private static final QName TAG_ENUM = QName.valueOf(ENUM);
private static final QName TAG_FLAG = QName.valueOf("flag");
private final ImmutableMap<String, ResourceXmlAttrValue> formats;
+ private final boolean weak;
- private AttrXmlResourceValue(ImmutableMap<String, ResourceXmlAttrValue> formats) {
+ private AttrXmlResourceValue(ImmutableMap<String, ResourceXmlAttrValue> formats, boolean weak) {
this.formats = formats;
+ this.weak = weak;
}
private static Map<String, String> readSubValues(XMLEventReader reader, QName subTagType)
@@ -119,7 +121,6 @@
}
}
- @VisibleForTesting
private static final class BuilderEntry implements Map.Entry<String, ResourceXmlAttrValue> {
private final String name;
private final ResourceXmlAttrValue value;
@@ -152,6 +153,13 @@
return of(ImmutableMap.copyOf(Arrays.asList(entries)));
}
+ @SafeVarargs
+ @VisibleForTesting
+ public static XmlResourceValue weakFromFormatEntries(
+ Map.Entry<String, ResourceXmlAttrValue>... entries) {
+ return of(ImmutableMap.copyOf(Arrays.asList(entries)), true);
+ }
+
@SuppressWarnings("deprecation")
public static XmlResourceValue from(SerializeFormat.DataValueXml proto)
throws InvalidProtocolBufferException {
@@ -254,7 +262,7 @@
throw new InvalidProtocolBufferException("Unexpected format flags: " + formatFlags);
}
}
- return of(formats.build());
+ return of(formats.build(), proto.getWeak());
}
/** Creates a new {@link AttrXmlResourceValue}. Returns null if there are no formats. */
@@ -322,7 +330,12 @@
}
public static XmlResourceValue of(ImmutableMap<String, ResourceXmlAttrValue> formats) {
- return new AttrXmlResourceValue(formats);
+ return new AttrXmlResourceValue(formats, /* weak= */ false);
+ }
+
+ public static XmlResourceValue of(
+ ImmutableMap<String, ResourceXmlAttrValue> formats, boolean weak) {
+ return new AttrXmlResourceValue(formats, weak);
}
@Override
@@ -334,17 +347,17 @@
return false;
}
AttrXmlResourceValue other = (AttrXmlResourceValue) o;
- return Objects.equals(formats, other.formats);
+ return Objects.equals(formats, other.formats) && weak == other.weak;
}
@Override
public int hashCode() {
- return formats.hashCode();
+ return Objects.hash(formats, weak);
}
@Override
public String toString() {
- return MoreObjects.toStringHelper(this).add("formats", formats).toString();
+ return MoreObjects.toStringHelper(this).add("formats", formats).add("weak", weak).toString();
}
@Override
@@ -424,6 +437,23 @@
throw new IllegalArgumentException(this + " is not a combinable resource.");
}
+ @Override
+ public int compareMergePriorityTo(XmlResourceValue value) {
+ if (!(value instanceof AttrXmlResourceValue)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Can only compare priority with another %s, but was given a %s",
+ AttrXmlResourceValue.class.getSimpleName(), value.getClass().getSimpleName()));
+ }
+ AttrXmlResourceValue that = (AttrXmlResourceValue) value;
+ if (!weak && that.weak && (that.formats.isEmpty() || formats.equals(that.formats))) {
+ return 1;
+ } else if (!that.weak && weak && (formats.isEmpty() || formats.equals(that.formats))) {
+ return -1;
+ }
+ return 0;
+ }
+
/** Represents the xml value for an attr definition. */
@CheckReturnValue
public interface ResourceXmlAttrValue {
@@ -812,6 +842,7 @@
@Override
public String asConflictStringWith(DataSource source) {
return String.format(
- "%s [format(s): %s]", source.asConflictString(), String.join("|", this.formats.keySet()));
+ "%s [format(s): %s], [weak: %s]",
+ source.asConflictString(), String.join("|", this.formats.keySet()), weak);
}
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java
index 7c61feb..0f2a42c 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java
@@ -147,6 +147,11 @@
}
@Override
+ public int compareMergePriorityTo(XmlResourceValue value) {
+ return 0;
+ }
+
+ @Override
public String asConflictStringWith(DataSource source) {
return source.asConflictString();
}
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 f26aa0c..5668d9b 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
@@ -179,7 +179,12 @@
public XmlResourceValue combineWith(XmlResourceValue value) {
throw new IllegalArgumentException(this + " is not a combinable resource.");
}
-
+
+ @Override
+ public int compareMergePriorityTo(XmlResourceValue value) {
+ return 0;
+ }
+
@Override
public String asConflictStringWith(DataSource source) {
return source.asConflictString();
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/PublicXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/PublicXmlResourceValue.java
index 826a9b1..c51636f 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/PublicXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/PublicXmlResourceValue.java
@@ -177,7 +177,12 @@
}
return of(combined);
}
-
+
+ @Override
+ public int compareMergePriorityTo(XmlResourceValue value) {
+ return 0;
+ }
+
@Override
public String asConflictStringWith(DataSource source) {
return source.asConflictString();
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java b/src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java
index 2b88826..6cc08b9 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java
@@ -171,6 +171,11 @@
}
@Override
+ public int compareMergePriorityTo(XmlResourceValue value) {
+ return 0;
+ }
+
+ @Override
public void writeResourceToClass(FullyQualifiedName key, AndroidResourceSymbolSink sink) {
// This is an xml attribute and does not have any java representation.
}
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 16de261..0acfa0e 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
@@ -268,6 +268,11 @@
}
@Override
+ public int compareMergePriorityTo(XmlResourceValue value) {
+ return 0;
+ }
+
+ @Override
public String asConflictStringWith(DataSource source) {
if (value != null) {
return String.format(" %s (with value %s)", source.asConflictString(), value);
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 4ec0454..28fb3a0 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
@@ -194,7 +194,12 @@
public XmlResourceValue combineWith(XmlResourceValue value) {
throw new IllegalArgumentException(this + " is not a combinable resource.");
}
-
+
+ @Override
+ public int compareMergePriorityTo(XmlResourceValue value) {
+ return 0;
+ }
+
@Override
public String asConflictStringWith(DataSource source) {
return source.asConflictString();
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 14f7fb1..66a3bfe 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
@@ -235,6 +235,11 @@
}
@Override
+ public int compareMergePriorityTo(XmlResourceValue value) {
+ return 0;
+ }
+
+ @Override
public String asConflictStringWith(DataSource source) {
return source.asConflictString();
}