diff options
author | Googler <noreply@google.com> | 2017-07-13 19:50:00 +0200 |
---|---|---|
committer | László Csomor <laszlocsomor@google.com> | 2017-07-14 10:52:06 +0200 |
commit | 5abf4ed4dc9fc134e47f9b56e3b65ba26d0ba9f0 (patch) | |
tree | a046d265ae8c46d59150f849c15fc87fcc94f182 | |
parent | fba07bb72570245d26bd8795709c5a004fc9026a (diff) |
Add flag to turn Android resource merge conflicts from warnings into errors
RELNOTES: none
PiperOrigin-RevId: 161831232
13 files changed, 248 insertions, 65 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AarGeneratorBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AarGeneratorBuilder.java index b0ffa9e5b5..c311d82f0f 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AarGeneratorBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AarGeneratorBuilder.java @@ -40,6 +40,7 @@ public class AarGeneratorBuilder { private Artifact classes; private Artifact aarOut; + private boolean throwOnResourceConflict; private final RuleContext ruleContext; private final SpawnAction.Builder builder; @@ -79,6 +80,11 @@ public class AarGeneratorBuilder { return this; } + public AarGeneratorBuilder setThrowOnResourceConflict(boolean throwOnResourceConflict) { + this.throwOnResourceConflict = throwOnResourceConflict; + return this; + } + public void build(ActionConstructionContext context) { List<Artifact> outs = new ArrayList<>(); List<Artifact> ins = new ArrayList<>(); @@ -115,6 +121,10 @@ public class AarGeneratorBuilder { args.add(aarOut.getExecPathString()); outs.add(aarOut); + if (throwOnResourceConflict) { + args.add("--throwOnResourceConflict"); + } + ruleContext.registerAction( this.builder .addInputs(ImmutableList.<Artifact>copyOf(ins)) diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java index 4820e03710..03878f5bfe 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java @@ -648,6 +648,15 @@ public class AndroidConfiguration extends BuildConfiguration.Fragment { ) public boolean generateRobolectricRClass; + @Option( + name = "experimental_android_throw_on_resource_conflict", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "If passed, resource merge conflicts will be treated as errors instead of warnings" + ) + public boolean throwOnResourceConflict; + @Override public FragmentOptions getHost(boolean fallback) { Options host = (Options) super.getHost(fallback); @@ -725,6 +734,7 @@ public class AndroidConfiguration extends BuildConfiguration.Fragment { private final boolean exportsManifestDefault; private final AndroidAaptVersion androidAaptVersion; private final boolean generateRobolectricRClass; + private final boolean throwOnResourceConflict; private final boolean useParallelDex2Oat; AndroidConfiguration(Options options) throws InvalidConfigurationException { @@ -760,6 +770,7 @@ public class AndroidConfiguration extends BuildConfiguration.Fragment { this.exportsManifestDefault = options.exportsManifestDefault; this.androidAaptVersion = options.androidAaptVersion; this.generateRobolectricRClass = options.generateRobolectricRClass; + this.throwOnResourceConflict = options.throwOnResourceConflict; this.useParallelDex2Oat = options.useParallelDex2Oat; if (!dexoptsSupportedInIncrementalDexing.contains("--no-locals")) { @@ -889,6 +900,10 @@ public class AndroidConfiguration extends BuildConfiguration.Fragment { return generateRobolectricRClass; } + boolean throwOnResourceConflict() { + return throwOnResourceConflict; + } + @Override public void addGlobalMakeVariables(ImmutableMap.Builder<String, String> globalMakeEnvBuilder) { globalMakeEnvBuilder.put("ANDROID_CPU", cpu); diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java index 98fcb45946..83cc88ec6c 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java @@ -180,6 +180,8 @@ public abstract class AndroidLibrary implements RuleConfiguredTargetFactory { .withPrimary(resourceContainer) .withDependencies(resourceApk.getResourceDependencies()) .setDebug(ruleContext.getConfiguration().getCompilationMode() != CompilationMode.OPT) + .setThrowOnResourceConflict( + ruleContext.getFragment(AndroidConfiguration.class).throwOnResourceConflict()) .build(ruleContext); } @@ -189,6 +191,8 @@ public abstract class AndroidLibrary implements RuleConfiguredTargetFactory { .withRtxt(primaryResources.getRTxt()) .withClasses(classesJar) .setAAROut(aarOut) + .setThrowOnResourceConflict( + ruleContext.getFragment(AndroidConfiguration.class).throwOnResourceConflict()) .build(ruleContext); RuleConfiguredTargetBuilder builder = new RuleConfiguredTargetBuilder(ruleContext); diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java index e86cae2441..5b63f24af6 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourceMergingActionBuilder.java @@ -65,6 +65,7 @@ public class AndroidResourceMergingActionBuilder { // Flags private String customJavaPackage; + private boolean throwOnResourceConflict; /** @param ruleContext The RuleContext that was used to create the SpawnAction.Builder. */ public AndroidResourceMergingActionBuilder(RuleContext ruleContext) { @@ -116,6 +117,12 @@ public class AndroidResourceMergingActionBuilder { return this; } + public AndroidResourceMergingActionBuilder setThrowOnResourceConflict( + boolean throwOnResourceConflict) { + this.throwOnResourceConflict = throwOnResourceConflict; + return this; + } + public ResourceContainer build(ActionConstructionContext context) { CustomCommandLine.Builder builder = new CustomCommandLine.Builder(); @@ -170,6 +177,10 @@ public class AndroidResourceMergingActionBuilder { outs.add(dataBindingInfoZip); } + if (throwOnResourceConflict) { + builder.add("--throwOnResourceConflict"); + } + SpawnAction.Builder spawnActionBuilder = new SpawnAction.Builder(); // Create the spawn action. ruleContext.registerAction( diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java index b61bee6269..fd827b6962 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java @@ -110,6 +110,7 @@ public class AndroidResourcesProcessorBuilder { private Artifact featureOf; private Artifact featureAfter; private AndroidAaptVersion aaptVersion; + private boolean throwOnResourceConflict; /** * @param ruleContext The RuleContext that was used to create the SpawnAction.Builder. @@ -232,6 +233,12 @@ public class AndroidResourcesProcessorBuilder { return this; } + public AndroidResourcesProcessorBuilder setThrowOnResourceConflict( + boolean throwOnResourceConflict) { + this.throwOnResourceConflict = throwOnResourceConflict; + return this; + } + public ResourceContainer build(ActionConstructionContext context) { if (aaptVersion == AndroidAaptVersion.AAPT2) { return createAapt2ApkAction(context); @@ -467,5 +474,9 @@ public class AndroidResourcesProcessorBuilder { builder.addExecPath("--featureAfter", featureAfter); inputs.add(featureAfter); } + + if (throwOnResourceConflict) { + builder.add("--throwOnResourceConflict"); + } } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java index a9d096ee2a..1ef2406f50 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java @@ -603,7 +603,10 @@ public final class ApplicationManifest { .setMergedResourcesOut(mergedResources) .setManifestOut(manifestOut) .setClassJarOut(rJavaClassJar) - .setDataBindingInfoZip(dataBindingInfoZip); + .setDataBindingInfoZip(dataBindingInfoZip) + .setThrowOnResourceConflict( + ruleContext.getConfiguration() + .getFragment(AndroidConfiguration.class).throwOnResourceConflict()); ResourceContainer merged = resourcesMergerBuilder.build(ruleContext); processed = @@ -642,7 +645,10 @@ public final class ApplicationManifest { .setVersionCode(manifestValues.get("versionCode")) .setVersionName(manifestValues.get("versionName")) .setFeatureOf(featureOf) - .setFeatureAfter(featureAfter); + .setFeatureAfter(featureAfter) + .setThrowOnResourceConflict( + ruleContext.getConfiguration() + .getFragment(AndroidConfiguration.class).throwOnResourceConflict()); if (!incremental) { builder .targetAaptVersion(AndroidAaptVersion.AAPT) diff --git a/src/test/java/com/google/devtools/build/android/AndroidDataMergerTest.java b/src/test/java/com/google/devtools/build/android/AndroidDataMergerTest.java index 1444b83fd6..e28d65ceb3 100644 --- a/src/test/java/com/google/devtools/build/android/AndroidDataMergerTest.java +++ b/src/test/java/com/google/devtools/build/android/AndroidDataMergerTest.java @@ -25,6 +25,7 @@ import com.google.common.jimfs.Jimfs; import com.google.common.truth.FailureStrategy; import com.google.common.truth.SubjectFactory; import com.google.devtools.build.android.AndroidDataBuilder.ResourceType; +import com.google.devtools.build.android.AndroidDataMerger.MergeConflictException; import com.google.devtools.build.android.AndroidDataMerger.SourceChecker; import com.google.devtools.build.android.xml.IdXmlResourceValue; import com.google.devtools.build.android.xml.PublicXmlResourceValue; @@ -102,7 +103,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); UnwrittenMergedAndroidData data = - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, true); UnwrittenMergedAndroidData expected = UnwrittenMergedAndroidData.of( @@ -161,7 +162,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); UnwrittenMergedAndroidData data = - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, true); UnwrittenMergedAndroidData expected = UnwrittenMergedAndroidData.of( @@ -226,7 +227,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); UnwrittenMergedAndroidData transitive = - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, true); UnwrittenMergedAndroidData expected = UnwrittenMergedAndroidData.of( @@ -276,7 +277,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, false); assertThat(loggingHandler.warnings) .containsExactly( @@ -325,7 +326,7 @@ public class AndroidDataMergerTest { }); assertAbout(unwrittenMergedAndroidData) - .that(merger.merge(transitiveDependency, directDependency, primary, false)) + .that(merger.merge(transitiveDependency, directDependency, primary, false, true)) .isEqualTo( UnwrittenMergedAndroidData.of( primary.getManifest(), @@ -367,7 +368,7 @@ public class AndroidDataMergerTest { .buildUnvalidated(); AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, true); assertThat(loggingHandler.warnings).isEmpty(); } @@ -402,7 +403,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); UnwrittenMergedAndroidData data = - merger.merge(transitiveDependency, directDependency, primary, true); + merger.merge(transitiveDependency, directDependency, primary, true, true); UnwrittenMergedAndroidData expected = UnwrittenMergedAndroidData.of( primary.getManifest(), @@ -442,7 +443,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, false); assertThat(loggingHandler.warnings) .containsExactly( @@ -489,7 +490,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); UnwrittenMergedAndroidData data = - merger.merge(transitiveDependency, directDependency, primary, true); + merger.merge(transitiveDependency, directDependency, primary, true, true); UnwrittenMergedAndroidData expected = UnwrittenMergedAndroidData.of( primary.getManifest(), @@ -533,7 +534,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, false); assertThat(loggingHandler.warnings) .containsExactly( MergeConflict.of( @@ -578,23 +579,62 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, false); FullyQualifiedName fullyQualifiedName = fqnFactory.parse("string/exit"); assertThat(loggingHandler.warnings) .containsExactly( MergeConflict.of( - fullyQualifiedName, - DataResourceXml.createWithNoNamespace( - directRoot.resolve("res/values/strings.xml"), - SimpleXmlResourceValue.createWithValue(Type.STRING, "no way out")), - DataResourceXml.createWithNoNamespace( - transitiveRoot.resolve("res/values/strings.xml"), - SimpleXmlResourceValue.createWithValue(Type.STRING, "wrong way out"))) + fullyQualifiedName, + DataResourceXml.createWithNoNamespace( + directRoot.resolve("res/values/strings.xml"), + SimpleXmlResourceValue.createWithValue(Type.STRING, "no way out")), + DataResourceXml.createWithNoNamespace( + transitiveRoot.resolve("res/values/strings.xml"), + SimpleXmlResourceValue.createWithValue(Type.STRING, "wrong way out"))) .toConflictMessage()); } @Test + public void mergeDirectTransitivePrimaryConflictWithThrowOnConflict() throws Exception { + Path primaryRoot = fileSystem.getPath("primary"); + Path directRoot = fileSystem.getPath("direct"); + Path transitiveRoot = fileSystem.getPath("transitive"); + + ParsedAndroidData transitiveDependency = + ParsedAndroidDataBuilder.buildOn(transitiveRoot, fqnFactory) + .overwritable( + xml("string/exit") + .source("values/strings.xml") + .value(SimpleXmlResourceValue.createWithValue(Type.STRING, "no way out"))) + .build(); + + ParsedAndroidData directDependency = + ParsedAndroidDataBuilder.buildOn(directRoot, fqnFactory) + .overwritable( + xml("string/exit") + .source("values/strings.xml") + .value(SimpleXmlResourceValue.createWithValue(Type.STRING, "wrong way out"))) + .build(); + + UnvalidatedAndroidData primary = + AndroidDataBuilder.of(primaryRoot) + .createManifest("AndroidManifest.xml", "com.google.mergetest") + .addResource( + "values/strings.xml", ResourceType.VALUE, "<string name='exit'>way out</string>") + .buildUnvalidated(); + + AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); + + try { + merger.merge(transitiveDependency, directDependency, primary, false, true); + throw new Exception("Expected a MergeConflictException!"); + } catch (MergeConflictException e) { + return; + } + } + + @Test public void mergeDirectTransitivePrimaryConflictWithPrimaryOverride() throws Exception { Path primaryRoot = fileSystem.getPath("primary"); Path directRoot = fileSystem.getPath("direct"); @@ -629,7 +669,7 @@ public class AndroidDataMergerTest { UnwrittenMergedAndroidData data = AndroidDataMerger.createWithDefaults() - .merge(transitiveDependency, directDependency, primary, true); + .merge(transitiveDependency, directDependency, primary, true, true); UnwrittenMergedAndroidData expected = UnwrittenMergedAndroidData.of( @@ -667,7 +707,7 @@ public class AndroidDataMergerTest { .buildUnvalidated(); AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, false); assertThat(loggingHandler.warnings) .containsExactly( @@ -699,7 +739,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); UnwrittenMergedAndroidData data = - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, true); UnwrittenMergedAndroidData expected = UnwrittenMergedAndroidData.of( @@ -753,7 +793,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); UnwrittenMergedAndroidData data = - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, true); UnwrittenMergedAndroidData expected = UnwrittenMergedAndroidData.of( @@ -816,7 +856,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); UnwrittenMergedAndroidData data = - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, true); UnwrittenMergedAndroidData expected = UnwrittenMergedAndroidData.of( @@ -885,7 +925,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); UnwrittenMergedAndroidData data = - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, true); UnwrittenMergedAndroidData expected = UnwrittenMergedAndroidData.of( @@ -948,7 +988,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); UnwrittenMergedAndroidData data = - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, true); UnwrittenMergedAndroidData expected = UnwrittenMergedAndroidData.of( @@ -996,7 +1036,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); UnwrittenMergedAndroidData data = - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, true); UnwrittenMergedAndroidData expected = UnwrittenMergedAndroidData.of( @@ -1034,7 +1074,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, false); assertThat(loggingHandler.warnings) .containsExactly( MergeConflict.of( @@ -1073,7 +1113,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); UnwrittenMergedAndroidData data = - merger.merge(transitiveDependency, directDependency, primary, true); + merger.merge(transitiveDependency, directDependency, primary, true, true); UnwrittenMergedAndroidData expected = UnwrittenMergedAndroidData.of( primary.getManifest(), @@ -1109,7 +1149,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, false); assertThat(loggingHandler.warnings) .containsExactly( MergeConflict.of( @@ -1140,7 +1180,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); UnwrittenMergedAndroidData data = - merger.merge(transitiveDependency, directDependency, primary, true); + merger.merge(transitiveDependency, directDependency, primary, true, true); UnwrittenMergedAndroidData expected = UnwrittenMergedAndroidData.of( primary.getManifest(), @@ -1174,7 +1214,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, false); assertThat(loggingHandler.warnings) .containsExactly( MergeConflict.of( @@ -1209,7 +1249,7 @@ public class AndroidDataMergerTest { AndroidDataMerger merger = AndroidDataMerger.createWithDefaults(); - merger.merge(transitiveDependency, directDependency, primary, false); + merger.merge(transitiveDependency, directDependency, primary, false, false); assertThat(loggingHandler.warnings) .containsExactly( MergeConflict.of( @@ -1248,7 +1288,7 @@ public class AndroidDataMergerTest { UnwrittenMergedAndroidData data = AndroidDataMerger.createWithDefaults() - .merge(transitiveDependency, directDependency, primary, true); + .merge(transitiveDependency, directDependency, primary, true, true); UnwrittenMergedAndroidData expected = UnwrittenMergedAndroidData.of( diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java index 9be5e5ca7f..704d6a4ee9 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java +++ b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidBinaryTest.java @@ -1569,6 +1569,24 @@ public class AndroidBinaryTest extends AndroidBuildViewTestCase { } } + @Test + public void testThrowOnResourceConflictFlagGetsPropagated() throws Exception { + scratch.file( + "java/r/android/BUILD", + "android_binary(", + " name = 'r',", + " manifest = 'AndroidManifest.xml',", + " resource_files = ['res/values/foo.xml'],", + ")"); + useConfiguration("--experimental_android_throw_on_resource_conflict"); + ConfiguredTarget binary = getConfiguredTarget("//java/r/android:r"); + + List<String> resourceProcessingArgs = + resourceGeneratingAction(getResourceContainer(binary)).getArguments(); + + assertThat(resourceProcessingArgs).contains("--throwOnResourceConflict"); + } + /** * Gets the paths of matching artifacts contained within a resource container * diff --git a/src/tools/android/java/com/google/devtools/build/android/AarGeneratorAction.java b/src/tools/android/java/com/google/devtools/build/android/AarGeneratorAction.java index 7a6947d698..16091348c1 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AarGeneratorAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/AarGeneratorAction.java @@ -19,6 +19,7 @@ import com.google.common.base.Joiner; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; +import com.google.devtools.build.android.AndroidDataMerger.MergeConflictException; import com.google.devtools.build.android.AndroidResourceMerger.MergingException; import com.google.devtools.build.android.Converters.ExistingPathConverter; import com.google.devtools.build.android.Converters.PathConverter; @@ -124,6 +125,14 @@ public class AarGeneratorAction { help = "Path to write the archive." ) public Path aarOutput; + + @Option(name = "throwOnResourceConflict", + defaultValue = "false", + category = "config", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "If passed, resource merge conflicts will be treated as errors instead of warnings") + public boolean throwOnResourceConflict; } public static void main(String[] args) { @@ -153,12 +162,17 @@ public class AarGeneratorAction { null, VariantType.LIBRARY, null, - /* filteredResources= */ ImmutableList.<String>of()); + /* filteredResources= */ ImmutableList.<String>of(), + options.throwOnResourceConflict + ); logger.fine(String.format("Merging finished at %dms", timer.elapsed(TimeUnit.MILLISECONDS))); writeAar(options.aarOutput, mergedData, options.manifest, options.rtxt, options.classes); logger.fine( String.format("Packaging finished at %dms", timer.elapsed(TimeUnit.MILLISECONDS))); + } catch (MergeConflictException e) { + logger.log(Level.SEVERE, e.getMessage()); + System.exit(1); } catch (IOException | MergingException e) { logger.log(Level.SEVERE, "Error during merging resources", e); System.exit(1); 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 44967168b9..f5b0b0040e 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 @@ -36,6 +36,17 @@ import java.util.logging.Logger; /** Handles the Merging of ParsedAndroidData. */ class AndroidDataMerger { + public static class MergeConflictException extends RuntimeException { + + private MergeConflictException(String message) { + super(message); + } + + static MergeConflictException withMessage(String message) { + return new MergeConflictException(message); + } + } + private static final Logger logger = Logger.getLogger(AndroidDataMerger.class.getCanonicalName()); /** Interface for comparing paths. */ @@ -70,7 +81,7 @@ class AndroidDataMerger { // getFileSize did not return correct size. logger.severe( String.format( - "Filesystem size of %s (%s) or %s (%s) is inconsistant with bytes read %s.", + "Filesystem size of %s (%s) or %s (%s) is inconsistent with bytes read %s.", one, one.getFileSize(), two, two.getFileSize(), bytesRead)); return false; } @@ -131,15 +142,15 @@ class AndroidDataMerger { * ParsedAndroidData}. * * @see AndroidDataMerger#merge(ParsedAndroidData, ParsedAndroidData, UnvalidatedAndroidData, - * boolean) for details. + * boolean, boolean) for details. */ UnwrittenMergedAndroidData loadAndMerge( List<? extends SerializedAndroidData> transitive, List<? extends SerializedAndroidData> direct, ParsedAndroidData primary, Path primaryManifest, - boolean allowPrimaryOverrideAll) - throws MergingException { + boolean allowPrimaryOverrideAll, + boolean throwOnResourceConflict) { Stopwatch timer = Stopwatch.createStarted(); try { logger.fine( @@ -150,7 +161,8 @@ class AndroidDataMerger { ParsedAndroidData.loadedFrom(direct, executorService, deserializer), primary, primaryManifest, - allowPrimaryOverrideAll); + allowPrimaryOverrideAll, + throwOnResourceConflict); } finally { logger.fine(String.format("Resources merged in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); } @@ -218,20 +230,26 @@ class AndroidDataMerger { * the ultimate source of truth, provided it doesn't conflict with itself. * @return An UnwrittenMergedAndroidData, containing DataResource objects that can be written to * disk for aapt processing or serialized for future merge passes. - * @throws MergingException if there are merge conflicts or issues with parsing resources from + * @throws MergingException if there are issues with parsing resources from * primaryData. + * @throws MergeConflictException if there are merge conflicts */ UnwrittenMergedAndroidData merge( ParsedAndroidData transitive, ParsedAndroidData direct, UnvalidatedAndroidData primaryData, - boolean allowPrimaryOverrideAll) - throws MergingException { + boolean allowPrimaryOverrideAll, + boolean throwOnResourceConflict) { try { // Extract the primary resources. ParsedAndroidData parsedPrimary = ParsedAndroidData.from(primaryData); return doMerge( - transitive, direct, parsedPrimary, primaryData.getManifest(), allowPrimaryOverrideAll); + transitive, + direct, + parsedPrimary, + primaryData.getManifest(), + allowPrimaryOverrideAll, + throwOnResourceConflict); } catch (IOException e) { throw MergingException.wrapException(e); } @@ -242,15 +260,14 @@ class AndroidDataMerger { ParsedAndroidData direct, ParsedAndroidData parsedPrimary, Path primaryManifest, - boolean allowPrimaryOverrideAll) - throws MergingException { + boolean allowPrimaryOverrideAll, + boolean throwOnResourceConflict) { try { // Create the builders for the final parsed data. final ParsedAndroidData.Builder primaryBuilder = ParsedAndroidData.Builder.newBuilder(); final ParsedAndroidData.Builder transitiveBuilder = ParsedAndroidData.Builder.newBuilder(); final KeyValueConsumers transitiveConsumers = transitiveBuilder.consumers(); final KeyValueConsumers primaryConsumers = primaryBuilder.consumers(); - final Set<MergeConflict> conflicts = new HashSet<>(); // Find all internal conflicts. @@ -286,7 +303,7 @@ class AndroidDataMerger { if (allowPrimaryOverrideAll && parsedPrimary.containsOverwritable(entry.getKey())) { continue; } - // If a transitive value is in the direct map report a conflict, as it is commonly + // If a transitive value is in the direct map, report a conflict, as it is commonly // unintentional. if (direct.containsOverwritable(entry.getKey())) { conflicts.add(direct.foundResourceConflict(entry.getKey(), entry.getValue())); @@ -367,8 +384,11 @@ class AndroidDataMerger { } } if (!messages.isEmpty()) { - // TODO(corysmith): Turn these into errors. - logger.warning(Joiner.on("").join(messages)); + String conflictMessage = Joiner.on("").join(messages); + if (throwOnResourceConflict) { + throw MergeConflictException.withMessage(conflictMessage); + } + logger.warning(conflictMessage); } } return UnwrittenMergedAndroidData.of( diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMerger.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMerger.java index 8fc6075382..eb7fd70711 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMerger.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMerger.java @@ -64,8 +64,8 @@ public class AndroidResourceMerger { final VariantType type, @Nullable final Path symbolsOut, @Nullable AndroidResourceClassWriter rclassWriter, - AndroidDataDeserializer deserializer) - throws MergingException { + AndroidDataDeserializer deserializer, + boolean throwOnResourceConflict) { Stopwatch timer = Stopwatch.createStarted(); final ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(15)); @@ -78,7 +78,8 @@ public class AndroidResourceMerger { primary, primaryManifest, type != VariantType.LIBRARY, - deserializer); + deserializer, + throwOnResourceConflict); timer.reset().start(); if (symbolsOut != null) { AndroidDataSerializer serializer = AndroidDataSerializer.create(); @@ -114,14 +115,19 @@ public class AndroidResourceMerger { ParsedAndroidData primary, Path primaryManifest, boolean allowPrimaryOverrideAll, - AndroidDataDeserializer deserializer) - throws MergingException { + AndroidDataDeserializer deserializer, + boolean throwOnResourceConflict) { Stopwatch timer = Stopwatch.createStarted(); try { AndroidDataMerger merger = AndroidDataMerger.createWithPathDeduplictor(executorService, deserializer); return merger.loadAndMerge( - transitive, direct, primary, primaryManifest, allowPrimaryOverrideAll); + transitive, + direct, + primary, + primaryManifest, + allowPrimaryOverrideAll, + throwOnResourceConflict); } finally { logger.fine(String.format("merge finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); } @@ -140,8 +146,8 @@ public class AndroidResourceMerger { @Nullable final PngCruncher cruncher, final VariantType type, @Nullable final Path symbolsOut, - final List<String> filteredResources) - throws MergingException { + final List<String> filteredResources, + boolean throwOnResourceConflict) { try { final ParsedAndroidData parsedPrimary = ParsedAndroidData.from(primary); return mergeData( @@ -155,7 +161,8 @@ public class AndroidResourceMerger { type, symbolsOut, null /* rclassWriter */, - AndroidDataDeserializer.withFilteredResources(filteredResources)); + AndroidDataDeserializer.withFilteredResources(filteredResources), + throwOnResourceConflict); } catch (IOException e) { throw MergingException.wrapException(e); } @@ -175,8 +182,8 @@ public class AndroidResourceMerger { @Nullable final PngCruncher cruncher, final VariantType type, @Nullable final Path symbolsOut, - @Nullable final AndroidResourceClassWriter rclassWriter) - throws MergingException { + @Nullable final AndroidResourceClassWriter rclassWriter, + boolean throwOnResourceConflict) { final ParsedAndroidData.Builder primaryBuilder = ParsedAndroidData.Builder.newBuilder(); final AndroidDataDeserializer deserializer = AndroidDataDeserializer.create(); primary.deserialize(deserializer, primaryBuilder.consumers()); @@ -192,7 +199,8 @@ public class AndroidResourceMerger { type, symbolsOut, rclassWriter, - deserializer); + deserializer, + throwOnResourceConflict); } } diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java index 9459ae6d99..4506fae73e 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceMergingAction.java @@ -21,6 +21,7 @@ import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; import com.google.common.base.Strings; import com.google.common.io.Files; +import com.google.devtools.build.android.AndroidDataMerger.MergeConflictException; import com.google.devtools.build.android.AndroidResourceMerger.MergingException; import com.google.devtools.build.android.AndroidResourceProcessor.AaptConfigOptions; import com.google.devtools.build.android.Converters.ExistingPathConverter; @@ -180,6 +181,14 @@ public class AndroidResourceMergingAction { help = "Path to where data binding's layout info output should be written." ) public Path dataBindingInfoOut; + + @Option(name = "throwOnResourceConflict", + defaultValue = "false", + category = "config", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "If passed, resource merge conflicts will be treated as errors instead of warnings") + public boolean throwOnResourceConflict; } public static void main(String[] args) throws Exception { @@ -223,7 +232,8 @@ public class AndroidResourceMergingAction { new StubPngCruncher(), packageType, options.symbolsBinOut, - resourceClassWriter); + resourceClassWriter, + options.throwOnResourceConflict); logger.fine(String.format("Merging finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); @@ -268,6 +278,9 @@ public class AndroidResourceMergingAction { String.format( "Create resources.zip finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); } + } catch (MergeConflictException e) { + logger.log(Level.SEVERE, e.getMessage()); + System.exit(1); } catch (MergingException e) { logger.log(Level.SEVERE, "Error during merging resources", e); throw e; diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java index 13eb0f5c90..0dfd21e5f5 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java @@ -22,6 +22,7 @@ import com.android.ide.common.process.LoggedProcessOutputHandler; import com.android.utils.StdLogger; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.android.AndroidDataMerger.MergeConflictException; import com.google.devtools.build.android.AndroidResourceMerger.MergingException; import com.google.devtools.build.android.AndroidResourceProcessor.AaptConfigOptions; import com.google.devtools.build.android.AndroidResourceProcessor.FlagAaptOptions; @@ -295,6 +296,14 @@ public class AndroidResourceProcessingAction { help = "A list of resources that were filtered out in analysis." ) public List<String> prefilteredResources; + + @Option(name = "throwOnResourceConflict", + defaultValue = "false", + category = "config", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "If passed, resource merge conflicts will be treated as errors instead of warnings") + public boolean throwOnResourceConflict; } private static AaptConfigOptions aaptConfigOptions; @@ -346,7 +355,8 @@ public class AndroidResourceProcessingAction { selectPngCruncher(), options.packageType, options.symbolsOut, - options.prefilteredResources); + options.prefilteredResources, + options.throwOnResourceConflict); logger.fine(String.format("Merging finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); @@ -429,6 +439,9 @@ public class AndroidResourceProcessingAction { } logger.fine( String.format("Packaging finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); + } catch (MergeConflictException e) { + logger.severe(e.getMessage()); + System.exit(1); } catch (MergingException e) { logger.log(java.util.logging.Level.SEVERE, "Error during merging resources", e); throw e; |