Files
navop/script/package-linux-gpu-stack-build.sh
T
胡飞 e38a35df99 build(linux): 渲染依赖独立成包,便携版改为同一二进制加标记文件
- 新增 script/package-linux-gpu-stack{,.py,-build.sh,-docker.sh} 与
  script/linux-gpu-stack-install.sh:在 Rocky 8 容器里收集 Mesa/EGL/LLVM
  闭包,产出 navop-<version>-linux-<x64|arm64>-gpu-stack.tar.gz;安装脚本
  按 ldconfig 只补宿主缺失的库,支持 --dry-run/--force/--uninstall,卸载
  只回放自己安装过的文件
- 删除 Linux portable 变体:musl 启动器 script/linux-portable-launcher.c、
  script/package-linux-portable.{py,sh},发布矩阵移除 linux-x64-portable
  与 linux-arm64-portable
- Linux 应用包统一为默认 feature 的裸二进制,构建走
  cargo zigbuild --target <triple>.2.28,不再拆 --no-default-features 分支
- 便携包回归:Package (Linux) 用同一个二进制平铺、加一个空的
  navop.portable 标记打成 -portable.tar.gz,不新增矩阵项、不二次编译
- upload-r2.yml 的 targetFor() 识别 -portable./-gpu-stack. 后缀并映射到
  对应 target,避免这两类资产被归入 universal
- 三语言 install-update.md 更新下载表,新增图形依赖包与便携版说明
- 重写 script/test-release-packaging.mjs:退役 portable 打包器用例,新增
  依赖包三方契约、便携标记跨平台锁定、install.sh 增量卸载等断言
2026-09-22 13:45:05 +08:00

282 lines
8.6 KiB
Bash
Executable File

#!/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 "$@"