mirror of https://github.com/astral-sh/uv
Always ensure wheels are compatible with the required Python versions
This commit is contained in:
parent
e23da5b315
commit
730bad4a88
|
|
@ -32,7 +32,10 @@ struct PrioritizedDistInner {
|
|||
/// The hashes for each distribution.
|
||||
hashes: Vec<HashDigest>,
|
||||
/// 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::<MarkerTree>().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'",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1418,6 +1418,51 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
return Ok(None);
|
||||
}
|
||||
|
||||
// If the package's Python markers are incompatible with the current environment, we need to
|
||||
// fork. We do not require users to explicitly add Python versions to their
|
||||
// `required-environments`.
|
||||
let dist_python_markers = dist.implied_python_markers();
|
||||
if !env.included_by_marker(dist_python_markers) {
|
||||
// Then we need to fork.
|
||||
let Some((left, right)) = fork_version_by_marker(env, dist_python_markers) else {
|
||||
return Ok(Some(ResolverVersion::Unavailable(
|
||||
candidate.version().clone(),
|
||||
UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
|
||||
// TODO(zanieb): Consider adding a Python-specific variant
|
||||
IncompatibleWheel::MissingPlatform(dist_python_markers),
|
||||
)),
|
||||
)));
|
||||
};
|
||||
|
||||
// TODO(zanieb): Consider a message that's focused on Python versions here
|
||||
debug!(
|
||||
"Forking on required Python `{}` for {}=={} ({})",
|
||||
dist_python_markers
|
||||
.try_to_string()
|
||||
.unwrap_or_else(|| "true".to_string()),
|
||||
name,
|
||||
candidate.version(),
|
||||
[&left, &right]
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.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...
|
||||
|
|
|
|||
Loading…
Reference in New Issue