Remove inverse-node tracking in `uv tree` (#8610)

## Summary

Just simplifying some of the representations.
This commit is contained in:
Charlie Marsh 2024-10-27 20:44:32 -04:00 committed by GitHub
parent 4caa1586fd
commit 001d00ba26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 91 additions and 66 deletions

View File

@ -1,5 +1,5 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::{BTreeSet, VecDeque}; use std::collections::VecDeque;
use std::path::Path; use std::path::Path;
use itertools::Itertools; use itertools::Itertools;
@ -19,14 +19,12 @@ use crate::Lock;
pub struct TreeDisplay<'env> { pub struct TreeDisplay<'env> {
/// The constructed dependency graph. /// The constructed dependency graph.
graph: petgraph::graph::Graph<&'env PackageId, Edge<'env>, petgraph::Directed>, graph: petgraph::graph::Graph<&'env PackageId, Edge<'env>, petgraph::Directed>,
/// An inverse map from [`PackageId`] to the corresponding node index in the graph. /// The packages considered as roots of the dependency tree.
inverse: FxHashMap<&'env PackageId, petgraph::graph::NodeIndex>, roots: Vec<NodeIndex>,
/// Maximum display depth of the dependency tree. /// Maximum display depth of the dependency tree.
depth: usize, depth: usize,
/// Whether to de-duplicate the displayed dependencies. /// Whether to de-duplicate the displayed dependencies.
no_dedupe: bool, no_dedupe: bool,
/// The packages considered as roots of the dependency tree.
roots: Vec<NodeIndex>,
} }
impl<'env> TreeDisplay<'env> { impl<'env> TreeDisplay<'env> {
@ -288,27 +286,18 @@ impl<'env> TreeDisplay<'env> {
roots roots
}; };
// Re-create the inverse map.
{
inverse.clear();
for node in graph.node_indices() {
inverse.insert(graph[node], node);
}
}
Self { Self {
graph, graph,
inverse, roots,
depth, depth,
no_dedupe, no_dedupe,
roots,
} }
} }
/// Perform a depth-first traversal of the given package and its dependencies. /// Perform a depth-first traversal of the given package and its dependencies.
fn visit( fn visit(
&'env self, &'env self,
node: Node<'env>, cursor: Cursor,
visited: &mut FxHashMap<&'env PackageId, Vec<&'env PackageId>>, visited: &mut FxHashMap<&'env PackageId, Vec<&'env PackageId>>,
path: &mut Vec<&'env PackageId>, path: &mut Vec<&'env PackageId>,
) -> Vec<String> { ) -> Vec<String> {
@ -317,59 +306,81 @@ impl<'env> TreeDisplay<'env> {
return Vec::new(); return Vec::new();
} }
let package_id = self.graph[cursor.node()];
let edge = cursor.edge().map(|edge_id| &self.graph[edge_id]);
let line = { let line = {
let mut line = format!("{}", node.package_id().name); let mut line = format!("{}", package_id.name);
if let Some(extras) = node.extras().filter(|extras| !extras.is_empty()) { if let Some(edge) = edge {
line.push_str(&format!("[{}]", extras.iter().join(","))); let extras = &edge.dependency().extra;
if !extras.is_empty() {
line.push('[');
line.push_str(extras.iter().join(", ").as_str());
line.push(']');
}
} }
line.push_str(&format!(" v{}", node.package_id().version)); line.push(' ');
line.push('v');
line.push_str(&format!("{}", package_id.version));
match node { if let Some(edge) = edge {
Node::Root(_) => line, match edge {
Node::Dependency(_, _) => line, Edge::Prod(_) => {}
Node::OptionalDependency(extra, _, _) => format!("{line} (extra: {extra})"), Edge::Optional(extra, _) => {
Node::DevDependency(group, _, _) => format!("{line} (group: {group})"), line.push_str(&format!(" (extra: {extra})"));
} }
Edge::Dev(group, _) => {
line.push_str(&format!(" (group: {group})"));
}
}
}
line
}; };
// Skip the traversal if: // Skip the traversal if:
// 1. The package is in the current traversal path (i.e., a dependency cycle). // 1. The package is in the current traversal path (i.e., a dependency cycle).
// 2. The package has been visited and de-duplication is enabled (default). // 2. The package has been visited and de-duplication is enabled (default).
if let Some(requirements) = visited.get(node.package_id()) { if let Some(requirements) = visited.get(package_id) {
if !self.no_dedupe || path.contains(&node.package_id()) { if !self.no_dedupe || path.contains(&package_id) {
return if requirements.is_empty() { return if requirements.is_empty() {
vec![line] vec![line]
} else { } else {
vec![format!("{} (*)", line)] vec![format!("{line} (*)")]
}; };
} }
} }
let mut dependencies = self let mut dependencies = self
.graph .graph
.edges_directed(self.inverse[node.package_id()], Direction::Outgoing) .edges_directed(cursor.node(), Direction::Outgoing)
.map(|edge| match edge.weight() { .map(|edge| {
Edge::Prod(dependency) => Node::Dependency(self.graph[edge.target()], dependency), let node = edge.target();
Edge::Optional(extra, dependency) => { Cursor::new(node, edge.id())
Node::OptionalDependency(extra, self.graph[edge.target()], dependency)
}
Edge::Dev(group, dependency) => {
Node::DevDependency(group, self.graph[edge.target()], dependency)
}
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
dependencies.sort_unstable(); dependencies.sort_by_key(|node| {
let package_id = self.graph[node.node()];
let edge = node
.edge()
.map(|edge_id| &self.graph[edge_id])
.map(Edge::kind);
(edge, package_id)
});
let mut lines = vec![line]; let mut lines = vec![line];
// Keep track of the dependency path to avoid cycles. // Keep track of the dependency path to avoid cycles.
visited.insert( visited.insert(
node.package_id(), package_id,
dependencies.iter().map(Node::package_id).collect(), dependencies
.iter()
.map(|node| self.graph[node.node()])
.collect(),
); );
path.push(node.package_id()); path.push(package_id);
for (index, dep) in dependencies.iter().enumerate() { for (index, dep) in dependencies.iter().enumerate() {
// For sub-visited packages, add the prefix to make the tree display user-friendly. // For sub-visited packages, add the prefix to make the tree display user-friendly.
@ -414,13 +425,14 @@ impl<'env> TreeDisplay<'env> {
/// Depth-first traverse the nodes to render the tree. /// Depth-first traverse the nodes to render the tree.
fn render(&self) -> Vec<String> { fn render(&self) -> Vec<String> {
let mut visited = FxHashMap::default();
let mut path = Vec::new(); let mut path = Vec::new();
let mut lines = Vec::new(); let mut lines = Vec::with_capacity(self.graph.node_count());
let mut visited =
FxHashMap::with_capacity_and_hasher(self.graph.node_count(), rustc_hash::FxBuildHasher);
for node in &self.roots { for node in &self.roots {
path.clear(); path.clear();
lines.extend(self.visit(Node::Root(self.graph[*node]), &mut visited, &mut path)); lines.extend(self.visit(Cursor::root(*node), &mut visited, &mut path));
} }
lines lines
@ -442,33 +454,46 @@ impl<'env> Edge<'env> {
Self::Dev(_, dependency) => dependency, Self::Dev(_, dependency) => dependency,
} }
} }
fn kind(&self) -> EdgeKind<'env> {
match self {
Self::Prod(_) => EdgeKind::Prod,
Self::Optional(extra, _) => EdgeKind::Optional(extra),
Self::Dev(group, _) => EdgeKind::Dev(group),
}
}
} }
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
enum EdgeKind<'env> {
Prod,
Optional(&'env ExtraName),
Dev(&'env GroupName),
}
/// A node in the dependency graph along with the edge that led to it, or `None` for root nodes.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
enum Node<'env> { struct Cursor(NodeIndex, Option<EdgeIndex>);
Root(&'env PackageId),
Dependency(&'env PackageId, &'env Dependency), impl Cursor {
OptionalDependency(&'env ExtraName, &'env PackageId, &'env Dependency), /// Create a [`Cursor`] representing a node in the dependency tree.
DevDependency(&'env GroupName, &'env PackageId, &'env Dependency), fn new(node: NodeIndex, edge: EdgeIndex) -> Self {
Self(node, Some(edge))
} }
impl<'env> Node<'env> { /// Create a [`Cursor`] representing a root node in the dependency tree.
fn package_id(&self) -> &'env PackageId { fn root(node: NodeIndex) -> Self {
match self { Self(node, None)
Self::Root(id) => id,
Self::Dependency(id, _) => id,
Self::OptionalDependency(_, id, _) => id,
Self::DevDependency(_, id, _) => id,
}
} }
fn extras(&self) -> Option<&BTreeSet<ExtraName>> { /// Return the [`NodeIndex`] of the node.
match self { fn node(&self) -> NodeIndex {
Self::Root(_) => None, self.0
Self::Dependency(_, dep) => Some(&dep.extra),
Self::OptionalDependency(_, _, dep) => Some(&dep.extra),
Self::DevDependency(_, _, dep) => Some(&dep.extra),
} }
/// Return the [`EdgeIndex`] of the edge that led to the node, if any.
fn edge(&self) -> Option<EdgeIndex> {
self.1
} }
} }