Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 32 additions & 36 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,33 +41,29 @@ jobs:
- name: Verify deterministic default theme icons
run: dotnet run --project tools/Forma.IconPipeline/Forma.IconPipeline.csproj -- verify

# The Windows legs cannot run on the local fleet (no Windows runner exists here), but the branch ruleset requires those two
# contexts to succeed. Under the flag they are reported as successful with this placeholder instead of being left pending.
windows-legs-not-run:
name: ${{ matrix.runtime }} / windows-latest
if: vars.TRACE_CI_RUNNERS == 'self-hosted'
runs-on: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && fromJSON('["self-hosted","Linux","X64"]') || 'ubuntu-latest' }}
strategy:
fail-fast: false
matrix:
runtime: [MonoGame, FNA]
steps:
- name: Report that the Windows leg did not run
run: echo "No Windows runner exists on the local machine; the ${{ matrix.runtime }} Windows leg is skipped under TRACE_CI_RUNNERS=self-hosted."

runtime:
needs: lint
name: ${{ matrix.runtime }} / ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# Windows legs are skipped under TRACE_CI_RUNNERS=self-hosted: no Windows runner exists on the local machine.
os: ${{ fromJSON(vars.TRACE_CI_RUNNERS == 'self-hosted' && '["ubuntu-latest","macos-latest"]' || '["ubuntu-latest","windows-latest","macos-latest"]') }}
runtime: [MonoGame, FNA]
# 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-native"]')) || matrix.os }}
# The Windows legs always run on GitHub-hosted windows-latest: no Windows machine is part of the local fleet, and Forma
# is a public repository, where standard runners (Windows included) do not consume billed minutes.
os: [ubuntu-latest, windows-latest, macos-latest]
include:
- os: ubuntu-latest
selfHosted: '["self-hosted","Linux","X64","yiazmat-linux-x64-native"]'
- os: macos-latest
selfHosted: '["self-hosted","macOS","ARM64"]'
- os: windows-latest
selfHosted: '["windows-latest"]'
# Under TRACE_CI_RUNNERS=self-hosted (docs/self-hosted-runners.md in Trace Arena) the Linux legs run on the local native x64
# runner and the macOS legs on the local macOS runner. The Windows legs stay on windows-latest either way (see the matrix).
# The Linux steps that render through the Vulkan software renderer are skipped on the self-hosted Linux runner: 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' && fromJSON(matrix.selfHosted) || matrix.os }}
env:
FORMA_RUNTIME: ${{ matrix.runtime }}
steps:
Expand Down Expand Up @@ -108,7 +104,7 @@ jobs:
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends libegl1 libgl1 libgl1-mesa-dri libglx0 libglx-mesa0 mesa-vulkan-drivers vulkan-tools xvfb
sudo apt-get install -y --no-install-recommends libegl1 libgl1 libgl1-mesa-dri libglx0 libglx-mesa0 mesa-vulkan-drivers vulkan-tools xvfb x11-utils
software_icd=$(find /opt/google/chrome -name 'vk_swiftshader_icd.json' -print -quit 2>/dev/null || true)
software_icd=${software_icd:-$(find /usr/share/vulkan/icd.d -name 'lvp_icd*.json' -print -quit)}
test -n "$software_icd"
Expand All @@ -125,24 +121,24 @@ jobs:
FNA3D_OPENGL_WINDOW_DEPTHSTENCILFORMAT: None
run: |
if [[ "$FORMA_RUNTIME" == "FNA" ]]; then
env -u VK_ICD_FILENAMES -u VK_DRIVER_FILES SvgBackend=Skia FormaRuntime=FNA xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' bash scripts/check-catalog-smoke.sh
env -u VK_ICD_FILENAMES -u VK_DRIVER_FILES xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' bash scripts/check-quick-start.sh
env -u VK_ICD_FILENAMES -u VK_DRIVER_FILES xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' dotnet test tests/Forma.RenderTests/Forma.RenderTests.csproj --configuration Release -p:FormaRuntime=FNA --no-build --nologo
env -u VK_ICD_FILENAMES -u VK_DRIVER_FILES xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' dotnet run --project tests/Forma.SvgRenderSmoke/Forma.SvgRenderSmoke.csproj --configuration Release -p:FormaRuntime=FNA -p:SvgBackend=Skia
env -u VK_ICD_FILENAMES -u VK_DRIVER_FILES SvgBackend=Skia FormaRuntime=FNA bash scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' bash scripts/check-catalog-smoke.sh
env -u VK_ICD_FILENAMES -u VK_DRIVER_FILES bash scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' bash scripts/check-quick-start.sh
env -u VK_ICD_FILENAMES -u VK_DRIVER_FILES bash scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' dotnet test tests/Forma.RenderTests/Forma.RenderTests.csproj --configuration Release -p:FormaRuntime=FNA --no-build --nologo
env -u VK_ICD_FILENAMES -u VK_DRIVER_FILES bash scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' dotnet run --project tests/Forma.SvgRenderSmoke/Forma.SvgRenderSmoke.csproj --configuration Release -p:FormaRuntime=FNA -p:SvgBackend=Skia
else
xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' vulkaninfo --summary
SvgBackend=Skia FormaRuntime=MonoGame xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' bash scripts/check-catalog-smoke.sh
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
bash scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' vulkaninfo --summary
SvgBackend=Skia FormaRuntime=MonoGame bash scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' bash scripts/check-catalog-smoke.sh
bash scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' bash scripts/check-quick-start.sh
bash scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' dotnet test tests/Forma.RenderTests/Forma.RenderTests.csproj --configuration Release -p:FormaRuntime=MonoGame --no-build --nologo
bash scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' dotnet run --project tests/Forma.SvgRenderSmoke/Forma.SvgRenderSmoke.csproj --configuration Release -p:FormaRuntime=MonoGame -p:SvgBackend=Skia
# 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
SvgBackend=Skia MonoGamePlatform=DesktopVK FormaNativeRuntime=true CatalogBackend=Vulkan bash scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' bash scripts/check-catalog-smoke.sh
MonoGamePlatform=DesktopVK FormaNativeRuntime=true bash scripts/xvfb-run.sh -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
Expand All @@ -156,7 +152,7 @@ jobs:
SDL_AUDIODRIVER: dummy
FNA3D_FORCE_DRIVER: OpenGL
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
run: env -u VK_ICD_FILENAMES -u VK_DRIVER_FILES bash scripts/xvfb-run.sh -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'
shell: bash
Expand All @@ -165,7 +161,7 @@ jobs:
SDL_VIDEODRIVER: x11
FormaCatalogViewportWidth: 800
FormaCatalogViewportHeight: 480
run: SvgBackend=Skia bash scripts/retry.sh xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' bash scripts/check-catalog-render-parity.sh
run: SvgBackend=Skia bash scripts/retry.sh bash scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' bash scripts/check-catalog-render-parity.sh
- name: Validate Windows selected runtime
if: runner.os == 'Windows'
shell: bash
Expand Down Expand Up @@ -304,7 +300,7 @@ jobs:
- name: Install Linux graphics dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends libegl1 libgl1 libgl1-mesa-dri libglx0 libglx-mesa0 mesa-vulkan-drivers xvfb
sudo apt-get install -y --no-install-recommends libegl1 libgl1 libgl1-mesa-dri libglx0 libglx-mesa0 mesa-vulkan-drivers xvfb x11-utils
software_icd=$(find /opt/google/chrome -name 'vk_swiftshader_icd.json' -print -quit 2>/dev/null || true)
software_icd=${software_icd:-$(find /usr/share/vulkan/icd.d -name 'lvp_icd*.json' -print -quit)}
test -n "$software_icd"
Expand All @@ -317,7 +313,7 @@ jobs:
# 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
run: bash scripts/retry.sh env -u VK_ICD_FILENAMES -u VK_DRIVER_FILES bash scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' bash scripts/test-package-consumer.sh
- uses: actions/upload-artifact@v7
with:
name: forma-peer-packages
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
- name: Install Linux graphics dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends libegl1 libgl1 libgl1-mesa-dri libglx0 libglx-mesa0 xvfb
sudo apt-get install -y --no-install-recommends libegl1 libgl1 libgl1-mesa-dri libglx0 libglx-mesa0 xvfb x11-utils
- name: Validate release tag
if: startsWith(github.ref, 'refs/tags/')
shell: bash
Expand All @@ -89,7 +89,7 @@ jobs:
env:
LIBGL_ALWAYS_SOFTWARE: 1
SDL_VIDEODRIVER: x11
run: xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' bash scripts/test-package-consumer.sh
run: bash scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' bash scripts/test-package-consumer.sh
- name: Validate SVG package consumers
run: bash scripts/test-svg-package-consumers.sh
- name: Pack approved release manifest
Expand Down
1 change: 1 addition & 0 deletions config/cspell-project-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,4 @@ premake
SIGSEGV
swapchain
yiazmat
xdpyinfo
9 changes: 8 additions & 1 deletion docs/continuous-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ supported configuration for forks.
| `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. |
| `runtime` (`windows-latest` legs) | `windows-latest` | Always GitHub-hosted: no Windows machine is part of the fleet, and the repository is public, so these minutes are not billed. |
| `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. |
Expand All @@ -40,6 +40,13 @@ These are deliberate, documented limits — not silent skips:
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 X program runs through `scripts/xvfb-run.sh`, not `xvfb-run` directly: `xvfb-run -a` hands the
display number to the payload as soon as Xvfb creates its socket, and Xvfb `2:21.1.12-1ubuntu1.9`
still needs a moment before it listens, so a client that connects immediately dies with
`XCB failed to connect to the X server due to error:1`. The wrapper waits for `xdpyinfo` to succeed
before starting the payload (4 of 5 plain `xvfb-run` runs failed in a fresh container on the server;
0 of 5 with the wrapper).

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
Expand Down
8 changes: 4 additions & 4 deletions scripts/check-runtime-svg-linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ cd "$repository_root"

