1use self::format::Pattern;
4use crate::core::compiler::{CompileKind, RustcTargetData};
5use crate::core::dependency::DepKind;
6use crate::core::resolver::{features::CliFeatures, ForceAllTargets, HasDevUnits};
7use crate::core::{Package, PackageId, PackageIdSpec, PackageIdSpecQuery, Workspace};
8use crate::ops::{self, Packages};
9use crate::util::CargoResult;
10use crate::{drop_print, drop_println};
11use anyhow::Context as _;
12use graph::Graph;
13use std::collections::{HashMap, HashSet};
14use std::str::FromStr;
15
16mod format;
17mod graph;
18
19pub use {graph::EdgeKind, graph::Node, graph::NodeId};
20
21pub struct TreeOptions {
22 pub cli_features: CliFeatures,
23 pub packages: Packages,
25 pub target: Target,
27 pub edge_kinds: HashSet<EdgeKind>,
29 pub invert: Vec<String>,
30 pub pkgs_to_prune: Vec<String>,
32 pub prefix: Prefix,
34 pub no_dedupe: bool,
38 pub duplicates: bool,
42 pub format: String,
44 pub graph_features: bool,
46 pub display_depth: DisplayDepth,
50 pub no_proc_macro: bool,
52}
53
54#[derive(PartialEq)]
55pub enum Target {
56 Host,
57 Specific(Vec<String>),
58 All,
59}
60
61impl Target {
62 pub fn from_cli(targets: Vec<String>) -> Target {
63 match targets.len() {
64 0 => Target::Host,
65 1 if targets[0] == "all" => Target::All,
66 _ => Target::Specific(targets),
67 }
68 }
69}
70
71#[derive(Clone, Copy)]
72pub enum Prefix {
73 None,
74 Indent,
75 Depth,
76}
77
78impl FromStr for Prefix {
79 type Err = &'static str;
80
81 fn from_str(s: &str) -> Result<Prefix, &'static str> {
82 match s {
83 "none" => Ok(Prefix::None),
84 "indent" => Ok(Prefix::Indent),
85 "depth" => Ok(Prefix::Depth),
86 _ => Err("invalid prefix"),
87 }
88 }
89}
90
91#[derive(Clone, Copy)]
92pub enum DisplayDepth {
93 MaxDisplayDepth(u32),
94 Public,
95 Workspace,
96}
97
98impl FromStr for DisplayDepth {
99 type Err = clap::Error;
100
101 fn from_str(s: &str) -> Result<Self, Self::Err> {
102 match s {
103 "workspace" => Ok(Self::Workspace),
104 "public" => Ok(Self::Public),
105 s => s.parse().map(Self::MaxDisplayDepth).map_err(|_| {
106 clap::Error::raw(
107 clap::error::ErrorKind::ValueValidation,
108 format!(
109 "supported values for --depth are non-negative integers and `workspace`, \
110 but `{}` is unknown",
111 s
112 ),
113 )
114 }),
115 }
116 }
117}
118
119struct Symbols {
120 down: &'static str,
121 tee: &'static str,
122 ell: &'static str,
123 right: &'static str,
124}
125
126static UTF8_SYMBOLS: Symbols = Symbols {
127 down: "│",
128 tee: "├",
129 ell: "└",
130 right: "─",
131};
132
133static ASCII_SYMBOLS: Symbols = Symbols {
134 down: "|",
135 tee: "|",
136 ell: "`",
137 right: "-",
138};
139
140pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<()> {
142 let requested_targets = match &opts.target {
143 Target::All | Target::Host => Vec::new(),
144 Target::Specific(t) => t.clone(),
145 };
146 let requested_kinds = CompileKind::from_requested_targets(ws.gctx(), &requested_targets)?;
149 let mut target_data = RustcTargetData::new(ws, &requested_kinds)?;
150 let specs = opts.packages.to_package_id_specs(ws)?;
151 let has_dev = if opts
152 .edge_kinds
153 .contains(&EdgeKind::Dep(DepKind::Development))
154 {
155 HasDevUnits::Yes
156 } else {
157 HasDevUnits::No
158 };
159 let force_all = if opts.target == Target::All {
160 ForceAllTargets::Yes
161 } else {
162 ForceAllTargets::No
163 };
164 let dry_run = false;
165 let ws_resolve = ops::resolve_ws_with_opts(
166 ws,
167 &mut target_data,
168 &requested_kinds,
169 &opts.cli_features,
170 &specs,
171 has_dev,
172 force_all,
173 dry_run,
174 )?;
175
176 let package_map: HashMap<PackageId, &Package> = ws_resolve
177 .pkg_set
178 .packages()
179 .map(|pkg| (pkg.package_id(), pkg))
180 .collect();
181
182 let mut graph = graph::build(
183 ws,
184 &ws_resolve.targeted_resolve,
185 &ws_resolve.resolved_features,
186 &specs,
187 &opts.cli_features,
188 &target_data,
189 &requested_kinds,
190 package_map,
191 opts,
192 )?;
193
194 let root_specs = if opts.invert.is_empty() {
195 specs
196 } else {
197 opts.invert
198 .iter()
199 .map(|p| PackageIdSpec::parse(p))
200 .collect::<Result<Vec<PackageIdSpec>, _>>()?
201 };
202 let root_ids = ws_resolve.targeted_resolve.specs_to_ids(&root_specs)?;
203 let root_indexes = graph.indexes_from_ids(&root_ids);
204
205 let root_indexes = if opts.duplicates {
206 graph = graph.from_reachable(root_indexes.as_slice());
208 graph.find_duplicates()
209 } else {
210 root_indexes
211 };
212
213 if !opts.invert.is_empty() || opts.duplicates {
214 graph.invert();
215 }
216
217 let pkgs_to_prune = opts
219 .pkgs_to_prune
220 .iter()
221 .map(|p| PackageIdSpec::parse(p).map_err(Into::into))
222 .map(|r| {
223 r.and_then(|spec| spec.query(ws_resolve.targeted_resolve.iter()).and(Ok(spec)))
226 })
227 .collect::<CargoResult<Vec<PackageIdSpec>>>()?;
228
229 if root_indexes.len() == 0 {
230 ws.gctx().shell().warn(
231 "nothing to print.\n\n\
232 To find dependencies that require specific target platforms, \
233 try to use option `--target all` first, and then narrow your search scope accordingly.",
234 )?;
235 } else {
236 print(ws, opts, root_indexes, &pkgs_to_prune, &graph)?;
237 }
238 Ok(())
239}
240
241fn print(
243 ws: &Workspace<'_>,
244 opts: &TreeOptions,
245 roots: Vec<NodeId>,
246 pkgs_to_prune: &[PackageIdSpec],
247 graph: &Graph<'_>,
248) -> CargoResult<()> {
249 let format = Pattern::new(&opts.format)
250 .with_context(|| format!("tree format `{}` not valid", opts.format))?;
251
252 let symbols = if ws.gctx().shell().out_unicode() {
253 &UTF8_SYMBOLS
254 } else {
255 &ASCII_SYMBOLS
256 };
257
258 let mut visited_deps = HashSet::new();
261
262 for (i, root_index) in roots.into_iter().enumerate() {
263 if i != 0 {
264 drop_println!(ws.gctx());
265 }
266
267 let mut levels_continue = vec![];
270 let mut print_stack = vec![];
273
274 print_node(
275 ws,
276 graph,
277 root_index,
278 &format,
279 symbols,
280 pkgs_to_prune,
281 opts.prefix,
282 opts.no_dedupe,
283 opts.display_depth,
284 &mut visited_deps,
285 &mut levels_continue,
286 &mut print_stack,
287 )?;
288 }
289
290 Ok(())
291}
292
293fn print_node<'a>(
295 ws: &Workspace<'_>,
296 graph: &'a Graph<'_>,
297 node_index: NodeId,
298 format: &Pattern,
299 symbols: &Symbols,
300 pkgs_to_prune: &[PackageIdSpec],
301 prefix: Prefix,
302 no_dedupe: bool,
303 display_depth: DisplayDepth,
304 visited_deps: &mut HashSet<NodeId>,
305 levels_continue: &mut Vec<(anstyle::Style, bool)>,
306 print_stack: &mut Vec<NodeId>,
307) -> CargoResult<()> {
308 let new = no_dedupe || visited_deps.insert(node_index);
309
310 match prefix {
311 Prefix::Depth => drop_print!(ws.gctx(), "{}", levels_continue.len()),
312 Prefix::Indent => {
313 if let Some(((last_style, last_continues), rest)) = levels_continue.split_last() {
314 for (style, continues) in rest {
315 let c = if *continues { symbols.down } else { " " };
316 drop_print!(ws.gctx(), "{style}{c}{style:#} ");
317 }
318
319 let c = if *last_continues {
320 symbols.tee
321 } else {
322 symbols.ell
323 };
324 drop_print!(
325 ws.gctx(),
326 "{last_style}{0}{1}{1}{last_style:#} ",
327 c,
328 symbols.right
329 );
330 }
331 }
332 Prefix::None => {}
333 }
334
335 let in_cycle = print_stack.contains(&node_index);
336 let has_deps = graph.has_outgoing_edges(node_index);
340 let star = if (new && !in_cycle) || !has_deps {
341 ""
342 } else {
343 color_print::cstr!(" <yellow,dim>(*)</>")
344 };
345 drop_println!(ws.gctx(), "{}{}", format.display(graph, node_index), star);
346
347 if !new || in_cycle {
348 return Ok(());
349 }
350 print_stack.push(node_index);
351
352 for kind in &[
353 EdgeKind::Dep(DepKind::Normal),
354 EdgeKind::Dep(DepKind::Build),
355 EdgeKind::Dep(DepKind::Development),
356 EdgeKind::Feature,
357 ] {
358 print_dependencies(
359 ws,
360 graph,
361 node_index,
362 format,
363 symbols,
364 pkgs_to_prune,
365 prefix,
366 no_dedupe,
367 display_depth,
368 visited_deps,
369 levels_continue,
370 print_stack,
371 kind,
372 )?;
373 }
374 print_stack.pop();
375
376 Ok(())
377}
378
379fn print_dependencies<'a>(
381 ws: &Workspace<'_>,
382 graph: &'a Graph<'_>,
383 node_index: NodeId,
384 format: &Pattern,
385 symbols: &Symbols,
386 pkgs_to_prune: &[PackageIdSpec],
387 prefix: Prefix,
388 no_dedupe: bool,
389 display_depth: DisplayDepth,
390 visited_deps: &mut HashSet<NodeId>,
391 levels_continue: &mut Vec<(anstyle::Style, bool)>,
392 print_stack: &mut Vec<NodeId>,
393 kind: &EdgeKind,
394) -> CargoResult<()> {
395 let deps = graph.edges_of_kind(node_index, kind);
396 if deps.is_empty() {
397 return Ok(());
398 }
399
400 let name = match kind {
401 EdgeKind::Dep(DepKind::Normal) => None,
402 EdgeKind::Dep(DepKind::Build) => {
403 Some(color_print::cstr!("<blue,bold>[build-dependencies]</>"))
404 }
405 EdgeKind::Dep(DepKind::Development) => {
406 Some(color_print::cstr!("<cyan,bold>[dev-dependencies]</>"))
407 }
408 EdgeKind::Feature => None,
409 };
410
411 if let Prefix::Indent = prefix {
412 if let Some(name) = name {
413 for (style, continues) in &**levels_continue {
414 let c = if *continues { symbols.down } else { " " };
415 drop_print!(ws.gctx(), "{style}{c}{style:#} ");
416 }
417
418 drop_println!(ws.gctx(), "{name}");
419 }
420 }
421
422 let (max_display_depth, filter_non_workspace_member, filter_private) = match display_depth {
423 DisplayDepth::MaxDisplayDepth(max) => (max, false, false),
424 DisplayDepth::Workspace => (u32::MAX, true, false),
425 DisplayDepth::Public => {
426 if !ws.gctx().cli_unstable().unstable_options {
427 anyhow::bail!("`--depth public` requires `-Zunstable-options`")
428 }
429 (u32::MAX, false, true)
430 }
431 };
432
433 if levels_continue.len() + 1 > max_display_depth as usize {
435 return Ok(());
436 }
437
438 let mut it = deps
439 .iter()
440 .filter(|dep| {
441 match graph.node(dep.node()) {
443 Node::Package { package_id, .. } => {
444 if filter_non_workspace_member && !ws.is_member_id(*package_id) {
445 return false;
446 }
447 if filter_private && !dep.public() {
448 return false;
449 }
450 !pkgs_to_prune.iter().any(|spec| spec.matches(*package_id))
451 }
452 Node::Feature { .. } => {
453 if filter_private && !dep.public() {
454 return false;
455 }
456 true
457 }
458 }
459 })
460 .peekable();
461
462 while let Some(dependency) = it.next() {
463 let style = edge_line_color(dependency.kind());
464 levels_continue.push((style, it.peek().is_some()));
465 print_node(
466 ws,
467 graph,
468 dependency.node(),
469 format,
470 symbols,
471 pkgs_to_prune,
472 prefix,
473 no_dedupe,
474 display_depth,
475 visited_deps,
476 levels_continue,
477 print_stack,
478 )?;
479 levels_continue.pop();
480 }
481
482 Ok(())
483}
484
485fn edge_line_color(kind: EdgeKind) -> anstyle::Style {
486 match kind {
487 EdgeKind::Dep(DepKind::Normal) => anstyle::Style::new() | anstyle::Effects::DIMMED,
488 EdgeKind::Dep(DepKind::Build) => {
489 anstyle::AnsiColor::Blue.on_default() | anstyle::Effects::BOLD
490 }
491 EdgeKind::Dep(DepKind::Development) => {
492 anstyle::AnsiColor::Cyan.on_default() | anstyle::Effects::BOLD
493 }
494 EdgeKind::Feature => anstyle::AnsiColor::Magenta.on_default() | anstyle::Effects::DIMMED,
495 }
496}