Add a string-to-label-dict attribute type.
This really just exposes the existing LABEL_DICT_UNARY type that already exists in Java.
Other than writing some boring conversion logic, all pieces fell together pretty pleasantly.
Work towards #7989. That one calls for a string-to-label-list-dict type, but I'm planning to resolve that as WAI, since string-to-label-dict is close enough.
RELNOTES: None.
PiperOrigin-RevId: 686415025
Change-Id: Ib07ada7ab2ede95220ed1cc2d3569996fc4afb88
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java
index fef34f0..4e23b27 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java
@@ -924,6 +924,57 @@
}
@Override
+ public Descriptor stringKeyedLabelDictAttribute(
+ Boolean allowEmpty,
+ Object configurable,
+ Object defaultValue, // Dict | StarlarkFunction
+ Object doc,
+ Object allowFiles,
+ Object allowRules,
+ Sequence<?> providers,
+ Object forDependencyResolution,
+ Sequence<?> flags,
+ Boolean mandatory,
+ Object cfg,
+ Sequence<?> aspects,
+ StarlarkThread thread)
+ throws EvalException {
+ checkContext(thread, "attr.label_keyed_string_dict()");
+ Map<String, Object> kwargs =
+ optionMap(
+ CONFIGURABLE_ARG,
+ configurable,
+ DEFAULT_ARG,
+ defaultValue,
+ ALLOW_FILES_ARG,
+ allowFiles,
+ ALLOW_RULES_ARG,
+ allowRules,
+ PROVIDERS_ARG,
+ providers,
+ FOR_DEPENDENCY_RESOLUTION_ARG,
+ forDependencyResolution,
+ FLAGS_ARG,
+ flags,
+ MANDATORY_ARG,
+ mandatory,
+ ALLOW_EMPTY_ARG,
+ allowEmpty,
+ CONFIGURATION_ARG,
+ cfg,
+ ASPECTS_ARG,
+ aspects);
+ ImmutableAttributeFactory attribute =
+ createAttributeFactory(
+ BuildType.LABEL_DICT_UNARY,
+ Starlark.toJavaOptional(doc, String.class),
+ kwargs,
+ thread,
+ "string_keyed_label_dict");
+ return new Descriptor("string_keyed_label_dict", attribute);
+ }
+
+ @Override
public Descriptor labelKeyedStringDictAttribute(
Boolean allowEmpty,
Object configurable,
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttributesCollection.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttributesCollection.java
index 7b53f36..33eb1da 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttributesCollection.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttributesCollection.java
@@ -16,6 +16,7 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.AliasProvider;
import com.google.devtools.build.lib.analysis.AspectContext;
@@ -192,6 +193,22 @@
this.prerequisites = prerequisitesCollection;
}
+ static Dict<String, TransitiveInfoCollection> convertStringToLabelMap(
+ Map<String, Label> unconfiguredValue,
+ List<? extends TransitiveInfoCollection> prerequisites) {
+ Map<Label, TransitiveInfoCollection> prerequisiteMap = Maps.newHashMap();
+ for (TransitiveInfoCollection prereq : prerequisites) {
+ prerequisiteMap.put(AliasProvider.getDependencyLabel(prereq), prereq);
+ }
+
+ Dict.Builder<String, TransitiveInfoCollection> builder = Dict.builder();
+ for (var entry : unconfiguredValue.entrySet()) {
+ builder.put(entry.getKey(), prerequisiteMap.get(entry.getValue()));
+ }
+
+ return builder.buildImmutable();
+ }
+
@Nullable
public static Object convertAttributeValue(
Supplier<List<? extends TransitiveInfoCollection>> prerequisiteSupplier,
@@ -228,10 +245,7 @@
return dormantDeps;
}
- // TODO(b/140636597): Remove the LABEL_DICT_UNARY special case of this conditional
- // LABEL_DICT_UNARY was previously not treated as a dependency-bearing type, and was put into
- // Starlark as a Map<String, Label>; this special case preserves that behavior temporarily.
- if (type.getLabelClass() != LabelClass.DEPENDENCY || type == BuildType.LABEL_DICT_UNARY) {
+ if (type.getLabelClass() != LabelClass.DEPENDENCY) {
// Attribute values should be type safe
return Attribute.valueToStarlark(val);
}
@@ -243,6 +257,9 @@
|| (type == BuildType.LABEL && a.getTransitionFactory().isSplit())) {
List<?> allPrereq = prerequisiteSupplier.get();
return StarlarkList.immutableCopyOf(allPrereq);
+ } else if (type == BuildType.LABEL_DICT_UNARY) {
+ return convertStringToLabelMap(
+ BuildType.LABEL_DICT_UNARY.cast(val), prerequisiteSupplier.get());
} else if (type == BuildType.LABEL_KEYED_STRING_DICT) {
Dict.Builder<TransitiveInfoCollection, String> builder = Dict.builder();
Map<Label, String> original = BuildType.LABEL_KEYED_STRING_DICT.cast(val);
@@ -277,7 +294,7 @@
}
attrBuilder.put(skyname, starlarkVal);
- if (type.getLabelClass() != LabelClass.DEPENDENCY || type == BuildType.LABEL_DICT_UNARY) {
+ if (type.getLabelClass() != LabelClass.DEPENDENCY) {
return;
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleContext.java
index 2b61b5f..3c33af1 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleContext.java
@@ -36,6 +36,7 @@
import com.google.devtools.build.lib.analysis.BashCommandConstructor;
import com.google.devtools.build.lib.analysis.CommandHelper;
import com.google.devtools.build.lib.analysis.ConfigurationMakeVariableContext;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.FileProvider;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.LocationExpander;
@@ -528,6 +529,16 @@
if (attr.getType() == BuildType.LABEL) {
Preconditions.checkState(splitPrereq.getValue().size() == 1);
value = splitPrereq.getValue().get(0).getConfiguredTarget();
+ } else if (attr.getType() == BuildType.LABEL_DICT_UNARY) {
+ ImmutableList<ConfiguredTarget> prerequisites =
+ splitPrereq.getValue().stream()
+ .map(ConfiguredTargetAndData::getConfiguredTarget)
+ .collect(ImmutableList.toImmutableList());
+
+ value =
+ StarlarkAttributesCollection.Builder.convertStringToLabelMap(
+ ruleContext.attributes().get(attr.getName(), BuildType.LABEL_DICT_UNARY),
+ prerequisites);
} else {
// BuildType.LABEL_LIST
value =
@@ -857,12 +868,12 @@
if (ruleContext.useAutoExecGroups()) {
return StarlarkToolchainContext.create(
/* targetDescription= */ ruleContext.getToolchainContext().targetDescription(),
- /* resolveToolchainInfoFunc= */ ruleContext::getToolchainInfo,
+ /* resolveToolchainDataFunc= */ ruleContext::getToolchainInfo,
/* resolvedToolchainTypeLabels= */ getRequestedToolchainTypeLabelsFromAutoExecGroups());
} else {
return StarlarkToolchainContext.create(
/* targetDescription= */ ruleContext.getToolchainContext().targetDescription(),
- /* resolveToolchainInfoFunc= */ ruleContext.getToolchainContext()::forToolchainType,
+ /* resolveToolchainDataFunc= */ ruleContext.getToolchainContext()::forToolchainType,
/* resolvedToolchainTypeLabels= */ ruleContext
.getToolchainContext()
.requestedToolchainTypeLabels()
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkAttrModuleApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkAttrModuleApi.java
index d85ff35..aac1c04 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkAttrModuleApi.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkAttrModuleApi.java
@@ -769,6 +769,124 @@
throws EvalException;
@StarlarkMethod(
+ name = "string_keyed_label_dict",
+ doc =
+ "<p>Creates a schema for an attribute whose value is a dictionary where the keys are "
+ + "strings and the values are labels. This is a dependency attribute.</p>"
+ + DEPENDENCY_ATTR_TEXT,
+ parameters = {
+ @Param(name = ALLOW_EMPTY_ARG, defaultValue = "True", doc = ALLOW_EMPTY_DOC, named = true),
+ @Param(
+ name = CONFIGURABLE_ARG,
+ allowedTypes = {
+ @ParamType(type = Boolean.class),
+ @ParamType(type = Starlark.UnboundMarker.class),
+ },
+ defaultValue = "unbound",
+ doc = CONFIGURABLE_ARG_DOC,
+ named = true,
+ positional = false),
+ @Param(
+ name = DEFAULT_ARG,
+ allowedTypes = {
+ @ParamType(type = Dict.class),
+ @ParamType(type = StarlarkFunction.class)
+ },
+ defaultValue = "{}",
+ named = true,
+ positional = false,
+ doc =
+ DEFAULT_DOC
+ + "Use strings or the <a"
+ + " href=\"../builtins/Label.html#Label\"><code>Label</code></a> function to"
+ + " specify default values, for example,"
+ + " <code>attr.string_keyed_label_dict(default = {\"foo\": \"//a:b\","
+ + " \"bar\": \"//a:c\"})</code>."),
+ @Param(
+ name = DOC_ARG,
+ allowedTypes = {@ParamType(type = String.class), @ParamType(type = NoneType.class)},
+ defaultValue = "None",
+ doc = DOC_DOC,
+ named = true,
+ positional = false),
+ @Param(
+ name = ALLOW_FILES_ARG,
+ allowedTypes = {
+ @ParamType(type = Boolean.class),
+ @ParamType(type = Sequence.class, generic1 = String.class),
+ @ParamType(type = NoneType.class),
+ },
+ defaultValue = "None",
+ named = true,
+ positional = false,
+ doc = ALLOW_FILES_DOC),
+ @Param(
+ name = ALLOW_RULES_ARG,
+ allowedTypes = {
+ @ParamType(type = Sequence.class, generic1 = String.class),
+ @ParamType(type = NoneType.class),
+ },
+ defaultValue = "None",
+ named = true,
+ positional = false,
+ doc = ALLOW_RULES_DOC),
+ @Param(
+ name = PROVIDERS_ARG,
+ defaultValue = "[]",
+ named = true,
+ positional = false,
+ doc = PROVIDERS_DOC),
+ @Param(
+ name = FOR_DEPENDENCY_RESOLUTION_ARG,
+ defaultValue = "unbound",
+ named = true,
+ positional = false,
+ doc = FOR_DEPENDENCY_RESOLUTION_DOC),
+ @Param(
+ name = FLAGS_ARG,
+ allowedTypes = {@ParamType(type = Sequence.class, generic1 = String.class)},
+ defaultValue = "[]",
+ named = true,
+ positional = false,
+ doc = FLAGS_DOC),
+ @Param(
+ name = MANDATORY_ARG,
+ defaultValue = "False",
+ named = true,
+ positional = false,
+ doc = MANDATORY_DOC),
+ @Param(
+ name = CONFIGURATION_ARG,
+ defaultValue = "None",
+ named = true,
+ positional = false,
+ doc = CONFIGURATION_DOC),
+ @Param(
+ name = ASPECTS_ARG,
+ allowedTypes = {@ParamType(type = Sequence.class, generic1 = StarlarkAspectApi.class)},
+ defaultValue = "[]",
+ named = true,
+ positional = false,
+ doc = ASPECTS_ARG_DOC)
+ },
+ useStarlarkThread = true)
+ Descriptor stringKeyedLabelDictAttribute(
+ Boolean allowEmpty,
+ Object configurable,
+ Object defaultValue,
+ Object doc,
+ Object allowFiles,
+ Object allowRules,
+ Sequence<?> providers,
+ Object forDependencyResolution,
+ Sequence<?> flags,
+ Boolean mandatory,
+ Object cfg,
+ Sequence<?> aspects,
+ StarlarkThread thread)
+ throws EvalException;
+
+ @StarlarkMethod(
name = "label_keyed_string_dict",
doc =
"<p>Creates a schema for an attribute holding a dictionary, where the keys are labels "
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
index ba9e98e..1ecdefc 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
@@ -3682,6 +3682,103 @@
@Test
@SuppressWarnings("unchecked")
+ public void stringKeyedLabelDictWithSplitConfiguration() throws Exception {
+ scratch.file(
+ "a/BUILD",
+ """
+ load(":a.bzl", "a")
+ a(name="a", dict={"foo_key": ":foo", "gen_key": ":gen"})
+
+ filegroup(name="foo", srcs=["foo.txt"])
+ genrule(name="gen", srcs=[], outs=["gen.txt"], cmd="exit 1")
+ """);
+
+ scratch.file(
+ "a/a.bzl",
+ """
+ DictInfo = provider(fields=["dict"])
+
+ def _a_impl(ctx):
+ return [DictInfo(dict = ctx.split_attr.dict)]
+
+ def _trans_impl(settings, attr):
+ return {
+ "fastbuild_key": {"//command_line_option:compilation_mode": "fastbuild"},
+ "dbg_key": {"//command_line_option:compilation_mode": "dbg"},
+ }
+
+ trans = transition(
+ implementation = _trans_impl,
+ inputs = [],
+ outputs = ["//command_line_option:compilation_mode"])
+
+ a = rule(
+ implementation=_a_impl,
+ attrs={"dict": attr.string_keyed_label_dict(cfg=trans)})
+ """);
+
+ ConfiguredTarget a = getConfiguredTarget("//a:a");
+ StructImpl info =
+ (StructImpl)
+ a.get(
+ new StarlarkProvider.Key(
+ keyForBuild(Label.parseCanonical("//a:a.bzl")), "DictInfo"));
+ Map<String, Map<String, ConfiguredTarget>> dict =
+ (Map<String, Map<String, ConfiguredTarget>>) info.getValue("dict");
+ assertThat(dict.keySet()).containsExactly("fastbuild_key", "dbg_key");
+ Map<String, ConfiguredTarget> fastbuild = dict.get("fastbuild_key");
+ Map<String, ConfiguredTarget> dbg = dict.get("dbg_key");
+ assertThat(fastbuild.keySet()).containsExactly("foo_key", "gen_key");
+ assertThat(dbg.keySet()).containsExactly("foo_key", "gen_key");
+
+ assertThat(getFilesToBuild(fastbuild.get("foo_key")).getSingleton().getExecPathString())
+ .isEqualTo("a/foo.txt");
+ assertThat(getFilesToBuild(dbg.get("foo_key")).getSingleton().getExecPathString())
+ .isEqualTo("a/foo.txt");
+ assertThat(getFilesToBuild(fastbuild.get("gen_key")).getSingleton().getExecPathString())
+ .endsWith("-fastbuild/bin/a/gen.txt");
+ assertThat(getFilesToBuild(dbg.get("gen_key")).getSingleton().getExecPathString())
+ .endsWith("-dbg/bin/a/gen.txt");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void stringKeyedLabelDict() throws Exception {
+ scratch.file(
+ "a/BUILD",
+ """
+ load(":a.bzl", "a")
+ a(name="a", dict={"foo_key": ":foo", "gen_key": ":gen"})
+
+ filegroup(name="foo", srcs=["foo.txt"])
+ genrule(name="gen", srcs=[], outs=["gen.txt"], cmd="exit 1")
+ """);
+
+ scratch.file(
+ "a/a.bzl",
+ """
+ DictInfo = provider(fields=["dict"])
+
+ def _a_impl(ctx):
+ return [DictInfo(dict = ctx.attr.dict)]
+
+ a = rule(implementation=_a_impl, attrs={"dict": attr.string_keyed_label_dict()})
+ """);
+
+ ConfiguredTarget a = getConfiguredTarget("//a:a");
+ StructImpl info =
+ (StructImpl)
+ a.get(
+ new StarlarkProvider.Key(
+ keyForBuild(Label.parseCanonical("//a:a.bzl")), "DictInfo"));
+ Map<String, ConfiguredTarget> dict = (Map<String, ConfiguredTarget>) info.getValue("dict");
+ assertThat(dict.keySet()).containsExactly("foo_key", "gen_key");
+ assertThat(dict.get("foo_key").getLabel()).isEqualTo(Label.parseCanonicalUnchecked("//a:foo"));
+ assertThat(dict.get("gen_key").getLabel()).isEqualTo(Label.parseCanonicalUnchecked("//a:gen"));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
public void initializer_labelKeyedStringDict() throws Exception {
scratch.file(
"BUILD", //