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(); }