forge_engine/exec/
backend.rs1use std::path::Path;
2
3use async_trait::async_trait;
4
5use crate::config::ForgeConfig;
6use crate::error::{ForgeError, ForgeResult};
7
8pub use check_runner::{
9 CheckCommand, CheckKind, CheckResult, CommandOutput, CommandTimings, ExecutionBackendKind,
10 LogBundle, ParsedCheckOutput,
11};
12pub use effect_signature::{EffectSignature, LocatedEffect};
13pub use sandbox_workspace::{PatchedWorkspace, Workspace};
14
15#[async_trait]
17pub trait ExecutionBackend: Send + Sync {
18 fn kind(&self) -> ExecutionBackendKind;
19
20 async fn prepare_workspace(&self, fixture: &Path) -> ForgeResult<Workspace>;
21
22 async fn run_command(
23 &self,
24 workspace: &Path,
25 program: &str,
26 args: &[&str],
27 env: &[(&str, &str)],
28 timeout_secs: u64,
29 ) -> ForgeResult<CommandOutput>;
30
31 async fn collect_logs(
32 &self,
33 fmt: &CommandOutput,
34 clippy: &CommandOutput,
35 test: &CommandOutput,
36 ) -> ForgeResult<LogBundle>;
37}
38
39pub(crate) fn runner_config(config: &ForgeConfig) -> check_runner::BackendConfig {
40 check_runner::BackendConfig {
41 mode: config.mode.clone(),
42 execution_backend_preference: config.execution_backend_preference.clone(),
43 container_runtime_preference: config.container_runtime_preference.clone(),
44 sealed_allow_host_backend: config.sealed_allow_host_backend,
45 rust_image: config.container.rust_image.clone(),
46 command_timeout_secs: config.container.command_timeout_secs,
47 memory_limit: config.container.memory_limit.clone(),
48 cpu_limit: config.container.cpu_limit.clone(),
49 }
50}
51
52pub fn select_backend(config: &ForgeConfig) -> ForgeResult<Box<dyn ExecutionBackend>> {
60 let sealed = config.mode == "sealed_local";
61
62 match config.execution_backend_preference.as_str() {
63 "host" => {
64 if sealed && !config.sealed_allow_host_backend {
65 return Err(ForgeError::SealedModeUnsupported {
66 runtime: "host backend requested in sealed_local mode".into(),
67 });
68 }
69 if sealed {
70 tracing::warn!("sealed_local mode with host backend — no network isolation");
71 }
72 Ok(Box::new(crate::exec::host::HostBackend::new(config)))
73 }
74 "container" => crate::exec::container::ContainerBackend::new(config)
75 .map(|backend| Box::new(backend) as Box<dyn ExecutionBackend>)
76 .map_err(|error| {
77 if sealed {
78 ForgeError::SealedModeUnsupported {
79 runtime: format!("container unavailable in sealed mode: {error}"),
80 }
81 } else {
82 error
83 }
84 }),
85 _ => match crate::exec::container::ContainerBackend::new(config) {
86 Ok(backend) => Ok(Box::new(backend)),
87 Err(_) => {
88 if sealed && !config.sealed_allow_host_backend {
89 return Err(ForgeError::SealedModeUnsupported {
90 runtime:
91 "container unavailable and host fallback not allowed in sealed mode"
92 .into(),
93 });
94 }
95 if sealed {
96 tracing::warn!(
97 "sealed_local: container unavailable, falling back to host — isolation not guaranteed"
98 );
99 }
100 Ok(Box::new(crate::exec::host::HostBackend::new(config)))
101 }
102 },
103 }
104}