use self::format::Pattern;
use crate::core::compiler::{CompileKind, RustcTargetData};
use crate::core::dependency::DepKind;
use crate::core::resolver::{features::CliFeatures, ForceAllTargets, HasDevUnits};
use crate::core::{Package, PackageId, PackageIdSpec, PackageIdSpecQuery, Workspace};
use crate::ops::{self, Packages};
use crate::util::CargoResult;
use crate::{drop_print, drop_println};
use anyhow::Context as _;
use graph::Graph;
use std::collections::{HashMap, HashSet};
use std::str::FromStr;
mod format;
mod graph;
pub use {graph::EdgeKind, graph::Node};
pub struct TreeOptions {
pub cli_features: CliFeatures,
pub packages: Packages,
pub target: Target,
pub edge_kinds: HashSet<EdgeKind>,
pub invert: Vec<String>,
pub pkgs_to_prune: Vec<String>,
pub prefix: Prefix,
pub no_dedupe: bool,
pub duplicates: bool,
pub format: String,
pub graph_features: bool,
pub display_depth: DisplayDepth,
pub no_proc_macro: bool,
}
#[derive(PartialEq)]
pub enum Target {
Host,
Specific(Vec<String>),
All,
}
impl Target {
pub fn from_cli(targets: Vec<String>) -> Target {
match targets.len() {
0 => Target::Host,
1 if targets[0] == "all" => Target::All,
_ => Target::Specific(targets),
}
}
}
#[derive(Clone, Copy)]
pub enum Prefix {
None,
Indent,
Depth,
}
impl FromStr for Prefix {
type Err = &'static str;
fn from_str(s: &str) -> Result<Prefix, &'static str> {
match s {
"none" => Ok(Prefix::None),
"indent" => Ok(Prefix::Indent),
"depth" => Ok(Prefix::Depth),
_ => Err("invalid prefix"),
}
}
}
#[derive(Clone, Copy)]
pub enum DisplayDepth {
MaxDisplayDepth(u32),
Workspace,
}
impl FromStr for DisplayDepth {
type Err = clap::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"workspace" => Ok(Self::Workspace),
s => s.parse().map(Self::MaxDisplayDepth).map_err(|_| {
clap::Error::raw(
clap::error::ErrorKind::ValueValidation,
format!(
"supported values for --depth are non-negative integers and `workspace`, \
but `{}` is unknown",
s
),
)
}),
}
}
}
struct Symbols {
down: &'static str,
tee: &'static str,
ell: &'static str,
right: &'static str,
}
static UTF8_SYMBOLS: Symbols = Symbols {
down: "│",
tee: "├",
ell: "└",
right: "─",
};
static ASCII_SYMBOLS: Symbols = Symbols {
down: "|",
tee: "|",
ell: "`",
right: "-",
};
pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<()> {
let requested_targets = match &opts.target {
Target::All | Target::Host => Vec::new(),
Target::Specific(t) => t.clone(),
};
let requested_kinds = CompileKind::from_requested_targets(ws.gctx(), &requested_targets)?;
let mut target_data = RustcTargetData::new(ws, &requested_kinds)?;
let specs = opts.packages.to_package_id_specs(ws)?;
let has_dev = if opts
.edge_kinds
.contains(&EdgeKind::Dep(DepKind::Development))
{
HasDevUnits::Yes
} else {
HasDevUnits::No
};
let force_all = if opts.target == Target::All {
ForceAllTargets::Yes
} else {
ForceAllTargets::No
};
let dry_run = false;
let ws_resolve = ops::resolve_ws_with_opts(
ws,
&mut target_data,
&requested_kinds,
&opts.cli_features,
&specs,
has_dev,
force_all,
dry_run,
)?;
let package_map: HashMap<PackageId, &Package> = ws_resolve
.pkg_set
.packages()
.map(|pkg| (pkg.package_id(), pkg))
.collect();
let mut graph = graph::build(
ws,
&ws_resolve.targeted_resolve,
&ws_resolve.resolved_features,
&specs,
&opts.cli_features,
&target_data,
&requested_kinds,
package_map,
opts,
)?;
let root_specs = if opts.invert.is_empty() {
specs
} else {
opts.invert
.iter()
.map(|p| PackageIdSpec::parse(p))
.collect::<Result<Vec<PackageIdSpec>, _>>()?
};
let root_ids = ws_resolve.targeted_resolve.specs_to_ids(&root_specs)?;
let root_indexes = graph.indexes_from_ids(&root_ids);
let root_indexes = if opts.duplicates {
graph = graph.from_reachable(root_indexes.as_slice());
graph.find_duplicates()
} else {
root_indexes
};
if !opts.invert.is_empty() || opts.duplicates {
graph.invert();
}
let pkgs_to_prune = opts
.pkgs_to_prune
.iter()
.map(|p| PackageIdSpec::parse(p).map_err(Into::into))
.map(|r| {
r.and_then(|spec| spec.query(ws_resolve.targeted_resolve.iter()).and(Ok(spec)))
})
.collect::<CargoResult<Vec<PackageIdSpec>>>()?;
if root_indexes.len() == 0 {
ws.gctx().shell().warn(
"nothing to print.\n\n\
To find dependencies that require specific target platforms, \
try to use option `--target all` first, and then narrow your search scope accordingly.",
)?;
} else {
print(ws, opts, root_indexes, &pkgs_to_prune, &graph)?;
}
Ok(())
}
fn print(
ws: &Workspace<'_>,
opts: &TreeOptions,
roots: Vec<usize>,
pkgs_to_prune: &[PackageIdSpec],
graph: &Graph<'_>,
) -> CargoResult<()> {
let format = Pattern::new(&opts.format)
.with_context(|| format!("tree format `{}` not valid", opts.format))?;
let symbols = if ws.gctx().shell().out_unicode() {
&UTF8_SYMBOLS
} else {
&ASCII_SYMBOLS
};
let mut visited_deps = HashSet::new();
for (i, root_index) in roots.into_iter().enumerate() {
if i != 0 {
drop_println!(ws.gctx());
}
let mut levels_continue = vec![];
let mut print_stack = vec![];
print_node(
ws,
graph,
root_index,
&format,
symbols,
pkgs_to_prune,
opts.prefix,
opts.no_dedupe,
opts.display_depth,
&mut visited_deps,
&mut levels_continue,
&mut print_stack,
);
}
Ok(())
}
fn print_node<'a>(
ws: &Workspace<'_>,
graph: &'a Graph<'_>,
node_index: usize,
format: &Pattern,
symbols: &Symbols,
pkgs_to_prune: &[PackageIdSpec],
prefix: Prefix,
no_dedupe: bool,
display_depth: DisplayDepth,
visited_deps: &mut HashSet<usize>,
levels_continue: &mut Vec<bool>,
print_stack: &mut Vec<usize>,
) {
let new = no_dedupe || visited_deps.insert(node_index);
match prefix {
Prefix::Depth => drop_print!(ws.gctx(), "{}", levels_continue.len()),
Prefix::Indent => {
if let Some((last_continues, rest)) = levels_continue.split_last() {
for continues in rest {
let c = if *continues { symbols.down } else { " " };
drop_print!(ws.gctx(), "{} ", c);
}
let c = if *last_continues {
symbols.tee
} else {
symbols.ell
};
drop_print!(ws.gctx(), "{0}{1}{1} ", c, symbols.right);
}
}
Prefix::None => {}
}
let in_cycle = print_stack.contains(&node_index);
let has_deps = graph.has_outgoing_edges(node_index);
let star = if (new && !in_cycle) || !has_deps {
""
} else {
" (*)"
};
drop_println!(ws.gctx(), "{}{}", format.display(graph, node_index), star);
if !new || in_cycle {
return;
}
print_stack.push(node_index);
for kind in &[
EdgeKind::Dep(DepKind::Normal),
EdgeKind::Dep(DepKind::Build),
EdgeKind::Dep(DepKind::Development),
EdgeKind::Feature,
] {
print_dependencies(
ws,
graph,
node_index,
format,
symbols,
pkgs_to_prune,
prefix,
no_dedupe,
display_depth,
visited_deps,
levels_continue,
print_stack,
kind,
);
}
print_stack.pop();
}
fn print_dependencies<'a>(
ws: &Workspace<'_>,
graph: &'a Graph<'_>,
node_index: usize,
format: &Pattern,
symbols: &Symbols,
pkgs_to_prune: &[PackageIdSpec],
prefix: Prefix,
no_dedupe: bool,
display_depth: DisplayDepth,
visited_deps: &mut HashSet<usize>,
levels_continue: &mut Vec<bool>,
print_stack: &mut Vec<usize>,
kind: &EdgeKind,
) {
let deps = graph.connected_nodes(node_index, kind);
if deps.is_empty() {
return;
}
let name = match kind {
EdgeKind::Dep(DepKind::Normal) => None,
EdgeKind::Dep(DepKind::Build) => Some("[build-dependencies]"),
EdgeKind::Dep(DepKind::Development) => Some("[dev-dependencies]"),
EdgeKind::Feature => None,
};
if let Prefix::Indent = prefix {
if let Some(name) = name {
for continues in &**levels_continue {
let c = if *continues { symbols.down } else { " " };
drop_print!(ws.gctx(), "{} ", c);
}
drop_println!(ws.gctx(), "{}", name);
}
}
let (max_display_depth, filter_non_workspace_member) = match display_depth {
DisplayDepth::MaxDisplayDepth(max) => (max, false),
DisplayDepth::Workspace => (u32::MAX, true),
};
if levels_continue.len() + 1 > max_display_depth as usize {
return;
}
let mut it = deps
.iter()
.filter(|dep| {
match graph.node(**dep) {
Node::Package { package_id, .. } => {
if filter_non_workspace_member && !ws.is_member_id(*package_id) {
return false;
}
!pkgs_to_prune.iter().any(|spec| spec.matches(*package_id))
}
_ => true,
}
})
.peekable();
while let Some(dependency) = it.next() {
levels_continue.push(it.peek().is_some());
print_node(
ws,
graph,
*dependency,
format,
symbols,
pkgs_to_prune,
prefix,
no_dedupe,
display_depth,
visited_deps,
levels_continue,
print_stack,
);
levels_continue.pop();
}
}