magicciv/.forgejo/workflows/ci.yml

166 lines
7.9 KiB
YAML
Raw Normal View History

# Magic Civilization — CI gate on push-to-main.
#
# Implements objective p2-10 (`.project/objectives/p2-10-regression-ci-gate.md`):
# every push to `main` must pass the full regression suite on the self-hosted
# apricot runner before the commit is considered "green" on the Forgejo commit page.
#
# Runner registration instructions: `.forgejo/RUNNER_SETUP.md`.
# Forge: http://10.0.0.11:3000/magicciv/magicciv (Forgejo Actions, drone-compatible).
#
# Budget: median completion <= 15 min. If a stage consistently blows this,
# demote it to a nightly workflow (see RUNNER_SETUP.md § "Budget overruns").
name: ci
on:
push:
branches:
- main
# Allow a user to kick the gate manually from the Forgejo Actions UI or
# via `POST /api/v1/repos/magicciv/magicciv/actions/workflows/ci.yml/dispatches`.
# Useful for a first-run smoke on a freshly-registered runner without
# needing a sacrificial commit.
workflow_dispatch:
# One CI run per commit on main; a later push supersedes an in-flight run.
concurrency:
group: ci-main
cancel-in-progress: true
env:
# Keeps cargo/rustup/pip caches inside the repo workspace so the runner
# can reuse them across runs without polluting $HOME on apricot.
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
regression:
name: regression gate
# Self-hosted apricot runner — see .forgejo/RUNNER_SETUP.md for registration.
# Labels must match forgejo-runner config on apricot exactly.
runs-on: [self-hosted, linux, apricot]
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
# ── Stage 1: Rust workspace tests ────────────────────────────────
# Covers all mc-* crates + api-wasm + api-gdext surface tests.
# GPU-feature tests are attempted but don't fail the build if the
# gpu feature fails to compile on this runner (CUDA/Vulkan env may
# not be present); a clean "skipped" message is preferable to a
# misleading red pipeline on an environment gap.
- name: cargo test --workspace
working-directory: src/simulator
run: cargo test --workspace --locked
- name: cargo test --workspace --features gpu (best-effort)
working-directory: src/simulator
# `cargo check` gates the attempt — if gpu deps won't even compile
# on this runner, skip the full test run loudly but don't fail.
run: |
set -uo pipefail
if cargo check --workspace --features gpu 2>/dev/null; then
echo "gpu feature compiles; running gpu tests"
cargo test --workspace --features gpu --locked
else
echo "::warning::gpu feature failed to compile on this runner — skipping gpu tests"
fi
# ── Stage 2: GDScript lint (advisory) ────────────────────────────
# gdtoolkit must be installed on the runner; see RUNNER_SETUP.md.
#
# Hard-gated 2026-04-25 (p2-10a). Backlog cleared via file-splitting:
# unit_renderer_draw.gd, city_rust_bridge.gd, game_state_serialization_helpers.gd,
# ai_turn_bridge_dispatch.gd, ai_turn_bridge_state.gd, turn_processor_helpers.gd,
# turn_processor_city_helpers.gd extracted; stale src/generation/auto_play.gd
# deleted (live autoload at scenes/tests/auto_play.gd was unaffected);
# StatsTracker.CATEGORY_LABELS renamed to category_labels per
# class-variable-name rule. `gdlint src/game/engine/src/` exits 0 on main HEAD.
- name: gdlint
run: gdlint src/game/engine/src/
# ── Stage 3: Game data JSON validator ────────────────────────────
- name: validate game data
run: python3 tools/validate-game-data.py
# ── Stage 4: Objectives dashboard sync check ─────────────────────
# Fails if the dashboard in .project/objectives/ drifts from the
# frontmatter in individual objective files.
- name: objectives report --check
run: python3 tools/objectives-report.py --check
# ── Stage 4.5: Build GDExtension ─────────────────────────────────
# Godot's GDScript compile depends on `GdClimatePhysics`, `GdGridState`,
# `GdTechWeb` etc. which come from the Rust GDExtension. The .so is
# gitignored (built per-host), so a fresh `actions/checkout@v4`
# workspace has no binary — Godot's parser errors before any test runs.
# We build release-profile `magic-civ-physics-gdext` into
# `src/game/engine/addons/magic_civ_physics/libmagic_civ_physics.x86_64.so`
# which is what `magic_civ_physics.gdextension` points at.
- name: build GDExtension
working-directory: src/simulator
run: bash build-gdext.sh
# ── Stage 4.75: Godot import — build class cache ─────────────────
# Fresh checkouts have no `.godot/global_script_class_cache.cfg`;
# without it Godot's parser cannot resolve GDExtension type names
# (e.g. `GdClimatePhysics`), so even well-formed GDScript fails to
# compile. `--headless --import` walks the project once to populate
# the cache.
- name: Godot import (build class cache)
run: |
flatpak run --filesystem=home org.godotengine.Godot \
--path src/game --headless --import
# ── Stage 5: Headless GUT ────────────────────────────────────────
# Runs via Flatpak Godot on apricot. `tools/gut-headless.sh` wraps
# the Flatpak invocation and parses JUnit XML exit code (Flatpak
# swallows Godot's quit() exit code).
#
# Hard-gated 2026-04-26 (p2-10b). Original backlog: 40 failures out
# of 439 tests. All 40 triaged: 32 fixed (stale assertions, wrong
# API names, type mismatches, missing production_cost fixture field,
# is_flying() keyword check, diplomacy null-partner guard) and 8
# spun out as pending with objective refs (p2-10c thru p2-10j).
# `gut-headless.sh` exits 0 only when JUnit XML reports 0 failures.
- name: headless GUT
run: bash tools/gut-headless.sh
# ── Stage 6: 1-seed T100 smoke batch (advisory) ──────────────────
# Minimum-viable determinism + no-stall check. Confirms the commit
# doesn't deadlock or crash the autoplay loop. PARALLEL=1 to keep
# runtime predictable within the 15-minute budget.
#
# Currently advisory — autoplay-batch + flatpak sandbox plumbing
# has remaining rough edges on fresh CI checkouts (meta.json /
# turn_stats.jsonl not always landing even when the game completes).
# The `cargo test` stage already covers the deeper simulation
# determinism; this stage is the end-to-end dual-language smoke.
# Flip to hard-fail when the last sandbox path bug is fixed.
- name: autoplay smoke (seed 1, 100 turns) (advisory)
continue-on-error: true
env:
PARALLEL: "1"
run: |
set -uo pipefail
# Absolute path keeps flatpak's sandbox happy across checkout
# layouts — relative paths break when the runner workdir is not
# the CWD the sandbox resolves against.
out_dir="$GITHUB_WORKSPACE/.local/iter/ci_smoke_${GITHUB_SHA:-unknown}"
mkdir -p "$out_dir"
bash tools/autoplay-batch.sh 1 100 "$out_dir"
# Surface the smoke batch's per-turn stats + events on failure so a
# red pipeline is debuggable without having to re-run locally.
- name: Upload smoke artifacts
if: failure()
uses: actions/upload-artifact@v3
with:
name: ci-smoke-${{ github.sha }}
path: .local/iter/ci_smoke_*/**
retention-days: 7