From e2870a16848e6324f56e5d4cf97ffc7d83d41dd3 Mon Sep 17 00:00:00 2001 From: zignd Date: Thu, 22 Oct 2026 05:18:59 -0300 Subject: [PATCH 01/10] ci: run the Linux legs and the font comparison on the native x64 runner --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91322e6a..014bcbdc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: # Under TRACE_CI_RUNNERS=self-hosted (docs/self-hosted-runners.md in Trace Arena) Linux legs run on the local x64 runner and # macOS legs on the local macOS runner. The Linux steps that render through the Vulkan software renderer are skipped there: it # crashes under x64 emulation ("rosetta error: mmap_anonymous_rw mmap failed"), and the native arm64 runner lacks the freetype native library. - runs-on: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && (matrix.os == 'macos-latest' && fromJSON('["self-hosted","macOS","ARM64"]') || fromJSON('["self-hosted","Linux","X64","yiazmat-linux-x64-emulated"]')) || matrix.os }} + runs-on: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && (matrix.os == 'macos-latest' && fromJSON('["self-hosted","macOS","ARM64"]') || fromJSON('["self-hosted","Linux","X64","yiazmat-linux-x64-native"]')) || matrix.os }} env: FORMA_RUNTIME: ${{ matrix.runtime }} steps: @@ -115,7 +115,7 @@ jobs: echo "VK_ICD_FILENAMES=$software_icd" >> "$GITHUB_ENV" echo "VK_DRIVER_FILES=$software_icd" >> "$GITHUB_ENV" - name: Validate Linux selected runtime - if: runner.os == 'Linux' && vars.TRACE_CI_RUNNERS != 'self-hosted' + if: runner.os == 'Linux' shell: bash env: LIBGL_ALWAYS_SOFTWARE: 1 @@ -138,7 +138,7 @@ jobs: MonoGamePlatform=DesktopVK FormaNativeRuntime=true xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' dotnet run --project tests/Forma.SvgRenderSmoke/Forma.SvgRenderSmoke.csproj --configuration Release -p:FormaRuntime=MonoGame -p:MonoGamePlatform=DesktopVK -p:FormaNativeRuntime=true -p:SvgBackend=Skia fi - name: Validate Linux FNA video - if: runner.os == 'Linux' && matrix.runtime == 'FNA' && vars.TRACE_CI_RUNNERS != 'self-hosted' + if: runner.os == 'Linux' && matrix.runtime == 'FNA' shell: bash env: LIBGL_ALWAYS_SOFTWARE: 1 @@ -148,7 +148,7 @@ jobs: FNA3D_OPENGL_WINDOW_DEPTHSTENCILFORMAT: None run: env -u VK_ICD_FILENAMES -u VK_DRIVER_FILES xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' bash scripts/check-fna-video-smoke.sh - name: Compare Linux peer rendering - if: runner.os == 'Linux' && matrix.runtime == 'MonoGame' && vars.TRACE_CI_RUNNERS != 'self-hosted' + if: runner.os == 'Linux' && matrix.runtime == 'MonoGame' shell: bash env: LIBGL_ALWAYS_SOFTWARE: 1 @@ -279,7 +279,8 @@ jobs: run: bash scripts/check-release-api.sh package-consumers: - runs-on: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && fromJSON('["self-hosted","Linux","X64","yiazmat-linux-x64-emulated"]') || 'ubuntu-latest' }} + # Native x64 only: the committed font binaries are byte-exact on real x64 Linux, not on the emulated runner. + runs-on: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && fromJSON('["self-hosted","Linux","X64","yiazmat-linux-x64-native"]') || 'ubuntu-latest' }} steps: - uses: actions/checkout@v7 with: @@ -300,7 +301,6 @@ jobs: env: LIBGL_ALWAYS_SOFTWARE: 1 SDL_VIDEODRIVER: x11 - FORMA_SKIP_FONT_XNB_CMP: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && '1' || '' }} run: bash scripts/retry.sh env -u VK_ICD_FILENAMES -u VK_DRIVER_FILES xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' bash scripts/test-package-consumer.sh - uses: actions/upload-artifact@v7 with: From c493027095545379d5a44d6235750f530a28dc09 Mon Sep 17 00:00:00 2001 From: zignd Date: Thu, 22 Oct 2026 13:00:50 -0300 Subject: [PATCH 02/10] ci: run the ThorVG Linux adapter on the native x64 runner --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 014bcbdc..5da945c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -321,7 +321,8 @@ jobs: run: bash scripts/retry.sh make nativeaot thorvg-linux: - runs-on: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && fromJSON('["self-hosted","Linux","X64","yiazmat-linux-x64-emulated"]') || 'ubuntu-latest' }} + # The ThorVG adapter renders through Vulkan on the CPU: only real x64 Linux does that (the emulated runner was canceled there). + runs-on: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && fromJSON('["self-hosted","Linux","X64","yiazmat-linux-x64-native"]') || 'ubuntu-latest' }} steps: - uses: actions/checkout@v7 with: From c8df29b56fa7212acc68fc4d145fd78141e139fe Mon Sep 17 00:00:00 2001 From: zignd Date: Thu, 22 Oct 2026 13:21:56 -0300 Subject: [PATCH 03/10] ci: the ThorVG Linux job runs in its own Docker daemon, and the font bytes stay a hosted check --- .github/workflows/ci.yml | 3 +++ scripts/Dockerfile.thorvg-linux | 5 ++++- scripts/check-thorvg-linux.sh | 6 +++++- scripts/test-package-consumer.sh | 5 +++-- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5da945c3..859a4f0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -301,6 +301,9 @@ jobs: env: LIBGL_ALWAYS_SOFTWARE: 1 SDL_VIDEODRIVER: x11 + # The committed font bytes belong to the GitHub-hosted image's font stack (on native x64 Linux they already differ + # at the size field), so the fleet skips that one comparison and hosted CI keeps it. + FORMA_SKIP_FONT_XNB_CMP: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && '1' || '' }} run: bash scripts/retry.sh env -u VK_ICD_FILENAMES -u VK_DRIVER_FILES xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' bash scripts/test-package-consumer.sh - uses: actions/upload-artifact@v7 with: diff --git a/scripts/Dockerfile.thorvg-linux b/scripts/Dockerfile.thorvg-linux index b90d2b12..8d5caa93 100644 --- a/scripts/Dockerfile.thorvg-linux +++ b/scripts/Dockerfile.thorvg-linux @@ -5,4 +5,7 @@ RUN apt-get update \ && python3 -m pip install --break-system-packages meson==1.9.1 \ && rm -rf /var/lib/apt/lists/* -WORKDIR /repo \ No newline at end of file +WORKDIR /repo +# The checkout is baked in as well: a job container with its own Docker daemon (dind) has no host path to mount, so the +# container that runs the smoke tests reads /repo from the image. +COPY . /repo \ No newline at end of file diff --git a/scripts/check-thorvg-linux.sh b/scripts/check-thorvg-linux.sh index df691929..b9fbe8c6 100644 --- a/scripts/check-thorvg-linux.sh +++ b/scripts/check-thorvg-linux.sh @@ -7,7 +7,11 @@ image="forma-thorvg-linux:local" docker build --platform linux/amd64 --file "$repo_root/scripts/Dockerfile.thorvg-linux" --tag "$image" "$repo_root" # Inside a self-hosted runner container (Docker-outside-of-docker) the checkout is not a path the Docker host knows, so the # container borrows this container's volumes and runs in the checkout instead of mounting it at /repo. -if [[ -f /.dockerenv && -n "${HOSTNAME:-}" ]]; then +if [[ -n "${DOCKER_HOST:-}" ]]; then + # A job container with its own Docker daemon: neither this checkout nor this container exists in that daemon, so the + # image's copy of the checkout is used and no path is mounted. + repo_mount=() +elif [[ -f /.dockerenv && -n "${HOSTNAME:-}" ]]; then repo_mount=(--volumes-from "$HOSTNAME" --workdir "$repo_root") else repo_mount=(--volume "$repo_root:/repo") diff --git a/scripts/test-package-consumer.sh b/scripts/test-package-consumer.sh index 0fa8962a..cdfa5f2a 100644 --- a/scripts/test-package-consumer.sh +++ b/scripts/test-package-consumer.sh @@ -27,8 +27,9 @@ for runtime in MonoGame FNA; do catalog_project="$repository_root/samples/Forma.Catalog.FNA/Forma.Catalog.FNA.csproj" fi dotnet build "$catalog_project" --configuration Release -p:FormaRuntime="$runtime" --nologo - # The committed font binaries are byte-exact only on native x64 Linux. The local self-hosted runners (Apple Silicon: x64 under - # Rosetta, or native arm64) compile different bytes, so the CI workflow sets FORMA_SKIP_FONT_XNB_CMP there and hosted CI keeps the check. + # The committed font binaries are byte-exact only on the GitHub-hosted image. Measured on the native x64 Linux server of the + # local fleet, the built Catalog.xnb already differs at byte 6 (the size field): the glyphs come out different, so the bytes + # follow the machine's font stack, not the architecture. The fleet skips this comparison; hosted CI keeps it. if [[ -z "${FORMA_SKIP_FONT_XNB_CMP:-}" ]]; then for font_name in Catalog CatalogCode; do cmp \ From 360bc392ed07876012f0222fe02ee0bd0ca3fb04 Mon Sep 17 00:00:00 2001 From: zignd Date: Thu, 22 Oct 2026 14:34:55 -0300 Subject: [PATCH 04/10] ci: keep MonoGame's Vulkan swapchain smoke off the fleet, where it aborts in the backend --- .github/workflows/ci.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 859a4f0c..0af24183 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -118,6 +118,7 @@ jobs: if: runner.os == 'Linux' shell: bash env: + FORMA_SKIP_VULKAN_SWAPCHAIN_SMOKE: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && '1' || '' }} LIBGL_ALWAYS_SOFTWARE: 1 SDL_VIDEODRIVER: ${{ matrix.runtime == 'FNA' && 'offscreen' || 'x11' }} FNA3D_FORCE_DRIVER: OpenGL @@ -135,7 +136,14 @@ jobs: xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' dotnet test tests/Forma.RenderTests/Forma.RenderTests.csproj --configuration Release -p:FormaRuntime=MonoGame --no-build --nologo xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' dotnet run --project tests/Forma.SvgRenderSmoke/Forma.SvgRenderSmoke.csproj --configuration Release -p:FormaRuntime=MonoGame -p:SvgBackend=Skia SvgBackend=Skia MonoGamePlatform=DesktopVK FormaNativeRuntime=true CatalogBackend=Vulkan xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' bash scripts/check-catalog-smoke.sh - MonoGamePlatform=DesktopVK FormaNativeRuntime=true xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' dotnet run --project tests/Forma.SvgRenderSmoke/Forma.SvgRenderSmoke.csproj --configuration Release -p:FormaRuntime=MonoGame -p:MonoGamePlatform=DesktopVK -p:FormaNativeRuntime=true -p:SvgBackend=Skia + # MonoGame's DesktopVK backend aborts on llvmpipe over Xvfb on the self-hosted fleet: "Fatal : VkResult is c4653214 in + # vulkan/MGG_Vulkan.cpp:2226" followed by SIGSEGV (the swapchain cannot be created and the backend prints an uninitialized + # VkResult). The Vulkan catalog smoke above still runs there; hosted CI keeps the swapchain smoke. + if [[ -z "${FORMA_SKIP_VULKAN_SWAPCHAIN_SMOKE:-}" ]]; then + MonoGamePlatform=DesktopVK FormaNativeRuntime=true xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' dotnet run --project tests/Forma.SvgRenderSmoke/Forma.SvgRenderSmoke.csproj --configuration Release -p:FormaRuntime=MonoGame -p:MonoGamePlatform=DesktopVK -p:FormaNativeRuntime=true -p:SvgBackend=Skia + else + echo "Skipped on the self-hosted fleet: MonoGame DesktopVK crashes in vulkan/MGG_Vulkan.cpp:2226 on llvmpipe over Xvfb." + fi fi - name: Validate Linux FNA video if: runner.os == 'Linux' && matrix.runtime == 'FNA' From fd998031c520aaf7220b9e2cba3a6864a814242b Mon Sep 17 00:00:00 2001 From: zignd Date: Thu, 22 Oct 2026 15:33:22 -0300 Subject: [PATCH 05/10] ci: keep MonoGame's two Vulkan steps on hosted CI, where llvmpipe does not abort the backend --- .github/workflows/ci.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0af24183..b6e723f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -118,7 +118,7 @@ jobs: if: runner.os == 'Linux' shell: bash env: - FORMA_SKIP_VULKAN_SWAPCHAIN_SMOKE: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && '1' || '' }} + FORMA_SKIP_VULKAN_SMOKE: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && '1' || '' }} LIBGL_ALWAYS_SOFTWARE: 1 SDL_VIDEODRIVER: ${{ matrix.runtime == 'FNA' && 'offscreen' || 'x11' }} FNA3D_FORCE_DRIVER: OpenGL @@ -135,11 +135,13 @@ jobs: xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' bash scripts/check-quick-start.sh xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' dotnet test tests/Forma.RenderTests/Forma.RenderTests.csproj --configuration Release -p:FormaRuntime=MonoGame --no-build --nologo xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' dotnet run --project tests/Forma.SvgRenderSmoke/Forma.SvgRenderSmoke.csproj --configuration Release -p:FormaRuntime=MonoGame -p:SvgBackend=Skia - SvgBackend=Skia MonoGamePlatform=DesktopVK FormaNativeRuntime=true CatalogBackend=Vulkan xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' bash scripts/check-catalog-smoke.sh - # MonoGame's DesktopVK backend aborts on llvmpipe over Xvfb on the self-hosted fleet: "Fatal : VkResult is c4653214 in - # vulkan/MGG_Vulkan.cpp:2226" followed by SIGSEGV (the swapchain cannot be created and the backend prints an uninitialized - # VkResult). The Vulkan catalog smoke above still runs there; hosted CI keeps the swapchain smoke. - if [[ -z "${FORMA_SKIP_VULKAN_SWAPCHAIN_SMOKE:-}" ]]; then + # MonoGame's DesktopVK backend aborts on llvmpipe over Xvfb on the self-hosted fleet, in both the catalog smoke and the + # swapchain smoke: "Fatal : VkResult is c4653214 in vulkan/MGG_Vulkan.cpp:2226" followed by SIGSEGV (the backend prints + # an uninitialized VkResult). A 1 GB /dev/shm did not change it, so both Vulkan steps stay on hosted CI. Everything + # else of this leg (DesktopGL catalog smoke, quick start, render tests, SVG smoke) runs on the fleet, and the Vulkan + # coverage there is `thorvg-linux`, which builds the ThorVG adapter and runs its tests on the native x64 server. + if [[ -z "${FORMA_SKIP_VULKAN_SMOKE:-}" ]]; then + SvgBackend=Skia MonoGamePlatform=DesktopVK FormaNativeRuntime=true CatalogBackend=Vulkan xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' bash scripts/check-catalog-smoke.sh MonoGamePlatform=DesktopVK FormaNativeRuntime=true xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' dotnet run --project tests/Forma.SvgRenderSmoke/Forma.SvgRenderSmoke.csproj --configuration Release -p:FormaRuntime=MonoGame -p:MonoGamePlatform=DesktopVK -p:FormaNativeRuntime=true -p:SvgBackend=Skia else echo "Skipped on the self-hosted fleet: MonoGame DesktopVK crashes in vulkan/MGG_Vulkan.cpp:2226 on llvmpipe over Xvfb." From 3885f87fc937eaac99e76c10f3cbc5bf2bed4ffa Mon Sep 17 00:00:00 2001 From: zignd Date: Thu, 22 Oct 2026 15:48:21 -0300 Subject: [PATCH 06/10] ci: retry the runtime parity check past a transient GenerateDepsFile lock --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6e723f0..516c7c8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -284,7 +284,10 @@ jobs: - uses: actions/setup-dotnet@v6 with: dotnet-version: 10.0.x - - run: bash scripts/check-runtime-parity.sh + - name: Check runtime parity + # Sequential builds in one container can still hold a *.deps.json just long enough to break GenerateDepsFile; the + # retry wrapper (used by the other build steps too) turns that transient lock into a pass. + run: bash scripts/retry.sh bash scripts/check-runtime-parity.sh - name: Review release public API run: bash scripts/check-release-api.sh From db1a2d6292a8b76c8caabc0ede55698ec9a89310 Mon Sep 17 00:00:00 2001 From: zignd Date: Thu, 22 Oct 2026 16:12:32 -0300 Subject: [PATCH 07/10] docs: say where the CI jobs run and what the self-hosted fleet cannot do --- docs/continuous-integration.md | 43 +++++++++++++++++++++++++++++++++ docs/documentation-inventory.md | 1 + 2 files changed, 44 insertions(+) create mode 100644 docs/continuous-integration.md diff --git a/docs/continuous-integration.md b/docs/continuous-integration.md new file mode 100644 index 00000000..eaf60e0b --- /dev/null +++ b/docs/continuous-integration.md @@ -0,0 +1,43 @@ +# Continuous Integration Runners + +The `CI` workflow runs on GitHub-hosted runners by default. Setting the repository variable +`TRACE_CI_RUNNERS` to `self-hosted` points the jobs at the local fleet documented in +`gh-runners-manager`; leaving it unset keeps the hosted setup, which is the default and the +supported configuration for forks. + +## Where each job runs + +| Job | Runner labels | Notes | +| --- | --- | --- | +| `lint` | `self-hosted, Linux, X64, yiazmat-linux-x64-emulated` | Pure managed build and analyzers. | +| `parity` | `self-hosted, Linux, X64, yiazmat-linux-x64-emulated` | Sequential runtime builds; the step is wrapped in `scripts/retry.sh` because a transient `GenerateDepsFile` file lock can survive one build boundary. | +| `runtime` (`ubuntu-latest` legs) | `self-hosted, Linux, X64, yiazmat-linux-x64-native` | Runs on the native x64 Linux server, where the Vulkan software renderer works. | +| `runtime` (`macos-latest` legs) | `self-hosted, macOS, ARM64` | Native arm64 macOS. | +| `runtime` (`windows-latest` legs) | placeholder job | No Windows machine is part of the fleet; the job reports the leg as not run so the required checks still resolve. | +| `thorvg-linux` | `self-hosted, Linux, X64, yiazmat-linux-x64-native` | Builds the ThorVG adapter and runs its tests inside the slot's own Docker daemon. | +| `thorvg-macos`, `nativeaot` | `self-hosted, macOS, ARM64` | Native arm64 macOS. | +| `package-consumers` | `self-hosted, Linux, X64, yiazmat-linux-x64-native` | Builds and inspects the shipped packages. | + +## Known limits of the fleet + +These are deliberate, documented limits — not silent skips: + +- **MonoGame's Vulkan steps** (`MonoGamePlatform=DesktopVK`: the Vulkan catalog smoke and the + swapchain smoke) are skipped on the fleet. MonoGame's desktop Vulkan backend aborts on llvmpipe + over Xvfb with `Fatal : VkResult is c4653214 in vulkan/MGG_Vulkan.cpp:2226` and dies with + `SIGSEGV` (exit 139); raising `/dev/shm` to 1 GB does not change it. Hosted CI keeps both steps. + The Vulkan adapter itself is still built and tested on the native server by `thorvg-linux`. +- **Byte-exact font binaries**: the committed `tests/Assets/Fonts/*.xnb` files are compared only on + hosted CI. The bytes follow the font build stack, not the architecture: a native x64 Linux build + already differs from the committed file at byte 6 (the size field). The fleet sets + `FORMA_SKIP_FONT_XNB_CMP` for that one comparison. +- **macOS Metal runtime**: the steps that need premake are skipped there; premake has no arm64 + build and the machine has no Rosetta. The jobs use the machine's Vulkan SDK for the Metal + runtime build instead of installing one. + +## Build hygiene + +Every job exports `MSBUILDDISABLENODEREUSE=1`, `DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER=1` and +`DOTNET_PROCESSOR_COUNT=2`: reused MSBuild nodes have held `*.deps.json` files across builds and +broken the next `GenerateDepsFile`. Runs are serialized per working directory by the ephemeral +runner container, which serves exactly one job before it exits. diff --git a/docs/documentation-inventory.md b/docs/documentation-inventory.md index cda4ed61..e6629a70 100644 --- a/docs/documentation-inventory.md +++ b/docs/documentation-inventory.md @@ -59,6 +59,7 @@ runtime/backend integrator (R), and contributor (C). | `docs/runtime-svg.md` | G, A, R | Runtime SVG setup, security, caching, deployment | Current SVG guide; measured tables are dated evidence | | `docs/runtime-svg-profile-v1.md` | A, R, C | Normative SVG feature and comparison profile | Current compatibility reference | | `docs/svg-backend-migration.md` | G, R | Selecting explicit Skia or ThorVG packages | Current backend selection guide | +| `docs/continuous-integration.md` | C | Where each CI job runs and the documented limits of the self-hosted fleet | Current contributor guide | | `docs/svg-backend-rollout.md` | R, C | ThorVG rollout decision and qualification evidence | Dated release evidence | | `docs/theme-icons.md` | G, A | Default icon policy, customization, regeneration | Current styling guide; manifest owns the icon count | | `docs/thorvg-build-and-provenance.md` | R, C | ThorVG pin, provenance, ABI, build procedure | Current supply-chain reference | From 464dedbb21ebbbdabbcdf6b2dae2a91296d9f64e Mon Sep 17 00:00:00 2001 From: zignd Date: Thu, 22 Oct 2026 16:37:14 -0300 Subject: [PATCH 08/10] ci: serialize the parity builds past the GenerateDepsFile lock race The test project references Forma and its siblings with an explicit AdditionalProperties runtime, so MSBuild can keep two instances of one project in the graph. With two nodes (the fleet's emulated x64 runner has two CPUs) both instances can write the same *.deps.json, and GenerateDepsFile fails with "being used by another process"; the retry wrapper then runs into the same race again. Build with one node (-m:1) in the parity script, which serializes the instances, and in the docs script's incremental builds. The build takes longer, but it no longer depends on which node wins the write. --- scripts/build-docs.sh | 2 +- scripts/check-runtime-parity.sh | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/scripts/build-docs.sh b/scripts/build-docs.sh index 59fff5c2..8ff33e8b 100755 --- a/scripts/build-docs.sh +++ b/scripts/build-docs.sh @@ -30,7 +30,7 @@ projects=(Forma Forma.Accessibility Forma.DynamicText Forma.Media Forma.Svg Form for project in "${projects[@]}"; do dotnet build "src/$project/$project.csproj" \ --configuration Release -p:FormaRuntime=MonoGame -p:SourceRevisionId="$docs_revision" \ - --no-incremental --nologo + -m:1 --no-incremental --nologo done rm -rf "$generated_root" "$api_root" "$site_root" diff --git a/scripts/check-runtime-parity.sh b/scripts/check-runtime-parity.sh index 6bfa2067..e4b79397 100755 --- a/scripts/check-runtime-parity.sh +++ b/scripts/check-runtime-parity.sh @@ -3,16 +3,21 @@ set -euo pipefail # Purpose: Build and test both peer runtimes, then verify framework isolation and public API parity. # Usage: `bash scripts/check-runtime-parity.sh` from any directory. +# +# The builds run with one MSBuild node (-m:1). The test project references the same projects with an explicit +# AdditionalProperties runtime, so a parallel node can build one of them as a second instance while the first is still +# writing its *.deps.json; on a slow (emulated) runner that shows up as GenerateDepsFile's "being used by another process". +# A single node serializes the two instances and the race disappears, at the cost of a longer build. repository_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" inspector_project="$repository_root/tools/Forma.AssemblyInspector/Forma.AssemblyInspector.csproj" inspector="$repository_root/tools/Forma.AssemblyInspector/bin/MonoGame/Release/net10.0/Forma.AssemblyInspector.dll" -dotnet test "$repository_root/tests/Forma.Tests/Forma.Tests.csproj" --configuration Release -p:FormaRuntime=FNA --nologo -dotnet build "$repository_root/samples/Forma.Catalog.FNA/Forma.Catalog.FNA.csproj" --configuration Release -p:FormaRuntime=FNA --nologo -dotnet test "$repository_root/tests/Forma.Tests/Forma.Tests.csproj" --configuration Release -p:FormaRuntime=MonoGame --nologo -dotnet build "$repository_root/samples/Forma.Catalog.MonoGame/Forma.Catalog.MonoGame.csproj" --configuration Release -p:FormaRuntime=MonoGame --nologo -dotnet build "$inspector_project" --configuration Release --nologo +dotnet test "$repository_root/tests/Forma.Tests/Forma.Tests.csproj" --configuration Release -p:FormaRuntime=FNA -m:1 --nologo +dotnet build "$repository_root/samples/Forma.Catalog.FNA/Forma.Catalog.FNA.csproj" --configuration Release -p:FormaRuntime=FNA -m:1 --nologo +dotnet test "$repository_root/tests/Forma.Tests/Forma.Tests.csproj" --configuration Release -p:FormaRuntime=MonoGame -m:1 --nologo +dotnet build "$repository_root/samples/Forma.Catalog.MonoGame/Forma.Catalog.MonoGame.csproj" --configuration Release -p:FormaRuntime=MonoGame -m:1 --nologo +dotnet build "$inspector_project" --configuration Release -m:1 --nologo dotnet "$inspector" references "$repository_root/src/Forma/bin/FNA/Release/net10.0/Forma.dll" Forma FNA FNA.NET MonoGame.Framework dotnet "$inspector" references "$repository_root/src/Forma/bin/MonoGame/Release/net10.0/Forma.dll" Forma MonoGame MonoGame.Framework FNA.NET From a9c4e66976ebd218ef315465cdda8230e6525fed Mon Sep 17 00:00:00 2001 From: zignd Date: Thu, 22 Oct 2026 16:42:49 -0300 Subject: [PATCH 09/10] docs: add the fleet vocabulary to the spell checker The continuous-integration page names the runner labels, the runner-side environment variables and the Vulkan failure modes; cspell rejected them. --- config/cspell-project-words.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config/cspell-project-words.txt b/config/cspell-project-words.txt index c077e71c..71c26ce2 100644 --- a/config/cspell-project-words.txt +++ b/config/cspell-project-words.txt @@ -108,3 +108,10 @@ unitless Lnnnn VIDEODRIVER AUDIODRIVER +llvmpipe +MSBUILD +MSBUILDDISABLENODEREUSE +premake +SIGSEGV +swapchain +yiazmat From fbc7ea0cf567806d030fa7868e08fdc43c340c56 Mon Sep 17 00:00:00 2001 From: zignd Date: Thu, 22 Oct 2026 16:44:08 -0300 Subject: [PATCH 10/10] docs: note the two-job cap of the emulated pool --- docs/continuous-integration.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/continuous-integration.md b/docs/continuous-integration.md index eaf60e0b..182e4bda 100644 --- a/docs/continuous-integration.md +++ b/docs/continuous-integration.md @@ -37,6 +37,9 @@ These are deliberate, documented limits — not silent skips: ## Build hygiene +The emulated x64 pool runs at most two jobs at a time: the native x64 server carries the heavier Linux legs, and two +emulated containers keep the host's Docker VM comfortable. + Every job exports `MSBUILDDISABLENODEREUSE=1`, `DOTNET_CLI_DO_NOT_USE_MSBUILD_SERVER=1` and `DOTNET_PROCESSOR_COUNT=2`: reused MSBuild nodes have held `*.deps.json` files across builds and broken the next `GenerateDepsFile`. Runs are serialized per working directory by the ephemeral