From 96a393719b855989ff63eaa563884d4fc44d8eff Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 19 Mar 2020 10:51:25 -0700 Subject: [PATCH 01/14] Add `cargo tree` command. --- src/bin/cargo/commands/mod.rs | 3 + src/bin/cargo/commands/tree.rs | 84 ++ src/cargo/core/compiler/build_config.rs | 20 +- src/cargo/core/compiler/compile_kind.rs | 27 + src/cargo/core/profiles.rs | 6 +- src/cargo/core/resolver/features.rs | 12 +- src/cargo/core/resolver/resolve.rs | 4 + src/cargo/ops/cargo_compile.rs | 22 +- src/cargo/ops/cargo_doc.rs | 5 +- src/cargo/ops/mod.rs | 1 + src/cargo/ops/tree/format/mod.rs | 108 ++ src/cargo/ops/tree/format/parse.rs | 97 ++ src/cargo/ops/tree/graph.rs | 562 +++++++++ src/cargo/ops/tree/mod.rs | 333 +++++ src/cargo/util/command_prelude.rs | 31 +- src/doc/man/cargo-tree.adoc | 193 +++ src/doc/man/cargo.adoc | 3 + src/doc/man/generated/cargo-tree.html | 443 +++++++ src/doc/man/generated/cargo.html | 4 + src/doc/man/options-packages.adoc | 2 + src/doc/src/SUMMARY.md | 1 + src/doc/src/commands/cargo-tree.md | 3 + src/doc/src/commands/manifest-commands.md | 1 + src/etc/_cargo | 15 + src/etc/cargo.bashcomp.sh | 1 + src/etc/man/cargo-tree.1 | 491 ++++++++ src/etc/man/cargo.1 | 9 +- tests/testsuite/main.rs | 2 + tests/testsuite/tree.rs | 1371 +++++++++++++++++++++ tests/testsuite/tree_graph_features.rs | 364 ++++++ 30 files changed, 4165 insertions(+), 53 deletions(-) create mode 100644 src/bin/cargo/commands/tree.rs create mode 100644 src/cargo/ops/tree/format/mod.rs create mode 100644 src/cargo/ops/tree/format/parse.rs create mode 100644 src/cargo/ops/tree/graph.rs create mode 100644 src/cargo/ops/tree/mod.rs create mode 100644 src/doc/man/cargo-tree.adoc create mode 100644 src/doc/man/generated/cargo-tree.html create mode 100644 src/doc/src/commands/cargo-tree.md create mode 100644 src/etc/man/cargo-tree.1 create mode 100644 tests/testsuite/tree.rs create mode 100644 tests/testsuite/tree_graph_features.rs diff --git a/src/bin/cargo/commands/mod.rs b/src/bin/cargo/commands/mod.rs index 7d4fe68c6e2..2c118f7eb54 100644 --- a/src/bin/cargo/commands/mod.rs +++ b/src/bin/cargo/commands/mod.rs @@ -27,6 +27,7 @@ pub fn builtin() -> Vec { rustdoc::cli(), search::cli(), test::cli(), + tree::cli(), uninstall::cli(), update::cli(), vendor::cli(), @@ -63,6 +64,7 @@ pub fn builtin_exec(cmd: &str) -> Option) -> Cli "rustdoc" => rustdoc::exec, "search" => search::exec, "test" => test::exec, + "tree" => tree::exec, "uninstall" => uninstall::exec, "update" => update::exec, "vendor" => vendor::exec, @@ -99,6 +101,7 @@ pub mod rustc; pub mod rustdoc; pub mod search; pub mod test; +pub mod tree; pub mod uninstall; pub mod update; pub mod vendor; diff --git a/src/bin/cargo/commands/tree.rs b/src/bin/cargo/commands/tree.rs new file mode 100644 index 00000000000..b37a7787622 --- /dev/null +++ b/src/bin/cargo/commands/tree.rs @@ -0,0 +1,84 @@ +use crate::command_prelude::*; +use cargo::ops::tree; +use std::str::FromStr; + +pub fn cli() -> App { + subcommand("tree") + .about("Display a tree visualization of a dependency graph") + .arg(opt("quiet", "Suppress status messages").short("q")) + .arg_manifest_path() + .arg_package_spec_no_all( + "Package to be used as the root of the tree", + "Display the tree for all packages in the workspace", + "Exclude specific workspace members", + ) + .arg_features() + .arg_target_triple( + "Filter dependencies matching the given target-triple (default host platform)", + ) + .arg(opt( + "no-filter-targets", + "Return dependencies for all targets", + )) + .arg(opt("no-dev-dependencies", "Skip dev dependencies")) + .arg(opt("invert", "Invert the tree direction").short("i")) + .arg(opt( + "no-indent", + "Display the dependencies as a list (rather than a tree)", + )) + .arg(opt( + "prefix-depth", + "Display the dependencies as a list (rather than a tree), but prefixed with the depth", + )) + .arg(opt( + "no-dedupe", + "Do not de-duplicate (repeats all shared dependencies)", + )) + .arg( + opt( + "duplicates", + "Show only dependencies which come in multiple versions (implies -i)", + ) + .short("d") + .alias("duplicate"), + ) + .arg( + opt("charset", "Character set to use in output: utf8, ascii") + .value_name("CHARSET") + .possible_values(&["utf8", "ascii"]) + .default_value("utf8"), + ) + .arg( + opt("format", "Format string used for printing dependencies") + .value_name("FORMAT") + .short("f") + .default_value("{p}"), + ) + .arg(opt("graph-features", "Include features in the tree")) +} + +pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { + let ws = args.workspace(config)?; + let charset = tree::Charset::from_str(args.value_of("charset").unwrap()) + .map_err(|e| anyhow::anyhow!("{}", e))?; + let opts = tree::TreeOptions { + features: values(args, "features"), + all_features: args.is_present("all-features"), + no_default_features: args.is_present("no-default-features"), + packages: args.packages_from_flags()?, + target: args.target(), + no_filter_targets: args.is_present("no-filter-targets"), + no_dev_dependencies: args.is_present("no-dev-dependencies"), + invert: args.is_present("invert"), + no_indent: args.is_present("no-indent"), + prefix_depth: args.is_present("prefix-depth"), + no_dedupe: args.is_present("no-dedupe"), + duplicates: args.is_present("duplicates"), + charset, + format: args.value_of("format").unwrap().to_string(), + graph_features: args.is_present("graph-features"), + }; + + tree::build_and_print(&ws, &opts)?; + Ok(()) +} diff --git a/src/cargo/core/compiler/build_config.rs b/src/cargo/core/compiler/build_config.rs index 8df5db2f71f..77b7810a594 100644 --- a/src/cargo/core/compiler/build_config.rs +++ b/src/cargo/core/compiler/build_config.rs @@ -1,4 +1,4 @@ -use crate::core::compiler::{CompileKind, CompileTarget}; +use crate::core::compiler::CompileKind; use crate::core::interning::InternedString; use crate::util::ProcessBuilder; use crate::util::{CargoResult, Config, RustfixDiagnosticServer}; @@ -45,22 +45,8 @@ impl BuildConfig { mode: CompileMode, ) -> CargoResult { let cfg = config.build_config()?; - let requested_kind = match requested_target { - Some(s) => CompileKind::Target(CompileTarget::new(s)?), - None => match &cfg.target { - Some(val) => { - let value = if val.raw_value().ends_with(".json") { - let path = val.clone().resolve_path(config); - path.to_str().expect("must be utf-8 in toml").to_string() - } else { - val.raw_value().to_string() - }; - CompileKind::Target(CompileTarget::new(&value)?) - } - None => CompileKind::Host, - }, - }; - + let requested_kind = + CompileKind::from_requested_target(config, requested_target.as_deref())?; if jobs == Some(0) { anyhow::bail!("jobs must be at least 1") } diff --git a/src/cargo/core/compiler/compile_kind.rs b/src/cargo/core/compiler/compile_kind.rs index 4ef30039da7..fd5537b4abb 100644 --- a/src/cargo/core/compiler/compile_kind.rs +++ b/src/cargo/core/compiler/compile_kind.rs @@ -1,5 +1,6 @@ use crate::core::{InternedString, Target}; use crate::util::errors::{CargoResult, CargoResultExt}; +use crate::util::Config; use serde::Serialize; use std::path::Path; @@ -39,6 +40,32 @@ impl CompileKind { CompileKind::Target(n) => CompileKind::Target(n), } } + + /// Creates a new `CompileKind` based on the requested target. + /// + /// If no target is given, this consults the config if the default is set. + /// Otherwise returns `CompileKind::Host`. + pub fn from_requested_target( + config: &Config, + target: Option<&str>, + ) -> CargoResult { + let kind = match target { + Some(s) => CompileKind::Target(CompileTarget::new(s)?), + None => match &config.build_config()?.target { + Some(val) => { + let value = if val.raw_value().ends_with(".json") { + let path = val.clone().resolve_path(config); + path.to_str().expect("must be utf-8 in toml").to_string() + } else { + val.raw_value().to_string() + }; + CompileKind::Target(CompileTarget::new(&value)?) + } + None => CompileKind::Host, + }, + }; + Ok(kind) + } } impl serde::ser::Serialize for CompileKind { diff --git a/src/cargo/core/profiles.rs b/src/cargo/core/profiles.rs index 30a612bf95f..6083842cf99 100644 --- a/src/cargo/core/profiles.rs +++ b/src/cargo/core/profiles.rs @@ -998,11 +998,7 @@ impl UnitFor { } pub(crate) fn map_to_features_for(&self) -> FeaturesFor { - if self.is_for_host_features() { - FeaturesFor::HostDep - } else { - FeaturesFor::NormalOrDev - } + FeaturesFor::from_for_host(self.is_for_host_features()) } } diff --git a/src/cargo/core/resolver/features.rs b/src/cargo/core/resolver/features.rs index 7c16010ab9c..bccb6f4d086 100644 --- a/src/cargo/core/resolver/features.rs +++ b/src/cargo/core/resolver/features.rs @@ -92,13 +92,23 @@ pub enum HasDevUnits { } /// Flag to indicate if features are requested for a build dependency or not. -#[derive(Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum FeaturesFor { NormalOrDev, /// Build dependency or proc-macro. HostDep, } +impl FeaturesFor { + pub fn from_for_host(for_host: bool) -> FeaturesFor { + if for_host { + FeaturesFor::HostDep + } else { + FeaturesFor::NormalOrDev + } + } +} + impl FeatureOpts { fn new(config: &Config, has_dev_units: HasDevUnits) -> CargoResult { let mut opts = FeatureOpts::default(); diff --git a/src/cargo/core/resolver/resolve.rs b/src/cargo/core/resolver/resolve.rs index 1b823dc7b9f..2d442c0214a 100644 --- a/src/cargo/core/resolver/resolve.rs +++ b/src/cargo/core/resolver/resolve.rs @@ -305,6 +305,10 @@ unable to verify that `{0}` is the same as when the lockfile was generated PackageIdSpec::query_str(spec, self.iter()) } + pub fn specs_to_ids(&self, specs: &[PackageIdSpec]) -> CargoResult> { + specs.iter().map(|s| s.query(self.iter())).collect() + } + pub fn unused_patches(&self) -> &[PackageId] { &self.unused_patches } diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 332c83831c0..85786aebd67 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -351,12 +351,8 @@ pub fn compile_ws<'a>( // Find the packages in the resolver that the user wants to build (those // passed in with `-p` or the defaults from the workspace), and convert - // Vec to a Vec<&PackageId>. - let to_build_ids = specs - .iter() - .map(|s| s.query(resolve.iter())) - .collect::>>()?; - + // Vec to a Vec. + let to_build_ids = resolve.specs_to_ids(&specs)?; // Now get the `Package` for each `PackageId`. This may trigger a download // if the user specified `-p` for a dependency that is not downloaded. // Dependencies will be downloaded during build_unit_dependencies. @@ -753,12 +749,8 @@ fn generate_targets<'a>( bcx.profiles .get_profile(pkg.package_id(), ws.is_member(pkg), unit_for, target_mode); - let features_for = if target.proc_macro() { - FeaturesFor::HostDep - } else { - // Root units are never build dependencies. - FeaturesFor::NormalOrDev - }; + // No need to worry about build-dependencies, roots are never build dependencies. + let features_for = FeaturesFor::from_for_host(target.proc_macro()); let features = Vec::from(resolved_features.activated_features(pkg.package_id(), features_for)); bcx.units.intern( @@ -969,11 +961,7 @@ pub fn resolve_all_features( .expect("packages downloaded") .proc_macro(); for dep in deps { - let features_for = if is_proc_macro || dep.is_build() { - FeaturesFor::HostDep - } else { - FeaturesFor::NormalOrDev - }; + let features_for = FeaturesFor::from_for_host(is_proc_macro || dep.is_build()); for feature in resolved_features.activated_features_unverified(dep_id, features_for) { features.insert(format!("{}/{}", dep.name_in_toml(), feature)); } diff --git a/src/cargo/ops/cargo_doc.rs b/src/cargo/ops/cargo_doc.rs index 07b3eabdf6d..85b2e0909bd 100644 --- a/src/cargo/ops/cargo_doc.rs +++ b/src/cargo/ops/cargo_doc.rs @@ -36,10 +36,7 @@ pub fn doc(ws: &Workspace<'_>, options: &DocOptions) -> CargoResult<()> { HasDevUnits::No, )?; - let ids = specs - .iter() - .map(|s| s.query(ws_resolve.targeted_resolve.iter())) - .collect::>>()?; + let ids = ws_resolve.targeted_resolve.specs_to_ids(&specs)?; let pkgs = ws_resolve.pkg_set.get_many(ids)?; let mut lib_names = HashMap::new(); diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index e4bc419e3a0..eed36c7867f 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -48,4 +48,5 @@ mod fix; mod lockfile; mod registry; mod resolve; +pub mod tree; mod vendor; diff --git a/src/cargo/ops/tree/format/mod.rs b/src/cargo/ops/tree/format/mod.rs new file mode 100644 index 00000000000..1d835e961cc --- /dev/null +++ b/src/cargo/ops/tree/format/mod.rs @@ -0,0 +1,108 @@ +use self::parse::{Parser, RawChunk}; +use super::{Graph, Node}; +use anyhow::{anyhow, Error}; +use std::fmt; + +mod parse; + +enum Chunk { + Raw(String), + Package, + License, + Repository, + Features, +} + +pub struct Pattern(Vec); + +impl Pattern { + pub fn new(format: &str) -> Result { + let mut chunks = vec![]; + + for raw in Parser::new(format) { + let chunk = match raw { + RawChunk::Text(text) => Chunk::Raw(text.to_owned()), + RawChunk::Argument("p") => Chunk::Package, + RawChunk::Argument("l") => Chunk::License, + RawChunk::Argument("r") => Chunk::Repository, + RawChunk::Argument("f") => Chunk::Features, + RawChunk::Argument(a) => { + return Err(anyhow!("unsupported pattern `{}`", a)); + } + RawChunk::Error(err) => return Err(anyhow!("{}", err)), + }; + chunks.push(chunk); + } + + Ok(Pattern(chunks)) + } + + pub fn display<'a>(&'a self, graph: &'a Graph<'a>, node_index: usize) -> Display<'a> { + Display { + pattern: self, + graph, + node_index, + } + } +} + +pub struct Display<'a> { + pattern: &'a Pattern, + graph: &'a Graph<'a>, + node_index: usize, +} + +impl<'a> fmt::Display for Display<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let node = self.graph.node(self.node_index); + match node { + Node::Package { + package_id, + features, + .. + } => { + let package = self.graph.package_for_id(*package_id); + for chunk in &self.pattern.0 { + match *chunk { + Chunk::Raw(ref s) => fmt.write_str(s)?, + Chunk::Package => { + write!(fmt, "{} v{}", package.name(), package.version())?; + + let source_id = package.package_id().source_id(); + if !source_id.is_default_registry() { + write!(fmt, " ({})", source_id)?; + } + } + Chunk::License => { + if let Some(ref license) = package.manifest().metadata().license { + write!(fmt, "{}", license)?; + } + } + Chunk::Repository => { + if let Some(ref repository) = package.manifest().metadata().repository { + write!(fmt, "{}", repository)?; + } + } + Chunk::Features => { + write!(fmt, "{}", features.join(","))?; + } + } + } + } + Node::Feature { name, node_index } => { + let for_node = self.graph.node(*node_index); + match for_node { + Node::Package { package_id, .. } => { + write!(fmt, "{} feature \"{}\"", package_id.name(), name)?; + if self.graph.is_cli_feature(self.node_index) { + write!(fmt, " (command-line)")?; + } + } + _ => panic!("unexpected feature node {:?}", for_node), + } + } + } + + Ok(()) + } +} diff --git a/src/cargo/ops/tree/format/parse.rs b/src/cargo/ops/tree/format/parse.rs new file mode 100644 index 00000000000..15f875ca865 --- /dev/null +++ b/src/cargo/ops/tree/format/parse.rs @@ -0,0 +1,97 @@ +use std::iter; +use std::str; + +pub enum RawChunk<'a> { + Text(&'a str), + Argument(&'a str), + Error(&'static str), +} + +pub struct Parser<'a> { + s: &'a str, + it: iter::Peekable>, +} + +impl<'a> Parser<'a> { + pub fn new(s: &'a str) -> Parser<'a> { + Parser { + s, + it: s.char_indices().peekable(), + } + } + + fn consume(&mut self, ch: char) -> bool { + match self.it.peek() { + Some(&(_, c)) if c == ch => { + self.it.next(); + true + } + _ => false, + } + } + + fn argument(&mut self) -> RawChunk<'a> { + RawChunk::Argument(self.name()) + } + + fn name(&mut self) -> &'a str { + let start = match self.it.peek() { + Some(&(pos, ch)) if ch.is_alphabetic() => { + self.it.next(); + pos + } + _ => return "", + }; + + loop { + match self.it.peek() { + Some(&(_, ch)) if ch.is_alphanumeric() => { + self.it.next(); + } + Some(&(end, _)) => return &self.s[start..end], + None => return &self.s[start..], + } + } + } + + fn text(&mut self, start: usize) -> RawChunk<'a> { + while let Some(&(pos, ch)) = self.it.peek() { + match ch { + '{' | '}' | ')' => return RawChunk::Text(&self.s[start..pos]), + _ => { + self.it.next(); + } + } + } + RawChunk::Text(&self.s[start..]) + } +} + +impl<'a> Iterator for Parser<'a> { + type Item = RawChunk<'a>; + + fn next(&mut self) -> Option> { + match self.it.peek() { + Some(&(_, '{')) => { + self.it.next(); + if self.consume('{') { + Some(RawChunk::Text("{")) + } else { + let chunk = self.argument(); + if self.consume('}') { + Some(chunk) + } else { + for _ in &mut self.it {} + Some(RawChunk::Error("expected '}'")) + } + } + } + Some(&(_, '}')) => { + self.it.next(); + Some(RawChunk::Error("unexpected '}'")) + } + Some(&(i, _)) => Some(self.text(i)), + None => None, + } + } +} diff --git a/src/cargo/ops/tree/graph.rs b/src/cargo/ops/tree/graph.rs new file mode 100644 index 00000000000..d9d947619b7 --- /dev/null +++ b/src/cargo/ops/tree/graph.rs @@ -0,0 +1,562 @@ +//! Code for building the graph used by `cargo tree`. + +use super::TreeOptions; +use crate::core::compiler::{CompileKind, RustcTargetData}; +use crate::core::dependency::DepKind; +use crate::core::resolver::features::{FeaturesFor, RequestedFeatures, ResolvedFeatures}; +use crate::core::resolver::Resolve; +use crate::core::{ + FeatureMap, FeatureValue, InternedString, Package, PackageId, PackageIdSpec, Workspace, +}; +use crate::util::CargoResult; +use std::collections::{HashMap, HashSet}; + +#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Node { + Package { + package_id: PackageId, + /// Features that are enabled on this package. + features: Vec, + kind: CompileKind, + }, + Feature { + /// Index of the package node this feature is for. + node_index: usize, + /// Name of the feature. + name: InternedString, + }, +} + +#[derive(Debug, Copy, Hash, Eq, Clone, PartialEq)] +pub enum Edge { + Dep(DepKind), + Feature, +} + +#[derive(Clone)] +struct Edges(HashMap>); + +impl Edges { + fn new() -> Edges { + Edges(HashMap::new()) + } + + fn add_edge(&mut self, kind: Edge, index: usize) { + let indexes = self.0.entry(kind).or_default(); + if !indexes.contains(&index) { + indexes.push(index) + } + } +} + +/// A graph of dependencies. +pub struct Graph<'a> { + nodes: Vec, + edges: Vec, + /// Index maps a node to an index, for fast lookup. + index: HashMap, + /// Map for looking up packages. + package_map: HashMap, + /// Set of indexes of feature nodes that were added via the command-line. + /// + /// For example `--features foo` will mark the "foo" node here. + cli_features: HashSet, + /// Map of dependency names, used for building internal feature map for + /// dep_name/feat_name syntax. + /// + /// Key is the index of a package node, value is a map of dep_name to a + /// set of `(pkg_node_index, is_optional)`. + dep_name_map: HashMap>>, +} + +impl<'a> Graph<'a> { + fn new(package_map: HashMap) -> Graph<'a> { + Graph { + nodes: Vec::new(), + edges: Vec::new(), + index: HashMap::new(), + package_map, + cli_features: HashSet::new(), + dep_name_map: HashMap::new(), + } + } + + /// Adds a new node to the graph, returning its new index. + fn add_node(&mut self, node: Node) -> usize { + let from_index = self.nodes.len(); + self.nodes.push(node); + self.edges.push(Edges::new()); + self.index + .insert(self.nodes[from_index].clone(), from_index); + from_index + } + + /// Returns a list of nodes the given node index points to for the given kind. + /// + /// Returns None if there are none. + pub fn connected_nodes(&self, from: usize, kind: &Edge) -> Option> { + match self.edges[from].0.get(kind) { + Some(indexes) => { + // Created a sorted list for consistent output. + let mut indexes = indexes.clone(); + indexes.sort_unstable_by(|a, b| self.nodes[*a].cmp(&self.nodes[*b])); + Some(indexes) + } + None => None, + } + } + + /// Gets a node by index. + pub fn node(&self, index: usize) -> &Node { + &self.nodes[index] + } + + /// Given a slice of PackageIds, returns the indexes of all nodes that match. + pub fn indexes_from_ids(&self, package_ids: &[PackageId]) -> Vec { + let mut result: Vec<(&Node, usize)> = self + .nodes + .iter() + .enumerate() + .filter(|(_i, node)| match node { + Node::Package { package_id, .. } => package_ids.contains(package_id), + _ => false, + }) + .map(|(i, node)| (node, i)) + .collect(); + result.sort_unstable(); + result.into_iter().map(|(_node, i)| i).collect() + } + + pub fn package_for_id(&self, id: PackageId) -> &Package { + self.package_map[&id] + } + + fn package_id_for_index(&self, index: usize) -> PackageId { + match self.nodes[index] { + Node::Package { package_id, .. } => package_id, + Node::Feature { .. } => panic!("unexpected feature node"), + } + } + + /// Returns `true` if the given feature node index is a feature enabled + /// via the command-line. + pub fn is_cli_feature(&self, index: usize) -> bool { + self.cli_features.contains(&index) + } + + /// Returns a new graph by removing all nodes not reachable from the + /// given nodes. + pub fn from_reachable(&self, roots: &[usize]) -> Graph<'a> { + // Graph built with features does not (yet) support --duplicates. + assert!(self.dep_name_map.is_empty()); + let mut new_graph = Graph::new(self.package_map.clone()); + // Maps old index to new index. None if not yet visited. + let mut remap: Vec> = vec![None; self.nodes.len()]; + + fn visit( + graph: &Graph<'_>, + new_graph: &mut Graph<'_>, + remap: &mut Vec>, + index: usize, + ) -> usize { + if let Some(new_index) = remap[index] { + // Already visited. + return new_index; + } + let node = graph.node(index).clone(); + let new_from = new_graph.add_node(node); + remap[index] = Some(new_from); + // Visit dependencies. + for (edge_kind, edge_indexes) in &graph.edges[index].0 { + for edge_index in edge_indexes { + let new_to_index = visit(graph, new_graph, remap, *edge_index); + new_graph.edges[new_from].add_edge(*edge_kind, new_to_index); + } + } + new_from + } + + // Walk the roots, generating a new graph as it goes along. + for root in roots { + visit(self, &mut new_graph, &mut remap, *root); + } + + new_graph + } + + /// Inverts the direction of all edges. + pub fn invert(&mut self) { + let mut new_edges = vec![Edges::new(); self.edges.len()]; + for (from_idx, node_edges) in self.edges.iter().enumerate() { + for (kind, edges) in &node_edges.0 { + for edge_idx in edges { + new_edges[*edge_idx].add_edge(*kind, from_idx); + } + } + } + self.edges = new_edges; + } + + /// Returns a list of nodes that are considered "duplicates" (same package + /// name, with different versions/features/source/etc.). + pub fn find_duplicates(&self) -> Vec { + // Graph built with features does not (yet) support --duplicates. + assert!(self.dep_name_map.is_empty()); + + // Collect a map of package name to Vec<(&Node, usize)>. + let mut packages = HashMap::new(); + for (i, node) in self.nodes.iter().enumerate() { + if let Node::Package { package_id, .. } = node { + packages + .entry(package_id.name()) + .or_insert_with(Vec::new) + .push((node, i)); + } + } + + let mut dupes: Vec<(&Node, usize)> = packages + .into_iter() + .filter(|(_name, indexes)| indexes.len() > 1) + .flat_map(|(_name, indexes)| indexes) + .collect(); + // For consistent output. + dupes.sort_unstable(); + dupes.into_iter().map(|(_node, i)| i).collect() + } +} + +/// Builds the graph. +pub fn build<'a>( + ws: &Workspace<'_>, + resolve: &Resolve, + resolved_features: &ResolvedFeatures, + specs: &[PackageIdSpec], + requested_features: &RequestedFeatures, + target_data: &RustcTargetData, + requested_kind: CompileKind, + package_map: HashMap, + opts: &TreeOptions, +) -> CargoResult> { + let mut graph = Graph::new(package_map); + let mut members_with_features = ws.members_with_features(specs, requested_features)?; + members_with_features.sort_unstable_by_key(|e| e.0.package_id()); + for (member, requested_features) in members_with_features { + let member_id = member.package_id(); + let features_for = FeaturesFor::from_for_host(member.proc_macro()); + let member_index = add_pkg( + &mut graph, + resolve, + resolved_features, + member_id, + features_for, + target_data, + requested_kind, + opts, + ); + if opts.graph_features { + let fmap = resolve.summary(member_id).features(); + add_cli_features(&mut graph, member_index, &requested_features, fmap); + } + } + if opts.graph_features { + add_internal_features(&mut graph, resolve); + } + Ok(graph) +} + +/// Adds a single package node (if it does not already exist). +/// +/// This will also recursively add all of its dependencies. +/// +/// Returns the index to the package node. +fn add_pkg( + graph: &mut Graph<'_>, + resolve: &Resolve, + resolved_features: &ResolvedFeatures, + package_id: PackageId, + features_for: FeaturesFor, + target_data: &RustcTargetData, + requested_kind: CompileKind, + opts: &TreeOptions, +) -> usize { + let node_features = resolved_features.activated_features(package_id, features_for); + let node_kind = match features_for { + FeaturesFor::HostDep => CompileKind::Host, + FeaturesFor::NormalOrDev => requested_kind, + }; + let node = Node::Package { + package_id, + features: node_features.clone(), + kind: node_kind, + }; + if let Some(idx) = graph.index.get(&node) { + return *idx; + } + let from_index = graph.add_node(node); + // Compute the dep name map which is later used for foo/bar feature lookups. + let mut dep_name_map: HashMap> = HashMap::new(); + let mut deps: Vec<_> = resolve.deps(package_id).collect(); + deps.sort_unstable_by_key(|(dep_id, _)| *dep_id); + for (dep_id, deps) in deps { + let mut deps: Vec<_> = deps + .iter() + // This filter is *similar* to the one found in `unit_dependencies::compute_deps`. + // Try to keep them in sync! + .filter(|dep| { + let kind = match (node_kind, dep.kind()) { + (CompileKind::Host, _) => CompileKind::Host, + (_, DepKind::Build) => CompileKind::Host, + (_, DepKind::Normal) => node_kind, + (_, DepKind::Development) => node_kind, + }; + // Filter out inactivated targets. + if !opts.no_filter_targets && !target_data.dep_platform_activated(dep, kind) { + return false; + } + // Filter out dev-dependencies if requested. + if opts.no_dev_dependencies && dep.kind() == DepKind::Development { + return false; + } + if dep.is_optional() { + // If the new feature resolver does not enable this + // optional dep, then don't use it. + if !node_features.contains(&dep.name_in_toml()) { + return false; + } + } + true + }) + .collect(); + deps.sort_unstable_by_key(|dep| dep.name_in_toml()); + let dep_pkg = graph.package_map[&dep_id]; + + for dep in deps { + let dep_features_for = if dep.is_build() || dep_pkg.proc_macro() { + FeaturesFor::HostDep + } else { + features_for + }; + let dep_index = add_pkg( + graph, + resolve, + resolved_features, + dep_id, + dep_features_for, + target_data, + requested_kind, + opts, + ); + if opts.graph_features { + // Add the dependency node with feature nodes in-between. + dep_name_map + .entry(dep.name_in_toml()) + .or_default() + .insert((dep_index, dep.is_optional())); + if dep.uses_default_features() { + add_feature( + graph, + InternedString::new("default"), + Some(from_index), + dep_index, + Edge::Dep(dep.kind()), + ); + } + for feature in dep.features() { + add_feature( + graph, + *feature, + Some(from_index), + dep_index, + Edge::Dep(dep.kind()), + ); + } + if !dep.uses_default_features() && dep.features().is_empty() { + // No features, use a direct connection. + graph.edges[from_index].add_edge(Edge::Dep(dep.kind()), dep_index); + } + } else { + graph.edges[from_index].add_edge(Edge::Dep(dep.kind()), dep_index); + } + } + } + if opts.graph_features { + assert!(graph + .dep_name_map + .insert(from_index, dep_name_map) + .is_none()); + } + + from_index +} + +/// Adds a feature node between two nodes. +/// +/// That is, it adds the following: +/// +/// ```text +/// from -Edge-> featname -Edge::Feature-> to +/// ``` +fn add_feature( + graph: &mut Graph<'_>, + name: InternedString, + from: Option, + to: usize, + kind: Edge, +) -> usize { + let node = Node::Feature { + node_index: to, + name, + }; + let (node_index, _new) = match graph.index.get(&node) { + Some(idx) => (*idx, false), + None => (graph.add_node(node), true), + }; + if let Some(from) = from { + graph.edges[from].add_edge(kind, node_index); + } + graph.edges[node_index].add_edge(Edge::Feature, to); + node_index +} + +/// Adds nodes for features requested on the command-line for the given member. +/// +/// Feature nodes are added as "roots" (i.e., they have no "from" index), +/// because they come from the outside world. They usually only appear with +/// `--invert`. +fn add_cli_features( + graph: &mut Graph<'_>, + package_index: usize, + requested_features: &RequestedFeatures, + feature_map: &FeatureMap, +) { + // NOTE: Recursive enabling of features will be handled by + // add_internal_features. + + // Create a list of feature names requested on the command-line. + let mut to_add: Vec = Vec::new(); + if requested_features.all_features { + to_add.extend(feature_map.keys().copied()); + // Add optional deps. + for (dep_name, deps) in &graph.dep_name_map[&package_index] { + if deps.iter().any(|(_idx, is_optional)| *is_optional) { + to_add.push(*dep_name); + } + } + } else { + if requested_features.uses_default_features { + to_add.push(InternedString::new("default")); + } + to_add.extend(requested_features.features.iter().copied()); + }; + + // Add each feature as a node, and mark as "from command-line" in graph.cli_features. + for name in to_add { + if name.contains('/') { + let mut parts = name.splitn(2, '/'); + let dep_name = InternedString::new(parts.next().unwrap()); + let feat_name = InternedString::new(parts.next().unwrap()); + for (dep_index, is_optional) in graph.dep_name_map[&package_index][&dep_name].clone() { + if is_optional { + // Activate the optional dep on self. + let index = add_feature(graph, dep_name, None, package_index, Edge::Feature); + graph.cli_features.insert(index); + } + let index = add_feature(graph, feat_name, None, dep_index, Edge::Feature); + graph.cli_features.insert(index); + } + } else { + let index = add_feature(graph, name, None, package_index, Edge::Feature); + graph.cli_features.insert(index); + } + } +} + +/// Recursively adds connections between features in the `[features]` table +/// for every package. +fn add_internal_features(graph: &mut Graph<'_>, resolve: &Resolve) { + // Collect features already activated by dependencies or command-line. + let feature_nodes: Vec<(PackageId, usize, usize, InternedString)> = graph + .nodes + .iter() + .enumerate() + .filter_map(|(i, node)| match node { + Node::Package { .. } => None, + Node::Feature { node_index, name } => { + let package_id = graph.package_id_for_index(*node_index); + Some((package_id, *node_index, i, *name)) + } + }) + .collect(); + + for (package_id, package_index, feature_index, feature_name) in feature_nodes { + add_feature_rec( + graph, + resolve, + feature_name, + package_id, + feature_index, + package_index, + ); + } +} + +/// Recursively add feature nodes for all features enabled by the given feature. +/// +/// `from` is the index of the node that enables this feature. +/// `package_index` is the index of the package node for the feature. +fn add_feature_rec( + graph: &mut Graph<'_>, + resolve: &Resolve, + feature_name: InternedString, + package_id: PackageId, + from: usize, + package_index: usize, +) { + let feature_map = resolve.summary(package_id).features(); + let fvs = match feature_map.get(&feature_name) { + Some(fvs) => fvs, + None => return, + }; + for fv in fvs { + match fv { + FeatureValue::Feature(fv_name) | FeatureValue::Crate(fv_name) => { + let feat_index = + add_feature(graph, *fv_name, Some(from), package_index, Edge::Feature); + add_feature_rec( + graph, + resolve, + *fv_name, + package_id, + feat_index, + package_index, + ); + } + FeatureValue::CrateFeature(dep_name, fv_name) => { + let dep_indexes = match graph.dep_name_map[&package_index].get(dep_name) { + Some(indexes) => indexes.clone(), + None => { + log::debug!( + "enabling feature {} on {}, found {}/{}, \ + dep appears to not be enabled", + feature_name, + package_id, + dep_name, + fv_name + ); + continue; + } + }; + for (dep_index, is_optional) in dep_indexes { + let dep_pkg_id = graph.package_id_for_index(dep_index); + if is_optional { + // Activate the optional dep on self. + add_feature(graph, *dep_name, Some(from), package_index, Edge::Feature); + } + let feat_index = + add_feature(graph, *fv_name, Some(from), dep_index, Edge::Feature); + add_feature_rec(graph, resolve, *fv_name, dep_pkg_id, feat_index, dep_index); + } + } + } + } +} diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs new file mode 100644 index 00000000000..e55a09ac1fa --- /dev/null +++ b/src/cargo/ops/tree/mod.rs @@ -0,0 +1,333 @@ +//! Implementation of `cargo tree`. + +use self::format::Pattern; +use crate::core::compiler::{CompileKind, RustcTargetData}; +use crate::core::dependency::DepKind; +use crate::core::resolver::{HasDevUnits, ResolveOpts}; +use crate::core::{Package, PackageId, Workspace}; +use crate::ops::{self, Packages}; +use crate::util::CargoResult; +use anyhow::{bail, Context}; +use graph::Graph; +use std::collections::{HashMap, HashSet}; +use std::str::FromStr; + +mod format; +mod graph; + +pub use {graph::Edge, graph::Node}; + +pub struct TreeOptions { + pub features: Vec, + pub no_default_features: bool, + pub all_features: bool, + /// The packages to display the tree for. + pub packages: Packages, + /// The platform to filter for. + /// If `None`, use the host platform. + pub target: Option, + /// If `true`, ignores the `target` field and returns all targets. + pub no_filter_targets: bool, + pub no_dev_dependencies: bool, + pub invert: bool, + /// Displays a list, with no indentation. + pub no_indent: bool, + /// Displays a list, with a number indicating the depth instead of using indentation. + pub prefix_depth: bool, + /// 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, +} + +pub enum Charset { + Utf8, + Ascii, +} + +impl FromStr for Charset { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "utf8" => Ok(Charset::Utf8), + "ascii" => Ok(Charset::Ascii), + _ => Err("invalid charset"), + } + } +} + +#[derive(Clone, Copy)] +enum Prefix { + None, + Indent, + Depth, +} + +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<()> { + if opts.no_filter_targets && opts.target.is_some() { + bail!("cannot specify both `--target` and `--no-filter-targets`"); + } + if opts.graph_features && opts.duplicates { + bail!("the `--graph-features` flag does not support `--duplicates`"); + } + let requested_kind = CompileKind::from_requested_target(ws.config(), opts.target.as_deref())?; + let target_data = RustcTargetData::new(ws, requested_kind)?; + let specs = opts.packages.to_package_id_specs(ws)?; + let resolve_opts = ResolveOpts::new( + /*dev_deps*/ true, + &opts.features, + opts.all_features, + !opts.no_default_features, + ); + let has_dev = if opts.no_dev_dependencies { + HasDevUnits::No + } else { + HasDevUnits::Yes + }; + let ws_resolve = ops::resolve_ws_with_opts( + ws, + &target_data, + requested_kind, + &resolve_opts, + &specs, + has_dev, + )?; + // Download all Packages. Some display formats need to display package metadata. + let package_map: HashMap = ws_resolve + .pkg_set + .get_many(ws_resolve.pkg_set.package_ids())? + .into_iter() + .map(|pkg| (pkg.package_id(), pkg)) + .collect(); + + let mut graph = graph::build( + ws, + &ws_resolve.targeted_resolve, + &ws_resolve.resolved_features, + &specs, + &resolve_opts.features, + &target_data, + requested_kind, + package_map, + opts, + )?; + + let root_ids = ws_resolve.targeted_resolve.specs_to_ids(&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 || opts.duplicates { + graph.invert(); + } + + print(opts, root_indexes, &graph)?; + Ok(()) +} + +/// Prints a tree for each given root. +fn print(opts: &TreeOptions, roots: Vec, 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, + }; + + let prefix = if opts.prefix_depth { + Prefix::Depth + } else if opts.no_indent { + Prefix::None + } else { + Prefix::Indent + }; + + for (i, root_index) in roots.into_iter().enumerate() { + if i != 0 { + println!(); + } + + // 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(); + // 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( + graph, + root_index, + &format, + symbols, + prefix, + opts.no_dedupe, + &mut visited_deps, + &mut levels_continue, + &mut print_stack, + ); + } + + Ok(()) +} + +/// Prints a package and all of its dependencies. +fn print_node<'a>( + graph: &'a Graph<'_>, + node_index: usize, + format: &Pattern, + symbols: &Symbols, + prefix: Prefix, + no_dedupe: bool, + visited_deps: &mut HashSet, + levels_continue: &mut Vec, + print_stack: &mut Vec, +) { + let new = no_dedupe || visited_deps.insert(node_index); + + match prefix { + Prefix::Depth => print!("{}", 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 { " " }; + print!("{} ", c); + } + + let c = if *last_continues { + symbols.tee + } else { + symbols.ell + }; + print!("{0}{1}{1} ", c, symbols.right); + } + } + Prefix::None => {} + } + + let in_cycle = print_stack.contains(&node_index); + let star = if new && !in_cycle { "" } else { " (*)" }; + println!("{}{}", format.display(graph, node_index), star); + + if !new || in_cycle { + return; + } + print_stack.push(node_index); + + for kind in &[ + Edge::Dep(DepKind::Normal), + Edge::Dep(DepKind::Build), + Edge::Dep(DepKind::Development), + Edge::Feature, + ] { + print_dependencies( + graph, + node_index, + format, + symbols, + prefix, + no_dedupe, + 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>( + graph: &'a Graph<'_>, + node_index: usize, + format: &Pattern, + symbols: &Symbols, + prefix: Prefix, + no_dedupe: bool, + visited_deps: &mut HashSet, + levels_continue: &mut Vec, + print_stack: &mut Vec, + kind: &Edge, +) { + let deps = match graph.connected_nodes(node_index, kind) { + Some(deps) => deps, + None => return, + }; + + let name = match kind { + Edge::Dep(DepKind::Normal) => None, + Edge::Dep(DepKind::Build) => Some("[build-dependencies]"), + Edge::Dep(DepKind::Development) => Some("[dev-dependencies]"), + Edge::Feature => None, + }; + + if let Prefix::Indent = prefix { + if let Some(name) = name { + for continues in &**levels_continue { + let c = if *continues { symbols.down } else { " " }; + print!("{} ", c); + } + + println!("{}", name); + } + } + + let mut it = deps.iter().peekable(); + while let Some(dependency) = it.next() { + levels_continue.push(it.peek().is_some()); + print_node( + graph, + *dependency, + format, + symbols, + prefix, + no_dedupe, + visited_deps, + levels_continue, + print_stack, + ); + levels_continue.pop(); + } +} diff --git a/src/cargo/util/command_prelude.rs b/src/cargo/util/command_prelude.rs index 48df8b95061..03fe98abb7b 100644 --- a/src/cargo/util/command_prelude.rs +++ b/src/cargo/util/command_prelude.rs @@ -37,6 +37,20 @@ pub trait AppExt: Sized { ._arg(multi_opt("exclude", "SPEC", exclude)) } + /// Variant of arg_package_spec that does not include the `--all` flag + /// (but does include `--workspace`). Used to avoid confusion with + /// historical uses of `--all`. + fn arg_package_spec_no_all( + self, + package: &'static str, + all: &'static str, + exclude: &'static str, + ) -> Self { + self.arg_package_spec_simple(package) + ._arg(opt("workspace", all)) + ._arg(multi_opt("exclude", "SPEC", exclude)) + } + fn arg_package_spec_simple(self, package: &'static str) -> Self { self._arg(multi_opt("package", "SPEC", package).short("p")) } @@ -362,6 +376,15 @@ pub trait ArgMatchesExt { } } + fn packages_from_flags(&self) -> CargoResult { + Packages::from_flags( + // TODO Integrate into 'workspace' + self._is_present("workspace") || self._is_present("all"), + self._values_of("exclude"), + self._values_of("package"), + ) + } + fn compile_options( &self, config: &Config, @@ -369,13 +392,7 @@ pub trait ArgMatchesExt { workspace: Option<&Workspace<'_>>, profile_checking: ProfileChecking, ) -> CargoResult { - let spec = Packages::from_flags( - // TODO Integrate into 'workspace' - self._is_present("workspace") || self._is_present("all"), - self._values_of("exclude"), - self._values_of("package"), - )?; - + let spec = self.packages_from_flags()?; let mut message_format = None; let default_json = MessageFormat::Json { short: false, diff --git a/src/doc/man/cargo-tree.adoc b/src/doc/man/cargo-tree.adoc new file mode 100644 index 00000000000..22d7aa642ad --- /dev/null +++ b/src/doc/man/cargo-tree.adoc @@ -0,0 +1,193 @@ += cargo-tree(1) +:idprefix: cargo_tree_ +:doctype: manpage +:actionverb: Display +:noall: true + +== NAME + +cargo-tree - Display a tree visualization of a dependency graph + +== SYNOPSIS + +`cargo tree [_OPTIONS_]` + +== DESCRIPTION + +This command will display a tree of dependencies to the terminal. An example of a simple project that depends on the "rand" package: + +---- +myproject v0.1.0 (/myproject) +└── rand v0.7.3 + ├── getrandom v0.1.14 + │ ├── cfg-if v0.1.10 + │ └── libc v0.2.68 + ├── libc v0.2.68 (*) + ├── rand_chacha v0.2.2 + │ ├── ppv-lite86 v0.2.6 + │ └── rand_core v0.5.1 + │ └── getrandom v0.1.14 (*) + └── rand_core v0.5.1 (*) +[build-dependencies] +└── cc v1.0.50 +---- + +Packages marked with `(*)` have been "de-duplicated". The dependencies for the +package have already been shown elswhere in the graph, and so are not +repeated. Use the `--no-dedupe` option to repeat the duplicates. + +== OPTIONS + +=== Tree Options + +*-i*:: +*--invert*:: + Invert the tree. Typically this flag is used with the `-p` flag to show + the dependencies for a specific package. + +*--no-dedupe*:: + Do not de-duplicate repeated dependencies. Usually, when a package has + already displayed its dependencies, further occurrences will not + re-display its dependencies, and will include a `(*)` to indicate it has + already been shown. This flag will cause those duplicates to be repeated. + +*-d*:: +*--duplicates*:: + Show only dependencies which come in multiple versions (implies + `--invert`). When used with the `-p` flag, only shows duplicates within + the subtree of the given package. ++ +It can be beneficial for build times and executable sizes to avoid building +that same package multiple times. This flag can help identify the offending +packages. You can then investigate if the package that depends on the +duplicate with the older version can be updated to the newer version so that +only one instance is built. + +*--no-dev-dependencies*:: + Do not include dev-dependencies. + +*--target* _TRIPLE_:: + Filter dependencies matching the given target-triple. + The default is the host platform. + +*--no-filter-targets*:: + Show dependencies for all target platforms. Cannot be specified with + `--target`. + +*--graph-features*:: + Runs in a special mode where features are included as individual nodes. + This is intended to be used to help explain why a feature is enabled on + any particular package. It is recommended to use with the `-p` and `-i` + flags to show how the features flow into the package. See the examples + below for more detail. + +=== Tree Formatting Options + +*--charset* _CHARSET_:: + Chooses the character set to use for the tree. Valid values are "utf8" or + "ascii". Default is "utf8". + +*-f* _FORMAT_:: +*--format* _FORMAT_:: + Set the format string for each package. The default is "{p}". ++ +This is an arbitrary string which will be used to display each package. The following +strings will be replaced with the corresponding value: ++ +- `{p}` — The package name. +- `{l}` — The package license. +- `{r}` — The package repository URL. +- `{f}` — Comma-separated list of package features that are enabled. + +*--no-indent*:: + Display the dependencies as a list (rather than a tree). + +*--prefix-depth*:: + Display the dependencies as a list (rather than a tree), but prefixed with + the depth. + +=== Package Selection + +include::options-packages.adoc[] + +=== Manifest Options + +include::options-manifest-path.adoc[] + +include::options-features.adoc[] + +=== Display Options + +include::options-display.adoc[] + +=== Common Options + +include::options-common.adoc[] + +include::options-locked.adoc[] + +include::section-environment.adoc[] + +include::section-exit-status.adoc[] + +== EXAMPLES + +. Display the tree for the package in the current directory: + + cargo tree + +. Display all the packages that depend on the specified package: + + cargo tree -i -p syn + +. Show the features enabled on each package: + + cargo tree --format "{p} {f}" + +. Show all packages that are built multiple times. This can happen if multiple + semver-incompatible versions appear in the tree (like 1.0.0 and 2.0.0). + + cargo tree -d + +. Explain why features are enabled for the given package: + + cargo tree --graph-features -i -p syn ++ +An example of what this would display: ++ +---- +syn v1.0.17 +├── syn feature "clone-impls" +│ └── syn feature "default" +│ └── rustversion v1.0.2 +│ └── rustversion feature "default" +│ └── myproject v0.1.0 (/myproject) +│ └── myproject feature "default" (command-line) +├── syn feature "default" (*) +├── syn feature "derive" +│ └── syn feature "default" (*) +├── syn feature "full" +│ └── rustversion v1.0.2 (*) +├── syn feature "parsing" +│ └── syn feature "default" (*) +├── syn feature "printing" +│ └── syn feature "default" (*) +├── syn feature "proc-macro" +│ └── syn feature "default" (*) +└── syn feature "quote" + ├── syn feature "printing" (*) + └── syn feature "proc-macro" (*) +---- ++ +To read this graph, you can follow the chain for each feature from the root to +see why it was included. For example, the "full" feature was added by the +`rustversion` crate which was included from `myproject` (with the default +features), and `myproject` was the package selected on the command-line. All +of the other `syn` features are added by the "default" feature ("quote" is +added by "printing" and "proc-macro", both of which are default features). ++ +If you're having difficulty cross-referencing the de-duplicated `(*)` entries, +try with the `--no-dedupe` flag to get the full output. + +== SEE ALSO +man:cargo[1], man:cargo-metadata[1] diff --git a/src/doc/man/cargo.adoc b/src/doc/man/cargo.adoc index 88e6c227b00..90e5e3485ca 100644 --- a/src/doc/man/cargo.adoc +++ b/src/doc/man/cargo.adoc @@ -71,6 +71,9 @@ man:cargo-metadata[1]:: man:cargo-pkgid[1]:: Print a fully qualified package specification. +man:cargo-tree[1]:: + Display a tree visualization of a dependency graph. + man:cargo-update[1]:: Update dependencies as recorded in the local lock file. diff --git a/src/doc/man/generated/cargo-tree.html b/src/doc/man/generated/cargo-tree.html new file mode 100644 index 00000000000..a0b87c828a3 --- /dev/null +++ b/src/doc/man/generated/cargo-tree.html @@ -0,0 +1,443 @@ +

