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