Skip to main content

rusty_stack/installers/components/
rocm_smi.rs

1//! ROCm SMI installer — ports `scripts/install_rocm_smi.sh`.
2//!
3//! Constructs correct package manager commands per distro for installing
4//! the ROCm System Management Interface. `rocm_smi_lib` has no `setup.py`
5//! (it is a C++ library whose Python tools ship via the distro package),
6//! so this installer is package-manager + git-clone only — no pip step.
7//!
8//! # Validation Assertion
9//!
10//! - **VAL-INSTALL-018**: ROCm SMI correct package command
11
12use crate::installers::common::DistroFacade;
13use crate::platform::detection::DistroFamily;
14use std::path::PathBuf;
15
16// ===========================================================================
17// Types
18// ===========================================================================
19
20/// A constructed shell command.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct ShellCommand {
23    /// The program to run.
24    pub program: String,
25    /// Arguments to pass.
26    pub args: Vec<String>,
27    /// Environment variables to set.
28    pub env: Vec<(String, String)>,
29    /// Working directory for the command.
30    pub working_dir: Option<PathBuf>,
31}
32
33impl ShellCommand {
34    /// Format as a shell command string.
35    pub fn to_command_string(&self) -> String {
36        let env_prefix = self
37            .env
38            .iter()
39            .map(|(k, v)| format!("{k}={v}"))
40            .collect::<Vec<_>>()
41            .join(" ");
42        let cmd = if self.args.is_empty() {
43            self.program.clone()
44        } else {
45            format!("{} {}", self.program, self.args.join(" "))
46        };
47        if env_prefix.is_empty() {
48            cmd
49        } else {
50            format!("{env_prefix} {cmd}")
51        }
52    }
53}
54
55/// The ROCm SMI installer — a pure package-manager dispatcher (no Python,
56/// no build config): `rocm_smi_lib` ships via the distro package, so this
57/// installer holds no configuration state.
58#[derive(Default)]
59pub struct RocmSmiInstaller;
60
61impl RocmSmiInstaller {
62    /// Create a new ROCm SMI installer.
63    pub fn new() -> Self {
64        Self
65    }
66
67    // -----------------------------------------------------------------------
68    // Git clone
69    // -----------------------------------------------------------------------
70
71    /// Get the ROCm SMI library git clone URL.
72    pub fn git_clone_url(&self) -> &'static str {
73        "https://github.com/RadeonOpenCompute/rocm_smi_lib.git"
74    }
75
76    /// Construct the git clone command.
77    pub fn build_git_clone_command(&self, target_dir: &str) -> ShellCommand {
78        ShellCommand {
79            program: "git".to_string(),
80            args: vec![
81                "clone".to_string(),
82                self.git_clone_url().to_string(),
83                target_dir.to_string(),
84            ],
85            env: vec![],
86            working_dir: None,
87        }
88    }
89
90    // -----------------------------------------------------------------------
91    // Package manager commands (VAL-INSTALL-018)
92    // -----------------------------------------------------------------------
93
94    /// Construct the package install command for the given distro.
95    ///
96    /// Maps to the correct package manager commands per distro:
97    /// - Debian/Ubuntu: `sudo apt install -y rocm-smi`
98    /// - Fedora/RHEL: `sudo dnf install -y rocm-smi`
99    /// - Arch: `sudo pacman -S --needed --noconfirm rocm-smi`
100    /// - SUSE: `sudo zypper install -y rocm-smi`
101    pub fn build_package_install_command(&self, distro: &DistroFacade) -> ShellCommand {
102        let (program, args) = match distro.family() {
103            DistroFamily::Debian => (
104                "sudo".to_string(),
105                vec![
106                    "apt-get".to_string(),
107                    "install".to_string(),
108                    "-y".to_string(),
109                    "rocm-smi".to_string(),
110                ],
111            ),
112            DistroFamily::Rhel => (
113                "sudo".to_string(),
114                vec![
115                    "dnf".to_string(),
116                    "install".to_string(),
117                    "-y".to_string(),
118                    "rocm-smi".to_string(),
119                ],
120            ),
121            DistroFamily::Arch => (
122                "sudo".to_string(),
123                vec![
124                    "pacman".to_string(),
125                    "-S".to_string(),
126                    "--needed".to_string(),
127                    "--noconfirm".to_string(),
128                    "rocm-smi-lib".to_string(),
129                ],
130            ),
131            DistroFamily::Suse => (
132                "sudo".to_string(),
133                vec![
134                    "zypper".to_string(),
135                    "install".to_string(),
136                    "-y".to_string(),
137                    "rocm-smi".to_string(),
138                ],
139            ),
140            _ => (
141                "sudo".to_string(),
142                vec![
143                    "apt-get".to_string(),
144                    "install".to_string(),
145                    "-y".to_string(),
146                    "rocm-smi".to_string(),
147                ],
148            ),
149        };
150
151        ShellCommand {
152            program,
153            args,
154            env: vec![],
155            working_dir: None,
156        }
157    }
158
159    /// Construct the package update command for the given distro.
160    pub fn build_package_update_command(&self, distro: &DistroFacade) -> ShellCommand {
161        let (program, args) = match distro.family() {
162            DistroFamily::Debian => (
163                "sudo".to_string(),
164                vec![
165                    "apt-get".to_string(),
166                    "update".to_string(),
167                    "-y".to_string(),
168                ],
169            ),
170            DistroFamily::Rhel => (
171                "sudo".to_string(),
172                vec!["dnf".to_string(), "makecache".to_string()],
173            ),
174            DistroFamily::Arch => (
175                "sudo".to_string(),
176                vec!["pacman".to_string(), "-Sy".to_string()],
177            ),
178            DistroFamily::Suse => (
179                "sudo".to_string(),
180                vec!["zypper".to_string(), "refresh".to_string()],
181            ),
182            _ => (
183                "sudo".to_string(),
184                vec![
185                    "apt-get".to_string(),
186                    "update".to_string(),
187                    "-y".to_string(),
188                ],
189            ),
190        };
191
192        ShellCommand {
193            program,
194            args,
195            env: vec![],
196            working_dir: None,
197        }
198    }
199}
200
201// ===========================================================================
202// Tests
203// ===========================================================================
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    use crate::platform::detection::{DistroInfo, PackageManager};
209
210    // --- VAL-INSTALL-018: ROCm SMI correct package command ---
211
212    #[test]
213    fn test_git_clone_url() {
214        let installer = RocmSmiInstaller::new();
215        assert_eq!(
216            installer.git_clone_url(),
217            "https://github.com/RadeonOpenCompute/rocm_smi_lib.git"
218        );
219    }
220
221    #[test]
222    fn test_git_clone_command() {
223        let installer = RocmSmiInstaller::new();
224        let cmd = installer.build_git_clone_command("/tmp/rocm_smi");
225        assert_eq!(cmd.program, "git");
226        assert!(cmd.args.contains(&"clone".to_string()));
227        assert!(cmd
228            .args
229            .iter()
230            .any(|a| a.contains("RadeonOpenCompute/rocm_smi_lib")));
231    }
232
233    #[test]
234    fn test_package_install_debian() {
235        let installer = RocmSmiInstaller::new();
236        let distro = DistroFacade::from_info(DistroInfo {
237            id: "ubuntu".to_string(),
238            family: DistroFamily::Debian,
239            pkg_manager: PackageManager::Apt,
240            ..Default::default()
241        });
242        let cmd = installer.build_package_install_command(&distro);
243        assert_eq!(cmd.program, "sudo");
244        assert!(cmd.args.contains(&"apt-get".to_string()));
245        assert!(cmd.args.contains(&"install".to_string()));
246        assert!(cmd.args.contains(&"-y".to_string()));
247        assert!(cmd.args.contains(&"rocm-smi".to_string()));
248    }
249
250    #[test]
251    fn test_package_install_rhel() {
252        let installer = RocmSmiInstaller::new();
253        let distro = DistroFacade::from_info(DistroInfo {
254            id: "fedora".to_string(),
255            family: DistroFamily::Rhel,
256            pkg_manager: PackageManager::Dnf,
257            ..Default::default()
258        });
259        let cmd = installer.build_package_install_command(&distro);
260        assert_eq!(cmd.program, "sudo");
261        assert!(cmd.args.contains(&"dnf".to_string()));
262        assert!(cmd.args.contains(&"install".to_string()));
263        assert!(cmd.args.contains(&"rocm-smi".to_string()));
264    }
265
266    #[test]
267    fn test_package_install_arch() {
268        let installer = RocmSmiInstaller::new();
269        let distro = DistroFacade::from_info(DistroInfo {
270            id: "arch".to_string(),
271            family: DistroFamily::Arch,
272            pkg_manager: PackageManager::Pacman,
273            ..Default::default()
274        });
275        let cmd = installer.build_package_install_command(&distro);
276        assert_eq!(cmd.program, "sudo");
277        assert!(cmd.args.contains(&"pacman".to_string()));
278        assert!(cmd.args.contains(&"-S".to_string()));
279        assert!(cmd.args.contains(&"--needed".to_string()));
280        assert!(cmd.args.contains(&"--noconfirm".to_string()));
281        assert!(cmd.args.contains(&"rocm-smi-lib".to_string()));
282    }
283
284    #[test]
285    fn test_package_install_suse() {
286        let installer = RocmSmiInstaller::new();
287        let distro = DistroFacade::from_info(DistroInfo {
288            id: "opensuse".to_string(),
289            family: DistroFamily::Suse,
290            pkg_manager: PackageManager::Zypper,
291            ..Default::default()
292        });
293        let cmd = installer.build_package_install_command(&distro);
294        assert_eq!(cmd.program, "sudo");
295        assert!(cmd.args.contains(&"zypper".to_string()));
296        assert!(cmd.args.contains(&"install".to_string()));
297        assert!(cmd.args.contains(&"rocm-smi".to_string()));
298    }
299
300    #[test]
301    fn test_package_update_debian() {
302        let installer = RocmSmiInstaller::new();
303        let distro = DistroFacade::from_info(DistroInfo {
304            id: "ubuntu".to_string(),
305            family: DistroFamily::Debian,
306            pkg_manager: PackageManager::Apt,
307            ..Default::default()
308        });
309        let cmd = installer.build_package_update_command(&distro);
310        assert!(cmd.args.contains(&"apt-get".to_string()));
311        assert!(cmd.args.contains(&"update".to_string()));
312    }
313}