From 730bad4a884a7f35793d14c899d1cd764371624b Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 18 Sep 2025 09:03:46 -0500 Subject: [PATCH] Always ensure wheels are compatible with the required Python versions --- .../src/prioritized_distribution.rs | 94 +++++++++---------- crates/uv-resolver/src/resolver/mod.rs | 45 +++++++++ 2 files changed, 89 insertions(+), 50 deletions(-) diff --git a/crates/uv-distribution-types/src/prioritized_distribution.rs b/crates/uv-distribution-types/src/prioritized_distribution.rs index c96ad9ade..f47fe1791 100644 --- a/crates/uv-distribution-types/src/prioritized_distribution.rs +++ b/crates/uv-distribution-types/src/prioritized_distribution.rs @@ -32,7 +32,10 @@ struct PrioritizedDistInner { /// The hashes for each distribution. hashes: Vec, /// The set of supported platforms for the distribution, described in terms of their markers. - markers: MarkerTree, + platform_markers: MarkerTree, + /// The set of supported Python versions for the distribution, described in terms of their + /// markers. + python_markers: MarkerTree, } impl Default for PrioritizedDistInner { @@ -42,7 +45,8 @@ impl Default for PrioritizedDistInner { best_wheel_index: None, wheels: Vec::new(), hashes: Vec::new(), - markers: MarkerTree::FALSE, + platform_markers: MarkerTree::FALSE, + python_markers: MarkerTree::FALSE, } } } @@ -101,10 +105,31 @@ impl CompatibleDist<'_> { } } - /// Return the set of supported platform the distribution, in terms of their markers. + /// Return the set of supported platforms the distribution, in terms of their markers. + pub fn implied_platform_markers(&self) -> MarkerTree { + match self.prioritized() { + Some(prioritized) => prioritized.0.platform_markers, + None => MarkerTree::TRUE, + } + } + + /// Return the set of supported Python versions for the distribution, in terms of their markers. + pub fn implied_python_markers(&self) -> MarkerTree { + match self.prioritized() { + Some(prioritized) => prioritized.0.python_markers, + None => MarkerTree::TRUE, + } + } + + /// Return the combined set of supported markers for the distribution. pub fn implied_markers(&self) -> MarkerTree { match self.prioritized() { - Some(prioritized) => prioritized.0.markers, + Some(prioritized) => { + let mut markers = MarkerTree::TRUE; + markers.and(prioritized.0.platform_markers); + markers.and(prioritized.0.python_markers); + markers + } None => MarkerTree::TRUE, } } @@ -343,7 +368,8 @@ impl PrioritizedDist { compatibility: WheelCompatibility, ) -> Self { Self(Box::new(PrioritizedDistInner { - markers: implied_markers(&dist.filename), + platform_markers: implied_platform_markers(&dist.filename), + python_markers: implied_python_markers(&dist.filename), best_wheel_index: Some(0), wheels: vec![(dist, compatibility)], source: None, @@ -358,7 +384,8 @@ impl PrioritizedDist { compatibility: SourceDistCompatibility, ) -> Self { Self(Box::new(PrioritizedDistInner { - markers: MarkerTree::TRUE, + python_markers: MarkerTree::TRUE, + platform_markers: MarkerTree::TRUE, best_wheel_index: None, wheels: vec![], source: Some((dist, compatibility)), @@ -375,8 +402,15 @@ impl PrioritizedDist { ) { // Track the implied markers. if compatibility.is_compatible() { - if !self.0.markers.is_true() { - self.0.markers.or(implied_markers(&dist.filename)); + if !self.0.python_markers.is_true() { + self.0 + .python_markers + .or(implied_python_markers(&dist.filename)); + } + if !self.0.platform_markers.is_true() { + self.0 + .platform_markers + .or(implied_platform_markers(&dist.filename)); } } // Track the hashes. @@ -403,7 +437,8 @@ impl PrioritizedDist { ) { // Track the implied markers. if compatibility.is_compatible() { - self.0.markers = MarkerTree::TRUE; + self.0.python_markers = MarkerTree::TRUE; + self.0.platform_markers = MarkerTree::TRUE; } // Track the hashes. if !compatibility.is_excluded() { @@ -804,14 +839,6 @@ impl IncompatibleWheel { } } -/// Given a wheel filename, determine the set of supported markers. -pub fn implied_markers(filename: &WheelFilename) -> MarkerTree { - let mut marker = implied_platform_markers(filename); - marker.and(implied_python_markers(filename)); - - marker -} - /// Given a wheel filename, determine the set of supported platforms, in terms of their markers. /// /// This is roughly the inverse of platform tag generation: given a tag, we want to infer the @@ -1027,15 +1054,6 @@ mod tests { ); } - #[track_caller] - fn assert_implied_markers(filename: &str, expected: &str) { - let filename = WheelFilename::from_str(filename).unwrap(); - assert_eq!( - implied_markers(&filename), - expected.parse::().unwrap() - ); - } - #[test] fn test_implied_platform_markers() { let filename = WheelFilename::from_str("example-1.0-py3-none-any.whl").unwrap(); @@ -1121,28 +1139,4 @@ mod tests { "python_full_version >= '3.11' and python_full_version < '3.13'", ); } - - #[test] - fn test_implied_markers() { - assert_implied_markers( - "numpy-1.0-cp310-cp310-win32.whl", - "python_full_version == '3.10.*' and platform_python_implementation == 'CPython' and sys_platform == 'win32' and platform_machine == 'x86'", - ); - assert_implied_markers( - "pywin32-311-cp314-cp314-win_arm64.whl", - "python_full_version == '3.14.*' and platform_python_implementation == 'CPython' and sys_platform == 'win32' and platform_machine == 'ARM64'", - ); - assert_implied_markers( - "numpy-1.0-cp311-cp311-macosx_10_9_x86_64.whl", - "python_full_version == '3.11.*' and platform_python_implementation == 'CPython' and sys_platform == 'darwin' and platform_machine == 'x86_64'", - ); - assert_implied_markers( - "numpy-1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", - "python_full_version == '3.12.*' and platform_python_implementation == 'CPython' and sys_platform == 'linux' and platform_machine == 'aarch64'", - ); - assert_implied_markers( - "example-1.0-py3-none-any.whl", - "python_full_version >= '3' and python_full_version < '4'", - ); - } } diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index c7d4a2523..0e8889ad3 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -1418,6 +1418,51 @@ impl ResolverState>() + .join(", ") + ); + let forks = vec![ + VersionFork { + env: left, + id, + version: None, + }, + VersionFork { + env: right, + id, + version: None, + }, + ]; + return Ok(Some(ResolverVersion::Forked(forks))); + } + // If the user explicitly marked a platform as required, ensure it has coverage. for marker in self.options.required_environments.iter().copied() { // If the platform is part of the current environment...