1use serde::{Deserialize, Serialize};
21
22use crate::error::{fail, Result};
23
24#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
26#[serde(rename_all = "camelCase", deny_unknown_fields)]
27pub struct BoxTarget {
28 pub platform: String,
30 pub arch: String,
32 pub accelerator: String,
34 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub cuda_version: Option<String>,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct PythonLayout {
42 pub payload_root: &'static str,
44 pub entry_point: &'static str,
46 pub scripts_directory: &'static str,
48 pub executable_suffix: &'static str,
50 pub launcher_kind: &'static str,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub struct BoxTargetAdapter {
57 pub id: &'static str,
59 pub platform: &'static str,
61 pub arch: &'static str,
63 pub host_os: &'static str,
65 pub host_arch: &'static str,
67 pub python: PythonLayout,
69 pub execution_affecting_environment_variables: &'static [&'static str],
71 pub self_test_python: &'static str,
73}
74
75const PYTHON_EXECUTION_ENVIRONMENT: &[&str] = &[
76 "PYTHONPATH",
77 "PYTHONHOME",
78 "PYTHONSTARTUP",
79 "PYTHONBREAKPOINT",
80];
81
82const MACOS_EXECUTION_ENVIRONMENT: &[&str] = &[
83 "PYTHONPATH",
84 "PYTHONHOME",
85 "PYTHONSTARTUP",
86 "PYTHONBREAKPOINT",
87 "DYLD_INSERT_LIBRARIES",
88];
89
90const LINUX_EXECUTION_ENVIRONMENT: &[&str] = &[
91 "PYTHONPATH",
92 "PYTHONHOME",
93 "PYTHONSTARTUP",
94 "PYTHONBREAKPOINT",
95 "LD_PRELOAD",
96];
97
98const POSIX_PYTHON: PythonLayout = PythonLayout {
99 payload_root: "venv",
100 entry_point: "venv/bin/python",
101 scripts_directory: "venv/bin",
102 executable_suffix: "",
103 launcher_kind: "posix-polyglot",
104};
105
106const TARGET_ADAPTERS: &[BoxTargetAdapter] = &[
107 BoxTargetAdapter {
108 id: "macos-aarch64",
109 platform: "macos",
110 arch: "aarch64",
111 host_os: "macos",
112 host_arch: "aarch64",
113 python: POSIX_PYTHON,
114 execution_affecting_environment_variables: MACOS_EXECUTION_ENVIRONMENT,
115 self_test_python: "import sys; assert sys.platform == 'darwin'",
116 },
117 BoxTargetAdapter {
118 id: "linux-x86_64",
119 platform: "linux",
120 arch: "x86_64",
121 host_os: "linux",
122 host_arch: "x86_64",
123 python: POSIX_PYTHON,
124 execution_affecting_environment_variables: LINUX_EXECUTION_ENVIRONMENT,
125 self_test_python: "import sys; assert sys.platform.startswith('linux')",
126 },
127 BoxTargetAdapter {
128 id: "windows-x86_64",
129 platform: "windows",
130 arch: "x86_64",
131 host_os: "windows",
132 host_arch: "x86_64",
133 python: PythonLayout {
134 payload_root: "venv",
135 entry_point: "venv/python.exe",
136 scripts_directory: "venv/Scripts",
137 executable_suffix: ".exe",
138 launcher_kind: "uv-windows-pe",
141 },
142 execution_affecting_environment_variables: PYTHON_EXECUTION_ENVIRONMENT,
143 self_test_python: "import sys; assert sys.platform == 'win32'",
144 },
145];
146
147fn supported_accelerators(platform: &str, arch: &str) -> Option<&'static [&'static str]> {
149 match (platform, arch) {
150 ("macos", "aarch64") => Some(&["metal", "cpu"]),
151 ("linux" | "windows", "x86_64") => Some(&["cpu", "cuda"]),
152 _ => None,
153 }
154}
155
156fn is_cuda_version(value: &str) -> bool {
162 let Some((major, minor)) = value.split_once('.') else {
163 return false;
164 };
165 let major_valid = !major.is_empty()
166 && !major.starts_with('0')
167 && major.bytes().all(|byte| byte.is_ascii_digit());
168 let minor_valid = !minor.is_empty() && minor.bytes().all(|byte| byte.is_ascii_digit());
169 major_valid && minor_valid
170}
171
172pub fn box_target_id(target: &BoxTarget) -> Result<String> {
179 let accelerators = supported_accelerators(&target.platform, &target.arch);
180 if !accelerators.is_some_and(|values| values.contains(&target.accelerator.as_str())) {
181 fail!(
182 "Unsupported box target: {}/{}/{}",
183 target.platform,
184 target.arch,
185 target.accelerator
186 );
187 }
188 if target.accelerator == "cuda" {
189 let Some(version) = target.cuda_version.as_deref().filter(|v| is_cuda_version(v)) else {
190 fail!("A CUDA box target requires a numeric major.minor CUDA version");
191 };
192 return Ok(format!(
193 "{}-{}-cuda{version}",
194 target.platform, target.arch
195 ));
196 }
197 if target.cuda_version.is_some() {
198 fail!("Only CUDA box targets may declare a CUDA version");
199 }
200 Ok(format!(
201 "{}-{}-{}",
202 target.platform, target.arch, target.accelerator
203 ))
204}
205
206pub fn box_target_adapter(target: &BoxTarget) -> Result<&'static BoxTargetAdapter> {
212 box_target_id(target)?;
213 let Some(adapter) = TARGET_ADAPTERS
214 .iter()
215 .find(|candidate| candidate.platform == target.platform && candidate.arch == target.arch)
216 else {
217 fail!(
218 "No box target adapter exists for {}/{}",
219 target.platform,
220 target.arch
221 );
222 };
223 Ok(adapter)
224}
225
226#[must_use]
228pub fn box_target_adapters() -> &'static [BoxTargetAdapter] {
229 TARGET_ADAPTERS
230}
231
232pub fn assert_native_host(adapter: &BoxTargetAdapter) -> Result<()> {
238 assert_host(adapter, std::env::consts::OS, std::env::consts::ARCH)
239}
240
241pub fn assert_host(adapter: &BoxTargetAdapter, os: &str, arch: &str) -> Result<()> {
247 if os != adapter.host_os || arch != adapter.host_arch {
248 fail!(
249 "{} boxes cannot run on {os}/{arch}; they require {}/{}",
250 adapter.id,
251 adapter.host_os,
252 adapter.host_arch
253 );
254 }
255 Ok(())
256}
257
258pub fn assert_python_entry_point(adapter: &BoxTargetAdapter, entry_point: &str) -> Result<()> {
264 if entry_point != adapter.python.entry_point {
265 fail!(
266 "{} boxes must use Python entry point {}",
267 adapter.id,
268 adapter.python.entry_point
269 );
270 }
271 Ok(())
272}
273
274#[cfg(test)]
275mod tests {
276 use super::{
277 assert_host, assert_python_entry_point, box_target_adapter, box_target_adapters,
278 box_target_id, is_cuda_version, BoxTarget,
279 };
280
281 fn target(platform: &str, arch: &str, accelerator: &str, cuda: Option<&str>) -> BoxTarget {
282 BoxTarget {
283 platform: platform.to_string(),
284 arch: arch.to_string(),
285 accelerator: accelerator.to_string(),
286 cuda_version: cuda.map(str::to_string),
287 }
288 }
289
290 #[test]
291 fn cuda_versions_follow_the_major_minor_rule() {
292 assert!(is_cuda_version("12.4"));
293 assert!(is_cuda_version("9.0"));
294 for invalid in ["", "12", "12.", ".4", "0.1", "01.2", "12.4.1", "12.x", " 12.4"] {
295 assert!(!is_cuda_version(invalid), "{invalid} was accepted");
296 }
297 }
298
299 #[test]
300 fn every_adapter_is_reachable_from_a_target() {
301 for adapter in box_target_adapters() {
302 let accelerator = if adapter.platform == "macos" {
303 "metal"
304 } else {
305 "cpu"
306 };
307 let resolved =
308 box_target_adapter(&target(adapter.platform, adapter.arch, accelerator, None))
309 .unwrap();
310 assert_eq!(resolved.id, adapter.id);
311 }
312 }
313
314 #[test]
315 fn a_foreign_host_is_refused_with_the_shared_wording() {
316 let adapter = box_target_adapter(&target("linux", "x86_64", "cpu", None)).unwrap();
317 assert!(assert_host(adapter, "linux", "x86_64").is_ok());
318 let error = assert_host(adapter, "macos", "aarch64").unwrap_err();
319 assert!(error.message().contains("cannot run on"), "{error}");
320 }
321
322 #[test]
323 fn an_entry_point_from_another_platform_is_refused() {
324 let windows = box_target_adapter(&target("windows", "x86_64", "cpu", None)).unwrap();
325 assert!(assert_python_entry_point(windows, "venv/python.exe").is_ok());
326 assert!(assert_python_entry_point(windows, "venv/bin/python").is_err());
327 }
328
329 #[test]
330 fn a_target_id_is_never_produced_for_an_unsupported_triple() {
331 assert!(box_target_id(&target("macos", "x86_64", "cpu", None)).is_err());
332 assert!(box_target_id(&target("linux", "x86_64", "metal", None)).is_err());
333 }
334}