Skip to content

Commit 78717bd

Browse files
committed
feat(tree): show artifact dependency details
Carry artifact dependency metadata on cargo tree edges and annotate artifact edges with a compact suffix like (bin), (bin:name), or (bin, target). The annotation is edge-local because the same package can be reached through both normal and artifact dependency edges, or through multiple artifact aliases with different requested artifacts or targets. Artifact dependencies remain in their normal/build/dev dependency sections. Render lib=true as two relationships: a normal dependency edge plus an artifact dependency edge. This matches the manifest semantics without needing a separate lib=true suffix. Refs #14804.
1 parent 5b4e1dc commit 78717bd

4 files changed

Lines changed: 165 additions & 55 deletions

File tree

src/cargo/ops/tree/graph.rs

Lines changed: 131 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,12 @@ impl Node {
7474
}
7575
}
7676

77-
#[derive(Debug, Copy, Hash, Eq, Clone, PartialEq)]
77+
#[derive(Debug, Hash, Eq, Clone, PartialEq)]
7878
pub struct Edge {
7979
kind: EdgeKind,
8080
node: NodeId,
8181
public: bool,
82+
artifact: Option<ArtifactEdge>,
8283
}
8384

8485
impl Edge {
@@ -93,6 +94,34 @@ impl Edge {
9394
pub fn public(&self) -> bool {
9495
self.public
9596
}
97+
98+
pub fn artifact(&self) -> Option<&ArtifactEdge> {
99+
self.artifact.as_ref()
100+
}
101+
}
102+
103+
#[derive(Debug, Hash, Eq, Clone, Ord, PartialEq, PartialOrd)]
104+
pub struct ArtifactEdge {
105+
kinds: Vec<String>,
106+
target: Option<String>,
107+
}
108+
109+
impl ArtifactEdge {
110+
pub fn display(&self) -> ArtifactEdgeDisplay<'_> {
111+
ArtifactEdgeDisplay(self)
112+
}
113+
}
114+
115+
pub struct ArtifactEdgeDisplay<'a>(&'a ArtifactEdge);
116+
117+
impl std::fmt::Display for ArtifactEdgeDisplay<'_> {
118+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119+
f.write_str(&self.0.kinds.join(", "))?;
120+
if let Some(target) = &self.0.target {
121+
write!(f, ", {target}")?;
122+
}
123+
Ok(())
124+
}
96125
}
97126

98127
/// The kind of edge, for separating dependencies into different sections.
@@ -189,7 +218,11 @@ impl<'a> Graph<'a> {
189218
let edges = self.edges(from).of_kind(kind);
190219
// Created a sorted list for consistent output.
191220
let mut edges = edges.to_owned();
192-
edges.sort_unstable_by(|a, b| self.node(a.node()).cmp(&self.node(b.node())));
221+
edges.sort_unstable_by(|a, b| {
222+
self.node(a.node())
223+
.cmp(&self.node(b.node()))
224+
.then_with(|| a.artifact.cmp(&b.artifact))
225+
});
193226
edges
194227
}
195228

