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
This commit is contained in:
Emil
2026-09-02 21:02:42 +03:00
co-authored by Claude Sonnet 5
parent cd8f221ddd
commit b238fb5b19
3 changed files with 75 additions and 18 deletions
+48 -18
View File
@@ -42,21 +42,26 @@ jobs:
cmake -S native/audio-native -B native/audio-native/build -DCMAKE_BUILD_TYPE=Release
cmake --build native/audio-native/build --config Release
# linux-x64's .so is committed (built and verified on the dev
# machine); win-x64's .dll can only ever be produced here, since
# nothing else in this project can build or run one to check it —
# landing it at exactly the path Engine.Physics.csproj/Engine.Audio.
# csproj's win-x64 Content item expects.
- name: Place win-x64 native libraries
if: matrix.rid == 'win-x64'
# 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/win-x64
mkdir -p plugins/engine.audio/Engine.Audio/native/win-x64
find native/physics-native/build -iname "lingua_physics.dll" -exec cp {} plugins/engine.physics/Engine.Physics/native/win-x64/lingua_physics.dll \;
find native/audio-native/build -iname "lingua_audio.dll" -exec cp {} plugins/engine.audio/Engine.Audio/native/win-x64/lingua_audio.dll \;
test -f plugins/engine.physics/Engine.Physics/native/win-x64/lingua_physics.dll
test -f plugins/engine.audio/Engine.Audio/native/win-x64/lingua_audio.dll
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
@@ -94,7 +99,32 @@ jobs:
cp -r samples/PhysicsDemo/assets "$DIST/samples/PhysicsDemo/assets"
cp samples/PhysicsDemo/project.json samples/PhysicsDemo/scene.json "$DIST/samples/PhysicsDemo/"
- name: Verify the shippable build actually runs (headless, real physics + real scene load)
# 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
@@ -102,10 +132,10 @@ jobs:
# 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.
DIST="$(pwd)/dist/${{ matrix.rid }}"
cd "$DIST/samples/PhysicsDemo"
dotnet "$DIST/engine/Engine.Host.dll" run --headless \
--plugins "$DIST/plugins" --project project.json --scene scene.json \
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 "