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};
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 Workspace,
95}
96
97impl FromStr for DisplayDepth {
98 type Err = clap::Error;
99
100 fn from_str(s: &str) -> Result<Self, Self::Err> {
101 match s {
102 "workspace" => Ok(Self::Workspace),
103 s => s.parse().map(Self::MaxDisplayDepth).map_err(|_| {
104 clap::Error::raw(
105 clap::error::ErrorKind::ValueValidation,
106 format!(
107 "supported values for --depth are non-negative integers and `workspace`, \
108 but `{}` is unknown",
109 s
110 ),
111 )
112 }),
113 }
114 }
115}
116
117struct Symbols {
118 down: &'static str,
119 tee: &'static str,
120 ell: &'static str,
121 right: &'static str,
122}
123
124static UTF8_SYMBOLS: Symbols = Symbols {
125 down: "│",
126 tee: "├",
127 ell: "└",
128 right: "─",
129};
130
131static ASCII_SYMBOLS: Symbols = Symbols {
132 down: "|",
133 tee: "|",
134 ell: "`",
135 right: "-",
136};
137
138pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<()> {
140 let requested_targets = match &opts.target {
141 Target::All | Target::Host => Vec::new(),
142 Target::Specific(t) => t.clone(),
143 };
144 let requested_kinds = CompileKind::from_requested_targets(ws.gctx(), &requested_targets)?;
147 let mut target_data = RustcTargetData::new(ws, &requested_kinds)?;
148 let specs = opts.packages.to_package_id_specs(ws)?;
149 let has_dev = if opts
150 .edge_kinds
151 .contains(&EdgeKind::Dep(DepKind::Development))
152 {
153 HasDevUnits::Yes
154 } else {
155 HasDevUnits::No
156 };
157 let force_all = if opts.target == Target::All {
158 ForceAllTargets::Yes
159 } else {
160 ForceAllTargets::No
161 };
162 let dry_run = false;
163 let ws_resolve = ops::resolve_ws_with_opts(
164 ws,
165 &mut target_data,
166 &requested_kinds,
167 &opts.cli_features,
168 &specs,
169 has_dev,
170 force_all,
171 dry_run,
172 )?;
173
174 let package_map: HashMap<PackageId, &Package> = ws_resolve
175 .pkg_set
176 .packages()
177 .map(|pkg| (pkg.package_id(), pkg))
178 .collect();
179
180 let mut graph = graph::build(
181 ws,
182 &ws_resolve.targeted_resolve,
183 &ws_resolve.resolved_features,
184 &specs,
185 &opts.cli_features,
186 &target_data,
187 &requested_kinds,
188 package_map,
189 opts,
190 )?;
191
192 let root_specs = if opts.invert.is_empty() {
193 specs
194 } else {
195 opts.invert
196 .iter()
197 .map(|p| PackageIdSpec::parse(p))
198 .collect::<Result<Vec<PackageIdSpec>, _>>()?
199 };
200 let root_ids = ws_resolve.targeted_resolve.specs_to_ids(&root_specs)?;
201 let root_indexes = graph.indexes_from_ids(&root_ids);
202
203 let root_indexes = if opts.duplicates {
204 graph = graph.from_reachable(root_indexes.as_slice());
206 graph.find_duplicates()
207 } else {
208 root_indexes
209 };
210
211 if !opts.invert.is_empty() || opts.duplicates {
212 graph.invert();
213 }
214
215 let pkgs_to_prune = opts
217 .pkgs_to_prune
218 .iter()
219 .map(|p| PackageIdSpec::parse(p).map_err(Into::into))
220 .map(|r| {
221 r.and_then(|spec| spec.query(ws_resolve.targeted_resolve.iter()).and(Ok(spec)))
224 })
225 .collect::<CargoResult<Vec<PackageIdSpec>>>()?;
226
227 if root_indexes.len() == 0 {
228 ws.gctx().shell().warn(
229 "nothing to print.\n\n\
230 To find dependencies that require specific target platforms, \
231 try to use option `--target all` first, and then narrow your search scope accordingly.",
232 )?;
233 } else {
234 print(ws, opts, root_indexes, &pkgs_to_prune, &graph)?;
235 }
236 Ok(())
237}
238
239fn print(
241 ws: &Workspace<'_>,
242 opts: &TreeOptions,
243 roots: Vec<usize>,
244 pkgs_to_prune: &[PackageIdSpec],
245 graph: &Graph<'_>,
246) -> CargoResult<()> {
247 let format = Pattern::new(&opts.format)
248 .with_context(|| format!("tree format `{}` not valid", opts.format))?;
249
250 let symbols = if ws.gctx().shell().out_unicode() {
251 &UTF8_SYMBOLS
252 } else {
253 &ASCII_SYMBOLS
254 };
255
256 let mut visited_deps = HashSet::new();
259
260 for (i, root_index) in roots.into_iter().enumerate() {
261 if i != 0 {
262 drop_println!(ws.gctx());
263 }
264
265 let mut levels_continue = vec![];
268 let mut print_stack = vec![];
271
272 print_node(
273 ws,
274 graph,
275 root_index,
276 &format,
277 symbols,
278 pkgs_to_prune,
279 opts.prefix,
280 opts.no_dedupe,
281 opts.display_depth,
282 &mut visited_deps,
283 &mut levels_continue,
284 &mut print_stack,
285 );
286 }
287
288 Ok(())
289}
290
291fn print_node<'a>(
293 ws: &Workspace<'_>,
294 graph: &'a Graph<'_>,
295 node_index: usize,
296 format: &Pattern,
297 symbols: &Symbols,
298 pkgs_to_prune: &[PackageIdSpec],
299 prefix: Prefix,
300 no_dedupe: bool,
301 display_depth: DisplayDepth,
302 visited_deps: &mut HashSet<usize>,
303 levels_continue: &mut Vec<bool>,
304 print_stack: &mut Vec<usize>,
305) {
306 let new = no_dedupe || visited_deps.insert(node_index);
307
308 match prefix {
309 Prefix::Depth => drop_print!(ws.gctx(), "{}", levels_continue.len()),
310 Prefix::Indent => {
311 if let Some((last_continues, rest)) = levels_continue.split_last() {
312 for continues in rest {
313 let c = if *continues { symbols.down } else { " " };
314 drop_print!(ws.gctx(), "{} ", c);
315 }
316
317 let c = if *last_continues {
318 symbols.tee
319 } else {
320 symbols.ell
321 };
322 drop_print!(ws.gctx(), "{0}{1}{1} ", c, symbols.right);
323 }
324 }
325 Prefix::None => {}
326 }
327
328 let in_cycle = print_stack.contains(&node_index);
329 let has_deps = graph.has_outgoing_edges(node_index);
333 let star = if (new && !in_cycle) || !has_deps {
334 ""
335 } else {
336 " (*)"
337 };
338 drop_println!(ws.gctx(), "{}{}", format.display(graph, node_index), star);
339
340 if !new || in_cycle {
341 return;
342 }
343 print_stack.push(node_index);
344
345 for kind in &[
346 EdgeKind::Dep(DepKind::Normal),
347 EdgeKind::Dep(DepKind::Build),
348 EdgeKind::Dep(DepKind::Development),
349 EdgeKind::Feature,
350 ] {
351 print_dependencies(
352 ws,
353 graph,
354 node_index,
355 format,
356 symbols,
357 pkgs_to_prune,
358 prefix,
359 no_dedupe,
360 display_depth,
361 visited_deps,
362 levels_continue,
363 print_stack,
364 kind,
365 );
366 }
367 print_stack.pop();
368}
369
370fn print_dependencies<'a>(
372 ws: &Workspace<'_>,
373 graph: &'a Graph<'_>,
374 node_index: usize,
375 format: &Pattern,
376 symbols: &Symbols,
377 pkgs_to_prune: &[PackageIdSpec],
378 prefix: Prefix,
379 no_dedupe: bool,
380 display_depth: DisplayDepth,
381 visited_deps: &mut HashSet<usize>,
382 levels_continue: &mut Vec<bool>,
383 print_stack: &mut Vec<usize>,
384 kind: &EdgeKind,
385) {
386 let deps = graph.connected_nodes(node_index, kind);
387 if deps.is_empty() {
388 return;
389 }
390
391 let name = match kind {
392 EdgeKind::Dep(DepKind::Normal) => None,
393 EdgeKind::Dep(DepKind::Build) => Some("[build-dependencies]"),
394 EdgeKind::Dep(DepKind::Development) => Some("[dev-dependencies]"),
395 EdgeKind::Feature => None,
396 };
397
398 if let Prefix::Indent = prefix {
399 if let Some(name) = name {
400 for continues in &**levels_continue {
401 let c = if *continues { symbols.down } else { " " };
402 drop_print!(ws.gctx(), "{} ", c);
403 }
404
405 drop_println!(ws.gctx(), "{}", name);
406 }
407 }
408
409 let (max_display_depth, filter_non_workspace_member) = match display_depth {
410 DisplayDepth::MaxDisplayDepth(max) => (max, false),
411 DisplayDepth::Workspace => (u32::MAX, true),
412 };
413
414 if levels_continue.len() + 1 > max_display_depth as usize {
416 return;
417 }
418
419 let mut it = deps
420 .iter()
421 .filter(|dep| {
422 match graph.node(**dep) {
424 Node::Package { package_id, .. } => {
425 if filter_non_workspace_member && !ws.is_member_id(*package_id) {
426 return false;
427 }
428 !pkgs_to_prune.iter().any(|spec| spec.matches(*package_id))
429 }
430 _ => true,
431 }
432 })
433 .peekable();
434
435 while let Some(dependency) = it.next() {
436 levels_continue.push(it.peek().is_some());
437 print_node(
438 ws,
439 graph,
440 *dependency,
441 format,
442 symbols,
443 pkgs_to_prune,
444 prefix,
445 no_dedupe,
446 display_depth,
447 visited_deps,
448 levels_continue,
449 print_stack,
450 );
451 levels_continue.pop();
452 }
453}