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 run: | sudo apt-get update sudo apt-get install -y \ 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 - 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 }}