From 9b9cddc422471abf15ae697e46691a2f994e4c0a Mon Sep 17 00:00:00 2001 From: konstin Date: Thu, 25 Jan 2024 13:02:51 +0100 Subject: [PATCH] Use python[w].exe adjacent to launcher --- .../posy-trampoline/src/bounce.rs | 104 +++++++++++++----- .../posy-trampoline/src/diagnostics.rs | 6 +- 2 files changed, 81 insertions(+), 29 deletions(-) diff --git a/src/trampolines/windows-trampolines/posy-trampoline/src/bounce.rs b/src/trampolines/windows-trampolines/posy-trampoline/src/bounce.rs index 5846ac4..80b73ab 100644 --- a/src/trampolines/windows-trampolines/posy-trampoline/src/bounce.rs +++ b/src/trampolines/windows-trampolines/posy-trampoline/src/bounce.rs @@ -13,6 +13,7 @@ use windows_sys::Win32::{ Console::*, Environment::{GetCommandLineA, GetEnvironmentVariableA, SetCurrentDirectoryA}, JobObjects::*, + LibraryLoader::GetModuleFileNameA, Threading::*, }, UI::WindowsAndMessaging::*, @@ -37,23 +38,9 @@ fn getenv(name: &CStr) -> Option { fn make_child_cmdline(is_gui: bool) -> Vec { unsafe { - let my_cmdline = CStr::from_ptr(GetCommandLineA() as _); - - let envvar = if is_gui { - c!("POSY_PYTHONW") - } else { - c!("POSY_PYTHON") - }; - let python_exe = getenv(envvar); - if python_exe.is_none() { - eprintln!( - "need {} to be set", - core::str::from_utf8_unchecked(envvar.to_bytes()) - ); - ExitProcess(1); - } - let python_exe = python_exe.unwrap_unchecked(); + let python_exe = find_python_exe(is_gui); + let my_cmdline = CStr::from_ptr(GetCommandLineA() as _); let mut child_cmdline = Vec::::new(); child_cmdline.push(b'"'); for byte in python_exe.as_bytes() { @@ -72,10 +59,62 @@ fn make_child_cmdline(is_gui: bool) -> Vec { } } +/// The scripts are in the same directory as the Python interpreter, so we can find Python by getting the locations of +/// the current .exe and replacing the filename with `python[w].exe`. +fn find_python_exe(is_gui: bool) -> CString { + unsafe { + let mut buffer: Vec = Vec::new(); + // MAX_PATH is a lie, Windows paths can be longer. + // https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation + // But it's a good first guess, usually paths are short and we should only need a single attempt. + buffer.resize(MAX_PATH as usize, 0); + loop { + // Call the Windows API function to get the module file name + let len = GetModuleFileNameA(0, buffer.as_mut_ptr(), buffer.len() as u32); + + if len as usize == buffer.len() { + let last_error = GetLastError(); + match last_error { + ERROR_INSUFFICIENT_BUFFER => { + SetLastError(ERROR_SUCCESS); + // Try again with twice the size + buffer.resize(buffer.len() * 2, 0); + } + err => { + eprintln!("File to get executable name: {}", err); + ExitProcess(1); + } + } + } else { + // The returned buffer length excludes the trailing null byte + buffer.truncate(len as usize + b"\0".len()); + break; + } + } + // Replace the filename, the last segment of the path, with "python.exe" + // Assumption: We are not in an encoding where a backslash byte can be part of a larger character. + let Some(last_backslash) = buffer.iter().rposition(|byte| *byte == b'\\') else { + eprintln!( + "Invalid current exe path (missing backslash): `{}`", + CString::from_vec_with_nul_unchecked(buffer).to_string_lossy().as_ref() + ); + ExitProcess(1); + }; + buffer.truncate(last_backslash + 1); + buffer.extend_from_slice(if is_gui { + b"pythonw.exe\0" + } else { + b"python.exe\0" + }); + CString::from_vec_with_nul_unchecked(buffer) + } +} + fn make_job_object() -> HANDLE { unsafe { let job = CreateJobObjectW(null(), null()); - let mut job_info = MaybeUninit::::uninit(); + let mut job_info = + MaybeUninit::::uninit(); let mut retlen = 0u32; check!(QueryInformationJobObject( job, @@ -86,7 +125,8 @@ fn make_job_object() -> HANDLE { )); let mut job_info = job_info.assume_init(); job_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - job_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; + job_info.BasicLimitInformation.LimitFlags |= + JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; check!(SetInformationJobObject( job, JobObjectExtendedLimitInformation, @@ -102,9 +142,21 @@ fn spawn_child(si: &STARTUPINFOA, child_cmdline: &mut [u8]) -> HANDLE { if si.dwFlags & STARTF_USESTDHANDLES != 0 { // ignore errors from these -- if the handle's not inheritable/not valid, then nothing // we can do - SetHandleInformation(si.hStdInput, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); - SetHandleInformation(si.hStdOutput, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); - SetHandleInformation(si.hStdError, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); + SetHandleInformation( + si.hStdInput, + HANDLE_FLAG_INHERIT, + HANDLE_FLAG_INHERIT, + ); + SetHandleInformation( + si.hStdOutput, + HANDLE_FLAG_INHERIT, + HANDLE_FLAG_INHERIT, + ); + SetHandleInformation( + si.hStdError, + HANDLE_FLAG_INHERIT, + HANDLE_FLAG_INHERIT, + ); } let mut child_process_info = MaybeUninit::::uninit(); check!(CreateProcessA( @@ -150,11 +202,11 @@ fn close_handles(si: &STARTUPINFOA) { } } -/* - I don't really understand what this function does. It's a straight port from +/* + I don't really understand what this function does. It's a straight port from https://github.com/pypa/distlib/blob/master/PC/launcher.c, which has the following comment: - + End the launcher's "app starting" cursor state. When Explorer launches a Windows (GUI) application, it displays the "app starting" (the "pointer + hourglass") cursor for a number @@ -163,8 +215,8 @@ fn close_handles(si: &STARTUPINFOA) { directly, that cursor remains even after the child process does these things. We avoid that by doing the stuff in here. See http://bugs.python.org/issue17290 and - https://github.com/pypa/pip/issues/10444#issuecomment-973408601 - + https://github.com/pypa/pip/issues/10444#issuecomment-973408601 + Why do we call `PostMessage`/`GetMessage` at the start, before waiting for the child? (Looking at the bpo issue above, this was originally the *whole* fix.) Is creating a window and calling PeekMessage the best way to do this? idk. diff --git a/src/trampolines/windows-trampolines/posy-trampoline/src/diagnostics.rs b/src/trampolines/windows-trampolines/posy-trampoline/src/diagnostics.rs index b67e0ff..b4715c2 100644 --- a/src/trampolines/windows-trampolines/posy-trampoline/src/diagnostics.rs +++ b/src/trampolines/windows-trampolines/posy-trampoline/src/diagnostics.rs @@ -1,6 +1,5 @@ use core::{ convert::Infallible, - ffi::c_void, ptr::{addr_of_mut, null, null_mut}, }; @@ -32,13 +31,14 @@ impl DiagnosticBuffer { while !remaining.is_empty() { let ok = WriteFile( handle, - remaining.as_ptr() as *const c_void, + remaining.as_ptr(), remaining.len() as u32, addr_of_mut!(written), null_mut(), ); if ok == 0 { - let nul_terminated = CString::new(self.buffer.as_bytes()).unwrap_unchecked(); + let nul_terminated = + CString::new(self.buffer.as_bytes()).unwrap_unchecked(); MessageBoxA(0, nul_terminated.as_ptr() as *const _, null(), 0); return; }