Skip to main content

lilo_rm_core/
launcher.rs

1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{RuntimeKind, SpawnRequest};
6
7#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
8pub struct LaunchEnv {
9    pub key: String,
10    pub value: String,
11}
12
13impl LaunchEnv {
14    pub fn new(key: impl Into<String>, value: impl Into<String>) -> Self {
15        Self {
16            key: key.into(),
17            value: value.into(),
18        }
19    }
20}
21
22pub fn upsert_launch_env(env: &mut Vec<LaunchEnv>, next: LaunchEnv) {
23    if let Some(existing) = env.iter_mut().find(|entry| entry.key == next.key) {
24        *existing = next;
25    } else {
26        env.push(next);
27    }
28}
29
30#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
31pub struct LaunchSpec {
32    pub argv: Vec<String>,
33    pub env: Vec<LaunchEnv>,
34    pub cwd: PathBuf,
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    pub shell_resume: Option<ShellResume>,
37}
38
39impl LaunchSpec {
40    pub fn command(&self) -> Result<&str, LauncherError> {
41        self.argv
42            .first()
43            .map(String::as_str)
44            .ok_or(LauncherError::EmptyArgv)
45    }
46}
47
48#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
49pub struct ShellResume {
50    pub argv: Vec<String>,
51    pub env: Vec<LaunchEnv>,
52    pub cwd: PathBuf,
53}
54
55impl ShellResume {
56    pub fn command(&self) -> Result<&str, LauncherError> {
57        self.argv
58            .first()
59            .map(String::as_str)
60            .ok_or(LauncherError::EmptyShellArgv)
61    }
62}
63
64pub trait RuntimeLauncher: Sync {
65    fn kind(&self) -> RuntimeKind;
66
67    fn argv(&self, request: &SpawnRequest) -> Result<Vec<String>, LauncherError>;
68
69    fn env(&self, request: &SpawnRequest) -> Result<Vec<LaunchEnv>, LauncherError>;
70
71    fn cwd(&self, request: &SpawnRequest) -> Result<PathBuf, LauncherError> {
72        Ok(request.cwd.clone())
73    }
74
75    fn launch_spec(&self, request: &SpawnRequest) -> Result<LaunchSpec, LauncherError> {
76        let spec = LaunchSpec {
77            argv: self.argv(request)?,
78            env: self.env(request)?,
79            cwd: self.cwd(request)?,
80            shell_resume: request.shell_resume.clone(),
81        };
82        if spec.argv.is_empty() {
83            return Err(LauncherError::EmptyArgv);
84        }
85        if spec.env.is_empty() {
86            return Err(LauncherError::EmptyEnv {
87                runtime_kind: self.kind().to_string(),
88            });
89        }
90        Ok(spec)
91    }
92}
93
94#[derive(Clone, Debug, thiserror::Error)]
95pub enum LauncherError {
96    #[error("no launcher registered for runtime kind: {runtime_kind}")]
97    NoLauncher { runtime_kind: String },
98    #[error("launcher produced empty argv")]
99    EmptyArgv,
100    #[error("launcher produced empty shell resume argv")]
101    EmptyShellArgv,
102    #[error("launcher {runtime_kind} produced empty env")]
103    EmptyEnv { runtime_kind: String },
104    #[error("failed to resolve launcher binary {binary}: {message}")]
105    BinaryLookupFailed { binary: String, message: String },
106}