mirror of https://github.com/astral-sh/uv
Remove inverse-node tracking in `uv tree` (#8610)
## Summary Just simplifying some of the representations.
This commit is contained in:
parent
4caa1586fd
commit
001d00ba26
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue