Files
TheRock/build_tools/packaging/linux/rpm_package.py
T
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

268 lines
9.2 KiB
Python

#!/usr/bin/env python3
# Copyright Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""RPM package creation functions for ROCm packaging."""
import os
import re
import subprocess
import sys
from dataclasses import replace
from jinja2 import Environment, FileSystemLoader, select_autoescape
from pathlib import Path
from packaging_utils import *
# Setup paths
SCRIPT_DIR = Path(__file__).resolve().parent
def create_nonversioned_rpm_package(pkg_name, config: PackageConfig):
"""Create a non-versioned RPM meta package (.rpm).
Builds a minimal RPM binary package whose payload is empty and whose primary
purpose is to express dependencies. The package name does not embed a version
Parameters:
pkg_name : Name of the package to be created
config: Configuration object containing package metadata
Returns:
output_list: List of packages created
"""
print_function_name()
# Create immutable config copy with versioned_pkg=False
build_config = replace(config, versioned_pkg=False)
# Use updated package name for build directory to avoid collisions between variants
updated_pkg_name = update_package_name(pkg_name, build_config)
package_dir = Path(build_config.dest_dir) / build_config.pkg_type / updated_pkg_name
specfile = package_dir / "specfile"
generate_spec_file(pkg_name, specfile, build_config)
package_with_rpmbuild(specfile)
# Move packages to destination
output_list = move_packages_to_destination(updated_pkg_name, build_config)
return output_list
def create_versioned_rpm_package(pkg_name, config: PackageConfig):
"""Create a versioned RPM package (.rpm).
This function automates the process of building a RPM package by:
1) Generating the spec file with appropriate fields (Package,
Version, Architecture, Maintainer, Description, and dependencies).
2) Invoking `rpmbuild` to assemble the final `.rpm` file.
Parameters:
pkg_name : Name of the package to be created
config: Configuration object containing package metadata
Returns:
output_list: List of packages created
"""
print_function_name()
# Explicitly ensure versioned_pkg=True
build_config = replace(config, versioned_pkg=True)
# Use updated package name for build directory to avoid collisions between variants
# Each variant (host, device, meta) gets its own build directory
updated_pkg_name = update_package_name(pkg_name, build_config)
package_dir = Path(build_config.dest_dir) / build_config.pkg_type / updated_pkg_name
specfile = package_dir / "specfile"
generate_spec_file(pkg_name, specfile, build_config)
package_with_rpmbuild(specfile)
# Move packages to destination
output_list = move_packages_to_destination(updated_pkg_name, build_config)
return output_list
def generate_spec_file(pkg_name, specfile, config: PackageConfig):
"""Generate an RPM spec file.
Parameters:
pkg_name : Package name
specfile: Path where the generated spec file should be saved
config: Configuration object containing package metadata
Returns: None
"""
print_function_name()
os.makedirs(os.path.dirname(specfile), exist_ok=True)
pkg_info = get_package_info(pkg_name) # Raises ValueError if not found
version = f"{config.rocm_version}"
is_meta = is_meta_package(pkg_info)
# Initialize optional fields
provides = obsoletes = conflicts = ""
rpmrecommends = rpmsuggests = ""
sourcedir_list = []
rpm_scripts = []
# amdrocm-debugger: Exclude libpython requirements
# Multiple Python-version-specific binaries are included; the wrapper script
# automatically selects the binary matching the system's Python version
exclude_libpython_requires = pkg_name == "amdrocm-debugger"
if config.versioned_pkg:
# Get -> Filter -> Transform
rpmrecommends = process_secondary_dependencies(
pkg_info, "RPMRecommends", config
)
rpmsuggests = process_secondary_dependencies(pkg_info, "RPMSuggests", config)
requires = process_main_dependencies(pkg_info, "RPMRequires", config)
dir_list = filter_components_fromartifactory(
pkg_name, config.artifacts_dir, config.gfx_arch, config.enable_kpack
)
sourcedir_list.extend(dir_list)
# Filter out non-existing directories
sourcedir_list = [path for path in sourcedir_list if os.path.isdir(path)]
# GFX_META is a versioned meta package (empty content, just dependencies)
is_gfx_meta = config.enable_kpack and config.gfx_arch == GFX_META
# Warn if we have no artifacts for non-meta packages
if not sourcedir_list and not is_meta and not is_gfx_meta:
if config.enable_kpack:
print(
f"WARNING: {pkg_name}: Empty sourcedir_list and not a meta package, creating empty RPM"
)
else:
sys.exit(
f"{pkg_name}: Empty sourcedir_list and not a meta package, exiting"
)
if is_postinstallscripts_available(pkg_info):
rpm_scripts = generate_rpm_postscripts(pkg_info, config)
else:
# Get -> Transform -> Join (no transform needed for RPM)
provides = process_name_field(pkg_info, "Provides")
obsoletes = process_name_field(pkg_info, "Obsoletes")
conflicts = process_name_field(pkg_info, "Conflicts")
# Non-versioned package requires versioned package itself
requires = resolve_versioned_dependencies([pkg_name], config, is_meta)
pkg_name = update_package_name(pkg_name, config)
env = Environment(
loader=FileSystemLoader(str(SCRIPT_DIR)),
autoescape=select_autoescape(
enabled_extensions=("html", "htm", "xml"),
default_for_string=True,
default=False,
),
)
template = env.get_template("template/rpm_specfile.j2")
context = {
"pkg_name": pkg_name,
"version": version,
"release": config.version_suffix,
"build_arch": pkg_info.get("BuildArch"),
"description_short": pkg_info.get("Description_Short"),
"description_long": pkg_info.get("Description_Long"),
"group": pkg_info.get("Group"),
"pkg_license": pkg_info.get("License"),
"vendor": pkg_info.get("Vendor"),
"install_prefix": config.install_prefix,
"requires": requires,
"provides": provides,
"obsoletes": obsoletes,
"conflicts": conflicts,
"rpmrecommends": rpmrecommends,
"rpmsuggests": rpmsuggests,
"disable_rpm_strip": True,
"disable_debug_package": is_debug_package_disabled(pkg_info),
"sourcedir_list": sourcedir_list,
"rpm_scripts": rpm_scripts,
"exclude_libpython_requires": exclude_libpython_requires,
}
with open(specfile, "w", encoding="utf-8") as f:
f.write(template.render(context))
def generate_rpm_postscripts(pkg_info, config: PackageConfig):
"""Generate RPM postinst/prerm sections.
Parameters:
pkg_info: Package details parsed from a JSON file
config: Configuration object containing package metadata
Returns: rpm script sections for specfile
"""
# RPM maintainer scripts
EXEC_SCRIPTS = {
"preinst": "%pre",
"postinst": "%post",
"prerm": "%preun",
"postrm": "%postun",
}
pkg_name = pkg_info.get("Package")
parts = config.rocm_version.split(".")
env = Environment(
loader=FileSystemLoader(str(SCRIPT_DIR)),
autoescape=select_autoescape(
enabled_extensions=("html", "htm", "xml"),
default_for_string=True,
default=False,
),
)
# Prepare your context dictionary
context = {
"install_prefix": config.install_prefix,
"version_major": int(re.match(r"^\d+", parts[0]).group()),
"version_minor": int(re.match(r"^\d+", parts[1]).group()),
"version_patch": int(re.match(r"^\d+", parts[2]).group()),
"target": "rpm",
}
templates_root = Path(SCRIPT_DIR) / "template" / "scripts"
# Collect all matching files
# This will hold rendered RPM script sections
rpm_script_sections = {}
for script, rpm_section in EXEC_SCRIPTS.items():
pattern = f"{pkg_name}-{script}.j2"
for file in templates_root.glob(pattern):
template = env.get_template(str(file.relative_to(SCRIPT_DIR)))
rendered = template.render(context)
# Store rendered script under its RPM section name
rpm_script_sections[rpm_section] = rendered
return rpm_script_sections
def package_with_rpmbuild(spec_file):
"""Generate a RPM package using `rpmbuild`
Parameters:
spec_file: Path to the RPM spec file
Returns: None
"""
print_function_name()
# Build the command
cmd = [
"rpmbuild",
"-bb",
spec_file,
"--define",
f"_topdir {spec_file.parent}",
]
# Execute the command
try:
subprocess.run(cmd, check=True)
print(f"RPM Package built successfully: {spec_file.name}")
except subprocess.CalledProcessError as e:
print(f"Error building RPM package: {spec_file.name}: {e}")
sys.exit(e.returncode)