diff --git a/Cargo.lock b/Cargo.lock index 07d2456cf..48d7558ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1813,6 +1813,8 @@ dependencies = [ name = "install-wheel-rs" version = "0.0.1" dependencies = [ + "anyhow", + "assert_fs", "clap", "configparser", "csv", diff --git a/crates/install-wheel-rs/Cargo.toml b/crates/install-wheel-rs/Cargo.toml index 273f2534d..a05e47caa 100644 --- a/crates/install-wheel-rs/Cargo.toml +++ b/crates/install-wheel-rs/Cargo.toml @@ -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" } diff --git a/crates/install-wheel-rs/src/wheel.rs b/crates/install-wheel-rs/src/wheel.rs index 55e960811..044002392 100644 --- a/crates/install-wheel-rs/src/wheel.rs +++ b/crates/install-wheel-rs/src/wheel.rs @@ -152,8 +152,7 @@ fn format_shebang(executable: impl AsRef, 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. /// /// #[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(()) + } }