NAME

+
+

cargo-tree - Display a tree visualization of a dependency graph

+
+
+

SYNOPSIS

+
+
+

cargo tree [OPTIONS]

+
+
+
+
+

DESCRIPTION

+
+
+

This command will display a tree of dependencies to the terminal. An example of a simple project that depends on the "rand" package:

+
+
+
+
myproject v0.1.0 (/myproject)
+└── rand v0.7.3
+    ├── getrandom v0.1.14
+    │   ├── cfg-if v0.1.10
+    │   └── libc v0.2.68
+    ├── libc v0.2.68 (*)
+    ├── rand_chacha v0.2.2
+    │   ├── ppv-lite86 v0.2.6
+    │   └── rand_core v0.5.1
+    │       └── getrandom v0.1.14 (*)
+    └── rand_core v0.5.1 (*)
+[build-dependencies]
+└── cc v1.0.50
+
+
+
+

Packages marked with (*) have been "de-duplicated". The dependencies for the +package have already been shown elswhere in the graph, and so are not +repeated. Use the --no-dedupe option to repeat the duplicates.

+
+
+
+
+

OPTIONS

+
+
+

Tree Options

+
+
+
-i
+
--invert
+
+

Invert the tree. Typically this flag is used with the -p flag to show +the dependencies for a specific package.

+
+
--no-dedupe
+
+

Do not de-duplicate repeated dependencies. Usually, when a package has +already displayed its dependencies, further occurrences will not +re-display its dependencies, and will include a (*) to indicate it has +already been shown. This flag will cause those duplicates to be repeated.

+
+
-d
+
--duplicates
+
+

Show only dependencies which come in multiple versions (implies +--invert). When used with the -p flag, only shows duplicates within +the subtree of the given package.

+
+

It can be beneficial for build times and executable sizes to avoid building +that same package multiple times. This flag can help identify the offending +packages. You can then investigate if the package that depends on the +duplicate with the older version can be updated to the newer version so that +only one instance is built.

+
+
+
--no-dev-dependencies
+
+

Do not include dev-dependencies.

+
+
--target TRIPLE
+
+

Filter dependencies matching the given target-triple. +The default is the host platform.

+
+
--no-filter-targets
+
+

Show dependencies for all target platforms. Cannot be specified with +--target.

+
+
--graph-features
+
+

Runs in a special mode where features are included as individual nodes. +This is intended to be used to help explain why a feature is enabled on +any particular package. It is recommended to use with the -p and -i +flags to show how the features flow into the package. See the examples +below for more detail.

+
+
+
+
+
+

Tree Formatting Options

+
+
+
--charset CHARSET
+
+

Chooses the character set to use for the tree. Valid values are "utf8" or +"ascii". Default is "utf8".

+
+
-f FORMAT
+
--format FORMAT
+
+

Set the format string for each package. The default is "{p}".

+
+

This is an arbitrary string which will be used to display each package. The following +strings will be replaced with the corresponding value:

+
+
+
    +
  • +

    {p} — The package name.

    +
  • +
  • +

    {l} — The package license.

    +
  • +
  • +

    {r} — The package repository URL.

    +
  • +
  • +

    {f} — Comma-separated list of package features that are enabled.

    +
  • +
+
+
+
--no-indent
+
+

Display the dependencies as a list (rather than a tree).

+
+
--prefix-depth
+
+

Display the dependencies as a list (rather than a tree), but prefixed with +the depth.

+
+
+
+
+
+

Package Selection

+
+

By default, when no package selection options are given, the packages selected +depend on the selected manifest file (based on the current working directory if +--manifest-path is not given). If the manifest is the root of a workspace then +the workspaces default members are selected, otherwise only the package defined +by the manifest will be selected.

+
+
+

The default members of a workspace can be set explicitly with the +workspace.default-members key in the root manifest. If this is not set, a +virtual workspace will include all workspace members (equivalent to passing +--workspace), and a non-virtual workspace will include only the root crate itself.

+
+
+
+
-p SPEC…​
+
--package SPEC…​
+
+

Display only the specified packages. See cargo-pkgid(1) for the +SPEC format. This flag may be specified multiple times.

+
+
--workspace
+
+

Display all members in the workspace.

+
+
--exclude SPEC…​
+
+

Exclude the specified packages. Must be used in conjunction with the +--workspace flag. This flag may be specified multiple times.

+
+
+
+
+
+

Manifest Options

+
+
+
--manifest-path PATH
+
+

Path to the Cargo.toml file. By default, Cargo searches for the +Cargo.toml file in the current directory or any parent directory.

+
+
+
+
+
+

Feature Selection

+
+

The feature flags allow you to control the enabled features for the "current" +package. The "current" package is the package in the current directory, or the +one specified in --manifest-path. If running in the root of a virtual +workspace, then the default features are selected for all workspace members, +or all features if --all-features is specified.

+
+
+

When no feature options are given, the default feature is activated for +every selected package.

+
+
+
+
--features FEATURES
+
+

Space or comma separated list of features to activate. These features only +apply to the current directory’s package. Features of direct dependencies +may be enabled with <dep-name>/<feature-name> syntax. This flag may be +specified multiple times, which enables all specified features.

+
+
--all-features
+
+

Activate all available features of all selected packages.

+
+
--no-default-features
+
+

Do not activate the default feature of the current directory’s +package.

+
+
+
+
+
+

Display Options

+
+
+
-v
+
--verbose
+
+

