xtask_todo_lib/devshell/vm/
mod.rs1#![allow(clippy::pedantic, clippy::nursery)]
4
5use std::cell::RefCell;
6use std::io::Write;
7use std::rc::Rc;
8
9mod config;
10#[cfg(unix)]
11mod lima_diagnostics;
12#[cfg(all(unix, feature = "beta-vm"))]
13mod session_beta;
14#[cfg(unix)]
15mod session_gamma;
16mod session_host;
17pub mod sync;
18
19pub use config::{
20 VmConfig, ENV_DEVSHELL_VM, ENV_DEVSHELL_VM_BACKEND, ENV_DEVSHELL_VM_EAGER,
21 ENV_DEVSHELL_VM_LIMA_INSTANCE, ENV_DEVSHELL_VM_SOCKET,
22};
23#[cfg(unix)]
24pub use lima_diagnostics::ENV_DEVSHELL_VM_LIMA_HINTS;
25#[cfg(unix)]
26pub use session_gamma::{
27 GammaSession, ENV_DEVSHELL_VM_GUEST_WORKSPACE, ENV_DEVSHELL_VM_LIMACTL,
28 ENV_DEVSHELL_VM_STOP_ON_EXIT, ENV_DEVSHELL_VM_WORKSPACE_PARENT,
29};
30pub use session_host::HostSandboxSession;
31pub use sync::{pull_workspace_to_vfs, push_full, push_incremental, VmSyncError};
32
33use std::process::ExitStatus;
34
35use super::sandbox;
36use super::vfs::Vfs;
37
38#[derive(Debug)]
40pub enum VmError {
41 Sandbox(sandbox::SandboxError),
42 Sync(VmSyncError),
43 BackendNotImplemented(&'static str),
45 Lima(String),
47 Ipc(String),
49}
50
51impl std::fmt::Display for VmError {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 match self {
54 Self::Sandbox(e) => write!(f, "{e}"),
55 Self::Sync(e) => write!(f, "{e}"),
56 Self::BackendNotImplemented(s) => write!(f, "vm backend not implemented: {s}"),
57 Self::Lima(s) => f.write_str(s),
58 Self::Ipc(s) => f.write_str(s),
59 }
60 }
61}
62
63impl std::error::Error for VmError {
64 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
65 match self {
66 Self::Sandbox(e) => Some(e),
67 Self::Sync(e) => Some(e),
68 Self::BackendNotImplemented(_) | Self::Lima(_) | Self::Ipc(_) => None,
69 }
70 }
71}
72
73pub trait VmExecutionSession {
75 fn ensure_ready(&mut self, vfs: &Vfs, vfs_cwd: &str) -> Result<(), VmError>;
77
78 fn run_rust_tool(
80 &mut self,
81 vfs: &mut Vfs,
82 vfs_cwd: &str,
83 program: &str,
84 args: &[String],
85 ) -> Result<ExitStatus, VmError>;
86
87 fn shutdown(&mut self, vfs: &mut Vfs, vfs_cwd: &str) -> Result<(), VmError>;
89}
90
91#[derive(Debug)]
93pub enum SessionHolder {
94 Host(HostSandboxSession),
95 #[cfg(unix)]
97 Gamma(GammaSession),
98 #[cfg(all(unix, feature = "beta-vm"))]
100 Beta(session_beta::BetaSession),
101}
102
103impl SessionHolder {
104 pub fn try_from_config(config: &VmConfig) -> Result<Self, VmError> {
110 if !config.enabled {
111 return Ok(Self::Host(HostSandboxSession::new()));
112 }
113 if config.use_host_sandbox() {
114 return Ok(Self::Host(HostSandboxSession::new()));
115 }
116 #[cfg(all(unix, feature = "beta-vm"))]
117 if config.backend.eq_ignore_ascii_case("beta") {
118 return session_beta::BetaSession::new(config).map(SessionHolder::Beta);
119 }
120 #[cfg(not(all(unix, feature = "beta-vm")))]
121 if config.backend.eq_ignore_ascii_case("beta") {
122 return Err(VmError::BackendNotImplemented(
123 "DEVSHELL_VM_BACKEND=beta requires Unix and building xtask-todo-lib with `--features beta-vm`",
124 ));
125 }
126 #[cfg(unix)]
127 if config.backend.eq_ignore_ascii_case("lima") {
128 return GammaSession::new(config).map(SessionHolder::Gamma);
129 }
130 #[cfg(not(unix))]
131 if config.backend.eq_ignore_ascii_case("lima") {
132 return Err(VmError::BackendNotImplemented(
133 "lima backend is only supported on Linux and macOS",
134 ));
135 }
136 Err(VmError::BackendNotImplemented(
137 "unknown DEVSHELL_VM_BACKEND (try host, auto, lima, or beta); see docs/devshell-vm-gamma.md",
138 ))
139 }
140
141 #[must_use]
143 pub fn new_host() -> Self {
144 Self::Host(HostSandboxSession::new())
145 }
146
147 pub fn ensure_ready(&mut self, vfs: &Vfs, vfs_cwd: &str) -> Result<(), VmError> {
148 match self {
149 Self::Host(s) => VmExecutionSession::ensure_ready(s, vfs, vfs_cwd),
150 #[cfg(unix)]
151 Self::Gamma(s) => VmExecutionSession::ensure_ready(s, vfs, vfs_cwd),
152 #[cfg(all(unix, feature = "beta-vm"))]
153 Self::Beta(s) => VmExecutionSession::ensure_ready(s, vfs, vfs_cwd),
154 }
155 }
156
157 pub fn run_rust_tool(
158 &mut self,
159 vfs: &mut Vfs,
160 vfs_cwd: &str,
161 program: &str,
162 args: &[String],
163 ) -> Result<ExitStatus, VmError> {
164 match self {
165 Self::Host(s) => VmExecutionSession::run_rust_tool(s, vfs, vfs_cwd, program, args),
166 #[cfg(unix)]
167 Self::Gamma(s) => VmExecutionSession::run_rust_tool(s, vfs, vfs_cwd, program, args),
168 #[cfg(all(unix, feature = "beta-vm"))]
169 Self::Beta(s) => VmExecutionSession::run_rust_tool(s, vfs, vfs_cwd, program, args),
170 }
171 }
172
173 pub fn shutdown(&mut self, vfs: &mut Vfs, vfs_cwd: &str) -> Result<(), VmError> {
174 match self {
175 Self::Host(s) => VmExecutionSession::shutdown(s, vfs, vfs_cwd),
176 #[cfg(unix)]
177 Self::Gamma(s) => VmExecutionSession::shutdown(s, vfs, vfs_cwd),
178 #[cfg(all(unix, feature = "beta-vm"))]
179 Self::Beta(s) => VmExecutionSession::shutdown(s, vfs, vfs_cwd),
180 }
181 }
182}
183
184#[allow(clippy::result_unit_err)] pub fn try_session_rc(stderr: &mut dyn Write) -> Result<Rc<RefCell<SessionHolder>>, ()> {
188 let config = VmConfig::from_env();
189 match SessionHolder::try_from_config(&config) {
190 Ok(s) => Ok(Rc::new(RefCell::new(s))),
191 Err(e) => {
192 let _ = writeln!(stderr, "dev_shell: {e}");
193 Err(())
194 }
195 }
196}