The gamescope prune deleted the compositor and shipped only its launcher #355

Merged
enricobuehler merged 5 commits from gamescope-show-version-banner into main 2026-08-20 18:00:11 +00:00
Owner

Gamescope now builds end to end — verified green on run 19625 with build-gamescope, the first time that has ever happened on nix.

The root cause

nixpkgs wraps this package. makeWrapper leaves the real compositor ELF at bin/.gamescope-wrapped and installs a small launcher at bin/gamescope that sets PATH (xwininfo) before exec'ing it. Our prune kept only gamescope:

find $out/bin -mindepth 1 ! -name gamescope -delete

…so it deleted the compositor and shipped the launcher pointing at a path that no longer existed. Measured on run 19622: $out/bin held a single 16 KB file, and its strings showed the PATH prologue and /bin/.gamescope-wrapped.

That one line explains every symptom, and it means two things I concluded earlier were wrong:

  • --version printing nothing was not a build-sandbox property, and not upstream's PrintVersion() landing after the tag. The launcher was exec'ing a deleted target.
  • The +pfhdr marker being "missing from the binary" was not a lost patch. A wrapper carries no version string, so it could never have been there.

The guards were right on every run. They were reporting a genuinely broken artifact — one that would have installed cleanly under our own name and then failed at runtime, which is exactly the outcome this file's header calls the worst case. I doubted them twice; they were correct both times.

What's in here

  • Keep .gamescope-wrapped through the prune, and assert on the wrapped ELF rather than the launcher (which would otherwise pass or fail by accident). The launcher references its target by absolute path, so renaming it stays safe.
  • Pin src to 5fb8dce4 (3.16.25-11) — the commit packaging/gamescope/README.md, the RPM spec, the PKGBUILD and build-punktfunk-gamescope.sh all already ship. nix was the only channel patching whatever nixpkgs happened to carry; nixpkgs' 3.16.24 could not take patch 0009 at all. Hash from nix-prefetch-git --fetch-submodules.
  • Assert +pfhdr in the artifact, not by running the binarybuild-punktfunk-gamescope.sh already avoids running it, checking src/meson.build. Grepping the installed ELF is stronger: it proves the marker survived patching, meson configuration and compilation.
  • A source-level gate in postPatch, so a lost marker names its own stage instead of costing a compositor build per guess.

Why the history is a bit long

Each commit here is a real step in a five-deep stack of latent faults, every one masking the next: patch drift → enableWsi defaulting off → prune permissions → a 7 GiB runner cap → this. Squash if you prefer a single commit; the reasoning is preserved in the comments either way.

Merging this is a flake-touching push to main, so it triggers the publish tier — and with gamescope finally building, the binary cache should fill for the first time.

**Gamescope now builds end to end** — verified green on run 19625 with `build-gamescope`, the first time that has ever happened on nix. ## The root cause nixpkgs **wraps** this package. `makeWrapper` leaves the real compositor ELF at `bin/.gamescope-wrapped` and installs a small launcher at `bin/gamescope` that sets PATH (xwininfo) before exec'ing it. Our prune kept only `gamescope`: ```sh find $out/bin -mindepth 1 ! -name gamescope -delete ``` …so it **deleted the compositor** and shipped the launcher pointing at a path that no longer existed. Measured on run 19622: `$out/bin` held a single 16 KB file, and its strings showed the PATH prologue and `/bin/.gamescope-wrapped`. That one line explains every symptom, and it means **two things I concluded earlier were wrong**: - `--version` printing nothing was *not* a build-sandbox property, and *not* upstream's `PrintVersion()` landing after the tag. The launcher was exec'ing a deleted target. - The `+pfhdr` marker being "missing from the binary" was *not* a lost patch. A wrapper carries no version string, so it could never have been there. **The guards were right on every run.** They were reporting a genuinely broken artifact — one that would have installed cleanly under our own name and then failed at runtime, which is exactly the outcome this file's header calls the worst case. I doubted them twice; they were correct both times. ## What's in here - **Keep `.gamescope-wrapped` through the prune**, and assert on the wrapped ELF rather than the launcher (which would otherwise pass or fail by accident). The launcher references its target by absolute path, so renaming it stays safe. - **Pin `src` to `5fb8dce4`** (3.16.25-11) — the commit `packaging/gamescope/README.md`, the RPM spec, the PKGBUILD and `build-punktfunk-gamescope.sh` all already ship. nix was the only channel patching whatever nixpkgs happened to carry; nixpkgs' 3.16.24 could not take patch 0009 at all. Hash from `nix-prefetch-git --fetch-submodules`. - **Assert `+pfhdr` in the artifact, not by running the binary** — `build-punktfunk-gamescope.sh` already avoids running it, checking `src/meson.build`. Grepping the installed ELF is stronger: it proves the marker survived patching, meson configuration *and* compilation. - **A source-level gate in `postPatch`**, so a lost marker names its own stage instead of costing a compositor build per guess. ## Why the history is a bit long Each commit here is a real step in a five-deep stack of latent faults, every one masking the next: patch drift → `enableWsi` defaulting off → prune permissions → a 7 GiB runner cap → this. Squash if you prefer a single commit; the reasoning is preserved in the comments either way. Merging this is a flake-touching push to `main`, so it triggers the publish tier — and with gamescope finally building, the binary cache should fill for the first time.
enricobuehler added 5 commits 2026-08-20 07:22:19 +00:00
The memory raise got gamescope building, and it now reaches installCheckPhase
and fails there:

    punktfunk-gamescope: the +pfhdr marker is missing — the patches did not take

