ModqueryCommand added
- `ModqueryCommand` added for the external dependency inspection `modquery` bazel command with option parsing logic implemented.
- `ModqueryOptions` added.
- `ModqueryExecutor` empty skeleton added to separate query execution logic and print to the injected output stream. Dummy test implementations provided for `tree` and `deps` query types.
https://github.com/bazelbuild/bazel/issues/15365
PiperOrigin-RevId: 454914018
Change-Id: Ic9cdd71748eafe63fbf67bb74c0a170a12f8647b
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/BUILD b/src/main/java/com/google/devtools/build/lib/bazel/BUILD
index 278b192..04c4235 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/bazel/BUILD
@@ -27,6 +27,7 @@
"//src/main/java/com/google/devtools/build/lib/analysis:blaze_directories",
"//src/main/java/com/google/devtools/build/lib/analysis:config/build_configuration",
"//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:common",
+ "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:inspection",
"//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:registry",
"//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:resolution",
"//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:resolution_impl",
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
index e87754a..8bf9411 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
@@ -25,6 +25,7 @@
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.RuleDefinition;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
+import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorFunction;
import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleResolutionFunction;
import com.google.devtools.build.lib.bazel.bzlmod.LocalPathOverride;
import com.google.devtools.build.lib.bazel.bzlmod.ModuleExtensionResolutionFunction;
@@ -36,6 +37,7 @@
import com.google.devtools.build.lib.bazel.bzlmod.SingleExtensionEvalFunction;
import com.google.devtools.build.lib.bazel.bzlmod.SingleExtensionUsagesFunction;
import com.google.devtools.build.lib.bazel.commands.FetchCommand;
+import com.google.devtools.build.lib.bazel.commands.ModqueryCommand;
import com.google.devtools.build.lib.bazel.commands.SyncCommand;
import com.google.devtools.build.lib.bazel.repository.LocalConfigPlatformFunction;
import com.google.devtools.build.lib.bazel.repository.LocalConfigPlatformRule;
@@ -200,6 +202,7 @@
@Override
public void serverInit(OptionsParsingResult startupOptions, ServerBuilder builder) {
builder.addCommands(new FetchCommand());
+ builder.addCommands(new ModqueryCommand());
builder.addCommands(new SyncCommand());
builder.addInfoItems(new RepositoryCacheInfoItem(repositoryCache));
}
@@ -256,6 +259,7 @@
SkyFunctions.MODULE_FILE,
new ModuleFileFunction(registryFactory, directories.getWorkspace(), builtinModules))
.addSkyFunction(SkyFunctions.BAZEL_MODULE_RESOLUTION, new BazelModuleResolutionFunction())
+ .addSkyFunction(SkyFunctions.BAZEL_MODULE_INSPECTION, new BazelModuleInspectorFunction())
.addSkyFunction(SkyFunctions.SINGLE_EXTENSION_EVAL, singleExtensionEvalFunction)
.addSkyFunction(SkyFunctions.SINGLE_EXTENSION_USAGES, new SingleExtensionUsagesFunction())
.addSkyFunction(
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD
index 897a1e1..a80c320 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BUILD
@@ -225,12 +225,14 @@
srcs = [
"BazelModuleInspectorFunction.java",
"BazelModuleInspectorValue.java",
+ "ModqueryExecutor.java",
],
deps = [
":common",
":resolution",
"//src/main/java/com/google/devtools/build/lib/skyframe:sky_functions",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec:serialization-constant",
+ "//src/main/java/com/google/devtools/build/lib/util/io",
"//src/main/java/com/google/devtools/build/skyframe",
"//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
"//third_party:auto_value",
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModqueryExecutor.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModqueryExecutor.java
new file mode 100644
index 0000000..278b748
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModqueryExecutor.java
@@ -0,0 +1,106 @@
+// 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.bzlmod;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorValue.AugmentedModule;
+import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorValue.AugmentedModule.ResolutionReason;
+import com.google.devtools.build.lib.util.io.AnsiTerminalPrinter;
+import com.google.devtools.build.lib.util.io.AnsiTerminalPrinter.Mode;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Executes inspection queries for {@link
+ * com.google.devtools.build.lib.bazel.commands.ModqueryCommand} and prints the resulted output.
+ */
+public class ModqueryExecutor {
+ private final ImmutableMap<ModuleKey, Module> resolvedDepGraph;
+ private final ImmutableMap<ModuleKey, AugmentedModule> depGraph;
+ private final ImmutableMap<String, ImmutableSet<ModuleKey>> modulesIndex;
+ private final AnsiTerminalPrinter printer;
+
+ public ModqueryExecutor(
+ ImmutableMap<ModuleKey, Module> resolvedDepGraph,
+ ImmutableMap<ModuleKey, AugmentedModule> depGraph,
+ ImmutableMap<String, ImmutableSet<ModuleKey>> modulesIndex,
+ AnsiTerminalPrinter printer) {
+ this.resolvedDepGraph = resolvedDepGraph;
+ this.depGraph = depGraph;
+ this.modulesIndex = modulesIndex;
+ this.printer = printer;
+ }
+
+ public void tree(ImmutableSet<ModuleKey> from) {
+ printer.printLn(Mode.WARNING + "DUMMY IMPLEMENTATION" + Mode.DEFAULT);
+ printer.printLn("");
+ printer.printLn("All modules index:");
+ printer.printLn(modulesIndex.toString());
+ printer.printLn("");
+ for (ModuleKey target : from) {
+ printer.printLn(Mode.INFO.toString() + target + Mode.DEFAULT);
+ Set<ModuleKey> seen = new HashSet<>();
+ Deque<ModuleKey> toVisit = new ArrayDeque<>();
+ toVisit.add(target);
+ seen.add(target);
+ while (!toVisit.isEmpty()) {
+ ModuleKey curr = toVisit.remove();
+ AugmentedModule module = depGraph.get(curr);
+ for (Entry<ModuleKey, ResolutionReason> e : module.getDeps().entrySet()) {
+ ModuleKey child = e.getKey();
+ if (!resolvedDepGraph.containsKey(child) || seen.contains(child)) {
+ continue;
+ }
+ seen.add(child);
+ toVisit.add(child);
+ printer.printLn(child + " " + e.getValue());
+ }
+ }
+ printer.printLn("");
+ }
+ }
+
+ public void deps(ImmutableSet<ModuleKey> targets) {
+ printer.printLn(Mode.WARNING + "DUMMY IMPLEMENTATION" + Mode.DEFAULT);
+ printer.printLn("");
+ for (ModuleKey target : targets) {
+ printer.printLn(Mode.INFO.toString() + target + Mode.DEFAULT);
+ for (Entry<ModuleKey, ResolutionReason> e : depGraph.get(target).getDeps().entrySet()) {
+ printer.printLn(e.getKey() + " " + e.getValue());
+ }
+ printer.printLn("");
+ }
+ }
+
+ public void path(ImmutableSet<ModuleKey> from, ImmutableSet<ModuleKey> to) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ public void allPaths(ImmutableSet<ModuleKey> from, ImmutableSet<ModuleKey> to) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ public void explain(ImmutableSet<ModuleKey> targets) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ public void show(ImmutableSet<ModuleKey> targets) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/commands/BUILD b/src/main/java/com/google/devtools/build/lib/bazel/commands/BUILD
index e04e817..e2c22f2 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/commands/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/bazel/commands/BUILD
@@ -15,6 +15,7 @@
srcs = glob(["*.java"]),
resources = [
"fetch.txt",
+ "modquery.txt",
"sync.txt",
],
deps = [
@@ -25,6 +26,9 @@
"//src/main/java/com/google/devtools/build/lib/analysis:no_build_event",
"//src/main/java/com/google/devtools/build/lib/analysis:no_build_request_finished_event",
"//src/main/java/com/google/devtools/build/lib/bazel:resolved_event",
+ "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:common",
+ "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:inspection",
+ "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:resolution",
"//src/main/java/com/google/devtools/build/lib/bazel/repository",
"//src/main/java/com/google/devtools/build/lib/bazel/repository/starlark",
"//src/main/java/com/google/devtools/build/lib/cmdline",
@@ -44,12 +48,15 @@
"//src/main/java/com/google/devtools/build/lib/util:detailed_exit_code",
"//src/main/java/com/google/devtools/build/lib/util:exit_code",
"//src/main/java/com/google/devtools/build/lib/util:interrupted_failure_details",
+ "//src/main/java/com/google/devtools/build/lib/util/io",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/build/skyframe",
"//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
"//src/main/java/com/google/devtools/common/options",
"//src/main/java/net/starlark/java/eval",
"//src/main/protobuf:failure_details_java_proto",
+ "//third_party:auto_value",
"//third_party:guava",
+ "//third_party:jsr305",
],
)
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/commands/ModqueryCommand.java b/src/main/java/com/google/devtools/build/lib/bazel/commands/ModqueryCommand.java
new file mode 100644
index 0000000..c0d9438
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/commands/ModqueryCommand.java
@@ -0,0 +1,298 @@
+// 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 com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorValue;
+import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleResolutionValue;
+import com.google.devtools.build.lib.bazel.bzlmod.ModqueryExecutor;
+import com.google.devtools.build.lib.bazel.bzlmod.ModuleKey;
+import com.google.devtools.build.lib.bazel.bzlmod.Version;
+import com.google.devtools.build.lib.bazel.commands.ModqueryOptions.QueryType;
+import com.google.devtools.build.lib.bazel.commands.ModqueryOptions.QueryTypeConverter;
+import com.google.devtools.build.lib.bazel.commands.ModqueryOptions.TargetModule;
+import com.google.devtools.build.lib.bazel.commands.ModqueryOptions.TargetModuleListConverter;
+import com.google.devtools.build.lib.events.Event;
+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.runtime.UiOptions;
+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.ModqueryCommand.Code;
+import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
+import com.google.devtools.build.lib.util.AbruptExitException;
+import com.google.devtools.build.lib.util.DetailedExitCode;
+import com.google.devtools.build.lib.util.InterruptedFailureDetails;
+import com.google.devtools.build.lib.util.io.AnsiTerminalPrinter;
+import com.google.devtools.build.skyframe.EvaluationContext;
+import com.google.devtools.build.skyframe.EvaluationResult;
+import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.common.options.OptionsParsingException;
+import com.google.devtools.common.options.OptionsParsingResult;
+import java.util.List;
+import java.util.Objects;
+
+/** Queries the Bzlmod external dependency graph. */
+@Command(
+ name = ModqueryCommand.NAME,
+ options = {
+ ModqueryOptions.class,
+ PackageOptions.class,
+ KeepGoingOption.class,
+ LoadingPhaseThreadsOption.class
+ },
+ // TODO(andreisolo): figure out which extra options are really needed
+ help = "resource:modquery.txt",
+ shortDescription = "Queries the Bzlmod external dependency graph",
+ allowResidue = true)
+public final class ModqueryCommand implements BlazeCommand {
+
+ public static final String NAME = "modquery";
+
+ @Override
+ public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult options) {
+ BazelModuleResolutionValue moduleResolution;
+ BazelModuleInspectorValue moduleInspector;
+
+ try {
+ // Don't know exactly what it does, used in 'fetch'
+ env.syncPackageLoading(options);
+
+ SkyframeExecutor skyframeExecutor = env.getSkyframeExecutor();
+ LoadingPhaseThreadsOption threadsOption = options.getOptions(LoadingPhaseThreadsOption.class);
+
+ EvaluationContext evaluationContext =
+ EvaluationContext.newBuilder()
+ .setNumThreads(threadsOption.threads)
+ .setEventHandler(env.getReporter())
+ .build();
+
+ EvaluationResult<SkyValue> evaluationResult =
+ skyframeExecutor.prepareAndGet(
+ ImmutableSet.of(BazelModuleResolutionValue.KEY, BazelModuleInspectorValue.KEY),
+ 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);
+ }
+
+ moduleResolution =
+ (BazelModuleResolutionValue) evaluationResult.get(BazelModuleResolutionValue.KEY);
+
+ moduleInspector =
+ (BazelModuleInspectorValue) evaluationResult.get(BazelModuleInspectorValue.KEY);
+
+ } catch (InterruptedException e) {
+ String errorMessage = "Modquery 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());
+ }
+
+ AnsiTerminalPrinter printer =
+ new AnsiTerminalPrinter(
+ env.getReporter().getOutErr().getOutputStream(),
+ options.getOptions(UiOptions.class).useColor());
+
+ ModqueryExecutor modqueryExecutor =
+ new ModqueryExecutor(
+ moduleResolution.getDepGraph(),
+ moduleInspector.getDepGraph(),
+ moduleInspector.getModulesIndex(),
+ printer);
+
+ ModqueryOptions modqueryOptions = options.getOptions(ModqueryOptions.class);
+ Preconditions.checkArgument(modqueryOptions != null);
+
+ if (options.getResidue().isEmpty()) {
+ String errorMessage =
+ String.format("No query type specified, choose one from : %s.", QueryType.printValues());
+ return reportAndCreateFailureResult(env, errorMessage, Code.MODQUERY_COMMAND_UNKNOWN);
+ }
+
+ String queryInput = options.getResidue().get(0);
+ QueryType query;
+ try {
+ query = new QueryTypeConverter().convert(queryInput);
+ } catch (OptionsParsingException e) {
+ String errorMessage =
+ String.format("Invalid query type, choose one from : %s.", QueryType.printValues());
+ return reportAndCreateFailureResult(env, errorMessage, Code.MODQUERY_COMMAND_UNKNOWN);
+ }
+
+ List<String> args = options.getResidue().subList(1, options.getResidue().size());
+
+ ImmutableList<ImmutableSet<ModuleKey>> argsKeysList;
+ try {
+ argsKeysList = parseTargetArgs(args, query.getArgNumber(), moduleInspector.getModulesIndex());
+ } catch (InvalidArgumentException e) {
+ return reportAndCreateFailureResult(env, e.getMessage(), e.getCode());
+ } catch (OptionsParsingException e) {
+ return reportAndCreateFailureResult(env, e.getMessage(), Code.INVALID_ARGUMENTS);
+ }
+ /* Extract and check the --from argument */
+ ImmutableSet<ModuleKey> fromKeys;
+ try {
+ fromKeys =
+ targetListToModuleKeySet(modqueryOptions.modulesFrom, moduleInspector.getModulesIndex());
+ } catch (InvalidArgumentException e) {
+ return reportAndCreateFailureResult(env, e.getMessage(), e.getCode());
+ }
+
+ switch (query) {
+ case TREE:
+ modqueryExecutor.tree(fromKeys);
+ break;
+ case DEPS:
+ modqueryExecutor.deps(argsKeysList.get(0));
+ break;
+ case PATH:
+ modqueryExecutor.path(fromKeys, argsKeysList.get(0));
+ break;
+ case ALL_PATHS:
+ modqueryExecutor.allPaths(fromKeys, argsKeysList.get(0));
+ break;
+ case EXPLAIN:
+ modqueryExecutor.explain(argsKeysList.get(0));
+ break;
+ case SHOW:
+ modqueryExecutor.show(argsKeysList.get(0));
+ break;
+ }
+
+ return BlazeCommandResult.success();
+ }
+
+ @VisibleForTesting
+ public static ImmutableList<ImmutableSet<ModuleKey>> parseTargetArgs(
+ List<String> args,
+ int requiredArgNum,
+ ImmutableMap<String, ImmutableSet<ModuleKey>> modulesIndex)
+ throws OptionsParsingException, InvalidArgumentException {
+ if (requiredArgNum != args.size()) {
+ throw new InvalidArgumentException(
+ String.format(
+ "Invalid number of arguments (provided %d, required %d).",
+ args.size(), requiredArgNum),
+ requiredArgNum > args.size() ? Code.MISSING_ARGUMENTS : Code.TOO_MANY_ARGUMENTS);
+ }
+
+ TargetModuleListConverter converter = new TargetModuleListConverter();
+ ImmutableList.Builder<ImmutableSet<ModuleKey>> argsKeysListBuilder =
+ new ImmutableList.Builder<>();
+
+ for (String arg : args) {
+ ImmutableList<TargetModule> targetList = converter.convert(arg);
+ ImmutableSet<ModuleKey> argModuleKeys = targetListToModuleKeySet(targetList, modulesIndex);
+ argsKeysListBuilder.add(argModuleKeys);
+ }
+ return argsKeysListBuilder.build();
+ }
+
+ private static ImmutableSet<ModuleKey> targetListToModuleKeySet(
+ ImmutableList<TargetModule> targetList,
+ ImmutableMap<String, ImmutableSet<ModuleKey>> modulesIndex)
+ throws InvalidArgumentException {
+ ImmutableSet.Builder<ModuleKey> allTargetKeys = new ImmutableSet.Builder<>();
+ for (TargetModule targetModule : targetList) {
+ allTargetKeys.addAll(targetToModuleKeySet(targetModule, modulesIndex));
+ }
+ return allTargetKeys.build();
+ }
+
+ // Helper to check the module-version argument exists and retrieve its present version(s)
+ // (ModuleKey(s)) if not specified
+ private static ImmutableSet<ModuleKey> targetToModuleKeySet(
+ TargetModule target, ImmutableMap<String, ImmutableSet<ModuleKey>> modulesIndex)
+ throws InvalidArgumentException {
+ if (target.getName().isEmpty() && Objects.equals(target.getVersion(), Version.EMPTY)) {
+ return ImmutableSet.of(ModuleKey.ROOT);
+ }
+ ImmutableSet<ModuleKey> existingKeys = modulesIndex.get(target.getName());
+
+ if (existingKeys == null) {
+ throw new InvalidArgumentException(
+ String.format("Module %s does not exist in the dependency graph.", target.getName()),
+ Code.INVALID_ARGUMENTS);
+ }
+
+ if (target.getVersion() == null) {
+ return existingKeys;
+ }
+ ModuleKey key = ModuleKey.create(target.getName(), target.getVersion());
+ if (!existingKeys.contains(key)) {
+ throw new InvalidArgumentException(
+ String.format(
+ "Module version %s@%s does not exist, available versions: %s.",
+ target.getName(), key, existingKeys),
+ Code.INVALID_ARGUMENTS);
+ }
+ return ImmutableSet.of(key);
+ }
+
+ private static BlazeCommandResult reportAndCreateFailureResult(
+ CommandEnvironment env, String message, Code detailedCode) {
+ if (message.charAt(message.length() - 1) != '.') {
+ message = message.concat(".");
+ }
+ String fullMessage =
+ String.format(
+ message.concat(" Type '%s help modquery' for syntax and help."),
+ 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()
+ .setModqueryCommand(
+ FailureDetails.ModqueryCommand.newBuilder().setCode(detailedCode).build())
+ .setMessage(message)
+ .build()));
+ }
+
+ /** Exception thrown when a user-input argument is invalid */
+ @VisibleForTesting
+ public static class InvalidArgumentException extends Exception {
+ private final Code code;
+
+ private InvalidArgumentException(String message, Code code) {
+ super(message);
+ this.code = code;
+ }
+
+ public Code getCode() {
+ return code;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/commands/ModqueryOptions.java b/src/main/java/com/google/devtools/build/lib/bazel/commands/ModqueryOptions.java
new file mode 100644
index 0000000..7019a9c6
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/commands/ModqueryOptions.java
@@ -0,0 +1,283 @@
+// 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 java.util.Arrays.stream;
+import static java.util.stream.Collectors.joining;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Ascii;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.bazel.bzlmod.Version;
+import com.google.devtools.build.lib.bazel.bzlmod.Version.ParseException;
+import com.google.devtools.common.options.Converter;
+import com.google.devtools.common.options.Converters.CommaSeparatedNonEmptyOptionListConverter;
+import com.google.devtools.common.options.EnumConverter;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionDocumentationCategory;
+import com.google.devtools.common.options.OptionEffectTag;
+import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParsingException;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Options for ModqueryCommand */
+public class ModqueryOptions extends OptionsBase {
+
+ @Option(
+ name = "from",
+ defaultValue = "root",
+ converter = TargetModuleListConverter.class,
+ documentationCategory = OptionDocumentationCategory.MODQUERY,
+ effectTags = {OptionEffectTag.EXECUTION},
+ help =
+ "The module(s) starting from which the dependency graph query will be displayed. Check"
+ + " each query’s description for the exact semantic. Defaults to root.\n")
+ public ImmutableList<TargetModule> modulesFrom;
+
+ @Option(
+ name = "extra",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.MODQUERY,
+ effectTags = {OptionEffectTag.EXECUTION},
+ help =
+ "The queries will also display the reason why modules were resolved to their current"
+ + " version (if changed). Defaults to true only for the explain query.")
+ public boolean extra;
+
+ @Option(
+ name = "unused",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.MODQUERY,
+ effectTags = {OptionEffectTag.EXECUTION},
+ help =
+ "The queries will also take into account and display the unused modules, which are not"
+ + " present in the module resolution graph after selection (due to the"
+ + " Minimal-Version Selection or override rules). This can have different effects for"
+ + " each of the query types i.e. include new paths in the all_paths command, or extra"
+ + " dependants in the explain command.\n")
+ public boolean unused;
+
+ @Option(
+ name = "depth",
+ defaultValue = "2147483647",
+ documentationCategory = OptionDocumentationCategory.MODQUERY,
+ effectTags = {OptionEffectTag.EXECUTION},
+ help =
+ "Maximum display depth of the dependency tree. A depth of 1 displays the direct"
+ + " dependencies, for example. For tree, path and all_paths it defaults to"
+ + " Integer.MAX_VALUE, while for explain it defaults to 1 (only displays direct deps"
+ + " of the root besides the leaves and their parents).\n")
+ public int depth;
+
+ @Option(
+ name = "cycles",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.MODQUERY,
+ effectTags = {OptionEffectTag.EXECUTION},
+ help =
+ "Points out dependency cycles inside the displayed tree, which are normally ignored by"
+ + " default.")
+ public boolean cycles;
+
+ @Option(
+ name = "charset",
+ defaultValue = "utf8",
+ converter = CharsetConverter.class,
+ documentationCategory = OptionDocumentationCategory.MODQUERY,
+ effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
+ help =
+ "Chooses the character set to use for the tree. Only affects text output. Valid values"
+ + " are \"utf8\" or \"ascii\". Default is \"utf8\"")
+ public Charset charset;
+
+ @Option(
+ name = "prefix",
+ defaultValue = "indent",
+ converter = PrefixConverter.class,
+ documentationCategory = OptionDocumentationCategory.MODQUERY,
+ effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
+ help =
+ "Sets how each line is displayed (only affects `text` output). The prefix value can be"
+ + " one of \"indent\", \"depth\" or \"none\". The default is \"indent\"")
+ public Prefix prefix;
+
+ @Option(
+ name = "output",
+ defaultValue = "text",
+ converter = OutputFormatConverter.class,
+ documentationCategory = OptionDocumentationCategory.MODQUERY,
+ effectTags = {OptionEffectTag.TERMINAL_OUTPUT},
+ help =
+ "The format in which the query results should be printed. Allowed values for query are: "
+ + "text, json, graph")
+ public OutputFormat outputFormat;
+
+ enum QueryType {
+ DEPS(1),
+ TREE(0),
+ ALL_PATHS(1),
+ PATH(1),
+ EXPLAIN(1),
+ SHOW(1);
+
+ /* Store the number of arguments that it accepts for easy pre-check */
+ private final int argNumber;
+
+ QueryType(int argNumber) {
+ this.argNumber = argNumber;
+ }
+
+ @Override
+ public String toString() {
+ return Ascii.toLowerCase(this.name());
+ }
+
+ public int getArgNumber() {
+ return argNumber;
+ }
+
+ public static String printValues() {
+ return "(" + stream(values()).map(QueryType::toString).collect(joining(", ")) + ")";
+ }
+ }
+
+ /** Converts a query type option string to a properly typed {@link QueryType} */
+ public static class QueryTypeConverter extends EnumConverter<QueryType> {
+ public QueryTypeConverter() {
+ super(QueryType.class, "query type");
+ }
+ }
+
+ enum Charset {
+ UTF8,
+ ASCII
+ }
+
+ /** Converts a charset option string to a properly typed {@link Charset} */
+ public static class CharsetConverter extends EnumConverter<Charset> {
+ public CharsetConverter() {
+ super(Charset.class, "output charset");
+ }
+ }
+
+ enum Prefix {
+ INDENT,
+ DEPTH,
+ NONE
+ }
+
+ /** Converts a prefix option string to a properly typed {@link Charset} */
+ public static class PrefixConverter extends EnumConverter<Prefix> {
+ public PrefixConverter() {
+ super(Prefix.class, "output tree prefix");
+ }
+ }
+
+ enum OutputFormat {
+ TEXT,
+ JSON,
+ GRAPH
+ }
+
+ /** Converts an output format option string to a properly typed {@link OutputFormat} */
+ public static class OutputFormatConverter extends EnumConverter<OutputFormat> {
+ public OutputFormatConverter() {
+ super(OutputFormat.class, "output format");
+ }
+ }
+
+ /** Argument of a modquery converted from the form name@version or name. */
+ @AutoValue
+ abstract static class TargetModule {
+ static TargetModule create(String name, Version version) {
+ return new AutoValue_ModqueryOptions_TargetModule(name, version);
+ }
+
+ abstract String getName();
+
+ /**
+ * If it is null, it represents any (one or multiple) present versions of the module in the dep
+ * graph, which is different from the empty version
+ */
+ @Nullable
+ abstract Version getVersion();
+ }
+
+ /** Converts a module target argument string to a properly typed {@link TargetModule} */
+ static class TargetModuleConverter implements Converter<TargetModule> {
+
+ @Override
+ public TargetModule convert(String input) throws OptionsParsingException {
+ String errorMessage = String.format("Cannot parse the given module argument: %s.", input);
+ Preconditions.checkArgument(input != null);
+ if (Ascii.equalsIgnoreCase(input, "root")) {
+ return TargetModule.create("", Version.EMPTY);
+ } else {
+ List<String> splits = Splitter.on('@').splitToList(input);
+ if (splits.isEmpty() || splits.get(0).isEmpty()) {
+ throw new OptionsParsingException(errorMessage);
+ }
+
+ if (splits.size() == 2) {
+ if (splits.get(1).equals("_")) {
+ return TargetModule.create(splits.get(0), Version.EMPTY);
+ }
+ if (splits.get(1).isEmpty()) {
+ throw new OptionsParsingException(errorMessage);
+ }
+ try {
+ return TargetModule.create(splits.get(0), Version.parse(splits.get(1)));
+ } catch (ParseException e) {
+ throw new OptionsParsingException(errorMessage, e);
+ }
+
+ } else if (splits.size() == 1) {
+ return TargetModule.create(splits.get(0), null);
+ } else {
+ throw new OptionsParsingException(errorMessage);
+ }
+ }
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "root, <module>@<version> or <module>";
+ }
+ }
+
+ /** Converts a comma-separated module list argument (i.e. A@1.0,B@2.0) */
+ public static class TargetModuleListConverter implements Converter<ImmutableList<TargetModule>> {
+
+ @Override
+ public ImmutableList<TargetModule> convert(String input) throws OptionsParsingException {
+ CommaSeparatedNonEmptyOptionListConverter listConverter =
+ new CommaSeparatedNonEmptyOptionListConverter();
+ TargetModuleConverter targetModuleConverter = new TargetModuleConverter();
+ ImmutableList<String> targetStrings = listConverter.convert(input);
+ ImmutableList.Builder<TargetModule> targetModules = new ImmutableList.Builder<>();
+ for (String targetInput : targetStrings) {
+ targetModules.add(targetModuleConverter.convert(targetInput));
+ }
+ return targetModules.build();
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "a list of <module>s separated by comma";
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/commands/modquery.txt b/src/main/java/com/google/devtools/build/lib/bazel/commands/modquery.txt
new file mode 100644
index 0000000..84c1dc3
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/commands/modquery.txt
@@ -0,0 +1,31 @@
+
+Usage: %{product} %{command} [<option> ...] <query_type> [<args> ...]
+
+The command will display a dependency tree or parts of the dependency tree, structured to display different kinds of insights depending on the query type.
+Calling the command with no argument will default to:
+bazel modquery tree root
+
+<query_type> [<args> ...] can be one of the following:
+
+ - tree: Displays the full dependency tree. Use the --from option to specify which module(s) you want the tree to start from (defaults to root which displays the whole dependency tree).
+ - deps <module(s)>: Displays the direct dependencies of the target module(s).
+ - path <module(s)_to>: Displays the shortest path found in the dependency graph from (any of) the --from module(s) to (any of) <module(s)_to>.
+ - all_paths <module(s)_to>: Display the dependency graph starting from (any of) the --from module(s) and containing any existing paths to (any of) the <module(s)_to>.
+ - explain <module(s)>: Prints all the places where the module is (or was) requested as a direct dependency, along with the reason why the respective final version was selected. It will display a pruned version of the all_paths root <module(s)> command which only contains the direct deps of the root, the <module(s)> leaves, along with their dependants (can be modified with --depth).
+ - show <module(s)>: Prints the rule that generated these modules’ repos (i.e. http_archive()).
+
+
+<module> arguments must be of type:
+
+ - root: The current (root) module you are inside of.
+ - <name>@<version>: A specific module version.
+ - <name>@_: Specifies the empty version of a module (for non-registry overridden) modules).
+ - <name>: Can be used as a placeholder for all the present versions of the module <name>
+
+<modules> means:
+ - <module>,<module>,... : A list of comma separated modules, where each <module> has the form of one of the above.
+
+NOTE: This command is still very experimental and the precise semantics
+will change in the near future.
+
+%{options}
\ No newline at end of file
diff --git a/src/main/java/com/google/devtools/common/options/OptionDocumentationCategory.java b/src/main/java/com/google/devtools/common/options/OptionDocumentationCategory.java
index 34a2b02..83da7c3 100644
--- a/src/main/java/com/google/devtools/common/options/OptionDocumentationCategory.java
+++ b/src/main/java/com/google/devtools/common/options/OptionDocumentationCategory.java
@@ -55,8 +55,8 @@
LOGGING,
/**
- * This option affects how strictly Bazel enforces valid build inputs (rule definitions,
- * flag combinations, etc).
+ * This option affects how strictly Bazel enforces valid build inputs (rule definitions, flag
+ * combinations, etc).
*/
INPUT_STRICTNESS,
@@ -79,8 +79,8 @@
OUTPUT_PARAMETERS,
/**
- * This option provides information about signing outputs of the build. (For example, signing
- * an iOS application with a certificate.)
+ * This option provides information about signing outputs of the build. (For example, signing an
+ * iOS application with a certificate.)
*/
SIGNING,
@@ -90,9 +90,7 @@
*/
STARLARK_SEMANTICS,
- /**
- * This option dictates information about the test environment or test runner.
- */
+ /** This option dictates information about the test environment or test runner. */
TESTING,
/**
@@ -106,6 +104,9 @@
/** This option relates to query output and semantics. */
QUERY,
+ /** This option relates to modquery (external dependencies) output and semantics. */
+ MODQUERY,
+
/**
* This option specifies or alters a generic input to a Bazel command. This category should only
* be used if the input is generic and does not fall into other categories, such as toolchain-
diff --git a/src/main/java/com/google/devtools/common/options/OptionFilterDescriptions.java b/src/main/java/com/google/devtools/common/options/OptionFilterDescriptions.java
index d85a8c4..1e66ed6 100644
--- a/src/main/java/com/google/devtools/common/options/OptionFilterDescriptions.java
+++ b/src/main/java/com/google/devtools/common/options/OptionFilterDescriptions.java
@@ -32,6 +32,7 @@
OptionDocumentationCategory.STARLARK_SEMANTICS,
OptionDocumentationCategory.TESTING,
OptionDocumentationCategory.QUERY,
+ OptionDocumentationCategory.MODQUERY,
OptionDocumentationCategory.BUILD_TIME_OPTIMIZATION,
OptionDocumentationCategory.LOGGING,
OptionDocumentationCategory.GENERIC_INPUTS,
@@ -86,6 +87,9 @@
"Options that configure the toolchain used for action execution")
.put(OptionDocumentationCategory.QUERY, "Options relating to query output and semantics")
.put(
+ OptionDocumentationCategory.MODQUERY,
+ "Options relating to modquery output and semantics")
+ .put(
OptionDocumentationCategory.GENERIC_INPUTS,
"Options specifying or altering a generic input to a Bazel command that does not fall "
+ "into other categories.")
diff --git a/src/main/protobuf/failure_details.proto b/src/main/protobuf/failure_details.proto
index c8a8721..54091ef 100644
--- a/src/main/protobuf/failure_details.proto
+++ b/src/main/protobuf/failure_details.proto
@@ -147,6 +147,7 @@
StarlarkLoading starlark_loading = 179;
ExternalDeps external_deps = 181;
DiffAwareness diff_awareness = 182;
+ ModqueryCommand modquery_command = 183;
}
reserved 102; // For internal use
@@ -1274,3 +1275,14 @@
Code code = 1;
}
+
+message ModqueryCommand {
+ enum Code {
+ MODQUERY_COMMAND_UNKNOWN = 0 [(metadata) = { exit_code: 37 }];
+ MISSING_ARGUMENTS = 1 [(metadata) = { exit_code: 2 }];
+ TOO_MANY_ARGUMENTS = 2 [(metadata) = { exit_code: 2 }];
+ INVALID_ARGUMENTS = 3 [(metadata) = { exit_code: 2 }];
+ }
+
+ Code code = 1;
+}
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/BUILD b/src/test/java/com/google/devtools/build/lib/bazel/BUILD
index 8b826e0..2579b51 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/bazel/BUILD
@@ -8,6 +8,7 @@
testonly = 0,
srcs = glob(["*"]) + [
"//src/test/java/com/google/devtools/build/lib/bazel/bzlmod:srcs",
+ "//src/test/java/com/google/devtools/build/lib/bazel/commands:srcs",
"//src/test/java/com/google/devtools/build/lib/bazel/debug:srcs",
"//src/test/java/com/google/devtools/build/lib/bazel/execlog:srcs",
"//src/test/java/com/google/devtools/build/lib/bazel/repository:srcs",
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/commands/BUILD b/src/test/java/com/google/devtools/build/lib/bazel/commands/BUILD
new file mode 100644
index 0000000..847dd62
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/bazel/commands/BUILD
@@ -0,0 +1,40 @@
+load("@rules_java//java:defs.bzl", "java_test")
+
+package(
+ default_testonly = 1,
+ default_visibility = ["//src:__subpackages__"],
+)
+
+licenses(["notice"])
+
+filegroup(
+ name = "srcs",
+ testonly = 0,
+ srcs = glob(["*"]),
+ visibility = ["//src:__subpackages__"],
+)
+
+java_library(
+ name = "BzlmodCommandsTests_lib",
+ srcs = glob(
+ ["*.java"],
+ ),
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:common",
+ "//src/main/java/com/google/devtools/build/lib/bazel/commands",
+ "//src/main/java/com/google/devtools/common/options",
+ "//src/main/protobuf:failure_details_java_proto",
+ "//third_party:guava",
+ "//third_party:junit4",
+ "//third_party:truth",
+ ],
+)
+
+java_test(
+ name = "BzlmodCommandsTests",
+ test_class = "com.google.devtools.build.lib.AllTests",
+ runtime_deps = [
+ ":BzlmodCommandsTests_lib",
+ "//src/test/java/com/google/devtools/build/lib:test_runner",
+ ],
+)
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/commands/ModqueryCommandTest.java b/src/test/java/com/google/devtools/build/lib/bazel/commands/ModqueryCommandTest.java
new file mode 100644
index 0000000..c3d0bee
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/bazel/commands/ModqueryCommandTest.java
@@ -0,0 +1,122 @@
+// 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.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.bazel.bzlmod.ModuleKey;
+import com.google.devtools.build.lib.bazel.bzlmod.Version;
+import com.google.devtools.build.lib.bazel.bzlmod.Version.ParseException;
+import com.google.devtools.build.lib.bazel.commands.ModqueryCommand.InvalidArgumentException;
+import com.google.devtools.build.lib.bazel.commands.ModqueryOptions.QueryType;
+import com.google.devtools.build.lib.server.FailureDetails.ModqueryCommand.Code;
+import com.google.devtools.common.options.OptionsParsingException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link ModqueryCommand}. */
+@RunWith(JUnit4.class)
+public class ModqueryCommandTest {
+
+ public final ImmutableMap<String, ImmutableSet<ModuleKey>> modulesIndex =
+ ImmutableMap.of(
+ "A",
+ ImmutableSet.of(ModuleKey.ROOT),
+ "B",
+ ImmutableSet.of(
+ ModuleKey.create("B", Version.parse("1.0")),
+ ModuleKey.create("B", Version.parse("2.0"))),
+ "C",
+ ImmutableSet.of(ModuleKey.create("C", Version.EMPTY)));
+
+ public ModqueryCommandTest() throws ParseException {}
+
+ @Test
+ public void testAllPathsNoArgsThrowsMissingArguments() {
+ InvalidArgumentException e =
+ assertThrows(
+ InvalidArgumentException.class,
+ () ->
+ ModqueryCommand.parseTargetArgs(
+ ImmutableList.of(), QueryType.ALL_PATHS.getArgNumber(), modulesIndex));
+ assertThat(e.getCode()).isEqualTo(Code.MISSING_ARGUMENTS);
+ }
+
+ @Test
+ public void testTreeNoArgs() throws InvalidArgumentException, OptionsParsingException {
+ ModqueryCommand.parseTargetArgs(
+ ImmutableList.of(), QueryType.TREE.getArgNumber(), modulesIndex);
+ }
+
+ @Test
+ public void testTreeWithArgsThrowsTooManyArguments() {
+ ImmutableList<String> args = ImmutableList.of("A");
+ InvalidArgumentException e =
+ assertThrows(
+ InvalidArgumentException.class,
+ () ->
+ ModqueryCommand.parseTargetArgs(args, QueryType.TREE.getArgNumber(), modulesIndex));
+ assertThat(e.getCode()).isEqualTo(Code.TOO_MANY_ARGUMENTS);
+ }
+
+ @Test
+ public void testDepsArgWrongFormat_noVersion() {
+ ImmutableList<String> args = ImmutableList.of("A@");
+ assertThrows(
+ OptionsParsingException.class,
+ () -> ModqueryCommand.parseTargetArgs(args, QueryType.DEPS.getArgNumber(), modulesIndex));
+ }
+
+ @Test
+ public void testDepsArgInvalid_missingModule() {
+ ImmutableList<String> args = ImmutableList.of("D");
+ InvalidArgumentException e =
+ assertThrows(
+ InvalidArgumentException.class,
+ () ->
+ ModqueryCommand.parseTargetArgs(args, QueryType.DEPS.getArgNumber(), modulesIndex));
+ assertThat(e.getCode()).isEqualTo(Code.INVALID_ARGUMENTS);
+ }
+
+ @Test
+ public void testDepsArgInvalid_missingModuleVersion() {
+ ImmutableList<String> args = ImmutableList.of("B@3.0");
+ InvalidArgumentException e =
+ assertThrows(
+ InvalidArgumentException.class,
+ () ->
+ ModqueryCommand.parseTargetArgs(args, QueryType.DEPS.getArgNumber(), modulesIndex));
+ assertThat(e.getCode()).isEqualTo(Code.INVALID_ARGUMENTS);
+ }
+
+ @Test
+ public void testDepsArgInvalid_invalidListFormat() {
+ ImmutableList<String> args = ImmutableList.of("B@1.0;B@2.0");
+ assertThrows(
+ OptionsParsingException.class,
+ () -> ModqueryCommand.parseTargetArgs(args, QueryType.DEPS.getArgNumber(), modulesIndex));
+ }
+
+ @Test
+ public void testDepsListArg_ok() throws InvalidArgumentException, OptionsParsingException {
+ ImmutableList<String> args = ImmutableList.of("A,B@1.0,B@2.0,C@_");
+ ModqueryCommand.parseTargetArgs(args, QueryType.DEPS.getArgNumber(), modulesIndex);
+ }
+}