@@ -275,6 +308,7 @@ impl<'a> Graph<'a> {
275308
kind: edge.kind(),
276309
node: new_to_index,
277310
public: edge.public(),
311+
artifact: edge.artifact().cloned(),
278312
};
279313
new_graph.edges_mut(new_from).add_edge(new_edge);
280314
}
@@ -298,6 +332,7 @@ impl<'a> Graph<'a> {
298332
kind: edge.kind(),
299333
node: NodeId::new(from_idx, self.nodes[from_idx].name()),
300334
public: edge.public(),
335+
artifact: edge.artifact().cloned(),
301336
};
302337
new_edges[edge.node().index].add_edge(new_edge);
303338
}
@@ -486,33 +521,48 @@ fn add_pkg(
486521
let dep_pkg = graph.package_map[&dep_id];
487522

488523
for dep in deps {
489-
let dep_features_for = match dep
524+
let base_features_for = if features_for != FeaturesFor::default() {
525+
features_for
526+
} else if dep.is_build() || dep_pkg.proc_macro() {
527+
FeaturesFor::HostDep
528+
} else {
529+
features_for
530+
};
531+
532+
if dep.artifact().is_some_and(|artifact| artifact.is_lib()) {
533+
let dep_index = add_pkg(
534+
graph,
535+
resolve,
536+
resolved_features,
537+
dep_id,
538+
base_features_for,
539+
target_data,
540+
requested_kind,
541+
opts,
542+
);
543+
let new_edge = Edge {
544+
kind: EdgeKind::Dep(dep.kind()),
545+
node: dep_index,
546+
public: dep.is_public(),
547+
artifact: None,
548+
};
549+
add_edge(
550+
graph,
551+
&mut dep_name_map,
552+
from_index,
553+
dep_index,
554+
dep,
555+
new_edge,
556+
opts,
557+
);
558+
}
559+
560+
let dep_features_for = dep
490561
.artifact()
491562
.and_then(|artifact| artifact.target())
492563
.and_then(|target| target.to_resolved_compile_target(requested_kind))
493-
{
494-
// Dependency has a `{ …, target = <triple> }`
495-
Some(target) => FeaturesFor::ArtifactDep(target),
496-
// Get the information of the dependent crate from `features_for`.
497-
// If a dependent crate is
498-
//
499-
// * specified as an artifact dep with a `target`, or
500-
// * a host dep,
501-
//
502-
// its transitive deps, including build-deps, need to be built on that target.
503-
None if features_for != FeaturesFor::default() => features_for,
504-
// Dependent crate is a normal dep, then back to old rules:
505-
//
506-
// * normal deps, dev-deps -> inherited target
507-
// * build-deps -> host
508-
None => {
509-
if dep.is_build() || dep_pkg.proc_macro() {
510-
FeaturesFor::HostDep
511-
} else {
512-
features_for
513-
}
514-
}
515-
};
564+
.map(FeaturesFor::ArtifactDep)
565+
.unwrap_or(base_features_for);
516566
let dep_index = add_pkg(
517567
graph,
518568
resolve,
@@ -527,26 +577,24 @@ fn add_pkg(
527577
kind: EdgeKind::Dep(dep.kind()),
528578
node: dep_index,
529579
public: dep.is_public(),
580+
artifact: dep.artifact().map(|artifact| ArtifactEdge {
581+
kinds: artifact
582+
.kinds()
583+
.iter()
584+
.map(|kind| kind.to_string())
585+
.collect(),
586+
target: artifact.target().map(|target| target.as_str().to_owned()),
587+
}),
530588
};
531-
if opts.graph_features {
532-
// Add the dependency node with feature nodes in-between.
533-
dep_name_map
534-
.entry(dep.name_in_toml())
535-
.or_default()
536-
.insert((dep_index, dep.is_optional()));
537-
if dep.uses_default_features() {
538-
add_feature(graph, INTERNED_DEFAULT, Some(from_index), new_edge);
539-
}
540-
for feature in dep.features().iter() {
541-
add_feature(graph, *feature, Some(from_index), new_edge);
542-
}
543-
if !dep.uses_default_features() && dep.features().is_empty() {
544-
// No features, use a direct connection.
545-
graph.edges_mut(from_index).add_edge(new_edge);
546-
}
547-
} else {
548-
graph.edges_mut(from_index).add_edge(new_edge);
549-
}
589+
add_edge(
590+
graph,
591+
&mut dep_name_map,
592+
from_index,
593+
dep_index,
594+
dep,
595+
new_edge,
596+
opts,
597+
);
550598
}
551599
}
552600
if opts.graph_features {
@@ -561,6 +609,36 @@ fn add_pkg(
561609
from_index
562610
}
563611

612+
fn add_edge(
613+
graph: &mut Graph<'_>,
614+
dep_name_map: &mut HashMap<InternedString, HashSet<(NodeId, bool)>>,
615+
from_index: NodeId,
616+
dep_index: NodeId,
617+
dep: &crate::core::Dependency,
618+
new_edge: Edge,
619+
opts: &TreeOptions,
620+
) {
621+
if opts.graph_features {
622+
// Add the dependency node with feature nodes in-between.
623+
dep_name_map
624+
.entry(dep.name_in_toml())
625+
.or_default()
626+
.insert((dep_index, dep.is_optional()));
627+
if dep.uses_default_features() {
628+
add_feature(graph, INTERNED_DEFAULT, Some(from_index), new_edge.clone());
629+
}
630+
for feature in dep.features().iter() {
631+
add_feature(graph, *feature, Some(from_index), new_edge.clone());
632+
}
633+
if !dep.uses_default_features() && dep.features().is_empty() {
634+
// No features, use a direct connection.
635+
graph.edges_mut(from_index).add_edge(new_edge);
636+
}
637+
} else {
638+
graph.edges_mut(from_index).add_edge(new_edge);
639+
}
640+
}
641+
564642
/// Adds a feature node between two nodes.
565643
///
566644
/// That is, it adds the following:
@@ -593,13 +671,15 @@ fn add_feature(
593671
kind: to.kind(),
594672
node: node_index,
595673
public: to.public(),
674+
artifact: to.artifact().cloned(),
596675
};
597676
graph.edges_mut(from).add_edge(from_edge);
598677
}
599678
let to_edge = Edge {
600679
kind: EdgeKind::Feature,
601680
node: to.node(),
602681
public: true,
682+
artifact: to.artifact().cloned(),
603683
};
604684
graph.edges_mut(node_index).add_edge(to_edge);
605685
(missing, node_index)
@@ -638,6 +718,7 @@ fn add_cli_features(
638718
kind: EdgeKind::Feature,
639719
node: package_index,
640720
public: true,
721+
artifact: None,
641722
};
642723
let index = add_feature(graph, feature, None, feature_edge).1;
643724
graph.cli_features.insert(index);
@@ -673,6 +754,7 @@ fn add_cli_features(
673754
kind: EdgeKind::Feature,
674755
node: package_index,
675756
public: true,
757+
artifact: None,
676758
};
677759
let index = add_feature(graph, dep_name, None, feature_edge).1;
678760
graph.cli_features.insert(index);
@@ -681,6 +763,7 @@ fn add_cli_features(
681763
kind: EdgeKind::Feature,
682764
node: dep_index,
683765
public: true,
766+
artifact: None,
684767
};
685768
let index = add_feature(graph, dep_feature, None, dep_edge).1;
686769
graph.cli_features.insert(index);
@@ -742,6 +825,7 @@ fn add_feature_rec(
742825
kind: EdgeKind::Feature,
743826
node: package_index,
744827
public: true,
828+
artifact: None,
745829
};
746830
let (missing, feat_index) = add_feature(graph, *dep_name, Some(from), feature_edge);
747831
// Don't recursive if the edge already exists to deal with cycles.
@@ -793,13 +877,15 @@ fn add_feature_rec(
793877
kind: EdgeKind::Feature,
794878
node: package_index,
795879
public: true,
880+
artifact: None,
796881
};
797882
add_feature(graph, *dep_name, Some(from), feature_edge);
798883
}
799884
let dep_edge = Edge {
800885
kind: EdgeKind::Feature,
801886
node: dep_index,
802887
public: true,
888+
artifact: None,
803889
};
804890
let (missing, feat_index) =
805891
add_feature(graph, *dep_feature, Some(from), dep_edge);

