cargo_hackerman/
explain.rs1use crate::{
2 feat_graph::{FeatGraph, HasIndex},
3 metadata::{DepKindInfo, Link},
4};
5
6use petgraph::{
7 graph::NodeIndex,
8 visit::{Dfs, EdgeFiltered, EdgeRef, IntoEdgesDirected, Reversed},
9};
10use semver::Version;
11use std::collections::BTreeSet;
12use tracing::{debug, info};
13
14fn collect_packages(
15 fg: &mut FeatGraph,
16
17 krate: &str,
18 feature: Option<&String>,
19 version: Option<&Version>,
20) -> Vec<NodeIndex> {
21 fg.features
22 .node_indices()
23 .filter(|&ix| {
24 if let Some(fid) = fg.features[ix].fid() {
25 let package = fid.pid.package();
26 package.name == krate
30 && feature.map_or(fid.pid.base() == fid, |f| fid.pid.named(f) == fid)
31 && version.map_or(true, |v| package.version == *v)
32 } else {
33 false
34 }
35 })
36 .collect::<Vec<_>>()
37}
38
39pub fn tree<'a>(
40 fg: &'a mut FeatGraph<'a>,
41 krate: Option<&String>,
42 feature: Option<&String>,
43 version: Option<&Version>,
44 package_nodes: bool,
45 workspace: bool,
46 no_dev: bool,
47 stdout: bool,
48) -> anyhow::Result<()> {
49 fg.shrink_to_target()?;
50
51 let mut packages = match krate {
52 Some(krate) => collect_packages(fg, krate, feature, version),
53 None => {
54 let members = fg.workspace_members.clone();
55 members
56 .iter()
57 .map(|f| fg.fid_index(f.base()))
58 .collect::<Vec<_>>()
59 }
60 };
61
62 info!("Found {} matching package(s)", packages.len());
63
64 let g = EdgeFiltered::from_fn(&fg.features, |e| {
65 (fg.features[e.target()].is_workspace() || !workspace)
66 && (!no_dev || !e.weight().is_dev_only())
67 });
68
69 let mut dfs = Dfs::new(&g, fg.root);
70
71 let mut nodes = BTreeSet::new();
72 let mut edges = BTreeSet::new();
73 let mut new_edges = BTreeSet::new();
74
75 debug!("Collecting dependencies");
76 while let Some(next) = packages.pop() {
77 dfs.move_to(next);
78 while let Some(node) = dfs.next(&g) {
79 let this_node = if package_nodes {
80 fg.base_node(node).expect("base node must exist")
81 } else {
82 node
83 };
84 nodes.insert(this_node);
85 for edge in g.edges_directed(node, petgraph::EdgeDirection::Outgoing) {
86 if package_nodes {
87 new_edges.insert((
88 fg.base_node(edge.target()).expect("base node must exist"),
89 this_node,
90 ));
91 } else {
92 edges.insert(edge.id());
93 }
94 }
95 }
96 }
97
98 if package_nodes {
99 for (a, b) in new_edges {
100 let a = a.get_index(fg)?;
101 if a != b {
102 let link = Link {
103 optional: false,
104 kinds: vec![DepKindInfo::NORMAL],
105 };
106 edges.insert(fg.features.add_edge(b, a, link));
107 }
108 }
109 }
110
111 info!("Done traversing");
112 debug!("Found {} nodes and {} edges", nodes.len(), edges.len());
113
114 fg.focus_nodes = Some(nodes);
115 fg.focus_edges = Some(edges);
116 dump_fg(fg, stdout)
117}
118
119pub fn explain<'a>(
120 fg: &'a mut FeatGraph<'a>,
121 krate: &str,
122 feature: Option<&String>,
123 version: Option<&Version>,
124 package_nodes: bool,
125 stdout: bool,
126) -> anyhow::Result<()> {
127 fg.shrink_to_target()?;
128 let mut packages = collect_packages(fg, krate, feature, version);
129
130 info!("Found {} matching package(s)", packages.len());
131
132 if packages.is_empty() {
133 anyhow::bail!("Can't find crate {krate} with feature {feature:?} and version {version:?}");
134 }
135
136 if package_nodes {
137 fg.focus_targets = Some(
138 packages
139 .iter()
140 .filter_map(|&ix| fg.base_node(ix))
141 .collect::<BTreeSet<_>>(),
142 );
143 } else {
144 fg.focus_targets = Some(packages.iter().copied().collect::<BTreeSet<_>>());
145 }
146 let g = EdgeFiltered::from_fn(Reversed(&fg.features), |e| {
147 !fg.features[e.source()].is_workspace()
148 });
149
150 let mut dfs = Dfs::new(&g, fg.root);
151
152 let mut nodes = BTreeSet::new();
153 let mut edges = BTreeSet::new();
154 let mut new_edges = BTreeSet::new();
155
156 debug!("Collecting dependencies");
157 while let Some(next) = packages.pop() {
158 dfs.move_to(next);
159 while let Some(node) = dfs.next(&g) {
160 let this_node = if package_nodes {
161 fg.base_node(node).expect("base package node must exist")
162 } else {
163 node
164 };
165 nodes.insert(this_node);
166 for edge in g.edges_directed(node, petgraph::EdgeDirection::Outgoing) {
167 if package_nodes {
168 new_edges.insert((
169 fg.base_node(edge.target()).expect("base node must exist"),
170 this_node,
171 ));
172 } else {
173 edges.insert(edge.id());
174 }
175 }
176 }
177 }
178
179 if package_nodes {
180 for (a, b) in new_edges {
181 let a = a.get_index(fg)?;
182 if a != b {
183 let link = Link {
184 optional: false,
185 kinds: vec![DepKindInfo::NORMAL],
186 };
187 edges.insert(fg.features.add_edge(a, b, link));
188 }
189 }
190 }
191
192 info!("Done traversing");
193 debug!("Found {} nodes and {} edges", nodes.len(), edges.len());
194
195 fg.focus_nodes = Some(nodes);
196 fg.focus_edges = Some(edges);
197 dump_fg(fg, stdout)
198}
199
200fn dump_fg(fg: &FeatGraph, stdout: bool) -> anyhow::Result<()> {
201 if !stdout {
202 let mut file = tempfile::NamedTempFile::new()?;
203 dot::render(fg, &mut file)?;
204 if std::process::Command::new("xdot")
205 .args([file.path()])
206 .output()
207 .is_ok()
208 {
209 return Ok(());
210 }
211 }
212
213 dot::render(fg, &mut std::io::stdout())?;
214
215 Ok(())
216}