blob: 991ea9fe51d493acaa91819614e332e79dd3101f [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// 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.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.flogger.GoogleLogger;
import com.google.devtools.build.lib.cmdline.TargetParsingException;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.runtime.StarlarkOptionsParser.BuildSettingLoader;
import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
import com.google.devtools.build.lib.server.FailureDetails;
import com.google.devtools.build.lib.server.FailureDetails.Command.Code;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.server.FailureDetails.TargetPatterns;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.lib.skyframe.TargetPatternPhaseValue;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.util.InterruptedFailureDetails;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.common.options.Converters;
import com.google.devtools.common.options.InvocationPolicyEnforcer;
import com.google.devtools.common.options.OptionAndRawValue;
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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Level;
/**
* Handles parsing the blaze command arguments.
*
* <p>This class manages rc options, configs, default options, and invocation policy.
*/
public final class BlazeOptionHandler {
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
// Keep in sync with options added in OptionProcessor::AddRcfileArgsAndOptions()
private static final ImmutableSet<String> INTERNAL_COMMAND_OPTIONS =
ImmutableSet.of(
"rc_source",
"default_override",
"isatty",
"terminal_columns",
"ignore_client_env",
"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.
static final String COMMON_PSEUDO_COMMAND = "common";
private static final ImmutableSet<String> BUILD_COMMAND_ANCESTORS =
ImmutableSet.of("build", COMMON_PSEUDO_COMMAND, ALWAYS_PSEUDO_COMMAND);
// 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.
static final String ERROR_SEPARATOR = " :: ";
private final BlazeRuntime runtime;
private final OptionsParser optionsParser;
private final BlazeWorkspace workspace;
private final BlazeCommand command;
private final Command commandAnnotation;
private final InvocationPolicy invocationPolicy;
private final List<String> rcfileNotes = new ArrayList<>();
private final ImmutableList<Class<? extends OptionsBase>> allOptionsClasses;
BlazeOptionHandler(
BlazeRuntime runtime,
BlazeWorkspace workspace,
BlazeCommand command,
Command commandAnnotation,
OptionsParser optionsParser,
InvocationPolicy invocationPolicy) {
this.runtime = runtime;
this.workspace = workspace;
this.command = command;
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());
}
/**
* Return options as {@link OptionsParsingResult} so the options can't be easily modified after
* we've applied the invocation policy.
*/
OptionsParsingResult getOptionsResult() {
return optionsParser;
}
public List<String> getRcfileNotes() {
return rcfileNotes;
}
/**
* Only some commands work if cwd != workspaceSuffix in Blaze. In that case, also check if Blaze
* was called from the output directory and fail if it was.
*/
private DetailedExitCode checkCwdInWorkspace(EventHandler eventHandler) {
if (!commandAnnotation.mustRunInWorkspace()) {
return DetailedExitCode.success();
}
if (!workspace.getDirectories().inWorkspace()) {
String message =
"The '"
+ commandAnnotation.name()
+ "' command is only supported from within a workspace"
+ " (below a directory having a MODULE.bazel file).\n"
+ "See documentation at"
+ " https://bazel.build/concepts/build-ref#workspace";
eventHandler.handle(Event.error(message));
return createDetailedExitCode(message, Code.NOT_IN_WORKSPACE);
}
Path workspacePath = workspace.getWorkspace();
if (workspacePath.getParentDirectory() != null) {
Path doNotBuild =
workspacePath.getParentDirectory().getRelative(BlazeWorkspace.DO_NOT_BUILD_FILE_NAME);
if (doNotBuild.exists()) {
String message = getNotInRealWorkspaceError(doNotBuild);
eventHandler.handle(Event.error(message));
return createDetailedExitCode(message, Code.IN_OUTPUT_DIRECTORY);
}
}
return DetailedExitCode.success();
}
/**
* Parses the unconditional options from .rc files for the current command.
*
* <p>This is not as trivial as simply taking the list of options for the specified command
* because commands can inherit arguments from each other, and we have to respect that (e.g. if an
* option is specified for 'build', it needs to take effect for the 'test' command, too). More
* specific commands should have priority over the broader commands (say a "build" option that
* conflicts with a "common" option should override the common one regardless of order.)
*
* <p>For each command, the options are parsed in rc order. This uses the primary rc file first,
* and follows import statements. This is the order in which they were passed by the client.
*/
@VisibleForTesting
void parseRcOptions(
EventHandler eventHandler, ListMultimap<String, RcChunkOfArgs> commandToRcArgs)
throws OptionsParsingException {
for (String commandToParse : getCommandNamesToParse(commandAnnotation)) {
// Get all args defined for this command (or "common"), grouped by rc chunk.
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());
}
}
}
}
private void parseArgsAndConfigs(List<String> args, ExtendedEventHandler eventHandler)
throws OptionsParsingException, InterruptedException, AbruptExitException {
Path workspaceDirectory = workspace.getWorkspace();
// TODO(ulfjack): The working directory is passed by the client as part of CommonCommandOptions,
// and we can't know it until after we've parsed the options, so use the workspace for now.
Path workingDirectory = workspace.getWorkspace();
Function<OptionDefinition, String> commandOptionSourceFunction =
option -> {
if (INTERNAL_COMMAND_OPTIONS.contains(option.getOptionName())) {
return "options generated by " + runtime.getProductName() + " launcher";
} else {
return "command line options";
}
};
// Explicit command-line options:
List<String> cmdLineAfterCommand = args.subList(1, args.size());
// Before parsing any rcfiles we need to first parse --rc_source so the parser can reference the
// proper rcfiles. The --default_override options should be parsed with the --rc_source since
// {@link #parseRcOptions} depends on the list populated by the {@link
// ClientOptions#OptionOverrideConverter}.
ImmutableList.Builder<String> defaultOverridesAndRcSources = new ImmutableList.Builder<>();
ImmutableList.Builder<String> remainingCmdLine = new ImmutableList.Builder<>();
partitionCommandLineArgs(cmdLineAfterCommand, defaultOverridesAndRcSources, remainingCmdLine);
// Parses options needed to parse rcfiles properly.
optionsParser.parseWithSourceFunction(
PriorityCategory.COMMAND_LINE,
commandOptionSourceFunction,
defaultOverridesAndRcSources.build(),
/* fallbackData= */ null);
// Command-specific options from .blazerc passed in via --default_override and --rc_source.
ClientOptions rcFileOptions = optionsParser.getOptions(ClientOptions.class);
ListMultimap<String, RcChunkOfArgs> commandToRcArgs =
structureRcOptionsAndConfigs(
eventHandler,
rcFileOptions.rcSource,
rcFileOptions.optionsOverrides,
runtime.getCommandMap().keySet());
parseRcOptions(eventHandler, commandToRcArgs);
// Parses the remaining command-line options.
optionsParser.parseWithSourceFunction(
PriorityCategory.COMMAND_LINE,
commandOptionSourceFunction,
remainingCmdLine.build(),
/* fallbackData= */ null);
if (commandAnnotation.buildPhase().analyzes()) {
// split project files from targets in the traditional sense.
ProjectFileSupport.handleProjectFiles(
eventHandler,
runtime.getProjectFileProvider(),
workspaceDirectory.asFragment(),
workingDirectory,
optionsParser,
commandAnnotation.name());
}
expandConfigOptions(eventHandler, commandToRcArgs);
}
/**
* {@link ExtendedEventHandler} override that passes through "normal" events but not events that
* would go to the build event proto.
*
* <p>Starlark flags are conceptually options but still need target pattern evaluation. If we pass
* {@link #post}able events from that evaluation, that would produce "target loaded" and "target
* configured" events in the build event proto output that consumers can confuse with actual
* targets requested by the build.
*
* <p>This is important because downstream services (like a continuous integration tool or build
* results dashboard) read these messages to reconcile which requested targets were built. If they
* determine Blaze tried to build {@code //foo //bar} then see a "target configured" message for
* some other target {@code //my_starlark_flag}, they might show misleading messages like "Built 3
* of 2 requested targets.".
*
* <p>Hence this class. By dropping those events, we restrict all info and error reporting logic
* to the options parsing pipeline.
*/
private static class NonPostingEventHandler implements ExtendedEventHandler {
private final ExtendedEventHandler delegate;
NonPostingEventHandler(ExtendedEventHandler delegate) {
this.delegate = delegate;
}
@Override
public void handle(Event e) {
delegate.handle(e);
}
@Override
public void post(ExtendedEventHandler.Postable e) {}
}
/**
* Lets {@link StarlarkOptionsParser} convert flag names to {@link Target}s through {@link
* TargetPatternPhaseValue}.
*
* <p>This is used for top-level flag parsing, outside any {@link SkyFunction}.
*/
public static class SkyframeExecutorTargetLoader
implements StarlarkOptionsParser.BuildSettingLoader {
private final SkyframeExecutor skyframeExecutor;
private final PathFragment relativeWorkingDirectory;
private final ExtendedEventHandler reporter;
public SkyframeExecutorTargetLoader(CommandEnvironment env) {
this.skyframeExecutor = env.getSkyframeExecutor();
this.relativeWorkingDirectory = env.getRelativeWorkingDirectory();
this.reporter = new NonPostingEventHandler(env.getReporter());
}
@VisibleForTesting
public SkyframeExecutorTargetLoader(
SkyframeExecutor skyframeExecutor,
PathFragment relativeWorkingDirectory,
ExtendedEventHandler reporter) {
this.skyframeExecutor = skyframeExecutor;
this.relativeWorkingDirectory = relativeWorkingDirectory;
this.reporter = new NonPostingEventHandler(reporter);
}
@Override
public Target loadBuildSetting(String targetLabel)
throws InterruptedException, TargetParsingException {
TargetPatternPhaseValue tpv =
skyframeExecutor.loadTargetPatternsWithoutFilters(
reporter,
Collections.singletonList(targetLabel),
relativeWorkingDirectory,
SkyframeExecutor.DEFAULT_THREAD_COUNT,
/* keepGoing= */ false);
ImmutableSet<Target> result = tpv.getTargets(reporter, skyframeExecutor.getPackageManager());
if (result.size() != 1) {
throw new TargetParsingException(
"user-defined flags must reference exactly one target",
TargetPatterns.Code.TARGET_FORMAT_INVALID);
}
return Iterables.getOnlyElement(result);
}
}
/**
* TODO(bazel-team): When we move CoreOptions options to be defined in starlark, make sure they're
* not passed in here during {@link #getOptionsResult}.
*/
DetailedExitCode parseStarlarkOptions(CommandEnvironment env) {
// For now, restrict starlark options to commands that already build to ensure that loading
// will work. We may want to open this up to other commands in the future.
if (!commandAnnotation.buildPhase().analyzes()) {
return DetailedExitCode.success();
}
try {
BuildSettingLoader buildSettingLoader = new SkyframeExecutorTargetLoader(env);
StarlarkOptionsParser starlarkOptionsParser =
StarlarkOptionsParser.builder()
.buildSettingLoader(buildSettingLoader)
.nativeOptionsParser(optionsParser)
.build();
Preconditions.checkState(starlarkOptionsParser.parse());
} catch (OptionsParsingException e) {
String logMessage = "Error parsing Starlark options";
logger.atInfo().withCause(e).log("%s", logMessage);
return processOptionsParsingException(
env.getReporter(), e, logMessage, Code.STARLARK_OPTIONS_PARSE_FAILURE);
} catch (InterruptedException e) {
String message = "Interrupted while parsing Starlark options";
env.getReporter().handle(Event.error(message));
return InterruptedFailureDetails.detailedExitCode(message);
}
return DetailedExitCode.success();
}
/**
* Parses the options, taking care not to generate any output to outErr, return, or throw an
* exception.
*
* @return {@code DetailedExitCode.success()} if everything went well, or some other value if not
*/
DetailedExitCode parseOptions(
List<String> args,
ExtendedEventHandler eventHandler,
ImmutableList.Builder<OptionAndRawValue> invocationPolicyFlagListBuilder) {
DetailedExitCode result =
parseOptionsInternal(args, eventHandler, invocationPolicyFlagListBuilder);
if (!result.isSuccess()) {
optionsParser.setError();
}
return result;
}
private DetailedExitCode parseOptionsInternal(
List<String> args,
ExtendedEventHandler eventHandler,
ImmutableList.Builder<OptionAndRawValue> invocationPolicyFlagListBuilder) {
// The initialization code here was carefully written to parse the options early before we call
// into the BlazeModule APIs, which means we must not generate any output to outErr, return, or
// throw an exception. All the events happening here are instead stored in a temporary event
// handler, and later replayed.
DetailedExitCode earlyExitCode = checkCwdInWorkspace(eventHandler);
if (!earlyExitCode.isSuccess()) {
return earlyExitCode;
}
try {
parseArgsAndConfigs(args, eventHandler);
// Allow the command to edit the options.
command.editOptions(optionsParser);
// Migration of --watchfs to a command option.
// TODO(ulfjack): Get rid of the startup option and drop this code.
if (runtime.getStartupOptionsProvider().getOptions(BlazeServerStartupOptions.class).watchFS) {
eventHandler.handle(
Event.error(
"--watchfs as startup option is deprecated, replace it with the equivalent command "
+ "option. For example, instead of 'bazel --watchfs build //foo', run "
+ "'bazel build --watchfs //foo'."));
try {
optionsParser.parse("--watchfs");
} catch (OptionsParsingException e) {
// This should never happen.
throw new IllegalStateException(e);
}
}
// Merge the invocation policy that is user-supplied, from the command line, and any
// invocation policy that was added by a module. The module one goes 'first,' so the user
// one has priority.
InvocationPolicy combinedPolicy =
InvocationPolicy.newBuilder()
.mergeFrom(runtime.getModuleInvocationPolicy())
.mergeFrom(invocationPolicy)
.build();
InvocationPolicyEnforcer optionsPolicyEnforcer =
new InvocationPolicyEnforcer(
combinedPolicy, Level.INFO, optionsParser.getConversionContext());
// Enforce the invocation policy. It is intentional that this is the last step in preparing
// the options. The invocation policy is used in security-critical contexts, and may be used
// as a last resort to override flags. That means that the policy can override flags set in
// BlazeCommand.editOptions, so the code needs to be safe regardless of the actual flag
// values. At the time of this writing, editOptions was only used as a convenience feature or
// to improve the user experience, but not required for safety or correctness.
optionsPolicyEnforcer.enforce(
optionsParser, commandAnnotation.name(), invocationPolicyFlagListBuilder);
// Print warnings for odd options usage
for (String warning : optionsParser.getWarnings()) {
eventHandler.handle(Event.warn(warning));
}
CommonCommandOptions commonOptions = optionsParser.getOptions(CommonCommandOptions.class);
for (String warning : commonOptions.deprecationWarnings) {
eventHandler.handle(Event.warn(warning));
}
} catch (OptionsParsingException e) {
String logMessage = "Error parsing options";
logger.atInfo().withCause(e).log("%s", logMessage);
return processOptionsParsingException(
eventHandler, e, logMessage, Code.OPTIONS_PARSE_FAILURE);
} catch (InterruptedException e) {
return DetailedExitCode.of(
FailureDetail.newBuilder()
.setInterrupted(
FailureDetails.Interrupted.newBuilder()
.setCode(FailureDetails.Interrupted.Code.INTERRUPTED))
.build());
} catch (AbruptExitException e) {
return e.getDetailedExitCode();
}
return DetailedExitCode.success();
}
/**
* Expand the values of --config according to the definitions provided in the rc files and the
* applicable command.
*/
@VisibleForTesting
void expandConfigOptions(
EventHandler eventHandler, ListMultimap<String, RcChunkOfArgs> commandToRcArgs)
throws OptionsParsingException {
ConfigExpander.expandConfigOptions(
eventHandler,
commandToRcArgs,
commandAnnotation.name(),
getCommandNamesToParse(commandAnnotation),
rcfileNotes::add,
optionsParser,
OptionsParser.getFallbackOptionsData(allOptionsClasses));
}
private static List<String> getCommandNamesToParse(Command commandAnnotation) {
List<String> result = new ArrayList<>();
result.add(ALWAYS_PSEUDO_COMMAND);
result.add(COMMON_PSEUDO_COMMAND);
getCommandNamesToParseHelper(commandAnnotation, result);
return result;
}
private static void getCommandNamesToParseHelper(
Command commandAnnotation, List<String> accumulator) {
for (Class<? extends BlazeCommand> base : commandAnnotation.inheritsOptionsFrom()) {
getCommandNamesToParseHelper(base.getAnnotation(Command.class), accumulator);
}
accumulator.add(commandAnnotation.name());
}
private static DetailedExitCode processOptionsParsingException(
ExtendedEventHandler eventHandler,
OptionsParsingException e,
String logMessage,
Code failureCode) {
Event error;
// Differentiates errors stemming from an invalid argument and errors from different parts of
// the codebase.
if (e.getInvalidArgument() != null) {
error =
Event.error(e.getInvalidArgument() + ERROR_SEPARATOR + e.getMessage())
.withTag(BAD_OPTION_TAG);
} else {
error = Event.error(e.getMessage());
}
eventHandler.handle(error);
return createDetailedExitCode(logMessage + ": " + e.getMessage(), failureCode);
}
private String getNotInRealWorkspaceError(Path doNotBuildFile) {
String message =
String.format(
"%1$s should not be called from a %1$s output directory. ", runtime.getProductName());
try {
String realWorkspace = new String(FileSystemUtils.readContentAsLatin1(doNotBuildFile));
message += String.format("The pertinent workspace directory is: '%s'", realWorkspace);
} catch (IOException e) {
// We are exiting anyway.
}
return message;
}
/**
* The rc options are passed via {@link ClientOptions#optionsOverrides} and {@link
* ClientOptions#rcSource}, which is basically a line-by-line transfer of the rc files read by the
* client. This is not a particularly useful format for expanding the options, so this method
* structures the list so that it is easier to find the arguments that apply to a command, or to
* find the definitions of a config value.
*/
@VisibleForTesting
static ListMultimap<String, RcChunkOfArgs> structureRcOptionsAndConfigs(
EventHandler eventHandler,
List<String> rcFiles,
List<ClientOptions.OptionOverride> rawOverrides,
Set<String> validCommands)
throws OptionsParsingException {
ListMultimap<String, RcChunkOfArgs> commandToRcArgs = ArrayListMultimap.create();
String lastRcFile = null;
ListMultimap<String, String> commandToArgMapForLastRc = null;
for (ClientOptions.OptionOverride override : rawOverrides) {
if (override.blazeRc < 0 || override.blazeRc >= rcFiles.size()) {
eventHandler.handle(
Event.warn("inconsistency in generated command line args. Ignoring bogus argument\n"));
continue;
}
String rcFile = rcFiles.get(override.blazeRc);
// The canonicalize-flags command only inherits bazelrc "build" commands. Not "test", not
// "build:foo". Restrict --flag_alias accordingly to prevent building with flags that
// canonicalize-flags can't recognize.
if ((override.option.startsWith("--" + Converters.BLAZE_ALIASING_FLAG + "=")
|| override.option.equals("--" + Converters.BLAZE_ALIASING_FLAG))
&& !BUILD_COMMAND_ANCESTORS.contains(override.command)) {
throw new OptionsParsingException(
String.format(
"%s: \"%s %s\" disallowed. --%s only supports these commands: %s",
rcFile,
override.command,
override.option,
Converters.BLAZE_ALIASING_FLAG,
String.join(", ", BUILD_COMMAND_ANCESTORS)));
}
String command = override.command;
int index = command.indexOf(':');
if (index > 0) {
command = command.substring(0, index);
}
if (!validCommands.contains(command)
&& !command.equals(ALWAYS_PSEUDO_COMMAND)
&& !command.equals(COMMON_PSEUDO_COMMAND)) {
eventHandler.handle(
Event.warn(
"while reading option defaults file '"
+ rcFile
+ "':\n"
+ " invalid command name '"
+ override.command
+ "'."));
continue;
}
// We've moved on to another rc file "chunk," store the accumulated args from the last one.
if (!rcFile.equals(lastRcFile)) {
if (lastRcFile != null) {
// Go through the various commands identified in this rc file (or chunk of file) and
// store them grouped first by command, then by rc chunk.
for (String commandKey : commandToArgMapForLastRc.keySet()) {
commandToRcArgs.put(
commandKey,
new RcChunkOfArgs(lastRcFile, commandToArgMapForLastRc.get(commandKey)));
}
}
lastRcFile = rcFile;
commandToArgMapForLastRc = ArrayListMultimap.create();
}
commandToArgMapForLastRc.put(override.command, override.option);
}
if (lastRcFile != null) {
// Once again, for this last rc file chunk, store them grouped by command.
for (String commandKey : commandToArgMapForLastRc.keySet()) {
commandToRcArgs.put(
commandKey, new RcChunkOfArgs(lastRcFile, commandToArgMapForLastRc.get(commandKey)));
}
}
return commandToRcArgs;
}
private static DetailedExitCode createDetailedExitCode(String message, Code detailedCode) {
return DetailedExitCode.of(
FailureDetail.newBuilder()
.setMessage(message)
.setCommand(FailureDetails.Command.newBuilder().setCode(detailedCode))
.build());
}
private static void partitionCommandLineArgs(
List<String> cmdLine,
ImmutableList.Builder<String> defaultOverridesAndRcSources,
ImmutableList.Builder<String> remainingCmdLine) {
Iterator<String> cmdLineIterator = cmdLine.iterator();
while (cmdLineIterator.hasNext()) {
String option = cmdLineIterator.next();
if (option.startsWith("--rc_source=") || option.startsWith("--default_override=")) {
defaultOverridesAndRcSources.add(option);
} else if (option.equals("--rc_source") || option.equals("--default_override")) {
Optional<String> possibleArgument =
cmdLineIterator.hasNext() ? Optional.of(cmdLineIterator.next()) : Optional.empty();
defaultOverridesAndRcSources.add(option);
if (possibleArgument.isPresent()) {
defaultOverridesAndRcSources.add(possibleArgument.get());
}
} else {
remainingCmdLine.add(option);
}
}
}
}