mirror of
				https://github.com/astral-sh/uv.git
				synced 2025-10-31 03:55:33 +00:00 
			
		
		
		
	Avoid failing when deserializing unknown tags (#10655)
	
		
			
	
		
	
	
		
	
		
			Some checks are pending
		
		
	
	
		
			
				
	
				CI / Determine changes (push) Waiting to run
				
			
		
			
				
	
				CI / lint (push) Waiting to run
				
			
		
			
				
	
				CI / cargo clippy | ubuntu (push) Blocked by required conditions
				
			
		
			
				
	
				CI / cargo clippy | windows (push) Blocked by required conditions
				
			
		
			
				
	
				CI / cargo dev generate-all (push) Blocked by required conditions
				
			
		
			
				
	
				CI / cargo shear (push) Waiting to run
				
			
		
			
				
	
				CI / cargo test | ubuntu (push) Blocked by required conditions
				
			
		
			
				
	
				CI / cargo test | macos (push) Blocked by required conditions
				
			
		
			
				
	
				CI / cargo test | windows (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check windows trampoline | aarch64 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check windows trampoline | i686 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check windows trampoline | x86_64 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / test windows trampoline | i686 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / test windows trampoline | x86_64 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / typos (push) Waiting to run
				
			
		
			
				
	
				CI / mkdocs (push) Waiting to run
				
			
		
			
				
	
				CI / build binary | linux (push) Blocked by required conditions
				
			
		
			
				
	
				CI / build binary | macos aarch64 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / build binary | macos x86_64 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / build binary | windows (push) Blocked by required conditions
				
			
		
			
				
	
				CI / cargo build (msrv) (push) Blocked by required conditions
				
			
		
			
				
	
				CI / build binary | freebsd (push) Blocked by required conditions
				
			
		
			
				
	
				CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
				
			
		
			
				
	
				CI / ecosystem test | pallets/flask (push) Blocked by required conditions
				
			
		
			
				
	
				CI / integration test | conda on ubuntu (push) Blocked by required conditions
				
			
		
			
				
	
				CI / integration test | free-threaded on linux (push) Blocked by required conditions
				
			
		
			
				
	
				CI / integration test | free-threaded on windows (push) Blocked by required conditions
				
			
		
			
				
	
				CI / integration test | pypy on ubuntu (push) Blocked by required conditions
				
			
		
			
				
	
				CI / integration test | pypy on windows (push) Blocked by required conditions
				
			
		
			
				
	
				CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
				
			
		
			
				
	
				CI / integration test | graalpy on windows (push) Blocked by required conditions
				
			
		
			
				
	
				CI / integration test | github actions (push) Blocked by required conditions
				
			
		
			
				
	
				CI / integration test | determine publish changes (push) Blocked by required conditions
				
			
		
			
				
	
				CI / integration test | uv publish (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check cache | ubuntu (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check cache | macos aarch64 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | python on debian (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | python on fedora (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | python on ubuntu (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | python on opensuse (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | python on rocky linux 8 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | python on rocky linux 9 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | pypy on ubuntu (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | pyston (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | alpine (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | python on macos aarch64 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | python on macos x86_64 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | python3.10 on windows (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | python3.13 on windows (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | python3.9 via pyenv (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | python3.13 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | conda3.11 on linux (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | conda3.8 on linux (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | conda3.11 on macos (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | conda3.8 on macos (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | conda3.11 on windows (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | conda3.8 on windows (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | amazonlinux (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | embedded python3.10 on windows (push) Blocked by required conditions
				
			
		
			
				
	
				CI / benchmarks (push) Blocked by required conditions
				
			
		
		
	
	
				
					
				
			
		
			Some checks are pending
		
		
	
	CI / Determine changes (push) Waiting to run
				
			CI / lint (push) Waiting to run
				
			CI / cargo clippy | ubuntu (push) Blocked by required conditions
				
			CI / cargo clippy | windows (push) Blocked by required conditions
				
			CI / cargo dev generate-all (push) Blocked by required conditions
				
			CI / cargo shear (push) Waiting to run
				
			CI / cargo test | ubuntu (push) Blocked by required conditions
				
			CI / cargo test | macos (push) Blocked by required conditions
				
			CI / cargo test | windows (push) Blocked by required conditions
				
			CI / check windows trampoline | aarch64 (push) Blocked by required conditions
				
			CI / check windows trampoline | i686 (push) Blocked by required conditions
				
			CI / check windows trampoline | x86_64 (push) Blocked by required conditions
				
			CI / test windows trampoline | i686 (push) Blocked by required conditions
				
			CI / test windows trampoline | x86_64 (push) Blocked by required conditions
				
			CI / typos (push) Waiting to run
				
			CI / mkdocs (push) Waiting to run
				
			CI / build binary | linux (push) Blocked by required conditions
				
			CI / build binary | macos aarch64 (push) Blocked by required conditions
				
			CI / build binary | macos x86_64 (push) Blocked by required conditions
				
			CI / build binary | windows (push) Blocked by required conditions
				
			CI / cargo build (msrv) (push) Blocked by required conditions
				
			CI / build binary | freebsd (push) Blocked by required conditions
				
			CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
				
			CI / ecosystem test | pallets/flask (push) Blocked by required conditions
				
			CI / integration test | conda on ubuntu (push) Blocked by required conditions
				
			CI / integration test | free-threaded on linux (push) Blocked by required conditions
				
			CI / integration test | free-threaded on windows (push) Blocked by required conditions
				
			CI / integration test | pypy on ubuntu (push) Blocked by required conditions
				
			CI / integration test | pypy on windows (push) Blocked by required conditions
				
			CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
				
			CI / integration test | graalpy on windows (push) Blocked by required conditions
				
			CI / integration test | github actions (push) Blocked by required conditions
				
			CI / integration test | determine publish changes (push) Blocked by required conditions
				
			CI / integration test | uv publish (push) Blocked by required conditions
				
			CI / check cache | ubuntu (push) Blocked by required conditions
				
			CI / check cache | macos aarch64 (push) Blocked by required conditions
				
			CI / check system | python on debian (push) Blocked by required conditions
				
			CI / check system | python on fedora (push) Blocked by required conditions
				
			CI / check system | python on ubuntu (push) Blocked by required conditions
				
			CI / check system | python on opensuse (push) Blocked by required conditions
				
			CI / check system | python on rocky linux 8 (push) Blocked by required conditions
				
			CI / check system | python on rocky linux 9 (push) Blocked by required conditions
				
			CI / check system | pypy on ubuntu (push) Blocked by required conditions
				
			CI / check system | pyston (push) Blocked by required conditions
				
			CI / check system | alpine (push) Blocked by required conditions
				
			CI / check system | python on macos aarch64 (push) Blocked by required conditions
				
			CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
				
			CI / check system | python on macos x86_64 (push) Blocked by required conditions
				
			CI / check system | python3.10 on windows (push) Blocked by required conditions
				
			CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
				
			CI / check system | python3.13 on windows (push) Blocked by required conditions
				
			CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
				
			CI / check system | python3.9 via pyenv (push) Blocked by required conditions
				
			CI / check system | python3.13 (push) Blocked by required conditions
				
			CI / check system | conda3.11 on linux (push) Blocked by required conditions
				
			CI / check system | conda3.8 on linux (push) Blocked by required conditions
				
			CI / check system | conda3.11 on macos (push) Blocked by required conditions
				
			CI / check system | conda3.8 on macos (push) Blocked by required conditions
				
			CI / check system | conda3.11 on windows (push) Blocked by required conditions
				
			CI / check system | conda3.8 on windows (push) Blocked by required conditions
				
			CI / check system | amazonlinux (push) Blocked by required conditions
				
			CI / check system | embedded python3.10 on windows (push) Blocked by required conditions
				
			CI / benchmarks (push) Blocked by required conditions
				
			## Summary Closes https://github.com/astral-sh/uv/issues/10654#issuecomment-2594022975.
This commit is contained in:
		
							parent
							
								
									37e31c38cb
								
							
						
					
					
						commit
						ed34d37e3c
					
				
					 10 changed files with 238 additions and 128 deletions
				
			
		|  | @ -97,9 +97,13 @@ impl Display for DistFilename { | |||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::WheelFilename; | ||||
|     use uv_platform_tags::{AbiTag, LanguageTag, PlatformTag}; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn wheel_filename_size() { | ||||
|         assert_eq!(size_of::<WheelFilename>(), 48); | ||||
|         assert_eq!(size_of::<WheelFilename>(), 72); | ||||
|         assert_eq!(size_of::<LanguageTag>(), 16); | ||||
|         assert_eq!(size_of::<AbiTag>(), 16); | ||||
|         assert_eq!(size_of::<PlatformTag>(), 16); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1343,8 +1343,8 @@ mod test { | |||
|     /// Ensure that we don't accidentally grow the `Dist` sizes.
 | ||||
|     #[test] | ||||
|     fn dist_size() { | ||||
|         assert!(size_of::<Dist>() <= 200, "{}", size_of::<Dist>()); | ||||
|         assert!(size_of::<BuiltDist>() <= 200, "{}", size_of::<BuiltDist>()); | ||||
|         assert!(size_of::<Dist>() <= 208, "{}", size_of::<Dist>()); | ||||
|         assert!(size_of::<BuiltDist>() <= 208, "{}", size_of::<BuiltDist>()); | ||||
|         assert!( | ||||
|             size_of::<SourceDist>() <= 176, | ||||
|             "{}", | ||||
|  |  | |||
|  | @ -523,20 +523,20 @@ impl PrioritizedDist { | |||
|     } | ||||
| 
 | ||||
|     /// Returns the set of all Python tags for the distribution.
 | ||||
|     pub fn python_tags(&self) -> BTreeSet<LanguageTag> { | ||||
|     pub fn python_tags(&self) -> BTreeSet<&LanguageTag> { | ||||
|         self.0 | ||||
|             .wheels | ||||
|             .iter() | ||||
|             .flat_map(|(wheel, _)| wheel.filename.python_tags().iter().copied()) | ||||
|             .flat_map(|(wheel, _)| wheel.filename.python_tags().iter()) | ||||
|             .collect() | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the set of all ABI tags for the distribution.
 | ||||
|     pub fn abi_tags(&self) -> BTreeSet<AbiTag> { | ||||
|     pub fn abi_tags(&self) -> BTreeSet<&AbiTag> { | ||||
|         self.0 | ||||
|             .wheels | ||||
|             .iter() | ||||
|             .flat_map(|(wheel, _)| wheel.filename.abi_tags().iter().copied()) | ||||
|             .flat_map(|(wheel, _)| wheel.filename.abi_tags().iter()) | ||||
|             .collect() | ||||
|     } | ||||
| 
 | ||||
|  | @ -547,7 +547,7 @@ impl PrioritizedDist { | |||
|         for (wheel, _) in &self.0.wheels { | ||||
|             for wheel_py in wheel.filename.python_tags() { | ||||
|                 for wheel_abi in wheel.filename.abi_tags() { | ||||
|                     if tags.is_compatible_abi(*wheel_py, *wheel_abi) { | ||||
|                     if tags.is_compatible_abi(wheel_py, wheel_abi) { | ||||
|                         candidates.extend(wheel.filename.platform_tags().iter()); | ||||
|                     } | ||||
|                 } | ||||
|  |  | |||
|  | @ -1,13 +1,14 @@ | |||
| use std::fmt::Formatter; | ||||
| use std::str::FromStr; | ||||
| 
 | ||||
| use uv_small_str::SmallString; | ||||
| 
 | ||||
| /// A tag to represent the ABI compatibility of a Python distribution.
 | ||||
| ///
 | ||||
| /// 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, | ||||
|     Copy, | ||||
|     Clone, | ||||
|     Eq, | ||||
|     PartialEq, | ||||
|  | @ -41,11 +42,13 @@ pub enum AbiTag { | |||
|     }, | ||||
|     /// Ex) `pyston_23_x86_64_linux_gnu`
 | ||||
|     Pyston { implementation_version: (u8, u8) }, | ||||
|     /// Ex) `pypy_73`
 | ||||
|     Unknown { tag: SmallString }, | ||||
| } | ||||
| 
 | ||||
| impl AbiTag { | ||||
|     /// Return a pretty string representation of the ABI tag.
 | ||||
|     pub fn pretty(self) -> Option<String> { | ||||
|     pub fn pretty(&self) -> Option<String> { | ||||
|         match self { | ||||
|             AbiTag::None => None, | ||||
|             AbiTag::Abi3 => None, | ||||
|  | @ -59,6 +62,7 @@ impl AbiTag { | |||
|                 Some(format!("GraalPy {}.{}", python_version.0, python_version.1)) | ||||
|             } | ||||
|             AbiTag::Pyston { .. } => Some("Pyston".to_string()), | ||||
|             AbiTag::Unknown { .. } => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -103,16 +107,14 @@ impl std::fmt::Display for AbiTag { | |||
|             } => { | ||||
|                 write!(f, "pyston_{impl_major}{impl_minor}_x86_64_linux_gnu") | ||||
|             } | ||||
|             Self::Unknown { tag } => write!(f, "{tag}"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromStr for AbiTag { | ||||
|     type Err = ParseAbiTagError; | ||||
| 
 | ||||
| impl AbiTag { | ||||
|     /// Parse an [`AbiTag`] from a string.
 | ||||
|     #[allow(clippy::cast_possible_truncation)] | ||||
|     fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||
|     fn parse(s: &str) -> Result<Self, ParseAbiTagError> { | ||||
|         /// Parse a Python version from a string (e.g., convert `39` into `(3, 9)`).
 | ||||
|         fn parse_python_version( | ||||
|             version_str: &str, | ||||
|  | @ -265,6 +267,14 @@ impl FromStr for AbiTag { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromStr for AbiTag { | ||||
|     type Err = ParseAbiTagError; | ||||
| 
 | ||||
|     fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||
|         Ok(AbiTag::parse(s).unwrap_or_else(|_| AbiTag::Unknown { tag: s.into() })) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, thiserror::Error, PartialEq, Eq)] | ||||
| pub enum ParseAbiTagError { | ||||
|     #[error("Unknown ABI tag format: {0}")] | ||||
|  | @ -318,19 +328,17 @@ pub enum ParseAbiTagError { | |||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use std::str::FromStr; | ||||
| 
 | ||||
|     use crate::abi_tag::{AbiTag, ParseAbiTagError}; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn none_abi() { | ||||
|         assert_eq!(AbiTag::from_str("none"), Ok(AbiTag::None)); | ||||
|         assert_eq!(AbiTag::parse("none"), Ok(AbiTag::None)); | ||||
|         assert_eq!(AbiTag::None.to_string(), "none"); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn abi3() { | ||||
|         assert_eq!(AbiTag::from_str("abi3"), Ok(AbiTag::Abi3)); | ||||
|         assert_eq!(AbiTag::parse("abi3"), Ok(AbiTag::Abi3)); | ||||
|         assert_eq!(AbiTag::Abi3.to_string(), "abi3"); | ||||
|     } | ||||
| 
 | ||||
|  | @ -340,25 +348,25 @@ mod tests { | |||
|             gil_disabled: false, | ||||
|             python_version: (3, 9), | ||||
|         }; | ||||
|         assert_eq!(AbiTag::from_str("cp39"), Ok(tag)); | ||||
|         assert_eq!(AbiTag::parse("cp39").as_ref(), Ok(&tag)); | ||||
|         assert_eq!(tag.to_string(), "cp39"); | ||||
| 
 | ||||
|         let tag = AbiTag::CPython { | ||||
|             gil_disabled: false, | ||||
|             python_version: (3, 7), | ||||
|         }; | ||||
|         assert_eq!(AbiTag::from_str("cp37m"), Ok(tag)); | ||||
|         assert_eq!(AbiTag::parse("cp37m").as_ref(), Ok(&tag)); | ||||
|         assert_eq!(tag.to_string(), "cp37m"); | ||||
| 
 | ||||
|         let tag = AbiTag::CPython { | ||||
|             gil_disabled: true, | ||||
|             python_version: (3, 13), | ||||
|         }; | ||||
|         assert_eq!(AbiTag::from_str("cp313t"), Ok(tag)); | ||||
|         assert_eq!(AbiTag::parse("cp313t").as_ref(), Ok(&tag)); | ||||
|         assert_eq!(tag.to_string(), "cp313t"); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             AbiTag::from_str("cpXY"), | ||||
|             AbiTag::parse("cpXY"), | ||||
|             Err(ParseAbiTagError::MissingMajorVersion { | ||||
|                 implementation: "CPython", | ||||
|                 tag: "cpXY".to_string() | ||||
|  | @ -372,25 +380,25 @@ mod tests { | |||
|             python_version: (3, 9), | ||||
|             implementation_version: (7, 3), | ||||
|         }; | ||||
|         assert_eq!(AbiTag::from_str("pypy39_pp73"), Ok(tag)); | ||||
|         assert_eq!(AbiTag::parse("pypy39_pp73").as_ref(), Ok(&tag)); | ||||
|         assert_eq!(tag.to_string(), "pypy39_pp73"); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             AbiTag::from_str("pypy39"), | ||||
|             AbiTag::parse("pypy39"), | ||||
|             Err(ParseAbiTagError::InvalidFormat { | ||||
|                 implementation: "PyPy", | ||||
|                 tag: "pypy39".to_string() | ||||
|             }) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             AbiTag::from_str("pypy39_73"), | ||||
|             AbiTag::parse("pypy39_73"), | ||||
|             Err(ParseAbiTagError::InvalidFormat { | ||||
|                 implementation: "PyPy", | ||||
|                 tag: "pypy39_73".to_string() | ||||
|             }) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             AbiTag::from_str("pypy39_ppXY"), | ||||
|             AbiTag::parse("pypy39_ppXY"), | ||||
|             Err(ParseAbiTagError::InvalidImplMajorVersion { | ||||
|                 implementation: "PyPy", | ||||
|                 tag: "pypy39_ppXY".to_string() | ||||
|  | @ -405,27 +413,27 @@ mod tests { | |||
|             implementation_version: (2, 40), | ||||
|         }; | ||||
|         assert_eq!( | ||||
|             AbiTag::from_str("graalpy310_graalpy240_310_native"), | ||||
|             Ok(tag) | ||||
|             AbiTag::parse("graalpy310_graalpy240_310_native").as_ref(), | ||||
|             Ok(&tag) | ||||
|         ); | ||||
|         assert_eq!(tag.to_string(), "graalpy310_graalpy240_310_native"); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             AbiTag::from_str("graalpy310"), | ||||
|             AbiTag::parse("graalpy310"), | ||||
|             Err(ParseAbiTagError::InvalidFormat { | ||||
|                 implementation: "GraalPy", | ||||
|                 tag: "graalpy310".to_string() | ||||
|             }) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             AbiTag::from_str("graalpy310_240"), | ||||
|             AbiTag::parse("graalpy310_240"), | ||||
|             Err(ParseAbiTagError::InvalidFormat { | ||||
|                 implementation: "GraalPy", | ||||
|                 tag: "graalpy310_240".to_string() | ||||
|             }) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             AbiTag::from_str("graalpy310_graalpyXY"), | ||||
|             AbiTag::parse("graalpy310_graalpyXY"), | ||||
|             Err(ParseAbiTagError::InvalidFormat { | ||||
|                 implementation: "GraalPy", | ||||
|                 tag: "graalpy310_graalpyXY".to_string() | ||||
|  | @ -438,18 +446,21 @@ mod tests { | |||
|         let tag = AbiTag::Pyston { | ||||
|             implementation_version: (2, 3), | ||||
|         }; | ||||
|         assert_eq!(AbiTag::from_str("pyston_23_x86_64_linux_gnu"), Ok(tag)); | ||||
|         assert_eq!( | ||||
|             AbiTag::parse("pyston_23_x86_64_linux_gnu").as_ref(), | ||||
|             Ok(&tag) | ||||
|         ); | ||||
|         assert_eq!(tag.to_string(), "pyston_23_x86_64_linux_gnu"); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             AbiTag::from_str("pyston23_x86_64_linux_gnu"), | ||||
|             AbiTag::parse("pyston23_x86_64_linux_gnu"), | ||||
|             Err(ParseAbiTagError::InvalidFormat { | ||||
|                 implementation: "Pyston", | ||||
|                 tag: "pyston23_x86_64_linux_gnu".to_string() | ||||
|             }) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             AbiTag::from_str("pyston_XY_x86_64_linux_gnu"), | ||||
|             AbiTag::parse("pyston_XY_x86_64_linux_gnu"), | ||||
|             Err(ParseAbiTagError::InvalidImplMajorVersion { | ||||
|                 implementation: "Pyston", | ||||
|                 tag: "pyston_XY_x86_64_linux_gnu".to_string() | ||||
|  | @ -460,11 +471,11 @@ mod tests { | |||
|     #[test] | ||||
|     fn unknown_abi() { | ||||
|         assert_eq!( | ||||
|             AbiTag::from_str("unknown"), | ||||
|             AbiTag::parse("unknown"), | ||||
|             Err(ParseAbiTagError::UnknownFormat("unknown".to_string())) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             AbiTag::from_str(""), | ||||
|             AbiTag::parse(""), | ||||
|             Err(ParseAbiTagError::UnknownFormat(String::new())) | ||||
|         ); | ||||
|     } | ||||
|  |  | |||
|  | @ -1,13 +1,14 @@ | |||
| use std::fmt::Formatter; | ||||
| use std::str::FromStr; | ||||
| 
 | ||||
| use uv_small_str::SmallString; | ||||
| 
 | ||||
| /// A tag to represent the language and implementation of the Python interpreter.
 | ||||
| ///
 | ||||
| /// 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, | ||||
|     Copy, | ||||
|     Clone, | ||||
|     Eq, | ||||
|     PartialEq, | ||||
|  | @ -32,11 +33,13 @@ pub enum LanguageTag { | |||
|     GraalPy { python_version: (u8, u8) }, | ||||
|     /// Ex) `pyston38`
 | ||||
|     Pyston { python_version: (u8, u8) }, | ||||
|     /// Ex) `ironpython27`
 | ||||
|     Unknown { tag: SmallString }, | ||||
| } | ||||
| 
 | ||||
| impl LanguageTag { | ||||
|     /// Return a pretty string representation of the language tag.
 | ||||
|     pub fn pretty(self) -> Option<String> { | ||||
|     pub fn pretty(&self) -> Option<String> { | ||||
|         match self { | ||||
|             Self::None => None, | ||||
|             Self::Python { major, minor } => { | ||||
|  | @ -58,6 +61,7 @@ impl LanguageTag { | |||
|             Self::Pyston { | ||||
|                 python_version: (major, minor), | ||||
|             } => Some(format!("Pyston {major}.{minor}")), | ||||
|             Self::Unknown { .. } => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -94,16 +98,15 @@ impl std::fmt::Display for LanguageTag { | |||
|             } => { | ||||
|                 write!(f, "pyston{major}{minor}") | ||||
|             } | ||||
|             Self::Unknown { tag } => write!(f, "{tag}"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromStr for LanguageTag { | ||||
|     type Err = ParseLanguageTagError; | ||||
| 
 | ||||
| impl LanguageTag { | ||||
|     /// Parse a [`LanguageTag`] from a string.
 | ||||
|     #[allow(clippy::cast_possible_truncation)] | ||||
|     fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||
|     fn parse(s: &str) -> Result<Self, ParseLanguageTagError> { | ||||
|         /// Parse a Python version from a string (e.g., convert `39` into `(3, 9)`).
 | ||||
|         fn parse_python_version( | ||||
|             version_str: &str, | ||||
|  | @ -206,6 +209,14 @@ impl FromStr for LanguageTag { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromStr for LanguageTag { | ||||
|     type Err = ParseLanguageTagError; | ||||
| 
 | ||||
|     fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||
|         Ok(LanguageTag::parse(s).unwrap_or_else(|_| LanguageTag::Unknown { tag: s.into() })) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, thiserror::Error, PartialEq, Eq)] | ||||
| pub enum ParseLanguageTagError { | ||||
|     #[error("Unknown language tag format: {0}")] | ||||
|  | @ -234,14 +245,13 @@ pub enum ParseLanguageTagError { | |||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use std::str::FromStr; | ||||
| 
 | ||||
|     use crate::language_tag::ParseLanguageTagError; | ||||
|     use crate::LanguageTag; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn none() { | ||||
|         assert_eq!(LanguageTag::from_str("none"), Ok(LanguageTag::None)); | ||||
|         assert_eq!(LanguageTag::parse("none"), Ok(LanguageTag::None)); | ||||
|         assert_eq!(LanguageTag::None.to_string(), "none"); | ||||
|     } | ||||
| 
 | ||||
|  | @ -251,32 +261,32 @@ mod tests { | |||
|             major: 3, | ||||
|             minor: None, | ||||
|         }; | ||||
|         assert_eq!(LanguageTag::from_str("py3"), Ok(tag)); | ||||
|         assert_eq!(LanguageTag::parse("py3").as_ref(), Ok(&tag)); | ||||
|         assert_eq!(tag.to_string(), "py3"); | ||||
| 
 | ||||
|         let tag = LanguageTag::Python { | ||||
|             major: 3, | ||||
|             minor: Some(9), | ||||
|         }; | ||||
|         assert_eq!(LanguageTag::from_str("py39"), Ok(tag)); | ||||
|         assert_eq!(LanguageTag::parse("py39").as_ref(), Ok(&tag)); | ||||
|         assert_eq!(tag.to_string(), "py39"); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("py"), | ||||
|             LanguageTag::parse("py"), | ||||
|             Err(ParseLanguageTagError::MissingMajorVersion { | ||||
|                 implementation: "Python", | ||||
|                 tag: "py".to_string() | ||||
|             }) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("pyX"), | ||||
|             LanguageTag::parse("pyX"), | ||||
|             Err(ParseLanguageTagError::InvalidMajorVersion { | ||||
|                 implementation: "Python", | ||||
|                 tag: "pyX".to_string() | ||||
|             }) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("py3X"), | ||||
|             LanguageTag::parse("py3X"), | ||||
|             Err(ParseLanguageTagError::InvalidMinorVersion { | ||||
|                 implementation: "Python", | ||||
|                 tag: "py3X".to_string() | ||||
|  | @ -289,25 +299,25 @@ mod tests { | |||
|         let tag = LanguageTag::CPython { | ||||
|             python_version: (3, 9), | ||||
|         }; | ||||
|         assert_eq!(LanguageTag::from_str("cp39"), Ok(tag)); | ||||
|         assert_eq!(LanguageTag::parse("cp39").as_ref(), Ok(&tag)); | ||||
|         assert_eq!(tag.to_string(), "cp39"); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("cp"), | ||||
|             LanguageTag::parse("cp"), | ||||
|             Err(ParseLanguageTagError::MissingMajorVersion { | ||||
|                 implementation: "CPython", | ||||
|                 tag: "cp".to_string() | ||||
|             }) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("cpX"), | ||||
|             LanguageTag::parse("cpX"), | ||||
|             Err(ParseLanguageTagError::InvalidMajorVersion { | ||||
|                 implementation: "CPython", | ||||
|                 tag: "cpX".to_string() | ||||
|             }) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("cp3X"), | ||||
|             LanguageTag::parse("cp3X"), | ||||
|             Err(ParseLanguageTagError::InvalidMinorVersion { | ||||
|                 implementation: "CPython", | ||||
|                 tag: "cp3X".to_string() | ||||
|  | @ -320,25 +330,25 @@ mod tests { | |||
|         let tag = LanguageTag::PyPy { | ||||
|             python_version: (3, 9), | ||||
|         }; | ||||
|         assert_eq!(LanguageTag::from_str("pp39"), Ok(tag)); | ||||
|         assert_eq!(LanguageTag::parse("pp39").as_ref(), Ok(&tag)); | ||||
|         assert_eq!(tag.to_string(), "pp39"); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("pp"), | ||||
|             LanguageTag::parse("pp"), | ||||
|             Err(ParseLanguageTagError::MissingMajorVersion { | ||||
|                 implementation: "PyPy", | ||||
|                 tag: "pp".to_string() | ||||
|             }) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("ppX"), | ||||
|             LanguageTag::parse("ppX"), | ||||
|             Err(ParseLanguageTagError::InvalidMajorVersion { | ||||
|                 implementation: "PyPy", | ||||
|                 tag: "ppX".to_string() | ||||
|             }) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("pp3X"), | ||||
|             LanguageTag::parse("pp3X"), | ||||
|             Err(ParseLanguageTagError::InvalidMinorVersion { | ||||
|                 implementation: "PyPy", | ||||
|                 tag: "pp3X".to_string() | ||||
|  | @ -351,25 +361,25 @@ mod tests { | |||
|         let tag = LanguageTag::GraalPy { | ||||
|             python_version: (3, 10), | ||||
|         }; | ||||
|         assert_eq!(LanguageTag::from_str("graalpy310"), Ok(tag)); | ||||
|         assert_eq!(LanguageTag::parse("graalpy310").as_ref(), Ok(&tag)); | ||||
|         assert_eq!(tag.to_string(), "graalpy310"); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("graalpy"), | ||||
|             LanguageTag::parse("graalpy"), | ||||
|             Err(ParseLanguageTagError::MissingMajorVersion { | ||||
|                 implementation: "GraalPy", | ||||
|                 tag: "graalpy".to_string() | ||||
|             }) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("graalpyX"), | ||||
|             LanguageTag::parse("graalpyX"), | ||||
|             Err(ParseLanguageTagError::InvalidMajorVersion { | ||||
|                 implementation: "GraalPy", | ||||
|                 tag: "graalpyX".to_string() | ||||
|             }) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("graalpy3X"), | ||||
|             LanguageTag::parse("graalpy3X"), | ||||
|             Err(ParseLanguageTagError::InvalidMinorVersion { | ||||
|                 implementation: "GraalPy", | ||||
|                 tag: "graalpy3X".to_string() | ||||
|  | @ -382,25 +392,25 @@ mod tests { | |||
|         let tag = LanguageTag::Pyston { | ||||
|             python_version: (3, 8), | ||||
|         }; | ||||
|         assert_eq!(LanguageTag::from_str("pyston38"), Ok(tag)); | ||||
|         assert_eq!(LanguageTag::parse("pyston38").as_ref(), Ok(&tag)); | ||||
|         assert_eq!(tag.to_string(), "pyston38"); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("pyston"), | ||||
|             LanguageTag::parse("pyston"), | ||||
|             Err(ParseLanguageTagError::MissingMajorVersion { | ||||
|                 implementation: "Pyston", | ||||
|                 tag: "pyston".to_string() | ||||
|             }) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("pystonX"), | ||||
|             LanguageTag::parse("pystonX"), | ||||
|             Err(ParseLanguageTagError::InvalidMajorVersion { | ||||
|                 implementation: "Pyston", | ||||
|                 tag: "pystonX".to_string() | ||||
|             }) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("pyston3X"), | ||||
|             LanguageTag::parse("pyston3X"), | ||||
|             Err(ParseLanguageTagError::InvalidMinorVersion { | ||||
|                 implementation: "Pyston", | ||||
|                 tag: "pyston3X".to_string() | ||||
|  | @ -411,11 +421,11 @@ mod tests { | |||
|     #[test] | ||||
|     fn unknown_language() { | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str("unknown"), | ||||
|             LanguageTag::parse("unknown"), | ||||
|             Err(ParseLanguageTagError::UnknownFormat("unknown".to_string())) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             LanguageTag::from_str(""), | ||||
|             LanguageTag::parse(""), | ||||
|             Err(ParseLanguageTagError::UnknownFormat(String::new())) | ||||
|         ); | ||||
|     } | ||||
|  |  | |||
|  | @ -69,6 +69,8 @@ pub enum PlatformTag { | |||
|     Illumos { release_arch: SmallString }, | ||||
|     /// Ex) `solaris_11_4_x86_64`
 | ||||
|     Solaris { release_arch: SmallString }, | ||||
|     /// Ex) `win_ia64`
 | ||||
|     Unknown { tag: SmallString }, | ||||
| } | ||||
| 
 | ||||
| impl PlatformTag { | ||||
|  | @ -94,6 +96,7 @@ impl PlatformTag { | |||
|             PlatformTag::Haiku { .. } => Some("Haiku"), | ||||
|             PlatformTag::Illumos { .. } => Some("Illumos"), | ||||
|             PlatformTag::Solaris { .. } => Some("Solaris"), | ||||
|             PlatformTag::Unknown { .. } => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -255,15 +258,14 @@ impl std::fmt::Display for PlatformTag { | |||
|             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"), | ||||
|             Self::Unknown { tag } => write!(f, "{tag}"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromStr for PlatformTag { | ||||
|     type Err = ParsePlatformTagError; | ||||
| 
 | ||||
| impl PlatformTag { | ||||
|     /// Parse a [`PlatformTag`] from a string.
 | ||||
|     fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||
|     fn parse(s: &str) -> Result<Self, ParsePlatformTagError> { | ||||
|         // Match against any static variants.
 | ||||
|         match s { | ||||
|             "any" => return Ok(Self::Any), | ||||
|  | @ -612,6 +614,14 @@ impl FromStr for PlatformTag { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| impl FromStr for PlatformTag { | ||||
|     type Err = ParsePlatformTagError; | ||||
| 
 | ||||
|     fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||
|         Ok(PlatformTag::parse(s).unwrap_or_else(|_| PlatformTag::Unknown { tag: s.into() })) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, thiserror::Error, PartialEq, Eq)] | ||||
| pub enum ParsePlatformTagError { | ||||
|     #[error("Unknown platform tag format: {0}")] | ||||
|  | @ -630,14 +640,13 @@ pub enum ParsePlatformTagError { | |||
| 
 | ||||
| #[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::parse("any"), Ok(PlatformTag::Any)); | ||||
|         assert_eq!(PlatformTag::Any.to_string(), "any"); | ||||
|     } | ||||
| 
 | ||||
|  | @ -649,13 +658,13 @@ mod tests { | |||
|             arch: Arch::X86_64, | ||||
|         }; | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("manylinux_2_24_x86_64").as_ref(), | ||||
|             PlatformTag::parse("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"), | ||||
|             PlatformTag::parse("manylinux_x_24_x86_64"), | ||||
|             Err(ParsePlatformTagError::InvalidMajorVersion { | ||||
|                 platform: "manylinux", | ||||
|                 tag: "manylinux_x_24_x86_64".to_string() | ||||
|  | @ -663,7 +672,7 @@ mod tests { | |||
|         ); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("manylinux_2_x_x86_64"), | ||||
|             PlatformTag::parse("manylinux_2_x_x86_64"), | ||||
|             Err(ParsePlatformTagError::InvalidMinorVersion { | ||||
|                 platform: "manylinux", | ||||
|                 tag: "manylinux_2_x_x86_64".to_string() | ||||
|  | @ -671,7 +680,7 @@ mod tests { | |||
|         ); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("manylinux_2_24_invalid"), | ||||
|             PlatformTag::parse("manylinux_2_24_invalid"), | ||||
|             Err(ParsePlatformTagError::InvalidArch { | ||||
|                 platform: "manylinux", | ||||
|                 tag: "manylinux_2_24_invalid".to_string() | ||||
|  | @ -682,14 +691,11 @@ mod tests { | |||
|     #[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!(PlatformTag::parse("manylinux1_x86_64").as_ref(), Ok(&tag)); | ||||
|         assert_eq!(tag.to_string(), "manylinux1_x86_64"); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("manylinux1_invalid"), | ||||
|             PlatformTag::parse("manylinux1_invalid"), | ||||
|             Err(ParsePlatformTagError::InvalidArch { | ||||
|                 platform: "manylinux1", | ||||
|                 tag: "manylinux1_invalid".to_string() | ||||
|  | @ -701,13 +707,13 @@ mod tests { | |||
|     fn manylinux2010_platform() { | ||||
|         let tag = PlatformTag::Manylinux2010 { arch: Arch::X86_64 }; | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("manylinux2010_x86_64").as_ref(), | ||||
|             PlatformTag::parse("manylinux2010_x86_64").as_ref(), | ||||
|             Ok(&tag) | ||||
|         ); | ||||
|         assert_eq!(tag.to_string(), "manylinux2010_x86_64"); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("manylinux2010_invalid"), | ||||
|             PlatformTag::parse("manylinux2010_invalid"), | ||||
|             Err(ParsePlatformTagError::InvalidArch { | ||||
|                 platform: "manylinux2010", | ||||
|                 tag: "manylinux2010_invalid".to_string() | ||||
|  | @ -719,13 +725,13 @@ mod tests { | |||
|     fn manylinux2014_platform() { | ||||
|         let tag = PlatformTag::Manylinux2014 { arch: Arch::X86_64 }; | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("manylinux2014_x86_64").as_ref(), | ||||
|             PlatformTag::parse("manylinux2014_x86_64").as_ref(), | ||||
|             Ok(&tag) | ||||
|         ); | ||||
|         assert_eq!(tag.to_string(), "manylinux2014_x86_64"); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("manylinux2014_invalid"), | ||||
|             PlatformTag::parse("manylinux2014_invalid"), | ||||
|             Err(ParsePlatformTagError::InvalidArch { | ||||
|                 platform: "manylinux2014", | ||||
|                 tag: "manylinux2014_invalid".to_string() | ||||
|  | @ -736,11 +742,11 @@ mod tests { | |||
|     #[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!(PlatformTag::parse("linux_x86_64").as_ref(), Ok(&tag)); | ||||
|         assert_eq!(tag.to_string(), "linux_x86_64"); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("linux_invalid"), | ||||
|             PlatformTag::parse("linux_invalid"), | ||||
|             Err(ParsePlatformTagError::InvalidArch { | ||||
|                 platform: "linux", | ||||
|                 tag: "linux_invalid".to_string() | ||||
|  | @ -756,13 +762,13 @@ mod tests { | |||
|             arch: Arch::X86_64, | ||||
|         }; | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("musllinux_1_2_x86_64").as_ref(), | ||||
|             PlatformTag::parse("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"), | ||||
|             PlatformTag::parse("musllinux_x_2_x86_64"), | ||||
|             Err(ParsePlatformTagError::InvalidMajorVersion { | ||||
|                 platform: "musllinux", | ||||
|                 tag: "musllinux_x_2_x86_64".to_string() | ||||
|  | @ -770,7 +776,7 @@ mod tests { | |||
|         ); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("musllinux_1_x_x86_64"), | ||||
|             PlatformTag::parse("musllinux_1_x_x86_64"), | ||||
|             Err(ParsePlatformTagError::InvalidMinorVersion { | ||||
|                 platform: "musllinux", | ||||
|                 tag: "musllinux_1_x_x86_64".to_string() | ||||
|  | @ -778,7 +784,7 @@ mod tests { | |||
|         ); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("musllinux_1_2_invalid"), | ||||
|             PlatformTag::parse("musllinux_1_2_invalid"), | ||||
|             Err(ParsePlatformTagError::InvalidArch { | ||||
|                 platform: "musllinux", | ||||
|                 tag: "musllinux_1_2_invalid".to_string() | ||||
|  | @ -794,13 +800,13 @@ mod tests { | |||
|             binary_format: BinaryFormat::Universal2, | ||||
|         }; | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("macosx_11_0_universal2").as_ref(), | ||||
|             PlatformTag::parse("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"), | ||||
|             PlatformTag::parse("macosx_x_0_universal2"), | ||||
|             Err(ParsePlatformTagError::InvalidMajorVersion { | ||||
|                 platform: "macosx", | ||||
|                 tag: "macosx_x_0_universal2".to_string() | ||||
|  | @ -808,7 +814,7 @@ mod tests { | |||
|         ); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("macosx_11_x_universal2"), | ||||
|             PlatformTag::parse("macosx_11_x_universal2"), | ||||
|             Err(ParsePlatformTagError::InvalidMinorVersion { | ||||
|                 platform: "macosx", | ||||
|                 tag: "macosx_11_x_universal2".to_string() | ||||
|  | @ -816,7 +822,7 @@ mod tests { | |||
|         ); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("macosx_11_0_invalid"), | ||||
|             PlatformTag::parse("macosx_11_0_invalid"), | ||||
|             Err(ParsePlatformTagError::InvalidArch { | ||||
|                 platform: "macosx", | ||||
|                 tag: "macosx_11_0_invalid".to_string() | ||||
|  | @ -826,25 +832,19 @@ mod tests { | |||
| 
 | ||||
|     #[test] | ||||
|     fn win32_platform() { | ||||
|         assert_eq!(PlatformTag::from_str("win32"), Ok(PlatformTag::Win32)); | ||||
|         assert_eq!(PlatformTag::parse("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::parse("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::parse("win_arm64"), Ok(PlatformTag::WinArm64)); | ||||
|         assert_eq!(PlatformTag::WinArm64.to_string(), "win_arm64"); | ||||
|     } | ||||
| 
 | ||||
|  | @ -854,7 +854,7 @@ mod tests { | |||
|             release_arch: "13_14_x86_64".into(), | ||||
|         }; | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("freebsd_13_14_x86_64").as_ref(), | ||||
|             PlatformTag::parse("freebsd_13_14_x86_64").as_ref(), | ||||
|             Ok(&tag) | ||||
|         ); | ||||
|         assert_eq!(tag.to_string(), "freebsd_13_14_x86_64"); | ||||
|  | @ -865,10 +865,7 @@ mod tests { | |||
|         let tag = PlatformTag::Illumos { | ||||
|             release_arch: "5_11_x86_64".into(), | ||||
|         }; | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("illumos_5_11_x86_64").as_ref(), | ||||
|             Ok(&tag) | ||||
|         ); | ||||
|         assert_eq!(PlatformTag::parse("illumos_5_11_x86_64").as_ref(), Ok(&tag)); | ||||
|         assert_eq!(tag.to_string(), "illumos_5_11_x86_64"); | ||||
|     } | ||||
| 
 | ||||
|  | @ -878,13 +875,13 @@ mod tests { | |||
|             release_arch: "11_4_x86_64".into(), | ||||
|         }; | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("solaris_11_4_x86_64_64bit").as_ref(), | ||||
|             PlatformTag::parse("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"), | ||||
|             PlatformTag::parse("solaris_11_4_x86_64"), | ||||
|             Err(ParsePlatformTagError::InvalidArch { | ||||
|                 platform: "solaris", | ||||
|                 tag: "solaris_11_4_x86_64".to_string() | ||||
|  | @ -895,11 +892,11 @@ mod tests { | |||
|     #[test] | ||||
|     fn unknown_platform() { | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str("unknown"), | ||||
|             PlatformTag::parse("unknown"), | ||||
|             Err(ParsePlatformTagError::UnknownFormat("unknown".to_string())) | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             PlatformTag::from_str(""), | ||||
|             PlatformTag::parse(""), | ||||
|             Err(ParsePlatformTagError::UnknownFormat(String::new())) | ||||
|         ); | ||||
|     } | ||||
|  |  | |||
|  | @ -298,13 +298,13 @@ impl Tags { | |||
|     } | ||||
| 
 | ||||
|     /// Return the highest-priority Python tag for the [`Tags`].
 | ||||
|     pub fn python_tag(&self) -> Option<LanguageTag> { | ||||
|         self.best.as_ref().map(|(python, _, _)| *python) | ||||
|     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<AbiTag> { | ||||
|         self.best.as_ref().map(|(_, abi, _)| *abi) | ||||
|     pub fn abi_tag(&self) -> Option<&AbiTag> { | ||||
|         self.best.as_ref().map(|(_, abi, _)| abi) | ||||
|     } | ||||
| 
 | ||||
|     /// Return the highest-priority platform tag for the [`Tags`].
 | ||||
|  | @ -314,10 +314,10 @@ impl Tags { | |||
| 
 | ||||
|     /// Returns `true` if the given language and ABI tags are compatible with the current
 | ||||
|     /// environment.
 | ||||
|     pub fn is_compatible_abi(&self, python_tag: LanguageTag, abi_tag: AbiTag) -> 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) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -744,8 +744,12 @@ impl PubGrubReportFormatter<'_> { | |||
|         match tag { | ||||
|             IncompatibleTag::Invalid => None, | ||||
|             IncompatibleTag::Python => { | ||||
|                 let best = tags.and_then(Tags::python_tag); | ||||
|                 let tags = prioritized.python_tags(); | ||||
|                 let best = tags.and_then(Tags::python_tag).cloned(); | ||||
|                 let tags = prioritized | ||||
|                     .python_tags() | ||||
|                     .into_iter() | ||||
|                     .cloned() | ||||
|                     .collect::<BTreeSet<_>>(); | ||||
|                 if tags.is_empty() { | ||||
|                     None | ||||
|                 } else { | ||||
|  | @ -758,7 +762,7 @@ impl PubGrubReportFormatter<'_> { | |||
|                 } | ||||
|             } | ||||
|             IncompatibleTag::Abi | IncompatibleTag::AbiPythonVersion => { | ||||
|                 let best = tags.and_then(Tags::abi_tag); | ||||
|                 let best = tags.and_then(Tags::abi_tag).cloned(); | ||||
|                 let tags = prioritized | ||||
|                     .abi_tags() | ||||
|                     .into_iter() | ||||
|  | @ -770,7 +774,8 @@ impl PubGrubReportFormatter<'_> { | |||
|                     // In that case, the wheel isn't compatible, but when solving for Python 3.13,
 | ||||
|                     // the `cp312` Python tag _can_ be compatible (e.g., for `cp312-abi3-macosx_11_0_arm64.whl`),
 | ||||
|                     // so this is considered an ABI incompatibility rather than Python incompatibility.
 | ||||
|                     .filter(|tag| *tag != AbiTag::None) | ||||
|                     .filter(|tag| **tag != AbiTag::None) | ||||
|                     .cloned() | ||||
|                     .collect::<BTreeSet<_>>(); | ||||
|                 if tags.is_empty() { | ||||
|                     None | ||||
|  | @ -1576,7 +1581,7 @@ impl std::fmt::Display for PubGrubHint { | |||
|                 tags, | ||||
|                 best, | ||||
|             } => { | ||||
|                 if let Some(best) = best { | ||||
|                 if let Some(best) = best.as_ref() { | ||||
|                     let s = if tags.len() == 1 { "" } else { "s" }; | ||||
|                     let best = if let Some(pretty) = best.pretty() { | ||||
|                         format!("{} (`{}`)", pretty.cyan(), best.cyan()) | ||||
|  | @ -1616,7 +1621,7 @@ impl std::fmt::Display for PubGrubHint { | |||
|                 tags, | ||||
|                 best, | ||||
|             } => { | ||||
|                 if let Some(best) = best { | ||||
|                 if let Some(best) = best.as_ref() { | ||||
|                     let s = if tags.len() == 1 { "" } else { "s" }; | ||||
|                     let best = if let Some(pretty) = best.pretty() { | ||||
|                         format!("{} (`{}`)", pretty.cyan(), best.cyan()) | ||||
|  |  | |||
|  | @ -3419,6 +3419,88 @@ fn lock_partial_git() -> Result<()> { | |||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| /// See: <https://github.com/astral-sh/uv/issues/10654#issuecomment-2594022975>
 | ||||
| #[test] | ||||
| fn lock_unsupported_tag() -> Result<()> { | ||||
|     let context = TestContext::new("3.12"); | ||||
| 
 | ||||
|     let pyproject_toml = context.temp_dir.child("pyproject.toml"); | ||||
|     pyproject_toml.write_str( | ||||
|         r#" | ||||
|         [project] | ||||
|         name = "project" | ||||
|         version = "0.1.0" | ||||
|         requires-python = ">=3.12" | ||||
|         dependencies = ["watchdog"] | ||||
|         "#,
 | ||||
|     )?; | ||||
| 
 | ||||
|     let lock = context.temp_dir.child("uv.lock"); | ||||
|     lock.write_str(r#" | ||||
|         version = 1 | ||||
|         requires-python = ">=3.12.0" | ||||
| 
 | ||||
|         [options] | ||||
|         exclude-newer = "2024-03-25T00:00:00Z" | ||||
| 
 | ||||
|         [[package]] | ||||
|         name = "project" | ||||
|         version = "0.1.0" | ||||
|         source = { virtual = "." } | ||||
|         dependencies = [ | ||||
|             { name = "watchdog" }, | ||||
|         ] | ||||
| 
 | ||||
|         [package.metadata] | ||||
|         requires-dist = [{ name = "watchdog" }] | ||||
| 
 | ||||
|         [[package]] | ||||
|         name = "watchdog" | ||||
|         version = "6.0.0" | ||||
|         source = { registry = "https://pypi.org/simple" } | ||||
|         sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 } | ||||
|         wheels = [ | ||||
|             { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 }, | ||||
|             { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 }, | ||||
|             { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 }, | ||||
|             { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 }, | ||||
|             { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 }, | ||||
|             { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 }, | ||||
|             { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 }, | ||||
|             { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 }, | ||||
|             { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 }, | ||||
|             { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 }, | ||||
|             { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 }, | ||||
|             { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 }, | ||||
|             { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 }, | ||||
|         ] | ||||
|     "#)?;
 | ||||
| 
 | ||||
|     // Re-run with `--locked`.
 | ||||
|     uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###" | ||||
|     success: true | ||||
|     exit_code: 0 | ||||
|     ----- stdout ----- | ||||
| 
 | ||||
|     ----- stderr ----- | ||||
|     Resolved 2 packages in [TIME] | ||||
|     "###);
 | ||||
| 
 | ||||
|     // Install from the lockfile.
 | ||||
|     uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" | ||||
|     success: true | ||||
|     exit_code: 0 | ||||
|     ----- stdout ----- | ||||
| 
 | ||||
|     ----- stderr ----- | ||||
|     Prepared 1 package in [TIME] | ||||
|     Installed 1 package in [TIME] | ||||
|      + watchdog==6.0.0 | ||||
|     "###);
 | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| /// Respect locked versions with `uv lock`, unless `--upgrade` is passed.
 | ||||
| #[test] | ||||
| #[cfg(feature = "git")] | ||||
|  |  | |||
|  | @ -4118,6 +4118,7 @@ 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
	
	 Charlie Marsh
						Charlie Marsh