Skip to content

Commit cdf01a3

Browse files
fmeumcopybara-github
authored andcommitted
Allow string_list flags to be set via repeated flag uses
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 #13817 Closes #14911. PiperOrigin-RevId: 462146752 Change-Id: I91ddc4328a1b2244f4303fcda7b4228b182a5d56
1 parent dcd8585 commit cdf01a3

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

+7-3
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

+11-4
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

+6-2
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

+20-6
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

+1-1
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

+45
Original file line numberDiff line numberDiff line change
@@ -1774,6 +1774,51 @@ public void buildsettings_allowMultipleWorks() throws Exception {
17741774
assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue();
17751775
}
17761776

1777+
@Test
1778+
public void buildsettings_repeatableWorks() throws Exception {
1779+
scratch.file(
1780+
"test/build_settings.bzl",
1781+
"def _impl(ctx):",
1782+
" return []",
1783+
"string_list_flag = rule(",
1784+
" implementation = _impl,",
1785+
" build_setting = config.string_list(flag = True, repeatable = True),",
1786+
")");
1787+
scratch.file(
1788+
"test/BUILD",
1789+
"load('//test:build_settings.bzl', 'string_list_flag')",
1790+
"config_setting(",
1791+
" name = 'match',",
1792+
" flag_values = {",
1793+
" ':cheese': 'pepperjack',",
1794+
" },",
1795+
")",
1796+
"string_list_flag(name = 'cheese', build_setting_default = ['gouda'])");
1797+
1798+
useConfiguration(ImmutableMap.of("//test:cheese", ImmutableList.of("pepperjack", "brie")));
1799+
assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue();
1800+
}
1801+
1802+
@Test
1803+
public void buildsettings_repeatableWithoutFlagErrors() throws Exception {
1804+
scratch.file(
1805+
"test/build_settings.bzl",
1806+
"def _impl(ctx):",
1807+
" return []",
1808+
"string_list_setting = rule(",
1809+
" implementation = _impl,",
1810+
" build_setting = config.string_list(repeatable = True),",
1811+
")");
1812+
scratch.file(
1813+
"test/BUILD",
1814+
"load('//test:build_settings.bzl', 'string_list_setting')",
1815+
"string_list_setting(name = 'cheese', build_setting_default = ['gouda'])");
1816+
1817+
reporter.removeHandler(failFastHandler);
1818+
getConfiguredTarget("//test:cheese");
1819+
assertContainsEvent("'repeatable' can only be set for a setting with 'flag = True'");
1820+
}
1821+
17771822
@Test
17781823
public void notBuildSettingOrFeatureFlag() throws Exception {
17791824
scratch.file(

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

+23
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

+60
Original file line numberDiff line numberDiff line change
@@ -3024,6 +3024,66 @@ public void testBuildSettingValue_allowMultipleSetting() throws Exception {
30243024
.containsExactly("some-other-value", "some-other-other-value");
30253025
}
30263026

3027+
@SuppressWarnings("unchecked")
3028+
@Test
3029+
public void testBuildSettingValue_isRepeatedSetting() throws Exception {
3030+
scratch.file(
3031+
"test/build_setting.bzl",
3032+
"BuildSettingInfo = provider(fields = ['name', 'value'])",
3033+
"def _impl(ctx):",
3034+
" return [BuildSettingInfo(name = ctx.attr.name, value = ctx.build_setting_value)]",
3035+
"",
3036+
"string_list_flag = rule(",
3037+
" implementation = _impl,",
3038+
" build_setting = config.string_list(flag = True, repeatable = True),",
3039+
")");
3040+
scratch.file(
3041+
"test/BUILD",
3042+
"load('//test:build_setting.bzl', 'string_list_flag')",
3043+
"string_list_flag(name = 'string_list_flag', build_setting_default = ['some-value'])");
3044+
3045+
// from default
3046+
ConfiguredTarget buildSetting = getConfiguredTarget("//test:string_list_flag");
3047+
Provider.Key key =
3048+
new StarlarkProvider.Key(
3049+
Label.create(buildSetting.getLabel().getPackageIdentifier(), "build_setting.bzl"),
3050+
"BuildSettingInfo");
3051+
StructImpl buildSettingInfo = (StructImpl) buildSetting.get(key);
3052+
3053+
assertThat(buildSettingInfo.getValue("value")).isInstanceOf(List.class);
3054+
assertThat((List<String>) buildSettingInfo.getValue("value")).containsExactly("some-value");
3055+
3056+
// Set multiple times
3057+
useConfiguration(
3058+
ImmutableMap.of(
3059+
"//test:string_list_flag",
3060+
ImmutableList.of("some-other-value", "some-other-other-value")));
3061+
buildSetting = getConfiguredTarget("//test:string_list_flag");
3062+
key =
3063+
new StarlarkProvider.Key(
3064+
Label.create(buildSetting.getLabel().getPackageIdentifier(), "build_setting.bzl"),
3065+
"BuildSettingInfo");
3066+
buildSettingInfo = (StructImpl) buildSetting.get(key);
3067+
3068+
assertThat(buildSettingInfo.getValue("value")).isInstanceOf(List.class);
3069+
assertThat((List<String>) buildSettingInfo.getValue("value"))
3070+
.containsExactly("some-other-value", "some-other-other-value");
3071+
3072+
// No splitting on comma.
3073+
useConfiguration(
3074+
ImmutableMap.of("//test:string_list_flag", ImmutableList.of("a,b,c", "a", "b,c")));
3075+
buildSetting = getConfiguredTarget("//test:string_list_flag");
3076+
key =
3077+
new StarlarkProvider.Key(
3078+
Label.create(buildSetting.getLabel().getPackageIdentifier(), "build_setting.bzl"),
3079+
"BuildSettingInfo");
3080+
buildSettingInfo = (StructImpl) buildSetting.get(key);
3081+
3082+
assertThat(buildSettingInfo.getValue("value")).isInstanceOf(List.class);
3083+
assertThat((List<String>) buildSettingInfo.getValue("value"))
3084+
.containsExactly("a,b,c", "a", "b,c");
3085+
}
3086+
30273087
@Test
30283088
public void testBuildSettingValue_nonBuildSettingRule() throws Exception {
30293089
scratch.file(

0 commit comments

Comments
 (0)