Fix duplicate-key crash on BBCode IsBold font resolution (#3530) (#3531) #3629
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Test Runtimes, Unit Tests, Generated Code Projects | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [master, main] | |
| workflow_dispatch: | |
| jobs: | |
| # The Gum tool (Gum.sln) builds with neither submodules nor .NET mobile | |
| # workloads: it contains no FNA/Sokol projects and no mobile-TFM projects | |
| # (MonoGameGum/RaylibGum are not in it; the in-solution KniGum defaults to | |
| # net8.0). Isolating the tool build + its WPF unit tests onto their own | |
| # Windows runner lets this lane skip the ~5 min submodule-init + workload- | |
| # restore setup tax that the runtime lane (Build-and-Test) pays, shrinking | |
| # overall PR wall-clock for free (standard runners cost nothing on a public | |
| # repo). This lane installs no workloads on purpose; if it ever fails on a | |
| # missing workload, the error names exactly which one to add here. (#3349) | |
| # | |
| # Skipped on push to main: the tool was already validated in the PR, and unlike | |
| # Build-and-Test this lane seeds no cache, so there is nothing to do on push. | |
| Build-Tool: | |
| if: github.event_name != 'push' | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| - name: Setup .NET SDKs | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: 9.0.x | |
| - name: Build Gum (tool) | |
| run: | | |
| dotnet restore "Gum.sln" --verbosity quiet | |
| dotnet build "Gum.sln" --configuration Release --no-restore --verbosity quiet -p:WarningLevel=0 | |
| - name: GumToolUnitTests | |
| run: dotnet test "Tool/Tests/GumToolUnitTests" --configuration Release --no-build --logger "trx" --results-directory "TestResults" | |
| - name: Publish Test Results | |
| if: always() | |
| uses: dorny/test-reporter@v2 | |
| with: | |
| name: Tests Tool (Windows) | |
| path: "TestResults/*.trx" | |
| reporter: dotnet-trx | |
| fail-on-error: true | |
| Build-and-Test: | |
| strategy: | |
| matrix: | |
| # We used to have ubuntu-latest here, but it can't build iOS, and it's easier to just use macos | |
| os: [windows-latest, macos-15] | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| # actions/checkout's `submodules: recursive` does shallow (--depth=1) | |
| # submodule fetches. Sokol.NET's box2d submodule pins a SHA that lives | |
| # on the `sokol-dev` branch and is not reachable from box2d's default | |
| # branch (`main`), so a shallow fetch misses it. git then tries a | |
| # direct-SHA fetch, falls back to credentialed cloning, and dies with | |
| # "could not read Username" because GIT_TERMINAL_PROMPT=0 on runners. | |
| # Doing `git submodule update --init --recursive` ourselves does full | |
| # (non-shallow) clones, so the pinned SHA is always reachable. | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| # Cache the submodule object stores (.git/modules) + working trees, keyed on | |
| # the pinned submodule SHAs, so a hit turns the recursive non-shallow init | |
| # below into a fast local verify instead of re-cloning FNA's nested native | |
| # submodules (SDL/FAudio/FNA3D/Theorafile) and Sokol.NET/box2d every run. | |
| # Keyed on the SHAs (not the workflow-file hash) so it re-caches only when a | |
| # submodule pointer actually moves. Save on main only, mirroring the workload | |
| # cache: a PR-scoped cache is not reused across PRs, and the 10 GB repo cache | |
| # budget is dominated by the workload packs — limiting submodule caches to one | |
| # per OS on main avoids evicting those. (#3349 item 2) | |
| - name: Compute submodule cache key | |
| id: submodule-key | |
| shell: bash | |
| run: echo "shas=$(git submodule status | cut -c2-41 | tr -d '\n')" >> "$GITHUB_OUTPUT" | |
| - name: Cache submodules (main only) | |
| id: cache-submodules | |
| if: github.ref == 'refs/heads/main' | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| .git/modules | |
| fna | |
| Sokol.NET | |
| key: submodules-${{ runner.os }}-${{ steps.submodule-key.outputs.shas }} | |
| - name: Restore cached submodules (PRs) | |
| id: cache-submodules-restore | |
| if: github.ref != 'refs/heads/main' | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: | | |
| .git/modules | |
| fna | |
| Sokol.NET | |
| key: submodules-${{ runner.os }}-${{ steps.submodule-key.outputs.shas }} | |
| # --jobs parallelizes the (still non-shallow) fetch of independent submodules | |
| # on a cache miss; on a hit the objects are already local so this is fast. | |
| - name: Init submodules (non-shallow) | |
| run: git submodule update --init --recursive --jobs 4 | |
| - name: Setup .NET SDKs | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: 9.0.x | |
| # On main: full cache (restore + save). On PRs: restore only, no save. | |
| # PR branch caches are scoped to that branch and never reused, so saving | |
| # the 3.6 GB cache on PRs is wasted upload time. | |
| - name: Cache .NET workloads (main only) | |
| id: cache-workloads | |
| if: github.ref == 'refs/heads/main' | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| C:\Program Files\dotnet\metadata | |
| C:\Program Files\dotnet\packs | |
| C:\Program Files\dotnet\sdk-manifests | |
| /usr/local/share/dotnet/metadata | |
| /usr/local/share/dotnet/packs | |
| /usr/local/share/dotnet/sdk-manifests | |
| key: workloads-${{ runner.os }}-${{ hashFiles('.github/workflows/build-and-test.yaml') }} | |
| - name: Restore cached .NET workloads (PRs) | |
| id: cache-workloads-restore | |
| if: github.ref != 'refs/heads/main' | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: | | |
| C:\Program Files\dotnet\metadata | |
| C:\Program Files\dotnet\packs | |
| C:\Program Files\dotnet\sdk-manifests | |
| /usr/local/share/dotnet/metadata | |
| /usr/local/share/dotnet/packs | |
| /usr/local/share/dotnet/sdk-manifests | |
| key: workloads-${{ runner.os }}-${{ hashFiles('.github/workflows/build-and-test.yaml') }} | |
| - name: Install workloads | |
| if: steps.cache-workloads.outputs.cache-hit != 'true' && steps.cache-workloads-restore.outputs.cache-hit != 'true' | |
| run: dotnet workload install wasm-tools-net9 ios android maui | |
| # Workload caching IS used above and saves ~2 minutes per run. | |
| # I tried caching both NuGet packages and also cache on the actions/setup-dotnet@v4 | |
| # In both cases the caching slowed down the total execution time. | |
| # Actually on further testing, it does seem like the NuGet cache can speed up the Windows | |
| # build by around 20-30 seconds. However, the script for it is a little confusing and it's | |
| # not a huge savings. Let's keep it simple and not use NuGet caching. | |
| # On push to main, the only purpose is to seed the workload cache. | |
| # If the cache already exists, skip the entire build and test. | |
| # (The Gum tool is built + unit-tested in the separate Build-Tool job at | |
| # the top of this file, which needs neither submodules nor workloads. #3349) | |
| # IncludeAndroid=true forces KniGum's net9.0-android TFM to be included so CI | |
| # actually validates the #if ANDROID code compiles. KniGum uses opt-in for Android | |
| # (see KniGum.csproj); without this flag Android regressions would slip past CI and | |
| # only surface when packing the Gum.KNI nupkg. | |
| - name: Build AllLibraries | |
| if: github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true' | |
| run: | | |
| dotnet restore "AllLibraries.sln" -p:IncludeAndroid=true --verbosity quiet | |
| dotnet build "AllLibraries.sln" --configuration Release --no-restore --verbosity quiet -p:WarningLevel=0 -p:IncludeAndroid=true | |
| # Regenerate every harness project with GumCli and fail if the checked-in | |
| # generated code is stale. This forces codegen behavior changes to ship with | |
| # their regenerated output in the same PR, which the compile step below then | |
| # proves still builds. GumCli exit code 1 means individual elements were | |
| # skipped by the per-element error check — expected, several harness projects | |
| # contain deliberately-broken fixture elements (e.g. DemoScreenGum's deleted | |
| # component reference). Exit code 2+ means the project failed to load and | |
| # must fail the build. | |
| - name: Codegen Drift Check (Windows Only) | |
| if: (github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true') && runner.os == 'Windows' | |
| run: | | |
| dotnet build "Tools/Gum.Cli/Gum.Cli.csproj" --configuration Release --verbosity quiet -p:WarningLevel=0 | |
| $cli = "Tools/Gum.Cli/bin/Release/net8.0/GumCli.exe" | |
| $gumxProjects = @( | |
| "Tests/CodeGen_Maui_FullCodegen/Content/GumProject/CodeGenTestProject.gumx", | |
| "Tests/CodeGen_MonoGame_ByReference/Content/GumProject/CodeGenTestProject.gumx", | |
| "Tests/CodeGen_MonoGameForms_ByReference/Content/GumProject/CodeGenTestProject.gumx", | |
| "Tests/CodeGen_MonoGameForms_Localization_ByReference/Content/GumProject/LocalizationCodeGenTestProject.gumx", | |
| "Tests/CodeGen_MonoGameForms_FullCodegen/Content/CodeGenProject.gumx", | |
| "Tests/CodeGen_Raylib_ByReference/Content/GumProject/CodeGenTestProject.gumx" | |
| ) | |
| foreach ($gumxProject in $gumxProjects) { | |
| & $cli codegen $gumxProject | |
| if ($LASTEXITCODE -gt 1) { exit $LASTEXITCODE } | |
| } | |
| $drift = git status --porcelain -- "Tests/CodeGen_*" | |
| if ($drift) { | |
| Write-Host "::error::Checked-in generated code is stale. Run 'gumcli codegen' on each Tests/CodeGen_* project (or Tests/GenerateAllCodeGenProjects.bat) and commit the result." | |
| Write-Host ($drift -join "`n") | |
| git diff -- "Tests/CodeGen_*" | |
| exit 1 | |
| } | |
| - name: Build Generated Code Projects (Windows Only) | |
| if: (github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true') && runner.os == 'Windows' | |
| run: | | |
| dotnet restore "Tests/CodeGen_Maui_FullCodegen/CodeGen_Maui_FullCodegen.sln" --verbosity quiet | |
| dotnet build "Tests/CodeGen_Maui_FullCodegen/CodeGen_Maui_FullCodegen.sln" --configuration Debug --no-restore --verbosity quiet -p:WarningLevel=0 -p:TargetFrameworks=net9.0-windows10.0.19041.0 | |
| dotnet restore "Tests/CodeGen_MonoGame_ByReference/CodeGen_MonoGame_ByReference.sln" --verbosity quiet | |
| dotnet build "Tests/CodeGen_MonoGame_ByReference/CodeGen_MonoGame_ByReference.sln" --configuration Debug --no-restore --verbosity quiet -p:WarningLevel=0 | |
| dotnet restore "Tests/CodeGen_MonoGameForms_ByReference/CodeGenProject.sln" --verbosity quiet | |
| dotnet build "Tests/CodeGen_MonoGameForms_ByReference/CodeGenProject.sln" --configuration Debug --no-restore --verbosity quiet -p:WarningLevel=0 | |
| dotnet restore "Tests/CodeGen_MonoGameForms_Localization_ByReference/CodeGen_MonoGameForms_Localization_ByReference.csproj" --verbosity quiet | |
| dotnet build "Tests/CodeGen_MonoGameForms_Localization_ByReference/CodeGen_MonoGameForms_Localization_ByReference.csproj" --configuration Debug --no-restore --verbosity quiet -p:WarningLevel=0 | |
| dotnet restore "Tests/CodeGen_MonoGameForms_FullCodegen/CodeGen_MonoGameForms_FullCodegen.sln" --verbosity quiet | |
| dotnet build "Tests/CodeGen_MonoGameForms_FullCodegen/CodeGen_MonoGameForms_FullCodegen.sln" --configuration Debug --no-restore --verbosity quiet -p:WarningLevel=0 | |
| dotnet restore "Tests/CodeGen_Raylib_ByReference/CodeGen_Raylib_ByReference.sln" --verbosity quiet | |
| dotnet build "Tests/CodeGen_Raylib_ByReference/CodeGen_Raylib_ByReference.sln" --configuration Debug --no-restore --verbosity quiet -p:WarningLevel=0 | |
| # --------------------------------------------------------------------- | |
| # Test allow-list policy | |
| # | |
| # GitHub Actions runners have no GPU. We can only run test projects whose | |
| # tests do not instantiate a real GraphicsDevice / window / native renderer. | |
| # Auto-discovery (`dotnet test <solution>`) is tempting but unsafe here — | |
| # it would pull in projects whose tests open raylib windows, spin up | |
| # MonoGame integration harnesses, etc. So we maintain an explicit list. | |
| # | |
| # When you add a new test project, decide which bucket it belongs to and | |
| # update both this list and the comments below. | |
| # | |
| # Bucket A suites BLOCK the merge — a red test fails the job (no | |
| # continue-on-error). Keep them green. | |
| # | |
| # Bucket A — RUN IN CI (headless: pure data, IO, codegen, analyzers, or | |
| # MonoGame's headless TestGame harness): | |
| # - MonoGameGum.Tests (headless TestGame harness) | |
| # - SokolGum.Tests (Sokol; headless) | |
| # - GumToolUnitTests (WPF tool unit tests; Windows only — | |
| # now runs in the separate Build-Tool | |
| # job at the top of this file, #3349) | |
| # - Gum.Bundle.Tests (brotli/tar/format; pure IO) | |
| # - Gum.ProjectServices.Tests (headless project loading, codegen) | |
| # - Gum.Presentation.Tests (headless undo/redo + relocated tool | |
| # ViewModels; references Gum.Presentation | |
| # alone, no WPF/WinForms — ADR-0005) | |
| # - Gum.ProjectServices.SkiaGum.Tests (SkiaGum SVG export — renders into | |
| # SKSvgCanvas, CPU-only like SkiaGum.Tests) | |
| # - Gum.Analyzers.Tests (Roslyn analyzers; pure compilation) | |
| # - Gum.Cli.Tests (CLI subcommand smoke tests; fonts | |
| # tests self-gate by OS, no screenshot | |
| # tests exist yet — if one is added, | |
| # fence it with a Trait and exclude here) | |
| # - SkiaGum.Tests (renders into an in-memory CPU raster | |
| # SKSurface — no GPU / window / GL. It | |
| # was wrongly excluded as "needs a | |
| # window"; it is fully headless. #3233) | |
| # - RaylibGum.Tests (raylib runtime; Windows-only. Needs an | |
| # OpenGL 3.3 context the GPU-less runners | |
| # lack, supplied by Mesa llvmpipe software | |
| # GL dropped in by the "Install Mesa" step | |
| # right before it. #3250) | |
| # | |
| # Bucket B — DEFERRED, NEEDS INVESTIGATION (likely safe in part, but may | |
| # mix headless and graphics-using tests in the same project; would need | |
| # xUnit traits or filter-by-class to split): | |
| # - MonoGameGum.Tests.V2 (V1 is headless; V2 likely is too — | |
| # verify before enabling) | |
| # - MonoGameGum.Tests.V3 (same — verify before enabling) | |
| # | |
| # Bucket C — (empty) RaylibGum.Tests used to live here as "cannot run in CI | |
| # yet". It graduated to Bucket A in #3250: the macOS probe hung at GLFW/Cocoa | |
| # window creation, but the Windows job supplies software GL via Mesa llvmpipe | |
| # (Win32 window creation is not main-thread-coupled like Cocoa), so it now | |
| # runs headless and blocks. See the Install Mesa + RaylibGum.Tests steps below. | |
| # | |
| # Bucket D — WILL NOT RUN IN CI (require a real graphics device / window; | |
| # do NOT add these even if they look tempting): | |
| # - MonoGameGum.IntegrationTests (integration → real GraphicsDevice) | |
| # - MonoGameGum.Shapes.Tests (shape rendering → GraphicsDevice) | |
| # - MonoGameFum.FromFile.Tests (instantiates real visuals) | |
| # --------------------------------------------------------------------- | |
| # continue-on-error was removed from the Bucket-A suites below (#3233): a red | |
| # test now fails the job and blocks the merge. Note the trade-off — the first | |
| # failing suite short-circuits the suites listed after it; the "Publish Test | |
| # Results" step runs with always() so whatever trx were produced are still | |
| # reported. | |
| - name: MonoGameGum.Tests | |
| if: github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true' | |
| run: dotnet test "MonoGameGum.Tests" --configuration Release --no-build --logger "trx" --results-directory "TestResults" | |
| - name: SokolGum.Tests | |
| if: github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true' | |
| run: dotnet test "Tests/SokolGum.Tests" --configuration Release --no-build --logger "trx" --results-directory "TestResults" | |
| - name: Gum.Bundle.Tests | |
| if: github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true' | |
| run: dotnet test "Tests/Gum.Bundle.Tests" --configuration Release --no-build --logger "trx" --results-directory "TestResults" | |
| - name: Gum.ProjectServices.Tests | |
| if: github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true' | |
| run: dotnet test "Tests/Gum.ProjectServices.Tests" --configuration Release --no-build --logger "trx" --results-directory "TestResults" | |
| - name: Gum.Presentation.Tests | |
| if: github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true' | |
| run: dotnet test "Tests/Gum.Presentation.Tests" --configuration Release --no-build --logger "trx" --results-directory "TestResults" | |
| - name: Gum.Analyzers.Tests | |
| if: github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true' | |
| run: dotnet test "Tests/Gum.Analyzers.Tests" --configuration Release --no-build --logger "trx" --results-directory "TestResults" | |
| - name: Gum.Cli.Tests | |
| if: github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true' | |
| run: dotnet test "Tests/Gum.Cli.Tests" --configuration Release --no-build --logger "trx" --results-directory "TestResults" | |
| # SkiaGum.Tests renders into an in-memory CPU raster SKSurface (no GPU / window / | |
| # GL), so it runs headless on CI like the suites above and blocks on failure. It | |
| # was previously excluded by mistake — the old comment claimed it needed window | |
| # plumbing, which is not true of these tests. #3233 | |
| - name: SkiaGum.Tests | |
| if: github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true' | |
| run: dotnet test "Tests/SkiaGum.Tests" --configuration Release --no-build --logger "trx" --results-directory "TestResults" | |
| # SkiaGum-backed SVG export service (gumcli svg / tool File ▸ Export). Drives | |
| # SkiaGumSvgExportService end-to-end via SKSvgCanvas — CPU-only, fully headless | |
| # like SkiaGum.Tests above, so it blocks on failure too. #3259 | |
| - name: Gum.ProjectServices.SkiaGum.Tests | |
| if: github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true' | |
| run: dotnet test "Tests/Gum.ProjectServices.SkiaGum.Tests" --configuration Release --no-build --logger "trx" --results-directory "TestResults" | |
| # --- RaylibGum.Tests via Mesa llvmpipe software GL (Windows, #3250) --------- | |
| # raylib's InitWindow needs an OpenGL 3.3 context the GPU-less runners lack. | |
| # Mesa's llvmpipe is a software GL rasterizer; raylib's native lib imports | |
| # opengl32.dll by name, and Windows resolves that from the test host's own | |
| # directory BEFORE System32, so dropping Mesa's opengl32.dll (+ its gallium/ | |
| # glapi siblings) next to the built test binaries overrides the GL-less system | |
| # stub with a working software implementation. An earlier macOS probe (#3233) | |
| # HUNG at window creation (GLFW/Cocoa needs the main thread); Win32 window | |
| # creation is not main-thread-coupled, so it works headless here. | |
| # | |
| # This is a BLOCKING Bucket-A suite (promoted from an advisory probe in #3250 | |
| # after a clean 351/351 run). The install step feeds that blocking gate, so | |
| # its download is retried against transient GitHub-releases hiccups; the | |
| # timeout-minutes guard on the test step stays so a hang fails fast instead of | |
| # burning the job budget, and the trx now lands in the shared TestResults dir | |
| # so the "Publish Test Results" reporter gates on it like every other suite. | |
| - name: "Install Mesa llvmpipe (Windows, #3250)" | |
| if: (github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true') && runner.os == 'Windows' | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $ProgressPreference = "SilentlyContinue" # Invoke-WebRequest is far faster without progress rendering | |
| $ver = "26.1.2" # pinned pal1000/mesa-dist-win release | |
| $asset = "mesa3d-$ver-release-msvc.7z" | |
| $url = "https://github.com/pal1000/mesa-dist-win/releases/download/$ver/$asset" | |
| $archive = Join-Path $env:RUNNER_TEMP $asset | |
| $extract = Join-Path $env:RUNNER_TEMP "mesa" | |
| # Retry the download — this feeds a blocking gate, so a transient GitHub | |
| # releases hiccup must not red the whole job on the first attempt. | |
| for ($i = 1; $i -le 3; $i++) { | |
| try { Write-Host "Downloading $url (attempt $i)"; Invoke-WebRequest -Uri $url -OutFile $archive; break } | |
| catch { | |
| if ($i -eq 3) { throw } | |
| Write-Host "Download failed: $($_.Exception.Message). Retrying in $(5 * $i)s..." | |
| Start-Sleep -Seconds (5 * $i) | |
| } | |
| } | |
| 7z x $archive "-o$extract" -y -bso0 -bsp0 | |
| # Find the x64 GL runtime in the archive and copy that folder's DLLs next | |
| # to the RaylibGum.Tests binaries (the test host's exe directory). | |
| $opengl = Get-ChildItem -Path $extract -Recurse -Filter opengl32.dll | | |
| Where-Object { $_.FullName -match '\\x64\\' } | Select-Object -First 1 | |
| if (-not $opengl) { Write-Error "x64 opengl32.dll not found in Mesa archive"; exit 1 } | |
| $dest = "Tests/RaylibGum.Tests/bin/Release/net8.0" | |
| Copy-Item (Join-Path $opengl.Directory.FullName "*.dll") -Destination $dest -Force | |
| Write-Host "Mesa GL runtime copied to $dest" | |
| Get-ChildItem (Join-Path $dest "*.dll") | | |
| Where-Object { $_.Name -match "opengl32|gallium|glapi|dxil" } | | |
| Format-Table Name, Length | |
| - name: "RaylibGum.Tests (Windows, #3250)" | |
| if: (github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true') && runner.os == 'Windows' | |
| timeout-minutes: 10 | |
| env: | |
| GALLIUM_DRIVER: llvmpipe # force Mesa's software rasterizer (no GPU on the runners) | |
| run: dotnet test "Tests/RaylibGum.Tests" --configuration Release --no-build --logger "trx" --results-directory "TestResults" | |
| - name: Publish Test Results | |
| if: always() && (github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true') | |
| uses: dorny/test-reporter@v2 | |
| with: | |
| name: Tests ${{ matrix.os }} | |
| path: "TestResults/*.trx" | |
| reporter: dotnet-trx | |
| fail-on-error: true | |
| # End-to-end verification for issue #731: Gum looked for loose content relative to the executable, | |
| # which is wrong inside a macOS .app bundle (exe in Contents/MacOS/, content in Contents/Resources/). | |
| # The unit tests above cover the path-rebasing logic on every OS; this step is the only thing that | |
| # exercises the real bundle layout by publishing a tiny harness, assembling a genuine .app, and | |
| # running it from Contents/MacOS/. The harness exits non-zero if Gum fails to resolve the content, | |
| # so `set -e` turns that into a failed build. This is what removes the need to test #731 by hand. | |
| - name: macOS .app bundle content resolution (issue #731) | |
| if: runner.os == 'macOS' && (github.event_name != 'push' || steps.cache-workloads.outputs.cache-hit != 'true') | |
| run: | | |
| set -euo pipefail | |
| ARCH=$(uname -m) | |
| if [ "$ARCH" = "arm64" ]; then RID="osx-arm64"; else RID="osx-x64"; fi | |
| echo "Publishing #731 harness for $RID" | |
| dotnet publish Tests/MacOSBundle.Harness/MacOSBundle.Harness.csproj -c Release -r "$RID" --self-contained true -o "$RUNNER_TEMP/harness-publish" | |
| APP="$RUNNER_TEMP/MacOSBundleHarness.app" | |
| rm -rf "$APP" | |
| mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources/Content" | |
| # Executable + runtime live in Contents/MacOS/. | |
| cp -R "$RUNNER_TEMP/harness-publish/." "$APP/Contents/MacOS/" | |
| # Content ships ONLY in Contents/Resources/ — the layout that broke #731. The harness also | |
| # asserts the file is absent next to the executable, so the bundle rebase is genuinely tested. | |
| # The fix keys off the Contents/MacOS -> Contents/Resources path relationship via | |
| # AppContext.BaseDirectory, so no Info.plist / NSBundle wiring is required. | |
| printf 'gum-bundle-ok' > "$APP/Contents/Resources/Content/macos-bundle-test.txt" | |
| chmod +x "$APP/Contents/MacOS/MacOSBundle.Harness" | |
| echo "Running harness from inside the .app bundle (its exit code is the assertion)..." | |
| "$APP/Contents/MacOS/MacOSBundle.Harness" |