if [[ "${FORMA_SKIP_APT:-0}" != "1" ]] && command -v apt-get >/dev/null 2>&1 && [[ "$(id -u)" == "0" ]]; then
apt-get update
apt-get install -y --no-install-recommends libegl1 libfreetype6-dev libgl1 libgl1-mesa-dri libglx0 libglx-mesa0 libharfbuzz-dev mesa-vulkan-drivers vulkan-tools xvfb xauth
apt-get install -y --no-install-recommends libegl1 libfreetype6-dev libgl1 libgl1-mesa-dri libglx0 libglx-mesa0 libharfbuzz-dev mesa-vulkan-drivers vulkan-tools xvfb x11-utils xauth
fi

software_icd="$(find /usr/share/vulkan/icd.d -name 'lvp_icd*.json' -print -quit)"
Expand All @@ -22,19 +22,19 @@ for runtime_name in MonoGame FNA; do
-p:FormaRuntime="$runtime_name" --filter 'FullyQualifiedName~Svg' --nologo
done

SDL_VIDEODRIVER=x11 xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' \
SDL_VIDEODRIVER=x11 bash scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' \
dotnet run --project tests/Forma.SvgRenderSmoke/Forma.SvgRenderSmoke.csproj \
--configuration Release -p:FormaRuntime=MonoGame

