Files
TheRock/build_tools/packaging/linux/native_linux_package_install_test.py
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

1505 lines
59 KiB
Python

#!/usr/bin/env python3
# Copyright Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""
Full installation and simulate-install test script for ROCm native packages.
Test modes (--test-type):
- sanity: Basic test. Repo-based install plus basic verification only
(steps 1 and 2).
- quick / standard: CI aliases for sanity.
- full: Full test. Repo-based install plus basic verification plus full
verification (steps 1, 2, and 3).
Steps (invoked one by one from main):
1. Repo setup and install: set up package-manager repository and install
ROCm packages. Arch suffixes in metapackage names are used only when both
``--gfx-arch`` and ``--rocm-version`` are set (e.g. ``amdrocm7.13-gfx1100``).
With ``--rocm-version`` only: ``amdrocm7.13`` / ``amdrocm-core-sdk7.13``.
With neither or ``--gfx-arch`` alone: ``amdrocm`` / ``amdrocm-core-sdk``.
2. Basic verification: install prefix, key components, installed packages
list, rocminfo. (Run for both sanity and full.)
3. Full verification: rdhc.py / RDHC test. (Run only for full.)
- comprehensive: CI alias for full.
- install: Repo-based install only (step 1). No rocminfo or component checks.
Used by release workflows that dispatch install tests off the critical path.
- simulate: Dry-run only. Simulated install of local .deb or .rpm files
(apt install --simulate or rpm -Uvh --test --nodeps). No repo setup or
actual install. Requires --packages-dir.
Path and repo name are overridable via environment variables: ROCM_REPO_NAME (repo id used for
APT list, Zypper/Yum repo file and section), ROCM_APT_KEYRING_DIR, ROCM_APT_SOURCES_LIST,
ROCM_APT_KEYRING_FILE, ROCM_ZYPP_REPOS_DIR, ROCM_YUM_REPOS_DIR,
ROCM_RDHC_REL_PATH (relative path from install prefix to rdhc binary).
Prerequisites:
- This script does NOT start Docker or a VM. You must run it inside an existing
container or VM that matches the target OS (e.g., Ubuntu for deb, AlmaLinux/RHEL
for rpm, SLES container for sles). Start the appropriate Docker image or VM
first, then invoke this script from inside that environment.
- Root or sudo permissions may be required (repository setup, package install, keyring writes).
- System packages (install with the OS package manager):
Debian/Ubuntu: apt install -y python3 python3-pip wget curl
RHEL/Alma/CentOS/AZL: dnf install -y python3 python3-pip wget curl
SLES: zypper install -y python3 python3-pip wget curl
- Python packages: listed in build_tools/packaging/linux/tests/requirements.txt.
Install with: pip install -r build_tools/packaging/linux/tests/requirements.txt
(or from build_tools/packaging/linux/tests: pip install -r requirements.txt).
Equivalent one-liner: pip install requests prettytable PyYAML
CI typically runs this module under pytest (same file; reporting handled by pytest)::
pytest build_tools/packaging/linux/native_linux_package_install_test.py -vv --tb=short
Workflow/container ``env`` maps to CLI flags via :func:`_argv_from_ci_env` + ``test_native_linux_package_install``.
For versioned metapackage names only, set ``NATIVE_LINUX_INSTALL_ROCM_VERSION`` and omit ``--rocm-version`` when unversioned packages are desired.
For multiple arches from CI, set ``GFX_ARCH`` to whitespace-separated tokens (e.g. ``gfx94x gfx1100``), semicolon-separated (e.g. ``gfx94x;gfx1100``), or a single comma-separated value (e.g. ``gfx94x,gfx1100``); optional ``NATIVE_LINUX_INSTALL_ROCM_VERSION`` pairs with ``GFX_ARCH`` like ``--rocm-version`` with ``--gfx-arch`` on the CLI.
You can still invoke this file as a script for ad-hoc runs (no pytest required).
Example invocations:
# Nightly DEB (Ubuntu 24.04) - run inside ubuntu:24.04 container or VM
python3 native_linux_package_install_test.py \\
--os-profile ubuntu2404 \\
--repo-url https://rocm.nightlies.amd.com/deb/20260204-21658678136/ \\
--gfx-arch gfx94x \\
--release-type nightly
# Prerelease DEB with GPG verification
python3 native_linux_package_install_test.py \\
--os-profile ubuntu2404 \\
--repo-url https://rocm.prereleases.amd.com/packages/ubuntu2404 \\
--release-type prerelease \\
--gpg-key-url https://rocm.prereleases.amd.com/packages/gpg/rocm.gpg
# Nightly RPM (RHEL 8) - run inside a rhel8/UBI 8 container or VM
python3 native_linux_package_install_test.py \\
--os-profile rhel8 \\
--repo-url https://rocm.nightlies.amd.com/rpm/20260204-21658678136/x86_64/ \\
--gfx-arch gfx94x \\
--release-type nightly
# Prerelease RPM (SLES 16)
python3 native_linux_package_install_test.py \\
--os-profile sles16 \\
--repo-url https://rocm.prereleases.amd.com/packages/sles16/x86_64/ \\
--release-type prerelease \\
--gpg-key-url https://rocm.prereleases.amd.com/packages/gpg/rocm.gpg
# --test-type sanity (default): repo install + basic verification only (steps 1-2)
python3 native_linux_package_install_test.py --test-type sanity \\
--os-profile ubuntu2404 \\
--repo-url https://rocm.nightlies.amd.com/deb/20260204-21658678136/ \\
--gfx-arch gfx94x --release-type nightly --install-prefix /opt/rocm/core
# --test-type full: same as sanity plus rdhc full verification (steps 1-3)
python3 native_linux_package_install_test.py --test-type full \\
--os-profile ubuntu2404 \\
--repo-url https://rocm.nightlies.amd.com/deb/20260204-21658678136/ \\
--gfx-arch gfx94x --release-type nightly --install-prefix /opt/rocm/core
# --test-type install: repo install only (no verification)
python3 native_linux_package_install_test.py --test-type install \\
--os-profile ubuntu2404 \\
--repo-url https://rocm.nightlies.amd.com/deb/20260204-21658678136/ \\
--gfx-arch gfx94x --release-type nightly
# Versioned metapackages with multiple GPU architectures (requires --rocm-version for arch in names).
# Installs e.g. amdrocm7.13-gfx94x, amdrocm-core-sdk7.13-gfx94x, amdrocm7.13-gfx1100, ...
python3 native_linux_package_install_test.py --os-profile ubuntu2404 \\
--repo-url https://rocm.nightlies.amd.com/deb/20260204-21658678136/ \\
--rocm-version 7.13.1 --gfx-arch gfx94x gfx1100 --release-type nightly \\
--install-prefix /opt/rocm/core
# Same semantics: comma- or semicolon-separated arches in one --gfx-arch argument (or repeat --gfx-arch).
python3 native_linux_package_install_test.py --os-profile ubuntu2404 \\
--repo-url https://rocm.nightlies.amd.com/deb/20260204-21658678136/ \\
--rocm-version 7.13 --gfx-arch gfx94x,gfx1100 --release-type nightly \\
--install-prefix /opt/rocm/core
python3 native_linux_package_install_test.py --os-profile ubuntu2404 \\
--repo-url https://rocm.nightlies.amd.com/deb/20260204-21658678136/ \\
--rocm-version 7.13 --gfx-arch 'gfx94x;gfx1100' --release-type nightly \\
--install-prefix /opt/rocm/core
# Versioned generic metapackages (no arch suffix) when --rocm-version is set without --gfx-arch.
python native_linux_package_install_test.py --os-profile ubuntu2404 \\
--repo-url https://therock-dev-artifacts.s3.amazonaws.com/25137154844-linux/packages/deb \\
--rocm-version 7.13 --release-type dev --install-prefix /opt/rocm/core
# Simulate install (dry-run) from local .deb or .rpm directory
python3 native_linux_package_install_test.py --test-type simulate --packages-dir /path/to/pkgs --os-profile ubuntu2404
python3 native_linux_package_install_test.py --test-type simulate --packages-dir /path/to/rpms --pkg-type rpm
"""
import argparse
import os
import re
import subprocess
import sys
import traceback
from argparse import ArgumentParser, Namespace
from pathlib import Path
_SCRIPT_DIR = Path(__file__).resolve().parent
if str(_SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(_SCRIPT_DIR))
from packaging_utils import normalize_target_list
def _env(key: str, default: str) -> str:
"""Return os.environ[key] if set and non-empty, else default."""
v = os.environ.get(key, "").strip()
return v if v else default
# --- Config: paths overridable via environment variables ---
# ROCM_REPO_NAME: logical repo name used for APT list, Zypper/Yum repo file and section id
# ROCM_APT_*, ROCM_ZYPP_*, ROCM_YUM_*, ROCM_RDHC_REL_PATH
REPO_NAME = _env("ROCM_REPO_NAME", "rocm-test")
APT_KEYRING_DIR = _env("ROCM_APT_KEYRING_DIR", "/etc/apt/keyrings")
APT_SOURCES_LIST = _env(
"ROCM_APT_SOURCES_LIST", f"/etc/apt/sources.list.d/{REPO_NAME}.list"
)
APT_KEYRING_FILE = _env("ROCM_APT_KEYRING_FILE", "/etc/apt/keyrings/rocm.gpg")
ZYPP_REPOS_DIR = _env("ROCM_ZYPP_REPOS_DIR", "/etc/zypp/repos.d")
YUM_REPOS_DIR = _env("ROCM_YUM_REPOS_DIR", "/etc/yum.repos.d")
VERIFY_KEY_COMPONENTS = [
"bin/rocminfo",
"bin/hipcc",
"bin/clinfo",
"include/hip/hip_runtime.h",
"lib/libamdhip64.so",
]
# Relative path from install prefix to rdhc binary (script); overridable via ROCM_RDHC_REL_PATH
RDHC_REL_PATH = _env("ROCM_RDHC_REL_PATH", "libexec/rocm-core/rdhc.py")
# Pytest/CI only: becomes ``--rocm-version``.
ENV_NATIVE_LINUX_INSTALL_ROCM_VERSION = "NATIVE_LINUX_INSTALL_ROCM_VERSION"
# Timeouts (seconds) and verification threshold
GPG_MKDIR_TIMEOUT_SEC = 10
GPG_KEY_TIMEOUT_SEC = 60
APT_UPDATE_TIMEOUT_SEC = 120
ZYPP_CLEAN_TIMEOUT_SEC = 60
ZYPP_REFRESH_TIMEOUT_SEC = 120
DNF_CLEAN_TIMEOUT_SEC = 60
INSTALL_TIMEOUT_SEC = 1800 # 30 minutes
ROCMINFO_TIMEOUT_SEC = 30
# rdhc.py ``--all`` runs the full ROCm deployment health check suite; 30s was too
# short in container CI (timeouts under load). Optional cluster checks are skipped
# separately via ``--skip-optional-cluster-checks`` in ``test_rdhc``.
RDHC_TIMEOUT_SEC = 600 # 10 minutes
VERIFY_MIN_COMPONENTS = 2
_TEST_TYPE_MAP = {
"": "sanity",
"quick": "sanity",
"standard": "sanity",
"comprehensive": "full",
"full": "full",
"install": "install",
"sanity": "sanity",
"simulate": "simulate",
}
def _normalize_test_type(test_type: str | None) -> str:
"""Map shared CI test types to native package install test modes.
quick/standard/empty -> sanity, comprehensive/full -> full.
Native modes (install/sanity/full/simulate) are also accepted directly.
"""
normalized = (test_type or "").strip().lower()
try:
return _TEST_TYPE_MAP[normalized]
except KeyError as e:
valid = ", ".join(sorted(k or "<empty>" for k in _TEST_TYPE_MAP))
raise ValueError(
f"Unsupported test_type {test_type!r}. Expected one of: {valid}."
) from e
def run_simulate_install_test(pkg_type: str, packages_dir: str) -> bool:
"""Run simulated package install test (dry-run only, no actual install).
Equivalent to the GitHub Actions 'Simulated install Test' step:
- deb: apt install --simulate *.deb
- rpm: rpm -Uvh --test --nodeps *.rpm
Returns:
True if simulate succeeded, False otherwise.
"""
path = Path(packages_dir).resolve()
if not path.is_dir():
print(f"[FAIL] Not a directory: {packages_dir}", file=sys.stderr)
return False
if pkg_type == "deb":
debs = [str(p.resolve()) for p in path.glob("*.deb")]
if not debs:
print(f"[FAIL] No .deb files found in {packages_dir}", file=sys.stderr)
return False
print("Simulate installing DEB packages on host system for testing")
# Use absolute paths so apt treats them as local files, not package names
cmd = ["apt", "install", "--simulate"] + debs
elif pkg_type == "rpm":
rpms = [str(p.resolve()) for p in path.glob("*.rpm")]
if not rpms:
print(f"[FAIL] No .rpm files found in {packages_dir}", file=sys.stderr)
return False
print("Simulate installing RPM packages for testing")
# Use absolute paths for consistency
cmd = ["rpm", "-Uvh", "--test", "--nodeps"] + rpms
else:
print(
f"[FAIL] Unsupported pkg_type: {pkg_type}. Use 'deb' or 'rpm'.",
file=sys.stderr,
)
return False
try:
subprocess.run(cmd, check=True)
print("[PASS] Simulated install test completed successfully")
return True
except subprocess.CalledProcessError as e:
print(
f"[FAIL] Simulated install failed with exit code {e.returncode}",
file=sys.stderr,
)
return False
except FileNotFoundError as e:
print(f"[FAIL] Command not found: {e}", file=sys.stderr)
return False
def _run_streaming(cmd: list[str], timeout_sec: int) -> int:
"""Run a command with streaming stdout/stderr and return its exit code.
Lines are printed as they are produced. Raises subprocess.TimeoutExpired
(after killing the process) or OSError on failure.
"""
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
)
try:
for line in process.stdout:
print(line.rstrip())
sys.stdout.flush()
return process.wait(timeout=timeout_sec)
except subprocess.TimeoutExpired:
process.kill()
raise
class NativeLinuxPackageInstallTest:
"""Runner for the native Linux package install test (repo setup, install, verification)."""
@staticmethod
def _derive_package_type(os_profile: str) -> str:
"""Derive package type from OS profile.
Args:
os_profile: OS profile (e.g., ubuntu2404, rhel8, debian12, sles16, almalinux9, centos7, azl3)
Returns:
Package type ('deb' or 'rpm')
"""
os_profile_lower = os_profile.lower()
if os_profile_lower.startswith(("ubuntu", "debian")):
return "deb"
elif os_profile_lower.startswith(
("rhel", "sles", "almalinux", "centos", "azl")
):
return "rpm"
else:
raise ValueError(
f"Unable to derive package type from OS profile: {os_profile}. "
"Supported profiles: ubuntu*, debian*, rhel*, sles*, almalinux*, centos*, azl*"
)
@staticmethod
def _major_minor_rocm_version_from_input(rocm_version: str | None) -> str | None:
"""Parse ROCm version for arch-specific package names: major.minor only.
Examples: ``7.13.1`` → ``7.13``, ``v7.13`` → ``7.13``,
``7.14.0~20260520`` / ``7.14.0~rc1-123456`` → ``7.14``. Used when forming
names like ``amdrocm7.13-gfx1100``. Returns ``None`` if input is absent
or blank.
Raises:
ValueError: Non-empty input that does not start with a major.minor pattern.
"""
if rocm_version is None:
return None
s = str(rocm_version).strip()
if not s:
return None
if s.lower().startswith("v"):
s = s[1:].lstrip()
m = re.match(r"^(\d+)\.(\d+)", s)
if not m:
raise ValueError(
"Invalid ROCm version "
f"{rocm_version!r}: expected major.minor (e.g. 7.13 or 7.13.1)."
)
return f"{int(m.group(1))}.{int(m.group(2))}"
def _is_sles(self) -> bool:
"""Check if the OS profile is SLES (SUSE Linux Enterprise Server).
Returns:
True if SLES, False otherwise
"""
return self.os_profile.lower().startswith("sles")
def __init__(
self,
repo_url: str,
os_profile: str,
release_type: str = "nightly",
install_prefix: str | None = None,
gfx_arch: str | list[str] | None = None,
rocm_version: str | None = None,
gpg_key_url: str | None = None,
build_variant: str = "",
):
"""Initialize the native Linux package install test runner.
Args:
repo_url: Full repository URL (constructed in YAML)
os_profile: OS profile (e.g., ubuntu2404, rhel8, debian12, sles15, sles16, almalinux9, centos7, azl3)
release_type: Type of release ('nightly' or 'prerelease')
install_prefix: Installation prefix (default: /opt/rocm/core)
gfx_arch: Optional GPU architecture(s). Used in package names only
together with ``rocm_version``; otherwise ignored for install targets.
rocm_version: Optional ROCm release (e.g. ``7.13`` or ``7.13.1``).
Major.minor only is used in package names. With ``gfx_arch`` and
``rocm_version``: ``amdrocm{version}-{arch}`` per arch. With
``rocm_version`` only: ``amdrocm{version}`` / ``amdrocm-core-sdk{version}``.
If unset: unversioned ``amdrocm`` / ``amdrocm-core-sdk`` (``gfx_arch`` alone
does not add arch suffixes).
gpg_key_url: GPG key URL
build_variant: Build variant (e.g. 'asan'). When set, '-{variant}' is
inserted before the version suffix in package names so that variant
packages are tested (e.g. ``amdrocm-asan7.15-gfx942`` instead of
``amdrocm7.15-gfx942``).
"""
self.os_profile = os_profile.lower()
self.package_type = self._derive_package_type(os_profile)
self.repo_url = repo_url.rstrip("/")
self.release_type = release_type.lower()
self.install_prefix = install_prefix
self.gfx_arch_list = normalize_target_list(
gfx_arch, lowercase=True, dedupe=True
)
self.rocm_version_major_minor = self._major_minor_rocm_version_from_input(
rocm_version
)
# Primary arch (compat / display): first listed after normalization, else unset
self.gfx_arch: str | None = (
self.gfx_arch_list[0] if self.gfx_arch_list else None
)
self.gpg_key_url = gpg_key_url
self.build_variant = build_variant.strip().lower()
# Build variants that produce distinct package names with a '-{variant}'
# suffix inserted before the version. 'release' is the default build and
# does NOT alter the package name (amdrocm7.15, not amdrocm-release7.15).
_NAMED_VARIANTS = {"asan"}
# Metapackage install targets (four combinations of optional inputs).
# When build_variant is a named variant (e.g. 'asan'), '-{variant}' is
# inserted before the version suffix:
# gfx_arch + rocm_version -> amdrocm-asan{major.minor}-{arch} per arch
# gfx_arch only -> amdrocm-asan / amdrocm-core-sdk-asan
# rocm_version only -> amdrocm-asan{major.minor} / amdrocm-core-sdk-asan{major.minor}
# neither -> amdrocm-asan / amdrocm-core-sdk-asan
ver = self.rocm_version_major_minor
variant_sep = (
f"-{self.build_variant}" if self.build_variant in _NAMED_VARIANTS else ""
)
if self.gfx_arch_list and ver:
self.package_names = []
for arch in self.gfx_arch_list:
self.package_names.extend(
[
f"amdrocm{variant_sep}{ver}-{arch}",
f"amdrocm-core-sdk{variant_sep}{ver}-{arch}",
]
)
elif ver:
self.package_names = [
f"amdrocm{variant_sep}{ver}",
f"amdrocm-core-sdk{variant_sep}{ver}",
]
else:
self.package_names = [
f"amdrocm{variant_sep}",
f"amdrocm-core-sdk{variant_sep}",
]
def setup_gpg_key(self) -> bool:
"""Setup GPG key for repositories that require GPG verification.
Returns:
True if setup successful, False otherwise
"""
if not self.gpg_key_url:
return True # Not needed if no GPG key URL provided
print("\n" + "=" * 80)
print("SETTING UP GPG KEY")
print("=" * 80)
print(f"\nGPG Key URL: {self.gpg_key_url}")
if self.package_type == "deb":
# For DEB, import GPG key using pipeline approach
keyring_dir = Path(APT_KEYRING_DIR)
keyring_file = keyring_dir / "rocm.gpg"
try:
# Create keyring directory
print(f"\nCreating keyring directory: {keyring_dir}...")
subprocess.run(
["sudo", "mkdir", "--parents", "--mode=0755", str(keyring_dir)],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=GPG_MKDIR_TIMEOUT_SEC,
)
print(f"[PASS] Created keyring directory: {keyring_dir}")
# Download, dearmor, and write GPG key using pipeline
# wget URL -O - | gpg --dearmor | sudo tee keyring_file > /dev/null
print(f"\nDownloading and importing GPG key from {self.gpg_key_url}...")
pipeline_cmd = (
f"wget -q -O - {self.gpg_key_url} | "
f"gpg --dearmor | "
f"sudo tee {keyring_file} > /dev/null"
)
subprocess.run(
pipeline_cmd,
shell=True,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=GPG_KEY_TIMEOUT_SEC,
)
# Set proper permissions on the keyring file
subprocess.run(
["sudo", "chmod", "0644", str(keyring_file)],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
print(f"[PASS] GPG key imported to {keyring_file}")
return True
except subprocess.CalledProcessError as e:
print(f"[FAIL] Failed to setup GPG key: {e}")
if e.stderr:
print(f"Error output: {e.stderr.decode()}")
return False
except OSError as e:
print(f"[FAIL] Error setting up GPG key: {e}")
return False
else: # rpm
# For RPM (including SLES), GPG key URL is specified in repo file
# zypper will automatically fetch and use the GPG key from the URL
# No need to download or import separately (following official ROCm documentation)
return True
def setup_deb_repository(self) -> bool:
"""Setup DEB repository on the system.
Returns:
True if setup successful, False otherwise
"""
print("\n" + "=" * 80)
print("SETTING UP DEB REPOSITORY")
print("=" * 80)
print(f"\nRepository URL: {self.repo_url}")
print(f"Release Type: {self.release_type}")
# Setup GPG key if GPG key URL is provided
if self.gpg_key_url:
if not self.setup_gpg_key():
return False
# Add repository to sources list
print("\nAdding ROCm repository...")
sources_list = Path(APT_SOURCES_LIST)
if self.gpg_key_url:
# Use GPG key verification (arch=amd64 matches ROCm Ubuntu install docs)
apt_keyring = Path(APT_KEYRING_FILE)
repo_entry = f"deb [arch=amd64 signed-by={apt_keyring}] {self.repo_url} stable main\n"
else:
# No GPG check (trusted=yes; arch=amd64 matches install_rocm_packages.sh)
repo_entry = f"deb [arch=amd64 trusted=yes] {self.repo_url} stable main\n"
try:
subprocess.run(
["sudo", "tee", str(sources_list)],
input=repo_entry.encode(),
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
)
print(f"[PASS] Repository added to {sources_list}")
print(f" {repo_entry.strip()}")
except (OSError, subprocess.CalledProcessError) as e:
print(f"[FAIL] Failed to add repository: {e}")
return False
# Update package lists
print("\nUpdating package lists...")
print("=" * 80)
try:
return_code = _run_streaming(
["sudo", "apt", "update"], APT_UPDATE_TIMEOUT_SEC
)
if return_code == 0:
print("\n[PASS] Package lists updated")
return True
print(f"\n[FAIL] Failed to update package lists (exit code: {return_code})")
return False
except subprocess.TimeoutExpired:
print("\n[FAIL] apt update timed out")
return False
except OSError as e:
print(f"[FAIL] Error updating package lists: {e}")
return False
def _setup_sles_repository(self) -> bool:
"""Setup repository for SLES using zypper.
Returns:
True if setup successful, False otherwise
"""
repo_name = REPO_NAME
repo_file = Path(ZYPP_REPOS_DIR) / f"{repo_name}.repo"
# Remove existing repository if it exists
print(f"\nRemoving existing repository '{repo_name}' if it exists...")
subprocess.run(
["zypper", "--non-interactive", "removerepo", repo_name],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
) # Ignore errors if repo doesn't exist
# Create repository file following official ROCm documentation format
# Reference: https://rocm.docs.amd.com/projects/install-on-linux/en/latest/install/install-methods/package-manager/package-manager-sles.html
print(f"\nCreating ROCm repository file at {repo_file}...")
if self.gpg_key_url:
# Use GPG key verification (gpgcheck=1)
repo_content = f"""[{repo_name}]
name=ROCm {self.release_type} repository
baseurl={self.repo_url}
enabled=1
gpgcheck=1
gpgkey={self.gpg_key_url}
"""
else:
# No GPG check (gpgcheck=0)
repo_content = f"""[{repo_name}]
name=ROCm {self.release_type} repository
baseurl={self.repo_url}
enabled=1
gpgcheck=0
"""
try:
repo_file.write_text(repo_content, encoding="utf-8")
print(f"[PASS] Repository file created: {repo_file}")
print("\nRepository configuration:")
print(repo_content)
except OSError as e:
print(f"[FAIL] Failed to create repository file: {e}")
return False
# Clean zypper cache
print("\nCleaning zypper cache...")
try:
result = subprocess.run(
["zypper", "--non-interactive", "clean", "--all"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
timeout=ZYPP_CLEAN_TIMEOUT_SEC,
)
if result.returncode == 0:
print("[PASS] zypper cache cleaned")
else:
print(
f"[WARN] zypper clean returned {result.returncode} (may not be critical)"
)
except subprocess.TimeoutExpired:
print("[WARN] zypper clean timed out (may not be critical)")
except (subprocess.CalledProcessError, OSError) as e:
print(f"[WARN] zypper clean failed: {e} (may not be critical)")
# Refresh repository metadata
print("\nRefreshing repository metadata...")
try:
# Use --non-interactive to avoid prompts
# If GPG key URL is provided, use --gpg-auto-import-keys to automatically import and trust GPG keys
refresh_cmd = ["zypper", "--non-interactive"]
if self.gpg_key_url:
refresh_cmd.append("--gpg-auto-import-keys")
refresh_cmd.extend(["refresh", repo_name])
return_code = _run_streaming(refresh_cmd, ZYPP_REFRESH_TIMEOUT_SEC)
if return_code == 0:
print("\n[PASS] Repository metadata refreshed")
return True
print(
f"\n[FAIL] Failed to refresh repository metadata (exit code: {return_code})"
)
return False
except subprocess.TimeoutExpired:
print("\n[FAIL] zypper refresh timed out")
return False
except OSError as e:
print(f"[FAIL] Error refreshing repository metadata: {e}")
return False
def _setup_dnf_repository(self) -> bool:
"""Setup repository for RHEL/AlmaLinux/CentOS using dnf/yum.
Returns:
True if setup successful, False otherwise
"""
print("\nUsing dnf/yum for repository setup...")
# Create repository file
print("\nCreating ROCm repository file...")
repo_name = REPO_NAME
repo_file = Path(YUM_REPOS_DIR) / f"{repo_name}.repo"
if self.gpg_key_url:
# Use GPG key verification
repo_content = f"""[{repo_name}]
name=ROCm Repository
baseurl={self.repo_url}
enabled=1
gpgcheck=1
gpgkey={self.gpg_key_url}
"""
else:
# No GPG check
repo_content = f"""[{repo_name}]
name=Native Linux Package Test Repository
baseurl={self.repo_url}
enabled=1
gpgcheck=0
"""
try:
repo_file.write_text(repo_content, encoding="utf-8")
print(f"[PASS] Repository file created: {repo_file}")
print("\nRepository configuration:")
print(repo_content)
except OSError as e:
print(f"[FAIL] Failed to create repository file: {e}")
return False
# Clean dnf cache
print("\nCleaning dnf cache...")
try:
subprocess.run(
["dnf", "clean", "all"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
timeout=DNF_CLEAN_TIMEOUT_SEC,
)
print("[PASS] dnf cache cleaned")
except subprocess.CalledProcessError as e:
print("[WARN] Failed to clean dnf cache (may not be critical)")
print(f"Error: {e.stdout}")
except subprocess.TimeoutExpired:
print("[WARN] dnf clean timed out (may not be critical)")
print("\n[PASS] DNF repository setup complete")
return True
def setup_rpm_repository(self) -> bool:
"""Setup RPM repository on the system.
Returns:
True if setup successful, False otherwise
"""
print("\n" + "=" * 80)
print("SETTING UP RPM REPOSITORY")
print("=" * 80)
print(f"\nRepository URL: {self.repo_url}")
print(f"Release Type: {self.release_type}")
print(f"OS Profile: {self.os_profile}")
# Setup GPG key if GPG key URL is provided (only needed for non-SLES systems)
# SLES uses --gpg-auto-import-keys flag which handles it automatically
if self.gpg_key_url and not self._is_sles():
if not self.setup_gpg_key():
return False
# SLES uses zypper, others use dnf/yum
if self._is_sles():
return self._setup_sles_repository()
else:
return self._setup_dnf_repository()
def install_deb_packages(self) -> bool:
"""Install ROCm DEB packages from repository.
Returns:
True if installation successful, False otherwise
"""
print("\n" + "=" * 80)
print("INSTALLING DEB PACKAGES FROM REPOSITORY")
print("=" * 80)
print(f"\nPackages to install (in order): {self.package_names}")
# Install using apt (packages in list order)
cmd = ["sudo", "apt", "install", "-y"] + self.package_names
print(f"\nRunning: {' '.join(cmd)}")
print("=" * 80)
print("Installation progress (streaming output):\n")
try:
return_code = _run_streaming(cmd, INSTALL_TIMEOUT_SEC)
if return_code == 0:
print("\n" + "=" * 80)
print("[PASS] DEB packages installed successfully from repository")
return True
print("\n" + "=" * 80)
print(f"[FAIL] Failed to install DEB packages (exit code: {return_code})")
return False
except subprocess.TimeoutExpired:
print("\n" + "=" * 80)
print(f"[FAIL] Installation timed out after {INSTALL_TIMEOUT_SEC} minutes")
return False
except OSError as e:
print(f"\n[FAIL] Error during installation: {e}")
return False
def install_rpm_packages(self) -> bool:
"""Install ROCm RPM packages from repository.
Returns:
True if installation successful, False otherwise
"""
print("\n" + "=" * 80)
print("INSTALLING RPM PACKAGES FROM REPOSITORY")
print("=" * 80)
print(f"\nPackages to install (in order): {self.package_names}")
# Use zypper for SLES, dnf for others
if self._is_sles():
# If no GPG key URL, skip GPG checks during installation
if not self.gpg_key_url:
cmd = [
"zypper",
"--non-interactive",
"--no-gpg-checks",
"install",
"-y",
] + self.package_names
else:
# If GPG key URL is provided, use --gpg-auto-import-keys to automatically import and trust GPG keys
cmd = [
"zypper",
"--non-interactive",
"--gpg-auto-import-keys",
"install",
"-y",
] + self.package_names
print("[INFO] Using zypper for SLES package installation")
else:
cmd = ["dnf", "install", "-y"] + self.package_names
print(f"\nRunning: {' '.join(cmd)}")
print("=" * 80)
print("Installation progress (streaming output):\n")
try:
return_code = _run_streaming(cmd, INSTALL_TIMEOUT_SEC)
if return_code == 0:
print("\n" + "=" * 80)
print("[PASS] RPM packages installed successfully from repository")
return True
print("\n" + "=" * 80)
print(f"[FAIL] Failed to install RPM packages (exit code: {return_code})")
return False
except subprocess.TimeoutExpired:
print("\n" + "=" * 80)
print(f"[FAIL] Installation timed out after {INSTALL_TIMEOUT_SEC} minutes")
return False
except OSError as e:
print(f"\n[FAIL] Error during installation: {e}")
return False
def run_repo_setup_and_install(self) -> bool:
"""Step 1: Repo setup and install. Run for both sanity (basic) and full test.
Returns:
True if repository setup and package installation both succeeded.
"""
print("\n" + "=" * 80)
print("STEP 1: REPOSITORY SETUP AND PACKAGE INSTALLATION")
print("=" * 80)
print(f"\nOS Profile: {self.os_profile}")
print(f"Package Type (derived): {self.package_type.upper()}")
# Log how GPU arch relates to package_names (same four cases as __init__).
if self.gfx_arch_list and self.rocm_version_major_minor:
print(f"GPU Architecture(s): {self.gfx_arch_list} (used in package names)")
elif self.gfx_arch_list:
print(
f"GPU Architecture(s): {self.gfx_arch_list} "
"(not used in package names without --rocm-version / "
f"{ENV_NATIVE_LINUX_INSTALL_ROCM_VERSION})"
)
else:
ver = self.rocm_version_major_minor
if ver:
print(
"GPU Architecture(s): (none — generic versioned packages "
f"amdrocm{ver}, amdrocm-core-sdk{ver})"
)
else:
print(
"GPU Architecture(s): (none — generic packages amdrocm, amdrocm-core-sdk)"
)
print(f"Repository URL: {self.repo_url}")
print(f"Packages (in order): {self.package_names}")
if self.package_type == "deb":
if not self.setup_deb_repository():
return False
return self.install_deb_packages()
else:
if not self.setup_rpm_repository():
return False
return self.install_rpm_packages()
def run_basic_verification(self) -> bool:
"""Step 2: Basic test — install prefix, key components, packages list, rocminfo.
Used by both --test-type sanity and full. Does not run test_rdhc
(that is Step 3 / run_full_verification, full test only).
Returns:
True if basic verification passed (enough components found).
"""
print("\n" + "=" * 80)
print("STEP 2: BASIC INSTALL VERIFICATION")
print("=" * 80)
install_path = Path(self.install_prefix)
if not install_path.exists():
print(f"\n[FAIL] Installation directory not found: {self.install_prefix}")
return False
print(f"\n[PASS] Installation directory exists: {self.install_prefix}")
key_components = VERIFY_KEY_COMPONENTS
print("\nChecking for key ROCm components:")
found_count = 0
for component in key_components:
component_path = install_path / component
if component_path.exists():
print(f" [PASS] {component}")
found_count += 1
else:
print(f" [WARN] {component} (not found)")
print(f"\nComponents found: {found_count}/{len(key_components)}")
# Check installed packages
print("\nChecking installed packages:")
try:
if self.package_type == "deb":
cmd = ["dpkg", "-l"]
grep_pattern = "rocm"
elif self._is_sles():
cmd = ["zypper", "--non-interactive", "search", "-i", "rocm"]
grep_pattern = "rocm"
else:
cmd = ["rpm", "-qa"]
grep_pattern = "rocm"
result = subprocess.run(
cmd,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
rocm_packages = [
line
for line in result.stdout.split("\n")
if grep_pattern.lower() in line.lower()
]
print(f" Found {len(rocm_packages)} ROCm packages installed")
if rocm_packages:
print("\n Sample packages (Show first 5):")
for pkg in rocm_packages[:5]:
print(f" {pkg.strip()}")
if len(rocm_packages) > 5:
print(f" ... and {len(rocm_packages) - 5} more")
except subprocess.CalledProcessError:
print(" [WARN] Could not query installed packages")
# Try to run rocminfo if available
rocminfo_path = install_path / "bin" / "rocminfo"
if rocminfo_path.exists():
print("\nTrying to run rocminfo...")
try:
result = subprocess.run(
[str(rocminfo_path)],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
timeout=ROCMINFO_TIMEOUT_SEC,
)
print(" [PASS] rocminfo executed successfully")
lines = result.stdout.split("\n")[:10]
print("\n First few lines of rocminfo output:")
for line in lines:
if line.strip():
print(f" {line}")
except subprocess.TimeoutExpired:
print(" [WARN] rocminfo timed out (may require GPU hardware)")
except subprocess.CalledProcessError:
print(" [WARN] rocminfo failed (may require GPU hardware)")
except OSError as e:
print(f" [WARN] Could not run rocminfo: {e}")
if found_count >= VERIFY_MIN_COMPONENTS:
print("\n[PASS] Basic verification PASSED")
return True
print("\n[FAIL] Basic verification FAILED (insufficient components)")
return False
def run_full_verification(self) -> bool:
"""Step 3: Full test — runs test_rdhc (rdhc.py) only. Used when --test-type is full."""
print("\n" + "=" * 80)
print("STEP 3: FULL VERIFICATION (RDHC)")
print("=" * 80)
return self.test_rdhc()
def test_rdhc(self) -> bool:
"""Test rdhc.py binary in libexec/rocm-core/.
Returns:
True if test successful, False otherwise
"""
print("\n" + "=" * 80)
print("TESTING RDHC.PY")
print("=" * 80)
install_path = Path(self.install_prefix).resolve()
rdhc_script = (install_path / RDHC_REL_PATH).resolve()
rocm_install_prefix_arg = str(install_path)
# Check if script exists
if not rdhc_script.exists():
print(f"\n[WARN] rdhc.py not found at: {rdhc_script}")
print(" This is expected if rocm-core package is not installed")
return False
print(f"\n[PASS] rdhc.py found at: {rdhc_script}")
# Always run rdhc with this process's interpreter.
cmd = [sys.executable, str(rdhc_script)]
# Set RDHC arguments for full test
test_args = [
"--rocm-install-prefix",
rocm_install_prefix_arg,
"--all",
"--skip-optional-cluster-checks",
]
print(
f"\nRun rdhc.py with --rocm-install-prefix {rocm_install_prefix_arg} --all..."
)
print(f"Command: {' '.join(cmd + test_args)}")
try:
result = subprocess.run(
cmd + test_args,
cwd=str(install_path),
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
timeout=RDHC_TIMEOUT_SEC,
)
print(" [PASS] rdhc.py executed successfully")
if result.stdout:
# Print first few lines of output
lines = result.stdout.split("\n")[:5]
print("\n First few lines of output:")
for line in lines:
if line.strip():
print(f" {line}")
return True
except subprocess.TimeoutExpired:
print(" [WARN] rdhc.py --all timed out")
return False
except subprocess.CalledProcessError:
print(" [WARN] rdhc.py --all failed")
return False
except OSError as e:
print(f" [WARN] Could not run rdhc.py: {e}")
return False
_CLI_EXAMPLES_EPILOG = """
Examples:
# Nightly DEB (Ubuntu 24.04) - run inside matching container/VM
python native_linux_package_install_test.py --os-profile ubuntu2404 \\
--repo-url https://rocm.nightlies.amd.com/deb/20260204-21658678136/ \\
--gfx-arch gfx94x --release-type nightly --install-prefix /opt/rocm/core
# Prerelease DEB with GPG verification
python native_linux_package_install_test.py --os-profile ubuntu2404 \\
--repo-url https://rocm.prereleases.amd.com/packages/ubuntu2404 \\
--gfx-arch gfx94x --release-type prerelease --install-prefix /opt/rocm/core \\
--gpg-key-url https://rocm.prereleases.amd.com/packages/gpg/rocm.gpg
# Nightly RPM (RHEL 8)
python native_linux_package_install_test.py --os-profile rhel8 \\
--repo-url https://rocm.nightlies.amd.com/rpm/20260204-21658678136/rhel8/x86_64/ \\
--gfx-arch gfx94x --release-type nightly --install-prefix /opt/rocm/core
# --test-type full on RHEL 8 (rdhc needs pciutils/kmod on the host — install before running)
python native_linux_package_install_test.py --test-type full --os-profile rhel8 \\
--repo-url https://rocm.nightlies.amd.com/rpm/20260204-21658678136/rhel8/x86_64/ \\
--gfx-arch gfx94x --release-type nightly --install-prefix /opt/rocm/core
# Prerelease RPM (RHEL 8)
python native_linux_package_install_test.py --os-profile rhel8 \\
--repo-url https://rocm.prereleases.amd.com/packages/rhel8/x86_64/ \\
--gfx-arch gfx94x --release-type prerelease --install-prefix /opt/rocm/core \\
--gpg-key-url https://rocm.prereleases.amd.com/packages/gpg/rocm.gpg
# --test-type sanity (default): repo install + basic verification only
python native_linux_package_install_test.py --test-type sanity --os-profile ubuntu2404 \\
--repo-url https://rocm.nightlies.amd.com/deb/20260204-21658678136/ \\
--gfx-arch gfx94x --release-type nightly --install-prefix /opt/rocm/core
# --test-type full: install + basic verification + rdhc
python native_linux_package_install_test.py --test-type full --os-profile ubuntu2404 \\
--repo-url https://rocm.nightlies.amd.com/deb/20260204-21658678136/ \\
--gfx-arch gfx94x --release-type nightly --install-prefix /opt/rocm/core
# --test-type install: install only
python native_linux_package_install_test.py --test-type install --os-profile ubuntu2404 \\
--repo-url https://therock-dev-artifacts.s3.amazonaws.com/26299074718-linux/packages/deb \\
--gfx-arch gfx94x --release-type dev --install-prefix /opt/rocm/core
# Versioned + multiple --gfx-arch (metapackages amdrocm7.13-<arch> per arch)
python native_linux_package_install_test.py --os-profile ubuntu2404 \\
--repo-url https://rocm.nightlies.amd.com/deb/20260204-21658678136/ \\
--rocm-version 7.12.1 --gfx-arch gfx94x gfx1100 --release-type nightly \\
--install-prefix /opt/rocm/core
# Comma-separated arches in one argument (equivalent normalization)
python native_linux_package_install_test.py --os-profile ubuntu2404 \\
--repo-url https://rocm.nightlies.amd.com/deb/20260204-21658678136/ \\
--rocm-version 7.12 --gfx-arch gfx94x,gfx1100 --release-type nightly \\
--install-prefix /opt/rocm/core
# Semicolon-separated (quote for POSIX shells so ``;`` is not a command separator)
python native_linux_package_install_test.py --os-profile ubuntu2404 \\
--repo-url https://rocm.nightlies.amd.com/deb/20260204-21658678136/ \\
--rocm-version 7.12 --gfx-arch 'gfx94x;gfx1100' --release-type nightly \\
--install-prefix /opt/rocm/core
# --rocm-version without --gfx-arch: amdrocm7.13 / amdrocm-core-sdk7.13 only
python native_linux_package_install_test.py --os-profile ubuntu2404 \\
--repo-url https://therock-dev-artifacts.s3.amazonaws.com/25137154844-linux/packages/deb \\
--rocm-version 7.13 --release-type dev --install-prefix /opt/rocm/core
# without --rocm-version without --gfx-arch: amdrocm / amdrocm-core-sdk only
python native_linux_package_install_test.py --os-profile ubuntu2404 \\
--repo-url https://therock-dev-artifacts.s3.amazonaws.com/25137154844-linux/packages/deb \\
--release-type dev --install-prefix /opt/rocm/core
# Simulate install (dry-run) from local packages
python native_linux_package_install_test.py --test-type simulate --packages-dir /path/to/pkgs --os-profile ubuntu2404
python native_linux_package_install_test.py --test-type simulate --packages-dir /path/to/rpms --pkg-type rpm
"""
def _build_argument_parser(*, exit_on_error: bool = True) -> ArgumentParser:
kwargs: dict = dict(
description="Full installation and simulate-install test for ROCm native packages",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=_CLI_EXAMPLES_EPILOG,
)
if sys.version_info >= (3, 9):
kwargs["exit_on_error"] = exit_on_error
parser = ArgumentParser(**kwargs)
parser.add_argument(
"--os-profile",
type=str,
help="OS profile (e.g., ubuntu2404, rhel8, debian12, sles15, sles16, almalinux9, centos7, azl3). Required for sanity/full; for simulate, used only to derive pkg-type if --pkg-type is omitted.",
)
parser.add_argument(
"--repo-url",
type=str,
help="Full repository URL (constructed in YAML workflow). Required for sanity/full; not used for simulate.",
)
parser.add_argument(
"--gfx-arch",
type=str,
nargs="+",
default=None,
metavar="ARCH",
help="GPU architecture(s), optional. Used in package names only with --rocm-version "
"(e.g. amdrocm7.13-gfx94x). Without --rocm-version, arch is ignored for install targets "
"(generic amdrocm / amdrocm-core-sdk). "
"Repeat flag or list; commas and semicolons split within each value. Not used for simulate.",
)
parser.add_argument(
"--rocm-version",
type=str,
default=None,
metavar="VER",
help=(
"ROCm release (major.minor only in package names). With --gfx-arch: amdrocm7.13-ARCH per arch; "
"without --gfx-arch: amdrocm7.13 and amdrocm-core-sdk7.13. Required for arch in package names. "
"Optional. Not used for simulate."
),
)
parser.add_argument(
"--release-type",
type=str,
choices=["dev", "nightly", "prerelease", "release", "ci"],
help="Type of release: 'dev', 'nightly', 'prerelease', 'release', or 'ci'",
)
parser.add_argument(
"--install-prefix",
type=str,
help="Installation prefix (e.g. /opt/rocm/core)",
)
parser.add_argument(
"--gpg-key-url",
type=str,
help="GPG key URL",
)
parser.add_argument(
"--build-variant",
type=str,
default="",
help="Build variant (e.g. 'asan'). Changes expected package names to match variant-suffixed packages.",
)
parser.add_argument(
"--test-type",
type=str,
default="sanity",
help="Test type: 'install' = repo install only; 'sanity' = install + basic verification; 'full' = sanity + rdhc; 'simulate' = dry-run local packages (requires --packages-dir). Also accepts CI test types: quick, standard, comprehensive.",
)
parser.add_argument(
"--packages-dir",
type=str,
metavar="DIR",
help="Directory containing .deb or .rpm files. Required when --test-type is 'simulate'.",
)
parser.add_argument(
"--pkg-type",
type=str,
choices=["deb", "rpm"],
help="Package type (deb or rpm). For --test-type simulate only; if omitted, derived from --os-profile.",
)
return parser
def _validate_cli_args(parser: ArgumentParser, args: Namespace) -> None:
if args.test_type == "simulate":
if not args.packages_dir:
parser.error("--packages-dir is required when --test-type is 'simulate'")
if not args.pkg_type and not args.os_profile:
parser.error(
"When --test-type is 'simulate', provide --pkg-type or --os-profile"
)
if args.os_profile and not args.pkg_type:
try:
NativeLinuxPackageInstallTest._derive_package_type(args.os_profile)
except ValueError as e:
parser.error(str(e))
return
if not args.os_profile:
parser.error(
"--os-profile is required when --test-type is 'install', 'sanity', or 'full'"
)
if not args.repo_url:
parser.error(
"--repo-url is required when --test-type is 'install', 'sanity', or 'full'"
)
if args.rocm_version:
try:
NativeLinuxPackageInstallTest._major_minor_rocm_version_from_input(
args.rocm_version
)
except ValueError as e:
parser.error(str(e))
def parse_cli_arguments(
argv: list[str] | None = None, *, raise_instead_of_exit: bool = False
) -> Namespace:
"""Build parser, parse argv, validate.
By default invalid input calls ``parser.error`` (exits the process). For pytest
or other callers, pass ``raise_instead_of_exit=True`` to get ``ValueError``
instead of ``sys.exit``.
"""
exit_on_error = not raise_instead_of_exit
parser = _build_argument_parser(exit_on_error=exit_on_error)
if raise_instead_of_exit:
def _raise(msg: str) -> None:
raise ValueError(msg)
parser.error = _raise # type: ignore[method-assign]
args = parser.parse_args(argv)
try:
args.test_type = _normalize_test_type(args.test_type)
except ValueError as e:
parser.error(str(e))
_validate_cli_args(parser, args)
return args
def run_tests(args: Namespace) -> int:
"""Run simulate or repo-based install test from parsed CLI args. Returns exit code (0 success)."""
if args.test_type == "simulate":
pkg_type = args.pkg_type or NativeLinuxPackageInstallTest._derive_package_type(
args.os_profile
)
print("\n" + "=" * 80)
print("SIMULATED INSTALL TEST")
print("=" * 80)
ok = run_simulate_install_test(pkg_type, args.packages_dir)
if ok:
return 0
return 1
try:
derived_package_type = NativeLinuxPackageInstallTest._derive_package_type(
args.os_profile
)
except ValueError as e:
print(f"Error: {e}", file=sys.stderr)
return 2
print("\n" + "=" * 80)
print("CONFIGURATION")
print("=" * 80)
print(f"OS Profile: {args.os_profile}")
print(f"Package Type (derived): {derived_package_type}")
print(f"Release Type: {args.release_type}")
print(f"Repository URL: {args.repo_url}")
# Preview package-name rules before NativeLinuxPackageInstallTest is constructed.
_norm = normalize_target_list(args.gfx_arch, lowercase=True, dedupe=True)
if _norm:
if args.rocm_version:
print(
f"GPU Architecture(s): {args.gfx_arch} "
f"(normalized: {_norm}; used in package names with --rocm-version)"
)
else:
print(
f"GPU Architecture(s): {args.gfx_arch} "
f"(normalized: {_norm}; not used in package names without --rocm-version)"
)
else:
if args.rocm_version:
_gv = NativeLinuxPackageInstallTest._major_minor_rocm_version_from_input(
args.rocm_version
)
print(
"GPU Architecture(s): (none — generic versioned packages "
f"amdrocm{_gv}, amdrocm-core-sdk{_gv})"
)
else:
print("GPU Architecture(s): (none — generic amdrocm, amdrocm-core-sdk)")
if args.rocm_version:
_rv = NativeLinuxPackageInstallTest._major_minor_rocm_version_from_input(
args.rocm_version
)
print(
f"ROCm version (for package names): {args.rocm_version} "
f"(major.minor: {_rv})"
)
else:
print("ROCm version (for package names): (not set)")
print(f"Install Prefix: {args.install_prefix}")
print(f"Test Type: {args.test_type}")
if args.gpg_key_url:
print(f"GPG Key URL: {args.gpg_key_url}")
print("=" * 80)
test_runner = NativeLinuxPackageInstallTest(
os_profile=args.os_profile,
repo_url=args.repo_url,
release_type=args.release_type,
install_prefix=args.install_prefix,
gfx_arch=args.gfx_arch,
rocm_version=args.rocm_version,
gpg_key_url=args.gpg_key_url,
build_variant=args.build_variant,
)
print("\n" + "=" * 80)
print("INSTALLATION TEST - NATIVE LINUX PACKAGES")
print("=" * 80)
print(f"Release Type: {test_runner.release_type.upper()}")
print(f"Install Prefix: {test_runner.install_prefix}")
print(f"Test Type: {args.test_type}")
print("=" * 80)
try:
if not test_runner.run_repo_setup_and_install():
print("\n[FAIL] Step 1 (repo setup and install) failed.")
return 1
if args.test_type == "install":
print("\n" + "=" * 80)
print("[PASS] INSTALLATION TEST PASSED")
print("(install: repo setup and package install completed)")
print("=" * 80 + "\n")
return 0
if not test_runner.run_basic_verification():
print("\n[FAIL] Step 2 (basic verification) failed.")
return 1
if args.test_type == "full":
if not test_runner.run_full_verification():
print("\n[FAIL] Step 3 (full verification) failed.")
return 1
print("\n" + "=" * 80)
print("[PASS] INSTALLATION TEST PASSED")
if args.test_type == "sanity":
print("(sanity: basic verification completed)")
else:
print("ROCm has been successfully installed from repository and verified!")
print("=" * 80 + "\n")
return 0
except Exception as e:
print(f"\n[FAIL] Error during installation test: {e}")
traceback.print_exc()
return 1
def _argv_from_ci_env() -> list[str] | None:
"""Build CLI argv from workflow/container env (see ``test_native_linux_packages_install.yml``).
Required for sanity/full: OS_PROFILE, REPO_URL, RELEASE_TYPE, INSTALL_PREFIX.
Optional: GFX_ARCH, GPG_KEY_URL; ``NATIVE_LINUX_INSTALL_ROCM_VERSION`` maps to ``--rocm-version``
only when versioned package names are needed (omit for unversioned installs).
"""
test_type = (os.environ.get("TEST_TYPE") or "sanity").strip().lower() or "sanity"
if test_type == "simulate":
packages_dir = (os.environ.get("PACKAGES_DIR") or "").strip()
if not packages_dir:
return None
argv: list[str] = [
"--test-type",
"simulate",
"--packages-dir",
packages_dir,
]
os_profile = (os.environ.get("OS_PROFILE") or "").strip()
if os_profile:
argv.extend(["--os-profile", os_profile])
pkg_type = (os.environ.get("SIMULATE_PKG_TYPE") or "").strip()
if pkg_type in ("deb", "rpm"):
argv.extend(["--pkg-type", pkg_type])
prefix = (os.environ.get("INSTALL_PREFIX") or "").strip()
if prefix:
argv.extend(["--install-prefix", prefix])
return argv
os_profile = (os.environ.get("OS_PROFILE") or "").strip()
repo_url = (os.environ.get("REPO_URL") or "").strip()
gfx_raw = (os.environ.get("GFX_ARCH") or "").strip()
# Semicolons delimit arches in CI; normalize to whitespace so split() does not
# leave stray ';' on the first token (e.g. "gfx94x; gfx1100").
gfx_arch = gfx_raw.replace(";", " ").split() if gfx_raw else []
release_type = (os.environ.get("RELEASE_TYPE") or "").strip()
install_prefix = (os.environ.get("INSTALL_PREFIX") or "").strip()
if not (os_profile and repo_url and release_type and install_prefix):
return None
argv = [
"--test-type",
test_type,
"--os-profile",
os_profile,
"--repo-url",
repo_url,
"--release-type",
release_type,
"--install-prefix",
install_prefix,
]
if gfx_arch:
argv.extend(["--gfx-arch", *gfx_arch])
rocm_version = (os.environ.get(ENV_NATIVE_LINUX_INSTALL_ROCM_VERSION) or "").strip()
if rocm_version:
argv.extend(["--rocm-version", rocm_version])
gpg = (os.environ.get("GPG_KEY_URL") or "").strip()
if gpg:
argv.extend(["--gpg-key-url", gpg])
build_variant = (os.environ.get("BUILD_VARIANT") or "").strip()
if build_variant:
argv.extend(["--build-variant", build_variant])
return argv
def test_native_linux_package_install() -> None:
"""Pytest entry: same run as CLI, driven by env vars in CI."""
import pytest
argv = _argv_from_ci_env()
if argv is None:
if os.environ.get("GITHUB_ACTIONS") == "true":
pytest.fail(
"Missing required environment variables for native install test "
"(expected OS_PROFILE, REPO_URL, RELEASE_TYPE, INSTALL_PREFIX; "
"optional GFX_ARCH, NATIVE_LINUX_INSTALL_ROCM_VERSION; or for simulate: PACKAGES_DIR)."
)
pytest.skip(
"Set workflow env vars (OS_PROFILE, REPO_URL, RELEASE_TYPE, INSTALL_PREFIX); "
"optional GFX_ARCH and NATIVE_LINUX_INSTALL_ROCM_VERSION."
)
args = parse_cli_arguments(argv, raise_instead_of_exit=True)
rc = run_tests(args)
assert rc == 0, f"run_tests exited with code {rc}"
def main() -> None:
"""Entry point: parse/validate CLI, then run tests."""
args = parse_cli_arguments()
sys.exit(run_tests(args))
if __name__ == "__main__":
main()