xtask_todo_lib/devshell/vm/config/
mod.rs1mod constants;
4mod repo;
5#[cfg(test)]
6mod tests;
7
8pub use constants::*;
9#[cfg(all(feature = "beta-vm", target_os = "windows"))]
10pub(crate) use repo::{devshell_repo_root_from_path, devshell_repo_root_with_containerfile};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum WorkspaceMode {
15 Sync,
17 Guest,
19}
20
21#[must_use]
23pub fn workspace_mode_from_env() -> WorkspaceMode {
24 match std::env::var(ENV_DEVSHELL_VM_WORKSPACE_MODE) {
25 Ok(s) if s.trim().eq_ignore_ascii_case("guest") => WorkspaceMode::Guest,
26 _ => WorkspaceMode::Sync,
27 }
28}
29
30#[must_use]
33pub fn exec_timeout_ms_from_env() -> Option<u64> {
34 std::env::var(ENV_DEVSHELL_VM_EXEC_TIMEOUT_MS)
35 .ok()
36 .and_then(|s| s.trim().parse::<u64>().ok())
37 .filter(|&ms| ms > 0)
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct VmConfig {
43 pub enabled: bool,
45 pub backend: String,
47 pub eager_start: bool,
49 pub lima_instance: String,
51}
52
53fn truthy(s: &str) -> bool {
54 let s = s.trim();
55 s == "1"
56 || s.eq_ignore_ascii_case("true")
57 || s.eq_ignore_ascii_case("yes")
58 || s.eq_ignore_ascii_case("on")
59}
60
61fn falsy(s: &str) -> bool {
62 let s = s.trim();
63 s == "0"
64 || s.eq_ignore_ascii_case("false")
65 || s.eq_ignore_ascii_case("no")
66 || s.eq_ignore_ascii_case("off")
67}
68
69fn default_backend_for_release() -> String {
70 #[cfg(all(windows, feature = "beta-vm"))]
71 {
72 return "beta".to_string();
73 }
74 #[cfg(unix)]
75 {
76 "lima".to_string()
77 }
78 #[cfg(not(any(unix, all(windows, feature = "beta-vm"))))]
79 {
80 "host".to_string()
81 }
82}
83
84fn vm_enabled_from_env() -> bool {
85 if cfg!(test) {
86 return std::env::var(ENV_DEVSHELL_VM).is_ok_and(|s| truthy(&s));
87 }
88 match std::env::var(ENV_DEVSHELL_VM) {
89 Err(_) => true,
90 Ok(s) if s.trim().is_empty() => false,
91 Ok(s) if falsy(&s) => false,
92 Ok(s) => truthy(&s),
93 }
94}
95
96fn backend_from_env() -> String {
97 let from_var = std::env::var(ENV_DEVSHELL_VM_BACKEND)
98 .ok()
99 .map(|s| s.trim().to_string())
100 .filter(|s| !s.is_empty());
101 if let Some(b) = from_var {
102 return b;
103 }
104 if cfg!(test) {
105 "auto".to_string()
106 } else {
107 default_backend_for_release()
108 }
109}
110
111impl VmConfig {
112 #[must_use]
114 pub fn from_env() -> Self {
115 let enabled = vm_enabled_from_env();
116
117 let backend = backend_from_env();
118
119 let eager_start = std::env::var(ENV_DEVSHELL_VM_EAGER).is_ok_and(|s| truthy(&s));
120
121 let lima_instance = std::env::var(ENV_DEVSHELL_VM_LIMA_INSTANCE)
122 .ok()
123 .map(|s| s.trim().to_string())
124 .filter(|s| !s.is_empty())
125 .unwrap_or_else(|| "devshell-rust".to_string());
126
127 Self {
128 enabled,
129 backend,
130 eager_start,
131 lima_instance,
132 }
133 }
134
135 #[must_use]
137 pub const fn disabled() -> Self {
138 Self {
139 enabled: false,
140 backend: String::new(),
141 eager_start: false,
142 lima_instance: String::new(),
143 }
144 }
145
146 #[must_use]
148 pub fn use_host_sandbox(&self) -> bool {
149 let b = self.backend.to_ascii_lowercase();
150 b == "host" || b == "auto" || b.is_empty()
151 }
152
153 #[must_use]
159 pub fn workspace_mode_effective(&self) -> WorkspaceMode {
160 let requested = workspace_mode_from_env();
161 if matches!(requested, WorkspaceMode::Sync) {
162 return WorkspaceMode::Sync;
163 }
164
165 let effective = if !self.enabled || self.use_host_sandbox() {
166 WorkspaceMode::Sync
167 } else {
168 let b = self.backend.to_ascii_lowercase();
169 if b == "lima" || b == "beta" {
170 WorkspaceMode::Guest
171 } else {
172 WorkspaceMode::Sync
173 }
174 };
175
176 if matches!(requested, WorkspaceMode::Guest)
177 && matches!(effective, WorkspaceMode::Sync)
178 && !cfg!(test)
179 {
180 eprintln!(
181 "dev_shell: DEVSHELL_VM_WORKSPACE_MODE=guest requires VM enabled and backend lima or beta; using sync mode."
182 );
183 }
184
185 effective
186 }
187}