Skip to content

Commit 8e52809

Browse files
authored
[7.6.0] Permit literal dicts as extra keyword args in MODULE.bazel (#25614)
Valid repository names are a strict superset of valid Starlark identifiers, but need to be specified as keyword arguments for `MODULE.bazel` functions such as `override_repo` and `use_repo`. This is made possible by relaxing the syntax checker for `MODULE.bazel` files only, which now allows ``` override_repo(ext2, **{"foo.2": "foo"}) ``` but not ``` KWARGS = {"foo.2": "foo"} override_repo(ext2, **KWARGS) ``` Fixes #19635 Fixes #25551 Closes #25553. PiperOrigin-RevId: 737928093 Change-Id: Iff5d740ef2101973e6d3da95edf73befd21afab7 (cherry picked from commit 1f54144) Fixes #25614
1 parent e882953 commit 8e52809

File tree

4 files changed

+96
-14
lines changed

4 files changed

+96
-14
lines changed

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/CompiledModuleFile.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ public static CompiledModuleFile parseAndCompile(
9393
static ImmutableList<IncludeStatement> checkModuleFileSyntax(StarlarkFile starlarkFile)
9494
throws SyntaxError.Exception {
9595
var includeStatements = ImmutableList.<IncludeStatement>builder();
96-
new DotBazelFileSyntaxChecker("MODULE.bazel files", /* canLoadBzl= */ false) {
96+
new DotBazelFileSyntaxChecker(
97+
"MODULE.bazel files", /* canLoadBzl= */ false, /* allowLiteralStarStarArgs= */ true) {
9798
// Once `include` the identifier is assigned to, we no longer care about its appearance
9899
// anywhere. This allows `include` to be used as a module extension proxy (and technically
99100
// any other variable binding).

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileGlobals.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -627,10 +627,15 @@ public void export(EventHandler handler, Label bzlFileLabel, String name) {
627627
@Param(
628628
name = "kwargs",
629629
doc =
630-
"Specifies certain repos to import into the scope of the current module with"
631-
+ " different names. The keys should be the name to use in the current scope,"
632-
+ " whereas the values should be the original names exported by the module"
633-
+ " extension."),
630+
"""
631+
Specifies certain repos to import into the scope of the current module with
632+
different names. The keys should be the name to use in the current scope,
633+
whereas the values should be the original names exported by the module
634+
extension.
635+
<p>Keys that are not valid identifiers can be specified via a literal dict
636+
passed as extra keyword arguments, e.g.,
637+
<code>use_repo(extension_proxy, **{"foo.2": "foo"})</code>.
638+
"""),
634639
useStarlarkThread = true)
635640
public void useRepo(
636641
ModuleExtensionProxy extensionProxy,
@@ -679,7 +684,11 @@ public void useRepo(
679684
"""
680685
The overrides to apply to the repos generated by the extension, where the values
681686
are the names of repos in the scope of the current module and the keys are the
682-
names of the repos they will override in the extension."""),
687+
names of the repos they will override in the extension.
688+
<p>Keys that are not valid identifiers can be specified via a literal dict
689+
passed as extra keyword arguments, e.g.,
690+
<code>override_repo(extension_proxy, **{"foo.2": "foo"})</code>.
691+
"""),
683692
useStarlarkThread = true)
684693
public void overrideRepo(
685694
ModuleExtensionProxy extensionProxy,
@@ -732,7 +741,11 @@ public void overrideRepo(
732741
"""
733742
The new repos to inject into the extension, where the values are the names of
734743
repos in the scope of the current module and the keys are the name they will be
735-
visible under in the extension."""),
744+
visible under in the extension.
745+
<p>Keys that are not valid identifiers can be specified via a literal dict
746+
passed as extra keyword arguments, e.g.,
747+
<code>inject_repo(extension_proxy, **{"foo.2": "foo"})</code>.
748+
"""),
736749
useStarlarkThread = true)
737750
public void injectRepo(
738751
ModuleExtensionProxy extensionProxy,

src/main/java/com/google/devtools/build/lib/packages/DotBazelFileSyntaxChecker.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import net.starlark.java.syntax.Argument;
2121
import net.starlark.java.syntax.CallExpression;
2222
import net.starlark.java.syntax.DefStatement;
23+
import net.starlark.java.syntax.DictExpression;
2324
import net.starlark.java.syntax.ForStatement;
2425
import net.starlark.java.syntax.IfStatement;
2526
import net.starlark.java.syntax.LambdaExpression;
@@ -46,16 +47,26 @@
4647
public class DotBazelFileSyntaxChecker extends NodeVisitor {
4748
private final String where;
4849
private final boolean canLoadBzl;
50+
private final boolean allowLiteralStarStarArgs;
4951
private ImmutableList.Builder<SyntaxError> errors = ImmutableList.builder();
5052

5153
/**
5254
* @param where describes the type of file being checked.
5355
* @param canLoadBzl whether the file type being check supports load statements. This is used to
5456
* generate more informative error messages.
57+
* @param allowLiteralStarStarArgs whether to allow **kwargs in function calls if the dict is a
58+
* literal. This is needed for some functions that take arbitrary keyword arguments whose keys
59+
* may have to contain non-identifier characters.
5560
*/
56-
public DotBazelFileSyntaxChecker(String where, boolean canLoadBzl) {
61+
public DotBazelFileSyntaxChecker(
62+
String where, boolean canLoadBzl, boolean allowLiteralStarStarArgs) {
5763
this.where = where;
5864
this.canLoadBzl = canLoadBzl;
65+
this.allowLiteralStarStarArgs = allowLiteralStarStarArgs;
66+
}
67+
68+
public DotBazelFileSyntaxChecker(String where, boolean canLoadBzl) {
69+
this(where, canLoadBzl, /* allowLiteralStarStarArgs= */ false);
5970
}
6071

6172
public final void check(StarlarkFile file) throws SyntaxError.Exception {
@@ -74,12 +85,19 @@ protected void error(Location loc, String message) {
7485
// Reject f(*args) and f(**kwargs) calls.
7586
private void rejectStarArgs(CallExpression call) {
7687
for (Argument arg : call.getArguments()) {
77-
if (arg instanceof Argument.StarStar) {
78-
error(
79-
arg.getStartLocation(),
80-
"**kwargs arguments are not allowed in "
81-
+ where
82-
+ ". Pass the arguments in explicitly.");
88+
if (arg instanceof Argument.StarStar starStar) {
89+
if (!allowLiteralStarStarArgs) {
90+
error(
91+
arg.getStartLocation(),
92+
"**kwargs arguments are not allowed in "
93+
+ where
94+
+ ". Pass the arguments in explicitly.");
95+
}
96+
if (!(starStar.getValue() instanceof DictExpression)) {
97+
error(
98+
arg.getStartLocation(),
99+
"**kwargs arguments must be a literal dict in " + where + ".");
100+
}
83101
} else if (arg instanceof Argument.Star) {
84102
error(
85103
arg.getStartLocation(),

src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunctionTest.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1947,6 +1947,56 @@ public void testOverrideRepo_cycle_multipleExtensions() throws Exception {
19471947
which is not supported.""");
19481948
}
19491949

1950+
@Test
1951+
public void testOverrideRepo_extraKeywordArguments_literal() throws Exception {
1952+
scratch.overwriteFile(
1953+
rootDirectory.getRelative("MODULE.bazel").getPathString(),
1954+
"""
1955+
module(name='aaa')
1956+
ext1 = use_extension('//:defs.bzl', 'ext1')
1957+
use_repo(ext1, "foo")
1958+
ext2 = use_extension('//:defs.bzl', 'ext2')
1959+
use_repo(ext2, "bar.2")
1960+
override_repo(ext2, **{"foo.2": "foo"})
1961+
""");
1962+
1963+
EvaluationResult<RootModuleFileValue> result =
1964+
evaluator.evaluate(
1965+
ImmutableList.of(ModuleFileValue.KEY_FOR_ROOT_MODULE), evaluationContext);
1966+
assertThat(result.hasError()).isFalse();
1967+
1968+
var ext2Usage =
1969+
result.get(ModuleFileValue.KEY_FOR_ROOT_MODULE).module().getExtensionUsages().get(1);
1970+
assertThat(ext2Usage.getRepoOverrides().keySet()).containsExactly("foo.2");
1971+
}
1972+
1973+
@Test
1974+
public void testOverrideRepo_extraKeywordArguments_nonLiteral() throws Exception {
1975+
scratch.overwriteFile(
1976+
rootDirectory.getRelative("MODULE.bazel").getPathString(),
1977+
"""
1978+
module(name='aaa')
1979+
ext1 = use_extension('//:defs.bzl', 'ext1')
1980+
use_repo(ext1, "foo")
1981+
ext2 = use_extension('//:defs.bzl', 'ext2')
1982+
use_repo(ext2, "bar.2")
1983+
KWARGS = {"foo.2": "foo"}
1984+
override_repo(ext2, **KWARGS)
1985+
""");
1986+
1987+
reporter.removeHandler(failFastHandler);
1988+
EvaluationResult<RootModuleFileValue> result =
1989+
evaluator.evaluate(
1990+
ImmutableList.of(ModuleFileValue.KEY_FOR_ROOT_MODULE), evaluationContext);
1991+
assertThat(result.hasError()).isTrue();
1992+
1993+
assertContainsEvent(
1994+
"""
1995+
ERROR /workspace/MODULE.bazel:7:21: **kwargs arguments must be a literal dict in \
1996+
MODULE.bazel files.\
1997+
""");
1998+
}
1999+
19502000
@Test
19512001
public void testInjectRepo_imported() throws Exception {
19522002
scratch.overwriteFile(

0 commit comments

Comments
 (0)