Preserve relative order of explicit and expanded Starlark flags
Previously, Starlark flags expanded from a --config stanze would always
be added to the end of the residue containing the explicitly given
Starlark flags. As a result, Starlark flags expanded from --config could
not be overridden.
With this commit, Starlark flags are parsed with the same semantics as
a regular allowMultiple option, thus preserving their order through
expansions.
This is implemented by introducing a synthetic allowMultiple option of
type List<String> in OptionsParserImpl that skipped args are parsed
into.
As a result, Starlark options are now available from the new
getSkippedArgs() method on OptionsParser rather than as part of the
residue, with the latter now only containing build targets.
Fixes #13231
Fixes #15679
Closes #15807.
PiperOrigin-RevId: 467931815
Change-Id: Ic64c6e075c08d898e5e7b8bf4c777827134d89fa
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java b/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java
index edde8cf..ee55f17 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java
@@ -25,8 +25,6 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
-import com.google.devtools.build.lib.cmdline.LabelValidator;
-import com.google.devtools.build.lib.cmdline.LabelValidator.BadLabelException;
import com.google.devtools.build.lib.cmdline.TargetParsingException;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.ExtendedEventHandler;
@@ -131,25 +129,19 @@
// require multiple rounds of parsing to fit starlark-defined options into native option format.
@VisibleForTesting
public void parse(ExtendedEventHandler eventHandler) throws OptionsParsingException {
- ImmutableList.Builder<String> residue = new ImmutableList.Builder<>();
+ parseGivenArgs(eventHandler, nativeOptionsParser.getSkippedArgs());
+ }
+
+ @VisibleForTesting
+ public void parseGivenArgs(ExtendedEventHandler eventHandler, List<String> args)
+ throws OptionsParsingException {
// Map of <option name (label), <unparsed option value, loaded option>>.
Multimap<String, Pair<String, Target>> unparsedOptions = LinkedListMultimap.create();
- // sort the old residue into starlark flags and legitimate residue
- for (String arg : nativeOptionsParser.getPreDoubleDashResidue()) {
- // TODO(bazel-team): support single dash options?
- if (!arg.startsWith("--")) {
- residue.add(arg);
- continue;
- }
-
+ for (String arg : args) {
parseArg(arg, unparsedOptions, eventHandler);
}
- List<String> postDoubleDashResidue = nativeOptionsParser.getPostDoubleDashResidue();
- residue.addAll(postDoubleDashResidue);
- nativeOptionsParser.setResidue(residue.build(), postDoubleDashResidue);
-
if (unparsedOptions.isEmpty()) {
return;
}
@@ -236,6 +228,9 @@
Multimap<String, Pair<String, Target>> unparsedOptions,
ExtendedEventHandler eventHandler)
throws OptionsParsingException {
+ if (!arg.startsWith("--")) {
+ throw new OptionsParsingException("Invalid options syntax: " + arg, arg);
+ }
int equalsAt = arg.indexOf('=');
String name = equalsAt == -1 ? arg.substring(2) : arg.substring(2, equalsAt);
if (name.trim().isEmpty()) {
@@ -308,51 +303,6 @@
return buildSetting;
}
- /**
- * Separates out any Starlark options from the given list
- *
- * <p>This method doesn't go through the trouble to actually load build setting targets and verify
- * they are build settings, it just assumes all strings that look like they could be build
- * settings, aka are formatted like a flag and can parse out to a proper label, are build
- * settings. Use actual parsing functions above to do full build setting verification.
- *
- * @param list List of strings from which to parse out starlark options
- * @return Returns a pair of string lists. The first item contains the list of starlark options
- * that were removed; the second contains the remaining string from the original list.
- */
- public static Pair<ImmutableList<String>, ImmutableList<String>> removeStarlarkOptions(
- List<String> list) {
- ImmutableList.Builder<String> keep = ImmutableList.builder();
- ImmutableList.Builder<String> remove = ImmutableList.builder();
- for (String name : list) {
- // Check if the string is a flag and trim off "--" if so.
- if (!name.startsWith("--")) {
- keep.add(name);
- continue;
- }
- String potentialStarlarkFlag = name.substring(2);
- // Check if the string uses the "no" prefix for setting boolean flags to false, trim
- // off "no" if so.
- if (potentialStarlarkFlag.startsWith("no")) {
- potentialStarlarkFlag = potentialStarlarkFlag.substring(2);
- }
- // Check if the string contains a value, trim off the value if so.
- int equalsIdx = potentialStarlarkFlag.indexOf('=');
- if (equalsIdx > 0) {
- potentialStarlarkFlag = potentialStarlarkFlag.substring(0, equalsIdx);
- }
- // Check if we can properly parse the (potentially trimmed) string as a label. If so, count
- // as starlark flag, else count as regular residue.
- try {
- LabelValidator.validateAbsoluteLabel(potentialStarlarkFlag);
- remove.add(name);
- } catch (BadLabelException e) {
- keep.add(name);
- }
- }
- return Pair.of(remove.build(), keep.build());
- }
-
@VisibleForTesting
public static StarlarkOptionsParser newStarlarkOptionsParserForTesting(
SkyframeExecutor skyframeExecutor,
@@ -364,11 +314,6 @@
}
@VisibleForTesting
- public void setResidueForTesting(List<String> residue) {
- nativeOptionsParser.setResidue(residue, ImmutableList.of());
- }
-
- @VisibleForTesting
public OptionsParser getNativeOptionsParserFortesting() {
return nativeOptionsParser;
}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java
index 248dee2..163d8fd 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/CleanCommand.java
@@ -16,7 +16,6 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
import com.google.common.flogger.GoogleLogger;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.analysis.NoBuildEvent;
@@ -29,7 +28,6 @@
import com.google.devtools.build.lib.runtime.BlazeRuntime;
import com.google.devtools.build.lib.runtime.Command;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
-import com.google.devtools.build.lib.runtime.StarlarkOptionsParser;
import com.google.devtools.build.lib.runtime.commands.events.CleanStartingEvent;
import com.google.devtools.build.lib.server.FailureDetails;
import com.google.devtools.build.lib.server.FailureDetails.CleanCommand.Code;
@@ -39,7 +37,6 @@
import com.google.devtools.build.lib.util.CommandBuilder;
import com.google.devtools.build.lib.util.InterruptedFailureDetails;
import com.google.devtools.build.lib.util.OS;
-import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
@@ -49,6 +46,7 @@
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.List;
import java.util.UUID;
import java.util.logging.LogManager;
@@ -132,17 +130,15 @@
@Override
public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult options) {
- // Assert that the only residue is starlark options and ignore them.
- Pair<ImmutableList<String>, ImmutableList<String>> starlarkOptionsAndResidue =
- StarlarkOptionsParser.removeStarlarkOptions(options.getResidue());
- ImmutableList<String> removedStarlarkOptions = starlarkOptionsAndResidue.getFirst();
- ImmutableList<String> residue = starlarkOptionsAndResidue.getSecond();
- if (!removedStarlarkOptions.isEmpty()) {
+ // Assert that there is no residue and warn about Starlark options.
+ List<String> starlarkOptions = options.getSkippedArgs();
+ List<String> residue = options.getResidue();
+ if (!starlarkOptions.isEmpty()) {
env.getReporter()
.handle(
Event.warn(
"Blaze clean does not support starlark options. Ignoring options: "
- + removedStarlarkOptions));
+ + starlarkOptions));
}
if (!residue.isEmpty()) {
String message = "Unrecognized arguments: " + Joiner.on(' ').join(residue);
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java
index 05e6b67..c53cdf5 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/InfoCommand.java
@@ -31,7 +31,6 @@
import com.google.devtools.build.lib.runtime.Command;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.runtime.InfoItem;
-import com.google.devtools.build.lib.runtime.StarlarkOptionsParser;
import com.google.devtools.build.lib.runtime.commands.info.BlazeBinInfoItem;
import com.google.devtools.build.lib.runtime.commands.info.BlazeGenfilesInfoItem;
import com.google.devtools.build.lib.runtime.commands.info.BlazeTestlogsInfoItem;
@@ -66,7 +65,6 @@
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.util.ExitCode;
import com.google.devtools.build.lib.util.InterruptedFailureDetails;
-import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.util.io.OutErr;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
@@ -191,16 +189,14 @@
}
}
- Pair<ImmutableList<String>, ImmutableList<String>> starlarkOptionsAndResidue =
- StarlarkOptionsParser.removeStarlarkOptions(optionsParsingResult.getResidue());
- ImmutableList<String> removedStarlarkOptions = starlarkOptionsAndResidue.getFirst();
- ImmutableList<String> residue = starlarkOptionsAndResidue.getSecond();
- if (!removedStarlarkOptions.isEmpty()) {
+ List<String> starlarkOptions = optionsParsingResult.getSkippedArgs();
+ List<String> residue = optionsParsingResult.getResidue();
+ if (!starlarkOptions.isEmpty()) {
env.getReporter()
.handle(
Event.warn(
"info command does not support starlark options. Ignoring options: "
- + removedStarlarkOptions));
+ + starlarkOptions));
}
env.getEventBus().post(new NoBuildEvent());
diff --git a/src/main/java/com/google/devtools/common/options/OptionsParser.java b/src/main/java/com/google/devtools/common/options/OptionsParser.java
index a055cb0..6462f1a 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsParser.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsParser.java
@@ -23,6 +23,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MoreCollectors;
import com.google.common.escape.Escaper;
@@ -695,12 +696,7 @@
Preconditions.checkNotNull(priority);
Preconditions.checkArgument(priority != OptionPriority.PriorityCategory.DEFAULT);
OptionsParserImplResult optionsParserImplResult = impl.parse(priority, sourceFunction, args);
- residue.addAll(optionsParserImplResult.getResidue());
- postDoubleDashResidue.addAll(optionsParserImplResult.postDoubleDashResidue);
- if (!allowResidue && !residue.isEmpty()) {
- String errorMsg = "Unrecognized arguments: " + Joiner.on(' ').join(residue);
- throw new OptionsParsingException(errorMsg);
- }
+ addResidueFromResult(optionsParserImplResult);
aliases.putAll(optionsParserImplResult.aliases);
}
@@ -726,10 +722,16 @@
args);
OptionsParserImplResult optionsParserImplResult =
impl.parseArgsAsExpansionOfOption(optionToExpand, o -> source, args);
- residue.addAll(optionsParserImplResult.getResidue());
- postDoubleDashResidue.addAll(optionsParserImplResult.postDoubleDashResidue);
- if (!allowResidue && !residue.isEmpty()) {
- String errorMsg = "Unrecognized arguments: " + Joiner.on(' ').join(residue);
+ addResidueFromResult(optionsParserImplResult);
+ }
+
+ private void addResidueFromResult(OptionsParserImplResult result) throws OptionsParsingException {
+ residue.addAll(result.getResidue());
+ postDoubleDashResidue.addAll(result.postDoubleDashResidue);
+ if (!allowResidue && (!getSkippedArgs().isEmpty() || !residue.isEmpty())) {
+ String errorMsg =
+ "Unrecognized arguments: "
+ + Joiner.on(' ').join(Iterables.concat(getSkippedArgs(), residue));
throw new OptionsParsingException(errorMsg);
}
}
@@ -771,7 +773,12 @@
}
@Override
- public List<String> getResidue() {
+ public ImmutableList<String> getSkippedArgs() {
+ return ImmutableList.copyOf(impl.getSkippedArgs());
+ }
+
+ @Override
+ public ImmutableList<String> getResidue() {
return ImmutableList.copyOf(residue);
}
diff --git a/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java b/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java
index 41b6a3a..4be0136 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java
@@ -28,6 +28,7 @@
import com.google.devtools.common.options.OptionValueDescription.ExpansionBundle;
import com.google.devtools.common.options.OptionsParser.OptionDescription;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.google.errorprone.annotations.Keep;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
@@ -37,6 +38,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -177,6 +179,33 @@
@Nullable private final String aliasFlag;
@Nullable private final Object conversionContext;
+ /**
+ * This option is used to collect skipped arguments while preserving the relative ordering between
+ * those given explicitly on the command line and those expanded by {@code ConfigExpander}. The
+ * field itself is not used for any purpose other than retrieving its {@link Option} annotation.
+ */
+ @Keep
+ @Option(
+ name = "skipped args",
+ allowMultiple = true,
+ defaultValue = "null",
+ documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ effectTags = {OptionEffectTag.NO_OP},
+ help = "Only used internally by OptionsParserImpl")
+ private final List<String> skippedArgs = new ArrayList<>();
+
+ private static final OptionDefinition skippedArgsDefinition;
+
+ static {
+ try {
+ skippedArgsDefinition =
+ OptionDefinition.extractOptionDefinition(
+ OptionsParserImpl.class.getDeclaredField("skippedArgs"));
+ } catch (NoSuchFieldException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
OptionsParserImpl(
OptionsData optionsData,
ArgsPreProcessor argsPreProcessor,
@@ -249,6 +278,7 @@
/** Implements {@link OptionsParser#canonicalize}. */
List<ParsedOptionDescription> asCanonicalizedListOfParsedOptions() {
return optionValues.keySet().stream()
+ .filter(k -> !Objects.equals(k, skippedArgsDefinition))
.map(optionDefinition -> optionValues.get(optionDefinition).getCanonicalInstances())
.flatMap(Collection::stream)
// Return the effective (canonical) options in the order they were applied.
@@ -388,6 +418,15 @@
return optionValues.get(optionDefinition) != null;
}
+ @SuppressWarnings("unchecked")
+ List<String> getSkippedArgs() {
+ OptionValueDescription value = optionValues.get(skippedArgsDefinition);
+ if (value == null) {
+ return ImmutableList.of();
+ }
+ return (List<String>) value.getValue();
+ }
+
/**
* Parses the args, and returns what it doesn't parse. May be called multiple times, and may be
* called recursively. The option's definition dictates how it reacts to multiple settings. By
@@ -435,19 +474,33 @@
arg = swapShorthandAlias(arg);
- if (containsSkippedPrefix(arg)) {
- unparsedArgs.add(arg);
- continue;
- }
-
if (arg.equals("--")) { // "--" means all remaining args aren't options
Iterators.addAll(unparsedPostDoubleDashArgs, argsIterator);
break;
}
- ParsedOptionDescription parsedOption =
- identifyOptionAndPossibleArgument(
- arg, argsIterator, priority, sourceFunction, implicitDependent, expandedFrom);
+ ParsedOptionDescription parsedOption;
+ if (containsSkippedPrefix(arg)) {
+ // Parse the skipped arg into a synthetic allowMultiple option to preserve its order
+ // relative to skipped args coming from expansions. Simply adding it to the residue would
+ // end up placing expanded skipped args after all explicitly given skipped args, which isn't
+ // correct.
+ parsedOption =
+ ParsedOptionDescription.newParsedOptionDescription(
+ skippedArgsDefinition,
+ arg,
+ arg,
+ new OptionInstanceOrigin(
+ priority,
+ sourceFunction.apply(skippedArgsDefinition),
+ implicitDependent,
+ expandedFrom),
+ conversionContext);
+ } else {
+ parsedOption =
+ identifyOptionAndPossibleArgument(
+ arg, argsIterator, priority, sourceFunction, implicitDependent, expandedFrom);
+ }
handleNewParsedOption(parsedOption);
priority = OptionPriority.nextOptionPriority(priority);
}
@@ -595,7 +648,11 @@
// As much as possible, we want the behaviors of these different types of flags to be
// identical, as this minimizes the number of edge cases, but we do not yet track these values
// in the same way.
- if (parsedOption.getImplicitDependent() == null) {
+
+ // Do not list the internal "skipped args" option that is only used to accumulate skipped
+ // arguments.
+ if (parsedOption.getImplicitDependent() == null
+ && !Objects.equals(parsedOption.getOptionDefinition(), skippedArgsDefinition)) {
// Log explicit options and expanded options in the order they are parsed (can be sorted
// later). This information is needed to correctly canonicalize flags.
parsedOptions.add(parsedOption);
@@ -638,7 +695,7 @@
} else if (arg.startsWith("--")) { // --long_option
int equalsAt = arg.indexOf('=');
- int nameStartsAt = arg.startsWith("--") ? 2 : 1;
+ int nameStartsAt = 2;
String name =
equalsAt == -1 ? arg.substring(nameStartsAt) : arg.substring(nameStartsAt, equalsAt);
if (name.trim().isEmpty()) {
diff --git a/src/main/java/com/google/devtools/common/options/OptionsParsingResult.java b/src/main/java/com/google/devtools/common/options/OptionsParsingResult.java
index c3d2685..64fc9a3 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsParsingResult.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsParsingResult.java
@@ -32,9 +32,12 @@
OptionValueDescription getOptionValueDescription(String name);
/**
- * Returns an immutable copy of the residue, that is, the arguments that
- * have not been parsed.
+ * Returns an immutable copy of all arguments that were skipped because they matched a skipped
+ * prefix.
*/
+ List<String> getSkippedArgs();
+
+ /** Returns an immutable copy of the residue, that is, the arguments that have not been parsed. */
List<String> getResidue();
/**
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 9c497cd..5382931 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
@@ -13,11 +13,13 @@
// limitations under the License.
package com.google.devtools.build.lib.runtime;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.devtools.build.lib.events.Event;
@@ -26,6 +28,7 @@
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.ParsedOptionDescription;
import com.google.devtools.common.options.TestOptions;
import java.util.Arrays;
import org.junit.Before;
@@ -54,7 +57,11 @@
ImmutableList.of(TestOptions.class, CommonCommandOptions.class, ClientOptions.class);
BlazeOptionHandlerTestHelper helper =
- new BlazeOptionHandlerTestHelper(optionsClasses, /* allowResidue= */ true);
+ new BlazeOptionHandlerTestHelper(
+ optionsClasses,
+ /* allowResidue= */ true,
+ /* aliasFlag= */ null,
+ /* skipStarlarkPrefixes= */ true);
eventHandler = helper.getEventHandler();
parser = helper.getOptionsParser();
optionHandler = helper.getOptionHandler();
@@ -336,6 +343,73 @@
}
@Test
+ public void testExpandConfigOptions_skippedArgsOrderPreserved() throws Exception {
+ ImmutableListMultimap<String, RcChunkOfArgs> rcContent =
+ ImmutableListMultimap.of(
+ "c0:config1",
+ new RcChunkOfArgs("rc1", ImmutableList.of("--//f=2", "--//f=3")),
+ "c0:config2b",
+ new RcChunkOfArgs("rc1", ImmutableList.of("--//f=6")),
+ "c0:config2",
+ new RcChunkOfArgs("rc1", ImmutableList.of("--config=config2a", "--config=config2b")),
+ "c0:config2a",
+ new RcChunkOfArgs("rc1", ImmutableList.of("--//f=5")));
+ parser.parse(
+ "--test_multiple_string=1",
+ "--//f=1",
+ "--test_multiple_string=2",
+ "--config=config1",
+ "--test_multiple_string=3",
+ "--//f=4",
+ "--test_multiple_string=4",
+ "--config=config2",
+ "--test_multiple_string=5",
+ "--//f=7",
+ "--test_multiple_string=6");
+ optionHandler.expandConfigOptions(eventHandler, rcContent);
+
+ assertThat(parser.getSkippedArgs())
+ .containsExactly(
+ "--//f=1", "--//f=2", "--//f=3", "--//f=4", "--//f=5", "--//f=6", "--//f=7")
+ .inOrder();
+
+ // Verify that the order of non-skipped args is as expected and that skipped args are not
+ // reported as parsed.
+ assertThat(
+ parser.asListOfCanonicalOptions().stream()
+ .map(ParsedOptionDescription::getCanonicalForm)
+ .collect(toImmutableList()))
+ .containsExactly(
+ "--test_multiple_string=1",
+ "--test_multiple_string=2",
+ "--config=config1",
+ "--test_multiple_string=3",
+ "--test_multiple_string=4",
+ "--config=config2",
+ "--config=config2a",
+ "--config=config2b",
+ "--test_multiple_string=5",
+ "--test_multiple_string=6")
+ .inOrder();
+ assertThat(
+ parser.asCompleteListOfParsedOptions().stream()
+ .map(ParsedOptionDescription::getCanonicalForm)
+ .collect(toImmutableList()))
+ .containsExactly(
+ "--test_multiple_string=1",
+ "--test_multiple_string=2",
+ "--config=config1",
+ "--test_multiple_string=3",
+ "--test_multiple_string=4",
+ "--config=config2",
+ "--config=config2a",
+ "--config=config2b",
+ "--test_multiple_string=5",
+ "--test_multiple_string=6")
+ .inOrder();
+ }
+
+ @Test
public void testUndefinedConfig() throws Exception {
parser.parse("--config=invalid");
OptionsParsingException e =
@@ -364,6 +438,24 @@
}
@Test
+ public void testParseOptions_disallowResidue_skippedArgsLeadToFailure() throws Exception {
+ ImmutableList<Class<? extends OptionsBase>> optionsClasses =
+ ImmutableList.of(TestOptions.class, CommonCommandOptions.class, ClientOptions.class);
+
+ BlazeOptionHandlerTestHelper helper =
+ new BlazeOptionHandlerTestHelper(
+ optionsClasses,
+ /* allowResidue= */ false,
+ /* aliasFlag= */ null,
+ /* skipStarlarkPrefixes= */ true);
+ OptionsParser parser = helper.getOptionsParser();
+
+ OptionsParsingException e =
+ assertThrows(OptionsParsingException.class, () -> parser.parse("--//f=1"));
+ assertThat(e).hasMessageThat().isEqualTo("Unrecognized arguments: --//f=1");
+ }
+
+ @Test
public void testParseOptions_explicitOption() {
optionHandler.parseOptions(
ImmutableList.of("c0", "--test_multiple_string=explicit"), eventHandler);
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/FlagAliasTest.java b/src/test/java/com/google/devtools/build/lib/runtime/FlagAliasTest.java
index 3ff89c6..5f675e8 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/FlagAliasTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/FlagAliasTest.java
@@ -116,7 +116,7 @@
ImmutableList.of(
"c0", "--rc_source=/somewhere/.blazerc", "--flag_alias=foo=//bar", "--foo");
optionHandler.parseOptions(args, eventHandler);
- assertThat(parser.getResidue()).contains("--//bar");
+ assertThat(parser.getSkippedArgs()).contains("--//bar");
}
@Test
@@ -142,9 +142,9 @@
"--foo",
"--flag_alias=foo=//baz",
"--foo");
- ImmutableList<String> expectedResidue = ImmutableList.of("--//bar", "--//baz");
+ ImmutableList<String> expectedSkippedArgs = ImmutableList.of("--//bar", "--//baz");
optionHandler.parseOptions(args, eventHandler);
- assertThat(parser.getResidue()).isEqualTo(expectedResidue);
+ assertThat(parser.getSkippedArgs()).isEqualTo(expectedSkippedArgs);
}
@Test
@@ -155,9 +155,9 @@
"--default_override=0:c0=--flag_alias=foo=//bar",
"--default_override=0:c0=--foo",
"--rc_source=/somewhere/.blazerc");
- ImmutableList<String> expectedResidue = ImmutableList.of("--//bar");
+ ImmutableList<String> expectedSkippedArgs = ImmutableList.of("--//bar");
optionHandler.parseOptions(args, eventHandler);
- assertThat(parser.getResidue()).isEqualTo(expectedResidue);
+ assertThat(parser.getSkippedArgs()).isEqualTo(expectedSkippedArgs);
}
@Test
@@ -168,9 +168,9 @@
"--default_override=0:c0=--flag_alias=foo=//bar",
"--rc_source=/somewhere/.blazerc",
"--foo");
- ImmutableList<String> expectedResidue = ImmutableList.of("--//bar");
+ ImmutableList<String> expectedSkippedArgs = ImmutableList.of("--//bar");
optionHandler.parseOptions(args, eventHandler);
- assertThat(parser.getResidue()).isEqualTo(expectedResidue);
+ assertThat(parser.getSkippedArgs()).isEqualTo(expectedSkippedArgs);
}
@Test
@@ -178,9 +178,9 @@
ImmutableList<String> args =
ImmutableList.of(
"c0", "--rc_source=/somewhere/.blazerc", "--flag_alias=foo=//bar", "--foo=7");
- ImmutableList<String> expectedResidue = ImmutableList.of("--//bar=7");
+ ImmutableList<String> expectedSkippedArgs = ImmutableList.of("--//bar=7");
optionHandler.parseOptions(args, eventHandler);
- assertThat(parser.getResidue()).isEqualTo(expectedResidue);
+ assertThat(parser.getSkippedArgs()).isEqualTo(expectedSkippedArgs);
}
// Regression test for b/172453517
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkOptionsParsingTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkOptionsParsingTest.java
index 6579eec..b42aee4 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkOptionsParsingTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkOptionsParsingTest.java
@@ -23,9 +23,7 @@
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.events.ExtendedEventHandler.Postable;
import com.google.devtools.build.lib.pkgcache.TargetParsingCompleteEvent;
-import com.google.devtools.build.lib.runtime.StarlarkOptionsParser;
import com.google.devtools.build.lib.starlark.util.StarlarkOptionsTestCase;
-import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.common.options.OptionsParsingException;
import com.google.devtools.common.options.OptionsParsingResult;
import java.util.ArrayList;
@@ -160,10 +158,11 @@
public void testSingleDash_notAllowed() throws Exception {
writeBasicIntFlag();
- OptionsParsingResult result = parseStarlarkOptions("-//test:my_int_setting=666");
-
- assertThat(result.getStarlarkOptions()).isEmpty();
- assertThat(result.getResidue()).containsExactly("-//test:my_int_setting=666");
+ OptionsParsingException e =
+ assertThrows(
+ OptionsParsingException.class,
+ () -> parseStarlarkOptions("-//test:my_int_setting=666"));
+ assertThat(e).hasMessageThat().isEqualTo("Invalid options syntax: -//test:my_int_setting=666");
}
// test --non_flag_setting=value
@@ -380,35 +379,6 @@
.isEqualTo(StarlarkInt.of(15));
}
- @Test
- public void testRemoveStarlarkOptionsWorks() throws Exception {
- Pair<ImmutableList<String>, ImmutableList<String>> residueAndStarlarkOptions =
- StarlarkOptionsParser.removeStarlarkOptions(
- ImmutableList.of(
- "--//local/starlark/option",
- "--//local/starlark/option=with_value",
- "--@some_repo//external/starlark/option",
- "--@some_repo//external/starlark/option=with_value",
- "--@//main/repo/option",
- "--@//main/repo/option=with_value",
- "some-random-residue",
- "--mangled//external/starlark/option",
- "--mangled//external/starlark/option=with_value"));
- assertThat(residueAndStarlarkOptions.getFirst())
- .containsExactly(
- "--//local/starlark/option",
- "--//local/starlark/option=with_value",
- "--@some_repo//external/starlark/option",
- "--@some_repo//external/starlark/option=with_value",
- "--@//main/repo/option",
- "--@//main/repo/option=with_value");
- assertThat(residueAndStarlarkOptions.getSecond())
- .containsExactly(
- "some-random-residue",
- "--mangled//external/starlark/option",
- "--mangled//external/starlark/option=with_value");
- }
-
/**
* When Starlark flags are only set as flags, they shouldn't produce {@link
* TargetParsingCompleteEvent}s. That's intended to communicate (to the build event protocol)
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/util/StarlarkOptionsTestCase.java b/src/test/java/com/google/devtools/build/lib/starlark/util/StarlarkOptionsTestCase.java
index ba64f41..d7baf5f 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/util/StarlarkOptionsTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/util/StarlarkOptionsTestCase.java
@@ -63,8 +63,8 @@
}
protected OptionsParsingResult parseStarlarkOptions(String options) throws Exception {
- starlarkOptionsParser.setResidueForTesting(Arrays.asList(options.split(" ")));
- starlarkOptionsParser.parse(new StoredEventHandler());
+ starlarkOptionsParser.parseGivenArgs(
+ new StoredEventHandler(), Arrays.asList(options.split(" ")));
return starlarkOptionsParser.getNativeOptionsParserFortesting();
}
diff --git a/src/test/py/bazel/BUILD b/src/test/py/bazel/BUILD
index a59f023..0e58a53 100644
--- a/src/test/py/bazel/BUILD
+++ b/src/test/py/bazel/BUILD
@@ -282,3 +282,9 @@
":test_base",
],
)
+
+py_test(
+ name = "starlark_options_test",
+ srcs = ["starlark_options_test.py"],
+ deps = [":test_base"],
+)
diff --git a/src/test/py/bazel/starlark_options_test.py b/src/test/py/bazel/starlark_options_test.py
new file mode 100644
index 0000000..f6433b5
--- /dev/null
+++ b/src/test/py/bazel/starlark_options_test.py
@@ -0,0 +1,82 @@
+# Copyright 2022 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.
+
+import unittest
+
+from src.test.py.bazel import test_base
+
+
+class StarlarkOptionsTest(test_base.TestBase):
+
+ def testCanOverrideStarlarkFlagInBazelrcConfigStanza(self):
+ self.ScratchFile("WORKSPACE.bazel")
+ self.ScratchFile("bazelrc", [
+ "build:red --//f:color=red",
+ ])
+ self.ScratchFile("f/BUILD.bazel", [
+ 'load(":f.bzl", "color", "r")',
+ "color(",
+ ' name = "color",',
+ ' build_setting_default = "white",',
+ ")",
+ 'r(name = "r")',
+ ])
+ self.ScratchFile("f/f.bzl", [
+ 'ColorValue = provider("color")',
+ "def _color_impl(ctx):",
+ " return [ColorValue(color = ctx.build_setting_value)]",
+ "color = rule(",
+ " implementation = _color_impl,",
+ "build_setting = config.string(flag = True),",
+ ")",
+ "def _r_impl(ctx):",
+ " print(ctx.attr._color[ColorValue].color)",
+ " return [DefaultInfo()]",
+ "r = rule(",
+ " implementation = _r_impl,",
+ ' attrs = {"_color": attr.label(default = "//f:color")},',
+ ")",
+ ])
+
+ exit_code, _, stderr = self.RunBazel([
+ "--bazelrc=bazelrc",
+ "build",
+ "--nobuild",
+ "//f:r",
+ "--config=red",
+ "--//f:color=green",
+ ])
+ self.AssertExitCode(exit_code, 0, stderr)
+ self.assertTrue(
+ any("/f/f.bzl:9:10: green" in line for line in stderr),
+ "\n".join(stderr),
+ )
+
+ exit_code, _, stderr = self.RunBazel([
+ "--bazelrc=bazelrc",
+ "build",
+ "--nobuild",
+ "//f:r",
+ "--//f:color=green",
+ "--config=red",
+ ])
+ self.AssertExitCode(exit_code, 0, stderr)
+ self.assertTrue(
+ any("/f/f.bzl:9:10: red" in line for line in stderr),
+ "\n".join(stderr),
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()