xref: /linux/tools/perf/scripts/install-build-deps.sh (revision 01c7a389d1ff89d42547004a0ca446cdab893564)
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# Running inside a container keeps the host system unmodified.
41#
42# RHEL support, whose package mapping is largely similar to Fedora's,
43# is the next planned distro, to be enabled once that mapping is
44# validated there.
45#
46# Usage: install-build-deps.sh [OPTIONS]
47#
48# Options:
49#    --list       list the packages that would be installed, then exit
50#    --dry-run    show the install command that would be run, without
51#                 running it
52#    --distro ID  force a distro: fedora, ubuntu, debian (default: auto-detect)
53#    -h, --help   print this help message
54#
55# Requires root (or passwordless sudo) to actually install packages.
56
57set -u
58
59DISTRO=""
60
61help() {
62	cat <<EOF
63Usage: $(basename "$0") [--list] [--dry-run] [--distro ID] [-h|--help]
64
65Install the development packages needed to build tools/perf.
66
67The package set is derived from the feature tests in tools/build/feature/,
68mapping each one with an external dependency to the devel package providing
69it, so the corresponding feature gets enabled on a build.
70
71Options:
72    --list      list the packages that would be installed, then exit
73    --dry-run   show the install command that would be run, without running it
74    --distro ID force a distro: fedora, ubuntu, debian
75    -h, --help  print this help message
76
77Distros supported: Fedora (dnf), validated in a toolbx container on
78Fedora 44, Ubuntu (apt-get), validated in a distrobox container on
79Ubuntu 26.04, and Debian (apt-get), reusing the Ubuntu mapping,
80validated in a distrobox container on Debian 13 (trixie).
81EOF
82	exit 0
83}
84
85# ---------------------------------------------------------------------
86# Return the Fedora package(s) providing the devel requirements of a
87# feature test in tools/build/feature/.  Multiple packages are separated
88# by spaces; an empty result means the test has no external devel
89# dependency (pure toolchain/glibc, or no equivalent Fedora package).
90#
91# The mapping was built by checking, for each feature test, which Fedora
92# devel package provides the headers the test compiles against.
93# ---------------------------------------------------------------------
94fedora_pkg_for() {
95	local feat="$1"
96	case "$feat" in
97	libelf|libelf-getphdrnum|libelf-gelf_getnote|libelf-getshdrstrndx)
98		echo "elfutils-libelf-devel"
99		;;
100	libelf-zstd)
101		echo "elfutils-libelf-devel libzstd-devel"
102		;;
103	libdw)
104		echo "elfutils-devel"
105		;;
106	# test-libdebuginfod.c includes <elfutils/debuginfod.h>, which is
107	# provided by elfutils-debuginfod-client-devel, not elfutils-devel.
108	libdebuginfod)
109		echo "elfutils-debuginfod-client-devel"
110		;;
111	libnuma|numa_num_possible_cpus)
112		echo "numactl-devel"
113		;;
114	libzstd)
115		echo "libzstd-devel"
116		;;
117	zlib)
118		echo "zlib-devel"
119		;;
120	lzma)
121		echo "xz-devel"
122		;;
123	libslang)
124		echo "slang-devel"
125		;;
126	libcapstone)
127		echo "capstone-devel"
128		;;
129	libpython)
130		echo "python3-devel"
131		;;
132	libtraceevent)
133		echo "libtraceevent-devel"
134		;;
135	cxa-demangle)
136		echo "libstdc++-devel"
137		;;
138	libbpf)
139		echo "libbpf-devel"
140		;;
141	babeltrace2-ctf-writer)
142		echo "libbabeltrace2-devel"
143		;;
144	libopenssl)
145		echo "openssl-devel"
146		;;
147	libpfm4)
148		echo "libpfm-devel"
149		;;
150	sdt)
151		echo "systemtap-sdt-devel"
152		;;
153	clang-bpf-co-re)
154		echo "clang-devel"
155		;;
156	llvm|llvm-perf)
157		echo "llvm-devel"
158		;;
159	jvmti|jvmti-cmlr)
160		echo "java-latest-openjdk-devel"
161		;;
162	esac
163	# Pure toolchain/libc features (backtrace, eventfd, fortify-source,
164	# gettid, glibc, hello, reallocarray, pthread-*, stackprotector-all,
165	# timerfd, scandirat, sched_getcpu, setns, file-handle,
166	# bionic...) need no external package: their
167	# requirements are covered by the base packages below.  Features that
168	# perf's own build does not check (libcap, libcheck, libcpupower,
169	# libtracefs, whose feature tests exist for rtla/bpftool/rv) and the
170	# opt-in features, which a default build does not enable: the libbfd
171	# disassembler family (libbfd, libbfd-threadsafe, libbfd-liberty,
172	# disassembler-*, cplus-demangle), only linked on BUILD_NONDISTRO
173	# builds and deprecated in favor of capstone, GTK2, LIBPERL and
174	# LIBUNWIND support (ifdef GTK2 / ifdef LIBPERL / LIBUNWIND=1),
175	# and CoreSight (ifdef CORESIGHT), are deliberately not mapped.
176	# libaio is not mapped either: its
177	# test uses the POSIX AIO API (aio.h, aio_*, -lrt), provided by
178	# glibc headers (pulled in by the libc headers package) in the
179	# base set, not the native libaio.h/io_submit API that
180	# libaio-devel provides.
181}
182
183# ---------------------------------------------------------------------
184# Return the Debian/Ubuntu package(s) providing the devel requirements
185# of a feature test in tools/build/feature/.  This mapping is shared by
186# Debian and Ubuntu, whose package names for these devel packages match,
187# and is validated on Ubuntu 26.04 and Debian 13 (trixie).
188# ---------------------------------------------------------------------
189debian_pkg_for() {
190	local feat="$1"
191	case "$feat" in
192	libelf|libelf-getphdrnum|libelf-gelf_getnote|libelf-getshdrstrndx)
193		echo "libelf-dev"
194		;;
195	libelf-zstd)
196		echo "libelf-dev libzstd-dev"
197		;;
198	libdw)
199		echo "libdw-dev"
200		;;
201	libdebuginfod)
202		echo "libdebuginfod-dev"
203		;;
204	libnuma|numa_num_possible_cpus)
205		echo "libnuma-dev"
206		;;
207	libzstd)
208		echo "libzstd-dev"
209		;;
210	zlib)
211		echo "zlib1g-dev"
212		;;
213	lzma)
214		echo "liblzma-dev"
215		;;
216	libslang)
217		echo "libslang2-dev"
218		;;
219	libcapstone)
220		echo "libcapstone-dev"
221		;;
222	libpython)
223		echo "python3-dev"
224		;;
225	libtraceevent)
226		echo "libtraceevent-dev"
227		;;
228	# The cxa-demangle feature test links against libstdc++ builtin
229	# demangling, so on Debian-derived distros it is covered by the
230	# g++ base package below, which pulls in libstdc++-*-dev.
231	cxa-demangle)
232		;;
233	libbpf)
234		echo "libbpf-dev"
235		;;
236	babeltrace2-ctf-writer)
237		echo "libbabeltrace2-dev"
238		;;
239	libopenssl)
240		echo "libssl-dev"
241		;;
242	libpfm4)
243		echo "libpfm4-dev"
244		;;
245	sdt)
246		echo "systemtap-sdt-dev"
247		;;
248	# test-clang-bpf-co-re.c invokes the clang binary (not libclang-cpp),
249	# so clang suffices; llvm-dev brings llvm-config, needed by the
250	# llvm/llvm-perf tests below.
251	clang-bpf-co-re)
252		echo "clang llvm-dev"
253		;;
254	llvm|llvm-perf)
255		echo "llvm-dev"
256		;;
257	jvmti|jvmti-cmlr)
258		echo "default-jdk"
259		;;
260	esac
261	# Same rationale as in fedora_pkg_for() above for unmapped tests.
262}
263
264# Base packages needed by any perf build, regardless of feature tests:
265# compiler, libc headers, flex/bison for the parser, kernel headers
266# for UAPI headers with no in-tree copy, e.g. <linux/capability.h>, and
267# gcc-c++ (dnf) / g++ (apt) is needed by the C++-based feature tests
268# (cxa-demangle, llvm, llvm-perf), compiled with $(CXX), and
269# pulls in libstdc++-devel / libstdc++-*-dev.
270# python3-setuptools is needed to build the python binding (perf's
271# util/setup.py uses it; without it binding is skipped with a warning).
272# rust is not a header-based feature test: test-rust.bin just checks
273# "$(RUSTC) --version" (tools/build/feature/Makefile), so it is mapped
274# here like the other toolchain packages.
275fedora_base_pkgs="gcc gcc-c++ make flex bison glibc-devel kernel-headers python3-setuptools rust"
276# pkg-config mirrors the pkgconf-pkg-config package that Fedora
277# installs by default, needed by the babeltrace2 feature test and
278# libopenssl's pkg-config checks.
279debian_base_pkgs="gcc g++ make pkg-config flex bison libc6-dev linux-libc-dev python3-setuptools rustc"
280
281# ---------------------------------------------------------------------
282# Distro detection
283# ---------------------------------------------------------------------
284detect_distro() {
285	if [ -n "$DISTRO" ]; then
286		echo "$DISTRO"
287		return
288	fi
289	local id
290	id=$( . /etc/os-release 2>/dev/null && echo "${ID:-}" )
291	case "$id" in
292	fedora)			echo "fedora" ;;
293	ubuntu)			echo "ubuntu" ;;
294	debian)			echo "debian" ;;
295	# RHEL and its derivatives share most Fedora package names, but the
296	# mapping is only validated on Fedora, so don't auto-detect them.
297	rhel|centos|rocky|alma|ol) echo "" ;;
298	*)			echo "" ;;
299	esac
300}
301
302# ---------------------------------------------------------------------
303# Feature test enumeration: mirror of the tests checked during a build,
304# from the actual test-*.c / test-*.cpp sources in tools/build/feature/.
305# ---------------------------------------------------------------------
306feature_tests() {
307	local srcdir="$1"
308	local f
309	for f in "$srcdir"/tools/build/feature/test-*.c "$srcdir"/tools/build/feature/test-*.cpp; do
310		[ -e "$f" ] || continue
311		basename "$f" | sed -e 's/^test-//' -e 's/\.c$//' -e 's/\.cpp$//'
312	done
313}
314
315# ---------------------------------------------------------------------
316# Assemble the unique package list.  Since the full feature set is mapped
317# unconditionally, this installs the complete devel environment; installing
318# an already-present package is a no-op for both dnf and apt-get, making
319# this idempotent.
320# ---------------------------------------------------------------------
321package_set() {
322	local distro="$1" srcdir="$2"
323	local feat pkg pkgs
324	case "$distro" in
325	fedora)	pkgs="$fedora_base_pkgs" ;;
326	ubuntu|debian)	pkgs="$debian_base_pkgs" ;;
327	esac
328
329	for feat in $(feature_tests "$srcdir"); do
330		if [ "$distro" = "fedora" ]; then
331			pkg=$(fedora_pkg_for "$feat")
332		else
333			pkg=$(debian_pkg_for "$feat")
334		fi
335		[ -n "$pkg" ] || continue
336		for pkg in $pkg; do
337			case " $pkgs " in
338			*" $pkg "*) ;;
339			*) pkgs="$pkgs $pkg" ;;
340			esac
341		done
342	done
343	echo "$pkgs"
344}
345
346# ---------------------------------------------------------------------
347# The install command proper for each supported package manager, plus
348# the command massaged for --dry-run.
349# ---------------------------------------------------------------------
350install_cmd() {
351	local distro="$1"; shift
352	case "$distro" in
353	fedora)
354		echo "dnf install -y $*"
355		;;
356	ubuntu|debian)
357		# a fresh container has no package index, so update first; run
358		# with a noninteractive debconf frontend: default-jdk, used by
359		# the jvmti feature tests, pulls in tzdata, which prompts for
360		# the timezone on a terminal and would block the install.  The
361		# env var prefix works with the 'sh -c' invocation below, and
362		# shows up in --dry-run.
363		echo "DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y $*"
364		;;
365	esac
366}
367
368main() {
369	local action="install"
370	# Package accumulation happens in package_set(), which, together
371	# with the *_pkg_for() helpers, declares the per-feature 'pkg'
372	# locals; main() holds no per-feature package state, just the
373	# distro-wide list assembled by package_set().
374	local srcdir distro pkgs cmd
375
376	while [ $# -gt 0 ]; do
377		case "$1" in
378		--list)		action="list"; shift ;;
379		--dry-run)	action="dry-run"; shift ;;
380		--distro)
381			[ $# -ge 2 ] || {
382				echo "error: --distro requires an argument (fedora, rhel, ubuntu, debian)" >&2
383				exit 1
384			}
385			DISTRO="$2"; shift 2 ;;
386		-h|--help)	help ;;
387		*)		echo "error: unknown argument: $1" >&2; exit 1 ;;
388		esac
389	done
390
391	srcdir=$(cd "$(dirname "$0")/../../.." && pwd)
392	distro=$(detect_distro)
393	case "$distro" in
394	fedora|ubuntu|debian) ;;
395	*)
396		echo "error: unsupported distro (got '$distro'); the package mapping is not validated on other distros." >&2
397		echo "Supported and validated: Fedora 44 (toolbx container), Ubuntu 26.04 and Debian 13 (distrobox containers)." >&2
398		exit 1
399		;;
400	esac
401
402	pkgs=$(package_set "$distro" "$srcdir")
403
404	case "$action" in
405	list)
406		echo "$pkgs" | tr ' ' '\n' | grep -v '^$' | sort
407		exit 0
408		;;
409	dry-run)
410		install_cmd "$distro" $pkgs
411		exit 0
412		;;
413	esac
414
415	echo "The following packages will be installed to enable perf features:"
416	echo "$pkgs" | tr ' ' '\n' | grep -v '^$' | sort | sed 's/^/  /'
417	echo
418	cmd=$(install_cmd "$distro" $pkgs)
419	if [ "$(id -u)" -eq 0 ]; then
420		sh -c "$cmd"
421	else
422		sudo sh -c "$cmd"
423	fi || {
424		echo "error: the install command failed, see the output above" >&2
425		exit 1
426	}
427}
428
429main "$@"
430