From a6392a11f9efc7a2227730b2da0a0a922b2c9dd8 Mon Sep 17 00:00:00 2001 From: Andrew Stein Date: Fri, 6 Feb 2026 13:42:11 -0500 Subject: [PATCH 1/2] Normalize wheel `RECORD` paths (on Windows) --- src/module_writer/wheel_writer.rs | 8 ++++++-- tests/common/other.rs | 13 +++++++++++++ tests/run.rs | 11 +++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/module_writer/wheel_writer.rs b/src/module_writer/wheel_writer.rs index c3874f888..541f9a1d5 100644 --- a/src/module_writer/wheel_writer.rs +++ b/src/module_writer/wheel_writer.rs @@ -110,11 +110,15 @@ impl WheelWriter { self.zip.start_file_from_path(&record_filename, options)?; for (filename, (hash, len)) in self.record { - let filename = filename.to_string_lossy(); + let filename = filename.to_string_lossy().replace("\\", "/"); writeln!(self.zip, "{filename},sha256={hash},{len}")?; } // Write the record for the RECORD file itself - writeln!(self.zip, "{},,", record_filename.display())?; + writeln!( + self.zip, + "{},,", + record_filename.to_string_lossy().replace("\\", "/") + )?; let file = self.zip.finish()?; Ok(file.into_path()) diff --git a/tests/common/other.rs b/tests/common/other.rs index e67dc0c3a..a293393bb 100644 --- a/tests/common/other.rs +++ b/tests/common/other.rs @@ -307,6 +307,19 @@ pub fn check_wheel_mtimes( Ok(()) } +pub fn check_wheel_paths( + package: impl AsRef, + record_file: &str, + unique_name: &str, +) -> Result<()> { + let mut wheel = build_wheel_files(package, unique_name)?; + let mut f = wheel.by_path(record_file)?; + let mut s = String::new(); + f.read_to_string(&mut s)?; + assert!(!s.contains("\\")); + Ok(()) +} + pub fn check_wheel_files( package: impl AsRef, expected_files: Vec<&str>, diff --git a/tests/run.rs b/tests/run.rs index 26819c125..0066a181f 100644 --- a/tests/run.rs +++ b/tests/run.rs @@ -951,6 +951,17 @@ fn pyo3_mixed_include_exclude_wheel_files() { )) } +// Tests that paths in the wheel `RECORD` use `/` instead of `\\`. Even +// (especially, exclusively) on Windows. +#[test] +fn pyo3_wheel_record_has_normalized_paths() { + handle_result(other::check_wheel_paths( + "test-crates/pyo3-mixed-include-exclude", + "pyo3_mixed_include_exclude-2.1.3.dist-info/RECORD", + "wheel-files-pyo3-mixed-include-exclude", + )) +} + #[test] fn workspace_sdist() { handle_result(other::test_source_distribution( From 0312f0558ce6020a55dce9194c65ab1e6a39659b Mon Sep 17 00:00:00 2001 From: messense Date: Sat, 7 Feb 2026 19:05:01 +0800 Subject: [PATCH 2/2] Use path-slash --- src/module_writer/wheel_writer.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/module_writer/wheel_writer.rs b/src/module_writer/wheel_writer.rs index 541f9a1d5..f47ee4c24 100644 --- a/src/module_writer/wheel_writer.rs +++ b/src/module_writer/wheel_writer.rs @@ -8,6 +8,7 @@ use std::path::PathBuf; use anyhow::Context as _; use anyhow::Result; use fs_err::File; +use path_slash::PathBufExt as _; use tracing::debug; use zip::ZipWriter; use zip::write::SimpleFileOptions; @@ -110,15 +111,11 @@ impl WheelWriter { self.zip.start_file_from_path(&record_filename, options)?; for (filename, (hash, len)) in self.record { - let filename = filename.to_string_lossy().replace("\\", "/"); + let filename = filename.to_slash_lossy(); writeln!(self.zip, "{filename},sha256={hash},{len}")?; } // Write the record for the RECORD file itself - writeln!( - self.zip, - "{},,", - record_filename.to_string_lossy().replace("\\", "/") - )?; + writeln!(self.zip, "{},,", record_filename.to_slash_lossy())?; let file = self.zip.finish()?; Ok(file.into_path())