process_wrap/lib.rs
1//! Composable wrappers over process::Command.
2//!
3//! # Quick start
4//!
5//! ```toml
6//! [dependencies]
7//! process-wrap = { version = "9.0.0", features = ["std"] }
8//! ```
9//!
10//! ```rust,no_run
11//! # fn main() -> std::io::Result<()> {
12//! use process_wrap::std::*;
13//!
14//! let mut command = CommandWrap::with_new("watch", |command| { command.arg("ls"); });
15//! #[cfg(unix)] { command.wrap(ProcessGroup::leader()); }
16//! #[cfg(windows)] { command.wrap(JobObject); }
17//! let mut child = command.spawn()?;
18//! let status = child.wait()?;
19//! dbg!(status);
20//! # Ok(()) }
21//! ```
22//!
23//! ## Migrating from command-group
24//!
25//! The above example is equivalent to the `command-group` 5.x usage. To migrate from versions 4.x
26//! and below, replace `ProcessGroup::leader()` with `ProcessSession`.
27//!
28//! # Overview
29//!
30//! This crate provides a composable set of wrappers over `process::Command` (either from std or
31//! from Tokio). It is a more flexible and composable successor to the `command-group` crate, and is
32//! meant to be adaptable to additional use cases: for example spawning processes in PTYs currently
33//! requires a different crate (such as `pty-process`) which won't function with `command-group`.
34//! Implementing a PTY wrapper for `process-wrap` would instead keep the same API and be composable
35//! with the existing process group/session implementations.
36//!
37//! # Usage
38//!
39//! The core API is [`CommandWrap`](std::CommandWrap) and [`CommandWrap`](tokio::CommandWrap),
40//! which can be constructed either directly from an existing `process::Command`:
41//!
42//! ```rust
43//! use process_wrap::std::*;
44//! use std::process::Command;
45//! let mut command = Command::new("ls");
46//! command.arg("-l");
47//! let mut command = CommandWrap::from(command);
48//! #[cfg(unix)] { command.wrap(ProcessGroup::leader()); }
49//! #[cfg(windows)] { command.wrap(JobObject); }
50//! ```
51//!
52//! ...or with a somewhat more ergonomic closure pattern:
53//!
54//! ```rust
55//! use process_wrap::std::*;
56//! let mut command = CommandWrap::with_new("ls", |command| { command.arg("-l"); });
57//! #[cfg(unix)] { command.wrap(ProcessGroup::leader()); }
58//! #[cfg(windows)] { command.wrap(JobObject); }
59//! ```
60//!
61//! If targetting a single platform, then a fluent style is possible:
62//!
63//! ```rust
64//! use process_wrap::std::*;
65//! CommandWrap::with_new("ls", |command| { command.arg("-l"); })
66//! .wrap(ProcessGroup::leader());
67//! ```
68//!
69//! The `wrap` method can be called multiple times to add multiple wrappers. The order of the
70//! wrappers can be important, as they are applied in the order they are added. The documentation
71//! for each wrapper will specify ordering concerns.
72//!
73//! The `spawn` method is used to spawn the process, after which the `Child` can be interacted with.
74//! Methods on `Child` mimic those on `process::Child`, but may be customised by the wrappers. For
75//! example, `kill` will send a signal to the process group if the `ProcessGroup` wrapper is used.
76//!
77//! # KillOnDrop and CreationFlags
78//!
79//! The options set on an underlying `Command` are not queryable from library or user code. In most
80//! cases this is not an issue; however on Windows, the `JobObject` wrapper needs to know the value
81//! of `.kill_on_drop()` and any `.creation_flags()` set. The `KillOnDrop` and `CreationFlags` are
82//! "shims" that _should_ be used instead of the aforementioned methods on `Command`. They will
83//! internally set the values on the `Command` and also store them in the wrapper, so that wrappers
84//! are able to access them.
85//!
86//! In practice:
87//!
88//! ## Instead of `.kill_on_drop(true)` (Tokio-only):
89//!
90//! ```rust
91//! use process_wrap::tokio::*;
92//! let mut command = CommandWrap::with_new("ls", |command| { command.arg("-l"); });
93//! command.wrap(KillOnDrop);
94//! ```
95//!
96//! ## Instead of `.creation_flags(CREATE_NO_WINDOW)` (Windows-only):
97//!
98//! ```rust,ignore
99//! use process_wrap::std::*;
100//! let mut command = CommandWrap::with_new("ls", |command| { command.arg("-l"); });
101//! command.wrap(CreationFlags(CREATE_NO_WINDOW));
102//! ```
103//!
104//! Internally the `JobObject` wrapper always sets the `CREATE_SUSPENDED` flag, but as it is able to
105//! access the `CreationFlags` value it will either resume the process after setting up, or leave it
106//! suspended if `CREATE_SUSPENDED` was explicitly set.
107//!
108//! # Extension
109//!
110//! The crate is designed to be extensible, and new wrappers can be added by implementing the
111//! required traits. The std and Tokio sides are completely separate, due to the different
112//! underlying APIs. Of course you can (and should) re-use/share code wherever possible if
113//! implementing both.
114//!
115//! At minimum, you must implement [`CommandWrapper`](crate::std::CommandWrapper) and/or
116//! [`CommandWrapper`](crate::tokio::CommandWrapper). These provide the same functionality
117//! (and indeed internally are generated using a common macro), but differ in the exact types used.
118//! Here's the most basic impl (shown for Tokio):
119//!
120//! ```rust
121//! use process_wrap::tokio::*;
122//! #[derive(Debug)]
123//! pub struct YourWrapper;
124//! impl CommandWrapper for YourWrapper {}
125//! ```
126//!
127//! The trait provides extension or hook points into the lifecycle of a `Command`:
128//!
129//! - **`fn extend(&mut self, other: Box<dyn CommandWrapper>)`** is called if
130//! `.wrap(YourWrapper)` is done twice. Only one of a wrapper type can exist, so this gives the
131//! opportunity to incorporate all or part of the second wrapper instance into the first. By
132//! default, this does nothing (ie only the first registered wrapper instance of a type applies).
133//!
134//! - **`fn pre_spawn(&mut self, command: &mut Command, core: &CommandWrap)`** is called before
135//! the command is spawned, and gives mutable access to it. It also gives mutable access to the
136//! wrapper instance, so state can be stored if needed. The `core` reference gives access to data
137//! from other wrappers; for example, that's how `CreationFlags` on Windows works along with
138//! `JobObject`. By default does nothing.
139//!
140//! - **`fn post_spawn(&mut self, child: &mut tokio::process::Child, core: &CommandWrap)`** is
141//! called after spawn, and should be used for any necessary cleanups. It is offered for
142//! completeness but is expected to be less used than `wrap_child()`. By default does nothing.
143//!
144//! - **`fn wrap_child(&mut self, child: Box<dyn TokioChildWrapper>, core: &CommandWrap)`** is
145//! called after all `post_spawn()`s have run. If your wrapper needs to override the methods on
146//! Child, then it should create an instance of its own type implementing `TokioChildWrapper` and
147//! return it here. Child wraps are _in order_: you may end up with a `Foo(Bar(Child))` or a
148//! `Bar(Foo(Child))` depending on if `.wrap(Foo).wrap(Bar)` or `.wrap(Bar).wrap(Foo)` was called.
149//! If your functionality is order-dependent, make sure to specify so in your documentation! By
150//! default does nothing: no wrapping is performed and the input `child` is returned as-is.
151//!
152//! ## An Example Logging Wrapper
153//!
154//! Let's implement a logging wrapper that redirects a `Command`'s `stdout` and `stderr` into a
155//! text file. We can use `std::io::pipe` to merge `stdout` and `stderr` into one channel, then
156//! `std::io::copy` in a background thread to non-blockingly stream that data to disk as it comes
157//! in.
158//!
159//! ```rust
160//! # use process_wrap::std::{CommandWrap, CommandWrapper};
161//! # use std::{fs::File, io, path::PathBuf, process::Command, thread};
162//! #[derive(Debug)]
163//! struct LogFile {
164//! path: PathBuf,
165//! }
166//!
167//! impl LogFile {
168//! fn new(path: impl Into<PathBuf>) -> Self {
169//! Self { path: path.into() }
170//! }
171//! }
172//!
173//! impl CommandWrapper for LogFile {
174//! fn pre_spawn(&mut self, command: &mut Command, _core: &CommandWrap) -> io::Result<()> {
175//! let mut logfile = File::create(&self.path)?;
176//! let (mut rx, tx) = io::pipe()?;
177//!
178//! thread::spawn(move || {
179//! io::copy(&mut rx, &mut logfile).unwrap();
180//! });
181//!
182//! command.stdout(tx.try_clone()?).stderr(tx);
183//! Ok(())
184//! }
185//! }
186//! ```
187//!
188//! That's a great start, but it's actually introduced a resource leak: if the main thread of your
189//! program exits before that background one does, then the background thread won't get a chance to
190//! call `logfile`'s `Drop` implementation which closes the file. The file handle will be left open!
191//! To fix this, we'll need to keep track of the background thread's `ThreadHandle` and `.join()` it
192//! when calling `.wait()` on the `ChildWrapper`.
193//!
194//! ```rust
195//! # use process_wrap::std::{ChildWrapper, CommandWrap, CommandWrapper};
196//! # use std::{
197//! # fs::File,
198//! # io, mem,
199//! # path::PathBuf,
200//! # process::{Command, ExitStatus},
201//! # thread::{self, JoinHandle},
202//! # };
203//! #[derive(Debug)]
204//! struct LogFile {
205//! path: PathBuf,
206//! thread: Option<JoinHandle<()>>,
207//! }
208//!
209//! impl LogFile {
210//! fn new(path: impl Into<PathBuf>) -> Self {
211//! Self {
212//! path: path.into(),
213//! thread: None,
214//! }
215//! }
216//! }
217//!
218//! impl CommandWrapper for LogFile {
219//! fn pre_spawn(&mut self, command: &mut Command, _core: &CommandWrap) -> io::Result<()> {
220//! let mut logfile = File::create(&self.path)?;
221//! let (mut rx, tx) = io::pipe()?;
222//!
223//! self.thread = Some(thread::spawn(move || {
224//! io::copy(&mut rx, &mut logfile).unwrap();
225//! }));
226//!
227//! command.stdout(tx.try_clone()?).stderr(tx);
228//! Ok(())
229//! }
230//!
231//! fn wrap_child(
232//! &mut self,
233//! child: Box<dyn ChildWrapper>,
234//! _core: &CommandWrap,
235//! ) -> io::Result<Box<dyn ChildWrapper>> {
236//! let wrapped_child = LogFileChild {
237//! inner: child,
238//! thread: mem::take(&mut self.thread),
239//! };
240//! Ok(Box::new(wrapped_child))
241//! }
242//! }
243//!
244//! #[derive(Debug)]
245//! struct LogFileChild {
246//! inner: Box<dyn ChildWrapper>,
247//! thread: Option<JoinHandle<()>>,
248//! }
249//!
250//! impl ChildWrapper for LogFileChild {
251//! fn inner(&self) -> &dyn ChildWrapper {
252//! &*self.inner
253//! }
254//!
255//! fn inner_mut(&mut self) -> &mut dyn ChildWrapper {
256//! &mut *self.inner
257//! }
258//!
259//! fn into_inner(self: Box<Self>) -> Box<dyn ChildWrapper> {
260//! self.inner
261//! }
262//!
263//! fn wait(&mut self) -> io::Result<ExitStatus> {
264//! let exit_status = self.inner.wait();
265//!
266//! if let Some(thread) = mem::take(&mut self.thread) {
267//! thread.join().unwrap();
268//! }
269//!
270//! exit_status
271//! }
272//! }
273//! ```
274//!
275//! Now we're cleaning up after ourselves, but there is one last issue: if you actually call
276//! `.wait()`, then your program will deadlock! This is because `io::copy` copies data until `rx`
277//! returns an EOF, but that only happens after *all* copies of `tx` are dropped. Currently, our
278//! `Command` is holding onto `tx` even after calling `.spawn()`, so unless we manually drop the
279//! `Command` (freeing both copies of `tx`) before calling `.wait()`, our program will deadlock!
280//! We can fix this by telling `Command` to drop `tx` right after spawning the child — by this
281//! point, the `ChildWrapper` will have already inherited the copies of `tx` that it needs, so
282//! dropping `tx` from `Command` should be totally safe. We'll get `Command` to "drop" `tx` by
283//! setting its `stdin` and `stdout` to `Stdio::null()` in `CommandWrapper::post_spawn()`.
284//!
285//! ```rust
286//! # use process_wrap::std::{CommandWrap, CommandWrapper};
287//! # use std::{
288//! # io,
289//! # path::PathBuf,
290//! # process::{Child, Command, Stdio},
291//! # thread::JoinHandle,
292//! # };
293//! # #[derive(Debug)]
294//! # struct LogFile {
295//! # path: PathBuf,
296//! # thread: Option<JoinHandle<()>>,
297//! # }
298//! #
299//! impl CommandWrapper for LogFile {
300//! // ... snip ...
301//! fn post_spawn(
302//! &mut self,
303//! command: &mut Command,
304//! _child: &mut Child,
305//! _core: &CommandWrap,
306//! ) -> io::Result<()> {
307//! command.stdout(Stdio::null()).stderr(Stdio::null());
308//!
309//! Ok(())
310//! }
311//! // ... snip ...
312//! }
313//! ```
314//!
315//! Finally, we can test that our new command-wrapper works:
316//!
317//! ```rust
318//! # use process_wrap::std::{ChildWrapper, CommandWrap, CommandWrapper};
319//! # use std::{
320//! # error::Error,
321//! # fs::{self, File},
322//! # io, mem,
323//! # path::PathBuf,
324//! # process::{Child, Command, ExitStatus, Stdio},
325//! # thread::{self, JoinHandle},
326//! # };
327//! # use tempfile::NamedTempFile;
328//! # #[derive(Debug)]
329//! # struct LogFile {
330//! # path: PathBuf,
331//! # thread: Option<JoinHandle<()>>,
332//! # }
333//! #
334//! # impl LogFile {
335//! # fn new(path: impl Into<PathBuf>) -> Self {
336//! # Self {
337//! # path: path.into(),
338//! # thread: None,
339//! # }
340//! # }
341//! # }
342//! #
343//! # impl CommandWrapper for LogFile {
344//! # fn pre_spawn(&mut self, command: &mut Command, _core: &CommandWrap) -> io::Result<()> {
345//! # let mut logfile = File::create(&self.path)?;
346//! # let (mut rx, tx) = io::pipe()?;
347//! #
348//! # self.thread = Some(thread::spawn(move || {
349//! # io::copy(&mut rx, &mut logfile).unwrap();
350//! # }));
351//! #
352//! # command.stdout(tx.try_clone()?).stderr(tx);
353//! # Ok(())
354//! # }
355//! #
356//! # fn post_spawn(
357//! # &mut self,
358//! # command: &mut Command,
359//! # _child: &mut Child,
360//! # _core: &CommandWrap,
361//! # ) -> io::Result<()> {
362//! # command.stdout(Stdio::null()).stderr(Stdio::null());
363//! #
364//! # Ok(())
365//! # }
366//! #
367//! # fn wrap_child(
368//! # &mut self,
369//! # child: Box<dyn ChildWrapper>,
370//! # _core: &CommandWrap,
371//! # ) -> io::Result<Box<dyn ChildWrapper>> {
372//! # let wrapped_child = LogFileChild {
373//! # inner: child,
374//! # thread: mem::take(&mut self.thread),
375//! # };
376//! # Ok(Box::new(wrapped_child))
377//! # }
378//! # }
379//! #
380//! # #[derive(Debug)]
381//! # struct LogFileChild {
382//! # inner: Box<dyn ChildWrapper>,
383//! # thread: Option<JoinHandle<()>>,
384//! # }
385//! #
386//! # impl ChildWrapper for LogFileChild {
387//! # fn inner(&self) -> &dyn ChildWrapper {
388//! # &*self.inner
389//! # }
390//! #
391//! # fn inner_mut(&mut self) -> &mut dyn ChildWrapper {
392//! # &mut *self.inner
393//! # }
394//! #
395//! # fn into_inner(self: Box<Self>) -> Box<dyn ChildWrapper> {
396//! # self.inner
397//! # }
398//! #
399//! # fn wait(&mut self) -> io::Result<ExitStatus> {
400//! # let exit_status = self.inner.wait();
401//! #
402//! # if let Some(thread) = mem::take(&mut self.thread) {
403//! # thread.join().unwrap();
404//! # }
405//! #
406//! # exit_status
407//! # }
408//! # }
409//! #
410//! fn main() -> Result<(), Box<dyn Error>> {
411//! #[cfg(windows)]
412//! let mut command = CommandWrap::with_new("cmd", |command| {
413//! command.args(["/c", "echo Hello && echo World 1>&2"]);
414//! });
415//! #[cfg(unix)]
416//! let mut command = CommandWrap::with_new("sh", |command| {
417//! command.args(["-c", "echo Hello && echo World 1>&2"]);
418//! });
419//!
420//! let logfile = NamedTempFile::new()?;
421//! let logfile_path = logfile.path();
422//!
423//! command.wrap(LogFile::new(logfile_path)).spawn()?.wait()?;
424//!
425//! let logfile_lines: Vec<String> = fs::read_to_string(logfile_path)?
426//! .lines()
427//! .map(|l| l.trim().into())
428//! .collect();
429//! assert_eq!(logfile_lines, vec!["Hello", "World"]);
430//!
431//! Ok(())
432//! }
433//! ```
434//!
435//! # Features
436//!
437//! ## Frontends
438//!
439//! The default features do not enable a frontend, so you must choose one of the following:
440//!
441//! - `std`: enables the std-based API.
442//! - `tokio1`: enables the Tokio-based API.
443//!
444//! Both can exist at the same time, but generally you'll want to use one or the other.
445//!
446//! ## Wrappers
447//!
448//! - `creation-flags`: **default**, enables the creation flags wrapper (Windows-only).
449//! - `job-object`: **default**, enables the job object wrapper (Windows-only).
450//! - `kill-on-drop`: **default**, enables the kill on drop wrapper (Tokio-only).
451//! - `process-group`: **default**, enables the process group wrapper (Unix-only).
452//! - `process-session`: **default**, enables the process session wrapper (Unix-only).
453//! - `reset-sigmask`: enables the sigmask reset wrapper (Unix-only).
454//!
455#![doc(html_favicon_url = "https://watchexec.github.io/logo:command-group.svg")]
456#![doc(html_logo_url = "https://watchexec.github.io/logo:command-group.svg")]
457#![cfg_attr(docsrs, feature(doc_auto_cfg))]
458#![warn(missing_docs)]
459
460pub(crate) mod generic_wrap;
461
462#[cfg(feature = "std")]
463pub mod std;
464
465#[cfg(feature = "tokio1")]
466pub mod tokio;
467
468#[cfg(all(
469 windows,
470 feature = "job-object",
471 any(feature = "std", feature = "tokio1")
472))]
473mod windows;
474
475/// Internal memoization of the exit status of a child process.
476#[allow(dead_code)] // easier than listing exactly which featuresets use it
477#[derive(Debug)]
478pub(crate) enum ChildExitStatus {
479 Running,
480 Exited(::std::process::ExitStatus),
481}