src/cargo/ops/tree/mod.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::util::CargoResult;
1111
use crate::util::style;
1212
use crate::{drop_print, drop_println};
1313
use anyhow::Context as _;
14-
use graph::Graph;
14+
use graph::{Edge, Graph};
1515
use std::collections::{HashMap, HashSet};
1616
use std::str::FromStr;
1717

@@ -283,6 +283,7 @@ fn print(
283283
ws,
284284
graph,
285285
root_index,
286+
None,
286287
&format,
287288
symbols,
288289
pkgs_to_prune,
@@ -303,6 +304,7 @@ fn print_node<'a>(
303304
ws: &Workspace<'_>,
304305
graph: &'a Graph<'_>,
305306
node_index: NodeId,
307+
incoming_edge: Option<&Edge>,
306308
format: &Pattern,
307309
symbols: &Symbols,
308310
pkgs_to_prune: &[PackageIdSpec],
@@ -350,7 +352,21 @@ fn print_node<'a>(
350352
} else {
351353
color_print::cstr!(" <yellow,dim>(*)</>")
352354
};
353-
drop_println!(ws.gctx(), "{}{}", format.display(graph, node_index), star);
355+
let artifact = match graph.node(node_index) {
356+
Node::Package { .. } => incoming_edge.and_then(|edge| edge.artifact()),
357+
Node::Feature { .. } => None,
358+
};
359+
if let Some(artifact) = artifact {
360+
drop_println!(
361+
ws.gctx(),
362+
"{} ({}){}",
363+
format.display(graph, node_index),
364+
artifact.display(),
365+
star
366+
);
367+
} else {
368+
drop_println!(ws.gctx(), "{}{}", format.display(graph, node_index), star);
369+
}
354370

355371
if !new || in_cycle {
356372
return Ok(());
@@ -460,6 +476,7 @@ fn print_dependencies<'a>(
460476
ws,
461477
graph,
462478
dependency.node(),
479+
Some(dependency),
463480
format,
464481
symbols,
465482
pkgs_to_prune,

src/doc/src/reference/unstable.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,11 @@ For each kind of dependency, these variables are supplied to the same part of th
10791079
- For normal dependencies, these variables are supplied during the compilation of the crate, and can be accessed using the [`env!`] macro.
10801080
- For dev-dependencies, these variables are supplied during the compilation of examples, tests, and benchmarks, and can be accessed using the [`env!`] macro.
10811081

1082+
`cargo tree -Z bindeps` annotates artifact dependency edges with the requested
1083+
artifact kinds and `target`, when present. An artifact dependency with
1084+
`lib = true` is shown as both a normal dependency edge and an artifact
1085+
dependency edge.
1086+
10821087
[`env!`]: https://doc.rust-lang.org/std/macro.env.html
10831088

10841089
### artifact-dependencies: Examples

0 commit comments

Comments
 (0)