Add cargo tree command.

This commit is contained in:
Eric Huss 2020-03-19 10:51:25 -07:00
parent c75216fc55
commit 96a393719b
30 changed files with 4165 additions and 53 deletions

View File

@ -27,6 +27,7 @@ pub fn builtin() -> Vec<App> {
rustdoc::cli(), rustdoc::cli(),
search::cli(), search::cli(),
test::cli(), test::cli(),
tree::cli(),
uninstall::cli(), uninstall::cli(),
update::cli(), update::cli(),
vendor::cli(), vendor::cli(),
@ -63,6 +64,7 @@ pub fn builtin_exec(cmd: &str) -> Option<fn(&mut Config, &ArgMatches<'_>) -> Cli
"rustdoc" => rustdoc::exec, "rustdoc" => rustdoc::exec,
"search" => search::exec, "search" => search::exec,
"test" => test::exec, "test" => test::exec,
"tree" => tree::exec,
"uninstall" => uninstall::exec, "uninstall" => uninstall::exec,
"update" => update::exec, "update" => update::exec,
"vendor" => vendor::exec, "vendor" => vendor::exec,
@ -99,6 +101,7 @@ pub mod rustc;
pub mod rustdoc; pub mod rustdoc;
pub mod search; pub mod search;
pub mod test; pub mod test;
pub mod tree;
pub mod uninstall; pub mod uninstall;
pub mod update; pub mod update;
pub mod vendor; pub mod vendor;

View File

@ -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(())
}

View File

@ -1,4 +1,4 @@
use crate::core::compiler::{CompileKind, CompileTarget}; use crate::core::compiler::CompileKind;
use crate::core::interning::InternedString; use crate::core::interning::InternedString;
use crate::util::ProcessBuilder; use crate::util::ProcessBuilder;
use crate::util::{CargoResult, Config, RustfixDiagnosticServer}; use crate::util::{CargoResult, Config, RustfixDiagnosticServer};
@ -45,22 +45,8 @@ impl BuildConfig {
mode: CompileMode, mode: CompileMode,
) -> CargoResult<BuildConfig> { ) -> CargoResult<BuildConfig> {
let cfg = config.build_config()?; let cfg = config.build_config()?;
let requested_kind = match requested_target { let requested_kind =
Some(s) => CompileKind::Target(CompileTarget::new(s)?), CompileKind::from_requested_target(config, requested_target.as_deref())?;
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,
},
};
if jobs == Some(0) { if jobs == Some(0) {
anyhow::bail!("jobs must be at least 1") anyhow::bail!("jobs must be at least 1")
} }

View File

@ -1,5 +1,6 @@
use crate::core::{InternedString, Target}; use crate::core::{InternedString, Target};
use crate::util::errors::{CargoResult, CargoResultExt}; use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::Config;
use serde::Serialize; use serde::Serialize;
use std::path::Path; use std::path::Path;
@ -39,6 +40,32 @@ impl CompileKind {
CompileKind::Target(n) => CompileKind::Target(n), 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<CompileKind> {
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 { impl serde::ser::Serialize for CompileKind {

View File

@ -998,11 +998,7 @@ impl UnitFor {
} }
pub(crate) fn map_to_features_for(&self) -> FeaturesFor { pub(crate) fn map_to_features_for(&self) -> FeaturesFor {
if self.is_for_host_features() { FeaturesFor::from_for_host(self.is_for_host_features())
FeaturesFor::HostDep
} else {
FeaturesFor::NormalOrDev
}
} }
} }

View File

@ -92,13 +92,23 @@ pub enum HasDevUnits {
} }
/// Flag to indicate if features are requested for a build dependency or not. /// Flag to indicate if features are requested for a build dependency or not.
#[derive(Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum FeaturesFor { pub enum FeaturesFor {
NormalOrDev, NormalOrDev,
/// Build dependency or proc-macro. /// Build dependency or proc-macro.
HostDep, HostDep,
} }
impl FeaturesFor {
pub fn from_for_host(for_host: bool) -> FeaturesFor {
if for_host {
FeaturesFor::HostDep
} else {
FeaturesFor::NormalOrDev
}
}
}
impl FeatureOpts { impl FeatureOpts {
fn new(config: &Config, has_dev_units: HasDevUnits) -> CargoResult<FeatureOpts> { fn new(config: &Config, has_dev_units: HasDevUnits) -> CargoResult<FeatureOpts> {
let mut opts = FeatureOpts::default(); let mut opts = FeatureOpts::default();

View File

@ -305,6 +305,10 @@ unable to verify that `{0}` is the same as when the lockfile was generated
PackageIdSpec::query_str(spec, self.iter()) PackageIdSpec::query_str(spec, self.iter())
} }
pub fn specs_to_ids(&self, specs: &[PackageIdSpec]) -> CargoResult<Vec<PackageId>> {
specs.iter().map(|s| s.query(self.iter())).collect()
}
pub fn unused_patches(&self) -> &[PackageId] { pub fn unused_patches(&self) -> &[PackageId] {
&self.unused_patches &self.unused_patches
} }

View File

@ -351,12 +351,8 @@ pub fn compile_ws<'a>(
// Find the packages in the resolver that the user wants to build (those // 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 // passed in with `-p` or the defaults from the workspace), and convert
// Vec<PackageIdSpec> to a Vec<&PackageId>. // Vec<PackageIdSpec> to a Vec<PackageId>.
let to_build_ids = specs let to_build_ids = resolve.specs_to_ids(&specs)?;
.iter()
.map(|s| s.query(resolve.iter()))
.collect::<CargoResult<Vec<_>>>()?;
// Now get the `Package` for each `PackageId`. This may trigger a download // Now get the `Package` for each `PackageId`. This may trigger a download
// if the user specified `-p` for a dependency that is not downloaded. // if the user specified `-p` for a dependency that is not downloaded.
// Dependencies will be downloaded during build_unit_dependencies. // Dependencies will be downloaded during build_unit_dependencies.
@ -753,12 +749,8 @@ fn generate_targets<'a>(
bcx.profiles bcx.profiles
.get_profile(pkg.package_id(), ws.is_member(pkg), unit_for, target_mode); .get_profile(pkg.package_id(), ws.is_member(pkg), unit_for, target_mode);
let features_for = if target.proc_macro() { // No need to worry about build-dependencies, roots are never build dependencies.
FeaturesFor::HostDep let features_for = FeaturesFor::from_for_host(target.proc_macro());
} else {
// Root units are never build dependencies.
FeaturesFor::NormalOrDev
};
let features = let features =
Vec::from(resolved_features.activated_features(pkg.package_id(), features_for)); Vec::from(resolved_features.activated_features(pkg.package_id(), features_for));
bcx.units.intern( bcx.units.intern(
@ -969,11 +961,7 @@ pub fn resolve_all_features(
.expect("packages downloaded") .expect("packages downloaded")
.proc_macro(); .proc_macro();
for dep in deps { for dep in deps {
let features_for = if is_proc_macro || dep.is_build() { let features_for = FeaturesFor::from_for_host(is_proc_macro || dep.is_build());
FeaturesFor::HostDep
} else {
FeaturesFor::NormalOrDev
};
for feature in resolved_features.activated_features_unverified(dep_id, features_for) { for feature in resolved_features.activated_features_unverified(dep_id, features_for) {
features.insert(format!("{}/{}", dep.name_in_toml(), feature)); features.insert(format!("{}/{}", dep.name_in_toml(), feature));
} }

View File

@ -36,10 +36,7 @@ pub fn doc(ws: &Workspace<'_>, options: &DocOptions) -> CargoResult<()> {
HasDevUnits::No, HasDevUnits::No,
)?; )?;
let ids = specs let ids = ws_resolve.targeted_resolve.specs_to_ids(&specs)?;
.iter()
.map(|s| s.query(ws_resolve.targeted_resolve.iter()))
.collect::<CargoResult<Vec<_>>>()?;
let pkgs = ws_resolve.pkg_set.get_many(ids)?; let pkgs = ws_resolve.pkg_set.get_many(ids)?;
let mut lib_names = HashMap::new(); let mut lib_names = HashMap::new();

View File

@ -48,4 +48,5 @@ mod fix;
mod lockfile; mod lockfile;
mod registry; mod registry;
mod resolve; mod resolve;
pub mod tree;
mod vendor; mod vendor;

View File

@ -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<Chunk>);
impl Pattern {
pub fn new(format: &str) -> Result<Pattern, Error> {
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(())
}
}

View File

@ -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<str::CharIndices<'a>>,
}
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<RawChunk<'a>> {
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,
}
}
}

562
src/cargo/ops/tree/graph.rs Normal file
View File

@ -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<InternedString>,
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<Edge, Vec<usize>>);
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<Node>,
edges: Vec<Edges>,
/// Index maps a node to an index, for fast lookup.
index: HashMap<Node, usize>,
/// Map for looking up packages.
package_map: HashMap<PackageId, &'a Package>,
/// 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<usize>,
/// 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<usize, HashMap<InternedString, HashSet<(usize, bool)>>>,
}
impl<'a> Graph<'a> {
fn new(package_map: HashMap<PackageId, &'a Package>) -> 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<Vec<usize>> {
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<usize> {
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<Option<usize>> = vec![None; self.nodes.len()];
fn visit(
graph: &Graph<'_>,
new_graph: &mut Graph<'_>,
remap: &mut Vec<Option<usize>>,
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<usize> {
// 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<PackageId, &'a Package>,
opts: &TreeOptions,
) -> CargoResult<Graph<'a>> {
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<InternedString, HashSet<(usize, bool)>> = 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<usize>,
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<InternedString> = 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);
}
}
}
}
}