env -u VK_ICD_FILENAMES -u VK_DRIVER_FILES SDL_VIDEODRIVER=offscreen FNA3D_FORCE_DRIVER=OpenGL \
xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' \
bash scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' \
dotnet run --project tests/Forma.SvgRenderSmoke/Forma.SvgRenderSmoke.csproj \
--configuration Release -p:FormaRuntime=FNA

VK_ICD_FILENAMES="$software_icd" VK_DRIVER_FILES="$software_icd" vulkaninfo --summary
VK_ICD_FILENAMES="$software_icd" VK_DRIVER_FILES="$software_icd" SDL_VIDEODRIVER=x11 \
MonoGamePlatform=DesktopVK FormaNativeRuntime=true \
xvfb-run -a -s '-screen 0 1440x900x24 +extension GLX' \
bash scripts/xvfb-run.sh -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

Expand Down
46 changes: 46 additions & 0 deletions scripts/xvfb-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Runs a command under a fresh X server, and returns only once the server accepts connections.
#
# `xvfb-run -a` hands the display number to the payload as soon as Xvfb creates its socket; Xvfb 2:21.1.12-1ubuntu1.9 then still needs a
# moment before it listens, so a client that connects immediately dies with
# XCB failed to connect to the X server due to error:1
# (vulkaninfo, and SDL/GL programs in general). Waiting for a successful X query before starting the payload removes the race.
# Usage mirrors the part of xvfb-run this repository uses:
#
# scripts/xvfb-run.sh -s '-screen 0 1440x900x24 +extension GLX' COMMAND [ARGS...]
set -euo pipefail

server_args=""
if [ "${1:-}" = "-s" ]; then
server_args=${2:?xvfb-run.sh: -s needs a value}
shift 2
fi
if [ $# -eq 0 ]; then
echo "usage: xvfb-run.sh [-s SERVER_ARGS] COMMAND [ARGS...]" >&2
exit 2
fi
if ! command -v xdpyinfo >/dev/null 2>&1; then
echo "xvfb-run.sh: xdpyinfo is required (package x11-utils)" >&2
exit 1
fi

# Xvfb starts in milliseconds on an idle machine and in a second or two under load; 15 s is the "something is actually wrong" limit.
wait_seconds=${XVFB_WAIT_SECONDS:-15}
args=(xvfb-run -a)
if [ -n "$server_args" ]; then
args+=(-s "$server_args")
fi

exec "${args[@]}" bash -c '
wait=$1
shift
deadline=$((SECONDS + wait))
while ! xdpyinfo >/dev/null 2>&1; do
if [ "$SECONDS" -ge "$deadline" ]; then
echo "xvfb-run.sh: the X server on ${DISPLAY:-?} did not accept connections within ${wait}s" >&2
exit 1
fi
sleep 0.1
done
exec "$@"
' _ "$wait_seconds" "$@"
Loading