Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cargo tree command. #8062

Merged
merged 14 commits into from
Apr 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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