diff --git a/crates/uv-publish/src/lib.rs b/crates/uv-publish/src/lib.rs index b412907b60f4..f832079ab181 100644 --- a/crates/uv-publish/src/lib.rs +++ b/crates/uv-publish/src/lib.rs @@ -198,9 +198,15 @@ impl PublishSendError { } } +/// Collect the source distributions and wheels for publishing. +/// +/// Returns the path, the raw filename and the parsed filename. The raw filename is a fixup for +/// caused by +/// in combination with +/// pub fn files_for_publishing( paths: Vec, -) -> Result, PublishError> { +) -> Result, PublishError> { let mut seen = FxHashSet::default(); let mut files = Vec::new(); for path in paths { @@ -212,15 +218,19 @@ pub fn files_for_publishing( if !seen.insert(dist.clone()) { continue; } - let Some(filename) = dist.file_name().and_then(|filename| filename.to_str()) else { + let Some(filename) = dist + .file_name() + .and_then(|filename| filename.to_str()) + .map(ToString::to_string) + else { continue; }; if filename == ".gitignore" { continue; } - let filename = DistFilename::try_from_normalized_filename(filename) + let dist_filename = DistFilename::try_from_normalized_filename(&filename) .ok_or_else(|| PublishError::InvalidFilename(dist.clone()))?; - files.push((dist, filename)); + files.push((dist, filename, dist_filename)); } } // TODO(konsti): Should we sort those files, e.g. wheels before sdists because they are more @@ -287,6 +297,7 @@ pub async fn check_trusted_publishing( /// Implements a custom retry flow since the request isn't cloneable. pub async fn upload( file: &Path, + raw_filename: &str, filename: &DistFilename, registry: &Url, client: &ClientWithMiddleware, @@ -305,6 +316,7 @@ pub async fn upload( attempt += 1; let (request, idx) = build_request( file, + raw_filename, filename, registry, client, @@ -489,6 +501,7 @@ async fn form_metadata( /// Returns the request and the reporter progress bar id. async fn build_request( file: &Path, + raw_filename: &str, filename: &DistFilename, registry: &Url, client: &ClientWithMiddleware, @@ -510,7 +523,8 @@ async fn build_request( // Stream wrapping puts a static lifetime requirement on the reader (so the request doesn't have // a lifetime) -> callback needs to be static -> reporter reference needs to be Arc'd. let file_reader = Body::wrap_stream(ReaderStream::new(reader)); - let part = Part::stream(file_reader).file_name(filename.to_string()); + // See [`files_for_publishing`] on `raw_filename` + let part = Part::stream(file_reader).file_name(raw_filename.to_string()); form = form.part("content", part); let url = if let Some(username) = username { diff --git a/crates/uv-publish/src/tests.rs b/crates/uv-publish/src/tests.rs index 26b052ef048a..aac1762600b1 100644 --- a/crates/uv-publish/src/tests.rs +++ b/crates/uv-publish/src/tests.rs @@ -21,9 +21,9 @@ impl Reporter for DummyReporter { /// Snapshot the data we send for an upload request for a source distribution. #[tokio::test] async fn upload_request_source_dist() { - let filename = "tqdm-999.0.0.tar.gz"; - let file = PathBuf::from("../../scripts/links/").join(filename); - let filename = DistFilename::try_from_normalized_filename(filename).unwrap(); + let raw_filename = "tqdm-999.0.0.tar.gz"; + let file = PathBuf::from("../../scripts/links/").join(raw_filename); + let filename = DistFilename::try_from_normalized_filename(raw_filename).unwrap(); let form_metadata = form_metadata(&file, &filename).await.unwrap(); @@ -81,6 +81,7 @@ async fn upload_request_source_dist() { let (request, _) = build_request( &file, + raw_filename, &filename, &Url::parse("https://example.org/upload").unwrap(), &BaseClientBuilder::new().build().client(), @@ -129,10 +130,10 @@ async fn upload_request_source_dist() { /// Snapshot the data we send for an upload request for a wheel. #[tokio::test] async fn upload_request_wheel() { - let filename = + let raw_filename = "tqdm-4.66.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl"; - let file = PathBuf::from("../../scripts/links/").join(filename); - let filename = DistFilename::try_from_normalized_filename(filename).unwrap(); + let file = PathBuf::from("../../scripts/links/").join(raw_filename); + let filename = DistFilename::try_from_normalized_filename(raw_filename).unwrap(); let form_metadata = form_metadata(&file, &filename).await.unwrap(); @@ -225,6 +226,7 @@ async fn upload_request_wheel() { let (request, _) = build_request( &file, + raw_filename, &filename, &Url::parse("https://example.org/upload").unwrap(), &BaseClientBuilder::new().build().client(), diff --git a/crates/uv/src/commands/publish.rs b/crates/uv/src/commands/publish.rs index a39f73768cf2..411b97c7d7ec 100644 --- a/crates/uv/src/commands/publish.rs +++ b/crates/uv/src/commands/publish.rs @@ -85,7 +85,7 @@ pub(crate) async fn publish( bail!("You need to provide a username with a password, use `--token` for tokens"); } - for (file, filename) in files { + for (file, raw_filename, filename) in files { let size = fs_err::metadata(&file)?.len(); let (bytes, unit) = human_readable_bytes(size); writeln!( @@ -97,6 +97,7 @@ pub(crate) async fn publish( let reporter = PublishReporter::single(printer); let uploaded = upload( &file, + &raw_filename, &filename, &publish_url, &upload_client.client(),