mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-24 13:43:45 +00:00
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:
parent
2ffa31946d
commit
5c91217488
33 changed files with 1624 additions and 487 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
@ -1038,7 +1038,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1912,7 +1912,7 @@ dependencies = [
|
|||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2800,7 +2800,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3140,6 +3140,7 @@ dependencies = [
|
|||
"rancor",
|
||||
"rend",
|
||||
"rkyv_derive",
|
||||
"smallvec",
|
||||
"tinyvec",
|
||||
"uuid",
|
||||
]
|
||||
|
@ -3237,7 +3238,7 @@ dependencies = [
|
|||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3806,7 +3807,7 @@ dependencies = [
|
|||
"getrandom",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4664,6 +4665,7 @@ dependencies = [
|
|||
"uv-normalize",
|
||||
"uv-pep440",
|
||||
"uv-pep508",
|
||||
"uv-platform-tags",
|
||||
"uv-pypi-types",
|
||||
"uv-version",
|
||||
"uv-warnings",
|
||||
|
@ -5002,6 +5004,7 @@ dependencies = [
|
|||
"insta",
|
||||
"rkyv",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thiserror 2.0.11",
|
||||
"url",
|
||||
"uv-normalize",
|
||||
|
@ -5318,6 +5321,8 @@ name = "uv-platform-tags"
|
|||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"insta",
|
||||
"memchr",
|
||||
"rkyv",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"thiserror 2.0.11",
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use uv_bench::criterion::{
|
||||
criterion_group, criterion_main, measurement::WallTime, BenchmarkId, Criterion, Throughput,
|
||||
};
|
||||
use uv_distribution_filename::WheelFilename;
|
||||
use uv_platform_tags::Tags;
|
||||
use uv_platform_tags::{AbiTag, LanguageTag, PlatformTag, Tags};
|
||||
|
||||
/// A set of platform tags extracted from burntsushi's Archlinux workstation.
|
||||
/// We could just re-create these via `Tags::from_env`, but those might differ
|
||||
|
@ -73,9 +75,15 @@ const INVALID_WHEEL_NAMES: &[(&str, &str)] = &[
|
|||
/// extra processing. We thus expect construction to become slower, but we
|
||||
/// write a benchmark to ensure it is still "reasonable."
|
||||
fn benchmark_build_platform_tags(c: &mut Criterion<WallTime>) {
|
||||
let tags: Vec<(String, String, String)> = PLATFORM_TAGS
|
||||
let tags: Vec<(LanguageTag, AbiTag, PlatformTag)> = PLATFORM_TAGS
|
||||
.iter()
|
||||
.map(|&(py, abi, plat)| (py.to_string(), abi.to_string(), plat.to_string()))
|
||||
.map(|&(py, abi, plat)| {
|
||||
(
|
||||
LanguageTag::from_str(py).unwrap(),
|
||||
AbiTag::from_str(abi).unwrap(),
|
||||
PlatformTag::from_str(plat).unwrap(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut group = c.benchmark_group("build_platform_tags");
|
||||
|
@ -132,9 +140,15 @@ fn benchmark_wheelname_parsing_failure(c: &mut Criterion<WallTime>) {
|
|||
/// implementation did an exhaustive search over each of them for each tag in
|
||||
/// the wheel filename.
|
||||
fn benchmark_wheelname_tag_compatibility(c: &mut Criterion<WallTime>) {
|
||||
let tags: Vec<(String, String, String)> = PLATFORM_TAGS
|
||||
let tags: Vec<(LanguageTag, AbiTag, PlatformTag)> = PLATFORM_TAGS
|
||||
.iter()
|
||||
.map(|&(py, abi, plat)| (py.to_string(), abi.to_string(), plat.to_string()))
|
||||
.map(|&(py, abi, plat)| {
|
||||
(
|
||||
LanguageTag::from_str(py).unwrap(),
|
||||
AbiTag::from_str(abi).unwrap(),
|
||||
PlatformTag::from_str(plat).unwrap(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let tags = Tags::new(tags);
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ uv-globfilter = { workspace = true }
|
|||
uv-normalize = { workspace = true }
|
||||
uv-pep440 = { workspace = true }
|
||||
uv-pep508 = { workspace = true }
|
||||
uv-platform-tags = { workspace = true }
|
||||
uv-pypi-types = { workspace = true }
|
||||
uv-version = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
use crate::metadata::{BuildBackendSettings, DEFAULT_EXCLUDES};
|
||||
use crate::{DirectoryWriter, Error, FileList, ListWriter, PyProjectToml};
|
||||
use std::io::{BufReader, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{io, mem};
|
||||
|
||||
use fs_err::File;
|
||||
use globset::{GlobSet, GlobSetBuilder};
|
||||
use itertools::Itertools;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::io::{BufReader, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{io, mem};
|
||||
use tracing::{debug, trace};
|
||||
use uv_distribution_filename::WheelFilename;
|
||||
use uv_fs::Simplified;
|
||||
use uv_globfilter::{parse_portable_glob, GlobDirFilter};
|
||||
use uv_warnings::warn_user_once;
|
||||
use walkdir::WalkDir;
|
||||
use zip::{CompressionMethod, ZipWriter};
|
||||
|
||||
use uv_distribution_filename::{TagSet, WheelFilename};
|
||||
use uv_fs::Simplified;
|
||||
use uv_globfilter::{parse_portable_glob, GlobDirFilter};
|
||||
use uv_platform_tags::{AbiTag, LanguageTag, PlatformTag};
|
||||
use uv_warnings::warn_user_once;
|
||||
|
||||
use crate::metadata::{BuildBackendSettings, DEFAULT_EXCLUDES};
|
||||
use crate::{DirectoryWriter, Error, FileList, ListWriter, PyProjectToml};
|
||||
|
||||
/// Build a wheel from the source tree and place it in the output directory.
|
||||
pub fn build_wheel(
|
||||
source_tree: &Path,
|
||||
|
@ -33,9 +37,12 @@ pub fn build_wheel(
|
|||
name: pyproject_toml.name().clone(),
|
||||
version: pyproject_toml.version().clone(),
|
||||
build_tag: None,
|
||||
python_tag: vec!["py3".to_string()],
|
||||
abi_tag: vec!["none".to_string()],
|
||||
platform_tag: vec!["any".to_string()],
|
||||
python_tag: TagSet::from_slice(&[LanguageTag::Python {
|
||||
major: 3,
|
||||
minor: None,
|
||||
}]),
|
||||
abi_tag: TagSet::from_buf([AbiTag::None]),
|
||||
platform_tag: TagSet::from_buf([PlatformTag::Any]),
|
||||
};
|
||||
|
||||
let wheel_path = wheel_dir.join(filename.to_string());
|
||||
|
@ -68,9 +75,12 @@ pub fn list_wheel(
|
|||
name: pyproject_toml.name().clone(),
|
||||
version: pyproject_toml.version().clone(),
|
||||
build_tag: None,
|
||||
python_tag: vec!["py3".to_string()],
|
||||
abi_tag: vec!["none".to_string()],
|
||||
platform_tag: vec!["any".to_string()],
|
||||
python_tag: TagSet::from_slice(&[LanguageTag::Python {
|
||||
major: 3,
|
||||
minor: None,
|
||||
}]),
|
||||
abi_tag: TagSet::from_buf([AbiTag::None]),
|
||||
platform_tag: TagSet::from_buf([PlatformTag::Any]),
|
||||
};
|
||||
|
||||
let mut files = FileList::new();
|
||||
|
@ -247,9 +257,12 @@ pub fn build_editable(
|
|||
name: pyproject_toml.name().clone(),
|
||||
version: pyproject_toml.version().clone(),
|
||||
build_tag: None,
|
||||
python_tag: vec!["py3".to_string()],
|
||||
abi_tag: vec!["none".to_string()],
|
||||
platform_tag: vec!["any".to_string()],
|
||||
python_tag: TagSet::from_slice(&[LanguageTag::Python {
|
||||
major: 3,
|
||||
minor: None,
|
||||
}]),
|
||||
abi_tag: TagSet::from_buf([AbiTag::None]),
|
||||
platform_tag: TagSet::from_buf([PlatformTag::Any]),
|
||||
};
|
||||
|
||||
let wheel_path = wheel_dir.join(filename.to_string());
|
||||
|
@ -299,9 +312,12 @@ pub fn metadata(
|
|||
name: pyproject_toml.name().clone(),
|
||||
version: pyproject_toml.version().clone(),
|
||||
build_tag: None,
|
||||
python_tag: vec!["py3".to_string()],
|
||||
abi_tag: vec!["none".to_string()],
|
||||
platform_tag: vec!["any".to_string()],
|
||||
python_tag: TagSet::from_slice(&[LanguageTag::Python {
|
||||
major: 3,
|
||||
minor: None,
|
||||
}]),
|
||||
abi_tag: TagSet::from_buf([AbiTag::None]),
|
||||
platform_tag: TagSet::from_buf([PlatformTag::Any]),
|
||||
};
|
||||
|
||||
debug!(
|
||||
|
@ -744,6 +760,7 @@ mod test {
|
|||
use uv_fs::Simplified;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
use uv_platform_tags::{AbiTag, PlatformTag};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#[test]
|
||||
|
@ -752,9 +769,18 @@ mod test {
|
|||
name: PackageName::from_str("foo").unwrap(),
|
||||
version: Version::from_str("1.2.3").unwrap(),
|
||||
build_tag: None,
|
||||
python_tag: vec!["py2".to_string(), "py3".to_string()],
|
||||
abi_tag: vec!["none".to_string()],
|
||||
platform_tag: vec!["any".to_string()],
|
||||
python_tag: TagSet::from_slice(&[
|
||||
LanguageTag::Python {
|
||||
major: 2,
|
||||
minor: None,
|
||||
},
|
||||
LanguageTag::Python {
|
||||
major: 3,
|
||||
minor: None,
|
||||
},
|
||||
]),
|
||||
abi_tag: TagSet::from_buf([AbiTag::None]),
|
||||
platform_tag: TagSet::from_buf([PlatformTag::Any]),
|
||||
};
|
||||
|
||||
assert_snapshot!(wheel_info(&filename, "1.0.0+test"), @r"
|
||||
|
|
|
@ -787,7 +787,7 @@ impl CacheBucket {
|
|||
Self::Interpreter => "interpreter-v4",
|
||||
// Note that when bumping this, you'll also need to bump it
|
||||
// in crates/uv/tests/cache_clean.rs.
|
||||
Self::Simple => "simple-v14",
|
||||
Self::Simple => "simple-v15",
|
||||
// Note that when bumping this, you'll also need to bump it
|
||||
// in crates/uv/tests/cache_prune.rs.
|
||||
Self::Wheels => "wheels-v3",
|
||||
|
|
|
@ -20,8 +20,9 @@ uv-normalize = { workspace = true }
|
|||
uv-pep440 = { workspace = true }
|
||||
uv-platform-tags = { workspace = true }
|
||||
|
||||
rkyv = { workspace = true }
|
||||
rkyv = { workspace = true, features = ["smallvec-1"] }
|
||||
serde = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ pub use build_tag::{BuildTag, BuildTagError};
|
|||
pub use egg::{EggInfoFilename, EggInfoFilenameError};
|
||||
pub use extension::{DistExtension, ExtensionError, SourceDistExtension};
|
||||
pub use source_dist::{SourceDistFilename, SourceDistFilenameError};
|
||||
pub use wheel::{WheelFilename, WheelFilenameError};
|
||||
pub use wheel::{TagSet, WheelFilename, WheelFilenameError};
|
||||
|
||||
mod build_tag;
|
||||
mod egg;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
source: crates/uv-distribution-filename/src/wheel.rs
|
||||
expression: "WheelFilename::from_str(\"foo-1.2.3-202206090410-python-abi-platform.whl\")"
|
||||
expression: "WheelFilename::from_str(\"foo-1.2.3-202206090410-py3-none-any.whl\")"
|
||||
---
|
||||
Ok(
|
||||
WheelFilename {
|
||||
|
@ -15,13 +15,16 @@ Ok(
|
|||
),
|
||||
),
|
||||
python_tag: [
|
||||
"python",
|
||||
Python {
|
||||
major: 3,
|
||||
minor: None,
|
||||
},
|
||||
],
|
||||
abi_tag: [
|
||||
"abi",
|
||||
None,
|
||||
],
|
||||
platform_tag: [
|
||||
"platform",
|
||||
Any,
|
||||
],
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
source: crates/uv-distribution-filename/src/wheel.rs
|
||||
expression: "WheelFilename::from_str(\"foo-1.2.3-ab.cd.ef-gh-ij.kl.mn.op.qr.st.whl\")"
|
||||
expression: "WheelFilename::from_str(\"foo-1.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\")"
|
||||
---
|
||||
Ok(
|
||||
WheelFilename {
|
||||
|
@ -10,20 +10,31 @@ Ok(
|
|||
version: "1.2.3",
|
||||
build_tag: None,
|
||||
python_tag: [
|
||||
"ab",
|
||||
"cd",
|
||||
"ef",
|
||||
CPython {
|
||||
python_version: (
|
||||
3,
|
||||
11,
|
||||
),
|
||||
},
|
||||
],
|
||||
abi_tag: [
|
||||
"gh",
|
||||
CPython {
|
||||
gil_disabled: false,
|
||||
python_version: (
|
||||
3,
|
||||
11,
|
||||
),
|
||||
},
|
||||
],
|
||||
platform_tag: [
|
||||
"ij",
|
||||
"kl",
|
||||
"mn",
|
||||
"op",
|
||||
"qr",
|
||||
"st",
|
||||
Manylinux {
|
||||
major: 2,
|
||||
minor: 17,
|
||||
arch: X86_64,
|
||||
},
|
||||
Manylinux2014 {
|
||||
arch: X86_64,
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
source: crates/uv-distribution-filename/src/wheel.rs
|
||||
expression: "WheelFilename::from_str(\"foo-1.2.3-foo-bar-baz.whl\")"
|
||||
expression: "WheelFilename::from_str(\"foo-1.2.3-py3-none-any.whl\")"
|
||||
---
|
||||
Ok(
|
||||
WheelFilename {
|
||||
|
@ -10,13 +10,16 @@ Ok(
|
|||
version: "1.2.3",
|
||||
build_tag: None,
|
||||
python_tag: [
|
||||
"foo",
|
||||
Python {
|
||||
major: 3,
|
||||
minor: None,
|
||||
},
|
||||
],
|
||||
abi_tag: [
|
||||
"bar",
|
||||
None,
|
||||
],
|
||||
platform_tag: [
|
||||
"baz",
|
||||
Any,
|
||||
],
|
||||
},
|
||||
)
|
||||
|
|
|
@ -7,10 +7,19 @@ use url::Url;
|
|||
|
||||
use uv_normalize::{InvalidNameError, PackageName};
|
||||
use uv_pep440::{Version, VersionParseError};
|
||||
use uv_platform_tags::{TagCompatibility, Tags};
|
||||
use uv_platform_tags::{
|
||||
AbiTag, LanguageTag, ParseAbiTagError, ParseLanguageTagError, ParsePlatformTagError,
|
||||
PlatformTag, TagCompatibility, Tags,
|
||||
};
|
||||
|
||||
use crate::{BuildTag, BuildTagError};
|
||||
|
||||
/// A [`SmallVec`] type for storing tags.
|
||||
///
|
||||
/// Wheels tend to include a single language, ABI, and platform tag, so we use a [`SmallVec`] with a
|
||||
/// capacity of 1 to optimize for this common case.
|
||||
pub type TagSet<T> = smallvec::SmallVec<[T; 1]>;
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
|
@ -28,9 +37,9 @@ pub struct WheelFilename {
|
|||
pub name: PackageName,
|
||||
pub version: Version,
|
||||
pub build_tag: Option<BuildTag>,
|
||||
pub python_tag: Vec<String>,
|
||||
pub abi_tag: Vec<String>,
|
||||
pub platform_tag: Vec<String>,
|
||||
pub python_tag: TagSet<LanguageTag>,
|
||||
pub abi_tag: TagSet<AbiTag>,
|
||||
pub platform_tag: TagSet<PlatformTag>,
|
||||
}
|
||||
|
||||
impl FromStr for WheelFilename {
|
||||
|
@ -87,13 +96,33 @@ impl WheelFilename {
|
|||
|
||||
/// Get the tag for this wheel.
|
||||
fn get_tag(&self) -> String {
|
||||
if let ([python_tag], [abi_tag], [platform_tag]) = (
|
||||
self.python_tag.as_slice(),
|
||||
self.abi_tag.as_slice(),
|
||||
self.platform_tag.as_slice(),
|
||||
) {
|
||||
format!("{python_tag}-{abi_tag}-{platform_tag}",)
|
||||
} else {
|
||||
format!(
|
||||
"{}-{}-{}",
|
||||
self.python_tag.join("."),
|
||||
self.abi_tag.join("."),
|
||||
self.platform_tag.join(".")
|
||||
self.python_tag
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join("."),
|
||||
self.abi_tag
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join("."),
|
||||
self.platform_tag
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join("."),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a wheel filename from the stem (e.g., `foo-1.2.3-py3-none-any`).
|
||||
///
|
||||
|
@ -177,11 +206,21 @@ impl WheelFilename {
|
|||
name,
|
||||
version,
|
||||
build_tag,
|
||||
// TODO(charlie): Consider storing structured tags here. We need to benchmark to
|
||||
// understand whether it's impactful.
|
||||
python_tag: python_tag.split('.').map(String::from).collect(),
|
||||
abi_tag: abi_tag.split('.').map(String::from).collect(),
|
||||
platform_tag: platform_tag.split('.').map(String::from).collect(),
|
||||
python_tag: python_tag
|
||||
.split('.')
|
||||
.map(LanguageTag::from_str)
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|err| WheelFilenameError::InvalidLanguageTag(filename.to_string(), err))?,
|
||||
abi_tag: abi_tag
|
||||
.split('.')
|
||||
.map(AbiTag::from_str)
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|err| WheelFilenameError::InvalidAbiTag(filename.to_string(), err))?,
|
||||
platform_tag: platform_tag
|
||||
.split('.')
|
||||
.map(PlatformTag::from_str)
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|err| WheelFilenameError::InvalidPlatformTag(filename.to_string(), err))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -238,6 +277,12 @@ pub enum WheelFilenameError {
|
|||
InvalidPackageName(String, InvalidNameError),
|
||||
#[error("The wheel filename \"{0}\" has an invalid build tag: {1}")]
|
||||
InvalidBuildTag(String, BuildTagError),
|
||||
#[error("The wheel filename \"{0}\" has an invalid language tag: {1}")]
|
||||
InvalidLanguageTag(String, ParseLanguageTagError),
|
||||
#[error("The wheel filename \"{0}\" has an invalid ABI tag: {1}")]
|
||||
InvalidAbiTag(String, ParseAbiTagError),
|
||||
#[error("The wheel filename \"{0}\" has an invalid platform tag: {1}")]
|
||||
InvalidPlatformTag(String, ParsePlatformTagError),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -264,63 +309,63 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn err_2_part_no_pythontag() {
|
||||
let err = WheelFilename::from_str("foo-version.whl").unwrap_err();
|
||||
insta::assert_snapshot!(err, @r###"The wheel filename "foo-version.whl" is invalid: Must have a Python tag"###);
|
||||
let err = WheelFilename::from_str("foo-1.2.3.whl").unwrap_err();
|
||||
insta::assert_snapshot!(err, @r###"The wheel filename "foo-1.2.3.whl" is invalid: Must have a Python tag"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_3_part_no_abitag() {
|
||||
let err = WheelFilename::from_str("foo-version-python.whl").unwrap_err();
|
||||
insta::assert_snapshot!(err, @r###"The wheel filename "foo-version-python.whl" is invalid: Must have an ABI tag"###);
|
||||
let err = WheelFilename::from_str("foo-1.2.3-py3.whl").unwrap_err();
|
||||
insta::assert_snapshot!(err, @r###"The wheel filename "foo-1.2.3-py3.whl" is invalid: Must have an ABI tag"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_4_part_no_platformtag() {
|
||||
let err = WheelFilename::from_str("foo-version-python-abi.whl").unwrap_err();
|
||||
insta::assert_snapshot!(err, @r###"The wheel filename "foo-version-python-abi.whl" is invalid: Must have a platform tag"###);
|
||||
let err = WheelFilename::from_str("foo-1.2.3-py3-none.whl").unwrap_err();
|
||||
insta::assert_snapshot!(err, @r###"The wheel filename "foo-1.2.3-py3-none.whl" is invalid: Must have a platform tag"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_too_many_parts() {
|
||||
let err =
|
||||
WheelFilename::from_str("foo-1.2.3-build-python-abi-platform-oops.whl").unwrap_err();
|
||||
insta::assert_snapshot!(err, @r###"The wheel filename "foo-1.2.3-build-python-abi-platform-oops.whl" is invalid: Must have 5 or 6 components, but has more"###);
|
||||
WheelFilename::from_str("foo-1.2.3-202206090410-py3-none-any-whoops.whl").unwrap_err();
|
||||
insta::assert_snapshot!(err, @r###"The wheel filename "foo-1.2.3-202206090410-py3-none-any-whoops.whl" is invalid: Must have 5 or 6 components, but has more"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_invalid_package_name() {
|
||||
let err = WheelFilename::from_str("f!oo-1.2.3-python-abi-platform.whl").unwrap_err();
|
||||
insta::assert_snapshot!(err, @r###"The wheel filename "f!oo-1.2.3-python-abi-platform.whl" has an invalid package name"###);
|
||||
let err = WheelFilename::from_str("f!oo-1.2.3-py3-none-any.whl").unwrap_err();
|
||||
insta::assert_snapshot!(err, @r###"The wheel filename "f!oo-1.2.3-py3-none-any.whl" has an invalid package name"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_invalid_version() {
|
||||
let err = WheelFilename::from_str("foo-x.y.z-python-abi-platform.whl").unwrap_err();
|
||||
insta::assert_snapshot!(err, @r###"The wheel filename "foo-x.y.z-python-abi-platform.whl" has an invalid version: expected version to start with a number, but no leading ASCII digits were found"###);
|
||||
let err = WheelFilename::from_str("foo-x.y.z-py3-none-any.whl").unwrap_err();
|
||||
insta::assert_snapshot!(err, @r###"The wheel filename "foo-x.y.z-py3-none-any.whl" has an invalid version: expected version to start with a number, but no leading ASCII digits were found"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn err_invalid_build_tag() {
|
||||
let err = WheelFilename::from_str("foo-1.2.3-tag-python-abi-platform.whl").unwrap_err();
|
||||
insta::assert_snapshot!(err, @r###"The wheel filename "foo-1.2.3-tag-python-abi-platform.whl" has an invalid build tag: must start with a digit"###);
|
||||
let err = WheelFilename::from_str("foo-1.2.3-tag-py3-none-any.whl").unwrap_err();
|
||||
insta::assert_snapshot!(err, @r###"The wheel filename "foo-1.2.3-tag-py3-none-any.whl" has an invalid build tag: must start with a digit"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_single_tags() {
|
||||
insta::assert_debug_snapshot!(WheelFilename::from_str("foo-1.2.3-foo-bar-baz.whl"));
|
||||
insta::assert_debug_snapshot!(WheelFilename::from_str("foo-1.2.3-py3-none-any.whl"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_multiple_tags() {
|
||||
insta::assert_debug_snapshot!(WheelFilename::from_str(
|
||||
"foo-1.2.3-ab.cd.ef-gh-ij.kl.mn.op.qr.st.whl"
|
||||
"foo-1.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_build_tag() {
|
||||
insta::assert_debug_snapshot!(WheelFilename::from_str(
|
||||
"foo-1.2.3-202206090410-python-abi-platform.whl"
|
||||
"foo-1.2.3-202206090410-py3-none-any.whl"
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -1343,8 +1343,8 @@ mod test {
|
|||
/// Ensure that we don't accidentally grow the `Dist` sizes.
|
||||
#[test]
|
||||
fn dist_size() {
|
||||
assert!(size_of::<Dist>() <= 288, "{}", size_of::<Dist>());
|
||||
assert!(size_of::<BuiltDist>() <= 288, "{}", size_of::<BuiltDist>());
|
||||
assert!(size_of::<Dist>() <= 312, "{}", size_of::<Dist>());
|
||||
assert!(size_of::<BuiltDist>() <= 312, "{}", size_of::<BuiltDist>());
|
||||
assert!(
|
||||
size_of::<SourceDist>() <= 264,
|
||||
"{}",
|
||||
|
|
|
@ -8,7 +8,7 @@ use tracing::debug;
|
|||
use uv_distribution_filename::{BuildTag, WheelFilename};
|
||||
use uv_pep440::VersionSpecifiers;
|
||||
use uv_pep508::{MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString};
|
||||
use uv_platform_tags::{AbiTag, IncompatibleTag, TagPriority, Tags};
|
||||
use uv_platform_tags::{AbiTag, IncompatibleTag, LanguageTag, PlatformTag, TagPriority, Tags};
|
||||
use uv_pypi_types::{HashDigest, Yanked};
|
||||
|
||||
use crate::{
|
||||
|
@ -175,11 +175,11 @@ impl IncompatibleDist {
|
|||
match self {
|
||||
Self::Wheel(incompatibility) => match incompatibility {
|
||||
IncompatibleWheel::Tag(IncompatibleTag::Python) => {
|
||||
let tag = tags?.python_tag().map(ToString::to_string)?;
|
||||
let tag = tags?.python_tag().as_ref().map(ToString::to_string)?;
|
||||
Some(format!("(e.g., `{tag}`)", tag = tag.cyan()))
|
||||
}
|
||||
IncompatibleWheel::Tag(IncompatibleTag::Abi) => {
|
||||
let tag = tags?.abi_tag().map(ToString::to_string)?;
|
||||
let tag = tags?.abi_tag().as_ref().map(ToString::to_string)?;
|
||||
Some(format!("(e.g., `{tag}`)", tag = tag.cyan()))
|
||||
}
|
||||
IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion) => {
|
||||
|
@ -523,32 +523,32 @@ impl PrioritizedDist {
|
|||
}
|
||||
|
||||
/// Returns the set of all Python tags for the distribution.
|
||||
pub fn python_tags(&self) -> BTreeSet<&str> {
|
||||
pub fn python_tags(&self) -> BTreeSet<LanguageTag> {
|
||||
self.0
|
||||
.wheels
|
||||
.iter()
|
||||
.flat_map(|(wheel, _)| wheel.filename.python_tag.iter().map(String::as_str))
|
||||
.flat_map(|(wheel, _)| wheel.filename.python_tag.iter().copied())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the set of all ABI tags for the distribution.
|
||||
pub fn abi_tags(&self) -> BTreeSet<&str> {
|
||||
pub fn abi_tags(&self) -> BTreeSet<AbiTag> {
|
||||
self.0
|
||||
.wheels
|
||||
.iter()
|
||||
.flat_map(|(wheel, _)| wheel.filename.abi_tag.iter().map(String::as_str))
|
||||
.flat_map(|(wheel, _)| wheel.filename.abi_tag.iter().copied())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the set of platform tags for the distribution that are ABI-compatible with the given
|
||||
/// tags.
|
||||
pub fn platform_tags<'a>(&'a self, tags: &'a Tags) -> BTreeSet<&'a str> {
|
||||
pub fn platform_tags<'a>(&'a self, tags: &'a Tags) -> BTreeSet<&'a PlatformTag> {
|
||||
let mut candidates = BTreeSet::new();
|
||||
for (wheel, _) in &self.0.wheels {
|
||||
for wheel_py in &wheel.filename.python_tag {
|
||||
for wheel_abi in &wheel.filename.abi_tag {
|
||||
if tags.is_compatible_abi(wheel_py.as_str(), wheel_abi.as_str()) {
|
||||
candidates.extend(wheel.filename.platform_tag.iter().map(String::as_str));
|
||||
if tags.is_compatible_abi(*wheel_py, *wheel_abi) {
|
||||
candidates.extend(wheel.filename.platform_tag.iter());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -725,13 +725,13 @@ impl IncompatibleWheel {
|
|||
pub fn implied_markers(filename: &WheelFilename) -> MarkerTree {
|
||||
let mut marker = MarkerTree::FALSE;
|
||||
for platform_tag in &filename.platform_tag {
|
||||
match platform_tag.as_str() {
|
||||
"any" => {
|
||||
match platform_tag {
|
||||
PlatformTag::Any => {
|
||||
return MarkerTree::TRUE;
|
||||
}
|
||||
|
||||
// Windows
|
||||
"win32" => {
|
||||
PlatformTag::Win32 => {
|
||||
let mut tag_marker = MarkerTree::expression(MarkerExpression::String {
|
||||
key: MarkerValueString::SysPlatform,
|
||||
operator: MarkerOperator::Equal,
|
||||
|
@ -744,7 +744,7 @@ pub fn implied_markers(filename: &WheelFilename) -> MarkerTree {
|
|||
}));
|
||||
marker.or(tag_marker);
|
||||
}
|
||||
"win_amd64" => {
|
||||
PlatformTag::WinAmd64 => {
|
||||
let mut tag_marker = MarkerTree::expression(MarkerExpression::String {
|
||||
key: MarkerValueString::SysPlatform,
|
||||
operator: MarkerOperator::Equal,
|
||||
|
@ -757,7 +757,7 @@ pub fn implied_markers(filename: &WheelFilename) -> MarkerTree {
|
|||
}));
|
||||
marker.or(tag_marker);
|
||||
}
|
||||
"win_arm64" => {
|
||||
PlatformTag::WinArm64 => {
|
||||
let mut tag_marker = MarkerTree::expression(MarkerExpression::String {
|
||||
key: MarkerValueString::SysPlatform,
|
||||
operator: MarkerOperator::Equal,
|
||||
|
@ -772,95 +772,20 @@ pub fn implied_markers(filename: &WheelFilename) -> MarkerTree {
|
|||
}
|
||||
|
||||
// macOS
|
||||
tag if tag.starts_with("macosx_") => {
|
||||
PlatformTag::Macos { binary_format, .. } => {
|
||||
let mut tag_marker = MarkerTree::expression(MarkerExpression::String {
|
||||
key: MarkerValueString::SysPlatform,
|
||||
operator: MarkerOperator::Equal,
|
||||
value: arcstr::literal!("darwin"),
|
||||
});
|
||||
|
||||
// Parse the macOS version from the tag.
|
||||
//
|
||||
// For example, given `macosx_10_9_x86_64`, infer `10.9`, followed by `x86_64`.
|
||||
//
|
||||
// If at any point we fail to parse, we assume the tag is invalid and skip it.
|
||||
let mut parts = tag.splitn(4, '_');
|
||||
|
||||
// Skip the "macosx_" prefix.
|
||||
if parts.next().is_none_or(|part| part != "macosx") {
|
||||
debug!("Failed to parse macOS prefix from tag: {tag}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip the major and minor version numbers.
|
||||
if parts
|
||||
.next()
|
||||
.and_then(|part| part.parse::<u16>().ok())
|
||||
.is_none()
|
||||
{
|
||||
debug!("Failed to parse macOS major version from tag: {tag}");
|
||||
continue;
|
||||
};
|
||||
if parts
|
||||
.next()
|
||||
.and_then(|part| part.parse::<u16>().ok())
|
||||
.is_none()
|
||||
{
|
||||
debug!("Failed to parse macOS minor version from tag: {tag}");
|
||||
continue;
|
||||
};
|
||||
|
||||
// Extract the architecture from the end of the tag.
|
||||
let Some(arch) = parts.next() else {
|
||||
debug!("Failed to parse macOS architecture from tag: {tag}");
|
||||
continue;
|
||||
};
|
||||
|
||||
// Extract the architecture from the end of the tag.
|
||||
let mut arch_marker = MarkerTree::FALSE;
|
||||
let supported_architectures = match arch {
|
||||
"universal" => {
|
||||
// Allow any of: "x86_64", "i386", "ppc64", "ppc", "intel"
|
||||
["x86_64", "i386", "ppc64", "ppc", "intel"].iter()
|
||||
}
|
||||
"universal2" => {
|
||||
// Allow any of: "x86_64", "arm64"
|
||||
["x86_64", "arm64"].iter()
|
||||
}
|
||||
"intel" => {
|
||||
// Allow any of: "x86_64", "i386"
|
||||
["x86_64", "i386"].iter()
|
||||
}
|
||||
"x86_64" => {
|
||||
// Allow only "x86_64"
|
||||
["x86_64"].iter()
|
||||
}
|
||||
"arm64" => {
|
||||
// Allow only "arm64"
|
||||
["arm64"].iter()
|
||||
}
|
||||
"ppc64" => {
|
||||
// Allow only "ppc64"
|
||||
["ppc64"].iter()
|
||||
}
|
||||
"ppc" => {
|
||||
// Allow only "ppc"
|
||||
["ppc"].iter()
|
||||
}
|
||||
"i386" => {
|
||||
// Allow only "i386"
|
||||
["i386"].iter()
|
||||
}
|
||||
_ => {
|
||||
debug!("Unknown macOS architecture in wheel tag: {tag}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
for arch in supported_architectures {
|
||||
for arch in binary_format.platform_machine() {
|
||||
arch_marker.or(MarkerTree::expression(MarkerExpression::String {
|
||||
key: MarkerValueString::PlatformMachine,
|
||||
operator: MarkerOperator::Equal,
|
||||
value: ArcStr::from(*arch),
|
||||
value: ArcStr::from(arch.name()),
|
||||
}));
|
||||
}
|
||||
tag_marker.and(arch_marker);
|
||||
|
@ -869,81 +794,28 @@ pub fn implied_markers(filename: &WheelFilename) -> MarkerTree {
|
|||
}
|
||||
|
||||
// Linux
|
||||
tag => {
|
||||
PlatformTag::Manylinux { arch, .. }
|
||||
| PlatformTag::Manylinux1 { arch, .. }
|
||||
| PlatformTag::Manylinux2010 { arch, .. }
|
||||
| PlatformTag::Manylinux2014 { arch, .. }
|
||||
| PlatformTag::Musllinux { arch, .. }
|
||||
| PlatformTag::Linux { arch } => {
|
||||
let mut tag_marker = MarkerTree::expression(MarkerExpression::String {
|
||||
key: MarkerValueString::SysPlatform,
|
||||
operator: MarkerOperator::Equal,
|
||||
value: arcstr::literal!("linux"),
|
||||
});
|
||||
|
||||
// Parse the architecture from the tag.
|
||||
let arch = if let Some(arch) = tag.strip_prefix("linux_") {
|
||||
arch
|
||||
} else if let Some(arch) = tag.strip_prefix("manylinux1_") {
|
||||
arch
|
||||
} else if let Some(arch) = tag.strip_prefix("manylinux2010_") {
|
||||
arch
|
||||
} else if let Some(arch) = tag.strip_prefix("manylinux2014_") {
|
||||
arch
|
||||
} else if let Some(arch) = tag.strip_prefix("musllinux_") {
|
||||
// Skip over the version tags (e.g., given `musllinux_1_2`, skip over `1` and `2`).
|
||||
let mut parts = arch.splitn(3, '_');
|
||||
if parts
|
||||
.next()
|
||||
.and_then(|part| part.parse::<u16>().ok())
|
||||
.is_none()
|
||||
{
|
||||
debug!("Failed to parse musllinux major version from tag: {tag}");
|
||||
continue;
|
||||
};
|
||||
if parts
|
||||
.next()
|
||||
.and_then(|part| part.parse::<u16>().ok())
|
||||
.is_none()
|
||||
{
|
||||
debug!("Failed to parse musllinux minor version from tag: {tag}");
|
||||
continue;
|
||||
};
|
||||
let Some(arch) = parts.next() else {
|
||||
debug!("Failed to parse musllinux architecture from tag: {tag}");
|
||||
continue;
|
||||
};
|
||||
arch
|
||||
} else if let Some(arch) = tag.strip_prefix("manylinux_") {
|
||||
// Skip over the version tags (e.g., given `manylinux_2_17`, skip over `2` and `17`).
|
||||
let mut parts = arch.splitn(3, '_');
|
||||
if parts
|
||||
.next()
|
||||
.and_then(|part| part.parse::<u16>().ok())
|
||||
.is_none()
|
||||
{
|
||||
debug!("Failed to parse manylinux major version from tag: {tag}");
|
||||
continue;
|
||||
};
|
||||
if parts
|
||||
.next()
|
||||
.and_then(|part| part.parse::<u16>().ok())
|
||||
.is_none()
|
||||
{
|
||||
debug!("Failed to parse manylinux minor version from tag: {tag}");
|
||||
continue;
|
||||
};
|
||||
let Some(arch) = parts.next() else {
|
||||
debug!("Failed to parse manylinux architecture from tag: {tag}");
|
||||
continue;
|
||||
};
|
||||
arch
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
tag_marker.and(MarkerTree::expression(MarkerExpression::String {
|
||||
key: MarkerValueString::PlatformMachine,
|
||||
operator: MarkerOperator::Equal,
|
||||
value: ArcStr::from(arch),
|
||||
value: ArcStr::from(arch.name()),
|
||||
}));
|
||||
|
||||
marker.or(tag_marker);
|
||||
}
|
||||
|
||||
tag => {
|
||||
debug!("Unknown platform tag in wheel tag: {tag}");
|
||||
}
|
||||
}
|
||||
}
|
||||
marker
|
||||
|
|
|
@ -16,6 +16,8 @@ doctest = false
|
|||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
memchr = { workspace = true }
|
||||
rkyv = { workspace = true}
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
|
|
@ -5,7 +5,20 @@ use std::str::FromStr;
|
|||
///
|
||||
/// This is the second segment in the wheel filename, following the language tag. For example,
|
||||
/// in `cp39-none-manylinux_2_24_x86_64.whl`, the ABI tag is `none`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Hash,
|
||||
rkyv::Archive,
|
||||
rkyv::Deserialize,
|
||||
rkyv::Serialize,
|
||||
)]
|
||||
#[rkyv(derive(Debug))]
|
||||
pub enum AbiTag {
|
||||
/// Ex) `none`
|
||||
None,
|
||||
|
@ -65,7 +78,7 @@ impl std::fmt::Display for AbiTag {
|
|||
} => {
|
||||
write!(
|
||||
f,
|
||||
"graalpy{py_major}{py_minor}_graalpy{impl_major}{impl_minor}_{py_major}{py_minor}native"
|
||||
"graalpy{py_major}{py_minor}_graalpy{impl_major}{impl_minor}_{py_major}{py_minor}_native"
|
||||
)
|
||||
}
|
||||
Self::Pyston {
|
||||
|
@ -203,6 +216,13 @@ impl FromStr for AbiTag {
|
|||
implementation: "GraalPy",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
let version_end = rest
|
||||
.find('_')
|
||||
.ok_or_else(|| ParseAbiTagError::InvalidFormat {
|
||||
implementation: "GraalPy",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
let rest = &rest[..version_end];
|
||||
let (impl_major, impl_minor) = parse_impl_version(rest, "GraalPy", s)?;
|
||||
Ok(Self::GraalPy {
|
||||
python_version: (major, minor),
|
||||
|
@ -374,8 +394,11 @@ mod tests {
|
|||
python_version: (3, 10),
|
||||
implementation_version: (2, 40),
|
||||
};
|
||||
assert_eq!(AbiTag::from_str("graalpy310_graalpy240"), Ok(tag));
|
||||
assert_eq!(tag.to_string(), "graalpy310_graalpy240_310native");
|
||||
assert_eq!(
|
||||
AbiTag::from_str("graalpy310_graalpy240_310_native"),
|
||||
Ok(tag)
|
||||
);
|
||||
assert_eq!(tag.to_string(), "graalpy310_graalpy240_310_native");
|
||||
|
||||
assert_eq!(
|
||||
AbiTag::from_str("graalpy310"),
|
||||
|
@ -393,7 +416,7 @@ mod tests {
|
|||
);
|
||||
assert_eq!(
|
||||
AbiTag::from_str("graalpy310_graalpyXY"),
|
||||
Err(ParseAbiTagError::InvalidImplMajorVersion {
|
||||
Err(ParseAbiTagError::InvalidFormat {
|
||||
implementation: "GraalPy",
|
||||
tag: "graalpy310_graalpyXY".to_string()
|
||||
})
|
||||
|
|
|
@ -5,7 +5,20 @@ use std::str::FromStr;
|
|||
///
|
||||
/// This is the first segment in the wheel filename. For example, in `cp39-none-manylinux_2_24_x86_64.whl`,
|
||||
/// the language tag is `cp39`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Hash,
|
||||
rkyv::Archive,
|
||||
rkyv::Deserialize,
|
||||
rkyv::Serialize,
|
||||
)]
|
||||
#[rkyv(derive(Debug))]
|
||||
pub enum LanguageTag {
|
||||
/// Ex) `none`
|
||||
None,
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
pub use abi_tag::AbiTag;
|
||||
pub use language_tag::LanguageTag;
|
||||
pub use abi_tag::{AbiTag, ParseAbiTagError};
|
||||
pub use language_tag::{LanguageTag, ParseLanguageTagError};
|
||||
pub use platform::{Arch, Os, Platform, PlatformError};
|
||||
pub use tags::{IncompatibleTag, TagCompatibility, TagPriority, Tags, TagsError};
|
||||
pub use platform_tag::{ParsePlatformTagError, PlatformTag};
|
||||
pub use tags::{BinaryFormat, IncompatibleTag, TagCompatibility, TagPriority, Tags, TagsError};
|
||||
|
||||
mod abi_tag;
|
||||
mod language_tag;
|
||||
mod platform;
|
||||
mod platform_tag;
|
||||
mod tags;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
//! Abstractions for understanding the current platform (operating system and architecture).
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::{fmt, io};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -13,7 +13,7 @@ pub enum PlatformError {
|
|||
OsVersionDetectionError(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Platform {
|
||||
os: Os,
|
||||
arch: Arch,
|
||||
|
@ -37,7 +37,7 @@ impl Platform {
|
|||
}
|
||||
|
||||
/// All supported operating systems.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(tag = "name", rename_all = "lowercase")]
|
||||
pub enum Os {
|
||||
Manylinux { major: u16, minor: u16 },
|
||||
|
@ -72,7 +72,22 @@ impl fmt::Display for Os {
|
|||
}
|
||||
|
||||
/// All supported CPU architectures
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Hash,
|
||||
rkyv::Archive,
|
||||
rkyv::Deserialize,
|
||||
rkyv::Serialize,
|
||||
serde::Deserialize,
|
||||
serde::Serialize,
|
||||
)]
|
||||
#[rkyv(derive(Debug))]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Arch {
|
||||
#[serde(alias = "arm64")]
|
||||
|
@ -85,6 +100,8 @@ pub enum Arch {
|
|||
Powerpc64Le,
|
||||
#[serde(alias = "ppc64")]
|
||||
Powerpc64,
|
||||
#[serde(alias = "ppc")]
|
||||
Powerpc,
|
||||
#[serde(alias = "i386", alias = "i686")]
|
||||
X86,
|
||||
#[serde(alias = "amd64")]
|
||||
|
@ -103,6 +120,7 @@ impl fmt::Display for Arch {
|
|||
Self::Armv7L => write!(f, "armv7l"),
|
||||
Self::Powerpc64Le => write!(f, "ppc64le"),
|
||||
Self::Powerpc64 => write!(f, "ppc64"),
|
||||
Self::Powerpc => write!(f, "ppc"),
|
||||
Self::X86 => write!(f, "i686"),
|
||||
Self::X86_64 => write!(f, "x86_64"),
|
||||
Self::S390X => write!(f, "s390x"),
|
||||
|
@ -112,6 +130,28 @@ impl fmt::Display for Arch {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromStr for Arch {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"aarch64" => Ok(Self::Aarch64),
|
||||
"armv5tel" => Ok(Self::Armv5TEL),
|
||||
"armv6l" => Ok(Self::Armv6L),
|
||||
"armv7l" => Ok(Self::Armv7L),
|
||||
"ppc64le" => Ok(Self::Powerpc64Le),
|
||||
"ppc64" => Ok(Self::Powerpc64),
|
||||
"ppc" => Ok(Self::Powerpc),
|
||||
"i686" => Ok(Self::X86),
|
||||
"x86_64" => Ok(Self::X86_64),
|
||||
"s390x" => Ok(Self::S390X),
|
||||
"loongarch64" => Ok(Self::LoongArch64),
|
||||
"riscv64" => Ok(Self::Riscv64),
|
||||
_ => Err(format!("Unknown architecture: {s}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Arch {
|
||||
/// Returns the oldest possible `manylinux` tag for this architecture, if it supports
|
||||
/// `manylinux`.
|
||||
|
@ -126,7 +166,45 @@ impl Arch {
|
|||
// manylinux_2_31
|
||||
Self::Riscv64 => Some(31),
|
||||
// unsupported
|
||||
Self::Armv5TEL | Self::Armv6L | Self::LoongArch64 => None,
|
||||
Self::Powerpc | Self::Armv5TEL | Self::Armv6L | Self::LoongArch64 => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the canonical name of the architecture.
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Aarch64 => "aarch64",
|
||||
Self::Armv5TEL => "armv5tel",
|
||||
Self::Armv6L => "armv6l",
|
||||
Self::Armv7L => "armv7l",
|
||||
Self::Powerpc64Le => "ppc64le",
|
||||
Self::Powerpc64 => "ppc64",
|
||||
Self::Powerpc => "ppc",
|
||||
Self::X86 => "i686",
|
||||
Self::X86_64 => "x86_64",
|
||||
Self::S390X => "s390x",
|
||||
Self::LoongArch64 => "loongarch64",
|
||||
Self::Riscv64 => "riscv64",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over all supported architectures.
|
||||
pub fn iter() -> impl Iterator<Item = Self> {
|
||||
[
|
||||
Self::Aarch64,
|
||||
Self::Armv5TEL,
|
||||
Self::Armv6L,
|
||||
Self::Armv7L,
|
||||
Self::Powerpc64Le,
|
||||
Self::Powerpc64,
|
||||
Self::Powerpc,
|
||||
Self::X86,
|
||||
Self::X86_64,
|
||||
Self::S390X,
|
||||
Self::LoongArch64,
|
||||
Self::Riscv64,
|
||||
]
|
||||
.iter()
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
|
|
884
crates/uv-platform-tags/src/platform_tag.rs
Normal file
884
crates/uv-platform-tags/src/platform_tag.rs
Normal file
|
@ -0,0 +1,884 @@
|
|||
use std::fmt::Formatter;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::{Arch, BinaryFormat};
|
||||
|
||||
/// A tag to represent the platform compatibility of a Python distribution.
|
||||
///
|
||||
/// This is the third segment in the wheel filename, following the language and ABI tags. For
|
||||
/// example, in `cp39-none-manylinux_2_24_x86_64.whl`, the platform tag is `manylinux_2_24_x86_64`.
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Hash,
|
||||
rkyv::Archive,
|
||||
rkyv::Deserialize,
|
||||
rkyv::Serialize,
|
||||
)]
|
||||
#[rkyv(derive(Debug))]
|
||||
pub enum PlatformTag {
|
||||
/// Ex) `any`
|
||||
Any,
|
||||
/// Ex) `manylinux_2_24_x86_64`
|
||||
Manylinux { major: u16, minor: u16, arch: Arch },
|
||||
/// Ex) `manylinux1_x86_64`
|
||||
Manylinux1 { arch: Arch },
|
||||
/// Ex) `manylinux2010_x86_64`
|
||||
Manylinux2010 { arch: Arch },
|
||||
/// Ex) `manylinux2014_x86_64`
|
||||
Manylinux2014 { arch: Arch },
|
||||
/// Ex) `linux_x86_64`
|
||||
Linux { arch: Arch },
|
||||
/// Ex) `musllinux_1_2_x86_64`
|
||||
Musllinux { major: u16, minor: u16, arch: Arch },
|
||||
/// Ex) `macosx_11_0_x86_64`
|
||||
Macos {
|
||||
major: u16,
|
||||
minor: u16,
|
||||
binary_format: BinaryFormat,
|
||||
},
|
||||
/// Ex) `win32`
|
||||
Win32,
|
||||
/// Ex) `win_amd64`
|
||||
WinAmd64,
|
||||
/// Ex) `win_arm64`
|
||||
WinArm64,
|
||||
/// Ex) `android_21_x86_64`
|
||||
Android { api_level: u16, arch: Arch },
|
||||
/// Ex) `freebsd_12_x86_64`
|
||||
FreeBsd { release: String, arch: Arch },
|
||||
/// Ex) `netbsd_9_x86_64`
|
||||
NetBsd { release: String, arch: Arch },
|
||||
/// Ex) `openbsd_6_x86_64`
|
||||
OpenBsd { release: String, arch: Arch },
|
||||
/// Ex) `dragonfly_6_x86_64`
|
||||
Dragonfly { release: String, arch: Arch },
|
||||
/// Ex) `haiku_1_x86_64`
|
||||
Haiku { release: String, arch: Arch },
|
||||
/// Ex) `illumos_5_11_x86_64`
|
||||
Illumos { release: String, arch: String },
|
||||
/// Ex) `solaris_11_4_x86_64`
|
||||
Solaris { release: String, arch: String },
|
||||
}
|
||||
|
||||
impl PlatformTag {
|
||||
/// Returns `true` if the platform is manylinux-compatible.
|
||||
pub fn is_manylinux_compatible(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Manylinux { .. }
|
||||
| Self::Manylinux1 { .. }
|
||||
| Self::Manylinux2010 { .. }
|
||||
| Self::Manylinux2014 { .. }
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if the platform is Linux-compatible.
|
||||
pub fn is_linux_compatible(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Manylinux { .. }
|
||||
| Self::Manylinux1 { .. }
|
||||
| Self::Manylinux2010 { .. }
|
||||
| Self::Manylinux2014 { .. }
|
||||
| Self::Musllinux { .. }
|
||||
| Self::Linux { .. }
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if the platform is macOS-compatible.
|
||||
pub fn is_macos_compatible(&self) -> bool {
|
||||
matches!(self, Self::Macos { .. })
|
||||
}
|
||||
|
||||
/// Returns `true` if the platform is Windows-compatible.
|
||||
pub fn is_windows_compatible(&self) -> bool {
|
||||
matches!(self, Self::Win32 | Self::WinAmd64 | Self::WinArm64)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PlatformTag {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Any => write!(f, "any"),
|
||||
Self::Manylinux { major, minor, arch } => {
|
||||
write!(f, "manylinux_{major}_{minor}_{arch}")
|
||||
}
|
||||
Self::Manylinux1 { arch } => write!(f, "manylinux1_{arch}"),
|
||||
Self::Manylinux2010 { arch } => write!(f, "manylinux2010_{arch}"),
|
||||
Self::Manylinux2014 { arch } => write!(f, "manylinux2014_{arch}"),
|
||||
Self::Linux { arch } => write!(f, "linux_{arch}"),
|
||||
Self::Musllinux { major, minor, arch } => {
|
||||
write!(f, "musllinux_{major}_{minor}_{arch}")
|
||||
}
|
||||
Self::Macos {
|
||||
major,
|
||||
minor,
|
||||
binary_format: format,
|
||||
} => write!(f, "macosx_{major}_{minor}_{format}"),
|
||||
Self::Win32 => write!(f, "win32"),
|
||||
Self::WinAmd64 => write!(f, "win_amd64"),
|
||||
Self::WinArm64 => write!(f, "win_arm64"),
|
||||
Self::Android { api_level, arch } => write!(f, "android_{api_level}_{arch}"),
|
||||
Self::FreeBsd { release, arch } => write!(f, "freebsd_{release}_{arch}"),
|
||||
Self::NetBsd { release, arch } => write!(f, "netbsd_{release}_{arch}"),
|
||||
Self::OpenBsd { release, arch } => write!(f, "openbsd_{release}_{arch}"),
|
||||
Self::Dragonfly { release, arch } => write!(f, "dragonfly_{release}_{arch}"),
|
||||
Self::Haiku { release, arch } => write!(f, "haiku_{release}_{arch}"),
|
||||
Self::Illumos { release, arch } => write!(f, "illumos_{release}_{arch}"),
|
||||
Self::Solaris { release, arch } => write!(f, "solaris_{release}_{arch}_64bit"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PlatformTag {
|
||||
type Err = ParsePlatformTagError;
|
||||
|
||||
/// Parse a [`PlatformTag`] from a string.
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// Match against any static variants.
|
||||
match s {
|
||||
"any" => return Ok(Self::Any),
|
||||
"win32" => return Ok(Self::Win32),
|
||||
"win_amd64" => return Ok(Self::WinAmd64),
|
||||
"win_arm64" => return Ok(Self::WinArm64),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(rest) = s.strip_prefix("manylinux_") {
|
||||
// Ex) manylinux_2_17_x86_64
|
||||
let first_underscore = memchr::memchr(b'_', rest.as_bytes()).ok_or_else(|| {
|
||||
ParsePlatformTagError::InvalidFormat {
|
||||
platform: "manylinux",
|
||||
tag: s.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let second_underscore = memchr::memchr(b'_', &rest.as_bytes()[first_underscore + 1..])
|
||||
.map(|i| i + first_underscore + 1)
|
||||
.ok_or_else(|| ParsePlatformTagError::InvalidFormat {
|
||||
platform: "manylinux",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
|
||||
let major = rest[..first_underscore].parse().map_err(|_| {
|
||||
ParsePlatformTagError::InvalidMajorVersion {
|
||||
platform: "manylinux",
|
||||
tag: s.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let minor = rest[first_underscore + 1..second_underscore]
|
||||
.parse()
|
||||
.map_err(|_| ParsePlatformTagError::InvalidMinorVersion {
|
||||
platform: "manylinux",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
|
||||
let arch_str = &rest[second_underscore + 1..];
|
||||
if arch_str.is_empty() {
|
||||
return Err(ParsePlatformTagError::InvalidFormat {
|
||||
platform: "manylinux",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let arch = arch_str
|
||||
.parse()
|
||||
.map_err(|_| ParsePlatformTagError::InvalidArch {
|
||||
platform: "manylinux",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
|
||||
return Ok(Self::Manylinux { major, minor, arch });
|
||||
}
|
||||
|
||||
if let Some(rest) = s.strip_prefix("manylinux1_") {
|
||||
// Ex) manylinux1_x86_64
|
||||
let arch = rest
|
||||
.parse()
|
||||
.map_err(|_| ParsePlatformTagError::InvalidArch {
|
||||
platform: "manylinux1",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
return Ok(Self::Manylinux1 { arch });
|
||||
}
|
||||
|
||||
if let Some(rest) = s.strip_prefix("manylinux2010_") {
|
||||
// Ex) manylinux2010_x86_64
|
||||
let arch = rest
|
||||
.parse()
|
||||
.map_err(|_| ParsePlatformTagError::InvalidArch {
|
||||
platform: "manylinux2010",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
return Ok(Self::Manylinux2010 { arch });
|
||||
}
|
||||
|
||||
if let Some(rest) = s.strip_prefix("manylinux2014_") {
|
||||
// Ex) manylinux2014_x86_64
|
||||
let arch = rest
|
||||
.parse()
|
||||
.map_err(|_| ParsePlatformTagError::InvalidArch {
|
||||
platform: "manylinux2014",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
return Ok(Self::Manylinux2014 { arch });
|
||||
}
|
||||
|
||||
if let Some(rest) = s.strip_prefix("linux_") {
|
||||
// Ex) linux_x86_64
|
||||
let arch = rest
|
||||
.parse()
|
||||
.map_err(|_| ParsePlatformTagError::InvalidArch {
|
||||
platform: "linux",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
return Ok(Self::Linux { arch });
|
||||
}
|
||||
|
||||
if let Some(rest) = s.strip_prefix("musllinux_") {
|
||||
// Ex) musllinux_1_1_x86_64
|
||||
let first_underscore = memchr::memchr(b'_', rest.as_bytes()).ok_or_else(|| {
|
||||
ParsePlatformTagError::InvalidFormat {
|
||||
platform: "musllinux",
|
||||
tag: s.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let second_underscore = memchr::memchr(b'_', &rest.as_bytes()[first_underscore + 1..])
|
||||
.map(|i| i + first_underscore + 1)
|
||||
.ok_or_else(|| ParsePlatformTagError::InvalidFormat {
|
||||
platform: "musllinux",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
|
||||
let major = rest[..first_underscore].parse().map_err(|_| {
|
||||
ParsePlatformTagError::InvalidMajorVersion {
|
||||
platform: "musllinux",
|
||||
tag: s.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let minor = rest[first_underscore + 1..second_underscore]
|
||||
.parse()
|
||||
.map_err(|_| ParsePlatformTagError::InvalidMinorVersion {
|
||||
platform: "musllinux",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
|
||||
let arch_str = &rest[second_underscore + 1..];
|
||||
if arch_str.is_empty() {
|
||||
return Err(ParsePlatformTagError::InvalidFormat {
|
||||
platform: "musllinux",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let arch = arch_str
|
||||
.parse()
|
||||
.map_err(|_| ParsePlatformTagError::InvalidArch {
|
||||
platform: "musllinux",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
|
||||
return Ok(Self::Musllinux { major, minor, arch });
|
||||
}
|
||||
|
||||
if let Some(rest) = s.strip_prefix("macosx_") {
|
||||
// Ex) macosx_11_0_arm64
|
||||
let first_underscore = memchr::memchr(b'_', rest.as_bytes()).ok_or_else(|| {
|
||||
ParsePlatformTagError::InvalidFormat {
|
||||
platform: "macosx",
|
||||
tag: s.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let second_underscore = memchr::memchr(b'_', &rest.as_bytes()[first_underscore + 1..])
|
||||
.map(|i| i + first_underscore + 1)
|
||||
.ok_or_else(|| ParsePlatformTagError::InvalidFormat {
|
||||
platform: "macosx",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
|
||||
let major = rest[..first_underscore].parse().map_err(|_| {
|
||||
ParsePlatformTagError::InvalidMajorVersion {
|
||||
platform: "macosx",
|
||||
tag: s.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let minor = rest[first_underscore + 1..second_underscore]
|
||||
.parse()
|
||||
.map_err(|_| ParsePlatformTagError::InvalidMinorVersion {
|
||||
platform: "macosx",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
|
||||
let binary_format_str = &rest[second_underscore + 1..];
|
||||
if binary_format_str.is_empty() {
|
||||
return Err(ParsePlatformTagError::InvalidFormat {
|
||||
platform: "macosx",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let binary_format =
|
||||
binary_format_str
|
||||
.parse()
|
||||
.map_err(|_| ParsePlatformTagError::InvalidArch {
|
||||
platform: "macosx",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
|
||||
return Ok(Self::Macos {
|
||||
major,
|
||||
minor,
|
||||
binary_format,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(rest) = s.strip_prefix("android_") {
|
||||
// Ex) android_21_arm64
|
||||
let underscore = memchr::memchr(b'_', rest.as_bytes()).ok_or_else(|| {
|
||||
ParsePlatformTagError::InvalidFormat {
|
||||
platform: "android",
|
||||
tag: s.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let api_level =
|
||||
rest[..underscore]
|
||||
.parse()
|
||||
.map_err(|_| ParsePlatformTagError::InvalidApiLevel {
|
||||
platform: "android",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
|
||||
let arch_str = &rest[underscore + 1..];
|
||||
if arch_str.is_empty() {
|
||||
return Err(ParsePlatformTagError::InvalidFormat {
|
||||
platform: "android",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let arch = arch_str
|
||||
.parse()
|
||||
.map_err(|_| ParsePlatformTagError::InvalidArch {
|
||||
platform: "android",
|
||||
tag: s.to_string(),
|
||||
})?;
|
||||
|
||||
return Ok(Self::Android { api_level, arch });
|
||||
}
|
||||
|
||||
if let Some(rest) = s.strip_prefix("freebsd_") {
|
||||
// Ex) freebsd_13_x86_64 or freebsd_13_14_x86_64
|
||||
if rest.is_empty() {
|
||||
return Err(ParsePlatformTagError::InvalidFormat {
|
||||
platform: "freebsd",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Try each known Arch value as a potential suffix
|
||||
for arch in Arch::iter() {
|
||||
if let Some(release) = rest.strip_suffix(arch.name()) {
|
||||
// Remove trailing underscore from release
|
||||
let release = release.strip_suffix('_').unwrap_or(release).to_string();
|
||||
if !release.is_empty() {
|
||||
return Ok(Self::FreeBsd { release, arch });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "freebsd",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(rest) = s.strip_prefix("netbsd_") {
|
||||
// Ex) netbsd_9_x86_64
|
||||
if rest.is_empty() {
|
||||
return Err(ParsePlatformTagError::InvalidFormat {
|
||||
platform: "netbsd",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Try each known Arch value as a potential suffix
|
||||
for arch in Arch::iter() {
|
||||
if let Some(release) = rest.strip_suffix(arch.name()) {
|
||||
// Remove trailing underscore from release
|
||||
let release = release.strip_suffix('_').unwrap_or(release).to_string();
|
||||
if !release.is_empty() {
|
||||
return Ok(Self::NetBsd { release, arch });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "netbsd",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(rest) = s.strip_prefix("openbsd_") {
|
||||
// Ex) openbsd_7_x86_64
|
||||
if rest.is_empty() {
|
||||
return Err(ParsePlatformTagError::InvalidFormat {
|
||||
platform: "openbsd",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Try each known Arch value as a potential suffix
|
||||
for arch in Arch::iter() {
|
||||
if let Some(release) = rest.strip_suffix(arch.name()) {
|
||||
// Remove trailing underscore from release
|
||||
let release = release.strip_suffix('_').unwrap_or(release).to_string();
|
||||
if !release.is_empty() {
|
||||
return Ok(Self::OpenBsd { release, arch });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "openbsd",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(rest) = s.strip_prefix("dragonfly_") {
|
||||
// Ex) dragonfly_6_x86_64
|
||||
if rest.is_empty() {
|
||||
return Err(ParsePlatformTagError::InvalidFormat {
|
||||
platform: "dragonfly",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Try each known Arch value as a potential suffix
|
||||
for arch in Arch::iter() {
|
||||
if let Some(release) = rest.strip_suffix(arch.name()) {
|
||||
// Remove trailing underscore from release
|
||||
let release = release.strip_suffix('_').unwrap_or(release).to_string();
|
||||
if !release.is_empty() {
|
||||
return Ok(Self::Dragonfly { release, arch });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "dragonfly",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(rest) = s.strip_prefix("haiku_") {
|
||||
// Ex) haiku_1_x86_64
|
||||
if rest.is_empty() {
|
||||
return Err(ParsePlatformTagError::InvalidFormat {
|
||||
platform: "haiku",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Try each known Arch value as a potential suffix
|
||||
for arch in Arch::iter() {
|
||||
if let Some(release) = rest.strip_suffix(arch.name()) {
|
||||
// Remove trailing underscore from release
|
||||
let release = release.strip_suffix('_').unwrap_or(release).to_string();
|
||||
if !release.is_empty() {
|
||||
return Ok(Self::Haiku { release, arch });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "haiku",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(rest) = s.strip_prefix("illumos_") {
|
||||
// Ex) illumos_5_11_x86_64
|
||||
if rest.is_empty() {
|
||||
return Err(ParsePlatformTagError::InvalidFormat {
|
||||
platform: "illumos",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Try each known Arch value as a potential suffix
|
||||
for arch in Arch::iter() {
|
||||
if let Some(release) = rest.strip_suffix(arch.name()) {
|
||||
// Remove trailing underscore from release
|
||||
let release = release.strip_suffix('_').unwrap_or(release).to_string();
|
||||
if !release.is_empty() {
|
||||
return Ok(Self::Illumos {
|
||||
release,
|
||||
arch: arch.name().to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "illumos",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(rest) = s.strip_prefix("solaris_") {
|
||||
// Ex) solaris_11_4_x86_64_64bit
|
||||
if rest.is_empty() {
|
||||
return Err(ParsePlatformTagError::InvalidFormat {
|
||||
platform: "solaris",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Try each known Arch value as a potential suffix
|
||||
for arch in Arch::iter() {
|
||||
if let Some(rest) = rest.strip_suffix("_64bit") {
|
||||
if let Some(rest) = rest.strip_suffix(&format!("_{}", arch.name())) {
|
||||
// Remove trailing underscore from release
|
||||
let release = rest.strip_suffix('_').unwrap_or(rest).to_string();
|
||||
if !release.is_empty() {
|
||||
return Ok(Self::Solaris {
|
||||
release,
|
||||
arch: arch.name().to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "solaris",
|
||||
tag: s.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Err(ParsePlatformTagError::UnknownFormat(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
||||
pub enum ParsePlatformTagError {
|
||||
#[error("Unknown platform tag format: {0}")]
|
||||
UnknownFormat(String),
|
||||
#[error("Invalid format for {platform} platform tag: {tag}")]
|
||||
InvalidFormat { platform: &'static str, tag: String },
|
||||
#[error("Invalid major version in {platform} platform tag: {tag}")]
|
||||
InvalidMajorVersion { platform: &'static str, tag: String },
|
||||
#[error("Invalid minor version in {platform} platform tag: {tag}")]
|
||||
InvalidMinorVersion { platform: &'static str, tag: String },
|
||||
#[error("Invalid architecture in {platform} platform tag: {tag}")]
|
||||
InvalidArch { platform: &'static str, tag: String },
|
||||
#[error("Invalid API level in {platform} platform tag: {tag}")]
|
||||
InvalidApiLevel { platform: &'static str, tag: String },
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::platform_tag::{ParsePlatformTagError, PlatformTag};
|
||||
use crate::{Arch, BinaryFormat};
|
||||
|
||||
#[test]
|
||||
fn any_platform() {
|
||||
assert_eq!(PlatformTag::from_str("any"), Ok(PlatformTag::Any));
|
||||
assert_eq!(PlatformTag::Any.to_string(), "any");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manylinux_platform() {
|
||||
let tag = PlatformTag::Manylinux {
|
||||
major: 2,
|
||||
minor: 24,
|
||||
arch: Arch::X86_64,
|
||||
};
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("manylinux_2_24_x86_64").as_ref(),
|
||||
Ok(&tag)
|
||||
);
|
||||
assert_eq!(tag.to_string(), "manylinux_2_24_x86_64");
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("manylinux_x_24_x86_64"),
|
||||
Err(ParsePlatformTagError::InvalidMajorVersion {
|
||||
platform: "manylinux",
|
||||
tag: "manylinux_x_24_x86_64".to_string()
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("manylinux_2_x_x86_64"),
|
||||
Err(ParsePlatformTagError::InvalidMinorVersion {
|
||||
platform: "manylinux",
|
||||
tag: "manylinux_2_x_x86_64".to_string()
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("manylinux_2_24_invalid"),
|
||||
Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "manylinux",
|
||||
tag: "manylinux_2_24_invalid".to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manylinux1_platform() {
|
||||
let tag = PlatformTag::Manylinux1 { arch: Arch::X86_64 };
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("manylinux1_x86_64").as_ref(),
|
||||
Ok(&tag)
|
||||
);
|
||||
assert_eq!(tag.to_string(), "manylinux1_x86_64");
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("manylinux1_invalid"),
|
||||
Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "manylinux1",
|
||||
tag: "manylinux1_invalid".to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manylinux2010_platform() {
|
||||
let tag = PlatformTag::Manylinux2010 { arch: Arch::X86_64 };
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("manylinux2010_x86_64").as_ref(),
|
||||
Ok(&tag)
|
||||
);
|
||||
assert_eq!(tag.to_string(), "manylinux2010_x86_64");
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("manylinux2010_invalid"),
|
||||
Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "manylinux2010",
|
||||
tag: "manylinux2010_invalid".to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manylinux2014_platform() {
|
||||
let tag = PlatformTag::Manylinux2014 { arch: Arch::X86_64 };
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("manylinux2014_x86_64").as_ref(),
|
||||
Ok(&tag)
|
||||
);
|
||||
assert_eq!(tag.to_string(), "manylinux2014_x86_64");
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("manylinux2014_invalid"),
|
||||
Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "manylinux2014",
|
||||
tag: "manylinux2014_invalid".to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linux_platform() {
|
||||
let tag = PlatformTag::Linux { arch: Arch::X86_64 };
|
||||
assert_eq!(PlatformTag::from_str("linux_x86_64").as_ref(), Ok(&tag));
|
||||
assert_eq!(tag.to_string(), "linux_x86_64");
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("linux_invalid"),
|
||||
Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "linux",
|
||||
tag: "linux_invalid".to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn musllinux_platform() {
|
||||
let tag = PlatformTag::Musllinux {
|
||||
major: 1,
|
||||
minor: 2,
|
||||
arch: Arch::X86_64,
|
||||
};
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("musllinux_1_2_x86_64").as_ref(),
|
||||
Ok(&tag)
|
||||
);
|
||||
assert_eq!(tag.to_string(), "musllinux_1_2_x86_64");
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("musllinux_x_2_x86_64"),
|
||||
Err(ParsePlatformTagError::InvalidMajorVersion {
|
||||
platform: "musllinux",
|
||||
tag: "musllinux_x_2_x86_64".to_string()
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("musllinux_1_x_x86_64"),
|
||||
Err(ParsePlatformTagError::InvalidMinorVersion {
|
||||
platform: "musllinux",
|
||||
tag: "musllinux_1_x_x86_64".to_string()
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("musllinux_1_2_invalid"),
|
||||
Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "musllinux",
|
||||
tag: "musllinux_1_2_invalid".to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_platform() {
|
||||
let tag = PlatformTag::Macos {
|
||||
major: 11,
|
||||
minor: 0,
|
||||
binary_format: BinaryFormat::Universal2,
|
||||
};
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("macosx_11_0_universal2").as_ref(),
|
||||
Ok(&tag)
|
||||
);
|
||||
assert_eq!(tag.to_string(), "macosx_11_0_universal2");
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("macosx_x_0_universal2"),
|
||||
Err(ParsePlatformTagError::InvalidMajorVersion {
|
||||
platform: "macosx",
|
||||
tag: "macosx_x_0_universal2".to_string()
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("macosx_11_x_universal2"),
|
||||
Err(ParsePlatformTagError::InvalidMinorVersion {
|
||||
platform: "macosx",
|
||||
tag: "macosx_11_x_universal2".to_string()
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("macosx_11_0_invalid"),
|
||||
Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "macosx",
|
||||
tag: "macosx_11_0_invalid".to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn win32_platform() {
|
||||
assert_eq!(PlatformTag::from_str("win32"), Ok(PlatformTag::Win32));
|
||||
assert_eq!(PlatformTag::Win32.to_string(), "win32");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn win_amd64_platform() {
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("win_amd64"),
|
||||
Ok(PlatformTag::WinAmd64)
|
||||
);
|
||||
assert_eq!(PlatformTag::WinAmd64.to_string(), "win_amd64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn win_arm64_platform() {
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("win_arm64"),
|
||||
Ok(PlatformTag::WinArm64)
|
||||
);
|
||||
assert_eq!(PlatformTag::WinArm64.to_string(), "win_arm64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn freebsd_platform() {
|
||||
let tag = PlatformTag::FreeBsd {
|
||||
release: "13_14".to_string(),
|
||||
arch: Arch::X86_64,
|
||||
};
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("freebsd_13_14_x86_64").as_ref(),
|
||||
Ok(&tag)
|
||||
);
|
||||
assert_eq!(tag.to_string(), "freebsd_13_14_x86_64");
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("freebsd_13_14"),
|
||||
Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "freebsd",
|
||||
tag: "freebsd_13_14".to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn illumos_platform() {
|
||||
let tag = PlatformTag::Illumos {
|
||||
release: "5_11".to_string(),
|
||||
arch: "x86_64".to_string(),
|
||||
};
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("illumos_5_11_x86_64").as_ref(),
|
||||
Ok(&tag)
|
||||
);
|
||||
assert_eq!(tag.to_string(), "illumos_5_11_x86_64");
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("illumos_5_11"),
|
||||
Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "illumos",
|
||||
tag: "illumos_5_11".to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solaris_platform() {
|
||||
let tag = PlatformTag::Solaris {
|
||||
release: "11_4".to_string(),
|
||||
arch: "x86_64".to_string(),
|
||||
};
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("solaris_11_4_x86_64_64bit").as_ref(),
|
||||
Ok(&tag)
|
||||
);
|
||||
assert_eq!(tag.to_string(), "solaris_11_4_x86_64_64bit");
|
||||
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("solaris_11_4_x86_64"),
|
||||
Err(ParsePlatformTagError::InvalidArch {
|
||||
platform: "solaris",
|
||||
tag: "solaris_11_4_x86_64".to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_platform() {
|
||||
assert_eq!(
|
||||
PlatformTag::from_str("unknown"),
|
||||
Err(ParsePlatformTagError::UnknownFormat("unknown".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
PlatformTag::from_str(""),
|
||||
Err(ParsePlatformTagError::UnknownFormat(String::new()))
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
use std::collections::BTreeSet;
|
||||
use std::fmt::Formatter;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::{cmp, num::NonZeroU32};
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{AbiTag, Arch, LanguageTag, Os, Platform, PlatformError};
|
||||
use crate::{AbiTag, Arch, LanguageTag, Os, Platform, PlatformError, PlatformTag};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum TagsError {
|
||||
|
@ -73,9 +74,9 @@ impl TagCompatibility {
|
|||
pub struct Tags {
|
||||
/// `python_tag` |--> `abi_tag` |--> `platform_tag` |--> priority
|
||||
#[allow(clippy::type_complexity)]
|
||||
map: Arc<FxHashMap<String, FxHashMap<String, FxHashMap<String, TagPriority>>>>,
|
||||
map: Arc<FxHashMap<LanguageTag, FxHashMap<AbiTag, FxHashMap<PlatformTag, TagPriority>>>>,
|
||||
/// The highest-priority tag for the Python version and platform.
|
||||
best: Option<(String, String, String)>,
|
||||
best: Option<(LanguageTag, AbiTag, PlatformTag)>,
|
||||
}
|
||||
|
||||
impl Tags {
|
||||
|
@ -83,7 +84,7 @@ impl Tags {
|
|||
///
|
||||
/// Tags are prioritized based on their position in the given vector. Specifically, tags that
|
||||
/// appear earlier in the vector are given higher priority than tags that appear later.
|
||||
pub fn new(tags: Vec<(String, String, String)>) -> Self {
|
||||
pub fn new(tags: Vec<(LanguageTag, AbiTag, PlatformTag)>) -> Self {
|
||||
// Store the highest-priority tag for each component.
|
||||
let best = tags.first().cloned();
|
||||
|
||||
|
@ -120,7 +121,7 @@ impl Tags {
|
|||
let platform_tags = {
|
||||
let mut platform_tags = compatible_tags(platform)?;
|
||||
if matches!(platform.os(), Os::Manylinux { .. }) && !manylinux_compatible {
|
||||
platform_tags.retain(|tag| !tag.starts_with("manylinux"));
|
||||
platform_tags.retain(|tag| !tag.is_manylinux_compatible());
|
||||
}
|
||||
platform_tags
|
||||
};
|
||||
|
@ -130,10 +131,8 @@ impl Tags {
|
|||
// 1. This exact c api version
|
||||
for platform_tag in &platform_tags {
|
||||
tags.push((
|
||||
implementation.language_tag(python_version).to_string(),
|
||||
implementation
|
||||
.abi_tag(python_version, implementation_version)
|
||||
.to_string(),
|
||||
implementation.language_tag(python_version),
|
||||
implementation.abi_tag(python_version, implementation_version),
|
||||
platform_tag.clone(),
|
||||
));
|
||||
}
|
||||
|
@ -145,10 +144,8 @@ impl Tags {
|
|||
if !gil_disabled {
|
||||
for platform_tag in &platform_tags {
|
||||
tags.push((
|
||||
implementation
|
||||
.language_tag((python_version.0, minor))
|
||||
.to_string(),
|
||||
AbiTag::Abi3.to_string(),
|
||||
implementation.language_tag((python_version.0, minor)),
|
||||
AbiTag::Abi3,
|
||||
platform_tag.clone(),
|
||||
));
|
||||
}
|
||||
|
@ -157,10 +154,8 @@ impl Tags {
|
|||
if minor == python_version.1 {
|
||||
for platform_tag in &platform_tags {
|
||||
tags.push((
|
||||
implementation
|
||||
.language_tag((python_version.0, minor))
|
||||
.to_string(),
|
||||
AbiTag::None.to_string(),
|
||||
implementation.language_tag((python_version.0, minor)),
|
||||
AbiTag::None,
|
||||
platform_tag.clone(),
|
||||
));
|
||||
}
|
||||
|
@ -174,9 +169,8 @@ impl Tags {
|
|||
LanguageTag::Python {
|
||||
major: python_version.0,
|
||||
minor: Some(minor),
|
||||
}
|
||||
.to_string(),
|
||||
AbiTag::None.to_string(),
|
||||
},
|
||||
AbiTag::None,
|
||||
platform_tag.clone(),
|
||||
));
|
||||
}
|
||||
|
@ -184,8 +178,11 @@ impl Tags {
|
|||
if minor == python_version.1 {
|
||||
for platform_tag in &platform_tags {
|
||||
tags.push((
|
||||
format!("py{}", python_version.0),
|
||||
AbiTag::None.to_string(),
|
||||
LanguageTag::Python {
|
||||
major: python_version.0,
|
||||
minor: None,
|
||||
},
|
||||
AbiTag::None,
|
||||
platform_tag.clone(),
|
||||
));
|
||||
}
|
||||
|
@ -194,9 +191,9 @@ impl Tags {
|
|||
// 4. no binary
|
||||
if matches!(implementation, Implementation::CPython { .. }) {
|
||||
tags.push((
|
||||
implementation.language_tag(python_version).to_string(),
|
||||
AbiTag::None.to_string(),
|
||||
"any".to_string(),
|
||||
implementation.language_tag(python_version),
|
||||
AbiTag::None,
|
||||
PlatformTag::Any,
|
||||
));
|
||||
}
|
||||
for minor in (0..=python_version.1).rev() {
|
||||
|
@ -204,10 +201,9 @@ impl Tags {
|
|||
LanguageTag::Python {
|
||||
major: python_version.0,
|
||||
minor: Some(minor),
|
||||
}
|
||||
.to_string(),
|
||||
AbiTag::None.to_string(),
|
||||
"any".to_string(),
|
||||
},
|
||||
AbiTag::None,
|
||||
PlatformTag::Any,
|
||||
));
|
||||
// After the matching version emit `none` tags for the major version i.e. `py3`
|
||||
if minor == python_version.1 {
|
||||
|
@ -215,10 +211,9 @@ impl Tags {
|
|||
LanguageTag::Python {
|
||||
major: python_version.0,
|
||||
minor: None,
|
||||
}
|
||||
.to_string(),
|
||||
AbiTag::None.to_string(),
|
||||
"any".to_string(),
|
||||
},
|
||||
AbiTag::None,
|
||||
PlatformTag::Any,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -232,9 +227,9 @@ impl Tags {
|
|||
/// tag is found.
|
||||
pub fn is_compatible(
|
||||
&self,
|
||||
wheel_python_tags: &[String],
|
||||
wheel_abi_tags: &[String],
|
||||
wheel_platform_tags: &[String],
|
||||
wheel_python_tags: &[LanguageTag],
|
||||
wheel_abi_tags: &[AbiTag],
|
||||
wheel_platform_tags: &[PlatformTag],
|
||||
) -> bool {
|
||||
// NOTE: A typical work-load is a context in which the platform tags
|
||||
// are quite large, but the tags of a wheel are quite small. It is
|
||||
|
@ -267,9 +262,9 @@ impl Tags {
|
|||
/// If incompatible, includes the tag part which was a closest match.
|
||||
pub fn compatibility(
|
||||
&self,
|
||||
wheel_python_tags: &[String],
|
||||
wheel_abi_tags: &[String],
|
||||
wheel_platform_tags: &[String],
|
||||
wheel_python_tags: &[LanguageTag],
|
||||
wheel_abi_tags: &[AbiTag],
|
||||
wheel_platform_tags: &[PlatformTag],
|
||||
) -> TagCompatibility {
|
||||
let mut max_compatibility = TagCompatibility::Incompatible(IncompatibleTag::Invalid);
|
||||
|
||||
|
@ -301,26 +296,26 @@ impl Tags {
|
|||
}
|
||||
|
||||
/// Return the highest-priority Python tag for the [`Tags`].
|
||||
pub fn python_tag(&self) -> Option<&str> {
|
||||
self.best.as_ref().map(|(py, _, _)| py.as_str())
|
||||
pub fn python_tag(&self) -> Option<LanguageTag> {
|
||||
self.best.as_ref().map(|(python, _, _)| *python)
|
||||
}
|
||||
|
||||
/// Return the highest-priority ABI tag for the [`Tags`].
|
||||
pub fn abi_tag(&self) -> Option<&str> {
|
||||
self.best.as_ref().map(|(_, abi, _)| abi.as_str())
|
||||
pub fn abi_tag(&self) -> Option<AbiTag> {
|
||||
self.best.as_ref().map(|(_, abi, _)| *abi)
|
||||
}
|
||||
|
||||
/// Return the highest-priority platform tag for the [`Tags`].
|
||||
pub fn platform_tag(&self) -> Option<&str> {
|
||||
self.best.as_ref().map(|(_, _, platform)| platform.as_str())
|
||||
pub fn platform_tag(&self) -> Option<&PlatformTag> {
|
||||
self.best.as_ref().map(|(_, _, platform)| platform)
|
||||
}
|
||||
|
||||
/// Returns `true` if the given language and ABI tags are compatible with the current
|
||||
/// environment.
|
||||
pub fn is_compatible_abi<'a>(&'a self, python_tag: &'a str, abi_tag: &'a str) -> bool {
|
||||
pub fn is_compatible_abi(&self, python_tag: LanguageTag, abi_tag: AbiTag) -> bool {
|
||||
self.map
|
||||
.get(python_tag)
|
||||
.map(|abis| abis.contains_key(abi_tag))
|
||||
.get(&python_tag)
|
||||
.map(|abis| abis.contains_key(&abi_tag))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
@ -436,7 +431,7 @@ impl Implementation {
|
|||
///
|
||||
/// We have two cases: Actual platform specific tags (including "merged" tags such as universal2)
|
||||
/// and "any".
|
||||
fn compatible_tags(platform: &Platform) -> Result<Vec<String>, PlatformError> {
|
||||
fn compatible_tags(platform: &Platform) -> Result<Vec<PlatformTag>, PlatformError> {
|
||||
let os = platform.os();
|
||||
let arch = platform.arch();
|
||||
|
||||
|
@ -445,30 +440,37 @@ fn compatible_tags(platform: &Platform) -> Result<Vec<String>, PlatformError> {
|
|||
let mut platform_tags = Vec::new();
|
||||
if let Some(min_minor) = arch.get_minimum_manylinux_minor() {
|
||||
for minor in (min_minor..=*minor).rev() {
|
||||
platform_tags.push(format!("manylinux_{major}_{minor}_{arch}"));
|
||||
platform_tags.push(PlatformTag::Manylinux {
|
||||
major: *major,
|
||||
minor,
|
||||
arch,
|
||||
});
|
||||
// Support legacy manylinux tags with lower priority
|
||||
// <https://peps.python.org/pep-0600/#legacy-manylinux-tags>
|
||||
if minor == 12 {
|
||||
platform_tags.push(format!("manylinux2010_{arch}"));
|
||||
platform_tags.push(PlatformTag::Manylinux2010 { arch });
|
||||
}
|
||||
if minor == 17 {
|
||||
platform_tags.push(format!("manylinux2014_{arch}"));
|
||||
platform_tags.push(PlatformTag::Manylinux2014 { arch });
|
||||
}
|
||||
if minor == 5 {
|
||||
platform_tags.push(format!("manylinux1_{arch}"));
|
||||
platform_tags.push(PlatformTag::Manylinux1 { arch });
|
||||
}
|
||||
}
|
||||
}
|
||||
// Non-manylinux is given lowest priority.
|
||||
// <https://github.com/pypa/packaging/blob/fd4f11139d1c884a637be8aa26bb60a31fbc9411/packaging/tags.py#L444>
|
||||
platform_tags.push(format!("linux_{arch}"));
|
||||
platform_tags.push(PlatformTag::Linux { arch });
|
||||
platform_tags
|
||||
}
|
||||
(Os::Musllinux { major, minor }, _) => {
|
||||
let mut platform_tags = vec![format!("linux_{arch}")];
|
||||
let mut platform_tags = vec![PlatformTag::Linux { arch }];
|
||||
// musl 1.1 is the lowest supported version in musllinux
|
||||
platform_tags
|
||||
.extend((1..=*minor).map(|minor| format!("musllinux_{major}_{minor}_{arch}")));
|
||||
platform_tags.extend((1..=*minor).map(|minor| PlatformTag::Musllinux {
|
||||
major: *major,
|
||||
minor,
|
||||
arch,
|
||||
}));
|
||||
platform_tags
|
||||
}
|
||||
(Os::Macos { major, minor }, Arch::X86_64) => {
|
||||
|
@ -479,8 +481,12 @@ fn compatible_tags(platform: &Platform) -> Result<Vec<String>, PlatformError> {
|
|||
// Prior to Mac OS 11, each yearly release of Mac OS bumped the "minor" version
|
||||
// number. The major version was always 10.
|
||||
for minor in (4..=*minor).rev() {
|
||||
for binary_format in get_mac_binary_formats(arch) {
|
||||
platform_tags.push(format!("macosx_{major}_{minor}_{binary_format}"));
|
||||
for binary_format in BinaryFormat::from_arch(arch) {
|
||||
platform_tags.push(PlatformTag::Macos {
|
||||
major: 10,
|
||||
minor,
|
||||
binary_format: *binary_format,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -488,16 +494,23 @@ fn compatible_tags(platform: &Platform) -> Result<Vec<String>, PlatformError> {
|
|||
// Starting with Mac OS 11, each yearly release bumps the major version number.
|
||||
// The minor versions are now the midyear updates.
|
||||
for major in (11..=*major).rev() {
|
||||
for binary_format in get_mac_binary_formats(arch) {
|
||||
platform_tags.push(format!("macosx_{}_{}_{}", major, 0, binary_format));
|
||||
for binary_format in BinaryFormat::from_arch(arch) {
|
||||
platform_tags.push(PlatformTag::Macos {
|
||||
major,
|
||||
minor: 0,
|
||||
binary_format: *binary_format,
|
||||
});
|
||||
}
|
||||
}
|
||||
// The "universal2" binary format can have a macOS version earlier than 11.0
|
||||
// when the x86_64 part of the binary supports that version of macOS.
|
||||
for minor in (4..=16).rev() {
|
||||
for binary_format in get_mac_binary_formats(arch) {
|
||||
platform_tags
|
||||
.push(format!("macosx_{}_{}_{}", 10, minor, binary_format));
|
||||
for binary_format in BinaryFormat::from_arch(arch) {
|
||||
platform_tags.push(PlatformTag::Macos {
|
||||
major: 10,
|
||||
minor,
|
||||
binary_format: *binary_format,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -515,36 +528,49 @@ fn compatible_tags(platform: &Platform) -> Result<Vec<String>, PlatformError> {
|
|||
// Starting with Mac OS 11, each yearly release bumps the major version number.
|
||||
// The minor versions are now the midyear updates.
|
||||
for major in (11..=*major).rev() {
|
||||
for binary_format in get_mac_binary_formats(arch) {
|
||||
platform_tags.push(format!("macosx_{}_{}_{}", major, 0, binary_format));
|
||||
for binary_format in BinaryFormat::from_arch(arch) {
|
||||
platform_tags.push(PlatformTag::Macos {
|
||||
major,
|
||||
minor: 0,
|
||||
binary_format: *binary_format,
|
||||
});
|
||||
}
|
||||
}
|
||||
// The "universal2" binary format can have a macOS version earlier than 11.0
|
||||
// when the x86_64 part of the binary supports that version of macOS.
|
||||
platform_tags.extend(
|
||||
(4..=16)
|
||||
.rev()
|
||||
.map(|minor| format!("macosx_{}_{}_universal2", 10, minor)),
|
||||
);
|
||||
platform_tags.extend((4..=16).rev().map(|minor| PlatformTag::Macos {
|
||||
major: 10,
|
||||
minor,
|
||||
binary_format: BinaryFormat::Universal2,
|
||||
}));
|
||||
platform_tags
|
||||
}
|
||||
(Os::Windows, Arch::X86) => {
|
||||
vec!["win32".to_string()]
|
||||
vec![PlatformTag::Win32]
|
||||
}
|
||||
(Os::Windows, Arch::X86_64) => {
|
||||
vec!["win_amd64".to_string()]
|
||||
vec![PlatformTag::WinAmd64]
|
||||
}
|
||||
(Os::Windows, Arch::Aarch64) => vec!["win_arm64".to_string()],
|
||||
(
|
||||
Os::FreeBsd { release }
|
||||
| Os::NetBsd { release }
|
||||
| Os::OpenBsd { release }
|
||||
| Os::Dragonfly { release }
|
||||
| Os::Haiku { release },
|
||||
_,
|
||||
) => {
|
||||
(Os::Windows, Arch::Aarch64) => vec![PlatformTag::WinArm64],
|
||||
(Os::FreeBsd { release }, _) => {
|
||||
let release = release.replace(['.', '-'], "_");
|
||||
vec![format!("{os}_{release}_{arch}")]
|
||||
vec![PlatformTag::FreeBsd { release, arch }]
|
||||
}
|
||||
(Os::NetBsd { release }, _) => {
|
||||
let release = release.replace(['.', '-'], "_");
|
||||
vec![PlatformTag::NetBsd { release, arch }]
|
||||
}
|
||||
(Os::OpenBsd { release }, _) => {
|
||||
let release = release.replace(['.', '-'], "_");
|
||||
vec![PlatformTag::OpenBsd { release, arch }]
|
||||
}
|
||||
(Os::Dragonfly { release }, _) => {
|
||||
let release = release.replace(['.', '-'], "_");
|
||||
vec![PlatformTag::Dragonfly { release, arch }]
|
||||
}
|
||||
(Os::Haiku { release }, _) => {
|
||||
let release = release.replace(['.', '-'], "_");
|
||||
vec![PlatformTag::Haiku { release, arch }]
|
||||
}
|
||||
(Os::Illumos { release, arch }, _) => {
|
||||
// See https://github.com/python/cpython/blob/46c8d915715aa2bd4d697482aa051fe974d440e1/Lib/sysconfig.py#L722-L730
|
||||
|
@ -556,17 +582,22 @@ fn compatible_tags(platform: &Platform) -> Result<Vec<String>, PlatformError> {
|
|||
})?;
|
||||
if major_ver >= 5 {
|
||||
// SunOS 5 == Solaris 2
|
||||
let os = "solaris";
|
||||
let release = format!("{}_{}", major_ver - 3, other);
|
||||
let arch = format!("{arch}_64bit");
|
||||
return Ok(vec![format!("{os}_{release}_{arch}")]);
|
||||
return Ok(vec![PlatformTag::Solaris { release, arch }]);
|
||||
}
|
||||
}
|
||||
|
||||
vec![format!("{os}_{release}_{arch}")]
|
||||
vec![PlatformTag::Illumos {
|
||||
release: release.to_string(),
|
||||
arch: arch.to_string(),
|
||||
}]
|
||||
}
|
||||
(Os::Android { api_level }, _) => {
|
||||
vec![format!("{os}_{api_level}_{arch}")]
|
||||
vec![PlatformTag::Android {
|
||||
api_level: *api_level,
|
||||
arch,
|
||||
}]
|
||||
}
|
||||
_ => {
|
||||
return Err(PlatformError::OsVersionDetectionError(format!(
|
||||
|
@ -577,31 +608,131 @@ fn compatible_tags(platform: &Platform) -> Result<Vec<String>, PlatformError> {
|
|||
Ok(platform_tags)
|
||||
}
|
||||
|
||||
/// Determine the appropriate binary formats for a macOS version.
|
||||
/// Source: <https://github.com/pypa/packaging/blob/fd4f11139d1c884a637be8aa26bb60a31fbc9411/packaging/tags.py#L314>
|
||||
fn get_mac_binary_formats(arch: Arch) -> Vec<String> {
|
||||
let mut formats = vec![match arch {
|
||||
Arch::Aarch64 => "arm64".to_string(),
|
||||
_ => arch.to_string(),
|
||||
}];
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Hash,
|
||||
rkyv::Archive,
|
||||
rkyv::Deserialize,
|
||||
rkyv::Serialize,
|
||||
)]
|
||||
#[rkyv(derive(Debug))]
|
||||
pub enum BinaryFormat {
|
||||
Arm64,
|
||||
Fat,
|
||||
Fat32,
|
||||
Fat64,
|
||||
I386,
|
||||
Intel,
|
||||
Ppc,
|
||||
Ppc64,
|
||||
Universal,
|
||||
Universal2,
|
||||
X86_64,
|
||||
}
|
||||
|
||||
if matches!(arch, Arch::X86_64) {
|
||||
formats.extend([
|
||||
"intel".to_string(),
|
||||
"fat64".to_string(),
|
||||
"fat32".to_string(),
|
||||
]);
|
||||
impl std::fmt::Display for BinaryFormat {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for BinaryFormat {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"arm64" => Ok(Self::Arm64),
|
||||
"fat" => Ok(Self::Fat),
|
||||
"fat32" => Ok(Self::Fat32),
|
||||
"fat64" => Ok(Self::Fat64),
|
||||
"i386" => Ok(Self::I386),
|
||||
"intel" => Ok(Self::Intel),
|
||||
"ppc" => Ok(Self::Ppc),
|
||||
"ppc64" => Ok(Self::Ppc64),
|
||||
"universal" => Ok(Self::Universal),
|
||||
"universal2" => Ok(Self::Universal2),
|
||||
"x86_64" => Ok(Self::X86_64),
|
||||
_ => Err(format!("Invalid binary format: {s}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryFormat {
|
||||
/// Determine the appropriate binary formats for a macOS version.
|
||||
///
|
||||
/// See: <https://github.com/pypa/packaging/blob/fd4f11139d1c884a637be8aa26bb60a31fbc9411/packaging/tags.py#L314>
|
||||
pub fn from_arch(arch: Arch) -> &'static [Self] {
|
||||
match arch {
|
||||
Arch::Aarch64 => &[Self::Arm64, Self::Universal2],
|
||||
Arch::Powerpc64 => &[Self::Ppc64, Self::Fat64, Self::Universal],
|
||||
Arch::Powerpc => &[Self::Ppc, Self::Fat32, Self::Fat, Self::Universal],
|
||||
Arch::X86 => &[
|
||||
Self::I386,
|
||||
Self::Intel,
|
||||
Self::Fat32,
|
||||
Self::Fat,
|
||||
Self::Universal,
|
||||
],
|
||||
Arch::X86_64 => &[
|
||||
Self::X86_64,
|
||||
Self::Intel,
|
||||
Self::Fat64,
|
||||
Self::Fat32,
|
||||
Self::Universal2,
|
||||
Self::Universal,
|
||||
],
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(arch, Arch::X86_64 | Arch::Aarch64) {
|
||||
formats.push("universal2".to_string());
|
||||
/// Return the supported `platform_machine` tags for the binary format.
|
||||
///
|
||||
/// This is roughly the inverse of the above: given a binary format, which `platform_machine`
|
||||
/// tags are supported?
|
||||
pub fn platform_machine(&self) -> &'static [BinaryFormat] {
|
||||
match self {
|
||||
Self::Arm64 => &[Self::Arm64],
|
||||
Self::Fat => &[Self::X86_64, Self::Ppc],
|
||||
Self::Fat32 => &[Self::X86_64, Self::I386, Self::Ppc, Self::Ppc64],
|
||||
Self::Fat64 => &[Self::X86_64, Self::Ppc64],
|
||||
Self::I386 => &[Self::I386],
|
||||
Self::Intel => &[Self::X86_64, Self::I386],
|
||||
Self::Ppc => &[Self::Ppc],
|
||||
Self::Ppc64 => &[Self::Ppc64],
|
||||
Self::Universal => &[
|
||||
Self::X86_64,
|
||||
Self::I386,
|
||||
Self::Ppc64,
|
||||
Self::Ppc,
|
||||
Self::Intel,
|
||||
],
|
||||
Self::Universal2 => &[Self::X86_64, Self::Arm64],
|
||||
Self::X86_64 => &[Self::X86_64],
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(arch, Arch::X86_64) {
|
||||
formats.push("universal".to_string());
|
||||
/// Return the canonical name of the binary format.
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Arm64 => "arm64",
|
||||
Self::Fat => "fat",
|
||||
Self::Fat32 => "fat32",
|
||||
Self::Fat64 => "fat64",
|
||||
Self::I386 => "i386",
|
||||
Self::Intel => "intel",
|
||||
Self::Ppc => "ppc",
|
||||
Self::Ppc64 => "ppc64",
|
||||
Self::Universal => "universal",
|
||||
Self::Universal2 => "universal2",
|
||||
Self::X86_64 => "x86_64",
|
||||
}
|
||||
}
|
||||
|
||||
formats
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -627,6 +758,7 @@ mod tests {
|
|||
Arch::X86_64,
|
||||
))
|
||||
.unwrap();
|
||||
let tags = tags.iter().map(ToString::to_string).collect::<Vec<_>>();
|
||||
assert_debug_snapshot!(
|
||||
tags,
|
||||
@r###"
|
||||
|
@ -666,6 +798,7 @@ mod tests {
|
|||
Arch::X86_64,
|
||||
))
|
||||
.unwrap();
|
||||
let tags = tags.iter().map(ToString::to_string).collect::<Vec<_>>();
|
||||
assert_debug_snapshot!(
|
||||
tags,
|
||||
@r###"
|
||||
|
@ -826,6 +959,7 @@ mod tests {
|
|||
Arch::X86_64,
|
||||
))
|
||||
.unwrap();
|
||||
let tags = tags.iter().map(ToString::to_string).collect::<Vec<_>>();
|
||||
assert_debug_snapshot!(
|
||||
tags,
|
||||
@r###"
|
||||
|
@ -944,6 +1078,7 @@ mod tests {
|
|||
Arch::X86_64,
|
||||
))
|
||||
.unwrap();
|
||||
let tags = tags.iter().map(ToString::to_string).collect::<Vec<_>>();
|
||||
assert_debug_snapshot!(
|
||||
tags,
|
||||
@r###"
|
||||
|
|
|
@ -674,7 +674,7 @@ async fn form_metadata(
|
|||
];
|
||||
|
||||
if let DistFilename::WheelFilename(wheel) = filename {
|
||||
form_metadata.push(("pyversion", wheel.python_tag.join(".")));
|
||||
form_metadata.push(("pyversion", wheel.python_tag.iter().join(".")));
|
||||
} else {
|
||||
form_metadata.push(("pyversion", "source".to_string()));
|
||||
}
|
||||
|
|
|
@ -256,6 +256,10 @@ impl From<&uv_platform_tags::Arch> for Arch {
|
|||
family: target_lexicon::Architecture::S390x,
|
||||
variant: None,
|
||||
},
|
||||
uv_platform_tags::Arch::Powerpc => Self {
|
||||
family: target_lexicon::Architecture::Powerpc,
|
||||
variant: None,
|
||||
},
|
||||
uv_platform_tags::Arch::Powerpc64 => Self {
|
||||
family: target_lexicon::Architecture::Powerpc64,
|
||||
variant: None,
|
||||
|
|
|
@ -265,18 +265,6 @@ impl Lock {
|
|||
.retain(|wheel| requires_python.matches_wheel_tag(&wheel.filename));
|
||||
|
||||
// Filter by platform tags.
|
||||
|
||||
// See https://github.com/pypi/warehouse/blob/ccff64920db7965078cf1fdb50f028e640328887/warehouse/forklift/legacy.py#L100-L169
|
||||
// for a list of relevant platforms.
|
||||
let linux_tags = [
|
||||
"manylinux1_",
|
||||
"manylinux2010_",
|
||||
"manylinux2014_",
|
||||
"musllinux_",
|
||||
"manylinux_",
|
||||
];
|
||||
let windows_tags = ["win32", "win_arm64", "win_amd64", "win_ia64"];
|
||||
|
||||
locked_dist.wheels.retain(|wheel| {
|
||||
// Naively, we'd check whether `platform_system == 'Linux'` is disjoint, or
|
||||
// `os_name == 'posix'` is disjoint, or `sys_platform == 'linux'` is disjoint (each on its
|
||||
|
@ -285,21 +273,23 @@ impl Lock {
|
|||
// a single disjointness check with the intersection is sufficient, so we have one
|
||||
// constant per platform.
|
||||
let platform_tags = &wheel.filename.platform_tag;
|
||||
if platform_tags.iter().all(|tag| {
|
||||
linux_tags.into_iter().any(|linux_tag| {
|
||||
// These two linux tags are allowed by warehouse.
|
||||
tag.starts_with(linux_tag) || tag == "linux_armv6l" || tag == "linux_armv7l"
|
||||
})
|
||||
}) {
|
||||
if platform_tags
|
||||
.iter()
|
||||
.all(uv_platform_tags::PlatformTag::is_linux_compatible)
|
||||
{
|
||||
!graph.graph[node_index].marker().is_disjoint(*LINUX_MARKERS)
|
||||
} else if platform_tags
|
||||
.iter()
|
||||
.all(|tag| windows_tags.contains(&&**tag))
|
||||
.all(uv_platform_tags::PlatformTag::is_windows_compatible)
|
||||
{
|
||||
// TODO(charlie): This omits `win_ia64`, which is accepted by Warehouse.
|
||||
!graph.graph[node_index]
|
||||
.marker()
|
||||
.is_disjoint(*WINDOWS_MARKERS)
|
||||
} else if platform_tags.iter().all(|tag| tag.starts_with("macosx_")) {
|
||||
} else if platform_tags
|
||||
.iter()
|
||||
.all(uv_platform_tags::PlatformTag::is_macos_compatible)
|
||||
{
|
||||
!graph.graph[node_index].marker().is_disjoint(*MAC_MARKERS)
|
||||
} else {
|
||||
true
|
||||
|
|
|
@ -68,13 +68,16 @@ Ok(
|
|||
version: "4.3.0",
|
||||
build_tag: None,
|
||||
python_tag: [
|
||||
"py3",
|
||||
Python {
|
||||
major: 3,
|
||||
minor: None,
|
||||
},
|
||||
],
|
||||
abi_tag: [
|
||||
"none",
|
||||
None,
|
||||
],
|
||||
platform_tag: [
|
||||
"any",
|
||||
Any,
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -75,13 +75,16 @@ Ok(
|
|||
version: "4.3.0",
|
||||
build_tag: None,
|
||||
python_tag: [
|
||||
"py3",
|
||||
Python {
|
||||
major: 3,
|
||||
minor: None,
|
||||
},
|
||||
],
|
||||
abi_tag: [
|
||||
"none",
|
||||
None,
|
||||
],
|
||||
platform_tag: [
|
||||
"any",
|
||||
Any,
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -71,13 +71,16 @@ Ok(
|
|||
version: "4.3.0",
|
||||
build_tag: None,
|
||||
python_tag: [
|
||||
"py3",
|
||||
Python {
|
||||
major: 3,
|
||||
minor: None,
|
||||
},
|
||||
],
|
||||
abi_tag: [
|
||||
"none",
|
||||
None,
|
||||
],
|
||||
platform_tag: [
|
||||
"any",
|
||||
Any,
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,26 +1,13 @@
|
|||
use std::cmp::Ordering;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::ops::Bound;
|
||||
|
||||
use indexmap::IndexSet;
|
||||
use itertools::Itertools;
|
||||
use owo_colors::OwoColorize;
|
||||
use pubgrub::{DerivationTree, Derived, External, Map, Range, ReportFormatter, Term};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::ops::Bound;
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::{PubGrubPackage, PubGrubPackageInner, PubGrubPython};
|
||||
use crate::candidate_selector::CandidateSelector;
|
||||
use crate::error::ErrorTree;
|
||||
use crate::fork_indexes::ForkIndexes;
|
||||
use crate::fork_urls::ForkUrls;
|
||||
use crate::prerelease::AllowPrerelease;
|
||||
use crate::python_requirement::{PythonRequirement, PythonRequirementSource};
|
||||
use crate::resolver::{
|
||||
MetadataUnavailable, UnavailablePackage, UnavailableReason, UnavailableVersion,
|
||||
};
|
||||
use crate::{
|
||||
Flexibility, InMemoryIndex, Options, RequiresPython, ResolverEnvironment, VersionsResponse,
|
||||
};
|
||||
use uv_configuration::{IndexStrategy, NoBinary, NoBuild};
|
||||
use uv_distribution_types::{
|
||||
IncompatibleDist, IncompatibleSource, IncompatibleWheel, Index, IndexCapabilities,
|
||||
|
@ -28,7 +15,21 @@ use uv_distribution_types::{
|
|||
};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::{Version, VersionSpecifiers};
|
||||
use uv_platform_tags::{AbiTag, IncompatibleTag, LanguageTag, Tags};
|
||||
use uv_platform_tags::{AbiTag, IncompatibleTag, LanguageTag, PlatformTag, Tags};
|
||||
|
||||
use crate::candidate_selector::CandidateSelector;
|
||||
use crate::error::ErrorTree;
|
||||
use crate::fork_indexes::ForkIndexes;
|
||||
use crate::fork_urls::ForkUrls;
|
||||
use crate::prerelease::AllowPrerelease;
|
||||
use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner, PubGrubPython};
|
||||
use crate::python_requirement::{PythonRequirement, PythonRequirementSource};
|
||||
use crate::resolver::{
|
||||
MetadataUnavailable, UnavailablePackage, UnavailableReason, UnavailableVersion,
|
||||
};
|
||||
use crate::{
|
||||
Flexibility, InMemoryIndex, Options, RequiresPython, ResolverEnvironment, VersionsResponse,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PubGrubReportFormatter<'a> {
|
||||
|
@ -755,11 +756,7 @@ impl PubGrubReportFormatter<'_> {
|
|||
IncompatibleTag::Invalid => None,
|
||||
IncompatibleTag::Python => {
|
||||
// Return all available language tags.
|
||||
let tags = prioritized
|
||||
.python_tags()
|
||||
.into_iter()
|
||||
.filter_map(|tag| LanguageTag::from_str(tag).ok())
|
||||
.collect::<BTreeSet<_>>();
|
||||
let tags = prioritized.python_tags();
|
||||
if tags.is_empty() {
|
||||
None
|
||||
} else {
|
||||
|
@ -774,7 +771,6 @@ impl PubGrubReportFormatter<'_> {
|
|||
let tags = prioritized
|
||||
.abi_tags()
|
||||
.into_iter()
|
||||
.filter_map(|tag| AbiTag::from_str(tag).ok())
|
||||
// Ignore `none`, which is universally compatible.
|
||||
//
|
||||
// As an example, `none` can appear here if we're solving for Python 3.13, and
|
||||
|
@ -809,7 +805,7 @@ impl PubGrubReportFormatter<'_> {
|
|||
let tags = prioritized
|
||||
.platform_tags(self.tags?)
|
||||
.into_iter()
|
||||
.map(ToString::to_string)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if tags.is_empty() {
|
||||
None
|
||||
|
@ -1146,7 +1142,7 @@ pub(crate) enum PubGrubHint {
|
|||
// excluded from `PartialEq` and `Hash`
|
||||
version: Version,
|
||||
// excluded from `PartialEq` and `Hash`
|
||||
tags: Vec<String>,
|
||||
tags: Vec<PlatformTag>,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ fn clean_package_pypi() -> Result<()> {
|
|||
// Assert that the `.rkyv` file is created for `iniconfig`.
|
||||
let rkyv = context
|
||||
.cache_dir
|
||||
.child("simple-v14")
|
||||
.child("simple-v15")
|
||||
.child("pypi")
|
||||
.child("iniconfig.rkyv");
|
||||
assert!(
|
||||
|
@ -123,7 +123,7 @@ fn clean_package_index() -> Result<()> {
|
|||
// Assert that the `.rkyv` file is created for `iniconfig`.
|
||||
let rkyv = context
|
||||
.cache_dir
|
||||
.child("simple-v14")
|
||||
.child("simple-v15")
|
||||
.child("index")
|
||||
.child("e8208120cae3ba69")
|
||||
.child("iniconfig.rkyv");
|
||||
|
|
|
@ -13949,7 +13949,7 @@ fn invalid_platform() -> Result<()> {
|
|||
|
||||
hint: Wheels are available for `open3d` (v0.15.2) with the following ABI tags: `cp36m`, `cp37m`, `cp38`, `cp39`
|
||||
|
||||
hint: Wheels are available for `open3d` (v0.18.0) on the following platforms: `macosx_11_0_x86_64`, `macosx_13_0_arm64`, `manylinux_2_27_aarch64`, `manylinux_2_27_x86_64`, `win_amd64`
|
||||
hint: Wheels are available for `open3d` (v0.18.0) on the following platforms: `manylinux_2_27_aarch64`, `manylinux_2_27_x86_64`, `macosx_11_0_x86_64`, `macosx_13_0_arm64`, `win_amd64`
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -4090,6 +4090,8 @@ fn no_sdist_no_wheels_with_matching_abi() {
|
|||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels with a matching Python ABI tag (e.g., `cp38`), we can conclude that all versions of package-a cannot be used.
|
||||
And because you require package-a, we can conclude that your requirements are unsatisfiable.
|
||||
|
||||
hint: Wheels are available for `package-a` (v1.0.0) with the following ABI tag: `graalpy310_graalpy240_310_native`
|
||||
"###);
|
||||
|
||||
assert_not_installed(
|
||||
|
|
|
@ -2599,7 +2599,7 @@ fn sync_editable_and_local() -> Result<()> {
|
|||
#[test]
|
||||
fn incompatible_wheel() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let wheel = context.temp_dir.child("foo-1.2.3-not-compatible-wheel.whl");
|
||||
let wheel = context.temp_dir.child("foo-1.2.3-py3-none-any.whl");
|
||||
wheel.touch()?;
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
|
|
|
@ -4118,7 +4118,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/24/01/a4034a94a5f1828eb050230e7cf13af3ac23cf763512b6afe008d3def97c/watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84", size = 83012 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/5e/c0d7dad506adedd584188578901871fe923abf6c0c5dc9e79d9be5c7c24e/watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429", size = 82996 },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/e0/2a9f43008902427b5f074c497705d6ef8f815c85d4bc25fbf83f720a6159/watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a", size = 83002 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/54/23e5845ef68e1817b3792b2a11fb2088d7422814d41af8186d9058c4ff07/watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d", size = 83002 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue