13
13
// limitations under the License.
14
14
package com .google .devtools .build .lib .runtime ;
15
15
16
+ import static com .google .common .collect .ImmutableList .toImmutableList ;
17
+
16
18
import com .google .common .annotations .VisibleForTesting ;
17
19
import com .google .common .base .Joiner ;
18
20
import com .google .common .collect .ArrayListMultimap ;
35
37
import com .google .devtools .common .options .InvocationPolicyEnforcer ;
36
38
import com .google .devtools .common .options .OptionDefinition ;
37
39
import com .google .devtools .common .options .OptionPriority .PriorityCategory ;
40
+ import com .google .devtools .common .options .OptionsBase ;
38
41
import com .google .devtools .common .options .OptionsParser ;
39
42
import com .google .devtools .common .options .OptionsParsingException ;
40
43
import com .google .devtools .common .options .OptionsParsingResult ;
@@ -66,6 +69,14 @@ public final class BlazeOptionHandler {
66
69
"client_env" ,
67
70
"client_cwd" );
68
71
72
+ // All options set on this pseudo command are inherited by all commands, with unrecognized options
73
+ // resulting in an error.
74
+ private static final String ALWAYS_PSEUDO_COMMAND = "always" ;
75
+
76
+ // All options set on this pseudo command are inherited by all commands, with unrecognized options
77
+ // being ignored as long as they are recognized by at least one (other) command.
78
+ private static final String COMMON_PSEUDO_COMMAND = "common" ;
79
+
69
80
// Marks an event to indicate a parsing error.
70
81
static final String BAD_OPTION_TAG = "invalidOption" ;
71
82
// Separates the invalid tag from the full error message for easier parsing.
@@ -78,6 +89,7 @@ public final class BlazeOptionHandler {
78
89
private final Command commandAnnotation ;
79
90
private final InvocationPolicy invocationPolicy ;
80
91
private final List <String > rcfileNotes = new ArrayList <>();
92
+ private final ImmutableList <Class <? extends OptionsBase >> allOptionsClasses ;
81
93
82
94
BlazeOptionHandler (
83
95
BlazeRuntime runtime ,
@@ -92,6 +104,16 @@ public final class BlazeOptionHandler {
92
104
this .commandAnnotation = commandAnnotation ;
93
105
this .optionsParser = optionsParser ;
94
106
this .invocationPolicy = invocationPolicy ;
107
+ this .allOptionsClasses =
108
+ runtime .getCommandMap ().values ().stream ()
109
+ .map (BlazeCommand ::getClass )
110
+ .flatMap (
111
+ cmd ->
112
+ BlazeCommandUtils .getOptions (
113
+ cmd , runtime .getBlazeModules (), runtime .getRuleClassProvider ())
114
+ .stream ())
115
+ .distinct ()
116
+ .collect (toImmutableList ());
95
117
}
96
118
97
119
/**
@@ -191,7 +213,36 @@ void parseRcOptions(
191
213
"%s:\n %s'%s' options: %s" ,
192
214
source , inherited , commandToParse , Joiner .on (' ' ).join (rcArgs .getArgs ())));
193
215
}
194
- optionsParser .parse (PriorityCategory .RC_FILE , rcArgs .getRcFile (), rcArgs .getArgs ());
216
+ if (commandToParse .equals (COMMON_PSEUDO_COMMAND )) {
217
+ // Pass in options data for all commands supported by the runtime so that options that
218
+ // apply to some but not the current command can be ignored.
219
+ //
220
+ // Important note: The consistency checks performed by
221
+ // OptionsParser#getFallbackOptionsData ensure that there aren't any two options across
222
+ // all commands that have the same name but parse differently (e.g. because one accepts
223
+ // a value and the other doesn't). This means that the options available on a command
224
+ // limit the options available on other commands even without command inheritance. This
225
+ // restriction is necessary to ensure that the options specified on the "common"
226
+ // pseudo command can be parsed unambiguously.
227
+ ImmutableList <String > ignoredArgs =
228
+ optionsParser .parseWithSourceFunction (
229
+ PriorityCategory .RC_FILE ,
230
+ o -> rcArgs .getRcFile (),
231
+ rcArgs .getArgs (),
232
+ OptionsParser .getFallbackOptionsData (allOptionsClasses ));
233
+ if (!ignoredArgs .isEmpty ()) {
234
+ // Append richer information to the note.
235
+ int index = rcfileNotes .size () - 1 ;
236
+ String note = rcfileNotes .get (index );
237
+ note +=
238
+ String .format (
239
+ "\n Ignored as unsupported by '%s': %s" ,
240
+ commandAnnotation .name (), Joiner .on (' ' ).join (ignoredArgs ));
241
+ rcfileNotes .set (index , note );
242
+ }
243
+ } else {
244
+ optionsParser .parse (PriorityCategory .RC_FILE , rcArgs .getRcFile (), rcArgs .getArgs ());
245
+ }
195
246
}
196
247
}
197
248
}
@@ -227,7 +278,8 @@ private void parseArgsAndConfigs(List<String> args, ExtendedEventHandler eventHa
227
278
optionsParser .parseWithSourceFunction (
228
279
PriorityCategory .COMMAND_LINE ,
229
280
commandOptionSourceFunction ,
230
- defaultOverridesAndRcSources .build ());
281
+ defaultOverridesAndRcSources .build (),
282
+ /* fallbackData= */ null );
231
283
232
284
// Command-specific options from .blazerc passed in via --default_override and --rc_source.
233
285
ClientOptions rcFileOptions = optionsParser .getOptions (ClientOptions .class );
@@ -241,7 +293,10 @@ private void parseArgsAndConfigs(List<String> args, ExtendedEventHandler eventHa
241
293
242
294
// Parses the remaining command-line options.
243
295
optionsParser .parseWithSourceFunction (
244
- PriorityCategory .COMMAND_LINE , commandOptionSourceFunction , remainingCmdLine .build ());
296
+ PriorityCategory .COMMAND_LINE ,
297
+ commandOptionSourceFunction ,
298
+ remainingCmdLine .build (),
299
+ /* fallbackData= */ null );
245
300
246
301
if (commandAnnotation .builds ()) {
247
302
// splits project files from targets in the traditional sense
@@ -372,14 +427,17 @@ void expandConfigOptions(
372
427
ConfigExpander .expandConfigOptions (
373
428
eventHandler ,
374
429
commandToRcArgs ,
430
+ commandAnnotation .name (),
375
431
getCommandNamesToParse (commandAnnotation ),
376
432
rcfileNotes ::add ,
377
- optionsParser );
433
+ optionsParser ,
434
+ OptionsParser .getFallbackOptionsData (allOptionsClasses ));
378
435
}
379
436
380
437
private static List <String > getCommandNamesToParse (Command commandAnnotation ) {
381
438
List <String > result = new ArrayList <>();
382
- result .add ("common" );
439
+ result .add (ALWAYS_PSEUDO_COMMAND );
440
+ result .add (COMMON_PSEUDO_COMMAND );
383
441
getCommandNamesToParseHelper (commandAnnotation , result );
384
442
return result ;
385
443
}
@@ -470,7 +528,9 @@ static ListMultimap<String, RcChunkOfArgs> structureRcOptionsAndConfigs(
470
528
if (index > 0 ) {
471
529
command = command .substring (0 , index );
472
530
}
473
- if (!validCommands .contains (command ) && !command .equals ("common" )) {
531
+ if (!validCommands .contains (command )
532
+ && !command .equals (ALWAYS_PSEUDO_COMMAND )
533
+ && !command .equals (COMMON_PSEUDO_COMMAND )) {
474
534
eventHandler .handle (
475
535
Event .warn (
476
536
"while reading option defaults file '"
0 commit comments