1#!/bin/bash 2# SPDX-License-Identifier: GPL-2.0 3# 4# No 'set -e': the script uses explicit checks for its error paths, and 5# is also run via '$(SHELL) .../install-build-deps.sh' from the make 6# target in tools/perf/Makefile.perf, where a shebang option would be 7# ignored anyway, so direct and make-driven runs behave the same. 8# 9# Install the development packages needed to build tools/perf. 10# 11# The package set is derived from the feature tests in tools/build/feature/: 12# each feature test that perf may compile is mapped to the devel package 13# that provides the headers/libraries it checks, so that a subsequent 14# 'make -C tools/perf' build enables the corresponding perf features. 15# 16# Currently supported distros, each validated in a fresh container: 17# 18# - Fedora, on dnf, validated on Fedora 44, in a toolbx container: 19# 20# toolbox create fedora:44 21# toolbox enter fedora:44 22# 23# - Ubuntu, on apt-get, validated on Ubuntu 26.04, in a distrobox 24# container: 25# 26# distrobox create --image ubuntu:26.04 27# distrobox enter ubuntu-26-04 28# 29# The apt-get install runs with a noninteractive debconf frontend: 30# default-jdk, needed by the jvmti feature tests, pulls in tzdata, 31# which prompts for the timezone on a terminal and would block the 32# install, e.g. in a container shared with an interactive session. 33# 34# - Debian, on apt-get, reusing the Ubuntu mapping, validated on 35# Debian 13 (trixie), in a distrobox container: 36# 37# distrobox create --image debian:trixie 38# distrobox enter debian-trixie 39# 40# - RHEL and its variants (RHEL, CentOS Stream, Rocky Linux, AlmaLinux, 41# Oracle Linux), on dnf (RHEL 8 and later), validated on CentOS 42# Stream 10, in a distrobox container: 43# 44# distrobox create --image quay.io/centos/centos:stream10 45# distrobox enter centos-stream10 46# 47# The RHEL family reuses most Fedora package names, with these 48# differences, handled by probing the enabled repos: 49# 50# - zlib.h comes from zlib-ng-compat-devel on the RHEL 10 family, 51# zlib-devel on RHEL 9 and earlier; 52# - the JDK devel package is versioned, there is no 53# java-latest-openjdk-devel: java-21-openjdk-devel on the RHEL 54# 10 family, java-17-openjdk-devel on RHEL 9, java-11-openjdk- 55# devel on RHEL 8; 56# - libbpf-devel and capstone-devel are in the CRB repo on RHEL 57# and CentOS Stream 10 ('dnf config-manager --set-enabled crb') 58# and in EPEL on RHEL 9 and earlier; 59# - libbabeltrace2-devel is not packaged on the RHEL 10 family. 60# 61# On the RHEL family, packages not available on the enabled repos 62# are skipped and listed at the end of the run, so that a distro 63# with CRB/EPEL enabled gets the full set, and a distro without 64# them still installs what it can. 65# 66# Running inside a container keeps the host system unmodified. 67# 68# Usage: install-build-deps.sh [OPTIONS] 69# 70# Options: 71# --list list the packages that would be installed, then exit 72# --dry-run show the install command that would be run, without 73# running it 74# --distro ID force a distro: fedora, rhel, ubuntu, debian 75# (default: auto-detect) 76# -h, --help print this help message 77# 78# Requires root (or passwordless sudo) to actually install packages. 79 80set -u 81 82DISTRO="" 83 84help() { 85 cat <<EOF 86Usage: $(basename "$0") [--list] [--dry-run] [--distro ID] [-h|--help] 87 88Install the development packages needed to build tools/perf. 89 90The package set is derived from the feature tests in tools/build/feature/, 91mapping each one with an external dependency to the devel package providing 92it, so the corresponding feature gets enabled on a build. 93 94Options: 95 --list list the packages that would be installed, then exit 96 --dry-run show the install command that would be run, without running it 97 --distro ID force a distro: fedora, rhel, ubuntu, debian 98 -h, --help print this help message 99 100Distros supported: Fedora (dnf), validated in a toolbx container on 101Fedora 44, Ubuntu (apt-get), validated in a distrobox container on 102Ubuntu 26.04, Debian (apt-get), reusing the Ubuntu mapping, validated 103in a distrobox container on Debian 13 (trixie), and the RHEL family 104(dnf, RHEL 8 and later), validated on CentOS Stream 10 in a distrobox 105container. On the RHEL family, packages not available on the enabled 106repos are skipped and listed at the end; some there, e.g. 107libbpf-devel and capstone-devel, come from the CRB repo on RHEL and 108CentOS Stream 10 ('dnf config-manager --set-enabled crb') and from 109EPEL on RHEL 9 and earlier. 110EOF 111 exit 0 112} 113 114# --------------------------------------------------------------------- 115# Return the Fedora package(s) providing the devel requirements of a 116# feature test in tools/build/feature/. Multiple packages are separated 117# by spaces; an empty result means the test has no external devel 118# dependency (pure toolchain/glibc, or no equivalent Fedora package). 119# 120# The mapping was built by checking, for each feature test, which Fedora 121# devel package provides the headers the test compiles against. 122# --------------------------------------------------------------------- 123fedora_pkg_for() { 124 local feat="$1" 125 case "$feat" in 126 libelf|libelf-getphdrnum|libelf-gelf_getnote|libelf-getshdrstrndx) 127 echo "elfutils-libelf-devel" 128 ;; 129 libelf-zstd) 130 echo "elfutils-libelf-devel libzstd-devel" 131 ;; 132 libdw) 133 echo "elfutils-devel" 134 ;; 135 # test-libdebuginfod.c includes <elfutils/debuginfod.h>, which is 136 # provided by elfutils-debuginfod-client-devel, not elfutils-devel. 137 libdebuginfod) 138 echo "elfutils-debuginfod-client-devel" 139 ;; 140 libnuma|numa_num_possible_cpus) 141 echo "numactl-devel" 142 ;; 143 libzstd) 144 echo "libzstd-devel" 145 ;; 146 zlib) 147 echo "zlib-devel" 148 ;; 149 lzma) 150 echo "xz-devel" 151 ;; 152 libslang) 153 echo "slang-devel" 154 ;; 155 libcapstone) 156 echo "capstone-devel" 157 ;; 158 libpython) 159 echo "python3-devel" 160 ;; 161 libtraceevent) 162 echo "libtraceevent-devel" 163 ;; 164 cxa-demangle) 165 echo "libstdc++-devel" 166 ;; 167 libbpf) 168 echo "libbpf-devel" 169 ;; 170 babeltrace2-ctf-writer) 171 echo "libbabeltrace2-devel" 172 ;; 173 libopenssl) 174 echo "openssl-devel" 175 ;; 176 libpfm4) 177 echo "libpfm-devel" 178 ;; 179 sdt) 180 echo "systemtap-sdt-devel" 181 ;; 182 clang-bpf-co-re) 183 echo "clang-devel" 184 ;; 185 llvm|llvm-perf) 186 echo "llvm-devel" 187 ;; 188 jvmti|jvmti-cmlr) 189 echo "java-latest-openjdk-devel" 190 ;; 191 esac 192 # Pure toolchain/libc features (backtrace, eventfd, fortify-source, 193 # gettid, glibc, hello, reallocarray, pthread-*, stackprotector-all, 194 # timerfd, scandirat, sched_getcpu, setns, file-handle, 195 # bionic...) need no external package: their 196 # requirements are covered by the base packages below. Features that 197 # perf's own build does not check (libcap, libcheck, libcpupower, 198 # libtracefs, whose feature tests exist for rtla/bpftool/rv) and the 199 # opt-in features, which a default build does not enable: the libbfd 200 # disassembler family (libbfd, libbfd-threadsafe, libbfd-liberty, 201 # disassembler-*, cplus-demangle), only linked on BUILD_NONDISTRO 202 # builds and deprecated in favor of capstone, GTK2, LIBPERL and 203 # LIBUNWIND support (ifdef GTK2 / ifdef LIBPERL / LIBUNWIND=1), 204 # and CoreSight (ifdef CORESIGHT), are deliberately not mapped. 205 # libaio is not mapped either: its 206 # test uses the POSIX AIO API (aio.h, aio_*, -lrt), provided by 207 # glibc headers (pulled in by the libc headers package) in the 208 # base set, not the native libaio.h/io_submit API that 209 # libaio-devel provides. 210} 211 212# --------------------------------------------------------------------- 213# Check whether the first argument is provided by the enabled repos, or 214# is already installed, returning 0 if so and printing nothing. 215# 216# dnf repoquery exits 0 even when the pattern does not match anything, 217# so availability is judged by the output being non-empty, not by the 218# exit code. 219# --------------------------------------------------------------------- 220pkg_available() { 221 local p out 222 for p in "$@"; do 223 out=$(dnf repoquery --quiet --available "$p" 2>/dev/null) 224 [ -n "$out" ] || out=$(dnf repoquery --quiet --installed "$p" 2>/dev/null) 225 [ -n "$out" ] && return 0 226 done 227 return 1 228} 229 230# --------------------------------------------------------------------- 231# Return the first of its arguments that pkg_available() accepts. 232# --------------------------------------------------------------------- 233pick_available() { 234 local p 235 for p in "$@"; do 236 if pkg_available "$p"; then 237 echo "$p" 238 return 0 239 fi 240 done 241 return 1 242} 243 244# --------------------------------------------------------------------- 245# Return the RHEL-family package(s) providing the devel requirements of 246# a feature test. This family reuses the Fedora mapping, whose names 247# apply to it as well, except for the features whose Fedora package 248# does not exist there, kept in the case below. 249# 250# Whether the resulting package is actually installable is checked in 251# package_set(), as some of these names live in the CRB or EPEL repos, 252# which may not be enabled: on the RHEL 10 family libbpf-devel and 253# capstone-devel come from CRB, and libbabeltrace2-devel is not 254# packaged at all. 255# --------------------------------------------------------------------- 256rhel_pkg_for() { 257 local feat="$1" 258 case "$feat" in 259 zlib) 260 # zlib-ng-compat-devel on the RHEL 10 family, zlib-devel on 261 # RHEL 9 and earlier. If neither is on the enabled repos, 262 # fall back to the first candidate so that package_set() 263 # reports it as missing instead of silently dropping the 264 # feature from the end-of-run note. 265 pick_available zlib-devel zlib-ng-compat-devel || echo "zlib-devel" 266 ;; 267 jvmti|jvmti-cmlr) 268 # No java-latest-openjdk-devel on the RHEL family: the JDK 269 # devel package is versioned per release. 270 pick_available java-latest-openjdk-devel java-21-openjdk-devel java-17-openjdk-devel java-11-openjdk-devel || echo "java-latest-openjdk-devel" 271 ;; 272 *) 273 fedora_pkg_for "$feat" 274 ;; 275 esac 276} 277 278# --------------------------------------------------------------------- 279# Return the Debian/Ubuntu package(s) providing the devel requirements 280# of a feature test in tools/build/feature/. This mapping is shared by 281# Debian and Ubuntu, whose package names for these devel packages match, 282# and is validated on Ubuntu 26.04 and Debian 13 (trixie). 283# --------------------------------------------------------------------- 284debian_pkg_for() { 285 local feat="$1" 286 case "$feat" in 287 libelf|libelf-getphdrnum|libelf-gelf_getnote|libelf-getshdrstrndx) 288 echo "libelf-dev" 289 ;; 290 libelf-zstd) 291 echo "libelf-dev libzstd-dev" 292 ;; 293 libdw) 294 echo "libdw-dev" 295 ;; 296 libdebuginfod) 297 echo "libdebuginfod-dev" 298 ;; 299 libnuma|numa_num_possible_cpus) 300 echo "libnuma-dev" 301 ;; 302 libzstd) 303 echo "libzstd-dev" 304 ;; 305 zlib) 306 echo "zlib1g-dev" 307 ;; 308 lzma) 309 echo "liblzma-dev" 310 ;; 311 libslang) 312 echo "libslang2-dev" 313 ;; 314 libcapstone) 315 echo "libcapstone-dev" 316 ;; 317 libpython) 318 echo "python3-dev" 319 ;; 320 libtraceevent) 321 echo "libtraceevent-dev" 322 ;; 323 # The cxa-demangle feature test links against libstdc++ builtin 324 # demangling, so on Debian-derived distros it is covered by the 325 # g++ base package below, which pulls in libstdc++-*-dev. 326 cxa-demangle) 327 ;; 328 libbpf) 329 echo "libbpf-dev" 330 ;; 331 babeltrace2-ctf-writer) 332 echo "libbabeltrace2-dev" 333 ;; 334 libopenssl) 335 echo "libssl-dev" 336 ;; 337 libpfm4) 338 echo "libpfm4-dev" 339 ;; 340 sdt) 341 echo "systemtap-sdt-dev" 342 ;; 343 # test-clang-bpf-co-re.c invokes the clang binary (not libclang-cpp), 344 # so clang suffices; llvm-dev brings llvm-config, needed by the 345 # llvm/llvm-perf tests below. 346 clang-bpf-co-re) 347 echo "clang llvm-dev" 348 ;; 349 llvm|llvm-perf) 350 echo "llvm-dev" 351 ;; 352 jvmti|jvmti-cmlr) 353 echo "default-jdk" 354 ;; 355 esac 356 # Same rationale as in fedora_pkg_for() above for unmapped tests. 357} 358 359# Base packages needed by any perf build, regardless of feature tests: 360# compiler, libc headers, flex/bison for the parser, kernel headers 361# for UAPI headers with no in-tree copy, e.g. <linux/capability.h>, and 362# gcc-c++ (dnf) / g++ (apt) is needed by the C++-based feature tests 363# (cxa-demangle, llvm, llvm-perf), compiled with $(CXX), and 364# pulls in libstdc++-devel / libstdc++-*-dev. 365# python3-setuptools is needed to build the python binding (perf's 366# util/setup.py uses it; without it binding is skipped with a warning). 367# rust is not a header-based feature test: test-rust.bin just checks 368# "$(RUSTC) --version" (tools/build/feature/Makefile), so it is mapped 369# here like the other toolchain packages. 370fedora_base_pkgs="gcc gcc-c++ make flex bison glibc-devel kernel-headers python3-setuptools rust" 371# pkg-config mirrors the pkgconf-pkg-config package that Fedora 372# installs by default, needed by the babeltrace2 feature test and 373# libopenssl's pkg-config checks. 374debian_base_pkgs="gcc g++ make pkg-config flex bison libc6-dev linux-libc-dev python3-setuptools rustc" 375# pkg-config: like Fedora, on pkgconf since Fedora 26 / RHEL 8, with 376# 'pkgconfig' live only as a virtual Provides of the pkgconf-pkg-config 377# subpackage. List it explicitly because, unlike Fedora, a minimal 378# RHEL-family container may not have it preinstalled. 379rhel_base_pkgs="gcc gcc-c++ make flex bison glibc-devel kernel-headers pkgconf-pkg-config python3-setuptools rust" 380 381# --------------------------------------------------------------------- 382# Distro detection 383# --------------------------------------------------------------------- 384detect_distro() { 385 if [ -n "$DISTRO" ]; then 386 echo "$DISTRO" 387 return 388 fi 389 local id 390 id=$( . /etc/os-release 2>/dev/null && echo "${ID:-}" ) 391 case "$id" in 392 fedora) echo "fedora" ;; 393 rhel|centos|rocky|almalinux|ol) 394 # All share the Fedora package names (with the exceptions 395 # handled by rhel_pkg_for()) and dnf. 396 echo "rhel" ;; 397 ubuntu) echo "ubuntu" ;; 398 debian) echo "debian" ;; 399 *) echo "" ;; 400 esac 401} 402 403# --------------------------------------------------------------------- 404# Feature test enumeration: mirror of the tests checked during a build, 405# from the actual test-*.c / test-*.cpp sources in tools/build/feature/. 406# --------------------------------------------------------------------- 407feature_tests() { 408 local srcdir="$1" 409 local f 410 for f in "$srcdir"/tools/build/feature/test-*.c "$srcdir"/tools/build/feature/test-*.cpp; do 411 [ -e "$f" ] || continue 412 basename "$f" | sed -e 's/^test-//' -e 's/\.c$//' -e 's/\.cpp$//' 413 done 414} 415 416# --------------------------------------------------------------------- 417# Assemble the unique package list. Since the full feature set is mapped 418# unconditionally, this installs the complete devel environment; installing 419# an already-present package is a no-op for both dnf and apt-get, making 420# this idempotent. On the RHEL family, packages not available on the 421# enabled repos are skipped and echoed on stderr, reported at the end of 422# the run by the caller (package_set() runs in a subshell, so a variable 423# would not survive it). 424# --------------------------------------------------------------------- 425package_set() { 426 local distro="$1" srcdir="$2" 427 local feat pkg pkgs base_pkgs missing_pkgs="" 428 case "$distro" in 429 fedora) pkgs="$fedora_base_pkgs" ;; 430 rhel) pkgs="$rhel_base_pkgs" ;; 431 ubuntu|debian) pkgs="$debian_base_pkgs" ;; 432 esac 433 434 # The base set must be checked against the enabled repos as well: 435 # e.g. 'rust' is only shipped as the rust-toolset AppStream module 436 # on RHEL 8 and 9, where dnf would abort the whole transaction 437 # with "No match for argument: rust" instead of skipping it, so 438 # unavailable base packages are skipped and noted like the mapped 439 # ones below. 440 if [ "$distro" = "rhel" ]; then 441 base_pkgs="$pkgs" 442 pkgs="" 443 for pkg in $base_pkgs; do 444 if pkg_available "$pkg"; then 445 pkgs="$pkgs $pkg" 446 else 447 missing_pkgs="$missing_pkgs $pkg" 448 fi 449 done 450 fi 451 452 for feat in $(feature_tests "$srcdir"); do 453 case "$distro" in 454 fedora) pkg=$(fedora_pkg_for "$feat") ;; 455 rhel) pkg=$(rhel_pkg_for "$feat") ;; 456 *) pkg=$(debian_pkg_for "$feat") ;; 457 esac 458 # An empty result means the feature test has no external devel 459 # dependency (see the unmapped list in fedora_pkg_for()), so 460 # there is nothing to install and nothing to report as missing: 461 # packages that are genuinely unavailable on the RHEL family 462 # surface through rhel_pkg_for() falling back to its first 463 # candidate, so that the pkg_available() check below catches 464 # them and adds them to missing_pkgs. 465 [ -n "$pkg" ] || continue 466 for pkg in $pkg; do 467 if [ "$distro" = "rhel" ] && ! pkg_available "$pkg"; then 468 missing_pkgs="$missing_pkgs $pkg" 469 continue 470 fi 471 case " $pkgs " in 472 *" $pkg "*) ;; 473 *) pkgs="$pkgs $pkg" ;; 474 esac 475 done 476 done 477 [ -n "$missing_pkgs" ] && echo "$missing_pkgs" >&2 478 echo "$pkgs" 479} 480 481# --------------------------------------------------------------------- 482# Report the RHEL-family packages that were skipped because the enabled 483# repos do not provide them. 484# --------------------------------------------------------------------- 485note_missing() { 486 [ -n "$1" ] || return 0 487 echo "Note: these packages are not available on the enabled repos" >&2 488 echo "and were not installed, so the features they enable will stay" >&2 489 echo "off in a perf build:" >&2 490 echo "$1" | tr ' ' '\n' | grep -v '^$' | sort -u | sed 's/^/ /' >&2 491 echo >&2 492} 493 494# --------------------------------------------------------------------- 495# The install command proper for each supported package manager, plus 496# the command massaged for --dry-run. 497# --------------------------------------------------------------------- 498install_cmd() { 499 local distro="$1"; shift 500 case "$distro" in 501 fedora|rhel) 502 echo "dnf install -y $*" 503 ;; 504 ubuntu|debian) 505 # a fresh container has no package index, so update first; run 506 # with a noninteractive debconf frontend: default-jdk, used by 507 # the jvmti feature tests, pulls in tzdata, which prompts for 508 # the timezone on a terminal and would block the install. The 509 # env var prefix works with the 'sh -c' invocation below, and 510 # shows up in --dry-run. 511 echo "DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y $*" 512 ;; 513 esac 514} 515 516main() { 517 local action="install" 518 # Package accumulation happens in package_set(), which, together 519 # with the *_pkg_for() helpers, declares the per-feature 'pkg' 520 # locals; main() holds no per-feature package state, just the 521 # distro-wide list assembled by package_set(). 522 local srcdir distro pkgs cmd missing_file missing_pkgs 523 524 while [ $# -gt 0 ]; do 525 case "$1" in 526 --list) action="list"; shift ;; 527 --dry-run) action="dry-run"; shift ;; 528 --distro) 529 [ $# -ge 2 ] || { 530 echo "error: --distro requires an argument (fedora, rhel, ubuntu, debian)" >&2 531 exit 1 532 } 533 DISTRO="$2"; shift 2 ;; 534 -h|--help) help ;; 535 *) echo "error: unknown argument: $1" >&2; exit 1 ;; 536 esac 537 done 538 539 srcdir=$(cd "$(dirname "$0")/../../.." && pwd) 540 distro=$(detect_distro) 541 case "$distro" in 542 fedora|rhel|ubuntu|debian) ;; 543 *) 544 echo "error: unsupported distro (got '$distro'); the package mapping is not validated on other distros." >&2 545 echo "Supported and validated: Fedora 44 (toolbx container), Ubuntu 26.04 and Debian 13 (distrobox containers), and CentOS Stream 10 (distrobox container)." >&2 546 exit 1 547 ;; 548 esac 549 550 # The RHEL family mapping probes the enabled repos with dnf 551 # repoquery and installs with dnf, so reject the yum-only members of 552 # the family (RHEL 7 and earlier, e.g. Oracle Linux 7) with a clear 553 # error instead of silently judging every package as unavailable. 554 if [ "$distro" = "rhel" ] && ! command -v dnf >/dev/null 2>&1; then 555 echo "error: dnf not found, the RHEL family package mapping requires dnf (RHEL 8 and later)." >&2 556 exit 1 557 fi 558 559 missing_file=$(mktemp) || { 560 echo "error: cannot create a temporary file (mktemp failed)" >&2 561 exit 1 562 } 563 # package_set() runs in a subshell, doing dnf repoquery loops that 564 # may be interrupted: a Ctrl-C during those loops hits these traps, 565 # turning SIGINT/SIGTERM into an exit that triggers the EXIT trap, 566 # removing the temp file, so the script terminates instead of 567 # resuming after the interrupted command, leaving no file behind. 568 trap 'rm -f "$missing_file"' EXIT 569 trap 'exit 1' INT TERM 570 pkgs=$(package_set "$distro" "$srcdir" 2>"$missing_file") 571 missing_pkgs=$(cat "$missing_file") 572 rm -f "$missing_file" 573 trap - EXIT INT TERM 574 575 case "$action" in 576 list) 577 echo "$pkgs" | tr ' ' '\n' | grep -v '^$' | sort 578 note_missing "$missing_pkgs" 579 exit 0 580 ;; 581 dry-run) 582 install_cmd "$distro" $pkgs 583 note_missing "$missing_pkgs" 584 exit 0 585 ;; 586 esac 587 588 echo "The following packages will be installed to enable perf features:" 589 echo "$pkgs" | tr ' ' '\n' | grep -v '^$' | sort | sed 's/^/ /' 590 echo 591 cmd=$(install_cmd "$distro" $pkgs) 592 if [ "$(id -u)" -eq 0 ]; then 593 sh -c "$cmd" 594 else 595 sudo sh -c "$cmd" 596 fi || { 597 echo "error: the install command failed, see the output above" >&2 598 exit 1 599 } 600 note_missing "$missing_pkgs" 601} 602 603main "$@" 604