diff --git a/crates/pep508-rs/src/lib.rs b/crates/pep508-rs/src/lib.rs index f4d8d64a6..6ce417e50 100644 --- a/crates/pep508-rs/src/lib.rs +++ b/crates/pep508-rs/src/lib.rs @@ -358,6 +358,18 @@ impl Requirement { } } + /// Returns whether the markers apply for the given environment + pub fn evaluate_markers2(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool { + if let Some(marker) = &self.marker { + marker.evaluate_extras_and_python_version( + &extras.into_iter().cloned().collect(), + &[env.python_version.version.clone()], + ) + } else { + true + } + } + /// Returns whether the requirement would be satisfied, independent of environment markers, i.e. /// if there is potentially an environment that could activate this requirement. /// diff --git a/crates/uv-dev/src/resolve_cli.rs b/crates/uv-dev/src/resolve_cli.rs index e39fe10c5..3ab3e0141 100644 --- a/crates/uv-dev/src/resolve_cli.rs +++ b/crates/uv-dev/src/resolve_cli.rs @@ -114,7 +114,7 @@ pub(crate) async fn resolve_cli(args: ResolveCliArgs) -> Result<()> { let graphviz = Dot::with_attr_getters( resolution_graph.petgraph(), &[DotConfig::NodeNoLabel, DotConfig::EdgeNoLabel], - &|_graph, edge_ref| format!("label={:?}", edge_ref.weight().to_string()), + &|_graph, edge_ref| format!("label={:?}", edge_ref.weight().0.to_string()), &|_graph, (_node_index, dist)| { format!("label={:?}", dist.to_string().replace("==", "\n")) }, diff --git a/crates/uv-resolver/src/candidate_selector.rs b/crates/uv-resolver/src/candidate_selector.rs index c83b8565e..ab489c00d 100644 --- a/crates/uv-resolver/src/candidate_selector.rs +++ b/crates/uv-resolver/src/candidate_selector.rs @@ -65,7 +65,7 @@ impl Preferences { requirements .iter() .filter_map(|requirement| { - if !requirement.evaluate_markers(markers, &[]) { + if !requirement.evaluate_markers2(markers, &[]) { return None; } let Some(VersionOrUrl::VersionSpecifier(version_specifiers)) = diff --git a/crates/uv-resolver/src/prerelease_mode.rs b/crates/uv-resolver/src/prerelease_mode.rs index c8942d333..108d1a66d 100644 --- a/crates/uv-resolver/src/prerelease_mode.rs +++ b/crates/uv-resolver/src/prerelease_mode.rs @@ -65,10 +65,10 @@ impl PreReleaseStrategy { .iter() .chain(manifest.constraints.iter()) .chain(manifest.overrides.iter()) - .filter(|requirement| requirement.evaluate_markers(markers, &[])) + .filter(|requirement| requirement.evaluate_markers2(markers, &[])) .chain(manifest.editables.iter().flat_map(|(editable, metadata)| { metadata.requires_dist.iter().filter(|requirement| { - requirement.evaluate_markers(markers, &editable.extras) + requirement.evaluate_markers2(markers, &editable.extras) }) })) .filter(|requirement| { @@ -94,10 +94,10 @@ impl PreReleaseStrategy { .iter() .chain(manifest.constraints.iter()) .chain(manifest.overrides.iter()) - .filter(|requirement| requirement.evaluate_markers(markers, &[])) + .filter(|requirement| requirement.evaluate_markers2(markers, &[])) .chain(manifest.editables.iter().flat_map(|(editable, metadata)| { metadata.requires_dist.iter().filter(|requirement| { - requirement.evaluate_markers(markers, &editable.extras) + requirement.evaluate_markers2(markers, &editable.extras) }) })) .filter(|requirement| { diff --git a/crates/uv-resolver/src/pubgrub/dependencies.rs b/crates/uv-resolver/src/pubgrub/dependencies.rs index e2d376817..4d08423c7 100644 --- a/crates/uv-resolver/src/pubgrub/dependencies.rs +++ b/crates/uv-resolver/src/pubgrub/dependencies.rs @@ -34,10 +34,10 @@ impl PubGrubDependencies { for requirement in overrides.apply(requirements) { // If the requirement isn't relevant for the current platform, skip it. if let Some(extra) = source_extra { - if !requirement.evaluate_markers(env, std::slice::from_ref(extra)) { + if !requirement.evaluate_markers2(env, std::slice::from_ref(extra)) { continue; } - } else if !requirement.evaluate_markers(env, &[]) { + } else if !requirement.evaluate_markers2(env, &[]) { continue; } @@ -68,10 +68,10 @@ impl PubGrubDependencies { for constraint in constraints.get(&requirement.name).into_iter().flatten() { // If the requirement isn't relevant for the current platform, skip it. if let Some(extra) = source_extra { - if !constraint.evaluate_markers(env, std::slice::from_ref(extra)) { + if !constraint.evaluate_markers2(env, std::slice::from_ref(extra)) { continue; } - } else if !constraint.evaluate_markers(env, &[]) { + } else if !constraint.evaluate_markers2(env, &[]) { continue; } diff --git a/crates/uv-resolver/src/resolution.rs b/crates/uv-resolver/src/resolution.rs index 49ac8b618..f3215dd5e 100644 --- a/crates/uv-resolver/src/resolution.rs +++ b/crates/uv-resolver/src/resolution.rs @@ -15,6 +15,7 @@ use url::Url; use distribution_types::{Dist, DistributionMetadata, LocalEditable, Name, PackageId, Verbatim}; use once_map::OnceMap; use pep440_rs::Version; +use pep508_rs::MarkerTree; use pypi_types::{Hashes, Metadata21}; use uv_normalize::{ExtraName, PackageName}; @@ -42,7 +43,8 @@ pub enum AnnotationStyle { #[derive(Debug)] pub struct ResolutionGraph { /// The underlying graph. - petgraph: petgraph::graph::Graph, petgraph::Directed>, + petgraph: + petgraph::graph::Graph, Option), petgraph::Directed>, /// The metadata for every distribution in this resolution. hashes: FxHashMap>, /// The set of editable requirements in this resolution. @@ -64,7 +66,11 @@ impl ResolutionGraph { ) -> Result { // TODO(charlie): petgraph is a really heavy and unnecessary dependency here. We should // write our own graph, given that our requirements are so simple. - let mut petgraph = petgraph::graph::Graph::with_capacity(selection.len(), selection.len()); + let mut petgraph: petgraph::graph::Graph< + Dist, + (Range, Option), + petgraph::Directed, + > = petgraph::graph::Graph::with_capacity(selection.len(), selection.len()); let mut hashes = FxHashMap::with_capacity_and_hasher(selection.len(), BuildHasherDefault::default()); let mut diagnostics = Vec::new(); @@ -207,13 +213,13 @@ impl ResolutionGraph { for (package, version) in selection { for id in &state.incompatibilities[package] { if let Kind::FromDependencyOf( - self_package, + self_package2, self_version, dependency_package, dependency_range, ) = &state.incompatibility_store[*id].kind { - let PubGrubPackage::Package(self_package, _, _) = self_package else { + let PubGrubPackage::Package(self_package, _, _) = self_package2 else { continue; }; let PubGrubPackage::Package(dependency_package, _, _) = dependency_package @@ -226,13 +232,39 @@ impl ResolutionGraph { continue; } + let dist = + PubGrubDistribution::from_registry(self_package, &selection[self_package2]); + let requires_dist = + if let Some((_editable, metadata)) = editables.get(self_package) { + metadata.requires_dist.clone() + } else { + distributions + .get(&dist.package_id()) + .expect("Missing metadata") + .requires_dist + .clone() + }; + + let markers: Vec = requires_dist + .iter() + .filter(|req| &req.name == dependency_package) + .filter_map(|req| req.marker.clone()) + .collect(); + + let markers_joined = match markers.as_slice() { + [] => None, + [marker_tree] => Some(marker_tree.clone()), + // The package gets installed if any marker is fulfilled. + _ => Some(MarkerTree::Or(markers)), + }; + if self_version.contains(version) { let self_index = &inverse[self_package]; let dependency_index = &inverse[dependency_package]; petgraph.update_edge( *self_index, *dependency_index, - dependency_range.clone(), + (dependency_range.clone(), markers_joined), ); } } @@ -270,7 +302,10 @@ impl ResolutionGraph { } /// Return the underlying graph. - pub fn petgraph(&self) -> &petgraph::graph::Graph, petgraph::Directed> { + pub fn petgraph( + &self, + ) -> &petgraph::graph::Graph, Option), petgraph::Directed> + { &self.petgraph } } @@ -406,6 +441,16 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> { } } + let markers: Vec<_> = self + .resolution + .petgraph + .edges_directed(index, Direction::Incoming) + .filter_map(|edge| edge.weight().1.clone()) + .collect(); + if !markers.is_empty() { + line.push_str(&format!(" ; {}", MarkerTree::Or(markers))); + } + // Determine the annotation comment and separator (between comment and requirement). let mut annotation = None; diff --git a/crates/uv-resolver/src/resolution_mode.rs b/crates/uv-resolver/src/resolution_mode.rs index c121598e2..b09177789 100644 --- a/crates/uv-resolver/src/resolution_mode.rs +++ b/crates/uv-resolver/src/resolution_mode.rs @@ -45,10 +45,10 @@ impl ResolutionStrategy { manifest .requirements .iter() - .filter(|requirement| requirement.evaluate_markers(markers, &[])) + .filter(|requirement| requirement.evaluate_markers2(markers, &[])) .chain(manifest.editables.iter().flat_map(|(editable, metadata)| { metadata.requires_dist.iter().filter(|requirement| { - requirement.evaluate_markers(markers, &editable.extras) + requirement.evaluate_markers2(markers, &editable.extras) }) })) .map(|requirement| requirement.name.clone()) diff --git a/crates/uv-resolver/src/resolver/urls.rs b/crates/uv-resolver/src/resolver/urls.rs index 5fa47cdd8..28853f4dc 100644 --- a/crates/uv-resolver/src/resolver/urls.rs +++ b/crates/uv-resolver/src/resolver/urls.rs @@ -22,7 +22,7 @@ impl Urls { .iter() .chain(manifest.constraints.iter()) { - if !requirement.evaluate_markers(markers, &[]) { + if !requirement.evaluate_markers2(markers, &[]) { continue; } @@ -56,7 +56,7 @@ impl Urls { } for requirement in &metadata.requires_dist { - if !requirement.evaluate_markers(markers, &editable.extras) { + if !requirement.evaluate_markers2(markers, &editable.extras) { continue; } @@ -79,7 +79,7 @@ impl Urls { // Add any overrides. Conflicts here are fine, as the overrides are meant to be // authoritative. for requirement in &manifest.overrides { - if !requirement.evaluate_markers(markers, &[]) { + if !requirement.evaluate_markers2(markers, &[]) { continue; } diff --git a/crates/uv-resolver/src/yanks.rs b/crates/uv-resolver/src/yanks.rs index 8c092bb3c..a945e9662 100644 --- a/crates/uv-resolver/src/yanks.rs +++ b/crates/uv-resolver/src/yanks.rs @@ -20,12 +20,12 @@ impl AllowedYanks { .chain(manifest.constraints.iter()) .chain(manifest.overrides.iter()) .chain(manifest.preferences.iter()) - .filter(|requirement| requirement.evaluate_markers(markers, &[])) + .filter(|requirement| requirement.evaluate_markers2(markers, &[])) .chain(manifest.editables.iter().flat_map(|(editable, metadata)| { metadata .requires_dist .iter() - .filter(|requirement| requirement.evaluate_markers(markers, &editable.extras)) + .filter(|requirement| requirement.evaluate_markers2(markers, &editable.extras)) })) { let Some(pep508_rs::VersionOrUrl::VersionSpecifier(specifiers)) = diff --git a/crates/uv/src/commands/pip_sync.rs b/crates/uv/src/commands/pip_sync.rs index a61551537..12a90c815 100644 --- a/crates/uv/src/commands/pip_sync.rs +++ b/crates/uv/src/commands/pip_sync.rs @@ -163,6 +163,15 @@ pub(crate) async fn pip_sync( ) .await?; + let requirements: Vec<_> = requirements + .into_iter() + .filter(|req| { + req.marker.as_ref().map_or(true, |marker| { + marker.evaluate(venv.interpreter().markers(), &[]) + }) + }) + .collect(); + // Partition into those that should be linked from the cache (`local`), those that need to be // downloaded (`remote`), and those that should be removed (`extraneous`). let Plan {