From b55cd118e8da6c751f2a5d6ef9594aa9de367c7e Mon Sep 17 00:00:00 2001 From: konstin Date: Wed, 10 Dec 2025 11:29:57 +0100 Subject: [PATCH] Support `cp3-none-any` According to https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/, `cp3-none-any` is a valid tag, even though you shouldn't use it. We should accept it. --- .../src/prioritized_distribution.rs | 4 +- crates/uv-platform-tags/src/language_tag.rs | 45 ++++++++++++++++--- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/crates/uv-distribution-types/src/prioritized_distribution.rs b/crates/uv-distribution-types/src/prioritized_distribution.rs index 7ccfe5cacda94..7abd7b9d15d61 100644 --- a/crates/uv-distribution-types/src/prioritized_distribution.rs +++ b/crates/uv-distribution-types/src/prioritized_distribution.rs @@ -933,7 +933,7 @@ fn implied_python_markers(filename: &WheelFilename) -> MarkerTree { // No Python tag means no Python version requirement. return MarkerTree::TRUE; } - LanguageTag::Python { major, minor: None } => { + LanguageTag::Python { major, minor: None } | LanguageTag::CPythonMajor { major } => { MarkerTree::expression(MarkerExpression::Version { key: uv_pep508::MarkerValueVersion::PythonVersion, specifier: VersionSpecifier::equals_star_version(Version::new([u64::from( @@ -970,7 +970,7 @@ fn implied_python_markers(filename: &WheelFilename) -> MarkerTree { LanguageTag::None | LanguageTag::Python { .. } => { // No implementation marker needed } - LanguageTag::CPython { .. } => { + LanguageTag::CPython { .. } | LanguageTag::CPythonMajor { .. } => { tree.and(MarkerTree::expression(MarkerExpression::String { key: MarkerValueString::PlatformPythonImplementation, operator: MarkerOperator::Equal, diff --git a/crates/uv-platform-tags/src/language_tag.rs b/crates/uv-platform-tags/src/language_tag.rs index 5c88b77bbf29c..dc171f5544bca 100644 --- a/crates/uv-platform-tags/src/language_tag.rs +++ b/crates/uv-platform-tags/src/language_tag.rs @@ -32,6 +32,8 @@ pub enum LanguageTag { GraalPy { python_version: (u8, u8) }, /// Ex) `pyston38` Pyston { python_version: (u8, u8) }, + /// Ex) `cp3`, specifically in `cp3-none-any` + CPythonMajor { major: u8 }, } impl LanguageTag { @@ -58,6 +60,7 @@ impl LanguageTag { Self::Pyston { python_version: (major, minor), } => Some(format!("Pyston {major}.{minor}")), + Self::CPythonMajor { major } => Some(format!("CPython {major}")), } } } @@ -94,6 +97,9 @@ impl std::fmt::Display for LanguageTag { } => { write!(f, "pyston{major}{minor}") } + Self::CPythonMajor { major } => { + write!(f, "cp{major}") + } } } } @@ -181,11 +187,36 @@ impl FromStr for LanguageTag { } } } else if let Some(cp) = s.strip_prefix("cp") { - // Ex) `cp39` - let (major, minor) = parse_python_version(cp, "CPython", s)?; - Ok(Self::CPython { - python_version: (major, minor), - }) + match cp.len() { + 0 => Err(ParseLanguageTagError::MissingMajorVersion { + implementation: "CPython", + tag: s.to_string(), + }), + 1 => { + // Ex) `cp3` + let major = cp + .chars() + .next() + .ok_or_else(|| ParseLanguageTagError::MissingMajorVersion { + implementation: "CPython", + tag: s.to_string(), + })? + .to_digit(10) + .ok_or_else(|| ParseLanguageTagError::InvalidMajorVersion { + implementation: "CPython", + tag: s.to_string(), + })? as u8; + Ok(Self::CPythonMajor { major }) + } + 2 | 3 => { + // Ex) `cp39`, `cp310` + let (major, minor) = parse_python_version(cp, "CPython", s)?; + Ok(Self::CPython { + python_version: (major, minor), + }) + } + _ => Err(ParseLanguageTagError::UnknownFormat(s.to_string())), + } } else if let Some(pp) = s.strip_prefix("pp") { // Ex) `pp39` let (major, minor) = parse_python_version(pp, "PyPy", s)?; @@ -290,6 +321,10 @@ mod tests { assert_eq!(LanguageTag::from_str("cp39"), Ok(tag)); assert_eq!(tag.to_string(), "cp39"); + let tag = LanguageTag::CPythonMajor { major: 3 }; + assert_eq!(LanguageTag::from_str("cp3"), Ok(tag)); + assert_eq!(tag.to_string(), "cp3"); + assert_eq!( LanguageTag::from_str("cp"), Err(ParseLanguageTagError::MissingMajorVersion {