From 34fb966f606bdfa2f65eb12468a0769054899bb9 Mon Sep 17 00:00:00 2001 From: John Sirois Date: Mon, 1 Jun 2026 11:10:50 -0700 Subject: [PATCH 1/2] Fix `pythonw.exe PEX` to not launch a console. --- CHANGES.md | 5 ++++- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/pexrs/src/lib.rs | 35 ++++++++++++++++++++++++++-------- crates/scripts/src/venv-pex.py | 10 ++++++++-- 5 files changed, 41 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3c3de0e..1801e6e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,12 +1,15 @@ # Release Notes -## 0.15.0-unreleased +## 0.15.0 This release adds defaults for Linux and Windows for the `platform_release` environment marker when generating platform details via `pexrc platform python `. Now the only `` marker when using a spec is the `platform_version` which is actively antagonistic to any likely real-world use. +This release also fixes injected windowed PEXes launched with `pythonw.exe` on Windows. Previously +these would incorrectly re-exec with `python.exe` and display a console. + ## 0.14.0 This release adds `pexrc platform {info,python}` for displaying both local and foreign platform diff --git a/Cargo.lock b/Cargo.lock index 6acef6b..9d1fe78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2096,7 +2096,7 @@ dependencies = [ [[package]] name = "pexrc" -version = "0.15.0-unreleased" +version = "0.15.0" dependencies = [ "anstream", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 6aa75e7..9d15297 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ cargo-features = ["profile-rustflags"] [package] name = "pexrc" -version = "0.15.0-unreleased" +version = "0.15.0" edition = { workspace = true } publish = false diff --git a/crates/pexrs/src/lib.rs b/crates/pexrs/src/lib.rs index 44a73c2..394481c 100644 --- a/crates/pexrs/src/lib.rs +++ b/crates/pexrs/src/lib.rs @@ -127,12 +127,31 @@ fn prepare_boot( argv: Vec, ) -> anyhow::Result { let venv = prepare_venv( - python, + python.as_ref(), pex.as_ref(), #[cfg(unix)] env::var_os("_PEXRC_SH_BOOT_SEED_DIR").map(PathBuf::from), )?; - let mut command = Command::new(&venv.interpreter.details.path); + + let mut command = { + #[cfg(windows)] + { + let python_exe = { + if let Some(file_name) = python.as_ref().file_name().and_then(OsStr::to_str) + // N.B.: This could be either pythonw.exe or pypyw.exe (or pypyw.exe). + && file_name.ends_with("w.exe") + { + // Either way, our venv machinery ensures "pythonw.exe" exists. + Cow::Owned(venv.interpreter.details.path.with_file_name("pythonw.exe")) + } else { + Cow::Borrowed(&venv.interpreter.details.path) + } + }; + Command::new(python_exe.as_ref()) + } + #[cfg(unix)] + Command::new(&venv.interpreter.details.path) + }; command .args(python_args) .arg(venv.prefix().as_os_str()) @@ -153,7 +172,7 @@ pub fn mount(python: impl AsRef, pex: impl AsRef) -> anyhow::Result< Err(err) => bail!("Failed to obtain PEXRC cache read lock: {err}"), }; prepare_venv( - python, + python.as_ref(), pex.as_ref(), #[cfg(unix)] None, @@ -163,19 +182,19 @@ pub fn mount(python: impl AsRef, pex: impl AsRef) -> anyhow::Result< #[time("debug", "{}")] fn prepare_venv<'a>( - python: impl AsRef, - pex: impl AsRef, + python: &Path, + pex: &Path, #[cfg(unix)] sh_boot_seed_dir: Option, ) -> anyhow::Result> { - let pex = Pex::load(pex.as_ref())?; + let pex = Pex::load(pex)?; let pex_path = PexPath::from_pex_info(&pex.info, true); let pex_info = pex.info.raw(); let additional_pexes = pex_path.load_pexes()?; let search_path = SearchPath::from_env()?; - let venv_dir = venv_dir(Some(python.as_ref()), &pex, &search_path, &additional_pexes)?; + let venv_dir = venv_dir(Some(python), &pex, &search_path, &additional_pexes)?; if let Some(venv_interpreter) = atomic_dir(&venv_dir, |work_dir| { let mut resolve = pex.resolve( - Some(python.as_ref()), + Some(python), additional_pexes.iter(), search_path, None, diff --git a/crates/scripts/src/venv-pex.py b/crates/scripts/src/venv-pex.py index fb3eb8a..6632ccd 100644 --- a/crates/scripts/src/venv-pex.py +++ b/crates/scripts/src/venv-pex.py @@ -51,7 +51,10 @@ def exec_function( return locals_map -if sys.platform == "win32": +IS_WINDOWS = sys.platform == "win32" + + +if IS_WINDOWS: def safe_execv(argv): # type: (List[str]) -> NoReturn @@ -113,11 +116,14 @@ def boot( venv_dir = os.path.abspath(os.path.dirname(__file__)) venv_bin_dir = os.path.join(venv_dir, venv_bin_dir) python = os.path.join(venv_bin_dir, os.path.basename(shebang_python)) + venv_pythons = [python, shebang_python] + if IS_WINDOWS: + venv_pythons.append(os.path.join(venv_bin_dir, "pythonw.exe")) def iter_valid_venv_pythons(): # Allow for both the known valid venv pythons and their fully resolved venv path # version in the case their parent directories contain symlinks. - for python_binary in (python, shebang_python): + for python_binary in venv_pythons: yield python_binary yield os.path.join( os.path.realpath(os.path.dirname(python_binary)), os.path.basename(python_binary) From edfd75dcc4d9c08810e6bf0d7f7e19e4a5006a7b Mon Sep 17 00:00:00 2001 From: John Sirois Date: Mon, 1 Jun 2026 11:23:03 -0700 Subject: [PATCH 2/2] Fix fmt. --- crates/pexrs/src/lib.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/pexrs/src/lib.rs b/crates/pexrs/src/lib.rs index 394481c..7d23705 100644 --- a/crates/pexrs/src/lib.rs +++ b/crates/pexrs/src/lib.rs @@ -193,12 +193,7 @@ fn prepare_venv<'a>( let search_path = SearchPath::from_env()?; let venv_dir = venv_dir(Some(python), &pex, &search_path, &additional_pexes)?; if let Some(venv_interpreter) = atomic_dir(&venv_dir, |work_dir| { - let mut resolve = pex.resolve( - Some(python), - additional_pexes.iter(), - search_path, - None, - )?; + let mut resolve = pex.resolve(Some(python), additional_pexes.iter(), search_path, None)?; let venv = Virtualenv::create( resolve.interpreter, Cow::Borrowed(work_dir),