From 12a96ad4227b82420443d80e19ea997af27f7284 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 22 Feb 2024 16:10:02 +0100 Subject: [PATCH] Win Trampoline: Use Python executable path encoded in binary (#1803) --- .github/workflows/ci.yml | 8 +- crates/install-wheel-rs/src/wheel.rs | 32 +- crates/uv-trampoline/.cargo/config.toml | 3 + crates/uv-trampoline/Cargo.lock | 20 +- crates/uv-trampoline/Cargo.toml | 10 +- crates/uv-trampoline/README.md | 48 ++- crates/uv-trampoline/src/bounce.rs | 365 ++++++++++++++---- crates/uv-trampoline/src/diagnostics.rs | 79 ++-- crates/uv-trampoline/src/helpers.rs | 39 -- .../uv-trampoline-aarch64-console.exe | Bin 16384 -> 18432 bytes .../trampolines/uv-trampoline-aarch64-gui.exe | Bin 16896 -> 18944 bytes .../uv-trampoline-x86_64-console.exe | Bin 16896 -> 18944 bytes .../trampolines/uv-trampoline-x86_64-gui.exe | Bin 17920 -> 19456 bytes crates/uv/tests/common/mod.rs | 2 +- crates/uv/tests/pip_install.rs | 112 ++++++ .../simple_launcher-0.1.0-py3-none-any.whl | Bin 0 -> 1386 bytes 16 files changed, 506 insertions(+), 212 deletions(-) create mode 100644 crates/uv-trampoline/.cargo/config.toml create mode 100644 scripts/wheels/simple_launcher-0.1.0-py3-none-any.whl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d49931868..d5fe8f2c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: - branches: [main] + branches: [ main ] pull_request: workflow_dispatch: @@ -32,7 +32,7 @@ jobs: name: "cargo clippy" strategy: matrix: - os: [ubuntu-latest, windows-latest] + os: [ ubuntu-latest, windows-latest ] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -113,7 +113,7 @@ jobs: workspaces: "crates/uv-trampoline" - name: "Clippy" working-directory: crates/uv-trampoline - run: cargo clippy --all-features --locked -- -D warnings + run: cargo clippy --all-features --locked --target x86_64-pc-windows-msvc -- -D warnings - name: "Build" working-directory: crates/uv-trampoline - run: cargo build --release -Z build-std=core,panic_abort,alloc -Z build-std-features=compiler-builtins-mem --target x86_64-pc-windows-msvc + run: cargo build --release --target x86_64-pc-windows-msvc diff --git a/crates/install-wheel-rs/src/wheel.rs b/crates/install-wheel-rs/src/wheel.rs index fa6ca6dcd..88a8fb91f 100644 --- a/crates/install-wheel-rs/src/wheel.rs +++ b/crates/install-wheel-rs/src/wheel.rs @@ -33,6 +33,8 @@ use crate::{find_dist_info, Error}; /// `#!/usr/bin/env python` pub const SHEBANG_PYTHON: &str = "#!/usr/bin/env python"; +const LAUNCHER_MAGIC_NUMBER: [u8; 4] = [b'U', b'V', b'U', b'V']; + #[cfg(all(windows, target_arch = "x86_64"))] const LAUNCHER_X86_64_GUI: &[u8] = include_bytes!("../../uv-trampoline/trampolines/uv-trampoline-x86_64-gui.exe"); @@ -281,19 +283,7 @@ fn unpack_wheel_files( } fn get_shebang(location: &InstallLocation>) -> String { - let path = location.python().to_string_lossy().to_string(); - let path = if cfg!(windows) { - // https://stackoverflow.com/a/50323079 - const VERBATIM_PREFIX: &str = r"\\?\"; - if let Some(stripped) = path.strip_prefix(VERBATIM_PREFIX) { - stripped.to_string() - } else { - path - } - } else { - path - }; - format!("#!{path}") + format!("#!{}", location.python().normalized().display()) } /// A Windows script is a minimal .exe launcher binary with the python entrypoint script appended as @@ -305,6 +295,7 @@ fn get_shebang(location: &InstallLocation>) -> String { pub(crate) fn windows_script_launcher( launcher_python_script: &str, is_gui: bool, + installation: &InstallLocation>, ) -> Result, Error> { // This method should only be called on Windows, but we avoid `#[cfg(windows)]` to retain // compilation on all platforms. @@ -352,9 +343,20 @@ pub(crate) fn windows_script_launcher( archive.finish().expect(error_msg); } + let python = installation.python(); + let python_path = python.normalized().to_string_lossy(); + let mut launcher: Vec = Vec::with_capacity(launcher_bin.len() + payload.len()); launcher.extend_from_slice(launcher_bin); launcher.extend_from_slice(&payload); + launcher.extend_from_slice(python_path.as_bytes()); + launcher.extend_from_slice( + &u32::try_from(python_path.as_bytes().len()) + .expect("File Path to be smaller than 4GB") + .to_le_bytes(), + ); + launcher.extend_from_slice(&LAUNCHER_MAGIC_NUMBER); + Ok(launcher) } @@ -393,7 +395,7 @@ pub(crate) fn write_script_entrypoints( write_file_recorded( site_packages, &entrypoint_relative, - &windows_script_launcher(&launcher_python_script, is_gui)?, + &windows_script_launcher(&launcher_python_script, is_gui, location)?, record, )?; } else { @@ -949,7 +951,7 @@ pub fn parse_key_value_file( /// /// Wheel 1.0: #[allow(clippy::too_many_arguments)] -#[instrument(skip_all, fields(name = %filename.name))] +#[instrument(skip_all, fields(name = % filename.name))] pub fn install_wheel( location: &InstallLocation, reader: impl Read + Seek, diff --git a/crates/uv-trampoline/.cargo/config.toml b/crates/uv-trampoline/.cargo/config.toml new file mode 100644 index 000000000..e21b0adad --- /dev/null +++ b/crates/uv-trampoline/.cargo/config.toml @@ -0,0 +1,3 @@ +[unstable] +build-std = ["core", "panic_abort", "alloc", "std"] +build-std-features = ["compiler-builtins-mem"] diff --git a/crates/uv-trampoline/Cargo.lock b/crates/uv-trampoline/Cargo.lock index bbff1a39c..24f4c8fd4 100644 --- a/crates/uv-trampoline/Cargo.lock +++ b/crates/uv-trampoline/Cargo.lock @@ -17,16 +17,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "uv-trampoline" -version = "0.1.0" -dependencies = [ - "embed-manifest", - "ufmt", - "ufmt-write", - "windows-sys", -] - [[package]] name = "quote" version = "1.0.35" @@ -80,6 +70,16 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "uv-trampoline" +version = "0.1.0" +dependencies = [ + "embed-manifest", + "ufmt", + "ufmt-write", + "windows-sys", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/crates/uv-trampoline/Cargo.toml b/crates/uv-trampoline/Cargo.toml index 124ab6dc7..3fac5d90f 100644 --- a/crates/uv-trampoline/Cargo.toml +++ b/crates/uv-trampoline/Cargo.toml @@ -43,15 +43,7 @@ windows-sys = { version = "0.52.0", features = [ "Win32_System_WindowsProgramming", "Win32_UI_WindowsAndMessaging", ] } -# This provides implementations of memcpy, memset, etc., which the compiler assumes -# are available. But there's also a hidden copy of this crate inside `core`/`alloc`, -# and they may or may not conflict depending on how clever the linker is feeling. -# The issue is that the hidden copy doesn't have the "mem" feature enabled, and we -# need it. So two options: -# - Uncomment this, and cross fingers that it doesn't cause conflicts -# - Use -Zbuild-std=... -Zbuild-std-features=compiler-builtins-mem, which enables -# the mem feature on the built-in builtins. -#compiler_builtins = { version = "*", features = ["mem"]} + ufmt-write = "0.1.0" ufmt = "0.2.0" diff --git a/crates/uv-trampoline/README.md b/crates/uv-trampoline/README.md index 1abaac693..6163ff809 100644 --- a/crates/uv-trampoline/README.md +++ b/crates/uv-trampoline/README.md @@ -1,6 +1,7 @@ # Windows trampolines -This is a fork of [posy trampolines](https://github.com/njsmith/posy/tree/dda22e6f90f5fefa339b869dd2bbe107f5b48448/src/trampolines/windows-trampolines/posy-trampoline). +This is a fork +of [posy trampolines](https://github.com/njsmith/posy/tree/dda22e6f90f5fefa339b869dd2bbe107f5b48448/src/trampolines/windows-trampolines/posy-trampoline). # What is this? @@ -13,27 +14,33 @@ That's what this does: it's a generic "trampoline" that lets us generate custom `.exe`s for arbitrary Python scripts, and when invoked it bounces to invoking `python ` instead. - # How do you use it? Basically, this looks up `python.exe` (for console programs) or `pythonw.exe` (for GUI programs) in the adjacent directory, and invokes `python[w].exe path\to\the\`. -The intended use is: take your Python script, name it `__main__.py`, and pack it -into a `.zip` file. Then concatenate that `.zip` file onto the end of one of our -prebuilt `.exe`s. +The intended use is: + +* take your Python script, name it `__main__.py`, and pack it + into a `.zip` file. Then concatenate that `.zip` file onto the end of one of our + prebuilt `.exe`s. +* After the zip file content, write the path to the Python executable that the script uses to run + the Python script as UTF-8 encoded string, followed by the path's length as a 32-bit little-endian + integer. +* At the very end, write the magic number `UVUV` in bytes. + +| `launcher.exe` | +|:---------------------------:| +| `` | +| `` | +| `` | +| `` | Then when you run `python` on the `.exe`, it will see the `.zip` trailer at the end of the `.exe`, and automagically look inside to find and execute `__main__.py`. Easy-peasy. -(TODO: we should probably make the Python-finding logic slightly more flexible -at some point -- in particular to support more conventional venv-style -installation where you find `python` by looking in the directory next to the -trampoline `.exe` -- but this is good enough to get started.) - - # Why does this exist? I probably could have used Vinay's C++ implementation from `distlib`, but what's @@ -47,7 +54,6 @@ Python-finding logic we want. But mostly it was just an interesting challenge. This does owe a *lot* to the `distlib` implementation though. The overall logic is copied more-or-less directly. - # Anything I should know for hacking on this? In order to minimize binary size, this uses `#![no_std]`, `panic="abort"`, and @@ -64,7 +70,7 @@ this: Though uh, this does mean that literally all of our code is `unsafe`. Sorry! - `runtime.rs` has the core glue to get panicking, heap allocation, and linking - working. + working. - `diagnostics.rs` uses `ufmt` and some cute Windows tricks to get a convenient version of `eprintln!` that works without `std`, and automatically prints to @@ -85,7 +91,6 @@ Miscellaneous tips: `.unwrap_unchecked()` avoids this. Similar for `slice[idx]` vs `slice.get_unchecked(idx)`. - # How do you build this stupid thing? Building this can be frustrating, because the low-level compiler/runtime @@ -99,15 +104,8 @@ might not realize that, and still emit references to the unwinding helper `__CxxFrameHandler3`. And then the linker blows up because that symbol doesn't exist. -Two approaches that are reasonably likely to work: - -- Uncomment `compiler-builtins` in `Cargo.toml`, and build normally: `cargo - build --profile release`. - -- Leave `compiler-builtins` commented-out, and build like: `cargo build - --release -Z build-std=core,panic_abort,alloc -Z - build-std-features=compiler-builtins-mem --target x86_64-pc-windows-msvc` - +`cargo build --release --target x86_64-pc-windows-msvc` +or `cargo build --release --target aarch64-pc-windows-msvc` Hopefully in the future as `#![no_std]` develops, this will get smoother. @@ -125,6 +123,6 @@ rustup target add aarch64-pc-windows-msvc ``` ```shell -cargo +nightly xwin build --release -Z build-std=core,panic_abort,alloc -Z build-std-features=compiler-builtins-mem --target x86_64-pc-windows-msvc -cargo +nightly xwin build --release -Z build-std=core,panic_abort,alloc -Z build-std-features=compiler-builtins-mem --target aarch64-pc-windows-msvc +cargo +nightly xwin build --release --target x86_64-pc-windows-msvc +cargo +nightly xwin build --release --target aarch64-pc-windows-msvc ``` diff --git a/crates/uv-trampoline/src/bounce.rs b/crates/uv-trampoline/src/bounce.rs index 690ce5bfb..cfc87ec84 100644 --- a/crates/uv-trampoline/src/bounce.rs +++ b/crates/uv-trampoline/src/bounce.rs @@ -1,10 +1,16 @@ +use alloc::string::String; use alloc::{ffi::CString, vec, vec::Vec}; use core::mem::MaybeUninit; use core::{ ffi::CStr, + mem, ptr::{addr_of, addr_of_mut, null, null_mut}, }; +use windows_sys::Win32::Storage::FileSystem::{ + CreateFileA, GetFileSizeEx, ReadFile, SetFilePointerEx, FILE_ATTRIBUTE_NORMAL, FILE_BEGIN, + FILE_SHARE_READ, OPEN_EXISTING, +}; use windows_sys::Win32::{ Foundation::*, System::{ @@ -18,7 +24,11 @@ use windows_sys::Win32::{ }; use crate::helpers::SizeOf; -use crate::{c, check, eprintln}; +use crate::{c, eprintln, format}; + +const MAGIC_NUMBER: [u8; 4] = [b'U', b'V', b'U', b'V']; +const PATH_LEN_SIZE: usize = mem::size_of::(); +const MAX_PATH_LEN: u32 = 32 * 1024; fn getenv(name: &CStr) -> Option { unsafe { @@ -38,9 +48,9 @@ fn getenv(name: &CStr) -> Option { } /// Transform ` ` to `python `. -fn make_child_cmdline(is_gui: bool) -> CString { +fn make_child_cmdline() -> CString { let executable_name: CString = executable_filename(); - let python_exe = find_python_exe(is_gui, &executable_name); + let python_exe = find_python_exe(&executable_name); let mut child_cmdline = Vec::::new(); push_quoted_path(&python_exe, &mut child_cmdline); @@ -59,12 +69,14 @@ fn make_child_cmdline(is_gui: bool) -> CString { // Helpful when debugging trampline issues // eprintln!( // "executable_name: '{}'\nnew_cmdline: {}", - // core::str::from_utf8(executable_name.to_bytes(), - // core::str::from_utf8(child_cmdline.as_slice()) + // core::str::from_utf8(executable_name.to_bytes()).unwrap(), + // core::str::from_utf8(child_cmdline.as_slice()).unwrap() // ); - // SAFETY: We push the null termination byte at the end. - unsafe { CString::from_vec_with_nul_unchecked(child_cmdline) } + CString::from_vec_with_nul(child_cmdline).unwrap_or_else(|_| { + eprintln!("Child command line is not correctly null terminated."); + exit_with_status(1) + }) } fn push_quoted_path(path: &CStr, command: &mut Vec) { @@ -94,18 +106,18 @@ fn executable_filename() -> CString { // That's the error condition because len doesn't include the trailing null byte if len as usize == buffer.len() { - unsafe { - let last_error = GetLastError(); - match last_error { - ERROR_INSUFFICIENT_BUFFER => { - SetLastError(ERROR_SUCCESS); - // Try again with twice the size - buffer.resize(buffer.len() * 2, 0); - } - err => { - eprintln!("Failed to get executable name: code {}", err); - ExitProcess(1); - } + let last_error = unsafe { GetLastError() }; + match last_error { + ERROR_INSUFFICIENT_BUFFER => { + unsafe { SetLastError(ERROR_SUCCESS) }; + // Try again with twice the size + buffer.resize(buffer.len() * 2, 0); + } + err => { + print_last_error_and_exit(&format!( + "Failed to get executable name (code: {})", + err + )); } } } else { @@ -114,34 +126,156 @@ fn executable_filename() -> CString { } } - unsafe { CString::from_vec_with_nul_unchecked(buffer) } + CString::from_vec_with_nul(buffer).unwrap_or_else(|_| { + eprintln!("Executable name is not correctly null terminated."); + exit_with_status(1) + }) } -/// The scripts are in the same directory as the Python interpreter, so we can find Python by getting the locations of -/// the current .exe and replacing the filename with `python[w].exe`. -fn find_python_exe(is_gui: bool, executable_name: &CStr) -> CString { - // Replace the filename (the last segment of the path) with "python.exe" - // Assumption: We are not in an encoding where a backslash byte can be part of a larger character. - let bytes = executable_name.to_bytes(); - let Some(last_backslash) = bytes.iter().rposition(|byte| *byte == b'\\') else { - eprintln!( - "Invalid current exe path (missing backslash): `{}`", - &*executable_name.to_string_lossy() - ); +/// Reads the executable binary from the back to find the path to the Python executable that is written +/// after the ZIP file content. +/// +/// The executable is expected to have the following format: +/// * The file must end with the magic number 'UVUV'. +/// * The last 4 bytes (little endian) are the length of the path to the Python executable. +/// * The path encoded as UTF-8 comes right before the length +/// +/// # Panics +/// If there's any IO error, or the file does not conform to the specified format. +fn find_python_exe(executable_name: &CStr) -> CString { + let file_handle = expect_result( unsafe { - ExitProcess(1); + CreateFileA( + executable_name.as_ptr() as _, + GENERIC_READ, + FILE_SHARE_READ, + null(), + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + 0, + ) + }, + INVALID_HANDLE_VALUE, + || { + format!( + "Failed to open executable '{}'", + &*executable_name.to_string_lossy(), + ) + }, + ); + + let mut file_size: i64 = 0; + // `SetFilePointerEx` supports setting the file pointer from the back, but pointing it past the file's start + // results in an error. That's why we need to know the file size to avoid ever seeking past the start of the file. + expect_result( + unsafe { GetFileSizeEx(file_handle, &mut file_size) }, + 0, + || { + format!( + "Failed to get the size of the executable '{}'", + &*executable_name.to_string_lossy(), + ) + }, + ); + + // Start with a size of 1024 bytes which should be enough for most paths but avoids reading the + // entire file. + let mut buffer: Vec = Vec::new(); + let mut bytes_to_read = 1024.min(u32::try_from(file_size).unwrap_or(u32::MAX)); + + let path = loop { + // SAFETY: Casting to usize is safe because we only support 64bit systems where usize is guaranteed to be larger than u32. + buffer.resize(bytes_to_read as usize, 0); + + expect_result( + unsafe { + SetFilePointerEx( + file_handle, + file_size - i64::from(bytes_to_read), + null_mut(), + FILE_BEGIN, + ) + }, + 0, + || String::from("Failed to set the file pointer to the end of the file."), + ); + + let mut read_bytes = 0u32; + + expect_result( + unsafe { + ReadFile( + file_handle, + buffer.as_mut_ptr() as *mut _, + bytes_to_read, + &mut read_bytes, + null_mut(), + ) + }, + 0, + || String::from("Failed to read the executable file"), + ); + + // Truncate the buffer to the actual number of bytes read. + buffer.truncate(read_bytes as usize); + + if !buffer.ends_with(&MAGIC_NUMBER) { + eprintln!("Magic number 'UVUV' not found at the end of the file. Did you append the magic number, the length and the path to the python executable at the end of the file?"); + exit_with_status(1); + } + + // Remove the magic number + buffer.truncate(buffer.len() - MAGIC_NUMBER.len()); + + let path_len = match buffer.get(buffer.len() - PATH_LEN_SIZE..) { + Some(path_len) => { + let path_len = u32::from_le_bytes(path_len.try_into().unwrap_or_else(|_| { + eprintln!("Slice length is not equal to 4 bytes"); + exit_with_status(1) + })); + + if path_len > MAX_PATH_LEN { + eprintln!("Only paths with a length up to 32KBs are supported but the python path has a length of {}.", path_len); + exit_with_status(1); + } + + // SAFETY: path len is guaranteed to be less than 32KBs + path_len as usize + } + None => { + eprintln!("Python executable length missing. Did you write the length of the path to the Python executable before the Magic number?"); + exit_with_status(1); + } + }; + + // Remove the path length + buffer.truncate(buffer.len() - PATH_LEN_SIZE); + + if let Some(path_offset) = buffer.len().checked_sub(path_len) { + buffer.drain(..path_offset); + buffer.push(b'\0'); + + break CString::from_vec_with_nul(buffer).unwrap_or_else(|_| { + eprintln!("Python executable path is not correctly null terminated."); + exit_with_status(1) + }); + } else { + // SAFETY: Casting to u32 is safe because `path_len` is guaranteed to be less than 32KBs, + // MAGIC_NUMBER is 4 bytes and PATH_LEN_SIZE is 4 bytes. + bytes_to_read = (path_len + MAGIC_NUMBER.len() + PATH_LEN_SIZE) as u32; + + if i64::from(bytes_to_read) > file_size { + eprintln!("The length of the python executable path exceeds the file size. Verify that the path length is appended to the end of the launcher script as a u32 in little endian."); + exit_with_status(1); + } } }; - let mut buffer = bytes[..last_backslash + 1].to_vec(); - buffer.extend_from_slice(if is_gui { - b"pythonw.exe" - } else { - b"python.exe" + expect_result(unsafe { CloseHandle(file_handle) }, 0, || { + String::from("Failed to close file handle") }); - buffer.push(b'\0'); - unsafe { CString::from_vec_with_nul_unchecked(buffer) } + path } fn push_arguments(output: &mut Vec) { @@ -192,22 +326,30 @@ fn make_job_object() -> HANDLE { let job = CreateJobObjectW(null(), null()); let mut job_info = MaybeUninit::::uninit(); let mut retlen = 0u32; - check!(QueryInformationJobObject( - job, - JobObjectExtendedLimitInformation, - job_info.as_mut_ptr() as *mut _, - job_info.size_of(), - &mut retlen as *mut _, - )); + expect_result( + QueryInformationJobObject( + job, + JobObjectExtendedLimitInformation, + job_info.as_mut_ptr() as *mut _, + job_info.size_of(), + &mut retlen as *mut _, + ), + 0, + || String::from("Error from QueryInformationJobObject"), + ); let mut job_info = job_info.assume_init(); job_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; job_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; - check!(SetInformationJobObject( - job, - JobObjectExtendedLimitInformation, - addr_of!(job_info) as *const _, - job_info.size_of(), - )); + expect_result( + SetInformationJobObject( + job, + JobObjectExtendedLimitInformation, + addr_of!(job_info) as *const _, + job_info.size_of(), + ), + 0, + || String::from("Error from SetInformationJobObject"), + ); job } } @@ -222,20 +364,24 @@ fn spawn_child(si: &STARTUPINFOA, child_cmdline: CString) -> HANDLE { SetHandleInformation(si.hStdError, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); } let mut child_process_info = MaybeUninit::::uninit(); - check!(CreateProcessA( - null(), - // Why does this have to be mutable? Who knows. But it's not a mistake -- - // MS explicitly documents that this buffer might be mutated by CreateProcess. - child_cmdline.into_bytes_with_nul().as_mut_ptr(), - null(), - null(), - 1, + expect_result( + CreateProcessA( + null(), + // Why does this have to be mutable? Who knows. But it's not a mistake -- + // MS explicitly documents that this buffer might be mutated by CreateProcess. + child_cmdline.as_ptr().cast_mut() as _, + null(), + null(), + 1, + 0, + null(), + null(), + addr_of!(*si), + child_process_info.as_mut_ptr(), + ), 0, - null(), - null(), - addr_of!(*si), - child_process_info.as_mut_ptr(), - )); + || String::from("Failed to spawn the python child process"), + ); let child_process_info = child_process_info.assume_init(); CloseHandle(child_process_info.hThread); child_process_info.hProcess @@ -311,7 +457,7 @@ fn clear_app_starting_state(child_handle: HANDLE) { pub fn bounce(is_gui: bool) -> ! { unsafe { - let child_cmdline = make_child_cmdline(is_gui); + let child_cmdline = make_child_cmdline(); let mut si = MaybeUninit::::uninit(); GetStartupInfoA(si.as_mut_ptr()); @@ -319,7 +465,9 @@ pub fn bounce(is_gui: bool) -> ! { let child_handle = spawn_child(&si, child_cmdline); let job = make_job_object(); - check!(AssignProcessToJobObject(job, child_handle)); + expect_result(AssignProcessToJobObject(job, child_handle), 0, || { + String::from("Error from AssignProcessToJobObject") + }); // (best effort) Close all the handles that we can close_handles(&si); @@ -345,7 +493,86 @@ pub fn bounce(is_gui: bool) -> ! { WaitForSingleObject(child_handle, INFINITE); let mut exit_code = 0u32; - check!(GetExitCodeProcess(child_handle, addr_of_mut!(exit_code))); - ExitProcess(exit_code); + expect_result( + GetExitCodeProcess(child_handle, addr_of_mut!(exit_code)), + 0, + || String::from("Error from GetExitCodeProcess"), + ); + exit_with_status(exit_code); + } +} + +/// Unwraps the result of the C call by asserting that it doesn't match the `error_code`. +/// +/// Prints the passed error message if the `actual_result` is equal to `error_code` and exits the process with status 1. +#[inline] +fn expect_result(actual_result: T, error_code: T, error_message: F) -> T +where + T: Eq, + F: FnOnce() -> String, +{ + if actual_result == error_code { + print_last_error_and_exit(&error_message()); + } + + actual_result +} + +#[cold] +fn print_last_error_and_exit(message: &str) -> ! { + use windows_sys::Win32::{ + Foundation::*, + System::Diagnostics::Debug::{ + FormatMessageA, FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, + FORMAT_MESSAGE_IGNORE_INSERTS, + }, + }; + + let err = unsafe { GetLastError() }; + eprintln!("Received error code: {}", err); + let mut msg_ptr: *mut u8 = core::ptr::null_mut(); + let size = unsafe { + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + null(), + err, + 0, + // Weird calling convention: this argument is typed as *mut u16, + // but if you pass FORMAT_MESSAGE_ALLOCATE_BUFFER then you have to + // *actually* pass in a *mut *mut u16 and just lie about the type. + // Getting Rust to do this requires some convincing. + core::ptr::addr_of_mut!(msg_ptr) as *mut _ as _, + 0, + core::ptr::null(), + ) + }; + + if size == 0 { + eprintln!( + "{}: with code {} (failed to get error message)", + message, err + ); + } else { + let reason = unsafe { + let reason = core::slice::from_raw_parts(msg_ptr, size as usize + 1); + CStr::from_bytes_with_nul_unchecked(reason) + }; + eprintln!( + "(uv internal error) {}: {}", + message, + &*reason.to_string_lossy() + ); + } + + // Note: We don't need to free the buffer here because we're going to exit anyway. + exit_with_status(1); +} + +#[cold] +fn exit_with_status(code: u32) -> ! { + unsafe { + ExitProcess(code); } } diff --git a/crates/uv-trampoline/src/diagnostics.rs b/crates/uv-trampoline/src/diagnostics.rs index 279c3412c..f4774a918 100644 --- a/crates/uv-trampoline/src/diagnostics.rs +++ b/crates/uv-trampoline/src/diagnostics.rs @@ -13,55 +13,54 @@ use windows_sys::Win32::{ UI::WindowsAndMessaging::MessageBoxA, }; -pub struct DiagnosticBuffer { - buffer: String, +#[macro_export] +macro_rules! eprintln { + ($($tt:tt)*) => {{ + $crate::diagnostics::write_diagnostic(&$crate::format!($($tt)*)); + }} } -impl DiagnosticBuffer { - pub fn new() -> DiagnosticBuffer { - DiagnosticBuffer { - buffer: String::new(), - } - } - - pub fn display(self) { - unsafe { - let handle = GetStdHandle(STD_ERROR_HANDLE); - let mut written: u32 = 0; - let mut remaining = self.buffer.as_str(); - while !remaining.is_empty() { - let ok = WriteFile( - handle, - remaining.as_ptr(), - remaining.len() as u32, - addr_of_mut!(written), - null_mut(), - ); - if ok == 0 { - let nul_terminated = CString::new(self.buffer.as_bytes()).unwrap_unchecked(); - MessageBoxA(0, nul_terminated.as_ptr() as *const _, null(), 0); - return; - } - remaining = &remaining.get_unchecked(written as usize..); - } - } - } +#[macro_export] +macro_rules! format { + ($($tt:tt)*) => {{ + let mut buffer = $crate::diagnostics::StringBuffer::default(); + _ = ufmt::uwriteln!(&mut buffer, $($tt)*); + buffer.0 + }} } -impl uWrite for DiagnosticBuffer { +#[derive(Default)] +pub(crate) struct StringBuffer(pub(crate) String); + +impl uWrite for StringBuffer { type Error = Infallible; fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { - self.buffer.push_str(s); + self.0.push_str(s); Ok(()) } } -#[macro_export] -macro_rules! eprintln { - ($($tt:tt)*) => {{ - let mut d = $crate::diagnostics::DiagnosticBuffer::new(); - _ = ufmt::uwriteln!(&mut d, $($tt)*); - d.display(); - }} +#[cold] +pub(crate) fn write_diagnostic(message: &str) { + unsafe { + let handle = GetStdHandle(STD_ERROR_HANDLE); + let mut written: u32 = 0; + let mut remaining = message; + while !remaining.is_empty() { + let ok = WriteFile( + handle, + remaining.as_ptr(), + remaining.len() as u32, + addr_of_mut!(written), + null_mut(), + ); + if ok == 0 { + let nul_terminated = CString::new(message.as_bytes()).unwrap_unchecked(); + MessageBoxA(0, nul_terminated.as_ptr() as *const _, null(), 0); + return; + } + remaining = &remaining.get_unchecked(written as usize..); + } + } } diff --git a/crates/uv-trampoline/src/helpers.rs b/crates/uv-trampoline/src/helpers.rs index 7050169d0..95f3831b8 100644 --- a/crates/uv-trampoline/src/helpers.rs +++ b/crates/uv-trampoline/src/helpers.rs @@ -10,45 +10,6 @@ impl SizeOf for T { } } -// Check result of win32 API call that returns BOOL -#[macro_export] -macro_rules! check { - ($e:expr) => { - if $e == 0 { - use windows_sys::Win32::{ - Foundation::*, - System::{ - Diagnostics::Debug::{ - FormatMessageA, FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, - FORMAT_MESSAGE_IGNORE_INSERTS, - }, - } - }; - let err = GetLastError(); - let mut msg_ptr: *mut u8 = core::ptr::null_mut(); - let size = FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER - | FORMAT_MESSAGE_FROM_SYSTEM - | FORMAT_MESSAGE_IGNORE_INSERTS, - null(), - err, - 0, - // Weird calling convention: this argument is typed as *mut u16, - // but if you pass FORMAT_MESSAGE_ALLOCATE_BUFFER then you have to - // *actually* pass in a *mut *mut u16 and just lie about the type. - // Getting Rust to do this requires some convincing. - core::ptr::addr_of_mut!(msg_ptr) as *mut _ as _, - 0, - core::ptr::null(), - ); - let msg = core::slice::from_raw_parts(msg_ptr, size as usize); - let msg = core::str::from_utf8_unchecked(msg); - $crate::eprintln!("Error: {} (from {})", msg, stringify!($e)); - ExitProcess(1); - } - } -} - // CStr literal: c!("...") #[macro_export] macro_rules! c { diff --git a/crates/uv-trampoline/trampolines/uv-trampoline-aarch64-console.exe b/crates/uv-trampoline/trampolines/uv-trampoline-aarch64-console.exe index d678453efd91ac64ec0f4fe932755e1536458ff6..cfbe3113971cee7d955a495e6b20639d553a04f2 100755 GIT binary patch delta 8783 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 delta 7161 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 diff --git a/crates/uv-trampoline/trampolines/uv-trampoline-aarch64-gui.exe b/crates/uv-trampoline/trampolines/uv-trampoline-aarch64-gui.exe index 18a19a9dbc6b6c130c876c0c0cd03a5f7e73d9fd..a406b687e6ed40690ede266a915348e77081d2e1 100755 GIT binary patch literal 18944 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 delta 7377 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 XU2TUt5A_}DKQ#U`@#ok}ea`(iVF99N diff --git a/crates/uv-trampoline/trampolines/uv-trampoline-x86_64-console.exe b/crates/uv-trampoline/trampolines/uv-trampoline-x86_64-console.exe index 9f3859b5ad446ac186fcd561da304f3fcaa4e0e2..a1e771a0eda46f6801d4b7da8909298908a3b478 100644 GIT binary patch literal 18944 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 diff --git a/crates/uv/tests/common/mod.rs b/crates/uv/tests/common/mod.rs index 10904e7fd..38696e4e1 100644 --- a/crates/uv/tests/common/mod.rs +++ b/crates/uv/tests/common/mod.rs @@ -319,7 +319,7 @@ pub fn run_and_format<'a>( let output = command .borrow_mut() .output() - .unwrap_or_else(|_| panic!("Failed to spawn {program}")); + .unwrap_or_else(|err| panic!("Failed to spawn {program}: {err}")); let mut snapshot = format!( "success: {:?}\nexit_code: {}\n----- stdout -----\n{}\n----- stderr -----\n{}", diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index f24e9fee1..5d6c2d054 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -1486,3 +1486,115 @@ fn direct_url_zip_file_bunk_permissions() -> Result<()> { Ok(()) } + +#[test] +fn launcher() -> Result<()> { + let context = TestContext::new("3.12"); + let project_root = fs_err::canonicalize(std::env::current_dir()?.join("../.."))?; + + let filters = [ + (r"(\d+m )?(\d+\.)?\d+(ms|s)", "[TIME]"), + ( + r"simple-launcher==0\.1\.0 \(from .+\.whl\)", + "simple_launcher.whl", + ), + ]; + + uv_snapshot!( + filters, + command(&context) + .arg(format!("simple_launcher@{}", project_root.join("scripts/wheels/simple_launcher-0.1.0-py3-none-any.whl").display())) + .arg("--strict"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + simple_launcher.whl + "### + ); + + let bin_path = if cfg!(windows) { "Scripts" } else { "bin" }; + + uv_snapshot!(Command::new( + context.venv.join(bin_path).join("simple_launcher") + ), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hi from the simple launcher! + + ----- stderr ----- + "###); + + Ok(()) +} + +#[test] +fn launcher_with_symlink() -> Result<()> { + let context = TestContext::new("3.12"); + let project_root = fs_err::canonicalize(std::env::current_dir()?.join("../.."))?; + + let filters = [ + (r"(\d+m )?(\d+\.)?\d+(ms|s)", "[TIME]"), + ( + r"simple-launcher==0\.1\.0 \(from .+\.whl\)", + "simple_launcher.whl", + ), + ]; + + uv_snapshot!(filters, + command(&context) + .arg(format!("simple_launcher@{}", project_root.join("scripts/wheels/simple_launcher-0.1.0-py3-none-any.whl").display())) + .arg("--strict"), + @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + simple_launcher.whl + "### + ); + + #[cfg(windows)] + if let Err(error) = std::os::windows::fs::symlink_file( + context.venv.join("Scripts\\simple_launcher.exe"), + context.temp_dir.join("simple_launcher.exe"), + ) { + if error.kind() == std::io::ErrorKind::PermissionDenied { + // Not running as an administrator or developer mode isn't enabled. + // Ignore the test + return Ok(()); + } + } + + #[cfg(unix)] + std::os::unix::fs::symlink( + context.venv.join("bin/simple_launcher"), + context.temp_dir.join("simple_launcher"), + )?; + + // Only support windows or linux + #[cfg(not(any(windows, unix)))] + return Ok(()); + + uv_snapshot!(Command::new( + context.temp_dir.join("simple_launcher") + ), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hi from the simple launcher! + + ----- stderr ----- + "###); + + Ok(()) +} diff --git a/scripts/wheels/simple_launcher-0.1.0-py3-none-any.whl b/scripts/wheels/simple_launcher-0.1.0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..3b93538622aa82df74354d2e30909f48a07642df GIT binary patch literal 1386 zcmWIWW@Zs#U|`??Vnv2%?z?stKo$r~195R?Zb43Jd`@C%UUEiik$!x9W?p7Ve7s&k zrMIVVu!i57p7STJT-fsHX%ItG)1otbgm_i^eOk9H`V-SSsVAiM(V06Kf=7>KW*zWEPj`X6B{k>!;?G6jjC- zdYHWs*k|sr=zuxEVxmEx3Q+q9%7k@amig1>5B>Se3fEzc4*gQ?d&Gxk90H$=eIJZ~xr;mo4sI!pFdm>$0Py6gNz` zvgfzRNv`=>D@;sebysyRKT&M9=lsLiO*~PDLyKQVh8%F@`im0l9=a+gf3h+%Fw6sD zH6q*<V_A~{gTuwU`JEC6IlS2&viw`K8krJLItFw&EmDzs_;8&o%hobP`5mb*d6 zT9dQaOp#J5aj9D;)3ZePrp>3|N%t%quHE)|aP-@$zPIA*&!qHA*j@eEzi;K;Wqa$* zv(4A9X`FZZOW^Ab=UnXFjyEvZChq%sS^xa!_#f#7``xx2mb^KWS?7$sjjCg&q^;C> zKfRbeuYLBn_wCSY`1;TG%bnaEObMS@0=yZSL>O@ASYT*^!IDN0g(v@_8-$({A;vH; zENLvnG>BlnL^mBhvms1>LxkzDtcPwYdiq9~dLEdv&^(XfW^igpGZG_d!i{81XCiDQ WB%ua)v$BEou>#=|phGq=gLnYXpW(Uy literal 0 HcmV?d00001