diff --git a/crates/uv-install-wheel/Cargo.toml b/crates/uv-install-wheel/Cargo.toml
index 3eac625f9..06e72af39 100644
--- a/crates/uv-install-wheel/Cargo.toml
+++ b/crates/uv-install-wheel/Cargo.toml
@@ -29,6 +29,7 @@ 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"] }
@@ -51,8 +52,6 @@ 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 ea835d7be..f36a699f1 100644
--- a/crates/uv-install-wheel/src/lib.rs
+++ b/crates/uv-install-wheel/src/lib.rs
@@ -80,7 +80,6 @@ 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 62d80e291..78ebab994 100644
--- a/crates/uv-install-wheel/src/wheel.rs
+++ b/crates/uv-install-wheel/src/wheel.rs
@@ -17,6 +17,7 @@ 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;
@@ -225,18 +226,14 @@ pub(crate) fn write_script_entrypoints(
);
// If necessary, wrap the launcher script in a Windows launcher binary.
- #[cfg(windows)]
- {
- use uv_trampoline_builder::windows_script_launcher;
+ if cfg!(windows) {
write_file_recorded(
site_packages,
&entrypoint_relative,
&windows_script_launcher(&launcher_python_script, is_gui, &launcher_executable)?,
record,
)?;
- }
- #[cfg(not(windows))]
- {
+ } else {
write_file_recorded(
site_packages,
&entrypoint_relative,
diff --git a/crates/uv-python/Cargo.toml b/crates/uv-python/Cargo.toml
index cccf911c0..1c6f09b15 100644
--- a/crates/uv-python/Cargo.toml
+++ b/crates/uv-python/Cargo.toml
@@ -34,6 +34,7 @@ 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 }
@@ -68,8 +69,6 @@ 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 581cb448d..09f51e085 100644
--- a/crates/uv-python/src/managed.rs
+++ b/crates/uv-python/src/managed.rs
@@ -20,6 +20,7 @@ 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, LauncherKind};
use crate::downloads::{Error as DownloadError, ManagedPythonDownload};
use crate::implementation::{
@@ -92,7 +93,6 @@ 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}")]
@@ -619,13 +619,9 @@ 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 {
- #[cfg(unix)]
- {
+ if cfg!(unix) {
same_file::is_same_file(path, self.executable(false)).unwrap_or_default()
- }
- #[cfg(windows)]
- {
- use uv_trampoline_builder::{Launcher, LauncherKind};
+ } else if cfg!(windows) {
let Some(launcher) = Launcher::try_from_path(path).unwrap_or_default() else {
return false;
};
@@ -637,9 +633,7 @@ impl ManagedPythonInstallation {
// directly.
dunce::canonicalize(&launcher.python_path).unwrap_or(launcher.python_path)
== self.executable(false)
- }
- #[cfg(not(any(unix, windows)))]
- {
+ } else {
unreachable!("Only Windows and Unix are supported")
}
}
@@ -882,8 +876,7 @@ pub fn create_link_to_executable(link: &Path, executable: &Path) -> Result<(), E
err,
})?;
- #[cfg(unix)]
- {
+ if 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(()),
@@ -896,9 +889,7 @@ pub fn create_link_to_executable(link: &Path, executable: &Path) -> Result<(), E
err,
}),
}
- }
- #[cfg(windows)]
- {
+ } else if cfg!(windows) {
use uv_trampoline_builder::windows_python_launcher;
// TODO(zanieb): Install GUI launchers as well
@@ -916,10 +907,8 @@ pub fn create_link_to_executable(link: &Path, executable: &Path) -> Result<(), E
err,
})
}
- }
- #[cfg(not(any(unix, windows)))]
- {
- unimplemented!("Only Windows and Unix systems are supported.")
+ } else {
+ unimplemented!("Only Windows and Unix are supported.")
}
}
diff --git a/crates/uv-trampoline-builder/src/lib.rs b/crates/uv-trampoline-builder/src/lib.rs
index d89c17eb9..f44314f1a 100644
--- a/crates/uv-trampoline-builder/src/lib.rs
+++ b/crates/uv-trampoline-builder/src/lib.rs
@@ -51,109 +51,113 @@ pub struct Launcher {
}
impl Launcher {
+ /// Attempt to read [`Launcher`] metadata from a trampoline executable file.
+ ///
+ /// On Unix, this always returns [`None`]. Trampolines are a Windows-specific feature and cannot
+ /// be read on other platforms.
+ #[cfg(not(windows))]
+ pub fn try_from_path(_path: &Path) -> Result