blob: c12675bfd03c4fd383cbfa297955d88413d9aaa5 [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 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 = "include_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 includeUnused;
@Option(
name = "depth",
defaultValue = "-1",
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 deps and explain it defaults to 1 (only displays"
+ " direct deps of the root besides the target 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 = "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 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 extends Converter.Contextless<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);
// The keyword root takes priority if any module is named the same it can only be referenced
// using the full key.
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
extends Converter.Contextless<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, /*conversionContext=*/ null);
ImmutableList.Builder<TargetModule> targetModules = new ImmutableList.Builder<>();
for (String targetInput : targetStrings) {
targetModules.add(targetModuleConverter.convert(targetInput, /*conversionContext=*/ null));
}
return targetModules.build();
}
@Override
public String getTypeDescription() {
return "a list of <module>s separated by comma";
}
}
static ModqueryOptions getDefaultOptions() {
ModqueryOptions options = new ModqueryOptions();
options.depth = Integer.MAX_VALUE;
options.cycles = false;
options.includeUnused = false;
options.extra = false;
options.modulesFrom = ImmutableList.of(TargetModule.create("", Version.EMPTY));
options.charset = Charset.UTF8;
options.outputFormat = OutputFormat.TEXT;
return options;
}
}