Skip to content

Commit

Permalink
Auto merge of #8062 - ehuss:tree, r=alexcrichton
Browse files Browse the repository at this point in the history
Add `cargo tree` command.

This migrates [cargo-tree](https://github.com/sfackler/cargo-tree/) into Cargo as a built-in command. This is based on a recent master (https://github.com/sfackler/cargo-tree/tree/4108d216ec072d2e7c9befb4de32175f97a9dbc4), and should be mostly similar in functionality. There are a variety changes:

* `--all-targets` renamed to `--no-filter-targets` to avoid confusion with the `--all-targets` flag used in other Cargo commands with a different meaning.
* `--all`/`-a` renamed to `--no-dedupe` to avoid confusion with the `-all` flag which means "all workspace crates" in other Cargo commands.
* `--duplicate` renamed to `--duplicates` (with alias), just a personal preference.
* Added support for multiple roots (workspace support).
* Added the `--graph-features` flag for including features in the graph (to "explain" why a feature is enabled).
* Added `{f}` to format string to show features.
* Handles new feature resolver.
* Handles cyclical dev dependencies.
* Added a test suite.
* Dropped the dependency on petgraph, in favor of a simpler custom graph.

Closes #7286.
  • Loading branch information
bors committed Apr 9, 2020
2 parents 239f2bf + 7c40344 commit 7d720ef
Show file tree
Hide file tree
Showing 31 changed files with 4,752 additions and 53 deletions.
3 changes: 3 additions & 0 deletions src/bin/cargo/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub fn builtin() -> Vec<App> {
rustdoc::cli(),
search::cli(),
test::cli(),
tree::cli(),
uninstall::cli(),
update::cli(),
vendor::cli(),
Expand Down Expand Up @@ -63,6 +64,7 @@ pub fn builtin_exec(cmd: &str) -> Option<fn(&mut Config, &ArgMatches<'_>) -> Cli
"rustdoc" => rustdoc::exec,
"search" => search::exec,
"test" => test::exec,
"tree" => tree::exec,
"uninstall" => uninstall::exec,
"update" => update::exec,
"vendor" => vendor::exec,
Expand Down Expand Up @@ -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;
Expand Down
254 changes: 254 additions & 0 deletions src/bin/cargo/commands/tree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
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;

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(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)",
)
.arg(
Arg::with_name("no-dev-dependencies")
.long("no-dev-dependencies")
.hidden(true),
)
.arg(
multi_opt(
"edges",
"KINDS",
"The kinds of dependencies to display \
(features, normal, build, dev, all, no-dev, no-build, no-normal)",
)
.short("e"),
)
.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")
.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)",
))
.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}"),
)
}

pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
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\
If you are looking to display all workspace members, use the --workspace flag."
)
.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 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))?;
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,
target,
edge_kinds,
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,
};

tree::build_and_print(&ws, &opts)?;
Ok(())
}

fn parse_edge_kinds(config: &Config, args: &ArgMatches<'_>) -> CargoResult<HashSet<EdgeKind>> {
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_defaults = |result: &mut HashSet<EdgeKind>| {
result.insert(EdgeKind::Dep(DepKind::Normal));
result.insert(EdgeKind::Dep(DepKind::Build));
result.insert(EdgeKind::Dep(DepKind::Development));
};
let unknown = |k| {
bail!(
"unknown edge kind `{}`, valid values are \
\"normal\", \"build\", \"dev\", \
\"no-normal\", \"no-build\", \"no-dev\", \
\"features\", or \"all\"",
k
)
};
if kinds.iter().any(|k| k.starts_with("no-")) {
insert_defaults(&mut result);
for kind in &kinds {
match *kind {
"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")
}
k => return unknown(k),
};
}
return Ok(result);
}
for kind in &kinds {
match *kind {
"all" => {
insert_defaults(&mut result);
result.insert(EdgeKind::Feature);
}
"features" => {
result.insert(EdgeKind::Feature);
}
"normal" => {
result.insert(EdgeKind::Dep(DepKind::Normal));
}
"build" => {
result.insert(EdgeKind::Dep(DepKind::Build));
}
"dev" => {
result.insert(EdgeKind::Dep(DepKind::Development));
}
k => return unknown(k),
}
}
if kinds.len() == 1 && kinds[0] == "features" {
insert_defaults(&mut result);
}
Ok(result)
}
20 changes: 3 additions & 17 deletions src/cargo/core/compiler/build_config.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -45,22 +45,8 @@ impl BuildConfig {
mode: CompileMode,
) -> CargoResult<BuildConfig> {
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")
}
Expand Down
27 changes: 27 additions & 0 deletions src/cargo/core/compiler/compile_kind.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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<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 {
Expand Down
6 changes: 1 addition & 5 deletions src/cargo/core/profiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}

Expand Down
12 changes: 11 additions & 1 deletion src/cargo/core/resolver/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<FeatureOpts> {
let mut opts = FeatureOpts::default();
Expand Down
4 changes: 4 additions & 0 deletions src/cargo/core/resolver/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<PackageId>> {
specs.iter().map(|s| s.query(self.iter())).collect()
}

pub fn unused_patches(&self) -> &[PackageId] {
&self.unused_patches
}
Expand Down
Loading

0 comments on commit 7d720ef

Please sign in to comment.