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 ;
42
44
import com .google .devtools .common .options .InvocationPolicyEnforcer ;
43
45
import com .google .devtools .common .options .OptionDefinition ;
44
46
import com .google .devtools .common .options .OptionPriority .PriorityCategory ;
47
+ import com .google .devtools .common .options .OptionsBase ;
45
48
import com .google .devtools .common .options .OptionsParser ;
46
49
import com .google .devtools .common .options .OptionsParsingException ;
47
50
import com .google .devtools .common .options .OptionsParsingResult ;
@@ -74,6 +77,14 @@ public final class BlazeOptionHandler {
74
77
"client_env" ,
75
78
"client_cwd" );
76
79
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
+
77
88
// Marks an event to indicate a parsing error.
78
89
static final String BAD_OPTION_TAG = "invalidOption" ;
79
90
// Separates the invalid tag from the full error message for easier parsing.
@@ -86,6 +97,7 @@ public final class BlazeOptionHandler {
86
97
private final Command commandAnnotation ;
87
98
private final InvocationPolicy invocationPolicy ;
88
99
private final List <String > rcfileNotes = new ArrayList <>();
100
+ private final ImmutableList <Class <? extends OptionsBase >> allOptionsClasses ;
89
101
90
102
BlazeOptionHandler (
91
103
BlazeRuntime runtime ,
@@ -100,6 +112,16 @@ public final class BlazeOptionHandler {
100
112
this .commandAnnotation = commandAnnotation ;
101
113
this .optionsParser = optionsParser ;
102
114
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 ());
103
125
}
104
126
105
127
/**
@@ -199,7 +221,36 @@ void parseRcOptions(
199
221
"%s:\n %s'%s' options: %s" ,
200
222
source , inherited , commandToParse , Joiner .on (' ' ).join (rcArgs .getArgs ())));
201
223
}
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
+ }
203
254
}
204
255
}
205
256
}
@@ -235,7 +286,8 @@ private void parseArgsAndConfigs(List<String> args, ExtendedEventHandler eventHa
235
286
optionsParser .parseWithSourceFunction (
236
287
PriorityCategory .COMMAND_LINE ,
237
288
commandOptionSourceFunction ,
238
- defaultOverridesAndRcSources .build ());
289
+ defaultOverridesAndRcSources .build (),
290
+ /* fallbackData= */ null );
239
291
240
292
// Command-specific options from .blazerc passed in via --default_override and --rc_source.
241
293
ClientOptions rcFileOptions = optionsParser .getOptions (ClientOptions .class );
@@ -249,7 +301,10 @@ private void parseArgsAndConfigs(List<String> args, ExtendedEventHandler eventHa
249
301
250
302
// Parses the remaining command-line options.
251
303
optionsParser .parseWithSourceFunction (
252
- PriorityCategory .COMMAND_LINE , commandOptionSourceFunction , remainingCmdLine .build ());
304
+ PriorityCategory .COMMAND_LINE ,
305
+ commandOptionSourceFunction ,
306
+ remainingCmdLine .build (),
307
+ /* fallbackData= */ null );
253
308
254
309
if (commandAnnotation .builds ()) {
255
310
// splits project files from targets in the traditional sense
@@ -459,14 +514,17 @@ void expandConfigOptions(
459
514
ConfigExpander .expandConfigOptions (
460
515
eventHandler ,
461
516
commandToRcArgs ,
517
+ commandAnnotation .name (),
462
518
getCommandNamesToParse (commandAnnotation ),
463
519
rcfileNotes ::add ,
464
- optionsParser );
520
+ optionsParser ,
521
+ OptionsParser .getFallbackOptionsData (allOptionsClasses ));
465
522
}
466
523
467
524
private static List <String > getCommandNamesToParse (Command commandAnnotation ) {
468
525
List <String > result = new ArrayList <>();
469
- result .add ("common" );
526
+ result .add (ALWAYS_PSEUDO_COMMAND );
527
+ result .add (COMMON_PSEUDO_COMMAND );
470
528
getCommandNamesToParseHelper (commandAnnotation , result );
471
529
return result ;
472
530
}
@@ -557,7 +615,9 @@ static ListMultimap<String, RcChunkOfArgs> structureRcOptionsAndConfigs(
557
615
if (index > 0 ) {
558
616
command = command .substring (0 , index );
559
617
}
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 )) {
561
621
eventHandler .handle (
562
622
Event .warn (
563
623
"while reading option defaults file '"
0 commit comments