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::{Package, PackageId, Source};
use crate::{Lock, LockError}; use crate::{Lock, LockError};
type LockGraph<'lock> = Graph<&'lock Package, Edge, Directed>; type LockGraph<'lock> = Graph<Node<'lock>, Edge, Directed>;
#[derive(Debug, Clone, PartialEq, Eq)]
struct Node<'lock> {
package: &'lock Package,
marker: MarkerTree,
}
/// An export of a [`Lock`] that renders in `requirements.txt` format. /// An export of a [`Lock`] that renders in `requirements.txt` format.
#[derive(Debug)] #[derive(Debug)]
pub struct RequirementsTxtExport<'lock> { pub struct RequirementsTxtExport<'lock> {
nodes: Vec<Node<'lock>>, nodes: Vec<Requirement<'lock>>,
hashes: bool, hashes: bool,
editable: EditableMode, editable: EditableMode,
} }
@ -55,45 +49,62 @@ impl<'lock> RequirementsTxtExport<'lock> {
let mut queue: VecDeque<(&Package, Option<&ExtraName>)> = VecDeque::new(); let mut queue: VecDeque<(&Package, Option<&ExtraName>)> = VecDeque::new();
let mut seen = FxHashSet::default(); let mut seen = FxHashSet::default();
let root = petgraph.add_node(Node::Root);
// Add the workspace package to the queue. // Add the workspace package to the queue.
let root = lock let dist = lock
.find_by_name(root_name) .find_by_name(root_name)
.expect("found too many packages matching root") .expect("found too many packages matching root")
.expect("could not find root"); .expect("could not find root");
if dev.prod() { if dev.prod() {
// Add the base package. // Add the workspace package to the graph.
queue.push_back((root, None)); 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 { match extras {
ExtrasSpecification::None => {} ExtrasSpecification::None => {}
ExtrasSpecification::All => { ExtrasSpecification::All => {
for extra in root.optional_dependencies.keys() { for extra in dist.optional_dependencies.keys() {
queue.push_back((root, Some(extra))); queue.push_back((dist, Some(extra)));
} }
} }
ExtrasSpecification::Some(extras) => { ExtrasSpecification::Some(extras) => {
for extra in extras { for extra in extras {
queue.push_back((root, Some(extra))); queue.push_back((dist, Some(extra)));
}
} }
} }
} }
// Add the root package to the graph. // Add any development dependencies.
inverse.insert(&root.id, petgraph.add_node(root));
}
// Add any dev dependencies.
for group in dev.iter() { 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); let dep_dist = lock.find_by_id(&dep.package_id);
// Add the dependency to the graph. // Add the dependency to the graph.
if let Entry::Vacant(entry) = inverse.entry(&dep.package_id) { 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)) { if seen.insert((&dep.package_id, None)) {
queue.push_back((dep_dist, None)); queue.push_back((dep_dist, None));
} }
@ -126,7 +137,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
// Add the dependency to the graph. // Add the dependency to the graph.
if let Entry::Vacant(entry) = inverse.entry(&dep.package_id) { 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. // Add the edge.
@ -152,12 +163,16 @@ impl<'lock> RequirementsTxtExport<'lock> {
let mut reachability = marker_reachability(&petgraph, &[]); let mut reachability = marker_reachability(&petgraph, &[]);
// Collect all packages. // Collect all packages.
let mut nodes: Vec<Node> = petgraph let mut nodes = petgraph
.node_references() .node_references()
.filter_map(|(index, node)| match node {
Node::Root => None,
Node::Package(package) => Some((index, package)),
})
.filter(|(_index, package)| { .filter(|(_index, package)| {
install_options.include_package(&package.id.name, Some(root_name), lock.members()) install_options.include_package(&package.id.name, Some(root_name), lock.members())
}) })
.map(|(index, package)| Node { .map(|(index, package)| Requirement {
package, package,
marker: reachability.remove(&index).unwrap_or_default(), 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. // Sort the nodes, such that unnamed URLs (editables) appear at the top.
nodes.sort_unstable_by(|a, b| { 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 { Ok(Self {
@ -179,7 +194,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
impl std::fmt::Display for RequirementsTxtExport<'_> { impl std::fmt::Display for RequirementsTxtExport<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// Write out each package. // Write out each package.
for Node { package, marker } in &self.nodes { for Requirement { package, marker } in &self.nodes {
match &package.id.source { match &package.id.source {
Source::Registry(_) => { Source::Registry(_) => {
write!(f, "{}=={}", package.id.name, package.id.version)?; 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`]. /// The edges of the [`LockGraph`].
type Edge = MarkerTree; 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)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum NodeComparator<'lock> { enum RequirementComparator<'lock> {
Editable(&'lock Path), Editable(&'lock Path),
Path(&'lock Path), Path(&'lock Path),
Package(&'lock PackageId), 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 { fn from(value: &'lock Package) -> Self {
match &value.id.source { match &value.id.source {
Source::Path(path) | Source::Directory(path) => Self::Path(path), Source::Path(path) | Source::Directory(path) => Self::Path(path),

View File

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