name: Build and Test

on:
  push:
    branches:
    - main
  pull_request:
    branches:
    - main
    paths:
    # Changes to the package itself
    - src/msgspec/**
    - pyproject.toml
    - setup.py
    - MANIFEST.in
    # Changes to CI execution
    - .github/workflows/ci.yml
    - .github/workflows/prepare-build.yml
    - .github/workflows/build-wheels.yml
    - .github/workflows/profile.yml
    - .github/actions/latest-supported-python/action.yml
    - .github/actions/fetch-release-history/action.yml
    - .github/actions/load-env/action.yml
    - .cibuildwheel.toml
    - .justfile
    # Changes to testing
    - tests/**
    - .codecov.yml
    - .codespellrc
    - .coveragerc
    - .pre-commit-config.yaml
    - .pytest.toml
    - .ruff.toml
  release:
    types:
    - published

jobs:
  prepare:
    name: Validate build
    uses: ./.github/workflows/prepare-build.yml
    with:
      run-static-analysis: true
      fetch-latest-python: true

  test:
    name: Test Python ${{ matrix.python-version }} on ${{ startsWith(matrix.os, 'macos-') && 'macOS' || startsWith(matrix.os, 'windows-') && 'Windows' || 'Linux' }}
    needs:
    - prepare
    runs-on: "${{ matrix.os }}"
    strategy:
      fail-fast: false
      matrix:
        os:
        - ubuntu-latest
        # - windows-latest
        # - macos-latest
        python-version:
        - "3.10"
        - "3.11"
        - "3.12"
        - "3.13"
        - "3.14"

    steps:
    - name: Checkout code
      uses: actions/checkout@v5

    - name: Load environment file
      uses: ./.github/actions/load-env

    - name: Install Python
      uses: actions/setup-python@v6
      with:
        python-version: "${{ matrix.python-version }}"

    - name: Install uv
      uses: astral-sh/setup-uv@v7

    - name: Install command runner
      run: uv tool install rust-just

    - name: Install testing dependencies
      env:
        MSGSPEC_SANITIZE: "true"
        MSGSPEC_COVERAGE: "true"
      run: just env-sync test

    - name: Run tests with sanitizers
      env:
        PYTHONMALLOC: "malloc"
        ASAN_OPTIONS: "detect_leaks=0"
      run: >-
        LD_PRELOAD=`gcc -print-file-name=libasan.so`
        just env-run test coverage run -m pytest tests/unit -s

    - name: Generate coverage files
      run: |-
        just env-run test coverage xml
        gcov -abcu `find build/temp*/ -name *.o`

    - name: Upload coverage artifacts
      uses: actions/upload-artifact@v5
      with:
        name: coverage-${{ matrix.python-version }}-${{ matrix.os }}
        path: |-
          coverage.xml
          _core.c.gcov
          atof.h.gcov
          ryu.h.gcov
        if-no-files-found: error

  upload-coverage:
    name: Upload coverage files to Codecov
    needs:
    - test
    runs-on: ubuntu-latest
    permissions:
      id-token: write

    steps:
    - name: Checkout code
      uses: actions/checkout@v5

    - name: Download coverage artifacts
      uses: actions/download-artifact@v5
      with:
        pattern: coverage-*
        path: coverage-files

    - name: See the coverage files
      run: tree -sh coverage-files

    - name: Upload Codecov
      uses: codecov/codecov-action@v5
      with:
        use_oidc: true
        directory: coverage-files

  test-integration:
    name: Run integration tests
    needs:
    - test
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v5

    - name: Load environment file
      uses: ./.github/actions/load-env

    - name: Install Python
      uses: actions/setup-python@v6
      with:
        python-version-file: pyproject.toml

    - name: Install uv
      uses: astral-sh/setup-uv@v7

    - name: Install command runner
      run: uv tool install rust-just

    - name: Install testing dependencies
      run: just env-sync test

    - name: Run doctests
      run: just test-doc

    - name: Run type checker compatibility tests
      run: just test-typing

  build-wheels:
    name: Build wheels ${{ github.event_name == 'release' && 'for release' || '' }}
    needs:
    - test
    - prepare
    uses: ./.github/workflows/build-wheels.yml
    with:
      sdist-name: "${{ needs.prepare.outputs.sdist-name }}"
      build-all: ${{ github.event_name == 'release' }}

  profile:
    name: Run profiling tests on Python ${{ needs.prepare.outputs.latest-supported-python }}
    needs:
    - prepare
    - build-wheels
    uses: ./.github/workflows/profile.yml
    with:
      python: "${{ needs.prepare.outputs.latest-supported-python }}"

  upload-pypi:
    name: Upload artifacts to PyPI
    if: github.event_name == 'release' && github.event.action == 'published'
    needs:
    - prepare
    - build-wheels
    runs-on: ubuntu-latest
    permissions:
      id-token: write

    steps:
    - uses: actions/download-artifact@v6
      with:
        pattern: artifact-*
        merge-multiple: true
        path: dist

    - name: Publish package distributions to PyPI
      uses: pypa/gh-action-pypi-publish@release/v1
