name: ci

on:
  push:
    branches:
      - main
    tags:
      - "**"
  pull_request: {}

env:
  COLUMNS: 120
  UV_PYTHON: 3.12
  UV_FROZEN: "1"

jobs:
  test:
    name: test ${{ matrix.python-version }}, rust ${{ matrix.rust-version }} on ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu, macos, windows]
        rust-version: [stable, "1.77"]
        python-version:
          - "3.9"
          - "3.10"
          - "3.11"
          - "3.12"
          - "3.13"
          - "3.13t"
          - "3.14"
          - "3.14t"
          - "pypy3.9"
          - "pypy3.10"
          - "pypy3.11"
        exclude:
          - rust-version: "1.77"
            os: macos
          - rust-version: "1.77"
            os: windows

    runs-on: ${{ matrix.os }}-latest

    env:
      UV_PYTHON: ${{ matrix.python-version }}
      RUST: ${{ matrix.rust-version }}
      OS: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v4

      - uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true

      - name: install rust
        uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: ${{ matrix.rust-version }}
          override: true

      - name: cache rust
        uses: Swatinem/rust-cache@v1

      - if: matrix.os == 'ubuntu'
        run: |
          mkdir -p ${{ github.workspace }}/protected
          touch ${{ github.workspace }}/protected/test
          sudo chown -R root:root ${{ github.workspace }}/protected
          sudo chmod 700 ${{ github.workspace }}/protected

      - run: uv run python -c 'import sys; print("free threading enable:", hasattr(sys, "_is_gil_enabled") and not sys._is_gil_enabled())'

      - run: make test
        env:
          WATCHFILES_TEST_PERMISSION_DENIED_PATH: ${{ github.workspace }}/protected

      - run: uv run coverage xml

      - uses: codecov/codecov-action@v1.0.13
        with:
          file: ./coverage.xml
          env_vars: UV_PYTHON,RUST,OS

  lint:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true

      - name: install rust
        uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: stable
          override: true
          components: rustfmt, clippy

      - name: cache rust
        uses: Swatinem/rust-cache@v1

      - run: uv sync --group lint

      - uses: pre-commit/action@v3.0.0
        with:
          extra_args: --all-files --verbose
        env:
          SKIP: no-commit-to-branch

  docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true

      - run: uv sync --group docs

      - run: make docs

      - name: store docs site
        uses: actions/upload-artifact@v4
        with:
          name: docs
          path: site

  build:
    name: >
      build on ${{ matrix.platform || matrix.os }} (${{ matrix.target }} - ${{ matrix.manylinux || 'auto' }})

    if: "!contains(github.event.pull_request.labels.*.name, 'Quick Build')"
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu, macos, windows]
        target: [x86_64, aarch64]
        manylinux: [auto]
        include:
          - os: ubuntu
            platform: linux
            pypy: true
          - os: macos
            target: x86_64
            pypy: true
          - os: macos
            target: aarch64
            pypy: true
          - os: windows
            ls: dir
          - os: windows
            ls: dir
            target: i686
            python-architecture: x86
            interpreter: 3.9 3.10 3.11 3.12 3.13 3.14
          - os: windows
            ls: dir
            target: x86_64
            interpreter: 3.9 3.10 3.11 3.12 3.13 3.14
          - os: windows
            ls: dir
            target: aarch64
            interpreter: 3.11 3.12 3.13 3.14
          - os: ubuntu
            platform: linux
            target: i686
          - os: ubuntu
            platform: linux
            target: armv7
          - os: ubuntu
            platform: linux
            target: ppc64le
          - os: ubuntu
            platform: linux
            target: s390x
          # musllinux
          - os: ubuntu
            platform: linux
            target: x86_64
            manylinux: musllinux_1_1
          - os: ubuntu
            platform: linux
            target: aarch64
            manylinux: musllinux_1_1

    runs-on: ${{ matrix.os }}-latest

    steps:
      - uses: actions/checkout@v5

      - name: set up python
        uses: actions/setup-python@v6
        with:
          python-version: "3.13"
          architecture: ${{ matrix.python-architecture || 'x64' }}

      - name: check GITHUB_REF matches package version
        uses: samuelcolvin/check-python-version@v4.1
        if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.os == 'ubuntu' }}
        with:
          version_file_path: Cargo.toml

      - name: build sdist
        if: ${{ matrix.os == 'ubuntu' && matrix.target == 'x86_64' && matrix.manylinux == 'auto' }}
        uses: PyO3/maturin-action@v1
        with:
          command: sdist
          args: --out dist

      - name: build wheels
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          manylinux: ${{ matrix.manylinux || 'auto' }}
          args: --release --out dist --interpreter ${{ matrix.interpreter || '3.9 3.10 3.11 3.12 3.13 3.13t 3.14 3.14t' }}

      - name: build pypy wheels
        if: ${{ matrix.pypy }}
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          manylinux: ${{ matrix.manylinux || 'auto' }}
          args: --release --out dist --interpreter pypy3.9 pypy3.10 pypy3.11

      - run: ${{ matrix.ls || 'ls -lh' }} dist/

      - uses: actions/upload-artifact@v4
        with:
          name: pypi_files_${{ matrix.os }}_${{ matrix.target }}_${{ matrix.interpreter || 'all' }}_${{ matrix.manylinux }}
          path: dist

  list-pypi-files:
    needs: [build]
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: get dist artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: pypi_files_*
          merge-multiple: true
          path: dist

      - name: list dist files
        run: |
          ls -lh dist/
          echo "`ls dist | wc -l` files"

      - name: extract and list sdist file
        run: |
          mkdir sdist-files
          tar -xvf dist/*.tar.gz -C sdist-files
          tree -a sdist-files

      - name: extract and list wheel file
        run: ls dist/*cp312-manylinux_2_17_x86_64*.whl | head -n 1

      - name: extract and list wheel file
        run: python -m zipfile --list `ls dist/*cp312-manylinux_2_17_x86_64*.whl | head -n 1`

  # Used for branch protection checks, see https://github.com/marketplace/actions/alls-green#why
  check:
    if: always()
    needs: [test, lint, docs]
    runs-on: ubuntu-latest
    steps:
      - name: Decide whether the needed jobs succeeded or failed
        uses: re-actors/alls-green@release/v1
        with:
          jobs: ${{ toJSON(needs) }}

  release:
    needs: [build, check, docs]
    if: "success() && startsWith(github.ref, 'refs/tags/')"
    runs-on: ubuntu-latest
    environment: release

    permissions:
      id-token: write
      contents: write

    steps:
      - uses: actions/checkout@v4

      - uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true

      - name: get dist artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: pypi_files_*
          merge-multiple: true
          path: dist

      - name: get docs
        uses: actions/download-artifact@v4
        with:
          name: docs
          path: site

      - name: Publish to PyPI
        run: "uv publish --trusted-publishing always dist/*"

      - name: publish docs
        uses: JamesIves/github-pages-deploy-action@v4.7.2
        with:
          branch: gh-pages
          folder: site
