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( graph: &Graph, fork_markers: &[MarkerTree], ) -> FxHashMap { // 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 }