Skip to content

Commit 96a3937

Browse files
committed
Add cargo tree command.
1 parent c75216f commit 96a3937

30 files changed

+4165
-53
lines changed

src/bin/cargo/commands/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub fn builtin() -> Vec<App> {
2727
rustdoc::cli(),
2828
search::cli(),
2929
test::cli(),
30+
tree::cli(),
3031
uninstall::cli(),
3132
update::cli(),
3233
vendor::cli(),
@@ -63,6 +64,7 @@ pub fn builtin_exec(cmd: &str) -> Option<fn(&mut Config, &ArgMatches<'_>) -> Cli
6364
"rustdoc" => rustdoc::exec,
6465
"search" => search::exec,
6566
"test" => test::exec,
67+
"tree" => tree::exec,
6668
"uninstall" => uninstall::exec,
6769
"update" => update::exec,
6870
"vendor" => vendor::exec,
@@ -99,6 +101,7 @@ pub mod rustc;
99101
pub mod rustdoc;
100102
pub mod search;
101103
pub mod test;
104+
pub mod tree;
102105
pub mod uninstall;
103106
pub mod update;
104107
pub mod vendor;

src/bin/cargo/commands/tree.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use crate::command_prelude::*;
2+
use cargo::ops::tree;
3+
use std::str::FromStr;
4+
5+
pub fn cli() -> App {
6+
subcommand("tree")
7+
.about("Display a tree visualization of a dependency graph")
8+
.arg(opt("quiet", "Suppress status messages").short("q"))
9+
.arg_manifest_path()
10+
.arg_package_spec_no_all(
11+
"Package to be used as the root of the tree",
12+
"Display the tree for all packages in the workspace",
13+
"Exclude specific workspace members",
14+
)
15+
.arg_features()
16+
.arg_target_triple(
17+
"Filter dependencies matching the given target-triple (default host platform)",
18+
)
19+
.arg(opt(
20+
"no-filter-targets",
21+
"Return dependencies for all targets",
22+
))
23+
.arg(opt("no-dev-dependencies", "Skip dev dependencies"))
24+
.arg(opt("invert", "Invert the tree direction").short("i"))
25+
.arg(opt(
26+
"no-indent",
27+
"Display the dependencies as a list (rather than a tree)",
28+
))
29+
.arg(opt(
30+
"prefix-depth",
31+
"Display the dependencies as a list (rather than a tree), but prefixed with the depth",
32+
))
33+
.arg(opt(
34+
"no-dedupe",
35+
"Do not de-duplicate (repeats all shared dependencies)",
36+
))
37+
.arg(
38+
opt(
39+
"duplicates",
40+
"Show only dependencies which come in multiple versions (implies -i)",
41+
)
42+
.short("d")
43+
.alias("duplicate"),
44+
)
45+
.arg(
46+
opt("charset", "Character set to use in output: utf8, ascii")
47+
.value_name("CHARSET")
48+
.possible_values(&["utf8", "ascii"])
49+
.default_value("utf8"),
50+
)
51+
.arg(
52+
opt("format", "Format string used for printing dependencies")
53+
.value_name("FORMAT")
54+
.short("f")
55+
.default_value("{p}"),
56+
)
57+
.arg(opt("graph-features", "Include features in the tree"))
58+
}
59+
60+
pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
61+
let ws = args.workspace(config)?;
62+
let charset = tree::Charset::from_str(args.value_of("charset").unwrap())
63+
.map_err(|e| anyhow::anyhow!("{}", e))?;
64+
let opts = tree::TreeOptions {
65+
features: values(args, "features"),
66+
all_features: args.is_present("all-features"),
67+
no_default_features: args.is_present("no-default-features"),
68+
packages: args.packages_from_flags()?,
69+
target: args.target(),
70+
no_filter_targets: args.is_present("no-filter-targets"),
71+
no_dev_dependencies: args.is_present("no-dev-dependencies"),
72+
invert: args.is_present("invert"),
73+
no_indent: args.is_present("no-indent"),
74+
prefix_depth: args.is_present("prefix-depth"),
75+
no_dedupe: args.is_present("no-dedupe"),
76+
duplicates: args.is_present("duplicates"),
77+
charset,
78+
format: args.value_of("format").unwrap().to_string(),
79+
graph_features: args.is_present("graph-features"),
80+
};
81+
82+
tree::build_and_print(&ws, &opts)?;
83+
Ok(())
84+
}

src/cargo/core/compiler/build_config.rs

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::core::compiler::{CompileKind, CompileTarget};
1+
use crate::core::compiler::CompileKind;
22
use crate::core::interning::InternedString;
33
use crate::util::ProcessBuilder;
44
use crate::util::{CargoResult, Config, RustfixDiagnosticServer};
@@ -45,22 +45,8 @@ impl BuildConfig {
4545
mode: CompileMode,
4646
) -> CargoResult<BuildConfig> {
4747
let cfg = config.build_config()?;
48-
let requested_kind = match requested_target {
49-
Some(s) => CompileKind::Target(CompileTarget::new(s)?),
50-
None => match &cfg.target {
51-
Some(val) => {
52-
let value = if val.raw_value().ends_with(".json") {
53-
let path = val.clone().resolve_path(config);
54-
path.to_str().expect("must be utf-8 in toml").to_string()
55-
} else {
56-
val.raw_value().to_string()
57-
};
58-
CompileKind::Target(CompileTarget::new(&value)?)
59-
}
60-
None => CompileKind::Host,
61-
},
62-
};
63-
48+
let requested_kind =
49+
CompileKind::from_requested_target(config, requested_target.as_deref())?;
6450
if jobs == Some(0) {
6551
anyhow::bail!("jobs must be at least 1")
6652
}

src/cargo/core/compiler/compile_kind.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::core::{InternedString, Target};
22
use crate::util::errors::{CargoResult, CargoResultExt};
3+
use crate::util::Config;
34
use serde::Serialize;
45
use std::path::Path;
56

@@ -39,6 +40,32 @@ impl CompileKind {
3940
CompileKind::Target(n) => CompileKind::Target(n),
4041
}
4142
}
43+
44+
/// Creates a new `CompileKind` based on the requested target.
45+
///
46+
/// If no target is given, this consults the config if the default is set.
47+
/// Otherwise returns `CompileKind::Host`.
48+
pub fn from_requested_target(
49+
config: &Config,
50+
target: Option<&str>,
51+
) -> CargoResult<CompileKind> {
52+
let kind = match target {
53+
Some(s) => CompileKind::Target(CompileTarget::new(s)?),
54+
None => match &config.build_config()?.target {
55+
Some(val) => {
56+
let value = if val.raw_value().ends_with(".json") {
57+
let path = val.clone().resolve_path(config);
58+
path.to_str().expect("must be utf-8 in toml").to_string()
59+
} else {
60+
val.raw_value().to_string()
61+
};
62+
CompileKind::Target(CompileTarget::new(&value)?)
63+
}
64+
None => CompileKind::Host,
65+
},
66+
};
67+
Ok(kind)
68+
}
4269
}
4370

