Skip to main content

process_wrap/
lib.rs

1//! Composable wrappers over process::Command.
2//!
3//! # Quick start
4//!
5//! ```toml
6//! [dependencies]
7//! process-wrap = { version = "10.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. `CreationFlags` and `JobObject` may be
107//! registered in either order.
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 [`CommandWrapper`](crate::std::CommandWrapper) and/or
117//! [`CommandWrapper`](crate::tokio::CommandWrapper). 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 CommandWrapper 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: Self)`** is called if `.wrap(YourWrapper)` is done twice.
131//!   Only one wrapper of a given type can exist, so this gives the stored instance an opportunity to
132//!   incorporate all or part of the second, concretely typed wrapper. By default, this does nothing
133//!   (that is, only the first registered wrapper instance of a type applies).
134//!
135//! - **`fn pre_spawn(&mut self, command: &mut Command, core: &CommandWrap)`** 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: &CommandWrap)`** 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: &CommandWrap)`** 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//! ## An Example Logging Wrapper
154//!
155//! Let's implement a logging wrapper that redirects a `Command`'s `stdout` and `stderr` into a
156//! text file. We can use `std::io::pipe` to merge `stdout` and `stderr` into one channel, then
157//! `std::io::copy` in a background thread to non-blockingly stream that data to disk as it comes
158//! in.
159//!
160//! ```rust
161//! # use process_wrap::std::{CommandWrap, CommandWrapper};
162//! # use std::{fs::File, io, path::PathBuf, process::Command, thread};
163//! #[derive(Debug)]
164//! struct LogFile {
165//!     path: PathBuf,
166//! }
167//!
168//! impl LogFile {
169//!     fn new(path: impl Into<PathBuf>) -> Self {
170//!         Self { path: path.into() }
171//!     }
172//! }
173//!
174//! impl CommandWrapper for LogFile {
175//!     fn pre_spawn(&mut self, command: &mut Command, _core: &CommandWrap) -> io::Result<()> {
176//!         let mut logfile = File::create(&self.path)?;
177//!         let (mut rx, tx) = io::pipe()?;
178//!
179//!         thread::spawn(move || {
180//!          io::copy(&mut rx, &mut logfile).unwrap();
181//!         });
182//!
183//!         command.stdout(tx.try_clone()?).stderr(tx);
184//!         Ok(())
185//!     }
186//! }
187//! ```
188//!
189//! That's a great start, but it's actually introduced a resource leak: if the main thread of your
190//! program exits before that background one does, then the background thread won't get a chance to
191//! call `logfile`'s `Drop` implementation which closes the file. The file handle will be left open!
192//! To fix this, we'll need to keep track of the background thread's `ThreadHandle` and `.join()` it
193//! when calling `.wait()` on the `ChildWrapper`.
194//!
195//! ```rust
196//! # use process_wrap::std::{ChildWrapper, CommandWrap, CommandWrapper};
197//! # use std::{
198//! #     fs::File,
199//! #     io, mem,
200//! #     path::PathBuf,
201//! #     process::{Command, ExitStatus},
202//! #     thread::{self, JoinHandle},
203//! # };
204//! #[derive(Debug)]
205//! struct LogFile {
206//!     path: PathBuf,
207//!     thread: Option<JoinHandle<()>>,
208//! }
209//!
210//! impl LogFile {
211//!     fn new(path: impl Into<PathBuf>) -> Self {
212//!         Self {
213//!          path: path.into(),
214//!          thread: None,
215//!         }
216//!     }
217//! }
218//!
219//! impl CommandWrapper for LogFile {
220//!     fn pre_spawn(&mut self, command: &mut Command, _core: &CommandWrap) -> io::Result<()> {
221//!         let mut logfile = File::create(&self.path)?;
222//!         let (mut rx, tx) = io::pipe()?;
223//!
224//!         self.thread = Some(thread::spawn(move || {
225//!          io::copy(&mut rx, &mut logfile).unwrap();
226//!         }));
227//!
228//!         command.stdout(tx.try_clone()?).stderr(tx);
229//!         Ok(())
230//!     }
231//!
232//!     fn wrap_child(
233//!         &mut self,
234//!         child: Box<dyn ChildWrapper>,
235//!         _core: &CommandWrap,
236//!     ) -> io::Result<Box<dyn ChildWrapper>> {
237//!         let wrapped_child = LogFileChild {
238//!          inner: child,
239//!          thread: mem::take(&mut self.thread),
240//!         };
241//!         Ok(Box::new(wrapped_child))
242//!     }
243//! }
244//!
245//! #[derive(Debug)]
246//! struct LogFileChild {
247//!     inner: Box<dyn ChildWrapper>,
248//!     thread: Option<JoinHandle<()>>,
249//! }
250//!
251//! impl ChildWrapper for LogFileChild {
252//!     fn inner(&self) -> &dyn ChildWrapper {
253//!         &*self.inner
254//!     }
255//!
256//!     fn inner_mut(&mut self) -> &mut dyn ChildWrapper {
257//!         &mut *self.inner
258//!     }
259//!
260//!     fn into_inner(self: Box<Self>) -> Box<dyn ChildWrapper> {
261//!         self.inner
262//!     }
263//!
264//!     #[cfg(windows)]
265//!     fn process_handle(
266//!         &self,
267//!     ) -> Option<std::os::windows::io::BorrowedHandle<'_>> {
268//!         self.inner.process_handle()
269//!     }
270//!
271//!     fn wait(&mut self) -> io::Result<ExitStatus> {
272//!         let exit_status = self.inner.wait();
273//!
274//!         if let Some(thread) = mem::take(&mut self.thread) {
275//!          thread.join().unwrap();
276//!         }
277//!
278//!         exit_status
279//!     }
280//! }
281//! ```
282//!
283//! Now we're cleaning up after ourselves, but there is one last issue: if you actually call
284//! `.wait()`, then your program will deadlock! This is because `io::copy` copies data until `rx`
285//! returns an EOF, but that only happens after *all* copies of `tx` are dropped. Currently, our
286//! `Command` is holding onto `tx` even after calling `.spawn()`, so unless we manually drop the
287//! `Command` (freeing both copies of `tx`) before calling `.wait()`, our program will deadlock!
288//! We can fix this by telling `Command` to drop `tx` right after spawning the child — by this
289//! point, the `ChildWrapper` will have already inherited the copies of `tx` that it needs, so
290//! dropping `tx` from `Command` should be totally safe. We'll get `Command` to "drop" `tx` by
291//! setting its `stdin` and `stdout` to `Stdio::null()` in `CommandWrapper::post_spawn()`.
292//!
293//! ```rust
294//! # use process_wrap::std::{CommandWrap, CommandWrapper};
295//! # use std::{
296//! #     io,
297//! #     path::PathBuf,
298//! #     process::{Child, Command, Stdio},
299//! #     thread::JoinHandle,
300//! # };
301//! # #[derive(Debug)]
302//! # struct LogFile {
303//! #     path: PathBuf,
304//! #     thread: Option<JoinHandle<()>>,
305//! # }
306//! #
307//! impl CommandWrapper for LogFile {
308//!     // ... snip ...
309//!     fn post_spawn(
310//!         &mut self,
311//!         command: &mut Command,
312//!         _child: &mut Child,
313//!         _core: &CommandWrap,
314//!     ) -> io::Result<()> {
315//!         command.stdout(Stdio::null()).stderr(Stdio::null());
316//!
317//!         Ok(())
318//!     }
319//!     // ... snip ...
320//! }
321//! ```
322//!
323//! Finally, we can test that our new command-wrapper works:
324//!
325//! ```rust
326//! # use process_wrap::std::{ChildWrapper, CommandWrap, CommandWrapper};
327//! # use std::{
328//! #     error::Error,
329//! #     fs::{self, File},
330//! #     io, mem,
331//! #     path::PathBuf,
332//! #     process::{Child, Command, ExitStatus, Stdio},
333//! #     thread::{self, JoinHandle},
334//! # };
335//! # use tempfile::NamedTempFile;
336//! # #[derive(Debug)]
337//! # struct LogFile {
338//! #     path: PathBuf,
339//! #     thread: Option<JoinHandle<()>>,
340//! # }
341//! #
342//! # impl LogFile {
343//! #     fn new(path: impl Into<PathBuf>) -> Self {
344//! #         Self {
345//! #          path: path.into(),
346//! #          thread: None,
347//! #         }
348//! #     }
349//! # }
350//! #
351//! # impl CommandWrapper for LogFile {
352//! #     fn pre_spawn(&mut self, command: &mut Command, _core: &CommandWrap) -> io::Result<()> {
353//! #         let mut logfile = File::create(&self.path)?;
354//! #         let (mut rx, tx) = io::pipe()?;
355//! #
356//! #         self.thread = Some(thread::spawn(move || {
357//! #          io::copy(&mut rx, &mut logfile).unwrap();
358//! #         }));
359//! #
360//! #         command.stdout(tx.try_clone()?).stderr(tx);
361//! #         Ok(())
362//! #     }
363//! #
364//! #     fn post_spawn(
365//! #         &mut self,
366//! #         command: &mut Command,
367//! #         _child: &mut Child,
368//! #         _core: &CommandWrap,
369//! #     ) -> io::Result<()> {
370//! #         command.stdout(Stdio::null()).stderr(Stdio::null());
371//! #
372//! #         Ok(())
373//! #     }
374//! #
375//! #     fn wrap_child(
376//! #         &mut self,
377//! #         child: Box<dyn ChildWrapper>,
378//! #         _core: &CommandWrap,
379//! #     ) -> io::Result<Box<dyn ChildWrapper>> {
380//! #         let wrapped_child = LogFileChild {
381//! #          inner: child,
382//! #          thread: mem::take(&mut self.thread),
383//! #         };
384//! #         Ok(Box::new(wrapped_child))
385//! #     }
386//! # }
387//! #
388//! # #[derive(Debug)]
389//! # struct LogFileChild {
390//! #     inner: Box<dyn ChildWrapper>,
391//! #     thread: Option<JoinHandle<()>>,
392//! # }
393//! #
394//! # impl ChildWrapper for LogFileChild {
395//! #     fn inner(&self) -> &dyn ChildWrapper {
396//! #         &*self.inner
397//! #     }
398//! #
399//! #     fn inner_mut(&mut self) -> &mut dyn ChildWrapper {
400//! #         &mut *self.inner
401//! #     }
402//! #
403//! #     fn into_inner(self: Box<Self>) -> Box<dyn ChildWrapper> {
404//! #         self.inner
405//! #     }
406//! #
407//! #     #[cfg(windows)]
408//! #     fn process_handle(
409//! #         &self,
410//! #     ) -> Option<std::os::windows::io::BorrowedHandle<'_>> {
411//! #         self.inner.process_handle()
412//! #     }
413//! #
414//! #     fn wait(&mut self) -> io::Result<ExitStatus> {
415//! #         let exit_status = self.inner.wait();
416//! #
417//! #         if let Some(thread) = mem::take(&mut self.thread) {
418//! #          thread.join().unwrap();
419//! #         }
420//! #
421//! #         exit_status
422//! #     }
423//! # }
424//! #
425//! fn main() -> Result<(), Box<dyn Error>> {
426//!     #[cfg(windows)]
427//!     let mut command = CommandWrap::with_new("cmd", |command| {
428//!         command.args(["/c", "echo Hello && echo World 1>&2"]);
429//!     });
430//!     #[cfg(unix)]
431//!     let mut command = CommandWrap::with_new("sh", |command| {
432//!         command.args(["-c", "echo Hello && echo World 1>&2"]);
433//!     });
434//!
435//!     let logfile = NamedTempFile::new()?;
436//!     let logfile_path = logfile.path();
437//!
438//!     command.wrap(LogFile::new(logfile_path)).spawn()?.wait()?;
439//!
440//!     let logfile_lines: Vec<String> = fs::read_to_string(logfile_path)?
441//!         .lines()
442//!         .map(|l| l.trim().into())
443//!         .collect();
444//!     assert_eq!(logfile_lines, vec!["Hello", "World"]);
445//!
446//!     Ok(())
447//! }
448//! ```
449//!
450//! # Features
451//!
452//! ## Frontends
453//!
454//! The default features do not enable a frontend, so you must choose one of the following:
455//!
456//! - `std`: enables the std-based API.
457//! - `tokio1`: enables the Tokio-based API.
458//!
459//! Both can exist at the same time, but generally you'll want to use one or the other.
460//!
461//! ## Wrappers
462//!
463//! - `creation-flags`: **default**, enables the creation flags wrapper (Windows-only).
464//! - `job-object`: **default**, enables the job object wrapper (Windows-only).
465//! - `kill-on-drop`: **default**, enables the kill on drop wrapper (Tokio-only).
466//! - `process-group`: **default**, enables the process group wrapper (Unix-only).
467//! - `process-session`: **default**, enables the process session wrapper (Unix-only).
468//! - `reset-sigmask`: enables the sigmask reset wrapper (Unix-only).
469//!
470#![doc(html_favicon_url = "https://watchexec.github.io/logo:command-group.svg")]
471#![doc(html_logo_url = "https://watchexec.github.io/logo:command-group.svg")]
472#![cfg_attr(docsrs, feature(doc_cfg))]
473#![warn(missing_docs)]
474
475pub(crate) mod generic_wrap;
476
477#[cfg(feature = "std")]
478pub mod std;
479
480#[cfg(feature = "tokio1")]
481pub mod tokio;
482
483#[cfg(all(
484	windows,
485	feature = "job-object",
486	any(feature = "std", feature = "tokio1")
487))]
488mod windows;
489
490/// Internal memoization of the exit status of a child process.
491#[allow(dead_code)] // easier than listing exactly which featuresets use it
492#[derive(Debug)]
493pub(crate) enum ChildExitStatus {
494	Running,
495	Exited(::std::process::ExitStatus),
496}