pipeline fixes (Hopefully)

This commit is contained in:
bryanthaboi
2026-08-20 09:45:47 -04:00
parent 7c9c2380d2
commit c2b6a7b937
5 changed files with 128 additions and 86 deletions
+8 -55
View File
@@ -12,10 +12,11 @@ name: ci
# #
on: on:
push: push:
# Integration branch + release branch. PRs already run via pull_request
# (any base); this list is only for post-merge push runs.
branches: [dev, main] branches: [dev, main]
# PRs into dev only: a dev -> main ship PR reuses the required checks the
# dev push already put on the same head SHA, so it needs no second run.
pull_request: pull_request:
branches: [dev]
# a force-push while CI is mid-run should cancel the stale run, not queue # a force-push while CI is mid-run should cancel the stale run, not queue
concurrency: concurrency:
@@ -318,52 +319,10 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
scripts/build_linux_arm64.sh --version 0.0.0 scripts/build_linux_arm64.sh --version 0.0.0
# Shared with the release workflow so shipped images get the same
# self-contained / glibc-floor checks as PR builds.
- name: Verify the AppImage is self-contained and bullseye-compatible - name: Verify the AppImage is self-contained and bullseye-compatible
run: | run: bash scripts/linux-arm64/verify_appimage.sh dist/linux-arm64/gen1recomp-0.0.0-linux-arm64.AppImage
set -euo pipefail
image="dist/linux-arm64/gen1recomp-0.0.0-linux-arm64.AppImage"
# --appimage-extract needs no FUSE, so this works on a runner
# without /dev/fuse and still exercises the real payload.
"$image" --appimage-extract >/dev/null
for required in AppRun bin/love game.love lib/liblove-11.5.so; do
[ -e "squashfs-root/$required" ] \
|| { echo "::error::AppImage is missing $required"; exit 1; }
done
# Every bundled object must resolve once AppRun's LD_LIBRARY_PATH is
# applied; an unresolved soname here is a user-visible launch crash.
#
# This runs on a HEADLESS runner on purpose, and that is the point.
# The first version of this build bundled Debian's SDL2, which
# hard-links libpulse/libasound/libX11/libwayland, so it only ever
# started on a full desktop -- a bare runner is what exposed it.
missing="$(LD_LIBRARY_PATH="$PWD/squashfs-root/lib" \
ldd squashfs-root/bin/love squashfs-root/lib/*.so* 2>/dev/null \
| grep 'not found' || true)"
[ -z "$missing" ] || { echo "::error::unresolved deps:"; echo "$missing"; exit 1; }
# Nothing may hard-link a driver, session or audio-stack library:
# those must be reached through dlopen so the AppImage runs on a box
# with only ALSA, only Wayland, or only KMSDRM.
linked="$(for f in squashfs-root/bin/love squashfs-root/lib/*.so*; do
objdump -p "$f" 2>/dev/null | awk '/NEEDED/{print $2}'
done | sort -u | grep -E '^lib(pulse|asound|X11|wayland|GL|EGL|drm|gbm|xcb|cairo|sndio|dbus)' || true)"
[ -z "$linked" ] \
|| { echo "::error::these must be dlopened, not linked:"; echo "$linked"; exit 1; }
# The whole point of compiling on bullseye. If a future change moves
# the builder to a newer base, the glibc floor silently rises and
# every user on an older distro gets "GLIBC_2.xx not found" -- catch
# it here instead of in a release.
floor="$(objdump -T squashfs-root/bin/love squashfs-root/lib/*.so* 2>/dev/null \
| grep -o 'GLIBC_[0-9.]*' | sort -V | tail -1)"
echo "highest required glibc symbol version: $floor"
[ -n "$floor" ] \
|| { echo "::error::found no versioned glibc symbols -- objdump read nothing"; exit 1; }
highest="$(printf '%s\n' "$floor" "GLIBC_2.31" | sort -V | tail -1)"
[ "$highest" = "GLIBC_2.31" ] \
|| { echo "::error::AppImage requires $floor, above the bullseye 2.31 floor"; exit 1; }
- name: Upload the AppImage - name: Upload the AppImage
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
@@ -400,7 +359,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
- run: sudo apt-get update && sudo apt-get install -y luajit
- run: python3 -m pip install --upgrade pillow - run: python3 -m pip install --upgrade pillow
# the fixture PNGs are committed (they are 8x8 placeholders, not # the fixture PNGs are committed (they are 8x8 placeholders, not
@@ -421,13 +379,8 @@ jobs:
print(f"\n{len(paths)} fixture assets valid") print(f"\n{len(paths)} fixture assets valid")
PY PY
# the fingerprint golden is the parity tripwire; prove it still # the fingerprint parity gates (gate_fingerprint / gate_meta_coverage)
# matches the dataset on a clean checkout # run in the headless job via run_engine; this job only guards the PNGs
- name: fingerprint gate
run: luajit tests/engine/gate_fingerprint.lua
- name: parity-guarantee meta-test
run: luajit tests/engine/gate_meta_coverage.lua
# Only the differ is under test here, and the job is named for that. The # Only the differ is under test here, and the job is named for that. The
# capture half of the golden pipeline does not exist: a POKEPORT_DRIVER # capture half of the golden pipeline does not exist: a POKEPORT_DRIVER
+14 -2
View File
@@ -161,6 +161,8 @@ jobs:
scripts/build_linux_arm64.sh \ scripts/build_linux_arm64.sh \
--version "${{ needs.version.outputs.version }}" \ --version "${{ needs.version.outputs.version }}" \
--game-love .bazinga/work/game.love --game-love .bazinga/work/game.love
- name: Verify the AppImage is self-contained and bullseye-compatible
run: bash scripts/linux-arm64/verify_appimage.sh "dist/linux-arm64/gen1recomp-${{ needs.version.outputs.version }}-linux-arm64.AppImage"
- name: Upload Linux arm64 release - name: Upload Linux arm64 release
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
@@ -285,7 +287,7 @@ jobs:
retention-days: 1 retention-days: 1
release: release:
needs: [version, xbox-uwp, linux-arm64, native-tls-win] needs: [version, love-payload, xbox-uwp, linux-arm64, native-tls-win]
runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }} runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }}
steps: steps:
@@ -307,6 +309,15 @@ jobs:
name: gen1tls-win-x64 name: gen1tls-win-x64
path: dist/native/win-x64 path: dist/native/win-x64
# The same game.love the arm64 AppImage and Xbox UWP builds fused, so
# every release asset ships one identical payload (build.sh's own pack
# would omit PATCH_NOTES.md and mobile/ios/app-repo.json).
- name: Download shared payload
uses: actions/download-artifact@v8
with:
name: gen1recomp-release-love
path: dist/payload
- name: Import signing certificate into a temporary keychain - name: Import signing certificate into a temporary keychain
if: github.repository == 'bryanthaboi/gen1recomp' if: github.repository == 'bryanthaboi/gen1recomp'
run: | run: |
@@ -357,7 +368,8 @@ jobs:
echo "::error::gen1tls.dll missing at $GEN1TLS_DLL (native-tls-win job)" echo "::error::gen1tls.dll missing at $GEN1TLS_DLL (native-tls-win job)"
exit 1 exit 1
fi fi
scripts/build.sh all --version "${{ needs.version.outputs.version }}" --no-notarize scripts/build.sh all --version "${{ needs.version.outputs.version }}" --no-notarize \
--game-love dist/payload/game.love
unzip -l dist/win/gen1recomp-win64.zip | grep -F gen1tls.dll \ unzip -l dist/win/gen1recomp-win64.zip | grep -F gen1tls.dll \
|| { echo "::error::Windows zip is missing gen1tls.dll"; exit 1; } || { echo "::error::Windows zip is missing gen1tls.dll"; exit 1; }
+11 -8
View File
@@ -105,8 +105,9 @@ trixie.
This is a statement about the *compile environment*, not about where the This is a statement about the *compile environment*, not about where the
artifact runs — building on your own newer distro would silently raise that artifact runs — building on your own newer distro would silently raise that
floor and strand every user on an older one, with no symptom until they floor and strand every user on an older one, with no symptom until they
download it. CI enforces the floor: `linux-arm64-build` fails if the highest download it. `scripts/linux-arm64/verify_appimage.sh` enforces the floor in
required glibc symbol version climbs above 2.31. both CI (`linux-arm64-build`) and the release workflow: the build fails if
the highest required glibc symbol version climbs above 2.31.
### Why five libraries are built from source ### Why five libraries are built from source
@@ -172,13 +173,15 @@ Three jobs, path-gated on `scripts/build_linux_arm64.sh`,
exclude list still classifies known sonames correctly, that AppRun still exclude list still classifies known sonames correctly, that AppRun still
launches `game.love` with `--fused`, and that the host-arch guard actually launches `game.love` with `--fused`, and that the host-arch guard actually
fires. Needs no container and no arm64 machine. fires. Needs no container and no arm64 machine.
- **`linux-arm64-build`** (`ubuntu-24.04-arm`) — the real build, then extracts - **`linux-arm64-build`** (`ubuntu-24.04-arm`) — the real build, then
the artifact and asserts the layout, that every bundled object resolves `scripts/linux-arm64/verify_appimage.sh` extracts the artifact and asserts
under AppRun's `LD_LIBRARY_PATH`, and that the glibc floor is still ≤ 2.31. the layout, that every bundled object resolves under AppRun's
Uploads the AppImage for 7 days. `LD_LIBRARY_PATH`, and that the glibc floor is still ≤ 2.31. Uploads the
AppImage for 7 days.
- **release** — `linux-arm64` runs on `ubuntu-24.04-arm`, reuses the shared - **release** — `linux-arm64` runs on `ubuntu-24.04-arm`, reuses the shared
`game.love` from the `love-payload` job, and the AppImage is staged and `game.love` from the `love-payload` job, runs the same
published like every other release asset. `verify_appimage.sh` checks on the shipped image, and the AppImage is
staged and published like every other release asset.
Unlike the Switch job, none of this needs secrets or self-hosted hardware, so Unlike the Switch job, none of this needs secrets or self-hosted hardware, so
it runs on fork PRs too. it runs on fork PRs too.
+38 -21
View File
@@ -6,6 +6,7 @@
# #
# Usage: scripts/build.sh [mac|win|linux|android|ios|all] [--version X.Y.Z] [--identity "Developer ID Application: ..."] # Usage: scripts/build.sh [mac|win|linux|android|ios|all] [--version X.Y.Z] [--identity "Developer ID Application: ..."]
# [--notary-profile NAME] [--no-notarize] # [--notary-profile NAME] [--no-notarize]
# [--game-love PATH] # fuse a prebuilt payload (scripts/pack_love.sh) instead of packing one
# [--release] # ios only: release config instead of debug # [--release] # ios only: release config instead of debug
# #
# Output: dist/mac/gen1recomp-macos.zip # Output: dist/mac/gen1recomp-macos.zip
@@ -36,6 +37,7 @@ NOTARY_PROFILE="notary-profile"
NOTARIZE=true NOTARIZE=true
IOS_RELEASE=false IOS_RELEASE=false
IOS_IPA=false IOS_IPA=false
GAME_LOVE_IN=""
say() { printf '\033[1;32m==>\033[0m %s\n' "$*"; } say() { printf '\033[1;32m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33mwarn:\033[0m %s\n' "$*" >&2; } warn() { printf '\033[1;33mwarn:\033[0m %s\n' "$*" >&2; }
@@ -48,6 +50,7 @@ while [ $# -gt 0 ]; do
--identity) IDENTITY="$2"; shift ;; --identity) IDENTITY="$2"; shift ;;
--notary-profile) NOTARY_PROFILE="$2"; shift ;; --notary-profile) NOTARY_PROFILE="$2"; shift ;;
--no-notarize) NOTARIZE=false ;; --no-notarize) NOTARIZE=false ;;
--game-love) GAME_LOVE_IN="${2:?--game-love needs a path}"; shift ;;
--release) IOS_RELEASE=true ;; --release) IOS_RELEASE=true ;;
--ipa) IOS_IPA=true ;; --ipa) IOS_IPA=true ;;
*) fail "unknown argument: $1" ;; *) fail "unknown argument: $1" ;;
@@ -62,17 +65,23 @@ mkdir -p "$CACHE" "$WORK" "$DIST/mac" "$DIST/win" "$DIST/linux"
# launcher's Edit button on a save row opens it in-process (main.lua), and # launcher's Edit button on a save row opens it in-process (main.lua), and
# `--editor` / POKEPORT_EDITOR=1 opens it standalone. It is required through # `--editor` / POKEPORT_EDITOR=1 opens it standalone. It is required through
# love.filesystem's require path, so it has to live inside the archive. # love.filesystem's require path, so it has to live inside the archive.
say "packing game.love"
LOVE_FILE="$WORK/game.love" LOVE_FILE="$WORK/game.love"
rm -f "$LOVE_FILE" rm -f "$LOVE_FILE"
# The launcher UI kit lives at src/ui/kit (inside src/, packed wholesale); if [ -n "$GAME_LOVE_IN" ]; then
# the vendored libs/flexlove tree it replaced is gone. [ -f "$GAME_LOVE_IN" ] || fail "--game-love: no such file: $GAME_LOVE_IN"
(cd "$ROOT" && zip -q -9 -r "$LOVE_FILE" \ say "using prebuilt payload: $GAME_LOVE_IN"
main.lua conf.lua src data assets tools/save-editor \ cp "$GAME_LOVE_IN" "$LOVE_FILE"
tools/rom_manifest.json tools/rom_manifest_blue.json \ else
tools/rom_manifest_yellow.json tools/rom_manifest_gold.json \ say "packing game.love"
tools/rom_manifest_silver.json \ # The launcher UI kit lives at src/ui/kit (inside src/, packed wholesale);
-x '*.DS_Store' 'data/generated/*' 'assets/generated/*') # the vendored libs/flexlove tree it replaced is gone.
(cd "$ROOT" && zip -q -9 -r "$LOVE_FILE" \
main.lua conf.lua src data assets tools/save-editor \
tools/rom_manifest.json tools/rom_manifest_blue.json \
tools/rom_manifest_yellow.json tools/rom_manifest_gold.json \
tools/rom_manifest_silver.json \
-x '*.DS_Store' 'data/generated/*' 'assets/generated/*')
fi
# Materialize the listing once and grep the file: piping unzip straight into # Materialize the listing once and grep the file: piping unzip straight into
# grep -q under `set -o pipefail` SIGPIPEs unzip when grep exits early on a # grep -q under `set -o pipefail` SIGPIPEs unzip when grep exits early on a
# match, and the pipeline's failure reads as "missing <file>" for whichever # match, and the pipeline's failure reads as "missing <file>" for whichever
@@ -107,18 +116,26 @@ say "game.love: $(du -h "$LOVE_FILE" | cut -f1)"
# mistaken for a release. The stamp is then read back out of the archive and the # mistaken for a release. The stamp is then read back out of the archive and the
# build fails if it did not take. # build fails if it did not take.
if printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then if printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
say "stamping engine version $VERSION into game.love" if [ -n "$GAME_LOVE_IN" ]; then
stamp_dir="$WORK/stamp" version_re="$(printf '%s' "$VERSION" | sed 's/\./\\./g')"
rm -rf "$stamp_dir" unzip -p "$LOVE_FILE" src/core/Version.lua \
mkdir -p "$stamp_dir/src/core" | grep -Eq "engine[[:space:]]*=[[:space:]]*\"$version_re\"" \
sed -E "s/(engine[[:space:]]*=[[:space:]]*\")[^\"]*(\")/\1$VERSION\2/" \ || fail "prebuilt payload does not report engine $VERSION (pack it with pack_love.sh --version $VERSION)"
"$ROOT/src/core/Version.lua" > "$stamp_dir/src/core/Version.lua" say "prebuilt payload already stamped: $VERSION"
(cd "$stamp_dir" && zip -q "$LOVE_FILE" src/core/Version.lua) else
version_re="$(printf '%s' "$VERSION" | sed 's/\./\\./g')" say "stamping engine version $VERSION into game.love"
unzip -p "$LOVE_FILE" src/core/Version.lua \ stamp_dir="$WORK/stamp"
| grep -Eq "engine[[:space:]]*=[[:space:]]*\"$version_re\"" \ rm -rf "$stamp_dir"
|| fail "version stamp failed: game.love does not report engine $VERSION" mkdir -p "$stamp_dir/src/core"
say "stamped engine version: $VERSION" sed -E "s/(engine[[:space:]]*=[[:space:]]*\")[^\"]*(\")/\1$VERSION\2/" \
"$ROOT/src/core/Version.lua" > "$stamp_dir/src/core/Version.lua"
(cd "$stamp_dir" && zip -q "$LOVE_FILE" src/core/Version.lua)
version_re="$(printf '%s' "$VERSION" | sed 's/\./\\./g')"
unzip -p "$LOVE_FILE" src/core/Version.lua \
| grep -Eq "engine[[:space:]]*=[[:space:]]*\"$version_re\"" \
|| fail "version stamp failed: game.love does not report engine $VERSION"
say "stamped engine version: $VERSION"
fi
else else
say "version '$VERSION' is not X.Y.Z, shipping default engine (no stamp)" say "version '$VERSION' is not X.Y.Z, shipping default engine (no stamp)"
fi fi
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Verifies a built arm64 AppImage is self-contained and bullseye-compatible.
# Usage: scripts/linux-arm64/verify_appimage.sh <AppImage>
set -euo pipefail
image="${1:?usage: verify_appimage.sh <AppImage>}"
[ -f "$image" ] || { echo "::error::no such AppImage: $image"; exit 1; }
image="$(cd "$(dirname "$image")" && pwd)/$(basename "$image")"
workdir="$(mktemp -d)"
trap 'rm -rf "$workdir"' EXIT
cd "$workdir"
# --appimage-extract needs no FUSE, so this works on a runner
# without /dev/fuse and still exercises the real payload.
"$image" --appimage-extract >/dev/null
for required in AppRun bin/love game.love lib/liblove-11.5.so; do
[ -e "squashfs-root/$required" ] \
|| { echo "::error::AppImage is missing $required"; exit 1; }
done
# Every bundled object must resolve once AppRun's LD_LIBRARY_PATH is
# applied; an unresolved soname here is a user-visible launch crash.
#
# This runs on a HEADLESS runner on purpose, and that is the point.
# The first version of this build bundled Debian's SDL2, which
# hard-links libpulse/libasound/libX11/libwayland, so it only ever
# started on a full desktop -- a bare runner is what exposed it.
missing="$(LD_LIBRARY_PATH="$PWD/squashfs-root/lib" \
ldd squashfs-root/bin/love squashfs-root/lib/*.so* 2>/dev/null \
| grep 'not found' || true)"
[ -z "$missing" ] || { echo "::error::unresolved deps:"; echo "$missing"; exit 1; }
# Nothing may hard-link a driver, session or audio-stack library:
# those must be reached through dlopen so the AppImage runs on a box
# with only ALSA, only Wayland, or only KMSDRM.
linked="$(for f in squashfs-root/bin/love squashfs-root/lib/*.so*; do
objdump -p "$f" 2>/dev/null | awk '/NEEDED/{print $2}'
done | sort -u | grep -E '^lib(pulse|asound|X11|wayland|GL|EGL|drm|gbm|xcb|cairo|sndio|dbus)' || true)"
[ -z "$linked" ] \
|| { echo "::error::these must be dlopened, not linked:"; echo "$linked"; exit 1; }
# The whole point of compiling on bullseye. If a future change moves
# the builder to a newer base, the glibc floor silently rises and
# every user on an older distro gets "GLIBC_2.xx not found" -- catch
# it here instead of in a release.
floor="$(objdump -T squashfs-root/bin/love squashfs-root/lib/*.so* 2>/dev/null \
| grep -o 'GLIBC_[0-9.]*' | sort -V | tail -1)"
echo "highest required glibc symbol version: $floor"
[ -n "$floor" ] \
|| { echo "::error::found no versioned glibc symbols -- objdump read nothing"; exit 1; }
highest="$(printf '%s\n' "$floor" "GLIBC_2.31" | sort -V | tail -1)"
[ "$highest" = "GLIBC_2.31" ] \
|| { echo "::error::AppImage requires $floor, above the bullseye 2.31 floor"; exit 1; }
echo "AppImage verified: $image"