Skip to content

Commit bf14b6a

Browse files
Respect dependency group markers in uv export (#8659)
## Summary Closes #8658.
1 parent 9fa4fea commit bf14b6a

File tree

2 files changed

+62
-33
lines changed

2 files changed

+62
-33
lines changed

crates/uv-resolver/src/lock/requirements_txt.rs

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,12 @@ use crate::graph_ops::marker_reachability;
2222
use crate::lock::{Package, PackageId, Source};
2323
use crate::{Lock, LockError};
2424

25-
type LockGraph<'lock> = Graph<&'lock Package, Edge, Directed>;
26-
27-
#[derive(Debug, Clone, PartialEq, Eq)]
28-
struct Node<'lock> {
29-
package: &'lock Package,
30-
marker: MarkerTree,
31-
}
25+
type LockGraph<'lock> = Graph<Node<'lock>, Edge, Directed>;
3226

3327
/// An export of a [`Lock`] that renders in `requirements.txt` format.
3428
#[derive(Debug)]
3529
pub struct RequirementsTxtExport<'lock> {
36-
nodes: Vec<Node<'lock>>,
30+
nodes: Vec<Requirement<'lock>>,
3731
hashes: bool,
3832
editable: EditableMode,
3933
}
@@ -55,45 +49,62 @@ impl<'lock> RequirementsTxtExport<'lock> {
5549
let mut queue: VecDeque<(&Package, Option<&ExtraName>)> = VecDeque::new();
5650
let mut seen = FxHashSet::default();
5751

52+
let root = petgraph.add_node(Node::Root);
53+
5854
// Add the workspace package to the queue.
59-
let root = lock
55+
let dist = lock
6056
.find_by_name(root_name)
6157
.expect("found too many packages matching root")
6258
.expect("could not find root");
6359

6460
if dev.prod() {
65-
// Add the base package.
66-
queue.push_back((root, None));
61+
// Add the workspace package to the graph.
62+
if let Entry::Vacant(entry) = inverse.entry(&dist.id) {
63+
entry.insert(petgraph.add_node(Node::Package(dist)));
64+
}
6765

68-
// Add any extras.
66+
// Add an edge from the root.
67+
let index = inverse[&dist.id];
68+
petgraph.add_edge(root, index, MarkerTree::TRUE);
69+
70+
// Push its dependencies on the queue.
71+
queue.push_back((dist, None));
6972
match extras {
7073
ExtrasSpecification::None => {}
7174
ExtrasSpecification::All => {
72-
for extra in root.optional_dependencies.keys() {
73-
queue.push_back((root, Some(extra)));
75+
for extra in dist.optional_dependencies.keys() {
76+
queue.push_back((dist, Some(extra)));
7477
}
7578
}
7679
ExtrasSpecification::Some(extras) => {
7780
for extra in extras {
78-
queue.push_back((root, Some(extra)));
81+
queue.push_back((dist, Some(extra)));
7982
}
8083
}
8184
}
82-
83-
// Add the root package to the graph.
84-
inverse.insert(&root.id, petgraph.add_node(root));
8585
}
8686

87-
// Add any dev dependencies.
87+
// Add any development dependencies.
8888
for group in dev.iter() {
89-
for dep in root.dependency_groups.get(group).into_iter().flatten() {
89+
for dep in dist.dependency_groups.get(group).into_iter().flatten() {
9090
let dep_dist = lock.find_by_id(&dep.package_id);
9191

9292
// Add the dependency to the graph.
9393
if let Entry::Vacant(entry) = inverse.entry(&dep.package_id) {
94-
entry.insert(petgraph.add_node(dep_dist));
94+
entry.insert(petgraph.add_node(Node::Package(dep_dist)));
9595
}
9696

97+
// Add an edge from the root. Development dependencies may be installed without
98+
// installing the workspace package itself (which can never have markers on it
99+
// anyway), so they're directly connected to the root.
100+
let dep_index = inverse[&dep.package_id];
101+
petgraph.add_edge(
102+
root,
103+
dep_index,
104+
dep.simplified_marker.as_simplified_marker_tree().clone(),
105+
);
106+
107+
// Push its dependencies on the queue.
97108
if seen.insert((&dep.package_id, None)) {
98109
queue.push_back((dep_dist, None));
99110
}
@@ -126,7 +137,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
126137

127138
// Add the dependency to the graph.
128139
if let Entry::Vacant(entry) = inverse.entry(&dep.package_id) {
129-
entry.insert(petgraph.add_node(dep_dist));
140+
entry.insert(petgraph.add_node(Node::Package(dep_dist)));
130141
}
131142

132143
// Add the edge.
@@ -152,20 +163,24 @@ impl<'lock> RequirementsTxtExport<'lock> {
152163
let mut reachability = marker_reachability(&petgraph, &[]);
153164

154165
// Collect all packages.
155-
let mut nodes: Vec<Node> = petgraph
166+
let mut nodes = petgraph
156167
.node_references()
168+
.filter_map(|(index, node)| match node {
169+
Node::Root => None,
170+
Node::Package(package) => Some((index, package)),
171+
})
157172
.filter(|(_index, package)| {
158173
install_options.include_package(&package.id.name, Some(root_name), lock.members())
159174
})
160-
.map(|(index, package)| Node {
175+
.map(|(index, package)| Requirement {
161176
package,
162177
marker: reachability.remove(&index).unwrap_or_default(),
163178
})
164179
.collect::<Vec<_>>();
165180

166181
// Sort the nodes, such that unnamed URLs (editables) appear at the top.
167182
nodes.sort_unstable_by(|a, b| {
168-
NodeComparator::from(a.package).cmp(&NodeComparator::from(b.package))
183+
RequirementComparator::from(a.package).cmp(&RequirementComparator::from(b.package))
169184
});
170185

171186
Ok(Self {
@@ -179,7 +194,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
179194
impl std::fmt::Display for RequirementsTxtExport<'_> {
180195
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
181196
// Write out each package.
182-
for Node { package, marker } in &self.nodes {
197+
for Requirement { package, marker } in &self.nodes {
183198
match &package.id.source {
184199
Source::Registry(_) => {
185200
write!(f, "{}=={}", package.id.name, package.id.version)?;
@@ -261,17 +276,31 @@ impl std::fmt::Display for RequirementsTxtExport<'_> {
261276
}
262277
}
263278

279+
/// A node in the [`LockGraph`].
280+
#[derive(Debug, Clone, PartialEq, Eq)]
281+
enum Node<'lock> {
282+
Root,
283+
Package(&'lock Package),
284+
}
285+
264286
/// The edges of the [`LockGraph`].
265287
type Edge = MarkerTree;
266288

289+
/// A flat requirement, with its associated marker.
290+
#[derive(Debug, Clone, PartialEq, Eq)]
291+
struct Requirement<'lock> {
292+
package: &'lock Package,
293+
marker: MarkerTree,
294+
}
295+
267296
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
268-
enum NodeComparator<'lock> {
297+
enum RequirementComparator<'lock> {
269298
Editable(&'lock Path),
270299
Path(&'lock Path),
271300
Package(&'lock PackageId),
272301
}
273302

274-
impl<'lock> From<&'lock Package> for NodeComparator<'lock> {
303+
impl<'lock> From<&'lock Package> for RequirementComparator<'lock> {
275304
fn from(value: &'lock Package) -> Self {
276305
match &value.id.source {
277306
Source::Path(path) | Source::Directory(path) => Self::Path(path),

crates/uv/tests/it/export.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,7 +1027,7 @@ fn export_group() -> Result<()> {
10271027
requires-python = ">=3.12"
10281028
dependencies = ["typing-extensions"]
10291029
[dependency-groups]
1030-
foo = ["anyio"]
1030+
foo = ["anyio ; sys_platform == 'darwin'"]
10311031
bar = ["iniconfig"]
10321032
dev = ["sniffio"]
10331033
"#,
@@ -1072,10 +1072,10 @@ fn export_group() -> Result<()> {
10721072
----- stdout -----
10731073
# This file was autogenerated by uv via the following command:
10741074
# uv export --cache-dir [CACHE_DIR] --group foo
1075-
anyio==4.3.0 \
1075+
anyio==4.3.0 ; sys_platform == 'darwin' \
10761076
--hash=sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6 \
10771077
--hash=sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8
1078-
idna==3.6 \
1078+
idna==3.6 ; sys_platform == 'darwin' \
10791079
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
10801080
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
10811081
sniffio==1.3.1 \
@@ -1095,10 +1095,10 @@ fn export_group() -> Result<()> {
10951095
----- stdout -----
10961096
# This file was autogenerated by uv via the following command:
10971097
# uv export --cache-dir [CACHE_DIR] --group foo --group bar
1098-
anyio==4.3.0 \
1098+
anyio==4.3.0 ; sys_platform == 'darwin' \
10991099
--hash=sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6 \
11001100
--hash=sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8
1101-
idna==3.6 \
1101+
idna==3.6 ; sys_platform == 'darwin' \
11021102
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
11031103
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
11041104
iniconfig==2.0.0 \

0 commit comments

Comments
 (0)