-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Let common
ignore unsupported options
#18130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
57b1e31
7d1d421
b5d22e0
e291f0e
d2488c4
d169c0e
22ebb20
e282272
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,8 @@ | |
// limitations under the License. | ||
package com.google.devtools.build.lib.runtime; | ||
|
||
import static com.google.common.collect.ImmutableList.toImmutableList; | ||
|
||
import com.google.common.annotations.VisibleForTesting; | ||
import com.google.common.base.Joiner; | ||
import com.google.common.collect.ArrayListMultimap; | ||
|
@@ -42,6 +44,7 @@ | |
import com.google.devtools.common.options.InvocationPolicyEnforcer; | ||
import com.google.devtools.common.options.OptionDefinition; | ||
import com.google.devtools.common.options.OptionPriority.PriorityCategory; | ||
import com.google.devtools.common.options.OptionsBase; | ||
import com.google.devtools.common.options.OptionsParser; | ||
import com.google.devtools.common.options.OptionsParsingException; | ||
import com.google.devtools.common.options.OptionsParsingResult; | ||
|
@@ -74,6 +77,14 @@ public final class BlazeOptionHandler { | |
"client_env", | ||
"client_cwd"); | ||
|
||
// All options set on this pseudo command are inherited by all commands, with unrecognized options | ||
// resulting in an error. | ||
private static final String ALWAYS_PSEUDO_COMMAND = "always"; | ||
|
||
// All options set on this pseudo command are inherited by all commands, with unrecognized options | ||
// being ignored as long as they are recognized by at least one (other) command. | ||
private static final String COMMON_PSEUDO_COMMAND = "common"; | ||
|
||
// Marks an event to indicate a parsing error. | ||
static final String BAD_OPTION_TAG = "invalidOption"; | ||
// Separates the invalid tag from the full error message for easier parsing. | ||
|
@@ -86,6 +97,7 @@ public final class BlazeOptionHandler { | |
private final Command commandAnnotation; | ||
private final InvocationPolicy invocationPolicy; | ||
private final List<String> rcfileNotes = new ArrayList<>(); | ||
private final List<Class<? extends OptionsBase>> allOptionsClasses; | ||
|
||
BlazeOptionHandler( | ||
BlazeRuntime runtime, | ||
|
@@ -100,6 +112,12 @@ public final class BlazeOptionHandler { | |
this.commandAnnotation = commandAnnotation; | ||
this.optionsParser = optionsParser; | ||
this.invocationPolicy = invocationPolicy; | ||
this.allOptionsClasses = runtime.getCommandMap().values().stream() | ||
.map(BlazeCommand::getClass) | ||
.flatMap(cmd -> BlazeCommandUtils.getOptions(cmd, runtime.getBlazeModules(), | ||
runtime.getRuleClassProvider()).stream()) | ||
.distinct() | ||
.collect(toImmutableList()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we used a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, we should add a I don't think that passing to a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks that wasn't clear to me. Hmm that is an annoying bit of necessary complexity of this FR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would definitely add more comments as part of polishing this PR if you approve of the approach. I agree that this additional complexity is annoying, but if it isn't taken care of by Bazel itself, it is pushed onto end users. I actually think that compared to the level of effort required to manually apply flags to all commands they are valid for, the complexity of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
What would happen if they weren't equivalent for parsing?
What would be wrong with two different options with the same name that were both booleans? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That would result in an error when
That would also work. It would only error if one is a boolean and the other accepts a value. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay I approve of the approach.
Thanks that's what I figured. Agree this restriction to the codebase is a sensible requirement for this FR. Please make this clear in a code comment.
Sounds good. I see that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not clear on why we're doing the |
||
} | ||
|
||
/** | ||
|
@@ -186,6 +204,24 @@ void parseRcOptions( | |
for (String commandToParse : getCommandNamesToParse(commandAnnotation)) { | ||
// Get all args defined for this command (or "common"), grouped by rc chunk. | ||
for (RcChunkOfArgs rcArgs : commandToRcArgs.get(commandToParse)) { | ||
ImmutableList<String> ignoredArgs = ImmutableList.of(); | ||
if (commandToParse.equals(COMMON_PSEUDO_COMMAND)) { | ||
// Pass in options data for all commands supported by the runtime so that options that | ||
// apply to some but not the current command can be ignored. | ||
// | ||
// Important note: The consistency checks performed by | ||
// OptionsParser#getFallbackOptionsData ensure that there aren't any two options across | ||
// all commands that have the same name but parse differently (e.g. because one accepts | ||
// a value and the other doesn't). This means that the options available on a command | ||
// limit the options available on other commands even without command inheritance. This | ||
// restriction is necessary to ensure that the options specified on the "common" | ||
// pseudo command can be parsed unambiguously. | ||
ignoredArgs = optionsParser.parseWithSourceFunction(PriorityCategory.RC_FILE, | ||
o -> rcArgs.getRcFile(), | ||
rcArgs.getArgs(), OptionsParser.getFallbackOptionsData(allOptionsClasses)); | ||
} else { | ||
optionsParser.parse(PriorityCategory.RC_FILE, rcArgs.getRcFile(), rcArgs.getArgs()); | ||
} | ||
if (!rcArgs.getArgs().isEmpty()) { | ||
String inherited = commandToParse.equals(commandAnnotation.name()) ? "" : "Inherited "; | ||
String source = | ||
|
@@ -194,12 +230,15 @@ void parseRcOptions( | |
: String.format( | ||
"Reading rc options for '%s' from %s", | ||
commandAnnotation.name(), rcArgs.getRcFile()); | ||
rcfileNotes.add( | ||
String.format( | ||
"%s:\n %s'%s' options: %s", | ||
source, inherited, commandToParse, Joiner.on(' ').join(rcArgs.getArgs()))); | ||
String rcFileNote = String.format( | ||
"%s:\n %s'%s' options: %s", | ||
source, inherited, commandToParse, Joiner.on(' ').join(rcArgs.getArgs())); | ||
if (!ignoredArgs.isEmpty()) { | ||
rcFileNote += String.format("\n Ignored as unsupported by '%s': %s", | ||
commandAnnotation.name(), Joiner.on(' ').join(ignoredArgs)); | ||
} | ||
rcfileNotes.add(rcFileNote.toString()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor issue here detected by an internal test when I tried to import. Adding the note after the call to for (RcChunkOfArgs rcArgs : commandToRcArgs.get(commandToParse)) {
if (!rcArgs.getArgs().isEmpty()) {
String inherited = commandToParse.equals(commandAnnotation.name()) ? "" : "Inherited ";
String source =
rcArgs.getRcFile().equals("client")
? "Options provided by the client"
: String.format(
"Reading rc options for '%s' from %s",
commandAnnotation.name(), rcArgs.getRcFile());
rcfileNotes.add(
String.format(
"%s:\n %s'%s' options: %s",
source, inherited, commandToParse, Joiner.on(' ').join(rcArgs.getArgs())));
}
if (commandToParse.equals(COMMON_PSEUDO_COMMAND)) {
// Pass in options data for all commands supported by the runtime so that options that
// apply to some but not the current command can be ignored.
//
// Important note: The consistency checks performed by
// OptionsParser#getFallbackOptionsData ensure that there aren't any two options across
// all commands that have the same name but parse differently (e.g. because one accepts
// a value and the other doesn't). This means that the options available on a command
// limit the options available on other commands even without command inheritance. This
// restriction is necessary to ensure that the options specified on the "common"
// pseudo command can be parsed unambiguously.
ImmutableList<String> ignoredArgs =
optionsParser.parseWithSourceFunction(
PriorityCategory.RC_FILE,
o -> rcArgs.getRcFile(),
rcArgs.getArgs(),
OptionsParser.getFallbackOptionsData(allOptionsClasses));
if (!ignoredArgs.isEmpty()) {
// Append richer information to the note.
int index = rcfileNotes.size() - 1;
String note = rcfileNotes.get(index);
note +=
String.format(
"\n Ignored as unsupported by '%s': %s",
commandAnnotation.name(), Joiner.on(' ').join(ignoredArgs));
rcfileNotes.set(index, note);
}
} else {
optionsParser.parse(PriorityCategory.RC_FILE, rcArgs.getRcFile(), rcArgs.getArgs());
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, the change looks good. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With that out of the way, tests are now passing. But apparently we have more internal code outside of blaze that depends on the options parsing libraries, and I need to fix them up too. Please bear with me. |
||
} | ||
optionsParser.parse(PriorityCategory.RC_FILE, rcArgs.getRcFile(), rcArgs.getArgs()); | ||
} | ||
} | ||
} | ||
|
@@ -235,7 +274,8 @@ private void parseArgsAndConfigs(List<String> args, ExtendedEventHandler eventHa | |
optionsParser.parseWithSourceFunction( | ||
PriorityCategory.COMMAND_LINE, | ||
commandOptionSourceFunction, | ||
defaultOverridesAndRcSources.build()); | ||
defaultOverridesAndRcSources.build(), | ||
/* fallbackData= */null); | ||
|
||
// Command-specific options from .blazerc passed in via --default_override and --rc_source. | ||
ClientOptions rcFileOptions = optionsParser.getOptions(ClientOptions.class); | ||
|
@@ -249,7 +289,8 @@ private void parseArgsAndConfigs(List<String> args, ExtendedEventHandler eventHa | |
|
||
// Parses the remaining command-line options. | ||
optionsParser.parseWithSourceFunction( | ||
PriorityCategory.COMMAND_LINE, commandOptionSourceFunction, remainingCmdLine.build()); | ||
PriorityCategory.COMMAND_LINE, commandOptionSourceFunction, remainingCmdLine.build(), | ||
/* fallbackData= */null); | ||
|
||
if (commandAnnotation.builds()) { | ||
// splits project files from targets in the traditional sense | ||
|
@@ -459,14 +500,17 @@ void expandConfigOptions( | |
ConfigExpander.expandConfigOptions( | ||
eventHandler, | ||
commandToRcArgs, | ||
commandAnnotation.name(), | ||
getCommandNamesToParse(commandAnnotation), | ||
rcfileNotes::add, | ||
optionsParser); | ||
optionsParser, | ||
OptionsParser.getFallbackOptionsData(allOptionsClasses)); | ||
} | ||
|
||
private static List<String> getCommandNamesToParse(Command commandAnnotation) { | ||
List<String> result = new ArrayList<>(); | ||
result.add("common"); | ||
result.add(ALWAYS_PSEUDO_COMMAND); | ||
result.add(COMMON_PSEUDO_COMMAND); | ||
getCommandNamesToParseHelper(commandAnnotation, result); | ||
return result; | ||
} | ||
|
@@ -557,7 +601,8 @@ static ListMultimap<String, RcChunkOfArgs> structureRcOptionsAndConfigs( | |
if (index > 0) { | ||
command = command.substring(0, index); | ||
} | ||
if (!validCommands.contains(command) && !command.equals("common")) { | ||
if (!validCommands.contains(command) && !command.equals(ALWAYS_PSEUDO_COMMAND) | ||
&& !command.equals(COMMON_PSEUDO_COMMAND)) { | ||
eventHandler.handle( | ||
Event.warn( | ||
"while reading option defaults file '" | ||
|
Uh oh!
There was an error while loading. Please reload this page.