# CI for punktfunk (Gitea Actions). Linux jobs run on the `ubuntu-latest` runner; the Rust
# job runs inside the prebuilt builder image (ci/rust-ci.Dockerfile — system FFmpeg 8,
# PipeWire, GL/GBM, libcuda link stub, pinned-channel rustup) so the workspace links the
# same libs as the dev boxes. Apple client CI lives in apple.yml (macOS runner).
name: ci

on:
  push:
    branches: [main]
  pull_request:

jobs:
  rust:
    runs-on: ubuntu-24.04
    container:
      image: git.unom.io/unom/punktfunk-rust-ci:latest
    timeout-minutes: 90
    steps:
      - uses: actions/checkout@v4

      # punktfunk-client-linux link deps. Also baked into rust-ci.Dockerfile — but ci.yml
      # runs against the image from the PREVIOUS push (docker.yml bootstrap note), so this
      # keeps the job green across image-content changes; a no-op once the image has them.
      - name: GTK4/libadwaita/SDL3 dev packages
        run: |
          apt-get update
          apt-get install -y --no-install-recommends libgtk-4-dev libadwaita-1-dev libsdl3-dev

      # Best-effort caches (act_runner's built-in cache server). Keyed on Cargo.lock:
      # registry/git are download caches, target/ the incremental build. The target key
      # carries the rustc version — resolved via `rustc --version` (below) rather than parsed
      # from rust-toolchain.toml, so a pin bump there invalidates stale incremental state too.
      - name: Cache keys
        run: echo "rustc=$(rustc --version | cut -d' ' -f2)" >> "$GITHUB_ENV"
      - uses: actions/cache@v4
        with:
          path: |
            /usr/local/cargo/registry
            /usr/local/cargo/git
          key: cargo-home-${{ hashFiles('Cargo.lock') }}
          restore-keys: cargo-home-
      - uses: actions/cache@v4
        with:
          path: target
          # -v3-: the prior `cargo-target-<rustc>-*` cache was poisoned when the runner ran
          # out of disk mid-build and actions/cache saved a truncated target/ (a dep's .rmeta
          # went missing -> E0463 "can't find crate"). A suffix bump wouldn't help — restore-keys
          # would fall back to the poisoned prefix — so the prefix itself is versioned.
          key: cargo-target-v3-${{ env.rustc }}-${{ hashFiles('Cargo.lock') }}
          restore-keys: cargo-target-v3-${{ env.rustc }}-

      - name: Format
        run: cargo fmt --all --check

      - name: Clippy (deny warnings)
        run: cargo clippy --workspace --all-targets --locked -- -D warnings

      - name: Build
        run: cargo build --workspace --locked

      - name: Test (unit + loopback + proptest + C ABI harness)
        run: cargo test --workspace --locked

      # The GPU encode backends are OFF by default, so every step above compiles ~none of them:
      # `nvenc` gates enc/linux/nvenc_cuda.rs (+ nvenc_core/nvenc_status) and `vulkan-encode` gates
      # enc/linux/vulkan_video.rs (+ the vendored vk_av1_encode/vk_valve_rgb bindings) — ~8,150
      # lines carrying ~70 `unsafe` blocks. Their ONLY prior CI coverage was deb.yml's
      # `cargo build`, where warnings are not errors, so pf-encode's own
      # `#![deny(clippy::undocumented_unsafe_blocks)]` — the crate's stated unsafe-proof gate —
      # was never actually enforced on them. (`pyrowave` needs no extra step: punktfunk-host has
      # `default = ["pyrowave"]`, so the steps above already cover it.)
      #
      # `--all-targets` is load-bearing, not decoration: without it the feature-gated
      # `#[cfg(test)]` modules are never compiled, which is exactly how all ten
      # `NvencCudaEncoder::open` call sites in nvenc_cuda.rs's tests drifted to the wrong arity
      # (E0061 x10) without any job noticing.
      #
      # GPU-free: every test needing real hardware is `#[ignore]`d, and NVENC/CUDA resolve their
      # entry points at RUNTIME (dlopen), so the test binary links without a driver present.
      # (On MSVC the same crate link-imports those symbols instead, which is why windows-host.yml
      # can only type-check these tests via clippy — see the note there.)
      #
      # Scoped to `-p pf-encode` with ITS OWN feature names: punktfunk-host has no code gated on
      # `nvenc`/`vulkan-encode` (its only `cfg(feature)` sites are the two `pyrowave` ones in
      # capture.rs, and pyrowave is default-on, so the steps above already cover them). Going
      # through `--features punktfunk-host/...` would force punktfunk-host into the selection and
      # re-run its entire test suite a second time for no extra coverage.
      #
      # `pyrowave` is listed explicitly even though it is punktfunk-host's default: selecting only
      # `-p pf-encode` takes the host out of the resolution, and pf-encode's own default is empty.
      # Naming it keeps this the SHIPPED Linux feature set — deb.yml builds
      # `--features punktfunk-host/nvenc,punktfunk-host/vulkan-encode` WITHOUT
      # `--no-default-features`, so the .deb carries nvenc + vulkan-encode + pyrowave together, and
      # that combination is what deserves the lint.
      - name: Clippy + test the feature-gated Linux encode backends
        run: |
          cargo clippy -p pf-encode --all-targets --locked \
            --features nvenc,vulkan-encode,pyrowave -- -D warnings
          cargo test -p pf-encode --locked --features nvenc,vulkan-encode,pyrowave

      - name: C ABI harness (standalone link proof)
        run: bash crates/punktfunk-core/tests/c/run.sh

      - name: Verify generated header is committed & up to date
        run: |
          cargo build -p punktfunk-core --locked
          git config --global --add safe.directory "$PWD"
          git diff --exit-code include/punktfunk_core.h \
            || (echo "include/punktfunk_core.h is stale — commit the regenerated header" && exit 1)

  # The client stack cross-checked for aarch64. NOT an artifact job — deb.yml ships those —
  # this exists so a portability defect fails here instead of surfacing in a release build or
  # on a user's board. It earns its runtime: the bug that motivated it (a Vulkan extension
  # array typed `*const i8`, where `c_char` is signed on x86_64 and UNSIGNED on aarch64)
  # compiled cleanly on every target CI built at the time.
  #
  # Client crates only, listed explicitly: the host's encode stack is x86 (NVENC/QSV/AMF) and
  # `--workspace` would drag it in. Runs in the cross image (amd64 toolchain + arm64 sysroot,
  # ci/rust-ci-arm64cross.Dockerfile) on the ordinary runner — no arm64 runner involved.
  rust-arm64:
    runs-on: ubuntu-24.04
    container:
      image: git.unom.io/unom/punktfunk-rust-ci-arm64cross:latest
    timeout-minutes: 60
    steps:
      - uses: actions/checkout@v4

      - name: Cache keys
        run: echo "rustc=$(rustc --version | cut -d' ' -f2)" >> "$GITHUB_ENV"
      - uses: actions/cache@v4
        with:
          path: |
            /usr/local/cargo/registry
            /usr/local/cargo/git
          key: cargo-home-${{ hashFiles('Cargo.lock') }}
          restore-keys: cargo-home-
      - uses: actions/cache@v4
        with:
          path: target
          # Its OWN prefix: aarch64 artifacts must never share the amd64 jobs' target cache.
          key: cargo-target-arm64-v1-${{ env.rustc }}-${{ hashFiles('Cargo.lock') }}
          restore-keys: cargo-target-arm64-v1-${{ env.rustc }}-

      - name: Clippy for aarch64 (deny warnings)
        run: |
          cargo clippy --target aarch64-unknown-linux-gnu --all-targets --locked \
            -p punktfunk-core -p pf-client-core -p pf-presenter -p pf-console-ui \
            -p punktfunk-client-session -p punktfunk-client-linux \
            -- -D warnings

      # The minimal embedded build — no Skia, no PyroWave — is what a small image installs, so
      # it has to keep compiling on its own, not just as a subset of the default features.
      - name: Build the session binary, minimal features
        run: |
          cargo build --release --target aarch64-unknown-linux-gnu --locked \
            -p punktfunk-client-session --no-default-features

  web:
    runs-on: ubuntu-24.04
    container:
      image: oven/bun:1
    timeout-minutes: 30
    defaults:
      run:
        working-directory: web
    steps:
      # oven/bun ships neither git nor a real node (only a bun shim) — actions/checkout
      # needs both. The slim Debian base also lacks ca-certificates, so without it git's
      # HTTPS fetch of the repo dies with "Problem with the SSL CA cert (path? access
      # rights?)" — no CA bundle to validate git.unom.io's (public) Let's Encrypt cert.
      - name: Install git + node + CA certs
        working-directory: /
        run: apt-get update && apt-get install -y --no-install-recommends ca-certificates git nodejs
      - uses: actions/checkout@v4
      - name: Install dependencies
        run: bun install --frozen-lockfile --ignore-scripts
      # Build first: it generates the orval API client + paraglide messages that
      # typechecking imports.
      - name: Build
        run: bun run build
      - name: Typecheck
        run: bun run lint

  docs-site:
    runs-on: ubuntu-24.04
    container:
      image: oven/bun:1
    timeout-minutes: 30
    defaults:
      run:
        working-directory: docs-site
    steps:
      # ca-certificates: the slim Debian base lacks a CA bundle, so actions/checkout's
      # HTTPS fetch otherwise fails with "Problem with the SSL CA cert" (see web job).
      - name: Install git + CA certs
        working-directory: /
        run: apt-get update && apt-get install -y --no-install-recommends ca-certificates git
      - uses: actions/checkout@v4
      - name: Install dependencies
        run: bun install --frozen-lockfile --ignore-scripts
      # Build first: fumadocs-mdx emits the .source typegen the typecheck imports.
      - name: Build
        run: bun run build
      - name: Typecheck
        run: bun run lint

  bench:
    # Tier-1 (criterion microbenchmarks) + Tier-2 (FEC loss recovery) — GPU-free, so they run here.
    # Report-only: prints the numbers + a diff vs the committed baseline to the job summary and never
    # fails the build (shared CI hardware is too noisy to gate on). The tight regression gate + the
    # real encode/stream path live on the self-hosted GPU runner (Tier 3, bench-gpu.yml).
    runs-on: ubuntu-24.04
    container:
      image: git.unom.io/unom/punktfunk-rust-ci:latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4
      - name: Prep
        run: |
          git config --global --add safe.directory "$PWD"
          command -v python3 >/dev/null || { apt-get update && apt-get install -y --no-install-recommends python3; }
      - name: Tier-1 microbenchmarks (criterion)
        run: cargo bench -p punktfunk-core --bench pipeline -- --warm-up-time 1 --measurement-time 3
      - name: Tier-2 FEC loss recovery (loss-harness)
        run: cargo run -q -p loss-harness
      - name: Compare vs baseline (report-only)
        run: python3 scripts/bench/compare.py --threshold 0.5
