summaryrefslogtreecommitdiffstats
path: root/src/cargo/ops/tree/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/cargo/ops/tree/mod.rs')
-rw-r--r--src/cargo/ops/tree/mod.rs451
1 files changed, 451 insertions, 0 deletions
diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs
new file mode 100644
index 0000000..02459f7
--- /dev/null
+++ b/src/cargo/ops/tree/mod.rs
@@ -0,0 +1,451 @@
+//! Implementation of `cargo tree`.
+
+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,
+ /// The packages to display the tree for.
+ pub packages: Packages,
+ /// The platform to filter for.
+ pub target: Target,
+ /// The dependency kinds to display.
+ pub edge_kinds: HashSet<EdgeKind>,
+ pub invert: Vec<String>,
+ /// The packages to prune from the display of the dependency tree.
+ pub pkgs_to_prune: Vec<String>,
+ /// The style of prefix for each line.
+ pub prefix: Prefix,
+ /// If `true`, duplicates will be repeated.
+ /// If `false`, duplicates will be marked with `*`, and their dependencies
+ /// won't be shown.
+ pub no_dedupe: bool,
+ /// If `true`, run in a special mode where it will scan for packages that
+ /// appear with different versions, and report if any where found. Implies
+ /// `invert`.
+ pub duplicates: bool,
+ /// The style of characters to use.
+ pub charset: Charset,
+ /// A format string indicating how each package should be displayed.
+ pub format: String,
+ /// Includes features in the tree as separate nodes.
+ pub graph_features: bool,
+ /// Maximum display depth of the dependency tree.
+ pub max_display_depth: u32,
+ /// Excludes proc-macro dependencies.
+ 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: "-",
+};
+
+/// Entry point for the `cargo tree` command.
+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(),
+ };
+ // TODO: Target::All is broken with -Zfeatures=itarget. To handle that properly,
+ // `FeatureResolver` will need to be taught what "all" means.
+ let requested_kinds = CompileKind::from_requested_targets(ws.config(), &requested_targets)?;
+ let 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 ws_resolve = ops::resolve_ws_with_opts(
+ ws,
+ &target_data,
+ &requested_kinds,
+ &opts.cli_features,
+ &specs,
+ has_dev,
+ force_all,
+ )?;
+
+ 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 {
+ // `-d -p foo` will only show duplicates within foo's subtree
+ graph = graph.from_reachable(root_indexes.as_slice());
+ graph.find_duplicates()
+ } else {
+ root_indexes
+ };
+
+ if !opts.invert.is_empty() || opts.duplicates {
+ graph.invert();
+ }
+
+ // Packages to prune.
+ let pkgs_to_prune = opts
+ .pkgs_to_prune
+ .iter()
+ .map(|p| PackageIdSpec::parse(p))
+ .map(|r| {
+ // Provide an error message if pkgid is not within the resolved
+ // dependencies graph.
+ 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(())
+}
+
+/// Prints a tree for each given root.
+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,
+ };
+
+ // The visited deps is used to display a (*) whenever a dep has
+ // already been printed (ignored with --no-dedupe).
+ let mut visited_deps = HashSet::new();
+
+ for (i, root_index) in roots.into_iter().enumerate() {
+ if i != 0 {
+ drop_println!(config);
+ }
+
+ // A stack of bools used to determine where | symbols should appear
+ // when printing a line.
+ let mut levels_continue = vec![];
+ // The print stack is used to detect dependency cycles when
+ // --no-dedupe is used. It contains a Node for each level.
+ 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,
+ opts.no_proc_macro,
+ &mut visited_deps,
+ &mut levels_continue,
+ &mut print_stack,
+ );
+ }
+
+ Ok(())
+}
+
+/// Prints a package and all of its dependencies.
+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,
+ no_proc_macro: bool,
+ 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);
+ // If this node does not have any outgoing edges, don't include the (*)
+ // since there isn't really anything "deduplicated", and it generally just
+ // adds noise.
+ 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,
+ no_proc_macro,
+ visited_deps,
+ levels_continue,
+ print_stack,
+ kind,
+ );
+ }
+ print_stack.pop();
+}
+
+/// Prints all the dependencies of a package for the given dependency kind.
+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,
+ no_proc_macro: bool,
+ 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);
+ }
+ }
+
+ // Current level exceeds maximum display depth. Skip.
+ if levels_continue.len() + 1 > max_display_depth as usize {
+ return;
+ }
+
+ let mut it = deps
+ .iter()
+ .filter(|dep| {
+ // Filter out proc-macro dependencies.
+ if no_proc_macro {
+ match graph.node(**dep) {
+ &Node::Package { package_id, .. } => {
+ !graph.package_for_id(package_id).proc_macro()
+ }
+ _ => true,
+ }
+ } else {
+ true
+ }
+ })
+ .filter(|dep| {
+ // Filter out packages to prune.
+ 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,
+ no_proc_macro,
+ visited_deps,
+ levels_continue,
+ print_stack,
+ );
+ levels_continue.pop();
+ }
+}