1use crate::installers::common::DistroFacade;
19use crate::platform::detection::DistroFamily;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum MigraphxSupport {
28 Full,
30 SystemOnly,
32 Unavailable,
34}
35
36impl std::fmt::Display for MigraphxSupport {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 match self {
39 MigraphxSupport::Full => write!(f, "full"),
40 MigraphxSupport::SystemOnly => write!(f, "system-only"),
41 MigraphxSupport::Unavailable => write!(f, "unavailable"),
42 }
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct ShellCommand {
53 pub program: String,
55 pub args: Vec<String>,
57 pub env: Vec<(String, String)>,
59}
60
61impl ShellCommand {
62 pub fn to_command_string(&self) -> String {
64 let env_prefix = self
65 .env
66 .iter()
67 .map(|(k, v)| format!("{k}={v}"))
68 .collect::<Vec<_>>()
69 .join(" ");
70 let cmd = if self.args.is_empty() {
71 self.program.clone()
72 } else {
73 format!("{} {}", self.program, self.args.join(" "))
74 };
75 if env_prefix.is_empty() {
76 cmd
77 } else {
78 format!("{env_prefix} {cmd}")
79 }
80 }
81}
82
83#[derive(Debug, Clone)]
85pub struct MigraphxConfig {
86 pub python_bin: String,
88 pub dry_run: bool,
90}
91
92impl Default for MigraphxConfig {
93 fn default() -> Self {
94 Self {
95 python_bin: "python3".to_string(),
96 dry_run: false,
97 }
98 }
99}
100
101pub struct MigraphxInstaller {
103 config: MigraphxConfig,
104}
105
106impl MigraphxInstaller {
107 pub fn new(config: MigraphxConfig) -> Self {
109 Self { config }
110 }
111
112 pub fn with_defaults() -> Self {
114 Self::new(MigraphxConfig::default())
115 }
116
117 pub fn required_packages(&self, distro: &DistroFacade) -> Vec<&str> {
123 match distro.family() {
124 DistroFamily::Arch => vec!["migraphx"],
125 _ => vec!["migraphx", "migraphx-dev"],
126 }
127 }
128
129 pub fn optional_packages(&self, distro: &DistroFacade) -> Vec<&str> {
131 match distro.family() {
132 DistroFamily::Arch => vec!["python3-migraphx", "half"],
133 _ => vec!["python3-migraphx", "half"],
134 }
135 }
136
137 pub fn build_package_install_command(
139 &self,
140 distro: &DistroFacade,
141 packages: &[&str],
142 ) -> ShellCommand {
143 let (program, mut base_args) = match distro.family() {
144 DistroFamily::Debian => (
145 "sudo".to_string(),
146 vec![
147 "apt-get".to_string(),
148 "install".to_string(),
149 "-y".to_string(),
150 ],
151 ),
152 DistroFamily::Rhel => (
153 "sudo".to_string(),
154 vec!["dnf".to_string(), "install".to_string(), "-y".to_string()],
155 ),
156 DistroFamily::Arch => (
157 "sudo".to_string(),
158 vec![
159 "pacman".to_string(),
160 "-S".to_string(),
161 "--needed".to_string(),
162 "--noconfirm".to_string(),
163 ],
164 ),
165 DistroFamily::Suse => (
166 "sudo".to_string(),
167 vec![
168 "zypper".to_string(),
169 "install".to_string(),
170 "-y".to_string(),
171 ],
172 ),
173 _ => (
174 "sudo".to_string(),
175 vec![
176 "apt-get".to_string(),
177 "install".to_string(),
178 "-y".to_string(),
179 ],
180 ),
181 };
182
183 for pkg in packages {
184 base_args.push(pkg.to_string());
185 }
186
187 ShellCommand {
188 program,
189 args: base_args,
190 env: vec![],
191 }
192 }
193
194 pub fn build_package_update_command(&self, distro: &DistroFacade) -> ShellCommand {
196 let (program, args) = match distro.family() {
197 DistroFamily::Debian => (
198 "sudo".to_string(),
199 vec![
200 "apt-get".to_string(),
201 "update".to_string(),
202 "-y".to_string(),
203 ],
204 ),
205 DistroFamily::Rhel => (
206 "sudo".to_string(),
207 vec!["dnf".to_string(), "makecache".to_string()],
208 ),
209 DistroFamily::Arch => (
210 "sudo".to_string(),
211 vec!["pacman".to_string(), "-Sy".to_string()],
212 ),
213 DistroFamily::Suse => (
214 "sudo".to_string(),
215 vec!["zypper".to_string(), "refresh".to_string()],
216 ),
217 _ => (
218 "sudo".to_string(),
219 vec![
220 "apt-get".to_string(),
221 "update".to_string(),
222 "-y".to_string(),
223 ],
224 ),
225 };
226
227 ShellCommand {
228 program,
229 args,
230 env: vec![],
231 }
232 }
233
234 pub fn build_pip_install_command(&self) -> ShellCommand {
243 ShellCommand {
244 program: self.config.python_bin.clone(),
245 args: vec![
246 "-m".to_string(),
247 "pip".to_string(),
248 "install".to_string(),
249 "--break-system-packages".to_string(),
250 "migraphx".to_string(),
251 ],
252 env: vec![],
253 }
254 }
255
256 pub fn is_available_on_distro(&self, distro: &DistroFacade) -> MigraphxSupport {
267 match distro.family() {
268 DistroFamily::Arch => MigraphxSupport::SystemOnly,
269 _ => MigraphxSupport::Full,
270 }
271 }
272
273 pub fn build_limitation_message(&self, distro: &DistroFacade) -> Option<String> {
276 match self.is_available_on_distro(distro) {
277 MigraphxSupport::Full => None,
278 MigraphxSupport::SystemOnly => Some(format!(
279 "MIGraphX on {} has limited support: only the system package is available. \
280 Python bindings (pip install migraphx) are not available on Arch-family distros. \
281 The system package ({}) has been installed. \
282 If you need Python bindings, consider using the ROCm Docker image or building from source.",
283 distro.id(),
284 self.required_packages(distro).join(", ")
285 )),
286 MigraphxSupport::Unavailable => Some(format!(
287 "MIGraphX is not available on {}. \
288 Consider using the ROCm Docker image or building from source.",
289 distro.id()
290 )),
291 }
292 }
293
294 pub fn should_skip_pip_install(&self, distro: &DistroFacade) -> bool {
300 matches!(
301 self.is_available_on_distro(distro),
302 MigraphxSupport::SystemOnly
303 )
304 }
305}
306
307#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::platform::detection::{DistroInfo, PackageManager};
315
316 #[test]
319 fn test_required_packages_debian() {
320 let installer = MigraphxInstaller::with_defaults();
321 let distro = DistroFacade::from_info(DistroInfo {
322 id: "ubuntu".to_string(),
323 family: DistroFamily::Debian,
324 pkg_manager: PackageManager::Apt,
325 ..Default::default()
326 });
327 let pkgs = installer.required_packages(&distro);
328 assert!(pkgs.contains(&"migraphx"));
329 assert!(pkgs.contains(&"migraphx-dev"));
330 }
331
332 #[test]
333 fn test_required_packages_arch() {
334 let installer = MigraphxInstaller::with_defaults();
335 let distro = DistroFacade::from_info(DistroInfo {
336 id: "arch".to_string(),
337 family: DistroFamily::Arch,
338 pkg_manager: PackageManager::Pacman,
339 ..Default::default()
340 });
341 let pkgs = installer.required_packages(&distro);
342 assert!(pkgs.contains(&"migraphx"));
343 assert!(!pkgs.contains(&"migraphx-dev"));
344 }
345
346 #[test]
347 fn test_optional_packages() {
348 let installer = MigraphxInstaller::with_defaults();
349 let distro = DistroFacade::from_info(DistroInfo {
350 id: "ubuntu".to_string(),
351 family: DistroFamily::Debian,
352 pkg_manager: PackageManager::Apt,
353 ..Default::default()
354 });
355 let pkgs = installer.optional_packages(&distro);
356 assert!(pkgs.contains(&"python3-migraphx"));
357 assert!(pkgs.contains(&"half"));
358 }
359
360 #[test]
361 fn test_package_install_debian() {
362 let installer = MigraphxInstaller::with_defaults();
363 let distro = DistroFacade::from_info(DistroInfo {
364 id: "ubuntu".to_string(),
365 family: DistroFamily::Debian,
366 pkg_manager: PackageManager::Apt,
367 ..Default::default()
368 });
369 let cmd = installer.build_package_install_command(&distro, &["migraphx", "migraphx-dev"]);
370 assert_eq!(cmd.program, "sudo");
371 assert!(cmd.args.contains(&"apt-get".to_string()));
372 assert!(cmd.args.contains(&"install".to_string()));
373 assert!(cmd.args.contains(&"migraphx".to_string()));
374 assert!(cmd.args.contains(&"migraphx-dev".to_string()));
375 }
376
377 #[test]
378 fn test_package_install_arch() {
379 let installer = MigraphxInstaller::with_defaults();
380 let distro = DistroFacade::from_info(DistroInfo {
381 id: "arch".to_string(),
382 family: DistroFamily::Arch,
383 pkg_manager: PackageManager::Pacman,
384 ..Default::default()
385 });
386 let cmd = installer.build_package_install_command(&distro, &["migraphx"]);
387 assert!(cmd.args.contains(&"pacman".to_string()));
388 assert!(cmd.args.contains(&"-S".to_string()));
389 assert!(cmd.args.contains(&"migraphx".to_string()));
390 }
391
392 #[test]
393 fn test_package_install_rhel() {
394 let installer = MigraphxInstaller::with_defaults();
395 let distro = DistroFacade::from_info(DistroInfo {
396 id: "fedora".to_string(),
397 family: DistroFamily::Rhel,
398 pkg_manager: PackageManager::Dnf,
399 ..Default::default()
400 });
401 let cmd = installer.build_package_install_command(&distro, &["migraphx", "migraphx-dev"]);
402 assert!(cmd.args.contains(&"dnf".to_string()));
403 assert!(cmd.args.contains(&"migraphx".to_string()));
404 }
405
406 #[test]
407 fn test_pip_install_command() {
408 let installer = MigraphxInstaller::with_defaults();
409 let cmd = installer.build_pip_install_command();
410 assert_eq!(cmd.program, "python3");
411 assert!(cmd.args.contains(&"-m".to_string()));
412 assert!(cmd.args.contains(&"pip".to_string()));
413 assert!(cmd.args.contains(&"install".to_string()));
414 assert!(cmd.args.contains(&"migraphx".to_string()));
415 }
416
417 #[test]
420 fn test_availability_debian_is_full() {
421 let installer = MigraphxInstaller::with_defaults();
422 let distro = DistroFacade::from_info(DistroInfo {
423 id: "ubuntu".to_string(),
424 family: DistroFamily::Debian,
425 pkg_manager: PackageManager::Apt,
426 ..Default::default()
427 });
428 assert_eq!(
429 installer.is_available_on_distro(&distro),
430 MigraphxSupport::Full
431 );
432 }
433
434 #[test]
435 fn test_availability_arch_is_system_only() {
436 let installer = MigraphxInstaller::with_defaults();
437 let distro = DistroFacade::from_info(DistroInfo {
438 id: "arch".to_string(),
439 family: DistroFamily::Arch,
440 pkg_manager: PackageManager::Pacman,
441 ..Default::default()
442 });
443 assert_eq!(
444 installer.is_available_on_distro(&distro),
445 MigraphxSupport::SystemOnly
446 );
447 }
448
449 #[test]
450 fn test_availability_cachyos_is_system_only() {
451 let installer = MigraphxInstaller::with_defaults();
452 let distro = DistroFacade::from_info(DistroInfo {
453 id: "cachyos".to_string(),
454 family: DistroFamily::Arch,
455 pkg_manager: PackageManager::Pacman,
456 ..Default::default()
457 });
458 assert_eq!(
459 installer.is_available_on_distro(&distro),
460 MigraphxSupport::SystemOnly
461 );
462 }
463
464 #[test]
465 fn test_availability_rhel_is_full() {
466 let installer = MigraphxInstaller::with_defaults();
467 let distro = DistroFacade::from_info(DistroInfo {
468 id: "fedora".to_string(),
469 family: DistroFamily::Rhel,
470 pkg_manager: PackageManager::Dnf,
471 ..Default::default()
472 });
473 assert_eq!(
474 installer.is_available_on_distro(&distro),
475 MigraphxSupport::Full
476 );
477 }
478
479 #[test]
480 fn test_limitation_message_debian_is_none() {
481 let installer = MigraphxInstaller::with_defaults();
482 let distro = DistroFacade::from_info(DistroInfo {
483 id: "ubuntu".to_string(),
484 family: DistroFamily::Debian,
485 pkg_manager: PackageManager::Apt,
486 ..Default::default()
487 });
488 assert!(installer.build_limitation_message(&distro).is_none());
489 }
490
491 #[test]
492 fn test_limitation_message_arch_is_some() {
493 let installer = MigraphxInstaller::with_defaults();
494 let distro = DistroFacade::from_info(DistroInfo {
495 id: "arch".to_string(),
496 family: DistroFamily::Arch,
497 pkg_manager: PackageManager::Pacman,
498 ..Default::default()
499 });
500 let msg = installer
501 .build_limitation_message(&distro)
502 .expect("Arch should have a limitation message");
503 assert!(
504 msg.contains("limited support"),
505 "Message should mention limited support: {msg}"
506 );
507 assert!(
508 msg.contains("Python bindings"),
509 "Message should mention Python bindings: {msg}"
510 );
511 assert!(
512 msg.contains("Arch"),
513 "Message should mention Arch context: {msg}"
514 );
515 }
516
517 #[test]
518 fn test_should_skip_pip_debian_is_false() {
519 let installer = MigraphxInstaller::with_defaults();
520 let distro = DistroFacade::from_info(DistroInfo {
521 id: "ubuntu".to_string(),
522 family: DistroFamily::Debian,
523 pkg_manager: PackageManager::Apt,
524 ..Default::default()
525 });
526 assert!(!installer.should_skip_pip_install(&distro));
527 }
528
529 #[test]
530 fn test_should_skip_pip_arch_is_true() {
531 let installer = MigraphxInstaller::with_defaults();
532 let distro = DistroFacade::from_info(DistroInfo {
533 id: "arch".to_string(),
534 family: DistroFamily::Arch,
535 pkg_manager: PackageManager::Pacman,
536 ..Default::default()
537 });
538 assert!(installer.should_skip_pip_install(&distro));
539 }
540
541 #[test]
542 fn test_arch_required_packages_no_dev() {
543 let installer = MigraphxInstaller::with_defaults();
544 let distro = DistroFacade::from_info(DistroInfo {
545 id: "cachyos".to_string(),
546 family: DistroFamily::Arch,
547 pkg_manager: PackageManager::Pacman,
548 ..Default::default()
549 });
550 let pkgs = installer.required_packages(&distro);
551 assert!(
552 pkgs.contains(&"migraphx"),
553 "Arch should have migraphx package"
554 );
555 assert!(
556 !pkgs.contains(&"migraphx-dev"),
557 "Arch should NOT have migraphx-dev (Debian-specific)"
558 );
559 assert!(
560 !pkgs.contains(&"python3-migraphx"),
561 "Arch should NOT have python3-migraphx (Debian-specific)"
562 );
563 }
564
565 #[test]
566 fn test_migraphx_support_display() {
567 assert_eq!(format!("{}", MigraphxSupport::Full), "full");
568 assert_eq!(format!("{}", MigraphxSupport::SystemOnly), "system-only");
569 assert_eq!(format!("{}", MigraphxSupport::Unavailable), "unavailable");
570 }
571}