Files
punktfunk/ci/arch-ci.Dockerfile
T
enricobuehler f1dc6c9f94 ci: cache the C/C++ half, link with mold, and split the debug/release target caches
Three independent reasons Rust CI stayed slow despite sccache, fixed together because
they share the same measurement.

1. sccache only ever covered RUSTC. Every C/C++ dependency in the tree — aws-lc-sys,
   openh264-sys2's vendored C++, the CMake-built libopus behind audiopus_sys — was
   compiled from scratch on every job of every workflow. CMAKE_{C,CXX}_COMPILER_LAUNCHER
   plus CC_/CXX_x86_64_unknown_linux_gnu route both build-script styles (cc-rs and
   cmake-rs) through the same shared cache.

   The CC_* vars are JOB-scoped in ci.yml and deb.yml, never workflow-scoped: the
   arm64 cross image sets its own CC_x86_64_unknown_linux_gnu=pf-host-cc, the wrapper
   that keeps ffmpeg-sys-next's host probe off the arm64 include dirs. Overwriting it
   would surface as a header mismatch rather than as a CI config error.

2. Linking is cacheable by nothing, and these jobs relink the host, client, session,
   cli, worker and tray on every run — twice per push for rpm (f43 + f44). The four
   Linux builder images now install mold and carry a $CARGO_HOME/config.toml that uses
   it for x86_64. aarch64 is deliberately left alone (cross driver, already-fast legs).
   Each image asserts `mold --version` in its build, so an image can never ship the
   flag without the linker: docker.yml goes red and :latest stays on the last good one.

3. THE EXPENSIVE ONE. ci.yml (debug) and deb.yml (release) named a byte-identical
   target-cache key, under a comment claiming the release build reused ci.yml's
   artifacts. It never could. actions/cache is first-saver-wins on an exact key and
   ci.yml is the faster job, so the shared key always held a debug-only target/ — and,
   worse, deb.yml could then never save its own, because the key was taken. Every
   canary .deb has been a from-scratch release build for as long as both keys existed.
   Same collision on the arm64 pair, and a third participant in
   linux-client-screenshots.yml. Split into -debug-/-release- key families; that job
   reads deb's tree via restore-keys but keeps its own exact key so it can never win
   the save race and replace a full tree with its single-crate one.

Also: one scripts/ci/ensure-sccache.sh replaces ten copy-pasted bootstrap blocks that
had already drifted into two dialects (GNU tar --wildcards vs bsdtar), every Rust job
now ends with --show-stats so a cache regression is visible instead of just "CI got
slower", and deb.yml's web install joins every other CI install on --ignore-scripts.

No behaviour change to any artifact: same compilers, same flags, same outputs.
2026-08-13 11:57:13 +02:00

85 lines
5.7 KiB
Docker

