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

feat: Implement --depth workspace for cargo tree command #14928

Merged
merged 5 commits into from
Dec 12, 2024
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
10 changes: 8 additions & 2 deletions src/bin/cargo/commands/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::cli;
use crate::command_prelude::*;
use anyhow::{bail, format_err};
use cargo::core::dependency::DepKind;
use cargo::ops::tree::{self, EdgeKind};
use cargo::ops::tree::{self, DisplayDepth, EdgeKind};
use cargo::ops::Packages;
use cargo::util::print_available_packages;
use cargo::util::CargoResult;
Expand Down Expand Up @@ -162,6 +162,12 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {

let pkgs_to_prune = args._values_of("prune");

let display_depth = args
._value_of("depth")
.map(|s| s.parse::<DisplayDepth>())
.transpose()?
.unwrap_or(DisplayDepth::MaxDisplayDepth(u32::MAX));

let packages = args.packages_from_flags()?;
let mut invert = args
.get_many::<String>("invert")
Expand Down Expand Up @@ -222,7 +228,7 @@ subtree of the package given to -p.\n\
duplicates: args.flag("duplicates"),
format: args.get_one::<String>("format").cloned().unwrap(),
graph_features,
max_display_depth: args.value_of_u32("depth")?.unwrap_or(u32::MAX),
display_depth,
no_proc_macro,
};

Expand Down
5 changes: 5 additions & 0 deletions src/cargo/core/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,11 @@ impl<'gctx> Workspace<'gctx> {
self.member_ids.contains(&pkg.package_id())
}

/// Returns true if the given package_id is a member of the workspace.
pub fn is_member_id(&self, package_id: PackageId) -> bool {
self.member_ids.contains(&package_id)
}

pub fn is_ephemeral(&self) -> bool {
self.is_ephemeral
}
Expand Down
82 changes: 59 additions & 23 deletions src/cargo/ops/tree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::core::dependency::DepKind;
use crate::core::resolver::{features::CliFeatures, ForceAllTargets, HasDevUnits};
use crate::core::{Package, PackageId, PackageIdSpec, PackageIdSpecQuery, Workspace};
use crate::ops::{self, Packages};
use crate::util::{CargoResult, GlobalContext};
use crate::util::CargoResult;
use crate::{drop_print, drop_println};
use anyhow::Context as _;
use graph::Graph;
Expand Down Expand Up @@ -43,8 +43,10 @@ pub struct TreeOptions {
pub format: String,
/// Includes features in the tree as separate nodes.
pub graph_features: bool,
/// Maximum display depth of the dependency tree.
pub max_display_depth: u32,
/// Display depth of the dependency tree.
/// If non-negative integer, display dependencies with that amount of max depth.
/// If `workspace`, display dependencies from current workspace only.
pub display_depth: DisplayDepth,
/// Excludes proc-macro dependencies.
pub no_proc_macro: bool,
}
Expand Down Expand Up @@ -86,6 +88,32 @@ impl FromStr for Prefix {
}
}

#[derive(Clone, Copy)]
pub enum DisplayDepth {
MaxDisplayDepth(u32),
Workspace,
}
epage marked this conversation as resolved.
Show resolved Hide resolved

impl FromStr for DisplayDepth {
type Err = clap::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"workspace" => Ok(Self::Workspace),
s => s.parse().map(Self::MaxDisplayDepth).map_err(|_| {
clap::Error::raw(
clap::error::ErrorKind::ValueValidation,
format!(
"supported values for --depth are non-negative integers and `workspace`, \
but `{}` is unknown",
s
),
)
}),
}
}
}

struct Symbols {
down: &'static str,
tee: &'static str,
Expand Down Expand Up @@ -203,14 +231,14 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<()
try to use option `--target all` first, and then narrow your search scope accordingly.",
)?;
} else {
print(ws.gctx(), opts, root_indexes, &pkgs_to_prune, &graph)?;
print(ws, opts, root_indexes, &pkgs_to_prune, &graph)?;
}
Ok(())
}

