From e6126ce0dc105c329dac4d45cf74c2ea8c944882 Mon Sep 17 00:00:00 2001
From: Frost Ming <me@frostming.com>
Date: Thu, 26 Dec 2024 22:33:32 +0800
Subject: [PATCH] fix: always write slash paths to RECORD file (#10164)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Frost Ming <me@frostming.com>

<!--
Thank you for contributing to uv! To help us out with reviewing, please
consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR solves an issue on Windows that platform-specific paths are
written to the `RECORD` file when installing, which is inconsistent with
PEP 376, quoting:

> Each record is composed of three elements:
>
>the file’s path
> * a ‘/’-separated path, relative to the base location, if the file is
under the base location.
> * a ‘/’-separated path, relative to the base location, if the file is
under the installation prefix AND if the base location is a subpath of
the installation prefix.
> * an absolute path, using the local platform separator

## Test Plan

<!-- How was it tested? -->
Test case included

---------

Signed-off-by: Frost Ming <me@frostming.com>
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
---
 crates/uv-install-wheel/src/wheel.rs | 37 ++++++++++++++++++++++++++--
 crates/uv-tool/src/lib.rs            | 11 ++++-----
 2 files changed, 40 insertions(+), 8 deletions(-)

diff --git a/crates/uv-install-wheel/src/wheel.rs b/crates/uv-install-wheel/src/wheel.rs
index a349b8a79854..8218f75ae758 100644
--- a/crates/uv-install-wheel/src/wheel.rs
+++ b/crates/uv-install-wheel/src/wheel.rs
@@ -604,7 +604,7 @@ pub(crate) fn write_file_recorded(
     let hash = Sha256::new().chain_update(content.as_ref()).finalize();
     let encoded_hash = format!("sha256={}", BASE64URL_NOPAD.encode(&hash));
     record.push(RecordEntry {
-        path: relative_path.display().to_string(),
+        path: relative_path.portable_display().to_string(),
         hash: Some(encoded_hash),
         size: Some(content.as_ref().len() as u64),
     });
@@ -741,7 +741,8 @@ mod test {
     use crate::Error;
 
     use super::{
-        get_script_executable, parse_email_message_file, parse_wheel_file, read_record_file, Script,
+        get_script_executable, parse_email_message_file, parse_wheel_file, read_record_file,
+        write_installer_metadata, RecordEntry, Script,
     };
 
     #[test]
@@ -1016,4 +1017,36 @@ mod test {
 
         Ok(())
     }
+
+    #[test]
+    fn test_write_installer_metadata() {
+        let temp_dir = assert_fs::TempDir::new().unwrap();
+        let site_packages = temp_dir.path();
+        let mut record: Vec<RecordEntry> = Vec::new();
+        temp_dir
+            .child("foo-0.1.0.dist-info")
+            .create_dir_all()
+            .unwrap();
+        write_installer_metadata(
+            site_packages,
+            "foo-0.1.0",
+            true,
+            None,
+            None,
+            Some("uv"),
+            &mut record,
+        )
+        .unwrap();
+        let expected = [
+            "foo-0.1.0.dist-info/REQUESTED",
+            "foo-0.1.0.dist-info/INSTALLER",
+        ]
+        .map(ToString::to_string)
+        .to_vec();
+        let actual = record
+            .into_iter()
+            .map(|entry| entry.path)
+            .collect::<Vec<String>>();
+        assert_eq!(expected, actual);
+    }
 }
diff --git a/crates/uv-tool/src/lib.rs b/crates/uv-tool/src/lib.rs
index 94e67a6cceee..081be9a72511 100644
--- a/crates/uv-tool/src/lib.rs
+++ b/crates/uv-tool/src/lib.rs
@@ -418,12 +418,11 @@ pub fn entrypoint_paths(
         };
 
         let absolute_path = layout.scheme.scripts.join(path_in_scripts);
-        let script_name = entry
-            .path
-            .rsplit(std::path::MAIN_SEPARATOR)
-            .next()
-            .unwrap_or(&entry.path)
-            .to_string();
+        let script_name = relative_path
+            .file_name()
+            .and_then(|filename| filename.to_str())
+            .map(ToString::to_string)
+            .unwrap_or(entry.path);
         entrypoints.push((script_name, absolute_path));
     }