Skip to main content

acp_agent/runtime/
process.rs

1use std::process::{ExitStatus, Stdio};
2
3use anyhow::{Context, Result};
4use tokio::process::{Child, Command};
5
6use crate::runtime::prepare::CommandSpec;
7
8/// Applies a `CommandSpec` to a `tokio::process::Command` without spawning it.
9///
10/// This configures the argument list, working directory (if set), and environment
11/// overrides so that callers can pipe stdio or set up transports before
12/// launching the process.
13pub fn apply_command_spec(command: &mut Command, spec: &CommandSpec) {
14    command.args(&spec.args);
15
16    if let Some(current_dir) = &spec.current_dir {
17        command.current_dir(current_dir);
18    }
19
20    for (key, value) in &spec.env {
21        command.env(key, value);
22    }
23}
24
25/// Spawns a child process with stdin/stdout piped and stderr inherited.
26///
27/// The `subject` is used solely for contextual error messages when spawning
28/// fails. The caller is responsible for wiring the pipes before awaiting the
29/// child.
30pub fn spawn_stream_child(spec: &CommandSpec, subject: &str) -> Result<Child> {
31    let program_display = spec.program.to_string_lossy().into_owned();
32    let mut command = Command::new(&spec.program);
33    apply_command_spec(&mut command, spec);
34    command
35        .stdin(Stdio::piped())
36        .stdout(Stdio::piped())
37        .stderr(Stdio::inherit());
38
39    command
40        .spawn()
41        .with_context(|| format!("failed to spawn {program_display} for {subject}"))
42}
43
44/// Terminates a child process, returning its exit status.
45///
46/// First attempts a non-blocking `try_wait`; if the process is still running
47/// it first sends `kill` and then waits for shutdown, surfacing any errors
48/// from the runtime.
49pub async fn terminate_child(child: &mut Child) -> Result<ExitStatus> {
50    if let Some(status) = child
51        .try_wait()
52        .context("failed while checking child process status")?
53    {
54        return Ok(status);
55    }
56
57    child
58        .kill()
59        .await
60        .context("failed to terminate child process")?;
61    child
62        .wait()
63        .await
64        .context("failed while waiting on child process")
65}