Home

Awesome

Crate release version Crate license: Apache 2.0 or MIT CI status

process-wrap

Unlike command-group, process-wrap doesn't implement a single cross-platform API. Instead, it provides composable wrappers which implement a single concern each. It is left to the developer to use the appropriate wrapper(s) for their use-case and platform.

As the successor to (and containing a lot of the code of) command-group, versioning starts at 6.0.0. You can think of it as a breaking change to command-group, though the paradigm is quite different. The full test suite from command-group was retained: process-wrap has parity on functionality as a starting point.

Quick start

[dependencies]
process-wrap = { version = "8.0.2", features = ["tokio1"] }

By default, the crate does nothing, you need to enable either the std or Tokio "frontend". A default set of wrappers are enabled; you may choose to only compile those you need, see the features list.

use tokio::process::Command;
use process_wrap::tokio::*;

let mut child = TokioCommandWrap::with_new("watch", |command| { command.arg("ls"); })
  .wrap(ProcessGroup::leader())
  .spawn()?;
let status = Box::into_pin(child.wait()).await?;
dbg!(status);

or on Windows

use tokio::process::Command;
use process_wrap::tokio::*;

let mut child = TokioCommandWrap::with_new("watch", |command| { command.arg("ls"); })
  .wrap(JobObject::new())
  .spawn()?;
let status = Box::into_pin(child.wait()).await?;
dbg!(status);

or with sessions

use tokio::process::Command;
use process_wrap::tokio::*;

let mut child = TokioCommandWrap::with_new("watch", |command| { command.arg("ls"); })
  .wrap(ProcessSession)
  .spawn()?;
let status = Box::into_pin(child.wait()).await?;
dbg!(status);

or with multiple wrappers

use tokio::process::Command;
use process_wrap::tokio::*;

let mut child = TokioCommandWrap::with_new("watch", |command| { command.arg("ls"); })
  .wrap(ProcessSession)
  .wrap(KillOnDrop)
  .spawn()?;
let status = Box::into_pin(child.wait()).await?;
dbg!(status);

or with std

[dependencies]
process-wrap = { version = "8.0.2", features = ["std"] }
use std::process::Command;
use process_wrap::std::*;

let mut child = StdCommandWrap::with_new("watch", |command| { command.arg("ls"); })
  .wrap(ProcessGroup::leader())
  .spawn()?;
let status = child.wait()?;
dbg!(status);

Wrappers

Job object

TokioCommandWrap::with_new("watch", |command| { command.arg("ls"); })
  .wrap(JobObject)
  .spawn()?;

Process group

TokioCommandWrap::with_new("watch", |command| { command.arg("ls"); })
  .wrap(ProcessGroup::leader())
  .spawn()?;

Or join a different group instead:

TokioCommandWrap::with_new("watch", |command| { command.arg("ls"); })
  .wrap(ProcessGroup::attach_to(pgid))
  .spawn()?;

For Windows process groups, use CreationFlags::NEW_PROCESS_GROUP and/or JobObject::new().

Process session

This combines creating a new session and a new group, and setting this process as leader. To join the session from another process, use ProcessGroup::attach_to() instead.

TokioCommandWrap::with_new("watch", |command| { command.arg("ls"); })
  .wrap(ProcessSession)
  .spawn()?;

Reset signal mask

This resets the signal mask of the process instead of inheriting it from the parent.

TokioCommandWrap::with_new("watch", |command| { command.arg("ls"); })
  .wrap(ResetSigmask)
  .spawn()?;

Creation flags

This is a shim to allow setting Windows process creation flags with this API, as otherwise they'd be overwritten.

use windows::Win32::System::Threading::*;
TokioCommandWrap::with_new("watch", |command| { command.arg("ls"); })
  .wrap(CreationFlags(CREATE_NO_WINDOW | CREATE_DETACHED))
  .wrap(JobObject)
  .spawn()?;

Kill on drop

This is a shim to allow wrappers to handle the kill-on-drop flag, as it can't be read from Command.

let child = TokioCommandWrap::with_new("watch", |command| { command.arg("ls"); })
  .wrap(KillOnDrop)
  .wrap(ProcessGroup::leader())
  .spawn()?;
drop(child);

Your own

Implementing a wrapper is done via a set of 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, but differ in the exact types specified. Here's the most basic impl (shown for Tokio):

#[derive(Debug)]
pub struct YourWrapper;
impl TokioCommandWrapper for YourWrapper {}

That's right, all member methods are optional. The trait provides extension or hook points into the lifecycle of a Command:

Refer to the API documentation for more detail and the specifics of child wrapper traits.

Features

Frontends

Both can exist at the same time, but generally you should use one or the other.

Wrappers