Skip to content

Commit

Permalink
Add bazel mod lock
Browse files Browse the repository at this point in the history
RELNOTES[INC]: Use `bazel mod lock` in combination with `--lockfile_mode=update` or `--lockfile_mode=refresh` to evaluate all module extensions and update their entries in the lockfile. In the future, other subcommands of `bazel mod` may no longer evaluate all extensions.
  • Loading branch information
fmeum committed Oct 1, 2024
1 parent 6fedd40 commit 2090102
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ public enum ModSubcommand {
SHOW_REPO(false),
SHOW_EXTENSION(false),
DUMP_REPO_MAPPING(false),
TIDY(false);
TIDY(false),
LOCK(false);

/** Whether this subcommand produces a graph output. */
private final boolean isGraph;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,20 +164,30 @@ private BlazeCommandResult execInternal(CommandEnvironment env, OptionsParsingRe

ImmutableList.Builder<RepositoryMappingValue.Key> repositoryMappingKeysBuilder =
ImmutableList.builder();
if (subcommand.equals(ModSubcommand.DUMP_REPO_MAPPING)) {
if (args.isEmpty()) {
// Make this case an error so that we are free to add a mode that emits all mappings in a
// single JSON object later.
return reportAndCreateFailureResult(
env, "No repository name(s) specified", Code.INVALID_ARGUMENTS);
switch (subcommand) {
case DUMP_REPO_MAPPING -> {
if (args.isEmpty()) {
// Make this case an error so that we are free to add a mode that emits all mappings in a
// single JSON object later.
return reportAndCreateFailureResult(
env, "No repository name(s) specified", Code.INVALID_ARGUMENTS);
}
for (String arg : args) {
try {
repositoryMappingKeysBuilder.add(
RepositoryMappingValue.key(RepositoryName.create(arg)));
} catch (LabelSyntaxException e) {
return reportAndCreateFailureResult(env, e.getMessage(), Code.INVALID_ARGUMENTS);
}
}
}
for (String arg : args) {
try {
repositoryMappingKeysBuilder.add(RepositoryMappingValue.key(RepositoryName.create(arg)));
} catch (LabelSyntaxException e) {
return reportAndCreateFailureResult(env, e.getMessage(), Code.INVALID_ARGUMENTS);
case LOCK -> {
if (!args.isEmpty()) {
return reportAndCreateFailureResult(
env, "lock doesn't support arguments", Code.INVALID_ARGUMENTS);
}
}
case null, default -> {}
}
ImmutableList<RepositoryMappingValue.Key> repoMappingKeys =
repositoryMappingKeysBuilder.build();
Expand All @@ -200,12 +210,10 @@ private BlazeCommandResult execInternal(CommandEnvironment env, OptionsParsingRe
env.syncPackageLoading(options);

ImmutableSet.Builder<SkyKey> keys = ImmutableSet.builder();
if (subcommand.equals(ModSubcommand.DUMP_REPO_MAPPING)) {
keys.addAll(repoMappingKeys);
} else if (subcommand.equals(ModSubcommand.TIDY)) {
keys.add(BazelModTidyValue.KEY);
} else {
keys.add(BazelDepGraphValue.KEY, BazelModuleInspectorValue.KEY);
switch (subcommand) {
case DUMP_REPO_MAPPING -> keys.addAll(repoMappingKeys);
case TIDY -> keys.add(BazelModTidyValue.KEY);
default -> keys.add(BazelDepGraphValue.KEY, BazelModuleInspectorValue.KEY);
}
EvaluationResult<SkyValue> evaluationResult =
skyframeExecutor.prepareAndGet(keys.build(), evaluationContext);
Expand Down Expand Up @@ -242,35 +250,43 @@ private BlazeCommandResult execInternal(CommandEnvironment env, OptionsParsingRe
}

// Handle commands that do not require BazelModuleInspectorValue.
if (subcommand.equals(ModSubcommand.DUMP_REPO_MAPPING)) {
String missingRepos =
IntStream.range(0, repoMappingKeys.size())
.filter(i -> repoMappingValues.get(i) == RepositoryMappingValue.NOT_FOUND_VALUE)
.mapToObj(repoMappingKeys::get)
.map(RepositoryMappingValue.Key::repoName)
.map(RepositoryName::getName)
.collect(joining(", "));
if (!missingRepos.isEmpty()) {
return reportAndCreateFailureResult(
env, "Repositories not found: " + missingRepos, Code.INVALID_ARGUMENTS);
switch (subcommand) {
case DUMP_REPO_MAPPING -> {
String missingRepos =
IntStream.range(0, repoMappingKeys.size())
.filter(i -> repoMappingValues.get(i) == RepositoryMappingValue.NOT_FOUND_VALUE)
.mapToObj(repoMappingKeys::get)
.map(RepositoryMappingValue.Key::repoName)
.map(RepositoryName::getName)
.collect(joining(", "));
if (!missingRepos.isEmpty()) {
return reportAndCreateFailureResult(
env, "Repositories not found: " + missingRepos, Code.INVALID_ARGUMENTS);
}
try {
dumpRepoMappings(
repoMappingValues,
new OutputStreamWriter(
env.getReporter().getOutErr().getOutputStream(),
modOptions.charset == UTF8 ? UTF_8 : US_ASCII));
} catch (IOException e) {
throw new IllegalStateException(e);
}
return BlazeCommandResult.success();
}
try {
dumpRepoMappings(
repoMappingValues,
new OutputStreamWriter(
env.getReporter().getOutErr().getOutputStream(),
modOptions.charset == UTF8 ? UTF_8 : US_ASCII));
} catch (IOException e) {
throw new IllegalStateException(e);
case TIDY -> {
// tidy doesn't take extra arguments.
if (!args.isEmpty()) {
return reportAndCreateFailureResult(
env, "the 'tidy' command doesn't take extra arguments", Code.TOO_MANY_ARGUMENTS);
}
return runTidy(env, modTidyValue);
}
return BlazeCommandResult.success();
} else if (subcommand == ModSubcommand.TIDY) {
// tidy doesn't take extra arguments.
if (!args.isEmpty()) {
return reportAndCreateFailureResult(
env, "the 'tidy' command doesn't take extra arguments", Code.TOO_MANY_ARGUMENTS);
case LOCK -> {
// Requesting BazelModuleInspectorValue implicitly evaluates all extensions and thus updates
// their entries in the lockfile in afterCommand.
return BlazeCommandResult.success();
}
return runTidy(env, modTidyValue);
}

// Extract and check the --base_module argument first to use it when parsing the other args.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The command will display the external dependency graph or parts thereof, structu
- show_repo <module>...: Prints the rule that generated the specified repos (i.e. http_archive()). The arguments may refer to extension-generated repos.
- show_extension <extension>...: Prints information about the given extension(s). Usages can be filtered down to only those from modules in --extension_usage.
- dump_repo_mapping <canonical_repo_name>...: Prints the mappings from apparent repo names to canonical repo names for the given repos in NDJSON format. The order of entries within each JSON object is unspecified. This command is intended for use by tools such as IDEs and Starlark language servers.
- lock: Evaluates all module extensions. Use with --lockfile_mode=update or --lockfile_mode=refresh to update the lockfile.


<module> arguments must be one of the following:
Expand Down
38 changes: 38 additions & 0 deletions src/test/py/bazel/bzlmod/mod_command_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,44 @@ def testModTidyWithIncludes(self):
module_file.read().split('\n'),
)

def testModLock(self):
self.ScratchFile(
'MODULE.bazel',
[
'ext1 = use_extension("//:extension.bzl", "ext1")',
'ext2 = use_extension("//:extension.bzl", "ext2")'
],
)
self.ScratchFile('BUILD.bazel')
self.ScratchFile(
'extension.bzl',
[
'def _repo_rule_impl(ctx):',
' ctx.file("WORKSPACE")',
' ctx.file("BUILD", "filegroup(name=\'lala\')")',
'',
'repo_rule = repository_rule(implementation=_repo_rule_impl)',
'',
'def _ext1_impl(ctx):',
' print("ext1 is being evaluated")',
' repo_rule(name="dep")',
'',
'ext1 = module_extension(implementation=_ext1_impl)',
'',
'def _ext2_impl(ctx):',
' print("ext2 is being evaluated")',
' repo_rule(name="dep")',
'',
'ext2 = module_extension(implementation=_ext2_impl)',
],
)

_, _, stderr = self.RunBazel(['mod', 'lock'])
stderr = '\n'.join(stderr)
# Verify that all extensions are evaluated.
self.assertIn('ext1 is being evaluated', stderr)
self.assertIn('ext2 is being evaluated', stderr)


if __name__ == '__main__':
absltest.main()

0 comments on commit 2090102

Please sign in to comment.