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 1 commit
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
84 changes: 84 additions & 0 deletions src/bin/cargo/commands/tree.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
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
22 changes: 5 additions & 17 deletions src/cargo/ops/cargo_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,12 +351,8 @@ pub fn compile_ws<'a>(

// Find the packages in the resolver that the user wants to build (those
// passed in with `-p` or the defaults from the workspace), and convert
// Vec<PackageIdSpec> to a Vec<&PackageId>.
let to_build_ids = specs
.iter()
.map(|s| s.query(resolve.iter()))
.collect::<CargoResult<Vec<_>>>()?;

// Vec<PackageIdSpec> to a Vec<PackageId>.
let to_build_ids = resolve.specs_to_ids(&specs)?;
// Now get the `Package` for each `PackageId`. This may trigger a download
// if the user specified `-p` for a dependency that is not downloaded.
// Dependencies will be downloaded during build_unit_dependencies.
Expand Down Expand Up @@ -753,12 +749,8 @@ fn generate_targets<'a>(
bcx.profiles
.get_profile(pkg.package_id(), ws.is_member(pkg), unit_for, target_mode);

let features_for = if target.proc_macro() {
FeaturesFor::HostDep
} else {
// Root units are never build dependencies.
FeaturesFor::NormalOrDev
};
// No need to worry about build-dependencies, roots are never build dependencies.
let features_for = FeaturesFor::from_for_host(target.proc_macro());
let features =
Vec::from(resolved_features.activated_features(pkg.package_id(), features_for));
bcx.units.intern(
Expand Down Expand Up @@ -969,11 +961,7 @@ pub fn resolve_all_features(
.expect("packages downloaded")
.proc_macro();
for dep in deps {
let features_for = if is_proc_macro || dep.is_build() {
FeaturesFor::HostDep
} else {
FeaturesFor::NormalOrDev
};
let features_for = FeaturesFor::from_for_host(is_proc_macro || dep.is_build());
for feature in resolved_features.activated_features_unverified(dep_id, features_for) {
features.insert(format!("{}/{}", dep.name_in_toml(), feature));
}
Expand Down
5 changes: 1 addition & 4 deletions src/cargo/ops/cargo_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ pub fn doc(ws: &Workspace<'_>, options: &DocOptions) -> CargoResult<()> {
HasDevUnits::No,
)?;

let ids = specs
.iter()
.map(|s| s.query(ws_resolve.targeted_resolve.iter()))
.collect::<CargoResult<Vec<_>>>()?;
let ids = ws_resolve.targeted_resolve.specs_to_ids(&specs)?;
let pkgs = ws_resolve.pkg_set.get_many(ids)?;

let mut lib_names = HashMap::new();
Expand Down
1 change: 1 addition & 0 deletions src/cargo/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ mod fix;
mod lockfile;
mod registry;
mod resolve;
pub mod tree;
mod vendor;
108 changes: 108 additions & 0 deletions src/cargo/ops/tree/format/mod.rs
Original file line number Diff line number Diff line change
@@ -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));
alexcrichton marked this conversation as resolved.
Show resolved Hide resolved
}
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)?,
alexcrichton marked this conversation as resolved.
Show resolved Hide resolved
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),
alexcrichton marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

Ok(())
}
}
Loading