uv/crates/uv-trampoline
samypr100 ed63be5dab
chore(🧹): const env var usage cleanup (#16950)
## Summary

* Updates existing references to use EnvVars where usage was missing.
* Adds missing entries to env var usages, e.g. new env var declarations
in uv-trampoline, tests, etc.
* Note: this doesn't affect trampoline sizes as the end result is the
same
* Fixes versioning of `UV_HIDE_BUILD_OUTPUT`.

## Test Plan

Existing Tests. Compiled the trampolines locally to verify zero changes
(size, binary).

## Question

Will this complicate the crates publishing release process? I'm not
certain yet if it will be an issue for uv-trampoline (non-workspace
member) to reference a uv workspace member from a bump & release
perspective wrt lock files. If so, I'll revert the uv-trampoline changes
but keep the others.
2025-12-02 22:16:46 -08:00
..
.cargo feat: fix uv-trampoline renovate issues (#5204) 2024-07-23 10:12:28 +02:00
src chore(🧹): const env var usage cleanup (#16950) 2025-12-02 22:16:46 -08:00
Cargo.lock chore(🧹): const env var usage cleanup (#16950) 2025-12-02 22:16:46 -08:00
Cargo.toml chore(🧹): const env var usage cleanup (#16950) 2025-12-02 22:16:46 -08:00
README.md Fix `uv-trampoline-builder` builds from crates.io by moving bundled executables (#16922) 2025-12-02 07:50:39 -06:00
build.rs chore(🧹): const env var usage cleanup (#16950) 2025-12-02 22:16:46 -08:00
rust-toolchain.toml Update Rust toolchain to 1.91 and MSRV to 1.89 (#16531) 2025-10-30 22:34:59 -05:00

README.md

Windows trampolines

This is a fork of posy trampolines.

Building

Cross-compiling from Linux

Install cargo xwin. Use your package manager to install LLD and add the rustup targets:

sudo apt install llvm clang lld
cargo install cargo-xwin
rustup toolchain install nightly-2025-06-23
rustup component add rust-src --toolchain nightly-2025-06-23-x86_64-unknown-linux-gnu
rustup target add --toolchain nightly-2025-06-23 i686-pc-windows-msvc
rustup target add --toolchain nightly-2025-06-23 x86_64-pc-windows-msvc
rustup target add --toolchain nightly-2025-06-23 aarch64-pc-windows-msvc

Then, build the trampolines for all supported architectures:

cargo +nightly-2025-06-23 xwin build --xwin-arch x86 --release --target i686-pc-windows-msvc
cargo +nightly-2025-06-23 xwin build --release --target x86_64-pc-windows-msvc
cargo +nightly-2025-06-23 xwin build --release --target aarch64-pc-windows-msvc

Cross-compiling from macOS

Install cargo xwin. Use your package manager to install LLVM and add the rustup targets:

brew install llvm
cargo install cargo-xwin
rustup toolchain install nightly-2025-06-23
rustup component add rust-src --toolchain nightly-2025-06-23-aarch64-apple-darwin
rustup target add --toolchain nightly-2025-06-23 i686-pc-windows-msvc
rustup target add --toolchain nightly-2025-06-23 x86_64-pc-windows-msvc
rustup target add --toolchain nightly-2025-06-23 aarch64-pc-windows-msvc

Then, build the trampolines for all supported architectures:

cargo +nightly-2025-06-23 xwin build --xwin-arch x86 --release --target i686-pc-windows-msvc
cargo +nightly-2025-06-23 xwin build --release --target x86_64-pc-windows-msvc
cargo +nightly-2025-06-23 xwin build --release --target aarch64-pc-windows-msvc

Updating the prebuilt executables

After building the trampolines for all supported architectures:

cp target/aarch64-pc-windows-msvc/release/uv-trampoline-console.exe ../uv-trampoline-builder/trampolines/uv-trampoline-aarch64-console.exe
cp target/aarch64-pc-windows-msvc/release/uv-trampoline-gui.exe ../uv-trampoline-builder/trampolines/uv-trampoline-aarch64-gui.exe
cp target/x86_64-pc-windows-msvc/release/uv-trampoline-console.exe ../uv-trampoline-builder/trampolines/uv-trampoline-x86_64-console.exe
cp target/x86_64-pc-windows-msvc/release/uv-trampoline-gui.exe ../uv-trampoline-builder/trampolines/uv-trampoline-x86_64-gui.exe
cp target/i686-pc-windows-msvc/release/uv-trampoline-console.exe ../uv-trampoline-builder/trampolines/uv-trampoline-i686-console.exe
cp target/i686-pc-windows-msvc/release/uv-trampoline-gui.exe ../uv-trampoline-builder/trampolines/uv-trampoline-i686-gui.exe

Testing the trampolines

To perform a basic smoke test of the trampolines, run the following commands on a Windows machine, from the root of the repository:

cargo clean
cargo run venv
cargo run pip install black
.venv\Scripts\black --version

Background

What is this?

Sometimes you want to run a tool on Windows that's written in Python, like black or mypy or jupyter or whatever. But, Windows does not know how to run Python files! It knows how to run .exe files. So we need to somehow convert our Python file a .exe file.

That's what this does: it's a generic "trampoline" that lets us generate custom .exes for arbitrary Python scripts, and when invoked it bounces to invoking python <the script> instead.

How do you use it?

Basically, this looks up python.exe (for console programs) and invokes python.exe path\to\the\<the .exe>.

It uses PE resources to store/load the information required to do this:

Resource name Contains
RESOURCE_TRAMPOLINE_KIND 1 (script) or 2 (Python launcher)
RESOURCE_PYTHON_PATH Path to python.exe
RESOURCE_SCRIPT_DATA Zip file, containing a Python script called __main__.py

This works because when you run python on the .exe, the zipimport mechanism will see the embedded .zip file, and automagically look inside to find and execute __main__.py. Easy-peasy.

Why does this exist?

I probably could have used Vinay's C++ implementation from distlib, but what's the fun in that? In particular, optimizing for binary size was entertaining (these are ~7x smaller than the distlib, which doesn't matter much, but does a little bit, considering that it gets added to every Python script). There are also some minor advantages, like I think the Rust code is easier to understand (multiple files!) and it's convenient to be able to straightforwardly code the Python-finding logic we want. But mostly it was just an interesting challenge.

This does owe a lot to the distlib implementation though. The overall logic is copied more-or-less directly.

Anything I should know for hacking on this?

In order to minimize binary size, this uses, 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 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 to access Win32 APIs directly. Who needs a C runtime? Though uh, this does mean that literally all of our code is unsafe. Sorry!

  • diagnostics.rs uses ufmt and some cute Windows tricks to get a convenient 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.

Miscellaneous tips:

  • cargo-bloat is a useful tool for checking what code is ending up in the final binary and how much space it's taking. (It makes it very obvious whether you've pulled in core::fmt!)

  • Lots of Rust built-in panicking checks will pull in core::fmt, e.g., if you ever use .unwrap() then suddenly our binaries double in size, because the if foo.is_none() { panic!(...) } that's hidden inside .unwrap() will invoke core::fmt, even if the unwrap will actually never fail. .unwrap_unchecked() avoids this. Similar for slice[idx] vs slice.get_unchecked(idx).

How do you build this stupid thing?

Building this can be frustrating, because the low-level compiler/runtime 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. 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