4471
impl serde::ser::Serialize for CompileKind {

src/cargo/core/profiles.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -998,11 +998,7 @@ impl UnitFor {
998998
}
999999

10001000
pub(crate) fn map_to_features_for(&self) -> FeaturesFor {
1001-
if self.is_for_host_features() {
1002-
FeaturesFor::HostDep
1003-
} else {
1004-
FeaturesFor::NormalOrDev
1005-
}
1001+
FeaturesFor::from_for_host(self.is_for_host_features())
10061002
}
10071003
}
10081004

src/cargo/core/resolver/features.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,23 @@ pub enum HasDevUnits {
9292
}
9393

9494
/// Flag to indicate if features are requested for a build dependency or not.
95-
#[derive(Debug, PartialEq)]
95+
#[derive(Copy, Clone, Debug, PartialEq)]
9696
pub enum FeaturesFor {
9797
NormalOrDev,
9898
/// Build dependency or proc-macro.
9999
HostDep,
100100
}
101101

102+
impl FeaturesFor {
103+
pub fn from_for_host(for_host: bool) -> FeaturesFor {
104+
if for_host {
105+
FeaturesFor::HostDep
106+
} else {
107+
FeaturesFor::NormalOrDev
108+
}
109+
}
110+
}
111+
102112
impl FeatureOpts {
103113
fn new(config: &Config, has_dev_units: HasDevUnits) -> CargoResult<FeatureOpts> {
104114
let mut opts = FeatureOpts::default();

src/cargo/core/resolver/resolve.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,10 @@ unable to verify that `{0}` is the same as when the lockfile was generated
305305
PackageIdSpec::query_str(spec, self.iter())
306306
}
307307

308+
pub fn specs_to_ids(&self, specs: &[PackageIdSpec]) -> CargoResult<Vec<PackageId>> {
309+
specs.iter().map(|s| s.query(self.iter())).collect()
310+
}
311+
308312
pub fn unused_patches(&self) -> &[PackageId] {
309313
&self.unused_patches
310314
}

src/cargo/ops/cargo_compile.rs

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -351,12 +351,8 @@ pub fn compile_ws<'a>(
351351

352352
// Find the packages in the resolver that the user wants to build (those
353353
// passed in with `-p` or the defaults from the workspace), and convert
354-
// Vec<PackageIdSpec> to a Vec<&PackageId>.
355-
let to_build_ids = specs
356-
.iter()
357-
.map(|s| s.query(resolve.iter()))
358-
.collect::<CargoResult<Vec<_>>>()?;
359-
354+
// Vec<PackageIdSpec> to a Vec<PackageId>.
355+
let to_build_ids = resolve.specs_to_ids(&specs)?;
360356
// Now get the `Package` for each `PackageId`. This may trigger a download
361357
// if the user specified `-p` for a dependency that is not downloaded.
362358
// Dependencies will be downloaded during build_unit_dependencies.
@@ -753,12 +749,8 @@ fn generate_targets<'a>(
753749
bcx.profiles
754750
.get_profile(pkg.package_id(), ws.is_member(pkg), unit_for, target_mode);
755751

756-
let features_for = if target.proc_macro() {
757-
FeaturesFor::HostDep
758-
} else {
759-
// Root units are never build dependencies.
760-
FeaturesFor::NormalOrDev
761-
};
752+
// No need to worry about build-dependencies, roots are never build dependencies.
753+
let features_for = FeaturesFor::from_for_host(target.proc_macro());
762754
let features =
763755
Vec::from(resolved_features.activated_features(pkg.package_id(), features_for));
764756
bcx.units.intern(
@@ -969,11 +961,7 @@ pub fn resolve_all_features(
969961
.expect("packages downloaded")
970962
.proc_macro();
971963
for dep in deps {
972-
let features_for = if is_proc_macro || dep.is_build() {
973-
FeaturesFor::HostDep
974-
} else {
975-
FeaturesFor::NormalOrDev
976-
};
964+
let features_for = FeaturesFor::from_for_host(is_proc_macro || dep.is_build());
977965
for feature in resolved_features.activated_features_unverified(dep_id, features_for) {
978966
features.insert(format!("{}/{}", dep.name_in_toml(), feature));
979967
}

src/cargo/ops/cargo_doc.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,7 @@ pub fn doc(ws: &Workspace<'_>, options: &DocOptions) -> CargoResult<()> {
3636
HasDevUnits::No,
3737
)?;
3838

39-
let ids = specs
40-
.iter()
41-
.map(|s| s.query(ws_resolve.targeted_resolve.iter()))
42-
.collect::<CargoResult<Vec<_>>>()?;
39+
let ids = ws_resolve.targeted_resolve.specs_to_ids(&specs)?;
4340
let pkgs = ws_resolve.pkg_set.get_many(ids)?;
4441

4542
let mut lib_names = HashMap::new();

src/cargo/ops/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,5 @@ mod fix;
4848
mod lockfile;
4949
mod registry;
5050
mod resolve;
51+
pub mod tree;
5152
mod vendor;

0 commit comments

Comments
 (0)