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