Skip to content

Commit a0cd355

Browse files
brandjoncopybara-github
authored andcommitted
Add support for loading .scl files
The Starlark Configuration Language (SCL) is a dialect of Starlark that resembles pure Starlark with just a few Bazel symbols (namely `visibility()` and `struct`), and a few restrictions on `load()` syntax and non-ASCII string data. This CL adds support for loading .scl files anywhere a .bzl could be used. The restrictions on `load()` are implemented in this CL, but follow-up CLs will handle differentiating the top-level environment from .bzl, restricting non-ASCII data, and adding user documentation. The dialect is gated on the flag `--experimental_enable_scl_dialect`. - `BzlLoadValue` gains a new bool field on its abstract Key type to indicate whether it is for .scl. This was chosen instead of creating a new Key subclass so that .scl can work equally well in different contexts (loaded by BUILD, WORKSPACE, MODULE.bazel, or even @_builtins). - `BzlLoadFunction.checkValidLoadLabel` and `.getLoadLabels` are both split into a public and private method. The public methods assume the load is coming from a .bzl file for validation purposes, and take a `StarlarkSemantics` to check whether the experimental flag is enabled. - Eliminate `BzlLoadFunction.getBUILDLabel()` helper, which is obsolete. - Modify existing tests to incidentally check that bzlmod .bzls can load .scl files and that .scl files can appear in transitive loads as reported by the Package (for `loadfiles()`). RELNOTES: None PiperOrigin-RevId: 528563228 Change-Id: I8493d1f33d35e1af8003dc61e5fdb626676d7e53
1 parent 51249ec commit a0cd355

File tree

9 files changed

+274
-38
lines changed

9 files changed

+274
-38
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,7 @@ public SkyValue compute(SkyKey skyKey, Environment env)
111111

