Files
gen1recomp/.github/workflows/ci.yml
T
dependabot[bot] 41e326f376 build(deps): bump actions/upload-artifact from 4 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-01 23:57:09 +00:00

243 lines
9.5 KiB
YAML

name: ci
# The ROM-free test run (21-testing-and-ci §CI).
#
# CI has no ROM and never will: data/generated/ is produced by a SHA-1
# verified import of a cartridge dump, and no ROM bytes are ever committed.
# That is why the suite is tiered -- T1 (primitives), T2 (engine invariants)
# and T4 (mod SDK) run against the committed tests/fixture_data dataset, so
# they need no ROM, no display and no assets beyond what is in the repo.
# The T3 content tier asserts Pokemon Red facts; scripts/test.sh detects
# data/generated/ is absent and skips it rather than failing.
#
on:
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]
pull_request:
# a force-push while CI is mid-run should cancel the stale run, not queue
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
ios-changes:
name: detect iOS changes
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.paths.outputs.changed }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- id: paths
env:
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
HEAD_SHA: ${{ github.sha }}
run: |
if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -Eq '^(mobile/ios/|scripts/build_ios\.sh$|\.github/workflows/(ci|release)\.yml$)'; then
echo "changed=true" >> "$GITHUB_OUTPUT"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
fi
ios-build:
name: iOS build
needs: ios-changes
if: needs.ios-changes.outputs.changed == 'true'
runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }}
outputs:
ipa_url: ${{ steps.upload-ipa.outputs.artifact-url }}
steps:
- uses: actions/checkout@v4
- name: import signing certificate
if: github.repository == 'bryanthaboi/gen1recomp'
run: |
keychain_path="$RUNNER_TEMP/gen1recomp-ci-signing.keychain-db"
ci_dir="${POKEMON_CI_DIR:-$HOME/.config/pokemon-ci}"
p12="$ci_dir/signing.p12"
passfile="$ci_dir/signing.pass"
[ -f "$p12" ] && [ -f "$passfile" ] || exit 1
p12pw="$(cat "$passfile")"
kcpw="$(openssl rand -base64 24)"
echo "::add-mask::$kcpw"
security delete-keychain "$keychain_path" 2>/dev/null || true
security create-keychain -p "$kcpw" "$keychain_path"
security set-keychain-settings "$keychain_path"
security unlock-keychain -p "$kcpw" "$keychain_path"
security import "$p12" -P "$p12pw" -k "$keychain_path" -T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$kcpw" "$keychain_path" >/dev/null
existing="$(security list-keychains -d user | sed -e 's/^[[:space:]]*//' -e 's/"//g')"
security list-keychains -d user -s "$keychain_path" $existing
- name: install xcbeautify
run: brew list xcbeautify >/dev/null 2>&1 || brew install xcbeautify
- name: build iOS release
env:
CANONICAL_REPOSITORY: ${{ github.repository == 'bryanthaboi/gen1recomp' }}
run: |
if [ "$CANONICAL_REPOSITORY" = true ]; then
scripts/build_ios.sh --fetch --device --release
else
scripts/build_ios.sh --fetch --release
fi
- name: upload iOS release artifact
id: upload-ipa
if: github.repository == 'bryanthaboi/gen1recomp'
uses: actions/upload-artifact@v7
with:
name: gen1recomp-ios-ipa
path: dist/ios/gen1recomp.ipa
if-no-files-found: error
retention-days: 7
- name: clean up signing keychain
if: ${{ always() && github.repository == 'bryanthaboi/gen1recomp' }}
run: security delete-keychain "$RUNNER_TEMP/gen1recomp-ci-signing.keychain-db" 2>/dev/null || true
headless:
name: headless suites (no ROM)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# LuaJIT, not lua5.4: LOVE 11.x embeds LuaJIT 2.1 and the engine is
# written to Lua 5.1 semantics, so CI must run the interpreter the
# game actually ships with or it would green-light 5.4-only syntax.
- name: install luajit
run: sudo apt-get update && sudo apt-get install -y luajit
- name: interpreter version
run: luajit -v
- name: run every ROM-free tier
run: ./scripts/test.sh
fixture-dataset:
name: fixture dataset integrity
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt-get update && sudo apt-get install -y luajit
- run: python3 -m pip install --upgrade pillow
# the fixture PNGs are committed (they are 8x8 placeholders, not
# ROM-derived); assert they are still readable 4-shade images rather
# than regenerating them, so a corrupted commit is caught
- name: fixture assets are valid PNGs
run: |
python3 - <<'PY'
import glob, sys
from PIL import Image
paths = sorted(glob.glob("tests/fixture_data/assets/*.png"))
if not paths:
sys.exit("no fixture assets found")
for path in paths:
with Image.open(path) as image:
image.load()
print(f"ok {path} {image.size} {image.mode}")
print(f"\n{len(paths)} fixture assets valid")
PY
# the fingerprint golden is the parity tripwire; prove it still
# matches the dataset on a clean checkout
- 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
# capture half of the golden pipeline does not exist: a POKEPORT_DRIVER
# chunk runs after main.lua has already booted the game, and
# src/core/Data.lua has no POKEPORT_DATA_DIR branch, so no LOVE process
# can be pointed at tests/fixture_data. There is deliberately no step
# here that runs scripts/test.sh with WITH_SHOTS: it would have nothing
# to capture and nothing to diff, and a job that green-lights on skipped
# work is worse than an absent one.
shot-differ:
name: screenshot differ (capture not yet wired)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: python3 -m pip install --upgrade pillow
# 21-testing-and-ci §"Testing & acceptance criteria": compare_shots
# flags a deliberately corrupted golden and passes the clean one.
# That is the half of the pipeline this repo can actually prove.
- name: compare_shots self-test
run: |
set -e
python3 - <<'PY'
import os
from PIL import Image
os.makedirs("/tmp/g", exist_ok=True)
os.makedirs("/tmp/s", exist_ok=True)
base = Image.new("RGB", (160, 144), (255, 255, 255))
for x in range(0, 160, 8):
for y in range(0, 144, 8):
base.putpixel((x, y), (0, 0, 0))
base.save("/tmp/g/clean.png")
base.save("/tmp/s/clean.png")
base.save("/tmp/g/broken.png")
bad = base.copy()
for x in range(40, 60):
for y in range(40, 60):
bad.putpixel((x, y), (255, 0, 0))
bad.save("/tmp/s/broken.png")
PY
if python3 tools/compare_shots.py /tmp/g /tmp/s; then
echo "compare_shots passed a corrupted golden -- differ is broken"
exit 1
fi
rm /tmp/g/broken.png /tmp/s/broken.png
python3 tools/compare_shots.py /tmp/g /tmp/s
# an empty golden directory must not read as success
- name: differ refuses to pass vacuously
run: |
set -e
mkdir -p /tmp/empty-goldens /tmp/empty-shots
if python3 tools/compare_shots.py /tmp/empty-goldens /tmp/empty-shots; then
echo "compare_shots passed with no goldens"
exit 1
fi
lint:
name: mod lint (no ROM-derived content)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# the MK305 dump check key-diffs shipped tables through luajit, and
# modkit treats a missing interpreter as a fatal MK100 -- without
# this install the gate would fail instead of failing open
- run: sudo apt-get update && sudo apt-get install -y luajit
- run: python3 -m pip install --upgrade pillow
# constraint 1, enforced automatically: a committed mod that ships
# ROM-derived bytes fails the build. `lint` is the no-ROM-content
# check; `validate` is deliberately not run here because it resolves
# a mod against the fixture dataset, and a Red-content mod such as
# example_mew_starter legitimately does not resolve against it.
- name: lint every committed mod
run: |
set -e
found=0
for mod in mods/*/; do
[ -f "$mod/manifest.json" ] || continue
found=1
echo "== $mod"
python3 tools/modkit.py lint "${mod%/}"
done
if [ "$found" = "0" ]; then
echo "no committed mods to lint"
fi