Refactor BlazeOptionHandler to pull out some functionality into dedicated classes.
RELNOTES:None
PiperOrigin-RevId: 301893566
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeOptionHandler.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeOptionHandler.java
index 9b5d9ff..4896a96 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeOptionHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeOptionHandler.java
@@ -16,9 +16,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
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.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
@@ -26,21 +24,16 @@
import com.google.devtools.build.lib.runtime.commands.ProjectFileSupport;
import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
import com.google.devtools.build.lib.util.ExitCode;
-import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
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.OptionValueDescription;
import com.google.devtools.common.options.OptionsParser;
import com.google.devtools.common.options.OptionsParsingException;
import com.google.devtools.common.options.OptionsParsingResult;
-import com.google.devtools.common.options.ParsedOptionDescription;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
@@ -168,20 +161,20 @@
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.args.isEmpty()) {
+ if (!rcArgs.getArgs().isEmpty()) {
String inherited = commandToParse.equals(commandAnnotation.name()) ? "" : "Inherited ";
String source =
- rcArgs.rcFile.equals("client")
+ rcArgs.getRcFile().equals("client")
? "Options provided by the client"
: String.format(
"Reading rc options for '%s' from %s",
- commandAnnotation.name(), rcArgs.rcFile);
+ commandAnnotation.name(), rcArgs.getRcFile());
rcfileNotes.add(
String.format(
"%s:\n %s'%s' options: %s",
- source, inherited, commandToParse, Joiner.on(' ').join(rcArgs.args)));
+ source, inherited, commandToParse, Joiner.on(' ').join(rcArgs.getArgs())));
}
- optionsParser.parse(PriorityCategory.RC_FILE, rcArgs.rcFile, rcArgs.args);
+ optionsParser.parse(PriorityCategory.RC_FILE, rcArgs.getRcFile(), rcArgs.getArgs());
}
}
}
@@ -316,44 +309,6 @@
return ExitCode.SUCCESS;
}
- private static String getPlatformName() {
- switch (OS.getCurrent()) {
- case LINUX:
- return "linux";
- case DARWIN:
- return "macos";
- case WINDOWS:
- return "windows";
- case FREEBSD:
- return "freebsd";
- case OPENBSD:
- return "openbsd";
- default:
- return OS.getCurrent().getCanonicalName();
- }
- }
-
- /**
- * If --enable_platform_specific_config is true and the corresponding config definition exists, we
- * should enable the platform specific config.
- */
- private boolean shouldEnablePlatformSpecificConfig(
- OptionValueDescription enablePlatformSpecificConfigDescription,
- ListMultimap<String, RcChunkOfArgs> commandToRcArgs) {
- if (enablePlatformSpecificConfigDescription == null
- || !(boolean) enablePlatformSpecificConfigDescription.getValue()) {
- return false;
- }
-
- for (String commandName : getCommandNamesToParse(commandAnnotation)) {
- String defaultConfigDef = commandName + ":" + getPlatformName();
- if (commandToRcArgs.containsKey(defaultConfigDef)) {
- return true;
- }
- }
- return false;
- }
-
/**
* Expand the values of --config according to the definitions provided in the rc files and the
* applicable command.
@@ -361,162 +316,12 @@
void expandConfigOptions(
EventHandler eventHandler, ListMultimap<String, RcChunkOfArgs> commandToRcArgs)
throws OptionsParsingException {
-
- OptionValueDescription configValueDescription =
- optionsParser.getOptionValueDescription("config");
- if (configValueDescription != null && configValueDescription.getCanonicalInstances() != null) {
- // Find the base set of configs. This does not include the config options that might be
- // recursively included.
- ImmutableList<ParsedOptionDescription> configInstances =
- ImmutableList.copyOf(configValueDescription.getCanonicalInstances());
-
- // Expand the configs that are mentioned in the input. Flatten these expansions before parsing
- // them, to preserve order.
- for (ParsedOptionDescription configInstance : configInstances) {
- String configValueToExpand = (String) configInstance.getConvertedValue();
- List<String> expansion = getExpansion(eventHandler, commandToRcArgs, configValueToExpand);
- optionsParser.parseArgsAsExpansionOfOption(
- configInstance, String.format("expanded from --%s", configValueToExpand), expansion);
- }
- }
-
- OptionValueDescription enablePlatformSpecificConfigDescription =
- optionsParser.getOptionValueDescription("enable_platform_specific_config");
- if (shouldEnablePlatformSpecificConfig(
- enablePlatformSpecificConfigDescription, commandToRcArgs)) {
- List<String> expansion = getExpansion(eventHandler, commandToRcArgs, getPlatformName());
- optionsParser.parseArgsAsExpansionOfOption(
- Iterables.getOnlyElement(enablePlatformSpecificConfigDescription.getCanonicalInstances()),
- String.format("enabled by --enable_platform_specific_config"),
- expansion);
- }
-
- // At this point, we've expanded everything, identify duplicates, if any, to warn about
- // re-application.
- List<String> configs = optionsParser.getOptions(CommonCommandOptions.class).configs;
- Set<String> configSet = new HashSet<>();
- LinkedHashSet<String> duplicateConfigs = new LinkedHashSet<>();
- for (String configValue : configs) {
- if (!configSet.add(configValue)) {
- duplicateConfigs.add(configValue);
- }
- }
- if (!duplicateConfigs.isEmpty()) {
- eventHandler.handle(
- Event.warn(
- String.format(
- "The following configs were expanded more than once: %s. For repeatable flags, "
- + "repeats are counted twice and may lead to unexpected behavior.",
- duplicateConfigs)));
- }
- }
-
- private List<String> getExpansion(
- EventHandler eventHandler,
- ListMultimap<String, RcChunkOfArgs> commandToRcArgs,
- String configToExpand)
- throws OptionsParsingException {
- LinkedHashSet<String> configAncestorSet = new LinkedHashSet<>();
- configAncestorSet.add(configToExpand);
- List<String> longestChain = new ArrayList<>();
- List<String> finalExpansion =
- getExpansion(
- eventHandler, commandToRcArgs, configAncestorSet, configToExpand, longestChain);
-
- // In order to prevent warning about a long chain of 13 configs at the 10, 11, 12, and 13
- // point, we identify the longest chain for this 'high-level' --config found and only warn
- // about it once. This may mean we missed a fork where each branch was independently long
- // enough to warn, but the single warning should convey the message reasonably.
- if (longestChain.size() >= 10) {
- eventHandler.handle(
- Event.warn(
- String.format(
- "There is a recursive chain of configs %s configs long: %s. This seems "
- + "excessive, and might be hiding errors.",
- longestChain.size(), longestChain)));
- }
- return finalExpansion;
- }
-
- /**
- * @param configAncestorSet is the chain of configs that have led to this one getting expanded.
- * This should only contain the configs that expanded, recursively, to this one, and should
- * not contain "siblings," as it is used to detect cycles. {@code build:foo --config=bar},
- * {@code build:bar --config=foo}, is a cycle, detected because this list will be [foo, bar]
- * when we find another 'foo' to expand. However, {@code build:foo --config=bar}, {@code
- * build:foo --config=bar} is not a cycle just because bar is expanded twice, and the 1st bar
- * should not be in the parents list of the second bar.
- * @param longestChain will be populated with the longest inheritance chain of configs.
- */
- private List<String> getExpansion(
- EventHandler eventHandler,
- ListMultimap<String, RcChunkOfArgs> commandToRcArgs,
- LinkedHashSet<String> configAncestorSet,
- String configToExpand,
- List<String> longestChain)
- throws OptionsParsingException {
- List<String> expansion = new ArrayList<>();
- boolean foundDefinition = false;
- // The expansion order of rc files is first by command priority, and then in the order the
- // rc files were read, respecting import statement placement.
- for (String commandToParse : getCommandNamesToParse(commandAnnotation)) {
- String configDef = commandToParse + ":" + configToExpand;
- for (RcChunkOfArgs rcArgs : commandToRcArgs.get(configDef)) {
- foundDefinition = true;
- rcfileNotes.add(
- String.format(
- "Found applicable config definition %s in file %s: %s",
- configDef, rcArgs.rcFile, String.join(" ", rcArgs.args)));
-
- // For each arg in the rcARgs chunk, we first check if it is a config, and if so, expand
- // it in place. We avoid cycles by tracking the parents of this config.
- for (String arg : rcArgs.args) {
- expansion.add(arg);
- if (arg.length() >= 8 && arg.substring(0, 8).equals("--config")) {
- // We have a config. For sanity, because we don't want to worry about formatting,
- // we will only accept --config=value, and will not accept value on a following line.
- int charOfConfigValue = arg.indexOf('=');
- if (charOfConfigValue < 0) {
- throw new OptionsParsingException(
- String.format(
- "In file %s, the definition of config %s expands to another config "
- + "that either has no value or is not in the form --config=value. For "
- + "recursive config definitions, please do not provide the value in a "
- + "separate token, such as in the form '--config value'.",
- rcArgs.rcFile, configToExpand));
- }
- String newConfigValue = arg.substring(charOfConfigValue + 1);
- LinkedHashSet<String> extendedConfigAncestorSet =
- new LinkedHashSet<>(configAncestorSet);
- if (!extendedConfigAncestorSet.add(newConfigValue)) {
- throw new OptionsParsingException(
- String.format(
- "Config expansion has a cycle: config value %s expands to itself, "
- + "see inheritance chain %s",
- newConfigValue, extendedConfigAncestorSet));
- }
- if (extendedConfigAncestorSet.size() > longestChain.size()) {
- longestChain.clear();
- longestChain.addAll(extendedConfigAncestorSet);
- }
-
- expansion.addAll(
- getExpansion(
- eventHandler,
- commandToRcArgs,
- extendedConfigAncestorSet,
- newConfigValue,
- longestChain));
- }
- }
- }
- }
-
- if (!foundDefinition) {
- throw new OptionsParsingException(
- "Config value " + configToExpand + " is not defined in any .rc file");
- }
- return expansion;
+ ConfigExpander.expandConfigOptions(
+ eventHandler,
+ commandToRcArgs,
+ getCommandNamesToParse(commandAnnotation),
+ rcfileNotes::add,
+ optionsParser);
}
private static List<String> getCommandNamesToParse(Command commandAnnotation) {
@@ -549,43 +354,6 @@
}
/**
- * We receive the rc file arguments from the client in an order that maintains the location of
- * "import" statements, expanding the imported rc file in place so that its args override previous
- * args in the file and are overridden by later arguments. We cannot group the args by rc file for
- * parsing, as we would lose this ordering, so we store them in these "chunks."
- *
- * <p>Each chunk comes from a single rc file, but the args stored here may not contain the entire
- * file if its contents were interrupted by an import statement.
- */
- static class RcChunkOfArgs {
- public RcChunkOfArgs(String rcFile, List<String> args) {
- this.rcFile = rcFile;
- this.args = args;
- }
-
- // The name of the rc file, usually a path.
- String rcFile;
- // The list of arguments specified in this rc "chunk". This is all for a single command (or
- // command:config definition), as different commands will be grouped together, so this list of
- // arguments can all be parsed as a continuous group.
- List<String> args;
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof RcChunkOfArgs) {
- RcChunkOfArgs other = (RcChunkOfArgs) o;
- return rcFile.equals(other.rcFile) && args.equals(other.args);
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return rcFile.hashCode() + args.hashCode();
- }
- }
-
- /**
* 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
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/ConfigExpander.java b/src/main/java/com/google/devtools/build/lib/runtime/ConfigExpander.java
new file mode 100644
index 0000000..31e86a7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/runtime/ConfigExpander.java
@@ -0,0 +1,271 @@
+// 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 com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.util.OS;
+import com.google.devtools.common.options.OptionValueDescription;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.OptionsParsingException;
+import com.google.devtools.common.options.ParsedOptionDescription;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/** Encapsulates logic for performing --config option expansion. */
+final class ConfigExpander {
+
+ private ConfigExpander() {}
+
+ private static String getPlatformName() {
+ switch (OS.getCurrent()) {
+ case LINUX:
+ return "linux";
+ case DARWIN:
+ return "macos";
+ case WINDOWS:
+ return "windows";
+ case FREEBSD:
+ return "freebsd";
+ case OPENBSD:
+ return "openbsd";
+ default:
+ return OS.getCurrent().getCanonicalName();
+ }
+ }
+
+ /**
+ * If --enable_platform_specific_config is true and the corresponding config definition exists, we
+ * should enable the platform specific config.
+ */
+ private static boolean shouldEnablePlatformSpecificConfig(
+ OptionValueDescription enablePlatformSpecificConfigDescription,
+ ListMultimap<String, RcChunkOfArgs> commandToRcArgs,
+ List<String> commandsToParse) {
+ if (enablePlatformSpecificConfigDescription == null
+ || !(boolean) enablePlatformSpecificConfigDescription.getValue()) {
+ return false;
+ }
+
+ for (String commandName : commandsToParse) {
+ String defaultConfigDef = commandName + ":" + getPlatformName();
+ if (commandToRcArgs.containsKey(defaultConfigDef)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Expands --config options present in the requested commands using the options configuration
+ * provided in commandToRcArgs.
+ *
+ * @param eventHandler collects any warnings encountered.
+ * @param rcFileNotesConsumer collects any informational messages encountered.
+ * @param optionsParser will parse the expanded --config representations.
+ * @throws OptionsParsingException if a fatal problem with the configuration is encountered.
+ */
+ static void expandConfigOptions(
+ EventHandler eventHandler,
+ ListMultimap<String, RcChunkOfArgs> commandToRcArgs,
+ List<String> commandsToParse,
+ Consumer<String> rcFileNotesConsumer,
+ OptionsParser optionsParser)
+ throws OptionsParsingException {
+
+ OptionValueDescription configValueDescription =
+ optionsParser.getOptionValueDescription("config");
+ if (configValueDescription != null && configValueDescription.getCanonicalInstances() != null) {
+ // Find the base set of configs. This does not include the config options that might be
+ // recursively included.
+ ImmutableList<ParsedOptionDescription> configInstances =
+ ImmutableList.copyOf(configValueDescription.getCanonicalInstances());
+
+ // Expand the configs that are mentioned in the input. Flatten these expansions before parsing
+ // them, to preserve order.
+ for (ParsedOptionDescription configInstance : configInstances) {
+ String configValueToExpand = (String) configInstance.getConvertedValue();
+ List<String> expansion =
+ getExpansion(
+ eventHandler,
+ commandToRcArgs,
+ commandsToParse,
+ configValueToExpand,
+ rcFileNotesConsumer);
+ optionsParser.parseArgsAsExpansionOfOption(
+ configInstance, String.format("expanded from --%s", configValueToExpand), expansion);
+ }
+ }
+
+ OptionValueDescription enablePlatformSpecificConfigDescription =
+ optionsParser.getOptionValueDescription("enable_platform_specific_config");
+ if (shouldEnablePlatformSpecificConfig(
+ enablePlatformSpecificConfigDescription, commandToRcArgs, commandsToParse)) {
+ List<String> expansion =
+ getExpansion(
+ eventHandler,
+ commandToRcArgs,
+ commandsToParse,
+ getPlatformName(),
+ rcFileNotesConsumer);
+ optionsParser.parseArgsAsExpansionOfOption(
+ Iterables.getOnlyElement(enablePlatformSpecificConfigDescription.getCanonicalInstances()),
+ String.format("enabled by --enable_platform_specific_config"),
+ expansion);
+ }
+
+ // At this point, we've expanded everything, identify duplicates, if any, to warn about
+ // re-application.
+ List<String> configs = optionsParser.getOptions(CommonCommandOptions.class).configs;
+ Set<String> configSet = new HashSet<>();
+ LinkedHashSet<String> duplicateConfigs = new LinkedHashSet<>();
+ for (String configValue : configs) {
+ if (!configSet.add(configValue)) {
+ duplicateConfigs.add(configValue);
+ }
+ }
+ if (!duplicateConfigs.isEmpty()) {
+ eventHandler.handle(
+ Event.warn(
+ String.format(
+ "The following configs were expanded more than once: %s. For repeatable flags, "
+ + "repeats are counted twice and may lead to unexpected behavior.",
+ duplicateConfigs)));
+ }
+ }
+
+ private static List<String> getExpansion(
+ EventHandler eventHandler,
+ ListMultimap<String, RcChunkOfArgs> commandToRcArgs,
+ List<String> commandsToParse,
+ String configToExpand,
+ Consumer<String> rcFileNotesConsumer)
+ throws OptionsParsingException {
+ LinkedHashSet<String> configAncestorSet = new LinkedHashSet<>();
+ configAncestorSet.add(configToExpand);
+ List<String> longestChain = new ArrayList<>();
+ List<String> finalExpansion =
+ getExpansion(
+ commandToRcArgs,
+ commandsToParse,
+ configAncestorSet,
+ configToExpand,
+ longestChain,
+ rcFileNotesConsumer);
+
+ // In order to prevent warning about a long chain of 13 configs at the 10, 11, 12, and 13
+ // point, we identify the longest chain for this 'high-level' --config found and only warn
+ // about it once. This may mean we missed a fork where each branch was independently long
+ // enough to warn, but the single warning should convey the message reasonably.
+ if (longestChain.size() >= 10) {
+ eventHandler.handle(
+ Event.warn(
+ String.format(
+ "There is a recursive chain of configs %s configs long: %s. This seems "
+ + "excessive, and might be hiding errors.",
+ longestChain.size(), longestChain)));
+ }
+ return finalExpansion;
+ }
+
+ /**
+ * @param configAncestorSet is the chain of configs that have led to this one getting expanded.
+ * This should only contain the configs that expanded, recursively, to this one, and should
+ * not contain "siblings," as it is used to detect cycles. {@code build:foo --config=bar},
+ * {@code build:bar --config=foo}, is a cycle, detected because this list will be [foo, bar]
+ * when we find another 'foo' to expand. However, {@code build:foo --config=bar}, {@code
+ * build:foo --config=bar} is not a cycle just because bar is expanded twice, and the 1st bar
+ * should not be in the parents list of the second bar.
+ * @param longestChain will be populated with the longest inheritance chain of configs.
+ */
+ private static List<String> getExpansion(
+ ListMultimap<String, RcChunkOfArgs> commandToRcArgs,
+ List<String> commandsToParse,
+ LinkedHashSet<String> configAncestorSet,
+ String configToExpand,
+ List<String> longestChain,
+ Consumer<String> rcFileNotesConsumer)
+ throws OptionsParsingException {
+ List<String> expansion = new ArrayList<>();
+ boolean foundDefinition = false;
+ // The expansion order of rc files is first by command priority, and then in the order the
+ // rc files were read, respecting import statement placement.
+ for (String commandToParse : commandsToParse) {
+ String configDef = commandToParse + ":" + configToExpand;
+ for (RcChunkOfArgs rcArgs : commandToRcArgs.get(configDef)) {
+ foundDefinition = true;
+ rcFileNotesConsumer.accept(
+ String.format(
+ "Found applicable config definition %s in file %s: %s",
+ configDef, rcArgs.getRcFile(), String.join(" ", rcArgs.getArgs())));
+
+ // For each arg in the rcARgs chunk, we first check if it is a config, and if so, expand
+ // it in place. We avoid cycles by tracking the parents of this config.
+ for (String arg : rcArgs.getArgs()) {
+ expansion.add(arg);
+ if (arg.length() >= 8 && arg.substring(0, 8).equals("--config")) {
+ // We have a config. For sanity, because we don't want to worry about formatting,
+ // we will only accept --config=value, and will not accept value on a following line.
+ int charOfConfigValue = arg.indexOf('=');
+ if (charOfConfigValue < 0) {
+ throw new OptionsParsingException(
+ String.format(
+ "In file %s, the definition of config %s expands to another config "
+ + "that either has no value or is not in the form --config=value. For "
+ + "recursive config definitions, please do not provide the value in a "
+ + "separate token, such as in the form '--config value'.",
+ rcArgs.getRcFile(), configToExpand));
+ }
+ String newConfigValue = arg.substring(charOfConfigValue + 1);
+ LinkedHashSet<String> extendedConfigAncestorSet =
+ new LinkedHashSet<>(configAncestorSet);
+ if (!extendedConfigAncestorSet.add(newConfigValue)) {
+ throw new OptionsParsingException(
+ String.format(
+ "Config expansion has a cycle: config value %s expands to itself, "
+ + "see inheritance chain %s",
+ newConfigValue, extendedConfigAncestorSet));
+ }
+ if (extendedConfigAncestorSet.size() > longestChain.size()) {
+ longestChain.clear();
+ longestChain.addAll(extendedConfigAncestorSet);
+ }
+
+ expansion.addAll(
+ getExpansion(
+ commandToRcArgs,
+ commandsToParse,
+ extendedConfigAncestorSet,
+ newConfigValue,
+ longestChain,
+ rcFileNotesConsumer));
+ }
+ }
+ }
+ }
+
+ if (!foundDefinition) {
+ throw new OptionsParsingException(
+ "Config value " + configToExpand + " is not defined in any .rc file");
+ }
+ return expansion;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/RcChunkOfArgs.java b/src/main/java/com/google/devtools/build/lib/runtime/RcChunkOfArgs.java
new file mode 100644
index 0000000..6e6fadc
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/runtime/RcChunkOfArgs.java
@@ -0,0 +1,63 @@
+// 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 java.util.List;
+
+/**
+ * We receive the rc file arguments from the client in an order that maintains the location of
+ * "import" statements, expanding the imported rc file in place so that its args override previous
+ * args in the file and are overridden by later arguments. We cannot group the args by rc file for
+ * parsing, as we would lose this ordering, so we store them in these "chunks."
+ *
+ * <p>Each chunk comes from a single rc file, but the args stored here may not contain the entire
+ * file if its contents were interrupted by an import statement.
+ */
+final class RcChunkOfArgs {
+ public RcChunkOfArgs(String rcFile, List<String> args) {
+ this.rcFile = rcFile;
+ this.args = args;
+ }
+
+ private final String rcFile;
+ private final List<String> args;
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof RcChunkOfArgs) {
+ RcChunkOfArgs other = (RcChunkOfArgs) o;
+ return getRcFile().equals(other.getRcFile()) && getArgs().equals(other.getArgs());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return getRcFile().hashCode() + getArgs().hashCode();
+ }
+
+ /** The name of the rc file, usually a path. */
+ String getRcFile() {
+ return rcFile;
+ }
+
+ /**
+ * The list of arguments specified in this rc "chunk". This is all for a single command (or
+ * command:config definition), as different commands will be grouped together, so this list of
+ * arguments can all be parsed as a continuous group.
+ */
+ List<String> getArgs() {
+ return args;
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/BlazeOptionHandlerTest.java b/src/test/java/com/google/devtools/build/lib/runtime/BlazeOptionHandlerTest.java
index f769fe0..e92b29f 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/BlazeOptionHandlerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/BlazeOptionHandlerTest.java
@@ -25,7 +25,6 @@
import com.google.devtools.build.lib.bazel.rules.BazelRulesModule;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.StoredEventHandler;
-import com.google.devtools.build.lib.runtime.BlazeOptionHandler.RcChunkOfArgs;
import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
import com.google.devtools.build.lib.testutil.Scratch;
import com.google.devtools.build.lib.testutil.TestConstants;