Expand description
Composable wrappers over process::Command.
§Quick start
[dependencies]
process-wrap = { version = "8.2.0", features = ["std"] }
use std::process::Command;
use process_wrap::std::*;
let mut command = StdCommandWrap::with_new("watch", |command| { command.arg("ls"); });
#[cfg(unix)] { command.wrap(ProcessGroup::leader()); }
#[cfg(windows)] { command.wrap(JobObject); }
let mut child = command.spawn()?;
let status = child.wait()?;
dbg!(status);
§Migrating from command-group
The above example is equivalent to the command-group
5.x usage. To migrate from versions 4.x
and below, replace ProcessGroup::leader()
with ProcessSession
.
§Overview
This crate provides a composable set of wrappers over process::Command
(either from std or
from Tokio). It is a more flexible and composable successor to the command-group
crate, and is
meant to be adaptable to additional use cases: for example spawning processes in PTYs currently
requires a different crate (such as pty-process
) which won’t function with command-group
.
Implementing a PTY wrapper for process-wrap
would instead keep the same API and be composable
with the existing process group/session implementations.
§Usage
The core API is StdCommandWrap
and TokioCommandWrap
,
which can be constructed either directly from an existing process::Command
:
use process_wrap::std::*;
use std::process::Command;
let mut command = Command::new("ls");
command.arg("-l");
let mut command = StdCommandWrap::from(command);
#[cfg(unix)] { command.wrap(ProcessGroup::leader()); }
#[cfg(windows)] { command.wrap(JobObject); }
…or with a somewhat more ergonomic closure pattern:
use process_wrap::std::*;
let mut command = StdCommandWrap::with_new("ls", |command| { command.arg("-l"); });
#[cfg(unix)] { command.wrap(ProcessGroup::leader()); }
#[cfg(windows)] { command.wrap(JobObject); }
If targetting a single platform, then a fluent style is possible:
use process_wrap::std::*;
StdCommandWrap::with_new("ls", |command| { command.arg("-l"); })
.wrap(ProcessGroup::leader());
The wrap
method can be called multiple times to add multiple wrappers. The order of the
wrappers can be important, as they are applied in the order they are added. The documentation
for each wrapper will specify ordering concerns.
The spawn
method is used to spawn the process, after which the Child
can be interacted with.
Methods on Child
mimic those on process::Child
, but may be customised by the wrappers. For
example, kill
will send a signal to the process group if the ProcessGroup
wrapper is used.
§KillOnDrop and CreationFlags
The options set on an underlying Command
are not queryable from library or user code. In most
cases this is not an issue; however on Windows, the JobObject
wrapper needs to know the value
of .kill_on_drop()
and any .creation_flags()
set. The KillOnDrop
and CreationFlags
are
“shims” that should be used instead of the aforementioned methods on Command
. They will
internally set the values on the Command
and also store them in the wrapper, so that wrappers
are able to access them.
In practice:
§Instead of .kill_on_drop(true)
(Tokio-only):
use process_wrap::tokio::*;
let mut command = TokioCommandWrap::with_new("ls", |command| { command.arg("-l"); });
command.wrap(KillOnDrop);
§Instead of .creation_flags(CREATE_NO_WINDOW)
(Windows-only):
use process_wrap::std::*;
let mut command = StdCommandWrap::with_new("ls", |command| { command.arg("-l"); });
command.wrap(CreationFlags(CREATE_NO_WINDOW));
Internally the JobObject
wrapper always sets the CREATE_SUSPENDED
flag, but as it is able to
access the CreationFlags
value it will either resume the process after setting up, or leave it
suspended if CREATE_SUSPENDED
was explicitly set.
§Extension
The crate is designed to be extensible, and new wrappers can be added by implementing the required traits. The std and Tokio sides are completely separate, due to the different underlying APIs. Of course you can (and should) re-use/share code wherever possible if implementing both.
At minimum, you must implement StdCommandWrapper
and/or
TokioCommandWrapper
. These provide the same functionality
(and indeed internally are generated using a common macro), but differ in the exact types used.
Here’s the most basic impl (shown for Tokio):
use process_wrap::tokio::*;
#[derive(Debug)]
pub struct YourWrapper;
impl TokioCommandWrapper for YourWrapper {}
The trait provides extension or hook points into the lifecycle of a Command
:
-
fn extend(&mut self, other: Box<dyn TokioCommandWrapper>)
is called if.wrap(YourWrapper)
is done twice. Only one of a wrapper type can exist, so this gives the opportunity to incorporate all or part of the second wrapper instance into the first. By default, this does nothing (ie only the first registered wrapper instance of a type applies). -
fn pre_spawn(&mut self, command: &mut Command, core: &TokioCommandWrap)
is called before the command is spawned, and gives mutable access to it. It also gives mutable access to the wrapper instance, so state can be stored if needed. Thecore
reference gives access to data from other wrappers; for example, that’s howCreationFlags
on Windows works along withJobObject
. By default does nothing. -
fn post_spawn(&mut self, child: &mut tokio::process::Child, core: &TokioCommandWrap)
is called after spawn, and should be used for any necessary cleanups. It is offered for completeness but is expected to be less used thanwrap_child()
. By default does nothing. -
fn wrap_child(&mut self, child: Box<dyn TokioChildWrapper>, core: &TokioCommandWrap)
is called after allpost_spawn()
s have run. If your wrapper needs to override the methods on Child, then it should create an instance of its own type implementingTokioChildWrapper
and return it here. Child wraps are in order: you may end up with aFoo(Bar(Child))
or aBar(Foo(Child))
depending on if.wrap(Foo).wrap(Bar)
or.wrap(Bar).wrap(Foo)
was called. If your functionality is order-dependent, make sure to specify so in your documentation! By default does nothing: no wrapping is performed and the inputchild
is returned as-is.
§Features
§Frontends
The default features do not enable a frontend, so you must choose one of the following:
std
: enables the std-based API.tokio1
: enables the Tokio-based API.
Both can exist at the same time, but generally you’ll want to use one or the other.
§Wrappers
creation-flags
: default, enables the creation flags wrapper (Windows-only).job-object
: default, enables the job object wrapper (Windows-only).kill-on-drop
: default, enables the kill on drop wrapper (Tokio-only).process-group
: default, enables the process group wrapper (Unix-only).process-session
: default, enables the process session wrapper (Unix-only).reset-sigmask
: enables the sigmask reset wrapper (Unix-only).