| #!/bin/bash |
| # SPDX-License-Identifier: GPL-2.0 |
| # |
| # No 'set -e': the script uses explicit checks for its error paths, and |
| # is also run via '$(SHELL) .../install-build-deps.sh' from the make |
| # target in tools/perf/Makefile.perf, where a shebang option would be |
| # ignored anyway, so direct and make-driven runs behave the same. |
| # |
| # Install the development packages needed to build tools/perf. |
| # |
| # The package set is derived from the feature tests in tools/build/feature/: |
| # each feature test that perf may compile is mapped to the devel package |
| # that provides the headers/libraries it checks, so that a subsequent |
| # 'make -C tools/perf' build enables the corresponding perf features. |
| # |
| # Currently supported distros, each validated in a fresh container: |
| # |
| # - Fedora, on dnf, validated on Fedora 44, in a toolbx container: |
| # |
| # toolbox create fedora:44 |
| # toolbox enter fedora:44 |
| # |
| # - Ubuntu, on apt-get, validated on Ubuntu 26.04, in a distrobox |
| # container: |
| # |
| # distrobox create --image ubuntu:26.04 |
| # distrobox enter ubuntu-26-04 |
| # |
| # The apt-get install runs with a noninteractive debconf frontend: |
| # default-jdk, needed by the jvmti feature tests, pulls in tzdata, |
| # which prompts for the timezone on a terminal and would block the |
| # install, e.g. in a container shared with an interactive session. |
| # |
| # - Debian, on apt-get, reusing the Ubuntu mapping, validated on |
| # Debian 13 (trixie), in a distrobox container: |
| # |
| # distrobox create --image debian:trixie |
| # distrobox enter debian-trixie |
| # |
| # - 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: |
| # |
| # 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] |
| # |
| # 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, rhel, ubuntu, debian |
| # (default: auto-detect) |
| # -h, --help print this help message |
| # |
| # Requires root (or passwordless sudo) to actually install packages. |
| |
| set -u |
| |
| DISTRO="" |
| |
| help() { |
| cat <<EOF |
| Usage: $(basename "$0") [--list] [--dry-run] [--distro ID] [-h|--help] |
| |
| Install the development packages needed to build tools/perf. |
| |
| The package set is derived from the feature tests in tools/build/feature/, |
| mapping each one with an external dependency to the devel package providing |
| 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, 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, 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 |
| } |
| |
| # --------------------------------------------------------------------- |
| # Return the Fedora package(s) providing the devel requirements of a |
| # feature test in tools/build/feature/. Multiple packages are separated |
| # by spaces; an empty result means the test has no external devel |
| # dependency (pure toolchain/glibc, or no equivalent Fedora package). |
| # |
| # The mapping was built by checking, for each feature test, which Fedora |
| # devel package provides the headers the test compiles against. |
| # --------------------------------------------------------------------- |
| fedora_pkg_for() { |
| local feat="$1" |
| case "$feat" in |
| libelf|libelf-getphdrnum|libelf-gelf_getnote|libelf-getshdrstrndx) |
| echo "elfutils-libelf-devel" |
| ;; |
| libelf-zstd) |
| echo "elfutils-libelf-devel libzstd-devel" |
| ;; |
| libdw) |
| echo "elfutils-devel" |
| ;; |
| # test-libdebuginfod.c includes <elfutils/debuginfod.h>, which is |
| # provided by elfutils-debuginfod-client-devel, not elfutils-devel. |
| libdebuginfod) |
| echo "elfutils-debuginfod-client-devel" |
| ;; |
| libnuma|numa_num_possible_cpus) |
| echo "numactl-devel" |
| ;; |
| libzstd) |
| echo "libzstd-devel" |
| ;; |
| zlib) |
| echo "zlib-devel" |
| ;; |
| lzma) |
| echo "xz-devel" |
| ;; |
| libslang) |
| echo "slang-devel" |
| ;; |
| libcapstone) |
| echo "capstone-devel" |
| ;; |
| libpython) |
| echo "python3-devel" |
| ;; |
| libtraceevent) |
| echo "libtraceevent-devel" |
| ;; |
| cxa-demangle) |
| echo "libstdc++-devel" |
| ;; |
| libbpf) |
| echo "libbpf-devel" |
| ;; |
| babeltrace2-ctf-writer) |
| echo "libbabeltrace2-devel" |
| ;; |
| libopenssl) |
| echo "openssl-devel" |
| ;; |
| libpfm4) |
| echo "libpfm-devel" |
| ;; |
| sdt) |
| echo "systemtap-sdt-devel" |
| ;; |
| clang-bpf-co-re) |
| echo "clang-devel" |
| ;; |
| llvm|llvm-perf) |
| echo "llvm-devel" |
| ;; |
| jvmti|jvmti-cmlr) |
| echo "java-latest-openjdk-devel" |
| ;; |
| esac |
| # Pure toolchain/libc features (backtrace, eventfd, fortify-source, |
| # gettid, glibc, hello, reallocarray, pthread-*, stackprotector-all, |
| # timerfd, scandirat, sched_getcpu, setns, file-handle, |
| # bionic...) need no external package: their |
| # requirements are covered by the base packages below. Features that |
| # perf's own build does not check (libcap, libcheck, libcpupower, |
| # libtracefs, whose feature tests exist for rtla/bpftool/rv) and the |
| # opt-in features, which a default build does not enable: the libbfd |
| # disassembler family (libbfd, libbfd-threadsafe, libbfd-liberty, |
| # disassembler-*, cplus-demangle), only linked on BUILD_NONDISTRO |
| # builds and deprecated in favor of capstone, GTK2, LIBPERL and |
| # LIBUNWIND support (ifdef GTK2 / ifdef LIBPERL / LIBUNWIND=1), |
| # and CoreSight (ifdef CORESIGHT), are deliberately not mapped. |
| # libaio is not mapped either: its |
| # test uses the POSIX AIO API (aio.h, aio_*, -lrt), provided by |
| # glibc headers (pulled in by the libc headers package) in the |
| # base set, not the native libaio.h/io_submit API that |
| # 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 |
| # Debian and Ubuntu, whose package names for these devel packages match, |
| # and is validated on Ubuntu 26.04 and Debian 13 (trixie). |
| # --------------------------------------------------------------------- |
| debian_pkg_for() { |
| local feat="$1" |
| case "$feat" in |
| libelf|libelf-getphdrnum|libelf-gelf_getnote|libelf-getshdrstrndx) |
| echo "libelf-dev" |
| ;; |
| libelf-zstd) |
| echo "libelf-dev libzstd-dev" |
| ;; |
| libdw) |
| echo "libdw-dev" |
| ;; |
| libdebuginfod) |
| echo "libdebuginfod-dev" |
| ;; |
| libnuma|numa_num_possible_cpus) |
| echo "libnuma-dev" |
| ;; |
| libzstd) |
| echo "libzstd-dev" |
| ;; |
| zlib) |
| echo "zlib1g-dev" |
| ;; |
| lzma) |
| echo "liblzma-dev" |
| ;; |
| libslang) |
| echo "libslang2-dev" |
| ;; |
| libcapstone) |
| echo "libcapstone-dev" |
| ;; |
| libpython) |
| echo "python3-dev" |
| ;; |
| libtraceevent) |
| echo "libtraceevent-dev" |
| ;; |
| # The cxa-demangle feature test links against libstdc++ builtin |
| # demangling, so on Debian-derived distros it is covered by the |
| # g++ base package below, which pulls in libstdc++-*-dev. |
| cxa-demangle) |
| ;; |
| libbpf) |
| echo "libbpf-dev" |
| ;; |
| babeltrace2-ctf-writer) |
| echo "libbabeltrace2-dev" |
| ;; |
| libopenssl) |
| echo "libssl-dev" |
| ;; |
| libpfm4) |
| echo "libpfm4-dev" |
| ;; |
| sdt) |
| echo "systemtap-sdt-dev" |
| ;; |
| # test-clang-bpf-co-re.c invokes the clang binary (not libclang-cpp), |
| # so clang suffices; llvm-dev brings llvm-config, needed by the |
| # llvm/llvm-perf tests below. |
| clang-bpf-co-re) |
| echo "clang llvm-dev" |
| ;; |
| llvm|llvm-perf) |
| echo "llvm-dev" |
| ;; |
| jvmti|jvmti-cmlr) |
| echo "default-jdk" |
| ;; |
| esac |
| # Same rationale as in fedora_pkg_for() above for unmapped tests. |
| } |
| |
| # Base packages needed by any perf build, regardless of feature tests: |
| # compiler, libc headers, flex/bison for the parser, kernel headers |
| # for UAPI headers with no in-tree copy, e.g. <linux/capability.h>, and |
| # gcc-c++ (dnf) / g++ (apt) is needed by the C++-based feature tests |
| # (cxa-demangle, llvm, llvm-perf), compiled with $(CXX), and |
| # pulls in libstdc++-devel / libstdc++-*-dev. |
| # python3-setuptools is needed to build the python binding (perf's |
| # util/setup.py uses it; without it binding is skipped with a warning). |
| # rust is not a header-based feature test: test-rust.bin just checks |
| # "$(RUSTC) --version" (tools/build/feature/Makefile), so it is mapped |
| # here like the other toolchain packages. |
| fedora_base_pkgs="gcc gcc-c++ make flex bison glibc-devel kernel-headers python3-setuptools rust" |
| # pkg-config mirrors the pkgconf-pkg-config package that Fedora |
| # 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 |
| # --------------------------------------------------------------------- |
| detect_distro() { |
| if [ -n "$DISTRO" ]; then |
| echo "$DISTRO" |
| return |
| fi |
| local id |
| 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" ;; |
| *) echo "" ;; |
| esac |
| } |
| |
| # --------------------------------------------------------------------- |
| # Feature test enumeration: mirror of the tests checked during a build, |
| # from the actual test-*.c / test-*.cpp sources in tools/build/feature/. |
| # --------------------------------------------------------------------- |
| feature_tests() { |
| local srcdir="$1" |
| local f |
| for f in "$srcdir"/tools/build/feature/test-*.c "$srcdir"/tools/build/feature/test-*.cpp; do |
| [ -e "$f" ] || continue |
| basename "$f" | sed -e 's/^test-//' -e 's/\.c$//' -e 's/\.cpp$//' |
| done |
| } |
| |
| # --------------------------------------------------------------------- |
| # 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. 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 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 |
| 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. |
| # --------------------------------------------------------------------- |
| install_cmd() { |
| local distro="$1"; shift |
| case "$distro" in |
| fedora|rhel) |
| echo "dnf install -y $*" |
| ;; |
| ubuntu|debian) |
| # a fresh container has no package index, so update first; run |
| # with a noninteractive debconf frontend: default-jdk, used by |
| # the jvmti feature tests, pulls in tzdata, which prompts for |
| # the timezone on a terminal and would block the install. The |
| # env var prefix works with the 'sh -c' invocation below, and |
| # shows up in --dry-run. |
| echo "DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y $*" |
| ;; |
| esac |
| } |
| |
| main() { |
| local action="install" |
| # Package accumulation happens in package_set(), which, together |
| # 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 missing_file missing_pkgs |
| |
| while [ $# -gt 0 ]; do |
| case "$1" in |
| --list) action="list"; shift ;; |
| --dry-run) action="dry-run"; shift ;; |
| --distro) |
| [ $# -ge 2 ] || { |
| echo "error: --distro requires an argument (fedora, rhel, ubuntu, debian)" >&2 |
| exit 1 |
| } |
| DISTRO="$2"; shift 2 ;; |
| -h|--help) help ;; |
| *) echo "error: unknown argument: $1" >&2; exit 1 ;; |
| esac |
| done |
| |
| srcdir=$(cd "$(dirname "$0")/../../.." && pwd) |
| distro=$(detect_distro) |
| case "$distro" in |
| 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), and CentOS Stream 10 (distrobox container)." >&2 |
| exit 1 |
| ;; |
| esac |
| |
| # 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 |
| |
| echo "The following packages will be installed to enable perf features:" |
| echo "$pkgs" | tr ' ' '\n' | grep -v '^$' | sort | sed 's/^/ /' |
| echo |
| cmd=$(install_cmd "$distro" $pkgs) |
| if [ "$(id -u)" -eq 0 ]; then |
| sh -c "$cmd" |
| else |
| sudo sh -c "$cmd" |
| fi || { |
| echo "error: the install command failed, see the output above" >&2 |
| exit 1 |
| } |
| note_missing "$missing_pkgs" |
| } |
| |
| main "$@" |