Skip to main content

rusty_stack/platform/
windows.rs

1//! Windows-native platform support.
2//!
3//! Provides cfg-gated Windows implementations that compile cleanly on the
4//! `x86_64-pc-windows-msvc` target without any Unix-only dependencies.
5//!
6//! # Platform Detection
7//!
8//! - `detect_backend_mode()` — classifies the current platform as
9//!   `LinuxNative`, `WslBackedLinux`, or `WindowsNative`
10//!
11//! # Backend Routing
12//!
13//! - [`BackendRouter`] — selects the correct execution backend per component
14//!   based on OS and WSL2 state (VAL-WIN-007)
15//! - Unsupported components produce clear messages without attempting install
16//!   (VAL-WIN-008)
17//!
18//! # Component Routing Matrix
19//!
20//! | Component     | Linux Native      | WSL-backed Linux  | Windows Native    |
21//! |---------------|-------------------|--------------------|-------------------|
22//! | rocm          | LegacyScript      | LegacyScript       | Unsupported       |
23//! | pytorch       | LegacyScript      | LegacyScript       | Unsupported       |
24//! | triton        | LegacyScript      | LegacyScript       | Unsupported       |
25//! | deepspeed     | LegacyScript      | LegacyScript       | Unsupported       |
26//! | vllm          | LegacyScript      | LegacyScript       | Unsupported       |
27//! | aiter         | LegacyScript      | LegacyScript       | Unsupported       |
28//! | onnx          | LegacyScript      | LegacyScript       | Unsupported       |
29//! | bitsandbytes  | LegacyScript      | LegacyScript       | Unsupported       |
30//! | migraphx      | LegacyScript      | LegacyScript       | Unsupported       |
31//! | flash-attn    | LegacyScript      | LegacyScript       | Unsupported       |
32//! | mpi4py        | LegacyScript      | LegacyScript       | Unsupported       |
33//! | wandb         | ExternalPackageManager | ExternalPackageManager | ExternalPackageManager |
34//! | comfyui       | LegacyScript      | LegacyScript       | Unsupported       |
35//! | vllm-studio   | LegacyScript      | LegacyScript       | Unsupported       |
36//! | textgen       | LegacyScript      | LegacyScript       | Unsupported       |
37//! | rocm-smi      | LegacyScript      | LegacyScript       | Unsupported       |
38//! | permanent-env | LegacyScript      | LegacyScript       | Unsupported       |
39
40use crate::core::types::{BackendMode, ExecutorKind};
41use serde::{Deserialize, Serialize};
42use std::collections::HashMap;
43
44// ===========================================================================
45// Backend Router
46// ===========================================================================
47
48/// Routes components to appropriate execution backends based on platform.
49///
50/// The router considers the [`BackendMode`] (Linux native, WSL-backed, or
51/// Windows native) and selects the best [`ExecutorKind`] for each component.
52///
53/// # Examples
54///
55/// ```
56/// use rusty_stack::platform::BackendRouter;
57/// use rusty_stack::core::types::{BackendMode, ExecutorKind};
58///
59/// let router = BackendRouter::new(BackendMode::LinuxNative);
60/// assert_ne!(router.executor_for("rocm"), ExecutorKind::Unsupported);
61///
62/// let win_router = BackendRouter::new(BackendMode::WindowsNative);
63/// assert_eq!(win_router.executor_for("rocm"), ExecutorKind::Unsupported);
64/// ```
65#[derive(Debug, Clone)]
66pub struct BackendRouter {
67    mode: BackendMode,
68    /// Component-specific overrides (component_id -> ExecutorKind).
69    overrides: HashMap<String, ExecutorKind>,
70}
71
72impl BackendRouter {
73    /// Create a new router for the given backend mode.
74    pub fn new(mode: BackendMode) -> Self {
75        Self {
76            mode,
77            overrides: HashMap::new(),
78        }
79    }
80
81    /// Set an executor override for a specific component.
82    pub fn set_override(&mut self, component_id: &str, kind: ExecutorKind) {
83        self.overrides.insert(component_id.to_string(), kind);
84    }
85
86    /// Get the appropriate executor kind for the given component.
87    ///
88    /// Returns [`ExecutorKind::Unsupported`] for unknown components.
89    pub fn executor_for(&self, component_id: &str) -> ExecutorKind {
90        // Check overrides first
91        if let Some(kind) = self.overrides.get(component_id) {
92            return *kind;
93        }
94
95        // Check if the component is known
96        if !is_known_component(component_id) {
97            return ExecutorKind::Unsupported;
98        }
99
100        // Route based on backend mode and component
101        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    /// Get the current backend mode.
109    pub fn mode(&self) -> BackendMode {
110        self.mode
111    }
112
113    /// Get a human-readable message explaining why a component is unsupported.
114    ///
115    /// Returns an empty string if the component is not unsupported.
116    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    /// Route component on Linux native.
148    fn route_linux_native(&self, component_id: &str) -> ExecutorKind {
149        // On Linux native, all known ML components use legacy scripts
150        // Python-only tools could use ExternalPackageManager
151        match component_id {
152            "wandb" => ExecutorKind::ExternalPackageManager,
153            _ => ExecutorKind::LegacyScript,
154        }
155    }
156
157    /// Route component on WSL-backed Linux.
158    fn route_wsl_backed(&self, component_id: &str) -> ExecutorKind {
159        // WSL-backed Linux can run the same scripts as native Linux
160        match component_id {
161            "wandb" => ExecutorKind::ExternalPackageManager,
162            _ => ExecutorKind::LegacyScript,
163        }
164    }
165
166    /// Route component on Windows native.
167    fn route_windows_native(&self, component_id: &str) -> ExecutorKind {
168        // On Windows native, only pure Python packages work
169        match component_id {
170            // Pure Python packages that work on Windows
171            "wandb" => ExecutorKind::ExternalPackageManager,
172            // Everything else requires Linux/ROCm
173            _ => ExecutorKind::Unsupported,
174        }
175    }
176}
177
178// ===========================================================================
179// Platform Detection
180// ===========================================================================
181
182/// Detect the current platform's backend mode.
183///
184/// Returns:
185/// - `LinuxNative` on Linux without WSL indicators
186/// - `WslBackedLinux` on Linux with WSL indicators (running inside WSL)
187/// - `WindowsNative` on Windows
188pub 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/// Check if the current Linux environment is running under WSL.
205#[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
214// ===========================================================================
215// Component Registry Helpers
216// ===========================================================================
217
218/// Check if a component ID is known.
219fn 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
242/// Get display name for a component.
243fn 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// ===========================================================================
267// Windows-Native Platform Info
268// ===========================================================================
269
270/// Windows-specific platform information.
271#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
272pub struct WindowsInfo {
273    /// Windows version string (e.g., "10.0.19045").
274    pub version: String,
275    /// Windows build number.
276    pub build: u32,
277    /// Whether WSL2 is available.
278    pub wsl2_available: bool,
279    /// Whether the system has a supported AMD GPU.
280    pub has_amd_gpu: bool,
281}
282
283// ===========================================================================
284// Tests
285// ===========================================================================
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290
291    // ---- Backend Router Tests ----
292
293    #[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    // ---- Unsupported Message Tests ----
408
409    #[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    // ---- Platform Detection Tests ----
436
437    #[test]
438    fn test_detect_backend_mode() {
439        let mode = detect_backend_mode();
440        // On this Linux system, should be LinuxNative or WslBackedLinux
441        match mode {
442            BackendMode::LinuxNative | BackendMode::WslBackedLinux => {}
443            BackendMode::WindowsNative => {
444                panic!("Should not detect WindowsNative on Linux")
445            }
446        }
447    }
448
449    // ---- Component Registry Tests ----
450
451    #[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    // ---- WindowsInfo Serde Tests ----
498
499    #[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    // ---- Router Mode Accessor ----
522
523    #[test]
524    fn test_router_mode() {
525        let router = BackendRouter::new(BackendMode::WindowsNative);
526        assert_eq!(router.mode(), BackendMode::WindowsNative);
527    }
528}