Patch 0005 applied cleanly to src/meson.build in that same run, so the message
is misleading: the patch DID take. Two very different causes are
indistinguishable from the log as written —

  * the binary never ran (shrunk RPATH, missing loader dep), so --version
    printed nothing at all; or
  * it ran and printed a version without +pfhdrN, meaning upstream no longer
    builds the banner from VCS_TAG.

A guard that reports 'missing' without showing what it read cannot be acted on,
and each guess costs a full compositor build. Capture the output and print it on
failure, with a note on how to read the two cases apart.

No behaviour change: same assertion, same exit. Also verified in this run: the
chmod fix works (prune, rename and layer install all completed) and the WSI
layer is installed.
nix was the ONLY channel not pinning the compositor — the RPM spec, the
PKGBUILD, build-punktfunk-gamescope.sh and packaging/gamescope/README.md all
ship 5fb8dce4 (3.16.25-11), while gamescope.nix patched whatever version
nixpkgs happened to carry. That produced two failures in two days, both the
same bug:

  * nixpkgs shipped 3.16.24, where patch 0009's context does not exist, so the
    build died at patchPhase. host.gamescopeHdr defaults true, so every
    `services.punktfunk.host.enable = true` failed the same way.
  * bumping the lock to 3.16.25 fixed the patches, and then --version printed
    NOTHING. Upstream's `gamescope::PrintVersion()` landed AFTER the 3.16.25
    tag; in that tag `--version` is `return 0;` with the comment "We always
    print the version to stderr anyway". The host reads that banner to decide a
    session's bit depth and cursor compositing BEFORE the virtual display
    exists, so a silent banner is a silent fall back to SDR.

The installCheck was right to fail — this was not a check problem.

Also in this commit: the +pfhdr guard now prints the banner it read. That is
how the empty output was identified at all; without it the failure is
indistinguishable from the binary not starting, at ~15 min per guess.

vcs_tag now substitutes the PINNED version (old.version is the pre-override
attr and would still say 3.16.25).

nix-instantiate --parse clean. Hash from nix-prefetch-git --fetch-submodules.
`--version` produces EMPTY output under nix's build sandbox — measured on BOTH
nixpkgs' 3.16.25 and the pinned 5fb8dce4 (runs 19551 / 19573 / 19594). It is a
sandbox property, not a defect: gamescope calls PrintVersion() before the getopt
loop (src/main.cpp:721), so `gamescope --version` does print the banner on a
real system, which is what the host's capability probe reads. My earlier claim
that the probe itself was broken was wrong.

packaging/gamescope/build-punktfunk-gamescope.sh already avoids running the
binary, asserting on src/meson.build instead. Grepping the installed ELF is
strictly stronger than either: the version string reaches .rodata via
GamescopeVersion.h's k_szGamescopeVersion, so this proves the marker survived
patching, meson configuration and compilation into the artifact we ship — and it
cannot be defeated by the binary being unable to start.

The src pin from the previous commit stays. It is still right for its own
reason: the patches are authored against 5fb8dce4, nixpkgs' 3.16.24 could not
take patch 0009 at all, and every other channel ships this exact commit.
The marker is not in the installed ELF, and patch 0005 applies cleanly to a
src/meson.build that still carries the exact line it rewrites. Those two facts
cannot both be reasoned from the log as it stands, so stop guessing and
instrument the two stages separately:

  * postPatch now asserts +pfhdr is in src/meson.build after our vcs_tag
    substitution — the same gate build-punktfunk-gamescope.sh applies. If it
    fires, patch 0005 or the substitution lost the marker, and it prints the
    version block as patched.
  * the installCheck failure branch now prints evidence instead of an
    assertion: $out/bin, anything under $out mentioning pfhdr, and the
    version-shaped strings actually present in the binary.

Whichever fires, the next run says which stage drops it. Each guess here costs
a full compositor build, and I have now spent three on this one symptom.
fix(nix): the prune deleted the compositor and shipped only its launcher
ci / docs-drift (pull_request) Successful in 30s
ci / bun-nix (pull_request) Successful in 1m0s
ci / web (pull_request) Successful in 1m15s
ci / docs-site (pull_request) Successful in 1m16s
ci / rust-arm64 (pull_request) Successful in 1m52s
ci / rust (pull_request) Successful in 5m16s
nix / flake (pull_request) Successful in 12m29s
7d2a8778d1
ROOT CAUSE of every gamescope symptom chased today. nixpkgs WRAPS this package:
makeWrapper leaves the real compositor ELF at bin/.gamescope-wrapped and
installs a small launcher at bin/gamescope that sets PATH (xwininfo) before
exec'ing it. Our prune kept only `gamescope`:

    find $out/bin -mindepth 1 ! -name gamescope -delete

so it deleted the compositor and shipped the launcher pointing at a path that
no longer existed. MEASURED (run 19622): $out/bin held one 16 KB file, and
`strings` on it showed the PATH prologue and '/bin/.gamescope-wrapped'.

That single line explains all of it:
  * `--version` printed NOTHING — the launcher exec'd a deleted target. I had
    attributed this to the build sandbox, and to upstream's PrintVersion()
    landing after 3.16.25. Both were wrong.
  * the +pfhdr marker was 'missing from the binary' — a wrapper carries no
    version string, so it never could have been there.

Keep the target through the prune, and assert on the WRAPPED ELF rather than
the launcher (which would otherwise pass or fail by accident). The launcher
references its target by absolute path, so renaming the launcher stays safe.

The guards were right on every run; they were reporting a genuinely broken
artifact, not a flaky check.
enricobuehler merged commit 33b029695f into main 2026-08-20 18:00:11 +00:00
enricobuehler deleted branch gamescope-show-version-banner 2026-08-20 18:00:22 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#355