Throw a rule error when a skylark rule implementation returns multiple providers of the same type.
This error-throwing functionality is tied to semantic flag --incompatible_disallow_conflicting_providers
Partial fix for #5902. (That issue will still track migration).
RELNOTES: A rule error will be thrown if a Skylark rule implementation function returns multiple providers of the same type. Try the `--incompatible_disallow_conflicting_providers` flag to ensure your code is forward-compatible.
PiperOrigin-RevId: 208884695
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
index 3b150c2..e0eafd6 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
@@ -36,6 +36,7 @@
import com.google.devtools.build.lib.packages.AdvertisedProviderSet;
import com.google.devtools.build.lib.packages.Info;
import com.google.devtools.build.lib.packages.NativeProvider;
+import com.google.devtools.build.lib.packages.Provider;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
import com.google.devtools.build.lib.packages.SkylarkProviderIdentifier;
@@ -57,8 +58,8 @@
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.FileType;
import com.google.devtools.build.lib.util.FileTypeSet;
-import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
@@ -299,7 +300,7 @@
throws EvalException {
Info oldStyleProviders = StructProvider.STRUCT.createEmpty(loc);
- ArrayList<Info> declaredProviders = new ArrayList<>();
+ Map<Provider.Key, Info> declaredProviders = new LinkedHashMap<>();
if (target instanceof Info) {
// Either an old-style struct or a single declared provider (not in a list)
@@ -319,12 +320,19 @@
Info.class,
loc,
"The value of 'providers' should be a sequence of declared providers");
- declaredProviders.add(declaredProvider);
+ Provider.Key providerKey = declaredProvider.getProvider().getKey();
+ if (declaredProviders.put(providerKey, declaredProvider) != null) {
+ if (context.getSkylarkSemantics().incompatibleDisallowConflictingProviders()) {
+ context.getRuleContext()
+ .ruleError("Multiple conflicting returned providers with key " + providerKey);
+ }
+ }
}
}
} else {
+ Provider.Key providerKey = struct.getProvider().getKey();
// Single declared provider
- declaredProviders.add(struct);
+ declaredProviders.put(providerKey, struct);
}
} else if (target instanceof Iterable) {
// Sequence of declared providers
@@ -336,13 +344,19 @@
loc,
"A return value of a rule implementation function should be "
+ "a sequence of declared providers");
- declaredProviders.add(declaredProvider);
+ Provider.Key providerKey = declaredProvider.getProvider().getKey();
+ if (declaredProviders.put(providerKey, declaredProvider) != null) {
+ if (context.getSkylarkSemantics().incompatibleDisallowConflictingProviders()) {
+ context.getRuleContext()
+ .ruleError("Multiple conflicting returned providers with key " + providerKey);
+ }
+ }
}
}
boolean defaultProviderProvidedExplicitly = false;
- for (Info declaredProvider : declaredProviders) {
+ for (Info declaredProvider : declaredProviders.values()) {
if (declaredProvider
.getProvider()
.getKey()
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java
index 5dec148..6fda353 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java
@@ -1012,6 +1012,10 @@
helper.getToolsRunfilesSuppliers());
}
+ public SkylarkSemantics getSkylarkSemantics() {
+ return skylarkSemantics;
+ }
+
/**
* Ensures the given {@link Map} has keys that have {@link Label} type and values that have either
* {@link Iterable} or {@link SkylarkNestedSet} type, and raises {@link EvalException} otherwise.
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkSemanticsOptions.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkSemanticsOptions.java
index 5ef70d3..126078d 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkSemanticsOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkSemanticsOptions.java
@@ -161,6 +161,21 @@
public boolean incompatibleDisableObjcProviderResources;
@Option(
+ name = "incompatible_disallow_conflicting_providers",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.BUILD_FILE_SEMANTICS},
+ metadataTags = {
+ OptionMetadataTag.INCOMPATIBLE_CHANGE,
+ OptionMetadataTag.TRIGGERED_BY_ALL_INCOMPATIBLE_CHANGES
+ },
+ help = "If set to true, disallow rule implementation functions from returning multiple "
+ + "instances of the same type of provider. (If false, only the last in the list will be "
+ + "used.)"
+ )
+ public boolean incompatibleDisallowConflictingProviders;
+
+ @Option(
name = "incompatible_disallow_data_transition",
defaultValue = "false",
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
@@ -376,6 +391,7 @@
.incompatibleDepsetUnion(incompatibleDepsetUnion)
.incompatibleDisableDeprecatedAttrParams(incompatibleDisableDeprecatedAttrParams)
.incompatibleDisableObjcProviderResources(incompatibleDisableObjcProviderResources)
+ .incompatibleDisallowConflictingProviders(incompatibleDisallowConflictingProviders)
.incompatibleDisallowDataTransition(incompatibleDisallowDataTransition)
.incompatibleDisallowDictPlus(incompatibleDisallowDictPlus)
.incompatibleDisallowFileType(incompatibleDisallowFileType)
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java
index 85f6d4d..ac13aad 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java
@@ -57,6 +57,8 @@
public abstract boolean incompatibleDisableObjcProviderResources();
+ public abstract boolean incompatibleDisallowConflictingProviders();
+
public abstract boolean incompatibleDisallowDataTransition();
public abstract boolean incompatibleDisallowDictPlus();
@@ -110,6 +112,7 @@
.incompatibleDepsetUnion(false)
.incompatibleDisableDeprecatedAttrParams(false)
.incompatibleDisableObjcProviderResources(false)
+ .incompatibleDisallowConflictingProviders(false)
.incompatibleDisallowDataTransition(false)
.incompatibleDisallowDictPlus(false)
.incompatibleDisallowFileType(false)
@@ -148,6 +151,8 @@
public abstract Builder incompatibleDisableObjcProviderResources(boolean value);
+ public abstract Builder incompatibleDisallowConflictingProviders(boolean value);
+
public abstract Builder incompatibleDisallowDataTransition(boolean value);
public abstract Builder incompatibleDisallowDictPlus(boolean value);
diff --git a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
index 5bc1109..3dd878c 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
@@ -129,6 +129,7 @@
"--incompatible_depset_union=" + rand.nextBoolean(),
"--incompatible_disable_deprecated_attr_params=" + rand.nextBoolean(),
"--incompatible_disable_objc_provider_resources=" + rand.nextBoolean(),
+ "--incompatible_disallow_conflicting_providers=" + rand.nextBoolean(),
"--incompatible_disallow_data_transition=" + rand.nextBoolean(),
"--incompatible_disallow_dict_plus=" + rand.nextBoolean(),
"--incompatible_disallow_filetype=" + rand.nextBoolean(),
@@ -162,6 +163,7 @@
.incompatibleDepsetUnion(rand.nextBoolean())
.incompatibleDisableDeprecatedAttrParams(rand.nextBoolean())
.incompatibleDisableObjcProviderResources(rand.nextBoolean())
+ .incompatibleDisallowConflictingProviders(rand.nextBoolean())
.incompatibleDisallowDataTransition(rand.nextBoolean())
.incompatibleDisallowDictPlus(rand.nextBoolean())
.incompatibleDisallowFileType(rand.nextBoolean())
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
index 1834735..17f115d 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
@@ -1185,6 +1185,104 @@
}
@Test
+ public void testConflictingProviderKeys_fromStruct_allowed() throws Exception {
+ setSkylarkSemanticsOptions("--incompatible_disallow_conflicting_providers=false");
+ scratch.file(
+ "test/extension.bzl",
+ "my_provider = provider()",
+ "other_provider = provider()",
+ "def _impl(ctx):",
+ " return struct(providers = [my_provider(x = 1), other_provider(), my_provider(x = 2)])",
+ "my_rule = rule(_impl)"
+ );
+
+ scratch.file(
+ "test/BUILD",
+ "load(':extension.bzl', 'my_rule')",
+ "my_rule(name = 'r')"
+ );
+
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:r");
+ Provider.Key key =
+ new SkylarkProvider.SkylarkKey(
+ Label.create(configuredTarget.getLabel().getPackageIdentifier(), "extension.bzl"),
+ "my_provider");
+ Info declaredProvider = configuredTarget.get(key);
+ assertThat(declaredProvider).isNotNull();
+ assertThat(declaredProvider.getProvider().getKey()).isEqualTo(key);
+ assertThat(declaredProvider.getValue("x")).isEqualTo(2);
+ }
+
+ @Test
+ public void testConflictingProviderKeys_fromStruct_disallowed() throws Exception {
+ setSkylarkSemanticsOptions("--incompatible_disallow_conflicting_providers=true");
+ scratch.file(
+ "test/extension.bzl",
+ "my_provider = provider()",
+ "other_provider = provider()",
+ "def _impl(ctx):",
+ " return struct(providers = [my_provider(x = 1), other_provider(), my_provider(x = 2)])",
+ "my_rule = rule(_impl)"
+ );
+
+ checkError(
+ "test",
+ "r",
+ "Multiple conflicting returned providers with key my_provider",
+ "load(':extension.bzl', 'my_rule')",
+ "my_rule(name = 'r')");
+ }
+
+ @Test
+ public void testConflictingProviderKeys_fromIterable_allowed() throws Exception {
+ setSkylarkSemanticsOptions("--incompatible_disallow_conflicting_providers=false");
+ scratch.file(
+ "test/extension.bzl",
+ "my_provider = provider()",
+ "other_provider = provider()",
+ "def _impl(ctx):",
+ " return [my_provider(x = 1), other_provider(), my_provider(x = 2)]",
+ "my_rule = rule(_impl)"
+ );
+
+ scratch.file(
+ "test/BUILD",
+ "load(':extension.bzl', 'my_rule')",
+ "my_rule(name = 'r')"
+ );
+
+ ConfiguredTarget configuredTarget = getConfiguredTarget("//test:r");
+ Provider.Key key =
+ new SkylarkProvider.SkylarkKey(
+ Label.create(configuredTarget.getLabel().getPackageIdentifier(), "extension.bzl"),
+ "my_provider");
+ Info declaredProvider = configuredTarget.get(key);
+ assertThat(declaredProvider).isNotNull();
+ assertThat(declaredProvider.getProvider().getKey()).isEqualTo(key);
+ assertThat(declaredProvider.getValue("x")).isEqualTo(2);
+ }
+
+ @Test
+ public void testConflictingProviderKeys_fromIterable_disallowed() throws Exception {
+ setSkylarkSemanticsOptions("--incompatible_disallow_conflicting_providers=true");
+ scratch.file(
+ "test/extension.bzl",
+ "my_provider = provider()",
+ "other_provider = provider()",
+ "def _impl(ctx):",
+ " return [my_provider(x = 1), other_provider(), my_provider(x = 2)]",
+ "my_rule = rule(_impl)"
+ );
+
+ checkError(
+ "test",
+ "r",
+ "Multiple conflicting returned providers with key my_provider",
+ "load(':extension.bzl', 'my_rule')",
+ "my_rule(name = 'r')");
+ }
+
+ @Test
public void testRecursionDetection() throws Exception {
reporter.removeHandler(failFastHandler);
scratch.file(