cargo/ops/tree/
mod.rs

1//! Implementation of `cargo tree`.
2
3use 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    /// The packages to display the tree for.
24    pub packages: Packages,
25    /// The platform to filter for.
26    pub target: Target,
27    /// The dependency kinds to display.
28    pub edge_kinds: HashSet<EdgeKind>,
29    pub invert: Vec<String>,
30    /// The packages to prune from the display of the dependency tree.
31    pub pkgs_to_prune: Vec<String>,
32    /// The style of prefix for each line.
33    pub prefix: Prefix,
34    /// If `true`, duplicates will be repeated.
35    /// If `false`, duplicates will be marked with `*`, and their dependencies
36    /// won't be shown.
37    pub no_dedupe: bool,
38    /// If `true`, run in a special mode where it will scan for packages that
39    /// appear with different versions, and report if any where found. Implies
40    /// `invert`.
41    pub duplicates: bool,
42    /// A format string indicating how each package should be displayed.
43    pub format: String,
44    /// Includes features in the tree as separate nodes.
45    pub graph_features: bool,
46    /// Display depth of the dependency tree.
47    /// If non-negative integer, display dependencies with that amount of max depth.
48    /// If `workspace`, display dependencies from current workspace only.
49    pub display_depth: DisplayDepth,
50    /// Excludes proc-macro dependencies.
51    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
138/// Entry point for the `cargo tree` command.
139pub 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    // TODO: Target::All is broken with -Zfeatures=itarget. To handle that properly,
145    // `FeatureResolver` will need to be taught what "all" means.
146    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        // `-d -p foo` will only show duplicates within foo's subtree
205        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    // Packages to prune.
216    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            // Provide an error message if pkgid is not within the resolved
222            // dependencies graph.
223            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
239/// Prints a tree for each given root.
240fn 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    // The visited deps is used to display a (*) whenever a dep has
257    // already been printed (ignored with --no-dedupe).
258    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        // A stack of bools used to determine where | symbols should appear
266        // when printing a line.
267        let mut levels_continue = vec![];
268        // The print stack is used to detect dependency cycles when
269        // --no-dedupe is used. It contains a Node for each level.
270        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
291/// Prints a package and all of its dependencies.
292fn 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    // If this node does not have any outgoing edges, don't include the (*)
330    // since there isn't really anything "deduplicated", and it generally just
331    // adds noise.
332    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
370/// Prints all the dependencies of a package for the given dependency kind.
371fn 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    // Current level exceeds maximum display depth. Skip.
415    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            // Filter out packages to prune.
423            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}