112112
// Check that the .bzl label isn't crazy.
113113
try {
114-
BzlLoadFunction.checkValidLoadLabel(
115-
extensionId.getBzlFileLabel(), /*fromBuiltinsRepo=*/ false);
114+
BzlLoadFunction.checkValidLoadLabel(extensionId.getBzlFileLabel(), starlarkSemantics);
116115
} catch (LabelSyntaxException e) {
117116
throw new SingleExtensionEvalFunctionException(
118117
ExternalDepsException.withCauseAndMessage(

src/main/java/com/google/devtools/build/lib/packages/semantics/BuildLanguageOptions.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,15 @@ public final class BuildLanguageOptions extends OptionsBase {
196196
help = "If set to true, enables the APIs required to support the Android Starlark migration.")
197197
public boolean experimentalEnableAndroidMigrationApis;
198198

199+
@Option(
200+
name = "experimental_enable_scl_dialect",
201+
defaultValue = "false",
202+
documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
203+
effectTags = OptionEffectTag.BUILD_FILE_SEMANTICS,
204+
// TODO(brandjon): point to more extensive user documentation somewhere
205+
help = "If set to true, .scl files may be used in load() statements.")
206+
public boolean experimentalEnableSclDialect;
207+
199208
@Option(
200209
name = "enable_bzlmod",
201210
oldName = "experimental_enable_bzlmod",
@@ -677,6 +686,7 @@ public StarlarkSemantics toStarlarkSemantics() {
677686
.setBool(CHECK_BZL_VISIBILITY, checkBzlVisibility)
678687
.setBool(
679688
EXPERIMENTAL_ENABLE_ANDROID_MIGRATION_APIS, experimentalEnableAndroidMigrationApis)
689+
.setBool(EXPERIMENTAL_ENABLE_SCL_DIALECT, experimentalEnableSclDialect)
680690
.setBool(ENABLE_BZLMOD, enableBzlmod)
681691
.setBool(
682692
EXPERIMENTAL_JAVA_PROTO_LIBRARY_DEFAULT_HAS_SERVICES,
@@ -770,6 +780,7 @@ public StarlarkSemantics toStarlarkSemantics() {
770780
"-experimental_disable_external_package";
771781
public static final String EXPERIMENTAL_ENABLE_ANDROID_MIGRATION_APIS =
772782
"-experimental_enable_android_migration_apis";
783+
public static final String EXPERIMENTAL_ENABLE_SCL_DIALECT = "-experimental_enable_scl_dialect";
773784
public static final String ENABLE_BZLMOD = "-enable_bzlmod";
774785
public static final String EXPERIMENTAL_JAVA_PROTO_LIBRARY_DEFAULT_HAS_SERVICES =
775786
"+experimental_java_proto_library_default_has_services";

src/main/java/com/google/devtools/build/lib/skyframe/BzlLoadFunction.java

Lines changed: 127 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@
8686
import net.starlark.java.syntax.StringLiteral;
8787

8888
/**
89-
* A Skyframe function to look up and load a single .bzl module.
89+
* A Skyframe function to look up and load a single .bzl (or .scl) module.
90+
*
91+
* <p>Note: Historically, all modules had the .bzl suffix, but this is no longer true now that Bazel
92+
* supports the .scl dialect. In identifiers, code comments, and documentation, you should generally
93+
* assume any "bzl" term could mean a .scl file as well.
9094
*
9195
* <p>Given a {@link Label} referencing a .bzl file, attempts to locate the file and load it. The
9296
* Label must be absolute, and must not reference the special {@code external} package. If loading
@@ -736,6 +740,14 @@ private BzlLoadValue computeInternalWithCompiledBzl(
736740
Label label = key.getLabel();
737741
PackageIdentifier pkg = label.getPackageIdentifier();
738742

743+
boolean isSclFlagEnabled =
744+
builtins.starlarkSemantics.getBool(BuildLanguageOptions.EXPERIMENTAL_ENABLE_SCL_DIALECT);
745+
if (key.isSclDialect() && !isSclFlagEnabled) {
746+
throw new BzlLoadFailedException(
747+
"loading .scl files requires setting --experimental_enable_scl_dialect",
748+
Code.PARSE_ERROR);
749+
}
750+
739751
// Determine dependency BzlLoadValue keys for the load statements in this bzl.
740752
// Labels are resolved relative to the current repo mapping.
741753
RepositoryMapping repoMapping = getRepositoryMapping(key, builtins.starlarkSemantics, env);
@@ -744,7 +756,13 @@ private BzlLoadValue computeInternalWithCompiledBzl(
744756
}
745757
ImmutableList<Pair<String, Location>> programLoads = getLoadsFromProgram(prog);
746758
ImmutableList<Label> loadLabels =
747-
getLoadLabels(env.getListener(), programLoads, pkg, repoMapping);
759+
getLoadLabels(
760+
env.getListener(),
761+
programLoads,
762+
pkg,
763+
repoMapping,
764+
key.isSclDialect(),
765+
isSclFlagEnabled);
748766
if (loadLabels == null) {
749767
throw new BzlLoadFailedException(
750768
String.format(
@@ -933,11 +951,59 @@ private static RepositoryMapping getRepositoryMapping(
933951
return repositoryMappingValue.getRepositoryMapping();
934952
}
935953

936-
public static void checkValidLoadLabel(Label label, boolean fromBuiltinsRepo)
954+
/**
955+
* Validates a label appearing in a {@code load()} statement, throwing {@link
956+
* LabelSyntaxException} on failure.
957+
*
958+
* <p>Different restrictions apply depending on what type of source file the load appears in. For
959+
* all kinds of files, {@code label}:
960+
*
961+
* <ul>
962+
* <li>may not be within {@code @//external}.
963+
* <li>must end with either {@code .bzl} or {@code .scl}.
964+
* </ul>
965+
*
966+
* <p>For source files appearing within {@code @_builtins}, {@code label} must also be within
967+
* {@code @_builtins}. (The reverse, that those files may not be loaded by user-defined files, is
968+
* enforced by the fact that the {@code @_builtins} pseudorepo cannot be resolved as an ordinary
969+
* repo.)
970+
*
971+
* <p>For .scl files only, {@code label} must end with {@code .scl} (not {@code .bzl}). (Loads in
972+
* .scl also should always begin with {@code //}, but that's syntactic and can't be enforced in
973+
* this method.)
974+
*
975+
* @param label the label to validate
976+
* @param fromBuiltinsRepo true if the file containing the load is within {@code @_builtins}
977+
* @param withinSclDialect true if the file containing the load is a .scl file
978+
* @param mentionSclInErrorMessage true if ".scl" should be advertised as a possible extension in
979+
* error messaging
980+
*/
981+
private static void checkValidLoadLabel(
982+
Label label,
983+
boolean fromBuiltinsRepo,
984+
boolean withinSclDialect,
985+
boolean mentionSclInErrorMessage)
937986
throws LabelSyntaxException {
938-
if (!label.getName().endsWith(".bzl")) {
939-
throw new LabelSyntaxException("The label must reference a file with extension '.bzl'");
987+
// Check file extension.
988+
String baseName = label.getName();
989+
if (withinSclDialect) {
990+
if (!baseName.endsWith(".scl")) {
991+
String msg = "The label must reference a file with extension \".scl\"";
992+
if (baseName.endsWith(".bzl")) {
993+
msg += " (.scl files cannot load .bzl files)";
994+
}
995+
throw new LabelSyntaxException(msg);
996+
}
997+
} else {
998+
if (!(baseName.endsWith(".scl") || baseName.endsWith(".bzl"))) {
999+
String msg = "The label must reference a file with extension \".bzl\"";
1000+
if (mentionSclInErrorMessage) {
1001+
msg += " or \".scl\"";
1002+
}
1003+
throw new LabelSyntaxException(msg);
1004+
}
9401005
}
1006+
9411007
if (label.getPackageIdentifier().equals(LabelConstants.EXTERNAL_PACKAGE_IDENTIFIER)) {
9421008
throw new LabelSyntaxException(
9431009
"Starlark files may not be loaded from the //external package");
@@ -948,35 +1014,57 @@ public static void checkValidLoadLabel(Label label, boolean fromBuiltinsRepo)
9481014
}
9491015
}
9501016

1017+
/**
1018+
* Validates a label appearing in a {@code load()} statement, throwing {@link
1019+
* LabelSyntaxException} on failure.
1020+
*/
1021+
public static void checkValidLoadLabel(Label label, StarlarkSemantics starlarkSemantics)
1022+
throws LabelSyntaxException {
1023+
checkValidLoadLabel(
1024+
label,
1025+
/* fromBuiltinsRepo= */ false,
1026+
/* withinSclDialect= */ false,
1027+
/* mentionSclInErrorMessage= */ starlarkSemantics.getBool(
1028+
BuildLanguageOptions.EXPERIMENTAL_ENABLE_SCL_DIALECT));
1029+
}
1030+
9511031
/**
9521032
* Given a list of {@code load("module")} strings and their locations, in source order, returns a
9531033
* corresponding list of Labels they each resolve to. Labels are resolved relative to {@code
9541034
* base}, the file's package. If any label is malformed, the function reports one or more errors
9551035
* to the handler and returns null.
1036+
*
1037+
* <p>If {@code withinSclDialect} is true, the labels are validated according to the rules of the
1038+
* .scl dialect: Only strings beginning with {@code //} are allowed (no repo syntax, no relative
1039+
* labels), and only .scl files may be loaded (not .bzl). If {@code isSclFlagEnabled} is true,
1040+
* then ".scl" is mentioned as a possible file extension in error messages.
9561041
*/
9571042
@Nullable
958-
static ImmutableList<Label> getLoadLabels(
1043+
private static ImmutableList<Label> getLoadLabels(
9591044
EventHandler handler,
9601045
ImmutableList<Pair<String, Location>> loads,
9611046
PackageIdentifier base,
962-
RepositoryMapping repoMapping) {
963-
// It's redundant that getRelativeWithRemapping needs a Label;
964-
// a PackageIdentifier should suffice. Make one here.
965-
Label buildLabel = getBUILDLabel(base);
966-
1047+
RepositoryMapping repoMapping,
1048+
boolean withinSclDialect,
1049+
boolean isSclFlagEnabled) {
9671050
boolean ok = true;
9681051

9691052
ImmutableList.Builder<Label> loadLabels = ImmutableList.builderWithExpectedSize(loads.size());
9701053
for (Pair<String, Location> load : loads) {
971-
// Parse the load statement's module string as a label.
972-
// It must end in .bzl and not be in package "//external".
1054+
// Parse the load statement's module string as a label. Validate the unparsed string for
1055+
// syntax and the parsed label for structure.
1056+
String unparsedLabel = load.first;
9731057
try {
1058+
if (withinSclDialect && !unparsedLabel.startsWith("//")) {
1059+
throw new LabelSyntaxException("in .scl files, load labels must begin with \"//\"");
1060+
}
9741061
Label label =
975-
Label.parseWithPackageContext(
976-
load.first, PackageContext.of(buildLabel.getPackageIdentifier(), repoMapping));
1062+
Label.parseWithPackageContext(unparsedLabel, PackageContext.of(base, repoMapping));
9771063
checkValidLoadLabel(
9781064
label,
979-
/* fromBuiltinsRepo= */ StarlarkBuiltinsValue.isBuiltinsRepo(base.getRepository()));
1065+
/* fromBuiltinsRepo= */ StarlarkBuiltinsValue.isBuiltinsRepo(base.getRepository()),
1066+
/* withinSclDialect= */ withinSclDialect,
1067+
/* mentionSclInErrorMessage= */ isSclFlagEnabled);
9801068
loadLabels.add(label);
9811069
} catch (LabelSyntaxException ex) {
9821070
handler.handle(Event.error(load.second, "in load statement: " + ex.getMessage()));
@@ -986,6 +1074,29 @@ static ImmutableList<Label> getLoadLabels(
9861074
return ok ? loadLabels.build() : null;
9871075
}
9881076

1077+
/**
1078+
* Given a list of {@code load("module")} strings and their locations, in source order, returns a
1079+
* corresponding list of Labels they each resolve to. Labels are resolved relative to {@code
1080+
* base}, the file's package. If any label is malformed, the function reports one or more errors
1081+
* to the handler and returns null.
1082+
*/
1083+
@Nullable
1084+
static ImmutableList<Label> getLoadLabels(
1085+
EventHandler handler,
1086+
ImmutableList<Pair<String, Location>> loads,
1087+
PackageIdentifier base,
1088+
RepositoryMapping repoMapping,
1089+
StarlarkSemantics starlarkSemantics) {
1090+
return getLoadLabels(
1091+
handler,
1092+
loads,
1093+
base,
1094+
repoMapping,
1095+
/* withinSclDialect= */ false,
1096+
/* isSclFlagEnabled= */ starlarkSemantics.getBool(
1097+
BuildLanguageOptions.EXPERIMENTAL_ENABLE_SCL_DIALECT));
1098+
}
1099+
9891100
/** Extracts load statements from compiled program (see {@link #getLoadLabels}). */
9901101
static ImmutableList<Pair<String, Location>> getLoadsFromProgram(Program prog) {
9911102
int n = prog.getLoads().size();
@@ -1010,15 +1121,6 @@ static ImmutableList<Pair<String, Location>> getLoadsFromStarlarkFiles(List<Star
10101121
return loads.build();
10111122
}
10121123

1013-
private static Label getBUILDLabel(PackageIdentifier pkgid) {
1014-
try {
1015-
return Label.create(pkgid, "BUILD");
1016-
} catch (LabelSyntaxException e) {
1017-
// Shouldn't happen; the Label is well-formed by construction.
1018-
throw new IllegalStateException(e);
1019-
}
1020-
}
1021-
10221124
/**
10231125
* Computes the BzlLoadValue for all given .bzl load keys using ordinary Skyframe evaluation,
10241126
* returning {@code null} if Skyframe deps were missing and have been requested. {@code

src/main/java/com/google/devtools/build/lib/skyframe/BzlLoadValue.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@
3131
import net.starlark.java.eval.Module;
3232

3333
/**
34-
* A value that represents the .bzl module loaded by a Starlark {@code load()} statement.
34+
* A value that represents the .bzl (or .scl) module loaded by a Starlark {@code load()} statement.
35+
*
36+
* <p>Note: Historically, all modules had the .bzl suffix, but this is no longer true now that Bazel
37+
* supports the .scl dialect. In identifiers, code comments, and documentation, you should generally
38+
* assume any "bzl" term could mean a .scl file as well.
3539
*
3640
* <p>The key consists of an absolute {@link Label} and the context in which the load occurs. The
3741
* Label should not reference the special {@code external} package.
@@ -95,6 +99,22 @@ boolean isBuiltins() {
9599
return false;
96100
}
97101

102+
/** Returns true if the requested file follows the .scl dialect. */
103+
// Note: Just as with .bzl, the same .scl file can be referred to from multiple key types, for
104+
// instance if a BUILD file and a module rule both load foo.scl. Conceptually, .scl files
105+
// shouldn't depend on what kind of top-level file caused them to load, but in practice, this
106+
// implementation quirk means that the .scl file will be loaded twice as separate copies.
107+
//
108+
// This shouldn't matter except in rare edge cases, such as if a Starlark function is loaded
109+
// from both copies and compared for equality. Performance wise, it also means that all
110+
// transitive .scl files will be double-loaded, but we don't expect that to be significant.
111+
//
112+
// The alternative is to use a separate key type just for .scl, but that complicates repo logic;
113+
// see BzlLoadFunction#getRepositoryMapping.
114+
final boolean isSclDialect() {
115+
return getLabel().getName().endsWith(".scl");
116+
}
117+
98118
/**
99119
* Constructs a new key suitable for evaluating a {@code load()} dependency of this key's .bzl
100120
* file.

src/main/java/com/google/devtools/build/lib/skyframe/BzlmodRepoRuleFunction.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,8 @@ private ImmutableMap<String, Module> loadBzlModules(
220220
env.getListener(),
221221
programLoads,
222222
PackageIdentifier.EMPTY_PACKAGE_ID,
223-
EMPTY_MAIN_REPO_MAPPING);
223+
EMPTY_MAIN_REPO_MAPPING,
224+
starlarkSemantics);
224225
if (loadLabels == null) {
225226
NoSuchPackageException e =
226227
PackageFunction.PackageFunctionException.builder()

src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1288,7 +1288,11 @@ private LoadedPackage loadPackage(
12881288
BzlLoadFunction.getLoadsFromProgram(compiled.prog);
12891289
ImmutableList<Label> loadLabels =
12901290
BzlLoadFunction.getLoadLabels(
1291-
env.getListener(), programLoads, packageId, repositoryMapping);
1291+
env.getListener(),
1292+
programLoads,
1293+
packageId,
1294+
repositoryMapping,
1295+
starlarkBuiltinsValue.starlarkSemantics);
12921296
if (loadLabels == null) {
12931297
throw PackageFunctionException.builder()
12941298
.setType(PackageFunctionException.Type.BUILD_FILE_CONTAINS_ERRORS)

src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceFileFunction.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,8 @@ public SkyValue compute(SkyKey skyKey, Environment env)
297297
ImmutableList<Pair<String, Location>> programLoads =
298298
BzlLoadFunction.getLoadsFromStarlarkFiles(chunk);
299299
ImmutableList<Label> loadLabels =
300-
BzlLoadFunction.getLoadLabels(env.getListener(), programLoads, rootPackage, repoMapping);
300+
BzlLoadFunction.getLoadLabels(
301+
env.getListener(), programLoads, rootPackage, repoMapping, starlarkSemantics);
301302
if (loadLabels == null) {
302303
NoSuchPackageException e =
303304
PackageFunction.PackageFunctionException.builder()

0 commit comments

Comments
 (0)