Skip to content

Commit

Permalink
Add new API for creating jobserver and run cmds with the named fifo o…
Browse files Browse the repository at this point in the history
…n unix (#38)

Fixed #36 

 - Add dep getrandom v0.2.7 for unix
 - Impl new fn `Client::new_with_fifo`
 - Fix `make-as-a-client` test: Use `Client::configure_make_and_run`
   so that `make` would actually load the jobserver and test it.
 - Impl `Client::configure_and_run_with_fifo`
 - Impl `Client::configure_make_and_run_with_fifo`

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
  • Loading branch information
NobodyXu committed Mar 1, 2023
1 parent 038ff80 commit 4a6e368
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 68 deletions.
10 changes: 6 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ cfg-if = "1.0.0"
tokio = { version = "1", default-features = false, features = ["process"], optional = true }
scopeguard = "1.1.0"

[target.'cfg(unix)'.dependencies]
libc = "0.2.132"

[target.'cfg(windows)'.dependencies]
[target.'cfg(any(unix, windows))'.dependencies]
# Features:
# - std: Implement std-only traits for getrandom::Error
# - rdrand: Enable fallback RDRAND-based implementation on x86/x86_64
getrandom = { version = "0.2.7", features = ["std", "rdrand"] }

[target.'cfg(unix)'.dependencies]
libc = "0.2.132"

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.45.0", features = ["Win32_System_WindowsProgramming", "Win32_System_Threading", "Win32_Foundation", "Win32_Security"] }

[dev-dependencies]
Expand Down
163 changes: 122 additions & 41 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
//! in communication on Unix, which is supported by this crate.
//!
//! However, [`Client::configure_and_run`] and [`Client::configure_make_and_run`]
//! still use the old syntax to keep backwards compatibility.
//! still use the old syntax to keep backwards compatibility with existing
//! programs, e.g. make < 4.4.
//!
//! To create a new fifo on unix, use [`Client::new_with_fifo`] and to use it
//! for spawning process, use [`Client::configure_and_run_with_fifo`] or
//! [`Client::configure_make_and_run_with_fifo`].
//!
//! The jobserver protocol in `make` also dictates when tokens are acquired to
//! run child work, and clients using this crate should take care to implement
Expand Down Expand Up @@ -228,6 +233,28 @@ impl<T: Command> Command for &mut T {
}
}

/// Returns RAII to ensure env_remove is called on unwinding
fn setup_envs<'a, Cmd>(
mut cmd: Cmd,
envs: &'a [&'a str],
value: &ffi::OsStr,
) -> ScopeGuard<Cmd, impl FnOnce(Cmd) + 'a>
where
Cmd: Command,
{
// Setup env
for env in envs {
cmd.env(env, value);
}

// Use RAII to ensure env_remove is called on unwinding
guard(cmd, move |mut cmd| {
for env in envs {
cmd.env_remove(env);
}
})
}

