Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Respect dependency group markers in uv export #8659

Merged
merged 1 commit into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 57 additions & 28 deletions crates/uv-resolver/src/lock/requirements_txt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand All @@ -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));
}
Expand Down Expand Up @@ -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.
Expand All @@ -152,20 +163,24 @@ 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(),
})
.collect::<Vec<_>>();

// 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 {
Expand All @@ -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)?;
Expand Down Expand Up @@ -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),
Expand Down
10 changes: 5 additions & 5 deletions crates/uv/tests/it/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
"#,
Expand Down Expand Up @@ -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 \
Expand All @@ -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 \
Expand Down
Loading