Skip to content

Commit 1474b5b

Browse files
Allow multiple matching select branches if they resolve to the same value (#18066)
Select branches that map to the same value can unambiguosly be resolved even when multiple are true. This is currently not allowed and requires the use of constructs like `bazel-skylib`'s `selects.config_setting_group`. With this change, assuming A and B are true, the following is allowed: ``` select({ "//:A": "Value", "//:B": "Value", }) ``` Closes #14874. PiperOrigin-RevId: 523597091 Change-Id: I751809b1b9e11d20a86ae2af4bbdb0228fd3c670 Co-authored-by: Alessandro Patti <[email protected]>
1 parent b9e6f7d commit 1474b5b

File tree

5 files changed

+60
-19
lines changed

5 files changed

+60
-19
lines changed

site/en/configure/attributes.md

+8-6
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,13 @@ command line. Specifically, `deps` becomes:
7171
targets. By using `select()` in a configurable attribute, the attribute
7272
effectively adopts different values when different conditions hold.
7373

74-
Matches must be unambiguous: either exactly one condition must match or, if
75-
multiple conditions match, one's `values` must be a strict superset of all
76-
others'. For example, `values = {"cpu": "x86", "compilation_mode": "dbg"}` is an
77-
unambiguous specialization of `values = {"cpu": "x86"}`. The built-in condition
78-
[`//conditions:default`](#default-condition) automatically matches when
74+
Matches must be unambiguous: if multiple conditions match then either
75+
* They all resolve to the same value. For example, when running on linux x86, this is unambiguous
76+
`{"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"}` because both branches resolve to "hello".
77+
* One's `values` is a strict superset of all others'. For example, `values = {"cpu": "x86", "compilation_mode": "dbg"}`
78+
is an unambiguous specialization of `values = {"cpu": "x86"}`.
79+
80+
The built-in condition [`//conditions:default`](#default-condition) automatically matches when
7981
nothing else does.
8082

8183
While this example uses `deps`, `select()` works just as well on `srcs`,
@@ -516,7 +518,7 @@ Unlike `selects.with_or`, different targets can share `:config1_or_2` across
516518
different attributes.
517519
518520
It's an error for multiple conditions to match unless one is an unambiguous
519-
"specialization" of the others. See [here](#configurable-build-example) for details.
521+
"specialization" of the others or they all resolve to the same value. See [here](#configurable-build-example) for details.
520522

521523
## AND chaining {:#and-chaining}
522524

site/en/docs/configurable-attributes.md

+8-6
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,13 @@ command line. Specifically, `deps` becomes:
7171
targets. By using `select()` in a configurable attribute, the attribute
7272
effectively adopts different values when different conditions hold.
7373

74-
Matches must be unambiguous: either exactly one condition must match or, if
75-
multiple conditions match, one's `values` must be a strict superset of all
76-
others'. For example, `values = {"cpu": "x86", "compilation_mode": "dbg"}` is an
77-
unambiguous specialization of `values = {"cpu": "x86"}`. The built-in condition
78-
[`//conditions:default`](#default-condition) automatically matches when
74+
Matches must be unambiguous: if multiple conditions match then either
75+
* They all resolve to the same value. For example, when running on linux x86, this is unambiguous
76+
`{"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"}` because both branches resolve to "hello".
77+
* One's `values` is a strict superset of all others'. For example, `values = {"cpu": "x86", "compilation_mode": "dbg"}`
78+
is an unambiguous specialization of `values = {"cpu": "x86"}`.
79+
80+
The built-in condition [`//conditions:default`](#default-condition) automatically matches when
7981
nothing else does.
8082

8183
While this example uses `deps`, `select()` works just as well on `srcs`,
@@ -516,7 +518,7 @@ Unlike `selects.with_or`, different targets can share `:config1_or_2` across
516518
different attributes.
517519
518520
It's an error for multiple conditions to match unless one is an unambiguous
519-
"specialization" of the others. See [here](#configurable-build-example) for details.
521+
"specialization" of the others or they all resolve to the same value. See [here](#configurable-build-example) for details.
520522

521523
## AND chaining {:#and-chaining}
522524

src/main/java/com/google/devtools/build/docgen/templates/be/functions.vm

+1-1
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ sh_binary(
672672
demonstrated in Example 2 below.
673673
</li>
674674
<li>If multiple conditions match and one is not a specialization of all the
675-
others, Bazel fails with an error.
675+
others, Bazel fails with an error, unless all conditions resolve to the same value.
676676
</li>
677677
<li>The special pseudo-label <code>//conditions:default</code> is
678678
considered to match if no other condition matches. If this condition

src/main/java/com/google/devtools/build/lib/packages/ConfiguredAttributeMapper.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,10 @@ private static class ConfigKeyAndValue<T> {
176176

177177
private <T> ConfigKeyAndValue<T> resolveSelector(String attributeName, Selector<T> selector)
178178
throws ValidationException {
179-
Map<Label, ConfigKeyAndValue<T>> matchingConditions = new LinkedHashMap<>();
179+
// Use a LinkedHashMap to guarantee a deterministic branch selection when multiple branches
180+
// matches but they
181+
// resolve to the same value.
182+
LinkedHashMap<Label, ConfigKeyAndValue<T>> matchingConditions = new LinkedHashMap<>();
180183
// Use a LinkedHashSet to guarantee deterministic error message ordering. We use a LinkedHashSet
181184
// vs. a more general SortedSet because the latter supports insertion-order, which should more
182185
// closely match how users see select() structures in BUILD files.
@@ -220,17 +223,19 @@ private <T> ConfigKeyAndValue<T> resolveSelector(String attributeName, Selector<
220223
}
221224
}
222225

223-
if (matchingConditions.size() > 1) {
226+
if (matchingConditions.values().stream().map(s -> s.value).distinct().count() > 1) {
224227
throw new ValidationException(
225228
"Illegal ambiguous match on configurable attribute \""
226229
+ attributeName
227230
+ "\" in "
228231
+ getLabel()
229232
+ ":\n"
230233
+ Joiner.on("\n").join(matchingConditions.keySet())
231-
+ "\nMultiple matches are not allowed unless one is unambiguously more specialized.");
232-
} else if (matchingConditions.size() == 1) {
233-
return Iterables.getOnlyElement(matchingConditions.values());
234+
+ "\nMultiple matches are not allowed unless one is unambiguously "
235+
+ "more specialized or they resolve to the same value. "
236+
+ "See https://bazel.build/reference/be/functions#select.");
237+
} else if (matchingConditions.size() > 0) {
238+
return Iterables.getFirst(matchingConditions.values(), null);
234239
}
235240

236241
// If nothing matched, choose the default condition.

src/test/java/com/google/devtools/build/lib/analysis/ConfigurableAttributesTest.java

+33-1
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,8 @@ public void multipleMatches() throws Exception {
642642
"Illegal ambiguous match on configurable attribute \"srcs\" in //a:gen:\n"
643643
+ "//conditions:dup1\n"
644644
+ "//conditions:dup2\n"
645-
+ "Multiple matches are not allowed unless one is unambiguously more specialized.");
645+
+ "Multiple matches are not allowed unless one is unambiguously more specialized "
646+
+ "or they resolve to the same value.");
646647
}
647648

648649
/**
@@ -688,6 +689,37 @@ public void multipleMatchesConditionAndSubcondition() throws Exception {
688689
"bin java/a/libgeneric.jar", "bin java/a/libprecise.jar"));
689690
}
690691

692+
/** Tests that multiple matches are allowed for conditions where the value is the same. */
693+
@Test
694+
public void multipleMatchesSameValue() throws Exception {
695+
reporter.removeHandler(failFastHandler); // Expect errors.
696+
scratch.file(
697+
"conditions/BUILD",
698+
"config_setting(",
699+
" name = 'dup1',",
700+
" values = {'compilation_mode': 'opt'})",
701+
"config_setting(",
702+
" name = 'dup2',",
703+
" values = {'define': 'foo=bar'})");
704+
scratch.file(
705+
"a/BUILD",
706+
"genrule(",
707+
" name = 'gen',",
708+
" cmd = '',",
709+
" outs = ['gen.out'],",
710+
" srcs = select({",
711+
" '//conditions:dup1': ['a.in'],",
712+
" '//conditions:dup2': ['a.in'],",
713+
" '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': [':default.in'],",
714+
" }))");
715+
checkRule(
716+
"//a:gen",
717+
"srcs",
718+
ImmutableList.of("-c", "opt", "--define", "foo=bar"),
719+
/*expected:*/ ImmutableList.of("src a/a.in"),
720+
/*not expected:*/ ImmutableList.of("src a/default.in"));
721+
}
722+
691723
/**
692724
* Tests that when multiple conditions match but one condition is more specialized than the
693725
* others, it is chosen and there is no error.

0 commit comments

Comments
 (0)