cargo_hackerman/
explain.rs

1use 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                // name must match.
27                // feature must match if given, otherwise look for base
28                // version must match if given
29                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}