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/**'
      - 'vendor/skia-safe-op/**'
      - 'rust-toolchain.toml'
      - 'deny.toml'
      - '.github/workflows/rust-multiplatform.yml'
  pull_request:
    paths:
      - 'Cargo.toml'
      - 'Cargo.lock'
      - 'crates/**'
      - 'vendor/jian/**'
      - 'vendor/skia-safe-op/**'
      - '.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 \
            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 are now `#[ignore]`
        # under LINUX_GPU_SKIA_LOADER_TBD (skia-safe Interface::new_native
        # cannot load GL syms from EGL pbuffer + llvmpipe; needs
        # `new_load_with(eglGetProcAddress)` loader, deferred to Step 1f or
        # spec §3.1 mini-patch). All other tests run normally.
        run: cargo test --workspace --target ${{ matrix.target }}
      - name: Test (host, macOS / Windows)
        if: matrix.cross == false && matrix.check_only != true && runner.os != 'Linux'
        run: cargo test --workspace --target ${{ matrix.target }}
      # 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 §1.2 wasm32-clean compile guard — without `skia` feature this
      # is a stub mount (validates host canvas, does not paint). The real
      # render artifact uses the SAME wasm32-unknown-unknown target plus
      # `--features skia` (post C-hard.2 lock-in 2026-05-09) and ships
      # via a separate Phase A job once the CI-side C-hard pipeline
      # (brew emscripten install + EMSDK + .wasm.a symlink hack +
      # wasm-bindgen + wasm-opt) lands. Until then this job only proves
      # the public surface compiles wasm32-clean.
      - 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. Restore the upload step once the skia-enabled build runs on
      # this CI target.

  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 }}
      # 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 }}
