mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
feat: pythonw support on gui scripts (#4409)
## Summary Closes https://github.com/astral-sh/uv/issues/2956 This changes the bootstrap launcher script to use `pythonw.exe` instead of `python.exe` on `gui_scripts` via a helper fn both in the shebang and the python exe path encoded before `UVUV` magic, that way uv-trampoline's `find_python_exe` can use the right pythonw executable. ## Test Plan New unit tests for the helper was added. Tested on example from #2956 on Windows to make sure it works as expected. ## Questions I noticed the docs in `fn windows_script_launcher` says ```The launcher will look for `python[w].exe` adjacent to it in the same directory to start the embedded script.``` but I didn't find such functionality when I looked in uv-trampoline. I only saw `clear_app_starting_state` getting called when `is_gui` is set. Was the intention to do this in uv-trampoline at some point instead? --------- Co-authored-by: konstin <konstin@mailbox.org>
This commit is contained in:
parent
3a63e1410d
commit
2288ff7bf4
3 changed files with 59 additions and 5 deletions
|
@ -50,4 +50,6 @@ walkdir = { workspace = true }
|
|||
zip = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { version = "1.0.80" }
|
||||
assert_fs = { version = "1.1.1" }
|
||||
indoc = { version = "2.0.4" }
|
||||
|
|
|
@ -152,8 +152,7 @@ fn format_shebang(executable: impl AsRef<Path>, os_name: &str) -> String {
|
|||
}
|
||||
|
||||
/// A Windows script is a minimal .exe launcher binary with the python entrypoint script appended as
|
||||
/// stored zip file. The launcher will look for `python[w].exe` adjacent to it in the same directory
|
||||
/// to start the embedded script.
|
||||
/// stored zip file.
|
||||
///
|
||||
/// <https://github.com/pypa/pip/blob/fd0ea6bc5e8cb95e518c23d901c26ca14db17f89/src/pip/_vendor/distlib/scripts.py#L248-L262>
|
||||
#[allow(unused_variables)]
|
||||
|
@ -233,6 +232,20 @@ pub(crate) fn windows_script_launcher(
|
|||
Ok(launcher)
|
||||
}
|
||||
|
||||
/// Returns a [`PathBuf`] to `python[w].exe` for script execution.
|
||||
fn get_script_executable(python_executable: &Path, is_gui: bool) -> PathBuf {
|
||||
// Only check for pythonw.exe on Windows
|
||||
if cfg!(windows) && is_gui {
|
||||
python_executable
|
||||
.parent()
|
||||
.map(|parent| parent.join("pythonw.exe"))
|
||||
.filter(|path| path.is_file())
|
||||
.unwrap_or_else(|| python_executable.to_path_buf())
|
||||
} else {
|
||||
python_executable.to_path_buf()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the wrapper scripts in the bin folder of the venv for launching console scripts.
|
||||
pub(crate) fn write_script_entrypoints(
|
||||
layout: &Layout,
|
||||
|
@ -269,9 +282,10 @@ pub(crate) fn write_script_entrypoints(
|
|||
})?;
|
||||
|
||||
// Generate the launcher script.
|
||||
let launcher_executable = get_script_executable(&layout.sys_executable, is_gui);
|
||||
let launcher_python_script = get_script_launcher(
|
||||
entrypoint,
|
||||
&format_shebang(&layout.sys_executable, &layout.os_name),
|
||||
&format_shebang(&launcher_executable, &layout.os_name),
|
||||
);
|
||||
|
||||
// If necessary, wrap the launcher script in a Windows launcher binary.
|
||||
|
@ -279,7 +293,7 @@ pub(crate) fn write_script_entrypoints(
|
|||
write_file_recorded(
|
||||
site_packages,
|
||||
&entrypoint_relative,
|
||||
&windows_script_launcher(&launcher_python_script, is_gui, &layout.sys_executable)?,
|
||||
&windows_script_launcher(&launcher_python_script, is_gui, &launcher_executable)?,
|
||||
record,
|
||||
)?;
|
||||
} else {
|
||||
|
@ -722,12 +736,16 @@ mod test {
|
|||
use std::io::Cursor;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use assert_fs::prelude::*;
|
||||
use indoc::{formatdoc, indoc};
|
||||
|
||||
use crate::wheel::format_shebang;
|
||||
use crate::Error;
|
||||
|
||||
use super::{parse_key_value_file, parse_wheel_file, read_record_file, Script};
|
||||
use super::{
|
||||
get_script_executable, parse_key_value_file, parse_wheel_file, read_record_file, Script,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_parse_key_value_file() {
|
||||
|
@ -922,4 +940,36 @@ mod test {
|
|||
super::LAUNCHER_AArch64_CONSOLE.len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_script_executable() -> Result<()> {
|
||||
// Test with adjacent pythonw.exe
|
||||
let temp_dir = assert_fs::TempDir::new()?;
|
||||
let python_exe = temp_dir.child("python.exe");
|
||||
let pythonw_exe = temp_dir.child("pythonw.exe");
|
||||
python_exe.write_str("")?;
|
||||
pythonw_exe.write_str("")?;
|
||||
|
||||
let script_path = get_script_executable(&python_exe, true);
|
||||
#[cfg(windows)]
|
||||
assert_eq!(script_path, pythonw_exe.to_path_buf());
|
||||
#[cfg(not(windows))]
|
||||
assert_eq!(script_path, python_exe.to_path_buf());
|
||||
|
||||
let script_path = get_script_executable(&python_exe, false);
|
||||
assert_eq!(script_path, python_exe.to_path_buf());
|
||||
|
||||
// Test without adjacent pythonw.exe
|
||||
let temp_dir = assert_fs::TempDir::new()?;
|
||||
let python_exe = temp_dir.child("python.exe");
|
||||
python_exe.write_str("")?;
|
||||
|
||||
let script_path = get_script_executable(&python_exe, true);
|
||||
assert_eq!(script_path, python_exe.to_path_buf());
|
||||
|
||||
let script_path = get_script_executable(&python_exe, false);
|
||||
assert_eq!(script_path, python_exe.to_path_buf());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue