mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-12 05:26:20 +00:00
Add uv export --format requirements.txt
(#6778)
## Summary The interface here is intentionally a bit more limited than `uv pip compile`, because we don't want `requirements.txt` to be a system of record -- it's just an export format. So, we don't write annotation comments (i.e., which dependency is requested from which), we don't allow writing extras, etc. It's just a flat list of requirements, with their markers and hashes. Closes #6007. Closes #6668. Closes #6670.
This commit is contained in:
parent
670e9603ee
commit
cbfc928a9c
21 changed files with 1610 additions and 105 deletions
81
crates/uv-resolver/src/graph_ops.rs
Normal file
81
crates/uv-resolver/src/graph_ops.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
use pep508_rs::MarkerTree;
|
||||
use petgraph::algo::greedy_feedback_arc_set;
|
||||
use petgraph::visit::{EdgeRef, Topo};
|
||||
use petgraph::{Directed, Direction, Graph};
|
||||
|
||||
/// 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue