1313// limitations under the License.
1414package com .google .devtools .build .lib .runtime ;
1515
16+ import static com .google .common .collect .ImmutableList .toImmutableList ;
17+
1618import com .google .common .annotations .VisibleForTesting ;
1719import com .google .common .base .Joiner ;
1820import com .google .common .collect .ArrayListMultimap ;
4244import com .google .devtools .common .options .InvocationPolicyEnforcer ;
4345import com .google .devtools .common .options .OptionDefinition ;
4446import com .google .devtools .common .options .OptionPriority .PriorityCategory ;
47+ import com .google .devtools .common .options .OptionsBase ;
4548import com .google .devtools .common .options .OptionsParser ;
4649import com .google .devtools .common .options .OptionsParsingException ;
4750import com .google .devtools .common .options .OptionsParsingResult ;
@@ -74,6 +77,14 @@ public final class BlazeOptionHandler {
7477 "client_env" ,
7578 "client_cwd" );
7679
80+ // All options set on this pseudo command are inherited by all commands, with unrecognized options
81+ // resulting in an error.
82+ private static final String ALWAYS_PSEUDO_COMMAND = "always" ;
83+
84+ // All options set on this pseudo command are inherited by all commands, with unrecognized options
85+ // being ignored as long as they are recognized by at least one (other) command.
86+ private static final String COMMON_PSEUDO_COMMAND = "common" ;
87+
7788 // Marks an event to indicate a parsing error.
7889 static final String BAD_OPTION_TAG = "invalidOption" ;
7990 // Separates the invalid tag from the full error message for easier parsing.
@@ -86,6 +97,7 @@ public final class BlazeOptionHandler {
8697 private final Command commandAnnotation ;
8798 private final InvocationPolicy invocationPolicy ;
8899 private final List <String > rcfileNotes = new ArrayList <>();
100+ private final ImmutableList <Class <? extends OptionsBase >> allOptionsClasses ;
89101
90102 BlazeOptionHandler (
91103 BlazeRuntime runtime ,
@@ -100,6 +112,16 @@ public final class BlazeOptionHandler {
100112 this .commandAnnotation = commandAnnotation ;
101113 this .optionsParser = optionsParser ;
102114 this .invocationPolicy = invocationPolicy ;
115+ this .allOptionsClasses =
116+ runtime .getCommandMap ().values ().stream ()
117+ .map (BlazeCommand ::getClass )
118+ .flatMap (
119+ cmd ->
120+ BlazeCommandUtils .getOptions (
121+ cmd , runtime .getBlazeModules (), runtime .getRuleClassProvider ())
122+ .stream ())
123+ .distinct ()
124+ .collect (toImmutableList ());
103125 }
104126
105127 /**
@@ -199,7 +221,36 @@ void parseRcOptions(
199221 "%s:\n %s'%s' options: %s" ,
200222 source , inherited , commandToParse , Joiner .on (' ' ).join (rcArgs .getArgs ())));
201223 }
202- optionsParser .parse (PriorityCategory .RC_FILE , rcArgs .getRcFile (), rcArgs .getArgs ());
224+ if (commandToParse .equals (COMMON_PSEUDO_COMMAND )) {
225+ // Pass in options data for all commands supported by the runtime so that options that
226+ // apply to some but not the current command can be ignored.
227+ //
228+ // Important note: The consistency checks performed by
229+ // OptionsParser#getFallbackOptionsData ensure that there aren't any two options across
230+ // all commands that have the same name but parse differently (e.g. because one accepts
231+ // a value and the other doesn't). This means that the options available on a command
232+ // limit the options available on other commands even without command inheritance. This
233+ // restriction is necessary to ensure that the options specified on the "common"
234+ // pseudo command can be parsed unambiguously.
235+ ImmutableList <String > ignoredArgs =
236+ optionsParser .parseWithSourceFunction (
237+ PriorityCategory .RC_FILE ,
238+ o -> rcArgs .getRcFile (),
239+ rcArgs .getArgs (),
240+ OptionsParser .getFallbackOptionsData (allOptionsClasses ));
241+ if (!ignoredArgs .isEmpty ()) {
242+ // Append richer information to the note.
243+ int index = rcfileNotes .size () - 1 ;
244+ String note = rcfileNotes .get (index );
245+ note +=
246+ String .format (
247+ "\n Ignored as unsupported by '%s': %s" ,
248+ commandAnnotation .name (), Joiner .on (' ' ).join (ignoredArgs ));
249+ rcfileNotes .set (index , note );
250+ }
251+ } else {
252+ optionsParser .parse (PriorityCategory .RC_FILE , rcArgs .getRcFile (), rcArgs .getArgs ());
253+ }
203254 }
204255 }
205256 }
@@ -235,7 +286,8 @@ private void parseArgsAndConfigs(List<String> args, ExtendedEventHandler eventHa
235286 optionsParser .parseWithSourceFunction (
236287 PriorityCategory .COMMAND_LINE ,
237288 commandOptionSourceFunction ,
238- defaultOverridesAndRcSources .build ());
289+ defaultOverridesAndRcSources .build (),
290+ /* fallbackData= */ null );
239291
240292 // Command-specific options from .blazerc passed in via --default_override and --rc_source.
241293 ClientOptions rcFileOptions = optionsParser .getOptions (ClientOptions .class );
@@ -249,7 +301,10 @@ private void parseArgsAndConfigs(List<String> args, ExtendedEventHandler eventHa
249301
250302 // Parses the remaining command-line options.
251303 optionsParser .parseWithSourceFunction (
252- PriorityCategory .COMMAND_LINE , commandOptionSourceFunction , remainingCmdLine .build ());
304+ PriorityCategory .COMMAND_LINE ,
305+ commandOptionSourceFunction ,
306+ remainingCmdLine .build (),
307+ /* fallbackData= */ null );
253308
254309 if (commandAnnotation .builds ()) {
255310 // splits project files from targets in the traditional sense
@@ -459,14 +514,17 @@ void expandConfigOptions(
459514 ConfigExpander .expandConfigOptions (
460515 eventHandler ,
461516 commandToRcArgs ,
517+ commandAnnotation .name (),
462518 getCommandNamesToParse (commandAnnotation ),
463519 rcfileNotes ::add ,
464- optionsParser );
520+ optionsParser ,
521+ OptionsParser .getFallbackOptionsData (allOptionsClasses ));
465522 }
466523
467524 private static List <String > getCommandNamesToParse (Command commandAnnotation ) {
468525 List <String > result = new ArrayList <>();
469- result .add ("common" );
526+ result .add (ALWAYS_PSEUDO_COMMAND );
527+ result .add (COMMON_PSEUDO_COMMAND );
470528 getCommandNamesToParseHelper (commandAnnotation , result );
471529 return result ;
472530 }
@@ -557,7 +615,9 @@ static ListMultimap<String, RcChunkOfArgs> structureRcOptionsAndConfigs(
557615 if (index > 0 ) {
558616 command = command .substring (0 , index );
559617 }
560- if (!validCommands .contains (command ) && !command .equals ("common" )) {
618+ if (!validCommands .contains (command )
619+ && !command .equals (ALWAYS_PSEUDO_COMMAND )
620+ && !command .equals (COMMON_PSEUDO_COMMAND )) {
561621 eventHandler .handle (
562622 Event .warn (
563623 "while reading option defaults file '"
0 commit comments