1use crate::core::types::{BackendMode, ExecutorKind};
41use serde::{Deserialize, Serialize};
42use std::collections::HashMap;
43
44#[derive(Debug, Clone)]
66pub struct BackendRouter {
67 mode: BackendMode,
68 overrides: HashMap<String, ExecutorKind>,
70}
71
72impl BackendRouter {
73 pub fn new(mode: BackendMode) -> Self {
75 Self {
76 mode,
77 overrides: HashMap::new(),
78 }
79 }
80
81 pub fn set_override(&mut self, component_id: &str, kind: ExecutorKind) {
83 self.overrides.insert(component_id.to_string(), kind);
84 }
85
86 pub fn executor_for(&self, component_id: &str) -> ExecutorKind {
90 if let Some(kind) = self.overrides.get(component_id) {
92 return *kind;
93 }
94
95 if !is_known_component(component_id) {
97 return ExecutorKind::Unsupported;
98 }
99
100 match self.mode {
102 BackendMode::LinuxNative => self.route_linux_native(component_id),
103 BackendMode::WslBackedLinux => self.route_wsl_backed(component_id),
104 BackendMode::WindowsNative => self.route_windows_native(component_id),
105 }
106 }
107
108 pub fn mode(&self) -> BackendMode {
110 self.mode
111 }
112
113 pub fn unsupported_message(&self, component_id: &str) -> String {
117 let kind = self.executor_for(component_id);
118 if kind != ExecutorKind::Unsupported {
119 return String::new();
120 }
121
122 if !is_known_component(component_id) {
123 return format!(
124 "Component '{}' is not recognized by Rusty Stack.",
125 component_id
126 );
127 }
128
129 match self.mode {
130 BackendMode::WindowsNative => format!(
131 "Component '{}' requires a Linux environment with ROCm support. \
132 It cannot be installed natively on Windows. \
133 Consider using WSL2 with ROCm to enable Linux GPU computing on Windows.",
134 display_name(component_id)
135 ),
136 BackendMode::WslBackedLinux => format!(
137 "Component '{}' is not supported in the current WSL2 configuration.",
138 display_name(component_id)
139 ),
140 BackendMode::LinuxNative => format!(
141 "Component '{}' is not supported on this Linux configuration.",
142 display_name(component_id)
143 ),
144 }
145 }
146
147 fn route_linux_native(&self, component_id: &str) -> ExecutorKind {
149 match component_id {
152 "wandb" => ExecutorKind::ExternalPackageManager,
153 _ => ExecutorKind::LegacyScript,
154 }
155 }
156
157 fn route_wsl_backed(&self, component_id: &str) -> ExecutorKind {
159 match component_id {
161 "wandb" => ExecutorKind::ExternalPackageManager,
162 _ => ExecutorKind::LegacyScript,
163 }
164 }
165
166 fn route_windows_native(&self, component_id: &str) -> ExecutorKind {
168 match component_id {
170 "wandb" => ExecutorKind::ExternalPackageManager,
172 _ => ExecutorKind::Unsupported,
174 }
175 }
176}
177
178pub fn detect_backend_mode() -> BackendMode {
189 #[cfg(target_os = "windows")]
190 {
191 BackendMode::WindowsNative
192 }
193
194 #[cfg(not(target_os = "windows"))]
195 {
196 if is_wsl_environment() {
197 BackendMode::WslBackedLinux
198 } else {
199 BackendMode::LinuxNative
200 }
201 }
202}
203
204#[cfg(not(target_os = "windows"))]
206fn is_wsl_environment() -> bool {
207 if let Ok(version) = std::fs::read_to_string("/proc/version") {
208 let version_lower = version.to_lowercase();
209 return version_lower.contains("microsoft") || version_lower.contains("wsl");
210 }
211 false
212}
213
214fn is_known_component(id: &str) -> bool {
220 matches!(
221 id,
222 "rocm"
223 | "pytorch"
224 | "triton"
225 | "deepspeed"
226 | "vllm"
227 | "aiter"
228 | "onnx"
229 | "bitsandbytes"
230 | "migraphx"
231 | "flash-attn"
232 | "mpi4py"
233 | "wandb"
234 | "comfyui"
235 | "vllm-studio"
236 | "textgen"
237 | "rocm-smi"
238 | "permanent-env"
239 )
240}
241
242fn display_name(id: &str) -> &str {
244 match id {
245 "rocm" => "ROCm",
246 "pytorch" => "PyTorch",
247 "triton" => "Triton",
248 "deepspeed" => "DeepSpeed",
249 "vllm" => "vLLM",
250 "aiter" => "AITER",
251 "onnx" => "ONNX Runtime",
252 "bitsandbytes" => "bitsandbytes",
253 "migraphx" => "MIGraphX",
254 "flash-attn" => "Flash Attention",
255 "mpi4py" => "MPI4Py",
256 "wandb" => "Weights & Biases",
257 "comfyui" => "ComfyUI",
258 "vllm-studio" => "vLLM Studio",
259 "textgen" => "Text Generation WebUI",
260 "rocm-smi" => "ROCm SMI",
261 "permanent-env" => "Permanent Environment",
262 _ => id,
263 }
264}
265
266#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
272pub struct WindowsInfo {
273 pub version: String,
275 pub build: u32,
277 pub wsl2_available: bool,
279 pub has_amd_gpu: bool,
281}
282
283#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
294 fn test_router_linux_native_rocm() {
295 let router = BackendRouter::new(BackendMode::LinuxNative);
296 assert_eq!(router.executor_for("rocm"), ExecutorKind::LegacyScript);
297 }
298
299 #[test]
300 fn test_router_linux_native_all_components() {
301 let router = BackendRouter::new(BackendMode::LinuxNative);
302 let components = [
303 "rocm",
304 "pytorch",
305 "triton",
306 "deepspeed",
307 "vllm",
308 "aiter",
309 "onnx",
310 "bitsandbytes",
311 "migraphx",
312 "flash-attn",
313 "mpi4py",
314 "comfyui",
315 "vllm-studio",
316 "textgen",
317 "rocm-smi",
318 "permanent-env",
319 ];
320 for c in &components {
321 assert_ne!(
322 router.executor_for(c),
323 ExecutorKind::Unsupported,
324 "{} should not be unsupported on Linux",
325 c
326 );
327 }
328 }
329
330 #[test]
331 fn test_router_linux_native_wandb() {
332 let router = BackendRouter::new(BackendMode::LinuxNative);
333 assert_eq!(
334 router.executor_for("wandb"),
335 ExecutorKind::ExternalPackageManager
336 );
337 }
338
339 #[test]
340 fn test_router_windows_native_rocm_unsupported() {
341 let router = BackendRouter::new(BackendMode::WindowsNative);
342 assert_eq!(router.executor_for("rocm"), ExecutorKind::Unsupported);
343 }
344
345 #[test]
346 fn test_router_windows_native_all_gpu_unsupported() {
347 let router = BackendRouter::new(BackendMode::WindowsNative);
348 let gpu_components = [
349 "rocm",
350 "pytorch",
351 "triton",
352 "deepspeed",
353 "vllm",
354 "aiter",
355 "onnx",
356 "bitsandbytes",
357 "migraphx",
358 "flash-attn",
359 "mpi4py",
360 "comfyui",
361 "vllm-studio",
362 "textgen",
363 "rocm-smi",
364 "permanent-env",
365 ];
366 for c in &gpu_components {
367 assert_eq!(
368 router.executor_for(c),
369 ExecutorKind::Unsupported,
370 "{} should be unsupported on Windows native",
371 c
372 );
373 }
374 }
375
376 #[test]
377 fn test_router_windows_native_wandb() {
378 let router = BackendRouter::new(BackendMode::WindowsNative);
379 assert_eq!(
380 router.executor_for("wandb"),
381 ExecutorKind::ExternalPackageManager
382 );
383 }
384
385 #[test]
386 fn test_router_wsl_backed_rocm() {
387 let router = BackendRouter::new(BackendMode::WslBackedLinux);
388 assert_eq!(router.executor_for("rocm"), ExecutorKind::LegacyScript);
389 }
390
391 #[test]
392 fn test_router_unknown_component() {
393 let router = BackendRouter::new(BackendMode::LinuxNative);
394 assert_eq!(
395 router.executor_for("nonexistent"),
396 ExecutorKind::Unsupported
397 );
398 }
399
400 #[test]
401 fn test_router_override() {
402 let mut router = BackendRouter::new(BackendMode::LinuxNative);
403 router.set_override("rocm", ExecutorKind::Rust);
404 assert_eq!(router.executor_for("rocm"), ExecutorKind::Rust);
405 }
406
407 #[test]
410 fn test_unsupported_message_windows_rocm() {
411 let router = BackendRouter::new(BackendMode::WindowsNative);
412 let msg = router.unsupported_message("rocm");
413 assert!(!msg.is_empty());
414 assert!(msg.contains("ROCm"));
415 assert!(msg.contains("Windows") || msg.contains("WSL"));
416 }
417
418 #[test]
419 fn test_unsupported_message_unknown_component() {
420 let router = BackendRouter::new(BackendMode::LinuxNative);
421 let msg = router.unsupported_message("nonexistent");
422 assert!(msg.contains("not recognized"));
423 }
424
425 #[test]
426 fn test_unsupported_message_supported_component_empty() {
427 let router = BackendRouter::new(BackendMode::LinuxNative);
428 let msg = router.unsupported_message("rocm");
429 assert!(
430 msg.is_empty(),
431 "Supported component should return empty message"
432 );
433 }
434
435 #[test]
438 fn test_detect_backend_mode() {
439 let mode = detect_backend_mode();
440 match mode {
442 BackendMode::LinuxNative | BackendMode::WslBackedLinux => {}
443 BackendMode::WindowsNative => {
444 panic!("Should not detect WindowsNative on Linux")
445 }
446 }
447 }
448
449 #[test]
452 fn test_is_known_component_all_17() {
453 let known = [
454 "rocm",
455 "pytorch",
456 "triton",
457 "deepspeed",
458 "vllm",
459 "aiter",
460 "onnx",
461 "bitsandbytes",
462 "migraphx",
463 "flash-attn",
464 "mpi4py",
465 "wandb",
466 "comfyui",
467 "vllm-studio",
468 "textgen",
469 "rocm-smi",
470 "permanent-env",
471 ];
472 for c in &known {
473 assert!(is_known_component(c), "{} should be known", c);
474 }
475 }
476
477 #[test]
478 fn test_is_known_component_unknowns() {
479 let unknowns = ["foo", "bar", "cuda", "nvidia", ""];
480 for c in &unknowns {
481 assert!(!is_known_component(c), "{} should be unknown", c);
482 }
483 }
484
485 #[test]
486 fn test_display_name_known() {
487 assert_eq!(display_name("rocm"), "ROCm");
488 assert_eq!(display_name("pytorch"), "PyTorch");
489 assert_eq!(display_name("wandb"), "Weights & Biases");
490 }
491
492 #[test]
493 fn test_display_name_unknown_passthrough() {
494 assert_eq!(display_name("custom-component"), "custom-component");
495 }
496
497 #[test]
500 fn test_windows_info_serde_roundtrip() {
501 let info = WindowsInfo {
502 version: "10.0.19045".to_string(),
503 build: 19045,
504 wsl2_available: true,
505 has_amd_gpu: true,
506 };
507 let json = serde_json::to_string(&info).unwrap();
508 let back: WindowsInfo = serde_json::from_str(&json).unwrap();
509 assert_eq!(info, back);
510 }
511
512 #[test]
513 fn test_windows_info_default() {
514 let info = WindowsInfo::default();
515 assert!(info.version.is_empty());
516 assert_eq!(info.build, 0);
517 assert!(!info.wsl2_available);
518 assert!(!info.has_amd_gpu);
519 }
520
521 #[test]
524 fn test_router_mode() {
525 let router = BackendRouter::new(BackendMode::WindowsNative);
526 assert_eq!(router.mode(), BackendMode::WindowsNative);
527 }
528}