gglib_core/utils/process.rs
1//! Process spawning utilities with consistent cross-platform behaviour.
2//!
3//! On Windows, every child process created with `std::process::Command::new`
4//! inherits a new console window unless `CREATE_NO_WINDOW` is explicitly set.
5//! The `windows_subsystem = "windows"` attribute on the main binary only
6//! suppresses the window for the *main* process — not any child processes.
7//!
8//! Use [`cmd`] and [`async_cmd`] instead of `Command::new` at every call site.
9//! The Windows-specific flag is applied here and nowhere else.
10
11use std::ffi::OsStr;
12
13/// `CREATE_NO_WINDOW` — the process-creation flag that stops a child from
14/// getting a console window. Named once here so the two spawn helpers below
15/// cannot drift apart on the value.
16#[cfg(windows)]
17const CREATE_NO_WINDOW: u32 = 0x0800_0000;
18
19/// Create a [`std::process::Command`] that will not open a console window on Windows.
20///
21/// Identical to `std::process::Command::new(program)` on macOS and Linux.
22///
23/// # Usage
24///
25/// ```rust,ignore
26/// use gglib_core::utils::process::cmd;
27///
28/// let output = cmd("nvidia-smi").arg("--list-gpus").output()?;
29/// ```
30pub fn cmd(program: impl AsRef<OsStr>) -> std::process::Command {
31 #[allow(unused_mut)]
32 let mut c = std::process::Command::new(program);
33 #[cfg(windows)]
34 {
35 use std::os::windows::process::CommandExt;
36 c.creation_flags(CREATE_NO_WINDOW);
37 }
38 c
39}
40
41/// Create a [`tokio::process::Command`] that will not open a console window on Windows.
42///
43/// Identical to `tokio::process::Command::new(program)` on macOS and Linux.
44///
45/// # Usage
46///
47/// ```rust,ignore
48/// use gglib_core::utils::process::async_cmd;
49///
50/// let child = async_cmd("llama-server").arg("--port").arg("8080").spawn()?;
51/// ```
52pub fn async_cmd(program: impl AsRef<OsStr>) -> tokio::process::Command {
53 #[allow(unused_mut)]
54 let mut c = tokio::process::Command::new(program);
55 #[cfg(windows)]
56 {
57 // No `CommandExt` import here: unlike `std`, tokio puts `creation_flags`
58 // directly on its own `Command`, so importing the trait warns as unused.
59 c.creation_flags(CREATE_NO_WINDOW);
60 }
61 c
62}