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, Workspace};
use crate::ops::{self, Packages};
use crate::util::{CargoResult, Config};
use crate::{drop_print, drop_println};
use anyhow::Context;
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 charset: Charset,
pub format: String,
pub graph_features: bool,
pub max_display_depth: u32,
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),
}
}
}
pub enum Charset {
Utf8,
Ascii,
}
impl FromStr for Charset {
type Err = &'static str;
fn from_str(s: &str) -> Result<Charset, &'static str> {
match s {
"utf8" => Ok(Charset::Utf8),
"ascii" => Ok(Charset::Ascii),
_ => Err("invalid charset"),
}
}
}
#[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"),
}
}
}
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.config(), &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 max_rust_version = ws.rust_version();
let ws_resolve = ops::resolve_ws_with_opts(
ws,
&mut target_data,
&requested_kinds,
&opts.cli_features,
&specs,
has_dev,
force_all,
max_rust_version,
)?;
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::<CargoResult<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(|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.config().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.config(), opts, root_indexes, &pkgs_to_prune, &graph)?;
}
Ok(())
}
fn print(
config: &Config,
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 = match opts.charset {
Charset::Utf8 => &UTF8_SYMBOLS,
Charset::Ascii => &ASCII_SYMBOLS,
};
let mut visited_deps = HashSet::new();
for (i, root_index) in roots.into_iter().enumerate() {
if i != 0 {
drop_println!(config);
}
let mut levels_continue = vec![];
let mut print_stack = vec![];
print_node(
config,
graph,
root_index,
&format,
symbols,
pkgs_to_prune,
opts.prefix,
opts.no_dedupe,
opts.max_display_depth,
&mut visited_deps,
&mut levels_continue,
&mut print_stack,
);
}
Ok(())
}
fn print_node<'a>(
config: &Config,
graph: &'a Graph<'_>,
node_index: usize,
format: &Pattern,
symbols: &Symbols,
pkgs_to_prune: &[PackageIdSpec],
prefix: Prefix,
no_dedupe: bool,
max_display_depth: u32,
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!(config, "{}", 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!(config, "{} ", c);
}
let c = if *last_continues {
symbols.tee
} else {
symbols.ell
};
drop_print!(config, "{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!(config, "{}{}", 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(
config,
graph,
node_index,
format,
symbols,
pkgs_to_prune,
prefix,
no_dedupe,
max_display_depth,
visited_deps,
levels_continue,
print_stack,
kind,
);
}
print_stack.pop();
}
fn print_dependencies<'a>(
config: &Config,
graph: &'a Graph<'_>,
node_index: usize,
format: &Pattern,
symbols: &Symbols,
pkgs_to_prune: &[PackageIdSpec],
prefix: Prefix,
no_dedupe: bool,
max_display_depth: u32,
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!(config, "{} ", c);
}
drop_println!(config, "{}", name);
}
}
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, .. } => {
!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(
config,
graph,
*dependency,
format,
symbols,
pkgs_to_prune,
prefix,
no_dedupe,
max_display_depth,
visited_deps,
levels_continue,
print_stack,
);
levels_continue.pop();
}
}