dev_prune/spawn.rs
1// Copyright 2026 VKrishna04
2// SPDX-License-Identifier: Apache-2.0
3
4// Building child processes that stay invisible when this process is.
5//
6// On Windows a console-subsystem child of a process that has no console — the scheduled
7// `devpw` pass, or anything launched from a GUI — gets a brand-new console window of its
8// own, visible to whoever is logged in. A background prune that spawns `git` per
9// repository would flash a black window per repository, which is exactly the behaviour
10// the windowless scheduler binary exists to prevent. `CREATE_NO_WINDOW` suppresses the
11// allocation; it is applied only when this process has no console, so interactive runs
12// spawn children exactly as they always did.
13
14use std::ffi::OsStr;
15use std::process::Command;
16
17/// Absolute path to an executable under `System32`.
18///
19/// By name alone these resolve through `PATH` — and some callers run exactly while
20/// `PATH` is being edited (the uninstaller's tail, the user-PATH registry writes).
21/// `SystemRoot` is set by the kernel for every process on every Windows, so it is the
22/// one thing in the environment here that cannot have been rearranged by the work
23/// just done.
24#[cfg(windows)]
25pub fn system32(relative: &str) -> String {
26 let root = std::env::var("SystemRoot").unwrap_or_else(|_| String::from(r"C:\Windows"));
27 format!("{root}\\System32\\{relative}")
28}
29
30/// A `Command` that will not flash a console window when this process has none.
31///
32/// Every child the CLI spawns goes through here. The one deliberate exception is the
33/// uninstaller's deletion helper, which manages its own creation flags because it must
34/// outlive this process.
35pub fn command<S: AsRef<OsStr>>(program: S) -> Command {
36 let mut cmd = Command::new(program);
37 apply_window_policy(&mut cmd);
38 cmd
39}
40
41#[cfg(windows)]
42fn apply_window_policy(cmd: &mut Command) {
43 use std::os::windows::process::CommandExt;
44 use std::sync::OnceLock;
45
46 /// Documented value of `CREATE_NO_WINDOW`.
47 const CREATE_NO_WINDOW: u32 = 0x0800_0000;
48
49 // A process gains or loses its console only through calls this crate never makes,
50 // so the answer cannot change over a run.
51 static HAS_CONSOLE: OnceLock<bool> = OnceLock::new();
52 let has_console = *HAS_CONSOLE.get_or_init(|| {
53 // SAFETY: `GetConsoleWindow` reads process state and takes no arguments.
54 !unsafe { windows_sys::Win32::System::Console::GetConsoleWindow() }.is_null()
55 });
56 if !has_console {
57 cmd.creation_flags(CREATE_NO_WINDOW);
58 }
59}
60
61#[cfg(not(windows))]
62fn apply_window_policy(_cmd: &mut Command) {}