mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-13 05:56:29 +00:00
Remove propagate_markers (#7076)
Follow-up to #6959 and #6961: Use the reachability computation instead of `propagate_markers` everywhere. With `marker_reachability`, we have a function that computes for each node the markers under which it is (`requirements.txt`, no markers provided on installation) or can be (`uv.lock`, depending on the markers provided on installation) included in the installation. Put differently: If the marker computed by `marker_reachability` is not fulfilled for the current platform, the package is never required on the current platform. We compute the markers for each package in the graph, this includes the virtual extra packages and the base packages. Since we know that each virtual extra package depends on its base package (`foo[bar]` implied `foo`), we only retain the base package marker in the `requirements.txt` graph. In #6959/#6961 we were only using it for pruning packages in `uv.lock`, now we're also using it for the markers in `requirements.txt`. I think this closes #4645, CC @bluss.
This commit is contained in:
parent
d5eb6eb12c
commit
750e8b1e2f
4 changed files with 88 additions and 218 deletions
|
@ -1,88 +1,10 @@
|
|||
use pep508_rs::MarkerTree;
|
||||
use petgraph::algo::greedy_feedback_arc_set;
|
||||
use petgraph::graph::NodeIndex;
|
||||
use petgraph::visit::{EdgeRef, Topo};
|
||||
use petgraph::{Directed, Direction, Graph};
|
||||
use petgraph::visit::EdgeRef;
|
||||
use petgraph::{Direction, Graph};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
/// A trait for a graph node that can be annotated with a [`MarkerTree`].
|
||||
pub(crate) trait Markers {
|
||||
fn set_markers(&mut self, markers: MarkerTree);
|
||||
}
|
||||
|
||||
/// Propagate the [`MarkerTree`] qualifiers across the graph.
|
||||
///
|
||||
/// The graph is directed, so if any edge contains a marker, we need to propagate it to all
|
||||
/// downstream nodes.
|
||||
pub(crate) fn propagate_markers<T: Markers>(
|
||||
mut graph: Graph<T, MarkerTree, Directed>,
|
||||
) -> Graph<T, MarkerTree, Directed> {
|
||||
// Remove any cycles. By absorption, it should be fine to ignore cycles.
|
||||
//
|
||||
// Imagine a graph: `A -> B -> C -> A`. Assume that `A` has weight `1`, `B` has weight `2`,
|
||||
// and `C` has weight `3`. The weights are the marker trees.
|
||||
//
|
||||
// When propagating, we'd return to `A` when we hit the cycle, to create `1 or (1 and 2 and 3)`,
|
||||
// which resolves to `1`.
|
||||
//
|
||||
// TODO(charlie): The above reasoning could be incorrect. Consider using a graph algorithm that
|
||||
// can handle weight propagation with cycles.
|
||||
let edges = {
|
||||
let mut fas = greedy_feedback_arc_set(&graph)
|
||||
.map(|edge| edge.id())
|
||||
.collect::<Vec<_>>();
|
||||
fas.sort_unstable();
|
||||
let mut edges = Vec::with_capacity(fas.len());
|
||||
for edge_id in fas.into_iter().rev() {
|
||||
edges.push(graph.edge_endpoints(edge_id).unwrap());
|
||||
graph.remove_edge(edge_id);
|
||||
}
|
||||
edges
|
||||
};
|
||||
|
||||
let mut topo = Topo::new(&graph);
|
||||
while let Some(index) = topo.next(&graph) {
|
||||
let marker_tree = {
|
||||
// Fold over the edges to combine the marker trees. If any edge is `None`, then
|
||||
// the combined marker tree is `None`.
|
||||
let mut edges = graph.edges_directed(index, Direction::Incoming);
|
||||
|
||||
edges
|
||||
.next()
|
||||
.and_then(|edge| graph.edge_weight(edge.id()).cloned())
|
||||
.and_then(|initial| {
|
||||
edges.try_fold(initial, |mut acc, edge| {
|
||||
acc.or(graph.edge_weight(edge.id())?.clone());
|
||||
Some(acc)
|
||||
})
|
||||
})
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
// Propagate the marker tree to all downstream nodes.
|
||||
let mut walker = graph
|
||||
.neighbors_directed(index, Direction::Outgoing)
|
||||
.detach();
|
||||
while let Some((outgoing, _)) = walker.next(&graph) {
|
||||
if let Some(weight) = graph.edge_weight_mut(outgoing) {
|
||||
weight.and(marker_tree.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let node = &mut graph[index];
|
||||
node.set_markers(marker_tree);
|
||||
}
|
||||
|
||||
// Re-add the removed edges. We no longer care about the edge _weights_, but we do want the
|
||||
// edges to be present, to power the `# via` annotations.
|
||||
for (source, target) in edges {
|
||||
graph.add_edge(source, target, MarkerTree::TRUE);
|
||||
}
|
||||
|
||||
graph
|
||||
}
|
||||
|
||||
/// 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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue