///|
/// Spawn a child process within a task group, with streaming I/O access.
///
/// Returns a `ChildProcess` handle with optional stdin writer,
/// stdout reader, and stderr reader for streaming communication.
///
/// The `pipe_stdin`, `pipe_stdout`, `pipe_stderr` parameters control
/// whether pipes are created for each stream.
pub async fn[X] spawn(
group : @async.TaskGroup[X],
command : String,
args? : Array[String] = [],
cwd? : String,
env? : Map[String, String],
inherit_env? : Bool,
pipe_stdin? : Bool = false,
pipe_stdout? : Bool = false,
pipe_stderr? : Bool = false,
cancel_handler? : @process.CancellationHandler,
) -> ChildProcess raise SubprocessError {
let env = env.unwrap_or(Map::new())
let do_inherit = inherit_env.unwrap_or(true)
let cmd : StringView = command
// Set up pipes
let (stdin_for_process, stdin_writer) = if pipe_stdin {
let (input, writer) = @process.write_to_process() catch {
e => raise SpawnFailed("Failed to create stdin pipe: " + e.to_string())
}
(Some(input), Some(writer))
} else {
(None, None)
}
let (stdout_reader, stdout_for_process) = if pipe_stdout {
let (reader, output) = @process.read_from_process() catch {
e => raise SpawnFailed("Failed to create stdout pipe: " + e.to_string())
}
(Some(reader), Some(output))
} else {
(None, None)
}
let (stderr_reader, stderr_for_process) = if pipe_stderr {
let (reader, output) = @process.read_from_process() catch {
e => raise SpawnFailed("Failed to create stderr pipe: " + e.to_string())
}
(Some(reader), Some(output))
} else {
(None, None)
}
// Build spawn call
let cwd_view : StringView? = match cwd {
Some(s) => Some(s)
None => None
}
let process = spawn_with_options(
group,
cmd,
args[:],
cwd=cwd_view,
env~,
do_inherit~,
stdin=stdin_for_process,
stdout=stdout_for_process,
stderr=stderr_for_process,
cancel_handler?,
) catch {
e => raise SpawnFailed("Failed to spawn process: " + e.to_string())
}
{ process, stdin: stdin_writer, stdout: stdout_reader, stderr: stderr_reader }
}
///|
/// Spawn a shell command within a task group, with streaming I/O access.
/// On Unix, the command is run through `/bin/sh -c`.
/// On Windows, the command is run through `cmd.exe /c`.
pub async fn[X] spawn_shell(
group : @async.TaskGroup[X],
command : String,
cwd? : String,
env? : Map[String, String],
inherit_env? : Bool,
pipe_stdin? : Bool = false,
pipe_stdout? : Bool = false,
pipe_stderr? : Bool = false,
cancel_handler? : @process.CancellationHandler,
) -> ChildProcess raise SubprocessError {
let (shell, shell_args) = shell_command(command)
spawn(
group,
shell,
args=shell_args,
cwd?,
env?,
inherit_env?,
pipe_stdin~,
pipe_stdout~,
pipe_stderr~,
cancel_handler?,
)
}
///|
async fn[X] spawn_with_options(
group : @async.TaskGroup[X],
cmd : StringView,
args : ArrayView[String],
cwd~ : StringView?,
env~ : Map[String, String],
do_inherit~ : Bool,
stdin~ : &@process.ProcessInput?,
stdout~ : &@process.ProcessOutput?,
stderr~ : &@process.ProcessOutput?,
cancel_handler? : @process.CancellationHandler,
) -> @process.Process {
match (cwd, stdin, stdout, stderr, cancel_handler) {
(Some(c), Some(si), Some(so), Some(se), Some(ch)) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stdin=si,
stdout=so,
stderr=se,
cwd=c,
cancel_handler=ch,
)
(Some(c), Some(si), Some(so), Some(se), None) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stdin=si,
stdout=so,
stderr=se,
cwd=c,
)
(Some(c), Some(si), Some(so), None, _) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stdin=si,
stdout=so,
cwd=c,
)
(Some(c), Some(si), None, Some(se), _) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stdin=si,
stderr=se,
cwd=c,
)
(Some(c), None, Some(so), Some(se), _) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stdout=so,
stderr=se,
cwd=c,
)
(Some(c), Some(si), None, None, _) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stdin=si,
cwd=c,
)
(Some(c), None, Some(so), None, _) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stdout=so,
cwd=c,
)
(Some(c), None, None, Some(se), _) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stderr=se,
cwd=c,
)
(Some(c), None, None, None, _) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
cwd=c,
)
(None, Some(si), Some(so), Some(se), Some(ch)) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stdin=si,
stdout=so,
stderr=se,
cancel_handler=ch,
)
(None, Some(si), Some(so), Some(se), None) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stdin=si,
stdout=so,
stderr=se,
)
(None, Some(si), Some(so), None, _) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stdin=si,
stdout=so,
)
(None, Some(si), None, Some(se), _) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stdin=si,
stderr=se,
)
(None, None, Some(so), Some(se), _) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stdout=so,
stderr=se,
)
(None, Some(si), None, None, _) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stdin=si,
)
(None, None, Some(so), None, _) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stdout=so,
)
(None, None, None, Some(se), _) =>
@process.spawn(
group,
cmd,
args,
extra_env=env,
inherit_env=do_inherit,
stderr=se,
)
(None, None, None, None, _) =>
@process.spawn(group, cmd, args, extra_env=env, inherit_env=do_inherit)
}
}
///|
/// Wait for the child process to terminate and return its exit code.
pub async fn ChildProcess::wait(self : ChildProcess) -> Int {
self.process.wait()
}
///|
/// Try to get the exit code without blocking.
/// Returns `Some(exit_code)` if the process has terminated, `None` if still running.
pub fn ChildProcess::try_wait(self : ChildProcess) -> Int? raise {
self.process.try_wait()
}
///|
/// Cancel the child process using its cancellation handler.
pub fn ChildProcess::cancel(self : ChildProcess) -> Unit {
self.process.cancel()
}
///|
/// Get the process ID of the child process.
pub fn ChildProcess::pid(self : ChildProcess) -> Int {
self.process.pid
}
///|
/// Close the stdin writer, signaling EOF to the child process.
pub fn ChildProcess::close_stdin(self : ChildProcess) -> Unit {
match self.stdin {
Some(writer) => writer.close()
None => ()
}
}