From 53ff3130465135f9d95cb65e8b5a3fd143b02495 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Thu, 30 Jul 2026 01:23:35 +0200 Subject: [PATCH] =?UTF-8?q?fix(packaging/gamescope):=20the=20shipped=20com?= =?UTF-8?q?positor=20starts=20on=20SteamOS=20=E2=80=94=20a=20rolling-distr?= =?UTF-8?q?o=20libstdc++=20never=20followed=20it=20there?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `punktfunk-gamescope-3.16.25.pfhdr2-1` off the pacman repo cannot start on SteamOS 3.8.16: `/usr/lib/libstdc++.so.6: version 'GLIBCXX_3.4.35' not found`. The Arch container CI builds it in is on gcc 16.1.1; SteamOS ships libstdc++ 3.4.34 and moves when Valve says so. Nothing else about the binary was wrong — every other soname resolved on the box, and glibc was never close (it asks for 2.38 at most against SteamOS's 2.41) — so the one dynamic C++ runtime was the whole reason the gamescope backend's own most important platform got a package that dies at `--version`. The C++ runtime therefore goes static, for the same reason wlroots already does: this binary is built on a rolling distro and has to start on a frozen one. It is safe here because gamescope links no shared C++ library at all — its NEEDED list is all C, and glslang/SPIRV are build-time only — so no C++ ABI crosses a shared boundary. Cost is ~1 MB (5.9 → 7.1). The flags are appended to LDFLAGS rather than passed as `-Dcpp_link_args`, which would replace the value meson derives from the environment and silently drop makepkg's `-z relro`/`-z now`/`--as-needed`. A static runtime is invisible in a passing build and only surfaces as a binary that will not start somewhere else, so the build now asserts it: no `libstdc++` in NEEDED, which needs no version threshold to check and turns the regression back into a build failure. Verified by building in `archlinux:base-devel` — the environment arch.yml uses, gcc 16.1.1 and glibc 2.44, both far newer than the target — and running the result on SteamOS 3.8.16 with nothing supplied: banner `punktfunk-gamescope version 3.16.25-4-g6bbe157+pfhdr2`, no libstdc++ in NEEDED, max GLIBC_2.38, every soname resolving. The host's whole HDR gate chain then answers on SteamOS for the first time (780M / RADV PHOENIX): 10-bit PQ capture offered, cursor painted in-node, native-plane HDR and GameStream HDR capable both true once `PUNKTFUNK_GAMESCOPE_HDR` is on, false with the distro's gamescope. Co-Authored-By: Claude Opus 5 (1M context) --- .../gamescope/build-punktfunk-gamescope.sh | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packaging/gamescope/build-punktfunk-gamescope.sh b/packaging/gamescope/build-punktfunk-gamescope.sh index 85f65c15..2f15545d 100755 --- a/packaging/gamescope/build-punktfunk-gamescope.sh +++ b/packaging/gamescope/build-punktfunk-gamescope.sh @@ -95,6 +95,19 @@ echo "==> configuring" # there would not start on the host. Pinning the fallback makes the outcome the same everywhere. # (gamescope's own meson.build hard-errors if libliftoff/vkroots are missing from this list, so # all three go together.) +# +# The C++ runtime goes STATIC for the same reason wlroots does: this binary is built on a ROLLING +# distro and has to start on a FROZEN one. Arch's gcc (16.1.1 when this was written) makes the +# compositor require `GLIBCXX_3.4.35`, and SteamOS 3.8.16 ships libstdc++ 3.4.34 — so the published +# Arch package died on the very platform the gamescope backend matters most on ("version +# GLIBCXX_3.4.35 not found"), while every other soname resolved and glibc was never close to the +# limit (the binary asks for 2.38 at most; SteamOS has 2.41). Safe because +# gamescope links NO shared C++ library (its `NEEDED` list is all C — glslang/SPIRV are build-time +# only), so no C++ ABI ever crosses a shared boundary; it costs ~1 MB. +# Appended to LDFLAGS rather than passed as `-Dcpp_link_args`, because that option would REPLACE +# the value meson derives from the environment and silently drop makepkg's hardening flags +# (`-z relro`, `-z now`, `--as-needed`). +export LDFLAGS="${LDFLAGS:-} -static-libstdc++ -static-libgcc" meson setup "$BUILD" "$SRCDIR" \ --prefix="$PREFIX" \ --buildtype=release \ @@ -112,6 +125,17 @@ ninja -C "$BUILD" ${JOBS:+-j "$JOBS"} # distro's gamescope package — and we need none of them: the host only ever execs the compositor. BIN="$BUILD/src/gamescope" [ -x "$BIN" ] || { echo "build produced no $BIN" >&2; exit 1; } +# The static C++ runtime above is invisible in a successful build and only shows up as a binary +# that will not start on an older distro — so assert it here, where a mistake is a build failure +# instead of a package that dies at `--version` on SteamOS. No `libstdc++.so.6` in NEEDED is the +# whole invariant (and it needs no version threshold to check). +if command -v objdump >/dev/null; then + objdump -p "$BIN" 2>/dev/null | grep -q 'NEEDED.*libstdc++' && { + echo "built binary links libstdc++ dynamically — the static C++ runtime did not take, and this" >&2 + echo "package would not start on a distro older than the build host" >&2 + exit 1 + } +fi DEST="${DESTDIR}${PREFIX}/bin/punktfunk-gamescope" echo "==> installing $DEST" install -Dm755 "$BIN" "$DEST"