diff --git a/src/cargo/ops/tree/graph.rs b/src/cargo/ops/tree/graph.rs index a16d452b925..8528530af7f 100644 --- a/src/cargo/ops/tree/graph.rs +++ b/src/cargo/ops/tree/graph.rs @@ -78,6 +78,7 @@ impl Node { pub struct Edge { kind: EdgeKind, node: NodeId, + public: bool, } impl Edge { @@ -88,6 +89,10 @@ impl Edge { pub fn node(&self) -> NodeId { self.node } + + pub fn public(&self) -> bool { + self.public + } } /// The kind of edge, for separating dependencies into different sections. @@ -105,7 +110,7 @@ pub enum EdgeKind { /// /// The value is a `Vec` because each edge kind can have multiple outgoing /// edges. For example, package "foo" can have multiple normal dependencies. -#[derive(Clone)] +#[derive(Clone, Debug)] struct Edges(HashMap>); impl Edges { @@ -135,6 +140,7 @@ impl Edges { } /// A graph of dependencies. +#[derive(Debug)] pub struct Graph<'a> { nodes: Vec, /// The indexes of `edges` correspond to the `nodes`. That is, `edges[0]` @@ -268,6 +274,7 @@ impl<'a> Graph<'a> { let new_edge = Edge { kind: edge.kind(), node: new_to_index, + public: edge.public(), }; new_graph.edges_mut(new_from).add_edge(new_edge); } @@ -290,6 +297,7 @@ impl<'a> Graph<'a> { let new_edge = Edge { kind: edge.kind(), node: NodeId::new(from_idx, self.nodes[from_idx].name()), + public: edge.public(), }; new_edges[edge.node().index].add_edge(new_edge); } @@ -514,6 +522,7 @@ fn add_pkg( let new_edge = Edge { kind: EdgeKind::Dep(dep.kind()), node: dep_index, + public: dep.is_public(), }; if opts.graph_features { // Add the dependency node with feature nodes in-between. @@ -577,12 +586,14 @@ fn add_feature( let from_edge = Edge { kind: to.kind(), node: node_index, + public: to.public(), }; graph.edges_mut(from).add_edge(from_edge); } let to_edge = Edge { kind: EdgeKind::Feature, node: to.node(), + public: true, }; graph.edges_mut(node_index).add_edge(to_edge); (missing, node_index) @@ -620,6 +631,7 @@ fn add_cli_features( let feature_edge = Edge { kind: EdgeKind::Feature, node: package_index, + public: true, }; let index = add_feature(graph, feature, None, feature_edge).1; graph.cli_features.insert(index); @@ -654,6 +666,7 @@ fn add_cli_features( let feature_edge = Edge { kind: EdgeKind::Feature, node: package_index, + public: true, }; let index = add_feature(graph, dep_name, None, feature_edge).1; graph.cli_features.insert(index); @@ -661,6 +674,7 @@ fn add_cli_features( let dep_edge = Edge { kind: EdgeKind::Feature, node: dep_index, + public: true, }; let index = add_feature(graph, dep_feature, None, dep_edge).1; graph.cli_features.insert(index); @@ -721,6 +735,7 @@ fn add_feature_rec( let feature_edge = Edge { kind: EdgeKind::Feature, node: package_index, + public: true, }; let (missing, feat_index) = add_feature(graph, *dep_name, Some(from), feature_edge); // Don't recursive if the edge already exists to deal with cycles. @@ -771,12 +786,14 @@ fn add_feature_rec( let feature_edge = Edge { kind: EdgeKind::Feature, node: package_index, + public: true, }; add_feature(graph, *dep_name, Some(from), feature_edge); } let dep_edge = Edge { kind: EdgeKind::Feature, node: dep_index, + public: true, }; let (missing, feat_index) = add_feature(graph, *dep_feature, Some(from), dep_edge); diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index 25745223de2..cdc451f3096 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -91,6 +91,7 @@ impl FromStr for Prefix { #[derive(Clone, Copy)] pub enum DisplayDepth { MaxDisplayDepth(u32), + Public, Workspace, } @@ -100,6 +101,7 @@ impl FromStr for DisplayDepth { fn from_str(s: &str) -> Result { match s { "workspace" => Ok(Self::Workspace), + "public" => Ok(Self::Public), s => s.parse().map(Self::MaxDisplayDepth).map_err(|_| { clap::Error::raw( clap::error::ErrorKind::ValueValidation, @@ -282,7 +284,7 @@ fn print( &mut visited_deps, &mut levels_continue, &mut print_stack, - ); + )?; } Ok(()) @@ -302,7 +304,7 @@ fn print_node<'a>( visited_deps: &mut HashSet, levels_continue: &mut Vec<(anstyle::Style, bool)>, print_stack: &mut Vec, -) { +) -> CargoResult<()> { let new = no_dedupe || visited_deps.insert(node_index); match prefix { @@ -343,7 +345,7 @@ fn print_node<'a>( drop_println!(ws.gctx(), "{}{}", format.display(graph, node_index), star); if !new || in_cycle { - return; + return Ok(()); } print_stack.push(node_index); @@ -367,9 +369,11 @@ fn print_node<'a>( levels_continue, print_stack, kind, - ); + )?; } print_stack.pop(); + + Ok(()) } /// Prints all the dependencies of a package for the given dependency kind. @@ -387,10 +391,10 @@ fn print_dependencies<'a>( levels_continue: &mut Vec<(anstyle::Style, bool)>, print_stack: &mut Vec, kind: &EdgeKind, -) { +) -> CargoResult<()> { let deps = graph.edges_of_kind(node_index, kind); if deps.is_empty() { - return; + return Ok(()); } let name = match kind { @@ -415,14 +419,20 @@ fn print_dependencies<'a>( } } - let (max_display_depth, filter_non_workspace_member) = match display_depth { - DisplayDepth::MaxDisplayDepth(max) => (max, false), - DisplayDepth::Workspace => (u32::MAX, true), + let (max_display_depth, filter_non_workspace_member, filter_private) = match display_depth { + DisplayDepth::MaxDisplayDepth(max) => (max, false, false), + DisplayDepth::Workspace => (u32::MAX, true, false), + DisplayDepth::Public => { + if !ws.gctx().cli_unstable().unstable_options { + anyhow::bail!("`--depth public` requires `-Zunstable-options`") + } + (u32::MAX, false, true) + } }; // Current level exceeds maximum display depth. Skip. if levels_continue.len() + 1 > max_display_depth as usize { - return; + return Ok(()); } let mut it = deps @@ -434,9 +444,17 @@ fn print_dependencies<'a>( if filter_non_workspace_member && !ws.is_member_id(*package_id) { return false; } + if filter_private && !dep.public() { + return false; + } !pkgs_to_prune.iter().any(|spec| spec.matches(*package_id)) } - _ => true, + Node::Feature { .. } => { + if filter_private && !dep.public() { + return false; + } + true + } } }) .peekable(); @@ -457,9 +475,11 @@ fn print_dependencies<'a>( visited_deps, levels_continue, print_stack, - ); + )?; levels_continue.pop(); } + + Ok(()) } fn edge_line_color(kind: EdgeKind) -> anstyle::Style { diff --git a/tests/testsuite/cargo_tree/deps.rs b/tests/testsuite/cargo_tree/deps.rs index 7841d84d788..bdd01f9cdb0 100644 --- a/tests/testsuite/cargo_tree/deps.rs +++ b/tests/testsuite/cargo_tree/deps.rs @@ -1901,6 +1901,138 @@ c v0.1.0 ([ROOT]/foo/c) (*) .run(); } +#[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] +fn depth_public() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["diamond", "left-pub", "right-priv", "dep"] + "#, + ) + .file( + "diamond/Cargo.toml", + r#" + cargo-features = ["public-dependency"] + + [package] + name = "diamond" + version = "0.1.0" + + [dependencies] + left-pub = { path = "../left-pub", public = true } + right-priv = { path = "../right-priv", public = true } + "#, + ) + .file("diamond/src/lib.rs", "") + .file( + "left-pub/Cargo.toml", + r#" + cargo-features = ["public-dependency"] + + [package] + name = "left-pub" + version = "0.1.0" + + [dependencies] + dep = { path = "../dep", public = true } + "#, + ) + .file("left-pub/src/lib.rs", "") + .file( + "right-priv/Cargo.toml", + r#" + [package] + name = "right-priv" + version = "0.1.0" + + [dependencies] + dep = { path = "../dep" } + "#, + ) + .file("right-priv/src/lib.rs", "") + .file( + "dep/Cargo.toml", + r#" + [package] + name = "dep" + version = "0.1.0" + "#, + ) + .file("dep/src/lib.rs", "") + .build(); + + p.cargo("tree --depth public") + .masquerade_as_nightly_cargo(&["public-dependency", "depth-public"]) + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] `--depth public` requires `-Zunstable-options` + +"#]]) + .run(); + + p.cargo("tree --depth public -p left-pub") + .arg("-Zunstable-options") + .masquerade_as_nightly_cargo(&["public-dependency", "depth-public"]) + .with_stdout_data(str![[r#" +left-pub v0.1.0 ([ROOT]/foo/left-pub) +└── dep v0.1.0 ([ROOT]/foo/dep) + +"#]]) + .run(); + + p.cargo("tree --depth public -p right-priv") + .arg("-Zunstable-options") + .masquerade_as_nightly_cargo(&["public-dependency", "depth-public"]) + .with_stdout_data(str![[r#" +right-priv v0.1.0 ([ROOT]/foo/right-priv) + +"#]]) + .run(); + + p.cargo("tree --depth public -p diamond") + .arg("-Zunstable-options") + .masquerade_as_nightly_cargo(&["public-dependency", "depth-public"]) + .with_stdout_data(str![[r#" +diamond v0.1.0 ([ROOT]/foo/diamond) +├── left-pub v0.1.0 ([ROOT]/foo/left-pub) +│ └── dep v0.1.0 ([ROOT]/foo/dep) +└── right-priv v0.1.0 ([ROOT]/foo/right-priv) + +"#]]) + .run(); + + p.cargo("tree --depth public") + .arg("-Zunstable-options") + .masquerade_as_nightly_cargo(&["public-dependency", "depth-public"]) + .with_stdout_data(str![[r#" +dep v0.1.0 ([ROOT]/foo/dep) + +diamond v0.1.0 ([ROOT]/foo/diamond) +├── left-pub v0.1.0 ([ROOT]/foo/left-pub) +│ └── dep v0.1.0 ([ROOT]/foo/dep) +└── right-priv v0.1.0 ([ROOT]/foo/right-priv) + +left-pub v0.1.0 ([ROOT]/foo/left-pub) (*) + +right-priv v0.1.0 ([ROOT]/foo/right-priv) (*) + +"#]]) + .run(); + + p.cargo("tree --depth public --invert dep") + .arg("-Zunstable-options") + .masquerade_as_nightly_cargo(&["public-dependency", "depth-public"]) + .with_stdout_data(str![[r#" +dep v0.1.0 ([ROOT]/foo/dep) +└── left-pub v0.1.0 ([ROOT]/foo/left-pub) + └── diamond v0.1.0 ([ROOT]/foo/diamond) + +"#]]) + .run(); +} + #[cargo_test] fn prune() { let p = make_simple_proj(); diff --git a/tests/testsuite/cargo_tree/features.rs b/tests/testsuite/cargo_tree/features.rs index 313afa72c5e..038a8ddb777 100644 --- a/tests/testsuite/cargo_tree/features.rs +++ b/tests/testsuite/cargo_tree/features.rs @@ -350,3 +350,164 @@ foo v0.1.0 ([ROOT]/foo) "#]]) .run(); } + +#[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] +fn depth_public_no_features() { + Package::new("pub-defaultdep", "1.0.0").publish(); + Package::new("priv-defaultdep", "1.0.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["public-dependency"] + + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + pub-defaultdep = { version = "1.0.0", public = true } + priv-defaultdep = "1.0.0" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree -e features --depth public") + .arg("-Zunstable-options") + .masquerade_as_nightly_cargo(&["public-dependency", "depth-public"]) + .with_stdout_data(str![[r#" +foo v0.1.0 ([ROOT]/foo) +└── pub-defaultdep feature "default" + └── pub-defaultdep v1.0.0 + +"#]]) + .run(); +} + +#[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] +fn depth_public_transitive_features() { + Package::new("pub-defaultdep", "1.0.0") + .feature("default", &["f1"]) + .feature("f1", &["f2"]) + .feature("f2", &["optdep"]) + .add_dep(Dependency::new("optdep", "1.0").optional(true).public(true)) + .publish(); + Package::new("priv-defaultdep", "1.0.0") + .feature("default", &["f1"]) + .feature("f1", &["f2"]) + .feature("f2", &["optdep"]) + .add_dep(Dependency::new("optdep", "1.0").optional(true)) + .publish(); + Package::new("optdep", "1.0.0") + .feature("default", &["f"]) + .feature("f", &[]) + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["public-dependency"] + + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + pub-defaultdep = { version = "1.0.0", public = true } + priv-defaultdep = { version = "1.0.0", public = true } + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree -e features --depth public") + .arg("-Zunstable-options") + .masquerade_as_nightly_cargo(&["public-dependency", "depth-public"]) + .with_stdout_data(str![[r#" +foo v0.1.0 ([ROOT]/foo) +├── priv-defaultdep feature "default" +│ ├── priv-defaultdep v1.0.0 +│ └── priv-defaultdep feature "f1" +│ ├── priv-defaultdep v1.0.0 (*) +│ └── priv-defaultdep feature "f2" +│ ├── priv-defaultdep v1.0.0 (*) +│ └── priv-defaultdep feature "optdep" +│ └── priv-defaultdep v1.0.0 (*) +└── pub-defaultdep feature "default" + ├── pub-defaultdep v1.0.0 + │ └── optdep feature "default" + │ ├── optdep v1.0.0 + │ └── optdep feature "f" + │ └── optdep v1.0.0 + └── pub-defaultdep feature "f1" + ├── pub-defaultdep v1.0.0 (*) + └── pub-defaultdep feature "f2" + ├── pub-defaultdep v1.0.0 (*) + └── pub-defaultdep feature "optdep" + └── pub-defaultdep v1.0.0 (*) + +"#]]) + .run(); +} + +#[cargo_test(nightly, reason = "exported_private_dependencies lint is unstable")] +fn depth_public_cli() { + Package::new("priv", "1.0.0").feature("f", &[]).publish(); + Package::new("pub", "1.0.0").feature("f", &[]).publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["public-dependency"] + + [package] + name = "foo" + version = "0.1.0" + + [features] + priv-indirect = ["priv"] + priv = ["dep:priv", "priv?/f"] + pub-indirect = ["pub"] + pub = ["dep:pub", "priv?/f"] + + [dependencies] + priv = { version = "1.0.0", optional = true } + pub = { version = "1.0.0", optional = true, public = true } + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("tree -e features --depth public") + .arg("-Zunstable-options") + .masquerade_as_nightly_cargo(&["public-dependency", "depth-public"]) + .with_stdout_data(str![[r#" +foo v0.1.0 ([ROOT]/foo) + +"#]]) + .run(); + + p.cargo("tree -e features --depth public --features pub-indirect") + .arg("-Zunstable-options") + .masquerade_as_nightly_cargo(&["public-dependency", "depth-public"]) + .with_stdout_data(str![[r#" +foo v0.1.0 ([ROOT]/foo) +└── pub feature "default" + └── pub v1.0.0 + +"#]]) + .run(); + + p.cargo("tree -e features --depth public --features priv-indirect") + .arg("-Zunstable-options") + .masquerade_as_nightly_cargo(&["public-dependency", "depth-public"]) + .with_stdout_data(str![[r#" +foo v0.1.0 ([ROOT]/foo) + +"#]]) + .run(); +}