process_wrap/
lib.rs

1//! Composable wrappers over process::Command.
2//!
3//! # Quick start
4//!
5//! ```toml
6//! [dependencies]
7//! process-wrap = { version = "8.2.1", features = ["std"] }
8//! ```
9//!
10//! ```rust,no_run
11//! # fn main() -> std::io::Result<()> {
12//! use std::process::Command;
13//! use process_wrap::std::*;
14//!
15//! let mut command = StdCommandWrap::with_new("watch", |command| { command.arg("ls"); });
16//! #[cfg(unix)] { command.wrap(ProcessGroup::leader()); }
17//! #[cfg(windows)] { command.wrap(JobObject); }
18//! let mut child = command.spawn()?;
19//! let status = child.wait()?;
20//! dbg!(status);
21//! # Ok(()) }
22//! ```
23//!
24//! ## Migrating from command-group
25//!
26//! The above example is equivalent to the `command-group` 5.x usage. To migrate from versions 4.x
27//! and below, replace `ProcessGroup::leader()` with `ProcessSession`.
28//!
29//! # Overview
30//!
31//! This crate provides a composable set of wrappers over `process::Command` (either from std or
32//! from Tokio). It is a more flexible and composable successor to the `command-group` crate, and is
33//! meant to be adaptable to additional use cases: for example spawning processes in PTYs currently
34//! requires a different crate (such as `pty-process`) which won't function with `command-group`.
35//! Implementing a PTY wrapper for `process-wrap` would instead keep the same API and be composable
36//! with the existing process group/session implementations.
37//!
38//! # Usage
39//!
40//! The core API is [`StdCommandWrap`](std::StdCommandWrap) and [`TokioCommandWrap`](tokio::TokioCommandWrap),
41//! which can be constructed either directly from an existing `process::Command`:
42//!
43//! ```rust
44//! use process_wrap::std::*;
45//! use std::process::Command;
46//! let mut command = Command::new("ls");
47//! command.arg("-l");
48//! let mut command = StdCommandWrap::from(command);
49//! #[cfg(unix)] { command.wrap(ProcessGroup::leader()); }
50//! #[cfg(windows)] { command.wrap(JobObject); }
51//! ```
52//!
53//! ...or with a somewhat more ergonomic closure pattern:
54//!
55//! ```rust
56//! use process_wrap::std::*;
57//! let mut command = StdCommandWrap::with_new("ls", |command| { command.arg("-l"); });
58//! #[cfg(unix)] { command.wrap(ProcessGroup::leader()); }
59//! #[cfg(windows)] { command.wrap(JobObject); }
60//! ```
61//!
62//! If targetting a single platform, then a fluent style is possible:
63//!
64//! ```rust
65//! use process_wrap::std::*;
66//! StdCommandWrap::with_new("ls", |command| { command.arg("-l"); })
67//!    .wrap(ProcessGroup::leader());
68//! ```
69//!
70//! The `wrap` method can be called multiple times to add multiple wrappers. The order of the
71//! wrappers can be important, as they are applied in the order they are added. The documentation
72//! for each wrapper will specify ordering concerns.
73//!
74//! The `spawn` method is used to spawn the process, after which the `Child` can be interacted with.
75//! Methods on `Child` mimic those on `process::Child`, but may be customised by the wrappers. For
76//! example, `kill` will send a signal to the process group if the `ProcessGroup` wrapper is used.
77//!
78//! # KillOnDrop and CreationFlags
79//!
80//! The options set on an underlying `Command` are not queryable from library or user code. In most
81//! cases this is not an issue; however on Windows, the `JobObject` wrapper needs to know the value
82//! of `.kill_on_drop()` and any `.creation_flags()` set. The `KillOnDrop` and `CreationFlags` are
83//! "shims" that _should_ be used instead of the aforementioned methods on `Command`. They will
84//! internally set the values on the `Command` and also store them in the wrapper, so that wrappers
85//! are able to access them.
86//!
87//! In practice:
88//!
89//! ## Instead of `.kill_on_drop(true)` (Tokio-only):
90//!
91//! ```rust
92//! use process_wrap::tokio::*;
93//! let mut command = TokioCommandWrap::with_new("ls", |command| { command.arg("-l"); });
94//! command.wrap(KillOnDrop);
95//! ```
96//!
97//! ## Instead of `.creation_flags(CREATE_NO_WINDOW)` (Windows-only):
98//!
99//! ```rust,ignore
100//! use process_wrap::std::*;
101//! let mut command = StdCommandWrap::with_new("ls", |command| { command.arg("-l"); });
102//! command.wrap(CreationFlags(CREATE_NO_WINDOW));
103//! ```
104//!
105//! Internally the `JobObject` wrapper always sets the `CREATE_SUSPENDED` flag, but as it is able to
106//! access the `CreationFlags` value it will either resume the process after setting up, or leave it
107//! suspended if `CREATE_SUSPENDED` was explicitly set.
108//!
109//! # Extension
110//!
111//! The crate is designed to be extensible, and new wrappers can be added by implementing the
112//! required traits. The std and Tokio sides are completely separate, due to the different
113//! underlying APIs. Of course you can (and should) re-use/share code wherever possible if
114//! implementing both.
115//!
116//! At minimum, you must implement [`StdCommandWrapper`](crate::std::StdCommandWrapper) and/or
117//! [`TokioCommandWrapper`](crate::tokio::TokioCommandWrapper). These provide the same functionality
118//! (and indeed internally are generated using a common macro), but differ in the exact types used.
119//! Here's the most basic impl (shown for Tokio):
120//!
121//! ```rust
122//! use process_wrap::tokio::*;
123//! #[derive(Debug)]
124//! pub struct YourWrapper;
125//! impl TokioCommandWrapper for YourWrapper {}
126//! ```
127//!
128//! The trait provides extension or hook points into the lifecycle of a `Command`:
129//!
130//! - **`fn extend(&mut self, other: Box<dyn TokioCommandWrapper>)`** is called if
131//!   `.wrap(YourWrapper)` is done twice. Only one of a wrapper type can exist, so this gives the
132//!   opportunity to incorporate all or part of the second wrapper instance into the first. By
133//!   default, this does nothing (ie only the first registered wrapper instance of a type applies).
134//!
135//! - **`fn pre_spawn(&mut self, command: &mut Command, core: &TokioCommandWrap)`** is called before
136//!   the command is spawned, and gives mutable access to it. It also gives mutable access to the
137//!   wrapper instance, so state can be stored if needed. The `core` reference gives access to data
138//!   from other wrappers; for example, that's how `CreationFlags` on Windows works along with
139//!   `JobObject`. By default does nothing.
140//!
141//! - **`fn post_spawn(&mut self, child: &mut tokio::process::Child, core: &TokioCommandWrap)`** is
142//!   called after spawn, and should be used for any necessary cleanups. It is offered for
143//!   completeness but is expected to be less used than `wrap_child()`. By default does nothing.
144//!
145//! - **`fn wrap_child(&mut self, child: Box<dyn TokioChildWrapper>, core: &TokioCommandWrap)`** is
146//!   called after all `post_spawn()`s have run. If your wrapper needs to override the methods on
147//!   Child, then it should create an instance of its own type implementing `TokioChildWrapper` and
148//!   return it here. Child wraps are _in order_: you may end up with a `Foo(Bar(Child))` or a
149//!   `Bar(Foo(Child))` depending on if `.wrap(Foo).wrap(Bar)` or `.wrap(Bar).wrap(Foo)` was called.
150//!   If your functionality is order-dependent, make sure to specify so in your documentation! By
151//!   default does nothing: no wrapping is performed and the input `child` is returned as-is.
152//!
153//! # Features
154//!
155//! ## Frontends
156//!
157//! The default features do not enable a frontend, so you must choose one of the following:
158//!
159//! - `std`: enables the std-based API.
160//! - `tokio1`: enables the Tokio-based API.
161//!
162//! Both can exist at the same time, but generally you'll want to use one or the other.
163//!
164//! ## Wrappers
165//!
166//! - `creation-flags`: **default**, enables the creation flags wrapper (Windows-only).
167//! - `job-object`: **default**, enables the job object wrapper (Windows-only).
168//! - `kill-on-drop`: **default**, enables the kill on drop wrapper (Tokio-only).
169//! - `process-group`: **default**, enables the process group wrapper (Unix-only).
170//! - `process-session`: **default**, enables the process session wrapper (Unix-only).
171//! - `reset-sigmask`: enables the sigmask reset wrapper (Unix-only).
172//!
173#![doc(html_favicon_url = "https://watchexec.github.io/logo:command-group.svg")]
174#![doc(html_logo_url = "https://watchexec.github.io/logo:command-group.svg")]
175#![cfg_attr(docsrs, feature(doc_auto_cfg))]
176#![warn(missing_docs)]
177
178pub(crate) mod generic_wrap;
179
180#[cfg(feature = "std")]
181pub mod std;
182
183#[cfg(feature = "tokio1")]
184pub mod tokio;
185
186#[cfg(all(
187	windows,
188	feature = "job-object",
189	any(feature = "std", feature = "tokio1")
190))]
191mod windows;
192
193/// Internal memoization of the exit status of a child process.
194#[allow(dead_code)] // easier than listing exactly which featuresets use it
195#[derive(Debug)]
196pub(crate) enum ChildExitStatus {
197	Running,
198	Exited(::std::process::ExitStatus),
199}