Skip to main content

oxdock_process/
contract.rs

1use std::collections::HashMap;
2
3use anyhow::Result;
4use oxdock_fs::{GuardedPath, PolicyPath};
5#[allow(clippy::disallowed_types, clippy::disallowed_methods)]
6use std::process::ExitStatus;
7
8use std::sync::{Arc, Mutex};
9
10/// Context passed to process managers describing the current execution
11/// environment. Clones are cheap and explicit so background handles can own
12/// their working roots without juggling lifetimes.
13#[derive(Clone, Debug)]
14pub struct CommandContext {
15    cwd: PolicyPath,
16    envs: Arc<HashMap<String, String>>,
17    cargo_target_dir: GuardedPath,
18    workspace_root: GuardedPath,
19    build_context: GuardedPath,
20}
21
22impl CommandContext {
23    pub fn new(
24        cwd: &PolicyPath,
25        envs: Arc<HashMap<String, String>>,
26        cargo_target_dir: &GuardedPath,
27        workspace_root: &GuardedPath,
28        build_context: &GuardedPath,
29    ) -> Self {
30        Self {
31            cwd: cwd.clone(),
32            envs,
33            cargo_target_dir: cargo_target_dir.clone(),
34            workspace_root: workspace_root.clone(),
35            build_context: build_context.clone(),
36        }
37    }
38
39    /// Convenience constructor cloning a plain map into a fresh `Arc`.
40    pub fn from_map(
41        cwd: &PolicyPath,
42        envs: &HashMap<String, String>,
43        cargo_target_dir: &GuardedPath,
44        workspace_root: &GuardedPath,
45        build_context: &GuardedPath,
46    ) -> Self {
47        Self::new(
48            cwd,
49            Arc::new(envs.clone()),
50            cargo_target_dir,
51            workspace_root,
52            build_context,
53        )
54    }
55
56    pub fn cwd(&self) -> &PolicyPath {
57        &self.cwd
58    }
59
60    pub fn envs(&self) -> &Arc<HashMap<String, String>> {
61        &self.envs
62    }
63
64    pub fn cargo_target_dir(&self) -> &GuardedPath {
65        &self.cargo_target_dir
66    }
67
68    pub fn workspace_root(&self) -> &GuardedPath {
69        &self.workspace_root
70    }
71
72    pub fn build_context(&self) -> &GuardedPath {
73        &self.build_context
74    }
75}
76
77/// Handle for background processes spawned by a [`ProcessManager`].
78pub trait BackgroundHandle: Send {
79    fn try_wait(&mut self) -> Result<Option<ExitStatus>>;
80    fn kill(&mut self) -> Result<()>;
81    fn wait(&mut self) -> Result<ExitStatus>;
82}
83
84pub type SharedInput = Arc<Mutex<dyn std::io::Read + Send>>;
85pub type SharedOutput = Arc<Mutex<dyn std::io::Write + Send>>;
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
88pub enum CommandMode {
89    #[default]
90    Foreground,
91    Background,
92}
93
94#[derive(Clone, Default)]
95pub enum CommandStdout {
96    #[default]
97    Inherit,
98    Stream(SharedOutput),
99    Capture,
100}
101
102#[derive(Clone, Default)]
103pub enum CommandStderr {
104    #[default]
105    Inherit,
106    Stream(SharedOutput),
107}
108
109#[derive(Clone, Default)]
110pub struct CommandOptions {
111    pub mode: CommandMode,
112    pub stdin: Option<SharedInput>,
113    pub stdout: CommandStdout,
114    pub stderr: CommandStderr,
115}
116
117impl CommandOptions {
118    pub fn foreground() -> Self {
119        Self::default()
120    }
121
122    pub fn background() -> Self {
123        Self {
124            mode: CommandMode::Background,
125            ..Self::default()
126        }
127    }
128}
129
130pub enum CommandResult<H> {
131    Completed,
132    Captured(Vec<u8>),
133    Background(H),
134}
135
136/// Abstraction for running shell commands both in the foreground and
137/// background. `oxdock-core` relies on this trait to decouple the executor
138/// from `std::process::Command`, which in turn enables Miri-friendly test
139/// doubles.
140pub trait ProcessManager: Clone + Send + 'static {
141    type Handle: BackgroundHandle + Clone + Send + 'static;
142
143    fn run_command(
144        &mut self,
145        ctx: &CommandContext,
146        script: &str,
147        options: CommandOptions,
148    ) -> Result<CommandResult<Self::Handle>>;
149
150    /// Spawn a command without waiting for completion. Returns a background
151    /// handle that can be polled or waited on later. The default implementation
152    /// delegates to `run_command` with `CommandMode::Background`.
153    fn spawn_command(
154        &mut self,
155        ctx: &CommandContext,
156        script: &str,
157        options: CommandOptions,
158    ) -> Result<CommandResult<Self::Handle>> {
159        self.run_command(ctx, script, options)
160    }
161}