diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index fe4ea54df47..c254072f63f 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -1,6 +1,7 @@ use std::collections::BTreeMap; use std::fmt::{self, Debug, Formatter}; -use std::path::PathBuf; +use std::io::BufReader; +use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; @@ -26,7 +27,7 @@ use uv_distribution_types::{ }; use uv_extract::hash::Hasher; use uv_git::{GIT_LFS, GitError, GitHttpSettings, GitResolver, Reporter}; -use uv_metadata::{read_metadata_async_seek, read_metadata_async_stream}; +use uv_metadata::{read_archive_metadata, read_metadata_async_stream}; use uv_normalize::PackageName; use uv_pep440::{Version, VersionSpecifiers}; use uv_pep508::MarkerEnvironment; @@ -903,22 +904,8 @@ impl RegistryClient { match location { WheelLocation::Path(path) => { - let file = fs_err::tokio::File::open(&path) - .await - .map_err(ErrorKind::Io)?; - let reader = tokio::io::BufReader::new(file); - let contents = read_metadata_async_seek(&wheel.filename, reader) - .await - .map_err(|err| { - ErrorKind::Metadata(path.to_string_lossy().to_string(), err) - })?; - ResolutionMetadata::parse_metadata(&contents).map_err(|err| { - ErrorKind::MetadataParseError( - wheel.filename.clone(), - built_dist.to_string(), - Box::new(err), - ) - })? + Self::wheel_metadata_local(&path, &path, &wheel.filename, built_dist) + .await? } WheelLocation::Url(url) => { self.wheel_metadata_registry(wheel, &url, capabilities) @@ -937,22 +924,13 @@ impl RegistryClient { .await? } BuiltDist::Path(wheel) => { - let file = fs_err::tokio::File::open(wheel.install_path.as_ref()) - .await - .map_err(ErrorKind::Io)?; - let reader = tokio::io::BufReader::new(file); - let contents = read_metadata_async_seek(&wheel.filename, reader) - .await - .map_err(|err| { - ErrorKind::Metadata(wheel.install_path.to_string_lossy().to_string(), err) - })?; - ResolutionMetadata::parse_metadata(&contents).map_err(|err| { - ErrorKind::MetadataParseError( - wheel.filename.clone(), - built_dist.to_string(), - Box::new(err), - ) - })? + Self::wheel_metadata_local( + &wheel.install_path, + &wheel.install_path, + &wheel.filename, + built_dist, + ) + .await? } BuiltDist::GitPath(wheel) => { // Fetch the Git repository. @@ -982,22 +960,13 @@ impl RegistryClient { } // Read the metadata. - let file = fs_err::tokio::File::open(fetch.path().join(&wheel.install_path)) - .await - .map_err(ErrorKind::Io)?; - let reader = tokio::io::BufReader::new(file); - let contents = read_metadata_async_seek(&wheel.filename, reader) - .await - .map_err(|err| { - ErrorKind::Metadata(wheel.install_path.to_string_lossy().to_string(), err) - })?; - ResolutionMetadata::parse_metadata(&contents).map_err(|err| { - ErrorKind::MetadataParseError( - wheel.filename.clone(), - built_dist.to_string(), - Box::new(err), - ) - })? + Self::wheel_metadata_local( + &fetch.path().join(&wheel.install_path), + &wheel.install_path, + &wheel.filename, + built_dist, + ) + .await? } }; @@ -1011,6 +980,31 @@ impl RegistryClient { Ok(metadata) } + /// Read and parse local wheel metadata in one blocking task. + /// + /// `metadata_path` identifies the wheel in diagnostics and may be relative to a Git checkout. + async fn wheel_metadata_local( + path: &Path, + metadata_path: &Path, + filename: &WheelFilename, + built_dist: &BuiltDist, + ) -> Result { + let path = path.to_path_buf(); + let metadata_path = metadata_path.to_string_lossy().into_owned(); + let filename = filename.clone(); + let built_dist = built_dist.to_string(); + tokio::task::spawn_blocking(move || { + let file = fs_err::File::open(path).map_err(ErrorKind::Io)?; + let contents = read_archive_metadata(&filename, BufReader::new(file)) + .map_err(|err| ErrorKind::Metadata(metadata_path, err))?; + ResolutionMetadata::parse_metadata(&contents).map_err(|err| { + ErrorKind::MetadataParseError(filename, built_dist, Box::new(err)).into() + }) + }) + .await + .map_err(|err| ErrorKind::Io(err.into()))? + } + /// Fetch the metadata from a wheel file. async fn wheel_metadata_registry( &self, diff --git a/crates/uv-metadata/src/lib.rs b/crates/uv-metadata/src/lib.rs index 0c651e1690e..13bbfde659e 100644 --- a/crates/uv-metadata/src/lib.rs +++ b/crates/uv-metadata/src/lib.rs @@ -9,7 +9,7 @@ use std::io; use std::path::Path; use thiserror::Error; use tokio::io::AsyncReadExt; -use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt}; +use tokio_util::compat::FuturesAsyncReadCompatExt; use uv_distribution_filename::WheelFilename; use uv_normalize::{DistInfoName, InvalidNameError}; use uv_pypi_types::ResolutionMetadata; @@ -226,36 +226,7 @@ fn read_dist_info_metadata( fs_err::read(metadata_file).map_err(Error::Io) } -/// Read a wheel's `METADATA` file from a zip file. -pub async fn read_metadata_async_seek( - filename: &WheelFilename, - reader: impl tokio::io::AsyncRead + tokio::io::AsyncSeek + Unpin, -) -> Result, Error> { - let reader = futures::io::BufReader::new(reader.compat()); - let mut zip_reader = async_zip::base::read::seek::ZipFileReader::new(reader).await?; - - let (metadata_idx, _dist_info_prefix) = find_archive_dist_info( - filename, - zip_reader - .file() - .entries() - .iter() - .enumerate() - .filter_map(|(index, entry)| Some((index, entry.filename().as_str().ok()?))), - )?; - - // Read the contents of the `METADATA` file. - let mut contents = Vec::new(); - zip_reader - .reader_with_entry(metadata_idx) - .await? - .read_to_end_checked(&mut contents) - .await?; - - Ok(contents) -} - -/// Like [`read_metadata_async_seek`], but doesn't use seek. +/// Read and parse a wheel's `METADATA` file from a zip stream without seeking. pub async fn read_metadata_async_stream( filename: &WheelFilename, debug_path: &str, diff --git a/crates/uv-publish/src/lib.rs b/crates/uv-publish/src/lib.rs index e3215e00d62..dbd8fe80418 100644 --- a/crates/uv-publish/src/lib.rs +++ b/crates/uv-publish/src/lib.rs @@ -40,7 +40,7 @@ use uv_distribution_filename::{DistFilename, SourceDistExtension, SourceDistFile use uv_distribution_types::{IndexCapabilities, IndexUrl}; use uv_extract::hash::Hasher; use uv_fs::{ProgressReader, Simplified}; -use uv_metadata::read_metadata_async_seek; +use uv_metadata::read_archive_metadata; use uv_preview::PreviewFeature; use uv_pypi_types::{HashAlgorithm, HashDigest, Metadata23, MetadataError}; use uv_redacted::{DisplaySafeUrl, DisplaySafeUrlError}; @@ -1099,8 +1099,15 @@ async fn metadata(file: &Path, filename: &DistFilename) -> Result { - let reader = BufReader::new(File::open(&file).await?); - read_metadata_async_seek(wheel, reader).await? + let file = file.to_path_buf(); + let wheel = wheel.clone(); + return tokio::task::spawn_blocking(move || { + let reader = io::BufReader::new(fs_err::File::open(file)?); + let contents = read_archive_metadata(&wheel, reader)?; + Ok(Metadata23::parse(&contents)?) + }) + .await + .map_err(io::Error::from)?; } }; Ok(Metadata23::parse(&contents)?)