Use verbose output. May be specified twice for "very verbose" output which +includes extra output such as dependency warnings and build script output. +May also be specified with the term.verbose +config value.

+
+
-q
+
--quiet
+
+

No output printed to stdout.

+
+
--color WHEN
+
+

Control when colored output is used. Valid values:

+
+
    +
  • +

    auto (default): Automatically detect if color support is available on the +terminal.

    +
  • +
  • +

    always: Always display colors.

    +
  • +
  • +

    never: Never display colors.

    +
  • +
+
+
+

May also be specified with the term.color +config value.

+
+
+
+
+
+
+

Common Options

+
+
+
-h
+
--help
+
+

Prints help information.

+
+
-Z FLAG…​
+
+

Unstable (nightly-only) flags to Cargo. Run cargo -Z help for +details.

+
+
--frozen
+
--locked
+
+

Either of these flags requires that the Cargo.lock file is +up-to-date. If the lock file is missing, or it needs to be updated, Cargo will +exit with an error. The --frozen flag also prevents Cargo from +attempting to access the network to determine if it is out-of-date.

+
+

These may be used in environments where you want to assert that the +Cargo.lock file is up-to-date (such as a CI build) or want to avoid network +access.

+
+
+
--offline
+
+

Prevents Cargo from accessing the network for any reason. Without this +flag, Cargo will stop with an error if it needs to access the network and +the network is not available. With this flag, Cargo will attempt to +proceed without the network if possible.

+
+

Beware that this may result in different dependency resolution than online +mode. Cargo will restrict itself to crates that are downloaded locally, even +if there might be a newer version as indicated in the local copy of the index. +See the cargo-fetch(1) command to download dependencies before going +offline.

+
+
+

May also be specified with the net.offline config value.

+
+
+
+
+
+
+
+
+

ENVIRONMENT

+
+
+

See the reference for +details on environment variables that Cargo reads.

+
+
+
+
+

Exit Status

+
+
+
+
0
+
+

Cargo succeeded.

+
+
101
+
+

Cargo failed to complete.

+
+
+
+
+
+
+

EXAMPLES

+
+
+
    +
  1. +

    Display the tree for the package in the current directory:

    +
    +
    +
    cargo tree
    +
    +
    +
  2. +
  3. +

    Display all the packages that depend on the specified package:

    +
    +
    +
    cargo tree -i -p syn
    +
    +
    +
  4. +
  5. +

    Show the features enabled on each package:

    +
    +
    +
    cargo tree --format "{p} {f}"
    +
    +
    +
  6. +
  7. +

    Show all packages that are built multiple times. This can happen if multiple +semver-incompatible versions appear in the tree (like 1.0.0 and 2.0.0).

    +
    +
    +
    cargo tree -d
    +
    +
    +
  8. +
  9. +

    Explain why features are enabled for the given package:

    +
    +
    +
    cargo tree --graph-features -i -p syn
    +
    +
    +
    +

    An example of what this would display:

    +
    +
    +
    +
    syn v1.0.17
    +├── syn feature "clone-impls"
    +│   └── syn feature "default"
    +│       └── rustversion v1.0.2
    +│           └── rustversion feature "default"
    +│               └── myproject v0.1.0 (/myproject)
    +│                   └── myproject feature "default" (command-line)
    +├── syn feature "default" (*)
    +├── syn feature "derive"
    +│   └── syn feature "default" (*)
    +├── syn feature "full"
    +│   └── rustversion v1.0.2 (*)
    +├── syn feature "parsing"
    +│   └── syn feature "default" (*)
    +├── syn feature "printing"
    +│   └── syn feature "default" (*)
    +├── syn feature "proc-macro"
    +│   └── syn feature "default" (*)
    +└── syn feature "quote"
    +    ├── syn feature "printing" (*)
    +    └── syn feature "proc-macro" (*)
    +
    +
    +
    +

    To read this graph, you can follow the chain for each feature from the root to +see why it was included. For example, the "full" feature was added by the +rustversion crate which was included from myproject (with the default +features), and myproject was the package selected on the command-line. All +of the other syn features are added by the "default" feature ("quote" is +added by "printing" and "proc-macro", both of which are default features).

    +
    +
    +

    If you’re having difficulty cross-referencing the de-duplicated (*) entries, +try with the --no-dedupe flag to get the full output.

    +
    +
  10. +
+
+
+
+
+

SEE ALSO

+ +
\ No newline at end of file diff --git a/src/doc/man/generated/cargo.html b/src/doc/man/generated/cargo.html index 47fc014f916..52b4abe01ec 100644 --- a/src/doc/man/generated/cargo.html +++ b/src/doc/man/generated/cargo.html @@ -98,6 +98,10 @@

Manifest Commands

Print a fully qualified package specification.

+
cargo-tree(1)
+
+

Display a tree visualization of a dependency graph.

+
cargo-update(1)

Update dependencies as recorded in the local lock file.

