From bf14b6a2829486e80964dcaaa0ba1e2369ebe154 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 29 Oct 2024 09:41:27 -0400 Subject: [PATCH] Respect dependency group markers in `uv export` (#8659) ## Summary Closes https://github.com/astral-sh/uv/issues/8658. --- .../uv-resolver/src/lock/requirements_txt.rs | 85 +++++++++++++------ crates/uv/tests/it/export.rs | 10 +-- 2 files changed, 62 insertions(+), 33 deletions(-) diff --git a/crates/uv-resolver/src/lock/requirements_txt.rs b/crates/uv-resolver/src/lock/requirements_txt.rs index 5510afb8d..b58c99649 100644 --- a/crates/uv-resolver/src/lock/requirements_txt.rs +++ b/crates/uv-resolver/src/lock/requirements_txt.rs @@ -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, Edge, Directed>; /// An export of a [`Lock`] that renders in `requirements.txt` format. #[derive(Debug)] pub struct RequirementsTxtExport<'lock> { - nodes: Vec>, + nodes: Vec>, 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 = 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), diff --git a/crates/uv/tests/it/export.rs b/crates/uv/tests/it/export.rs index 17eb19f4a..3b6f0666f 100644 --- a/crates/uv/tests/it/export.rs +++ b/crates/uv/tests/it/export.rs @@ -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 \