Skip to content

Commit 3ebca6e

Browse files
committed
Do not validate input-only settings in transitions
Starlark transition logic temporarily explicitly sets all input build settings of a transition to their defaults. Since #13971, these values are cleared after the transition. However, since then they have also been subject to type validation, which is not only unnecessary, but also breaks in the special case of a string build setting with allow_multiple. With this commit, input-only build settings are unconditionally stripped from the post-transition BuildOptions and do not undergo validation. This is made possible by a refactoring of `StarlarkTransition#validate` that extracts the validation logic into a separate function and also improves some variable names.
1 parent a9fb0fc commit 3ebca6e

File tree

2 files changed

+154
-72
lines changed

2 files changed

+154
-72
lines changed

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

+97-72
Original file line numberDiff line numberDiff line change
@@ -267,31 +267,38 @@ public static Map<String, BuildOptions> validate(
267267
Map<PackageValue.Key, PackageValue> buildSettingPackages,
268268
Map<String, BuildOptions> toOptions)
269269
throws TransitionException {
270-
// Collect settings changed during this transition and their types. This includes settings that
271-
// were only used as inputs as to the transition and thus had their default values added to the
272-
// fromOptions, which in case of a no-op transition directly end up in toOptions.
273-
Map<Label, Rule> changedSettingToRule = Maps.newHashMap();
270+
// Collect settings that are input or outputs of the transition together with their types.
271+
// Output setting values will be validated and cleaned if set to the default.
272+
Map<Label, Rule> inputAndOutputSettingsToRule = Maps.newHashMap();
273+
// Collect settings that were only used as inputs to the transition and thus had their default
274+
// values added to the fromOptions. They always have to be cleaned.
275+
Set<Label> inputOnlySettings = Sets.newHashSet();
274276
root.visit(
275277
(StarlarkTransitionVisitor)
276278
transition -> {
277-
ImmutableSet<Label> changedSettings =
279+
ImmutableSet<Label> inputAndOutputSettings =
278280
getRelevantStarlarkSettingsFromTransition(
279281
transition, Settings.INPUTS_AND_OUTPUTS);
280-
for (Label setting : changedSettings) {
281-
changedSettingToRule.put(
282+
ImmutableSet<Label> outputSettings =
283+
getRelevantStarlarkSettingsFromTransition(transition, Settings.OUTPUTS);
284+
for (Label setting : inputAndOutputSettings) {
285+
inputAndOutputSettingsToRule.put(
282286
setting, getActual(buildSettingPackages, setting).getAssociatedRule());
287+
if (!outputSettings.contains(setting)) {
288+
inputOnlySettings.add(setting);
289+
}
283290
}
284291
});
285292

286293
// Return early if no starlark settings were affected.
287-
if (changedSettingToRule.isEmpty()) {
294+
if (inputAndOutputSettingsToRule.isEmpty()) {
288295
return toOptions;
289296
}
290297

291298
ImmutableMap.Builder<Label, Label> aliasToActualBuilder = new ImmutableMap.Builder<>();
292-
for (Map.Entry<Label, Rule> changedSettingWithRule : changedSettingToRule.entrySet()) {
293-
Label setting = changedSettingWithRule.getKey();
294-
Rule rule = changedSettingWithRule.getValue();
299+
for (Map.Entry<Label, Rule> settingWithRule : inputAndOutputSettingsToRule.entrySet()) {
300+
Label setting = settingWithRule.getKey();
301+
Rule rule = settingWithRule.getValue();
295302
if (!rule.getLabel().equals(setting)) {
296303
aliasToActualBuilder.put(setting, rule.getLabel());
297304
}
@@ -307,69 +314,19 @@ public static Map<String, BuildOptions> validate(
307314
BuildOptions.Builder cleanedOptions = null;
308315
// Clean up aliased values.
309316
BuildOptions options = unalias(entry.getValue(), aliasToActual);
310-
for (Map.Entry<Label, Rule> changedSettingWithRule : changedSettingToRule.entrySet()) {
317+
for (Map.Entry<Label, Rule> settingWithRule : inputAndOutputSettingsToRule.entrySet()) {
311318
// If the build setting was referenced in the transition via an alias, this is that alias
312-
Label maybeAliasSetting = changedSettingWithRule.getKey();
313-
Rule rule = changedSettingWithRule.getValue();
314-
// If the build setting was *not* referenced in the transition by an alias, this is the same
315-
// value as {@code maybeAliasSetting} above.
316-
Label actualSetting = rule.getLabel();
317-
Object newValue = options.getStarlarkOptions().get(actualSetting);
318-
// TODO(b/154132845): fix NPE occasionally observed here.
319-
Preconditions.checkState(
320-
newValue != null,
321-
"Error while attempting to validate new values from starlark"
322-
+ " transition(s) with the outputs %s. Post-transition configuration should include"
323-
+ " '%s' but only includes starlark options: %s. If you run into this error"
324-
+ " please ping b/154132845 or email [email protected].",
325-
changedSettingToRule.keySet(),
326-
actualSetting,
327-
options.getStarlarkOptions().keySet());
328-
boolean allowsMultiple = rule.getRuleClassObject().getBuildSetting().allowsMultiple();
329-
if (allowsMultiple) {
330-
// if this setting allows multiple settings
331-
if (!(newValue instanceof List)) {
332-
throw new TransitionException(
333-
String.format(
334-
"'%s' allows multiple values and must be set"
335-
+ " in transition using a starlark list instead of single value '%s'",
336-
actualSetting, newValue));
337-
}
338-
List<?> rawNewValueAsList = (List<?>) newValue;
339-
List<Object> convertedValue = new ArrayList<>();
340-
Type<?> type = rule.getRuleClassObject().getBuildSetting().getType();
341-
for (Object value : rawNewValueAsList) {
342-
try {
343-
convertedValue.add(type.convert(value, maybeAliasSetting));
344-
} catch (ConversionException e) {
345-
throw new TransitionException(e);
346-
}
347-
}
348-
if (convertedValue.equals(
349-
ImmutableList.of(rule.getAttr(STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME)))) {
350-
if (cleanedOptions == null) {
351-
cleanedOptions = options.toBuilder();
352-
}
353-
cleanedOptions.removeStarlarkOption(rule.getLabel());
354-
}
355-
} else {
356-
// if this setting does not allow multiple settings
357-
Object convertedValue;
358-
try {
359-
convertedValue =
360-
rule.getRuleClassObject()
361-
.getBuildSetting()
362-
.getType()
363-
.convert(newValue, maybeAliasSetting);
364-
} catch (ConversionException e) {
365-
throw new TransitionException(e);
366-
}
367-
if (convertedValue.equals(rule.getAttr(STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME))) {
368-
if (cleanedOptions == null) {
369-
cleanedOptions = options.toBuilder();
370-
}
371-
cleanedOptions.removeStarlarkOption(rule.getLabel());
319+
Label maybeAliasSetting = settingWithRule.getKey();
320+
Rule rule = settingWithRule.getValue();
321+
// Input-only settings had their default value added to the BuildOptions and thus always
322+
// have to be cleaned, whereas output settings have their value validated, converted and
323+
// stripped if it is equivalent to the setting's default value.
324+
if (inputOnlySettings.contains(maybeAliasSetting) || validateAndCheckIfAtDefault(
325+
rule, options, maybeAliasSetting, inputAndOutputSettingsToRule.keySet())) {
326+
if (cleanedOptions == null) {
327+
cleanedOptions = options.toBuilder();
372328
}
329+
cleanedOptions.removeStarlarkOption(rule.getLabel());
373330
}
374331
}
375332
// Keep the same instance if we didn't do anything to maintain reference equality later on.
@@ -381,6 +338,74 @@ public static Map<String, BuildOptions> validate(
381338
return cleanedOptionMap.buildOrThrow();
382339
}
383340

341+
/**
342+
* Validate the value of a particular build setting after a transition has been applied.
343+
* @param buildSettingRule the build setting to validate.
344+
* @param options the {@link BuildOptions} reflecting the post-transition configuration.
345+
* @param maybeAliasSetting the label used to refer to the build setting in the transition,
346+
* possibly an alias. This is only used for error messages.
347+
* @param inputAndOutputSettings the transition input and output settings. This is only used for
348+
* error messages.
349+
* @return {@code true} if and only if the setting is set to its default value after the
350+
* transition.
351+
* @throws TransitionException if the value returned by the transition for this setting has an
352+
* invalid type.
353+
*/
354+
private static boolean validateAndCheckIfAtDefault(Rule buildSettingRule, BuildOptions options,
355+
Label maybeAliasSetting, Set<Label> inputAndOutputSettings) throws TransitionException {
356+
// If the build setting was *not* referenced in the transition by an alias, this is the same
357+
// value as {@code maybeAliasSetting}.
358+
Label actualSetting = buildSettingRule.getLabel();
359+
Object newValue = options.getStarlarkOptions().get(actualSetting);
360+
// TODO(b/154132845): fix NPE occasionally observed here.
361+
Preconditions.checkState(
362+
newValue != null,
363+
"Error while attempting to validate new values from starlark"
364+
+ " transition(s) with the inputs and outputs %s. Post-transition configuration should"
365+
+ " include '%s' but only includes starlark options: %s. If you run into this error"
366+
+ " please ping b/154132845 or email [email protected].",
367+
inputAndOutputSettings,
368+
actualSetting,
369+
options.getStarlarkOptions().keySet());
370+
boolean allowsMultiple = buildSettingRule.getRuleClassObject().getBuildSetting()
371+
.allowsMultiple();
372+
if (allowsMultiple) {
373+
// if this setting allows multiple settings
374+
if (!(newValue instanceof List)) {
375+
throw new TransitionException(
376+
String.format(
377+
"'%s' allows multiple values and must be set"
378+
+ " in transition using a starlark list instead of single value '%s'",
379+
actualSetting, newValue));
380+
}
381+
List<?> rawNewValueAsList = (List<?>) newValue;
382+
List<Object> convertedValue = new ArrayList<>();
383+
Type<?> type = buildSettingRule.getRuleClassObject().getBuildSetting().getType();
384+
for (Object value : rawNewValueAsList) {
385+
try {
386+
convertedValue.add(type.convert(value, maybeAliasSetting));
387+
} catch (ConversionException e) {
388+
throw new TransitionException(e);
389+
}
390+
}
391+
return convertedValue.equals(
392+
ImmutableList.of(buildSettingRule.getAttr(STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME)));
393+
} else {
394+
// if this setting does not allow multiple settings
395+
Object convertedValue;
396+
try {
397+
convertedValue =
398+
buildSettingRule.getRuleClassObject()
399+
.getBuildSetting()
400+
.getType()
401+
.convert(newValue, maybeAliasSetting);
402+
} catch (ConversionException e) {
403+
throw new TransitionException(e);
404+
}
405+
return convertedValue.equals(buildSettingRule.getAttr(STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME));
406+
}
407+
}
408+
384409
/*
385410
* Resolve aliased build setting issues
386411
*

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

+57
Original file line numberDiff line numberDiff line change
@@ -2549,4 +2549,61 @@ public void testExplicitNoopTransitionTrimsInputBuildSettings() throws Exception
25492549
new EventBus());
25502550
assertNoEvents();
25512551
}
2552+
2553+
@Test
2554+
public void testTransitionPreservesAllowMultipleDefault() throws Exception {
2555+
writeAllowlistFile();
2556+
scratch.file(
2557+
"test/starlark/rules.bzl",
2558+
"P = provider(fields = ['value'])",
2559+
"def _s_impl(ctx):",
2560+
" return [P(value = ctx.build_setting_value)]",
2561+
"def _t_impl(settings, attr):",
2562+
" if 'foo' in settings['//test/starlark:a']:",
2563+
" return {'//test/starlark:b': ['bar']}",
2564+
" else:",
2565+
" return {'//test/starlark:b': ['baz']}",
2566+
"def _r_impl(ctx):",
2567+
" pass",
2568+
"s = rule(",
2569+
" implementation = _s_impl,",
2570+
" build_setting = config.string(allow_multiple = True, flag = True),",
2571+
")",
2572+
"t = transition(",
2573+
" implementation = _t_impl,",
2574+
" inputs = ['//test/starlark:a'],",
2575+
" outputs = ['//test/starlark:b'],",
2576+
")",
2577+
"r = rule(",
2578+
" implementation = _r_impl,",
2579+
" attrs = {",
2580+
" 'setting': attr.label(cfg = t),",
2581+
" '_allowlist_function_transition': attr.label(",
2582+
" default = '//tools/allowlists/function_transition_allowlist',",
2583+
" ),",
2584+
" },",
2585+
")");
2586+
scratch.file(
2587+
"test/starlark/BUILD",
2588+
"load(':rules.bzl', 's', 'r')",
2589+
"s(",
2590+
" name = 'a',",
2591+
" build_setting_default = '',",
2592+
")",
2593+
"s(",
2594+
" name = 'b',",
2595+
" build_setting_default = '',",
2596+
")",
2597+
"r(",
2598+
" name = 'c',",
2599+
" setting = ':b',",
2600+
")");
2601+
update(
2602+
ImmutableList.of("//test/starlark:c"),
2603+
/*keepGoing=*/ false,
2604+
LOADING_PHASE_THREADS,
2605+
/*doAnalysis=*/ true,
2606+
new EventBus());
2607+
assertNoEvents();
2608+
}
25522609
}

0 commit comments

Comments
 (0)