use std::{collections::BTreeMap, ffi::OsString, process::Stdio};
use tokio::io::{AsyncRead, AsyncReadExt};
use tokio_util::sync::CancellationToken;
use crate::launch::{Finished, Launch, LaunchError};
pub(crate) async fn run(
launch: Launch,
cancellation: &CancellationToken,
) -> Result<Finished, LaunchError> {
let mut command = tokio::process::Command::new(&launch.program);
command
.args(&launch.args)
.current_dir(&launch.cwd)
.env_clear()
.envs(passed_environment())
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.kill_on_drop(true);
let mut tree =
super::spawn_tree(&mut command).map_err(|error| LaunchError::Failed(error.to_string()))?;
let stdout = tree.child.stdout.take().expect("piped stdout");
let stderr = tree.child.stderr.take().expect("piped stderr");
let limit = launch.output_limit;
let execution = async {
let (stdout, stderr, status) = tokio::try_join!(
capture(stdout, limit),
capture(stderr, limit),
tree.child.wait()
)
.map_err(|error| LaunchError::Failed(error.to_string()))?;
Ok(Finished {
status,
truncated: stdout.len() > limit || stderr.len() > limit,
stdout,
stderr,
})
};
let result = tokio::select! {
_ = cancellation.cancelled() => Err(LaunchError::Cancelled),
result = tokio::time::timeout(launch.timeout, execution) => {
result.unwrap_or(Err(LaunchError::TimedOut))
}
};
tree.kill();
let _ = tree.child.wait().await;
result
}
async fn capture(reader: impl AsyncRead + Unpin, limit: usize) -> std::io::Result<Vec<u8>> {
let mut bytes = Vec::new();
reader
.take(limit as u64 + 1)
.read_to_end(&mut bytes)
.await?;
Ok(bytes)
}
pub(crate) fn passed_environment() -> BTreeMap<OsString, OsString> {
std::env::vars_os()
.filter(|(name, _)| {
!name
.to_str()
.is_some_and(|name| name.to_ascii_uppercase().starts_with("ARTIFACTIZE_"))
})
.collect()
}