blob: 4472765a92d139bbce62a778ca5d0950592cb6bc [file] [log] [blame]
use crate::cli;
use crate::command_prelude::*;
use anyhow::{bail, format_err};
use cargo::core::dependency::DepKind;
use cargo::ops::tree::{self, EdgeKind};
use cargo::ops::Packages;
use cargo::util::print_available_packages;
use cargo::util::CargoResult;
use std::collections::HashSet;
use std::str::FromStr;
pub fn cli() -> Command {
subcommand("tree")
.about("Display a tree visualization of a dependency graph")
.arg(
flag("all", "Deprecated, use --no-dedupe instead")
.short('a')
.hide(true),
)
.arg_quiet()
.arg(flag("no-dev-dependencies", "Deprecated, use -e=no-dev instead").hide(true))
.arg(
multi_opt(
"edges",
"KINDS",
"The kinds of dependencies to display \
(features, normal, build, dev, all, \
no-normal, no-build, no-dev, no-proc-macro)",
)
.short('e'),
)
.arg(
optional_multi_opt(
"invert",
"SPEC",
"Invert the tree direction and focus on the given package",
)
.short('i'),
)
.arg(multi_opt(
"prune",
"SPEC",
"Prune the given package from the display of the dependency tree",
))
.arg(opt("depth", "Maximum display depth of the dependency tree").value_name("DEPTH"))
.arg(flag("no-indent", "Deprecated, use --prefix=none instead").hide(true))
.arg(flag("prefix-depth", "Deprecated, use --prefix=depth instead").hide(true))
.arg(
opt(
"prefix",
"Change the prefix (indentation) of how each entry is displayed",
)
.value_name("PREFIX")
.value_parser(["depth", "indent", "none"])
.default_value("indent"),
)
.arg(flag(
"no-dedupe",
"Do not de-duplicate (repeats all shared dependencies)",
))
.arg(
flag(
"duplicates",
"Show only dependencies which come in multiple versions (implies -i)",
)
.short('d')
.alias("duplicate"),
)
.arg(
opt("charset", "Character set to use in output")
.value_name("CHARSET")
.value_parser(["utf8", "ascii"])
.default_value("utf8"),
)
.arg(
opt("format", "Format string used for printing dependencies")
.value_name("FORMAT")
.short('f')
.default_value("{p}"),
)
.arg(
// Backwards compatibility with old cargo-tree.
flag("version", "Print version info and exit")
.short('V')
.hide(true),
)
.arg_package_spec_no_all(
"Package to be used as the root of the tree",
"Display the tree for all packages in the workspace",
"Exclude specific workspace members",
)
.arg_features()
.arg(flag("all-targets", "Deprecated, use --target=all instead").hide(true))
.arg_target_triple(
"Filter dependencies matching the given target-triple (default host platform). \
Pass `all` to include all targets.",
)
.arg_manifest_path()
.after_help("Run `cargo help tree` for more detailed information.\n")
}
pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
if args.flag("version") {
let verbose = args.verbose() > 0;
let version = cli::get_version_string(verbose);
cargo::drop_print!(config, "{}", version);
return Ok(());
}
let prefix = if args.flag("no-indent") {
config
.shell()
.warn("the --no-indent flag has been changed to --prefix=none")?;
"none"
} else if args.flag("prefix-depth") {
config
.shell()
.warn("the --prefix-depth flag has been changed to --prefix=depth")?;
"depth"
} else {
args.get_one::<String>("prefix").unwrap().as_str()
};
let prefix = tree::Prefix::from_str(prefix).map_err(|e| anyhow::anyhow!("{}", e))?;
let no_dedupe = args.flag("no-dedupe") || args.flag("all");
if args.flag("all") {
config.shell().warn(
"The `cargo tree` --all flag has been changed to --no-dedupe, \
and may be removed in a future version.\n\
If you are looking to display all workspace members, use the --workspace flag.",
)?;
}
let targets = if args.flag("all-targets") {
config
.shell()
.warn("the --all-targets flag has been changed to --target=all")?;
vec!["all".to_string()]
} else {
args._values_of("target")
};
let target = tree::Target::from_cli(targets);
let (edge_kinds, no_proc_macro) = parse_edge_kinds(config, args)?;
let graph_features = edge_kinds.contains(&EdgeKind::Feature);
let pkgs_to_prune = args._values_of("prune");
let packages = args.packages_from_flags()?;
let mut invert = args
.get_many::<String>("invert")
.map_or_else(|| Vec::new(), |is| is.map(|s| s.to_string()).collect());
if args.is_present_with_zero_values("invert") {
match &packages {
Packages::Packages(ps) => {
// Backwards compatibility with old syntax of `cargo tree -i -p foo`.
invert.extend(ps.clone());
}
_ => {
return Err(format_err!(
"The `-i` flag requires a package name.\n\
\n\
The `-i` flag is used to inspect the reverse dependencies of a specific\n\
package. It will invert the tree and display the packages that depend on the\n\
given package.\n\
\n\
Note that in a workspace, by default it will only display the package's\n\
reverse dependencies inside the tree of the workspace member in the current\n\
directory. The --workspace flag can be used to extend it so that it will show\n\
the package's reverse dependencies across the entire workspace. The -p flag\n\
can be used to display the package's reverse dependencies only with the\n\
subtree of the package given to -p.\n\
"
)
.into());
}
}
}
let ws = args.workspace(config)?;
if args.is_present_with_zero_values("package") {
print_available_packages(&ws)?;
}
let charset = tree::Charset::from_str(args.get_one::<String>("charset").unwrap())
.map_err(|e| anyhow::anyhow!("{}", e))?;
let opts = tree::TreeOptions {
cli_features: args.cli_features()?,
packages,
target,
edge_kinds,
invert,
pkgs_to_prune,
prefix,
no_dedupe,
duplicates: args.flag("duplicates"),
charset,
format: args.get_one::<String>("format").cloned().unwrap(),
graph_features,
max_display_depth: args.value_of_u32("depth")?.unwrap_or(u32::MAX),
no_proc_macro,
};
if opts.graph_features && opts.duplicates {
return Err(format_err!("the `-e features` flag does not support `--duplicates`").into());
}
tree::build_and_print(&ws, &opts)?;
Ok(())
}
/// Parses `--edges` option.
///
/// Returns a tuple of `EdgeKind` map and `no_proc_marco` flag.
fn parse_edge_kinds(config: &Config, args: &ArgMatches) -> CargoResult<(HashSet<EdgeKind>, bool)> {
let (kinds, no_proc_macro) = {
let mut no_proc_macro = false;
let mut kinds = args.get_many::<String>("edges").map_or_else(
|| Vec::new(),
|es| {
es.flat_map(|e| e.split(','))
.filter(|e| {
no_proc_macro = *e == "no-proc-macro";
!no_proc_macro
})
.collect()
},
);
if args.flag("no-dev-dependencies") {
config
.shell()
.warn("the --no-dev-dependencies flag has changed to -e=no-dev")?;
kinds.push("no-dev");
}
if kinds.is_empty() {
kinds.extend(&["normal", "build", "dev"]);
}
(kinds, no_proc_macro)
};
let mut result = HashSet::new();
let insert_defaults = |result: &mut HashSet<EdgeKind>| {
result.insert(EdgeKind::Dep(DepKind::Normal));
result.insert(EdgeKind::Dep(DepKind::Build));
result.insert(EdgeKind::Dep(DepKind::Development));
};
let unknown = |k| {
bail!(
"unknown edge kind `{}`, valid values are \
\"normal\", \"build\", \"dev\", \
\"no-normal\", \"no-build\", \"no-dev\", \"no-proc-macro\", \
\"features\", or \"all\"",
k
)
};
if kinds.iter().any(|k| k.starts_with("no-")) {
insert_defaults(&mut result);
for kind in &kinds {
match *kind {
"no-normal" => result.remove(&EdgeKind::Dep(DepKind::Normal)),
"no-build" => result.remove(&EdgeKind::Dep(DepKind::Build)),
"no-dev" => result.remove(&EdgeKind::Dep(DepKind::Development)),
"features" => result.insert(EdgeKind::Feature),
"normal" | "build" | "dev" | "all" => {
bail!(
"`{}` dependency kind cannot be mixed with \
\"no-normal\", \"no-build\", or \"no-dev\" \
dependency kinds",
kind
)
}
k => return unknown(k),
};
}
return Ok((result, no_proc_macro));
}
for kind in &kinds {
match *kind {
"all" => {
insert_defaults(&mut result);
result.insert(EdgeKind::Feature);
}
"features" => {
result.insert(EdgeKind::Feature);
}
"normal" => {
result.insert(EdgeKind::Dep(DepKind::Normal));
}
"build" => {
result.insert(EdgeKind::Dep(DepKind::Build));
}
"dev" => {
result.insert(EdgeKind::Dep(DepKind::Development));
}
k => return unknown(k),
}
}
if kinds.len() == 1 && kinds[0] == "features" {
insert_defaults(&mut result);
}
Ok((result, no_proc_macro))
}