Repository navigation
Expand file tree
/
Copy pathpackage-linux-gpu-stack-build.sh
More file actions
executable file
·281 lines (250 loc) · 8.61 KB
/
Copy pathpackage-linux-gpu-stack-build.sh
File metadata and controls
executable file
·281 lines (250 loc) · 8.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#!/usr/bin/env bash
#
# Builds the standalone Linux GPU dependency stack *inside* a RHEL 8 generation
# container. scripts/package-linux-gpu-stack-docker.sh is the host side of this;
# it re-enters here with the repository and the output directory mounted.
#
# Why a container at all: the stack has to satisfy the glibc baseline Navop
# itself is built against (2.28), because the host dynamic loader resolves these
# libraries directly. Only a RHEL 8 generation distribution ships a Mesa user
# space at that baseline, and collecting it means driving dnf.
#
# The RPMs are downloaded with an empty --installroot on purpose. dnf skips
# dependencies that are already installed on the running system, so an
# installroot-less download silently omits the base libraries the closure needs
# (libmount, libblkid, ...). With an empty root dnf resolves the whole tree, and
# --releasever has to be stated explicitly because an empty root has no rpmdb
# to read the release version from.
#
# Nothing here executes the collected libraries; the packager only reads ELF
# headers, which is why a single container can also produce the other
# architecture with --forcearch.
set -euo pipefail
PROGRAM_NAME="navop-gpu-stack build"
DEFAULT_GLIBC_BASELINE="2.28"
# Packages that provide the sonames Navop's own DT_NEEDED closure and the Mesa
# EGL software renderer reach. The packager fails loudly when a needed soname
# has no provider, so this list is verified rather than guesswork: a missing
# entry surfaces as "missing required shared library ... download the RPM".
GPU_STACK_PACKAGES=(
mesa-dri-drivers mesa-libEGL mesa-libgbm mesa-libglapi
libglvnd-egl libglvnd
libwayland-client libwayland-server
libxkbcommon libxkbcommon-x11 libxcb
libX11 libX11-xcb libXext libXau libXdmcp
libdrm libxshmfence freetype libpng
libstdc++ elfutils-libelf systemd-libs libmount libblkid
libcap xz-libs lz4-libs libgcrypt libidn2
libselinux libsepol pcre2 libunistring
)
binary=""
target=""
output=""
source_distribution=""
glibc_baseline="$DEFAULT_GLIBC_BASELINE"
usage() {
cat <<'USAGE'
Usage: package-linux-gpu-stack-build.sh --binary PATH --target TRIPLE --output DIR [options]
Runs inside the RHEL 8 generation container created by
script/package-linux-gpu-stack-docker.sh and needs no arguments beyond the ones
that wrapper passes.
Options:
--binary PATH release Navop binary; its DT_NEEDED closure
decides which desktop client libraries ship
--target TRIPLE x86_64-unknown-linux-gnu or aarch64-unknown-linux-gnu
--output DIR directory to receive navop-gpu-stack-linux-<arch>.tar.gz
--source-distribution TEXT recorded in the manifest
--glibc-baseline VERSION maximum GLIBC symbol version allowed (default 2.28)
--help
USAGE
}
fail() {
printf 'Error: %s\n' "$*" >&2
exit 1
}
say() {
printf '\n==> %s\n' "$*"
}
parse_arguments() {
while [ "$#" -gt 0 ]; do
case "$1" in
--binary)
shift
[ "$#" -gt 0 ] || fail "--binary requires a path"
binary="$1"
;;
--target)
shift
[ "$#" -gt 0 ] || fail "--target requires a triple"
target="$1"
;;
--output)
shift
[ "$#" -gt 0 ] || fail "--output requires a directory"
output="$1"
;;
--source-distribution)
shift
[ "$#" -gt 0 ] || fail "--source-distribution requires text"
source_distribution="$1"
;;
--glibc-baseline)
shift
[ "$#" -gt 0 ] || fail "--glibc-baseline requires a version"
glibc_baseline="$1"
;;
-h | --help)
usage
exit 0
;;
*)
fail "unknown argument: $1 (try --help)"
;;
esac
shift
done
[ -n "$binary" ] || fail "--binary is required"
[ -n "$target" ] || fail "--target is required"
[ -n "$output" ] || fail "--output is required"
[ -f "$binary" ] || fail "release binary does not exist: $binary"
}
# Maps a Rust target triple onto the RPM architecture dnf has to resolve for and
# the asset label the archive carries.
target_architecture() {
case "$target" in
x86_64-unknown-linux-gnu)
rpm_arch="x86_64"
asset_label="x64"
;;
aarch64-unknown-linux-gnu)
rpm_arch="aarch64"
asset_label="arm64"
;;
*)
fail "unsupported target: $target"
;;
esac
}
install_tooling() {
dnf install -y --setopt=install_weak_deps=False \
cpio findutils dnf-plugins-core python39 binutils rpm >/dev/null
}
kept_rpm_directory=""
# Scratch space is a global so the EXIT trap can still find it: an EXIT trap runs
# after main returns, by which point main's locals are gone.
work_directory=""
cleanup() {
if [ -n "$work_directory" ]; then
rm -rf "$work_directory"
fi
}
download_rpms() {
local destination="$1"
local empty_root="$2"
local release_version
release_version="$(rpm -E %{rhel})"
[ -n "$release_version" ] || fail "cannot determine the distribution release version"
mkdir -p "$destination" "$empty_root"
dnf download --resolve --setopt=install_weak_deps=False \
--releasever="$release_version" --nogpgcheck --forcearch="$rpm_arch" \
--installroot "$empty_root" --destdir "$destination" \
"${GPU_STACK_PACKAGES[@]}"
}
# An x86_64 repository also offers i686 multilib builds, and dnf happily resolves
# them. Their files land under /usr/lib (the 64-bit ones under /usr/lib64), but
# feeding them to the packager would only add ambiguity, so keep the target
# architecture and the arch-independent packages.
filter_rpms_to_architecture() {
local directory="$1"
local keep="$directory/$rpm_arch"
local dropped=0
local rpm package_arch
mkdir -p "$keep"
for rpm in "$directory"/*.rpm; do
[ -e "$rpm" ] || continue
package_arch="$(rpm -qp --qf '%{ARCH}' "$rpm" 2>/dev/null || echo unknown)"
case "$package_arch" in
"$rpm_arch" | noarch)
mv -- "$rpm" "$keep/"
;;
*)
rm -f -- "$rpm"
dropped=$((dropped + 1))
;;
esac
done
kept_rpm_directory="$keep"
printf 'kept %s RPMs for %s, dropped %s foreign-architecture RPMs\n' \
"$(find "$keep" -name '*.rpm' | wc -l)" "$rpm_arch" "$dropped"
}
unpack_rpms() {
local rpm_directory="$1"
local destination="$2"
local rpm
mkdir -p "$destination"
for rpm in "$rpm_directory"/*.rpm; do
[ -e "$rpm" ] || continue
# cpio writes relative to its working directory. --no-absolute-filenames
# keeps a malformed archive from writing outside the tree; the RPM payloads
# only ever carry standard FHS paths.
(cd "$destination" && rpm2cpio "$rpm" | cpio -idm --no-absolute-filenames --quiet)
done
}
default_source_distribution() {
local pretty_name=""
if [ -r /etc/os-release ]; then
# shellcheck disable=SC1091
pretty_name="$(. /etc/os-release && printf '%s' "${PRETTY_NAME:-}")"
fi
[ -n "$pretty_name" ] || pretty_name="RHEL 8 generation distribution"
printf '%s (AppStream %s)' "$pretty_name" "$rpm_arch"
}
main() {
parse_arguments "$@"
target_architecture
if [ -z "$source_distribution" ]; then
source_distribution="$(default_source_distribution)"
fi
# dnf and rpm come from the base image; everything else is installed below.
for command in dnf rpm; do
command -v "$command" >/dev/null 2>&1 || fail "required command is missing: $command"
done
work_directory="$(mktemp -d)"
trap cleanup EXIT
local work="$work_directory"
say "Installing packaging tooling"
install_tooling
for command in rpm2cpio cpio python3; do
command -v "$command" >/dev/null 2>&1 || fail "required command is missing: $command"
done
say "Resolving the ${rpm_arch} dependency tree"
download_rpms "$work/rpms" "$work/empty-root"
filter_rpms_to_architecture "$work/rpms"
say "Unpacking RPMs into a file tree"
unpack_rpms "$kept_rpm_directory" "$work/root"
say "Collecting the dependency stack"
local repository_root
repository_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
mkdir -p "$output"
python3 "$repository_root/script/package-linux-gpu-stack.py" \
--binary "$binary" \
--library-root "$work/root" \
--rpm-directory "$kept_rpm_directory" \
--target "$target" \
--output "$work/gpu-stack" \
--installer-source "$repository_root/script/linux-gpu-stack-install.sh" \
--glibc-baseline "$glibc_baseline" \
--source-distribution "$source_distribution"
local archive="navop-gpu-stack-linux-${asset_label}.tar.gz"
say "Archiving $archive"
tar \
--sort=name \
--mtime='UTC 1970-01-01' \
--owner=0 \
--group=0 \
--numeric-owner \
-czf "$output/$archive" \
-C "$work/gpu-stack" .
ls -l "$output/$archive"
}
main "$@"