mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
feat: re-enable std in uv-trampoline (#4722)
## Summary Partially closes #1917 This PR picks up on some of the great work from #1864 and opted to keep `panic_immediate_abort` (for size reasons). I split the PR in different isolated commits in case we want to separate/cherry-pick them out. 1. The first commit ports mostly all std changes from that PR into this PR. Binary sizes stayed the same ~16kb. 2. The second commit migrates our existing usage of windows-sys to windows for a safer ffi calls with Results!. It also changes all large unsafe blocks to be isolated to the actual unsafe calls, and switches some areas to use std such as getenv port ( which seemed buggy! ) from launcher.c. In addition, this also adds more error checking in order to match some missing assertions from distlib's launcher.c. Note, due to the additional .text data, the binary sizes increased to ~20.5kb, but we can cut back on some of the added error msgs as needed. 3. The third commit switches to using xwin for building on all 3 supported trampoline targets for sanity, and adds a CI bloat check for core::fmt and panic as a precaution. Sadly, this will invalidate the xwin cache on the first run. ## Test Plan Most changes were tested on a couple of local GUI apps and console apps, also tested some of the error states manually by using SetLastError at different points in the code and/or passing in invalid handles. I'm not sure how far we can get with migrating some of the other calls without increasing binary size substantially. An initial attempt at using std::path didn't seem so bad size wise when I tried it (~1k). On other cases, such as std::process::exit added ~10k to the total binary size. --------- Co-authored-by: konstin <konstin@mailbox.org>
This commit is contained in:
parent
6c8ce1d013
commit
eee90a340c
14 changed files with 482 additions and 494 deletions
80
.github/workflows/ci.yml
vendored
80
.github/workflows/ci.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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"]
|
||||
|
|
104
crates/uv-trampoline/Cargo.lock
generated
104
crates/uv-trampoline/Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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\<the .exe>`.
|
||||
Basically, this looks up `python.exe` (for console programs)
|
||||
and invokes `python.exe path\to\the\<the .exe>`.
|
||||
|
||||
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.
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "nightly-2024-05-27"
|
||||
channel = "nightly-2024-06-08"
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
#![windows_subsystem = "console"]
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
#![windows_subsystem = "windows"]
|
||||
|
||||
|
|
|
@ -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::<u32>();
|
||||
const PATH_LEN_SIZE: usize = size_of::<u32>();
|
||||
const MAX_PATH_LEN: u32 = 32 * 1024;
|
||||
|
||||
fn getenv(name: &CStr) -> Option<CString> {
|
||||
unsafe {
|
||||
let count = GetEnvironmentVariableA(name.as_ptr() as _, null_mut(), 0);
|
||||
if count == 0 {
|
||||
return None;
|
||||
}
|
||||
let mut value = Vec::<u8>::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 `<command> <arguments>` to `python <command> <arguments>`.
|
||||
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<u8>) {
|
|||
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<u8> = 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<u8>) {
|
||||
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::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>::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::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>::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::<PROCESS_INFORMATION>::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::<PROCESS_INFORMATION>::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::<MSG>::uninit();
|
||||
unsafe {
|
||||
PostMessageA(0, 0, 0, 0);
|
||||
let mut msg = MaybeUninit::<MSG>::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::<STARTUPINFOA>::uninit();
|
||||
GetStartupInfoA(si.as_mut_ptr());
|
||||
let si = si.assume_init();
|
||||
let mut si = MaybeUninit::<STARTUPINFOA>::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<T, F>(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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use core::mem::size_of;
|
||||
use std::mem::size_of;
|
||||
|
||||
pub trait SizeOf {
|
||||
fn size_of(&self) -> u32;
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
#![feature(panic_info_message)]
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod bounce;
|
||||
mod diagnostics;
|
||||
mod helpers;
|
||||
mod runtime;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue