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