/// Prints a tree for each given root.
fn print(
gctx: &GlobalContext,
ws: &Workspace<'_>,
opts: &TreeOptions,
roots: Vec<usize>,
pkgs_to_prune: &[PackageIdSpec],
Expand All @@ -219,7 +247,7 @@ fn print(
let format = Pattern::new(&opts.format)
.with_context(|| format!("tree format `{}` not valid", opts.format))?;

let symbols = if gctx.shell().out_unicode() {
let symbols = if ws.gctx().shell().out_unicode() {
&UTF8_SYMBOLS
} else {
&ASCII_SYMBOLS
Expand All @@ -231,7 +259,7 @@ fn print(

for (i, root_index) in roots.into_iter().enumerate() {
if i != 0 {
drop_println!(gctx);
drop_println!(ws.gctx());
}

// A stack of bools used to determine where | symbols should appear
Expand All @@ -242,15 +270,15 @@ fn print(
let mut print_stack = vec![];

print_node(
gctx,
ws,
graph,
root_index,
&format,
symbols,
pkgs_to_prune,
opts.prefix,
opts.no_dedupe,
opts.max_display_depth,
opts.display_depth,
&mut visited_deps,
&mut levels_continue,
&mut print_stack,
Expand All @@ -262,36 +290,36 @@ fn print(

/// Prints a package and all of its dependencies.
fn print_node<'a>(
gctx: &GlobalContext,
ws: &Workspace<'_>,
graph: &'a Graph<'_>,
node_index: usize,
format: &Pattern,
symbols: &Symbols,
pkgs_to_prune: &[PackageIdSpec],
prefix: Prefix,
no_dedupe: bool,
max_display_depth: u32,
display_depth: DisplayDepth,
visited_deps: &mut HashSet<usize>,
levels_continue: &mut Vec<bool>,
print_stack: &mut Vec<usize>,
) {
let new = no_dedupe || visited_deps.insert(node_index);

match prefix {
Prefix::Depth => drop_print!(gctx, "{}", levels_continue.len()),
Prefix::Depth => drop_print!(ws.gctx(), "{}", levels_continue.len()),
Prefix::Indent => {
if let Some((last_continues, rest)) = levels_continue.split_last() {
for continues in rest {
let c = if *continues { symbols.down } else { " " };
drop_print!(gctx, "{} ", c);
drop_print!(ws.gctx(), "{} ", c);
}

let c = if *last_continues {
symbols.tee
} else {
symbols.ell
};
drop_print!(gctx, "{0}{1}{1} ", c, symbols.right);
drop_print!(ws.gctx(), "{0}{1}{1} ", c, symbols.right);
}
}
Prefix::None => {}
Expand All @@ -307,7 +335,7 @@ fn print_node<'a>(
} else {
" (*)"
};
drop_println!(gctx, "{}{}", format.display(graph, node_index), star);
drop_println!(ws.gctx(), "{}{}", format.display(graph, node_index), star);

if !new || in_cycle {
return;
Expand All @@ -321,15 +349,15 @@ fn print_node<'a>(
EdgeKind::Feature,
] {
print_dependencies(
gctx,
ws,
graph,
node_index,
format,
symbols,
pkgs_to_prune,
prefix,
no_dedupe,
max_display_depth,
display_depth,
visited_deps,
levels_continue,
print_stack,
Expand All @@ -341,15 +369,15 @@ fn print_node<'a>(

/// Prints all the dependencies of a package for the given dependency kind.
fn print_dependencies<'a>(
gctx: &GlobalContext,
ws: &Workspace<'_>,
epage marked this conversation as resolved.
Show resolved Hide resolved
graph: &'a Graph<'_>,
node_index: usize,
format: &Pattern,
symbols: &Symbols,
pkgs_to_prune: &[PackageIdSpec],
prefix: Prefix,
no_dedupe: bool,
max_display_depth: u32,
display_depth: DisplayDepth,
epage marked this conversation as resolved.
Show resolved Hide resolved
visited_deps: &mut HashSet<usize>,
levels_continue: &mut Vec<bool>,
print_stack: &mut Vec<usize>,
Expand All @@ -371,13 +399,18 @@ fn print_dependencies<'a>(
if let Some(name) = name {
for continues in &**levels_continue {
let c = if *continues { symbols.down } else { " " };
drop_print!(gctx, "{} ", c);
drop_print!(ws.gctx(), "{} ", c);
}

drop_println!(gctx, "{}", name);
drop_println!(ws.gctx(), "{}", name);
}
}

let (max_display_depth, filter_non_workspace_member) = match display_depth {
DisplayDepth::MaxDisplayDepth(max) => (max, false),
DisplayDepth::Workspace => (u32::MAX, true),
};

// Current level exceeds maximum display depth. Skip.
if levels_continue.len() + 1 > max_display_depth as usize {
return;
Expand All @@ -389,6 +422,9 @@ fn print_dependencies<'a>(
// Filter out packages to prune.
match graph.node(**dep) {
Node::Package { package_id, .. } => {
if filter_non_workspace_member && !ws.is_member_id(*package_id) {
return false;
}
!pkgs_to_prune.iter().any(|spec| spec.matches(*package_id))
}
_ => true,
Expand All @@ -399,15 +435,15 @@ fn print_dependencies<'a>(
while let Some(dependency) = it.next() {
levels_continue.push(it.peek().is_some());
print_node(
gctx,
ws,
graph,
*dependency,
format,
symbols,
pkgs_to_prune,
prefix,
no_dedupe,
max_display_depth,
display_depth,
visited_deps,
levels_continue,
print_stack,
Expand Down
3 changes: 3 additions & 0 deletions src/doc/man/cargo-tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ Prune the given package from the display of the dependency tree.
{{#option "`--depth` _depth_" }}
Maximum display depth of the dependency tree. A depth of 1 displays the direct
dependencies, for example.

If the given value is `workspace`, only shows the dependencies that are member
epage marked this conversation as resolved.
Show resolved Hide resolved
of the current workspace, instead.
{{/option}}

{{#option "`--no-dedupe`" }}
Expand Down
3 changes: 3 additions & 0 deletions src/doc/man/generated_txt/cargo-tree.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ OPTIONS
Maximum display depth of the dependency tree. A depth of 1 displays
the direct dependencies, for example.

If the given value is workspace, only shows the dependencies that
are member of the current workspace, instead.

--no-dedupe
Do not de-duplicate repeated dependencies. Usually, when a package
has already displayed its dependencies, further occurrences will not
Expand Down
4 changes: 3 additions & 1 deletion src/doc/src/commands/cargo-tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ subtree of the package given to <code>-p</code>.</dd>

<dt class="option-term" id="option-cargo-tree---depth"><a class="option-anchor" href="#option-cargo-tree---depth"></a><code>--depth</code> <em>depth</em></dt>
<dd class="option-desc">Maximum display depth of the dependency tree. A depth of 1 displays the direct
dependencies, for example.</dd>
dependencies, for example.</p>
<p>If the given value is <code>workspace</code>, only shows the dependencies that are member
of the current workspace, instead.</dd>


<dt class="option-term" id="option-cargo-tree---no-dedupe"><a class="option-anchor" href="#option-cargo-tree---no-dedupe"></a><code>--no-dedupe</code></dt>
Expand Down
3 changes: 3 additions & 0 deletions src/etc/man/cargo-tree.1
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ Prune the given package from the display of the dependency tree.
.RS 4
Maximum display depth of the dependency tree. A depth of 1 displays the direct
dependencies, for example.
.sp
If the given value is \fBworkspace\fR, only shows the dependencies that are member
of the current workspace, instead.
.RE
.sp
\fB\-\-no\-dedupe\fR
Expand Down
55 changes: 55 additions & 0 deletions tests/testsuite/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1846,6 +1846,61 @@ c v1.0.0
.run();
}

#[cargo_test]
fn depth_workspace() {
Package::new("somedep", "1.0.0").publish();
Package::new("otherdep", "1.0.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[workspace]
members = ["a", "b", "c"]
"#,
)
.file("a/Cargo.toml", &basic_manifest("a", "1.0.0"))
.file("a/src/lib.rs", "")
.file(
"b/Cargo.toml",
r#"
[package]
name = "b"
version = "0.1.0"

[dependencies]
c = { path = "../c" }
somedep = "1"
"#,
)
.file("b/src/lib.rs", "")
.file(
"c/Cargo.toml",
r#"
[package]
name = "c"
version = "0.1.0"

[dependencies]
somedep = "1"
otherdep = "1"
"#,
)
.file("c/src/lib.rs", "")
.build();

p.cargo("tree --depth workspace")
epage marked this conversation as resolved.
Show resolved Hide resolved
epage marked this conversation as resolved.
Show resolved Hide resolved
.with_stdout_data(str![[r#"
a v1.0.0 ([ROOT]/foo/a)

b v0.1.0 ([ROOT]/foo/b)
└── c v0.1.0 ([ROOT]/foo/c)

c v0.1.0 ([ROOT]/foo/c) (*)

"#]])
.run();
}

#[cargo_test]
fn prune() {
let p = make_simple_proj();
Expand Down
Loading