Use structured wheel tags everywhere (#10542)

## Summary

This PR extends the thinking in #10525 to platform tags, and then uses
the structured tag enums everywhere, rather than passing around strings.
I think this is a big improvement! It means we're no longer doing ad hoc
tag parsing all over the place.
This commit is contained in:
Charlie Marsh
2025-01-13 20:39:39 -05:00
committed by GitHub
parent 2ffa31946d
commit 5c91217488
33 changed files with 1624 additions and 487 deletions
+78 -59
View File
@@ -7,7 +7,7 @@ use pubgrub::Range;
use uv_distribution_filename::WheelFilename;
use uv_pep440::{release_specifiers_to_ranges, Version, VersionSpecifier, VersionSpecifiers};
use uv_pep508::{MarkerExpression, MarkerTree, MarkerValueVersion};
use uv_platform_tags::AbiTag;
use uv_platform_tags::{AbiTag, LanguageTag};
/// The `Requires-Python` requirement specifier.
///
@@ -381,53 +381,73 @@ impl RequiresPython {
/// sensitivity, we return `true` if the tags are unknown.
pub fn matches_wheel_tag(&self, wheel: &WheelFilename) -> bool {
wheel.abi_tag.iter().any(|abi_tag| {
if abi_tag == "abi3" {
if *abi_tag == AbiTag::Abi3 {
// Universal tags are allowed.
true
} else if abi_tag == "none" {
} else if *abi_tag == AbiTag::None {
wheel.python_tag.iter().any(|python_tag| {
// Remove `py2-none-any` and `py27-none-any` and analogous `cp` and `pp` tags.
if python_tag.starts_with("py2")
|| python_tag.starts_with("cp2")
|| python_tag.starts_with("pp2")
{
if matches!(
python_tag,
LanguageTag::Python { major: 2, .. }
| LanguageTag::CPython {
python_version: (2, ..)
}
| LanguageTag::PyPy {
python_version: (2, ..)
}
| LanguageTag::GraalPy {
python_version: (2, ..)
}
| LanguageTag::Pyston {
python_version: (2, ..)
}
) {
return false;
}
// Remove (e.g.) `py312-none-any` if the specifier is `==3.10.*`. However,
// `py37-none-any` would be fine, since the `3.7` represents a lower bound.
if let Some(minor) = python_tag.strip_prefix("py3") {
let Ok(minor) = minor.parse::<u64>() else {
return true;
};
if let LanguageTag::Python {
major: 3,
minor: Some(minor),
} = python_tag
{
// Ex) If the wheel bound is `3.12`, then it doesn't match `<=3.10.`.
let wheel_bound = UpperBound(Bound::Included(Version::new([3, minor])));
let wheel_bound =
UpperBound(Bound::Included(Version::new([3, u64::from(*minor)])));
if wheel_bound > self.range.upper().major_minor() {
return false;
}
return true;
};
}
// Remove (e.g.) `cp36-none-any` or `cp312-none-any` if the specifier is
// `==3.10.*`, since these tags require an exact match.
if let Some(minor) = python_tag
.strip_prefix("cp3")
.or_else(|| python_tag.strip_prefix("pp3"))
if let LanguageTag::CPython {
python_version: (3, minor),
}
| LanguageTag::PyPy {
python_version: (3, minor),
}
| LanguageTag::GraalPy {
python_version: (3, minor),
}
| LanguageTag::Pyston {
python_version: (3, minor),
} = python_tag
{
let Ok(minor) = minor.parse::<u64>() else {
return true;
};
// Ex) If the wheel bound is `3.6`, then it doesn't match `>=3.10`.
let wheel_bound = LowerBound(Bound::Included(Version::new([3, minor])));
let wheel_bound =
LowerBound(Bound::Included(Version::new([3, u64::from(*minor)])));
if wheel_bound < self.range.lower().major_minor() {
return false;
}
// Ex) If the wheel bound is `3.12`, then it doesn't match `<=3.10.`.
let wheel_bound = UpperBound(Bound::Included(Version::new([3, minor])));
let wheel_bound =
UpperBound(Bound::Included(Version::new([3, u64::from(*minor)])));
if wheel_bound > self.range.upper().major_minor() {
return false;
}
@@ -438,50 +458,49 @@ impl RequiresPython {
// Unknown tags are allowed.
true
})
} else if abi_tag.starts_with("cp2") || abi_tag.starts_with("pypy2") {
} else if matches!(
abi_tag,
AbiTag::CPython {
python_version: (2, ..),
..
} | AbiTag::PyPy {
python_version: (2, ..),
..
} | AbiTag::GraalPy {
python_version: (2, ..),
..
} | AbiTag::Pyston {
python_version: (2, ..),
..
}
) {
// Python 2 is never allowed.
false
} else if let Some(minor_no_dot_abi) = abi_tag.strip_prefix("cp3") {
// Remove ABI tags, both old (dmu) and future (t, and all other letters).
let minor_not_dot = minor_no_dot_abi.trim_matches(char::is_alphabetic);
let Ok(minor) = minor_not_dot.parse::<u64>() else {
// Unknown version pattern are allowed.
return true;
};
} else if let AbiTag::CPython {
python_version: (3, minor),
..
}
| AbiTag::PyPy {
python_version: (3, minor),
..
}
| AbiTag::GraalPy {
python_version: (3, minor),
..
}
| AbiTag::Pyston {
python_version: (3, minor),
..
} = abi_tag
{
// Ex) If the wheel bound is `3.6`, then it doesn't match `>=3.10`.
let wheel_bound = LowerBound(Bound::Included(Version::new([3, minor])));
let wheel_bound = LowerBound(Bound::Included(Version::new([3, u64::from(*minor)])));
if wheel_bound < self.range.lower().major_minor() {
return false;
}
// Ex) If the wheel bound is `3.12`, then it doesn't match `<=3.10.`.
let wheel_bound = UpperBound(Bound::Included(Version::new([3, minor])));
if wheel_bound > self.range.upper().major_minor() {
return false;
}
true
} else if let Some(minor_no_dot_abi) = abi_tag.strip_prefix("pypy3") {
// Given `pypy39_pp73`, we just removed `pypy3`, now we remove `_pp73` ...
let Some((minor_not_dot, _)) = minor_no_dot_abi.split_once('_') else {
// Unknown version pattern are allowed.
return true;
};
// ... and get `9`.
let Ok(minor) = minor_not_dot.parse::<u64>() else {
// Unknown version pattern are allowed.
return true;
};
// Ex) If the wheel bound is `3.6`, then it doesn't match `>=3.10`.
let wheel_bound = LowerBound(Bound::Included(Version::new([3, minor])));
if wheel_bound < self.range.lower().major_minor() {
return false;
}
// Ex) If the wheel bound is `3.12`, then it doesn't match `<=3.10.`.
let wheel_bound = UpperBound(Bound::Included(Version::new([3, minor])));
let wheel_bound = UpperBound(Bound::Included(Version::new([3, u64::from(*minor)])));
if wheel_bound > self.range.upper().major_minor() {
return false;
}