Respect dependency group markers in `uv export` (#8659)

## Summary

Closes https://github.com/astral-sh/uv/issues/8658.
This commit is contained in:
Charlie Marsh 2024-10-29 09:41:27 -04:00 committed by GitHub
parent 9fa4fea8f2
commit bf14b6a282
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 62 additions and 33 deletions

View File

@ -22,18 +22,12 @@ use crate::graph_ops::marker_reachability;
use crate::lock::{Package, PackageId, Source};
use crate::{Lock, LockError};
type LockGraph<'lock> = Graph<&'lock Package, Edge, Directed>;
#[derive(Debug, Clone, PartialEq, Eq)]
struct Node<'lock> {
package: &'lock Package,
marker: MarkerTree,
}
type LockGraph<'lock> = Graph<Node<'lock>, Edge, Directed>;
/// An export of a [`Lock`] that renders in `requirements.txt` format.
#[derive(Debug)]
pub struct RequirementsTxtExport<'lock> {
nodes: Vec<Node<'lock>>,
nodes: Vec<Requirement<'lock>>,
hashes: bool,
editable: EditableMode,
}
@ -55,45 +49,62 @@ impl<'lock> RequirementsTxtExport<'lock> {
let mut queue: VecDeque<(&Package, Option<&ExtraName>)> = VecDeque::new();
let mut seen = FxHashSet::default();
let root = petgraph.add_node(Node::Root);
// Add the workspace package to the queue.
let root = lock
let dist = lock
.find_by_name(root_name)
.expect("found too many packages matching root")
.expect("could not find root");
if dev.prod() {
// Add the base package.
queue.push_back((root, None));
// Add the workspace package to the graph.
if let Entry::Vacant(entry) = inverse.entry(&dist.id) {
entry.insert(petgraph.add_node(Node::Package(dist)));
}
// Add any extras.
// Add an edge from the root.
let index = inverse[&dist.id];
petgraph.add_edge(root, index, MarkerTree::TRUE);
// Push its dependencies on the queue.
queue.push_back((dist, None));
match extras {
ExtrasSpecification::None => {}
ExtrasSpecification::All => {
for extra in root.optional_dependencies.keys() {
queue.push_back((root, Some(extra)));
for extra in dist.optional_dependencies.keys() {
queue.push_back((dist, Some(extra)));
}
}
ExtrasSpecification::Some(extras) => {
for extra in extras {
queue.push_back((root, Some(extra)));
queue.push_back((dist, Some(extra)));
}
}
}
// Add the root package to the graph.
inverse.insert(&root.id, petgraph.add_node(root));
}
// Add any dev dependencies.
// Add any development dependencies.
for group in dev.iter() {
for dep in root.dependency_groups.get(group).into_iter().flatten() {
for dep in dist.dependency_groups.get(group).into_iter().flatten() {
let dep_dist = lock.find_by_id(&dep.package_id);
// Add the dependency to the graph.
if let Entry::Vacant(entry) = inverse.entry(&dep.package_id) {
entry.insert(petgraph.add_node(dep_dist));
entry.insert(petgraph.add_node(Node::Package(dep_dist)));
}
// Add an edge from the root. Development dependencies may be installed without
// installing the workspace package itself (which can never have markers on it
// anyway), so they're directly connected to the root.
let dep_index = inverse[&dep.package_id];
petgraph.add_edge(
root,
dep_index,
dep.simplified_marker.as_simplified_marker_tree().clone(),
);
// Push its dependencies on the queue.
if seen.insert((&dep.package_id, None)) {
queue.push_back((dep_dist, None));
}
@ -126,7 +137,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
// Add the dependency to the graph.
if let Entry::Vacant(entry) = inverse.entry(&dep.package_id) {
entry.insert(petgraph.add_node(dep_dist));
entry.insert(petgraph.add_node(Node::Package(dep_dist)));
}
// Add the edge.
@ -152,12 +163,16 @@ impl<'lock> RequirementsTxtExport<'lock> {
let mut reachability = marker_reachability(&petgraph, &[]);
// Collect all packages.
let mut nodes: Vec<Node> = petgraph
let mut nodes = petgraph
.node_references()
.filter_map(|(index, node)| match node {
Node::Root => None,
Node::Package(package) => Some((index, package)),
})
.filter(|(_index, package)| {
install_options.include_package(&package.id.name, Some(root_name), lock.members())
})
.map(|(index, package)| Node {
.map(|(index, package)| Requirement {
package,
marker: reachability.remove(&index).unwrap_or_default(),
})
@ -165,7 +180,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
// Sort the nodes, such that unnamed URLs (editables) appear at the top.
nodes.sort_unstable_by(|a, b| {
NodeComparator::from(a.package).cmp(&NodeComparator::from(b.package))
RequirementComparator::from(a.package).cmp(&RequirementComparator::from(b.package))
});
Ok(Self {
@ -179,7 +194,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
impl std::fmt::Display for RequirementsTxtExport<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// Write out each package.
for Node { package, marker } in &self.nodes {
for Requirement { package, marker } in &self.nodes {
match &package.id.source {
Source::Registry(_) => {
write!(f, "{}=={}", package.id.name, package.id.version)?;
@ -261,17 +276,31 @@ impl std::fmt::Display for RequirementsTxtExport<'_> {
}
}
/// A node in the [`LockGraph`].
#[derive(Debug, Clone, PartialEq, Eq)]
enum Node<'lock> {
Root,
Package(&'lock Package),
}
/// The edges of the [`LockGraph`].
type Edge = MarkerTree;
/// A flat requirement, with its associated marker.
#[derive(Debug, Clone, PartialEq, Eq)]
struct Requirement<'lock> {
package: &'lock Package,
marker: MarkerTree,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum NodeComparator<'lock> {
enum RequirementComparator<'lock> {
Editable(&'lock Path),
Path(&'lock Path),
Package(&'lock PackageId),
}
impl<'lock> From<&'lock Package> for NodeComparator<'lock> {
impl<'lock> From<&'lock Package> for RequirementComparator<'lock> {
fn from(value: &'lock Package) -> Self {
match &value.id.source {
Source::Path(path) | Source::Directory(path) => Self::Path(path),

View File

@ -1027,7 +1027,7 @@ fn export_group() -> Result<()> {
requires-python = ">=3.12"
dependencies = ["typing-extensions"]
[dependency-groups]
foo = ["anyio"]
foo = ["anyio ; sys_platform == 'darwin'"]
bar = ["iniconfig"]
dev = ["sniffio"]
"#,
@ -1072,10 +1072,10 @@ fn export_group() -> Result<()> {
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR] --group foo
anyio==4.3.0 \
anyio==4.3.0 ; sys_platform == 'darwin' \
--hash=sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6 \
--hash=sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8
idna==3.6 \
idna==3.6 ; sys_platform == 'darwin' \
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
sniffio==1.3.1 \
@ -1095,10 +1095,10 @@ fn export_group() -> Result<()> {
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR] --group foo --group bar
anyio==4.3.0 \
anyio==4.3.0 ; sys_platform == 'darwin' \
--hash=sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6 \
--hash=sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8
idna==3.6 \
idna==3.6 ; sys_platform == 'darwin' \
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
iniconfig==2.0.0 \