use std::collections::HashMap;
use anyhow::{Result, bail};
use oxdock_fs::{GuardedPath, PolicyPath};
#[allow(clippy::disallowed_types, clippy::disallowed_methods)]
use std::process::ExitStatus;
use std::sync::{Arc, Mutex};
#[derive(Clone, Debug)]
pub struct CommandContext {
cwd: PolicyPath,
envs: Arc<HashMap<String, String>>,
cargo_target_dir: GuardedPath,
workspace_root: GuardedPath,
build_context: GuardedPath,
}
impl CommandContext {
pub fn new(
cwd: &PolicyPath,
envs: Arc<HashMap<String, String>>,
cargo_target_dir: &GuardedPath,
workspace_root: &GuardedPath,
build_context: &GuardedPath,
) -> Self {
Self {
cwd: cwd.clone(),
envs,
cargo_target_dir: cargo_target_dir.clone(),
workspace_root: workspace_root.clone(),
build_context: build_context.clone(),
}
}
pub fn from_map(
cwd: &PolicyPath,
envs: &HashMap<String, String>,
cargo_target_dir: &GuardedPath,
workspace_root: &GuardedPath,
build_context: &GuardedPath,
) -> Self {
Self::new(
cwd,
Arc::new(envs.clone()),
cargo_target_dir,
workspace_root,
build_context,
)
}
pub fn cwd(&self) -> &PolicyPath {
&self.cwd
}
pub fn envs(&self) -> &Arc<HashMap<String, String>> {
&self.envs
}
pub fn cargo_target_dir(&self) -> &GuardedPath {
&self.cargo_target_dir
}
pub fn workspace_root(&self) -> &GuardedPath {
&self.workspace_root
}
pub fn build_context(&self) -> &GuardedPath {
&self.build_context
}
}
pub trait BackgroundHandle: Send {
fn try_wait(&mut self) -> Result<Option<ExitStatus>>;
fn kill(&mut self) -> Result<()>;
fn wait(&mut self) -> Result<ExitStatus>;
}
pub type SharedInput = Arc<Mutex<dyn std::io::Read + Send>>;
pub type SharedOutput = Arc<Mutex<dyn std::io::Write + Send>>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum CommandMode {
#[default]
Foreground,
Background,
}
#[derive(Clone, Default)]
pub enum CommandStdout {
#[default]
Inherit,
Stream(SharedOutput),
Capture,
#[cfg(not(miri))]
OsPipe(OsPipeWriter),
}
#[cfg(not(miri))]
#[derive(Clone)]
pub struct OsPipeReader {
inner: Arc<Mutex<Option<std::io::PipeReader>>>,
}
#[cfg(not(miri))]
#[derive(Clone)]
pub struct OsPipeWriter {
inner: Arc<Mutex<Option<std::io::PipeWriter>>>,
}
#[cfg(not(miri))]
impl OsPipeReader {
fn new(reader: std::io::PipeReader) -> Self {
Self {
inner: Arc::new(Mutex::new(Some(reader))),
}
}
pub fn take(&self) -> Result<std::io::PipeReader> {
self.inner
.lock()
.map_err(|_| anyhow::anyhow!("os pipe reader lock poisoned"))?
.take()
.ok_or_else(|| {
anyhow::anyhow!("os pipe handle has already been consumed by another process")
})
}
}
#[cfg(not(miri))]
impl OsPipeWriter {
fn new(writer: std::io::PipeWriter) -> Self {
Self {
inner: Arc::new(Mutex::new(Some(writer))),
}
}
pub fn take(&self) -> Result<std::io::PipeWriter> {
self.inner
.lock()
.map_err(|_| anyhow::anyhow!("os pipe writer lock poisoned"))?
.take()
.ok_or_else(|| {
anyhow::anyhow!("os pipe handle has already been consumed by another process")
})
}
}
#[cfg(not(miri))]
pub fn create_os_pipe() -> Result<(OsPipeReader, OsPipeWriter)> {
let (reader, writer) = std::io::pipe()?;
Ok((OsPipeReader::new(reader), OsPipeWriter::new(writer)))
}
#[derive(Clone, Default)]
pub enum CommandStdin {
#[default]
Null,
Inherit,
Stream(SharedInput),
#[cfg(not(miri))]
OsPipe(OsPipeReader),
}
impl From<Option<SharedInput>> for CommandStdin {
fn from(stdin: Option<SharedInput>) -> Self {
match stdin {
Some(reader) => CommandStdin::Stream(reader),
None => CommandStdin::Null,
}
}
}
#[derive(Clone, Default)]
pub enum CommandStderr {
#[default]
Inherit,
Stream(SharedOutput),
#[cfg(not(miri))]
OsPipe(OsPipeWriter),
}
#[derive(Clone, Default)]
pub struct CommandOptions {
pub mode: CommandMode,
pub stdin: CommandStdin,
pub stdout: CommandStdout,
pub stderr: CommandStderr,
}
impl CommandOptions {
pub fn foreground() -> Self {
Self::default()
}
pub fn background() -> Self {
Self {
mode: CommandMode::Background,
..Self::default()
}
}
}
pub enum CommandResult<H> {
Completed,
Captured(Vec<u8>),
Background(H),
}
pub const INHERIT_STDOUT_ENV_VAR: &str = "OXDOCK_INHERIT_STDOUT";
pub const PROCESS_DEBUG_ENV_VAR: &str = "OXBOOK_DEBUG";
pub trait ProcessManager: Clone + Send + 'static {
type Handle: BackgroundHandle + Clone + Send + 'static;
fn run_command(
&mut self,
ctx: &CommandContext,
script: &str,
options: CommandOptions,
) -> Result<CommandResult<Self::Handle>>;
fn spawn_command(
&mut self,
ctx: &CommandContext,
script: &str,
options: CommandOptions,
) -> Result<CommandResult<Self::Handle>> {
self.run_command(ctx, script, options)
}
fn run_argv(
&mut self,
_ctx: &CommandContext,
argv: &[String],
_options: CommandOptions,
) -> Result<CommandResult<Self::Handle>> {
bail!("run_argv not implemented for argv {argv:?}")
}
fn spawn_argv(
&mut self,
ctx: &CommandContext,
argv: &[String],
options: CommandOptions,
) -> Result<CommandResult<Self::Handle>> {
self.run_argv(ctx, argv, options)
}
}