@@ -267,31 +267,39 @@ 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 inputs or outputs of the transition together with their types.
271+ // Output setting values will be validated and removed if set to their default.
272+ Map <Label , Rule > inputAndOutputSettingsToRule = Maps .newHashMap ();
273+ // Collect settings that were only used as inputs to the transition and thus possibly had their
274+ // default values added to the fromOptions. They will be removed if set to ther default, but
275+ // should not be validated.
276+ Set <Label > inputOnlySettings = Sets .newHashSet ();
274277 root .visit (
275278 (StarlarkTransitionVisitor )
276279 transition -> {
277- ImmutableSet <Label > changedSettings =
280+ ImmutableSet <Label > inputAndOutputSettings =
278281 getRelevantStarlarkSettingsFromTransition (
279282 transition , Settings .INPUTS_AND_OUTPUTS );
280- for (Label setting : changedSettings ) {
281- changedSettingToRule .put (
283+ ImmutableSet <Label > outputSettings =
284+ getRelevantStarlarkSettingsFromTransition (transition , Settings .OUTPUTS );
285+ for (Label setting : inputAndOutputSettings ) {
286+ inputAndOutputSettingsToRule .put (
282287 setting , getActual (buildSettingPackages , setting ).getAssociatedRule ());
288+ if (!outputSettings .contains (setting )) {
289+ inputOnlySettings .add (setting );
290+ }
283291 }
284292 });
285293
286- // Return early if no starlark settings were affected .
287- if (changedSettingToRule .isEmpty ()) {
294+ // Return early if the transition has neither inputs nor outputs (rare) .
295+ if (inputAndOutputSettingsToRule .isEmpty ()) {
288296 return toOptions ;
289297 }
290298
291299 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 ();
300+ for (Map .Entry <Label , Rule > settingWithRule : inputAndOutputSettingsToRule .entrySet ()) {
301+ Label setting = settingWithRule .getKey ();
302+ Rule rule = settingWithRule .getValue ();
295303 if (!rule .getLabel ().equals (setting )) {
296304 aliasToActualBuilder .put (setting , rule .getLabel ());
297305 }
@@ -307,69 +315,28 @@ public static Map<String, BuildOptions> validate(
307315 BuildOptions .Builder cleanedOptions = null ;
308316 // Clean up aliased values.
309317 BuildOptions options = unalias (entry .getValue (), aliasToActual );
310- for (Map .Entry <Label , Rule > changedSettingWithRule : changedSettingToRule .entrySet ()) {
318+ for (Map .Entry <Label , Rule > settingWithRule : inputAndOutputSettingsToRule .entrySet ()) {
311319 // 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.
320+ Label maybeAliasSetting = settingWithRule .getKey ();
321+ Rule rule = settingWithRule .getValue ();
316322 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 ());
323+ // Input-only settings may have had their literal default value added to the BuildOptions
324+ // so that the transition can read them. We have to remove these explicitly set value here
325+ // to preserve the invariant that Starlark settings at default values are not explicitly set
326+ // in the BuildOptions.
327+ final boolean isInputOnlySettingAtDefault =
328+ inputOnlySettings .contains (maybeAliasSetting )
329+ && rule .getAttr (STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME )
330+ .equals (options .getStarlarkOptions ().get (actualSetting ));
331+ // For output settings, the raw value returned by the transition first has to be validated
332+ // and converted to the proper type before it can be compared to the default value.
333+ if (isInputOnlySettingAtDefault
334+ || validateAndCheckIfAtDefault (
335+ rule , options , maybeAliasSetting , inputAndOutputSettingsToRule .keySet ())) {
336+ if (cleanedOptions == null ) {
337+ cleanedOptions = options .toBuilder ();
372338 }
339+ cleanedOptions .removeStarlarkOption (rule .getLabel ());
373340 }
374341 }
375342 // Keep the same instance if we didn't do anything to maintain reference equality later on.
@@ -381,6 +348,81 @@ public static Map<String, BuildOptions> validate(
381348 return cleanedOptionMap .build ();
382349 }
383350
351+ /**
352+ * Validate the value of a particular build setting after a transition has been applied.
353+ *
354+ * @param buildSettingRule the build setting to validate.
355+ * @param options the {@link BuildOptions} reflecting the post-transition configuration.
356+ * @param maybeAliasSetting the label used to refer to the build setting in the transition,
357+ * possibly an alias. This is only used for error messages.
358+ * @param inputAndOutputSettings the transition input and output settings. This is only used for
359+ * error messages.
360+ * @return {@code true} if and only if the setting is set to its default value after the
361+ * transition.
362+ * @throws TransitionException if the value returned by the transition for this setting has an
363+ * invalid type.
364+ */
365+ private static boolean validateAndCheckIfAtDefault (
366+ Rule buildSettingRule ,
367+ BuildOptions options ,
368+ Label maybeAliasSetting ,
369+ Set <Label > inputAndOutputSettings )
370+ throws TransitionException {
371+ // If the build setting was *not* referenced in the transition by an alias, this is the same
372+ // value as {@code maybeAliasSetting}.
373+ Label actualSetting = buildSettingRule .getLabel ();
374+ Object newValue = options .getStarlarkOptions ().get (actualSetting );
375+ // TODO(b/154132845): fix NPE occasionally observed here.
376+ Preconditions .checkState (
377+ newValue != null ,
378+ "Error while attempting to validate new values from starlark"
379+ + " transition(s) with the inputs and outputs %s. Post-transition configuration should"
380+ + " include '%s' but only includes starlark options: %s. If you run into this error"
381+ +
" please ping b/154132845 or email [email protected] ." ,
382+ inputAndOutputSettings ,
383+ actualSetting ,
384+ options .getStarlarkOptions ().keySet ());
385+ boolean allowsMultiple =
386+ buildSettingRule .getRuleClassObject ().getBuildSetting ().allowsMultiple ();
387+ if (allowsMultiple ) {
388+ // if this setting allows multiple settings
389+ if (!(newValue instanceof List )) {
390+ throw new TransitionException (
391+ String .format (
392+ "'%s' allows multiple values and must be set"
393+ + " in transition using a starlark list instead of single value '%s'" ,
394+ actualSetting , newValue ));
395+ }
396+ List <?> rawNewValueAsList = (List <?>) newValue ;
397+ List <Object > convertedValue = new ArrayList <>();
398+ Type <?> type = buildSettingRule .getRuleClassObject ().getBuildSetting ().getType ();
399+ for (Object value : rawNewValueAsList ) {
400+ try {
401+ convertedValue .add (type .convert (value , maybeAliasSetting ));
402+ } catch (ConversionException e ) {
403+ throw new TransitionException (e );
404+ }
405+ }
406+ return convertedValue .equals (
407+ ImmutableList .of (buildSettingRule .getAttr (STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME )));
408+ } else {
409+ // if this setting does not allow multiple settings
410+ Object convertedValue ;
411+ try {
412+ convertedValue =
413+ buildSettingRule
414+ .getRuleClassObject ()
415+ .getBuildSetting ()
416+ .getType ()
417+ .convert (newValue , maybeAliasSetting );
418+ } catch (ConversionException e ) {
419+ throw new TransitionException (e );
420+ }
421+ return convertedValue .equals (
422+ buildSettingRule .getAttr (STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME ));
423+ }
424+ }
425+
384426 /*
385427 * Resolve aliased build setting issues
386428 *
0 commit comments