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 # FFmpeg build + 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 needs 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 cargo/FFmpeg 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" )) { 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): # The prebuilt 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 the # project FFmpeg build (tooling/ffmpeg/build-ffmpeg.sh) requires. # 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 source 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" # ------------------------------------------------------------------ # FFmpeg + caches # ------------------------------------------------------------------ # The Linux/macOS jobs build the project FFmpeg from source with the # distro's codec packages — the same path local builds use # (tooling/install-deps.sh + tooling/ffmpeg/build-ffmpeg.sh; the # latter also drives the vendored static OCIO). Windows uses the # prebuilt archive downloaded above. - name: Install FFmpeg dependencies if: matrix.platform != 'windows' shell: bash run: tooling/install-deps.sh # The built FFmpeg is cached as a whole (keyed on the script, distro # and arch): a cache hit skips the ~10 minute build. The # `.build-complete` marker distinguishes a finished build from a # partial cache save; without it the tree is rebuilt. - name: Restore FFmpeg (WarpCache) if: matrix.platform == 'linux' id: ffmpeg-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: .cache/ffmpeg key: ffmpeg-${{ matrix.distro }}-${{ matrix.arch }}-${{ hashFiles('tooling/ffmpeg/build-ffmpeg.sh') }}-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: | ffmpeg-${{ matrix.distro }}-${{ matrix.arch }}-${{ hashFiles('tooling/ffmpeg/build-ffmpeg.sh') }}- ffmpeg-${{ matrix.distro }}-${{ matrix.arch }}- - name: Restore FFmpeg (GitHub) if: matrix.platform == 'macos' id: ffmpeg-cache-github uses: actions/cache/restore@v6 with: path: .cache/ffmpeg key: ffmpeg-${{ runner.os }}-${{ matrix.arch }}-${{ hashFiles('tooling/ffmpeg/build-ffmpeg.sh') }}-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: | ffmpeg-${{ runner.os }}-${{ matrix.arch }}-${{ hashFiles('tooling/ffmpeg/build-ffmpeg.sh') }}- ffmpeg-${{ runner.os }}-${{ matrix.arch }}- - name: Build FFmpeg if: matrix.platform != 'windows' shell: bash run: | if [ -f .cache/ffmpeg/.build-complete ]; then echo "FFmpeg restored from cache; skipping the build" else rm -rf .cache/ffmpeg tooling/ffmpeg/build-ffmpeg.sh # The cargo cache may hold ffmpeg-sys-next rlibs that bundled # the previous archives (cargo does not track archive # changes); flag a refresh so the build relinks the new one. echo "FFMPEG_REBUILT=1" >> "$GITHUB_ENV" fi - name: Save FFmpeg (WarpCache) if: always() && matrix.platform == 'linux' uses: WarpBuilds/cache/save@v2 env: WARPBUILD_RUNNER_VERIFICATION_TOKEN: ${{ env.WARPBUILD_RUNNER_VERIFICATION_TOKEN }} with: path: .cache/ffmpeg key: ${{ steps.ffmpeg-cache.outputs.cache-primary-key }} - name: Save FFmpeg (GitHub) if: always() && matrix.platform == 'macos' uses: actions/cache/save@v6 with: path: .cache/ffmpeg key: ${{ steps.ffmpeg-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" ffmpeg="$PWD/.cache/ffmpeg" echo "FFMPEG_DIR=$ffmpeg" >> "$GITHUB_ENV" echo "PKG_CONFIG_PATH=$ffmpeg/lib/pkgconfig" >> "$GITHUB_ENV" - 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" ffmpeg="$PWD/.cache/ffmpeg" echo "FFMPEG_DIR=$ffmpeg" >> "$GITHUB_ENV" echo "PKG_CONFIG_PATH=$ffmpeg/lib/pkgconfig" >> "$GITHUB_ENV" - name: Configure build environment (Windows) if: matrix.platform == 'windows' shell: pwsh run: | # FFmpeg comes from the prebuilt archive extracted above. $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 # 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 # ------------------------------------------------------------------ # 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 # A freshly built FFmpeg must not keep linking the archives the # cargo cache bundled in ffmpeg-sys-next's rlib (cargo does not # track them); drop just that package so the link picks up the new # .cache/ffmpeg. Skipped on cache hits and on Windows (prebuilt). - name: Refresh the FFmpeg binding if: matrix.platform != 'windows' && env.FFMPEG_REBUILT == '1' run: cargo clean -p ffmpeg-sys-next # ------------------------------------------------------------------ # 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