From 9ada4c0c7756dca4056735636d24f18ddc8e5fe1 Mon Sep 17 00:00:00 2001 From: Pavel Dikov Date: Mon, 4 Aug 2025 23:34:58 +0100 Subject: [PATCH] refactor(uv-trampoline): use PE resources to store trampoline type + path to python binary `.rsrc` is the idiomatic way of storing metadata and non-code resources in PE binaries. This should make the resulting binaries more robust as they are no longer dependent on the exact location of a certain magic number. Addresses: #15022 --- Cargo.lock | 218 +++++++-- Cargo.toml | 2 + .../src/metadata/requires_dist.rs | 6 +- crates/uv-install-wheel/Cargo.toml | 3 +- crates/uv-install-wheel/src/lib.rs | 1 + crates/uv-install-wheel/src/wheel.rs | 9 +- crates/uv-python/Cargo.toml | 3 +- crates/uv-python/src/managed.rs | 32 +- crates/uv-trampoline-builder/Cargo.toml | 10 +- crates/uv-trampoline-builder/src/lib.rs | 463 +++++++++++------- crates/uv-trampoline/Cargo.toml | 1 + crates/uv-trampoline/README.md | 24 +- crates/uv-trampoline/src/bounce.rs | 230 +++------ .../uv-trampoline-i686-console.exe | Bin 34304 -> 33792 bytes .../trampolines/uv-trampoline-i686-gui.exe | Bin 35328 -> 34304 bytes .../uv-trampoline-x86_64-console.exe | Bin 40960 -> 40448 bytes .../trampolines/uv-trampoline-x86_64-gui.exe | Bin 41984 -> 41472 bytes crates/uv/Cargo.toml | 3 +- crates/uv/src/commands/project/run.rs | 6 +- crates/uv/src/commands/python/install.rs | 44 +- 20 files changed, 619 insertions(+), 436 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13d693a65..e9d615da8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -599,9 +599,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.30" +version = "1.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" dependencies = [ "jobserver", "libc", @@ -675,6 +675,8 @@ checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f" dependencies = [ "clap_builder", "clap_derive", + "crypto-common", + "inout", ] [[package]] @@ -1071,6 +1073,15 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + [[package]] name = "derive_arbitrary" version = "1.4.1" @@ -1082,6 +1093,15 @@ dependencies = [ "syn", ] +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + [[package]] name = "diff" version = "0.1.13" @@ -1157,9 +1177,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "dyn-clone" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" @@ -1286,9 +1306,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -1476,9 +1496,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ "fastrand", "futures-core", @@ -2260,9 +2280,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ "bitflags 2.9.1", "libc", @@ -2496,18 +2516,18 @@ dependencies = [ [[package]] name = "munge" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cce144fab80fbb74ec5b89d1ca9d41ddf6b644ab7e986f7d3ed0aab31625cb1" +checksum = "d7feb0b48aa0a25f9fe0899482c6e1379ee7a11b24a53073eacdecb9adb6dc60" dependencies = [ "munge_macro", ] [[package]] name = "munge_macro" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "574af9cd5b9971cbfdf535d6a8d533778481b241c447826d976101e0149392a1" +checksum = "f2e3795a5d2da581a8b252fec6022eee01aea10161a4d1bf237d4cbe47f7e988" dependencies = [ "proc-macro2", "quote", @@ -2606,6 +2626,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.46" @@ -2726,6 +2752,23 @@ version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" +[[package]] +name = "p12" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4873306de53fe82e7e484df31e1e947d61514b6ea2ed6cd7b45d63006fd9224" +dependencies = [ + "cbc", + "cipher", + "des", + "getrandom 0.2.16", + "hmac", + "lazy_static", + "rc2", + "sha1", + "yasna", +] + [[package]] name = "parking" version = "2.2.1" @@ -2773,6 +2816,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2931,6 +2984,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -3229,6 +3288,28 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rc2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd" +dependencies = [ + "cipher", +] + +[[package]] +name = "rcgen" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0068c5b3cab1d4e271e0bb6539c87563c43411cad90b057b15c79958fbeb41f7" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + [[package]] name = "rctree" version = "0.5.0" @@ -3237,18 +3318,18 @@ checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" [[package]] name = "redox_syscall" -version = "0.5.15" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags 2.9.1", ] [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", @@ -3564,9 +3645,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -3602,9 +3683,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.29" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "once_cell", "ring", @@ -3909,6 +3990,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.9" @@ -3954,9 +4046,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -4398,6 +4490,25 @@ dependencies = [ "tikv-jemalloc-sys", ] +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + [[package]] name = "tiny-skia" version = "0.8.4" @@ -4465,9 +4576,9 @@ source = "git+https://github.com/astral-sh/tl.git?rev=6e25b2ee2513d75385101a8ff9 [[package]] name = "tokio" -version = "1.47.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", @@ -4519,9 +4630,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -4533,9 +4644,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" dependencies = [ "foldhash", "indexmap", @@ -4564,20 +4675,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "toml_datetime 0.6.11", - "winnow", -] - -[[package]] -name = "toml_edit" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1dee9dc43ac2aaf7d3b774e2fba5148212bf2bd9374f4e50152ebe9afd03d42" +checksum = "17d3b47e6b7a040216ae5302712c94d1cf88c95b47efa80e2c59ce96c878267e" dependencies = [ "indexmap", "serde", @@ -4590,9 +4690,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" dependencies = [ "winnow", ] @@ -6362,9 +6462,13 @@ dependencies = [ "assert_cmd", "assert_fs", "fs-err", + "p12", + "rcgen", + "tempfile", "thiserror 2.0.12", "uv-fs", "which", + "windows 0.61.3", "zip", ] @@ -6734,7 +6838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" dependencies = [ "windows-core 0.59.0", - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -6769,7 +6873,7 @@ dependencies = [ "windows-interface", "windows-result", "windows-strings 0.3.1", - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -6916,7 +7020,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -6952,10 +7056,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -7198,6 +7303,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "yoke" version = "0.8.0" @@ -7337,9 +7451,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "bdbb9122ea75b11bf96e7492afb723e8a7fbe12c67417aa95e7e3d18144d37cd" dependencies = [ "yoke", "zerofrom", diff --git a/Cargo.toml b/Cargo.toml index 5b3fcafd2..ae4416f81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -206,7 +206,9 @@ hyper = { version = "1.4.1", features = ["server", "http1"] } hyper-util = { version = "0.1.8", features = ["tokio"] } ignore = { version = "0.4.23" } insta = { version = "1.40.0", features = ["json", "filters", "redactions"] } +p12 = { version = "0.6.3" } predicates = { version = "3.1.2" } +rcgen = { version = "0.14.3" } similar = { version = "2.6.0" } temp-env = { version = "0.3.6" } test-case = { version = "3.3.1" } diff --git a/crates/uv-distribution/src/metadata/requires_dist.rs b/crates/uv-distribution/src/metadata/requires_dist.rs index a5645c126..e2f342234 100644 --- a/crates/uv-distribution/src/metadata/requires_dist.rs +++ b/crates/uv-distribution/src/metadata/requires_dist.rs @@ -619,11 +619,11 @@ mod test { "#}; assert_snapshot!(format_err(input).await, @r#" - error: TOML parse error at line 8, column 28 + error: TOML parse error at line 8, column 16 | 8 | tqdm = { url = invalid url to tqdm-4.66.0-py3-none-any.whl" } - | ^ - missing comma between key-value pairs, expected `,` + | ^ + missing opening quote, expected `"` "#); } diff --git a/crates/uv-install-wheel/Cargo.toml b/crates/uv-install-wheel/Cargo.toml index 06e72af39..3eac625f9 100644 --- a/crates/uv-install-wheel/Cargo.toml +++ b/crates/uv-install-wheel/Cargo.toml @@ -29,7 +29,6 @@ uv-normalize = { workspace = true } uv-pep440 = { workspace = true } uv-pypi-types = { workspace = true } uv-shell = { workspace = true } -uv-trampoline-builder = { workspace = true } uv-warnings = { workspace = true } clap = { workspace = true, optional = true, features = ["derive"] } @@ -52,6 +51,8 @@ tracing = { workspace = true } walkdir = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] +uv-trampoline-builder = { workspace = true } + same-file = { workspace = true } self-replace = { workspace = true } diff --git a/crates/uv-install-wheel/src/lib.rs b/crates/uv-install-wheel/src/lib.rs index f36a699f1..ea835d7be 100644 --- a/crates/uv-install-wheel/src/lib.rs +++ b/crates/uv-install-wheel/src/lib.rs @@ -80,6 +80,7 @@ pub enum Error { MismatchedVersion(Version, Version), #[error("Invalid egg-link")] InvalidEggLink(PathBuf), + #[cfg(windows)] #[error(transparent)] LauncherError(#[from] uv_trampoline_builder::Error), #[error("Scripts must not use the reserved name {0}")] diff --git a/crates/uv-install-wheel/src/wheel.rs b/crates/uv-install-wheel/src/wheel.rs index 78ebab994..62d80e291 100644 --- a/crates/uv-install-wheel/src/wheel.rs +++ b/crates/uv-install-wheel/src/wheel.rs @@ -17,7 +17,6 @@ use uv_fs::{Simplified, persist_with_retry_sync, relative_to}; use uv_normalize::PackageName; use uv_pypi_types::DirectUrl; use uv_shell::escape_posix_for_single_quotes; -use uv_trampoline_builder::windows_script_launcher; use uv_warnings::warn_user_once; use crate::record::RecordEntry; @@ -226,14 +225,18 @@ pub(crate) fn write_script_entrypoints( ); // If necessary, wrap the launcher script in a Windows launcher binary. - if cfg!(windows) { + #[cfg(windows)] + { + use uv_trampoline_builder::windows_script_launcher; write_file_recorded( site_packages, &entrypoint_relative, &windows_script_launcher(&launcher_python_script, is_gui, &launcher_executable)?, record, )?; - } else { + } + #[cfg(not(windows))] + { write_file_recorded( site_packages, &entrypoint_relative, diff --git a/crates/uv-python/Cargo.toml b/crates/uv-python/Cargo.toml index 1c6f09b15..cccf911c0 100644 --- a/crates/uv-python/Cargo.toml +++ b/crates/uv-python/Cargo.toml @@ -34,7 +34,6 @@ uv-pypi-types = { workspace = true } uv-redacted = { workspace = true } uv-state = { workspace = true } uv-static = { workspace = true } -uv-trampoline-builder = { workspace = true } uv-warnings = { workspace = true } anyhow = { workspace = true } @@ -69,6 +68,8 @@ which = { workspace = true } once_cell = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] +uv-trampoline-builder = { workspace = true } + windows-registry = { workspace = true } windows-result = { workspace = true } windows-sys = { workspace = true } diff --git a/crates/uv-python/src/managed.rs b/crates/uv-python/src/managed.rs index 34e239e0a..581cb448d 100644 --- a/crates/uv-python/src/managed.rs +++ b/crates/uv-python/src/managed.rs @@ -9,7 +9,6 @@ use std::str::FromStr; use fs_err as fs; use itertools::Itertools; -use same_file::is_same_file; use thiserror::Error; use tracing::{debug, warn}; use uv_configuration::{Preview, PreviewFeatures}; @@ -21,7 +20,6 @@ use uv_platform::{Error as PlatformError, Os}; use uv_platform::{LibcDetectionError, Platform}; use uv_state::{StateBucket, StateStore}; use uv_static::EnvVars; -use uv_trampoline_builder::{Launcher, windows_python_launcher}; use crate::downloads::{Error as DownloadError, ManagedPythonDownload}; use crate::implementation::{ @@ -94,6 +92,7 @@ pub enum Error { }, #[error("Failed to find a directory to install executables into")] NoExecutableDirectory, + #[cfg(windows)] #[error(transparent)] LauncherError(#[from] uv_trampoline_builder::Error), #[error("Failed to read managed Python directory name: {0}")] @@ -620,13 +619,17 @@ impl ManagedPythonInstallation { /// Returns `true` if the path is a link to this installation's binary, e.g., as created by /// [`create_bin_link`]. pub fn is_bin_link(&self, path: &Path) -> bool { - if cfg!(unix) { - is_same_file(path, self.executable(false)).unwrap_or_default() - } else if cfg!(windows) { + #[cfg(unix)] + { + same_file::is_same_file(path, self.executable(false)).unwrap_or_default() + } + #[cfg(windows)] + { + use uv_trampoline_builder::{Launcher, LauncherKind}; let Some(launcher) = Launcher::try_from_path(path).unwrap_or_default() else { return false; }; - if !matches!(launcher.kind, uv_trampoline_builder::LauncherKind::Python) { + if !matches!(launcher.kind, LauncherKind::Python) { return false; } // We canonicalize the target path of the launcher in case it includes a minor version @@ -634,7 +637,9 @@ impl ManagedPythonInstallation { // directly. dunce::canonicalize(&launcher.python_path).unwrap_or(launcher.python_path) == self.executable(false) - } else { + } + #[cfg(not(any(unix, windows)))] + { unreachable!("Only Windows and Unix are supported") } } @@ -877,7 +882,8 @@ pub fn create_link_to_executable(link: &Path, executable: &Path) -> Result<(), E err, })?; - if cfg!(unix) { + #[cfg(unix)] + { // Note this will never copy on Unix — we use it here to allow compilation on Windows match symlink_or_copy_file(executable, link) { Ok(()) => Ok(()), @@ -890,7 +896,11 @@ pub fn create_link_to_executable(link: &Path, executable: &Path) -> Result<(), E err, }), } - } else if cfg!(windows) { + } + #[cfg(windows)] + { + use uv_trampoline_builder::windows_python_launcher; + // TODO(zanieb): Install GUI launchers as well let launcher = windows_python_launcher(executable, false)?; @@ -906,7 +916,9 @@ pub fn create_link_to_executable(link: &Path, executable: &Path) -> Result<(), E err, }) } - } else { + } + #[cfg(not(any(unix, windows)))] + { unimplemented!("Only Windows and Unix systems are supported.") } } diff --git a/crates/uv-trampoline-builder/Cargo.toml b/crates/uv-trampoline-builder/Cargo.toml index be38ddd82..7227bd7be 100644 --- a/crates/uv-trampoline-builder/Cargo.toml +++ b/crates/uv-trampoline-builder/Cargo.toml @@ -23,14 +23,22 @@ workspace = true [dependencies] uv-fs = { workspace = true } - fs-err = {workspace = true } +tempfile = { workspace = true } thiserror = { workspace = true } zip = { workspace = true } +[target.'cfg(target_os = "windows")'.dependencies] +windows = { version = "0.61.0", features = [ + "std", + "Win32_System_LibraryLoader", +] } + [dev-dependencies] assert_cmd = { workspace = true } assert_fs = { workspace = true } anyhow = { workspace = true } fs-err = { workspace = true } +p12 = { workspace = true } +rcgen = { workspace = true } which = { workspace = true } diff --git a/crates/uv-trampoline-builder/src/lib.rs b/crates/uv-trampoline-builder/src/lib.rs index 1a25b9454..4f982d9c4 100644 --- a/crates/uv-trampoline-builder/src/lib.rs +++ b/crates/uv-trampoline-builder/src/lib.rs @@ -1,8 +1,7 @@ -use std::io::{self, Cursor, Read, Seek, Write}; +use std::io::{self, Cursor, Write}; use std::path::{Path, PathBuf}; use std::str::Utf8Error; -use fs_err::File; use thiserror::Error; use uv_fs::Simplified; use zip::ZipWriter; @@ -32,16 +31,23 @@ const LAUNCHER_AARCH64_GUI: &[u8] = const LAUNCHER_AARCH64_CONSOLE: &[u8] = include_bytes!("../../uv-trampoline/trampolines/uv-trampoline-aarch64-console.exe"); -// See `uv-trampoline::bounce`. These numbers must match. -const PATH_LENGTH_SIZE: usize = size_of::(); -const MAX_PATH_LENGTH: u32 = 32 * 1024; -const MAGIC_NUMBER_SIZE: usize = 4; +// https://learn.microsoft.com/en-us/windows/win32/menurc/resource-types +#[cfg(windows)] +const RT_RCDATA: u16 = 10; + +// Resource IDs matching uv-trampoline +const RESOURCE_TRAMPOLINE_KIND: &str = "UV_TRAMPOLINE_KIND\0"; +const RESOURCE_PYTHON_PATH: &str = "UV_PYTHON_PATH\0"; +// Note: This does not need to be looked up as a resource, as we rely on `zipimport` +// to do the loading work. Still, keeping the content under a resource means that it +// sits nicely under the PE format. +const RESOURCE_SCRIPT_DATA: &str = "UV_SCRIPT_DATA\0"; #[derive(Debug)] pub struct Launcher { pub kind: LauncherKind, pub python_path: PathBuf, - payload: Vec, + pub script_data: Option>, } impl Launcher { @@ -49,119 +55,90 @@ impl Launcher { /// /// Returns `Ok(None)` if the file is not a trampoline executable. /// Returns `Err` if the file looks like a trampoline executable but is formatted incorrectly. - /// - /// Expects the following metadata to be at the end of the file: - /// - /// ```text - /// - file path (no greater than 32KB) - /// - file path length (u32) - /// - magic number(4 bytes) - /// ``` - /// - /// This should only be used on Windows, but should just return `Ok(None)` on other platforms. - /// - /// This is an implementation of [`uv-trampoline::bounce::read_trampoline_metadata`] that - /// returns errors instead of panicking. Unlike the utility there, we don't assume that the - /// file we are reading is a trampoline. - #[allow(clippy::cast_possible_wrap)] + #[allow(unused_variables)] pub fn try_from_path(path: &Path) -> Result, Error> { - let mut file = File::open(path)?; + #[cfg(not(windows))] + { + Err(Error::NotWindows) + } + #[cfg(windows)] + { + use std::os::windows::ffi::OsStrExt; + use windows::Win32::System::LibraryLoader::LOAD_LIBRARY_AS_DATAFILE; + use windows::Win32::System::LibraryLoader::LoadLibraryExW; - // Read the magic number - let Some(kind) = LauncherKind::try_from_file(&mut file)? else { - return Ok(None); - }; + let mut path_str = path.as_os_str().encode_wide().collect::>(); + path_str.push(0); - // Seek to the start of the path length. - let path_length_offset = (MAGIC_NUMBER_SIZE + PATH_LENGTH_SIZE) as i64; - file.seek(io::SeekFrom::End(-path_length_offset)) - .map_err(|err| { - Error::InvalidLauncherSeek("path length".to_string(), path_length_offset, err) - })?; + #[allow(unsafe_code)] + let Some(module) = (unsafe { + LoadLibraryExW( + windows::core::PCWSTR(path_str.as_ptr()), + None, + LOAD_LIBRARY_AS_DATAFILE, + ) + .ok() + }) else { + return Ok(None); + }; - // Read the path length - let mut buffer = [0; PATH_LENGTH_SIZE]; - file.read_exact(&mut buffer) - .map_err(|err| Error::InvalidLauncherRead("path length".to_string(), err))?; + let result = (|| { + let Some(kind_data) = read_resource(module, RESOURCE_TRAMPOLINE_KIND) else { + return Ok(None); + }; + let Some(kind) = LauncherKind::from_resource_value(kind_data[0]) else { + return Err(Error::UnprocessableMetadata); + }; - let path_length = { - let raw_length = u32::from_le_bytes(buffer); + let Some(path_data) = read_resource(module, RESOURCE_PYTHON_PATH) else { + return Ok(None); + }; + let python_path = PathBuf::from( + String::from_utf8(path_data) + .map_err(|err| Error::InvalidPath(err.utf8_error()))?, + ); - if raw_length > MAX_PATH_LENGTH { - return Err(Error::InvalidPathLength(raw_length)); - } + let script_data = read_resource(module, RESOURCE_SCRIPT_DATA); - // SAFETY: Above we guarantee the length is less than 32KB - raw_length as usize - }; + Ok(Some(Self { + kind, + python_path, + script_data, + })) + })(); - // Seek to the start of the path - let path_offset = (MAGIC_NUMBER_SIZE + PATH_LENGTH_SIZE + path_length) as i64; - file.seek(io::SeekFrom::End(-path_offset)).map_err(|err| { - Error::InvalidLauncherSeek("executable path".to_string(), path_offset, err) - })?; + #[allow(unsafe_code)] + unsafe { + windows::Win32::Foundation::FreeLibrary(module) + .map_err(|err| Error::Io(io::Error::from_raw_os_error(err.code().0)))?; + }; - // Read the path - let mut buffer = vec![0u8; path_length]; - file.read_exact(&mut buffer) - .map_err(|err| Error::InvalidLauncherRead("executable path".to_string(), err))?; - - let path = PathBuf::from( - String::from_utf8(buffer).map_err(|err| Error::InvalidPath(err.utf8_error()))?, - ); - - #[allow(clippy::cast_possible_truncation)] - let file_size = { - let raw_length = file - .seek(io::SeekFrom::End(0)) - .map_err(|e| Error::InvalidLauncherSeek("size probe".into(), 0, e))?; - - if raw_length > usize::MAX as u64 { - return Err(Error::InvalidDataLength(raw_length)); - } - - // SAFETY: Above we guarantee the length is less than uszie - raw_length as usize - }; - - // Read the payload - file.seek(io::SeekFrom::Start(0)) - .map_err(|e| Error::InvalidLauncherSeek("rewind".into(), 0, e))?; - let payload_len = - file_size.saturating_sub(MAGIC_NUMBER_SIZE + PATH_LENGTH_SIZE + path_length); - let mut buffer = vec![0u8; payload_len]; - file.read_exact(&mut buffer) - .map_err(|err| Error::InvalidLauncherRead("payload".into(), err))?; - - Ok(Some(Self { - kind, - payload: buffer, - python_path: path, - })) + result + } } - pub fn write_to_file(self, file: &mut File) -> Result<(), Error> { + pub fn write_to_file(self, file_path: &Path, is_gui: bool) -> Result<(), Error> { let python_path = self.python_path.simplified_display().to_string(); - if python_path.len() > MAX_PATH_LENGTH as usize { - return Err(Error::InvalidPathLength( - u32::try_from(python_path.len()).expect("path length already checked"), - )); + // Write the launcher binary + fs_err::write(file_path, get_launcher_bin(is_gui)?)?; + + // Write resources + let resources = &[ + ( + RESOURCE_TRAMPOLINE_KIND, + &[self.kind.to_resource_value()][..], + ), + (RESOURCE_PYTHON_PATH, python_path.as_bytes()), + ]; + if let Some(script_data) = self.script_data { + let mut all_resources = resources.to_vec(); + all_resources.push((RESOURCE_SCRIPT_DATA, &script_data)); + write_resources(file_path, &all_resources)?; + } else { + write_resources(file_path, resources)?; } - let mut launcher: Vec = Vec::with_capacity( - self.payload.len() + python_path.len() + PATH_LENGTH_SIZE + MAGIC_NUMBER_SIZE, - ); - launcher.extend_from_slice(&self.payload); - launcher.extend_from_slice(python_path.as_bytes()); - launcher.extend_from_slice( - &u32::try_from(python_path.len()) - .expect("file path should be smaller than 4GB") - .to_le_bytes(), - ); - launcher.extend_from_slice(self.kind.magic_number()); - - file.write_all(&launcher)?; Ok(()) } @@ -169,8 +146,8 @@ impl Launcher { pub fn with_python_path(self, path: PathBuf) -> Self { Self { kind: self.kind, - payload: self.payload, python_path: path, + script_data: self.script_data, } } } @@ -187,45 +164,20 @@ pub enum LauncherKind { } impl LauncherKind { - /// Return the magic number for this [`LauncherKind`]. - const fn magic_number(self) -> &'static [u8; 4] { + fn to_resource_value(self) -> u8 { match self { - Self::Script => b"UVSC", - Self::Python => b"UVPY", + Self::Script => 1, + Self::Python => 2, } } - /// Read a [`LauncherKind`] from 4 byte buffer. - /// - /// If the buffer does not contain a matching magic number, `None` is returned. - fn try_from_bytes(bytes: [u8; MAGIC_NUMBER_SIZE]) -> Option { - if &bytes == Self::Script.magic_number() { - return Some(Self::Script); + #[cfg(windows)] + fn from_resource_value(value: u8) -> Option { + match value { + 1 => Some(Self::Script), + 2 => Some(Self::Python), + _ => None, } - if &bytes == Self::Python.magic_number() { - return Some(Self::Python); - } - None - } - - /// Read a [`LauncherKind`] from a file handle, based on the magic number. - /// - /// This will mutate the file handle, seeking to the end of the file. - /// - /// If the file cannot be read, an [`io::Error`] is returned. If the path is not a launcher, - /// `None` is returned. - #[allow(clippy::cast_possible_wrap)] - pub fn try_from_file(file: &mut File) -> Result, Error> { - // If the file is less than four bytes, it's not a launcher. - let Ok(_) = file.seek(io::SeekFrom::End(-(MAGIC_NUMBER_SIZE as i64))) else { - return Ok(None); - }; - - let mut buffer = [0; MAGIC_NUMBER_SIZE]; - file.read_exact(&mut buffer) - .map_err(|err| Error::InvalidLauncherRead("magic number".to_string(), err))?; - - Ok(Self::try_from_bytes(buffer)) } } @@ -234,22 +186,18 @@ impl LauncherKind { pub enum Error { #[error(transparent)] Io(#[from] io::Error), - #[error("Only paths with a length up to 32KB are supported but found a length of {0} bytes")] - InvalidPathLength(u32), - #[error("Only data with a length up to usize is supported but found a length of {0} bytes")] - InvalidDataLength(u64), #[error("Failed to parse executable path")] InvalidPath(#[source] Utf8Error), - #[error("Failed to seek to {0} at offset {1}")] - InvalidLauncherSeek(String, i64, #[source] io::Error), - #[error("Failed to read launcher {0}")] - InvalidLauncherRead(String, #[source] io::Error), #[error( "Unable to create Windows launcher for: {0} (only x86_64, x86, and arm64 are supported)" )] UnsupportedWindowsArch(&'static str), #[error("Unable to create Windows launcher on non-Windows platform")] NotWindows, + #[error("Cannot process launcher metadata from resource")] + UnprocessableMetadata, + #[error("Resources over 2^32 bytes are not supported")] + ResourceTooLarge, } #[allow(clippy::unnecessary_wraps, unused_variables)] @@ -288,6 +236,75 @@ fn get_launcher_bin(gui: bool) -> Result<&'static [u8], Error> { }) } +/// Helper to write Windows PE resources +#[allow(unused_variables)] +fn write_resources(path: &Path, resources: &[(&str, &[u8])]) -> Result<(), Error> { + #[cfg(not(windows))] + { + Err(Error::NotWindows) + } + #[cfg(windows)] + { + #[allow(unsafe_code)] + unsafe { + use std::os::windows::ffi::OsStrExt; + use windows::Win32::System::LibraryLoader::{ + BeginUpdateResourceW, EndUpdateResourceW, UpdateResourceW, + }; + + let mut path_str = path.as_os_str().encode_wide().collect::>(); + path_str.push(0); + let handle = BeginUpdateResourceW(windows::core::PCWSTR(path_str.as_ptr()), false) + .map_err(|err| Error::Io(io::Error::from_raw_os_error(err.code().0)))?; + + for (name, data) in resources { + let mut name_null_term = name.encode_utf16().collect::>(); + name_null_term.push(0); + UpdateResourceW( + handle, + windows::core::PCWSTR(RT_RCDATA as *const _), + windows::core::PCWSTR(name_null_term.as_ptr()), + 0, + Some(data.as_ptr().cast()), + u32::try_from(data.len()).map_err(|_| Error::ResourceTooLarge)?, + ) + .map_err(|err| Error::Io(io::Error::from_raw_os_error(err.code().0)))?; + } + + EndUpdateResourceW(handle, false) + .map_err(|err| Error::Io(io::Error::from_raw_os_error(err.code().0)))?; + } + + Ok(()) + } +} + +#[cfg(windows)] +/// Safely reads a resource from a PE file +fn read_resource(handle: windows::Win32::Foundation::HMODULE, name: &str) -> Option> { + #[allow(unsafe_code)] + unsafe { + use windows::Win32::System::LibraryLoader::{ + FindResourceW, LoadResource, LockResource, SizeofResource, + }; + // Find the resource + let resource = FindResourceW( + Some(handle), + windows::core::PCWSTR(name.encode_utf16().collect::>().as_ptr()), + windows::core::PCWSTR(RT_RCDATA as *const _), + ); + + // Get resource size and data + let size = SizeofResource(Some(handle), resource); + let data = LoadResource(Some(handle), resource).ok()?; + let ptr = LockResource(data); + let ptr = ptr.cast::(); + + // Copy the resource data into a Vec + Some(std::slice::from_raw_parts(ptr, size as usize).to_vec()) + } +} + /// A Windows script is a minimal .exe launcher binary with the python entrypoint script appended as /// stored zip file. /// @@ -325,16 +342,28 @@ pub fn windows_script_launcher( let python = python_executable.as_ref(); let python_path = python.simplified_display().to_string(); - let mut launcher: Vec = Vec::with_capacity(launcher_bin.len() + payload.len()); - launcher.extend_from_slice(launcher_bin); - launcher.extend_from_slice(&payload); - launcher.extend_from_slice(python_path.as_bytes()); - launcher.extend_from_slice( - &u32::try_from(python_path.len()) - .expect("file path should be smaller than 4GB") - .to_le_bytes(), - ); - launcher.extend_from_slice(LauncherKind::Script.magic_number()); + // Start with base launcher binary + // Create temporary file for the launcher + let temp_dir = tempfile::tempdir()?; + let temp_file = temp_dir + .path() + .join(format!("uv-trampoline-{}.exe", std::process::id())); + fs_err::write(&temp_file, launcher_bin)?; + + // Write resources + let resources = &[ + ( + RESOURCE_TRAMPOLINE_KIND, + &[LauncherKind::Script.to_resource_value()][..], + ), + (RESOURCE_PYTHON_PATH, python_path.as_bytes()), + (RESOURCE_SCRIPT_DATA, &payload), + ]; + write_resources(&temp_file, resources)?; + + // Read back the complete file + let launcher = fs_err::read(&temp_file)?; + fs_err::remove_file(temp_file)?; Ok(launcher) } @@ -358,15 +387,26 @@ pub fn windows_python_launcher( let python = python_executable.as_ref(); let python_path = python.simplified_display().to_string(); - let mut launcher: Vec = Vec::with_capacity(launcher_bin.len()); - launcher.extend_from_slice(launcher_bin); - launcher.extend_from_slice(python_path.as_bytes()); - launcher.extend_from_slice( - &u32::try_from(python_path.len()) - .expect("file path should be smaller than 4GB") - .to_le_bytes(), - ); - launcher.extend_from_slice(LauncherKind::Python.magic_number()); + // Create temporary file for the launcher + let temp_dir = tempfile::tempdir()?; + let temp_file = temp_dir + .path() + .join(format!("uv-trampoline-{}.exe", std::process::id())); + fs_err::write(&temp_file, launcher_bin)?; + + // Write resources + let resources = &[ + ( + RESOURCE_TRAMPOLINE_KIND, + &[LauncherKind::Python.to_resource_value()][..], + ), + (RESOURCE_PYTHON_PATH, python_path.as_bytes()), + ]; + write_resources(&temp_file, resources)?; + + // Read back the complete file + let launcher = fs_err::read(&temp_file)?; + fs_err::remove_file(temp_file)?; Ok(launcher) } @@ -376,6 +416,7 @@ pub fn windows_python_launcher( mod test { use std::io::Write; use std::path::Path; + use std::path::PathBuf; use std::process::Command; use anyhow::Result; @@ -486,6 +527,66 @@ if __name__ == "__main__": format!("#!{executable}") } + /// Creates a self-signed certificate and returns its path. + fn create_temp_certificate(temp_dir: &tempfile::TempDir) -> Result { + use p12::PFX; + use rcgen::{CertificateParams, KeyPair}; + + let signing_key = KeyPair::generate()?; + let mut cert_params = CertificateParams::new(vec!["UvTrampolineTest".to_string()])?; + cert_params.insert_extended_key_usage(rcgen::ExtendedKeyUsagePurpose::CodeSigning); + let cert = cert_params.self_signed(&signing_key)?; + + // Create PKCS#12 archive + let pfx = PFX::new( + cert.der(), + &signing_key.serialize_der(), + None, + "", + "UvTrampolineTest", + ) + .expect("Failed to create PFX archive"); + + // Create temp file + let temp_pfx = temp_dir.path().join("uv-trampoline-test.pfx"); + fs_err::write(&temp_pfx, pfx.to_der())?; + + println!( + "Wrote testing code-signing certificate in {}", + temp_pfx.display() + ); + Ok(temp_pfx) + } + + /// Signs the given binary using `PowerShell`'s `Set-AuthenticodeSignature` with a temporary certificate. + fn sign_authenticode(bin_path: impl AsRef) { + let temp_dir = tempfile::tempdir().expect("Failed to create temporary directory"); + let temp_pfx = + create_temp_certificate(&temp_dir).expect("Failed to create self-signed certificate"); + + Command::new("powershell") + .args([ + "-NoProfile", + "-NonInteractive", + "-Command", + &format!( + r" + $ErrorActionPreference = 'Stop' + Import-Module Microsoft.PowerShell.Security + $pfx = Get-PfxCertificate -FilePath '{}'; + Set-AuthenticodeSignature -FilePath '{}' -Certificate $pfx; + ", + temp_pfx.display().to_string().replace('\'', "''"), + bin_path.as_ref().display().to_string().replace('\'', "''"), + ), + ]) + .env_remove("PSModulePath") + .assert() + .success(); + + println!("Signed binary: {}", bin_path.as_ref().display()); + } + #[test] fn console_script_launcher() -> Result<()> { // Create Temp Dirs @@ -540,6 +641,17 @@ if __name__ == "__main__": assert!(launcher.kind == LauncherKind::Script); assert!(launcher.python_path == python_executable_path); + // Now code-sign the launcher and verify that it still works. + sign_authenticode(console_bin_path.path()); + + let stdout_predicate = "Hello from uv-trampoline-console.exe\r\n"; + let stderr_predicate = "Hello from uv-trampoline-console.exe\r\n"; + Command::new(console_bin_path.path()) + .assert() + .success() + .stdout(stdout_predicate) + .stderr(stderr_predicate); + Ok(()) } @@ -556,7 +668,9 @@ if __name__ == "__main__": let console_launcher = windows_python_launcher(&python_executable_path, false)?; // Create Launcher - File::create(console_bin_path.path())?.write_all(console_launcher.as_ref())?; + { + File::create(console_bin_path.path())?.write_all(console_launcher.as_ref())?; + } println!( "Wrote Python Launcher in {}", @@ -578,6 +692,15 @@ if __name__ == "__main__": assert!(launcher.kind == LauncherKind::Python); assert!(launcher.python_path == python_executable_path); + // Now code-sign the launcher and verify that it still works. + sign_authenticode(console_bin_path.path()); + Command::new(console_bin_path.path()) + .arg("-c") + .arg("print('Hello from Python Launcher')") + .assert() + .success() + .stdout("Hello from Python Launcher\r\n"); + Ok(()) } @@ -600,7 +723,9 @@ if __name__ == "__main__": windows_script_launcher(&launcher_gui_script, true, &pythonw_executable_path)?; // Create Launcher - File::create(gui_bin_path.path())?.write_all(gui_launcher.as_ref())?; + { + File::create(gui_bin_path.path())?.write_all(gui_launcher.as_ref())?; + } println!("Wrote GUI Launcher in {}", gui_bin_path.path().display()); diff --git a/crates/uv-trampoline/Cargo.toml b/crates/uv-trampoline/Cargo.toml index 718c7e3f9..b5b5e2ca8 100644 --- a/crates/uv-trampoline/Cargo.toml +++ b/crates/uv-trampoline/Cargo.toml @@ -42,6 +42,7 @@ windows = { version = "0.61.0", features = [ "Win32_System_Console", "Win32_System_Environment", "Win32_System_JobObjects", + "Win32_System_LibraryLoader", "Win32_System_Threading", "Win32_UI_WindowsAndMessaging", ] } diff --git a/crates/uv-trampoline/README.md b/crates/uv-trampoline/README.md index 2e5be82ce..e9c426ebf 100644 --- a/crates/uv-trampoline/README.md +++ b/crates/uv-trampoline/README.md @@ -92,24 +92,16 @@ arbitrary Python scripts, and when invoked it bounces to invoking `python `. -The intended use is: +It uses PE resources to store/load the information required to do this: -- take your Python script, name it `__main__.py`, and pack it into a `.zip` file. Then concatenate - that `.zip` file onto the end of one of our prebuilt `.exe`s. -- After the zip file content, write the path to the Python executable that the script uses to run - the Python script as UTF-8 encoded string, followed by the path's length as a 32-bit little-endian - integer. -- At the very end, write the magic number `UVUV` in bytes. +| 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` | -| `launcher.exe` | -| :-------------------------: | -| `` | -| `` | -| `` | -| `` | - -Then when you run `python` on the `.exe`, it will see the `.zip` trailer at the end of the `.exe`, -and automagically look inside to find and execute `__main__.py`. Easy-peasy. +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? diff --git a/crates/uv-trampoline/src/bounce.rs b/crates/uv-trampoline/src/bounce.rs index f60f28e97..0eaffb0ba 100644 --- a/crates/uv-trampoline/src/bounce.rs +++ b/crates/uv-trampoline/src/bounce.rs @@ -1,8 +1,5 @@ #![allow(clippy::disallowed_types)] use std::ffi::{CString, c_void}; -use std::fs::File; -use std::io::{Read, Seek, SeekFrom}; -use std::mem::size_of; use std::path::{Path, PathBuf}; use std::vec::Vec; @@ -20,6 +17,7 @@ use windows::Win32::{ JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation, QueryInformationJobObject, SetInformationJobObject, }, + System::LibraryLoader::{FindResourceW, LoadResource, LockResource, SizeofResource}, System::Threading::{ CreateProcessA, GetExitCodeProcess, GetStartupInfoA, INFINITE, PROCESS_CREATION_FLAGS, PROCESS_INFORMATION, STARTF_USESTDHANDLES, STARTUPINFOA, WaitForInputIdle, @@ -34,8 +32,12 @@ use windows::core::{BOOL, PSTR, s}; use crate::{error, format, warn}; -const PATH_LEN_SIZE: usize = size_of::(); -const MAX_PATH_LEN: u32 = 32 * 1024; +// https://learn.microsoft.com/en-us/windows/win32/menurc/resource-types +const RT_RCDATA: u16 = 10; + +/// Resource IDs for the trampoline metadata +const RESOURCE_TRAMPOLINE_KIND: &str = "UV_TRAMPOLINE_KIND\0"; +const RESOURCE_PYTHON_PATH: &str = "UV_PYTHON_PATH\0"; /// The kind of trampoline. enum TrampolineKind { @@ -46,21 +48,35 @@ enum TrampolineKind { } impl TrampolineKind { - const fn magic_number(&self) -> &'static [u8; 4] { - match self { - Self::Script => b"UVSC", - Self::Python => b"UVPY", + fn from_resource(data: &[u8]) -> Option { + match data.first() { + Some(1) => Some(Self::Script), + Some(2) => Some(Self::Python), + _ => None, } } +} - fn from_buffer(buffer: &[u8]) -> Option { - if buffer.ends_with(Self::Script.magic_number()) { - Some(Self::Script) - } else if buffer.ends_with(Self::Python.magic_number()) { - Some(Self::Python) - } else { - None - } +/// Safely loads a resource from the current module +fn load_resource(resource_id: &str) -> Option> { + unsafe { + let mut resource_id_null_term = resource_id.encode_utf16().collect::>(); + resource_id_null_term.push(0); + // Find the resource + let resource = FindResourceW( + None, + windows::core::PCWSTR(resource_id_null_term.as_ptr()), + windows::core::PCWSTR(RT_RCDATA as *const _), + ); + + // Get resource size and data + let size = SizeofResource(None, resource); + let data = LoadResource(None, resource).ok(); + + let ptr = LockResource(data?) as *const u8; + + // Copy the resource data into a Vec + Some(std::slice::from_raw_parts(ptr, size as usize).to_vec()) } } @@ -70,14 +86,50 @@ fn make_child_cmdline() -> CString { let executable_name = std::env::current_exe().unwrap_or_else(|_| { error_and_exit("Failed to get executable name"); }); - let (kind, python_exe) = read_trampoline_metadata(executable_name.as_ref()); - let mut child_cmdline = Vec::::new(); + // Load trampoline kind + let trampoline_kind = load_resource(RESOURCE_TRAMPOLINE_KIND) + .and_then(|data| TrampolineKind::from_resource(&data)) + .unwrap_or_else(|| error_and_exit("Failed to load trampoline kind from resources")); + + // Load Python path + let python_path = load_resource(RESOURCE_PYTHON_PATH) + .and_then(|data| String::from_utf8(data).ok()) + .map(PathBuf::from) + .unwrap_or_else(|| error_and_exit("Failed to load Python path from resources")); + + let python_exe = if python_path.is_absolute() { + python_path + } else { + let parent_dir = match executable_name.parent() { + Some(parent) => parent, + None => { + error_and_exit("Executable path has no parent directory"); + } + }; + parent_dir.join(python_path) + }; + + let python_exe = + if !python_exe.is_absolute() || matches!(trampoline_kind, TrampolineKind::Script) { + // NOTICE: dunce adds 5kb~ + // TODO(john): In order to avoid resolving junctions and symlinks for relative paths and + // scripts, we can consider reverting https://github.com/astral-sh/uv/pull/5750/files#diff-969979506be03e89476feade2edebb4689a9c261f325988d3c7efc5e51de26d1L273-L277. + dunce::canonicalize(python_exe.as_path()).unwrap_or_else(|_| { + error_and_exit("Failed to canonicalize script path"); + }) + } else { + // For Python trampolines with absolute paths, we skip `dunce::canonicalize` to + // avoid resolving junctions. + python_exe + }; + + let mut child_cmdline = Vec::::new(); push_quoted_path(python_exe.as_ref(), &mut child_cmdline); child_cmdline.push(b' '); // Only execute the trampoline again if it's a script, otherwise, just invoke Python. - match kind { + match trampoline_kind { TrampolineKind::Python => { // SAFETY: `std::env::set_var` is safe to call on Windows, and // this code only ever runs on Windows. @@ -159,144 +211,6 @@ fn is_virtualenv(executable: &Path) -> bool { .unwrap_or(false) } -/// Reads the executable binary from the back to find: -/// -/// * The path to the Python executable -/// * The kind of trampoline we are executing -/// -/// The executable is expected to have the following format: -/// -/// * The file must end with the magic number 'UVPY' or 'UVSC' (identifying the trampoline kind) -/// * The last 4 bytes (little endian) are the length of the path to the Python executable. -/// * The path encoded as UTF-8 comes right before the length -/// -/// # Panics -/// -/// If there's any IO error, or the file does not conform to the specified format. -fn read_trampoline_metadata(executable_name: &Path) -> (TrampolineKind, PathBuf) { - let mut file_handle = File::open(executable_name).unwrap_or_else(|_| { - print_last_error_and_exit(&format!( - "Failed to open executable '{}'", - &*executable_name.to_string_lossy(), - )); - }); - - let metadata = executable_name.metadata().unwrap_or_else(|_| { - print_last_error_and_exit(&format!( - "Failed to get the size of the executable '{}'", - &*executable_name.to_string_lossy(), - )); - }); - let file_size = metadata.len(); - - // Start with a size of 1024 bytes which should be enough for most paths but avoids reading the - // entire file. - let mut buffer: Vec = Vec::new(); - let mut bytes_to_read = 1024.min(u32::try_from(file_size).unwrap_or(u32::MAX)); - - let mut kind; - let path: String = loop { - // SAFETY: Casting to usize is safe because we only support 64bit systems where usize is guaranteed to be larger than u32. - buffer.resize(bytes_to_read as usize, 0); - - file_handle - .seek(SeekFrom::Start(file_size - u64::from(bytes_to_read))) - .unwrap_or_else(|_| { - print_last_error_and_exit("Failed to set the file pointer to the end of the file"); - }); - - // Pulls in core::fmt::{write, Write, getcount} - let read_bytes = file_handle.read(&mut buffer).unwrap_or_else(|_| { - 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); - - let Some(inner_kind) = TrampolineKind::from_buffer(&buffer) else { - error_and_exit( - "Magic number 'UVSC' or 'UVPY' not found at the end of the file. Did you append the magic number, the length and the path to the python executable at the end of the file?", - ); - }; - kind = inner_kind; - - // Remove the magic number - buffer.truncate(buffer.len() - kind.magic_number().len()); - - let path_len = match buffer.get(buffer.len() - PATH_LEN_SIZE..) { - Some(path_len) => { - let path_len = u32::from_le_bytes(path_len.try_into().unwrap_or_else(|_| { - error_and_exit("Slice length is not equal to 4 bytes"); - })); - - if path_len > MAX_PATH_LEN { - error_and_exit(&format!( - "Only paths with a length up to 32KBs are supported but the python path has a length of {}", - path_len - )); - } - - // SAFETY: path len is guaranteed to be less than 32KBs - path_len as usize - } - None => { - error_and_exit( - "Python executable length missing. Did you write the length of the path to the Python executable before the Magic number?", - ); - } - }; - - // Remove the path length - buffer.truncate(buffer.len() - PATH_LEN_SIZE); - - if let Some(path_offset) = buffer.len().checked_sub(path_len) { - buffer.drain(..path_offset); - - break String::from_utf8(buffer).unwrap_or_else(|_| { - error_and_exit("Python executable path is not a valid UTF-8 encoded path"); - }); - } else { - // SAFETY: Casting to u32 is safe because `path_len` is guaranteed to be less than 32KBs, - // MAGIC_NUMBER is 4 bytes and PATH_LEN_SIZE is 4 bytes. - bytes_to_read = (path_len + kind.magic_number().len() + PATH_LEN_SIZE) as u32; - - if u64::from(bytes_to_read) > file_size { - error_and_exit( - "The length of the python executable path exceeds the file size. Verify that the path length is appended to the end of the launcher script as a u32 in little endian", - ); - } - } - }; - - let path = PathBuf::from(path); - let path = if path.is_absolute() { - path - } else { - let parent_dir = match executable_name.parent() { - Some(parent) => parent, - None => { - error_and_exit("Executable path has no parent directory"); - } - }; - parent_dir.join(path) - }; - - let path = if !path.is_absolute() || matches!(kind, TrampolineKind::Script) { - // NOTICE: dunce adds 5kb~ - // TODO(john): In order to avoid resolving junctions and symlinks for relative paths and - // scripts, we can consider reverting https://github.com/astral-sh/uv/pull/5750/files#diff-969979506be03e89476feade2edebb4689a9c261f325988d3c7efc5e51de26d1L273-L277. - dunce::canonicalize(path.as_path()).unwrap_or_else(|_| { - error_and_exit("Failed to canonicalize script path"); - }) - } else { - // For Python trampolines with absolute paths, we skip `dunce::canonicalize` to - // avoid resolving junctions. - path - }; - - (kind, path) -} - fn push_arguments(output: &mut Vec) { // SAFETY: We rely on `GetCommandLineA` to return a valid pointer to a null terminated string. let arguments_as_str = unsafe { GetCommandLineA() }; diff --git a/crates/uv-trampoline/trampolines/uv-trampoline-i686-console.exe b/crates/uv-trampoline/trampolines/uv-trampoline-i686-console.exe index 3aca836cde821ec07d82084064780659ecb6602c..8d5feabf27c06b8125ba0452f0cad995100e7719 100755 GIT binary patch delta 13735 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 N>&MM;9Nn(c{V(1bTH^o! delta 14455 zcmb_@3sh5A_V>LZXn^RA8Wa^J;;lkOO?XEVh>B=i)L4*Lse*z+%BzqFT5N)W(QCwM z=h(+|>^JSd&WyI58CzP$R~5wis$;FT)~Z!&9eXjh6`!Dv`Tx#MP+MoM@0+#0xHk9R zefHUBpMCc0oO^Ywk~!AM2F%Q>V_1e^iwsjlo7x!0;s5feLCPZx za~bq&&=88}5mwxUfMCmVhS@VUD~!YWTnj*5W!Vkj>BF4;?w5Tw+JYf3L=E z6<{!+0PxVy#4x4_MTOQv;2J6ZB>)CxmXKuG*g16+_26fs)Yh^ZiWxxvYz9#3W%S{} z&xBb_sIIAA2$aJgf7$?azV&3SY*>FfE9MA*>UTv;tL#aZoRgU%MQc369D4A}5)M0W z2UPpHmfZCOmpDvJmW45Ug6kb5QXV>#BC@?02X5Unnw1lUV`6p;nJ3qT{CzINIQJ+Y z?fR;Gjx231>6DL=JvWyO@PAA8LndkQA1nJtM^5`+k{!(;`}^!-Z<1AgpJd-7$NJ8j zospfTYcmO~Fj?+;Ue$Ed%Da{-T$7cq`3n6B>p<6RIlG_zCd)iW{CbWwDtkt#Rm_|J zOo#Kfyv9%0*5OL!Eb--P2T^rFVe#gpNIuz4c*|)QaGVq_WEqBPNKpj7Fw$$D2 z&JvCYhb^)^q1_@A-}ZPsxk4WyOYU5xV91+6iFWQEa}j2ISUmGgzIpzNt5N) z!4S3C%nAdz){w?ib%DakPP(d)pt(vWoD_7<^I=?T9qard#c}8C5?ynZU;7Vz1Rd8} zC39X(3ApFDbEP`GxlZmp-?#l@A3@)KejwLs@^hX`3Ahrl+pTOr-zVU2TEhAT%Ge(A zICoop=uj5dsuLj~lV|CZt7I##cb1^yRWu@!cdnL zER?+d3b%4Qgl>+KvBz~sr|553UsyepX?~V%JWwYWzHqA?J?Cl$I6sx?uW{49wa9YW z{dVD7_X$VOkG668Yt74L+`4xCEpFYXiho-Ca)oQQvs~*;c2PsmADLQ{^HV>Hbh{6F zIi$LEr}Q_tS2|d?qvuME+~Vi{yY2v3ztYZrW|6!92NTw{39?>sxz-`vKMpv*3v2(b zkMn#=^O^xziGd8Lgeh{mAWtex?k~8YmRAQfvNF*|LI-JNS8XJDkV)2UBaT5UWY=v( zuD&39?>4!l-rryB_IS9~ZuTtHzPE?81zXub5;u4n`xAL#a8;jSqfN}t(JftrW7xr; zT=sZ!a-Zpd0qc$mho``RS+R#q3JD#fxaRS|Bj=&*uqQYNF6$h_2x;AFayX20k78cOq3o>HGRaovK^DK*PqvT`LY`pd5 zJR%B&T+?enF5)WM0S93Z+=6z~BJQ}q293>`ys|l+*Sr%!S%x!vr^p#`3+SCjEimZ3 zfIjr24<3inRLoE1e4zj8Li+ zCaVyj#5XQd6Q_KZ(+JX!Qn`W?@mHL60fAik%`%R^U-1D4P4xR2|}b*Gb!G9jhi+ zM~}(af@V)}0lLEpR3+_n98YkO^lbCrGDd4+-eDkt=iJ*?AU)@Nan)51>An}f5)KRB zAUN)YyJpYz)X2qiapXDvwb*2bUC9V;;f7VyT*fPft3s!R$q?UmQ@3@ZCP7P|z0*N+ z1NpBPjDtM!lzAbwm@$%!kt-v!xl`R zNLNJ4XgXS_0@mJa<)aY!g28@uA6Ttg_+}Lr76_6N87DjQ6ImTONp{;t_C~HX3_dMI zIRsl60a8n~SsVb{%0WLl7%7CVX1T>I{`+6RfoX0JWR_TpCw?R^Jsv&rb;yN1UTtU_0|c*!Th=jX{sQMO?jm@Pte7POnw83s49$a95* zmQe9WGIjhbvJcOYljB2W#ov9owJ_MQH^WAFrg|p+%w*N#&zL@wzOe)Nzsf~N3%^eFlRd6}ATd{2p zl}nZbWfqymA*cj$HnZ+Q^l=*9)-hah*4ES4=lo_5RJg0;}Fzu!q)-~3AbcIcffv|9qj7_7^>KbrE9qDpmhQs zk+e1Kjvm%F55m=9#Cos`fg>n+0R|!TU_YRdSf7Uua&qmizLa+=uW(+G(E$SXqgsUM zU{)etL{`1Io3&ooY099_Qb`KzQ=H;$VDyOq?j z8!`S?C%K}F=F%U=wRRF+^!m_jgj36v+@=FojdQDr)n#q{QOEVyzkJz7I-{q_{&tmU zW0GYd4pJ2pK3EGD&TbFacoP$&-i%llke{=Yd7*_FJs`2Hu!%dz3I ztn=jKSWVQfZ;*tg>aF3jgLXdxy`%~Ug)Xx=6@Hond5f@oJ>rdvgpFG-yYV&oV_Y1! z@QlZ!-?t=G=oC(hjx*%@xEH5hfs4Uk!G)RQ366kNQW;Z%8+fbIyAP?u)4UE{+dN;b zszRlDQRqUz;YB|;*$_WQcIpy26#rPzI#8m_*e$v*kh}3C(|(5W(|{&k#Eo;0N=hxL z<^9uM0O8ld;cY)Jqxr&c2x23+UKNKxl4k$uw7F{=r80|QBseia`@?6w96GYFjtb#0 zd&De$0c_5%=s%&jd1OMu9Qz`vlluO!9T-g-cB3@xbV?~1MVD}!?KX??UUr{SvaW5E z+AM}k?B;h+u?rCmr2aE;V;1{L{rd`CE|tq9r}Er5D9J;k0T|dRy;=N@B3;wvpfk%| zCIuz@r;#Mymg6RXgKY8hXp^%p;Zk|^nsy|60DzjN!BC()mPG@Atl#0C! zelevvJ_d7rk>pClWfk_bNaTj?bmO}Z45&#sHOxMfl#!hU3L3Ad*EQT>K~ggaZbwfO zK&zsCgfs53<51?n2@SBXr>X1Nt-JyG*MwpxRj7x(Aa4uDMs%HYMfrswIQDAj!geL^%f8H~lUob-4Rs-~!=xhB)Rp)i%PC zE7Q)a#jEfLJQWE0Z=HC)pJhbe*5)Nhu8(V^3|P?ID9u7x!+!}jt{3IlIXMeLnMKY5 z4V^!oxU)bBJ*W^aajOP|Fndj*ELE`?*;tz)ZtBFCxsGJHH3)TTft+ia1(mq$sR)Ys zd=vs7Rh4iXU7*1X@yQ!h#y-MrM9`o#xos*u*nmkDVDEq$2(%2{6bn5W;$t}8$Z^ew z@dzh9wF)ul9_`9O#RK3gu32(dmO{K7hT9#y0Ucdy_(WW=mRgksIv=Y$%C=Alr!lV+Z8-Yp{+3oy>aTf7K_+#e_P@bUpE_!;789|uO@&vDU zm>8ewApQ4PH*VZEeG3pmd)!+_{N@ZAHx z`V%&l#e;{P>9$`v|LybYlttfY)0 ze@~J2#x|}|hasHz9OH( zo?tj<4VHTA>RJ=C?Hvdt){lU!nJC(N#E)@%->80NGhN~s9AHR>VONMVKxwJV+v=u5 zCE)=}lgKo$k#x8!pC2nsR#ICLma0(7i|-yl*Gy=guBesQ_O4V93@HT5tTBt%L6UQ` zFKZ3Eb%twro)!@}KR^c!OpodlWFasujVs*_d?v3EE00NqS`vstu$~&>BYqFHzeaE7 zUBe&3)80m&9-gu8BJnun+&aDv6W>H9-V};4()oNkdGXdwsh(<_KM)L#VImTz-AG0n zEK6v1hPc)()v9n9`)%>vz1X{WiCj46dP31F-D=DoOeFyw*I4loR=4p4*VuiX%D3#sy9hka2$%ELOj7{ zsy3P^`uFZ_xTaX-E&UxET656q>|Vw-Eyd=8^$2%5%TVugD;MD}@h3MpcR(yFn=Q=#Gl7;?7kocZIp^NYU6! zCVWcI>bS-~B7jcO-{zWL#SJDS#rmPb^KxOSLi`Je;5;fAK?Kz$Re`v%1FU>02N7C) z9nV;gw=ginW!u}Rrn$&Q{w4a1t3$eCp}0u2AWW5EvNs#~X5b+J&46I{IxZrMaEH<@ zwRg?@7o&*UM(c-PS}d&=#dge%=Dd+eB6R;CDq)d1c4?>F$1CG{%Ji>T&|ffJMVV60 zk1|~+F`XxOB~zx~fa#W@V2aRqQv8sraE&xiG56OzH&LFHvHek|rbn0--Df%vOu6i~ zW^X2^>Y@u^h7OC&-t=H$=@rWvMU9H5X4EeJ$k91*O=Sv6dDK|NA0%7`*Q7<*cX=6M z;ZRDTaKTBb_!S5wUcnm5Wv|KE&DL__J@~cmCgoO+j#$YEaHIZXz;e>nG!Ts`#SRha z&f9DWK#p4F6=S?lj8*+m#jN26pj~UJ0wop5OS!>I>F1+-4wRTnD0ry7EuDBpLkHiE zsqFLiM1kSKE!6}9rpMAfzsY0g8vjBkyWT*h$nXSLJ(xp~$Vyq!HcEFBA?(i*N83<~ z2W0PiM``rp>YV%J?iOioaBCAd(TS#_YCnRtqyDvxuB2Ja+(kX7i!vdO<2!)>F3Q=2}!la5OF z9NTbM`HKPVW^p%dMQAi(M1wRUHPG|4#-at5cp4IQj=)%!#JsKJmzo&S5_~_^ZmU8P zmG(VR(0_qtkl4^Ar5Y4|T+!0tKZZgtm5;i0!p=3!LWF<=(6x1~ zlZ-)3Lq3HfrGgu_=FON^oXWuAIM`{abjvlxAy(;5bqYu*mYBS)@6v>RSPi2Mf%H(a z3ClL`3-C&l3SzLLd)^T78Q^Fn^90*z8l&olI1#r_aE{LdrJe>?ngU1CP=Gf+A;~{2 zZG^mKnY5LpvCfeq>)XXm_^V*#;WM(SF;}Bj={>hGSXxIlH0QaEu6@ zT}CThQpBa9M+&jjSTM_KT2n|aDsDz7FyiG6!>znbJ-8QN4SZ7@?EhG}2N(xB`S zylhI~T8BFCXt)*IforZgJ>7Kwf*5L+9SNiNgvK5usSK5ND;R7~8G8pETFGu)QG`3-_LTw7Vk~sF;*W}@*ecp6{Yt*AI#6v0;72Lwx zNo}fikP1bW8fL|{=Kb4orw#iOCApV6bYv*1A|+mo`Jp&+E1lnmxd(YM9Na)f;-<}# zWmb`7a5ckJE(RYo=6Y8q}Q9&H@e0gZ*LXqx(a( zlzvP1twuhmpf9Mtw+e5qeXfrDthKtm0g5CZ)@zcY3>=GYN{F~t^bf?(4 z3>MF=q#*-|AIbeSG{8f!Jrue!#4ljVQf$FWg}UO!KR^^+8{U37wo=P!t>-K8CNiHd zhx(;6t<~v#8DuSJ|6bAl15My+x?ro)`Otu~oo~=JJx=#-bUR}SEcp(OnI#@RBSi+( z3(emB3Jpng|2FJjbaX$u@tX?c{u!nD5w*p+#6+E}Q?F#4jY$rV%vP9%<{hG!h znt3ov5vu=Fa893Thx)d^;t4O#dj^+<)UEUEM0W@1qA*XD_zw^WSE$!tMX7TWmc`np zphbOOJ}E=0#IxvZ$I*8ZV;$Xa4;JO%e)|H}m}~XEDpb><1NOo5)F{)GP5`xewa|q_ zPAEqy-o<;$&R1wbwT4gkMmjSt;zn%Og=2~il-ur+mPvVAJ#=FKMp{8ER7$q{nfM2| zs?L52gA~BHKLjf*gHmwvPvG>VK-r$@9 zwf-JNoll^MkL9QhBj>T`D{vOwyf-vVwZ;>}omfuFl7cuHZEg0d>M27##LS zNfyLaJFoGbf6`n{NG_-W zFVOr!P)!}*>w5fFq*BAV zxfTwMwWNhCM38n?@GB}47E>dB3+2I;Lfr3K!t%EMiE`=zCpy2C^x<*gI&3$^DLWxrt}_`Shi-o$96i=lBYl+NRm^XZKVY9L+o@XRF+wblo@)3W zs?Vqo(X-j#Zo`svZL7z{EQU~=3siV>CY*Hc^~=f@&wXN-hN#C3Y){b$r@vBd)pyrc z3IE1xMmZ06RN|kH+_=S2`MHJH+=OsC2}S39E6a{25jOCY=BV z&EsiWYeG6+?PmzLsW7=SK+F}5l)F2SYdr$Wet4KV+kK+hpEiQ#TU^ZIP)Z|>g;!=~ zGhQ^jknWZF?Ug&$C|gf{y> zMV*Fvj&m%tZ#rFb#<`fDmYT&k(3MvIx5eXN+U!ry>4lzcw1-(-P8(g*F@05EAq^LD z(#$LS@1X^0AD6s|U)T~jhm#GeUkJDQVY9+*-#pm;f}i1)*o7@o88ca(a=D3dIp~=^ zJ;LAh{;${#=;OqPAoDxKvwy_|=!pb*H}jpAr{_kp#E~^Ig029*|G+7&)z9R<)zR4> z5w3GB{R7Q=Ia%;j*;@DmT&MP`K?5kY;)3U1H7- zWe<_(vPa2YT1?hv$M^dZ{!Odr&5{IPWKWcxa1dFJ!OkQ9=C~^92}ZU;=*$u$p*pIq zOJd4h_)C|#0WZ%am51O3&k@+5QaJdl$OI+pv7>zWNP3pUmmWz!hRE(alBV`ZKWs9= zIW!2aN+FDHqPxVN?b1*qktw9QNlojfEb)z7MDI8y=KtIdo<>2bi&#VGV7tuvfubqYI;IF8+vnZvkc1H zx`!(3FR%qseZp&t4?bJ`FYn_2OL`wUIhUVlXR7dq9rokryY}&21AJE{uGmO3D&JM@ zyN39#p}woecjbN82;Vizch&l?vA%1f_d1R-_z))FHPv^W;k%~$uI8$iGtWNG;+v^} z`C|vhVjV*v)(`-aD`T#1nKJ*LEbP!Y6LSGTe^hJ=%O;qZ-vNH!^$E#e5aq|%ZDjR= zzo`QeF=wM4rw+{Atcr4@@OXi`gyG)pUOn%e48Yq7>!a-X7dk@Qqm2x@DkbpI%~1;vXhtG(T37A`Jb zsHw1(FDR}~ndTjklrPlSGau#oh*Z)j#dQmdi;HTcp%#@w)|%2~#S=7H#nq*YmI7O7 zeISu<8l^Rw!m6s`ilXA8ULo7kWp3 z$R;SoqiZXP2L;pF7iyLimcdfyW~Pl#gw7ULQjJpzGNLHF#ZfehwLci65-aks&5a!! z_@HsIPi(rly$kh_EMsR^lr5Dw*Jx@>VS9zWCTpvrvx|v-Y6?~nMpt92s;aEELXHJC zXhK6Z>eZqod5N^xK9$2D0?FIO0vWex9cf#%(Ov}4vjUm`uL1r7_y^!1;548Ua0{TU zF)<;4(SSt2Oh7)s0$2`s2XG2-9S~$SF+%_vz~g{PfH{EqfO5bxz*@koR?`IL1KjKa zd=B^*;5NV?)!%SH1Rw^G44}UqWg<@@yXRE;@x$^^2pMDz3ui%d)^eVyU^z17Gt zY6m{Re+c(i;C29a{UO{I;PwC~2lW1S1NWuliGX=O= z26!FtK41^v4B#igJ%Adb(jV`;M&oJ%%GKzJ%EW==9w5h|?3X^0Kk=?Kl-d zrMEMaFPl#OUOC+FS?M~|e_9>G6qCrR;f4ws=+kUvWoCq*S%u}2cnomqg*Db`)zy{N z*nm-*nUzI0gg%Vtqgo>)SIfrO(c~LuMsZ;kod_@)*1~G5t?G%2MU`|W5~j$f3ud4n zo->QftLTta8w~9^xvZ>mq3=Gey0{p-ML&vvy4c&o$!FTitff=g3X$`J#n`Db8SG!lD5IdrtMB9xpgA7)3xLvusHO~t)0 z9IEERQfpdebp{f8S+RG0AXYZb=u;~zYAU@eI1`IhxVU&qW!+?Em~58yX=DOA9wGvs zBx>&?^R%^W7FCGVR+CwoT1oy`7D%?1B@B74xVoaaEGBwFQCS&dt0}Jb-jZt-<6Dl& zLfOze6HOTS!O!+O!+bDwSL&>brtK}Gs(9bHk23EEh(<9!M(M(hGvK-ij3hp?F8)vU1Cgp^aNcvI^WM9vopx=Ux@1e zD^yzjBdBYSqTX5_6^4!ze=+>Tg_Y&VL8S{yQDiJ7Pt-+`%}a}jt!^=yy|j>=uB%}u zl6iGQNYpYlu6>BLc8Q#FA-}C1P9o|?2TkmqesUn47zRGV9E$!TdRpxI*o(0dakX&`aqq|Nj0=xn8Q&E@D&b7R z#Y8@7Mp9K$ZPFV_A0&N}{fZXL#GN(Xid{nc-{01w*iLjIqdAX@a?2JZAjHc-HuXQJ%~v$0qBOZOJdC+)ZJ$43mn1W!fO^K<#j?20|{6{zLSo z=&BfJ%)7BWV)w@$j~x>CUfibmZSno|g-J`3)+D{1^ifipq0sPH$|5SfiD4YLjMR?P zeyBaJy`*i4*%5OvCN8!%))D)vJ@(Dm?pRN3S)477#JzzzeH`~?+^M*`aenc9{J3~y zd|G@_{B!ZwCWIuECy0qA{Y?FD^dIRD=m$^ApVTku^`whQ%Z)DMYsO8+b;-Mu z_aqNW2}xO;QkHU<3SjmMFi5*ryG46ctI{Rt=IcsyD|CYHI8K*!*L1ga8Yfwr2M4vBzqG5-)`@NX{I(`Tcj=1TDA3Hyav{@Ub{j2zV;*S4(%T8A?=si)7lH# z%i8POyV{6ob96y;L0nOsHLebt&W~3b7pRPCV~8=-s4?=!2xF8{Ym7A}8VyF1G1WN3 zm~J#1GmSaMd}9Gjro>ogtTI}Sb;f$5!`NtCWn5!?$+*t=x^X=;u)(;|_`Y$o@w$;o Wu1dBh*Cp2{JCaW)xBP8IIQzd?iN@3b diff --git a/crates/uv-trampoline/trampolines/uv-trampoline-i686-gui.exe b/crates/uv-trampoline/trampolines/uv-trampoline-i686-gui.exe index 9b978d9a72b10a156f0c97209e311e9763f73c0d..6ec25f6cd191f60ad6836dbaf4513d5409147dbf 100755 GIT binary patch delta 14170 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 delta 15071 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 W%Z$%7W+r8(MP#neY(Cha;Que{@UlDr diff --git a/crates/uv-trampoline/trampolines/uv-trampoline-x86_64-console.exe b/crates/uv-trampoline/trampolines/uv-trampoline-x86_64-console.exe index a439763511b104da6ac4e2875ab2a4170a1757c6..a8b1e1d8d14e6c99f48ef5090653b6a81e0b37fd 100755 GIT binary patch delta 16929 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 delta 17997 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 diff --git a/crates/uv-trampoline/trampolines/uv-trampoline-x86_64-gui.exe b/crates/uv-trampoline/trampolines/uv-trampoline-x86_64-gui.exe index 5ab044a64ce7180488c9233d723959bdc6cfe744..34a252d193dd76dcd33903e4b4f135d43844a9fa 100755 GIT binary patch delta 17438 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 delta 18621 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 U%i%32ww&E^ehaL2TV*2uKSHdNzW@LL diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index facb52a7a..a053fe3cb 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -53,7 +53,6 @@ uv-shell = { workspace = true } uv-static = { workspace = true } uv-tool = { workspace = true } uv-torch = { workspace = true } -uv-trampoline-builder = { workspace = true } uv-types = { workspace = true } uv-version = { workspace = true } uv-virtualenv = { workspace = true } @@ -109,6 +108,8 @@ which = { workspace = true } zip = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] +uv-trampoline-builder = { workspace = true } + arrayvec = { workspace = true } self-replace = { workspace = true } windows = { workspace = true } diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 3018512fc..f0e8090ab 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -1960,11 +1960,7 @@ fn copy_entrypoint( }; let launcher = launcher.with_python_path(python_path); - let mut file = fs_err::OpenOptions::new() - .create_new(true) - .write(true) - .open(target)?; - launcher.write_to_file(&mut file)?; + launcher.write_to_file(target, is_gui)?; trace!("Updated entrypoint at {}", target.user_display()); diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 4bdeab474..af4cd8f87 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -29,7 +29,6 @@ use uv_python::{ PythonVersionFile, VersionFileDiscoveryOptions, VersionFilePreference, VersionRequest, }; use uv_shell::Shell; -use uv_trampoline_builder::{Launcher, LauncherKind}; use uv_warnings::{warn_user, write_error_chain}; use crate::commands::python::{ChangeEvent, ChangeEventKind}; @@ -1051,20 +1050,33 @@ fn find_matching_bin_link<'a>( mut installations: impl Iterator, path: &Path, ) -> Option<&'a ManagedPythonInstallation> { - let target = if cfg!(unix) { - if !path.is_symlink() { - return None; - } - fs_err::canonicalize(path).ok()? - } else if cfg!(windows) { - let launcher = Launcher::try_from_path(path).ok()??; - if !matches!(launcher.kind, LauncherKind::Python) { - return None; - } - dunce::canonicalize(launcher.python_path).ok()? - } else { - unreachable!("Only Windows and Unix are supported") - }; + #[cfg(not(any(unix, windows)))] + { + unreachable!("Only Windows and Unix are supported"); + } - installations.find(|installation| installation.executable(false) == target) + #[cfg(unix)] + { + let target = if !path.is_symlink() { + return None; + } else { + fs_err::canonicalize(path).ok()? + }; + + installations.find(|installation| installation.executable(false) == target) + } + #[cfg(windows)] + { + let target = { + use uv_trampoline_builder::{Launcher, LauncherKind}; + + let launcher: Launcher = Launcher::try_from_path(path).ok()??; + if !matches!(launcher.kind, LauncherKind::Python) { + return None; + } + dunce::canonicalize(launcher.python_path).ok()? + }; + + installations.find(|installation| installation.executable(false) == target) + } }