diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0bf0ab255..17258b853 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,7 @@ jobs: uses: actions/cache@v4 with: path: "${{ github.workspace}}/.xwin" - key: cargo-xwin + key: cargo-xwin-x86_64 - name: Load rust cache uses: Swatinem/rust-cache@v2 with: @@ -274,57 +274,65 @@ jobs: windows-trampoline: needs: determine_changes if: ${{ github.repository == 'astral-sh/uv' && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} - runs-on: windows-latest - name: "check windows trampoline" + runs-on: ubuntu-latest + name: "check windows trampoline | ${{ matrix.target-arch }}" + strategy: + fail-fast: false + matrix: + target-arch: ["x86_64", "i686", "aarch64"] steps: - - name: Create Dev Drive using ReFS - run: | - $Volume = New-VHD -Path C:/uv_dev_drive.vhdx -SizeBytes 10GB | - Mount-VHD -Passthru | - Initialize-Disk -Passthru | - New-Partition -AssignDriveLetter -UseMaximumSize | - Format-Volume -FileSystem ReFS -Confirm:$false -Force - Write-Output $Volume - Write-Output "DEV_DRIVE=$($Volume.DriveLetter):" >> $env:GITHUB_ENV - - uses: actions/checkout@v4 - # actions/checkout does not let us clone into anywhere outside ${{ github.workspace }}, so we have to copy the clone... - - name: Copy Git Repo to Dev Drive - run: | - Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.DEV_DRIVE }}/uv" -Recurse - - name: "Install Rust toolchain" - working-directory: ${{ env.DEV_DRIVE }}/uv/crates/uv-trampoline - env: - CARGO_HOME: ${{ env.DEV_DRIVE }}/.cargo - RUSTUP_HOME: ${{ env.DEV_DRIVE }}/.rustup + working-directory: ${{ github.workspace }}/crates/uv-trampoline run: | - rustup target add x86_64-pc-windows-msvc - rustup component add clippy rust-src --toolchain nightly-2024-05-27-x86_64-pc-windows-msvc + rustup target add ${{ matrix.target-arch }}-pc-windows-msvc + rustup component add rust-src --target ${{ matrix.target-arch }}-pc-windows-msvc + + - name: Load xwin cache + uses: actions/cache@v4 + with: + path: "${{ github.workspace }}/.xwin" + key: cargo-xwin-${{ matrix.target-arch }} - uses: rui314/setup-mold@v1 - uses: Swatinem/rust-cache@v2 with: - workspaces: ${{ env.DEV_DRIVE }}/uv/crates/uv-trampoline - env: - CARGO_HOME: ${{ env.DEV_DRIVE }}/.cargo - RUSTUP_HOME: ${{ env.DEV_DRIVE }}/.rustup + workspaces: ${{ github.workspace }}/crates/uv-trampoline + + - name: "Install cargo-xwin and cargo-bloat" + uses: taiki-e/install-action@v2 + with: + tool: cargo-xwin,cargo-bloat + + - name: "Install xwin dependencies" + run: sudo apt-get install --no-install-recommends -y lld llvm clang cmake ninja-build - name: "Clippy" - working-directory: ${{ env.DEV_DRIVE }}/uv/crates/uv-trampoline + working-directory: ${{ github.workspace }}/crates/uv-trampoline + if: matrix.target-arch == 'x86_64' + run: cargo xwin clippy --all-features --locked --target x86_64-pc-windows-msvc -- -D warnings env: - CARGO_HOME: ${{ env.DEV_DRIVE }}/.cargo - RUSTUP_HOME: ${{ env.DEV_DRIVE }}/.rustup - run: cargo clippy --all-features --locked --target x86_64-pc-windows-msvc -- -D warnings + XWIN_ARCH: "x86_64" + XWIN_CACHE_DIR: "${{ github.workspace }}/.xwin" + + - name: "Bloat Check" + working-directory: ${{ github.workspace }}/crates/uv-trampoline + if: matrix.target-arch == 'x86_64' + run: | + cargo xwin bloat --release --target x86_64-pc-windows-msvc | \ + grep -q -E 'core::fmt|std::panicking|std::backtrace_rs' && exit 1 || exit 0 + env: + XWIN_ARCH: "x86_64" + XWIN_CACHE_DIR: "${{ github.workspace }}/.xwin" - name: "Build" - working-directory: ${{ env.DEV_DRIVE }}/uv/crates/uv-trampoline + working-directory: ${{ github.workspace }}/crates/uv-trampoline + run: cargo xwin build --release --target ${{ matrix.target-arch }}-pc-windows-msvc env: - CARGO_HOME: ${{ env.DEV_DRIVE }}/.cargo - RUSTUP_HOME: ${{ env.DEV_DRIVE }}/.rustup - run: cargo build --release --target x86_64-pc-windows-msvc + XWIN_ARCH: "${{ matrix.target-arch == 'i686' && 'x86' || matrix.target-arch }}" + XWIN_CACHE_DIR: "${{ github.workspace }}/.xwin" typos: runs-on: ubuntu-latest diff --git a/crates/install-wheel-rs/src/wheel.rs b/crates/install-wheel-rs/src/wheel.rs index d77087f8f..ee6ab1d5f 100644 --- a/crates/install-wheel-rs/src/wheel.rs +++ b/crates/install-wheel-rs/src/wheel.rs @@ -913,17 +913,33 @@ mod test { Ok(()) } + #[test] + #[cfg(all(windows, target_arch = "x86"))] + fn test_launchers_are_small() { + // At time of writing, they are 17408 bytes. + assert!( + super::LAUNCHER_I686_GUI.len() < 25 * 1024, + "GUI launcher: {}", + super::LAUNCHER_I686_GUI.len() + ); + assert!( + super::LAUNCHER_I686_CONSOLE.len() < 25 * 1024, + "CLI launcher: {}", + super::LAUNCHER_I686_CONSOLE.len() + ); + } + #[test] #[cfg(all(windows, target_arch = "x86_64"))] fn test_launchers_are_small() { - // At time of writing, they are 15872 bytes. + // At time of writing, they are 21504 and 20480 bytes. assert!( - super::LAUNCHER_X86_64_GUI.len() < 20 * 1024, + super::LAUNCHER_X86_64_GUI.len() < 25 * 1024, "GUI launcher: {}", super::LAUNCHER_X86_64_GUI.len() ); assert!( - super::LAUNCHER_X86_64_CONSOLE.len() < 20 * 1024, + super::LAUNCHER_X86_64_CONSOLE.len() < 25 * 1024, "CLI launcher: {}", super::LAUNCHER_X86_64_CONSOLE.len() ); @@ -932,16 +948,16 @@ mod test { #[test] #[cfg(all(windows, target_arch = "aarch64"))] fn test_launchers_are_small() { - // At time of writing, they are 14848 and 14336 bytes. + // At time of writing, they are 20480 and 19456 bytes. assert!( - super::LAUNCHER_AArch64_GUI.len() < 20 * 1024, + super::LAUNCHER_AARCH64_GUI.len() < 25 * 1024, "GUI launcher: {}", - super::LAUNCHER_AArch64_GUI.len() + super::LAUNCHER_AARCH64_GUI.len() ); assert!( - super::LAUNCHER_AArch64_CONSOLE.len() < 20 * 1024, + super::LAUNCHER_AARCH64_CONSOLE.len() < 25 * 1024, "CLI launcher: {}", - super::LAUNCHER_AArch64_CONSOLE.len() + super::LAUNCHER_AARCH64_CONSOLE.len() ); } diff --git a/crates/uv-trampoline/.cargo/config.toml b/crates/uv-trampoline/.cargo/config.toml index e21b0adad..39413188a 100644 --- a/crates/uv-trampoline/.cargo/config.toml +++ b/crates/uv-trampoline/.cargo/config.toml @@ -1,3 +1,3 @@ [unstable] -build-std = ["core", "panic_abort", "alloc", "std"] -build-std-features = ["compiler-builtins-mem"] +build-std = ["std", "panic_abort"] +build-std-features = ["compiler-builtins-mem", "panic_immediate_abort"] diff --git a/crates/uv-trampoline/Cargo.lock b/crates/uv-trampoline/Cargo.lock index 24f4c8fd4..1182ff5b5 100644 --- a/crates/uv-trampoline/Cargo.lock +++ b/crates/uv-trampoline/Cargo.lock @@ -37,6 +37,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "ufmt" version = "0.2.0" @@ -55,7 +66,7 @@ checksum = "d337d3be617449165cb4633c8dece429afd83f84051024079f97ad32a9663716" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -77,27 +88,72 @@ dependencies = [ "embed-manifest", "ufmt", "ufmt-write", - "windows-sys", + "windows", ] [[package]] -name = "windows-sys" -version = "0.52.0" +name = "windows" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", + "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", @@ -106,42 +162,48 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/crates/uv-trampoline/Cargo.toml b/crates/uv-trampoline/Cargo.toml index 3fac5d90f..3a8f898c1 100644 --- a/crates/uv-trampoline/Cargo.toml +++ b/crates/uv-trampoline/Cargo.toml @@ -6,46 +6,48 @@ license = "MIT OR Apache-2.0" edition = "2021" autotests = false - # Need to optimize etc. or else build fails [profile.dev] lto = true -codegen-units = 1 opt-level = 1 panic = "abort" -debug-assertions = false -overflow-checks = false debug = true [profile.release] +# Enable Link Time Optimization. lto = true +# Reduce number of codegen units to increase optimizations. codegen-units = 1 +# Optimize for size. opt-level = "z" +# Abort on panic. panic = "abort" +# Automatically strip symbols from the binary. +strip = true debug = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -windows-sys = { version = "0.52.0", features = [ - "Win32_Foundation", - "Win32_Security", - "Win32_Storage_FileSystem", - "Win32_System_Console", - "Win32_System_Diagnostics_Debug", - "Win32_System_Environment", - "Win32_System_IO", - "Win32_System_JobObjects", - "Win32_System_JobObjects", - "Win32_System_LibraryLoader", - "Win32_System_Memory", - "Win32_System_Threading", - "Win32_System_WindowsProgramming", - "Win32_UI_WindowsAndMessaging", +windows = { version = "0.57.0", features = [ + "Win32_Foundation", + "Win32_Security", + "Win32_Storage_FileSystem", + "Win32_System_Console", + "Win32_System_Diagnostics_Debug", + "Win32_System_Environment", + "Win32_System_IO", + "Win32_System_JobObjects", + "Win32_System_JobObjects", + "Win32_System_LibraryLoader", + "Win32_System_Memory", + "Win32_System_Threading", + "Win32_System_WindowsProgramming", + "Win32_UI_WindowsAndMessaging", ] } ufmt-write = "0.1.0" -ufmt = "0.2.0" +ufmt = { version = "0.2.0", features = ["std"] } [build-dependencies] embed-manifest = "1.4.0" diff --git a/crates/uv-trampoline/README.md b/crates/uv-trampoline/README.md index 883b2e8ea..3c9daf561 100644 --- a/crates/uv-trampoline/README.md +++ b/crates/uv-trampoline/README.md @@ -19,9 +19,9 @@ rustup target add aarch64-pc-windows-msvc Then, build the trampolines for both supported architectures: ```shell -cargo +nightly-2024-05-27 xwin build --xwin-arch x86 --release --target i686-pc-windows-msvc -cargo +nightly-2024-05-27 xwin build --release --target x86_64-pc-windows-msvc -cargo +nightly-2024-05-27 xwin build --release --target aarch64-pc-windows-msvc +cargo +nightly-2024-06-08 xwin build --xwin-arch x86 --release --target i686-pc-windows-msvc +cargo +nightly-2024-06-08 xwin build --release --target x86_64-pc-windows-msvc +cargo +nightly-2024-06-08 xwin build --release --target aarch64-pc-windows-msvc ``` ### Cross-compiling from macOS @@ -39,9 +39,9 @@ rustup target add aarch64-pc-windows-msvc Then, build the trampolines for both supported architectures: ```shell -cargo +nightly-2024-05-27 xwin build --release --target i686-pc-windows-msvc -cargo +nightly-2024-05-27 xwin build --release --target x86_64-pc-windows-msvc -cargo +nightly-2024-05-27 xwin build --release --target aarch64-pc-windows-msvc +cargo +nightly-2024-06-08 xwin build --release --target i686-pc-windows-msvc +cargo +nightly-2024-06-08 xwin build --release --target x86_64-pc-windows-msvc +cargo +nightly-2024-06-08 xwin build --release --target aarch64-pc-windows-msvc ``` ### Updating the prebuilt executables @@ -84,9 +84,8 @@ That's what this does: it's a generic "trampoline" that lets us generate custom ### 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\`. +Basically, this looks up `python.exe` (for console programs) +and invokes `python.exe path\to\the\`. The intended use is: @@ -124,25 +123,21 @@ 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 -carefully avoids using `core::fmt`. This removes a bunch of runtime overhead: by +In order to minimize binary size, this uses, `panic="abort"`, and carefully +avoids using `core::fmt`. This removes a bunch of runtime overhead: by default, Rust "hello world" on Windows is ~150 KB! So these binaries are ~10x smaller. -Of course the tradeoff is that `#![no_std]` is an awkward super-limited -environment. No C runtime, no platform APIs, very few features... you don't even -get `Vec` or memory allocation or panicking support by default. To work around -this: +Of course the tradeoff is that this is an awkward super-limited +environment. No C runtime and limited platform APIs... you don't +even panicking support by default. To work around this: -- We use `windows-sys` to access Win32 APIs directly. Who needs a C runtime? +- We use `windows` to access Win32 APIs directly. Who needs a C runtime? 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. - - `diagnostics.rs` uses `ufmt` and some cute Windows tricks to get a convenient - version of `eprintln!` that works without `std`, and automatically prints to - either the console if available or pops up a message box if not. + version of `eprintln!` that works without `core::fmt`, and automatically prints + to either the console if available or pops up a message box if not. - All the meat is in `bounce.rs`. @@ -164,20 +159,15 @@ Miscellaneous tips: Building this can be frustrating, because the low-level compiler/runtime machinery have a bunch of implicit assumptions about the environment they'll run in, and the facilities it provides for things like `memcpy`, unwinding, etc. -With `#![no_std]` most of this machinery is missing. So we need to replace the -bits that we actually need, and which bits we need can change depending on stuff -like optimization options. For example: we use `panic="abort"`, so we don't -actually need unwinding support, but at lower optimization levels the compiler -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. +So we need to replace the bits that we actually need, and which bits we need +can change depending on stuff like optimization options. +For example: we use `panic="abort"`, so we don't actually need unwinding support, +but at lower optimization levels the compiler 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. ``` cargo build --release --target i686-pc-windows-msvc cargo build --release --target x86_64-pc-windows-msvc cargo build --release --target aarch64-pc-windows-msvc ``` - -Hopefully in the future as `#![no_std]` develops, this will get smoother. - -Also, sometimes it helps to fiddle with optimization levels. diff --git a/crates/uv-trampoline/rust-toolchain.toml b/crates/uv-trampoline/rust-toolchain.toml index b933c66a7..a0c842080 100644 --- a/crates/uv-trampoline/rust-toolchain.toml +++ b/crates/uv-trampoline/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2024-05-27" +channel = "nightly-2024-06-08" diff --git a/crates/uv-trampoline/src/bin/uv-trampoline-console.rs b/crates/uv-trampoline/src/bin/uv-trampoline-console.rs index 315b4b9da..62ccef436 100644 --- a/crates/uv-trampoline/src/bin/uv-trampoline-console.rs +++ b/crates/uv-trampoline/src/bin/uv-trampoline-console.rs @@ -1,4 +1,3 @@ -#![no_std] #![no_main] #![windows_subsystem = "console"] diff --git a/crates/uv-trampoline/src/bin/uv-trampoline-gui.rs b/crates/uv-trampoline/src/bin/uv-trampoline-gui.rs index ad6d20989..63d2eab51 100644 --- a/crates/uv-trampoline/src/bin/uv-trampoline-gui.rs +++ b/crates/uv-trampoline/src/bin/uv-trampoline-gui.rs @@ -1,4 +1,3 @@ -#![no_std] #![no_main] #![windows_subsystem = "windows"] diff --git a/crates/uv-trampoline/src/bounce.rs b/crates/uv-trampoline/src/bounce.rs index ab19be141..b9cd030a2 100644 --- a/crates/uv-trampoline/src/bounce.rs +++ b/crates/uv-trampoline/src/bounce.rs @@ -1,52 +1,53 @@ -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 std::ffi::{CStr, CString}; +use std::mem::size_of; +use std::mem::MaybeUninit; +use std::ptr::addr_of; +use std::vec::Vec; -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::{ - Console::*, - Environment::{GetCommandLineA, GetEnvironmentVariableA, SetCurrentDirectoryA}, - JobObjects::*, - LibraryLoader::GetModuleFileNameA, - Threading::*, +use windows::core::{s, PCSTR, PSTR}; +use windows::Win32::{ + Foundation::{ + CloseHandle, GetLastError, SetHandleInformation, SetLastError, BOOL, + ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS, HANDLE, HANDLE_FLAG_INHERIT, + INVALID_HANDLE_VALUE, MAX_PATH, TRUE, + }, + Storage::FileSystem::{ + CreateFileA, GetFileSizeEx, ReadFile, SetFilePointerEx, FILE_ATTRIBUTE_NORMAL, FILE_BEGIN, + FILE_GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, + }, + System::Console::{ + GetStdHandle, SetConsoleCtrlHandler, SetStdHandle, STD_ERROR_HANDLE, STD_INPUT_HANDLE, + STD_OUTPUT_HANDLE, + }, + System::Diagnostics::Debug::{ + FormatMessageA, FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, + FORMAT_MESSAGE_IGNORE_INSERTS, + }, + System::Environment::GetCommandLineA, + System::JobObjects::{ + AssignProcessToJobObject, CreateJobObjectW, JobObjectExtendedLimitInformation, + QueryInformationJobObject, SetInformationJobObject, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK, + }, + System::LibraryLoader::GetModuleFileNameA, + System::Threading::{ + CreateProcessA, ExitProcess, GetExitCodeProcess, GetStartupInfoA, WaitForInputIdle, + WaitForSingleObject, INFINITE, PROCESS_CREATION_FLAGS, PROCESS_INFORMATION, + STARTF_USESTDHANDLES, STARTUPINFOA, + }, + UI::WindowsAndMessaging::{ + CreateWindowExA, DestroyWindow, GetMessageA, PeekMessageA, PostMessageA, HWND_MESSAGE, MSG, + PEEK_MESSAGE_REMOVE_TYPE, WINDOW_EX_STYLE, WINDOW_STYLE, }, - UI::WindowsAndMessaging::*, }; use crate::helpers::SizeOf; use crate::{eprintln, format}; const MAGIC_NUMBER: [u8; 4] = [b'U', b'V', b'U', b'V']; -const PATH_LEN_SIZE: usize = mem::size_of::(); +const PATH_LEN_SIZE: usize = size_of::(); const MAX_PATH_LEN: u32 = 32 * 1024; -fn getenv(name: &CStr) -> Option { - unsafe { - let count = GetEnvironmentVariableA(name.as_ptr() as _, null_mut(), 0); - if count == 0 { - return None; - } - let mut value = Vec::::with_capacity(count as usize); - GetEnvironmentVariableA( - name.as_ptr() as _, - value.as_mut_ptr(), - value.capacity() as u32, - ); - value.set_len(count as usize); - Some(CString::from_vec_with_nul_unchecked(value)) - } -} - /// Transform ` ` to `python `. fn make_child_cmdline() -> CString { let executable_name: CString = executable_filename(); @@ -69,8 +70,8 @@ fn make_child_cmdline() -> CString { // Helpful when debugging trampoline issues // eprintln!( // "executable_name: '{}'\nnew_cmdline: {}", - // core::str::from_utf8(executable_name.to_bytes()).unwrap(), - // core::str::from_utf8(child_cmdline.as_slice()).unwrap() + // std::str::from_utf8(executable_name.to_bytes()).unwrap(), + // std::str::from_utf8(child_cmdline.as_slice()).unwrap() // ); CString::from_vec_with_nul(child_cmdline).unwrap_or_else(|_| { @@ -98,11 +99,11 @@ fn push_quoted_path(path: &CStr, command: &mut Vec) { fn executable_filename() -> CString { // MAX_PATH is a lie, Windows paths can be longer. // https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation - // But it's a good first guess, usually paths are short and we should only need a single attempt. + // But it's a good first guess, usually paths are short, and we should only need a single attempt. let mut buffer: Vec = vec![0; MAX_PATH as usize]; loop { // Call the Windows API function to get the module file name - let len = unsafe { GetModuleFileNameA(0, buffer.as_mut_ptr(), buffer.len() as u32) }; + let len = unsafe { GetModuleFileNameA(None, &mut buffer) }; // That's the error condition because len doesn't include the trailing null byte if len as usize == buffer.len() { @@ -116,7 +117,7 @@ fn executable_filename() -> CString { err => { print_last_error_and_exit(&format!( "Failed to get executable name (code: {})", - err + err.0 )); } } @@ -143,40 +144,33 @@ fn executable_filename() -> CString { /// # 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 { - 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 file_handle = unsafe { + CreateFileA( + PCSTR::from_raw(executable_name.as_ptr() as *const _), + FILE_GENERIC_READ.0, + FILE_SHARE_READ, + None, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + None, + ) + } + .unwrap_or_else(|_| { + print_last_error_and_exit(&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(), - ) - }, - ); + if unsafe { GetFileSizeEx(file_handle, &mut file_size) }.is_err() { + print_last_error_and_exit(&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. @@ -187,34 +181,24 @@ fn find_python_exe(executable_name: &CStr) -> CString { // 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."), - ); + if unsafe { + SetFilePointerEx( + file_handle, + file_size - i64::from(bytes_to_read), + None, + FILE_BEGIN, + ) + } + .is_err() + { + print_last_error_and_exit("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"), - ); + let mut read_bytes = bytes_to_read; + if unsafe { ReadFile(file_handle, Some(&mut buffer), Some(&mut read_bytes), None) }.is_err() + { + print_last_error_and_exit("Failed to read the executable file."); + } // Truncate the buffer to the actual number of bytes read. buffer.truncate(read_bytes as usize); @@ -271,9 +255,9 @@ fn find_python_exe(executable_name: &CStr) -> CString { } }; - expect_result(unsafe { CloseHandle(file_handle) }, 0, || { - String::from("Failed to close file handle") - }); + if unsafe { CloseHandle(file_handle) }.is_err() { + print_last_error_and_exit("Failed to close file handle."); + } if is_absolute(&path) { path @@ -307,13 +291,12 @@ fn is_absolute(path: &CStr) -> bool { } fn push_arguments(output: &mut Vec) { - let arguments_as_str = unsafe { - // SAFETY: We rely on `GetCommandLineA` to return a valid pointer to a null terminated string. - CStr::from_ptr(GetCommandLineA() as _) - }; + // SAFETY: We rely on `GetCommandLineA` to return a valid pointer to a null terminated string. + let arguments_as_str = unsafe { GetCommandLineA() }; + let arguments_as_bytes = unsafe { arguments_as_str.as_bytes() }; // Skip over the executable name and then push the rest of the arguments - let after_executable = skip_one_argument(arguments_as_str.to_bytes()); + let after_executable = skip_one_argument(arguments_as_bytes); output.extend_from_slice(after_executable) } @@ -350,92 +333,110 @@ fn skip_one_argument(arguments: &[u8]) -> &[u8] { } fn make_job_object() -> HANDLE { - unsafe { - let job = CreateJobObjectW(null(), null()); - let mut job_info = MaybeUninit::::uninit(); - let mut retlen = 0u32; - 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; - expect_result( - SetInformationJobObject( - job, - JobObjectExtendedLimitInformation, - addr_of!(job_info) as *const _, - job_info.size_of(), - ), - 0, - || String::from("Error from SetInformationJobObject"), - ); - job + let job = unsafe { CreateJobObjectW(None, None) } + .unwrap_or_else(|_| print_last_error_and_exit("Job creation failed.")); + let mut job_info = MaybeUninit::::uninit(); + let mut retlen = 0u32; + if unsafe { + QueryInformationJobObject( + job, + JobObjectExtendedLimitInformation, + job_info.as_mut_ptr() as *mut _, + job_info.size_of(), + Some(&mut retlen), + ) } + .is_err() + { + print_last_error_and_exit("Job information querying failed."); + } + let mut job_info = unsafe { 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; + if unsafe { + SetInformationJobObject( + job, + JobObjectExtendedLimitInformation, + addr_of!(job_info) as *const _, + job_info.size_of(), + ) + } + .is_err() + { + print_last_error_and_exit("Job information setting failed."); + } + job } fn spawn_child(si: &STARTUPINFOA, child_cmdline: CString) -> HANDLE { - unsafe { - if si.dwFlags & STARTF_USESTDHANDLES != 0 { - // ignore errors from these -- if the handle's not inheritable/not valid, then nothing - // we can do - SetHandleInformation(si.hStdInput, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); - SetHandleInformation(si.hStdOutput, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); - SetHandleInformation(si.hStdError, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); - } - let mut child_process_info = MaybeUninit::::uninit(); - 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, - || 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 + // See distlib/PC/launcher.c::run_child + if (si.dwFlags & STARTF_USESTDHANDLES).0 != 0 { + // ignore errors, if the handles are not inheritable/valid, then nothing we can do + unsafe { SetHandleInformation(si.hStdInput, HANDLE_FLAG_INHERIT.0, HANDLE_FLAG_INHERIT) } + .unwrap_or_else(|_| eprintln!("Making stdin inheritable failed.")); + unsafe { SetHandleInformation(si.hStdOutput, HANDLE_FLAG_INHERIT.0, HANDLE_FLAG_INHERIT) } + .unwrap_or_else(|_| eprintln!("Making stdout inheritable failed.")); + unsafe { SetHandleInformation(si.hStdError, HANDLE_FLAG_INHERIT.0, HANDLE_FLAG_INHERIT) } + .unwrap_or_else(|_| eprintln!("Making stderr inheritable failed.")); } + let mut child_process_info = MaybeUninit::::uninit(); + unsafe { + CreateProcessA( + None, + // 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. + PSTR::from_raw(child_cmdline.as_ptr() as *mut _), + None, + None, + true, + PROCESS_CREATION_FLAGS(0), + None, + None, + si, + child_process_info.as_mut_ptr(), + ) + } + .unwrap_or_else(|_| { + print_last_error_and_exit("Failed to spawn the python child process."); + }); + let child_process_info = unsafe { child_process_info.assume_init() }; + unsafe { CloseHandle(child_process_info.hThread) }.unwrap_or_else(|_| { + print_last_error_and_exit("Failed to close handle to python child process main thread."); + }); + // Return handle to child process. + child_process_info.hProcess } // Apparently, the Windows C runtime has a secret way to pass file descriptors into child // processes, by using the .lpReserved2 field. We want to close those file descriptors too. // The UCRT source code has details on the memory layout (see also initialize_inherited_file_handles_nolock): -// https://github.com/huangqinjin/ucrt/blob/10.0.19041.0/lowio/ioinit.cpp#L190-L223 +// https://github.com/huangqinjin/ucrt/blob/10.0.19041.0/lowio/ioinit.cpp#L190-L223 fn close_handles(si: &STARTUPINFOA) { - unsafe { - for handle in [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE] { - CloseHandle(GetStdHandle(handle)); - SetStdHandle(handle, INVALID_HANDLE_VALUE); + // See distlib/PC/launcher.c::cleanup_standard_io() + for std_handle in [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE] { + if let Ok(handle) = unsafe { GetStdHandle(std_handle) } { + unsafe { CloseHandle(handle) }.unwrap_or_else(|_| { + eprintln!("Failed to close standard device handle {}.", handle.0); + }); + unsafe { SetStdHandle(std_handle, INVALID_HANDLE_VALUE) }.unwrap_or_else(|_| { + eprintln!("Failed to modify standard device handle {}.", std_handle.0); + }); } + } - if si.cbReserved2 == 0 || si.lpReserved2.is_null() { - return; - } - let crt_magic = si.lpReserved2 as *const u32; - let handle_count = crt_magic.read_unaligned() as isize; - let handle_start = crt_magic.offset(1 + handle_count); - for i in 0..handle_count { - CloseHandle(handle_start.offset(i).read_unaligned() as HANDLE); - } + // See distlib/PC/launcher.c::cleanup_fds() + if si.cbReserved2 == 0 || si.lpReserved2.is_null() { + return; + } + let crt_magic = si.lpReserved2 as *const u32; + let handle_count = unsafe { crt_magic.read_unaligned() } as isize; + let handle_start = unsafe { crt_magic.offset(1 + handle_count) }; + for i in 0..handle_count { + let handle_ptr = unsafe { handle_start.offset(i).read_unaligned() } as *const HANDLE; + // Close all fds inherited from the parent, except for the standard I/O fds. + unsafe { CloseHandle(*handle_ptr) }.unwrap_or_else(|_| { + eprintln!("Failed to close child file descriptors at {}.", i); + }); } } @@ -459,139 +460,123 @@ fn close_handles(si: &STARTUPINFOA) { Is creating a window and calling PeekMessage the best way to do this? idk. */ fn clear_app_starting_state(child_handle: HANDLE) { + let mut msg = MaybeUninit::::uninit(); unsafe { - PostMessageA(0, 0, 0, 0); - let mut msg = MaybeUninit::::uninit(); - GetMessageA(msg.as_mut_ptr(), 0, 0, 0); - WaitForInputIdle(child_handle, INFINITE); + // End the launcher's "app starting" cursor state. + PostMessageA(None, 0, None, None).unwrap_or_else(|_| { + eprintln!("Failed to post a message to specified window."); + }); + if GetMessageA(msg.as_mut_ptr(), None, 0, 0) != TRUE { + eprintln!("Failed to retrieve posted window message."); + } + // Proxy the child's input idle event. + if WaitForInputIdle(child_handle, INFINITE) != 0 { + eprintln!("Failed to wait for input from window."); + } + // Signal the process input idle event by creating a window and pumping + // sent messages. The window class isn't important, so just use the + // system "STATIC" class. let hwnd = CreateWindowExA( - 0, - c"STATIC".as_ptr() as *const _, - c"uv Python Trampoline".as_ptr() as *const _, - 0, + WINDOW_EX_STYLE(0), + s!("STATIC"), + s!("uv Python Trampoline"), + WINDOW_STYLE(0), 0, 0, 0, 0, HWND_MESSAGE, - 0, - 0, - null(), + None, + None, + None, ); - PeekMessageA(msg.as_mut_ptr(), hwnd, 0, 0, 0); - DestroyWindow(hwnd); + // Process all sent messages and signal input idle. + _ = PeekMessageA(msg.as_mut_ptr(), hwnd, 0, 0, PEEK_MESSAGE_REMOVE_TYPE(0)); + DestroyWindow(hwnd).unwrap_or_else(|_| { + print_last_error_and_exit("Failed to destroy temporary window."); + }); } } pub fn bounce(is_gui: bool) -> ! { - unsafe { - let child_cmdline = make_child_cmdline(); + let child_cmdline = make_child_cmdline(); - let mut si = MaybeUninit::::uninit(); - GetStartupInfoA(si.as_mut_ptr()); - let si = si.assume_init(); + let mut si = MaybeUninit::::uninit(); + unsafe { GetStartupInfoA(si.as_mut_ptr()) } + let si = unsafe { si.assume_init() }; - let child_handle = spawn_child(&si, child_cmdline); - let job = make_job_object(); - expect_result(AssignProcessToJobObject(job, child_handle), 0, || { - String::from("Error from AssignProcessToJobObject") - }); + let child_handle = spawn_child(&si, child_cmdline); + let job = make_job_object(); - // (best effort) Close all the handles that we can - close_handles(&si); - - // (best effort) Switch to some innocuous directory so we don't hold the original - // cwd open. - if let Some(tmp) = getenv(c"TEMP") { - SetCurrentDirectoryA(tmp.as_ptr() as *const _); - } else { - SetCurrentDirectoryA(c"c:\\".as_ptr() as *const _); - } - - // We want to ignore control-C/control-Break/logout/etc.; the same event will - // be delivered to the child, so we let them decide whether to exit or not. - unsafe extern "system" fn control_key_handler(_: u32) -> BOOL { - 1 - } - SetConsoleCtrlHandler(Some(control_key_handler), 1); - - if is_gui { - clear_app_starting_state(child_handle); - } - - WaitForSingleObject(child_handle, INFINITE); - let mut exit_code = 0u32; - 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()); + if unsafe { AssignProcessToJobObject(job, child_handle) }.is_err() { + print_last_error_and_exit("Failed to assign child process to the job.") } - actual_result + // (best effort) Close all the handles that we can + close_handles(&si); + + // (best effort) Switch to some innocuous directory, so we don't hold the original cwd open. + // See distlib/PC/launcher.c::switch_working_directory + if std::env::set_current_dir(std::env::temp_dir()).is_err() { + eprintln!("Failed to set cwd to temp dir."); + } + + // We want to ignore control-C/control-Break/logout/etc.; the same event will + // be delivered to the child, so we let them decide whether to exit or not. + unsafe extern "system" fn control_key_handler(_: u32) -> BOOL { + TRUE + } + // See distlib/PC/launcher.c::control_key_handler + unsafe { SetConsoleCtrlHandler(Some(control_key_handler), true) }.unwrap_or_else(|_| { + print_last_error_and_exit("Control handler setting failed."); + }); + + if is_gui { + clear_app_starting_state(child_handle); + } + + _ = unsafe { WaitForSingleObject(child_handle, INFINITE) }; + let mut exit_code = 0u32; + if unsafe { GetExitCodeProcess(child_handle, &mut exit_code) }.is_err() { + print_last_error_and_exit("Failed to get exit code of child process."); + } + exit_with_status(exit_code); } #[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(); + eprintln!("Received error code: {}", err.0); + let mut msg_ptr = PSTR::null(); let size = unsafe { FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - null(), - err, + None, + err.0, 0, - // Weird calling convention: this argument is typed as *mut u16, + // Weird calling convention: this argument is typed as *mut u8, // 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. + // *actually* pass in a *mut *mut u8 and just lie about the type. // Getting Rust to do this requires some convincing. - core::ptr::addr_of_mut!(msg_ptr) as *mut _ as _, + PSTR(&mut msg_ptr.0 as *mut _ as *mut _), 0, - core::ptr::null(), + None, ) }; - if size == 0 { + if size == 0 || msg_ptr.0.is_null() { eprintln!( "{}: with code {} (failed to get error message)", - message, err + message, err.0 ); } else { let reason = unsafe { - let reason = core::slice::from_raw_parts(msg_ptr, size as usize + 1); - CStr::from_bytes_with_nul_unchecked(reason) + let reason = std::slice::from_raw_parts(msg_ptr.0, size as usize); + std::str::from_utf8_unchecked(reason) }; - eprintln!( - "(uv internal error) {}: {}", - message, - &*reason.to_string_lossy() - ); + eprintln!("(uv internal error) {}: {}", message, reason); } // Note: We don't need to free the buffer here because we're going to exit anyway. diff --git a/crates/uv-trampoline/src/diagnostics.rs b/crates/uv-trampoline/src/diagnostics.rs index 7ca9a7943..f184135af 100644 --- a/crates/uv-trampoline/src/diagnostics.rs +++ b/crates/uv-trampoline/src/diagnostics.rs @@ -1,16 +1,14 @@ -extern crate alloc; - -use alloc::{ffi::CString, string::String}; -use core::{ - convert::Infallible, - ptr::{addr_of_mut, null, null_mut}, -}; +use std::convert::Infallible; +use std::ffi::CString; +use std::string::String; use ufmt_write::uWrite; -use windows_sys::Win32::{ +use windows::core::PCSTR; +use windows::Win32::{ + Foundation::INVALID_HANDLE_VALUE, Storage::FileSystem::WriteFile, System::Console::{GetStdHandle, STD_ERROR_HANDLE}, - UI::WindowsAndMessaging::MessageBoxA, + UI::WindowsAndMessaging::{MessageBoxA, MESSAGEBOX_STYLE}, }; #[macro_export] @@ -43,24 +41,22 @@ impl uWrite for StringBuffer { #[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..); + let handle = unsafe { GetStdHandle(STD_ERROR_HANDLE) }.unwrap_or(INVALID_HANDLE_VALUE); + let mut written: u32 = 0; + let mut remaining = message; + while !remaining.is_empty() { + // If we get an error, it means we tried to write to an invalid handle (GUI Application) + // and we should try to write to a window instead + if unsafe { WriteFile(handle, Some(remaining.as_bytes()), Some(&mut written), None) } + .is_err() + { + let nul_terminated = unsafe { CString::new(message.as_bytes()).unwrap_unchecked() }; + let pcstr_message = PCSTR::from_raw(nul_terminated.as_ptr() as *const _); + unsafe { MessageBoxA(None, pcstr_message, None, MESSAGEBOX_STYLE(0)) }; + return; + } + if let Some(out) = remaining.get(written as usize..) { + remaining = out } } } diff --git a/crates/uv-trampoline/src/helpers.rs b/crates/uv-trampoline/src/helpers.rs index 7f88a01d5..6a7e2b49f 100644 --- a/crates/uv-trampoline/src/helpers.rs +++ b/crates/uv-trampoline/src/helpers.rs @@ -1,4 +1,4 @@ -use core::mem::size_of; +use std::mem::size_of; pub trait SizeOf { fn size_of(&self) -> u32; diff --git a/crates/uv-trampoline/src/lib.rs b/crates/uv-trampoline/src/lib.rs index 1c5904fa5..a887b24c8 100644 --- a/crates/uv-trampoline/src/lib.rs +++ b/crates/uv-trampoline/src/lib.rs @@ -1,9 +1,3 @@ -#![feature(panic_info_message)] -#![no_std] - -extern crate alloc; - pub mod bounce; mod diagnostics; mod helpers; -mod runtime; diff --git a/crates/uv-trampoline/src/runtime.rs b/crates/uv-trampoline/src/runtime.rs deleted file mode 100644 index f81b92145..000000000 --- a/crates/uv-trampoline/src/runtime.rs +++ /dev/null @@ -1,63 +0,0 @@ -// Nothing in this file is directly imported anywhere else; it just fills in -// some of the no_std gaps. - -extern crate alloc; - -use alloc::alloc::{GlobalAlloc, Layout}; -use core::ffi::c_void; - -use windows_sys::Win32::System::{ - Memory::{GetProcessHeap, HeapAlloc, HeapFree, HeapReAlloc, HEAP_ZERO_MEMORY}, - Threading::ExitProcess, -}; - -use crate::eprintln; - -// Windows wants this symbol. It has something to do with floating point usage? -// idk, defining it gets rid of link errors. -#[no_mangle] -#[used] -static _fltused: i32 = 0; - -struct SystemAlloc; - -#[global_allocator] -static SYSTEM_ALLOC: SystemAlloc = SystemAlloc; - -unsafe impl Sync for SystemAlloc {} -unsafe impl GlobalAlloc for SystemAlloc { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - HeapAlloc(GetProcessHeap(), 0, layout.size()) as *mut u8 - } - unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { - HeapFree(GetProcessHeap(), 0, ptr as *const c_void); - } - unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { - HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, layout.size()) as *mut u8 - } - unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 { - HeapReAlloc(GetProcessHeap(), 0, ptr as *const c_void, new_size) as *mut u8 - } -} - -#[panic_handler] -fn panic(info: &core::panic::PanicInfo) -> ! { - if let Some(location) = info.location() { - let mut msg = "(couldn't format message)"; - if let Some(msg_args) = info.message() { - if let Some(msg_str) = msg_args.as_str() { - msg = msg_str; - } - } - eprintln!( - "panic at {}:{} (column {}): {}", - location.file(), - location.line(), - location.column(), - msg, - ); - } - unsafe { - ExitProcess(128); - } -}