# Arch CI builder: base-devel + every dependency arch.yml's two makepkg legs used to
# pacman-install per run (~1 GB of mirror traffic each time) + bun + sccache + nodejs
# (JS actions exec node INSIDE the job container — the same lesson as android-ci).
# Content-keyed and rebuilt only when the ci/ tree changes (docker.yml `builders`).
#
# docker build -f ci/arch-ci.Dockerfile -t punktfunk-arch-ci ci
#
# ROLLING-RELEASE TRADEOFF, on purpose: packages now build against the Arch snapshot
# from the last image rebuild instead of a fresh -Syu per run. That is the same staleness
# the gamescope cache already embraces ("a stale binary against newer system libs is the
# same risk the distro's own package carries between rebuilds"), and any ci/ edit — or
# bumping the date in this line (refreshed: 2026-08-08) — re-keys and re-snapshots it.
#
# ⚠ That staleness has a sharp edge, and 2026-08-08 is why the date above moved: this snapshot is
# what decides which FFmpeg the HOST links, and arch.yml deliberately runs no -Syu, so the builder
# stayed frozen on ffmpeg 8 (libavcodec 62) even after Arch shipped 2:9.0-5 (libavcodec 63) to
# every user. A canary built from the old snapshot therefore CANNOT satisfy the soname dep that
# packaging/arch/PKGBUILD now derives from the link (libavcodec.so=62-64 against a box that has
# 63-64), so it would simply refuse to install rather than start. Re-keying this image is the step
# that makes the ffmpeg-9 bump actually reach the package — a Cargo.toml bump alone does nothing
# here. Whenever Arch moves to an FFmpeg major, bump the date in the same commit.
#
# ⚠ AND KNOW WHY THAT WAS NOT ENOUGH: bumping this date only helps once docker.yml has actually
# republished the image, and nothing sequences the two workflows. v0.25.0 was tagged four minutes
# after the ffmpeg-9 merge, so the release build still pulled the FFmpeg-8 `:latest` and published
# a punktfunk-host that no up-to-date Arch box could install — which blocks the user's ENTIRE
# `pacman -Syu`, not just our package. arch.yml therefore no longer trusts this image on that one
# axis: it compares the builder's libav sonames against the repos before building (and `-Syu`s
# itself if they differ), and refuses to publish anything a pristine-db `pacman -U --print` says
# is unsatisfiable. This file staying current is still the CHEAP path — those guards are the
# backstop, not the plan.
FROM docker.io/library/archlinux:base-devel
# One transaction: the main build/runtime deps (first list) + the gamescope companion's
# deps (second list) — both copied verbatim from what arch.yml installed in-job, where
# they now no-op as `--needed` guards.
# vulkan-headers rides the first list only because arch.yml's copy does; the package it actually
# serves is the gamescope companion (packaging/gamescope/PKGBUILD makedepends). punktfunk itself
# needs no system Vulkan headers — pyrowave-sys bindgens its own vendored copy and ash dlopens the
# loader — but arch.yml builds gamescope with `makepkg -d`, so an absent makedepend would not be
# reported as a missing dependency, only as a compile failure. Keep it.
RUN pacman -Syu --noconfirm --needed \
git nodejs rust clang cmake ninja nasm pkgconf python vulkan-headers \
gtk4 libadwaita sdl3 ffmpeg pipewire wayland libxkbcommon opus libei \
mesa libglvnd unzip libarchive \
glslang libcap libdrm libinput libx11 libxcomposite libxdamage libxext \
libxmu libxrender libxres libxtst libxxf86vm libavif libdecor \
hwdata luajit seatd sdl2-compat vulkan-icd-loader \
xcb-util-errors xcb-util-wm xorg-xwayland \
meson glm wayland-protocols benchmark libxcursor \
# mold: link-phase accelerator (sccache cannot cache linking). makepkg links the release
# host, client, worker and tray on every arch.yml run. Wired via cargo-config-mold.toml
# below. It does NOT affect the gamescope companion leg — that is meson + its own linker,
# and its `-static-libstdc++` link is untouched.
mold \
&& pacman -Scc --noconfirm
# bun builds the punktfunk-web console + the punktfunk-scripting runner AND is vendored
# as their runtime (PF_WITH_WEB=1 / PF_WITH_SCRIPTING=1); it's AUR-only on Arch, so
# bootstrap the official binary — once, here, instead of per run.
RUN curl -fsSL https://bun.sh/install | bash \
&& install -m0755 /root/.bun/bin/bun /usr/local/bin/bun \
&& rm -rf /root/.bun \
&& bun --version
# Shared compile cache: jobs set RUSTC_WRAPPER=sccache (backend = RustFS S3 on the LAN,
# see .gitea/workflows — the env lives there so dev use of this image stays uncached).
ARG SCCACHE_VERSION=0.10.0
RUN curl -fsSL "https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl.tar.gz" \
| tar -xz --wildcards --strip-components=1 -C /usr/local/bin '*/sccache' \
&& sccache --version
# CARGO_HOME is declared here only so this image agrees with what arch.yml already sets at job
# level (and so `cargo` finds the config below when the image is used by hand). The workflow still
# passes CARGO_HOME explicitly across the `sudo -u builder env …` boundary, which strips ambient
# env — that is why the C/C++ sccache wiring has to be re-exported there by name while THIS file,
# being a file, crosses the boundary for free.
ENV CARGO_HOME=/usr/local/cargo
RUN mkdir -p /usr/local/cargo && chmod -R a+w /usr/local/cargo
# Link x86_64 with mold — see cargo-config-mold.toml's header for the rustflags traps, and
# rust-ci.Dockerfile for why the `mold --version` assertion sits next to the COPY.
COPY cargo-config-mold.toml /usr/local/cargo/config.toml
RUN mold --version && test -r /usr/local/cargo/config.toml