blob: 4d40a27628cf294634ef0974fb737b57d098fe15 [file] [log] [blame]
// Copyright 2022 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.bazel.commands;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.devtools.build.lib.bazel.bzlmod.modcommand.ModOptions.Charset.UTF8;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.joining;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.eventbus.Subscribe;
import com.google.devtools.build.lib.analysis.NoBuildEvent;
import com.google.devtools.build.lib.analysis.NoBuildRequestFinishedEvent;
import com.google.devtools.build.lib.bazel.bzlmod.BazelDepGraphValue;
import com.google.devtools.build.lib.bazel.bzlmod.BazelModTidyValue;
import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorValue;
import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorValue.AugmentedModule;
import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleResolutionEvent;
import com.google.devtools.build.lib.bazel.bzlmod.BzlmodRepoRuleValue;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleExtensionId;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileFunction;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleKey;
import com.google.devtools.build.lib.bazel.bzlmod.RootModuleFileFixupEvent;
import com.google.devtools.build.lib.bazel.bzlmod.modcommand.ExtensionArg;
import com.google.devtools.build.lib.bazel.bzlmod.modcommand.ExtensionArg.ExtensionArgConverter;
import com.google.devtools.build.lib.bazel.bzlmod.modcommand.InvalidArgumentException;
import com.google.devtools.build.lib.bazel.bzlmod.modcommand.ModExecutor;
import com.google.devtools.build.lib.bazel.bzlmod.modcommand.ModOptions;
import com.google.devtools.build.lib.bazel.bzlmod.modcommand.ModOptions.ModSubcommand;
import com.google.devtools.build.lib.bazel.bzlmod.modcommand.ModOptions.ModSubcommandConverter;
import com.google.devtools.build.lib.bazel.bzlmod.modcommand.ModuleArg;
import com.google.devtools.build.lib.bazel.bzlmod.modcommand.ModuleArg.ModuleArgConverter;
import com.google.devtools.build.lib.bazel.repository.RepositoryOptions.LockfileMode;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
import com.google.devtools.build.lib.pkgcache.PackageOptions;
import com.google.devtools.build.lib.runtime.BlazeCommand;
import com.google.devtools.build.lib.runtime.BlazeCommandResult;
import com.google.devtools.build.lib.runtime.Command;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.runtime.KeepGoingOption;
import com.google.devtools.build.lib.runtime.LoadingPhaseThreadsOption;
import com.google.devtools.build.lib.server.FailureDetails;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.server.FailureDetails.ModCommand.Code;
import com.google.devtools.build.lib.shell.AbnormalTerminationException;
import com.google.devtools.build.lib.shell.CommandException;
import com.google.devtools.build.lib.skyframe.RepositoryMappingValue;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.CommandBuilder;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.util.InterruptedFailureDetails;
import com.google.devtools.build.lib.util.MaybeCompleteSet;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.skyframe.EvaluationContext;
import com.google.devtools.build.skyframe.EvaluationResult;
import com.google.devtools.build.skyframe.SkyFunctionException;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.common.options.OptionsParsingException;
import com.google.devtools.common.options.OptionsParsingResult;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import net.starlark.java.eval.SymbolGenerator;
/** Queries the Bzlmod external dependency graph. */
@Command(
name = ModCommand.NAME,
// TODO(andreisolo): figure out which extra options are really needed
options = {
ModOptions.class,
PackageOptions.class,
KeepGoingOption.class,
LoadingPhaseThreadsOption.class
},
help = "resource:mod.txt",
shortDescription = "Queries the Bzlmod external dependency graph",
allowResidue = true)
public final class ModCommand implements BlazeCommand {
public static final String NAME = "mod";
@Override
public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult options) {
if (!options.getOptions(BuildLanguageOptions.class).enableBzlmod) {
return reportAndCreateFailureResult(
env,
"Bzlmod has to be enabled for mod command to work, run with --enable_bzlmod",
Code.MISSING_ARGUMENTS);
}
env.getEventBus()
.post(
new NoBuildEvent(
env.getCommandName(),
env.getCommandStartTime(),
/* separateFinishedEvent= */ true,
/* showProgress= */ true,
/* id= */ null));
BlazeCommandResult result = execInternal(env, options);
env.getEventBus()
.post(
new NoBuildRequestFinishedEvent(
result.getExitCode(), env.getRuntime().getClock().currentTimeMillis()));
return result;
}
private BlazeCommandResult execInternal(CommandEnvironment env, OptionsParsingResult options) {
ModOptions modOptions = options.getOptions(ModOptions.class);
Preconditions.checkArgument(modOptions != null);
if (options.getResidue().isEmpty()) {
String errorMessage =
String.format(
"No subcommand specified, choose one of : %s.", ModSubcommand.printValues());
return reportAndCreateFailureResult(env, errorMessage, Code.MOD_COMMAND_UNKNOWN);
}
// The first element in the residue must be the subcommand, and then comes a list of arguments.
String subcommandStr = options.getResidue().get(0);
ModSubcommand subcommand;
try {
subcommand = new ModSubcommandConverter().convert(subcommandStr);
} catch (OptionsParsingException e) {
String errorMessage =
String.format("Invalid subcommand, choose one from : %s.", ModSubcommand.printValues());
return reportAndCreateFailureResult(env, errorMessage, Code.MOD_COMMAND_UNKNOWN);
}
List<String> args = options.getResidue().subList(1, options.getResidue().size());
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);
}
for (String arg : args) {
try {
repositoryMappingKeysBuilder.add(RepositoryMappingValue.key(RepositoryName.create(arg)));
} catch (LabelSyntaxException e) {
return reportAndCreateFailureResult(env, e.getMessage(), Code.INVALID_ARGUMENTS);
}
}
}
ImmutableList<RepositoryMappingValue.Key> repoMappingKeys =
repositoryMappingKeysBuilder.build();
BazelDepGraphValue depGraphValue;
@Nullable BazelModuleInspectorValue moduleInspector;
@Nullable BazelModTidyValue modTidyValue;
ImmutableList<RepositoryMappingValue> repoMappingValues;
TidyEventRecorder tidyEventRecorder = new TidyEventRecorder();
SkyframeExecutor skyframeExecutor = env.getSkyframeExecutor();
LoadingPhaseThreadsOption threadsOption = options.getOptions(LoadingPhaseThreadsOption.class);
EvaluationContext evaluationContext =
EvaluationContext.newBuilder()
.setParallelism(threadsOption.threads)
.setEventHandler(env.getReporter())
.build();
try {
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);
env.getEventBus().register(tidyEventRecorder);
} else {
keys.add(BazelDepGraphValue.KEY, BazelModuleInspectorValue.KEY);
}
EvaluationResult<SkyValue> evaluationResult =
skyframeExecutor.prepareAndGet(keys.build(), evaluationContext);
if (evaluationResult.hasError()) {
Exception e = evaluationResult.getError().getException();
String message = "Unexpected error during repository rule evaluation.";
if (e != null) {
message = e.getMessage();
}
return reportAndCreateFailureResult(env, message, Code.INVALID_ARGUMENTS);
}
depGraphValue = (BazelDepGraphValue) evaluationResult.get(BazelDepGraphValue.KEY);
moduleInspector =
(BazelModuleInspectorValue) evaluationResult.get(BazelModuleInspectorValue.KEY);
modTidyValue = (BazelModTidyValue) evaluationResult.get(BazelModTidyValue.KEY);
repoMappingValues =
repoMappingKeys.stream()
.map(evaluationResult::get)
.map(RepositoryMappingValue.class::cast)
.collect(toImmutableList());
} catch (InterruptedException e) {
String errorMessage = "mod command interrupted: " + e.getMessage();
env.getReporter().handle(Event.error(errorMessage));
return BlazeCommandResult.detailedExitCode(
InterruptedFailureDetails.detailedExitCode(errorMessage));
} catch (AbruptExitException e) {
env.getReporter().handle(Event.error(null, "Unknown error: " + e.getMessage()));
return BlazeCommandResult.detailedExitCode(e.getDetailedExitCode());
}
// 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);
}
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();
} 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);
}
return runTidy(env, modTidyValue, tidyEventRecorder);
}
// Extract and check the --base_module argument first to use it when parsing the other args.
// Can only be a TargetModule or a repoName relative to the ROOT.
ModuleKey baseModuleKey;
AugmentedModule rootModule = moduleInspector.getDepGraph().get(ModuleKey.ROOT);
try {
ImmutableSet<ModuleKey> keys =
modOptions.baseModule.resolveToModuleKeys(
moduleInspector.getModulesIndex(),
moduleInspector.getDepGraph(),
moduleInspector.getModuleKeyToCanonicalNames(),
rootModule.getDeps(),
rootModule.getUnusedDeps(),
false,
false);
if (keys.size() > 1) {
throw new InvalidArgumentException(
String.format(
"The --base_module option can only specify exactly one module version, choose one"
+ " of: %s.",
keys.stream().map(ModuleKey::toString).collect(joining(", "))),
Code.INVALID_ARGUMENTS);
}
baseModuleKey = Iterables.getOnlyElement(keys);
} catch (InvalidArgumentException e) {
return reportAndCreateFailureResult(
env,
String.format(
"In --base_module %s option: %s (Note that unused modules cannot be used here)",
modOptions.baseModule, e.getMessage()),
Code.INVALID_ARGUMENTS);
}
// The args can have different types depending on the subcommand, so create multiple containers
// which can be filled accordingly.
ImmutableSet<ModuleKey> argsAsModules = null;
ImmutableSortedSet<ModuleExtensionId> argsAsExtensions = null;
ImmutableMap<String, RepositoryName> argsAsRepos = null;
AugmentedModule baseModule =
Objects.requireNonNull(moduleInspector.getDepGraph().get(baseModuleKey));
RepositoryMapping baseModuleMapping = depGraphValue.getFullRepoMapping(baseModuleKey);
try {
switch (subcommand) {
case GRAPH:
// GRAPH doesn't take extra arguments.
if (!args.isEmpty()) {
throw new InvalidArgumentException(
"the 'graph' command doesn't take extra arguments", Code.TOO_MANY_ARGUMENTS);
}
break;
case SHOW_REPO:
ImmutableMap.Builder<String, RepositoryName> targetToRepoName =
new ImmutableMap.Builder<>();
for (String arg : args) {
try {
targetToRepoName.putAll(
ModuleArgConverter.INSTANCE
.convert(arg)
.resolveToRepoNames(
moduleInspector.getModulesIndex(),
moduleInspector.getDepGraph(),
moduleInspector.getModuleKeyToCanonicalNames(),
baseModuleMapping));
} catch (InvalidArgumentException | OptionsParsingException e) {
throw new InvalidArgumentException(
String.format(
"In repo argument %s: %s (Note that unused modules cannot be used here)",
arg, e.getMessage()),
Code.INVALID_ARGUMENTS,
e);
}
}
argsAsRepos = targetToRepoName.buildKeepingLast();
break;
case SHOW_EXTENSION:
ImmutableSortedSet.Builder<ModuleExtensionId> extensionsBuilder =
new ImmutableSortedSet.Builder<>(ModuleExtensionId.LEXICOGRAPHIC_COMPARATOR);
for (String arg : args) {
try {
extensionsBuilder.add(
ExtensionArgConverter.INSTANCE
.convert(arg)
.resolveToExtensionId(
moduleInspector.getModulesIndex(),
moduleInspector.getDepGraph(),
moduleInspector.getModuleKeyToCanonicalNames(),
baseModule.getDeps(),
baseModule.getUnusedDeps()));
} catch (InvalidArgumentException | OptionsParsingException e) {
throw new InvalidArgumentException(
String.format("In extension argument %s: %s", arg, e.getMessage()),
Code.INVALID_ARGUMENTS,
e);
}
}
argsAsExtensions = extensionsBuilder.build();
break;
default:
ImmutableSet.Builder<ModuleKey> keysBuilder = new ImmutableSet.Builder<>();
for (String arg : args) {
try {
keysBuilder.addAll(
ModuleArgConverter.INSTANCE
.convert(arg)
.resolveToModuleKeys(
moduleInspector.getModulesIndex(),
moduleInspector.getDepGraph(),
moduleInspector.getModuleKeyToCanonicalNames(),
baseModule.getDeps(),
baseModule.getUnusedDeps(),
modOptions.includeUnused,
/* warnUnused= */ true));
} catch (InvalidArgumentException | OptionsParsingException e) {
throw new InvalidArgumentException(
String.format("In module argument %s: %s", arg, e.getMessage()),
Code.INVALID_ARGUMENTS);
}
}
argsAsModules = keysBuilder.build();
}
} catch (InvalidArgumentException e) {
return reportAndCreateFailureResult(env, e.getMessage(), e.getCode());
}
/* Extract and check the --from and --extension_usages argument */
ImmutableSet<ModuleKey> fromKeys;
ImmutableSet<ModuleKey> usageKeys;
try {
fromKeys =
moduleArgListToKeys(
modOptions.modulesFrom,
moduleInspector.getModulesIndex(),
moduleInspector.getDepGraph(),
moduleInspector.getModuleKeyToCanonicalNames(),
baseModule.getDeps(),
baseModule.getUnusedDeps(),
modOptions.includeUnused);
} catch (InvalidArgumentException e) {
return reportAndCreateFailureResult(
env,
String.format("In --from %s option: %s", modOptions.modulesFrom, e.getMessage()),
Code.INVALID_ARGUMENTS);
}
try {
usageKeys =
moduleArgListToKeys(
modOptions.extensionUsages,
moduleInspector.getModulesIndex(),
moduleInspector.getDepGraph(),
moduleInspector.getModuleKeyToCanonicalNames(),
baseModule.getDeps(),
baseModule.getUnusedDeps(),
modOptions.includeUnused);
} catch (InvalidArgumentException e) {
return reportAndCreateFailureResult(
env,
String.format(
"In --extension_usages %s option: %s (Note that unused modules cannot be used"
+ " here)",
modOptions.extensionUsages, e.getMessage()),
Code.INVALID_ARGUMENTS);
}
/* Extract and check the --extension_filter argument */
Optional<MaybeCompleteSet<ModuleExtensionId>> filterExtensions = Optional.empty();
if (subcommand.isGraph() && modOptions.extensionFilter != null) {
if (modOptions.extensionFilter.isEmpty()) {
filterExtensions = Optional.of(MaybeCompleteSet.completeSet());
} else {
try {
filterExtensions =
Optional.of(
MaybeCompleteSet.copyOf(
extensionArgListToIds(
modOptions.extensionFilter,
moduleInspector.getModulesIndex(),
moduleInspector.getDepGraph(),
moduleInspector.getModuleKeyToCanonicalNames(),
baseModule.getDeps(),
baseModule.getUnusedDeps())));
} catch (InvalidArgumentException e) {
return reportAndCreateFailureResult(
env,
String.format(
"In --extension_filter %s option: %s",
modOptions.extensionFilter, e.getMessage()),
Code.INVALID_ARGUMENTS);
}
}
}
ImmutableMap<String, BzlmodRepoRuleValue> targetRepoRuleValues = null;
try {
// If subcommand is a SHOW, also request the BzlmodRepoRuleValues from Skyframe.
if (subcommand == ModSubcommand.SHOW_REPO) {
ImmutableSet<SkyKey> skyKeys =
argsAsRepos.values().stream().map(BzlmodRepoRuleValue::key).collect(toImmutableSet());
EvaluationResult<SkyValue> result =
env.getSkyframeExecutor().prepareAndGet(skyKeys, evaluationContext);
if (result.hasError()) {
Exception e = result.getError().getException();
String message = "Unexpected error during repository rule evaluation.";
if (e != null) {
message = e.getMessage();
}
return reportAndCreateFailureResult(env, message, Code.INVALID_ARGUMENTS);
}
targetRepoRuleValues =
argsAsRepos.entrySet().stream()
.collect(
toImmutableMap(
Entry::getKey,
e ->
(BzlmodRepoRuleValue)
result.get(BzlmodRepoRuleValue.key(e.getValue()))));
for (Map.Entry<String, BzlmodRepoRuleValue> entry : targetRepoRuleValues.entrySet()) {
if (entry.getValue() == BzlmodRepoRuleValue.REPO_RULE_NOT_FOUND_VALUE) {
return reportAndCreateFailureResult(
env,
String.format("In repo argument %s: no such repo", entry.getKey()),
Code.INVALID_ARGUMENTS);
}
}
}
} catch (InterruptedException e) {
String errorMessage = "mod command interrupted: " + e.getMessage();
env.getReporter().handle(Event.error(errorMessage));
return BlazeCommandResult.detailedExitCode(
InterruptedFailureDetails.detailedExitCode(errorMessage));
}
// Workaround to allow different default value for DEPS and EXPLAIN, and also use
// Integer.MAX_VALUE instead of the exact number string.
if (modOptions.depth < 1) {
switch (subcommand) {
case EXPLAIN:
modOptions.depth = 1;
break;
case DEPS:
modOptions.depth = 2;
break;
default:
modOptions.depth = Integer.MAX_VALUE;
}
}
ModExecutor modExecutor =
new ModExecutor(
moduleInspector.getDepGraph(),
depGraphValue.getExtensionUsagesTable(),
moduleInspector.getExtensionToRepoInternalNames(),
filterExtensions,
modOptions,
new OutputStreamWriter(
env.getReporter().getOutErr().getOutputStream(),
modOptions.charset == UTF8 ? UTF_8 : US_ASCII));
switch (subcommand) {
case GRAPH:
modExecutor.graph(fromKeys);
break;
case DEPS:
modExecutor.graph(argsAsModules);
break;
case PATH:
modExecutor.path(fromKeys, argsAsModules);
break;
case ALL_PATHS:
case EXPLAIN:
modExecutor.allPaths(fromKeys, argsAsModules);
break;
case SHOW_REPO:
modExecutor.showRepo(targetRepoRuleValues);
break;
case SHOW_EXTENSION:
modExecutor.showExtension(argsAsExtensions, usageKeys);
break;
default:
throw new IllegalStateException("Unexpected subcommand: " + subcommand);
}
return BlazeCommandResult.success();
}
private static class TidyEventRecorder {
final List<RootModuleFileFixupEvent> fixupEvents = new ArrayList<>();
@Nullable BazelModuleResolutionEvent bazelModuleResolutionEvent;
@Subscribe
public void fixupGenerated(RootModuleFileFixupEvent event) {
fixupEvents.add(event);
}
@Subscribe
public void bazelModuleResolved(BazelModuleResolutionEvent event) {
bazelModuleResolutionEvent = event;
}
}
private BlazeCommandResult runTidy(
CommandEnvironment env, BazelModTidyValue modTidyValue, TidyEventRecorder eventRecorder) {
CommandBuilder buildozerCommand =
new CommandBuilder()
.setWorkingDir(env.getWorkspace())
.addArg(modTidyValue.buildozer().getPathString())
.addArgs(
Stream.concat(
eventRecorder.fixupEvents.stream()
.map(RootModuleFileFixupEvent::getBuildozerCommands)
.flatMap(Collection::stream),
Stream.of("format"))
.collect(toImmutableList()))
.addArg("MODULE.bazel:all");
try {
buildozerCommand.build().execute();
} catch (InterruptedException | CommandException e) {
String suffix = "";
if (e instanceof AbnormalTerminationException) {
if (((AbnormalTerminationException) e).getResult().getTerminationStatus().getRawExitCode()
== 3) {
// Buildozer exits with exit code 3 if it didn't make any changes.
return BlazeCommandResult.success();
}
suffix =
":\n" + new String(((AbnormalTerminationException) e).getResult().getStderr(), UTF_8);
}
return reportAndCreateFailureResult(
env,
"Unexpected error while running buildozer: " + e.getMessage() + suffix,
Code.BUILDOZER_FAILED);
}
for (RootModuleFileFixupEvent fixupEvent : eventRecorder.fixupEvents) {
env.getReporter().handle(Event.info(fixupEvent.getSuccessMessage()));
}
if (modTidyValue.lockfileMode().equals(LockfileMode.UPDATE)) {
// We cannot safely rerun Skyframe evaluation here to pick up the updated module file.
// Instead, we construct a new BazelModuleResolutionEvent with the updated module file
// contents to be picked up by BazelLockFileModule. Since changing use_repos doesn't affect
// module resolution or module extension evaluation, we can reuse the existing lockfile
// information except for the root module file value.
RootedPath moduleFilePath = ModuleFileFunction.getModuleFilePath(env.getWorkspace());
byte[] moduleFileContents;
try {
moduleFileContents = FileSystemUtils.readContent(moduleFilePath.asPath());
} catch (IOException e) {
return reportAndCreateFailureResult(
env,
"Unexpected error while reading module file after running buildozer: " + e.getMessage(),
Code.BUILDOZER_FAILED);
}
ModuleFileValue.RootModuleFileValue newRootModuleFileValue;
try {
newRootModuleFileValue =
ModuleFileFunction.evaluateRootModuleFile(
moduleFileContents,
moduleFilePath,
ModuleFileFunction.getBuiltinModules(
env.getDirectories().getEmbeddedBinariesRoot()),
modTidyValue.moduleOverrides(),
modTidyValue.ignoreDevDeps(),
modTidyValue.starlarkSemantics(),
env.getRuntime().getRuleClassProvider().getBazelStarlarkEnvironment(),
env.getReporter(),
// Not persisted to Skyframe.
SymbolGenerator.createTransient());
} catch (SkyFunctionException | InterruptedException e) {
return reportAndCreateFailureResult(
env,
"Unexpected error parsing module file after running buildozer: " + e.getMessage(),
Code.BUILDOZER_FAILED);
}
// BazelModuleResolutionEvent is cached by Skyframe and thus always emitted.
BazelModuleResolutionEvent updatedModuleResolutionEvent =
BazelModuleResolutionEvent.create(
eventRecorder.bazelModuleResolutionEvent.getOnDiskLockfileValue(),
eventRecorder
.bazelModuleResolutionEvent
.getResolutionOnlyLockfileValue()
.withShallowlyReplacedRootModule(newRootModuleFileValue),
eventRecorder.bazelModuleResolutionEvent.getExtensionUsagesById());
env.getReporter().post(updatedModuleResolutionEvent);
}
return BlazeCommandResult.success();
}
/** Collects a list of {@link ModuleArg} into a set of {@link ModuleKey}s. */
private static ImmutableSet<ModuleKey> moduleArgListToKeys(
ImmutableList<ModuleArg> argList,
ImmutableMap<String, ImmutableSet<ModuleKey>> modulesIndex,
ImmutableMap<ModuleKey, AugmentedModule> depGraph,
ImmutableMap<ModuleKey, RepositoryName> moduleKeyToCanonicalNames,
ImmutableBiMap<String, ModuleKey> baseModuleDeps,
ImmutableBiMap<String, ModuleKey> baseModuleUnusedDeps,
boolean includeUnused)
throws InvalidArgumentException {
ImmutableSet.Builder<ModuleKey> allTargetKeys = new ImmutableSet.Builder<>();
for (ModuleArg moduleArg : argList) {
allTargetKeys.addAll(
moduleArg.resolveToModuleKeys(
modulesIndex,
depGraph,
moduleKeyToCanonicalNames,
baseModuleDeps,
baseModuleUnusedDeps,
includeUnused,
true));
}
return allTargetKeys.build();
}
private static ImmutableSortedSet<ModuleExtensionId> extensionArgListToIds(
ImmutableList<ExtensionArg> args,
ImmutableMap<String, ImmutableSet<ModuleKey>> modulesIndex,
ImmutableMap<ModuleKey, AugmentedModule> depGraph,
ImmutableMap<ModuleKey, RepositoryName> moduleKeyToCanonicalNames,
ImmutableBiMap<String, ModuleKey> baseModuleDeps,
ImmutableBiMap<String, ModuleKey> baseModuleUnusedDeps)
throws InvalidArgumentException {
ImmutableSortedSet.Builder<ModuleExtensionId> extensionsBuilder =
new ImmutableSortedSet.Builder<>(ModuleExtensionId.LEXICOGRAPHIC_COMPARATOR);
for (ExtensionArg arg : args) {
extensionsBuilder.add(
arg.resolveToExtensionId(
modulesIndex,
depGraph,
moduleKeyToCanonicalNames,
baseModuleDeps,
baseModuleUnusedDeps));
}
return extensionsBuilder.build();
}
private static BlazeCommandResult reportAndCreateFailureResult(
CommandEnvironment env, String message, Code detailedCode) {
String fullMessage =
String.format(
"%s%s Type '%s help mod' for syntax and help.",
message, message.endsWith(".") ? "" : ".", env.getRuntime().getProductName());
env.getReporter().handle(Event.error(fullMessage));
return createFailureResult(fullMessage, detailedCode);
}
private static BlazeCommandResult createFailureResult(String message, Code detailedCode) {
return BlazeCommandResult.detailedExitCode(
DetailedExitCode.of(
FailureDetail.newBuilder()
.setModCommand(FailureDetails.ModCommand.newBuilder().setCode(detailedCode).build())
.setMessage(message)
.build()));
}
public static void dumpRepoMappings(List<RepositoryMappingValue> repoMappings, Writer writer)
throws IOException {
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
for (RepositoryMappingValue repoMapping : repoMappings) {
JsonWriter jsonWriter = gson.newJsonWriter(writer);
jsonWriter.beginObject();
for (Entry<String, RepositoryName> entry :
repoMapping.getRepositoryMapping().entries().entrySet()) {
jsonWriter.name(entry.getKey());
jsonWriter.value(entry.getValue().getName());
}
jsonWriter.endObject();
writer.write('\n');
}
writer.flush();
}
}