| // 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); | |
| } | |
| } | |
| } | |
| } |