fix(desktop): recover from unusable native GL on Windows startup #604
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Rust multi-platform build | |
| # Builds the OpenPencil Rust workspace across all supported targets. | |
| # Runs on every push/PR to verify the matrix stays green; release artifacts | |
| # are produced by `rust-release.yml` on tag pushes. | |
| on: | |
| push: | |
| branches: ['**'] | |
| paths: | |
| - 'Cargo.toml' | |
| - 'Cargo.lock' | |
| - 'crates/**' | |
| - 'vendor/jian/**' | |
| # The casement winit fork + agent runtime link into every desktop | |
| # target — a change there must re-run the multiplatform matrix. | |
| - 'vendor/casement/**' | |
| - 'vendor/agent/**' | |
| - 'rust-toolchain.toml' | |
| - 'deny.toml' | |
| - '.github/workflows/rust-multiplatform.yml' | |
| pull_request: | |
| paths: | |
| - 'Cargo.toml' | |
| - 'Cargo.lock' | |
| - 'crates/**' | |
| - 'vendor/jian/**' | |
| - 'vendor/casement/**' | |
| - 'vendor/agent/**' | |
| - '.github/workflows/rust-multiplatform.yml' | |
| workflow_dispatch: | |
| jobs: | |
| desktop: | |
| name: ${{ matrix.label }} | |
| runs-on: ${{ matrix.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - label: macos-aarch64 | |
| runner: macos-latest | |
| target: aarch64-apple-darwin | |
| cross: false | |
| # macos-13 (Intel runners) deprecated; build x86_64-apple-darwin via | |
| # cross-compile from Apple Silicon. cargo build/check run; no test | |
| # since binary arch ≠ host arch. | |
| - label: macos-x86_64 | |
| runner: macos-latest | |
| target: x86_64-apple-darwin | |
| cross: false | |
| check_only: true | |
| - label: linux-x86_64 | |
| runner: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| cross: false | |
| # Native ARM64 hosted runner — no `cross`. The cross 0.2.x aarch64 | |
| # image (Ubuntu 16.04 / GCC 5 / FreeType 2.6) is too old to link the | |
| # modern skia that skia-bindings builds (missing COLR/variable-font | |
| # FreeType symbols + libstdc++ ABI mismatch). | |
| - label: linux-aarch64 | |
| runner: ubuntu-24.04-arm | |
| target: aarch64-unknown-linux-gnu | |
| cross: false | |
| - label: windows-x86_64 | |
| runner: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| cross: false | |
| # Windows ARM64 — cargo cross-compile from x86_64 host (no Win11 | |
| # ARM hosted runner GA yet); cargo check only since binary arch | |
| # ≠ host arch. | |
| - label: windows-aarch64 | |
| runner: windows-latest | |
| target: aarch64-pc-windows-msvc | |
| cross: false | |
| check_only: true | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: '1.94' | |
| targets: ${{ matrix.target }} | |
| components: rustfmt, clippy | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: ${{ matrix.target }} | |
| - name: Install Linux GL/EGL prereqs | |
| if: runner.os == 'Linux' && matrix.cross == false | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| packages=( | |
| libxkbcommon-dev libxkbcommon-x11-dev \ | |
| libwayland-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev \ | |
| libegl1-mesa-dev libgles2-mesa-dev libgbm-dev mesa-utils \ | |
| libfreetype-dev libfontconfig1-dev fonts-noto-cjk \ | |
| xvfb | |
| ) | |
| for attempt in 1 2 3 4; do | |
| if sudo apt-get \ | |
| -o Acquire::ForceIPv4=true \ | |
| -o Acquire::Retries=3 \ | |
| -o Acquire::http::Timeout=30 \ | |
| update && \ | |
| sudo apt-get \ | |
| -o Acquire::ForceIPv4=true \ | |
| -o Acquire::Retries=3 \ | |
| -o Acquire::http::Timeout=30 \ | |
| install --yes "${packages[@]}"; then | |
| break | |
| fi | |
| if [[ "$attempt" == 4 ]]; then | |
| echo "::error::apt prerequisites failed after $attempt attempts" | |
| exit 1 | |
| fi | |
| delay=$((attempt * 20)) | |
| echo "::warning::apt prerequisites attempt $attempt failed; retrying in ${delay}s" | |
| sleep "$delay" | |
| done | |
| - name: Build (host) | |
| if: matrix.cross == false && matrix.check_only != true | |
| run: cargo build --workspace --target ${{ matrix.target }} --release | |
| - name: Build examples (host, spec §1.2 acceptance #7) | |
| if: matrix.cross == false && matrix.check_only != true | |
| run: cargo build --examples --workspace --target ${{ matrix.target }} --release | |
| - name: Check (cross-arch host, e.g. macos-x86_64 from Apple Silicon) | |
| if: matrix.check_only == true | |
| run: cargo check --workspace --target ${{ matrix.target }} | |
| - name: Test (host, Linux) | |
| if: matrix.cross == false && runner.os == 'Linux' | |
| # Linux GPU smoke + gpu_chrome_stub_composition now run un-ignored: | |
| # Skia loads its GL interface through the provider's | |
| # `gl_proc_address` (eglGetProcAddress), closing the old | |
| # LINUX_GPU_SKIA_LOADER_TBD gap. On runners without a working | |
| # EGL/Mesa stack they soft-skip (INCONCLUSIVE eprintln); set | |
| # STEP1A_REQUIRE_GPU=1 on a known-good GPU runner to fail hard. | |
| run: cargo test --workspace --target ${{ matrix.target }} | |
| - name: Test (host, macOS — parallel) | |
| if: matrix.cross == false && matrix.check_only != true && runner.os == 'macOS' | |
| run: cargo test --workspace --target ${{ matrix.target }} | |
| # Windows: skia's FontMgr (DirectWrite) segfaults when a test spawns a | |
| # worker thread doing font layout (SkiaMeasure) concurrently with the | |
| # main thread — an intra-test race --test-threads=1 can't serialize. | |
| # nextest runs each test in its own process so such a crash is isolated | |
| # + named. Keep those test processes serial on Windows: DirectWrite/Skia | |
| # still aborts in CI when several native text-layout processes run at | |
| # once. Doctests run separately (nextest doesn't execute them). Matches | |
| # rust-check.yml. | |
| - name: Install cargo-nextest (Windows) | |
| if: matrix.cross == false && matrix.check_only != true && runner.os == 'Windows' | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: nextest | |
| - name: Test (host, Windows — nextest per-process isolation) | |
| if: matrix.cross == false && matrix.check_only != true && runner.os == 'Windows' | |
| env: | |
| OP_TEST_ESTIMATE_TEXT_MEASURE: "1" | |
| run: cargo nextest run --workspace --target ${{ matrix.target }} --test-threads=1 | |
| - name: Doctests (host, Windows) | |
| if: matrix.cross == false && matrix.check_only != true && runner.os == 'Windows' | |
| run: cargo test --workspace --target ${{ matrix.target }} --doc | |
| # Desktop binary artifact upload is intentionally not wired here yet. | |
| # The real desktop binary lives in the `op-host-desktop` crate; a | |
| # build + upload step can be added once release packaging is finalized. | |
| wasm-web: | |
| name: wasm32-unknown-unknown / op-host-web (compile guard) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: '1.94' | |
| targets: wasm32-unknown-unknown | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: wasm32 | |
| # Step 1b wasm32-clean compile guard. The default `web` feature is a | |
| # stub mount that validates the canvas and does not paint; it stays here | |
| # to prove the public surface remains wasm32-clean without the production | |
| # bundle deps. The real deployable renderer is built in | |
| # wasm-bundle-build.yml with the `canvaskit` feature and the | |
| # tools/check-wasm-bundle.sh gate. | |
| - run: cargo build -p op-host-web --target wasm32-unknown-unknown --release --no-default-features --features web | |
| # Artifact upload deliberately removed: the stub `.wasm` from this | |
| # path would mislead downstream consumers into thinking the shell can | |
| # render. The deployable artifact is uploaded by wasm-bundle-build.yml. | |
| mobile-check: | |
| name: ${{ matrix.label }} (cargo check only) | |
| runs-on: ${{ matrix.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # iOS targets need Xcode SDK — macOS runner only. | |
| - label: ios-aarch64 | |
| runner: macos-latest | |
| target: aarch64-apple-ios | |
| - label: ios-aarch64-sim | |
| runner: macos-latest | |
| target: aarch64-apple-ios-sim | |
| # Android targets via NDK — Linux runner. | |
| - label: android-aarch64 | |
| runner: ubuntu-latest | |
| target: aarch64-linux-android | |
| - label: android-x86_64 | |
| runner: ubuntu-latest | |
| target: x86_64-linux-android | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: '1.94' | |
| targets: ${{ matrix.target }} | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: mobile-${{ matrix.target }} | |
| - name: Configure Android NDK toolchain | |
| if: startsWith(matrix.target, 'aarch64-linux-android') || startsWith(matrix.target, 'x86_64-linux-android') | |
| shell: bash | |
| run: | | |
| NDK_HOME="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}}}" | |
| if [ -z "$NDK_HOME" ]; then | |
| SDK_HOME="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-}}" | |
| if [ -z "$SDK_HOME" ]; then | |
| echo "Android SDK root is not available on this runner" >&2 | |
| exit 1 | |
| fi | |
| NDK_HOME="$(find "$SDK_HOME/ndk" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -n 1)" | |
| fi | |
| TOOLCHAIN="$NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" | |
| echo "ANDROID_NDK_HOME=$NDK_HOME" >> "$GITHUB_ENV" | |
| API_LEVEL=21 | |
| case "${{ matrix.target }}" in | |
| aarch64-linux-android) | |
| CC="$TOOLCHAIN/aarch64-linux-android${API_LEVEL}-clang" | |
| CXX="$TOOLCHAIN/aarch64-linux-android${API_LEVEL}-clang++" | |
| echo "CC_aarch64_linux_android=$CC" >> "$GITHUB_ENV" | |
| echo "CXX_aarch64_linux_android=$CXX" >> "$GITHUB_ENV" | |
| echo "AR_aarch64_linux_android=$TOOLCHAIN/llvm-ar" >> "$GITHUB_ENV" | |
| echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=$CC" >> "$GITHUB_ENV" | |
| ;; | |
| x86_64-linux-android) | |
| CC="$TOOLCHAIN/x86_64-linux-android${API_LEVEL}-clang" | |
| CXX="$TOOLCHAIN/x86_64-linux-android${API_LEVEL}-clang++" | |
| echo "CC_x86_64_linux_android=$CC" >> "$GITHUB_ENV" | |
| echo "CXX_x86_64_linux_android=$CXX" >> "$GITHUB_ENV" | |
| echo "AR_x86_64_linux_android=$TOOLCHAIN/llvm-ar" >> "$GITHUB_ENV" | |
| echo "CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER=$CC" >> "$GITHUB_ENV" | |
| ;; | |
| esac | |
| test -x "$CC" | |
| test -x "$CXX" | |
| test -x "$TOOLCHAIN/llvm-ar" | |
| # Step 1a spec §11 mobile invariants verify on iOS / Android cargo check: | |
| # - op-editor-ui wasm32/ios/android-clean (no platform deps) — the | |
| # widget facade / editor-UI composition crate. | |
| # - op-host-native compiles on mobile targets with EaglProvider / | |
| # AndroidEglProvider stubs (`unimplemented!("Step 1f")`); desktop GL | |
| # stack (glutin / winit) is target-gated to desktop in Cargo.toml + | |
| # GlutinProvider source is cfg-gated to desktop OS only. Real SDK | |
| # linking and iOS/Android runtime is Step 1f. | |
| - run: cargo check -p op-editor-ui --target ${{ matrix.target }} | |
| - run: cargo check -p op-host-native --target ${{ matrix.target }} |