diff --git a/apps/rocm/src/main.rs b/apps/rocm/src/main.rs index b0a3fd13..3b6d0d7f 100644 --- a/apps/rocm/src/main.rs +++ b/apps/rocm/src/main.rs @@ -2549,7 +2549,7 @@ fn build_driver_install_plan( os_release_text: &str, dkms: bool, ) -> DriverInstallPlan { - let repo_version_expr = "${ROCM_CLI_AMDGPU_VERSION:-7.2.4}".to_owned(); + let repo_version_expr = "${ROCM_CLI_AMDGPU_DRIVER_VERSION:-latest}".to_owned(); if examine.os == "windows" { return DriverInstallPlan { supported: false, @@ -2809,7 +2809,7 @@ fn apt_driver_plan( driver_command( DriverCommandPhase::Prepare, &format!( - "printf '%s\\n' 'deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/graphics/{repo_version_expr}/ubuntu {codename} main' | sudo tee /etc/apt/sources.list.d/amdgpu.list >/dev/null" + "printf '%s\\n' 'deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/amdgpu/{repo_version_expr}/ubuntu {codename} main' | sudo tee /etc/apt/sources.list.d/amdgpu.list >/dev/null" ), ), driver_command( @@ -2898,11 +2898,15 @@ fn dnf_driver_plan( )); } } + commands.push(driver_command( + DriverCommandPhase::Prepare, + "sudo rpm --import https://repo.radeon.com/rocm/rocm.gpg.key", + )); commands.push(driver_command( DriverCommandPhase::Prepare, &format!( - "sudo dnf install -y {}", - amdgpu_install_rpm_url(&repo_version_expr, &version_id, distro) + "printf '%s\\n' '[amdgpu]' 'name=amdgpu' 'baseurl={}' 'enabled=1' 'priority=50' 'gpgcheck=1' 'gpgkey=https://repo.radeon.com/rocm/rocm.gpg.key' | sudo tee /etc/yum.repos.d/amdgpu.repo >/dev/null", + dnf_repo_baseurl(&repo_version_expr, &version_id, distro) ), )); commands.push(driver_command( @@ -2988,11 +2992,15 @@ fn sles_driver_plan( DriverCommandPhase::Prepare, "sudo zypper install -y kernel-default-devel", ), + driver_command( + DriverCommandPhase::Prepare, + "sudo rpm --import https://repo.radeon.com/rocm/rocm.gpg.key", + ), driver_command( DriverCommandPhase::Prepare, &format!( - "sudo zypper --no-gpg-checks install -y {}", - amdgpu_install_sles_rpm_url(&repo_version_expr, &version_id) + "printf '%s\\n' '[amdgpu]' 'name=amdgpu' 'baseurl={}' 'enabled=1' 'autorefresh=0' 'type=rpm-md' 'gpgcheck=1' 'gpgkey=https://repo.radeon.com/rocm/rocm.gpg.key' | sudo tee /etc/zypp/repos.d/amdgpu.repo >/dev/null", + sles_repo_baseurl(&repo_version_expr, &version_id) ), ), driver_command(DriverCommandPhase::Prepare, "sudo zypper refresh"), @@ -3061,26 +3069,19 @@ fn rhel_kernel_prepare_commands(version_id: &str) -> Vec<&'static str> { } } -fn amdgpu_install_rpm_url( - repo_version_expr: &str, - version_id: &str, - distro: DnfDriverDistro, -) -> String { +fn dnf_repo_baseurl(repo_version_expr: &str, version_id: &str, distro: DnfDriverDistro) -> String { let repo_family = match distro { DnfDriverDistro::Rhel => "rhel", DnfDriverDistro::Oracle | DnfDriverDistro::Rocky | DnfDriverDistro::Generic => "el", }; let repo_version = dnf_repo_version_path(version_id); - let el_major = linux_major_version(version_id); format!( - "https://repo.radeon.com/amdgpu-install/{repo_version_expr}/{repo_family}/{repo_version}/amdgpu-install-{repo_version_expr}.${{ROCM_CLI_AMDGPU_PACKAGE_RELEASE:-70204}}-1.el{el_major}.noarch.rpm" + "https://repo.radeon.com/amdgpu/{repo_version_expr}/{repo_family}/{repo_version}/main/x86_64" ) } -fn amdgpu_install_sles_rpm_url(repo_version_expr: &str, version_id: &str) -> String { - format!( - "https://repo.radeon.com/amdgpu-install/{repo_version_expr}/sle/{version_id}/amdgpu-install-{repo_version_expr}.${{ROCM_CLI_AMDGPU_PACKAGE_RELEASE:-70204}}-1.noarch.rpm" - ) +fn sles_repo_baseurl(repo_version_expr: &str, version_id: &str) -> String { + format!("https://repo.radeon.com/amdgpu/{repo_version_expr}/sle/{version_id}/main/x86_64") } fn dnf_repo_version_path(version_id: &str) -> String { @@ -21078,7 +21079,7 @@ VERSION_CODENAME=noble assert!( commands .iter() - .any(|command| command.contains("repo.radeon.com/graphics")) + .any(|command| command.contains("repo.radeon.com/amdgpu")) ); assert!( commands @@ -21262,10 +21263,11 @@ VERSION_ID="9.7" assert!(rendered.contains("kernel-headers-$(uname -r)")); assert!(rendered.contains("kernel-devel-$(uname -r)")); assert!(rendered.contains("kernel-devel-matched-$(uname -r)")); + assert!(rendered.contains("sudo rpm --import https://repo.radeon.com/rocm/rocm.gpg.key")); assert!(rendered.contains( - "repo.radeon.com/amdgpu-install/${ROCM_CLI_AMDGPU_VERSION:-7.2.4}/rhel/9.7/" + "baseurl=https://repo.radeon.com/amdgpu/${ROCM_CLI_AMDGPU_DRIVER_VERSION:-latest}/rhel/9.7/main/x86_64" )); - assert!(rendered.contains("amdgpu-install-${ROCM_CLI_AMDGPU_VERSION:-7.2.4}.${ROCM_CLI_AMDGPU_PACKAGE_RELEASE:-70204}-1.el9.noarch.rpm")); + assert!(rendered.contains("/etc/yum.repos.d/amdgpu.repo")); assert!(rendered.contains("Execute: sudo dnf install -y amdgpu-dkms")); assert!(rendered.contains("approval: required")); } @@ -21284,10 +21286,9 @@ VERSION_ID="10.1" assert!(rendered.contains("kernel-uek-devel-$(uname -r)")); assert!( rendered.contains( - "repo.radeon.com/amdgpu-install/${ROCM_CLI_AMDGPU_VERSION:-7.2.4}/el/10/" + "baseurl=https://repo.radeon.com/amdgpu/${ROCM_CLI_AMDGPU_DRIVER_VERSION:-latest}/el/10/main/x86_64" ) ); - assert!(rendered.contains("amdgpu-install-${ROCM_CLI_AMDGPU_VERSION:-7.2.4}.${ROCM_CLI_AMDGPU_PACKAGE_RELEASE:-70204}-1.el10.noarch.rpm")); assert!(rendered.contains("dry run only")); } @@ -21307,7 +21308,7 @@ VERSION_ID="9.7" ); assert!( rendered.contains( - "repo.radeon.com/amdgpu-install/${ROCM_CLI_AMDGPU_VERSION:-7.2.4}/el/9.7/" + "baseurl=https://repo.radeon.com/amdgpu/${ROCM_CLI_AMDGPU_DRIVER_VERSION:-latest}/el/9.7/main/x86_64" ) ); assert!(rendered.contains("Execute: sudo dnf install -y amdgpu-dkms")); @@ -21327,10 +21328,9 @@ VERSION_ID="9.4" assert!(plan.mutating); assert!( rendered.contains( - "repo.radeon.com/amdgpu-install/${ROCM_CLI_AMDGPU_VERSION:-7.2.4}/el/9.4/" + "baseurl=https://repo.radeon.com/amdgpu/${ROCM_CLI_AMDGPU_DRIVER_VERSION:-latest}/el/9.4/main/x86_64" ) ); - assert!(rendered.contains("amdgpu-install-${ROCM_CLI_AMDGPU_VERSION:-7.2.4}.${ROCM_CLI_AMDGPU_PACKAGE_RELEASE:-70204}-1.el9.noarch.rpm")); assert!(rendered.contains("Execute: sudo dnf install -y amdgpu-dkms")); } @@ -21365,7 +21365,7 @@ VERSION_CODENAME=bookworm assert!(plan.supported); assert_eq!(plan.codename, "jammy"); assert!(rendered.contains( - "https://repo.radeon.com/graphics/${ROCM_CLI_AMDGPU_VERSION:-7.2.4}/ubuntu jammy main" + "https://repo.radeon.com/amdgpu/${ROCM_CLI_AMDGPU_DRIVER_VERSION:-latest}/ubuntu jammy main" )); assert!( plan.reason @@ -21387,10 +21387,11 @@ VERSION_ID="15.7" assert!(rendered.contains("SUSEConnect")); assert!(rendered.contains("sle-module-desktop-applications/15.7/x86_64")); assert!(rendered.contains("sudo zypper install -y kernel-default-devel")); + assert!(rendered.contains("sudo rpm --import https://repo.radeon.com/rocm/rocm.gpg.key")); assert!(rendered.contains( - "repo.radeon.com/amdgpu-install/${ROCM_CLI_AMDGPU_VERSION:-7.2.4}/sle/15.7/" + "baseurl=https://repo.radeon.com/amdgpu/${ROCM_CLI_AMDGPU_DRIVER_VERSION:-latest}/sle/15.7/main/x86_64" )); - assert!(rendered.contains("sudo zypper --no-gpg-checks install -y")); + assert!(rendered.contains("/etc/zypp/repos.d/amdgpu.repo")); assert!(rendered.contains("Execute: sudo zypper install -y amdgpu-dkms")); assert!(rendered.contains("approval: required")); } @@ -21464,7 +21465,7 @@ ID_LIKE="ubuntu debian" // Ubuntu-family derivatives ship the Ubuntu kernel, so linux-modules-extra applies. assert!(rendered.contains("linux-modules-extra-$(uname -r)")); assert!(rendered.contains( - "https://repo.radeon.com/graphics/${ROCM_CLI_AMDGPU_VERSION:-7.2.4}/ubuntu jammy main" + "https://repo.radeon.com/amdgpu/${ROCM_CLI_AMDGPU_DRIVER_VERSION:-latest}/ubuntu jammy main" )); assert!(rendered.contains("Execute: sudo apt-get install -y amdgpu-dkms")); } @@ -21483,7 +21484,7 @@ ID_LIKE=debian assert!(plan.supported); // Debian-family maps to the Ubuntu jammy repo and omits linux-modules-extra. assert!(rendered.contains( - "https://repo.radeon.com/graphics/${ROCM_CLI_AMDGPU_VERSION:-7.2.4}/ubuntu jammy main" + "https://repo.radeon.com/amdgpu/${ROCM_CLI_AMDGPU_DRIVER_VERSION:-latest}/ubuntu jammy main" )); assert!(!rendered.contains("linux-modules-extra-$(uname -r)")); assert!(rendered.contains("amdgpu-dkms")); @@ -21506,11 +21507,10 @@ ID_LIKE="rhel centos fedora" // EL rebuilds use the vendor-neutral el/ repo path, not rhel/. assert!( rendered.contains( - "repo.radeon.com/amdgpu-install/${ROCM_CLI_AMDGPU_VERSION:-7.2.4}/el/9.6/" + "baseurl=https://repo.radeon.com/amdgpu/${ROCM_CLI_AMDGPU_DRIVER_VERSION:-latest}/el/9.6/main/x86_64" ) ); assert!(!rendered.contains("/rhel/9.6/")); - assert!(rendered.contains("amdgpu-install-${ROCM_CLI_AMDGPU_VERSION:-7.2.4}.${ROCM_CLI_AMDGPU_PACKAGE_RELEASE:-70204}-1.el9.noarch.rpm")); // el9 uses the version-aware standard-kernel prepare commands. assert!(rendered.contains("kernel-devel-matched-$(uname -r)")); assert!(rendered.contains("Execute: sudo dnf install -y amdgpu-dkms")); @@ -21528,11 +21528,9 @@ ID_LIKE="rhel centos fedora" assert!(plan.supported); // EL 8 is served from the major-version path (el/8), matching AMD docs. - assert!( - rendered - .contains("repo.radeon.com/amdgpu-install/${ROCM_CLI_AMDGPU_VERSION:-7.2.4}/el/8/") - ); - assert!(rendered.contains("-1.el8.noarch.rpm")); + assert!(rendered.contains( + "baseurl=https://repo.radeon.com/amdgpu/${ROCM_CLI_AMDGPU_DRIVER_VERSION:-latest}/el/8/main/x86_64" + )); // el8 has no kernel-devel-matched package. assert!(!rendered.contains("kernel-devel-matched")); assert!(rendered.contains("kernel-devel-$(uname -r)")); @@ -21569,7 +21567,7 @@ ID_LIKE=fedora assert!(plan.supported); assert!(rendered.contains( - "repo.radeon.com/amdgpu-install/${ROCM_CLI_AMDGPU_VERSION:-7.2.4}/rhel/9.7/" + "baseurl=https://repo.radeon.com/amdgpu/${ROCM_CLI_AMDGPU_DRIVER_VERSION:-latest}/rhel/9.7/main/x86_64" )); assert!(!rendered.contains("/el/9.7/")); } diff --git a/crates/rocm-core/src/diagnose.rs b/crates/rocm-core/src/diagnose.rs index 77cc93d6..b8a01a3a 100644 --- a/crates/rocm-core/src/diagnose.rs +++ b/crates/rocm-core/src/diagnose.rs @@ -86,7 +86,6 @@ fn upstream_tracker(target: &str) -> &'static str { "lemonade" => "https://github.com/lemonade-sdk/lemonade/issues", "ollama" => "https://github.com/ollama/ollama/issues", "lm-studio" => "https://lmstudio.ai/docs/app (use in-app support; no public repo)", - "amdgpu-install" => "https://repo.radeon.com (raise via your AMD support contact)", _ => "https://github.com/ROCm/ROCm/issues", } } @@ -503,7 +502,7 @@ fn check_3_rocm_kernel_unsupported(e: &Examination, symptom: &str) -> Diagnosis "# Compare to the live AMD matrix:".to_owned(), "# https://rocm.docs.amd.com/projects/install-on-linux/en/latest/reference/system-requirements.html".to_owned(), "# If your kernel is above the supported range, install the HWE".to_owned(), - "# kernel that matches ROCm, or rerun amdgpu-install with --no-dkms.".to_owned(), + "# kernel that matches ROCm, or rerun `rocm install driver --dkms`.".to_owned(), ], fix_id: "fix-3-rocm-kernel".to_owned(), auto_applicable: false, @@ -752,11 +751,8 @@ fn check_7_stale_repos(e: &Examination, symptom: &str) -> Diagnosis { )); } commands.push("sudo apt update".to_owned()); - commands.push("# If apt now resolves, reinstall via the correct method only:".to_owned()); - commands.push( - "# amdgpu-install --usecase=rocm,hip --no-dkms # if you want amdgpu-install".to_owned(), - ); - commands.push("# or use the distro packages exclusively".to_owned()); + commands.push("# If apt now resolves, reinstall via the repo-native flow only:".to_owned()); + commands.push("# rocm install driver".to_owned()); let fix = Fix { summary: "Quarantine duplicate ROCm/AMDGPU repo files and resolve apt before re-running any installer.".to_owned(), commands, @@ -1050,44 +1046,99 @@ fn check_11_iommu_hang(e: &Examination, symptom: &str) -> Diagnosis { ) } -fn check_12_amdgpu_install_broken(e: &Examination, symptom: &str) -> Diagnosis { +/// Best-effort package-manager family for this host: prefer the family +/// implied by the repo dir the marker files live under, since that's the +/// config these commands act on, and fall back to the same `ID_LIKE`-aware +/// resolver `openmpi` uses only when no repo file was found. +fn repo_pkg_family(e: &Examination) -> &'static str { + if e.rocm_repos_seen.iter().any(|r| r.contains("/etc/apt/")) { + return "apt"; + } + if e.rocm_repos_seen + .iter() + .any(|r| r.contains("/etc/yum.repos.d/")) + { + return "dnf"; + } + if e.rocm_repos_seen.iter().any(|r| r.contains("/etc/zypp/")) { + return "zypper"; + } + crate::openmpi::resolve_package_manager( + &e.distro_id.to_ascii_lowercase(), + &e.distro_id_like.to_ascii_lowercase(), + ) + .map_or("apt", crate::openmpi::PackageManager::as_str) +} + +fn check_12_repo_native_broken(e: &Examination, symptom: &str) -> Diagnosis { let mut score = 0; let mut evidence = Vec::new(); let method = &e.rocm_install_method; - if method == "amdgpu-install" { - evidence.push("ROCm was installed via amdgpu-install".to_owned()); + if method == "repo-native" { + evidence.push("ROCm was installed via the repo-native package-manager flow".to_owned()); } let (kw_score, kw_ev) = keyword_score(symptom, KEYWORDS_DPKG_BROKEN); score += kw_score; evidence.extend(kw_ev); - if method == "amdgpu-install" && kw_score > 0 { + if method == "repo-native" && kw_score > 0 { score += 20; } if score <= 0 { - return zero("fix-12-installer", "amdgpu-install broken state"); + return zero("fix-12-installer", "repo-native install broken state"); + } + let family = repo_pkg_family(e); + let mut commands = vec!["# Clear the half-configured package/DKMS state first.".to_owned()]; + match family { + "apt" => { + commands.push("sudo dpkg --configure -a".to_owned()); + commands.push("sudo apt-get install -f".to_owned()); + commands.push("sudo apt-get purge -y amdgpu-dkms".to_owned()); + } + "dnf" => commands.push("sudo dnf remove -y amdgpu-dkms".to_owned()), + "zypper" => commands.push("sudo zypper remove -y amdgpu-dkms".to_owned()), + _ => {} + } + commands.push( + "# Quarantine (do not delete) the repo files, then let the package manager".to_owned(), + ); + commands.push("# forget the broken state before reinstalling.".to_owned()); + if e.rocm_repos_seen.is_empty() { + commands.push( + "# sudo mv /etc/apt/sources.list.d/amdgpu.list /etc/apt/sources.list.d/amdgpu.list.bak # or the yum/zypp equivalent".to_owned(), + ); + } else { + for r in &e.rocm_repos_seen { + commands.push(format!( + "# sudo mv {r} {r}.bak # quarantine, do not delete yet" + )); + } } + commands.push( + match family { + "dnf" => "sudo dnf clean all", + "zypper" => "sudo zypper refresh", + _ => "sudo apt update", + } + .to_owned(), + ); + commands.push("# Reinstall via rocm-cli's repo-native flow:".to_owned()); + commands.push("rocm install driver".to_owned()); + let fix = Fix { - summary: "Run amdgpu-install's documented uninstall sequence to clear the half-configured state, THEN reinstall without the flag that broke it.".to_owned(), - commands: vec![ - "sudo amdgpu-install --uninstall".to_owned(), - "sudo apt autoremove --purge -y".to_owned(), - "sudo apt update".to_owned(), - "# Reinstall. Drop --accept-eula if you used it previously; the".to_owned(), - "# newer installer rejects it and leaves a half-configured repo.".to_owned(), - "sudo amdgpu-install --usecase=rocm,hip".to_owned(), - ], + summary: "Clear the half-configured package/DKMS state, quarantine the repo-native install's repo files, THEN reinstall via `rocm install driver`.".to_owned(), + commands, needs_sudo: true, needs_reboot: true, fix_id: "fix-12-installer".to_owned(), auto_applicable: false, - verify: "dpkg -l | grep -E 'rocm|amdgpu' | head -n 20 && rocminfo | head -n 5".to_owned(), - notes: vec!["If `apt autoremove` warns it will remove unrelated packages, stop and resolve those by hand before continuing.".to_owned()], + verify: "rocm examine --json | grep -q '\"rocm_install_method\": \"repo-native\"' && rocminfo | head -n 5".to_owned(), + notes: vec!["If the package-manager update/refresh warns it will remove unrelated packages, stop and resolve those by hand before continuing.".to_owned()], ..Fix::default() }; finalize( "fix-12-installer", - "amdgpu-install left a broken state (repo regression / partial DKMS)", + "Repo-native install left a broken state (repo regression / partial DKMS)", score, evidence, fix, @@ -1255,7 +1306,7 @@ const CHECKERS: &[Checker] = &[ (check_9_igpu_dgpu_collision, &["linux", "windows"]), (check_10_container_devices, &["linux"]), (check_11_iommu_hang, &["linux"]), - (check_12_amdgpu_install_broken, &["linux"]), + (check_12_repo_native_broken, &["linux"]), (check_13_hip_sdk_missing, &["windows"]), (check_14_adrenalin_too_old, &["windows"]), (check_15_msvc_redist, &["windows"]), @@ -1658,4 +1709,115 @@ mod tests { assert_eq!(v["min_score_for_match"], 50); assert_eq!(v["high_confidence_threshold"], 75); } + + #[test] + fn repo_pkg_family_prefers_seen_repos_over_distro_id() { + let mut e = linux_base(); + e.distro_id = "sles".to_owned(); + e.rocm_repos_seen = vec!["/etc/apt/sources.list.d/amdgpu.list".to_owned()]; + assert_eq!(repo_pkg_family(&e), "apt"); + } + + #[test] + fn repo_pkg_family_detects_dnf_and_zypper_from_seen_repos() { + let mut e = linux_base(); + e.rocm_repos_seen = vec!["/etc/yum.repos.d/amdgpu.repo".to_owned()]; + assert_eq!(repo_pkg_family(&e), "dnf"); + + e.rocm_repos_seen = vec!["/etc/zypp/repos.d/amdgpu.repo".to_owned()]; + assert_eq!(repo_pkg_family(&e), "zypper"); + } + + #[test] + fn repo_pkg_family_falls_back_to_distro_id_when_no_repo_seen() { + let mut e = linux_base(); + e.distro_id = "sles".to_owned(); + assert_eq!(repo_pkg_family(&e), "zypper"); + + e.distro_id = "rocky".to_owned(); + assert_eq!(repo_pkg_family(&e), "dnf"); + + e.distro_id = "ubuntu".to_owned(); + assert_eq!(repo_pkg_family(&e), "apt"); + } + + #[test] + fn repo_pkg_family_fallback_is_id_like_aware() { + // Amazon Linux has no ROCm repo file in this scenario and isn't in + // the old hardcoded list; it must not default to "apt". + let mut e = linux_base(); + e.distro_id = "amzn".to_owned(); + assert_eq!(repo_pkg_family(&e), "dnf"); + + // A RHEL rebuild with an unrecognized ID must still resolve via + // ID_LIKE, same as openmpi's resolver. + let mut e = linux_base(); + e.distro_id = "some-rhel-rebuild".to_owned(); + e.distro_id_like = "rhel fedora".to_owned(); + assert_eq!(repo_pkg_family(&e), "dnf"); + } + + #[test] + fn repo_native_broken_fires_and_clears_state_before_reinstall() { + let mut e = linux_base(); + e.rocm_install_method = "repo-native".to_owned(); + e.rocm_repos_seen = vec!["/etc/apt/sources.list.d/amdgpu.list".to_owned()]; + let report = diagnose( + &e, + "dpkg: error processing package amdgpu-dkms (half-configured)", + ); + let hit = report + .matched + .iter() + .find(|d| d.id == "fix-12-installer") + .expect("repo-native broken state should be diagnosed"); + let fix = hit.fix.as_ref().unwrap(); + let commands = fix.commands.join("\n"); + assert!( + commands.contains("dpkg --configure -a"), + "must clear the half-configured dpkg state before reinstalling: {commands}" + ); + assert!( + commands.contains("purge -y amdgpu-dkms"), + "must purge the broken driver package: {commands}" + ); + assert!( + commands.contains("# sudo mv /etc/apt/sources.list.d/amdgpu.list"), + "repo quarantine command must be commented out, not ready-to-run: {commands}" + ); + assert!(fix.verify.contains("repo-native")); + } + + #[test] + fn repo_native_broken_does_not_fire_on_non_repo_native_installs_without_symptom() { + let mut e = linux_base(); + e.rocm_install_method = "runfile-or-tarball".to_owned(); + let report = diagnose(&e, ""); + assert!(report.matched.iter().all(|d| d.id != "fix-12-installer")); + } + + #[test] + fn repo_native_broken_fires_on_dpkg_symptom_regardless_of_install_method() { + // The keyword score alone (half-configured + generic dpkg error = 75) + // clears MIN_SCORE_FOR_MATCH without the +20 repo-native bonus, so + // fix-12 still fires even when rocm_install_method isn't + // "repo-native" -- the method only adds confidence, it doesn't gate + // whether the check fires at all. + let mut e = linux_base(); + e.rocm_install_method = "runfile-or-tarball".to_owned(); + let report = diagnose( + &e, + "dpkg: error processing package amdgpu-dkms (half-configured)", + ); + let hit = report + .matched + .iter() + .find(|d| d.id == "fix-12-installer") + .expect("dpkg keyword evidence alone should still surface fix-12"); + assert!( + !hit.evidence.iter().any(|l| l.contains("repo-native")), + "the repo-native install-method evidence line must not appear for a non-repo-native install: {:?}", + hit.evidence + ); + } } diff --git a/crates/rocm-core/src/examine.rs b/crates/rocm-core/src/examine.rs index 20cf1b81..6b95d1d8 100644 --- a/crates/rocm-core/src/examine.rs +++ b/crates/rocm-core/src/examine.rs @@ -44,14 +44,19 @@ const TRACKED_ENV_VARS: &[&str] = &[ "PATH", ]; -/// Repo files dropped by the `amdgpu-install` pipeline; their presence marks an -/// amdgpu-install-managed ROCm. -const AMDGPU_INSTALL_MARKERS: &[&str] = &[ +/// AMD ROCm/AMDGPU repo files: `amdgpu.list` / `amdgpu.repo` are written by +/// rocm-cli's own repo-native install flow; `rocm.list` / `radeon.list` / +/// `rocm.repo` are names from AMD's documented manual/legacy setup steps that +/// rocm-cli itself never creates. Either kind marks a package-manager-repo- +/// managed ROCm. +const REPO_NATIVE_INSTALL_MARKERS: &[&str] = &[ "/etc/apt/sources.list.d/amdgpu.list", "/etc/apt/sources.list.d/rocm.list", "/etc/apt/sources.list.d/radeon.list", "/etc/yum.repos.d/amdgpu.repo", "/etc/yum.repos.d/rocm.repo", + "/etc/zypp/repos.d/amdgpu.repo", + "/etc/zypp/repos.d/rocm.repo", ]; /// Marketing-name fragments that identify an AMD APU when `rocminfo` is absent. @@ -103,6 +108,11 @@ pub struct Examination { pub os_version: String, pub distro_id: String, pub distro_version: String, + /// `/etc/os-release` `ID_LIKE`, for `openmpi::resolve_package_manager` + /// fallback matching. Not part of the examine.py wire contract, so it's + /// excluded from JSON. + #[serde(skip)] + pub distro_id_like: String, pub kernel_release: String, pub kernel_cmdline: String, pub is_wsl: bool, @@ -180,6 +190,7 @@ impl Default for Examination { os_version: String::new(), distro_id: String::new(), distro_version: String::new(), + distro_id_like: String::new(), kernel_release: String::new(), kernel_cmdline: String::new(), is_wsl: false, @@ -418,6 +429,7 @@ fn probe_os(e: &mut Examination) { let value = value.trim().trim_matches('"'); match key { "ID" => e.distro_id = value.to_owned(), + "ID_LIKE" => e.distro_id_like = value.to_owned(), "VERSION_ID" => e.distro_version = value.to_owned(), _ => {} } @@ -890,6 +902,23 @@ fn probe_secure_boot(e: &mut Examination) { // ROCm install probe (Linux) // --------------------------------------------------------------------------- +/// Pure marker-path -> install-method mapping, split out from +/// `probe_rocm_install` so it's testable without touching the real +/// filesystem. `existing` is the subset of `REPO_NATIVE_INSTALL_MARKERS` +/// found present on the host; returns `None` when none are present. +fn repo_native_method_from_markers(existing: &[&str]) -> Option<(&'static str, Vec)> { + let seen: Vec = REPO_NATIVE_INSTALL_MARKERS + .iter() + .filter(|m| existing.contains(m)) + .map(|m| (*m).to_owned()) + .collect(); + if seen.is_empty() { + None + } else { + Some(("repo-native", seen)) + } +} + fn probe_rocm_install(e: &mut Examination) { let mut rocm_dir = String::new(); let rocm_path_env = std::env::var("ROCM_PATH").unwrap_or_default(); @@ -917,11 +946,14 @@ fn probe_rocm_install(e: &mut Examination) { } } - for marker in AMDGPU_INSTALL_MARKERS { - if Path::new(marker).exists() { - e.rocm_install_method = "amdgpu-install".to_owned(); - e.rocm_repos_seen.push((*marker).to_owned()); - } + let existing_markers: Vec<&str> = REPO_NATIVE_INSTALL_MARKERS + .iter() + .copied() + .filter(|m| Path::new(m).exists()) + .collect(); + if let Some((method, seen)) = repo_native_method_from_markers(&existing_markers) { + e.rocm_install_method = method.to_owned(); + e.rocm_repos_seen.extend(seen); } if e.rocm_install_method.is_empty() { @@ -942,11 +974,15 @@ fn probe_rocm_install(e: &mut Examination) { e.rocm_install_method = if rocm_dir.is_empty() { "none".to_owned() } else { - "tarball-or-other".to_owned() + "runfile-or-tarball".to_owned() }; } - for dir in ["/etc/apt/sources.list.d", "/etc/yum.repos.d"] { + for dir in [ + "/etc/apt/sources.list.d", + "/etc/yum.repos.d", + "/etc/zypp/repos.d", + ] { let Ok(entries) = std::fs::read_dir(dir) else { continue; }; @@ -1680,4 +1716,45 @@ mod tests { ); assert_eq!(extract_rocm_version("/opt/rocm"), None); } + + #[test] + fn no_markers_present_yields_no_repo_native_method() { + assert!(repo_native_method_from_markers(&[]).is_none()); + } + + #[test] + fn every_declared_marker_individually_maps_to_repo_native() { + for marker in REPO_NATIVE_INSTALL_MARKERS { + let (method, seen) = + repo_native_method_from_markers(&[marker]).expect("marker should match"); + assert_eq!(method, "repo-native"); + assert_eq!(seen, vec![(*marker).to_owned()]); + } + } + + #[test] + fn multiple_markers_all_recorded_in_declaration_order() { + // Passed in reverse of REPO_NATIVE_INSTALL_MARKERS' declared order to + // confirm the output order tracks the constant, not the input. + let existing = [ + "/etc/zypp/repos.d/amdgpu.repo", + "/etc/apt/sources.list.d/amdgpu.list", + ]; + let (method, seen) = repo_native_method_from_markers(&existing).expect("some"); + assert_eq!(method, "repo-native"); + assert_eq!( + seen, + vec![ + "/etc/apt/sources.list.d/amdgpu.list".to_owned(), + "/etc/zypp/repos.d/amdgpu.repo".to_owned(), + ] + ); + } + + #[test] + fn unrelated_paths_do_not_match_any_marker() { + assert!( + repo_native_method_from_markers(&["/etc/apt/sources.list.d/some-other.list"]).is_none() + ); + } } diff --git a/crates/rocm-core/src/fix.rs b/crates/rocm-core/src/fix.rs index 67602038..4fef62ae 100644 --- a/crates/rocm-core/src/fix.rs +++ b/crates/rocm-core/src/fix.rs @@ -106,7 +106,7 @@ const RECIPES: &[FixRecipe] = &[ FixRecipe { fix_id: "fix-3-rocm-kernel", title: "ROCm/distro/kernel triple unsupported", - rationale: "ROCm is installed but your kernel/distro combination is outside the supported matrix. Match the kernel to the matrix before reinstalling, or rerun with --no-dkms and accept the risk.", + rationale: "ROCm is installed but your kernel/distro combination is outside the supported matrix. Match the kernel to the matrix before reinstalling, or rerun `rocm install driver --dkms` and accept the risk.", auto_applicable: false, commands: &[ "# Cross-check the live AMD matrix before changing anything:", @@ -285,21 +285,27 @@ const RECIPES: &[FixRecipe] = &[ }, FixRecipe { fix_id: "fix-12-installer", - title: "Reset amdgpu-install state and reinstall", - rationale: "amdgpu-install left a half-configured DKMS / repo state. Run the documented uninstall, clean up, and reinstall without the flag that broke things (commonly --accept-eula on newer installers).", + title: "Reset repo-native install state and reinstall", + rationale: "The repo-native package-manager install left a half-configured DKMS / repo state. Clear the broken package state, quarantine the repo files rocm-cli's install wrote, let the package manager forget the broken state, then reinstall via rocm-cli's own repo-native flow.", auto_applicable: false, commands: &[ - "sudo amdgpu-install --uninstall", - "sudo apt autoremove --purge -y", - "sudo apt update", - "sudo amdgpu-install --usecase=rocm,hip", + "# Run only the block matching your package manager:", + "sudo dpkg --configure -a && sudo apt-get install -f && sudo apt-get purge -y amdgpu-dkms # Debian/Ubuntu", + "sudo dnf remove -y amdgpu-dkms # RHEL/Fedora/Rocky/Alma/Oracle", + "sudo zypper remove -y amdgpu-dkms # SLES/openSUSE", + "# sudo mv /etc/apt/sources.list.d/amdgpu.list /etc/apt/sources.list.d/amdgpu.list.bak # or the yum/zypp equivalent; quarantine, do not delete yet", + "# Run only the line matching your package manager:", + "sudo apt update # Debian/Ubuntu", + "sudo dnf clean all # RHEL/Fedora/Rocky/Alma/Oracle", + "sudo zypper refresh # SLES/openSUSE", + "rocm install driver", ], needs_sudo: true, needs_reboot: true, needs_relogin: false, - verify: "dpkg -l | grep -E 'rocm|amdgpu' | head -n 20 && rocminfo | head -n 5", + verify: "rocm examine --json | grep -q '\"rocm_install_method\": \"repo-native\"' && rocminfo | head -n 5", notes: &[ - "If `apt autoremove --purge` warns it will remove unrelated packages, stop and resolve those by hand before continuing.", + "If the package-manager update/refresh warns it will remove unrelated packages, stop and resolve those by hand before continuing.", ], applies_on: LINUX_ONLY, runner: None, @@ -1040,4 +1046,26 @@ mod tests { assert!(listing.contains(r.fix_id), "listing missing {}", r.fix_id); } } + + #[test] + fn fix_12_recipe_clears_state_before_reinstall_and_keeps_mv_commented() { + let recipe = RECIPES + .iter() + .find(|r| r.fix_id == "fix-12-installer") + .expect("fix-12-installer recipe must exist"); + let commands = recipe.commands.join("\n"); + assert!( + commands.contains("dpkg --configure -a"), + "must clear the half-configured dpkg state before reinstalling: {commands}" + ); + assert!( + commands.contains("purge -y amdgpu-dkms"), + "must purge the broken driver package: {commands}" + ); + assert!( + commands.contains("# sudo mv /etc/apt/sources.list.d/amdgpu.list"), + "repo quarantine command must be commented out, not ready-to-run: {commands}" + ); + assert!(recipe.verify.contains("repo-native")); + } } diff --git a/crates/rocm-core/src/openmpi.rs b/crates/rocm-core/src/openmpi.rs index 93054f52..4531fd71 100644 --- a/crates/rocm-core/src/openmpi.rs +++ b/crates/rocm-core/src/openmpi.rs @@ -542,7 +542,7 @@ fn scan_libnuma_path() -> Option { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum PackageManager { +pub(crate) enum PackageManager { Apt, Dnf, Zypper, @@ -550,7 +550,7 @@ enum PackageManager { } impl PackageManager { - const fn as_str(self) -> &'static str { + pub(crate) const fn as_str(self) -> &'static str { match self { Self::Apt => "apt", Self::Dnf => "dnf", @@ -914,7 +914,7 @@ pub(crate) fn parse_os_release_field(text: &str, key: &str) -> Option { None } -fn resolve_package_manager(os_id: &str, id_like: &str) -> Option { +pub(crate) fn resolve_package_manager(os_id: &str, id_like: &str) -> Option { const APT: &[&str] = &[ "ubuntu", "debian",