Files
EmilandClaude Sonnet 5 b238fb5b19 Fix CI: real failures found by the first actual workflow run, not guessed
Both jobs failed, at genuinely informative points:

- linux-x64: DllNotFoundException loading lingua_physics — the .so
  committed for scripts/run-sample.sh's local-dev convenience was built on
  this dev machine's own (newer) glibc, and didn't load on ubuntu-latest's
  runner. liblingua_audio.so, built the same way, loaded fine there — this
  wasn't a generic "the file isn't where expected" problem, it was
  specific to what that one binary happened to require. Fixed by having CI
  rebuild and overwrite the native libs fresh for both platforms, every
  run, rather than trusting the committed one for anything but casual
  local use — only a binary built on the actual target platform is
  trustworthy on it.

- win-x64: "WGL: The driver does not appear to support OpenGL" — from
  inside the --headless verification run. --headless only controls
  whether Engine.Host's own loop pumps a window; it does nothing to stop
  a *loaded* engine.windowing/engine.render from creating a real window
  and GL context regardless, which the full PhysicsDemo project always
  does. Fine on a real desktop, fatal on windows-latest's GPU-less
  runner. Fixed with a separate, minimal verification stage — engine.
  physics only, project.ci-headless.json/scene.ci-headless.json, no
  windowing/render/audio/game plugin at all — alongside the original full
  stage, which still produces the real uploaded artifact. engine.physics
  needs no display, so this is what CI can actually check without one;
  audio's own correctness is separately covered by Engine.Audio.Tests
  (forced onto miniaudio's null backend already) on both platforms.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N1qPfzq8TDCUMFMV3UwV5N
2026-09-02 21:02:42 +03:00

154 lines
6.9 KiB
YAML

name: Build
# M4's "done when": the build runs on both platforms with no editor
# plugins in the shipped binary. Neither half of that is verifiable from
# a single Linux dev machine — this workflow is what actually proves it,
# real builds on real ubuntu-latest and windows-latest runners, not just
# a cross-platform-looking csproj and hope.
on:
push:
branches: [master]
pull_request:
workflow_dispatch:
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
rid: linux-x64
- os: windows-latest
rid: win-x64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
- name: Build native physics shim (Box3D, pinned commit — see native/physics-native/CMakeLists.txt)
shell: bash
run: |
cmake -S native/physics-native -B native/physics-native/build -DCMAKE_BUILD_TYPE=Release
cmake --build native/physics-native/build --config Release
- name: Build native audio shim (miniaudio)
shell: bash
run: |
cmake -S native/audio-native -B native/audio-native/build -DCMAKE_BUILD_TYPE=Release
cmake --build native/audio-native/build --config Release
# win-x64's .dll can only ever be produced here — nothing else in
# this project can build or run one to check it. linux-x64's .so
# IS also committed (for scripts/run-sample.sh's convenience on a
# dev machine), but CI rebuilds and overwrites it fresh too, every
# run: a real failure found by this exact workflow, not a
# hypothetical one — the committed .so, built locally against a
# newer glibc than ubuntu-latest ships, loaded fine here and threw
# DllNotFoundException on the runner. Only a binary built on the
# actual target platform is trustworthy there.
- name: Place native libraries
shell: bash
run: |
mkdir -p "plugins/engine.physics/Engine.Physics/native/${{ matrix.rid }}"
mkdir -p "plugins/engine.audio/Engine.Audio/native/${{ matrix.rid }}"
find native/physics-native/build -iname "liblingua_physics.so" -o -iname "lingua_physics.dll" \
| xargs -I{} cp {} "plugins/engine.physics/Engine.Physics/native/${{ matrix.rid }}/"
find native/audio-native/build -iname "liblingua_audio.so" -o -iname "lingua_audio.dll" \
| xargs -I{} cp {} "plugins/engine.audio/Engine.Audio/native/${{ matrix.rid }}/"
ls "plugins/engine.physics/Engine.Physics/native/${{ matrix.rid }}"
ls "plugins/engine.audio/Engine.Audio/native/${{ matrix.rid }}"
- name: Build
run: dotnet build LinguaEngine.sln -c Release
# Nothing in the suite opens a real GL window or a real audio device
# (Engine.Audio.Tests forces miniaudio's null backend specifically so
# this works) — the whole thing runs headless on both runners.
- name: Test
run: dotnet test LinguaEngine.sln -c Release --no-build
- name: Stage the shippable build (engine.editor deliberately excluded)
shell: bash
run: |
set -euo pipefail
DIST="dist/${{ matrix.rid }}"
STAGE="$DIST/plugins"
mkdir -p "$STAGE"
for id in engine.windowing engine.assets engine.render engine.input engine.physics engine.audio; do
impl="$(awk -F. '{for(i=1;i<=NF;i++){$i=toupper(substr($i,1,1)) substr($i,2)}}1' OFS=. <<<"$id")"
src="plugins/$id/$impl/bin/Release/net9.0"
mkdir -p "$STAGE/$id"
cp -r "$src/." "$STAGE/$id/"
cp "plugins/$id/plugin.json" "$STAGE/$id/"
done
mkdir -p "$DIST/engine"
cp -r src/Engine.Host/bin/Release/net9.0/. "$DIST/engine/"
GAME_DST="$DIST/samples/PhysicsDemo/GamePlugins/physics-demo-game"
mkdir -p "$GAME_DST"
cp -r samples/PhysicsDemo/GamePlugins/physics-demo-game/bin/Release/net9.0/. "$GAME_DST/"
cp samples/PhysicsDemo/GamePlugins/physics-demo-game/plugin.json "$GAME_DST/"
cp -r samples/PhysicsDemo/assets "$DIST/samples/PhysicsDemo/assets"
cp samples/PhysicsDemo/project.json samples/PhysicsDemo/scene.json "$DIST/samples/PhysicsDemo/"
# A deliberately minimal, engine.windowing/engine.render-free stage,
# separate from the full one above: found the hard way that --headless
# only controls whether Engine.Host's own loop pumps a window — it
# does nothing to stop a *loaded* engine.windowing/engine.render from
# creating a real window and GL context regardless, which the full
# PhysicsDemo project always does. That's fine on a real desktop; on
# windows-latest's GPU-less runner it's "WGL: The driver does not
# appear to support OpenGL," failing before physics ever got to run.
# engine.physics needs no display at all, so this checks only that.
- name: Stage a minimal headless-safe build for verification
shell: bash
run: |
set -euo pipefail
VERIFY="verify/${{ matrix.rid }}"
mkdir -p "$VERIFY/plugins/engine.physics"
cp -r plugins/engine.physics/Engine.Physics/bin/Release/net9.0/. "$VERIFY/plugins/engine.physics/"
cp plugins/engine.physics/plugin.json "$VERIFY/plugins/engine.physics/"
mkdir -p "$VERIFY/engine"
cp -r src/Engine.Host/bin/Release/net9.0/. "$VERIFY/engine/"
mkdir -p "$VERIFY/samples/PhysicsDemo"
cp samples/PhysicsDemo/project.ci-headless.json "$VERIFY/samples/PhysicsDemo/project.json"
cp samples/PhysicsDemo/scene.ci-headless.json "$VERIFY/samples/PhysicsDemo/scene.json"
- name: Verify the shippable build actually runs (headless, real physics + real scene load, real P/Invoke)
shell: bash
run: |
set -euo pipefail
# Absolute paths only: Assembly.LoadFromAssemblyPath (which
# PluginHost.Load uses) throws on a relative one — found by
# hand running this exact staging locally before trusting it to
# a workflow run at all.
VERIFY="$(pwd)/verify/${{ matrix.rid }}"
cd "$VERIFY/samples/PhysicsDemo"
dotnet "$VERIFY/engine/Engine.Host.dll" run --headless \
--plugins "$VERIFY/plugins" --project project.json --scene scene.json \
--frames 200 --dump dump.json
cat dump.json
python3 -c "
import json
data = json.load(open('dump.json'))
box = next(o for o in data if o['name'] == 'DemoBox')
y = box['transform']['position'][1]
assert 0.9 < y < 1.1, f'DemoBox should have settled near y=1.0, got y={y}'
print(f'DemoBox settled at y={y:.4f} — physics ran for real on ${{ matrix.rid }}')
"
- uses: actions/upload-artifact@v4
with:
name: lingua-engine-${{ matrix.rid }}
path: dist/${{ matrix.rid }}