cargo_lock/dependency/
tree.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
//! Dependency trees computed from `Cargo.lock` files.
//!
//! Uses the `petgraph` crate for modeling the dependency structure.

// Includes code from `cargo-tree`, Copyright (c) 2015-2016 Steven Fackler
// Licensed under the same terms as `cargo-audit` (i.e. Apache 2.0 + MIT)

use super::{
    graph::{EdgeDirection, Graph, NodeIndex, Nodes},
    Dependency,
};
use crate::{error::Error, lockfile::Lockfile, Map};
use std::{collections::BTreeSet as Set, io};

/// Dependency tree computed from a `Cargo.lock` file
#[derive(Clone, Debug)]
pub struct Tree {
    /// Dependency graph for a particular package
    graph: Graph,

    /// Package data associated with nodes in the graph
    nodes: Nodes,
}

impl Tree {
    /// Construct a new dependency tree for the given [`Lockfile`].
    pub fn new(lockfile: &Lockfile) -> Result<Self, Error> {
        let mut graph = Graph::new();
        let mut nodes = Map::new();

        // Populate all graph nodes in the first pass
        for package in &lockfile.packages {
            let node_index = graph.add_node(package.clone());
            nodes.insert(Dependency::from(package), node_index);
        }

        // Populate all graph edges in the second pass
        for package in &lockfile.packages {
            let parent_index = nodes[&Dependency::from(package)];

            for dependency in &package.dependencies {
                if let Some(node_index) = nodes.get(dependency) {
                    graph.add_edge(parent_index, *node_index, dependency.clone());
                } else {
                    return Err(Error::Resolution(format!(
                        "failed to find dependency: {dependency}"
                    )));
                }
            }
        }

        Ok(Tree { graph, nodes })
    }

    /// Render the dependency graph for the given [`NodeIndex`] using the
    /// default set of [`Symbols`].
    pub fn render(
        &self,
        w: &mut impl io::Write,
        node_index: NodeIndex,
        direction: EdgeDirection,
        exact: bool,
    ) -> io::Result<()> {
        self.render_with_symbols(w, node_index, direction, &Symbols::default(), exact)
    }

    /// Render the dependency graph for the given [`NodeIndex`] using the
    /// provided set of [`Symbols`].
    pub fn render_with_symbols(
        &self,
        w: &mut impl io::Write,
        node_index: NodeIndex,
        direction: EdgeDirection,
        symbols: &Symbols,
        exact: bool,
    ) -> io::Result<()> {
        Presenter::new(&self.graph, symbols).print_node(w, node_index, direction, exact)
    }

    /// Get the indexes of the root packages in the workspace
    /// (i.e. toplevel packages which are not used as dependencies)
    pub fn roots(&self) -> Vec<NodeIndex> {
        self.graph.externals(EdgeDirection::Incoming).collect()
    }

    /// Get the `petgraph` dependency graph.
    pub fn graph(&self) -> &Graph {
        &self.graph
    }

    /// Get the nodes of the `petgraph` dependency graph.
    pub fn nodes(&self) -> &Nodes {
        &self.nodes
    }
}

/// Symbols to use when printing the dependency tree
pub struct Symbols {
    down: &'static str,
    tee: &'static str,
    ell: &'static str,
    right: &'static str,
}

impl Default for Symbols {
    fn default() -> Symbols {
        Self {
            down: "│",
            tee: "├",
            ell: "└",
            right: "─",
        }
    }
}

/// Dependency tree presenter
struct Presenter<'g, 's> {
    /// Dependency graph being displayed
    graph: &'g Graph,

    /// Symbols to use to display graph
    symbols: &'s Symbols,

    /// Are there continuing levels?
    levels_continue: Vec<bool>,

    /// Dependencies we've already visited
    visited: Set<NodeIndex>,
}

impl<'g, 's> Presenter<'g, 's> {
    /// Create a new dependency tree `Presenter`.
    fn new(graph: &'g Graph, symbols: &'s Symbols) -> Self {
        Self {
            graph,
            symbols,
            levels_continue: vec![],
            visited: Set::new(),
        }
    }

    /// Print a node in the dependency tree.
    fn print_node(
        &mut self,
        w: &mut impl io::Write,
        node_index: NodeIndex,
        direction: EdgeDirection,
        exact: bool,
    ) -> io::Result<()> {
        let package = &self.graph[node_index];
        let new = self.visited.insert(node_index);

        if let Some((&last_continues, rest)) = self.levels_continue.split_last() {
            for &continues in rest {
                let c = if continues { self.symbols.down } else { " " };
                write!(w, "{}   ", c)?;
            }

            let c = if last_continues {
                self.symbols.tee
            } else {
                self.symbols.ell
            };

            write!(w, "{0}{1}{1} ", c, self.symbols.right)?;
        }

        if exact {
            let spec = if let Some(checksum) = &package.checksum {
                format!("checksum:{}", checksum)
            } else if let Some(src) = &package.source {
                src.to_string()
            } else {
                "inexact".to_string()
            };
            writeln!(w, "{} {} {}", &package.name, &package.version, spec)?;
        } else {
            writeln!(w, "{} {}", &package.name, &package.version)?;
        }

        if !new {
            return Ok(());
        }

        use petgraph::visit::EdgeRef;
        let dependencies = self
            .graph
            .edges_directed(node_index, direction)
            .map(|edge| match direction {
                EdgeDirection::Incoming => edge.source(),
                EdgeDirection::Outgoing => edge.target(),
            })
            .collect::<Vec<_>>();

        for (i, dependency) in dependencies.iter().enumerate() {
            self.levels_continue.push(i < (dependencies.len() - 1));
            self.print_node(w, *dependency, direction, exact)?;
            self.levels_continue.pop();
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Load this crate's `Cargo.lock`
    fn load_lockfile() -> Lockfile {
        Lockfile::load("tests/examples/Cargo.lock.v3").unwrap()
    }

    #[test]
    fn compute_tree() {
        // TODO(tarcieri): test dependency tree is computed correctly
        Tree::new(&load_lockfile()).unwrap();
    }

    #[test]
    fn compute_roots() {
        let tree = Tree::new(&load_lockfile()).unwrap();
        let roots = tree.roots();
        assert_eq!(roots.len(), 1);

        let root_package = &tree.graph[roots[0]];
        assert_eq!(root_package.name.as_str(), "cargo-lock");
    }
}