diff --git a/crates/uv-cache/src/lib.rs b/crates/uv-cache/src/lib.rs index af28bb26c..d1feeddf8 100644 --- a/crates/uv-cache/src/lib.rs +++ b/crates/uv-cache/src/lib.rs @@ -998,7 +998,7 @@ impl CacheBucket { Self::Interpreter => "interpreter-v4", // Note that when bumping this, you'll also need to bump it // in `crates/uv/tests/it/cache_clean.rs`. - Self::Simple => "simple-v16", + Self::Simple => "simple-v17", // Note that when bumping this, you'll also need to bump it // in `crates/uv/tests/it/cache_prune.rs`. Self::Wheels => "wheels-v5", diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index b53e1ed9a..b1d62edb1 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -21,8 +21,9 @@ use uv_configuration::KeyringProviderType; use uv_configuration::{IndexStrategy, TrustedHost}; use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename}; use uv_distribution_types::{ - BuiltDist, File, FileLocation, IndexCapabilities, IndexFormat, IndexLocations, - IndexMetadataRef, IndexStatusCodeDecision, IndexStatusCodeStrategy, IndexUrl, IndexUrls, Name, + BuiltDist, File, FileLocation, IndexCapabilities, IndexEntryFilename, IndexFormat, + IndexLocations, IndexMetadataRef, IndexStatusCodeDecision, IndexStatusCodeStrategy, IndexUrl, + IndexUrls, Name, VariantsJson, }; use uv_metadata::{read_metadata_async_seek, read_metadata_async_stream}; use uv_normalize::PackageName; @@ -1050,15 +1051,21 @@ impl FlatIndexCache { pub struct VersionFiles { pub wheels: Vec, pub source_dists: Vec, + pub variant_jsons: Vec, } impl VersionFiles { - fn push(&mut self, filename: DistFilename, file: File) { + fn push(&mut self, filename: IndexEntryFilename, file: File) { match filename { - DistFilename::WheelFilename(name) => self.wheels.push(VersionWheel { name, file }), - DistFilename::SourceDistFilename(name) => { + IndexEntryFilename::DistFilename(DistFilename::WheelFilename(name)) => { + self.wheels.push(VersionWheel { name, file }) + } + IndexEntryFilename::DistFilename(DistFilename::SourceDistFilename(name)) => { self.source_dists.push(VersionSourceDist { name, file }); } + IndexEntryFilename::VariantJson(variants_json) => { + self.variant_jsons.push(VersionVariantJson { name: variants_json, file }); + } } } @@ -1088,6 +1095,13 @@ pub struct VersionSourceDist { pub file: File, } +#[derive(Debug, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)] +#[rkyv(derive(Debug))] +pub struct VersionVariantJson { + pub name: VariantsJson, + pub file: File, +} + #[derive(Default, Debug, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)] #[rkyv(derive(Debug))] pub struct SimpleMetadata(Vec); @@ -1112,15 +1126,11 @@ impl SimpleMetadata { // Group the distributions by version and kind for file in files { - let Some(filename) = DistFilename::try_from_filename(&file.filename, package_name) + let Some(filename) = IndexEntryFilename::try_from_filename(&file.filename, package_name) else { warn!("Skipping file for {package_name}: {}", file.filename); continue; }; - let version = match filename { - DistFilename::SourceDistFilename(ref inner) => &inner.version, - DistFilename::WheelFilename(ref inner) => &inner.version, - }; let file = match File::try_from(file, &base) { Ok(file) => file, Err(err) => { @@ -1129,7 +1139,7 @@ impl SimpleMetadata { continue; } }; - match map.entry(version.clone()) { + match map.entry(filename.version().clone()) { std::collections::btree_map::Entry::Occupied(mut entry) => { entry.get_mut().push(filename, file); } diff --git a/crates/uv-distribution-types/src/dist_or_variant_filename.rs b/crates/uv-distribution-types/src/dist_or_variant_filename.rs index 69e2058b2..0bbc34e23 100644 --- a/crates/uv-distribution-types/src/dist_or_variant_filename.rs +++ b/crates/uv-distribution-types/src/dist_or_variant_filename.rs @@ -1,5 +1,8 @@ +use std::str::FromStr; + use uv_distribution_filename::DistFilename; use uv_normalize::PackageName; +use uv_pep440::Version; use crate::VariantsJson; @@ -18,12 +21,29 @@ impl IndexEntryFilename { } } + pub fn version(&self) -> &Version { + match self { + Self::DistFilename(filename) => filename.version(), + Self::VariantJson(variant_json) => &variant_json.version, + } + } + /// Parse a filename as either a distribution filename or a `variants.json` filename. - #[allow(clippy::manual_map)] pub fn try_from_normalized_filename(filename: &str) -> Option { if let Some(dist_filename) = DistFilename::try_from_normalized_filename(filename) { Some(Self::DistFilename(dist_filename)) - } else if let Some(variant_json) = VariantsJson::try_from_normalized_filename(filename) { + } else if let Ok(variant_json) = VariantsJson::from_str(filename) { + Some(Self::VariantJson(variant_json)) + } else { + None + } + } + + /// Parse a filename as either a distribution filename or a `variants.json` filename. + pub fn try_from_filename(filename: &str, package_name: &PackageName) -> Option { + if let Some(dist_filename) = DistFilename::try_from_filename(filename, package_name) { + Some(Self::DistFilename(dist_filename)) + } else if let Ok(variant_json) = VariantsJson::from_str(filename) { Some(Self::VariantJson(variant_json)) } else { None diff --git a/crates/uv-distribution-types/src/variant_json.rs b/crates/uv-distribution-types/src/variant_json.rs index 4ecf55193..447d1a29a 100644 --- a/crates/uv-distribution-types/src/variant_json.rs +++ b/crates/uv-distribution-types/src/variant_json.rs @@ -1,27 +1,52 @@ -use std::str::FromStr; +use std::{fmt::Display, str::FromStr}; -use uv_normalize::PackageName; -use uv_pep440::Version; +use uv_normalize::{InvalidNameError, PackageName}; +use uv_pep440::{Version, VersionParseError}; + +#[derive(Debug, thiserror::Error)] +pub enum VariantsJsonError { + #[error("Invalid variants.json filename")] + InvalidFilename, + #[error("Invalid variants.json package name: {0}")] + InvalidName(#[from] InvalidNameError), + #[error("Invalid variants.json version: {0}")] + InvalidVersion(#[from] VersionParseError), +} /// A `--variants.json` file. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive( + Debug, Clone, PartialEq, Eq, PartialOrd, Ord, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, +)] +#[rkyv(derive(Debug))] pub struct VariantsJson { pub name: PackageName, pub version: Version, } -impl VariantsJson { +impl FromStr for VariantsJson { + type Err = VariantsJsonError; + /// Parse a `--variants.json` filename. /// /// name and version must be normalized, i.e., they don't contain dashes. - pub fn try_from_normalized_filename(filename: &str) -> Option { - let stem = filename.strip_suffix("-variants.json")?; + fn from_str(filename: &str) -> Result { + let stem = filename + .strip_suffix("-variants.json") + .ok_or(VariantsJsonError::InvalidFilename)?; - let (name, version) = stem.split_once('-')?; - let name = PackageName::from_str(name).ok()?; - let version = Version::from_str(version).ok()?; + let (name, version) = stem + .split_once('-') + .ok_or(VariantsJsonError::InvalidFilename)?; + let name = PackageName::from_str(name)?; + let version = Version::from_str(version)?; - Some(VariantsJson { name, version }) + Ok(VariantsJson { name, version }) + } +} + +impl Display for VariantsJson { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}-{}-variants.json", self.name, self.version) } } @@ -31,8 +56,7 @@ mod tests { #[test] fn variants_json_parsing() { - let variant = - VariantsJson::try_from_normalized_filename("numpy-1.21.0-variants.json").unwrap(); + let variant = VariantsJson::from_str("numpy-1.21.0-variants.json").unwrap(); assert_eq!(variant.name.as_str(), "numpy"); assert_eq!(variant.version.to_string(), "1.21.0"); } diff --git a/crates/uv/tests/it/cache_clean.rs b/crates/uv/tests/it/cache_clean.rs index 857024947..0678ed996 100644 --- a/crates/uv/tests/it/cache_clean.rs +++ b/crates/uv/tests/it/cache_clean.rs @@ -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-v16") + .child("simple-v17") .child("pypi") .child("iniconfig.rkyv"); assert!( @@ -125,7 +125,7 @@ fn clean_package_index() -> Result<()> { // Assert that the `.rkyv` file is created for `iniconfig`. let rkyv = context .cache_dir - .child("simple-v16") + .child("simple-v17") .child("index") .child("e8208120cae3ba69") .child("iniconfig.rkyv");