name: Builds

on:
  push:
    branches:
      - main
    tags:
      - '*'
    paths-ignore:
      - CHANGELOG.md
      - README.md
  pull_request:
  workflow_dispatch:

permissions:
  contents: read

jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: llvm-tools
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - name: Set up Rust cache
        uses: Swatinem/rust-cache@v2
      - uses: astral-sh/setup-uv@v7
        with:
          enable-cache: true
      - name: Install cargo-llvm-cov
        run: cargo install cargo-llvm-cov
      - uses: extractions/setup-just@v2

      - run: uv sync --dev --no-install-project # special version will be installed by coverage-lcov
      - run: just coverage-python-full
      - run: just coverage-rust
      - uses: coverallsapp/github-action@v2
        with:
          files: coverage-rust-from-python.lcov coverage-python.xml coverage-rust.lcov
          github-token: ${{ secrets.GITHUB_TOKEN }}

  linux:
    name: Python Linux builds
    if: startsWith(github.ref, 'refs/tags/')
    needs: [tests]
    runs-on: ubuntu-24.04
    strategy:
      matrix:
        target: [x86_64]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: 3.x
      - name: Set up Rust cache
        uses: Swatinem/rust-cache@v2
      - name: Build wheels
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          args: --release --out dist --find-interpreter
          sccache: "true"
          manylinux: auto
      - name: Upload wheels
        uses: actions/upload-artifact@v4
        with:
          name: wheels-linux-${{ matrix.target }}
          path: dist

  linux-freethreaded:
    name: Python Linux free-threaded builds
    if: startsWith(github.ref, 'refs/tags/')
    needs: [tests]
    runs-on: ubuntu-24.04
    strategy:
      matrix:
        target: [x86_64]
        python-version: ['3.13t', '3.14t']
    steps:
      - uses: actions/checkout@v4
      - name: Compute container interpreter path
        id: interp
        run: |
          VER="${{ matrix.python-version }}"
          MM=$(echo "$VER" | tr -d '.t')
          echo "path=/opt/python/cp${MM}-cp${MM}t/bin/python3" >> "$GITHUB_OUTPUT"
      - name: Set up Rust cache
        uses: Swatinem/rust-cache@v2
      - name: Build wheels
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          args: --release --out dist --interpreter ${{ steps.interp.outputs.path }} --no-default-features --features python
          sccache: "true"
          manylinux: auto
      - name: Upload wheels
        uses: actions/upload-artifact@v4
        with:
          name: wheels-linux-freethreaded-${{ matrix.python-version }}-${{ matrix.target }}
          path: dist

  windows:
    name: Python Windows builds
    if: startsWith(github.ref, 'refs/tags/')
    needs: [tests]
    runs-on: windows-latest
    strategy:
      matrix:
        target: [x64]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: 3.x
          architecture: ${{ matrix.target }}
      - name: Set up Rust cache
        uses: Swatinem/rust-cache@v2
      - name: Build wheels
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          args: --release --out dist --find-interpreter
          sccache: "true"
      - name: Upload wheels
        uses: actions/upload-artifact@v4
        with:
          name: wheels-windows-${{ matrix.target }}
          path: dist

  windows-freethreaded:
    name: Python Windows free-threaded builds
    if: startsWith(github.ref, 'refs/tags/')
    needs: [tests]
    runs-on: windows-latest
    strategy:
      matrix:
        target: [x64]
        python-version: ['3.13t', '3.14t']
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          architecture: ${{ matrix.target }}
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable
      - name: Set up Rust cache
        uses: Swatinem/rust-cache@v2
      - name: Build wheels
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          args: --release --out dist --interpreter python3 --no-default-features --features python
          sccache: "true"
          rust-toolchain: stable
      - name: Upload wheels
        uses: actions/upload-artifact@v4
        with:
          name: wheels-windows-freethreaded-${{ matrix.python-version }}-${{ matrix.target }}
          path: dist

  macos:
    name: Python MacOS builds
    if: startsWith(github.ref, 'refs/tags/')
    needs: [tests]
    runs-on: ${{ matrix.runner }}
    strategy:
      matrix:
        include:
          - runner: macos-15-intel
            target: x86_64
          - runner: macos-15
            target: aarch64
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: 3.x
      - name: Set up Rust cache
        uses: Swatinem/rust-cache@v2
      - name: Build wheels
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          args: --release --out dist --find-interpreter
          sccache: "true"
      - name: Upload wheels
        uses: actions/upload-artifact@v4
        with:
          name: wheels-macos-${{ matrix.target }}
          path: dist

  macos-freethreaded:
    name: Python MacOS free-threaded builds
    if: startsWith(github.ref, 'refs/tags/')
    needs: [tests]
    runs-on: macos-15
    strategy:
      matrix:
        target: [x86_64, aarch64]
        python-version: ['3.13t', '3.14t']
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable
      - name: Set up Rust cache
        uses: Swatinem/rust-cache@v2
      - name: Build wheels
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          args: --release --out dist --interpreter python3 --no-default-features --features python
          sccache: "true"
          rust-toolchain: stable
      - name: Upload wheels
        uses: actions/upload-artifact@v4
        with:
          name: wheels-macos-freethreaded-${{ matrix.python-version }}-${{ matrix.target }}
          path: dist

  sdist:
    if: startsWith(github.ref, 'refs/tags/')
    needs: [tests]
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4
      - name: Build sdist
        uses: PyO3/maturin-action@v1
        with:
          command: sdist
          args: --out dist
      - name: Upload sdist
        uses: actions/upload-artifact@v4
        with:
          name: wheels-sdist
          path: dist

  release:
    name: PyPI Release
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/')
    needs: [linux, linux-freethreaded, windows, windows-freethreaded, macos, macos-freethreaded, sdist]
    permissions:
      id-token: write
    steps:
      - uses: actions/download-artifact@v4
        with:
          pattern: wheels-*
          merge-multiple: true
          path: dist
      - name: Install uv
        uses: astral-sh/setup-uv@v7
      - name: Publish to PyPI
        run: uv publish --trusted-publishing always --check-url https://pypi.org/simple/ dist/*

  github_release:
    name: GitHub Release
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/')
    needs: [linux, linux-freethreaded, windows, windows-freethreaded, macos, macos-freethreaded, sdist]
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          pattern: wheels-*
          merge-multiple: true
          path: dist
      - name: Extract changelog for this version
        id: changelog
        run: |
          TAG="${GITHUB_REF_NAME}"
          VERSION="${TAG#v}"
          NOTES=$(awk -v ver="$VERSION" '
            BEGIN { found=0 }
            /^## \[/ {
              if (found) exit
              header=$0; sub(/^## \[/, "", header); sub(/\].*/, "", header)
              if (header == ver) found=1
              next
            }
            found { print }
          ' CHANGELOG.md)
          echo "$NOTES" > /tmp/release-notes.md
      - name: Create GitHub Release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release create "$GITHUB_REF_NAME" \
            --title "$GITHUB_REF_NAME" \
            --notes-file /tmp/release-notes.md \
            dist/*

  rust_release:
    name: Rust Crates.io Release
    runs-on: ubuntu-24.04
    if: startsWith(github.ref, 'refs/tags/')
    needs: [tests]
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable
      - name: Set up Rust cache
        uses: Swatinem/rust-cache@v2
      - name: Publish to crates.io
        run: cargo publish -p equiconc
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
