diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index f606a442d..c26109f94 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -1,10 +1,15 @@ -# actionlint configuration: whitelist the WarpBuild runner labels used by -# the CI/CD workflows so `actionlint ci.yml` / `actionlint cd.yml` passes. -# See https://github.com/rhysd/actionlint/blob/main/docs/config.md +# actionlint configuration (dev tooling; not used by GitHub Actions). +# +# WarpBuild's runner labels are not in actionlint's built-in list; declare +# the sizes this repository uses so `actionlint` stops warning about them. self-hosted-runner: labels: - warp-ubuntu-latest-x64-8x + - warp-ubuntu-latest-x64-16x + - warp-ubuntu-latest-x64-32x - warp-ubuntu-latest-arm64-16x - - warp-windows-latest-x64-16x - - warp-windows-2025-vs2026-x64-16x + - warp-ubuntu-latest-arm64-32x - warp-macos-26-arm64-6x + - warp-macos-26-arm64-12x + - warp-windows-2025-vs2026-x64-16x + - warp-windows-2025-vs2026-x64-32x diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index c8968e6d7..bffd69b36 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -9,400 +9,109 @@ on: permissions: contents: write -# Release packages are built from scratch: CD uses no vcpkg/cargo caches. -# A restored vcpkg_installed or target tree has masked packaging problems -# before (stale ports, missing tools); a release build must not depend on -# restored state. +# One matrix, seven platforms, the same environments CI tests in (see +# .github/workflows/ci.yml): Debian 12 / Fedora 41 / Arch / openKylin x64 +# and arm64 containers plus the macOS and Windows hosts. Every package is +# built from scratch — no vcpkg/cargo caches: a restored vcpkg_installed +# or target tree has masked packaging problems before (stale ports, +# missing tools), and a release must not depend on restored state. jobs: - # ------------------------------------------------------------------ - # Linux: deb + AppImage + pacman in one job. cargo-packager does not - # support rpm (its format list is deb/appimage/pacman/nsis/dmg/app/wix), - # and its "pacman" format emits a PKGBUILD + source tarball rather than a - # compiled pkg.tar.zst — both are upstream limitations. - # ------------------------------------------------------------------ - # ------------------------------------------------------------------ - # Linux: one native package per distro, each built INSIDE that - # distro's container so the declared dependencies always resolve to - # the distro's own package names (dpkg-shlibdeps / rpmbuild - # auto-requires / Arch static base list). The FFmpeg/codec libraries - # come from the vcpkg manifest (root vcpkg.json, the distro/arch - # triplet), so these containers carry the build toolchain and the - # headless/UI runtime deps only. deb: hand-rolled dpkg-deb, built once - # in debian:12 (the general Debian-family package, labeled "+debian") - # and once per openKylin arch (x64/arm64, labeled "+openkylin") so each - # family gets deps that resolve against its own repos; rpm: rpmbuild; - # arch: makepkg. AppImage stays on the Ubuntu runner (self-contained by - # design). - # ------------------------------------------------------------------ - linux: - name: Linux packages (${{ matrix.distro }} ${{ matrix.arch }}) + package: + name: Package (${{ matrix.name }}) runs-on: ${{ matrix.runner }} - container: ${{ matrix.image }} - + # 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) || '' }} + # Cold vcpkg install + release build + packaging. + timeout-minutes: 150 strategy: fail-fast: false matrix: include: - - distro: debian - image: debian:12 + - name: Debian + platform: linux + distro: debian arch: x64 - runner: warp-ubuntu-latest-x64-16x + runner: warp-ubuntu-latest-x64-32x triplet: x64-linux - - distro: fedora - image: fedora:41 + artifact: linux-debian + container: '{"image":"debian:12","options":"--shm-size=8g"}' + - name: Fedora + platform: linux + distro: fedora arch: x64 - runner: warp-ubuntu-latest-x64-16x + runner: warp-ubuntu-latest-x64-32x triplet: x64-linux - - distro: arch - image: archlinux:latest + artifact: linux-fedora + container: '{"image":"fedora:41","options":"--shm-size=8g"}' + - name: Arch + platform: linux + distro: arch arch: x64 - runner: warp-ubuntu-latest-x64-16x + runner: warp-ubuntu-latest-x64-32x triplet: x64-linux - - distro: openkylin - image: openkylin/openkylin:latest + artifact: linux-arch + container: '{"image":"archlinux:latest","options":"--shm-size=8g"}' + - name: openKylin x64 + platform: linux + distro: openkylin arch: x64 - runner: warp-ubuntu-latest-x64-16x + runner: warp-ubuntu-latest-x64-32x triplet: x64-linux - - distro: openkylin - image: openkylin/openkylin:latest + artifact: linux-openkylin-x64 + container: '{"image":"openkylin/openkylin:latest","options":"--shm-size=8g"}' + - name: openKylin arm64 + platform: linux + distro: openkylin arch: arm64 runner: warp-ubuntu-latest-arm64-32x triplet: arm64-linux + artifact: linux-openkylin-arm64 + 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 + artifact: macos + container: '' + - name: Windows + platform: windows + distro: windows + arch: x64 + runner: warp-windows-2025-vs2026-x64-32x + triplet: x64-windows + artifact: windows + container: '' steps: - # git/curl must land BEFORE actions/checkout runs inside the - # container. - - name: Install git and fetch tools - run: | - case "${{ matrix.distro }}" in - debian|openkylin) apt-get update && apt-get install -y git curl ;; - fedora) dnf install -y git curl ;; - arch) pacman -Sy --noconfirm git curl ;; - esac - - - name: Checkout - uses: actions/checkout@v7 - with: - submodules: true - - # The openKylin image runs as root with HOME=/github/home; rustup - # refuses the euid mismatch and installs a toolchain the later steps - # cannot find. Pin the whole job to root's home (same as the CI - # openKylin jobs). - - name: Pin HOME for rustup (openKylin) - if: matrix.distro == 'openkylin' - run: | - { - echo "HOME=/root" - echo "CARGO_HOME=/root/.cargo" - echo "RUSTUP_HOME=/root/.rustup" - } >> "$GITHUB_ENV" - - - name: Install Rust (stable) - uses: dtolnay/rust-toolchain@stable - - - name: Install system dependencies - run: | - case "${{ matrix.distro }}" in - debian) - 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 \ - gdb xvfb libdrm-dev librsvg2-bin autoconf autoconf-archive automake libtool - ;; - fedora) - dnf install -y \ - gcc gcc-c++ clang clang-devel cmake pkgconf-pkg-config nasm \ - git curl zip unzip tar python3 \ - 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 autoconf autoconf-archive automake libtool - ;; - arch) - pacman -S --needed --noconfirm \ - base-devel clang cmake pkgconf nasm \ - git curl zip unzip tar python \ - pipewire jack2 alsa-lib libpulse libsndfile \ - mesa vulkan-headers vulkan-icd-loader \ - libxkbcommon libxkbcommon-x11 librsvg libdrm autoconf autoconf-archive automake libtool - ;; - openkylin) - # The CI openKylin build set plus dpkg-dev (dpkg-shlibdeps - # computes the runtime deps) and librsvg2-bin (app icon). - 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 \ - libdrm-dev file librsvg2-bin \ - autoconf autoconf-archive automake libtool - ;; - esac - - # ------------------------------------------------------------------ - # vcpkg (manifest mode) — built from scratch, no caches (see the - # policy note at the top of this file) - # ------------------------------------------------------------------ - - name: Bootstrap vcpkg - 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: Install dependencies (vcpkg manifest) - run: vcpkg install --triplet ${{ matrix.triplet }} - - - name: Configure build environment - run: | - { - echo "CC=clang" - echo "CXX=clang++" - } >> "$GITHUB_ENV" - # tooling/ocio-env.sh: vendored static OCIO everywhere it - # builds (the [patch.crates-io] ocio-sys tracks shaloong/ocio-rs - # main, whose vendored sources build on GCC >= 16). - 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: Build (release) - run: cargo build --release --locked - - - name: Generate app icon (PNG from Oak_Icon.svg) - run: | - mkdir -p icons - if command -v rsvg-convert >/dev/null 2>&1; then - rsvg-convert -w 512 -h 512 Oak_Icon.svg -o icons/icon.png - else - # Defensive: some containers (openKylin) may not carry - # librsvg2-bin; fall back to the committed 512x512 render. - cp assets/app-icon.png icons/icon.png - fi - - - name: Package - run: | - set -euo pipefail - # The release version lives in [workspace.package] of the root - # Cargo.toml (single source of truth; tags do not carry it). - VERSION=$(sed -n '/^\[workspace\.package\]/,/^\[/s/^version = "\(.*\)"/\1/p' Cargo.toml | head -1) - case "${{ matrix.distro }}" in - debian) tooling/package/build-deb.sh "$VERSION" debian ;; - openkylin) tooling/package/build-deb.sh "$VERSION" openkylin ;; - fedora) tooling/package/build-rpm.sh "$VERSION" ;; - arch) tooling/package/build-pkg.sh "$VERSION" ;; - esac + # The container images are bare (Fedora/Arch even lack git); + # checkout and vcpkg need git/curl. 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 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: Upload artifact - uses: actions/upload-artifact@v7 - with: - name: oak-linux-${{ matrix.distro }}-${{ matrix.arch }} - path: | - target/release/*.deb - target/release/*.rpm - target/release/*.pkg.tar.zst - if-no-files-found: error - - # ------------------------------------------------------------------ - # AppImage (self-contained; cargo-packager on the Ubuntu runner). - # ------------------------------------------------------------------ - appimage: - name: Linux AppImage - runs-on: warp-ubuntu-latest-x64-16x - - steps: - - name: Checkout - uses: actions/checkout@v7 - with: - submodules: true - - - name: Install Rust (stable) - uses: dtolnay/rust-toolchain@stable - - - name: Install system dependencies - run: | - sudo apt-get update - # FFmpeg/codec libraries come from the vcpkg manifest (root - # vcpkg.json); this list is the toolchain vcpkg needs plus the - # GUI runtime deps the AppImage bundles. - sudo apt-get install -y \ - build-essential clang libclang-dev cmake pkg-config nasm \ - git curl zip unzip tar python3 \ - librsvg2-bin \ - 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 libdrm-dev \ - autoconf autoconf-archive automake libtool - - # ------------------------------------------------------------------ - # vcpkg (manifest mode) — built from scratch, no caches (see the - # policy note at the top of this file) - # ------------------------------------------------------------------ - - name: Bootstrap vcpkg - run: | - # Full clone (no --depth): the manifest pins a builtin-baseline - # and port trees, and a shallow vcpkg cannot check them out - # ("failed to unpack tree object ... Try again with a full - # vcpkg clone"). - 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: Install dependencies (vcpkg manifest) - run: vcpkg install --triplet x64-linux - - - name: Configure build environment - run: | - bash tooling/ocio-env.sh >> "$GITHUB_ENV" - prefix="$PWD/vcpkg_installed/x64-linux" - echo "FFMPEG_DIR=$prefix" >> "$GITHUB_ENV" - echo "PKG_CONFIG_PATH=$prefix/lib/pkgconfig" >> "$GITHUB_ENV" - echo "$prefix/tools/pkgconf" >> "$GITHUB_PATH" - - - name: Install cargo-packager - run: cargo install cargo-packager --locked - - - name: Generate app icon (PNG from Oak_Icon.svg) - run: | - mkdir -p icons - rsvg-convert -w 512 -h 512 Oak_Icon.svg -o icons/icon.png - file icons/icon.png - - - name: Build (release) - run: cargo build --release --locked - - - name: Package (AppImage) - run: cargo packager --release --formats appimage - - - name: Upload artifact - uses: actions/upload-artifact@v7 - with: - name: oak-linux-appimage - path: target/release/*.AppImage - if-no-files-found: error - - macos: - name: macOS DMG (Apple Silicon) - runs-on: warp-macos-26-arm64-12x - - steps: - - name: Checkout - uses: actions/checkout@v7 - with: - submodules: true - - - name: Install Rust (stable) - uses: dtolnay/rust-toolchain@stable - - - name: Install system dependencies - 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. librsvg stays for - # rsvg-convert (app icon) and is bundled into the .app below. - brew install cmake pkg-config nasm librsvg nasm autoconf automake libtool autoconf-archive - - # ------------------------------------------------------------------ - # vcpkg (manifest mode) — built from scratch, no caches (see the - # policy note at the top of this file) - # ------------------------------------------------------------------ - - name: Bootstrap vcpkg - 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: Install dependencies (vcpkg manifest) - run: vcpkg install --triplet arm64-osx - - - name: Configure build environment - 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/arm64-osx" - echo "FFMPEG_DIR=$prefix" >> "$GITHUB_ENV" - echo "PKG_CONFIG_PATH=$prefix/lib/pkgconfig" >> "$GITHUB_ENV" - echo "$prefix/tools/pkgconf" >> "$GITHUB_PATH" - - - name: Install cargo-packager - run: cargo install cargo-packager --locked - - - name: Generate app icon (PNG from Oak_Icon.svg) - run: | - mkdir -p icons - # cargo-packager's tauri-icns 0.1.0 maps only 512x512@1x (and - # 1024x1024@2x); a plain 1024x1024 PNG aborts with "No matching - # IconType", so render 512x512. - rsvg-convert -w 512 -h 512 Oak_Icon.svg -o icons/icon.png - file icons/icon.png - - # Build the packaged binaries (default members: the app, oak-cli, - # oak-worker). - - name: Build (release) - run: cargo build --release --locked - - - name: Package .app bundle - run: cargo packager --release --formats app - - # Pull the Homebrew dylibs the binaries reference into - # Contents/Frameworks and rewrite install names to - # @executable_path-relative (the script ad-hoc re-signs the bundle). - - name: Bundle dylibs into the .app - run: tooling/package/bundle-dylibs-macos.sh target/release/Oak.app - - - name: Create DMG - run: | - rm -rf dmg-staging - mkdir -p dmg-staging - cp -R target/release/Oak.app dmg-staging/ - ln -s /Applications dmg-staging/Applications - hdiutil create -volname "Oak Video Editor" \ - -srcfolder dmg-staging -ov -format UDZO Oak-macOS-arm64.dmg - - - name: Upload artifact - uses: actions/upload-artifact@v7 - with: - name: oak-macos - path: Oak-macOS-arm64.dmg - if-no-files-found: error - - # ------------------------------------------------------------------ - # Windows: NSIS installer (restored; cargo-packager downloads its own - # makensis, SHA-1 verified). The obsolete `-p oakengine` cdylib prebuild - # from before M14 R4 is dropped — no packaged binary links the cdylib. - # ------------------------------------------------------------------ - windows: - name: Windows installer (NSIS) - runs-on: warp-windows-2025-vs2026-x64-32x - - steps: - 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 # Defender's real-time scanning slows the MSVC/vcpkg build down # badly; disable it for the job and keep exclusions as the fallback - # when policy blocks the change (same step as the CI Windows job). + # when policy blocks the change. - name: Disable Windows Defender scanning + if: matrix.platform == 'windows' shell: pwsh run: | try { @@ -429,21 +138,120 @@ jobs: Write-Host "Defender status unavailable: $_" } - - name: Install Rust (stable, MSVC) + # 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: stable-x86_64-pc-windows-msvc + toolchain: ${{ matrix.platform == 'windows' && 'stable-x86_64-pc-windows-msvc' || 'stable' }} - # vcpkg.json at the repo root pins the dependency set (FFmpeg with - # every free codec + hwaccel, pkgconf, librsvg); the resolved tree - # lands in vcpkg_installed/ and is built from scratch (no cache, see - # the policy note at the top). Bootstrapping a fresh clone rather - # than leaning on whatever vcpkg the image carries: the - # `builtin-baseline`/`overrides` are only honored by a recent - # vcpkg-tool, and every OS job must behave alike. + # ------------------------------------------------------------------ + # System dependencies — one list per distro, byte-for-byte the same + # lists CI uses (see .github/workflows/ci.yml): what compiles there + # compiles 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: | + dnf install -y \ + gcc gcc-c++ clang clang-devel cmake pkgconf-pkg-config nasm \ + git curl zip unzip tar python3 patch xz-utils 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 \ + 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-utils 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 stays for rsvg-convert (app + # icon) and is bundled into the .app by the dylib script. + brew install cmake pkg-config nasm librsvg autoconf automake libtool autoconf-archive + + # ------------------------------------------------------------------ + # vcpkg (manifest mode) — built from scratch, no 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 @@ -452,11 +260,78 @@ jobs: "VCPKG_ROOT=$env:GITHUB_WORKSPACE\.cache\vcpkg" >> $env:GITHUB_ENV - name: Install dependencies (vcpkg manifest) - run: vcpkg install --triplet x64-windows - - - name: Configure build environment + if: matrix.platform != 'windows' + shell: bash run: | - $prefix = "$env:GITHUB_WORKSPACE\vcpkg_installed\x64-windows" + # 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. + for attempt in 1 2 3; do + vcpkg install --triplet ${{ matrix.triplet }} && 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 }} + if ($LASTEXITCODE -eq 0) { exit 0 } + Write-Host "vcpkg install failed (attempt $i); retrying" + Start-Sleep -Seconds 15 + } + exit 1 + + # ------------------------------------------------------------------ + # 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" + echo "LD_LIBRARY_PATH=$prefix/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" >> "$GITHUB_ENV" + # The Debian-family packaging tools resolve the ELF needs through + # ldd (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: | + $prefix = "$env:GITHUB_WORKSPACE\vcpkg_installed\${{ matrix.triplet }}" "FFMPEG_DIR=$prefix" >> $env:GITHUB_ENV "PKG_CONFIG_PATH=$prefix\lib\pkgconfig" >> $env:GITHUB_ENV "$prefix\tools\pkgconf" >> $env:GITHUB_PATH @@ -469,41 +344,129 @@ jobs: vcpkg list - name: Install cargo-packager + if: matrix.distro == 'debian' || matrix.platform != 'linux' run: cargo install cargo-packager --locked - name: Generate app icon (PNG from Oak_Icon.svg) + if: matrix.platform != 'windows' run: | - # vcpkg's librsvg port builds with -Drsvg-convert=disabled (it - # ships the library only and provides no rsvg-convert.exe), so - # use the committed 512x512 render of Oak_Icon.svg. - New-Item -ItemType Directory -Force icons | Out-Null - Copy-Item assets/app-icon.png icons/icon.png + mkdir -p icons + if command -v rsvg-convert >/dev/null 2>&1; then + rsvg-convert -w 512 -h 512 Oak_Icon.svg -o icons/icon.png + else + # Defensive: some containers may not carry an SVG renderer; + # fall back to the committed 512x512 render. + cp assets/app-icon.png icons/icon.png + fi + # ------------------------------------------------------------------ + # Build + # ------------------------------------------------------------------ + # Default dynamic CRT on Windows: the vendored OCIO is compiled /MD, + # so forcing Rust to /MT fails with LNK2038 'RuntimeLibrary' + # mismatch; the redistributable DLLs ship with the installer below. - name: Build (release) + run: cargo build --release --locked + + # ------------------------------------------------------------------ + # Package + # ------------------------------------------------------------------ + - name: Package (Linux) + if: matrix.platform == 'linux' + shell: bash run: | - # Static CRT: the installer then needs no vcruntime DLLs (the - # vcpkg DLLs below are the only runtime pieces to bundle). - $env:RUSTFLAGS = "-C target-feature=+crt-static" - cargo build --release --locked + set -euo pipefail + # The release version lives in [workspace.package] of the root + # Cargo.toml (single source of truth; tags do not carry it). + VERSION=$(sed -n '/^\[workspace\.package\]/,/^\[/s/^version = "\(.*\)"/\1/p' Cargo.toml | head -1) + case "${{ matrix.distro }}" in + debian) + # The general Debian-family package, labeled "+debian". + VCPKG_LIB="$PWD/vcpkg_installed/${{ matrix.triplet }}/lib" \ + tooling/package/build-deb.sh "$VERSION" debian + # appimagetool self-extracts instead of mounting (containers + # have no FUSE). + APPIMAGE_EXTRACT_AND_RUN=1 cargo packager --release --formats appimage + ;; + fedora) + tooling/package/build-rpm.sh "$VERSION" + ;; + arch) + tooling/package/build-pkg.sh "$VERSION" + ;; + openkylin) + # The openKylin build, labeled "+openkylin"; dpkg-shlibdeps + # resolves the runtime deps against openKylin's own repos + # and the vcpkg libva/libdrm ship next to the app (openKylin's + # system libva predates FFmpeg's vaMapBuffer2). + VCPKG_LIB="$PWD/vcpkg_installed/${{ matrix.triplet }}/lib" \ + tooling/package/build-deb.sh "$VERSION" openkylin + ;; + esac + + - name: Package (macOS) + if: matrix.platform == 'macos' + run: | + cargo packager --release --formats app + # cargo-packager names the bundle after the packager + # `productName` ("Oak Video Editor.app"), so resolve it instead + # of guessing. + APP="$(ls -d target/release/*.app | head -1)" + tooling/package/bundle-dylibs-macos.sh "$APP" + rm -rf dmg-staging + mkdir -p dmg-staging + cp -R "$APP" dmg-staging/ + ln -s /Applications dmg-staging/Applications + hdiutil create -volname "Oak Video Editor" \ + -srcfolder dmg-staging -ov -format UDZO Oak-macOS-arm64.dmg - # The vcpkg runtime DLLs (avcodec/avformat/... and the codec libs) - # ship next to the executables: copy them into target/pkg/win-dlls, - # which the packager `resources` glob installs alongside (the MSVC - # CRT itself is covered by the toolchain's static linking story; - # OCIO is bundled statically). - name: Bundle runtime DLLs + if: matrix.platform == 'windows' + shell: pwsh run: | New-Item -ItemType Directory -Force target/pkg/win-dlls | Out-Null - Copy-Item "vcpkg_installed\x64-windows\bin\*.dll" target/pkg/win-dlls/ + # vcpkg's dynamic libs (FFmpeg + codecs). + Copy-Item "vcpkg_installed\${{ matrix.triplet }}\bin\*.dll" target/pkg/win-dlls/ + # The MSVC runtime: the build keeps the default dynamic CRT (see + # the Build step), so ship the redistributable DLLs app-locally. + $crt = Get-ChildItem "$env:ProgramFiles\Microsoft Visual Studio\*\*\VC\Redist\MSVC\*\x64\Microsoft.VC*.CRT" -Directory -ErrorAction SilentlyContinue | + Sort-Object FullName | Select-Object -Last 1 + if (-not $crt) { throw "MSVC CRT redist directory not found" } + Copy-Item "$($crt.FullName)\*.dll" target/pkg/win-dlls/ - name: Package (NSIS) + if: matrix.platform == 'windows' + shell: pwsh run: cargo packager --release --formats nsis + # ------------------------------------------------------------------ + # Upload + # ------------------------------------------------------------------ + - name: Stage artifacts + if: matrix.platform != 'windows' + shell: bash + run: | + mkdir -p dist + cp target/release/*.deb dist/ 2>/dev/null || true + cp target/release/*.rpm dist/ 2>/dev/null || true + cp target/release/*.pkg.tar.zst dist/ 2>/dev/null || true + cp target/release/*.AppImage dist/ 2>/dev/null || true + cp ./*.dmg dist/ 2>/dev/null || true + ls -la dist + + - name: Stage artifacts (Windows) + if: matrix.platform == 'windows' + shell: pwsh + run: | + New-Item -ItemType Directory -Force dist | Out-Null + Copy-Item target/release/*-setup.exe dist/ + Get-ChildItem dist + - name: Upload artifact uses: actions/upload-artifact@v7 with: - name: oak-windows - path: target/release/*-setup.exe + name: oak-${{ matrix.artifact }} + path: dist/* if-no-files-found: error # ------------------------------------------------------------------ @@ -512,9 +475,9 @@ jobs: # ------------------------------------------------------------------ release: name: Publish GitHub release - needs: [linux, appimage, macos, windows] + needs: [package] if: startsWith(github.ref, 'refs/tags/v') - runs-on: warp-ubuntu-latest-x64-8x + runs-on: warp-ubuntu-latest-x64-32x steps: - name: Download all artifacts uses: actions/download-artifact@v8 @@ -529,4 +492,3 @@ jobs: name: ${{ github.ref_name }} draft: false files: artifacts/* - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4608b8330..552e3f92c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,11 +15,91 @@ concurrency: permissions: contents: read +# One matrix, seven platforms: the three Linux-family packaging distros +# (Debian 12, Fedora 41, 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: - linux: - name: Build & test (Linux) - runs-on: warp-ubuntu-latest-x64-8x + 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-32x + triplet: x64-linux + container: '{"image":"debian:12","options":"--shm-size=8g"}' + - name: Fedora + platform: linux + distro: fedora + arch: x64 + runner: warp-ubuntu-latest-x64-32x + triplet: x64-linux + container: '{"image":"fedora:41","options":"--shm-size=8g"}' + - name: Arch + platform: linux + distro: arch + arch: x64 + runner: warp-ubuntu-latest-x64-32x + 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-32x + 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-32x + 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 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: @@ -27,254 +107,14 @@ jobs: # their own repo and build as path dependencies of oakapp. submodules: true - - name: Install Rust (stable) - uses: dtolnay/rust-toolchain@stable - - # ------------------------------------------------------------------ - # System dependencies - # ------------------------------------------------------------------ - - name: Install system dependencies - run: | - sudo apt-get update - # The codec/filter libraries behind FFmpeg come from the vcpkg - # manifest (root vcpkg.json; see docs/build.md) — this list is - # the toolchain vcpkg itself needs, cmake/make for the vendored - # OpenColorIO build (ocio-sys `bundled`; Ubuntu's - # libopencolorio-dev is 2.1, older than the bridge's API floor), - # and the headless test infra gpui needs: X11, software Mesa - # Vulkan (lavapipe) and xvfb. `icc-profiles-free` gives the - # oak-core display-ICC tests a system profile (sRGB.icc); without - # it `color.rs::system_icc` finds nothing on this runner and the - # viewer black-screen guard silently skips. - sudo apt-get install -y \ - build-essential clang libclang-dev cmake pkg-config nasm \ - git curl zip unzip tar python3 \ - 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 \ - autoconf autoconf-archive automake libtool - - # ------------------------------------------------------------------ - # vcpkg (manifest mode) + caches - # ------------------------------------------------------------------ - # The runner has no vcpkg preinstalled; bootstrap a fresh clone. - # The manifest at the repo root pins the dependency set (FFmpeg 8.1.2 - # via `overrides`, everything else via `builtin-baseline`) and the - # resolved tree lands in vcpkg_installed/. - - name: Bootstrap vcpkg - 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" - - # 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 this job (the GitHub Actions - # cache quota is full); macOS/Windows keep actions/cache. - - name: Restore vcpkg artifacts - id: vcpkg-cache - uses: WarpBuilds/cache/restore@v2 - with: - path: | - vcpkg_installed - ~/.cache/vcpkg/archives - key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json') }}-${{ github.run_id }}-${{ github.run_attempt }} - restore-keys: | - vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json') }}- - vcpkg-${{ runner.os }}- - - - name: Install dependencies (vcpkg manifest) - 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 x64-linux && exit 0 - echo "vcpkg install failed (attempt $attempt); retrying" - sleep 15 - done - 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 - # (~40 min on Windows). - - name: Save vcpkg artifacts - if: always() - uses: WarpBuilds/cache/save@v2 - with: - path: | - vcpkg_installed - ~/.cache/vcpkg/archives - key: ${{ steps.vcpkg-cache.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 - run: | - bash tooling/ocio-env.sh >> "$GITHUB_ENV" - prefix="$PWD/vcpkg_installed/x64-linux" - 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" - - # ------------------------------------------------------------------ - # Caches - # ------------------------------------------------------------------ - # Covers the whole target/ dir plus ~/.cargo; shared across branches - # of the same OS. - - name: Cache cargo artifacts - uses: WarpBuilds/rust-cache@v2 - with: - shared-key: oak-ci-linux - 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 - timeout-minutes: 40 - env: - # lavapipe is present on this job: a missing adapter must fail - # the GPU acceptance tests instead of silently skipping them. - OAK_REQUIRE_GPU: "1" - run: | - sudo apt-get install -y gdb - 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)) =====" - sudo 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 - - # 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 - if: failure() - run: | - sudo 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 - - # ------------------------------------------------------------------ - # OFX plugin discovery end-to-end - # ------------------------------------------------------------------ - # 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 - run: crates/oak-plugin/tests/fixtures/build_fixture.sh .cache/ofx-fixture - - - name: Probe OFX plugin discovery - 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 - - windows: - name: Build & test (Windows) - runs-on: warp-windows-2025-vs2026-x64-32x - steps: - - 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). + # 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 { @@ -301,24 +141,119 @@ jobs: Write-Host "Defender status unavailable: $_" } - - name: Install Rust (stable, MSVC) + # 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: stable-x86_64-pc-windows-msvc + 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: | + dnf install -y \ + gcc gcc-c++ clang clang-devel cmake pkgconf-pkg-config nasm \ + git curl zip unzip tar python3 patch xz-utils 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 \ + 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-utils 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 # ------------------------------------------------------------------ # vcpkg (manifest mode) + caches # ------------------------------------------------------------------ - # vcpkg.json at the repo root pins the dependency set (FFmpeg with - # every free codec + hwaccel, pkgconf, librsvg); the resolved tree - # lands in vcpkg_installed/ and is keyed on the manifest. The - # binary-cache archives dir makes a manifest bump rebuild cheap. # 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 all three OS jobs must behave alike. + # 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 @@ -326,48 +261,146 @@ jobs: "$env:GITHUB_WORKSPACE\.cache\vcpkg" >> $env:GITHUB_PATH "VCPKG_ROOT=$env:GITHUB_WORKSPACE\.cache\vcpkg" >> $env:GITHUB_ENV - - name: Restore vcpkg artifacts + # 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 }}-${{ hashFiles('vcpkg.json') }}-${{ github.run_id }}-${{ github.run_attempt }} + key: vcpkg-${{ runner.os }}-${{ matrix.triplet }}-${{ hashFiles('vcpkg.json') }}-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: | - vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json') }}- - vcpkg-${{ runner.os }}- + 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 }} && 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: | - # See the Linux job: transient source-download failures abort - # the manifest install; retry before giving up. for ($i = 1; $i -le 3; $i++) { - vcpkg install --triplet x64-windows + vcpkg install --triplet ${{ matrix.triplet }} if ($LASTEXITCODE -eq 0) { exit 0 } Write-Host "vcpkg install failed (attempt $i); retrying" Start-Sleep -Seconds 15 } exit 1 - # Explicit restore/save pair (see the Linux job: `save-always` did - # not save after a failed job, so Windows rebuilt FFmpeg from source - # for ~40 minutes on every run). - - name: Save vcpkg artifacts - if: always() + # 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.outputs.cache-primary-key }} + key: ${{ steps.vcpkg-cache-github.outputs.cache-primary-key }} # ------------------------------------------------------------------ # Build environment # ------------------------------------------------------------------ - - name: Configure 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: | - $prefix = "$env:GITHUB_WORKSPACE\vcpkg_installed\x64-windows" + { + 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: | + $prefix = "$env:GITHUB_WORKSPACE\vcpkg_installed\${{ matrix.triplet }}" "FFMPEG_DIR=$prefix" >> $env:GITHUB_ENV "PKG_CONFIG_PATH=$prefix\lib\pkgconfig" >> $env:GITHUB_ENV "$prefix\tools\pkgconf" >> $env:GITHUB_PATH @@ -385,12 +418,24 @@ jobs: # pins them via overrides + builtin-baseline). vcpkg list - # Covers the whole target/ dir plus ~/.cargo; shared across branches - # of the same OS. - - name: Cache cargo artifacts + # ------------------------------------------------------------------ + # 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-windows + shared-key: oak-ci-${{ matrix.distro }}-${{ matrix.arch }} cache-on-failure: true # ------------------------------------------------------------------ @@ -402,136 +447,66 @@ jobs: - name: Build run: cargo check --workspace --locked - - name: Test - timeout-minutes: 40 + # 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: | - 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 } + 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 } - - macos: - name: Build & test (macOS) - runs-on: warp-macos-26-arm64-12x - steps: - - 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 - - - name: Install Rust (stable) - uses: dtolnay/rust-toolchain@stable - - # ------------------------------------------------------------------ - # System dependencies - # ------------------------------------------------------------------ - - name: Install system dependencies - 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 (same split as the other - # platforms: FFmpeg libraries come from the vcpkg manifest). - brew install cmake pkg-config nasm autoconf automake libtool autoconf-archive - - # ------------------------------------------------------------------ - # vcpkg (manifest mode) + caches - # ------------------------------------------------------------------ - # The runner has no vcpkg preinstalled; bootstrap a fresh clone. - - name: Bootstrap vcpkg - 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" - - # 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`. - - name: Restore vcpkg artifacts - id: vcpkg-cache - uses: actions/cache/restore@v6 - with: - path: | - vcpkg_installed - ~/.cache/vcpkg/archives - key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json') }}-${{ github.run_id }}-${{ github.run_attempt }} - restore-keys: | - vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json') }}- - vcpkg-${{ runner.os }}- - - - name: Install dependencies (vcpkg manifest) - run: | - # See the Linux job: transient source-download failures abort - # the manifest install; retry before giving up. - for attempt in 1 2 3; do - vcpkg install --triplet arm64-osx && exit 0 - echo "vcpkg install failed (attempt $attempt); retrying" - sleep 15 - done - exit 1 - - - name: Save vcpkg artifacts - if: always() - uses: actions/cache/save@v6 - with: - path: | - vcpkg_installed - ~/.cache/vcpkg/archives - key: ${{ steps.vcpkg-cache.outputs.cache-primary-key }} - - # ------------------------------------------------------------------ - # Build environment - # ------------------------------------------------------------------ - - name: Configure build environment - 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/arm64-osx" - echo "FFMPEG_DIR=$prefix" >> "$GITHUB_ENV" - echo "PKG_CONFIG_PATH=$prefix/lib/pkgconfig" >> "$GITHUB_ENV" - echo "$prefix/tools/pkgconf" >> "$GITHUB_PATH" - - # ------------------------------------------------------------------ - # Caches - # ------------------------------------------------------------------ - # Covers the whole target/ dir plus ~/.cargo; shared across branches - # of the same OS. - - name: Cache cargo artifacts - uses: Swatinem/rust-cache@v2 - with: - shared-key: oak-ci-macos - 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 + # 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 + - name: Test (macOS) + if: matrix.platform == 'macos' timeout-minutes: 40 run: | - # Retry once (same policy as the other platforms): worker-pool - # startup under full-suite parallelism has flaked on Linux. A - # real regression fails both passes. 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. + # 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=$! @@ -557,223 +532,65 @@ jobs: run_suite fi - # 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 - if: failure() + - name: Test (Windows) + if: matrix.platform == 'windows' + timeout-minutes: 40 + shell: pwsh run: | - for f in $(ls -t ~/Library/Logs/DiagnosticReports/*.ips 2>/dev/null | head -3); do - echo "===== $f" - head -c 6000 "$f" - echo - done - - # second upstream target: openKylin (Debian/Ubuntu-derived) in its own - # image. The base image is bare (no sudo, no make, no python3) and the - # steps run as root, so there is no sudo prefix anywhere below. - openkylin: - name: Build & test (openKylin ${{ matrix.arch }}) - runs-on: ${{ matrix.runner }} - container: - image: openkylin/openkylin:latest - # A container's default /dev/shm is 64 MiB; the process pool reserves - # >64 MiB per worker eagerly (posix_fallocate) and the render manager - # fails to start when the reservation is refused. The suite also runs - # the 512 MiB shared-memory spike and several worker pools in - # parallel — 2 GiB used to run dry mid-suite (observed as an - # intermittent SIGSEGV in the oak-render tests), so give it headroom. - options: --shm-size=8g - # WarpCache authenticates with the runner's verification token, and - # a container does not inherit the runner's environment: pass it in - # explicitly (WarpBuilds/cache README, "Running inside a - # container"). Without it every cache step warns "Authentication - # token is invalid" and the cache is silently not restored/saved. - env: - WARPBUILD_RUNNER_VERIFICATION_TOKEN: ${{ env.WARPBUILD_RUNNER_VERIFICATION_TOKEN }} - strategy: - fail-fast: false - matrix: - include: - - arch: x64 - runner: warp-ubuntu-latest-x64-8x - triplet: x64-linux - - arch: arm64 - runner: warp-ubuntu-latest-arm64-16x - triplet: arm64-linux - # The Test step's watchdog caps a hung suite at 30 min; give a cold - # vcpkg install + full compile + test room beyond that. - timeout-minutes: 90 - steps: - # The image ships neither git nor curl (checkout and vcpkg need - # both); wget is what WarpCache uses to download cache segments - # inside a container (its README requires it), and without it the - # cache steps fail. First step of the job, so the package lists are - # still fresh. - - name: Install git, curl and wget - run: apt-get update && apt-get install -y git curl ca-certificates wget - - - 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 - - # The container runs 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 whole job - # to root's home so rustup/cargo and the toolchain agree. - - name: Pin HOME for rustup - run: | - { - echo "HOME=/root" - echo "CARGO_HOME=/root/.cargo" - echo "RUSTUP_HOME=/root/.rustup" - } >> "$GITHUB_ENV" - - - name: Install Rust (stable) - uses: dtolnay/rust-toolchain@stable + 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 } + } # ------------------------------------------------------------------ - # System dependencies + # Failure diagnostics # ------------------------------------------------------------------ - # Same package set as the Linux job (both distros carry the Ubuntu - # names); patch/xz-utils are openKylin's own packaging brought in by - # zip/unzip. nasm and zip come from the kylinsoft "anything" PPA, - # which the image enables by default. - - name: Install system dependencies - 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 \ - 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 autoconf autoconf-archive automake libtool \ - fonts-dejavu-core - - # ------------------------------------------------------------------ - # vcpkg (manifest mode) + caches - # ------------------------------------------------------------------ - # The image has no vcpkg preinstalled; bootstrap a fresh clone. - - name: Bootstrap vcpkg - 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" - - # The archives dir is vcpkg's binary cache: a manifest bump then - # rebuilds only what changed. - # WarpBuild's cache service backs this job (the GitHub Actions - # cache quota is full); macOS/Windows keep actions/cache. - - name: Restore vcpkg artifacts - id: vcpkg-cache - uses: WarpBuilds/cache/restore@v2 - with: - path: | - vcpkg_installed - ~/.cache/vcpkg/archives - # The arch prefix matters here: both jobs run the same distro - # image through the same cache scope, unlike the OS jobs. - key: vcpkg-${{ runner.os }}-${{ matrix.arch }}-openkylin-${{ hashFiles('vcpkg.json') }}-${{ github.run_id }}-${{ github.run_attempt }} - restore-keys: | - vcpkg-${{ runner.os }}-${{ matrix.arch }}-openkylin-${{ hashFiles('vcpkg.json') }}- - vcpkg-${{ runner.os }}-${{ matrix.arch }}-openkylin- - - - name: Install dependencies (vcpkg manifest) - run: | - # See the Linux job: transient source-download failures abort - # the manifest install; retry before giving up. - for attempt in 1 2 3; do - vcpkg install --triplet ${{ matrix.triplet }} && exit 0 - echo "vcpkg install failed (attempt $attempt); retrying" - sleep 15 - done - exit 1 - - - name: Save vcpkg artifacts - if: always() - uses: WarpBuilds/cache/save@v2 - with: - path: | - vcpkg_installed - ~/.cache/vcpkg/archives - key: ${{ steps.vcpkg-cache.outputs.cache-primary-key }} - - # ------------------------------------------------------------------ - # Build environment - # ------------------------------------------------------------------ - - name: Configure build environment - 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" - # See the Linux job: vcpkg's shared libva must win at runtime. - echo "LD_LIBRARY_PATH=$prefix/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" >> "$GITHUB_ENV" - - # Covers the whole target/ dir plus ~/.cargo. - - name: Cache cargo artifacts - uses: WarpBuilds/rust-cache@v2 - with: - shared-key: oak-ci-openkylin-${{ matrix.arch }} - cache-on-failure: true - - # ------------------------------------------------------------------ - # Build & test - # ------------------------------------------------------------------ - - name: Build - run: cargo check --workspace --locked - - # xvfb + 24-bit screen, same as the Linux job: the gpui - # #[gpui::test] tests open real windows and render through wgpu on - # Mesa's software Vulkan (lavapipe). The watchdog logic is the Linux - # job's; the retry once below covers the same worker-pool flake, - # which is a stale-tmpfs/container property too — a real regression - # fails both passes. - - name: Test - timeout-minutes: 45 + # 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: | - run_suite() { - xvfb-run -a -s "-screen 0 1920x1080x24" cargo test --workspace --locked & - TEST_PID=$! - ( - 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 - 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 + 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 - # A crashing (SIGSEGV) test binary gives no Rust backtrace: rerun the - # big suites under gdb so the native stack lands in the log. gdb is - # installed with the system dependencies. - - name: Backtrace on test failure - if: failure() + # 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) @@ -784,3 +601,38 @@ jobs: -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 diff --git a/tooling/package/build-deb.sh b/tooling/package/build-deb.sh index cbbc8ad5f..585e7e55c 100755 --- a/tooling/package/build-deb.sh +++ b/tooling/package/build-deb.sh @@ -54,9 +54,50 @@ fi install -m644 Oak_Icon.svg "$STAGING/usr/share/icons/hicolor/scalable/apps/oak.svg" install -m644 assets/i18n/*.yaml "$STAGING/usr/share/oak/i18n/" +# vcpkg's libva/libva-drm are shared libraries that FFmpeg links +# dynamically. Debian 12 / openKylin carry an older libva than FFmpeg 8 +# expects (vaMapBuffer2), so ship vcpkg's copies next to the app and give +# the executables a relative RUNPATH (`$ORIGIN`) — a system libva can then +# not shadow them. `VCPKG_LIB` is the vcpkg_installed//lib dir +# (CD passes it; a local build without it just skips the bundle). +if [ -n "${VCPKG_LIB:-}" ]; then + mkdir -p "$STAGING/usr/lib/oak-editor" + bundled=0 + for so in "$VCPKG_LIB"/libva*.so.* "$VCPKG_LIB"/libdrm*.so.*; do + [ -f "$so" ] || continue + install -m755 "$so" "$STAGING/usr/lib/oak-editor/" + bundled=1 + done + if [ "$bundled" = 1 ]; then + for bin in "$STAGING"/usr/bin/*; do + patchelf --set-rpath '$ORIGIN/../lib/oak-editor' "$bin" + done + fi +fi + # The full shlib dependency set (FFmpeg/OCIO are statically linked, so -# only base-OS packages appear). -DEPS=$(for bin in "$STAGING"/usr/bin/*; do dpkg-shlibdeps -O "$bin"; done \ +# mostly base-OS packages appear). dpkg-shlibdeps only runs inside a +# Debian source tree, so give it a synthetic one; +# `--ignore-missing-info` skips libraries no distro package provides +# (those are the vcpkg copies bundled above). +DEPS_DIR=target/pkg/deb-deps +rm -rf "$DEPS_DIR" +mkdir -p "$DEPS_DIR/debian" +cat > "$DEPS_DIR/debian/control" <<'EOF' +Source: oak-editor +Section: video +Priority: optional +Maintainer: Oak Team +Standards-Version: 4.6.0 + +Package: oak-editor +Architecture: any +Description: Oak Video Editor +EOF +STAGING_ABS="$PWD/$STAGING" +DEPS=$(cd "$DEPS_DIR" && for bin in "$STAGING_ABS"/usr/bin/*; do + dpkg-shlibdeps -O --ignore-missing-info "$bin" || exit 1 +done \ | sed 's/^shlibs:Depends=//' | tr ',' '\n' | sed 's/^ //;s/ $//' | sort -u \ | paste -sd', ' -) echo "declared deps: $DEPS"