xref: /linux/tools/perf/scripts/install-build-deps.sh (revision 83684c4e4d62cb02b2e4d0d18963d1035439278e)
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