Skip to content

Commit

Permalink
Add a new module_import() directive to MODULE.bazel files
Browse files Browse the repository at this point in the history
This new directive allows the root module to divide its MODULE.bazel into multiple segments. TODO: add more context

RELNOTES: Added a new `module_import()` directive to MODULE.bazel files. TODO: elaborate a bit more

Fixes #17880.
  • Loading branch information
Wyverald committed Mar 29, 2024
1 parent 5151bc3 commit daadadb
Show file tree
Hide file tree
Showing 13 changed files with 820 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ java_library(
"BazelModuleResolutionEvent.java",
"BazelModuleResolutionValue.java",
"BzlmodFlagsAndEnvVars.java",
"CompiledModuleFile.java",
"GitOverride.java",
"InterimModule.java",
"LocalPathOverride.java",
Expand All @@ -148,6 +149,7 @@ java_library(
],
deps = [
":common",
":exception",
":inspection",
":module_extension",
":module_extension_metadata",
Expand All @@ -168,6 +170,7 @@ java_library(
"//src/main/java/net/starlark/java/annot",
"//src/main/java/net/starlark/java/eval",
"//src/main/java/net/starlark/java/syntax",
"//src/main/protobuf:failure_details_java_proto",
"//third_party:auto_value",
"//third_party:gson",
"//third_party:guava",
Expand Down Expand Up @@ -229,6 +232,8 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/skyframe:bzl_load_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:client_environment_function",
"//src/main/java/com/google/devtools/build/lib/skyframe:client_environment_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:package_lookup_function",
"//src/main/java/com/google/devtools/build/lib/skyframe:package_lookup_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:precomputed_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:repository_mapping_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:skyframe_cluster",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue.RootModuleFileValue;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.RepositoryName;
Expand Down Expand Up @@ -50,6 +51,11 @@ public class BazelModTidyFunction implements SkyFunction {
@Nullable
public SkyValue compute(SkyKey skyKey, Environment env)
throws InterruptedException, SkyFunctionException {
RootModuleFileValue rootModuleFileValue =
(RootModuleFileValue) env.getValue(ModuleFileValue.KEY_FOR_ROOT_MODULE);
if (rootModuleFileValue == null) {
return null;
}
BazelDepGraphValue depGraphValue = (BazelDepGraphValue) env.getValue(BazelDepGraphValue.KEY);
if (depGraphValue == null) {
return null;
Expand Down Expand Up @@ -112,6 +118,7 @@ public SkyValue compute(SkyKey skyKey, Environment env)

return BazelModTidyValue.create(
buildozer.asPath(),
rootModuleFileValue.getImportLabelToCompiledModuleFile(),
MODULE_OVERRIDES.get(env),
IGNORE_DEV_DEPS.get(env),
LOCKFILE_MODE.get(env),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public abstract class BazelModTidyValue implements SkyValue {
/** The path of the buildozer binary provided by the "buildozer" module. */
public abstract Path buildozer();

public abstract ImmutableMap<String, CompiledModuleFile> importLabelToCompiledModuleFile();

/** The value of {@link ModuleFileFunction#MODULE_OVERRIDES}. */
public abstract ImmutableMap<String, ModuleOverride> moduleOverrides();

Expand All @@ -52,12 +54,14 @@ public abstract class BazelModTidyValue implements SkyValue {

static BazelModTidyValue create(
Path buildozer,
ImmutableMap<String, CompiledModuleFile> importLabelToCompiledModuleFile,
Map<String, ModuleOverride> moduleOverrides,
boolean ignoreDevDeps,
LockfileMode lockfileMode,
StarlarkSemantics starlarkSemantics) {
return new AutoValue_BazelModTidyValue(
buildozer,
importLabelToCompiledModuleFile,
ImmutableMap.copyOf(moduleOverrides),
ignoreDevDeps,
lockfileMode,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.google.devtools.build.lib.bazel.bzlmod;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.packages.BazelStarlarkEnvironment;
import com.google.devtools.build.lib.packages.DotBazelFileSyntaxChecker;
import com.google.devtools.build.lib.server.FailureDetails.ExternalDeps.Code;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Module;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkSemantics;
import net.starlark.java.eval.StarlarkThread;
import net.starlark.java.syntax.Argument;
import net.starlark.java.syntax.CallExpression;
import net.starlark.java.syntax.ExpressionStatement;
import net.starlark.java.syntax.Identifier;
import net.starlark.java.syntax.Location;
import net.starlark.java.syntax.ParserInput;
import net.starlark.java.syntax.Program;
import net.starlark.java.syntax.StarlarkFile;
import net.starlark.java.syntax.StringLiteral;
import net.starlark.java.syntax.SyntaxError;

/**
* Represents a compiled MODULE.bazel file, ready to be executed on a {@link StarlarkThread}. It's
* been successfully checked for syntax errors.
*
* <p>Use the {@link #parseAndCompile} factory method instead of directly instantiating this record.
*/
public record CompiledModuleFile(
ModuleFile moduleFile,
Program program,
Module predeclaredEnv,
ImmutableList<ModuleImportStatement> importStatements) {

record ModuleImportStatement(String importLabel, Location location) {}

/** Parses and compiles a given module file, checking it for syntax errors. */
public static CompiledModuleFile parseAndCompile(
ModuleFile moduleFile,
ModuleKey moduleKey,
StarlarkSemantics starlarkSemantics,
BazelStarlarkEnvironment starlarkEnv,
ExtendedEventHandler eventHandler)
throws ExternalDepsException {
StarlarkFile starlarkFile =
StarlarkFile.parse(ParserInput.fromUTF8(moduleFile.getContent(), moduleFile.getLocation()));
if (!starlarkFile.ok()) {
Event.replayEventsOn(eventHandler, starlarkFile.errors());
throw ExternalDepsException.withMessage(
Code.BAD_MODULE, "error parsing MODULE.bazel file for %s", moduleKey);
}
try {
ImmutableList<ModuleImportStatement> importStatements = checkModuleFileSyntax(starlarkFile);
Module predeclaredEnv =
Module.withPredeclared(
starlarkSemantics, starlarkEnv.getStarlarkGlobals().getModuleToplevels());
Program program = Program.compileFile(starlarkFile, predeclaredEnv);
return new CompiledModuleFile(moduleFile, program, predeclaredEnv, importStatements);
} catch (SyntaxError.Exception e) {
Event.replayEventsOn(eventHandler, e.errors());
throw ExternalDepsException.withMessage(
Code.BAD_MODULE, "syntax error in MODULE.bazel file for %s", moduleKey);
}
}

@VisibleForTesting
static ImmutableList<ModuleImportStatement> checkModuleFileSyntax(StarlarkFile starlarkFile)
throws SyntaxError.Exception {
var importStatements = ImmutableList.<ModuleImportStatement>builder();
new DotBazelFileSyntaxChecker("MODULE.bazel files", /* canLoadBzl= */ false) {
@Override
public void visit(ExpressionStatement node) {
// We can assume this statement isn't nested in any block, since we don't allow
// `if`/`def`/`for` in MODULE.bazel.
if (node.getExpression() instanceof CallExpression call
&& call.getFunction() instanceof Identifier id
&& id.getName().equals("module_import")) {
// Found a top-level call to module_import!
if (call.getArguments().size() == 1
&& call.getArguments().getFirst() instanceof Argument.Positional pos
&& pos.getValue() instanceof StringLiteral str) {
importStatements.add(
new ModuleImportStatement(str.getValue(), call.getStartLocation()));
// We can stop going down this rabbit hole now.
return;
}
error(
node.getStartLocation(),
"the `module_import` directive MUST be called with exactly one positional "
+ "argument that is a string literal");
return;
}
super.visit(node);
}

@Override
public void visit(Identifier node) {
if (node.getName().equals("module_import")) {
// If we somehow reach the `module_import` identifier but NOT as part of a top-level call
// expression, cry foul.
error(
node.getStartLocation(),
"the `module_import` directive MUST be called directly at the top-level");
}
super.visit(node);
}
}.check(starlarkFile);
return importStatements.build();
}

public void runOnThread(StarlarkThread thread) throws EvalException, InterruptedException {
Starlark.execFileProgram(program, predeclaredEnv, thread);
}
}
Loading

0 comments on commit daadadb

Please sign in to comment.