From b238fb5b1976aaf0f8f63e14801111f23000a33f Mon Sep 17 00:00:00 2001 From: Emil Date: Wed, 2 Sep 2026 21:02:42 +0300 Subject: [PATCH] Fix CI: real failures found by the first actual workflow run, not guessed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01N1qPfzq8TDCUMFMV3UwV5N --- .github/workflows/build.yml | 66 ++++++++++++++------ samples/PhysicsDemo/project.ci-headless.json | 7 +++ samples/PhysicsDemo/scene.ci-headless.json | 20 ++++++ 3 files changed, 75 insertions(+), 18 deletions(-) create mode 100644 samples/PhysicsDemo/project.ci-headless.json create mode 100644 samples/PhysicsDemo/scene.ci-headless.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9fbbfd0..464779a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 " diff --git a/samples/PhysicsDemo/project.ci-headless.json b/samples/PhysicsDemo/project.ci-headless.json new file mode 100644 index 0000000..6671d6d --- /dev/null +++ b/samples/PhysicsDemo/project.ci-headless.json @@ -0,0 +1,7 @@ +{ + "engineVersion": "^0.1", + "plugins": [ + { "id": "engine.physics" } + ], + "pluginPaths": [] +} diff --git a/samples/PhysicsDemo/scene.ci-headless.json b/samples/PhysicsDemo/scene.ci-headless.json new file mode 100644 index 0000000..606dfd2 --- /dev/null +++ b/samples/PhysicsDemo/scene.ci-headless.json @@ -0,0 +1,20 @@ +[ + { + "name": "Ground", + "transform": { "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "scale": [1, 1, 1] }, + "components": [ + { "type": "Engine.Physics.Contracts.Rigidbody, Engine.Physics.Contracts", "data": { "Type": 0, "Friction": 0.6, "Restitution": 0.3 } }, + { "type": "Engine.Physics.Contracts.BoxCollider, Engine.Physics.Contracts", "data": { "HalfExtents": { "X": 4, "Y": 0.5, "Z": 4 } } } + ], + "children": [] + }, + { + "name": "DemoBox", + "transform": { "position": [0, 3, 0], "rotation": [0, 0, 0, 1], "scale": [1, 1, 1] }, + "components": [ + { "type": "Engine.Physics.Contracts.Rigidbody, Engine.Physics.Contracts", "data": { "Type": 2, "Friction": 0.6, "Restitution": 0.3 } }, + { "type": "Engine.Physics.Contracts.BoxCollider, Engine.Physics.Contracts", "data": {} } + ], + "children": [] + } +]