1use crate::installers::common::DistroFacade;
13use std::fmt;
14
15pub const LEGACY_FALLBACK_VERSION: &str = "7.2.3";
21pub const THEROCK_REPO_ROOT: &str = "https://repo.amd.com/rocm/packages-multi-arch";
23pub const THEROCK_GPG_URL: &str = "https://repo.amd.com/rocm/packages-multi-arch/gpg/rocm.gpg";
25pub const THEROCK_GPG_FINGERPRINT: &str = "D0F004A0025A1145C7807FCD0701EAC4D5E02107";
27pub const RUNFILE_BUILD_DEFAULT: &str = "7";
29pub const THEROCK_PIP_INDEX: &str = "https://repo.amd.com/rocm/whl-multi-arch/";
31pub const PREVIEW_PIP_INDEX: &str = "https://stable.repo.amd.com/rocm/whl-next/";
33pub const THEROCK_TARBALL_ROOT: &str = "https://repo.amd.com/rocm/tarball-multi-arch";
35pub const RUNFILE_ROOT: &str = "https://repo.radeon.com/installer/rocm-runfile-installer";
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub enum RocmChannel {
41 Legacy,
43 Stable,
45 Latest,
47 Preview,
49}
50
51impl fmt::Display for RocmChannel {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match self {
54 RocmChannel::Legacy => write!(f, "legacy"),
55 RocmChannel::Stable => write!(f, "stable"),
56 RocmChannel::Latest => write!(f, "latest"),
57 RocmChannel::Preview => write!(f, "preview"),
58 }
59 }
60}
61
62impl RocmChannel {
63 pub fn from_choice(choice: u8) -> Option<Self> {
65 match choice {
66 1 => Some(RocmChannel::Legacy),
67 2 => Some(RocmChannel::Stable),
68 3 => Some(RocmChannel::Latest),
69 4 => Some(RocmChannel::Preview),
70 _ => None,
71 }
72 }
73
74 pub fn parse(name: &str) -> Option<Self> {
76 match name.trim().to_ascii_lowercase().as_str() {
77 "legacy" => Some(RocmChannel::Legacy),
78 "stable" => Some(RocmChannel::Stable),
79 "latest" => Some(RocmChannel::Latest),
80 "preview" => Some(RocmChannel::Preview),
81 _ => None,
82 }
83 }
84
85 pub fn for_version(version: &str) -> Self {
87 let mut it = version.split('.');
88 let major: u32 = it.next().and_then(|v| v.parse().ok()).unwrap_or(0);
89 let minor: u32 = it.next().and_then(|v| v.parse().ok()).unwrap_or(0);
90 let patch: u32 = it.next().and_then(|v| v.parse().ok()).unwrap_or(0);
91 match (major, minor) {
92 (10.., _) => RocmChannel::Preview,
93 (7, 14..) if patch >= 1 || minor > 14 => RocmChannel::Latest,
94 (7, 14..) => RocmChannel::Stable,
95 _ => RocmChannel::Legacy,
96 }
97 }
98
99 pub fn is_therock(&self) -> bool {
101 !matches!(self, RocmChannel::Legacy)
102 }
103
104 pub fn version(&self) -> &'static str {
106 match self {
107 RocmChannel::Legacy => "7.2.4",
108 RocmChannel::Stable => "7.14.0",
109 RocmChannel::Latest => "7.14.1",
110 RocmChannel::Preview => "10.1.0",
111 }
112 }
113
114 pub fn dir_path(&self) -> &'static str {
116 self.version()
117 }
118
119 pub fn pkg_version(&self) -> &'static str {
122 match self {
123 RocmChannel::Legacy => "7.2.4.70204-1",
124 RocmChannel::Stable => "7.14.0",
125 RocmChannel::Latest => "7.14.1",
126 RocmChannel::Preview => "10.1.0",
127 }
128 }
129
130 pub fn major_minor(&self) -> &'static str {
132 match self {
133 RocmChannel::Legacy => "7.2",
134 RocmChannel::Stable | RocmChannel::Latest => "7.14",
135 RocmChannel::Preview => "10.1",
136 }
137 }
138
139 pub fn version_from_manifest(manifest: &crate::core::manifest::Manifest) -> String {
144 manifest
145 .components
146 .iter()
147 .find(|c| c.id == "rocm")
148 .map(|c| c.version.clone())
149 .unwrap_or_else(|| RocmChannel::Latest.version().to_string())
150 }
151
152 pub fn pkg_version_from_manifest(manifest: &crate::core::manifest::Manifest) -> String {
157 let version = Self::version_from_manifest(manifest);
158 if version == "6.4.3" {
159 return "6.4.60403-1".to_string();
160 }
161 let parts: Vec<&str> = version.split('.').collect();
162 if parts.len() >= 3 {
163 let major: u64 = parts[0].parse().unwrap_or(7);
164 let minor: u64 = parts[1].parse().unwrap_or(2);
165 let patch: u64 = parts[2].parse().unwrap_or(0);
166 if RocmChannel::for_version(&version).is_therock() {
168 return version;
169 }
170 let build = format!("{major}{minor:02}{patch:02}");
171 format!("{}.{build}-1", version)
172 } else {
173 RocmChannel::Latest.pkg_version().to_string()
174 }
175 }
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180pub enum RocmInstallType {
181 Standard,
183 Minimal,
185 Full,
187 Custom,
189}
190
191impl fmt::Display for RocmInstallType {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 match self {
194 RocmInstallType::Standard => write!(f, "standard"),
195 RocmInstallType::Minimal => write!(f, "minimal"),
196 RocmInstallType::Full => write!(f, "full"),
197 RocmInstallType::Custom => write!(f, "custom"),
198 }
199 }
200}
201
202#[derive(Debug, Clone)]
204pub struct RocmConfig {
205 pub channel: RocmChannel,
207 pub install_type: RocmInstallType,
209 pub force_reinstall: bool,
211 pub dry_run: bool,
213 pub gfx_arch: String,
215 pub legacy_fallback: bool,
217}
218
219impl Default for RocmConfig {
220 fn default() -> Self {
221 Self {
222 channel: RocmChannel::Latest,
223 install_type: RocmInstallType::Standard,
224 force_reinstall: false,
225 dry_run: false,
226 gfx_arch: "gfx1100".to_string(),
227 legacy_fallback: false,
228 }
229 }
230}
231
232#[derive(Debug, Clone, PartialEq, Eq)]
234pub struct PackageCommand {
235 pub program: String,
237 pub args: Vec<String>,
239}
240
241impl PackageCommand {
242 pub fn to_command_string(&self) -> String {
244 if self.args.is_empty() {
245 self.program.clone()
246 } else {
247 format!("{} {}", self.program, self.args.join(" "))
248 }
249 }
250}
251
252#[derive(Debug, Clone)]
254pub struct RepoConfig {
255 pub repo_type: String,
257 pub url: String,
259 pub gpg_key_url: String,
261 pub file_path: String,
263}
264
265pub struct RocmInstaller {
267 config: RocmConfig,
268}
269
270impl RocmInstaller {
271 pub fn new(config: RocmConfig) -> Self {
273 Self { config }
274 }
275
276 pub fn with_defaults() -> Self {
278 Self::new(RocmConfig::default())
279 }
280
281 pub fn channel(&self) -> RocmChannel {
283 self.config.channel
284 }
285
286 pub fn effective_version(&self) -> &'static str {
288 if self.config.channel == RocmChannel::Legacy && self.config.legacy_fallback {
289 LEGACY_FALLBACK_VERSION
290 } else {
291 self.config.channel.version()
292 }
293 }
294
295 fn classic_release(&self) -> &'static str {
296 self.effective_version()
297 }
298
299 fn classic_pkg_version(&self) -> &'static str {
300 if self.config.channel == RocmChannel::Legacy && self.config.legacy_fallback {
301 "7.2.3.70203-1"
302 } else {
303 self.config.channel.pkg_version()
304 }
305 }
306
307 pub fn therock_distro_dir(distro: &DistroFacade) -> Option<String> {
314 let v = distro.version();
315 let major = v.split('.').next().unwrap_or("");
316 if distro.uses_apt() {
317 if distro.id() == "debian" {
318 return match major {
319 "12" | "13" => Some(format!("debian{major}")),
320 _ => None,
321 };
322 }
323 let compact = v.replace('.', "");
324 match compact.as_str() {
325 "2204" | "2404" | "2604" => Some(format!("ubuntu{compact}")),
326 _ => match distro.codename() {
327 "jammy" => Some("ubuntu2204".into()),
328 "noble" => Some("ubuntu2404".into()),
329 _ => None,
330 },
331 }
332 } else if distro.uses_dnf() || distro.uses_yum() {
333 match major {
334 "8" | "9" | "10" => Some(format!("rhel{major}")),
335 _ => None,
336 }
337 } else if distro.uses_zypper() {
338 match major {
339 "15" => Some("sles15".into()),
340 "16" => Some("sles16".into()),
341 _ => None,
342 }
343 } else {
344 None
345 }
346 }
347
348 pub fn therock_package(&self) -> String {
350 format!(
351 "amdrocm{}-{}",
352 self.config.channel.major_minor(),
353 self.config.gfx_arch
354 )
355 }
356
357 pub fn therock_apt_version_glob(&self) -> String {
362 format!("{}-*", self.config.channel.version())
363 }
364
365 pub fn therock_apt_commands(
367 &self,
368 distro: &DistroFacade,
369 ) -> Result<Vec<PackageCommand>, String> {
370 let dir = Self::therock_distro_dir(distro).ok_or_else(|| {
371 format!(
372 "ROCm {} has no apt repo for {} {} (supported: Ubuntu 22.04/24.04/26.04, Debian 12/13)",
373 self.config.channel.version(),
374 distro.id(),
375 distro.version()
376 )
377 })?;
378 let sh = |script: String| PackageCommand {
379 program: "sudo".to_string(),
380 args: vec!["sh".to_string(), "-c".to_string(), script],
381 };
382 let key_script = format!(
385 "set -e; mkdir -p /etc/apt/keyrings; \
386 wget -qO /tmp/rocm-multi-arch.gpg {THEROCK_GPG_URL}; \
387 gpg --show-keys --with-colons /tmp/rocm-multi-arch.gpg | grep -q '^fpr:::::::::{THEROCK_GPG_FINGERPRINT}:' \
388 || {{ echo 'ROCm repo key fingerprint mismatch' >&2; rm -f /tmp/rocm-multi-arch.gpg; exit 1; }}; \
389 mv /tmp/rocm-multi-arch.gpg /etc/apt/keyrings/rocm-multi-arch.gpg"
390 );
391 Ok(vec![
392 sh(key_script),
393 sh(format!(
394 "echo 'deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm-multi-arch.gpg] {THEROCK_REPO_ROOT}/{dir} stable main' > /etc/apt/sources.list.d/rocm-multi-arch.list"
395 )),
396 PackageCommand {
397 program: "sudo".to_string(),
398 args: vec!["apt-get".to_string(), "update".to_string()],
399 },
400 PackageCommand {
401 program: "sudo".to_string(),
402 args: vec![
403 "apt-get".to_string(),
404 "install".to_string(),
405 "-y".to_string(),
406 format!("{}={}", self.therock_package(), self.therock_apt_version_glob()),
407 ],
408 },
409 ])
410 }
411
412 pub fn therock_rpm_commands(
414 &self,
415 distro: &DistroFacade,
416 ) -> Result<Vec<PackageCommand>, String> {
417 let dir = Self::therock_distro_dir(distro).ok_or_else(|| {
418 format!(
419 "ROCm {} has no rpm repo for {} {} (supported: RHEL 8/9/10, SLES 15/16)",
420 self.config.channel.version(),
421 distro.id(),
422 distro.version()
423 )
424 })?;
425 let url = format!("{THEROCK_REPO_ROOT}/{dir}/x86_64");
426 let pkg = format!(
427 "{}-{}",
428 self.therock_package(),
429 self.config.channel.version()
430 );
431 let sudo = |args: Vec<&str>| PackageCommand {
432 program: "sudo".to_string(),
433 args: args.into_iter().map(String::from).collect(),
434 };
435 let import_key = PackageCommand {
436 program: "sh".to_string(),
437 args: vec![
438 "-c".to_string(),
439 format!(
440 "set -e; wget -qO /tmp/rocm-multi-arch.gpg {THEROCK_GPG_URL}; \
441 gpg --show-keys --with-colons /tmp/rocm-multi-arch.gpg | grep -q '^fpr:::::::::{THEROCK_GPG_FINGERPRINT}:' \
442 || {{ echo 'ROCm GPG key fingerprint mismatch' >&2; exit 1; }}; \
443 sudo rpm --import /tmp/rocm-multi-arch.gpg"
444 ),
445 ],
446 };
447 Ok(if distro.uses_zypper() {
448 vec![
449 import_key,
450 sudo(vec![
451 "zypper",
452 "--non-interactive",
453 "addrepo",
454 "--refresh",
455 &url,
456 "rocm-multi-arch",
457 ]),
458 sudo(vec![
459 "zypper",
460 "--non-interactive",
461 "refresh",
462 ]),
463 sudo(vec!["zypper", "--non-interactive", "install", &pkg]),
464 ]
465 } else {
466 vec![
467 import_key,
468 sudo(vec!["dnf", "config-manager", "--add-repo", &url]),
469 sudo(vec!["dnf", "install", "-y", &pkg]),
470 ]
471 })
472 }
473
474 pub fn pip_requirement(&self) -> String {
477 format!(
478 "rocm[libraries,device-all]=={}",
479 self.config.channel.version()
480 )
481 }
482
483 pub fn pip_index_url(&self) -> Option<&'static str> {
485 match self.config.channel {
486 RocmChannel::Legacy => None,
487 RocmChannel::Stable | RocmChannel::Latest => Some(THEROCK_PIP_INDEX),
488 RocmChannel::Preview => Some(PREVIEW_PIP_INDEX),
489 }
490 }
491
492 pub fn therock_family(gfx: &str) -> Option<String> {
494 let g = gfx.trim().to_ascii_lowercase();
495 let fam = match g.as_str() {
496 s if s.starts_with("gfx101") => "gfx101X-dgpu",
497 s if s.starts_with("gfx103") => "gfx103X-all",
498 s if s.starts_with("gfx110") => "gfx110X-all",
499 s if s.starts_with("gfx115") => "gfx115X-all",
500 s if s.starts_with("gfx120") => "gfx120X-all",
501 "gfx908" => "gfx908",
502 "gfx90a" => "gfx90a",
503 "gfx942" | "gfx941" | "gfx940" => "gfx94X-dcgpu",
504 "gfx950" => "gfx950-dcgpu",
505 _ => return None,
506 };
507 Some(fam.to_string())
508 }
509
510 pub fn tarball_url(&self) -> Result<String, String> {
512 let family = Self::therock_family(&self.config.gfx_arch).ok_or_else(|| {
513 format!(
514 "no TheRock tarball family known for GPU architecture '{}'",
515 self.config.gfx_arch
516 )
517 })?;
518 Ok(format!(
519 "{THEROCK_TARBALL_ROOT}/therock-dist-linux-{family}-{}.tar.gz",
520 self.config.channel.version()
521 ))
522 }
523
524 pub fn runfile_url(&self) -> String {
529 let build = std::env::var("ROCM_RUNFILE_BUILD")
530 .ok()
531 .map(|b| b.trim().to_string())
532 .filter(|b| !b.is_empty() && b.chars().all(|c| c.is_ascii_digit()))
533 .unwrap_or_else(|| RUNFILE_BUILD_DEFAULT.to_string());
534 let v = self.config.channel.version();
535 let mm = self.config.channel.major_minor();
536 format!("{RUNFILE_ROOT}/rocm-rel-{mm}/rocm-installer-{v}-{build}.run")
537 }
538
539 pub fn repo_base_url(&self, distro: &DistroFacade) -> String {
554 let major_minor = self.config.channel.major_minor();
555 if distro.uses_apt() {
556 let codename = if distro.codename().is_empty() {
557 "noble"
558 } else {
559 distro.codename()
560 };
561 format!("https://repo.radeon.com/amdgpu/{major_minor}/ubuntu/{codename}")
562 } else if distro.uses_dnf() || distro.uses_yum() {
563 let rhel_version = Self::rhel_version(distro);
564 format!("https://repo.radeon.com/amdgpu/{major_minor}/rhel/{rhel_version}")
565 } else if distro.uses_zypper() {
566 format!("https://repo.radeon.com/amdgpu/{major_minor}/sle/15.7")
567 } else {
568 format!("https://repo.radeon.com/amdgpu/{major_minor}/ubuntu/noble")
570 }
571 }
572
573 pub fn rocm_core_repo_url(&self, _distro: &DistroFacade) -> String {
575 let major_minor = self.config.channel.major_minor();
576 format!("https://repo.radeon.com/rocm/apt/{major_minor}")
577 }
578
579 pub fn gpg_key_url(&self) -> &'static str {
581 "https://repo.radeon.com/rocm/rocm.gpg.key"
582 }
583
584 fn rhel_version(distro: &DistroFacade) -> &'static str {
586 let v = distro.version();
587 if v.starts_with("10") {
588 "10"
589 } else if v.starts_with("9") {
590 "9"
591 } else if v.starts_with("8") {
592 "8"
593 } else {
594 "9"
595 }
596 }
597
598 pub fn amdgpu_install_deb_url(&self) -> String {
604 let release_dir = self.classic_release();
605 let pkg_ver = self.classic_pkg_version();
606 format!(
607 "https://repo.radeon.com/amdgpu-install/{release_dir}/ubuntu/noble/amdgpu-install_{pkg_ver}_all.deb"
608 )
609 }
610
611 pub fn amdgpu_install_rpm_url(&self, rhel_version: &str) -> String {
613 let release_dir = self.classic_release();
614 let pkg_ver = self.classic_pkg_version();
615 format!(
616 "https://repo.radeon.com/amdgpu-install/{release_dir}/rhel/{rhel_version}/amdgpu-install-{pkg_ver}.el{rhel_version}.noarch.rpm"
617 )
618 }
619
620 pub fn amdgpu_install_sles_rpm_url(&self) -> String {
622 let release_dir = self.classic_release();
623 let pkg_ver = self.classic_pkg_version();
624 format!(
625 "https://repo.radeon.com/amdgpu-install/{release_dir}/sle/15.7/amdgpu-install-{pkg_ver}.noarch.rpm"
626 )
627 }
628
629 pub fn apt_install_commands(&self) -> Vec<PackageCommand> {
631 debug_assert!(
632 !self.config.channel.is_therock(),
633 "TheRock channels use therock_*_commands"
634 );
635 let deb_url = self.amdgpu_install_deb_url();
636 vec![
637 PackageCommand {
639 program: "wget".to_string(),
640 args: vec!["-q".to_string(), deb_url.clone()],
641 },
642 PackageCommand {
644 program: "sudo".to_string(),
645 args: vec![
646 "dpkg".to_string(),
647 "-i".to_string(),
648 format!("amdgpu-install_{}_all.deb", self.classic_pkg_version()),
649 ],
650 },
651 PackageCommand {
653 program: "sudo".to_string(),
654 args: vec!["apt-get".to_string(), "update".to_string()],
655 },
656 self.apt_rocm_metapackage_command(),
658 ]
659 }
660
661 fn apt_rocm_metapackage_command(&self) -> PackageCommand {
663 let metapkg = match self.config.install_type {
664 RocmInstallType::Standard => "rocm-libs",
665 RocmInstallType::Minimal => "rocm-core",
666 RocmInstallType::Full => "rocm-dev",
667 RocmInstallType::Custom => "rocm-libs",
668 };
669 PackageCommand {
670 program: "sudo".to_string(),
671 args: vec![
672 "apt-get".to_string(),
673 "install".to_string(),
674 "-y".to_string(),
675 metapkg.to_string(),
676 ],
677 }
678 }
679
680 pub fn dnf_install_commands(&self, rhel_version: &str) -> Vec<PackageCommand> {
682 debug_assert!(
683 !self.config.channel.is_therock(),
684 "TheRock channels use therock_*_commands"
685 );
686 let rpm_url = self.amdgpu_install_rpm_url(rhel_version);
687 vec![
688 PackageCommand {
689 program: "sudo".to_string(),
690 args: vec![
691 "dnf".to_string(),
692 "install".to_string(),
693 "-y".to_string(),
694 rpm_url,
695 ],
696 },
697 self.dnf_rocm_metapackage_command(),
698 ]
699 }
700
701 fn dnf_rocm_metapackage_command(&self) -> PackageCommand {
703 let metapkg = match self.config.install_type {
704 RocmInstallType::Standard => "rocm-libs",
705 RocmInstallType::Minimal => "rocm-core",
706 RocmInstallType::Full => "rocm-dev",
707 RocmInstallType::Custom => "rocm-libs",
708 };
709 PackageCommand {
710 program: "sudo".to_string(),
711 args: vec![
712 "dnf".to_string(),
713 "install".to_string(),
714 "-y".to_string(),
715 metapkg.to_string(),
716 ],
717 }
718 }
719
720 pub fn pacman_install_commands(&self, aur_helper: &str) -> Vec<PackageCommand> {
733 let packages = self.pacman_rocm_packages();
734 let mut args = vec![
735 "-S".to_string(),
736 "--needed".to_string(),
737 "--noconfirm".to_string(),
738 ];
739 args.extend(packages);
740
741 vec![PackageCommand {
742 program: aur_helper.to_string(),
743 args,
744 }]
745 }
746
747 pub fn pacman_rocm_packages(&self) -> Vec<String> {
749 let mut pkgs = vec![
750 "rocm-hip-sdk".to_string(),
751 "rocminfo".to_string(),
752 "rocm-smi-lib".to_string(),
753 "hipblas".to_string(),
754 "rocblas".to_string(),
755 "hipsparselt".to_string(),
756 ];
757 if self.config.channel != RocmChannel::Legacy {
759 pkgs.push("rocm-opencl-sdk".to_string());
760 pkgs.push("rccl".to_string());
761 pkgs.push("rocwmma".to_string());
765 }
766 pkgs
767 }
768
769 pub fn zypper_install_commands(&self) -> Vec<PackageCommand> {
771 debug_assert!(
772 !self.config.channel.is_therock(),
773 "TheRock channels use therock_*_commands"
774 );
775 let rpm_url = self.amdgpu_install_sles_rpm_url();
776 vec![
777 PackageCommand {
778 program: "sudo".to_string(),
779 args: vec![
780 "zypper".to_string(),
781 "--non-interactive".to_string(),
782 "install".to_string(),
783 rpm_url,
784 ],
785 },
786 PackageCommand {
787 program: "sudo".to_string(),
788 args: vec![
789 "zypper".to_string(),
790 "--non-interactive".to_string(),
791 "install".to_string(),
792 "-y".to_string(),
793 "rocm-libs".to_string(),
794 ],
795 },
796 ]
797 }
798
799 pub fn apt_repo_config(&self, distro: &DistroFacade) -> RepoConfig {
801 let codename = if distro.codename().is_empty() {
802 "noble"
803 } else {
804 distro.codename()
805 };
806 let major_minor = self.config.channel.major_minor();
807 RepoConfig {
808 repo_type: "apt".to_string(),
809 url: format!("deb [signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/{major_minor} {codename} main"),
810 gpg_key_url: "https://repo.radeon.com/rocm/rocm.gpg.key".to_string(),
811 file_path: "/etc/apt/sources.list.d/rocm.list".to_string(),
812 }
813 }
814
815 pub fn amdgpu_apt_repo_config(&self, distro: &DistroFacade) -> RepoConfig {
817 let codename = if distro.codename().is_empty() {
818 "noble"
819 } else {
820 distro.codename()
821 };
822 let major_minor = self.config.channel.major_minor();
823 RepoConfig {
824 repo_type: "apt".to_string(),
825 url: format!("deb [signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/amdgpu/{major_minor}/ubuntu/{codename} {codename} main"),
826 gpg_key_url: "https://repo.radeon.com/rocm/rocm.gpg.key".to_string(),
827 file_path: "/etc/apt/sources.list.d/amdgpu.list".to_string(),
828 }
829 }
830
831 pub fn apt_pin_config(&self) -> (String, String) {
833 (
834 "Package: *\nPin: release o=repo.radeon.com\nPin-Priority: 600\n".to_string(),
835 "/etc/apt/preferences.d/rocm-pin-600".to_string(),
836 )
837 }
838
839 pub fn build_pacman_check_installed_command(package: &str) -> PackageCommand {
844 PackageCommand {
845 program: "pacman".to_string(),
846 args: vec!["-Qi".to_string(), package.to_string()],
847 }
848 }
849
850 pub fn filter_already_installed_pacman(packages: &[String]) -> Vec<String> {
856 use std::process::Command;
857
858 packages
859 .iter()
860 .filter(|pkg| {
861 let status = Command::new("pacman").args(["-Qi", pkg.as_str()]).output();
862 match status {
863 Ok(output) => !output.status.success(),
864 Err(_) => true, }
866 })
867 .cloned()
868 .collect()
869 }
870
871 pub fn is_up_to_date_output(output: &str) -> bool {
880 let lower = output.to_lowercase();
881 lower.contains("up to date -- skipping")
883 || lower.contains("up to date, skipping")
884 || lower.contains("up to date -- reinstalling")
885 || lower.contains("there is nothing to do")
886 || lower.contains("already installed")
887 || lower.contains("warning:") && lower.contains("up to date")
889 }
890}
891
892#[cfg(test)]
897mod tests {
898 use super::*;
899 use crate::platform::detection::{DistroFamily, DistroInfo, PackageManager};
900
901 fn ubuntu_distro() -> DistroFacade {
902 DistroFacade::from_info(DistroInfo {
903 id: "ubuntu".to_string(),
904 name: "Ubuntu 24.04 LTS".to_string(),
905 version: "24.04".to_string(),
906 codename: "noble".to_string(),
907 family: DistroFamily::Debian,
908 pkg_manager: PackageManager::Apt,
909 id_like: "debian".to_string(),
910 })
911 }
912
913 fn fedora_distro() -> DistroFacade {
914 DistroFacade::from_info(DistroInfo {
915 id: "fedora".to_string(),
916 name: "Fedora Linux 41".to_string(),
917 version: "41".to_string(),
918 codename: String::new(),
919 family: DistroFamily::Rhel,
920 pkg_manager: PackageManager::Dnf,
921 id_like: "fedora".to_string(),
922 })
923 }
924
925 fn rhel9_distro() -> DistroFacade {
926 DistroFacade::from_info(DistroInfo {
927 id: "rhel".to_string(),
928 name: "Red Hat Enterprise Linux 9".to_string(),
929 version: "9.3".to_string(),
930 codename: String::new(),
931 family: DistroFamily::Rhel,
932 pkg_manager: PackageManager::Dnf,
933 id_like: "rhel".to_string(),
934 })
935 }
936
937 fn suse_distro() -> DistroFacade {
938 DistroFacade::from_info(DistroInfo {
939 id: "opensuse-leap".to_string(),
940 name: "openSUSE Leap 15.7".to_string(),
941 version: "15.7".to_string(),
942 codename: String::new(),
943 family: DistroFamily::Suse,
944 pkg_manager: PackageManager::Zypper,
945 id_like: "suse".to_string(),
946 })
947 }
948
949 #[test]
952 fn test_channel_versions() {
953 assert_eq!(RocmChannel::Legacy.version(), "7.2.4");
954 assert_eq!(RocmChannel::Legacy.pkg_version(), "7.2.4.70204-1");
955 assert_eq!(RocmChannel::Legacy.major_minor(), "7.2");
956 assert_eq!(RocmChannel::Stable.version(), "7.14.0");
957 assert_eq!(RocmChannel::Latest.version(), "7.14.1");
958 assert_eq!(RocmChannel::Stable.major_minor(), "7.14");
959 assert_eq!(RocmChannel::Preview.version(), "10.1.0");
960 assert_eq!(RocmChannel::Preview.major_minor(), "10.1");
961 assert!(!RocmChannel::Legacy.is_therock());
962 assert!(RocmChannel::Preview.is_therock());
963 }
964
965 #[test]
966 fn test_channel_from_choice() {
967 assert_eq!(RocmChannel::from_choice(1), Some(RocmChannel::Legacy));
968 assert_eq!(RocmChannel::from_choice(2), Some(RocmChannel::Stable));
969 assert_eq!(RocmChannel::from_choice(3), Some(RocmChannel::Latest));
970 assert_eq!(RocmChannel::from_choice(4), Some(RocmChannel::Preview));
971 assert_eq!(RocmChannel::from_choice(0), None);
972 assert_eq!(RocmChannel::from_choice(5), None);
973 }
974
975 #[test]
976 fn test_channel_parse_and_for_version() {
977 assert_eq!(RocmChannel::parse("Preview"), Some(RocmChannel::Preview));
978 assert_eq!(RocmChannel::parse("bogus"), None);
979 assert_eq!(RocmChannel::for_version("10.1.0"), RocmChannel::Preview);
980 assert_eq!(RocmChannel::for_version("7.14.1"), RocmChannel::Latest);
981 assert_eq!(RocmChannel::for_version("7.14.0"), RocmChannel::Stable);
982 assert_eq!(RocmChannel::for_version("7.2.4"), RocmChannel::Legacy);
983 assert_eq!(RocmChannel::for_version("6.4.3"), RocmChannel::Legacy);
984 }
985
986 #[test]
987 fn test_legacy_fallback_723() {
988 let i = RocmInstaller::new(RocmConfig {
989 channel: RocmChannel::Legacy,
990 legacy_fallback: true,
991 ..Default::default()
992 });
993 assert_eq!(i.effective_version(), "7.2.3");
994 assert!(i
995 .amdgpu_install_deb_url()
996 .contains("7.2.3/ubuntu/noble/amdgpu-install_7.2.3.70203-1_all.deb"));
997 }
998
999 #[test]
1000 fn test_therock_urls_and_package() {
1001 let i = RocmInstaller::new(RocmConfig {
1002 channel: RocmChannel::Stable,
1003 gfx_arch: "gfx1100".into(),
1004 ..Default::default()
1005 });
1006 assert_eq!(i.therock_package(), "amdrocm7.14-gfx1100");
1007 assert_eq!(i.pip_requirement(), "rocm[libraries,device-all]==7.14.0");
1008 assert_eq!(
1009 i.pip_index_url(),
1010 Some("https://repo.amd.com/rocm/whl-multi-arch/")
1011 );
1012 assert_eq!(
1013 i.tarball_url().unwrap(),
1014 "https://repo.amd.com/rocm/tarball-multi-arch/therock-dist-linux-gfx110X-all-7.14.0.tar.gz"
1015 );
1016 assert_eq!(
1017 i.runfile_url(),
1018 "https://repo.radeon.com/installer/rocm-runfile-installer/rocm-rel-7.14/rocm-installer-7.14.0-7.run"
1019 );
1020 }
1021
1022 #[test]
1023 fn test_therock_apt_commands_latest() {
1024 let i = RocmInstaller::new(RocmConfig {
1025 channel: RocmChannel::Latest,
1026 ..Default::default()
1027 });
1028 let cmds = i.therock_apt_commands(&ubuntu_distro()).unwrap();
1029 let all: String = cmds
1030 .iter()
1031 .map(|c| c.to_command_string())
1032 .collect::<Vec<_>>()
1033 .join("\n");
1034 assert!(all.contains("packages-multi-arch/gpg/rocm.gpg"));
1035 assert!(all.contains("packages-multi-arch/ubuntu2404 stable main"));
1036 assert!(all.contains("amdrocm7.14-gfx1100=7.14.1-*"));
1037 }
1038
1039 #[test]
1040 fn test_therock_rpm_and_distro_dir() {
1041 let i = RocmInstaller::new(RocmConfig {
1042 channel: RocmChannel::Latest,
1043 ..Default::default()
1044 });
1045 assert_eq!(
1046 RocmInstaller::therock_distro_dir(&ubuntu_distro()).as_deref(),
1047 Some("ubuntu2404")
1048 );
1049 assert_eq!(
1050 RocmInstaller::therock_distro_dir(&rhel9_distro()).as_deref(),
1051 Some("rhel9")
1052 );
1053 let cmds = i.therock_rpm_commands(&rhel9_distro()).unwrap();
1054 assert!(cmds
1055 .iter()
1056 .any(|c| c.to_command_string().contains("amdrocm7.14-gfx1100-7.14.1")));
1057 }
1058
1059 #[test]
1060 fn test_therock_unknown_gpu_and_distro_error() {
1061 let i = RocmInstaller::new(RocmConfig {
1062 channel: RocmChannel::Latest,
1063 gfx_arch: "gfx9999".into(),
1064 ..Default::default()
1065 });
1066 assert!(i.tarball_url().is_err());
1067 assert_eq!(RocmInstaller::therock_family("gfx9999"), None);
1068 }
1069
1070 #[test]
1071 fn test_therock_apt_pins_gpg_fingerprint() {
1072 let i = RocmInstaller::new(RocmConfig {
1073 channel: RocmChannel::Latest,
1074 ..Default::default()
1075 });
1076 let all: String = i
1077 .therock_apt_commands(&ubuntu_distro())
1078 .unwrap()
1079 .iter()
1080 .map(|c| c.to_command_string())
1081 .collect();
1082 assert!(all.contains(THEROCK_GPG_FINGERPRINT));
1083 }
1084
1085 #[test]
1086 fn test_pkg_version_classification_matches_for_version() {
1087 for v in ["8.0.0", "9.1.0", "7.2.4", "7.13.0"] {
1088 assert!(!RocmChannel::for_version(v).is_therock(), "{v}");
1089 }
1090 for v in ["7.14.0", "7.14.1", "10.1.0"] {
1091 assert!(RocmChannel::for_version(v).is_therock(), "{v}");
1092 }
1093 }
1094
1095 #[test]
1096 fn test_preview_channel_consistency() {
1097 let ch = RocmChannel::from_choice(4).unwrap();
1098 assert_eq!(ch, RocmChannel::Preview);
1099 assert_eq!(ch.version(), "10.1.0");
1100 assert_eq!(ch.major_minor(), "10.1");
1101 let i = RocmInstaller::new(RocmConfig {
1102 channel: ch,
1103 ..Default::default()
1104 });
1105 assert_eq!(i.pip_requirement(), "rocm[libraries,device-all]==10.1.0");
1106 }
1107
1108 #[test]
1109 fn test_preview_pip_index() {
1110 let i = RocmInstaller::new(RocmConfig {
1111 channel: RocmChannel::Preview,
1112 ..Default::default()
1113 });
1114 assert_eq!(
1115 i.pip_index_url(),
1116 Some("https://stable.repo.amd.com/rocm/whl-next/")
1117 );
1118 assert_eq!(i.pip_requirement(), "rocm[libraries,device-all]==10.1.0");
1119 }
1120
1121 #[test]
1122 fn test_pkg_version_from_manifest_latest() {
1123 let manifest = crate::core::manifest::Manifest {
1124 schema_version: 2,
1125 sequence: 1,
1126 generated_at: "2026-05-31T00:00:00Z".to_string(),
1127 expires_at: None,
1128 min_runtime_version: String::new(),
1129 components: vec![crate::core::manifest::ManifestComponent {
1130 id: "rocm".to_string(),
1131 version: "7.2.4".to_string(),
1132 script: "scripts/install_rocm.sh".to_string(),
1133 category: crate::core::Category::Foundation,
1134 validation_tier: crate::core::ValidationTier::Validated,
1135 min_rocm_version: String::new(),
1136 dependencies: vec![],
1137 compatible_channels: vec!["latest".to_string()],
1138 exclusive_group: String::new(),
1139 }],
1140 signature: None,
1141 };
1142
1143 assert_eq!(
1144 RocmChannel::pkg_version_from_manifest(&manifest),
1145 "7.2.4.70204-1"
1146 );
1147 }
1148
1149 #[test]
1150 fn test_pkg_version_from_manifest_legacy() {
1151 let manifest = crate::core::manifest::Manifest {
1152 schema_version: 2,
1153 sequence: 1,
1154 generated_at: "2026-05-31T00:00:00Z".to_string(),
1155 expires_at: None,
1156 min_runtime_version: String::new(),
1157 components: vec![crate::core::manifest::ManifestComponent {
1158 id: "rocm".to_string(),
1159 version: "6.4.3".to_string(),
1160 script: "scripts/install_rocm.sh".to_string(),
1161 category: crate::core::Category::Foundation,
1162 validation_tier: crate::core::ValidationTier::Validated,
1163 min_rocm_version: String::new(),
1164 dependencies: vec![],
1165 compatible_channels: vec!["legacy".to_string()],
1166 exclusive_group: String::new(),
1167 }],
1168 signature: None,
1169 };
1170
1171 assert_eq!(
1172 RocmChannel::pkg_version_from_manifest(&manifest),
1173 "6.4.60403-1"
1174 );
1175 }
1176
1177 #[test]
1180 fn test_apt_install_commands_latest() {
1181 let installer = RocmInstaller::new(RocmConfig {
1182 channel: RocmChannel::Legacy,
1183 install_type: RocmInstallType::Standard,
1184 ..Default::default()
1185 });
1186 let cmds = installer.apt_install_commands();
1187 assert_eq!(cmds.len(), 4);
1188
1189 assert_eq!(cmds[0].program, "wget");
1191 assert!(cmds[0]
1192 .args
1193 .iter()
1194 .any(|a| a.contains("7.2.4/ubuntu/noble")));
1195 assert!(cmds[0].args.iter().any(|a| a.contains("7.2.4.70204-1")));
1196 assert_eq!(cmds[1].program, "sudo");
1197 assert!(cmds[1].args.contains(&"dpkg".to_string()));
1198
1199 assert_eq!(cmds[2].program, "sudo");
1201 assert!(cmds[2].args.contains(&"update".to_string()));
1202
1203 assert_eq!(cmds[3].program, "sudo");
1205 assert!(cmds[3].args.contains(&"rocm-libs".to_string()));
1206 }
1207
1208 #[test]
1209 fn test_apt_install_commands_full() {
1210 let installer = RocmInstaller::new(RocmConfig {
1211 channel: RocmChannel::Legacy,
1212 install_type: RocmInstallType::Full,
1213 ..Default::default()
1214 });
1215 let cmds = installer.apt_install_commands();
1216 let meta_cmd = &cmds[3];
1217 assert!(meta_cmd.args.contains(&"rocm-dev".to_string()));
1218 }
1219
1220 #[test]
1221 fn test_dnf_install_commands() {
1222 let installer = RocmInstaller::new(RocmConfig {
1223 channel: RocmChannel::Legacy,
1224 install_type: RocmInstallType::Standard,
1225 ..Default::default()
1226 });
1227 let cmds = installer.dnf_install_commands("9");
1228 assert_eq!(cmds.len(), 2);
1229
1230 assert_eq!(cmds[0].program, "sudo");
1232 assert!(cmds[0].args.iter().any(|a| a.contains("7.2.4/rhel/9")));
1233 assert!(cmds[0].args.iter().any(|a| a.contains("7.2.4.70204-1")));
1234 assert!(cmds[0].args.iter().any(|a| a.contains("el9")));
1235
1236 assert_eq!(cmds[1].program, "sudo");
1238 assert!(cmds[1].args.contains(&"rocm-libs".to_string()));
1239 }
1240
1241 #[test]
1242 fn test_pacman_install_commands() {
1243 let installer = RocmInstaller::new(RocmConfig {
1244 channel: RocmChannel::Latest,
1245 install_type: RocmInstallType::Standard,
1246 ..Default::default()
1247 });
1248 let cmds = installer.pacman_install_commands("yay");
1249 assert_eq!(
1254 cmds.len(),
1255 1,
1256 "pacman install must be a single user-space yay command"
1257 );
1258 assert_eq!(cmds[0].program, "yay");
1259 assert!(
1260 !cmds.iter().any(|c| c.program == "sudo"),
1261 "yay must not run under sudo"
1262 );
1263 assert!(cmds[0].args.contains(&"-S".to_string()));
1264 assert!(cmds[0].args.contains(&"--needed".to_string()));
1265 assert!(cmds[0].args.contains(&"--noconfirm".to_string()));
1266 let pkgs = installer.pacman_rocm_packages();
1268 assert!(pkgs.contains(&"rocm-hip-sdk".to_string()));
1269 assert!(pkgs.contains(&"rocminfo".to_string()));
1270 assert!(pkgs.contains(&"rocm-opencl-sdk".to_string())); assert!(pkgs.contains(&"rccl".to_string())); assert!(pkgs.contains(&"rocwmma".to_string())); for p in &pkgs {
1274 assert!(cmds[0].args.contains(p), "yay args missing package {p}");
1275 }
1276 }
1277
1278 #[test]
1279 fn test_pacman_legacy_no_extras() {
1280 let installer = RocmInstaller::new(RocmConfig {
1281 channel: RocmChannel::Legacy,
1282 install_type: RocmInstallType::Standard,
1283 ..Default::default()
1284 });
1285 let pkgs = installer.pacman_rocm_packages();
1286 assert!(!pkgs.contains(&"rocm-opencl-sdk".to_string()));
1287 assert!(!pkgs.contains(&"rccl".to_string()));
1288 assert!(!pkgs.contains(&"rocwmma".to_string())); }
1290
1291 #[test]
1292 fn test_zypper_install_commands() {
1293 let installer = RocmInstaller::new(RocmConfig {
1294 channel: RocmChannel::Legacy,
1295 install_type: RocmInstallType::Standard,
1296 ..Default::default()
1297 });
1298 let cmds = installer.zypper_install_commands();
1299 assert_eq!(cmds.len(), 2);
1300 assert_eq!(cmds[0].program, "sudo");
1301 assert!(cmds[0].args.iter().any(|a| a.contains("zypper")));
1302 assert!(cmds[0].args.iter().any(|a| a.contains("7.2.4/sle/15.7")));
1303 assert!(cmds[0].args.iter().any(|a| a.contains("7.2.4.70204-1")));
1304 }
1305
1306 #[test]
1309 fn test_repo_base_url_ubuntu() {
1310 let installer = RocmInstaller::new(RocmConfig {
1311 channel: RocmChannel::Legacy,
1312 ..Default::default()
1313 });
1314 let distro = ubuntu_distro();
1315 let url = installer.repo_base_url(&distro);
1316 assert_eq!(url, "https://repo.radeon.com/amdgpu/7.2/ubuntu/noble");
1317 }
1318
1319 #[test]
1320 fn test_repo_base_url_rhel() {
1321 let installer = RocmInstaller::new(RocmConfig {
1322 channel: RocmChannel::Legacy,
1323 ..Default::default()
1324 });
1325 let distro = rhel9_distro();
1326 let url = installer.repo_base_url(&distro);
1327 assert_eq!(url, "https://repo.radeon.com/amdgpu/7.2/rhel/9");
1328 }
1329
1330 #[test]
1331 fn test_repo_base_url_fedora() {
1332 let installer = RocmInstaller::new(RocmConfig {
1333 channel: RocmChannel::Legacy,
1334 ..Default::default()
1335 });
1336 let distro = fedora_distro();
1337 let url = installer.repo_base_url(&distro);
1338 assert_eq!(url, "https://repo.radeon.com/amdgpu/7.2/rhel/9");
1340 }
1341
1342 #[test]
1343 fn test_repo_base_url_suse() {
1344 let installer = RocmInstaller::new(RocmConfig {
1345 channel: RocmChannel::Legacy,
1346 ..Default::default()
1347 });
1348 let distro = suse_distro();
1349 let url = installer.repo_base_url(&distro);
1350 assert_eq!(url, "https://repo.radeon.com/amdgpu/7.2/sle/15.7");
1351 }
1352
1353 #[test]
1354 fn test_amdgpu_install_deb_url() {
1355 let installer = RocmInstaller::new(RocmConfig {
1356 channel: RocmChannel::Legacy,
1357 ..Default::default()
1358 });
1359 let url = installer.amdgpu_install_deb_url();
1360 assert!(url.contains("7.2.4/ubuntu/noble"));
1361 assert!(url.contains("7.2.4.70204-1"));
1362 assert!(url.contains("ubuntu/noble"));
1363 assert!(url.ends_with(".deb"));
1364 }
1365
1366 #[test]
1367 fn test_amdgpu_install_rpm_url() {
1368 let installer = RocmInstaller::new(RocmConfig {
1369 channel: RocmChannel::Legacy,
1370 ..Default::default()
1371 });
1372 let url = installer.amdgpu_install_rpm_url("9");
1373 assert!(url.contains("7.2.4/rhel/9"));
1374 assert!(url.contains("7.2.4.70204-1"));
1375 assert!(url.contains("rhel/9"));
1376 assert!(url.ends_with(".noarch.rpm"));
1377 }
1378
1379 #[test]
1380 fn test_apt_repo_config() {
1381 let installer = RocmInstaller::new(RocmConfig {
1382 channel: RocmChannel::Legacy,
1383 ..Default::default()
1384 });
1385 let distro = ubuntu_distro();
1386 let repo = installer.apt_repo_config(&distro);
1387 assert_eq!(repo.file_path, "/etc/apt/sources.list.d/rocm.list");
1388 assert!(repo.url.contains("rocm/apt/7.2"));
1389 assert!(repo.url.contains("noble"));
1390 }
1391
1392 #[test]
1393 fn test_amdgpu_apt_repo_config() {
1394 let installer = RocmInstaller::new(RocmConfig {
1395 channel: RocmChannel::Legacy,
1396 ..Default::default()
1397 });
1398 let distro = ubuntu_distro();
1399 let repo = installer.amdgpu_apt_repo_config(&distro);
1400 assert_eq!(repo.file_path, "/etc/apt/sources.list.d/amdgpu.list");
1401 assert!(repo.url.contains("amdgpu/7.2"));
1402 }
1403
1404 #[test]
1405 fn test_apt_pin_config() {
1406 let installer = RocmInstaller::with_defaults();
1407 let (content, path) = installer.apt_pin_config();
1408 assert_eq!(path, "/etc/apt/preferences.d/rocm-pin-600");
1409 assert!(content.contains("Pin-Priority: 600"));
1410 }
1411
1412 #[test]
1415 fn test_all_channels_version_consistency() {
1416 for choice in [1u8, 2, 3] {
1417 let ch = RocmChannel::from_choice(choice).unwrap();
1418 let config = RocmConfig {
1419 channel: ch,
1420 ..Default::default()
1421 };
1422 let installer = RocmInstaller::new(config);
1423
1424 let deb_url = installer.amdgpu_install_deb_url();
1426 assert!(
1427 deb_url.contains(ch.pkg_version()),
1428 "deb URL for {:?} should contain pkg_version {}",
1429 ch,
1430 ch.pkg_version()
1431 );
1432
1433 let distro = ubuntu_distro();
1435 let repo_url = installer.repo_base_url(&distro);
1436 assert!(
1437 repo_url.contains(ch.major_minor()),
1438 "repo URL for {:?} should contain major_minor {}",
1439 ch,
1440 ch.major_minor()
1441 );
1442 }
1443 }
1444
1445 #[test]
1446 fn test_install_type_metapackages() {
1447 for (itype, expected_pkg) in [
1448 (RocmInstallType::Standard, "rocm-libs"),
1449 (RocmInstallType::Minimal, "rocm-core"),
1450 (RocmInstallType::Full, "rocm-dev"),
1451 (RocmInstallType::Custom, "rocm-libs"),
1452 ] {
1453 let installer = RocmInstaller::new(RocmConfig {
1454 channel: RocmChannel::Legacy,
1455 install_type: itype,
1456 ..Default::default()
1457 });
1458 let cmds = installer.apt_install_commands();
1459 let meta_cmd = &cmds[3];
1460 assert!(
1461 meta_cmd.args.contains(&expected_pkg.to_string()),
1462 "Install type {:?} should use metapackage {}",
1463 itype,
1464 expected_pkg
1465 );
1466 }
1467 }
1468
1469 #[test]
1472 fn test_is_up_to_date_yay_warning() {
1473 assert!(RocmInstaller::is_up_to_date_output(
1474 "warning: rocminfo is up to date -- skipping"
1475 ));
1476 }
1477
1478 #[test]
1479 fn test_is_up_to_date_yay_warning_case_insensitive() {
1480 assert!(RocmInstaller::is_up_to_date_output(
1481 "WARNING: rocminfo is UP TO DATE -- skipping"
1482 ));
1483 }
1484
1485 #[test]
1486 fn test_is_up_to_date_nothing_to_do() {
1487 assert!(RocmInstaller::is_up_to_date_output(
1488 " there is nothing to do\n"
1489 ));
1490 }
1491
1492 #[test]
1493 fn test_is_up_to_date_already_installed() {
1494 assert!(RocmInstaller::is_up_to_date_output(
1495 "package already installed"
1496 ));
1497 }
1498
1499 #[test]
1500 fn test_is_up_to_date_normal_output_is_false() {
1501 assert!(!RocmInstaller::is_up_to_date_output(
1502 "resolving dependencies...\nlooking for conflicting packages...\n"
1503 ));
1504 }
1505
1506 #[test]
1507 fn test_is_up_to_date_error_output_is_false() {
1508 assert!(!RocmInstaller::is_up_to_date_output(
1509 "error: failed to prepare transaction\nerror: target not found: rocminfo"
1510 ));
1511 }
1512
1513 #[test]
1514 fn test_is_up_to_date_empty_output_is_false() {
1515 assert!(!RocmInstaller::is_up_to_date_output(""));
1516 }
1517
1518 #[test]
1519 fn test_is_up_to_date_pacman_skipping() {
1520 assert!(RocmInstaller::is_up_to_date_output(
1521 "warning: rocm-hip-sdk is up to date -- skipping"
1522 ));
1523 }
1524
1525 #[test]
1528 fn test_build_pacman_check_installed_command() {
1529 let cmd = RocmInstaller::build_pacman_check_installed_command("rocminfo");
1530 assert_eq!(cmd.program, "pacman");
1531 assert_eq!(cmd.args, vec!["-Qi", "rocminfo"]);
1532 }
1533
1534 #[test]
1535 fn test_build_pacman_check_installed_command_complex_pkg() {
1536 let cmd = RocmInstaller::build_pacman_check_installed_command("rocm-hip-sdk");
1537 assert_eq!(cmd.program, "pacman");
1538 assert!(cmd.args.contains(&"rocm-hip-sdk".to_string()));
1539 }
1540}