diff --git a/tools/perf/scripts/install-build-deps.sh b/tools/perf/scripts/install-build-deps.sh index aaaf315516f0..d003e7fab2be 100755 --- a/tools/perf/scripts/install-build-deps.sh +++ b/tools/perf/scripts/install-build-deps.sh @@ -37,11 +37,33 @@ # distrobox create --image debian:trixie # distrobox enter debian-trixie # -# Running inside a container keeps the host system unmodified. +# - RHEL and its variants (RHEL, CentOS Stream, Rocky Linux, AlmaLinux, +# Oracle Linux), on dnf (RHEL 8 and later), validated on CentOS +# Stream 10, in a distrobox container: # -# RHEL support, whose package mapping is largely similar to Fedora's, -# is the next planned distro, to be enabled once that mapping is -# validated there. +# distrobox create --image quay.io/centos/centos:stream10 +# distrobox enter centos-stream10 +# +# The RHEL family reuses most Fedora package names, with these +# differences, handled by probing the enabled repos: +# +# - zlib.h comes from zlib-ng-compat-devel on the RHEL 10 family, +# zlib-devel on RHEL 9 and earlier; +# - the JDK devel package is versioned, there is no +# java-latest-openjdk-devel: java-21-openjdk-devel on the RHEL +# 10 family, java-17-openjdk-devel on RHEL 9, java-11-openjdk- +# devel on RHEL 8; +# - libbpf-devel and capstone-devel are in the CRB repo on RHEL +# and CentOS Stream 10 ('dnf config-manager --set-enabled crb') +# and in EPEL on RHEL 9 and earlier; +# - libbabeltrace2-devel is not packaged on the RHEL 10 family. +# +# On the RHEL family, packages not available on the enabled repos +# are skipped and listed at the end of the run, so that a distro +# with CRB/EPEL enabled gets the full set, and a distro without +# them still installs what it can. +# +# Running inside a container keeps the host system unmodified. # # Usage: install-build-deps.sh [OPTIONS] # @@ -49,7 +71,8 @@ # --list list the packages that would be installed, then exit # --dry-run show the install command that would be run, without # running it -# --distro ID force a distro: fedora, ubuntu, debian (default: auto-detect) +# --distro ID force a distro: fedora, rhel, ubuntu, debian +# (default: auto-detect) # -h, --help print this help message # # Requires root (or passwordless sudo) to actually install packages. @@ -71,13 +94,19 @@ it, so the corresponding feature gets enabled on a build. Options: --list list the packages that would be installed, then exit --dry-run show the install command that would be run, without running it - --distro ID force a distro: fedora, ubuntu, debian + --distro ID force a distro: fedora, rhel, ubuntu, debian -h, --help print this help message Distros supported: Fedora (dnf), validated in a toolbx container on Fedora 44, Ubuntu (apt-get), validated in a distrobox container on -Ubuntu 26.04, and Debian (apt-get), reusing the Ubuntu mapping, -validated in a distrobox container on Debian 13 (trixie). +Ubuntu 26.04, Debian (apt-get), reusing the Ubuntu mapping, validated +in a distrobox container on Debian 13 (trixie), and the RHEL family +(dnf, RHEL 8 and later), validated on CentOS Stream 10 in a distrobox +container. On the RHEL family, packages not available on the enabled +repos are skipped and listed at the end; some there, e.g. +libbpf-devel and capstone-devel, come from the CRB repo on RHEL and +CentOS Stream 10 ('dnf config-manager --set-enabled crb') and from +EPEL on RHEL 9 and earlier. EOF exit 0 } @@ -180,6 +209,72 @@ fedora_pkg_for() { # libaio-devel provides. } +# --------------------------------------------------------------------- +# Check whether the first argument is provided by the enabled repos, or +# is already installed, returning 0 if so and printing nothing. +# +# dnf repoquery exits 0 even when the pattern does not match anything, +# so availability is judged by the output being non-empty, not by the +# exit code. +# --------------------------------------------------------------------- +pkg_available() { + local p out + for p in "$@"; do + out=$(dnf repoquery --quiet --available "$p" 2>/dev/null) + [ -n "$out" ] || out=$(dnf repoquery --quiet --installed "$p" 2>/dev/null) + [ -n "$out" ] && return 0 + done + return 1 +} + +# --------------------------------------------------------------------- +# Return the first of its arguments that pkg_available() accepts. +# --------------------------------------------------------------------- +pick_available() { + local p + for p in "$@"; do + if pkg_available "$p"; then + echo "$p" + return 0 + fi + done + return 1 +} + +# --------------------------------------------------------------------- +# Return the RHEL-family package(s) providing the devel requirements of +# a feature test. This family reuses the Fedora mapping, whose names +# apply to it as well, except for the features whose Fedora package +# does not exist there, kept in the case below. +# +# Whether the resulting package is actually installable is checked in +# package_set(), as some of these names live in the CRB or EPEL repos, +# which may not be enabled: on the RHEL 10 family libbpf-devel and +# capstone-devel come from CRB, and libbabeltrace2-devel is not +# packaged at all. +# --------------------------------------------------------------------- +rhel_pkg_for() { + local feat="$1" + case "$feat" in + zlib) + # zlib-ng-compat-devel on the RHEL 10 family, zlib-devel on + # RHEL 9 and earlier. If neither is on the enabled repos, + # fall back to the first candidate so that package_set() + # reports it as missing instead of silently dropping the + # feature from the end-of-run note. + pick_available zlib-devel zlib-ng-compat-devel || echo "zlib-devel" + ;; + jvmti|jvmti-cmlr) + # No java-latest-openjdk-devel on the RHEL family: the JDK + # devel package is versioned per release. + pick_available java-latest-openjdk-devel java-21-openjdk-devel java-17-openjdk-devel java-11-openjdk-devel || echo "java-latest-openjdk-devel" + ;; + *) + fedora_pkg_for "$feat" + ;; + esac +} + # --------------------------------------------------------------------- # Return the Debian/Ubuntu package(s) providing the devel requirements # of a feature test in tools/build/feature/. This mapping is shared by @@ -277,6 +372,11 @@ fedora_base_pkgs="gcc gcc-c++ make flex bison glibc-devel kernel-headers python3 # installs by default, needed by the babeltrace2 feature test and # libopenssl's pkg-config checks. debian_base_pkgs="gcc g++ make pkg-config flex bison libc6-dev linux-libc-dev python3-setuptools rustc" +# pkg-config: like Fedora, on pkgconf since Fedora 26 / RHEL 8, with +# 'pkgconfig' live only as a virtual Provides of the pkgconf-pkg-config +# subpackage. List it explicitly because, unlike Fedora, a minimal +# RHEL-family container may not have it preinstalled. +rhel_base_pkgs="gcc gcc-c++ make flex bison glibc-devel kernel-headers pkgconf-pkg-config python3-setuptools rust" # --------------------------------------------------------------------- # Distro detection @@ -290,11 +390,12 @@ detect_distro() { id=$( . /etc/os-release 2>/dev/null && echo "${ID:-}" ) case "$id" in fedora) echo "fedora" ;; + rhel|centos|rocky|almalinux|ol) + # All share the Fedora package names (with the exceptions + # handled by rhel_pkg_for()) and dnf. + echo "rhel" ;; ubuntu) echo "ubuntu" ;; debian) echo "debian" ;; - # RHEL and its derivatives share most Fedora package names, but the - # mapping is only validated on Fedora, so don't auto-detect them. - rhel|centos|rocky|alma|ol) echo "" ;; *) echo "" ;; esac } @@ -316,33 +417,80 @@ feature_tests() { # Assemble the unique package list. Since the full feature set is mapped # unconditionally, this installs the complete devel environment; installing # an already-present package is a no-op for both dnf and apt-get, making -# this idempotent. +# this idempotent. On the RHEL family, packages not available on the +# enabled repos are skipped and echoed on stderr, reported at the end of +# the run by the caller (package_set() runs in a subshell, so a variable +# would not survive it). # --------------------------------------------------------------------- package_set() { local distro="$1" srcdir="$2" - local feat pkg pkgs + local feat pkg pkgs base_pkgs missing_pkgs="" case "$distro" in fedora) pkgs="$fedora_base_pkgs" ;; + rhel) pkgs="$rhel_base_pkgs" ;; ubuntu|debian) pkgs="$debian_base_pkgs" ;; esac + # The base set must be checked against the enabled repos as well: + # e.g. 'rust' is only shipped as the rust-toolset AppStream module + # on RHEL 8 and 9, where dnf would abort the whole transaction + # with "No match for argument: rust" instead of skipping it, so + # unavailable base packages are skipped and noted like the mapped + # ones below. + if [ "$distro" = "rhel" ]; then + base_pkgs="$pkgs" + pkgs="" + for pkg in $base_pkgs; do + if pkg_available "$pkg"; then + pkgs="$pkgs $pkg" + else + missing_pkgs="$missing_pkgs $pkg" + fi + done + fi + for feat in $(feature_tests "$srcdir"); do - if [ "$distro" = "fedora" ]; then - pkg=$(fedora_pkg_for "$feat") - else - pkg=$(debian_pkg_for "$feat") - fi + case "$distro" in + fedora) pkg=$(fedora_pkg_for "$feat") ;; + rhel) pkg=$(rhel_pkg_for "$feat") ;; + *) pkg=$(debian_pkg_for "$feat") ;; + esac + # An empty result means the feature test has no external devel + # dependency (see the unmapped list in fedora_pkg_for()), so + # there is nothing to install and nothing to report as missing: + # packages that are genuinely unavailable on the RHEL family + # surface through rhel_pkg_for() falling back to its first + # candidate, so that the pkg_available() check below catches + # them and adds them to missing_pkgs. [ -n "$pkg" ] || continue for pkg in $pkg; do + if [ "$distro" = "rhel" ] && ! pkg_available "$pkg"; then + missing_pkgs="$missing_pkgs $pkg" + continue + fi case " $pkgs " in *" $pkg "*) ;; *) pkgs="$pkgs $pkg" ;; esac done done + [ -n "$missing_pkgs" ] && echo "$missing_pkgs" >&2 echo "$pkgs" } +# --------------------------------------------------------------------- +# Report the RHEL-family packages that were skipped because the enabled +# repos do not provide them. +# --------------------------------------------------------------------- +note_missing() { + [ -n "$1" ] || return 0 + echo "Note: these packages are not available on the enabled repos" >&2 + echo "and were not installed, so the features they enable will stay" >&2 + echo "off in a perf build:" >&2 + echo "$1" | tr ' ' '\n' | grep -v '^$' | sort -u | sed 's/^/ /' >&2 + echo >&2 +} + # --------------------------------------------------------------------- # The install command proper for each supported package manager, plus # the command massaged for --dry-run. @@ -350,7 +498,7 @@ package_set() { install_cmd() { local distro="$1"; shift case "$distro" in - fedora) + fedora|rhel) echo "dnf install -y $*" ;; ubuntu|debian) @@ -371,7 +519,7 @@ main() { # with the *_pkg_for() helpers, declares the per-feature 'pkg' # locals; main() holds no per-feature package state, just the # distro-wide list assembled by package_set(). - local srcdir distro pkgs cmd + local srcdir distro pkgs cmd missing_file missing_pkgs while [ $# -gt 0 ]; do case "$1" in @@ -391,23 +539,48 @@ main() { srcdir=$(cd "$(dirname "$0")/../../.." && pwd) distro=$(detect_distro) case "$distro" in - fedora|ubuntu|debian) ;; + fedora|rhel|ubuntu|debian) ;; *) echo "error: unsupported distro (got '$distro'); the package mapping is not validated on other distros." >&2 - echo "Supported and validated: Fedora 44 (toolbx container), Ubuntu 26.04 and Debian 13 (distrobox containers)." >&2 + echo "Supported and validated: Fedora 44 (toolbx container), Ubuntu 26.04 and Debian 13 (distrobox containers), and CentOS Stream 10 (distrobox container)." >&2 exit 1 ;; esac - pkgs=$(package_set "$distro" "$srcdir") + # The RHEL family mapping probes the enabled repos with dnf + # repoquery and installs with dnf, so reject the yum-only members of + # the family (RHEL 7 and earlier, e.g. Oracle Linux 7) with a clear + # error instead of silently judging every package as unavailable. + if [ "$distro" = "rhel" ] && ! command -v dnf >/dev/null 2>&1; then + echo "error: dnf not found, the RHEL family package mapping requires dnf (RHEL 8 and later)." >&2 + exit 1 + fi + + missing_file=$(mktemp) || { + echo "error: cannot create a temporary file (mktemp failed)" >&2 + exit 1 + } + # package_set() runs in a subshell, doing dnf repoquery loops that + # may be interrupted: a Ctrl-C during those loops hits these traps, + # turning SIGINT/SIGTERM into an exit that triggers the EXIT trap, + # removing the temp file, so the script terminates instead of + # resuming after the interrupted command, leaving no file behind. + trap 'rm -f "$missing_file"' EXIT + trap 'exit 1' INT TERM + pkgs=$(package_set "$distro" "$srcdir" 2>"$missing_file") + missing_pkgs=$(cat "$missing_file") + rm -f "$missing_file" + trap - EXIT INT TERM case "$action" in list) echo "$pkgs" | tr ' ' '\n' | grep -v '^$' | sort + note_missing "$missing_pkgs" exit 0 ;; dry-run) install_cmd "$distro" $pkgs + note_missing "$missing_pkgs" exit 0 ;; esac @@ -424,6 +597,7 @@ main() { echo "error: the install command failed, see the output above" >&2 exit 1 } + note_missing "$missing_pkgs" } main "$@"