Skip to content

Commit 9c2c3de

Browse files
ckolli5fmeum
andauthored
Allow string_list flags to be set via repeated flag uses (bazelbuild#15943)
For all parts of Bazel other than option parsing, string build setting flags with `allow_multiple = True` should behave just like `string_list` settings, even though they are fundamentally of a different type. This requires a lot of special casing all over the code base and also causes confusing user-facing behavior when transitioning on such settings. This commit deprecates the `allow_multiple` named parameter of `config.string` and as a replacement introduces a new named parameter `repeatable` on `config.string_list`. Settings with the latter set to True behave exactly like `string` settings with `allow_multiple = True` with respect to command-line option parsing and exactly like ordinary `string_list` flags in all other situations. Fixes bazelbuild#13817 Closes bazelbuild#14911. PiperOrigin-RevId: 462146752 Change-Id: I91ddc4328a1b2244f4303fcda7b4228b182a5d56 Co-authored-by: Fabian Meumertzheim <[email protected]>
1 parent aaf19a8 commit 9c2c3de

File tree

8 files changed

+173
-16
lines changed

8 files changed

+173
-16
lines changed

src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkConfig.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.devtools.build.lib.analysis.config.ExecutionTransitionFactory;
2323
import com.google.devtools.build.lib.packages.BuildSetting;
2424
import com.google.devtools.build.lib.starlarkbuildapi.StarlarkConfigApi;
25+
import net.starlark.java.eval.EvalException;
2526
import net.starlark.java.eval.Printer;
2627
import net.starlark.java.eval.Starlark;
2728

@@ -40,12 +41,15 @@ public BuildSetting boolSetting(Boolean flag) {
4041

4142
@Override
4243
public BuildSetting stringSetting(Boolean flag, Boolean allowMultiple) {
43-
return BuildSetting.create(flag, STRING, allowMultiple);
44+
return BuildSetting.create(flag, STRING, allowMultiple, false);
4445
}
4546

4647
@Override
47-
public BuildSetting stringListSetting(Boolean flag) {
48-
return BuildSetting.create(flag, STRING_LIST);
48+
public BuildSetting stringListSetting(Boolean flag, Boolean repeatable) throws EvalException {
49+
if (repeatable && !flag) {
50+
throw Starlark.errorf("'repeatable' can only be set for a setting with 'flag = True'");
51+
}
52+
return BuildSetting.create(flag, STRING_LIST, false, repeatable);
4953
}
5054

5155
@Override

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,25 @@ public class BuildSetting implements BuildSettingApi {
2727
private final boolean isFlag;
2828
private final Type<?> type;
2929
private final boolean allowMultiple;
30+
private final boolean repeatable;
3031

31-
private BuildSetting(boolean isFlag, Type<?> type, boolean allowMultiple) {
32+
private BuildSetting(boolean isFlag, Type<?> type, boolean allowMultiple, boolean repeatable) {
3233
this.isFlag = isFlag;
3334
this.type = type;
3435
this.allowMultiple = allowMultiple;
36+
this.repeatable = repeatable;
3537
}
3638

37-
public static BuildSetting create(boolean isFlag, Type<?> type, boolean allowMultiple) {
38-
return new BuildSetting(isFlag, type, allowMultiple);
39+
public static BuildSetting create(
40+
boolean isFlag, Type<?> type, boolean allowMultiple, boolean repeatable) {
41+
return new BuildSetting(isFlag, type, allowMultiple, repeatable);
3942
}
4043

4144
public static BuildSetting create(boolean isFlag, Type<?> type) {
4245
Preconditions.checkState(
4346
type.getLabelClass() != LabelClass.DEPENDENCY,
4447
"Build settings should not create a dependency with their default attribute");
45-
return new BuildSetting(isFlag, type, /* allowMultiple= */ false);
48+
return new BuildSetting(isFlag, type, /* allowMultiple= */ false, false);
4649
}
4750

4851
public Type<?> getType() {
@@ -58,6 +61,10 @@ public boolean allowsMultiple() {
5861
return allowMultiple;
5962
}
6063

64+
public boolean isRepeatableFlag() {
65+
return repeatable;
66+
}
67+
6168
@Override
6269
public void repr(Printer printer) {
6370
printer.append("<build_setting." + type + ">");

src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ public void parse(ExtendedEventHandler eventHandler) throws OptionsParsingExcept
168168
String.format("Unrecognized option: %s=%s", loadedFlag, unparsedValue));
169169
}
170170
Type<?> type = buildSetting.getType();
171+
if (buildSetting.isRepeatableFlag()) {
172+
type = Preconditions.checkNotNull(type.getListElementType());
173+
}
171174
Converter<?> converter = BUILD_SETTING_CONVERTERS.get(type);
172175
Object value;
173176
try {
@@ -179,7 +182,7 @@ public void parse(ExtendedEventHandler eventHandler) throws OptionsParsingExcept
179182
loadedFlag, unparsedValue, unparsedValue, type),
180183
e);
181184
}
182-
if (buildSetting.allowsMultiple()) {
185+
if (buildSetting.allowsMultiple() || buildSetting.isRepeatableFlag()) {
183186
List<Object> newValue;
184187
if (buildSettingWithTargetAndValue.containsKey(loadedFlag)) {
185188
newValue =
@@ -371,7 +374,8 @@ public OptionsParser getNativeOptionsParserFortesting() {
371374
}
372375

373376
public boolean checkIfParsedOptionAllowsMultiple(String option) {
374-
return parsedBuildSettings.get(option).allowsMultiple();
377+
BuildSetting setting = parsedBuildSettings.get(option);
378+
return setting.allowsMultiple() || setting.isRepeatableFlag();
375379
}
376380

377381
public Type<?> getParsedOptionType(String option) {

src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkConfigApi.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import net.starlark.java.annot.ParamType;
2020
import net.starlark.java.annot.StarlarkBuiltin;
2121
import net.starlark.java.annot.StarlarkMethod;
22+
import net.starlark.java.eval.EvalException;
2223
import net.starlark.java.eval.NoneType;
2324
import net.starlark.java.eval.StarlarkValue;
2425

@@ -90,11 +91,13 @@ public interface StarlarkConfigApi extends StarlarkValue {
9091
name = "allow_multiple",
9192
defaultValue = "False",
9293
doc =
93-
"If set, this flag is allowed to be set multiple times on the command line. The"
94-
+ " Value of the flag as accessed in transitions and build setting"
95-
+ " implementation function will be a list of strings. Insertion order and"
96-
+ " repeated values are both maintained. This list can be post-processed in the"
97-
+ " build setting implementation function if different behavior is desired.",
94+
"Deprecated, use a <code>string_list</code> setting with"
95+
+ " <code>repeatable = True</code> instead. If set, this flag is allowed to be"
96+
+ " set multiple times on the command line. The Value of the flag as accessed"
97+
+ " in transitions and build setting implementation function will be a list of"
98+
+ " strings. Insertion order and repeated values are both maintained. This list"
99+
+ " can be post-processed in the build setting implementation function if"
100+
+ " different behavior is desired.",
98101
named = true,
99102
positional = false)
100103
})
@@ -111,9 +114,20 @@ public interface StarlarkConfigApi extends StarlarkValue {
111114
defaultValue = "False",
112115
doc = FLAG_ARG_DOC,
113116
named = true,
117+
positional = false),
118+
@Param(
119+
name = "repeatable",
120+
defaultValue = "False",
121+
doc =
122+
"If set, instead of expecting a comma-separated value, this flag is allowed to be"
123+
+ " set multiple times on the command line with each individual value treated"
124+
+ " as a single string to add to the list value. Insertion order and repeated"
125+
+ " values are both maintained. This list can be post-processed in the build"
126+
+ " setting implementation function if different behavior is desired.",
127+
named = true,
114128
positional = false)
115129
})
116-
BuildSettingApi stringListSetting(Boolean flag);
130+
BuildSettingApi stringListSetting(Boolean flag, Boolean repeatable) throws EvalException;
117131

118132
/** The API for build setting descriptors. */
119133
@StarlarkBuiltin(

src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeConfigApi.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public BuildSettingApi stringSetting(Boolean flag, Boolean allowMultiple) {
3838
}
3939

4040
@Override
41-
public BuildSettingApi stringListSetting(Boolean flag) {
41+
public BuildSettingApi stringListSetting(Boolean flag, Boolean repeated) {
4242
return new FakeBuildSettingDescriptor();
4343
}
4444

src/test/java/com/google/devtools/build/lib/rules/config/ConfigSettingTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,6 +1534,51 @@ public void buildsettings_allowMultipleWorks() throws Exception {
15341534
assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue();
15351535
}
15361536

1537+
@Test
1538+
public void buildsettings_repeatableWorks() throws Exception {
1539+
scratch.file(
1540+
"test/build_settings.bzl",
1541+
"def _impl(ctx):",
1542+
" return []",
1543+
"string_list_flag = rule(",
1544+
" implementation = _impl,",
1545+
" build_setting = config.string_list(flag = True, repeatable = True),",
1546+
")");
1547+
scratch.file(
1548+
"test/BUILD",
1549+
"load('//test:build_settings.bzl', 'string_list_flag')",
1550+
"config_setting(",
1551+
" name = 'match',",
1552+
" flag_values = {",
1553+
" ':cheese': 'pepperjack',",
1554+
" },",
1555+
")",
1556+
"string_list_flag(name = 'cheese', build_setting_default = ['gouda'])");
1557+
1558+
useConfiguration(ImmutableMap.of("//test:cheese", ImmutableList.of("pepperjack", "brie")));
1559+
assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue();
1560+
}
1561+
1562+
@Test
1563+
public void buildsettings_repeatableWithoutFlagErrors() throws Exception {
1564+
scratch.file(
1565+
"test/build_settings.bzl",
1566+
"def _impl(ctx):",
1567+
" return []",
1568+
"string_list_setting = rule(",
1569+
" implementation = _impl,",
1570+
" build_setting = config.string_list(repeatable = True),",
1571+
")");
1572+
scratch.file(
1573+
"test/BUILD",
1574+
"load('//test:build_settings.bzl', 'string_list_setting')",
1575+
"string_list_setting(name = 'cheese', build_setting_default = ['gouda'])");
1576+
1577+
reporter.removeHandler(failFastHandler);
1578+
getConfiguredTarget("//test:cheese");
1579+
assertContainsEvent("'repeatable' can only be set for a setting with 'flag = True'");
1580+
}
1581+
15371582
@Test
15381583
public void notBuildSettingOrFeatureFlag() throws Exception {
15391584
scratch.file(

src/test/java/com/google/devtools/build/lib/starlark/StarlarkOptionsParsingTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,4 +478,27 @@ public void testAllowMultipleStringFlag() throws Exception {
478478
assertThat((List<String>) result.getStarlarkOptions().get("//test:cats"))
479479
.containsExactly("calico", "bengal");
480480
}
481+
482+
@Test
483+
@SuppressWarnings("unchecked")
484+
public void testRepeatedStringListFlag() throws Exception {
485+
scratch.file(
486+
"test/build_setting.bzl",
487+
"def _build_setting_impl(ctx):",
488+
" return []",
489+
"repeated_flag = rule(",
490+
" implementation = _build_setting_impl,",
491+
" build_setting = config.string_list(flag=True, repeatable=True)",
492+
")");
493+
scratch.file(
494+
"test/BUILD",
495+
"load('//test:build_setting.bzl', 'repeated_flag')",
496+
"repeated_flag(name = 'cats', build_setting_default = ['tabby'])");
497+
498+
OptionsParsingResult result = parseStarlarkOptions("--//test:cats=calico --//test:cats=bengal");
499+
500+
assertThat(result.getStarlarkOptions().keySet()).containsExactly("//test:cats");
501+
assertThat((List<String>) result.getStarlarkOptions().get("//test:cats"))
502+
.containsExactly("calico", "bengal");
503+
}
481504
}

src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2960,6 +2960,66 @@ public void testBuildSettingValue_allowMultipleSetting() throws Exception {
29602960
.containsExactly("some-other-value", "some-other-other-value");
29612961
}
29622962

2963+
@SuppressWarnings("unchecked")
2964+
@Test
2965+
public void testBuildSettingValue_isRepeatedSetting() throws Exception {
2966+
scratch.file(
2967+
"test/build_setting.bzl",
2968+
"BuildSettingInfo = provider(fields = ['name', 'value'])",
2969+
"def _impl(ctx):",
2970+
" return [BuildSettingInfo(name = ctx.attr.name, value = ctx.build_setting_value)]",
2971+
"",
2972+
"string_list_flag = rule(",
2973+
" implementation = _impl,",
2974+
" build_setting = config.string_list(flag = True, repeatable = True),",
2975+
")");
2976+
scratch.file(
2977+
"test/BUILD",
2978+
"load('//test:build_setting.bzl', 'string_list_flag')",
2979+
"string_list_flag(name = 'string_list_flag', build_setting_default = ['some-value'])");
2980+
2981+
// from default
2982+
ConfiguredTarget buildSetting = getConfiguredTarget("//test:string_list_flag");
2983+
Provider.Key key =
2984+
new StarlarkProvider.Key(
2985+
Label.create(buildSetting.getLabel().getPackageIdentifier(), "build_setting.bzl"),
2986+
"BuildSettingInfo");
2987+
StructImpl buildSettingInfo = (StructImpl) buildSetting.get(key);
2988+
2989+
assertThat(buildSettingInfo.getValue("value")).isInstanceOf(List.class);
2990+
assertThat((List<String>) buildSettingInfo.getValue("value")).containsExactly("some-value");
2991+
2992+
// Set multiple times
2993+
useConfiguration(
2994+
ImmutableMap.of(
2995+
"//test:string_list_flag",
2996+
ImmutableList.of("some-other-value", "some-other-other-value")));
2997+
buildSetting = getConfiguredTarget("//test:string_list_flag");
2998+
key =
2999+
new StarlarkProvider.Key(
3000+
Label.create(buildSetting.getLabel().getPackageIdentifier(), "build_setting.bzl"),
3001+
"BuildSettingInfo");
3002+
buildSettingInfo = (StructImpl) buildSetting.get(key);
3003+
3004+
assertThat(buildSettingInfo.getValue("value")).isInstanceOf(List.class);
3005+
assertThat((List<String>) buildSettingInfo.getValue("value"))
3006+
.containsExactly("some-other-value", "some-other-other-value");
3007+
3008+
// No splitting on comma.
3009+
useConfiguration(
3010+
ImmutableMap.of("//test:string_list_flag", ImmutableList.of("a,b,c", "a", "b,c")));
3011+
buildSetting = getConfiguredTarget("//test:string_list_flag");
3012+
key =
3013+
new StarlarkProvider.Key(
3014+
Label.create(buildSetting.getLabel().getPackageIdentifier(), "build_setting.bzl"),
3015+
"BuildSettingInfo");
3016+
buildSettingInfo = (StructImpl) buildSetting.get(key);
3017+
3018+
assertThat(buildSettingInfo.getValue("value")).isInstanceOf(List.class);
3019+
assertThat((List<String>) buildSettingInfo.getValue("value"))
3020+
.containsExactly("a,b,c", "a", "b,c");
3021+
}
3022+
29633023
@Test
29643024
public void testBuildSettingValue_nonBuildSettingRule() throws Exception {
29653025
scratch.file(

0 commit comments

Comments
 (0)