Skip to content
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));
Comment thread
alexcrichton marked this conversation as resolved.
Outdated
}
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)?,
Comment thread
alexcrichton marked this conversation as resolved.
Outdated
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),
Comment thread
alexcrichton marked this conversation as resolved.
}
}
}

Ok(())
}
}
Loading