cargo_lock/dependency/
tree.rs

1//! Dependency trees computed from `Cargo.lock` files.
2//!
3//! Uses the `petgraph` crate for modeling the dependency structure.
4
5// Includes code from `cargo-tree`, Copyright (c) 2015-2016 Steven Fackler
6// Licensed under the same terms as `cargo-audit` (i.e. Apache 2.0 + MIT)
7
8use super::{
9    graph::{EdgeDirection, Graph, NodeIndex, Nodes},
10    Dependency,
11};
12use crate::{error::Error, lockfile::Lockfile, Map};
13use std::{collections::BTreeSet as Set, io};
14
15/// Dependency tree computed from a `Cargo.lock` file
16#[derive(Clone, Debug)]
17pub struct Tree {
18    /// Dependency graph for a particular package
19    graph: Graph,
20
21    /// Package data associated with nodes in the graph
22    nodes: Nodes,
23}
24
25impl Tree {
26    /// Construct a new dependency tree for the given [`Lockfile`].
27    pub fn new(lockfile: &Lockfile) -> Result<Self, Error> {
28        let mut graph = Graph::new();
29        let mut nodes = Map::new();
30
31        // Populate all graph nodes in the first pass
32        for package in &lockfile.packages {
33            let node_index = graph.add_node(package.clone());
34            nodes.insert(Dependency::from(package), node_index);
35        }
36
37        // Populate all graph edges in the second pass
38        for package in &lockfile.packages {
39            let parent_index = nodes[&Dependency::from(package)];
40
41            for dependency in &package.dependencies {
42                if let Some(node_index) = nodes.get(dependency) {
43                    graph.add_edge(parent_index, *node_index, dependency.clone());
44                } else {
45                    return Err(Error::Resolution(format!(
46                        "failed to find dependency: {dependency}"
47                    )));
48                }
49            }
50        }
51
52        Ok(Tree { graph, nodes })
53    }
54
55    /// Render the dependency graph for the given [`NodeIndex`] using the
56    /// default set of [`Symbols`].
57    pub fn render(
58        &self,
59        w: &mut impl io::Write,
60        node_index: NodeIndex,
61        direction: EdgeDirection,
62        exact: bool,
63    ) -> io::Result<()> {
64        self.render_with_symbols(w, node_index, direction, &Symbols::default(), exact)
65    }
66
67    /// Render the dependency graph for the given [`NodeIndex`] using the
68    /// provided set of [`Symbols`].
69    pub fn render_with_symbols(
70        &self,
71        w: &mut impl io::Write,
72        node_index: NodeIndex,
73        direction: EdgeDirection,
74        symbols: &Symbols,
75        exact: bool,
76    ) -> io::Result<()> {
77        Presenter::new(&self.graph, symbols).print_node(w, node_index, direction, exact)
78    }
79
80    /// Get the indexes of the root packages in the workspace
81    /// (i.e. toplevel packages which are not used as dependencies)
82    pub fn roots(&self) -> Vec<NodeIndex> {
83        self.graph.externals(EdgeDirection::Incoming).collect()
84    }
85
86    /// Get the `petgraph` dependency graph.
87    pub fn graph(&self) -> &Graph {
88        &self.graph
89    }
90
91    /// Get the nodes of the `petgraph` dependency graph.
92    pub fn nodes(&self) -> &Nodes {
93        &self.nodes
94    }
95}
96
97/// Symbols to use when printing the dependency tree
98pub struct Symbols {
99    down: &'static str,
100    tee: &'static str,
101    ell: &'static str,
102    right: &'static str,
103}
104
105impl Default for Symbols {
106    fn default() -> Symbols {
107        Self {
108            down: "│",
109            tee: "├",
110            ell: "└",
111            right: "─",
112        }
113    }
114}
115
116/// Dependency tree presenter
117struct Presenter<'g, 's> {
118    /// Dependency graph being displayed
119    graph: &'g Graph,
120
121    /// Symbols to use to display graph
122    symbols: &'s Symbols,
123
124    /// Are there continuing levels?
125    levels_continue: Vec<bool>,
126
127    /// Dependencies we've already visited
128    visited: Set<NodeIndex>,
129}
130
131impl<'g, 's> Presenter<'g, 's> {
132    /// Create a new dependency tree `Presenter`.
133    fn new(graph: &'g Graph, symbols: &'s Symbols) -> Self {
134        Self {
135            graph,
136            symbols,
137            levels_continue: vec![],
138            visited: Set::new(),
139        }
140    }
141
142    /// Print a node in the dependency tree.
143    fn print_node(
144        &mut self,
145        w: &mut impl io::Write,
146        node_index: NodeIndex,
147        direction: EdgeDirection,
148        exact: bool,
149    ) -> io::Result<()> {
150        let package = &self.graph[node_index];
151        let new = self.visited.insert(node_index);
152
153        if let Some((&last_continues, rest)) = self.levels_continue.split_last() {
154            for &continues in rest {
155                let c = if continues { self.symbols.down } else { " " };
156                write!(w, "{c}   ")?;
157            }
158
159            let c = if last_continues {
160                self.symbols.tee
161            } else {
162                self.symbols.ell
163            };
164
165            write!(w, "{0}{1}{1} ", c, self.symbols.right)?;
166        }
167
168        if exact {
169            let spec = if let Some(checksum) = &package.checksum {
170                format!("checksum:{checksum}")
171            } else if let Some(src) = &package.source {
172                src.to_string()
173            } else {
174                "inexact".to_string()
175            };
176            writeln!(w, "{} {} {}", &package.name, &package.version, spec)?;
177        } else {
178            writeln!(w, "{} {}", &package.name, &package.version)?;
179        }
180
181        if !new {
182            return Ok(());
183        }
184
185        use petgraph::visit::EdgeRef;
186        let dependencies = self
187            .graph
188            .edges_directed(node_index, direction)
189            .map(|edge| match direction {
190                EdgeDirection::Incoming => edge.source(),
191                EdgeDirection::Outgoing => edge.target(),
192            })
193            .collect::<Vec<_>>();
194
195        for (i, dependency) in dependencies.iter().enumerate() {
196            self.levels_continue.push(i < (dependencies.len() - 1));
197            self.print_node(w, *dependency, direction, exact)?;
198            self.levels_continue.pop();
199        }
200
201        Ok(())
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn compute_tree_v3() {
211        // TODO(tarcieri): test dependency tree is computed correctly
212        let lockfile = Lockfile::load("tests/examples/Cargo.lock.v3").unwrap();
213        Tree::new(&lockfile).unwrap();
214    }
215
216    #[test]
217    fn compute_roots_v3() {
218        let lockfile = Lockfile::load("tests/examples/Cargo.lock.v3").unwrap();
219        let tree = Tree::new(&lockfile).unwrap();
220        let roots = tree.roots();
221        assert_eq!(roots.len(), 1);
222
223        let root_package = &tree.graph[roots[0]];
224        assert_eq!(root_package.name.as_str(), "cargo-lock");
225    }
226
227    #[test]
228    fn compute_tree_git_ref() {
229        let lockfile = Lockfile::load("tests/examples/Cargo.lock.git-ref").unwrap();
230        Tree::new(&lockfile).unwrap();
231    }
232}