mirror of https://github.com/astral-sh/uv
81 lines
3.4 KiB
Rust
81 lines
3.4 KiB
Rust
use petgraph::graph::NodeIndex;
|
||
use petgraph::visit::EdgeRef;
|
||
use petgraph::{Direction, Graph};
|
||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||
use std::collections::hash_map::Entry;
|
||
use uv_pep508::MarkerTree;
|
||
|
||
/// Determine the markers under which a package is reachable in the dependency tree.
|
||
///
|
||
/// The algorithm is a variant of Dijkstra's algorithm for not totally ordered distances:
|
||
/// Whenever we find a shorter distance to a node (a marker that is not a subset of the existing
|
||
/// marker), we re-queue the node and update all its children. This implicitly handles cycles,
|
||
/// whenever we re-reach a node through a cycle the marker we have is a more
|
||
/// specific marker/longer path, so we don't update the node and don't re-queue it.
|
||
pub(crate) fn marker_reachability<T>(
|
||
graph: &Graph<T, MarkerTree>,
|
||
fork_markers: &[MarkerTree],
|
||
) -> FxHashMap<NodeIndex, MarkerTree> {
|
||
// Note that we build including the virtual packages due to how we propagate markers through
|
||
// the graph, even though we then only read the markers for base packages.
|
||
let mut reachability = FxHashMap::with_capacity_and_hasher(graph.node_count(), FxBuildHasher);
|
||
|
||
// Collect the root nodes.
|
||
//
|
||
// Besides the actual virtual root node, virtual dev dependencies packages are also root
|
||
// nodes since the edges don't cover dev dependencies.
|
||
let mut queue: Vec<_> = graph
|
||
.node_indices()
|
||
.filter(|node_index| {
|
||
graph
|
||
.edges_directed(*node_index, Direction::Incoming)
|
||
.next()
|
||
.is_none()
|
||
})
|
||
.collect();
|
||
|
||
// The root nodes are always applicable, unless the user has restricted resolver
|
||
// environments with `tool.uv.environments`.
|
||
let root_markers: MarkerTree = if fork_markers.is_empty() {
|
||
MarkerTree::TRUE
|
||
} else {
|
||
fork_markers
|
||
.iter()
|
||
.fold(MarkerTree::FALSE, |mut acc, marker| {
|
||
acc.or(marker.clone());
|
||
acc
|
||
})
|
||
};
|
||
for root_index in &queue {
|
||
reachability.insert(*root_index, root_markers.clone());
|
||
}
|
||
|
||
// Propagate all markers through the graph, so that the eventual marker for each node is the
|
||
// union of the markers of each path we can reach the node by.
|
||
while let Some(parent_index) = queue.pop() {
|
||
let marker = reachability[&parent_index].clone();
|
||
for child_edge in graph.edges_directed(parent_index, Direction::Outgoing) {
|
||
// The marker for all paths to the child through the parent.
|
||
let mut child_marker = child_edge.weight().clone();
|
||
child_marker.and(marker.clone());
|
||
match reachability.entry(child_edge.target()) {
|
||
Entry::Occupied(mut existing) => {
|
||
// If the marker is a subset of the existing marker (A ⊆ B exactly if
|
||
// A ∪ B = A), updating the child wouldn't change child's marker.
|
||
child_marker.or(existing.get().clone());
|
||
if &child_marker != existing.get() {
|
||
existing.insert(child_marker);
|
||
queue.push(child_edge.target());
|
||
}
|
||
}
|
||
Entry::Vacant(vacant) => {
|
||
vacant.insert(child_marker.clone());
|
||
queue.push(child_edge.target());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
reachability
|
||
}
|