/// A client of a jobserver
///
/// This structure is the main type exposed by this library, and is where
Expand Down Expand Up @@ -270,10 +297,29 @@ impl Client {
///
/// Returns an error if any I/O error happens when attempting to create the
/// jobserver client.
pub fn new(limit: usize) -> io::Result<Client> {
Ok(Client {
inner: Arc::new(imp::Client::new(limit)?),
})
pub fn new(limit: usize) -> io::Result<Self> {
imp::Client::new(limit).map(Self::new_inner)
}

/// Same as [`Client::new`] except that it will create a named fifo on
/// unix so that you can use [`Client::configure_and_run_with_fifo`] or
/// [`Client::configure_make_and_run_with_fifo`] to pass the fifo
/// instead of fds.
pub fn new_with_fifo(limit: usize) -> io::Result<Self> {
#[cfg(unix)]
{
imp::Client::new_fifo(limit).map(Self::new_inner)
}
#[cfg(not(unix))]
{
Self::new(limit)
}
}

fn new_inner(client: imp::Client) -> Self {
Self {
inner: Arc::new(client),
}
}

/// Attempts to connect to the jobserver specified in this process's
Expand Down Expand Up @@ -315,7 +361,7 @@ impl Client {
///
/// Note, though, that on Windows and Unix it should be safe to
/// call this function any number of times.
pub unsafe fn from_env() -> Option<Client> {
pub unsafe fn from_env() -> Option<Self> {
let var = env::var_os("CARGO_MAKEFLAGS")
.or_else(|| env::var_os("MAKEFLAGS"))
.or_else(|| env::var_os("MFLAGS"))?;
Expand Down Expand Up @@ -389,31 +435,9 @@ impl Client {
self.configure_and_run_inner(cmd, f, &["CARGO_MAKEFLAGS"])
}

/// Configures a child process to have access to this client's jobserver as
/// well and run the `f` which spawns the process.
///
/// NOTE that you have to spawn the process inside `f`, otherwise the jobserver
/// would not be inherited.
///
/// This function is required to be called to ensure that a jobserver is
/// properly inherited to a child process. If this function is *not* called
/// then this `Client` will not be accessible in the child process. In other
/// words, if not called, then `Client::from_env` will return `None` in the
/// child process (or the equivalent of `Child::from_env` that `make` uses).
///
/// ## Environment variables
///
/// This function sets up `CARGO_MAKEFLAGS`, `MAKEFLAGS` and `MFLAGS`,
/// which is used by `cargo` and `make`.
///
/// ## Platform-specific behavior
///
/// On Unix and Windows this will clobber the `CARGO_MAKEFLAGS`,
/// `MAKEFLAGS` and `MFLAGS` environment variables for the child process,
/// and on Unix this will also allow the two file descriptors for
/// this client to be inherited to the child.
///
/// On platforms other than Unix and Windows this panics.
/// Same as [`Client::configure_and_run`] except that it sets up environment
/// variables `CARGO_MAKEFLAGS`, `MAKEFLAGS` and `MFLAGS`, which is used by
/// `cargo` and `make`.
pub fn configure_make_and_run<Cmd, F, R>(&self, cmd: Cmd, f: F) -> io::Result<R>
where
Cmd: Command,
Expand All @@ -437,19 +461,76 @@ impl Client {
// both implementations.
let value = format!("-j --jobserver-fds={0} --jobserver-auth={0}", arg);

// Setup env
for env in envs {
cmd.env(env, &value);
}
let mut cmd = setup_envs(cmd, envs, ffi::OsStr::new(&value));

f(&mut cmd)
}

// Use RAII to ensure env_remove is called on unwinding
let mut cmd = guard(cmd, |mut cmd| {
for env in envs {
cmd.env_remove(env);
/// Same as [`Client::configure_and_run`] except that it tries to pass
/// `--jobserver-auth=fifo:/path/to/fifo` to pass path to fifo instead of
/// fds and it does not pass `--jobserver-fds=r,w` on unix and will
/// fallback to [`Client::configure_and_run`] if the client is not
/// created using [`Client::new_with_fifo`] or [`Client::from_env`]
/// with `CARGO_MAKEFLAGS`/`MAKEFLAGS`/`MFLAGS` containing
/// `--jobserver-auth=fifo:/path/to/fifo`.
///
/// On windows, [`Client`] always uses a named semaphore and on wasm,
/// such API is not supported since spawning processes is not supported
/// by wasm yet.
///
/// Using this function will break backwards compatibility for
/// some programs, e.g. make < `4.4`.
///
/// Using this method does provide better performance since it doesn't need
/// to call [`Command::pre_run`] to register a callback to run in the new
/// process and thus can use vfork + exec for better performance.
pub fn configure_and_run_with_fifo<Cmd, F, R>(&self, cmd: Cmd, f: F) -> io::Result<R>
where
Cmd: Command,
F: FnOnce(&mut Cmd) -> io::Result<R>,
{
self.configure_and_run_with_fifo_inner(cmd, f, &["CARGO_MAKEFLAGS"])
}

/// Same as [`Client::configure_and_run_with_fifo`] except that it sets up
/// environment variables `CARGO_MAKEFLAGS`, `MAKEFLAGS` and `MFLAGS`,
/// which is used by `cargo` and `make`.
pub fn configure_make_and_run_with_fifo<Cmd, F, R>(&self, cmd: Cmd, f: F) -> io::Result<R>
where
Cmd: Command,
F: FnOnce(&mut Cmd) -> io::Result<R>,
{
self.configure_and_run_with_fifo_inner(cmd, f, &["CARGO_MAKEFLAGS", "MAKEFLAGS", "MFLAGS"])
}

fn configure_and_run_with_fifo_inner<Cmd, F, R>(
&self,
cmd: Cmd,
f: F,
envs: &[&str],
) -> io::Result<R>
where
Cmd: Command,
F: FnOnce(&mut Cmd) -> io::Result<R>,
{
#[cfg(unix)]
{
if let Some(path) = self.inner.get_fifo() {
let path = path.as_os_str();

let prefix = "-j --jobserver-auth=";

let mut value = ffi::OsString::with_capacity(prefix.len() + path.len());
value.push(prefix);
value.push(path);

let mut cmd = setup_envs(cmd, envs, &value);

return f(&mut cmd);
}
});
}

f(&mut cmd)
self.configure_and_run_inner(cmd, f, envs)
}

/// Converts this `Client` into a helper thread to deal with a blocking
Expand Down
Loading

0 comments on commit 4a6e368

Please sign in to comment.