333
src/cargo/ops/tree/mod.rs Normal file
View File

@ -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<String>,
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<String>,
/// 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<Charset, &'static str> {
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<PackageId, &Package> = 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<usize>, 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<usize>,
levels_continue: &mut Vec<bool>,
print_stack: &mut Vec<usize>,
) {
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<usize>,
levels_continue: &mut Vec<bool>,
print_stack: &mut Vec<usize>,
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();
}
}

View File

@ -37,6 +37,20 @@ pub trait AppExt: Sized {
._arg(multi_opt("exclude", "SPEC", exclude)) ._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 { fn arg_package_spec_simple(self, package: &'static str) -> Self {
self._arg(multi_opt("package", "SPEC", package).short("p")) self._arg(multi_opt("package", "SPEC", package).short("p"))
} }
@ -362,6 +376,15 @@ pub trait ArgMatchesExt {
} }
} }
fn packages_from_flags(&self) -> CargoResult<Packages> {
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( fn compile_options(
&self, &self,
config: &Config, config: &Config,
@ -369,13 +392,7 @@ pub trait ArgMatchesExt {
workspace: Option<&Workspace<'_>>, workspace: Option<&Workspace<'_>>,
profile_checking: ProfileChecking, profile_checking: ProfileChecking,
) -> CargoResult<CompileOptions> { ) -> CargoResult<CompileOptions> {
let spec = Packages::from_flags( let spec = self.packages_from_flags()?;
// TODO Integrate into 'workspace'
self._is_present("workspace") || self._is_present("all"),
self._values_of("exclude"),
self._values_of("package"),
)?;
let mut message_format = None; let mut message_format = None;
let default_json = MessageFormat::Json { let default_json = MessageFormat::Json {
short: false, short: false,

193
src/doc/man/cargo-tree.adoc Normal file
View File

@ -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]

View File

@ -71,6 +71,9 @@ man:cargo-metadata[1]::
man:cargo-pkgid[1]:: man:cargo-pkgid[1]::
Print a fully qualified package specification. Print a fully qualified package specification.
man:cargo-tree[1]::
Display a tree visualization of a dependency graph.
man:cargo-update[1]:: man:cargo-update[1]::
Update dependencies as recorded in the local lock file. Update dependencies as recorded in the local lock file.

View File

@ -0,0 +1,443 @@
<h2 id="cargo_tree_name">NAME</h2>
<div class="sectionbody">
<p>cargo-tree - Display a tree visualization of a dependency graph</p>
</div>
<div class="sect1">
<h2 id="cargo_tree_synopsis">SYNOPSIS</h2>
<div class="sectionbody">
<div class="paragraph">
<p><code>cargo tree [<em>OPTIONS</em>]</code></p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="cargo_tree_description">DESCRIPTION</h2>
<div class="sectionbody">
<div class="paragraph">
<p>This command will display a tree of dependencies to the terminal. An example of a simple project that depends on the "rand" package:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>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</pre>
</div>
</div>
<div class="paragraph">
<p>Packages marked with <code>(*)</code> have been "de-duplicated". The dependencies for the
package have already been shown elswhere in the graph, and so are not
repeated. Use the <code>--no-dedupe</code> option to repeat the duplicates.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="cargo_tree_options">OPTIONS</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="cargo_tree_tree_options">Tree Options</h3>
<div class="dlist">
<dl>
<dt class="hdlist1"><strong>-i</strong></dt>
<dt class="hdlist1"><strong>--invert</strong></dt>
<dd>
<p>Invert the tree. Typically this flag is used with the <code>-p</code> flag to show
the dependencies for a specific package.</p>
</dd>
<dt class="hdlist1"><strong>--no-dedupe</strong></dt>
<dd>
<p>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 <code>(*)</code> to indicate it has
already been shown. This flag will cause those duplicates to be repeated.</p>
</dd>
<dt class="hdlist1"><strong>-d</strong></dt>
<dt class="hdlist1"><strong>--duplicates</strong></dt>
<dd>
<p>Show only dependencies which come in multiple versions (implies
<code>--invert</code>). When used with the <code>-p</code> flag, only shows duplicates within
the subtree of the given package.</p>
<div class="paragraph">
<p>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.</p>
</div>
</dd>
<dt class="hdlist1"><strong>--no-dev-dependencies</strong></dt>
<dd>
<p>Do not include dev-dependencies.</p>
</dd>
<dt class="hdlist1"><strong>--target</strong> <em>TRIPLE</em></dt>
<dd>
<p>Filter dependencies matching the given target-triple.
The default is the host platform.</p>
</dd>
<dt class="hdlist1"><strong>--no-filter-targets</strong></dt>
<dd>
<p>Show dependencies for all target platforms. Cannot be specified with
<code>--target</code>.</p>
</dd>
<dt class="hdlist1"><strong>--graph-features</strong></dt>
<dd>
<p>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 <code>-p</code> and <code>-i</code>
flags to show how the features flow into the package. See the examples
below for more detail.</p>
</dd>
</dl>
</div>
</div>
<div class="sect2">
<h3 id="cargo_tree_tree_formatting_options">Tree Formatting Options</h3>
<div class="dlist">
<dl>
<dt class="hdlist1"><strong>--charset</strong> <em>CHARSET</em></dt>
<dd>
<p>Chooses the character set to use for the tree. Valid values are "utf8" or
"ascii". Default is "utf8".</p>
</dd>
<dt class="hdlist1"><strong>-f</strong> <em>FORMAT</em></dt>
<dt class="hdlist1"><strong>--format</strong> <em>FORMAT</em></dt>
<dd>
<p>Set the format string for each package. The default is "{p}".</p>
<div class="paragraph">
<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>
</div>
<div class="ulist">
<ul>
<li>
<p><code>{p}</code> The package name.</p>
</li>
<li>
<p><code>{l}</code> The package license.</p>
</li>
<li>
<p><code>{r}</code> The package repository URL.</p>
</li>
<li>
<p><code>{f}</code> — Comma-separated list of package features that are enabled.</p>
</li>
</ul>
</div>
</dd>
<dt class="hdlist1"><strong>--no-indent</strong></dt>
<dd>
<p>Display the dependencies as a list (rather than a tree).</p>
</dd>
<dt class="hdlist1"><strong>--prefix-depth</strong></dt>
<dd>
<p>Display the dependencies as a list (rather than a tree), but prefixed with
the depth.</p>
</dd>
</dl>
</div>
</div>
<div class="sect2">
<h3 id="cargo_tree_package_selection">Package Selection</h3>
<div class="paragraph">
<p>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
<code>--manifest-path</code> 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.</p>
</div>
<div class="paragraph">
<p>The default members of a workspace can be set explicitly with the
<code>workspace.default-members</code> key in the root manifest. If this is not set, a
virtual workspace will include all workspace members (equivalent to passing
<code>--workspace</code>), and a non-virtual workspace will include only the root crate itself.</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1"><strong>-p</strong> <em>SPEC</em>&#8230;&#8203;</dt>
<dt class="hdlist1"><strong>--package</strong> <em>SPEC</em>&#8230;&#8203;</dt>
<dd>
<p>Display only the specified packages. See <a href="cargo-pkgid.html">cargo-pkgid(1)</a> for the
SPEC format. This flag may be specified multiple times.</p>
</dd>
<dt class="hdlist1"><strong>--workspace</strong></dt>
<dd>
<p>Display all members in the workspace.</p>
</dd>
<dt class="hdlist1"><strong>--exclude</strong> <em>SPEC</em>&#8230;&#8203;</dt>
<dd>
<p>Exclude the specified packages. Must be used in conjunction with the
<code>--workspace</code> flag. This flag may be specified multiple times.</p>
</dd>
</dl>
</div>
</div>
<div class="sect2">
<h3 id="cargo_tree_manifest_options">Manifest Options</h3>
<div class="dlist">
<dl>
<dt class="hdlist1"><strong>--manifest-path</strong> <em>PATH</em></dt>
<dd>
<p>Path to the <code>Cargo.toml</code> file. By default, Cargo searches for the
<code>Cargo.toml</code> file in the current directory or any parent directory.</p>
</dd>
</dl>
</div>
</div>
<div class="sect2">
<h3 id="cargo_tree_feature_selection">Feature Selection</h3>
<div class="paragraph">
<p>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 <code>--manifest-path</code>. If running in the root of a virtual
workspace, then the default features are selected for all workspace members,
or all features if <code>--all-features</code> is specified.</p>
</div>
<div class="paragraph">
<p>When no feature options are given, the <code>default</code> feature is activated for
every selected package.</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1"><strong>--features</strong> <em>FEATURES</em></dt>
<dd>
<p>Space or comma separated list of features to activate. These features only
apply to the current directory&#8217;s package. Features of direct dependencies
may be enabled with <code>&lt;dep-name&gt;/&lt;feature-name&gt;</code> syntax. This flag may be
specified multiple times, which enables all specified features.</p>
</dd>
<dt class="hdlist1"><strong>--all-features</strong></dt>
<dd>
<p>Activate all available features of all selected packages.</p>
</dd>
<dt class="hdlist1"><strong>--no-default-features</strong></dt>
<dd>
<p>Do not activate the <code>default</code> feature of the current directory&#8217;s
package.</p>
</dd>
</dl>
</div>
</div>
<div class="sect2">
<h3 id="cargo_tree_display_options">Display Options</h3>
<div class="dlist">
<dl>
<dt class="hdlist1"><strong>-v</strong></dt>
<dt class="hdlist1"><strong>--verbose</strong></dt>
<dd>
<p>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 <code>term.verbose</code>
<a href="../reference/config.html">config value</a>.</p>
</dd>
<dt class="hdlist1"><strong>-q</strong></dt>
<dt class="hdlist1"><strong>--quiet</strong></dt>
<dd>
<p>No output printed to stdout.</p>
</dd>
<dt class="hdlist1"><strong>--color</strong> <em>WHEN</em></dt>
<dd>
<p>Control when colored output is used. Valid values:</p>
<div class="ulist">
<ul>
<li>
<p><code>auto</code> (default): Automatically detect if color support is available on the
terminal.</p>
</li>
<li>
<p><code>always</code>: Always display colors.</p>
</li>
<li>
<p><code>never</code>: Never display colors.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>May also be specified with the <code>term.color</code>
<a href="../reference/config.html">config value</a>.</p>
</div>
</dd>
</dl>
</div>
</div>
<div class="sect2">
<h3 id="cargo_tree_common_options">Common Options</h3>
<div class="dlist">
<dl>
<dt class="hdlist1"><strong>-h</strong></dt>
<dt class="hdlist1"><strong>--help</strong></dt>
<dd>
<p>Prints help information.</p>
</dd>
<dt class="hdlist1"><strong>-Z</strong> <em>FLAG</em>&#8230;&#8203;</dt>
<dd>
<p>Unstable (nightly-only) flags to Cargo. Run <code>cargo -Z help</code> for
details.</p>
</dd>
<dt class="hdlist1"><strong>--frozen</strong></dt>
<dt class="hdlist1"><strong>--locked</strong></dt>
<dd>
<p>Either of these flags requires that the <code>Cargo.lock</code> file is
up-to-date. If the lock file is missing, or it needs to be updated, Cargo will
exit with an error. The <code>--frozen</code> flag also prevents Cargo from
attempting to access the network to determine if it is out-of-date.</p>
<div class="paragraph">
<p>These may be used in environments where you want to assert that the
<code>Cargo.lock</code> file is up-to-date (such as a CI build) or want to avoid network
access.</p>
</div>
</dd>
<dt class="hdlist1"><strong>--offline</strong></dt>
<dd>
<p>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.</p>
<div class="paragraph">
<p>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 <a href="cargo-fetch.html">cargo-fetch(1)</a> command to download dependencies before going
offline.</p>
</div>
<div class="paragraph">
<p>May also be specified with the <code>net.offline</code> <a href="../reference/config.html">config value</a>.</p>
</div>
</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="cargo_tree_environment">ENVIRONMENT</h2>
<div class="sectionbody">
<div class="paragraph">
<p>See <a href="../reference/environment-variables.html">the reference</a> for
details on environment variables that Cargo reads.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="cargo_tree_exit_status">Exit Status</h2>
<div class="sectionbody">
<div class="dlist">
<dl>
<dt class="hdlist1">0</dt>
<dd>
<p>Cargo succeeded.</p>
</dd>
<dt class="hdlist1">101</dt>
<dd>
<p>Cargo failed to complete.</p>
</dd>
</dl>
</div>
</div>
</div>
<div class="sect1">
<h2 id="cargo_tree_examples">EXAMPLES</h2>
<div class="sectionbody">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>Display the tree for the package in the current directory:</p>
<div class="literalblock">
<div class="content">
<pre>cargo tree</pre>
</div>
</div>
</li>
<li>
<p>Display all the packages that depend on the specified package:</p>
<div class="literalblock">
<div class="content">
<pre>cargo tree -i -p syn</pre>
</div>
</div>
</li>
<li>
<p>Show the features enabled on each package:</p>
<div class="literalblock">
<div class="content">
<pre>cargo tree --format "{p} {f}"</pre>
</div>
</div>
</li>
<li>
<p>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).</p>
<div class="literalblock">
<div class="content">
<pre>cargo tree -d</pre>
</div>
</div>
</li>
<li>
<p>Explain why features are enabled for the given package:</p>
<div class="literalblock">
<div class="content">
<pre>cargo tree --graph-features -i -p syn</pre>
</div>
</div>
<div class="paragraph">
<p>An example of what this would display:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>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" (*)</pre>
</div>
</div>
<div class="paragraph">
<p>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
<code>rustversion</code> crate which was included from <code>myproject</code> (with the default
features), and <code>myproject</code> was the package selected on the command-line. All
of the other <code>syn</code> features are added by the "default" feature ("quote" is
added by "printing" and "proc-macro", both of which are default features).</p>
</div>
<div class="paragraph">
<p>If you&#8217;re having difficulty cross-referencing the de-duplicated <code>(*)</code> entries,
try with the <code>--no-dedupe</code> flag to get the full output.</p>
</div>
</li>
</ol>
</div>
</div>
</div>
<div class="sect1">
<h2 id="cargo_tree_see_also">SEE ALSO</h2>
<div class="sectionbody">
<div class="paragraph">
<p><a href="index.html">cargo(1)</a>, <a href="cargo-metadata.html">cargo-metadata(1)</a></p>
</div>
</div>
</div>

View File

@ -98,6 +98,10 @@ including overrides, in machine-readable format.</p>
<dd> <dd>
<p>Print a fully qualified package specification.</p> <p>Print a fully qualified package specification.</p>
</dd> </dd>
<dt class="hdlist1"><a href="cargo-tree.html">cargo-tree(1)</a></dt>
<dd>
<p>Display a tree visualization of a dependency graph.</p>
</dd>
<dt class="hdlist1"><a href="cargo-update.html">cargo-update(1)</a></dt> <dt class="hdlist1"><a href="cargo-update.html">cargo-update(1)</a></dt>
<dd> <dd>
<p>Update dependencies as recorded in the local lock file.</p> <p>Update dependencies as recorded in the local lock file.</p>

View File

@ -17,8 +17,10 @@ virtual workspace will include all workspace members (equivalent to passing
*--workspace*:: *--workspace*::
{actionverb} all members in the workspace. {actionverb} all members in the workspace.
ifndef::noall[]
*--all*:: *--all*::
Deprecated alias for `--workspace`. Deprecated alias for `--workspace`.
endif::noall[]
*--exclude* _SPEC_...:: *--exclude* _SPEC_...::
Exclude the specified packages. Must be used in conjunction with the Exclude the specified packages. Must be used in conjunction with the

View File

@ -59,6 +59,7 @@
* [cargo locate-project](commands/cargo-locate-project.md) * [cargo locate-project](commands/cargo-locate-project.md)
* [cargo metadata](commands/cargo-metadata.md) * [cargo metadata](commands/cargo-metadata.md)
* [cargo pkgid](commands/cargo-pkgid.md) * [cargo pkgid](commands/cargo-pkgid.md)
* [cargo tree](commands/cargo-tree.md)
* [cargo update](commands/cargo-update.md) * [cargo update](commands/cargo-update.md)
* [cargo vendor](commands/cargo-vendor.md) * [cargo vendor](commands/cargo-vendor.md)
* [cargo verify-project](commands/cargo-verify-project.md) * [cargo verify-project](commands/cargo-verify-project.md)

View File

@ -0,0 +1,3 @@
# cargo tree
{{#include command-common.html}}
{{#include ../../man/generated/cargo-tree.html}}

View File

@ -3,6 +3,7 @@
* [cargo locate-project](cargo-locate-project.md) * [cargo locate-project](cargo-locate-project.md)
* [cargo metadata](cargo-metadata.md) * [cargo metadata](cargo-metadata.md)
* [cargo pkgid](cargo-pkgid.md) * [cargo pkgid](cargo-pkgid.md)
* [cargo tree](cargo-tree.md)
* [cargo update](cargo-update.md) * [cargo update](cargo-update.md)
* [cargo vendor](cargo-vendor.md) * [cargo vendor](cargo-vendor.md)
* [cargo verify-project](cargo-verify-project.md) * [cargo verify-project](cargo-verify-project.md)

View File

@ -271,6 +271,21 @@ _cargo() {
'*: :_default' '*: :_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) uninstall)
_arguments -s -S $common \ _arguments -s -S $common \
'(-p --package)'{-p+,--package=}'[specify package to uninstall]:package:_cargo_package_names' \ '(-p --package)'{-p+,--package=}'[specify package to uninstall]:package:_cargo_package_names' \

View File

@ -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__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__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__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__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__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" local opt__vendor="$opt_common $opt_mani $opt_lock $opt_sync --no-delete --respect-source-config --versioned-dirs"

491
src/etc/man/cargo-tree.1 Normal file
View File

@ -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<dep\-name>/<feature\-name>\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)

View File

@ -2,12 +2,12 @@
.\" Title: cargo .\" Title: cargo
.\" Author: [see the "AUTHOR(S)" section] .\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10 .\" Generator: Asciidoctor 2.0.10
.\" Date: 2019-12-04 .\" Date: 2020-03-30
.\" Manual: \ \& .\" Manual: \ \&
.\" Source: \ \& .\" Source: \ \&
.\" Language: English .\" Language: English
.\" .\"
.TH "CARGO" "1" "2019-12-04" "\ \&" "\ \&" .TH "CARGO" "1" "2020-03-30" "\ \&" "\ \&"
.ie \n(.g .ds Aq \(aq .ie \n(.g .ds Aq \(aq
.el .ds Aq ' .el .ds Aq '
.ss \n[.ss] 0 .ss \n[.ss] 0
@ -125,6 +125,11 @@ including overrides, in machine\-readable format.
Print a fully qualified package specification. Print a fully qualified package specification.
.RE .RE
.sp .sp
\fBcargo\-tree\fP(1)
.RS 4
Display a tree visualization of a dependency graph.
.RE
.sp
\fBcargo\-update\fP(1) \fBcargo\-update\fP(1)
.RS 4 .RS 4
Update dependencies as recorded in the local lock file. Update dependencies as recorded in the local lock file.

View File

@ -103,6 +103,8 @@ mod standard_lib;
mod test; mod test;
mod timings; mod timings;
mod tool_paths; mod tool_paths;
mod tree;
mod tree_graph_features;
mod unit_graph; mod unit_graph;
mod update; mod update;
mod vendor; mod vendor;

1371
tests/testsuite/tree.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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();
}