From 8988b909d31f1f9c2914ea4dba16af8dcc4cb10b Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Wed, 24 Jun 2026 09:14:13 -0700 Subject: [PATCH 1/3] test(tree): record artifact dependency display Add passing snapshots for how cargo tree currently renders artifact dependencies. The fixtures cover selected binaries, multiple artifact kinds, explicit targets, feature trees, custom formatting, inversion, pruning, dependency categories, and lib = true. Shared artifact fixtures record reversed artifact-kind declarations, depth limiting, deduplication, and negative dependency-kind filtering. These snapshots establish the behavior changed by the later feature commit. --- tests/testsuite/artifact_dep.rs | 263 ++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) diff --git a/tests/testsuite/artifact_dep.rs b/tests/testsuite/artifact_dep.rs index 4842127bcc5..bcc96cb06ad 100644 --- a/tests/testsuite/artifact_dep.rs +++ b/tests/testsuite/artifact_dep.rs @@ -986,6 +986,209 @@ fn normal_build_deps_are_picked_up_in_presence_of_an_artifact_build_dep_to_the_s p.cargo("check -Z bindeps") .masquerade_as_nightly_cargo(&["bindeps"]) .run(); + + p.cargo("tree -Z bindeps") + .masquerade_as_nightly_cargo(&["bindeps"]) + .with_stdout_data(str![[r#" +foo v0.0.0 ([ROOT]/foo) +└── bar v0.5.0 ([ROOT]/foo/bar) +[build-dependencies] +└── bar v0.5.0 ([ROOT]/foo/bar) + +"#]]) + .with_status(0) + .run(); + + p.cargo("tree -Z bindeps --format '<{p}>'") + .masquerade_as_nightly_cargo(&["bindeps"]) + .with_stdout_data(str![[r#" + +└── +[build-dependencies] +└── + +"#]]) + .with_status(0) + .run(); + + p.cargo("tree -Z bindeps --invert bar") + .masquerade_as_nightly_cargo(&["bindeps"]) + .with_stdout_data(str![[r#" +bar v0.5.0 ([ROOT]/foo/bar) +└── foo v0.0.0 ([ROOT]/foo) +[build-dependencies] +└── foo v0.0.0 ([ROOT]/foo) + +"#]]) + .with_status(0) + .run(); + + p.cargo("tree -Z bindeps --prune bar") + .masquerade_as_nightly_cargo(&["bindeps"]) + .with_stdout_data(str![[r#" +foo v0.0.0 ([ROOT]/foo) +[build-dependencies] + +"#]]) + .with_status(0) + .run(); +} + +#[cargo_test] +fn cargo_tree_displays_lib_true_as_separate_lib_and_artifact_edges() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.0" + edition = "2015" + authors = [] + resolver = "2" + + [dependencies] + bar = { path = "bar", artifact = "bin", lib = true } + "#, + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_bin_manifest("bar")) + .file("bar/src/lib.rs", "pub fn f() {}") + .file("bar/src/main.rs", "fn main() {}") + .build(); + + p.cargo("tree -Z bindeps") + .masquerade_as_nightly_cargo(&["bindeps"]) + .with_stdout_data(str![[r#" +foo v0.0.0 ([ROOT]/foo) +└── bar v0.5.0 ([ROOT]/foo/bar) + +"#]]) + .with_status(0) + .run(); +} + +#[cargo_test] +fn cargo_tree_artifact_node_traversal() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.0" + + [dependencies] + left = { path = "left" } + right = { path = "right" } + "#, + ) + .file("src/lib.rs", "") + .file( + "left/Cargo.toml", + r#" + [package] + name = "left" + version = "0.0.0" + + [build-dependencies] + bar = { path = "../bar", artifact = ["staticlib", "cdylib"] } + "#, + ) + .file("left/src/lib.rs", "") + .file("left/build.rs", "fn main() {}") + .file( + "right/Cargo.toml", + r#" + [package] + name = "right" + version = "0.0.0" + + [build-dependencies] + bar = { path = "../bar", artifact = ["cdylib", "staticlib"] } + "#, + ) + .file("right/src/lib.rs", "") + .file("right/build.rs", "fn main() {}") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.5.0" + + [lib] + crate-type = ["cdylib", "staticlib"] + "#, + ) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("tree -Z bindeps") + .masquerade_as_nightly_cargo(&["bindeps"]) + .with_stdout_data(str![[r#" +foo v0.0.0 ([ROOT]/foo) +├── left v0.0.0 ([ROOT]/foo/left) +│ [build-dependencies] +│ └── bar v0.5.0 ([ROOT]/foo/bar) +└── right v0.0.0 ([ROOT]/foo/right) + [build-dependencies] + └── bar v0.5.0 ([ROOT]/foo/bar) + +"#]]) + .run(); + + p.cargo("tree -Z bindeps --no-dedupe") + .masquerade_as_nightly_cargo(&["bindeps"]) + .with_stdout_data(str![[r#" +foo v0.0.0 ([ROOT]/foo) +├── left v0.0.0 ([ROOT]/foo/left) +│ [build-dependencies] +│ └── bar v0.5.0 ([ROOT]/foo/bar) +└── right v0.0.0 ([ROOT]/foo/right) + [build-dependencies] + └── bar v0.5.0 ([ROOT]/foo/bar) + +"#]]) + .run(); + + p.cargo("tree -Z bindeps --depth 2") + .masquerade_as_nightly_cargo(&["bindeps"]) + .with_stdout_data(str![[r#" +foo v0.0.0 ([ROOT]/foo) +├── left v0.0.0 ([ROOT]/foo/left) +│ [build-dependencies] +│ └── bar v0.5.0 ([ROOT]/foo/bar) +└── right v0.0.0 ([ROOT]/foo/right) + [build-dependencies] + └── bar v0.5.0 ([ROOT]/foo/bar) + +"#]]) + .run(); + + p.cargo("tree -Z bindeps --depth 3") + .masquerade_as_nightly_cargo(&["bindeps"]) + .with_stdout_data(str![[r#" +foo v0.0.0 ([ROOT]/foo) +├── left v0.0.0 ([ROOT]/foo/left) +│ [build-dependencies] +│ └── bar v0.5.0 ([ROOT]/foo/bar) +└── right v0.0.0 ([ROOT]/foo/right) + [build-dependencies] + └── bar v0.5.0 ([ROOT]/foo/bar) + +"#]]) + .run(); + + p.cargo("tree -Z bindeps -e no-build") + .masquerade_as_nightly_cargo(&["bindeps"]) + .with_stdout_data(str![[r#" +foo v0.0.0 ([ROOT]/foo) +├── left v0.0.0 ([ROOT]/foo/left) +└── right v0.0.0 ([ROOT]/foo/right) + +"#]]) + .run(); } #[cargo_test] @@ -1495,6 +1698,55 @@ foo v0.0.0 ([ROOT]/foo) .run(); } +#[cargo_test] +fn cargo_tree_displays_multiple_artifact_kinds() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.0" + edition = "2015" + authors = [] + resolver = "2" + + [dependencies] + bar = { path = "bar", artifact = ["bin:baz-suffix", "staticlib", "cdylib"] } + "#, + ) + .file("src/lib.rs", "") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.5.0" + edition = "2015" + authors = [] + + [lib] + crate-type = ["staticlib", "cdylib"] + + [[bin]] + name = "baz-suffix" + "#, + ) + .file("bar/src/lib.rs", "pub fn bar() {}") + .file("bar/src/main.rs", "fn main() {}") + .build(); + + p.cargo("tree -Z bindeps") + .masquerade_as_nightly_cargo(&["bindeps"]) + .with_stdout_data(str![[r#" +foo v0.0.0 ([ROOT]/foo) +└── bar v0.5.0 ([ROOT]/foo/bar) + +"#]]) + .with_status(0) + .run(); +} + #[cargo_test] fn artifact_dep_target_specified() { if cross_compile_disabled() { @@ -1541,6 +1793,17 @@ fn artifact_dep_target_specified() { foo v0.0.0 ([ROOT]/foo) └── bindep v0.0.0 ([ROOT]/foo/bindep) +"#]]) + .with_status(0) + .run(); + + p.cargo("tree -Z bindeps -e features") + .masquerade_as_nightly_cargo(&["bindeps"]) + .with_stdout_data(str![[r#" +foo v0.0.0 ([ROOT]/foo) +└── bindep feature "default" + └── bindep v0.0.0 ([ROOT]/foo/bindep) + "#]]) .with_status(0) .run(); From 439267937814a9fb85adbfb033616c112f173ef8 Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Sat, 11 Jul 2026 19:14:37 -0400 Subject: [PATCH 2/3] refactor(tree): extract dependency edge insertion The logic that routes a dependency edge through feature nodes was inline in `add_pkg`. Extract it into `add_dependency_edge`, alongside the existing `add_pkg` and `add_feature` free functions. The helper accepts the source node and edge so graph construction can reuse feature routing without maintaining a second package index. Also name the inherited compilation context `base_features_for` before applying an artifact target override. Tree output is unchanged and no tests are modified. --- src/cargo/ops/tree/graph.rs | 95 ++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 44 deletions(-) diff --git a/src/cargo/ops/tree/graph.rs b/src/cargo/ops/tree/graph.rs index a75025c39da..492a8535d4f 100644 --- a/src/cargo/ops/tree/graph.rs +++ b/src/cargo/ops/tree/graph.rs @@ -5,7 +5,9 @@ use crate::core::compiler::{CompileKind, RustcTargetData}; use crate::core::dependency::DepKind; use crate::core::resolver::Resolve; use crate::core::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures}; -use crate::core::{FeatureMap, FeatureValue, Package, PackageId, PackageIdSpec, Workspace}; +use crate::core::{ + Dependency, FeatureMap, FeatureValue, Package, PackageId, PackageIdSpec, Workspace, +}; use crate::util::CargoResult; use crate::util::data_structures::{HashMap, HashSet}; use crate::util::interning::{INTERNED_DEFAULT, InternedString}; @@ -486,33 +488,23 @@ fn add_pkg( let dep_pkg = graph.package_map[&dep_id]; for dep in deps { - let dep_features_for = match dep + // Inherit the dependent package's compilation context. Artifact + // dependencies with an explicit target and host dependencies need + // their transitive dependencies built in that same context. + let base_features_for = if features_for != FeaturesFor::default() { + features_for + } else if dep.is_build() || dep_pkg.proc_macro() { + FeaturesFor::HostDep + } else { + features_for + }; + // An explicit artifact target overrides the inherited context. + let dep_features_for = dep .artifact() .and_then(|artifact| artifact.target()) .and_then(|target| target.to_resolved_compile_target(requested_kind)) - { - // Dependency has a `{ …, target = }` - Some(target) => FeaturesFor::ArtifactDep(target), - // Get the information of the dependent crate from `features_for`. - // If a dependent crate is - // - // * specified as an artifact dep with a `target`, or - // * a host dep, - // - // its transitive deps, including build-deps, need to be built on that target. - None if features_for != FeaturesFor::default() => features_for, - // Dependent crate is a normal dep, then back to old rules: - // - // * normal deps, dev-deps -> inherited target - // * build-deps -> host - None => { - if dep.is_build() || dep_pkg.proc_macro() { - FeaturesFor::HostDep - } else { - features_for - } - } - }; + .map(FeaturesFor::ArtifactDep) + .unwrap_or(base_features_for); let dep_index = add_pkg( graph, resolve, @@ -528,25 +520,7 @@ fn add_pkg( node: dep_index, public: dep.is_public(), }; - if opts.graph_features { - // Add the dependency node with feature nodes in-between. - dep_name_map - .entry(dep.name_in_toml()) - .or_default() - .insert((dep_index, dep.is_optional())); - if dep.uses_default_features() { - add_feature(graph, INTERNED_DEFAULT, Some(from_index), new_edge); - } - for feature in dep.features().iter() { - add_feature(graph, *feature, Some(from_index), new_edge); - } - if !dep.uses_default_features() && dep.features().is_empty() { - // No features, use a direct connection. - graph.edges_mut(from_index).add_edge(new_edge); - } - } else { - graph.edges_mut(from_index).add_edge(new_edge); - } + add_dependency_edge(graph, &mut dep_name_map, from_index, dep, new_edge, opts); } } if opts.graph_features { @@ -561,6 +535,39 @@ fn add_pkg( from_index } +/// Adds an edge from `from` to the package `edge` points at. +/// +/// When feature edges are enabled, the edge is routed through the feature +/// nodes requested by the dependency. Otherwise, it is added directly. +fn add_dependency_edge( + graph: &mut Graph<'_>, + dep_name_map: &mut HashMap>, + from: NodeId, + dep: &Dependency, + edge: Edge, + opts: &TreeOptions, +) { + if opts.graph_features { + // Add the dependency node with feature nodes in-between. + dep_name_map + .entry(dep.name_in_toml()) + .or_default() + .insert((edge.node(), dep.is_optional())); + if dep.uses_default_features() { + add_feature(graph, INTERNED_DEFAULT, Some(from), edge); + } + for feature in dep.features().iter() { + add_feature(graph, *feature, Some(from), edge); + } + if !dep.uses_default_features() && dep.features().is_empty() { + // No features, use a direct connection. + graph.edges_mut(from).add_edge(edge); + } + } else { + graph.edges_mut(from).add_edge(edge); + } +} + /// Adds a feature node between two nodes. /// /// That is, it adds the following: From e36bbbf580fa2f6b4c3368615d3ac013686112f6 Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Wed, 24 Jun 2026 09:16:31 -0700 Subject: [PATCH 3/3] feat(tree): display artifact dependency requests as nodes Represent each artifact request as an intermediate node between the dependent and the package that provides the artifact. The node records artifact kinds and target while the incoming edge preserves the dependency category. Artifact kinds use canonical ordering so requests that differ only in manifest order share one node. Feature trees route artifact requests through feature nodes before reaching the package. Inverted trees print requests before dependency section headings so their original category remains unambiguous. Custom package formats do not hide artifact details, and dependencies with lib = true keep a separate ordinary package edge. Artifact request nodes deliberately count as a level for --depth. --- src/cargo/ops/tree/format/mod.rs | 18 +++++ src/cargo/ops/tree/graph.rs | 113 ++++++++++++++++++++++++++---- src/cargo/ops/tree/mod.rs | 9 +++ src/doc/src/reference/unstable.md | 10 +++ tests/testsuite/artifact_dep.rs | 60 ++++++++++------ 5 files changed, 176 insertions(+), 34 deletions(-) diff --git a/src/cargo/ops/tree/format/mod.rs b/src/cargo/ops/tree/format/mod.rs index 55bd954d67e..e2cad5bc15c 100644 --- a/src/cargo/ops/tree/format/mod.rs +++ b/src/cargo/ops/tree/format/mod.rs @@ -128,6 +128,24 @@ impl<'a> fmt::Display for Display<'a> { _ => panic!("unexpected feature node {:?}", for_node), } } + Node::Artifact { + package_id, + artifacts, + target, + .. + } => { + write!(fmt, "{} artifact \"", package_id.name())?; + for (index, artifact) in artifacts.iter().enumerate() { + if index != 0 { + fmt.write_str(", ")?; + } + write!(fmt, "{artifact}")?; + } + fmt.write_str("\"")?; + if let Some(target) = target { + write!(fmt, " (target: {})", target.as_str())?; + } + } } Ok(()) diff --git a/src/cargo/ops/tree/graph.rs b/src/cargo/ops/tree/graph.rs index 492a8535d4f..fd830f5b230 100644 --- a/src/cargo/ops/tree/graph.rs +++ b/src/cargo/ops/tree/graph.rs @@ -2,7 +2,7 @@ use super::TreeOptions; use crate::core::compiler::{CompileKind, RustcTargetData}; -use crate::core::dependency::DepKind; +use crate::core::dependency::{Artifact, ArtifactKind, ArtifactTarget, DepKind}; use crate::core::resolver::Resolve; use crate::core::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures}; use crate::core::{ @@ -65,6 +65,20 @@ pub enum Node { /// Name of the feature. name: InternedString, }, + /// An artifact request that connects a dependent to the package build + /// providing the requested artifacts. + Artifact { + /// The package providing the artifacts. + package_id: PackageId, + /// Features enabled for this package build. + features: Vec, + /// The resolved compilation target for this package build. + kind: CompileKind, + /// Artifact kinds requested by the dependent. + artifacts: Vec, + /// The target expression written on the artifact dependency. + target: Option, + }, } impl Node { @@ -72,6 +86,7 @@ impl Node { match self { Self::Package { package_id, .. } => package_id.name(), Self::Feature { name, .. } => *name, + Self::Artifact { package_id, .. } => package_id.name(), } } } @@ -101,14 +116,16 @@ impl Edge { #[derive(Debug, Copy, Hash, Eq, Clone, PartialEq)] pub enum EdgeKind { Dep(DepKind), + /// Connects an artifact request node to its package or feature nodes. + Artifact, Feature, } /// Set of outgoing edges for a single node. /// -/// Edges are separated by the edge kind (`DepKind` or `Feature`). This is -/// primarily done so that the output can easily display separate sections -/// like `[build-dependencies]`. +/// Edges are separated by [`EdgeKind`]. Dependency edges determine sections +/// like `[build-dependencies]`. Artifact and feature edges connect +/// intermediate request nodes without creating another dependency section. /// /// The value is a `Vec` because each edge kind can have multiple outgoing /// edges. For example, package "foo" can have multiple normal dependencies. @@ -238,7 +255,9 @@ impl<'a> Graph<'a> { fn package_id_for_index(&self, index: NodeId) -> PackageId { match self.node(index) { Node::Package { package_id, .. } => *package_id, - Node::Feature { .. } => panic!("unexpected feature node"), + Node::Feature { .. } | Node::Artifact { .. } => { + panic!("unexpected non-package node") + } } } @@ -498,6 +517,25 @@ fn add_pkg( } else { features_for }; + if dep.artifact().is_some_and(|artifact| artifact.is_lib()) { + let dep_index = add_pkg( + graph, + resolve, + resolved_features, + dep_id, + base_features_for, + target_data, + requested_kind, + opts, + ); + let new_edge = Edge { + kind: EdgeKind::Dep(dep.kind()), + node: dep_index, + public: dep.is_public(), + }; + add_dependency_edge(graph, &mut dep_name_map, from_index, dep, new_edge, opts); + } + // An explicit artifact target overrides the inherited context. let dep_features_for = dep .artifact() @@ -515,12 +553,39 @@ fn add_pkg( requested_kind, opts, ); - let new_edge = Edge { - kind: EdgeKind::Dep(dep.kind()), - node: dep_index, - public: dep.is_public(), - }; - add_dependency_edge(graph, &mut dep_name_map, from_index, dep, new_edge, opts); + if let Some(artifact) = dep.artifact() { + let artifact_index = add_artifact(graph, dep_index, artifact); + graph.edges_mut(from_index).add_edge(Edge { + kind: EdgeKind::Dep(dep.kind()), + node: artifact_index, + public: dep.is_public(), + }); + add_dependency_edge( + graph, + &mut dep_name_map, + artifact_index, + dep, + Edge { + kind: EdgeKind::Artifact, + node: dep_index, + public: true, + }, + opts, + ); + } else { + add_dependency_edge( + graph, + &mut dep_name_map, + from_index, + dep, + Edge { + kind: EdgeKind::Dep(dep.kind()), + node: dep_index, + public: dep.is_public(), + }, + opts, + ); + } } } if opts.graph_features { @@ -535,6 +600,30 @@ fn add_pkg( from_index } +fn add_artifact(graph: &mut Graph<'_>, package_index: NodeId, artifact: &Artifact) -> NodeId { + let Node::Package { + package_id, + features, + kind, + } = graph.node(package_index) + else { + unreachable!("artifact destination must be a package node"); + }; + let mut artifacts = artifact.kinds().to_vec(); + artifacts.sort_unstable(); + let node = Node::Artifact { + package_id: *package_id, + features: features.clone(), + kind: *kind, + artifacts, + target: artifact.target(), + }; + match graph.index.get(&node) { + Some(index) => *index, + None => graph.add_node(node), + } +} + /// Adds an edge from `from` to the package `edge` points at. /// /// When feature edges are enabled, the edge is routed through the feature @@ -706,7 +795,7 @@ fn add_internal_features(graph: &mut Graph<'_>, resolve: &Resolve) { .iter() .enumerate() .filter_map(|(i, node)| match node { - Node::Package { .. } => None, + Node::Package { .. } | Node::Artifact { .. } => None, Node::Feature { node_index, name } => { let package_id = graph.package_id_for_index(*node_index); Some((package_id, *node_index, NodeId::new(i, *name), *name)) diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index 9291a18724d..790810bffb0 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -359,6 +359,7 @@ fn print_node<'a>( for kind in &[ EdgeKind::Dep(DepKind::Normal), + EdgeKind::Artifact, EdgeKind::Dep(DepKind::Build), EdgeKind::Dep(DepKind::Development), EdgeKind::Feature, @@ -413,6 +414,7 @@ fn print_dependencies<'a>( EdgeKind::Dep(DepKind::Development) => Some(color_print::cstr!( "[dev-dependencies]" )), + EdgeKind::Artifact => None, EdgeKind::Feature => None, }; @@ -448,6 +450,12 @@ fn print_dependencies<'a>( } !pkgs_to_prune.iter().any(|spec| spec.matches(*package_id)) } + Node::Artifact { 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)) + } Node::Feature { .. } => true, } }) @@ -481,6 +489,7 @@ fn edge_line_color(kind: EdgeKind) -> anstyle::Style { EdgeKind::Dep(DepKind::Normal) => style::DEP_NORMAL, EdgeKind::Dep(DepKind::Build) => style::DEP_BUILD, EdgeKind::Dep(DepKind::Development) => style::DEP_DEV, + EdgeKind::Artifact => style::DEP_NORMAL, EdgeKind::Feature => style::DEP_FEATURE, } } diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 06eaf2943f2..743896ef5fd 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -1082,6 +1082,16 @@ For each kind of dependency, these variables are supplied to the same part of th [`env!`]: https://doc.rust-lang.org/std/macro.env.html +### artifact-dependencies: `cargo tree` + +`cargo tree -Z bindeps` displays an artifact dependency as an artifact request +node followed by the package that provides it. The request node shows the +artifact kinds and `target`, when present, and remains in the dependency's +normal, build, or development section. An artifact dependency with `lib = true` +is shown as both a normal package dependency and an artifact request. Custom +`--format` strings apply to package nodes, but not to artifact request nodes. +Artifact request nodes count as a level when limiting the tree with `--depth`. + ### artifact-dependencies: Examples #### Example: use a binary executable from a build script diff --git a/tests/testsuite/artifact_dep.rs b/tests/testsuite/artifact_dep.rs index bcc96cb06ad..fae0361d164 100644 --- a/tests/testsuite/artifact_dep.rs +++ b/tests/testsuite/artifact_dep.rs @@ -991,7 +991,8 @@ fn normal_build_deps_are_picked_up_in_presence_of_an_artifact_build_dep_to_the_s .masquerade_as_nightly_cargo(&["bindeps"]) .with_stdout_data(str![[r#" foo v0.0.0 ([ROOT]/foo) -└── bar v0.5.0 ([ROOT]/foo/bar) +└── bar artifact "bin:bar" + └── bar v0.5.0 ([ROOT]/foo/bar) [build-dependencies] └── bar v0.5.0 ([ROOT]/foo/bar) @@ -1003,7 +1004,8 @@ foo v0.0.0 ([ROOT]/foo) .masquerade_as_nightly_cargo(&["bindeps"]) .with_stdout_data(str![[r#" -└── +└── bar artifact "bin:bar" + └── [build-dependencies] └── @@ -1015,7 +1017,8 @@ foo v0.0.0 ([ROOT]/foo) .masquerade_as_nightly_cargo(&["bindeps"]) .with_stdout_data(str![[r#" bar v0.5.0 ([ROOT]/foo/bar) -└── foo v0.0.0 ([ROOT]/foo) +└── bar artifact "bin:bar" + └── foo v0.0.0 ([ROOT]/foo) [build-dependencies] └── foo v0.0.0 ([ROOT]/foo) @@ -1061,7 +1064,9 @@ fn cargo_tree_displays_lib_true_as_separate_lib_and_artifact_edges() { .masquerade_as_nightly_cargo(&["bindeps"]) .with_stdout_data(str![[r#" foo v0.0.0 ([ROOT]/foo) -└── bar v0.5.0 ([ROOT]/foo/bar) +├── bar v0.5.0 ([ROOT]/foo/bar) +└── bar artifact "bin" + └── bar v0.5.0 ([ROOT]/foo/bar) "#]]) .with_status(0) @@ -1130,10 +1135,11 @@ fn cargo_tree_artifact_node_traversal() { foo v0.0.0 ([ROOT]/foo) ├── left v0.0.0 ([ROOT]/foo/left) │ [build-dependencies] -│ └── bar v0.5.0 ([ROOT]/foo/bar) +│ └── bar artifact "cdylib, staticlib" +│ └── bar v0.5.0 ([ROOT]/foo/bar) └── right v0.0.0 ([ROOT]/foo/right) [build-dependencies] - └── bar v0.5.0 ([ROOT]/foo/bar) + └── bar artifact "cdylib, staticlib" (*) "#]]) .run(); @@ -1144,10 +1150,12 @@ foo v0.0.0 ([ROOT]/foo) foo v0.0.0 ([ROOT]/foo) ├── left v0.0.0 ([ROOT]/foo/left) │ [build-dependencies] -│ └── bar v0.5.0 ([ROOT]/foo/bar) +│ └── bar artifact "cdylib, staticlib" +│ └── bar v0.5.0 ([ROOT]/foo/bar) └── right v0.0.0 ([ROOT]/foo/right) [build-dependencies] - └── bar v0.5.0 ([ROOT]/foo/bar) + └── bar artifact "cdylib, staticlib" + └── bar v0.5.0 ([ROOT]/foo/bar) "#]]) .run(); @@ -1158,10 +1166,10 @@ foo v0.0.0 ([ROOT]/foo) foo v0.0.0 ([ROOT]/foo) ├── left v0.0.0 ([ROOT]/foo/left) │ [build-dependencies] -│ └── bar v0.5.0 ([ROOT]/foo/bar) +│ └── bar artifact "cdylib, staticlib" └── right v0.0.0 ([ROOT]/foo/right) [build-dependencies] - └── bar v0.5.0 ([ROOT]/foo/bar) + └── bar artifact "cdylib, staticlib" (*) "#]]) .run(); @@ -1172,10 +1180,11 @@ foo v0.0.0 ([ROOT]/foo) foo v0.0.0 ([ROOT]/foo) ├── left v0.0.0 ([ROOT]/foo/left) │ [build-dependencies] -│ └── bar v0.5.0 ([ROOT]/foo/bar) +│ └── bar artifact "cdylib, staticlib" +│ └── bar v0.5.0 ([ROOT]/foo/bar) └── right v0.0.0 ([ROOT]/foo/right) [build-dependencies] - └── bar v0.5.0 ([ROOT]/foo/bar) + └── bar artifact "cdylib, staticlib" (*) "#]]) .run(); @@ -1685,14 +1694,16 @@ fn dependencies_of_dependencies_work_in_artifacts() { .masquerade_as_nightly_cargo(&["bindeps"]) .run(); - // cargo tree sees artifacts as the dependency kind they are in and doesn't do anything special with it. + // cargo tree shows artifact dependencies in their dependency section as + // request nodes followed by the packages that provide them. p.cargo("tree -Z bindeps") .masquerade_as_nightly_cargo(&["bindeps"]) .with_stdout_data(str![[r#" foo v0.0.0 ([ROOT]/foo) [build-dependencies] -└── bar v0.5.0 ([ROOT]/foo/bar) - └── baz v1.0.0 +└── bar artifact "bin" + └── bar v0.5.0 ([ROOT]/foo/bar) + └── baz v1.0.0 "#]]) .run(); @@ -1740,7 +1751,8 @@ fn cargo_tree_displays_multiple_artifact_kinds() { .masquerade_as_nightly_cargo(&["bindeps"]) .with_stdout_data(str![[r#" foo v0.0.0 ([ROOT]/foo) -└── bar v0.5.0 ([ROOT]/foo/bar) +└── bar artifact "bin:baz-suffix, cdylib, staticlib" + └── bar v0.5.0 ([ROOT]/foo/bar) "#]]) .with_status(0) @@ -1791,7 +1803,8 @@ fn artifact_dep_target_specified() { .masquerade_as_nightly_cargo(&["bindeps"]) .with_stdout_data(str![[r#" foo v0.0.0 ([ROOT]/foo) -└── bindep v0.0.0 ([ROOT]/foo/bindep) +└── bindep artifact "bin" (target: [ALT_TARGET]) + └── bindep v0.0.0 ([ROOT]/foo/bindep) "#]]) .with_status(0) @@ -1801,8 +1814,9 @@ foo v0.0.0 ([ROOT]/foo) .masquerade_as_nightly_cargo(&["bindeps"]) .with_stdout_data(str![[r#" foo v0.0.0 ([ROOT]/foo) -└── bindep feature "default" - └── bindep v0.0.0 ([ROOT]/foo/bindep) +└── bindep artifact "bin" (target: [ALT_TARGET]) + └── bindep feature "default" + └── bindep v0.0.0 ([ROOT]/foo/bindep) "#]]) .with_status(0) @@ -1881,8 +1895,9 @@ fn dep_of_artifact_dep_same_target_specified() { .with_stdout_data( r#"... foo v0.1.0 ([ROOT]/foo) -└── bar v0.1.0 ([ROOT]/foo/bar) - └── baz v0.1.0 ([ROOT]/foo/baz) +└── bar artifact "bin" (target: [ALT_TARGET]) + └── bar v0.1.0 ([ROOT]/foo/bar) + └── baz v0.1.0 ([ROOT]/foo/baz) "#, ) .with_status(0) @@ -3643,7 +3658,8 @@ fn transitive_artifact_dep_with_target_and_platform_specific_dep() { .with_stdout_data(str![[r#" foo v0.0.0 ([ROOT]/foo) └── bar v0.0.0 ([ROOT]/foo/bar) - └── baz v0.0.0 ([ROOT]/foo/bar/baz) + └── baz artifact "bin" (target: x86_64-unknown-none) + └── baz v0.0.0 ([ROOT]/foo/bar/baz) "#]]) .run();