name: CI
on:
  pull_request:
  push:
    branches:
      - main
    tags:
      - "*"

concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
  cancel-in-progress: true

env:
  # Debug info is most of a build's disk footprint and a large share of its
  # link time, and nothing in CI reads it. Dropping it keeps every job's
  # dependency cache small enough to stay under the repository's 10 GB cache
  # limit instead of being evicted before the next run can use it.
  CARGO_PROFILE_DEV_DEBUG: 0

# Every job that compiles Rust relies on the dependency cache that
# `actions-rust-lang/setup-rust-toolchain` installs (Swatinem/rust-cache).
# Only `main` saves caches; pull requests restore the closest `main` cache
# through rust-cache's prefix fallback, so PR runs never crowd `main`'s
# caches out of the 10 GB limit. Keep each job on one feature set: a second
# `cargo` invocation with different features recompiles GPUI from scratch,
# which is what made the old single macOS job take half an hour.
jobs:
  checks:
    name: Checks
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Typo check
        uses: crate-ci/typos@v1.40.0
      - name: Verify published recipe fragments
        run: python3 script/check-ai docs
      - uses: oven-sh/setup-bun@v2
      - name: gpui-pre is pinned exactly
        run: bun script/check-gpui-pin.ts
      - uses: taiki-e/install-action@v2
        with:
          tool: cargo-machete@0.9.1
      - name: Machete
        run: cargo machete

  lint:
    name: Lint
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          components: clippy
          cache-save-if: ${{ github.ref == 'refs/heads/main' }}
      # Lints every member and, as a side effect, type-checks the example
      # crates that the `test` job leaves out.
      - name: Lint
        run: cargo clippy --workspace --exclude gpui-shell --locked -- --deny warnings

  no-default-features:
    name: No default features
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          cache-save-if: ${{ github.ref == 'refs/heads/main' }}
      # Replaces the system `ld` on Linux and is a no-op elsewhere.
      - uses: rui314/setup-mold@v1
      - name: Install system dependencies
        run: script/bootstrap
      - name: Check without default features (tree-sitter disabled)
        run: cargo check -p gpui-component --no-default-features --locked
      # Control callback, legacy alias, and Form contract tests on the same
      # feature set, so they reuse the build above.
      - name: Test without default features
        run: python3 script/check-ai rust

  test:
    name: Test
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: aarch64-apple-darwin
            run_on: macos-latest
          - target: x86_64-linux-gnu
            run_on: ubuntu-latest
          - target: x86_64-windows-msvc
            run_on: windows-latest
    runs-on: ${{ matrix.run_on }}
    defaults:
      run:
        # `$TEST_EXCLUDES` must word-split, which PowerShell does not do.
        shell: bash
    steps:
      - uses: actions/checkout@v4
      - uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          cache-save-if: ${{ github.ref == 'refs/heads/main' }}
      # Replaces the system `ld` on Linux and is a no-op elsewhere.
      - uses: rui314/setup-mold@v1
      - name: Install system dependencies
        if: ${{ matrix.run_on != 'windows-latest' }}
        run: script/bootstrap
      # Compiling and linking a test binary for every `examples/` crate was
      # most of the warm-cache build time; the `lint` job type-checks them
      # instead. Leaving them out changes no crate's feature set. The recipes
      # crate stays in: its tests are what certify the documentation's code.
      # `jq -j` writes the list as one line with no newline at all: on Windows
      # `jq` emits CRLF, and a `\r` left on a name breaks `--exclude`.
      - name: Leave the example crates to the lint job
        run: |
          excludes=$(cargo metadata --no-deps --format-version 1 | jq -j '
            ((.workspace_root | gsub("\\\\"; "/")) + "/examples/") as $examples
            | .packages[]
            | select((.manifest_path | gsub("\\\\"; "/")) | startswith($examples))
            | select(.name != "gpui-kit-recipes")
            | "--exclude \(.name) "')
          echo "TEST_EXCLUDES=$excludes" >> "$GITHUB_ENV"
      # `gpui-component-story/test-support` turns on `gpui-kit/test-support`, so
      # this single run also covers the headless UI integration tests in
      # `crates/kit/tests`.
      - name: Test
        run: >-
          cargo test --workspace --exclude gpui-shell
          --features gpui-component-story/test-support --locked
          $TEST_EXCLUDES
      - name: Doc tests
        if: ${{ matrix.run_on == 'macos-latest' }}
        run: >-
          cargo test --workspace --exclude gpui-shell
          --features gpui-component-story/test-support --locked
          $TEST_EXCLUDES --doc
      # `crates/kit/tests/rendering.rs` needs a real Metal device and is opted
      # out of the default test set; select it with the same feature set so it
      # reuses the build above.
      - name: Metal UI rendering tests
        if: ${{ matrix.run_on == 'macos-latest' }}
        run: >-
          cargo test --workspace --exclude gpui-shell
          --features gpui-component-story/test-support --locked
          $TEST_EXCLUDES --test rendering

  gpui-shell-core:
    name: GPUI Shell Core
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          components: clippy
          cache-save-if: ${{ github.ref == 'refs/heads/main' }}
          cache-shared-key: gpui-shell-release
      # Replaces the system `ld` on Linux and is a no-op elsewhere.
      - uses: rui314/setup-mold@v1
      - name: Install system dependencies
        run: script/bootstrap
      - name: Test runtime core without standard-library integration cases
        run: |
          cargo test -p gpui-shell --release --lib -- \
            --skip tests::standard_runtime \
            --skip tests::network \
            --skip tests::fs \
            --skip tests::process \
            --skip tests::benchmark
          cargo test -p gpui-shell --release tests::benchmark
          cargo test -p gpui-shell --release --doc
      - name: Lint GPUI Shell
        run: cargo clippy -p gpui-shell --all-targets -- -D warnings

  gpui-component-shell:
    name: GPUI Component Shell
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          components: clippy
          cache-save-if: ${{ github.ref == 'refs/heads/main' }}
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      # Replaces the system `ld` on Linux and is a no-op elsewhere.
      - uses: rui314/setup-mold@v1
      - name: Install system dependencies
        run: script/bootstrap
      - name: Test component bindings
        run: cargo test -p gpui-component-shell --all-targets
      - name: Verify generated TypeScript contracts
        run: |
          npm --prefix crates/component-shell/tests/types ci
          npm --prefix crates/component-shell/tests/types test
      - name: Lint component bindings
        run: cargo clippy -p gpui-component-shell --all-targets -- -D warnings
      # The inventory test proves every public component is registered or
      # explicitly deferred; this proves the JavaScript gallery covers what the
      # inventory claims. Without it the two drift silently.
      - name: Verify story coverage against the component inventory
        run: node examples/js_story/fixtures/verify-coverage.mjs

  gpui-shell-standard-runtime:
    name: GPUI Shell Standard Runtime (${{ matrix.target }})
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: aarch64-apple-darwin
            run_on: macos-latest
          - target: x86_64-linux-gnu
            run_on: ubuntu-latest
          - target: x86_64-windows-msvc
            run_on: windows-latest
    runs-on: ${{ matrix.run_on }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          cache-save-if: ${{ github.ref == 'refs/heads/main' }}
          cache-shared-key: gpui-shell-release
      # Replaces the system `ld` on Linux and is a no-op elsewhere.
      - uses: rui314/setup-mold@v1
      - name: Install system dependencies
        if: ${{ matrix.run_on != 'windows-latest' }}
        run: script/bootstrap
      - name: Test LLRT module integration
        run: |
          cargo test -p gpui-shell --release tests::standard_runtime
          cargo test -p gpui-shell --release tests::fs
          cargo test -p gpui-shell --release tests::process
          cargo test -p gpui-shell --release tests::network
          cargo test -p gpui-shell --release tests::render
          cargo test -p gpui-shell --release tests::snapshot
          cargo test -p gpui-shell --release tests::host_api
          cargo test -p gpui-shell --release tests::http_request