Files
oak-editor/.github/workflows/ci.yml
T
Mike-Solar df65a3f1a4 ci/cd: use BtbN's prebuilt FFmpeg on Windows
A cold vcpkg FFmpeg build costs ~40 minutes per Windows run and the
Windows dependency chain is the hardest to keep healthy; BtbN's builds
come from public GitHub Actions on the release/8.1 branch (BtbN is an
FFmpeg developer; ffmpeg.org links these builds as the official Windows
option). The archive ships include/, MSVC import libs (.lib), pkg-config
files and the runtime DLLs, and is fetched straight from his release
page with the checksums.sha256 published in the same release — nothing
is mirrored here, so provenance stays upstream.

The Windows jobs download/verify/extract it to .cache/ffmpeg (the
checksum comes from that release's own checksums.sha256), point
FFMPEG_DIR/PKG_CONFIG_PATH at it (pkgconf still comes from vcpkg) and
the installer bundles its DLLs. The manifest gates ffmpeg to !windows
and drops librsvg (the icon now ships as the committed PNG).
2026-09-24 18:39:10 +08:00

680 lines
31 KiB
YAML

name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
# One matrix, seven platforms: the three Linux-family packaging distros
# (Debian 12, Fedora 43, Arch), openKylin on x64 and arm64, macOS and
# Windows. The environments match .github/workflows/cd.yml exactly (same
# container images, same dependency lists, same runner sizes), so a
# "passes CI, fails CD" dependency drift is caught here first.
jobs:
build-test:
name: Build & test (${{ matrix.name }})
runs-on: ${{ matrix.runner }}
# Container entries carry the container as JSON ({"image":...,
# "options":...}); the empty string means "run on the host"
# (actions/runner#265 allows an empty container value).
container: ${{ matrix.container != '' && fromJSON(matrix.container) || '' }}
# The Test step's watchdog caps a hung suite at 30 min; leave a cold
# vcpkg install + full compile room beyond that.
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
include:
- name: Debian
platform: linux
distro: debian
arch: x64
runner: warp-ubuntu-latest-x64-8x
triplet: x64-linux
container: '{"image":"debian:12","options":"--shm-size=8g"}'
- name: Fedora
platform: linux
distro: fedora
arch: x64
runner: warp-ubuntu-latest-x64-8x
triplet: x64-linux
container: '{"image":"fedora:43","options":"--shm-size=8g"}'
- name: Arch
platform: linux
distro: arch
arch: x64
runner: warp-ubuntu-latest-x64-8x
triplet: x64-linux
container: '{"image":"archlinux:latest","options":"--shm-size=8g"}'
- name: openKylin x64
platform: linux
distro: openkylin
arch: x64
runner: warp-ubuntu-latest-x64-8x
triplet: x64-linux
container: '{"image":"openkylin/openkylin:latest","options":"--shm-size=8g"}'
- name: openKylin arm64
platform: linux
distro: openkylin
arch: arm64
runner: warp-ubuntu-latest-arm64-16x
triplet: arm64-linux
container: '{"image":"openkylin/openkylin:latest","options":"--shm-size=8g"}'
- name: macOS
platform: macos
distro: macos
arch: arm64
runner: warp-macos-26-arm64-12x
triplet: arm64-osx
container: ''
- name: Windows
platform: windows
distro: windows
arch: x64
runner: warp-windows-2025-vs2026-x64-32x
triplet: x64-windows
container: ''
steps:
# The container images are bare (Fedora/Arch even lack git);
# checkout and vcpkg need git/curl, and WarpCache needs wget inside
# a container (its README requires it). First step of the job, so
# the package lists are still fresh.
- name: Bootstrap container (git, curl, wget)
if: matrix.container != ''
shell: bash
run: |
case "${{ matrix.distro }}" in
fedora) dnf install -y --setopt=install_weak_deps=False --setopt=max_parallel_downloads=16 git curl wget which ;;
arch) pacman -Sy --noconfirm git curl wget which ;;
debian|openkylin) apt-get update && apt-get install -y git curl ca-certificates wget ;;
esac
- name: Checkout
uses: actions/checkout@v7
with:
# gpui/ is a git submodule; its crates are workspace members of
# their own repo and build as path dependencies of oakapp.
submodules: true
# Taste the disk before the toolchains land: Defender scans every
# file the vcpkg/cargo builds touch (tens of thousands of small
# writes), which dominates a cold Windows build. The runner is an
# ephemeral VM, so the scanner is turned off for the job
# (exclusions are kept as a fallback for images where real-time
# protection cannot be disabled).
- name: Disable Windows Defender scanning
if: matrix.platform == 'windows'
shell: pwsh
run: |
try {
Set-MpPreference -DisableRealtimeMonitoring $true -ErrorAction Stop
Set-MpPreference -DisableScriptScanning $true -ErrorAction SilentlyContinue
Set-MpPreference -DisableArchiveScanning $true -ErrorAction SilentlyContinue
Write-Host "Windows Defender real-time scanning disabled for this job"
} catch {
Write-Host "Windows Defender could not be disabled (non-fatal, falling back to exclusions): $_"
}
foreach ($path in @(
$env:GITHUB_WORKSPACE,
"$env:USERPROFILE\.cargo",
"$env:USERPROFILE\.rustup",
"$env:LOCALAPPDATA\vcpkg"
)) {
Add-MpPreference -ExclusionPath $path -ErrorAction SilentlyContinue
}
try {
Get-MpPreference |
Select-Object DisableRealtimeMonitoring, DisableScriptScanning, ExclusionPath |
Format-List
} catch {
Write-Host "Defender status unavailable: $_"
}
# The containers run as root but Actions sets HOME=/github/home;
# rustup refuses the euid mismatch ("$HOME differs from
# euid-obtained home directory") and would install a toolchain the
# later steps cannot find under the Actions home. Pin the job to
# root's home so rustup/cargo and the toolchain agree.
- name: Pin HOME for rustup
if: matrix.container != ''
shell: bash
run: |
{
echo "HOME=/root"
echo "CARGO_HOME=/root/.cargo"
echo "RUSTUP_HOME=/root/.rustup"
} >> "$GITHUB_ENV"
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
# The Windows build is MSVC-ABI (the runner carries VS 2026):
# vcpkg's FFmpeg and the vendored OCIO build both want it.
toolchain: ${{ matrix.platform == 'windows' && 'stable-x86_64-pc-windows-msvc' || 'stable' }}
# ------------------------------------------------------------------
# System dependencies — one list per distro, byte-for-byte the same
# lists CD uses (see .github/workflows/cd.yml): a package a CD build
# needs cannot be missing here.
# ------------------------------------------------------------------
- name: Install system dependencies (Debian)
if: matrix.distro == 'debian'
shell: bash
run: |
apt-get update
apt-get install -y \
build-essential clang libclang-dev cmake pkg-config nasm \
git curl zip unzip tar python3 dpkg-dev \
libpipewire-0.3-dev libspa-0.2-dev libjack-jackd2-dev \
libasound2-dev libpulse-dev libsndfile1-dev \
libgl1-mesa-dev libgl1-mesa-dri mesa-vulkan-drivers \
libvulkan-dev libxkbcommon-dev libxkbcommon-x11-dev xvfb libdrm-dev \
icc-profiles-free gdb file librsvg2-bin patchelf \
autoconf autoconf-archive automake libtool
- name: Install system dependencies (Fedora)
if: matrix.distro == 'fedora'
shell: bash
run: |
# Fedora 41 is EOL (its mirrors moved to the slow archive), so
# the matrix uses the current release; weak dependencies (docs,
# fonts, optional tooling) are skipped and downloads run wide.
dnf install -y --setopt=install_weak_deps=False \
--setopt=max_parallel_downloads=16 \
gcc gcc-c++ clang clang-devel cmake pkgconf-pkg-config nasm \
git curl zip unzip tar python3 patch xz which \
pipewire-devel jack-audio-connection-kit-devel \
alsa-lib-devel pulseaudio-libs-devel libsndfile-devel \
mesa-libGL-devel mesa-vulkan-drivers \
vulkan-headers vulkan-loader-devel \
libxkbcommon-devel libxkbcommon-x11-devel \
rpm-build librsvg2-tools libdrm-devel \
perl-IPC-Cmd perl-FindBin perl-File-Basename perl-File-Compare \
perl-File-Copy perl-File-Path perl-File-Temp perl-Time-Piece \
xorg-x11-server-Xvfb xorg-x11-xauth gdb file \
autoconf autoconf-archive automake libtool
- name: Install system dependencies (Arch)
if: matrix.distro == 'arch'
shell: bash
run: |
pacman -S --needed --noconfirm \
base-devel clang cmake pkgconf nasm \
git curl zip unzip tar python patch xz which \
pipewire jack2 alsa-lib libpulse libsndfile \
mesa vulkan-headers vulkan-icd-loader \
libxkbcommon libxkbcommon-x11 librsvg libdrm \
xorg-server-xvfb xorg-xauth gdb file \
autoconf autoconf-archive automake libtool
- name: Install system dependencies (openKylin)
if: matrix.distro == 'openkylin'
shell: bash
run: |
apt-get update
apt-get install -y \
build-essential clang libclang-dev cmake pkg-config nasm \
git curl zip unzip tar python3 patch xz-utils dpkg-dev \
libpipewire-0.3-dev libspa-0.2-dev libjack-jackd2-dev \
libasound2-dev libpulse-dev libsndfile1-dev \
libgl1-mesa-dev libgl1-mesa-dri mesa-vulkan-drivers \
libvulkan-dev libxkbcommon-dev libxkbcommon-x11-dev xvfb libdrm-dev \
gdb file patchelf fonts-dejavu-core \
autoconf autoconf-archive automake libtool
- name: Install system dependencies (macOS)
if: matrix.platform == 'macos'
run: |
# Homebrew's pkgconf installs a `pkg-config` symlink, which is
# the name crates/oak-ffmpeg-link/build.rs invokes; nasm is what
# vcpkg's ffmpeg port requires to build (FFmpeg libraries come
# from the vcpkg manifest). librsvg is CD's icon renderer.
brew install cmake pkg-config nasm librsvg autoconf automake libtool autoconf-archive
# Windows uses BtbN's prebuilt FFmpeg (GPL, shared): the archive
# ships include/, MSVC import libs (.lib), pkg-config files and the
# runtime DLLs, built from FFmpeg's release/8.1 branch by BtbN's
# public GitHub Actions (BtbN is an FFmpeg developer and ffmpeg.org
# links these builds). Downloaded from his release page and
# verified against the checksums.sha256 published in the same
# release; nothing is mirrored here, so provenance stays upstream.
# The alternative — a cold vcpkg FFmpeg build — costs ~40 minutes
# per run on this platform.
- name: Install prebuilt FFmpeg (Windows)
if: matrix.platform == 'windows'
shell: pwsh
run: |
$base = "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest"
$asset = "ffmpeg-n8.1-latest-win64-gpl-shared-8.1.zip"
$dir = Join-Path $env:RUNNER_TEMP "ffmpeg-prebuilt"
New-Item -ItemType Directory -Force $dir | Out-Null
Invoke-WebRequest -Uri "$base/checksums.sha256" -OutFile "$dir\checksums.sha256"
$line = Select-String -Path "$dir\checksums.sha256" -Pattern ([regex]::Escape($asset) + "\s*$") |
Select-Object -First 1
if (-not $line) { throw "no checksum for $asset in the release's checksums.sha256" }
$expected = $line.Line.Split()[0].ToLowerInvariant()
Invoke-WebRequest -Uri "$base/$asset" -OutFile "$dir\$asset"
$actual = (Get-FileHash "$dir\$asset" -Algorithm SHA256).Hash.ToLowerInvariant()
if ($actual -ne $expected) { throw "FFmpeg checksum mismatch: expected $expected, got $actual" }
$root = "$env:GITHUB_WORKSPACE\.cache"
Remove-Item "$root\ffmpeg", "$root\ffmpeg-extract" -Recurse -Force -ErrorAction SilentlyContinue
Expand-Archive -Path "$dir\$asset" -DestinationPath "$root\ffmpeg-extract" -Force
$inner = Get-ChildItem "$root\ffmpeg-extract" -Directory | Select-Object -First 1
Move-Item $inner.FullName "$root\ffmpeg"
# ------------------------------------------------------------------
# vcpkg (manifest mode) + caches
# ------------------------------------------------------------------
# Bootstrap a fresh clone rather than leaning on whatever vcpkg the
# image carries: `builtin-baseline`/`overrides` are only honored by
# a recent vcpkg-tool, and every platform must behave alike.
- name: Bootstrap vcpkg
if: matrix.platform != 'windows'
shell: bash
run: |
git clone https://github.com/microsoft/vcpkg.git .cache/vcpkg
.cache/vcpkg/bootstrap-vcpkg.sh -disableMetrics
echo "$PWD/.cache/vcpkg" >> "$GITHUB_PATH"
echo "VCPKG_ROOT=$PWD/.cache/vcpkg" >> "$GITHUB_ENV"
- name: Bootstrap vcpkg (Windows)
if: matrix.platform == 'windows'
shell: pwsh
run: |
git clone https://github.com/microsoft/vcpkg.git "$env:GITHUB_WORKSPACE\.cache\vcpkg"
& "$env:GITHUB_WORKSPACE\.cache\vcpkg\bootstrap-vcpkg.bat" -disableMetrics
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
"$env:GITHUB_WORKSPACE\.cache\vcpkg" >> $env:GITHUB_PATH
"VCPKG_ROOT=$env:GITHUB_WORKSPACE\.cache\vcpkg" >> $env:GITHUB_ENV
# The archives dir is vcpkg's binary cache: a manifest bump then
# rebuilds only what changed. Every run saves under a fresh key (so
# an existing cache is updated, never a save failure) and restores
# the newest matching entry through `restore-keys`. WarpBuild's
# cache service backs the Linux jobs (the GitHub Actions cache
# quota is full); macOS/Windows keep actions/cache.
- name: Restore vcpkg artifacts (WarpCache)
if: matrix.platform == 'linux'
id: vcpkg-cache
uses: WarpBuilds/cache/restore@v2
# A job container does not inherit the runner environment;
# WarpCache authenticates with this token (README: "Running
# inside a container").
env:
WARPBUILD_RUNNER_VERIFICATION_TOKEN: ${{ env.WARPBUILD_RUNNER_VERIFICATION_TOKEN }}
with:
path: |
vcpkg_installed
~/.cache/vcpkg/archives
key: vcpkg-${{ matrix.distro }}-${{ matrix.triplet }}-${{ hashFiles('vcpkg.json') }}-${{ github.run_id }}-${{ github.run_attempt }}
restore-keys: |
vcpkg-${{ matrix.distro }}-${{ matrix.triplet }}-${{ hashFiles('vcpkg.json') }}-
vcpkg-${{ matrix.distro }}-${{ matrix.triplet }}-
- name: Restore vcpkg artifacts (GitHub)
if: matrix.platform != 'linux'
id: vcpkg-cache-github
uses: actions/cache/restore@v6
with:
path: |
vcpkg_installed
~/.cache/vcpkg/archives
~/AppData/Local/vcpkg/archives
key: vcpkg-${{ runner.os }}-${{ matrix.triplet }}-${{ hashFiles('vcpkg.json') }}-${{ github.run_id }}-${{ github.run_attempt }}
restore-keys: |
vcpkg-${{ runner.os }}-${{ matrix.triplet }}-${{ hashFiles('vcpkg.json') }}-
vcpkg-${{ runner.os }}-${{ matrix.triplet }}-
- name: Install dependencies (vcpkg manifest)
if: matrix.platform != 'windows'
shell: bash
run: |
# Source tarballs come from third-party hosts (x264 lives on
# code.videolan.org); a transient connection failure aborts the
# whole install — vcpkg refuses to retry that class of curl
# error — so retry here. vcpkg resumes from its archive and
# download caches, so a repeat attempt is cheap.
for attempt in 1 2 3; do
vcpkg install --triplet ${{ matrix.triplet }} --overlay-triplets tooling/vcpkg-triplets/release --overlay-ports tooling/vcpkg-ports && exit 0
echo "vcpkg install failed (attempt $attempt); retrying"
sleep 15
done
exit 1
- name: Install dependencies (vcpkg manifest, Windows)
if: matrix.platform == 'windows'
shell: pwsh
run: |
for ($i = 1; $i -le 3; $i++) {
vcpkg install --triplet ${{ matrix.triplet }} --overlay-triplets tooling/vcpkg-triplets/release --overlay-ports tooling/vcpkg-ports
if ($LASTEXITCODE -eq 0) { exit 0 }
Write-Host "vcpkg install failed (attempt $i); retrying"
Start-Sleep -Seconds 15
}
exit 1
# Explicit restore/save pair: the old `save-always: true` on the
# combined step does not actually save on a failed job (the action
# deprecation warning), so a run that failed after the install left
# no binary cache and the next run rebuilt FFmpeg from source.
- name: Save vcpkg artifacts (WarpCache)
if: always() && matrix.platform == 'linux'
uses: WarpBuilds/cache/save@v2
env:
WARPBUILD_RUNNER_VERIFICATION_TOKEN: ${{ env.WARPBUILD_RUNNER_VERIFICATION_TOKEN }}
with:
path: |
vcpkg_installed
~/.cache/vcpkg/archives
key: ${{ steps.vcpkg-cache.outputs.cache-primary-key }}
- name: Save vcpkg artifacts (GitHub)
if: always() && matrix.platform != 'linux'
uses: actions/cache/save@v6
with:
path: |
vcpkg_installed
~/.cache/vcpkg/archives
~/AppData/Local/vcpkg/archives
key: ${{ steps.vcpkg-cache-github.outputs.cache-primary-key }}
# ------------------------------------------------------------------
# Build environment
# ------------------------------------------------------------------
# ocio-sys builds a stub bridge unless these are set; the oak-core
# ocioutils tests need the real library.
# tooling/ocio-env.sh: vendored static OCIO (the [patch.crates-io]
# ocio-sys tracks shaloong/ocio-rs main, whose vendored sources build
# on GCC >= 16).
- name: Configure build environment (Linux)
if: matrix.platform == 'linux'
shell: bash
run: |
{
echo "CC=clang"
echo "CXX=clang++"
} >> "$GITHUB_ENV"
bash tooling/ocio-env.sh >> "$GITHUB_ENV"
prefix="$PWD/vcpkg_installed/${{ matrix.triplet }}"
echo "FFMPEG_DIR=$prefix" >> "$GITHUB_ENV"
echo "PKG_CONFIG_PATH=$prefix/lib/pkgconfig" >> "$GITHUB_ENV"
echo "$prefix/tools/pkgconf" >> "$GITHUB_PATH"
# vcpkg's libva/libva-drm are shared libraries that FFmpeg links
# dynamically; without this path the loader picks a system libva
# that may predate symbols FFmpeg uses (undefined vaMapBuffer2).
echo "LD_LIBRARY_PATH=$prefix/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" >> "$GITHUB_ENV"
# The packaging tools resolve the ELF needs through ldd as well
# (dpkg-shlibdeps, linuxdeploy): register the vcpkg libs with the
# dynamic linker so `libva-drm.so.2` is found when a package is
# assembled.
echo "$prefix/lib" > /etc/ld.so.conf.d/oak-vcpkg.conf
ldconfig
- name: Configure build environment (macOS)
if: matrix.platform == 'macos'
shell: bash
run: |
# Vendored static OCIO (same as every non-Windows platform via
# tooling/ocio-env.sh); no OCIO_INSTALL_DIR override.
bash tooling/ocio-env.sh >> "$GITHUB_ENV"
prefix="$PWD/vcpkg_installed/${{ matrix.triplet }}"
echo "FFMPEG_DIR=$prefix" >> "$GITHUB_ENV"
echo "PKG_CONFIG_PATH=$prefix/lib/pkgconfig" >> "$GITHUB_ENV"
echo "$prefix/tools/pkgconf" >> "$GITHUB_PATH"
- name: Configure build environment (Windows)
if: matrix.platform == 'windows'
shell: pwsh
run: |
# FFmpeg comes from the prebuilt archive extracted above (the
# manifest no longer installs it on Windows); vcpkg only
# provides pkgconf for oak-ffmpeg-link's build script.
$prefix = "$env:GITHUB_WORKSPACE\vcpkg_installed\${{ matrix.triplet }}"
$ffmpeg = "$env:GITHUB_WORKSPACE\.cache\ffmpeg"
"FFMPEG_DIR=$ffmpeg" >> $env:GITHUB_ENV
"PKG_CONFIG_PATH=$ffmpeg\lib\pkgconfig" >> $env:GITHUB_ENV
# The test binaries load the FFmpeg DLLs: without this the
# runner reports STATUS_DLL_NOT_FOUND when the first test
# executable starts.
"$ffmpeg\bin" >> $env:GITHUB_PATH
"$prefix\tools\pkgconf" >> $env:GITHUB_PATH
# Bundled OCIO: ocio-sys' vendored sources build with the MSVC
# toolchain (what they need — the MSYS2 package was the
# workaround, not the preference), so no OCIO_INSTALL_DIR and
# no OCIO_RS_NO_MSVC_INCLUDES anywhere.
"OCIO_RS_ENABLE_REAL=1" >> $env:GITHUB_ENV
"OCIO_RS_LINK=static" >> $env:GITHUB_ENV
# Record the resolved versions in the build log (the manifest
# pins them via overrides + builtin-baseline).
vcpkg list
# ------------------------------------------------------------------
# Cargo caches: whole target/ dir plus ~/.cargo, shared per
# distro+arch (WarpCache for Linux, GitHub for macOS/Windows).
# ------------------------------------------------------------------
- name: Cache cargo artifacts (WarpCache)
if: matrix.platform == 'linux'
uses: WarpBuilds/rust-cache@v2
env:
WARPBUILD_RUNNER_VERIFICATION_TOKEN: ${{ env.WARPBUILD_RUNNER_VERIFICATION_TOKEN }}
with:
shared-key: oak-ci-${{ matrix.distro }}-${{ matrix.arch }}
cache-on-failure: true
- name: Cache cargo artifacts (GitHub)
if: matrix.platform != 'linux'
uses: Swatinem/rust-cache@v2
with:
shared-key: oak-ci-${{ matrix.distro }}-${{ matrix.arch }}
cache-on-failure: true
# ------------------------------------------------------------------
# Build & test
# ------------------------------------------------------------------
# `cargo check` (not build): the Test step links the test binaries
# anyway, and a full build would codegen every workspace crate twice
# (once without and once with cfg(test)).
- name: Build
run: cargo check --workspace --locked
# xvfb + 24-bit screen: the gpui #[gpui::test] tests open real
# windows and render through wgpu on Mesa's software Vulkan
# (lavapipe). The watchdog bounds the step: a deadlocked test
# produces no output and no failure, so after 1800 s it dumps every
# hung process's thread stacks and kills the suite.
- name: Test (Linux)
if: matrix.platform == 'linux'
timeout-minutes: 45
shell: bash
env:
# lavapipe is present in these images: a missing adapter must
# fail the GPU acceptance tests instead of silently skipping
# them (Debian is the x64 reference; the other distros keep the
# historical lenient policy).
OAK_REQUIRE_GPU: ${{ matrix.distro == 'debian' && '1' || '0' }}
run: |
run_suite() {
xvfb-run -a -s "-screen 0 1920x1080x24" cargo test --workspace --locked &
TEST_PID=$!
# The watchdog inherits the step's stdout/stderr; detach it so
# it cannot keep the runner's I/O pipes open after the step
# ends ("WaitDelay expired before I/O complete" otherwise).
(
sleep 1800
echo "::warning::test suite exceeded 1800s; dumping hung-process stacks"
for p in $(pgrep -f 'target/debug/deps/|target/debug/oak-worker'); do
echo "===== thread stacks of pid $p ($(readlink /proc/$p/exe 2>/dev/null)) ====="
gdb -batch -ex 'thread apply all bt' -p "$p" || true
done
pkill -9 -f 'target/debug/deps/' || true
pkill -9 -f 'target/debug/oak-worker' || true
) >/dev/null 2>&1 &
WATCHDOG_PID=$!
wait $TEST_PID
rc=$?
kill $WATCHDOG_PID 2>/dev/null || true
# Reap the watchdog so no child holds the step's pipes.
wait $WATCHDOG_PID 2>/dev/null || true
return $rc
}
# Retry once (same policy as the Windows job): worker-pool
# startup under full-suite parallelism has flaked once
# (full_res_worker_outlives_a_dropped_project: the render
# manager's process dispatcher failed to start while every other
# test passed). A real regression fails both passes.
if ! run_suite; then
echo "first pass failed; retrying once for worker-pool flakes"
run_suite
fi
# The runner's GUI session doubles as the display for the gpui
# #[gpui::test] windows; wgpu renders through Metal.
- name: Test (macOS)
if: matrix.platform == 'macos'
timeout-minutes: 40
run: |
# Retry once (same policy as the other platforms). A hang trips
# the in-script watchdog, which samples the test processes (the
# offending test's native stack lands in the log) before killing
# the suite.
run_suite() {
cargo test --workspace --locked &
TEST_PID=$!
(
sleep 900
echo "::warning::macOS test suite exceeded 900s; sampling hung processes"
for p in $(pgrep -f 'target/debug/deps/' || true); do
echo "===== sample of pid $p ====="
sample "$p" 2 10 2>&1 | head -120 || true
done
pkill -9 -f 'target/debug/deps/' || true
pkill -9 -f 'target/debug/oak-worker' || true
) &
WATCHDOG_PID=$!
wait $TEST_PID
rc=$?
kill $WATCHDOG_PID 2>/dev/null || true
wait $WATCHDOG_PID 2>/dev/null || true
return $rc
}
if ! run_suite; then
echo "first pass failed; retrying once for worker-pool flakes"
run_suite
fi
- name: Test (Windows)
if: matrix.platform == 'windows'
timeout-minutes: 40
shell: pwsh
run: |
cargo test --workspace --locked
if ($LASTEXITCODE -ne 0) {
# Retry once: a few gpui keystroke tests flake on Windows CI —
# a synthetic keystroke is occasionally never delivered (the
# undo/redo pair and a plain 's' toggle both failed once,
# each identically to its pass state). A real regression
# fails both passes.
Write-Host "first pass failed; retrying once for gpui keystroke flakes"
cargo test --workspace --locked
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
# ------------------------------------------------------------------
# Failure diagnostics
# ------------------------------------------------------------------
# A crashing (SIGSEGV) test gives no Rust backtrace; rerun the
# crashing test binaries under gdb to capture the native stack.
# `--args` is required — plain `--` makes gdb treat the test args as
# a core file. The extra probes target loader-stage crashes (the
# copier_test SIGSEGV happens inside ld.so's dl_main): si_addr/si_code
# pin down the fault type, the dynsym dump exposes symbols the
# executable exports for interposition, strace shows the last loader
# syscalls, and valgrind catches a corrupting static initializer.
- name: Backtrace on test failure (Debian)
if: failure() && matrix.distro == 'debian'
shell: bash
run: |
apt-get install -y gdb strace valgrind
for name in node_e2e_test suites_test copier_test; do
BIN=$(ls -t target/debug/deps/$name-* | grep -v '\.d$' | head -1)
[ -n "$BIN" ] || continue
echo "===== $BIN ====="
file "$BIN" || true
echo "--- exported defined dynsyms:"
readelf --dyn-syms -W "$BIN" 2>/dev/null | grep -v ' UND ' | tail -n +4 | head -30 || true
echo "--- strace tail:"
strace -f "$BIN" --list 2>&1 | tail -15 || true
echo "--- valgrind tail:"
valgrind -q "$BIN" --list 2>&1 | tail -25 || true
echo "--- gdb:"
xvfb-run -a gdb -batch \
-ex run \
-ex 'bt' \
-ex 'p $_siginfo.si_code' \
-ex 'p/x $_siginfo._sifields._sigfault.si_addr' \
-ex 'x/6i $rip' \
--args "$BIN" --nocapture || true
done
# Same idea for the other distro containers (gdb is installed with
# the system dependencies).
- name: Backtrace on test failure (containers)
if: failure() && matrix.platform == 'linux' && matrix.distro != 'debian'
shell: bash
run: |
for name in oak_render oakapp oak_plugin; do
BIN=$(ls -t target/debug/deps/$name-* 2>/dev/null | grep -v '\.d$' | head -1)
[ -n "$BIN" ] || continue
echo "===== $BIN ====="
timeout 900 xvfb-run -a gdb -batch \
-ex run \
-ex 'thread apply all bt' \
--args "$BIN" || true
done
# A SIGSEGV in a test binary gives no Rust backtrace; Apple's crash
# reports carry the native stack, so surface the newest ones.
- name: Crash reports (macOS)
if: failure() && matrix.platform == 'macos'
run: |
for f in $(ls -t ~/Library/Logs/DiagnosticReports/*.ips 2>/dev/null | head -3); do
echo "===== $f"
head -c 6000 "$f"
echo
done
# ------------------------------------------------------------------
# OFX plugin discovery end-to-end (x64 Linux reference)
# ------------------------------------------------------------------
# Build a minimal but real OFX plugin into a .ofx.bundle, point
# OFX_PLUGIN_PATH at it and let the scan_probe example run the full
# host path (directory scan -> dlopen -> setHost -> load -> describe
# -> register). The assertion is the plugin's registration line; CI
# machines have no system-wide OFX plugins, so the fixture is the
# only discovery.
- name: Build OFX fixture plugin
if: matrix.distro == 'debian'
run: crates/oak-plugin/tests/fixtures/build_fixture.sh .cache/ofx-fixture
- name: Probe OFX plugin discovery
if: matrix.distro == 'debian'
run: |
OFX_PLUGIN_PATH="$PWD/.cache/ofx-fixture" \
cargo run --locked -p oak-plugin --example scan_probe > probe.log 2>&1
grep -q 'type_id=rs.oak.CiTestPlugin' probe.log
# A project carrying a plugin node must survive save/load (the
# serializer resolves plugin types via the dynamic factory).
OAK_OFX_FIXTURE_DIR="$PWD/.cache/ofx-fixture" \
cargo test --locked -p oak-plugin --test ofx_roundtrip