diff --git a/src/doc/man/options-packages.adoc b/src/doc/man/options-packages.adoc index 19e11be557f..dbddfb9c021 100644 --- a/src/doc/man/options-packages.adoc +++ b/src/doc/man/options-packages.adoc @@ -17,8 +17,10 @@ virtual workspace will include all workspace members (equivalent to passing *--workspace*:: {actionverb} all members in the workspace. +ifndef::noall[] *--all*:: Deprecated alias for `--workspace`. +endif::noall[] *--exclude* _SPEC_...:: Exclude the specified packages. Must be used in conjunction with the diff --git a/src/doc/src/SUMMARY.md b/src/doc/src/SUMMARY.md index b77fce9ed52..e462b8ec357 100644 --- a/src/doc/src/SUMMARY.md +++ b/src/doc/src/SUMMARY.md @@ -59,6 +59,7 @@ * [cargo locate-project](commands/cargo-locate-project.md) * [cargo metadata](commands/cargo-metadata.md) * [cargo pkgid](commands/cargo-pkgid.md) + * [cargo tree](commands/cargo-tree.md) * [cargo update](commands/cargo-update.md) * [cargo vendor](commands/cargo-vendor.md) * [cargo verify-project](commands/cargo-verify-project.md) diff --git a/src/doc/src/commands/cargo-tree.md b/src/doc/src/commands/cargo-tree.md new file mode 100644 index 00000000000..0b134a71569 --- /dev/null +++ b/src/doc/src/commands/cargo-tree.md @@ -0,0 +1,3 @@ +# cargo tree +{{#include command-common.html}} +{{#include ../../man/generated/cargo-tree.html}} diff --git a/src/doc/src/commands/manifest-commands.md b/src/doc/src/commands/manifest-commands.md index e7662b4c9e6..e9b5c708d40 100644 --- a/src/doc/src/commands/manifest-commands.md +++ b/src/doc/src/commands/manifest-commands.md @@ -3,6 +3,7 @@ * [cargo locate-project](cargo-locate-project.md) * [cargo metadata](cargo-metadata.md) * [cargo pkgid](cargo-pkgid.md) +* [cargo tree](cargo-tree.md) * [cargo update](cargo-update.md) * [cargo vendor](cargo-vendor.md) * [cargo verify-project](cargo-verify-project.md) diff --git a/src/etc/_cargo b/src/etc/_cargo index 864f15339ec..ce257ab0bdb 100644 --- a/src/etc/_cargo +++ b/src/etc/_cargo @@ -271,6 +271,21 @@ _cargo() { '*: :_default' ;; + tree) + _arguments -s -S $common $features $triple $manifest \ + '(-p --package)'{-p+,--package=}'[package to use as the root]:package:_cargo_package_names' \ + '--no-filter-targets[return dependencies for all targets]' \ + '--no-dev-dependencies[skip dev dependencies]' \ + '(-i --invert)'{-i,--invert}'[invert the tree]' \ + '--no-indent[display as a list]' \ + '--prefix-depth[display as a list with numeric depth]' \ + '--no-dedupe[repeat shared dependencies]' \ + '(-d --duplicates)'{-d,--duplicates}'[packages with multiple versions]' \ + '--charset=[utf8 or ascii]' \ + '(-f --format)'{-f,--format=}'[format string]' \ + '--graph-features[show features]' \ + ;; + uninstall) _arguments -s -S $common \ '(-p --package)'{-p+,--package=}'[specify package to uninstall]:package:_cargo_package_names' \ diff --git a/src/etc/cargo.bashcomp.sh b/src/etc/cargo.bashcomp.sh index 0f423d98daa..e7f0af04093 100644 --- a/src/etc/cargo.bashcomp.sh +++ b/src/etc/cargo.bashcomp.sh @@ -73,6 +73,7 @@ _cargo() local opt__rustdoc="$opt_common $opt_pkg $opt_feat $opt_mani $opt_lock $opt_jobs $opt_targets --message-format --target --release --open --target-dir --profile" local opt__search="$opt_common $opt_lock --limit --index --registry" local opt__test="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_lock $opt_jobs $opt_targets --message-format --doc --target --no-run --release --no-fail-fast --target-dir --profile" + local opt__tree="$opt_common $opt_pkg_spec $opt_feat $opt_mani $opt_lock --target --no-filter-targets --no-dev-dependencies -i --invert --no-indent --prefix-depth --no-dedupe --duplicates -d --charset -f --format --graph-features" local opt__uninstall="$opt_common $opt_lock $opt_pkg --bin --root" local opt__update="$opt_common $opt_mani $opt_lock $opt_pkg --aggressive --precise --dry-run" local opt__vendor="$opt_common $opt_mani $opt_lock $opt_sync --no-delete --respect-source-config --versioned-dirs" diff --git a/src/etc/man/cargo-tree.1 b/src/etc/man/cargo-tree.1 new file mode 100644 index 00000000000..99547b1edc6 --- /dev/null +++ b/src/etc/man/cargo-tree.1 @@ -0,0 +1,491 @@ +'\" t +.\" Title: cargo-tree +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.10 +.\" Date: 2020-03-30 +.\" Manual: \ \& +.\" Source: \ \& +.\" Language: English +.\" +.TH "CARGO\-TREE" "1" "2020-03-30" "\ \&" "\ \&" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +cargo\-tree \- Display a tree visualization of a dependency graph +.SH "SYNOPSIS" +.sp +\fBcargo tree [\fIOPTIONS\fP]\fP +.SH "DESCRIPTION" +.sp +This command will display a tree of dependencies to the terminal. An example of a simple project that depends on the "rand" package: +.sp +.if n .RS 4 +.nf +myproject v0.1.0 (/myproject) +└── rand v0.7.3 + ├── getrandom v0.1.14 + │ ├── cfg\-if v0.1.10 + │ └── libc v0.2.68 + ├── libc v0.2.68 (*) + ├── rand_chacha v0.2.2 + │ ├── ppv\-lite86 v0.2.6 + │ └── rand_core v0.5.1 + │ └── getrandom v0.1.14 (*) + └── rand_core v0.5.1 (*) +[build\-dependencies] +└── cc v1.0.50 +.fi +.if n .RE +.sp +Packages marked with \fB(*)\fP have been "de\-duplicated". The dependencies for the +package have already been shown elswhere in the graph, and so are not +repeated. Use the \fB\-\-no\-dedupe\fP option to repeat the duplicates. +.SH "OPTIONS" +.SS "Tree Options" +.sp +\fB\-i\fP, \fB\-\-invert\fP +.RS 4 +Invert the tree. Typically this flag is used with the \fB\-p\fP flag to show +the dependencies for a specific package. +.RE +.sp +\fB\-\-no\-dedupe\fP +.RS 4 +Do not de\-duplicate repeated dependencies. Usually, when a package has +already displayed its dependencies, further occurrences will not +re\-display its dependencies, and will include a \fB(*)\fP to indicate it has +already been shown. This flag will cause those duplicates to be repeated. +.RE +.sp +\fB\-d\fP, \fB\-\-duplicates\fP +.RS 4 +Show only dependencies which come in multiple versions (implies +\fB\-\-invert\fP). When used with the \fB\-p\fP flag, only shows duplicates within +the subtree of the given package. +.sp +It can be beneficial for build times and executable sizes to avoid building +that same package multiple times. This flag can help identify the offending +packages. You can then investigate if the package that depends on the +duplicate with the older version can be updated to the newer version so that +only one instance is built. +.RE +.sp +\fB\-\-no\-dev\-dependencies\fP +.RS 4 +Do not include dev\-dependencies. +.RE +.sp +\fB\-\-target\fP \fITRIPLE\fP +.RS 4 +Filter dependencies matching the given target\-triple. +The default is the host platform. +.RE +.sp +\fB\-\-no\-filter\-targets\fP +.RS 4 +Show dependencies for all target platforms. Cannot be specified with +\fB\-\-target\fP. +.RE +.sp +\fB\-\-graph\-features\fP +.RS 4 +Runs in a special mode where features are included as individual nodes. +This is intended to be used to help explain why a feature is enabled on +any particular package. It is recommended to use with the \fB\-p\fP and \fB\-i\fP +flags to show how the features flow into the package. See the examples +below for more detail. +.RE +.SS "Tree Formatting Options" +.sp +\fB\-\-charset\fP \fICHARSET\fP +.RS 4 +Chooses the character set to use for the tree. Valid values are "utf8" or +"ascii". Default is "utf8". +.RE +.sp +\fB\-f\fP \fIFORMAT\fP, \fB\-\-format\fP \fIFORMAT\fP +.RS 4 +Set the format string for each package. The default is "{p}". +.sp +This is an arbitrary string which will be used to display each package. The following +strings will be replaced with the corresponding value: +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fB{p}\fP — The package name. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fB{l}\fP — The package license. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fB{r}\fP — The package repository URL. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fB{f}\fP — Comma\-separated list of package features that are enabled. +.RE +.RE +.sp +\fB\-\-no\-indent\fP +.RS 4 +Display the dependencies as a list (rather than a tree). +.RE +.sp +\fB\-\-prefix\-depth\fP +.RS 4 +Display the dependencies as a list (rather than a tree), but prefixed with +the depth. +.RE +.SS "Package Selection" +.sp +By default, when no package selection options are given, the packages selected +depend on the selected manifest file (based on the current working directory if +\fB\-\-manifest\-path\fP is not given). If the manifest is the root of a workspace then +the workspaces default members are selected, otherwise only the package defined +by the manifest will be selected. +.sp +The default members of a workspace can be set explicitly with the +\fBworkspace.default\-members\fP key in the root manifest. If this is not set, a +virtual workspace will include all workspace members (equivalent to passing +\fB\-\-workspace\fP), and a non\-virtual workspace will include only the root crate itself. +.sp +\fB\-p\fP \fISPEC\fP..., \fB\-\-package\fP \fISPEC\fP... +.RS 4 +Display only the specified packages. See \fBcargo\-pkgid\fP(1) for the +SPEC format. This flag may be specified multiple times. +.RE +.sp +\fB\-\-workspace\fP +.RS 4 +Display all members in the workspace. +.RE +.sp +\fB\-\-exclude\fP \fISPEC\fP... +.RS 4 +Exclude the specified packages. Must be used in conjunction with the +\fB\-\-workspace\fP flag. This flag may be specified multiple times. +.RE +.SS "Manifest Options" +.sp +\fB\-\-manifest\-path\fP \fIPATH\fP +.RS 4 +Path to the \fBCargo.toml\fP file. By default, Cargo searches for the +\fBCargo.toml\fP file in the current directory or any parent directory. +.RE +.SS "Feature Selection" +.sp +The feature flags allow you to control the enabled features for the "current" +package. The "current" package is the package in the current directory, or the +one specified in \fB\-\-manifest\-path\fP. If running in the root of a virtual +workspace, then the default features are selected for all workspace members, +or all features if \fB\-\-all\-features\fP is specified. +.sp +When no feature options are given, the \fBdefault\fP feature is activated for +every selected package. +.sp +\fB\-\-features\fP \fIFEATURES\fP +.RS 4 +Space or comma separated list of features to activate. These features only +apply to the current directory\(cqs package. Features of direct dependencies +may be enabled with \fB/\fP syntax. This flag may be +specified multiple times, which enables all specified features. +.RE +.sp +\fB\-\-all\-features\fP +.RS 4 +Activate all available features of all selected packages. +.RE +.sp +\fB\-\-no\-default\-features\fP +.RS 4 +Do not activate the \fBdefault\fP feature of the current directory\(cqs +package. +.RE +.SS "Display Options" +.sp +\fB\-v\fP, \fB\-\-verbose\fP +.RS 4 +Use verbose output. May be specified twice for "very verbose" output which +includes extra output such as dependency warnings and build script output. +May also be specified with the \fBterm.verbose\fP +.URL "https://doc.rust\-lang.org/cargo/reference/config.html" "config value" "." +.RE +.sp +\fB\-q\fP, \fB\-\-quiet\fP +.RS 4 +No output printed to stdout. +.RE +.sp +\fB\-\-color\fP \fIWHEN\fP +.RS 4 +Control when colored output is used. Valid values: +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fBauto\fP (default): Automatically detect if color support is available on the +terminal. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fBalways\fP: Always display colors. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fBnever\fP: Never display colors. +.RE +.sp +May also be specified with the \fBterm.color\fP +.URL "https://doc.rust\-lang.org/cargo/reference/config.html" "config value" "." +.RE +.SS "Common Options" +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Prints help information. +.RE +.sp +\fB\-Z\fP \fIFLAG\fP... +.RS 4 +Unstable (nightly\-only) flags to Cargo. Run \fBcargo \-Z help\fP for +details. +.RE +.sp +\fB\-\-frozen\fP, \fB\-\-locked\fP +.RS 4 +Either of these flags requires that the \fBCargo.lock\fP file is +up\-to\-date. If the lock file is missing, or it needs to be updated, Cargo will +exit with an error. The \fB\-\-frozen\fP flag also prevents Cargo from +attempting to access the network to determine if it is out\-of\-date. +.sp +These may be used in environments where you want to assert that the +\fBCargo.lock\fP file is up\-to\-date (such as a CI build) or want to avoid network +access. +.RE +.sp +\fB\-\-offline\fP +.RS 4 +Prevents Cargo from accessing the network for any reason. Without this +flag, Cargo will stop with an error if it needs to access the network and +the network is not available. With this flag, Cargo will attempt to +proceed without the network if possible. +.sp +Beware that this may result in different dependency resolution than online +mode. Cargo will restrict itself to crates that are downloaded locally, even +if there might be a newer version as indicated in the local copy of the index. +See the \fBcargo\-fetch\fP(1) command to download dependencies before going +offline. +.sp +May also be specified with the \fBnet.offline\fP \c +.URL "https://doc.rust\-lang.org/cargo/reference/config.html" "config value" "." +.RE +.SH "ENVIRONMENT" +.sp +See \c +.URL "https://doc.rust\-lang.org/cargo/reference/environment\-variables.html" "the reference" " " +for +details on environment variables that Cargo reads. +.SH "EXIT STATUS" +.sp +0 +.RS 4 +Cargo succeeded. +.RE +.sp +101 +.RS 4 +Cargo failed to complete. +.RE +.SH "EXAMPLES" +.sp +.RS 4 +.ie n \{\ +\h'-04' 1.\h'+01'\c +.\} +.el \{\ +. sp -1 +. IP " 1." 4.2 +.\} +Display the tree for the package in the current directory: +.sp +.if n .RS 4 +.nf +cargo tree +.fi +.if n .RE +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04' 2.\h'+01'\c +.\} +.el \{\ +. sp -1 +. IP " 2." 4.2 +.\} +Display all the packages that depend on the specified package: +.sp +.if n .RS 4 +.nf +cargo tree \-i \-p syn +.fi +.if n .RE +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04' 3.\h'+01'\c +.\} +.el \{\ +. sp -1 +. IP " 3." 4.2 +.\} +Show the features enabled on each package: +.sp +.if n .RS 4 +.nf +cargo tree \-\-format "{p} {f}" +.fi +.if n .RE +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04' 4.\h'+01'\c +.\} +.el \{\ +. sp -1 +. IP " 4." 4.2 +.\} +Show all packages that are built multiple times. This can happen if multiple +semver\-incompatible versions appear in the tree (like 1.0.0 and 2.0.0). +.sp +.if n .RS 4 +.nf +cargo tree \-d +.fi +.if n .RE +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04' 5.\h'+01'\c +.\} +.el \{\ +. sp -1 +. IP " 5." 4.2 +.\} +Explain why features are enabled for the given package: +.sp +.if n .RS 4 +.nf +cargo tree \-\-graph\-features \-i \-p syn +.fi +.if n .RE +.sp +An example of what this would display: +.sp +.if n .RS 4 +.nf +syn v1.0.17 +├── syn feature "clone\-impls" +│ └── syn feature "default" +│ └── rustversion v1.0.2 +│ └── rustversion feature "default" +│ └── myproject v0.1.0 (/myproject) +│ └── myproject feature "default" (command\-line) +├── syn feature "default" (*) +├── syn feature "derive" +│ └── syn feature "default" (*) +├── syn feature "full" +│ └── rustversion v1.0.2 (*) +├── syn feature "parsing" +│ └── syn feature "default" (*) +├── syn feature "printing" +│ └── syn feature "default" (*) +├── syn feature "proc\-macro" +│ └── syn feature "default" (*) +└── syn feature "quote" + ├── syn feature "printing" (*) + └── syn feature "proc\-macro" (*) +.fi +.if n .RE +.sp +To read this graph, you can follow the chain for each feature from the root to +see why it was included. For example, the "full" feature was added by the +\fBrustversion\fP crate which was included from \fBmyproject\fP (with the default +features), and \fBmyproject\fP was the package selected on the command\-line. All +of the other \fBsyn\fP features are added by the "default" feature ("quote" is +added by "printing" and "proc\-macro", both of which are default features). +.sp +If you\(cqre having difficulty cross\-referencing the de\-duplicated \fB(*)\fP entries, +try with the \fB\-\-no\-dedupe\fP flag to get the full output. +.RE +.SH "SEE ALSO" +.sp +\fBcargo\fP(1), \fBcargo\-metadata\fP(1) \ No newline at end of file diff --git a/src/etc/man/cargo.1 b/src/etc/man/cargo.1 index 05b61918079..854bb641558 100644 --- a/src/etc/man/cargo.1 +++ b/src/etc/man/cargo.1 @@ -2,12 +2,12 @@ .\" Title: cargo .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.10 -.\" Date: 2019-12-04 +.\" Date: 2020-03-30 .\" Manual: \ \& .\" Source: \ \& .\" Language: English .\" -.TH "CARGO" "1" "2019-12-04" "\ \&" "\ \&" +.TH "CARGO" "1" "2020-03-30" "\ \&" "\ \&" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -125,6 +125,11 @@ including overrides, in machine\-readable format. Print a fully qualified package specification. .RE .sp +\fBcargo\-tree\fP(1) +.RS 4 +Display a tree visualization of a dependency graph. +.RE +.sp \fBcargo\-update\fP(1) .RS 4 Update dependencies as recorded in the local lock file. diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index c5dd7802044..e5806aebbe9 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -103,6 +103,8 @@ mod standard_lib; mod test; mod timings; mod tool_paths; +mod tree; +mod tree_graph_features; mod unit_graph; mod update; mod vendor; diff --git a/tests/testsuite/tree.rs b/tests/testsuite/tree.rs new file mode 100644 index 00000000000..8ccdfbd9825 --- /dev/null +++ b/tests/testsuite/tree.rs @@ -0,0 +1,1371 @@ +//! Tests for the `cargo tree` command. + +use cargo_test_support::cross_compile::alternate; +use cargo_test_support::registry::{Dependency, Package}; +use cargo_test_support::{basic_manifest, git, project, rustc_host, Project}; + +fn make_simple_proj() -> Project { + Package::new("c", "1.0.0").publish(); + Package::new("b", "1.0.0").dep("c", "1.0").publish(); + Package::new("a", "1.0.0").dep("b", "1.0").publish(); + Package::new("bdep", "1.0.0").dep("b", "1.0").publish(); + Package::new("devdep", "1.0.0").dep("b", "1.0.0").publish(); + + project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + a = "1.0" + c = "1.0" + + [build-dependencies] + bdep = "1.0" + + [dev-dependencies] + devdep = "1.0" + "#, + ) + .file("src/lib.rs", "") + .file("build.rs", "fn main() {}") + .build() +} + +#[cargo_test] +fn simple() { + // A simple test with a few different dependencies. + let p = make_simple_proj(); + + p.cargo("tree") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── a v1.0.0 +│ └── b v1.0.0 +│ └── c v1.0.0 +└── c v1.0.0 (*) +[build-dependencies] +└── bdep v1.0.0 + └── b v1.0.0 (*) +[dev-dependencies] +└── devdep v1.0.0 + └── b v1.0.0 (*) +", + ) + .run(); + + p.cargo("tree -p bdep") + .with_stdout( + "\ +bdep v1.0.0 +└── b v1.0.0 + └── c v1.0.0 +", + ) + .run(); +} + +#[cargo_test] +fn virtual_workspace() { + // Multiple packages in a virtual workspace. + Package::new("somedep", "1.0.0").publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["a", "b", "c"] + "#, + ) + .file("a/Cargo.toml", &basic_manifest("a", "1.0.0")) + .file("a/src/lib.rs", "") + .file( + "b/Cargo.toml", + r#" + [package] + name = "b" + version = "0.1.0" + + [dependencies] + c = { path = "../c" } + somedep = "1.0" + "#, + ) + .file("b/src/lib.rs", "") + .file("c/Cargo.toml", &basic_manifest("c", "1.0.0")) + .file("c/src/lib.rs", "") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +a v1.0.0 ([..]/foo/a) + +b v0.1.0 ([..]/foo/b) +├── c v1.0.0 ([..]/foo/c) +└── somedep v1.0.0 + +c v1.0.0 ([..]/foo/c) +", + ) + .run(); + + p.cargo("tree -p a").with_stdout("a v1.0.0 [..]").run(); + + p.cargo("tree") + .cwd("b") + .with_stdout( + "\ +b v0.1.0 ([..]/foo/b) +├── c v1.0.0 ([..]/foo/c) +└── somedep v1.0.0 +", + ) + .run(); +} + +#[cargo_test] +fn dedupe_edges() { + // Works around https://github.com/rust-lang/cargo/issues/7985 + Package::new("bitflags", "1.0.0").publish(); + Package::new("manyfeat", "1.0.0") + .feature("f1", &[]) + .feature("f2", &[]) + .feature("f3", &[]) + .dep("bitflags", "1.0") + .publish(); + Package::new("a", "1.0.0") + .feature_dep("manyfeat", "1.0", &["f1"]) + .publish(); + Package::new("b", "1.0.0") + .feature_dep("manyfeat", "1.0", &["f2"]) + .publish(); + Package::new("c", "1.0.0") + .feature_dep("manyfeat", "1.0", &["f3"]) + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + a = "1.0" + b = "1.0" + c = "1.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── a v1.0.0 +│ └── manyfeat v1.0.0 +│ └── bitflags v1.0.0 +├── b v1.0.0 +│ └── manyfeat v1.0.0 (*) +└── c v1.0.0 + └── manyfeat v1.0.0 (*) +", + ) + .run(); +} + +#[cargo_test] +fn renamed_deps() { + // Handles renamed dependencies. + Package::new("one", "1.0.0").publish(); + Package::new("two", "1.0.0").publish(); + Package::new("bar", "1.0.0").dep("one", "1.0").publish(); + Package::new("bar", "2.0.0").dep("two", "1.0").publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + + [dependencies] + bar1 = {version = "1.0", package="bar"} + bar2 = {version = "2.0", package="bar"} + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +foo v1.0.0 ([..]/foo) +├── bar v1.0.0 +│ └── one v1.0.0 +└── bar v2.0.0 + └── two v1.0.0 +", + ) + .run(); +} + +#[cargo_test] +fn source_kinds() { + // Handles git and path sources. + Package::new("regdep", "1.0.0").publish(); + let git_project = git::new("gitdep", |p| { + p.file("Cargo.toml", &basic_manifest("gitdep", "1.0.0")) + .file("src/lib.rs", "") + }); + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + regdep = "1.0" + pathdep = {{ path = "pathdep" }} + gitdep = {{ git = "{}" }} + "#, + git_project.url() + ), + ) + .file("src/lib.rs", "") + .file("pathdep/Cargo.toml", &basic_manifest("pathdep", "1.0.0")) + .file("pathdep/src/lib.rs", "") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── gitdep v1.0.0 (file://[..]/gitdep#[..]) +├── pathdep v1.0.0 ([..]/foo/pathdep) +└── regdep v1.0.0 +", + ) + .run(); +} + +#[cargo_test] +fn features() { + // Exercises a variety of feature behaviors. + Package::new("optdep_default", "1.0.0").publish(); + Package::new("optdep", "1.0.0").publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "a" + version = "0.1.0" + + [dependencies] + optdep_default = { version = "1.0", optional = true } + optdep = { version = "1.0", optional = true } + + [features] + default = ["optdep_default"] + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +a v0.1.0 ([..]/foo) +└── optdep_default v1.0.0 +", + ) + .run(); + + p.cargo("tree --no-default-features") + .with_stdout( + "\ +a v0.1.0 ([..]/foo) +", + ) + .run(); + + p.cargo("tree --all-features") + .with_stdout( + "\ +a v0.1.0 ([..]/foo) +├── optdep v1.0.0 +└── optdep_default v1.0.0 +", + ) + .run(); + + p.cargo("tree --features optdep") + .with_stdout( + "\ +a v0.1.0 ([..]/foo) +├── optdep v1.0.0 +└── optdep_default v1.0.0 +", + ) + .run(); +} + +#[cargo_test] +fn filters_target() { + // --target flag + Package::new("targetdep", "1.0.0").publish(); + Package::new("hostdep", "1.0.0").publish(); + Package::new("devdep", "1.0.0").publish(); + Package::new("build_target_dep", "1.0.0").publish(); + Package::new("build_host_dep", "1.0.0") + .target_dep("targetdep", "1.0", alternate()) + .target_dep("hostdep", "1.0", &rustc_host()) + .publish(); + Package::new("pm_target", "1.0.0") + .proc_macro(true) + .publish(); + Package::new("pm_host", "1.0.0").proc_macro(true).publish(); + + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.1.0" + + [target.'{alt}'.dependencies] + targetdep = "1.0" + pm_target = "1.0" + + [target.'{host}'.dependencies] + hostdep = "1.0" + pm_host = "1.0" + + [target.'{alt}'.dev-dependencies] + devdep = "1.0" + + [target.'{alt}'.build-dependencies] + build_target_dep = "1.0" + + [target.'{host}'.build-dependencies] + build_host_dep = "1.0" + "#, + alt = alternate(), + host = rustc_host() + ), + ) + .file("src/lib.rs", "") + .file("build.rs", "fn main() {}") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── hostdep v1.0.0 +└── pm_host v1.0.0 +[build-dependencies] +└── build_host_dep v1.0.0 + └── hostdep v1.0.0 (*) +", + ) + .run(); + + p.cargo("tree --target") + .arg(alternate()) + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── pm_target v1.0.0 +└── targetdep v1.0.0 +[build-dependencies] +└── build_host_dep v1.0.0 + └── hostdep v1.0.0 +[dev-dependencies] +└── devdep v1.0.0 +", + ) + .run(); + + p.cargo("tree --target") + .arg(rustc_host()) + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── hostdep v1.0.0 +└── pm_host v1.0.0 +[build-dependencies] +└── build_host_dep v1.0.0 + └── hostdep v1.0.0 +", + ) + .run(); + + p.cargo("tree --no-filter-targets --target") + .arg(alternate()) + .with_status(101) + .with_stderr("[ERROR] cannot specify both `--target` and `--no-filter-targets`") + .run(); + + p.cargo("tree --no-filter-targets") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── hostdep v1.0.0 +├── pm_host v1.0.0 +├── pm_target v1.0.0 +└── targetdep v1.0.0 +[build-dependencies] +├── build_host_dep v1.0.0 +│ ├── hostdep v1.0.0 (*) +│ └── targetdep v1.0.0 (*) +└── build_target_dep v1.0.0 +[dev-dependencies] +└── devdep v1.0.0 +", + ) + .run(); +} + +#[cargo_test] +fn no_dev_dependencies() { + Package::new("devdep", "1.0.0").publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dev-dependencies] + devdep = "1.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +foo v0.1.0 ([..]foo) +[dev-dependencies] +└── devdep v1.0.0 +", + ) + .run(); + + p.cargo("tree --no-dev-dependencies") + .with_stdout( + "\ +foo v0.1.0 ([..]foo) +", + ) + .run(); +} + +#[cargo_test] +fn cyclic_dev_dep() { + // Cyclical dev-dependency and inverse flag. + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dev-dependencies] + dev-dep = { path = "dev-dep" } + "#, + ) + .file("src/lib.rs", "") + .file( + "dev-dep/Cargo.toml", + r#" + [package] + name = "dev-dep" + version = "0.1.0" + + [dependencies] + foo = { path=".." } + "#, + ) + .file("dev-dep/src/lib.rs", "") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +[dev-dependencies] +└── dev-dep v0.1.0 ([..]/foo/dev-dep) + └── foo v0.1.0 ([..]/foo) (*) +", + ) + .run(); + + p.cargo("tree --invert") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +└── dev-dep v0.1.0 ([..]/foo/dev-dep) + [dev-dependencies] + └── foo v0.1.0 ([..]/foo) (*) +", + ) + .run(); +} + +#[cargo_test] +fn invert() { + Package::new("b1", "1.0.0").dep("c", "1.0").publish(); + Package::new("b2", "1.0.0").dep("d", "1.0").publish(); + Package::new("c", "1.0.0").publish(); + Package::new("d", "1.0.0").publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + b1 = "1.0" + b2 = "1.0" + c = "1.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── b1 v1.0.0 +│ └── c v1.0.0 +├── b2 v1.0.0 +│ └── d v1.0.0 +└── c v1.0.0 (*) +", + ) + .run(); + + p.cargo("tree --invert -p c") + .with_stdout( + "\ +c v1.0.0 +├── b1 v1.0.0 +│ └── foo v0.1.0 ([..]/foo) +└── foo v0.1.0 ([..]/foo) (*) +", + ) + .run(); +} + +#[cargo_test] +fn invert_with_build_dep() { + // -i with -p for a common dependency between normal and build deps. + Package::new("common", "1.0.0").publish(); + Package::new("bdep", "1.0.0").dep("common", "1.0").publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + common = "1.0" + + [build-dependencies] + bdep = "1.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +└── common v1.0.0 +[build-dependencies] +└── bdep v1.0.0 + └── common v1.0.0 (*) +", + ) + .run(); + + p.cargo("tree -i -p common") + .with_stdout( + "\ +common v1.0.0 +├── bdep v1.0.0 +│ [build-dependencies] +│ └── foo v0.1.0 ([..]/foo) +└── foo v0.1.0 ([..]/foo) (*) +", + ) + .run(); +} + +#[cargo_test] +fn no_indent() { + let p = make_simple_proj(); + + p.cargo("tree --no-indent") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +a v1.0.0 +b v1.0.0 +c v1.0.0 +c v1.0.0 (*) +bdep v1.0.0 +b v1.0.0 (*) +devdep v1.0.0 +b v1.0.0 (*) +", + ) + .run(); +} + +#[cargo_test] +fn prefix_depth() { + let p = make_simple_proj(); + + p.cargo("tree --prefix-depth") + .with_stdout( + "\ +0foo v0.1.0 ([..]/foo) +1a v1.0.0 +2b v1.0.0 +3c v1.0.0 +1c v1.0.0 (*) +1bdep v1.0.0 +2b v1.0.0 (*) +1devdep v1.0.0 +2b v1.0.0 (*) +", + ) + .run(); +} + +#[cargo_test] +fn no_dedupe() { + let p = make_simple_proj(); + + p.cargo("tree --no-dedupe") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── a v1.0.0 +│ └── b v1.0.0 +│ └── c v1.0.0 +└── c v1.0.0 +[build-dependencies] +└── bdep v1.0.0 + └── b v1.0.0 + └── c v1.0.0 +[dev-dependencies] +└── devdep v1.0.0 + └── b v1.0.0 + └── c v1.0.0 +", + ) + .run(); +} + +#[cargo_test] +fn no_dedupe_cycle() { + // --no-dedupe with a dependency cycle + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dev-dependencies] + bar = {path = "bar"} + "#, + ) + .file("src/lib.rs", "") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.1.0" + + [dependencies] + foo = {path=".."} + "#, + ) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +[dev-dependencies] +└── bar v0.1.0 ([..]/foo/bar) + └── foo v0.1.0 ([..]/foo) (*) +", + ) + .run(); + + p.cargo("tree --no-dedupe") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +[dev-dependencies] +└── bar v0.1.0 ([..]/foo/bar) + └── foo v0.1.0 ([..]/foo) (*) +", + ) + .run(); +} + +#[cargo_test] +fn duplicates() { + Package::new("dog", "1.0.0").publish(); + Package::new("dog", "2.0.0").publish(); + Package::new("cat", "1.0.0").publish(); + Package::new("cat", "2.0.0").publish(); + Package::new("dep", "1.0.0") + .dep("dog", "1.0") + .dep("cat", "1.0") + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["a", "b"] + "#, + ) + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "0.1.0" + + [dependencies] + dog1 = { version = "1.0", package = "dog" } + dog2 = { version = "2.0", package = "dog" } + "#, + ) + .file("a/src/lib.rs", "") + .file( + "b/Cargo.toml", + r#" + [package] + name = "b" + version = "0.1.0" + + [dependencies] + dep = "1.0" + cat = "2.0" + "#, + ) + .file("b/src/lib.rs", "") + .build(); + + p.cargo("tree -p a") + .with_stdout( + "\ +a v0.1.0 ([..]/foo/a) +├── dog v1.0.0 +└── dog v2.0.0 +", + ) + .run(); + + p.cargo("tree -p b") + .with_stdout( + "\ +b v0.1.0 ([..]/foo/b) +├── cat v2.0.0 +└── dep v1.0.0 + ├── cat v1.0.0 + └── dog v1.0.0 +", + ) + .run(); + + p.cargo("tree -p a -d") + .with_stdout( + "\ +dog v1.0.0 +└── a v0.1.0 ([..]/foo/a) + +dog v2.0.0 +└── a v0.1.0 ([..]/foo/a) +", + ) + .run(); + + p.cargo("tree -p b -d") + .with_stdout( + "\ +cat v1.0.0 +└── dep v1.0.0 + └── b v0.1.0 ([..]/foo/b) + +cat v2.0.0 +└── b v0.1.0 ([..]/foo/b) +", + ) + .run(); +} + +#[cargo_test] +fn charset() { + let p = make_simple_proj(); + p.cargo("tree --charset ascii") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +|-- a v1.0.0 +| `-- b v1.0.0 +| `-- c v1.0.0 +`-- c v1.0.0 (*) +[build-dependencies] +`-- bdep v1.0.0 + `-- b v1.0.0 (*) +[dev-dependencies] +`-- devdep v1.0.0 + `-- b v1.0.0 (*) +", + ) + .run(); +} + +#[cargo_test] +fn format() { + Package::new("dep", "1.0.0").publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + license = "MIT" + repository = "https://github.com/rust-lang/cargo" + + [dependencies] + dep = {version="1.0", optional=true} + + [features] + default = ["foo"] + foo = ["bar"] + bar = [] + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree --format <<<{p}>>>") + .with_stdout("<<>>") + .run(); + + p.cargo("tree --format {}") + .with_stderr( + "\ +[ERROR] tree format `{}` not valid + +Caused by: + unsupported pattern `` +", + ) + .with_status(101) + .run(); + + p.cargo("tree --format") + .arg("{p} {l} {r}") + .with_stdout("foo v0.1.0 ([..]/foo) MIT https://github.com/rust-lang/cargo") + .run(); + + p.cargo("tree --format") + .arg("{p} {f}") + .with_stdout("foo v0.1.0 ([..]/foo) bar,default,foo") + .run(); + + p.cargo("tree --all-features --format") + .arg("{p} [{f}]") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) [bar,default,dep,foo] +└── dep v1.0.0 [] +", + ) + .run(); +} + +#[cargo_test] +fn dev_dep_feature() { + // -Zfeatures=dev_dep with optional dep + Package::new("optdep", "1.0.0").publish(); + Package::new("bar", "1.0.0") + .add_dep(Dependency::new("optdep", "1.0").optional(true)) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dev-dependencies] + bar = { version = "1.0", features = ["optdep"] } + + [dependencies] + bar = "1.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +└── bar v1.0.0 + └── optdep v1.0.0 +[dev-dependencies] +└── bar v1.0.0 (*) +", + ) + .run(); + + p.cargo("tree --no-dev-dependencies") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +└── bar v1.0.0 + └── optdep v1.0.0 +", + ) + .run(); + + p.cargo("tree -Zfeatures=dev_dep") + .masquerade_as_nightly_cargo() + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +└── bar v1.0.0 + └── optdep v1.0.0 +[dev-dependencies] +└── bar v1.0.0 (*) +", + ) + .run(); + + p.cargo("tree --no-dev-dependencies -Zfeatures=dev_dep") + .masquerade_as_nightly_cargo() + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +└── bar v1.0.0 +", + ) + .run(); +} + +#[cargo_test] +fn host_dep_feature() { + // -Zfeatures=host_dep with optional dep + Package::new("optdep", "1.0.0").publish(); + Package::new("bar", "1.0.0") + .add_dep(Dependency::new("optdep", "1.0").optional(true)) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [build-dependencies] + bar = { version = "1.0", features = ["optdep"] } + + [dependencies] + bar = "1.0" + "#, + ) + .file("src/lib.rs", "") + .file("build.rs", "fn main() {}") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +└── bar v1.0.0 + └── optdep v1.0.0 +[build-dependencies] +└── bar v1.0.0 (*) +", + ) + .run(); + + p.cargo("tree -Zfeatures=host_dep") + .masquerade_as_nightly_cargo() + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +└── bar v1.0.0 +[build-dependencies] +└── bar v1.0.0 + └── optdep v1.0.0 +", + ) + .run(); + + // -p + p.cargo("tree -p bar") + .with_stdout( + "\ +bar v1.0.0 +└── optdep v1.0.0 +", + ) + .run(); + + p.cargo("tree -p bar -Zfeatures=host_dep") + .masquerade_as_nightly_cargo() + .with_stdout( + "\ +bar v1.0.0 + +bar v1.0.0 +└── optdep v1.0.0 +", + ) + .run(); + + // invert -p + p.cargo("tree -i -p optdep") + .with_stdout( + "\ +optdep v1.0.0 +└── bar v1.0.0 + └── foo v0.1.0 ([..]/foo) + [build-dependencies] + └── foo v0.1.0 ([..]/foo) (*) +", + ) + .run(); + + p.cargo("tree -i -p optdep -Zfeatures=host_dep") + .masquerade_as_nightly_cargo() + .with_stdout( + "\ +optdep v1.0.0 +└── bar v1.0.0 + [build-dependencies] + └── foo v0.1.0 ([..]/foo) +", + ) + .run(); + + // Check that -d handles duplicates with features. + p.cargo("tree -d -Zfeatures=host_dep") + .masquerade_as_nightly_cargo() + .with_stdout( + "\ +bar v1.0.0 +└── foo v0.1.0 ([..]/foo) + +bar v1.0.0 +[build-dependencies] +└── foo v0.1.0 ([..]/foo) +", + ) + .run(); +} + +#[cargo_test] +fn proc_macro_features() { + // -Zfeatures=host_dep with a proc-macro + Package::new("optdep", "1.0.0").publish(); + Package::new("somedep", "1.0.0") + .add_dep(Dependency::new("optdep", "1.0").optional(true)) + .publish(); + Package::new("pm", "1.0.0") + .proc_macro(true) + .feature_dep("somedep", "1.0", &["optdep"]) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + pm = "1.0" + somedep = "1.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── pm v1.0.0 +│ └── somedep v1.0.0 +│ └── optdep v1.0.0 +└── somedep v1.0.0 (*) +", + ) + .run(); + + // Note the missing (*) + p.cargo("tree -Zfeatures=host_dep") + .masquerade_as_nightly_cargo() + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── pm v1.0.0 +│ └── somedep v1.0.0 +│ └── optdep v1.0.0 +└── somedep v1.0.0 +", + ) + .run(); + + // -p + p.cargo("tree -p somedep") + .with_stdout( + "\ +somedep v1.0.0 +└── optdep v1.0.0 +", + ) + .run(); + + p.cargo("tree -p somedep -Zfeatures=host_dep") + .masquerade_as_nightly_cargo() + .with_stdout( + "\ +somedep v1.0.0 + +somedep v1.0.0 +└── optdep v1.0.0 +", + ) + .run(); + + // invert -p + p.cargo("tree -i -p somedep") + .with_stdout( + "\ +somedep v1.0.0 +├── foo v0.1.0 ([..]/foo) +└── pm v1.0.0 + └── foo v0.1.0 ([..]/foo) (*) +", + ) + .run(); + + p.cargo("tree -i -p somedep -Zfeatures=host_dep") + .masquerade_as_nightly_cargo() + .with_stdout( + "\ +somedep v1.0.0 +└── foo v0.1.0 ([..]/foo) + +somedep v1.0.0 +└── pm v1.0.0 + └── foo v0.1.0 ([..]/foo) +", + ) + .run(); +} + +#[cargo_test] +fn itarget_opt_dep() { + // -Zfeatures=itarget with optional dep + Package::new("optdep", "1.0.0").publish(); + Package::new("common", "1.0.0") + .add_dep(Dependency::new("optdep", "1.0").optional(true)) + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + + [dependencies] + common = "1.0" + + [target.'cfg(whatever)'.dependencies] + common = { version = "1.0", features = ["optdep"] } + + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +foo v1.0.0 ([..]/foo) +└── common v1.0.0 + └── optdep v1.0.0 +", + ) + .run(); + + p.cargo("tree -Zfeatures=itarget") + .with_stdout( + "\ +foo v1.0.0 ([..]/foo) +└── common v1.0.0 +", + ) + .masquerade_as_nightly_cargo() + .run(); +} + +#[cargo_test] +fn ambiguous_name() { + // -p that is ambiguous. + Package::new("dep", "1.0.0").publish(); + Package::new("dep", "2.0.0").publish(); + Package::new("bar", "1.0.0").dep("dep", "2.0").publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + dep = "1.0" + bar = "1.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree -p dep") + .with_stderr_contains( + "\ +error: There are multiple `dep` packages in your project, and the specification `dep` is ambiguous. +Please re-run this command with `-p ` where `` is one of the following: + dep:1.0.0 + dep:2.0.0 +", + ) + .with_status(101) + .run(); +} + +#[cargo_test] +fn workspace_features_are_local() { + // The features for workspace packages should be the same as `cargo build` + // (i.e., the features selected depend on the "current" package). + Package::new("optdep", "1.0.0").publish(); + Package::new("somedep", "1.0.0") + .add_dep(Dependency::new("optdep", "1.0").optional(true)) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["a", "b"] + "#, + ) + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "0.1.0" + + [dependencies] + somedep = {version="1.0", features=["optdep"]} + "#, + ) + .file("a/src/lib.rs", "") + .file( + "b/Cargo.toml", + r#" + [package] + name = "b" + version = "0.1.0" + + [dependencies] + somedep = "1.0" + "#, + ) + .file("b/src/lib.rs", "") + .build(); + + p.cargo("tree") + .with_stdout( + "\ +a v0.1.0 ([..]/foo/a) +└── somedep v1.0.0 + └── optdep v1.0.0 + +b v0.1.0 ([..]/foo/b) +└── somedep v1.0.0 + └── optdep v1.0.0 +", + ) + .run(); + + p.cargo("tree -p a") + .with_stdout( + "\ +a v0.1.0 ([..]/foo/a) +└── somedep v1.0.0 + └── optdep v1.0.0 +", + ) + .run(); + + p.cargo("tree -p b") + .with_stdout( + "\ +b v0.1.0 ([..]/foo/b) +└── somedep v1.0.0 +", + ) + .run(); +} diff --git a/tests/testsuite/tree_graph_features.rs b/tests/testsuite/tree_graph_features.rs new file mode 100644 index 00000000000..bfb0e5a0724 --- /dev/null +++ b/tests/testsuite/tree_graph_features.rs @@ -0,0 +1,364 @@ +//! Tests for the `cargo tree` command with --graph-features option. + +use cargo_test_support::project; +use cargo_test_support::registry::{Dependency, Package}; + +#[cargo_test] +fn dep_feature_various() { + // Checks different ways of setting features via dependencies. + Package::new("optdep", "1.0.0") + .feature("default", &["cat"]) + .feature("cat", &[]) + .publish(); + Package::new("defaultdep", "1.0.0") + .feature("default", &["f1"]) + .feature("f1", &["optdep"]) + .add_dep(Dependency::new("optdep", "1.0").optional(true)) + .publish(); + Package::new("nodefaultdep", "1.0.0") + .feature("default", &["f1"]) + .feature("f1", &[]) + .publish(); + Package::new("nameddep", "1.0.0") + .add_dep(Dependency::new("serde", "1.0").optional(true)) + .feature("default", &["serde-stuff"]) + .feature("serde-stuff", &["serde/derive"]) + .feature("vehicle", &["car"]) + .feature("car", &[]) + .publish(); + Package::new("serde_derive", "1.0.0").publish(); + Package::new("serde", "1.0.0") + .feature("derive", &["serde_derive"]) + .add_dep(Dependency::new("serde_derive", "1.0").optional(true)) + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + defaultdep = "1.0" + nodefaultdep = {version="1.0", default-features = false} + nameddep = {version="1.0", features = ["vehicle", "serde"]} + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree --graph-features") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── nodefaultdep v1.0.0 +├── defaultdep feature \"default\" +│ ├── defaultdep v1.0.0 +│ │ └── optdep feature \"default\" +│ │ ├── optdep v1.0.0 +│ │ └── optdep feature \"cat\" +│ │ └── optdep v1.0.0 (*) +│ └── defaultdep feature \"f1\" +│ ├── defaultdep v1.0.0 (*) +│ └── defaultdep feature \"optdep\" +│ └── defaultdep v1.0.0 (*) +├── nameddep feature \"default\" +│ ├── nameddep v1.0.0 +│ │ └── serde feature \"default\" +│ │ └── serde v1.0.0 +│ │ └── serde_derive feature \"default\" +│ │ └── serde_derive v1.0.0 +│ └── nameddep feature \"serde-stuff\" +│ ├── nameddep v1.0.0 (*) +│ ├── nameddep feature \"serde\" +│ │ └── nameddep v1.0.0 (*) +│ └── serde feature \"derive\" +│ ├── serde v1.0.0 (*) +│ └── serde feature \"serde_derive\" +│ └── serde v1.0.0 (*) +├── nameddep feature \"serde\" (*) +└── nameddep feature \"vehicle\" + ├── nameddep v1.0.0 (*) + └── nameddep feature \"car\" + └── nameddep v1.0.0 (*) +", + ) + .run(); +} + +#[cargo_test] +fn graph_features_ws_interdependent() { + // A workspace with interdependent crates. + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["a", "b"] + "#, + ) + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "0.1.0" + + [dependencies] + b = {path="../b", features=["feat2"]} + + [features] + default = ["a1"] + a1 = [] + a2 = [] + "#, + ) + .file("a/src/lib.rs", "") + .file( + "b/Cargo.toml", + r#" + [package] + name = "b" + version = "0.1.0" + + [features] + default = ["feat1"] + feat1 = [] + feat2 = [] + "#, + ) + .file("b/src/lib.rs", "") + .build(); + + p.cargo("tree --graph-features") + .with_stdout( + "\ +a v0.1.0 ([..]/foo/a) +├── b feature \"default\" (command-line) +│ ├── b v0.1.0 ([..]/foo/b) +│ └── b feature \"feat1\" +│ └── b v0.1.0 ([..]/foo/b) (*) +└── b feature \"feat2\" + └── b v0.1.0 ([..]/foo/b) (*) + +b v0.1.0 ([..]/foo/b) +", + ) + .run(); + + p.cargo("tree --graph-features -i") + .with_stdout( + "\ +a v0.1.0 ([..]/foo/a) +├── a feature \"a1\" +│ └── a feature \"default\" (command-line) +└── a feature \"default\" (command-line) (*) + +b v0.1.0 ([..]/foo/b) +├── b feature \"default\" (command-line) +│ └── a v0.1.0 ([..]/foo/a) +│ ├── a feature \"a1\" +│ │ └── a feature \"default\" (command-line) +│ └── a feature \"default\" (command-line) (*) +├── b feature \"feat1\" +│ └── b feature \"default\" (command-line) (*) +└── b feature \"feat2\" + └── a v0.1.0 ([..]/foo/a) (*) +", + ) + .run(); +} + +#[cargo_test] +fn slash_feature_name() { + // dep_name/feat_name syntax + Package::new("opt", "1.0.0").feature("feat1", &[]).publish(); + Package::new("notopt", "1.0.0") + .feature("cat", &[]) + .feature("animal", &["cat"]) + .publish(); + Package::new("opt2", "1.0.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + opt = {version = "1.0", optional=true} + opt2 = {version = "1.0", optional=true} + notopt = "1.0" + + [features] + f1 = ["opt/feat1", "notopt/animal"] + f2 = ["f1"] + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree --graph-features --features f1") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── notopt feature \"default\" +│ └── notopt v1.0.0 +└── opt feature \"default\" + └── opt v1.0.0 +", + ) + .run(); + + p.cargo("tree --graph-features --features f1 -i") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── foo feature \"default\" (command-line) +├── foo feature \"f1\" (command-line) +└── foo feature \"opt\" + └── foo feature \"f1\" (command-line) (*) +", + ) + .run(); + + p.cargo("tree --graph-features --features f1 -p notopt -i") + .with_stdout( + "\ +notopt v1.0.0 +├── notopt feature \"animal\" +│ └── foo feature \"f1\" (command-line) +├── notopt feature \"cat\" +│ └── notopt feature \"animal\" (*) +└── notopt feature \"default\" + └── foo v0.1.0 ([..]/foo) + ├── foo feature \"default\" (command-line) + ├── foo feature \"f1\" (command-line) (*) + └── foo feature \"opt\" + └── foo feature \"f1\" (command-line) (*) +", + ) + .run(); + + p.cargo("tree --graph-features --features notopt/animal -p notopt -i") + .with_stdout( + "\ +notopt v1.0.0 +├── notopt feature \"animal\" (command-line) +├── notopt feature \"cat\" +│ └── notopt feature \"animal\" (command-line) (*) +└── notopt feature \"default\" + └── foo v0.1.0 ([..]/foo) + └── foo feature \"default\" (command-line) +", + ) + .run(); + + p.cargo("tree --graph-features --all-features") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── notopt feature \"default\" +│ └── notopt v1.0.0 +├── opt feature \"default\" +│ └── opt v1.0.0 +└── opt2 feature \"default\" + └── opt2 v1.0.0 +", + ) + .run(); + + p.cargo("tree --graph-features --all-features -p opt2 -i") + .with_stdout( + "\ +opt2 v1.0.0 +└── opt2 feature \"default\" + └── foo v0.1.0 ([..]/foo) + ├── foo feature \"f1\" (command-line) + │ └── foo feature \"f2\" (command-line) + ├── foo feature \"f2\" (command-line) (*) + ├── foo feature \"opt\" (command-line) + │ └── foo feature \"f1\" (command-line) (*) + └── foo feature \"opt2\" (command-line) +", + ) + .run(); +} + +#[cargo_test] +fn features_enables_inactive_target() { + // Features that enable things on targets that are not enabled. + Package::new("optdep", "1.0.0") + .feature("feat1", &[]) + .publish(); + Package::new("dep1", "1.0.0") + .feature("somefeat", &[]) + .publish(); + Package::new("dep2", "1.0.0") + .add_dep( + Dependency::new("optdep", "1.0.0") + .optional(true) + .target("cfg(whatever)"), + ) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [target.'cfg(whatever)'.dependencies] + optdep = {version="1.0", optional=true} + dep1 = "1.0" + + [dependencies] + dep2 = "1.0" + + [features] + f1 = ["optdep"] + f2 = ["optdep/feat1"] + f3 = ["dep1/somefeat"] + f4 = ["dep2/optdep"] + "#, + ) + .file("src/lib.rs", "") + .build(); + p.cargo("tree --graph-features") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +└── dep2 feature \"default\" + └── dep2 v1.0.0 +", + ) + .run(); + p.cargo("tree --graph-features --all-features") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +└── dep2 feature \"default\" + └── dep2 v1.0.0 +", + ) + .run(); + p.cargo("tree --graph-features --all-features --no-filter-targets") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── dep1 feature \"default\" +│ └── dep1 v1.0.0 +├── dep2 feature \"default\" +│ └── dep2 v1.0.0 +│ └── optdep feature \"default\" +│ └── optdep v1.0.0 +└── optdep feature \"default\" (*) +", + ) + .run(); +} From feaa3123b64bb85322823f1bf262a97443d5d4d5 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 2 Apr 2020 10:40:44 -0700 Subject: [PATCH 02/14] Update `cargo tree` from review comments. --- src/cargo/ops/tree/format/mod.rs | 16 +++++++++------- src/cargo/ops/tree/format/parse.rs | 22 ++++++++++++++++++++++ src/cargo/ops/tree/graph.rs | 18 ++++++++++-------- src/cargo/ops/tree/mod.rs | 8 ++++---- 4 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/cargo/ops/tree/format/mod.rs b/src/cargo/ops/tree/format/mod.rs index 1d835e961cc..c1d8892c831 100644 --- a/src/cargo/ops/tree/format/mod.rs +++ b/src/cargo/ops/tree/format/mod.rs @@ -1,6 +1,6 @@ use self::parse::{Parser, RawChunk}; use super::{Graph, Node}; -use anyhow::{anyhow, Error}; +use anyhow::{bail, Error}; use std::fmt; mod parse; @@ -27,9 +27,9 @@ impl Pattern { RawChunk::Argument("r") => Chunk::Repository, RawChunk::Argument("f") => Chunk::Features, RawChunk::Argument(a) => { - return Err(anyhow!("unsupported pattern `{}`", a)); + bail!("unsupported pattern `{}`", a); } - RawChunk::Error(err) => return Err(anyhow!("{}", err)), + RawChunk::Error(err) => bail!("{}", err), }; chunks.push(chunk); } @@ -63,8 +63,8 @@ impl<'a> fmt::Display for Display<'a> { } => { let package = self.graph.package_for_id(*package_id); for chunk in &self.pattern.0 { - match *chunk { - Chunk::Raw(ref s) => fmt.write_str(s)?, + match chunk { + Chunk::Raw(s) => fmt.write_str(s)?, Chunk::Package => { write!(fmt, "{} v{}", package.name(), package.version())?; @@ -74,12 +74,12 @@ impl<'a> fmt::Display for Display<'a> { } } Chunk::License => { - if let Some(ref license) = package.manifest().metadata().license { + if let Some(license) = &package.manifest().metadata().license { write!(fmt, "{}", license)?; } } Chunk::Repository => { - if let Some(ref repository) = package.manifest().metadata().repository { + if let Some(repository) = &package.manifest().metadata().repository { write!(fmt, "{}", repository)?; } } @@ -98,6 +98,8 @@ impl<'a> fmt::Display for Display<'a> { write!(fmt, " (command-line)")?; } } + // The node_index in Node::Feature must point to a package + // node, see `add_feature`. _ => panic!("unexpected feature node {:?}", for_node), } } diff --git a/src/cargo/ops/tree/format/parse.rs b/src/cargo/ops/tree/format/parse.rs index 15f875ca865..49e14d1bdd7 100644 --- a/src/cargo/ops/tree/format/parse.rs +++ b/src/cargo/ops/tree/format/parse.rs @@ -1,12 +1,34 @@ +//! Parser for the `--format` string for `cargo tree`. + use std::iter; use std::str; pub enum RawChunk<'a> { + /// Raw text to include in the output. Text(&'a str), + /// A substitution to place in the output. For example, the argument "p" + /// emits the package name. Argument(&'a str), + /// Indicates an error in the format string. The given string is a + /// human-readable message explaining the error. Error(&'static str), } +/// `cargo tree` format parser. +/// +/// The format string indicates how each package should be displayed. It +/// includes simple markers surrounded in curly braces that will be +/// substituted with their corresponding values. For example, the text +/// "{p} license:{l}" will substitute the `{p}` with the package name/version +/// (and optionally source), and the `{l}` will be the license from +/// `Cargo.toml`. +/// +/// Substitutions are alphabetic characters between curly braces, like `{p}` +/// or `{foo}`. The actual interpretation of these are done in the `Pattern` +/// struct. +/// +/// Bare curly braces can be included in the output with double braces like +/// `{{` will include a single `{`, similar to Rust's format strings. pub struct Parser<'a> { s: &'a str, it: iter::Peekable>, diff --git a/src/cargo/ops/tree/graph.rs b/src/cargo/ops/tree/graph.rs index d9d947619b7..f6427854fd2 100644 --- a/src/cargo/ops/tree/graph.rs +++ b/src/cargo/ops/tree/graph.rs @@ -92,17 +92,15 @@ impl<'a> Graph<'a> { } /// Returns a list of nodes the given node index points to for the given kind. - /// - /// Returns None if there are none. - pub fn connected_nodes(&self, from: usize, kind: &Edge) -> Option> { + pub fn connected_nodes(&self, from: usize, kind: &Edge) -> Vec { match self.edges[from].0.get(kind) { Some(indexes) => { // Created a sorted list for consistent output. let mut indexes = indexes.clone(); indexes.sort_unstable_by(|a, b| self.nodes[*a].cmp(&self.nodes[*b])); - Some(indexes) + indexes } - None => None, + None => Vec::new(), } } @@ -123,6 +121,8 @@ impl<'a> Graph<'a> { }) .map(|(i, node)| (node, i)) .collect(); + // Sort for consistent output (the same command should always return + // the same output). "unstable" since nodes should always be unique. result.sort_unstable(); result.into_iter().map(|(_node, i)| i).collect() } @@ -403,13 +403,15 @@ fn add_feature( to: usize, kind: Edge, ) -> usize { + // `to` *must* point to a package node. + assert!(matches! {graph.nodes[to], Node::Package{..}}); let node = Node::Feature { node_index: to, name, }; - let (node_index, _new) = match graph.index.get(&node) { - Some(idx) => (*idx, false), - None => (graph.add_node(node), true), + let node_index = match graph.index.get(&node) { + Some(idx) => *idx, + None => graph.add_node(node), }; if let Some(from) = from { graph.edges[from].add_edge(kind, node_index); diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index e55a09ac1fa..bedd79b33eb 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -291,10 +291,10 @@ fn print_dependencies<'a>( print_stack: &mut Vec, kind: &Edge, ) { - let deps = match graph.connected_nodes(node_index, kind) { - Some(deps) => deps, - None => return, - }; + let deps = graph.connected_nodes(node_index, kind); + if deps.is_empty() { + return; + } let name = match kind { Edge::Dep(DepKind::Normal) => None, From b1fd8e986a6daeb59c680456124435d33f1ebe27 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 2 Apr 2020 11:05:52 -0700 Subject: [PATCH 03/14] Try to clarify how node edges works. --- src/cargo/ops/tree/graph.rs | 59 ++++++++++++++++++++++++++----------- src/cargo/ops/tree/mod.rs | 20 ++++++------- 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/src/cargo/ops/tree/graph.rs b/src/cargo/ops/tree/graph.rs index f6427854fd2..55f38f10d8e 100644 --- a/src/cargo/ops/tree/graph.rs +++ b/src/cargo/ops/tree/graph.rs @@ -27,21 +27,31 @@ pub enum Node { }, } +/// The kind of edge, for separating dependencies into different sections. #[derive(Debug, Copy, Hash, Eq, Clone, PartialEq)] -pub enum Edge { +pub enum EdgeKind { Dep(DepKind), Feature, } +/// Set of outgoing edges for a single node. +/// +/// Edges are separated by the edge kind (`DepKind` or `Feature`). This is +/// primarily done so that the output can easily display separate sections +/// like `[build-dependencies]`. +/// +/// The value is a `Vec` because each edge kind can have multiple outgoing +/// edges. For example, package "foo" can have multiple normal dependencies. #[derive(Clone)] -struct Edges(HashMap>); +struct Edges(HashMap>); impl Edges { fn new() -> Edges { Edges(HashMap::new()) } - fn add_edge(&mut self, kind: Edge, index: usize) { + /// Adds an edge pointing to the given node. + fn add_edge(&mut self, kind: EdgeKind, index: usize) { let indexes = self.0.entry(kind).or_default(); if !indexes.contains(&index) { indexes.push(index) @@ -52,6 +62,9 @@ impl Edges { /// A graph of dependencies. pub struct Graph<'a> { nodes: Vec, + /// The indexes of `edges` correspond to the `nodes`. That is, `edges[0]` + /// is the set of outgoing edges for `nodes[0]`. They should always be in + /// sync. edges: Vec, /// Index maps a node to an index, for fast lookup. index: HashMap, @@ -92,7 +105,7 @@ impl<'a> Graph<'a> { } /// Returns a list of nodes the given node index points to for the given kind. - pub fn connected_nodes(&self, from: usize, kind: &Edge) -> Vec { + pub fn connected_nodes(&self, from: usize, kind: &EdgeKind) -> Vec { match self.edges[from].0.get(kind) { Some(indexes) => { // Created a sorted list for consistent output. @@ -358,7 +371,7 @@ fn add_pkg( InternedString::new("default"), Some(from_index), dep_index, - Edge::Dep(dep.kind()), + EdgeKind::Dep(dep.kind()), ); } for feature in dep.features() { @@ -367,15 +380,15 @@ fn add_pkg( *feature, Some(from_index), dep_index, - Edge::Dep(dep.kind()), + EdgeKind::Dep(dep.kind()), ); } if !dep.uses_default_features() && dep.features().is_empty() { // No features, use a direct connection. - graph.edges[from_index].add_edge(Edge::Dep(dep.kind()), dep_index); + graph.edges[from_index].add_edge(EdgeKind::Dep(dep.kind()), dep_index); } } else { - graph.edges[from_index].add_edge(Edge::Dep(dep.kind()), dep_index); + graph.edges[from_index].add_edge(EdgeKind::Dep(dep.kind()), dep_index); } } } @@ -401,7 +414,7 @@ fn add_feature( name: InternedString, from: Option, to: usize, - kind: Edge, + kind: EdgeKind, ) -> usize { // `to` *must* point to a package node. assert!(matches! {graph.nodes[to], Node::Package{..}}); @@ -416,7 +429,7 @@ fn add_feature( if let Some(from) = from { graph.edges[from].add_edge(kind, node_index); } - graph.edges[node_index].add_edge(Edge::Feature, to); + graph.edges[node_index].add_edge(EdgeKind::Feature, to); node_index } @@ -460,14 +473,15 @@ fn add_cli_features( for (dep_index, is_optional) in graph.dep_name_map[&package_index][&dep_name].clone() { if is_optional { // Activate the optional dep on self. - let index = add_feature(graph, dep_name, None, package_index, Edge::Feature); + let index = + add_feature(graph, dep_name, None, package_index, EdgeKind::Feature); graph.cli_features.insert(index); } - let index = add_feature(graph, feat_name, None, dep_index, Edge::Feature); + let index = add_feature(graph, feat_name, None, dep_index, EdgeKind::Feature); graph.cli_features.insert(index); } } else { - let index = add_feature(graph, name, None, package_index, Edge::Feature); + let index = add_feature(graph, name, None, package_index, EdgeKind::Feature); graph.cli_features.insert(index); } } @@ -522,8 +536,13 @@ fn add_feature_rec( for fv in fvs { match fv { FeatureValue::Feature(fv_name) | FeatureValue::Crate(fv_name) => { - let feat_index = - add_feature(graph, *fv_name, Some(from), package_index, Edge::Feature); + let feat_index = add_feature( + graph, + *fv_name, + Some(from), + package_index, + EdgeKind::Feature, + ); add_feature_rec( graph, resolve, @@ -552,10 +571,16 @@ fn add_feature_rec( let dep_pkg_id = graph.package_id_for_index(dep_index); if is_optional { // Activate the optional dep on self. - add_feature(graph, *dep_name, Some(from), package_index, Edge::Feature); + add_feature( + graph, + *dep_name, + Some(from), + package_index, + EdgeKind::Feature, + ); } let feat_index = - add_feature(graph, *fv_name, Some(from), dep_index, Edge::Feature); + add_feature(graph, *fv_name, Some(from), dep_index, EdgeKind::Feature); add_feature_rec(graph, resolve, *fv_name, dep_pkg_id, feat_index, dep_index); } } diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index bedd79b33eb..f427e35de6e 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -15,7 +15,7 @@ use std::str::FromStr; mod format; mod graph; -pub use {graph::Edge, graph::Node}; +pub use {graph::EdgeKind, graph::Node}; pub struct TreeOptions { pub features: Vec, @@ -257,10 +257,10 @@ fn print_node<'a>( print_stack.push(node_index); for kind in &[ - Edge::Dep(DepKind::Normal), - Edge::Dep(DepKind::Build), - Edge::Dep(DepKind::Development), - Edge::Feature, + EdgeKind::Dep(DepKind::Normal), + EdgeKind::Dep(DepKind::Build), + EdgeKind::Dep(DepKind::Development), + EdgeKind::Feature, ] { print_dependencies( graph, @@ -289,7 +289,7 @@ fn print_dependencies<'a>( visited_deps: &mut HashSet, levels_continue: &mut Vec, print_stack: &mut Vec, - kind: &Edge, + kind: &EdgeKind, ) { let deps = graph.connected_nodes(node_index, kind); if deps.is_empty() { @@ -297,10 +297,10 @@ fn print_dependencies<'a>( } let name = match kind { - Edge::Dep(DepKind::Normal) => None, - Edge::Dep(DepKind::Build) => Some("[build-dependencies]"), - Edge::Dep(DepKind::Development) => Some("[dev-dependencies]"), - Edge::Feature => None, + 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 { From 9dc56c62800627eb50d6de457c160b1632ad2734 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 2 Apr 2020 11:09:06 -0700 Subject: [PATCH 04/14] Deduplicate nodes across roots. --- src/cargo/ops/tree/mod.rs | 7 ++++--- tests/testsuite/tree.rs | 13 ++++++------- tests/testsuite/tree_graph_features.rs | 7 ++----- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index f427e35de6e..0140297c799 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -182,14 +182,15 @@ fn print(opts: &TreeOptions, roots: Vec, graph: &Graph<'_>) -> CargoResul Prefix::Indent }; + // 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 { println!(); } - // 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(); // A stack of bools used to determine where | symbols should appear // when printing a line. let mut levels_continue = vec![]; diff --git a/tests/testsuite/tree.rs b/tests/testsuite/tree.rs index 8ccdfbd9825..3ddd70b18de 100644 --- a/tests/testsuite/tree.rs +++ b/tests/testsuite/tree.rs @@ -109,7 +109,7 @@ b v0.1.0 ([..]/foo/b) ├── c v1.0.0 ([..]/foo/c) └── somedep v1.0.0 -c v1.0.0 ([..]/foo/c) +c v1.0.0 ([..]/foo/c) (*) ", ) .run(); @@ -821,7 +821,7 @@ dog v1.0.0 └── a v0.1.0 ([..]/foo/a) dog v2.0.0 -└── a v0.1.0 ([..]/foo/a) +└── a v0.1.0 ([..]/foo/a) (*) ", ) .run(); @@ -834,7 +834,7 @@ cat v1.0.0 └── b v0.1.0 ([..]/foo/b) cat v2.0.0 -└── b v0.1.0 ([..]/foo/b) +└── b v0.1.0 ([..]/foo/b) (*) ", ) .run(); @@ -1103,7 +1103,7 @@ bar v1.0.0 bar v1.0.0 [build-dependencies] -└── foo v0.1.0 ([..]/foo) +└── foo v0.1.0 ([..]/foo) (*) ", ) .run(); @@ -1205,7 +1205,7 @@ somedep v1.0.0 somedep v1.0.0 └── pm v1.0.0 - └── foo v0.1.0 ([..]/foo) + └── foo v0.1.0 ([..]/foo) (*) ", ) .run(); @@ -1344,8 +1344,7 @@ a v0.1.0 ([..]/foo/a) └── optdep v1.0.0 b v0.1.0 ([..]/foo/b) -└── somedep v1.0.0 - └── optdep v1.0.0 +└── somedep v1.0.0 (*) ", ) .run(); diff --git a/tests/testsuite/tree_graph_features.rs b/tests/testsuite/tree_graph_features.rs index bfb0e5a0724..44b0f9515fa 100644 --- a/tests/testsuite/tree_graph_features.rs +++ b/tests/testsuite/tree_graph_features.rs @@ -143,7 +143,7 @@ a v0.1.0 ([..]/foo/a) └── b feature \"feat2\" └── b v0.1.0 ([..]/foo/b) (*) -b v0.1.0 ([..]/foo/b) +b v0.1.0 ([..]/foo/b) (*) ", ) .run(); @@ -158,10 +158,7 @@ a v0.1.0 ([..]/foo/a) b v0.1.0 ([..]/foo/b) ├── b feature \"default\" (command-line) -│ └── a v0.1.0 ([..]/foo/a) -│ ├── a feature \"a1\" -│ │ └── a feature \"default\" (command-line) -│ └── a feature \"default\" (command-line) (*) +│ └── a v0.1.0 ([..]/foo/a) (*) ├── b feature \"feat1\" │ └── b feature \"default\" (command-line) (*) └── b feature \"feat2\" From 6868215982760fb00b60c3c2cffa6f1eee5ca18a Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 2 Apr 2020 11:25:20 -0700 Subject: [PATCH 05/14] Don't show (*) deduplicate if a node doesn't have any dependencies to show. --- src/cargo/ops/tree/graph.rs | 5 ++++ src/cargo/ops/tree/mod.rs | 10 ++++++- tests/testsuite/tree.rs | 36 +++++++++++++------------- tests/testsuite/tree_graph_features.rs | 20 +++++++------- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/cargo/ops/tree/graph.rs b/src/cargo/ops/tree/graph.rs index 55f38f10d8e..e568b9bebbd 100644 --- a/src/cargo/ops/tree/graph.rs +++ b/src/cargo/ops/tree/graph.rs @@ -117,6 +117,11 @@ impl<'a> Graph<'a> { } } + /// Returns `true` if the given node has any outgoing edges. + pub fn has_outgoing_edges(&self, index: usize) -> bool { + !self.edges[index].0.is_empty() + } + /// Gets a node by index. pub fn node(&self, index: usize) -> &Node { &self.nodes[index] diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index 0140297c799..616de0682d6 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -249,7 +249,15 @@ fn print_node<'a>( } let in_cycle = print_stack.contains(&node_index); - let star = if new && !in_cycle { "" } else { " (*)" }; + // 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 { + " (*)" + }; println!("{}{}", format.display(graph, node_index), star); if !new || in_cycle { diff --git a/tests/testsuite/tree.rs b/tests/testsuite/tree.rs index 3ddd70b18de..5303b3cd30a 100644 --- a/tests/testsuite/tree.rs +++ b/tests/testsuite/tree.rs @@ -47,7 +47,7 @@ foo v0.1.0 ([..]/foo) ├── a v1.0.0 │ └── b v1.0.0 │ └── c v1.0.0 -└── c v1.0.0 (*) +└── c v1.0.0 [build-dependencies] └── bdep v1.0.0 └── b v1.0.0 (*) @@ -109,7 +109,7 @@ b v0.1.0 ([..]/foo/b) ├── c v1.0.0 ([..]/foo/c) └── somedep v1.0.0 -c v1.0.0 ([..]/foo/c) (*) +c v1.0.0 ([..]/foo/c) ", ) .run(); @@ -379,7 +379,7 @@ foo v0.1.0 ([..]/foo) └── pm_host v1.0.0 [build-dependencies] └── build_host_dep v1.0.0 - └── hostdep v1.0.0 (*) + └── hostdep v1.0.0 ", ) .run(); @@ -430,8 +430,8 @@ foo v0.1.0 ([..]/foo) └── targetdep v1.0.0 [build-dependencies] ├── build_host_dep v1.0.0 -│ ├── hostdep v1.0.0 (*) -│ └── targetdep v1.0.0 (*) +│ ├── hostdep v1.0.0 +│ └── targetdep v1.0.0 └── build_target_dep v1.0.0 [dev-dependencies] └── devdep v1.0.0 @@ -561,7 +561,7 @@ foo v0.1.0 ([..]/foo) │ └── c v1.0.0 ├── b2 v1.0.0 │ └── d v1.0.0 -└── c v1.0.0 (*) +└── c v1.0.0 ", ) .run(); @@ -572,7 +572,7 @@ foo v0.1.0 ([..]/foo) c v1.0.0 ├── b1 v1.0.0 │ └── foo v0.1.0 ([..]/foo) -└── foo v0.1.0 ([..]/foo) (*) +└── foo v0.1.0 ([..]/foo) ", ) .run(); @@ -608,7 +608,7 @@ foo v0.1.0 ([..]/foo) └── common v1.0.0 [build-dependencies] └── bdep v1.0.0 - └── common v1.0.0 (*) + └── common v1.0.0 ", ) .run(); @@ -620,7 +620,7 @@ common v1.0.0 ├── bdep v1.0.0 │ [build-dependencies] │ └── foo v0.1.0 ([..]/foo) -└── foo v0.1.0 ([..]/foo) (*) +└── foo v0.1.0 ([..]/foo) ", ) .run(); @@ -637,7 +637,7 @@ foo v0.1.0 ([..]/foo) a v1.0.0 b v1.0.0 c v1.0.0 -c v1.0.0 (*) +c v1.0.0 bdep v1.0.0 b v1.0.0 (*) devdep v1.0.0 @@ -658,7 +658,7 @@ fn prefix_depth() { 1a v1.0.0 2b v1.0.0 3c v1.0.0 -1c v1.0.0 (*) +1c v1.0.0 1bdep v1.0.0 2b v1.0.0 (*) 1devdep v1.0.0 @@ -821,7 +821,7 @@ dog v1.0.0 └── a v0.1.0 ([..]/foo/a) dog v2.0.0 -└── a v0.1.0 ([..]/foo/a) (*) +└── a v0.1.0 ([..]/foo/a) ", ) .run(); @@ -834,7 +834,7 @@ cat v1.0.0 └── b v0.1.0 ([..]/foo/b) cat v2.0.0 -└── b v0.1.0 ([..]/foo/b) (*) +└── b v0.1.0 ([..]/foo/b) ", ) .run(); @@ -850,7 +850,7 @@ foo v0.1.0 ([..]/foo) |-- a v1.0.0 | `-- b v1.0.0 | `-- c v1.0.0 -`-- c v1.0.0 (*) +`-- c v1.0.0 [build-dependencies] `-- bdep v1.0.0 `-- b v1.0.0 (*) @@ -1076,7 +1076,7 @@ optdep v1.0.0 └── bar v1.0.0 └── foo v0.1.0 ([..]/foo) [build-dependencies] - └── foo v0.1.0 ([..]/foo) (*) + └── foo v0.1.0 ([..]/foo) ", ) .run(); @@ -1103,7 +1103,7 @@ bar v1.0.0 bar v1.0.0 [build-dependencies] -└── foo v0.1.0 ([..]/foo) (*) +└── foo v0.1.0 ([..]/foo) ", ) .run(); @@ -1191,7 +1191,7 @@ somedep v1.0.0 somedep v1.0.0 ├── foo v0.1.0 ([..]/foo) └── pm v1.0.0 - └── foo v0.1.0 ([..]/foo) (*) + └── foo v0.1.0 ([..]/foo) ", ) .run(); @@ -1205,7 +1205,7 @@ somedep v1.0.0 somedep v1.0.0 └── pm v1.0.0 - └── foo v0.1.0 ([..]/foo) (*) + └── foo v0.1.0 ([..]/foo) ", ) .run(); diff --git a/tests/testsuite/tree_graph_features.rs b/tests/testsuite/tree_graph_features.rs index 44b0f9515fa..4087ace5112 100644 --- a/tests/testsuite/tree_graph_features.rs +++ b/tests/testsuite/tree_graph_features.rs @@ -59,7 +59,7 @@ foo v0.1.0 ([..]/foo) │ │ └── optdep feature \"default\" │ │ ├── optdep v1.0.0 │ │ └── optdep feature \"cat\" -│ │ └── optdep v1.0.0 (*) +│ │ └── optdep v1.0.0 │ └── defaultdep feature \"f1\" │ ├── defaultdep v1.0.0 (*) │ └── defaultdep feature \"optdep\" @@ -139,11 +139,11 @@ a v0.1.0 ([..]/foo/a) ├── b feature \"default\" (command-line) │ ├── b v0.1.0 ([..]/foo/b) │ └── b feature \"feat1\" -│ └── b v0.1.0 ([..]/foo/b) (*) +│ └── b v0.1.0 ([..]/foo/b) └── b feature \"feat2\" - └── b v0.1.0 ([..]/foo/b) (*) + └── b v0.1.0 ([..]/foo/b) -b v0.1.0 ([..]/foo/b) (*) +b v0.1.0 ([..]/foo/b) ", ) .run(); @@ -154,7 +154,7 @@ b v0.1.0 ([..]/foo/b) (*) a v0.1.0 ([..]/foo/a) ├── a feature \"a1\" │ └── a feature \"default\" (command-line) -└── a feature \"default\" (command-line) (*) +└── a feature \"default\" (command-line) b v0.1.0 ([..]/foo/b) ├── b feature \"default\" (command-line) @@ -218,7 +218,7 @@ foo v0.1.0 ([..]/foo) ├── foo feature \"default\" (command-line) ├── foo feature \"f1\" (command-line) └── foo feature \"opt\" - └── foo feature \"f1\" (command-line) (*) + └── foo feature \"f1\" (command-line) ", ) .run(); @@ -234,9 +234,9 @@ notopt v1.0.0 └── notopt feature \"default\" └── foo v0.1.0 ([..]/foo) ├── foo feature \"default\" (command-line) - ├── foo feature \"f1\" (command-line) (*) + ├── foo feature \"f1\" (command-line) └── foo feature \"opt\" - └── foo feature \"f1\" (command-line) (*) + └── foo feature \"f1\" (command-line) ", ) .run(); @@ -247,7 +247,7 @@ notopt v1.0.0 notopt v1.0.0 ├── notopt feature \"animal\" (command-line) ├── notopt feature \"cat\" -│ └── notopt feature \"animal\" (command-line) (*) +│ └── notopt feature \"animal\" (command-line) └── notopt feature \"default\" └── foo v0.1.0 ([..]/foo) └── foo feature \"default\" (command-line) @@ -277,7 +277,7 @@ opt2 v1.0.0 └── foo v0.1.0 ([..]/foo) ├── foo feature \"f1\" (command-line) │ └── foo feature \"f2\" (command-line) - ├── foo feature \"f2\" (command-line) (*) + ├── foo feature \"f2\" (command-line) ├── foo feature \"opt\" (command-line) │ └── foo feature \"f1\" (command-line) (*) └── foo feature \"opt2\" (command-line) From e1c95e2f4226d58af7c1f885c0fcd7a6c914c201 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 2 Apr 2020 11:31:39 -0700 Subject: [PATCH 06/14] Fix tree format parsing for bare `}` --- src/cargo/ops/tree/format/parse.rs | 8 ++++++-- tests/testsuite/tree.rs | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/cargo/ops/tree/format/parse.rs b/src/cargo/ops/tree/format/parse.rs index 49e14d1bdd7..ee112fbee50 100644 --- a/src/cargo/ops/tree/format/parse.rs +++ b/src/cargo/ops/tree/format/parse.rs @@ -79,7 +79,7 @@ impl<'a> Parser<'a> { fn text(&mut self, start: usize) -> RawChunk<'a> { while let Some(&(pos, ch)) = self.it.peek() { match ch { - '{' | '}' | ')' => return RawChunk::Text(&self.s[start..pos]), + '{' | '}' => return RawChunk::Text(&self.s[start..pos]), _ => { self.it.next(); } @@ -110,7 +110,11 @@ impl<'a> Iterator for Parser<'a> { } Some(&(_, '}')) => { self.it.next(); - Some(RawChunk::Error("unexpected '}'")) + if self.consume('}') { + Some(RawChunk::Text("}")) + } else { + Some(RawChunk::Error("unexpected '}'")) + } } Some(&(i, _)) => Some(self.text(i)), None => None, diff --git a/tests/testsuite/tree.rs b/tests/testsuite/tree.rs index 5303b3cd30a..d7f39fb6fc3 100644 --- a/tests/testsuite/tree.rs +++ b/tests/testsuite/tree.rs @@ -903,6 +903,10 @@ Caused by: .with_status(101) .run(); + p.cargo("tree --format {p}-{{hello}}") + .with_stdout("foo v0.1.0 ([..]/foo)-{hello}") + .run(); + p.cargo("tree --format") .arg("{p} {l} {r}") .with_stdout("foo v0.1.0 ([..]/foo) MIT https://github.com/rust-lang/cargo") From a9ff02ed5a58a4da3e3cde18570f62f88b7c342d Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 2 Apr 2020 11:48:52 -0700 Subject: [PATCH 07/14] Change --no-indent and --prefix-depth to a single --prefix option. --- src/bin/cargo/commands/tree.rs | 39 ++++++++++++++++++------- src/cargo/ops/tree/mod.rs | 31 +++++++++++--------- src/doc/man/cargo-tree.adoc | 12 ++++---- src/doc/man/generated/cargo-tree.html | 22 +++++++++----- src/etc/man/cargo-tree.1 | 41 ++++++++++++++++++++++----- tests/testsuite/tree.rs | 4 +-- 6 files changed, 103 insertions(+), 46 deletions(-) diff --git a/src/bin/cargo/commands/tree.rs b/src/bin/cargo/commands/tree.rs index b37a7787622..7fb4f788d97 100644 --- a/src/bin/cargo/commands/tree.rs +++ b/src/bin/cargo/commands/tree.rs @@ -22,14 +22,21 @@ pub fn cli() -> App { )) .arg(opt("no-dev-dependencies", "Skip dev dependencies")) .arg(opt("invert", "Invert the tree direction").short("i")) - .arg(opt( - "no-indent", - "Display the dependencies as a list (rather than a tree)", - )) - .arg(opt( - "prefix-depth", - "Display the dependencies as a list (rather than a tree), but prefixed with the depth", - )) + .arg(Arg::with_name("no-indent").long("no-indent").hidden(true)) + .arg( + Arg::with_name("prefix-depth") + .long("prefix-depth") + .hidden(true), + ) + .arg( + opt( + "prefix", + "Change the prefix (indentation) of how each entry is displayed", + ) + .value_name("PREFIX") + .possible_values(&["depth", "indent", "none"]) + .default_value("indent"), + ) .arg(opt( "no-dedupe", "Do not de-duplicate (repeats all shared dependencies)", @@ -58,9 +65,22 @@ pub fn cli() -> App { } pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { + if args.is_present("no-indent") { + return Err( + anyhow::format_err!("the --no-indent flag has been changed to --prefix=none").into(), + ); + } + if args.is_present("prefix-depth") { + return Err(anyhow::format_err!( + "the --prefix-depth flag has been changed to --prefix=depth" + ) + .into()); + } let ws = args.workspace(config)?; let charset = tree::Charset::from_str(args.value_of("charset").unwrap()) .map_err(|e| anyhow::anyhow!("{}", e))?; + let prefix = tree::Prefix::from_str(args.value_of("prefix").unwrap()) + .map_err(|e| anyhow::anyhow!("{}", e))?; let opts = tree::TreeOptions { features: values(args, "features"), all_features: args.is_present("all-features"), @@ -70,8 +90,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { no_filter_targets: args.is_present("no-filter-targets"), no_dev_dependencies: args.is_present("no-dev-dependencies"), invert: args.is_present("invert"), - no_indent: args.is_present("no-indent"), - prefix_depth: args.is_present("prefix-depth"), + prefix, no_dedupe: args.is_present("no-dedupe"), duplicates: args.is_present("duplicates"), charset, diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index 616de0682d6..7f281ad838c 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -30,10 +30,8 @@ pub struct TreeOptions { pub no_filter_targets: bool, pub no_dev_dependencies: bool, pub invert: bool, - /// Displays a list, with no indentation. - pub no_indent: bool, - /// Displays a list, with a number indicating the depth instead of using indentation. - pub prefix_depth: bool, + /// 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. @@ -68,12 +66,25 @@ impl FromStr for Charset { } #[derive(Clone, Copy)] -enum Prefix { +pub enum Prefix { None, Indent, Depth, } +impl FromStr for Prefix { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + 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, @@ -174,14 +185,6 @@ fn print(opts: &TreeOptions, roots: Vec, graph: &Graph<'_>) -> CargoResul Charset::Ascii => &ASCII_SYMBOLS, }; - let prefix = if opts.prefix_depth { - Prefix::Depth - } else if opts.no_indent { - Prefix::None - } else { - Prefix::Indent - }; - // 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(); @@ -203,7 +206,7 @@ fn print(opts: &TreeOptions, roots: Vec, graph: &Graph<'_>) -> CargoResul root_index, &format, symbols, - prefix, + opts.prefix, opts.no_dedupe, &mut visited_deps, &mut levels_continue, diff --git a/src/doc/man/cargo-tree.adoc b/src/doc/man/cargo-tree.adoc index 22d7aa642ad..b6cfb9fd919 100644 --- a/src/doc/man/cargo-tree.adoc +++ b/src/doc/man/cargo-tree.adoc @@ -99,12 +99,12 @@ strings will be replaced with the corresponding value: - `{r}` — The package repository URL. - `{f}` — Comma-separated list of package features that are enabled. -*--no-indent*:: - Display the dependencies as a list (rather than a tree). - -*--prefix-depth*:: - Display the dependencies as a list (rather than a tree), but prefixed with - the depth. +*--prefix* _PREFIX_:: + Sets how each line is displayed. The _PREFIX_ value can be one of: ++ +- `indent` (default) — Shows each line indented as a tree. +- `depth` — Show as a list, with the numeric depth printed before each entry. +- `none` — Show as a flat list. === Package Selection diff --git a/src/doc/man/generated/cargo-tree.html b/src/doc/man/generated/cargo-tree.html index a0b87c828a3..a4964965c0f 100644 --- a/src/doc/man/generated/cargo-tree.html +++ b/src/doc/man/generated/cargo-tree.html @@ -133,14 +133,22 @@

Tree Formatting Options

-
--no-indent
+
--prefix PREFIX
-

Display the dependencies as a list (rather than a tree).

-
-
--prefix-depth
-
-

Display the dependencies as a list (rather than a tree), but prefixed with -the depth.

+

Sets how each line is displayed. The PREFIX value can be one of:

+
+
    +
  • +

    indent (default) — Shows each line indented as a tree.

    +
  • +
  • +

    depth — Show as a list, with the numeric depth printed before each entry.

    +
  • +
  • +

    none — Show as a flat list.

    +
  • +
+
diff --git a/src/etc/man/cargo-tree.1 b/src/etc/man/cargo-tree.1 index 99547b1edc6..30a5181f3ae 100644 --- a/src/etc/man/cargo-tree.1 +++ b/src/etc/man/cargo-tree.1 @@ -2,12 +2,12 @@ .\" Title: cargo-tree .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.10 -.\" Date: 2020-03-30 +.\" Date: 2020-04-02 .\" Manual: \ \& .\" Source: \ \& .\" Language: English .\" -.TH "CARGO\-TREE" "1" "2020-03-30" "\ \&" "\ \&" +.TH "CARGO\-TREE" "1" "2020-04-02" "\ \&" "\ \&" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -172,15 +172,42 @@ strings will be replaced with the corresponding value: .RE .RE .sp -\fB\-\-no\-indent\fP +\fB\-\-prefix\fP \fIPREFIX\fP .RS 4 -Display the dependencies as a list (rather than a tree). +Sets how each line is displayed. The \fIPREFIX\fP value can be one of: +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fBindent\fP (default) — Shows each line indented as a tree. .RE .sp -\fB\-\-prefix\-depth\fP .RS 4 -Display the dependencies as a list (rather than a tree), but prefixed with -the depth. +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fBdepth\fP — Show as a list, with the numeric depth printed before each entry. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fBnone\fP — Show as a flat list. +.RE .RE .SS "Package Selection" .sp diff --git a/tests/testsuite/tree.rs b/tests/testsuite/tree.rs index d7f39fb6fc3..bec947ba573 100644 --- a/tests/testsuite/tree.rs +++ b/tests/testsuite/tree.rs @@ -630,7 +630,7 @@ common v1.0.0 fn no_indent() { let p = make_simple_proj(); - p.cargo("tree --no-indent") + p.cargo("tree --prefix=none") .with_stdout( "\ foo v0.1.0 ([..]/foo) @@ -651,7 +651,7 @@ b v1.0.0 (*) fn prefix_depth() { let p = make_simple_proj(); - p.cargo("tree --prefix-depth") + p.cargo("tree --prefix=depth") .with_stdout( "\ 0foo v0.1.0 ([..]/foo) From ec5e297b70c6596b2a9063afce4cc2126f9d4510 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 2 Apr 2020 12:20:55 -0700 Subject: [PATCH 08/14] Change --no-filter-targets to --target=all. --- src/bin/cargo/commands/tree.rs | 8 ++----- src/cargo/ops/tree/graph.rs | 3 ++- src/cargo/ops/tree/mod.rs | 33 +++++++++++++++++++------- src/doc/man/cargo-tree.adoc | 7 ++---- src/doc/man/generated/cargo-tree.html | 8 ++----- src/etc/man/cargo-tree.1 | 9 ++----- tests/testsuite/tree.rs | 8 +------ tests/testsuite/tree_graph_features.rs | 2 +- 8 files changed, 37 insertions(+), 41 deletions(-) diff --git a/src/bin/cargo/commands/tree.rs b/src/bin/cargo/commands/tree.rs index 7fb4f788d97..9534b805265 100644 --- a/src/bin/cargo/commands/tree.rs +++ b/src/bin/cargo/commands/tree.rs @@ -16,10 +16,6 @@ pub fn cli() -> App { .arg_target_triple( "Filter dependencies matching the given target-triple (default host platform)", ) - .arg(opt( - "no-filter-targets", - "Return dependencies for all targets", - )) .arg(opt("no-dev-dependencies", "Skip dev dependencies")) .arg(opt("invert", "Invert the tree direction").short("i")) .arg(Arg::with_name("no-indent").long("no-indent").hidden(true)) @@ -81,13 +77,13 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { .map_err(|e| anyhow::anyhow!("{}", e))?; let prefix = tree::Prefix::from_str(args.value_of("prefix").unwrap()) .map_err(|e| anyhow::anyhow!("{}", e))?; + let target = tree::Target::from_cli(args.value_of("target")); let opts = tree::TreeOptions { features: values(args, "features"), all_features: args.is_present("all-features"), no_default_features: args.is_present("no-default-features"), packages: args.packages_from_flags()?, - target: args.target(), - no_filter_targets: args.is_present("no-filter-targets"), + target, no_dev_dependencies: args.is_present("no-dev-dependencies"), invert: args.is_present("invert"), prefix, diff --git a/src/cargo/ops/tree/graph.rs b/src/cargo/ops/tree/graph.rs index e568b9bebbd..a537c9da560 100644 --- a/src/cargo/ops/tree/graph.rs +++ b/src/cargo/ops/tree/graph.rs @@ -315,6 +315,7 @@ fn add_pkg( let mut dep_name_map: HashMap> = HashMap::new(); let mut deps: Vec<_> = resolve.deps(package_id).collect(); deps.sort_unstable_by_key(|(dep_id, _)| *dep_id); + let show_all_targets = opts.target == super::Target::All; for (dep_id, deps) in deps { let mut deps: Vec<_> = deps .iter() @@ -328,7 +329,7 @@ fn add_pkg( (_, DepKind::Development) => node_kind, }; // Filter out inactivated targets. - if !opts.no_filter_targets && !target_data.dep_platform_activated(dep, kind) { + if !show_all_targets && !target_data.dep_platform_activated(dep, kind) { return false; } // Filter out dev-dependencies if requested. diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index 7f281ad838c..20125786c22 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -24,10 +24,7 @@ pub struct TreeOptions { /// The packages to display the tree for. pub packages: Packages, /// The platform to filter for. - /// If `None`, use the host platform. - pub target: Option, - /// If `true`, ignores the `target` field and returns all targets. - pub no_filter_targets: bool, + pub target: Target, pub no_dev_dependencies: bool, pub invert: bool, /// The style of prefix for each line. @@ -48,6 +45,23 @@ pub struct TreeOptions { pub graph_features: bool, } +#[derive(PartialEq)] +pub enum Target { + Host, + Specific(String), + All, +} + +impl Target { + pub fn from_cli(target: Option<&str>) -> Target { + match target { + None => Target::Host, + Some("all") => Target::All, + Some(target) => Target::Specific(target.to_string()), + } + } +} + pub enum Charset { Utf8, Ascii, @@ -108,13 +122,16 @@ static ASCII_SYMBOLS: Symbols = Symbols { /// Entry point for the `cargo tree` command. pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<()> { - if opts.no_filter_targets && opts.target.is_some() { - bail!("cannot specify both `--target` and `--no-filter-targets`"); - } if opts.graph_features && opts.duplicates { bail!("the `--graph-features` flag does not support `--duplicates`"); } - let requested_kind = CompileKind::from_requested_target(ws.config(), opts.target.as_deref())?; + let requested_target = match &opts.target { + Target::All | Target::Host => None, + Target::Specific(t) => Some(t.as_ref()), + }; + // TODO: Target::All is broken with -Zfeatures=itarget. To handle that properly, + // `FeatureResolver` will need to be taught what "all" means. + let requested_kind = CompileKind::from_requested_target(ws.config(), requested_target)?; let target_data = RustcTargetData::new(ws, requested_kind)?; let specs = opts.packages.to_package_id_specs(ws)?; let resolve_opts = ResolveOpts::new( diff --git a/src/doc/man/cargo-tree.adoc b/src/doc/man/cargo-tree.adoc index b6cfb9fd919..f3f18a5dfe1 100644 --- a/src/doc/man/cargo-tree.adoc +++ b/src/doc/man/cargo-tree.adoc @@ -68,11 +68,8 @@ only one instance is built. *--target* _TRIPLE_:: Filter dependencies matching the given target-triple. - The default is the host platform. - -*--no-filter-targets*:: - Show dependencies for all target platforms. Cannot be specified with - `--target`. + The default is the host platform. Use the value `all` to include *all* + targets. *--graph-features*:: Runs in a special mode where features are included as individual nodes. diff --git a/src/doc/man/generated/cargo-tree.html b/src/doc/man/generated/cargo-tree.html index a4964965c0f..50df0e0696d 100644 --- a/src/doc/man/generated/cargo-tree.html +++ b/src/doc/man/generated/cargo-tree.html @@ -81,12 +81,8 @@

Tree Options

--target TRIPLE

Filter dependencies matching the given target-triple. -The default is the host platform.

-
-
--no-filter-targets
-
-

Show dependencies for all target platforms. Cannot be specified with ---target.

+The default is the host platform. Use the value all to include all +targets.

--graph-features
diff --git a/src/etc/man/cargo-tree.1 b/src/etc/man/cargo-tree.1 index 30a5181f3ae..c6fcde393ee 100644 --- a/src/etc/man/cargo-tree.1 +++ b/src/etc/man/cargo-tree.1 @@ -95,13 +95,8 @@ Do not include dev\-dependencies. \fB\-\-target\fP \fITRIPLE\fP .RS 4 Filter dependencies matching the given target\-triple. -The default is the host platform. -.RE -.sp -\fB\-\-no\-filter\-targets\fP -.RS 4 -Show dependencies for all target platforms. Cannot be specified with -\fB\-\-target\fP. +The default is the host platform. Use the value \fBall\fP to include \fBall\fP +targets. .RE .sp \fB\-\-graph\-features\fP diff --git a/tests/testsuite/tree.rs b/tests/testsuite/tree.rs index bec947ba573..5fc8faf24bd 100644 --- a/tests/testsuite/tree.rs +++ b/tests/testsuite/tree.rs @@ -414,13 +414,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree --no-filter-targets --target") - .arg(alternate()) - .with_status(101) - .with_stderr("[ERROR] cannot specify both `--target` and `--no-filter-targets`") - .run(); - - p.cargo("tree --no-filter-targets") + p.cargo("tree --target=all") .with_stdout( "\ foo v0.1.0 ([..]/foo) diff --git a/tests/testsuite/tree_graph_features.rs b/tests/testsuite/tree_graph_features.rs index 4087ace5112..c7b0840b1ec 100644 --- a/tests/testsuite/tree_graph_features.rs +++ b/tests/testsuite/tree_graph_features.rs @@ -344,7 +344,7 @@ foo v0.1.0 ([..]/foo) ", ) .run(); - p.cargo("tree --graph-features --all-features --no-filter-targets") + p.cargo("tree --graph-features --all-features --target=all") .with_stdout( "\ foo v0.1.0 ([..]/foo) From afdcab7ced4b3218fc5f68f1fa8b336ef8f0081c Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 2 Apr 2020 13:00:33 -0700 Subject: [PATCH 09/14] Catch and display error about the --all flag. --- src/bin/cargo/commands/tree.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/bin/cargo/commands/tree.rs b/src/bin/cargo/commands/tree.rs index 9534b805265..bb23d647a2b 100644 --- a/src/bin/cargo/commands/tree.rs +++ b/src/bin/cargo/commands/tree.rs @@ -12,6 +12,7 @@ pub fn cli() -> App { "Display the tree for all packages in the workspace", "Exclude specific workspace members", ) + .arg(Arg::with_name("all").long("all").short("a").hidden(true)) .arg_features() .arg_target_triple( "Filter dependencies matching the given target-triple (default host platform)", @@ -72,6 +73,13 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { ) .into()); } + if args.is_present("all") { + return Err(anyhow::format_err!( + "The `cargo tree` --all flag has been changed to --no-dedupe.\n\ + If you are looking to display all workspace members, use the --workspace flag." + ) + .into()); + } let ws = args.workspace(config)?; let charset = tree::Charset::from_str(args.value_of("charset").unwrap()) .map_err(|e| anyhow::anyhow!("{}", e))?; From dd10b597ed79d726c266f8ba9a16001a1bcd30fb Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 2 Apr 2020 13:09:35 -0700 Subject: [PATCH 10/14] Add hint about removal of --all-targets --- src/bin/cargo/commands/tree.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/bin/cargo/commands/tree.rs b/src/bin/cargo/commands/tree.rs index bb23d647a2b..42b44fc1d4c 100644 --- a/src/bin/cargo/commands/tree.rs +++ b/src/bin/cargo/commands/tree.rs @@ -13,6 +13,11 @@ pub fn cli() -> App { "Exclude specific workspace members", ) .arg(Arg::with_name("all").long("all").short("a").hidden(true)) + .arg( + Arg::with_name("all-targets") + .long("all-targets") + .hidden(true), + ) .arg_features() .arg_target_triple( "Filter dependencies matching the given target-triple (default host platform)", @@ -80,6 +85,11 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { ) .into()); } + if args.is_present("all-targets") { + return Err( + anyhow::format_err!("the --all-targets flag has been changed to --target=all").into(), + ); + } let ws = args.workspace(config)?; let charset = tree::Charset::from_str(args.value_of("charset").unwrap()) .map_err(|e| anyhow::anyhow!("{}", e))?; From 3b1df3a46f16084722096b5f6418e5268fdf6eef Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 2 Apr 2020 15:12:17 -0700 Subject: [PATCH 11/14] Change --no-dev-dependencies to --dep-kinds. --- src/bin/cargo/commands/tree.rs | 94 +++++++++++++++++++++++---- src/cargo/ops/tree/graph.rs | 2 +- src/cargo/ops/tree/mod.rs | 9 +-- src/doc/man/cargo-tree.adoc | 14 +++- src/doc/man/generated/cargo-tree.html | 32 ++++++++- src/etc/man/cargo-tree.1 | 83 ++++++++++++++++++++++- tests/testsuite/tree.rs | 84 ++++++++++++++++++++++-- 7 files changed, 287 insertions(+), 31 deletions(-) diff --git a/src/bin/cargo/commands/tree.rs b/src/bin/cargo/commands/tree.rs index 42b44fc1d4c..db90b0e3dd6 100644 --- a/src/bin/cargo/commands/tree.rs +++ b/src/bin/cargo/commands/tree.rs @@ -1,5 +1,9 @@ use crate::command_prelude::*; +use anyhow::{bail, format_err}; +use cargo::core::dependency::DepKind; use cargo::ops::tree; +use cargo::util::CargoResult; +use std::collections::HashSet; use std::str::FromStr; pub fn cli() -> App { @@ -22,7 +26,19 @@ pub fn cli() -> App { .arg_target_triple( "Filter dependencies matching the given target-triple (default host platform)", ) - .arg(opt("no-dev-dependencies", "Skip dev dependencies")) + .arg( + Arg::with_name("no-dev-dependencies") + .long("no-dev-dependencies") + .hidden(true), + ) + .arg( + opt( + "dep-kinds", + "Dependency kinds to display \ + (normal, build, dev, no-dev, no-build, no-normal, all)", + ) + .value_name("KINDS"), + ) .arg(opt("invert", "Invert the tree direction").short("i")) .arg(Arg::with_name("no-indent").long("no-indent").hidden(true)) .arg( @@ -68,27 +84,28 @@ pub fn cli() -> App { pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { if args.is_present("no-indent") { - return Err( - anyhow::format_err!("the --no-indent flag has been changed to --prefix=none").into(), - ); + return Err(format_err!("the --no-indent flag has been changed to --prefix=none").into()); } if args.is_present("prefix-depth") { - return Err(anyhow::format_err!( - "the --prefix-depth flag has been changed to --prefix=depth" - ) - .into()); + return Err( + format_err!("the --prefix-depth flag has been changed to --prefix=depth").into(), + ); } if args.is_present("all") { - return Err(anyhow::format_err!( + return Err(format_err!( "The `cargo tree` --all flag has been changed to --no-dedupe.\n\ If you are looking to display all workspace members, use the --workspace flag." ) .into()); } if args.is_present("all-targets") { - return Err( - anyhow::format_err!("the --all-targets flag has been changed to --target=all").into(), - ); + return Err(format_err!("the --all-targets flag has been changed to --target=all").into()); + } + if args.is_present("no-dev-dependencies") { + return Err(format_err!( + "the --no-dev-dependencies flag has changed to --dep-kinds=no-dev" + ) + .into()); } let ws = args.workspace(config)?; let charset = tree::Charset::from_str(args.value_of("charset").unwrap()) @@ -96,13 +113,14 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { let prefix = tree::Prefix::from_str(args.value_of("prefix").unwrap()) .map_err(|e| anyhow::anyhow!("{}", e))?; let target = tree::Target::from_cli(args.value_of("target")); + let dep_kinds = parse_dep_kinds(args.value_of("dep-kinds"))?; let opts = tree::TreeOptions { features: values(args, "features"), all_features: args.is_present("all-features"), no_default_features: args.is_present("no-default-features"), packages: args.packages_from_flags()?, target, - no_dev_dependencies: args.is_present("no-dev-dependencies"), + dep_kinds, invert: args.is_present("invert"), prefix, no_dedupe: args.is_present("no-dedupe"), @@ -115,3 +133,53 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { tree::build_and_print(&ws, &opts)?; Ok(()) } + +fn parse_dep_kinds(kinds: Option<&str>) -> CargoResult> { + let kinds: Vec<&str> = kinds.unwrap_or("all").split(',').collect(); + let mut result = HashSet::new(); + let insert_all = |result: &mut HashSet| { + result.insert(DepKind::Normal); + result.insert(DepKind::Build); + result.insert(DepKind::Development); + }; + let unknown = |k| { + bail!( + "unknown dependency kind `{}`, valid values are \ + \"normal\", \"build\", \"dev\", \ + \"no-normal\", \"no-build\", \"no-dev\", \ + or \"all\"", + k + ) + }; + if kinds.iter().any(|k| k.starts_with("no-")) { + insert_all(&mut result); + for kind in &kinds { + match *kind { + "no-normal" => result.remove(&DepKind::Normal), + "no-build" => result.remove(&DepKind::Build), + "no-dev" => result.remove(&DepKind::Development), + "normal" | "build" | "dev" | "all" => { + bail!("`no-` dependency kinds cannot be mixed with other dependency kinds") + } + k => return unknown(k), + }; + } + return Ok(result); + } + for kind in kinds { + match kind { + "all" => insert_all(&mut result), + "normal" => { + result.insert(DepKind::Normal); + } + "build" => { + result.insert(DepKind::Build); + } + "dev" => { + result.insert(DepKind::Development); + } + k => return unknown(k), + } + } + Ok(result) +} diff --git a/src/cargo/ops/tree/graph.rs b/src/cargo/ops/tree/graph.rs index a537c9da560..8046cf752b2 100644 --- a/src/cargo/ops/tree/graph.rs +++ b/src/cargo/ops/tree/graph.rs @@ -333,7 +333,7 @@ fn add_pkg( return false; } // Filter out dev-dependencies if requested. - if opts.no_dev_dependencies && dep.kind() == DepKind::Development { + if !opts.dep_kinds.contains(&dep.kind()) { return false; } if dep.is_optional() { diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index 20125786c22..7a3347662b5 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -25,7 +25,8 @@ pub struct TreeOptions { pub packages: Packages, /// The platform to filter for. pub target: Target, - pub no_dev_dependencies: bool, + /// The dependency kinds to display. + pub dep_kinds: HashSet, pub invert: bool, /// The style of prefix for each line. pub prefix: Prefix, @@ -140,10 +141,10 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<() opts.all_features, !opts.no_default_features, ); - let has_dev = if opts.no_dev_dependencies { - HasDevUnits::No - } else { + let has_dev = if opts.dep_kinds.contains(&DepKind::Development) { HasDevUnits::Yes + } else { + HasDevUnits::No }; let ws_resolve = ops::resolve_ws_with_opts( ws, diff --git a/src/doc/man/cargo-tree.adoc b/src/doc/man/cargo-tree.adoc index f3f18a5dfe1..6e285d807d9 100644 --- a/src/doc/man/cargo-tree.adoc +++ b/src/doc/man/cargo-tree.adoc @@ -63,8 +63,18 @@ packages. You can then investigate if the package that depends on the duplicate with the older version can be updated to the newer version so that only one instance is built. -*--no-dev-dependencies*:: - Do not include dev-dependencies. +*--dep-kinds* KINDS:: + The dependency kinds to display. Takes a comma separated list of values: + + - `all` (default) — Show all dependency kinds. + - `normal` — Show normal dependencies. + - `build` — Show build dependencies. + - `dev` — Show development dependencies. + - `no-normal` — Do not include normal dependencies. + - `no-build` — Do not include build dependencies. + - `no-dev` — Do not include development dependencies. ++ +The `no-` prefixed options cannot be mixed with the other kinds. *--target* _TRIPLE_:: Filter dependencies matching the given target-triple. diff --git a/src/doc/man/generated/cargo-tree.html b/src/doc/man/generated/cargo-tree.html index 50df0e0696d..d88c2893992 100644 --- a/src/doc/man/generated/cargo-tree.html +++ b/src/doc/man/generated/cargo-tree.html @@ -74,9 +74,37 @@

Tree Options

only one instance is built.

-
--no-dev-dependencies
+
--dep-kinds KINDS
-

Do not include dev-dependencies.

+

The dependency kinds to display. Takes a comma separated list of values:

+
+
    +
  • +

    all (default) — Show all dependency kinds.

    +
  • +
  • +

    normal — Show normal dependencies.

    +
  • +
  • +

    build — Show build dependencies.

    +
  • +
  • +

    dev — Show development dependencies.

    +
  • +
  • +

    no-normal — Do not include normal dependencies.

    +
  • +
  • +

    no-build — Do not include build dependencies.

    +
  • +
  • +

    no-dev — Do not include development dependencies.

    +
    +

    The no- prefixed options cannot be mixed with the other kinds.

    +
    +
  • +
+
--target TRIPLE
diff --git a/src/etc/man/cargo-tree.1 b/src/etc/man/cargo-tree.1 index c6fcde393ee..eaa9ad9599e 100644 --- a/src/etc/man/cargo-tree.1 +++ b/src/etc/man/cargo-tree.1 @@ -87,9 +87,88 @@ duplicate with the older version can be updated to the newer version so that only one instance is built. .RE .sp -\fB\-\-no\-dev\-dependencies\fP +\fB\-\-dep\-kinds\fP KINDS .RS 4 -Do not include dev\-dependencies. +The dependency kinds to display. Takes a comma separated list of values: +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fBall\fP (default) — Show all dependency kinds. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fBnormal\fP — Show normal dependencies. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fBbuild\fP — Show build dependencies. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fBdev\fP — Show development dependencies. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fBno\-normal\fP — Do not include normal dependencies. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fBno\-build\fP — Do not include build dependencies. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +\fBno\-dev\fP — Do not include development dependencies. +.sp +The \fBno\-\fP prefixed options cannot be mixed with the other kinds. +.RE .RE .sp \fB\-\-target\fP \fITRIPLE\fP diff --git a/tests/testsuite/tree.rs b/tests/testsuite/tree.rs index 5fc8faf24bd..f588e53fb98 100644 --- a/tests/testsuite/tree.rs +++ b/tests/testsuite/tree.rs @@ -435,8 +435,25 @@ foo v0.1.0 ([..]/foo) } #[cargo_test] -fn no_dev_dependencies() { - Package::new("devdep", "1.0.0").publish(); +fn dep_kinds() { + Package::new("inner-devdep", "1.0.0").publish(); + Package::new("inner-builddep", "1.0.0").publish(); + Package::new("inner-normal", "1.0.0").publish(); + Package::new("normaldep", "1.0.0") + .dep("inner-normal", "1.0") + .dev_dep("inner-devdep", "1.0") + .build_dep("inner-builddep", "1.0") + .publish(); + Package::new("devdep", "1.0.0") + .dep("inner-normal", "1.0") + .dev_dep("inner-devdep", "1.0") + .build_dep("inner-builddep", "1.0") + .publish(); + Package::new("builddep", "1.0.0") + .dep("inner-normal", "1.0") + .dev_dep("inner-devdep", "1.0") + .build_dep("inner-builddep", "1.0") + .publish(); let p = project() .file( "Cargo.toml", @@ -445,8 +462,14 @@ fn no_dev_dependencies() { name = "foo" version = "0.1.0" + [dependencies] + normaldep = "1.0" + [dev-dependencies] devdep = "1.0" + + [build-dependencies] + builddep = "1.0" "#, ) .file("src/lib.rs", "") @@ -455,17 +478,64 @@ fn no_dev_dependencies() { p.cargo("tree") .with_stdout( "\ -foo v0.1.0 ([..]foo) +foo v0.1.0 ([..]/foo) +└── normaldep v1.0.0 + └── inner-normal v1.0.0 + [build-dependencies] + └── inner-builddep v1.0.0 +[build-dependencies] +└── builddep v1.0.0 + └── inner-normal v1.0.0 + [build-dependencies] + └── inner-builddep v1.0.0 [dev-dependencies] └── devdep v1.0.0 + └── inner-normal v1.0.0 + [build-dependencies] + └── inner-builddep v1.0.0 +", + ) + .run(); + + p.cargo("tree --dep-kinds=no-dev") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +└── normaldep v1.0.0 + └── inner-normal v1.0.0 + [build-dependencies] + └── inner-builddep v1.0.0 +[build-dependencies] +└── builddep v1.0.0 + └── inner-normal v1.0.0 + [build-dependencies] + └── inner-builddep v1.0.0 +", + ) + .run(); + + p.cargo("tree --dep-kinds=normal") + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +└── normaldep v1.0.0 + └── inner-normal v1.0.0 ", ) .run(); - p.cargo("tree --no-dev-dependencies") + p.cargo("tree --dep-kinds=dev,build") .with_stdout( "\ -foo v0.1.0 ([..]foo) +foo v0.1.0 ([..]/foo) +[build-dependencies] +└── builddep v1.0.0 + [build-dependencies] + └── inner-builddep v1.0.0 +[dev-dependencies] +└── devdep v1.0.0 + [build-dependencies] + └── inner-builddep v1.0.0 ", ) .run(); @@ -959,7 +1029,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree --no-dev-dependencies") + p.cargo("tree --dep-kinds=normal") .with_stdout( "\ foo v0.1.0 ([..]/foo) @@ -982,7 +1052,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree --no-dev-dependencies -Zfeatures=dev_dep") + p.cargo("tree --dep-kinds=normal -Zfeatures=dev_dep") .masquerade_as_nightly_cargo() .with_stdout( "\ From 5ccc5e01775d1322f0cd6f8ad395c9536a6b7012 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 2 Apr 2020 15:23:48 -0700 Subject: [PATCH 12/14] Change old flags to warnings instead of errors. Except --all which is ambiguous which flag it means. --- src/bin/cargo/commands/tree.rs | 57 +++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/bin/cargo/commands/tree.rs b/src/bin/cargo/commands/tree.rs index db90b0e3dd6..64411c3dd6b 100644 --- a/src/bin/cargo/commands/tree.rs +++ b/src/bin/cargo/commands/tree.rs @@ -83,14 +83,21 @@ pub fn cli() -> App { } pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { - if args.is_present("no-indent") { - return Err(format_err!("the --no-indent flag has been changed to --prefix=none").into()); - } - if args.is_present("prefix-depth") { - return Err( - format_err!("the --prefix-depth flag has been changed to --prefix=depth").into(), - ); - } + let prefix = if args.is_present("no-indent") { + config + .shell() + .warn("the --no-indent flag has been changed to --prefix=none")?; + "none" + } else if args.is_present("prefix-depth") { + config + .shell() + .warn("the --prefix-depth flag has been changed to --prefix=depth")?; + "depth" + } else { + args.value_of("prefix").unwrap() + }; + let prefix = tree::Prefix::from_str(prefix).map_err(|e| anyhow::anyhow!("{}", e))?; + if args.is_present("all") { return Err(format_err!( "The `cargo tree` --all flag has been changed to --no-dedupe.\n\ @@ -98,22 +105,30 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { ) .into()); } - if args.is_present("all-targets") { - return Err(format_err!("the --all-targets flag has been changed to --target=all").into()); - } - if args.is_present("no-dev-dependencies") { - return Err(format_err!( - "the --no-dev-dependencies flag has changed to --dep-kinds=no-dev" - ) - .into()); - } + + let target = if args.is_present("all-targets") { + config + .shell() + .warn("the --all-targets flag has been changed to --target=all")?; + Some("all") + } else { + args.value_of("target") + }; + let target = tree::Target::from_cli(target); + + let dep_kinds = if args.is_present("no-dev-dependencies") { + config + .shell() + .warn("the --no-dev-dependencies flag has changed to --dep-kinds=no-dev")?; + Some("no-dev") + } else { + args.value_of("dep-kinds") + }; + let dep_kinds = parse_dep_kinds(dep_kinds)?; + let ws = args.workspace(config)?; let charset = tree::Charset::from_str(args.value_of("charset").unwrap()) .map_err(|e| anyhow::anyhow!("{}", e))?; - let prefix = tree::Prefix::from_str(args.value_of("prefix").unwrap()) - .map_err(|e| anyhow::anyhow!("{}", e))?; - let target = tree::Target::from_cli(args.value_of("target")); - let dep_kinds = parse_dep_kinds(args.value_of("dep-kinds"))?; let opts = tree::TreeOptions { features: values(args, "features"), all_features: args.is_present("all-features"), From 96ff434cb96bd386309f811147bc3a52fbfcbf24 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Fri, 3 Apr 2020 17:48:09 -0700 Subject: [PATCH 13/14] Rename --dep-kinds to --edges and fold in --graph-features. --- src/bin/cargo/commands/tree.rs | 87 +++++++++++++++----------- src/cargo/ops/tree/graph.rs | 2 +- src/cargo/ops/tree/mod.rs | 9 ++- src/doc/man/cargo-tree.adoc | 54 +++++++++++----- src/doc/man/generated/cargo-tree.html | 63 +++++++++++++------ src/etc/man/cargo-tree.1 | 71 ++++++++++++++------- tests/testsuite/tree.rs | 10 +-- tests/testsuite/tree_graph_features.rs | 26 ++++---- 8 files changed, 207 insertions(+), 115 deletions(-) diff --git a/src/bin/cargo/commands/tree.rs b/src/bin/cargo/commands/tree.rs index 64411c3dd6b..831d54849da 100644 --- a/src/bin/cargo/commands/tree.rs +++ b/src/bin/cargo/commands/tree.rs @@ -1,7 +1,7 @@ use crate::command_prelude::*; use anyhow::{bail, format_err}; use cargo::core::dependency::DepKind; -use cargo::ops::tree; +use cargo::ops::tree::{self, EdgeKind}; use cargo::util::CargoResult; use std::collections::HashSet; use std::str::FromStr; @@ -32,12 +32,13 @@ pub fn cli() -> App { .hidden(true), ) .arg( - opt( - "dep-kinds", - "Dependency kinds to display \ - (normal, build, dev, no-dev, no-build, no-normal, all)", + multi_opt( + "edges", + "KINDS", + "The kinds of dependencies to display \ + (features, normal, build, dev, all, no-dev, no-build, no-normal)", ) - .value_name("KINDS"), + .short("e"), ) .arg(opt("invert", "Invert the tree direction").short("i")) .arg(Arg::with_name("no-indent").long("no-indent").hidden(true)) @@ -79,7 +80,6 @@ pub fn cli() -> App { .short("f") .default_value("{p}"), ) - .arg(opt("graph-features", "Include features in the tree")) } pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { @@ -116,15 +116,8 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { }; let target = tree::Target::from_cli(target); - let dep_kinds = if args.is_present("no-dev-dependencies") { - config - .shell() - .warn("the --no-dev-dependencies flag has changed to --dep-kinds=no-dev")?; - Some("no-dev") - } else { - args.value_of("dep-kinds") - }; - let dep_kinds = parse_dep_kinds(dep_kinds)?; + let edge_kinds = parse_edge_kinds(config, args)?; + let graph_features = edge_kinds.contains(&EdgeKind::Feature); let ws = args.workspace(config)?; let charset = tree::Charset::from_str(args.value_of("charset").unwrap()) @@ -135,44 +128,57 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { no_default_features: args.is_present("no-default-features"), packages: args.packages_from_flags()?, target, - dep_kinds, + edge_kinds, invert: args.is_present("invert"), prefix, no_dedupe: args.is_present("no-dedupe"), duplicates: args.is_present("duplicates"), charset, format: args.value_of("format").unwrap().to_string(), - graph_features: args.is_present("graph-features"), + graph_features, }; tree::build_and_print(&ws, &opts)?; Ok(()) } -fn parse_dep_kinds(kinds: Option<&str>) -> CargoResult> { - let kinds: Vec<&str> = kinds.unwrap_or("all").split(',').collect(); +fn parse_edge_kinds(config: &Config, args: &ArgMatches<'_>) -> CargoResult> { + let mut kinds: Vec<&str> = args + .values_of("edges") + .map_or_else(|| Vec::new(), |es| es.flat_map(|e| e.split(',')).collect()); + if args.is_present("no-dev-dependencies") { + config + .shell() + .warn("the --no-dev-dependencies flag has changed to -e=no-dev")?; + kinds.push("no-dev"); + } + if kinds.len() == 0 { + kinds.extend(&["normal", "build", "dev"]); + } + let mut result = HashSet::new(); - let insert_all = |result: &mut HashSet| { - result.insert(DepKind::Normal); - result.insert(DepKind::Build); - result.insert(DepKind::Development); + let insert_defaults = |result: &mut HashSet| { + result.insert(EdgeKind::Dep(DepKind::Normal)); + result.insert(EdgeKind::Dep(DepKind::Build)); + result.insert(EdgeKind::Dep(DepKind::Development)); }; let unknown = |k| { bail!( - "unknown dependency kind `{}`, valid values are \ + "unknown edge kind `{}`, valid values are \ \"normal\", \"build\", \"dev\", \ \"no-normal\", \"no-build\", \"no-dev\", \ - or \"all\"", + \"features\", or \"all\"", k ) }; if kinds.iter().any(|k| k.starts_with("no-")) { - insert_all(&mut result); + insert_defaults(&mut result); for kind in &kinds { match *kind { - "no-normal" => result.remove(&DepKind::Normal), - "no-build" => result.remove(&DepKind::Build), - "no-dev" => result.remove(&DepKind::Development), + "no-normal" => result.remove(&EdgeKind::Dep(DepKind::Normal)), + "no-build" => result.remove(&EdgeKind::Dep(DepKind::Build)), + "no-dev" => result.remove(&EdgeKind::Dep(DepKind::Development)), + "features" => result.insert(EdgeKind::Feature), "normal" | "build" | "dev" | "all" => { bail!("`no-` dependency kinds cannot be mixed with other dependency kinds") } @@ -181,20 +187,29 @@ fn parse_dep_kinds(kinds: Option<&str>) -> CargoResult> { } return Ok(result); } - for kind in kinds { - match kind { - "all" => insert_all(&mut result), + for kind in &kinds { + match *kind { + "all" => { + insert_defaults(&mut result); + result.insert(EdgeKind::Feature); + } + "features" => { + result.insert(EdgeKind::Feature); + } "normal" => { - result.insert(DepKind::Normal); + result.insert(EdgeKind::Dep(DepKind::Normal)); } "build" => { - result.insert(DepKind::Build); + result.insert(EdgeKind::Dep(DepKind::Build)); } "dev" => { - result.insert(DepKind::Development); + result.insert(EdgeKind::Dep(DepKind::Development)); } k => return unknown(k), } } + if kinds.len() == 1 && kinds[0] == "features" { + insert_defaults(&mut result); + } Ok(result) } diff --git a/src/cargo/ops/tree/graph.rs b/src/cargo/ops/tree/graph.rs index 8046cf752b2..a221da9ea7f 100644 --- a/src/cargo/ops/tree/graph.rs +++ b/src/cargo/ops/tree/graph.rs @@ -333,7 +333,7 @@ fn add_pkg( return false; } // Filter out dev-dependencies if requested. - if !opts.dep_kinds.contains(&dep.kind()) { + if !opts.edge_kinds.contains(&EdgeKind::Dep(dep.kind())) { return false; } if dep.is_optional() { diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index 7a3347662b5..5d603a16a79 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -26,7 +26,7 @@ pub struct TreeOptions { /// The platform to filter for. pub target: Target, /// The dependency kinds to display. - pub dep_kinds: HashSet, + pub edge_kinds: HashSet, pub invert: bool, /// The style of prefix for each line. pub prefix: Prefix, @@ -124,7 +124,7 @@ static ASCII_SYMBOLS: Symbols = Symbols { /// Entry point for the `cargo tree` command. pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<()> { if opts.graph_features && opts.duplicates { - bail!("the `--graph-features` flag does not support `--duplicates`"); + bail!("the `-e features` flag does not support `--duplicates`"); } let requested_target = match &opts.target { Target::All | Target::Host => None, @@ -141,7 +141,10 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<() opts.all_features, !opts.no_default_features, ); - let has_dev = if opts.dep_kinds.contains(&DepKind::Development) { + let has_dev = if opts + .edge_kinds + .contains(&EdgeKind::Dep(DepKind::Development)) + { HasDevUnits::Yes } else { HasDevUnits::No diff --git a/src/doc/man/cargo-tree.adoc b/src/doc/man/cargo-tree.adoc index 6e285d807d9..f7086044814 100644 --- a/src/doc/man/cargo-tree.adoc +++ b/src/doc/man/cargo-tree.adoc @@ -14,7 +14,8 @@ cargo-tree - Display a tree visualization of a dependency graph == DESCRIPTION -This command will display a tree of dependencies to the terminal. An example of a simple project that depends on the "rand" package: +This command will display a tree of dependencies to the terminal. An example +of a simple project that depends on the "rand" package: ---- myproject v0.1.0 (/myproject) @@ -36,6 +37,24 @@ Packages marked with `(*)` have been "de-duplicated". The dependencies for the package have already been shown elswhere in the graph, and so are not repeated. Use the `--no-dedupe` option to repeat the duplicates. +The `-e` flag can be used to select the dependency kinds to display. The +"features" kind changes the output to display the features enabled by +each dependency. For example, `cargo tree -e features`: + +---- +myproject v0.1.0 (/myproject) +└── log feature "serde" + └── log v0.4.8 + ├── serde v1.0.106 + └── cfg-if feature "default" + └── cfg-if v0.1.10 +---- + +In this tree, `myproject` depends on `log` with the `serde` feature. `log` in +turn depends on `cfg-if` with "default" features. When using `-e features` it +can be helpful to use `-i` and `-p` flags to show how the features flow into a +package. See the examples below for more detail. + == OPTIONS === Tree Options @@ -63,31 +82,30 @@ packages. You can then investigate if the package that depends on the duplicate with the older version can be updated to the newer version so that only one instance is built. -*--dep-kinds* KINDS:: +*-e* _KINDS_:: +*--edges* _KINDS_:: The dependency kinds to display. Takes a comma separated list of values: - - - `all` (default) — Show all dependency kinds. ++ + - `all` — Show all edge kinds. - `normal` — Show normal dependencies. - `build` — Show build dependencies. - `dev` — Show development dependencies. + - `features` — Show features enabled by each dependency. If this is + the only kind given, then it will automatically include the other + dependency kinds. - `no-normal` — Do not include normal dependencies. - `no-build` — Do not include build dependencies. - `no-dev` — Do not include development dependencies. + -The `no-` prefixed options cannot be mixed with the other kinds. +The `no-` prefixed options cannot be mixed with the other dependency kinds. ++ +The default is `normal,build,dev`. *--target* _TRIPLE_:: Filter dependencies matching the given target-triple. The default is the host platform. Use the value `all` to include *all* targets. -*--graph-features*:: - Runs in a special mode where features are included as individual nodes. - This is intended to be used to help explain why a feature is enabled on - any particular package. It is recommended to use with the `-p` and `-i` - flags to show how the features flow into the package. See the examples - below for more detail. - === Tree Formatting Options *--charset* _CHARSET_:: @@ -158,9 +176,11 @@ include::section-exit-status.adoc[] . Explain why features are enabled for the given package: - cargo tree --graph-features -i -p syn + cargo tree -e features -i -p syn + -An example of what this would display: +The `-e features` flag is used to show features. The `-i` flag is used to +invert the graph so that it displays the packages that depend on `syn` (not +what `syn` depends on). An example of what this would display: + ---- syn v1.0.17 @@ -187,9 +207,9 @@ syn v1.0.17 ---- + To read this graph, you can follow the chain for each feature from the root to -see why it was included. For example, the "full" feature was added by the -`rustversion` crate which was included from `myproject` (with the default -features), and `myproject` was the package selected on the command-line. All +see why it is included. For example, the "full" feature is added by the +`rustversion` crate which is included from `myproject` (with the default +features), and `myproject` is the package selected on the command-line. All of the other `syn` features are added by the "default" feature ("quote" is added by "printing" and "proc-macro", both of which are default features). + diff --git a/src/doc/man/generated/cargo-tree.html b/src/doc/man/generated/cargo-tree.html index d88c2893992..2c000d327af 100644 --- a/src/doc/man/generated/cargo-tree.html +++ b/src/doc/man/generated/cargo-tree.html @@ -14,7 +14,8 @@

SYNOPSIS

DESCRIPTION

-

This command will display a tree of dependencies to the terminal. An example of a simple project that depends on the "rand" package:

+

This command will display a tree of dependencies to the terminal. An example +of a simple project that depends on the "rand" package:

@@ -38,6 +39,27 @@

DESCRIPTION

package have already been shown elswhere in the graph, and so are not repeated. Use the --no-dedupe option to repeat the duplicates.

+
+

The -e flag can be used to select the dependency kinds to display. The +"features" kind changes the output to display the features enabled by +each dependency. For example, cargo tree -e features:

+
+
+
+
myproject v0.1.0 (/myproject)
+└── log feature "serde"
+    └── log v0.4.8
+        ├── serde v1.0.106
+        └── cfg-if feature "default"
+            └── cfg-if v0.1.10
+
+
+
+

In this tree, myproject depends on log with the serde feature. log in +turn depends on cfg-if with "default" features. When using -e features it +can be helpful to use -i and -p flags to show how the features flow into a +package. See the examples below for more detail.

+
@@ -74,13 +96,14 @@

Tree Options

only one instance is built.

-
--dep-kinds KINDS
+
-e KINDS
+
--edges KINDS

The dependency kinds to display. Takes a comma separated list of values:

  • -

    all (default) — Show all dependency kinds.

    +

    all — Show all edge kinds.

  • normal — Show normal dependencies.

    @@ -92,6 +115,11 @@

    Tree Options

    dev — Show development dependencies.

  • +

    features — Show features enabled by each dependency. If this is +the only kind given, then it will automatically include the other +dependency kinds.

    +
  • +
  • no-normal — Do not include normal dependencies.

  • @@ -99,12 +127,15 @@

    Tree Options

  • no-dev — Do not include development dependencies.

    -
    -

    The no- prefixed options cannot be mixed with the other kinds.

    -
+
+

The no- prefixed options cannot be mixed with the other dependency kinds.

+
+
+

The default is normal,build,dev.

+
--target TRIPLE
@@ -112,14 +143,6 @@

Tree Options

The default is the host platform. Use the value all to include all targets.

-
--graph-features
-
-

Runs in a special mode where features are included as individual nodes. -This is intended to be used to help explain why a feature is enabled on -any particular package. It is recommended to use with the -p and -i -flags to show how the features flow into the package. See the examples -below for more detail.

-
@@ -417,11 +440,13 @@

EXAMPLES

Explain why features are enabled for the given package:

-
cargo tree --graph-features -i -p syn
+
cargo tree -e features -i -p syn
-

An example of what this would display:

+

The -e features flag is used to show features. The -i flag is used to +invert the graph so that it displays the packages that depend on syn (not +what syn depends on). An example of what this would display:

@@ -450,9 +475,9 @@

EXAMPLES

To read this graph, you can follow the chain for each feature from the root to -see why it was included. For example, the "full" feature was added by the -rustversion crate which was included from myproject (with the default -features), and myproject was the package selected on the command-line. All +see why it is included. For example, the "full" feature is added by the +rustversion crate which is included from myproject (with the default +features), and myproject is the package selected on the command-line. All of the other syn features are added by the "default" feature ("quote" is added by "printing" and "proc-macro", both of which are default features).

diff --git a/src/etc/man/cargo-tree.1 b/src/etc/man/cargo-tree.1 index eaa9ad9599e..0cadfaaeb2b 100644 --- a/src/etc/man/cargo-tree.1 +++ b/src/etc/man/cargo-tree.1 @@ -2,12 +2,12 @@ .\" Title: cargo-tree .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.10 -.\" Date: 2020-04-02 +.\" Date: 2020-04-03 .\" Manual: \ \& .\" Source: \ \& .\" Language: English .\" -.TH "CARGO\-TREE" "1" "2020-04-02" "\ \&" "\ \&" +.TH "CARGO\-TREE" "1" "2020-04-03" "\ \&" "\ \&" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -34,7 +34,8 @@ cargo\-tree \- Display a tree visualization of a dependency graph \fBcargo tree [\fIOPTIONS\fP]\fP .SH "DESCRIPTION" .sp -This command will display a tree of dependencies to the terminal. An example of a simple project that depends on the "rand" package: +This command will display a tree of dependencies to the terminal. An example +of a simple project that depends on the "rand" package: .sp .if n .RS 4 .nf @@ -57,6 +58,26 @@ myproject v0.1.0 (/myproject) Packages marked with \fB(*)\fP have been "de\-duplicated". The dependencies for the package have already been shown elswhere in the graph, and so are not repeated. Use the \fB\-\-no\-dedupe\fP option to repeat the duplicates. +.sp +The \fB\-e\fP flag can be used to select the dependency kinds to display. The +"features" kind changes the output to display the features enabled by +each dependency. For example, \fBcargo tree \-e features\fP: +.sp +.if n .RS 4 +.nf +myproject v0.1.0 (/myproject) +└── log feature "serde" + └── log v0.4.8 + ├── serde v1.0.106 + └── cfg\-if feature "default" + └── cfg\-if v0.1.10 +.fi +.if n .RE +.sp +In this tree, \fBmyproject\fP depends on \fBlog\fP with the \fBserde\fP feature. \fBlog\fP in +turn depends on \fBcfg\-if\fP with "default" features. When using \fB\-e features\fP it +can be helpful to use \fB\-i\fP and \fB\-p\fP flags to show how the features flow into a +package. See the examples below for more detail. .SH "OPTIONS" .SS "Tree Options" .sp @@ -87,7 +108,7 @@ duplicate with the older version can be updated to the newer version so that only one instance is built. .RE .sp -\fB\-\-dep\-kinds\fP KINDS +\fB\-e\fP \fIKINDS\fP, \fB\-\-edges\fP \fIKINDS\fP .RS 4 The dependency kinds to display. Takes a comma separated list of values: .sp @@ -99,7 +120,7 @@ The dependency kinds to display. Takes a comma separated list of values: . sp -1 . IP \(bu 2.3 .\} -\fBall\fP (default) — Show all dependency kinds. +\fBall\fP — Show all edge kinds. .RE .sp .RS 4 @@ -143,6 +164,19 @@ The dependency kinds to display. Takes a comma separated list of values: . sp -1 . IP \(bu 2.3 .\} +\fBfeatures\fP — Show features enabled by each dependency. If this is +the only kind given, then it will automatically include the other +dependency kinds. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} \fBno\-normal\fP — Do not include normal dependencies. .RE .sp @@ -166,9 +200,11 @@ The dependency kinds to display. Takes a comma separated list of values: . IP \(bu 2.3 .\} \fBno\-dev\fP — Do not include development dependencies. -.sp -The \fBno\-\fP prefixed options cannot be mixed with the other kinds. .RE +.sp +The \fBno\-\fP prefixed options cannot be mixed with the other dependency kinds. +.sp +The default is \fBnormal,build,dev\fP. .RE .sp \fB\-\-target\fP \fITRIPLE\fP @@ -177,15 +213,6 @@ Filter dependencies matching the given target\-triple. The default is the host platform. Use the value \fBall\fP to include \fBall\fP targets. .RE -.sp -\fB\-\-graph\-features\fP -.RS 4 -Runs in a special mode where features are included as individual nodes. -This is intended to be used to help explain why a feature is enabled on -any particular package. It is recommended to use with the \fB\-p\fP and \fB\-i\fP -flags to show how the features flow into the package. See the examples -below for more detail. -.RE .SS "Tree Formatting Options" .sp \fB\-\-charset\fP \fICHARSET\fP @@ -545,11 +572,13 @@ Explain why features are enabled for the given package: .sp .if n .RS 4 .nf -cargo tree \-\-graph\-features \-i \-p syn +cargo tree \-e features \-i \-p syn .fi .if n .RE .sp -An example of what this would display: +The \fB\-e features\fP flag is used to show features. The \fB\-i\fP flag is used to +invert the graph so that it displays the packages that depend on \fBsyn\fP (not +what \fBsyn\fP depends on). An example of what this would display: .sp .if n .RS 4 .nf @@ -578,9 +607,9 @@ syn v1.0.17 .if n .RE .sp To read this graph, you can follow the chain for each feature from the root to -see why it was included. For example, the "full" feature was added by the -\fBrustversion\fP crate which was included from \fBmyproject\fP (with the default -features), and \fBmyproject\fP was the package selected on the command\-line. All +see why it is included. For example, the "full" feature is added by the +\fBrustversion\fP crate which is included from \fBmyproject\fP (with the default +features), and \fBmyproject\fP is the package selected on the command\-line. All of the other \fBsyn\fP features are added by the "default" feature ("quote" is added by "printing" and "proc\-macro", both of which are default features). .sp diff --git a/tests/testsuite/tree.rs b/tests/testsuite/tree.rs index f588e53fb98..097f7c0ba39 100644 --- a/tests/testsuite/tree.rs +++ b/tests/testsuite/tree.rs @@ -497,7 +497,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree --dep-kinds=no-dev") + p.cargo("tree -e no-dev") .with_stdout( "\ foo v0.1.0 ([..]/foo) @@ -514,7 +514,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree --dep-kinds=normal") + p.cargo("tree -e normal") .with_stdout( "\ foo v0.1.0 ([..]/foo) @@ -524,7 +524,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree --dep-kinds=dev,build") + p.cargo("tree -e dev,build") .with_stdout( "\ foo v0.1.0 ([..]/foo) @@ -1029,7 +1029,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree --dep-kinds=normal") + p.cargo("tree -e normal") .with_stdout( "\ foo v0.1.0 ([..]/foo) @@ -1052,7 +1052,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree --dep-kinds=normal -Zfeatures=dev_dep") + p.cargo("tree -e normal -Zfeatures=dev_dep") .masquerade_as_nightly_cargo() .with_stdout( "\ diff --git a/tests/testsuite/tree_graph_features.rs b/tests/testsuite/tree_graph_features.rs index c7b0840b1ec..e7ee106a3b9 100644 --- a/tests/testsuite/tree_graph_features.rs +++ b/tests/testsuite/tree_graph_features.rs @@ -1,4 +1,4 @@ -//! Tests for the `cargo tree` command with --graph-features option. +//! Tests for the `cargo tree` command with -e features option. use cargo_test_support::project; use cargo_test_support::registry::{Dependency, Package}; @@ -49,7 +49,7 @@ fn dep_feature_various() { .file("src/lib.rs", "") .build(); - p.cargo("tree --graph-features") + p.cargo("tree -e features") .with_stdout( "\ foo v0.1.0 ([..]/foo) @@ -132,7 +132,7 @@ fn graph_features_ws_interdependent() { .file("b/src/lib.rs", "") .build(); - p.cargo("tree --graph-features") + p.cargo("tree -e features") .with_stdout( "\ a v0.1.0 ([..]/foo/a) @@ -148,7 +148,7 @@ b v0.1.0 ([..]/foo/b) ) .run(); - p.cargo("tree --graph-features -i") + p.cargo("tree -e features -i") .with_stdout( "\ a v0.1.0 ([..]/foo/a) @@ -199,7 +199,7 @@ fn slash_feature_name() { .file("src/lib.rs", "") .build(); - p.cargo("tree --graph-features --features f1") + p.cargo("tree -e features --features f1") .with_stdout( "\ foo v0.1.0 ([..]/foo) @@ -211,7 +211,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree --graph-features --features f1 -i") + p.cargo("tree -e features --features f1 -i") .with_stdout( "\ foo v0.1.0 ([..]/foo) @@ -223,7 +223,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree --graph-features --features f1 -p notopt -i") + p.cargo("tree -e features --features f1 -p notopt -i") .with_stdout( "\ notopt v1.0.0 @@ -241,7 +241,7 @@ notopt v1.0.0 ) .run(); - p.cargo("tree --graph-features --features notopt/animal -p notopt -i") + p.cargo("tree -e features --features notopt/animal -p notopt -i") .with_stdout( "\ notopt v1.0.0 @@ -255,7 +255,7 @@ notopt v1.0.0 ) .run(); - p.cargo("tree --graph-features --all-features") + p.cargo("tree -e features --all-features") .with_stdout( "\ foo v0.1.0 ([..]/foo) @@ -269,7 +269,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree --graph-features --all-features -p opt2 -i") + p.cargo("tree -e features --all-features -p opt2 -i") .with_stdout( "\ opt2 v1.0.0 @@ -326,7 +326,7 @@ fn features_enables_inactive_target() { ) .file("src/lib.rs", "") .build(); - p.cargo("tree --graph-features") + p.cargo("tree -e features") .with_stdout( "\ foo v0.1.0 ([..]/foo) @@ -335,7 +335,7 @@ foo v0.1.0 ([..]/foo) ", ) .run(); - p.cargo("tree --graph-features --all-features") + p.cargo("tree -e features --all-features") .with_stdout( "\ foo v0.1.0 ([..]/foo) @@ -344,7 +344,7 @@ foo v0.1.0 ([..]/foo) ", ) .run(); - p.cargo("tree --graph-features --all-features --target=all") + p.cargo("tree -e features --all-features --target=all") .with_stdout( "\ foo v0.1.0 ([..]/foo) From 7c403447bdc9caff42f1521c2436fe8e53e38e33 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Fri, 3 Apr 2020 19:52:21 -0700 Subject: [PATCH 14/14] Switch --invert to take the package name as an argument. --- src/bin/cargo/commands/tree.rs | 45 ++++++++++- src/cargo/ops/tree/mod.rs | 16 +++- src/doc/asciidoc-extension.rb | 15 ++++ src/doc/man/cargo-tree.adoc | 31 +++++--- src/doc/man/generated/cargo-tree.html | 32 +++++--- src/etc/man/cargo-tree.1 | 101 +++++++++++++------------ tests/testsuite/tree.rs | 20 ++--- tests/testsuite/tree_graph_features.rs | 10 +-- 8 files changed, 177 insertions(+), 93 deletions(-) diff --git a/src/bin/cargo/commands/tree.rs b/src/bin/cargo/commands/tree.rs index 831d54849da..a20bb9bfa2b 100644 --- a/src/bin/cargo/commands/tree.rs +++ b/src/bin/cargo/commands/tree.rs @@ -2,6 +2,7 @@ use crate::command_prelude::*; use anyhow::{bail, format_err}; use cargo::core::dependency::DepKind; use cargo::ops::tree::{self, EdgeKind}; +use cargo::ops::Packages; use cargo::util::CargoResult; use std::collections::HashSet; use std::str::FromStr; @@ -40,7 +41,14 @@ pub fn cli() -> App { ) .short("e"), ) - .arg(opt("invert", "Invert the tree direction").short("i")) + .arg( + optional_multi_opt( + "invert", + "SPEC", + "Invert the tree direction and focus on the given package", + ) + .short("i"), + ) .arg(Arg::with_name("no-indent").long("no-indent").hidden(true)) .arg( Arg::with_name("prefix-depth") @@ -119,6 +127,37 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { let edge_kinds = parse_edge_kinds(config, args)?; let graph_features = edge_kinds.contains(&EdgeKind::Feature); + let packages = args.packages_from_flags()?; + let mut invert = args + .values_of("invert") + .map_or_else(|| Vec::new(), |is| is.map(|s| s.to_string()).collect()); + if args.is_present_with_zero_values("invert") { + match &packages { + Packages::Packages(ps) => { + // Backwards compatibility with old syntax of `cargo tree -i -p foo`. + invert.extend(ps.clone()); + } + _ => { + return Err(format_err!( + "The `-i` flag requires a package name.\n\ +\n\ +The `-i` flag is used to inspect the reverse dependencies of a specific\n\ +package. It will invert the tree and display the packages that depend on the\n\ +given package.\n\ +\n\ +Note that in a workspace, by default it will only display the package's\n\ +reverse dependencies inside the tree of the workspace member in the current\n\ +directory. The --workspace flag can be used to extend it so that it will show\n\ +the package's reverse dependencies across the entire workspace. The -p flag\n\ +can be used to display the package's reverse dependencies only with the\n\ +subtree of the package given to -p.\n\ +" + ) + .into()); + } + } + } + let ws = args.workspace(config)?; let charset = tree::Charset::from_str(args.value_of("charset").unwrap()) .map_err(|e| anyhow::anyhow!("{}", e))?; @@ -126,10 +165,10 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult { features: values(args, "features"), all_features: args.is_present("all-features"), no_default_features: args.is_present("no-default-features"), - packages: args.packages_from_flags()?, + packages, target, edge_kinds, - invert: args.is_present("invert"), + invert, prefix, no_dedupe: args.is_present("no-dedupe"), duplicates: args.is_present("duplicates"), diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index 5d603a16a79..fe4f56d1f25 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -4,7 +4,7 @@ use self::format::Pattern; use crate::core::compiler::{CompileKind, RustcTargetData}; use crate::core::dependency::DepKind; use crate::core::resolver::{HasDevUnits, ResolveOpts}; -use crate::core::{Package, PackageId, Workspace}; +use crate::core::{Package, PackageId, PackageIdSpec, Workspace}; use crate::ops::{self, Packages}; use crate::util::CargoResult; use anyhow::{bail, Context}; @@ -27,7 +27,7 @@ pub struct TreeOptions { pub target: Target, /// The dependency kinds to display. pub edge_kinds: HashSet, - pub invert: bool, + pub invert: Vec, /// The style of prefix for each line. pub prefix: Prefix, /// If `true`, duplicates will be repeated. @@ -177,7 +177,15 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<() opts, )?; - let root_ids = ws_resolve.targeted_resolve.specs_to_ids(&specs)?; + let root_specs = if opts.invert.is_empty() { + specs + } else { + opts.invert + .iter() + .map(|p| PackageIdSpec::parse(p)) + .collect::>>()? + }; + 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 { @@ -188,7 +196,7 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<() root_indexes }; - if opts.invert || opts.duplicates { + if !opts.invert.is_empty() || opts.duplicates { graph.invert(); } diff --git a/src/doc/asciidoc-extension.rb b/src/doc/asciidoc-extension.rb index 87f4f2a2731..1fc8e99d96b 100644 --- a/src/doc/asciidoc-extension.rb +++ b/src/doc/asciidoc-extension.rb @@ -84,6 +84,20 @@ def process document, output end end +# Man pages are ASCII only. Unfortunately asciidoc doesn't process these +# characters for us. The `cargo tree` manpage needs a little assistance. +class SpecialCharPostprocessor < Extensions::Postprocessor + def process document, output + if document.basebackend? 'manpage' + output = output.gsub(/│/, '|') + .gsub(/├/, '|') + .gsub(/└/, '`') + .gsub(/─/, '\-') + end + output + end +end + # General utility for converting text. Example: # # convert:lowercase[{somevar}] @@ -107,4 +121,5 @@ def process parent, target, attrs inline_macro LinkCargoInlineMacro inline_macro ConvertInlineMacro postprocessor MonoPostprocessor + postprocessor SpecialCharPostprocessor end diff --git a/src/doc/man/cargo-tree.adoc b/src/doc/man/cargo-tree.adoc index f7086044814..86b3276a48e 100644 --- a/src/doc/man/cargo-tree.adoc +++ b/src/doc/man/cargo-tree.adoc @@ -52,17 +52,24 @@ myproject v0.1.0 (/myproject) In this tree, `myproject` depends on `log` with the `serde` feature. `log` in turn depends on `cfg-if` with "default" features. When using `-e features` it -can be helpful to use `-i` and `-p` flags to show how the features flow into a -package. See the examples below for more detail. +can be helpful to use `-i` flag to show how the features flow into a package. +See the examples below for more detail. == OPTIONS === Tree Options -*-i*:: -*--invert*:: - Invert the tree. Typically this flag is used with the `-p` flag to show - the dependencies for a specific package. +*-i* _SPEC_:: +*--invert* _SPEC_:: + Show the reverse dependencies for the given package. This flag will invert + the tree and display the packages that depend on the given package. ++ +Note that in a workspace, by default it will only display the package's +reverse dependencies inside the tree of the workspace member in the current +directory. The `--workspace` flag can be used to extend it so that it will +show the package's reverse dependencies across the entire workspace. The `-p` +flag can be used to display the package's reverse dependencies only with the +subtree of the package given to `-p`. *--no-dedupe*:: Do not de-duplicate repeated dependencies. Usually, when a package has @@ -161,9 +168,9 @@ include::section-exit-status.adoc[] cargo tree -. Display all the packages that depend on the specified package: +. Display all the packages that depend on the `syn` package: - cargo tree -i -p syn + cargo tree -i syn . Show the features enabled on each package: @@ -174,13 +181,13 @@ include::section-exit-status.adoc[] cargo tree -d -. Explain why features are enabled for the given package: +. Explain why features are enabled for the `syn` package: - cargo tree -e features -i -p syn + cargo tree -e features -i syn + The `-e features` flag is used to show features. The `-i` flag is used to -invert the graph so that it displays the packages that depend on `syn` (not -what `syn` depends on). An example of what this would display: +invert the graph so that it displays the packages that depend on `syn`. An +example of what this would display: + ---- syn v1.0.17 diff --git a/src/doc/man/generated/cargo-tree.html b/src/doc/man/generated/cargo-tree.html index 2c000d327af..8b822936a7a 100644 --- a/src/doc/man/generated/cargo-tree.html +++ b/src/doc/man/generated/cargo-tree.html @@ -57,8 +57,8 @@

DESCRIPTION

In this tree, myproject depends on log with the serde feature. log in turn depends on cfg-if with "default" features. When using -e features it -can be helpful to use -i and -p flags to show how the features flow into a -package. See the examples below for more detail.

+can be helpful to use -i flag to show how the features flow into a package. +See the examples below for more detail.

@@ -69,11 +69,19 @@

OPTIONS

Tree Options

-
-i
-
--invert
+
-i SPEC
+
--invert SPEC
-

Invert the tree. Typically this flag is used with the -p flag to show -the dependencies for a specific package.

+

Show the reverse dependencies for the given package. This flag will invert +the tree and display the packages that depend on the given package.

+
+

Note that in a workspace, by default it will only display the package’s +reverse dependencies inside the tree of the workspace member in the current +directory. The --workspace flag can be used to extend it so that it will +show the package’s reverse dependencies across the entire workspace. The -p +flag can be used to display the package’s reverse dependencies only with the +subtree of the package given to -p.

+
--no-dedupe
@@ -412,10 +420,10 @@

EXAMPLES

  • -

    Display all the packages that depend on the specified package:

    +

    Display all the packages that depend on the syn package:

    -
    cargo tree -i -p syn
    +
    cargo tree -i syn
  • @@ -437,16 +445,16 @@

    EXAMPLES

  • -

    Explain why features are enabled for the given package:

    +

    Explain why features are enabled for the syn package:

    -
    cargo tree -e features -i -p syn
    +
    cargo tree -e features -i syn

    The -e features flag is used to show features. The -i flag is used to -invert the graph so that it displays the packages that depend on syn (not -what syn depends on). An example of what this would display:

    +invert the graph so that it displays the packages that depend on syn. An +example of what this would display:

    diff --git a/src/etc/man/cargo-tree.1 b/src/etc/man/cargo-tree.1 index 0cadfaaeb2b..24031b3e29e 100644 --- a/src/etc/man/cargo-tree.1 +++ b/src/etc/man/cargo-tree.1 @@ -40,18 +40,18 @@ of a simple project that depends on the "rand" package: .if n .RS 4 .nf myproject v0.1.0 (/myproject) -└── rand v0.7.3 - ├── getrandom v0.1.14 - │ ├── cfg\-if v0.1.10 - │ └── libc v0.2.68 - ├── libc v0.2.68 (*) - ├── rand_chacha v0.2.2 - │ ├── ppv\-lite86 v0.2.6 - │ └── rand_core v0.5.1 - │ └── getrandom v0.1.14 (*) - └── rand_core v0.5.1 (*) +`\-\- rand v0.7.3 + |\-\- getrandom v0.1.14 + | |\-\- cfg\-if v0.1.10 + | `\-\- libc v0.2.68 + |\-\- libc v0.2.68 (*) + |\-\- rand_chacha v0.2.2 + | |\-\- ppv\-lite86 v0.2.6 + | `\-\- rand_core v0.5.1 + | `\-\- getrandom v0.1.14 (*) + `\-\- rand_core v0.5.1 (*) [build\-dependencies] -└── cc v1.0.50 +`\-\- cc v1.0.50 .fi .if n .RE .sp @@ -66,25 +66,32 @@ each dependency. For example, \fBcargo tree \-e features\fP: .if n .RS 4 .nf myproject v0.1.0 (/myproject) -└── log feature "serde" - └── log v0.4.8 - ├── serde v1.0.106 - └── cfg\-if feature "default" - └── cfg\-if v0.1.10 +`\-\- log feature "serde" + `\-\- log v0.4.8 + |\-\- serde v1.0.106 + `\-\- cfg\-if feature "default" + `\-\- cfg\-if v0.1.10 .fi .if n .RE .sp In this tree, \fBmyproject\fP depends on \fBlog\fP with the \fBserde\fP feature. \fBlog\fP in turn depends on \fBcfg\-if\fP with "default" features. When using \fB\-e features\fP it -can be helpful to use \fB\-i\fP and \fB\-p\fP flags to show how the features flow into a -package. See the examples below for more detail. +can be helpful to use \fB\-i\fP flag to show how the features flow into a package. +See the examples below for more detail. .SH "OPTIONS" .SS "Tree Options" .sp -\fB\-i\fP, \fB\-\-invert\fP +\fB\-i\fP \fISPEC\fP, \fB\-\-invert\fP \fISPEC\fP .RS 4 -Invert the tree. Typically this flag is used with the \fB\-p\fP flag to show -the dependencies for a specific package. +Show the reverse dependencies for the given package. This flag will invert +the tree and display the packages that depend on the given package. +.sp +Note that in a workspace, by default it will only display the package\(cqs +reverse dependencies inside the tree of the workspace member in the current +directory. The \fB\-\-workspace\fP flag can be used to extend it so that it will +show the package\(cqs reverse dependencies across the entire workspace. The \fB\-p\fP +flag can be used to display the package\(cqs reverse dependencies only with the +subtree of the package given to \fB\-p\fP. .RE .sp \fB\-\-no\-dedupe\fP @@ -516,11 +523,11 @@ cargo tree . sp -1 . IP " 2." 4.2 .\} -Display all the packages that depend on the specified package: +Display all the packages that depend on the \fBsyn\fP package: .sp .if n .RS 4 .nf -cargo tree \-i \-p syn +cargo tree \-i syn .fi .if n .RE .RE @@ -568,41 +575,41 @@ cargo tree \-d . sp -1 . IP " 5." 4.2 .\} -Explain why features are enabled for the given package: +Explain why features are enabled for the \fBsyn\fP package: .sp .if n .RS 4 .nf -cargo tree \-e features \-i \-p syn +cargo tree \-e features \-i syn .fi .if n .RE .sp The \fB\-e features\fP flag is used to show features. The \fB\-i\fP flag is used to -invert the graph so that it displays the packages that depend on \fBsyn\fP (not -what \fBsyn\fP depends on). An example of what this would display: +invert the graph so that it displays the packages that depend on \fBsyn\fP. An +example of what this would display: .sp .if n .RS 4 .nf syn v1.0.17 -├── syn feature "clone\-impls" -│ └── syn feature "default" -│ └── rustversion v1.0.2 -│ └── rustversion feature "default" -│ └── myproject v0.1.0 (/myproject) -│ └── myproject feature "default" (command\-line) -├── syn feature "default" (*) -├── syn feature "derive" -│ └── syn feature "default" (*) -├── syn feature "full" -│ └── rustversion v1.0.2 (*) -├── syn feature "parsing" -│ └── syn feature "default" (*) -├── syn feature "printing" -│ └── syn feature "default" (*) -├── syn feature "proc\-macro" -│ └── syn feature "default" (*) -└── syn feature "quote" - ├── syn feature "printing" (*) - └── syn feature "proc\-macro" (*) +|\-\- syn feature "clone\-impls" +| `\-\- syn feature "default" +| `\-\- rustversion v1.0.2 +| `\-\- rustversion feature "default" +| `\-\- myproject v0.1.0 (/myproject) +| `\-\- myproject feature "default" (command\-line) +|\-\- syn feature "default" (*) +|\-\- syn feature "derive" +| `\-\- syn feature "default" (*) +|\-\- syn feature "full" +| `\-\- rustversion v1.0.2 (*) +|\-\- syn feature "parsing" +| `\-\- syn feature "default" (*) +|\-\- syn feature "printing" +| `\-\- syn feature "default" (*) +|\-\- syn feature "proc\-macro" +| `\-\- syn feature "default" (*) +`\-\- syn feature "quote" + |\-\- syn feature "printing" (*) + `\-\- syn feature "proc\-macro" (*) .fi .if n .RE .sp diff --git a/tests/testsuite/tree.rs b/tests/testsuite/tree.rs index 097f7c0ba39..a8059228d7f 100644 --- a/tests/testsuite/tree.rs +++ b/tests/testsuite/tree.rs @@ -582,7 +582,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree --invert") + p.cargo("tree --invert foo") .with_stdout( "\ foo v0.1.0 ([..]/foo) @@ -630,7 +630,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree --invert -p c") + p.cargo("tree --invert c") .with_stdout( "\ c v1.0.0 @@ -644,7 +644,7 @@ c v1.0.0 #[cargo_test] fn invert_with_build_dep() { - // -i with -p for a common dependency between normal and build deps. + // -i for a common dependency between normal and build deps. Package::new("common", "1.0.0").publish(); Package::new("bdep", "1.0.0").dep("common", "1.0").publish(); let p = project() @@ -677,7 +677,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree -i -p common") + p.cargo("tree -i common") .with_stdout( "\ common v1.0.0 @@ -1136,8 +1136,8 @@ bar v1.0.0 ) .run(); - // invert -p - p.cargo("tree -i -p optdep") + // invert + p.cargo("tree -i optdep") .with_stdout( "\ optdep v1.0.0 @@ -1149,7 +1149,7 @@ optdep v1.0.0 ) .run(); - p.cargo("tree -i -p optdep -Zfeatures=host_dep") + p.cargo("tree -i optdep -Zfeatures=host_dep") .masquerade_as_nightly_cargo() .with_stdout( "\ @@ -1252,8 +1252,8 @@ somedep v1.0.0 ) .run(); - // invert -p - p.cargo("tree -i -p somedep") + // invert + p.cargo("tree -i somedep") .with_stdout( "\ somedep v1.0.0 @@ -1264,7 +1264,7 @@ somedep v1.0.0 ) .run(); - p.cargo("tree -i -p somedep -Zfeatures=host_dep") + p.cargo("tree -i somedep -Zfeatures=host_dep") .masquerade_as_nightly_cargo() .with_stdout( "\ diff --git a/tests/testsuite/tree_graph_features.rs b/tests/testsuite/tree_graph_features.rs index e7ee106a3b9..2c90482ef80 100644 --- a/tests/testsuite/tree_graph_features.rs +++ b/tests/testsuite/tree_graph_features.rs @@ -148,7 +148,7 @@ b v0.1.0 ([..]/foo/b) ) .run(); - p.cargo("tree -e features -i") + p.cargo("tree -e features -i a -i b") .with_stdout( "\ a v0.1.0 ([..]/foo/a) @@ -211,7 +211,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree -e features --features f1 -i") + p.cargo("tree -e features --features f1 -i foo") .with_stdout( "\ foo v0.1.0 ([..]/foo) @@ -223,7 +223,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree -e features --features f1 -p notopt -i") + p.cargo("tree -e features --features f1 -i notopt") .with_stdout( "\ notopt v1.0.0 @@ -241,7 +241,7 @@ notopt v1.0.0 ) .run(); - p.cargo("tree -e features --features notopt/animal -p notopt -i") + p.cargo("tree -e features --features notopt/animal -i notopt") .with_stdout( "\ notopt v1.0.0 @@ -269,7 +269,7 @@ foo v0.1.0 ([..]/foo) ) .run(); - p.cargo("tree -e features --all-features -p opt2 -i") + p.cargo("tree -e features --all-features -i opt2") .with_stdout( "\ opt2 v1.0.0