name: Build and test

# This file tests the claimed support range of NiBabel including
#
# * Operating systems: Linux, Windows (x64 & x86), OSX
# * Dependencies: minimum requirements, optional requirements
# * Installation methods: setup.py, sdist, wheel, archive

on:
  push:
    branches:
      - master
      - maint/*
    tags:
      - "*"
  pull_request:
    branches:
      - master
      - maint/*
  schedule:
    - cron: '0 0 * * 1'
  # Allow job to be triggered manually from GitHub interface
  workflow_dispatch:

defaults:
  run:
    shell: bash

# Force tox and pytest to use color
env:
  FORCE_COLOR: true

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-python@v5
        with:
          python-version: 3
      - run: pip install --upgrade build twine
      - name: Build sdist and wheel
        run: python -m build
      - run: twine check dist/*
      - name: Build git archive
        run: mkdir archive && git archive -v -o archive/nibabel-archive.tgz HEAD
      - name: Upload sdist and wheel artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
      - name: Upload git archive artifact
        uses: actions/upload-artifact@v4
        with:
          name: archive
          path: archive/

  test-package:
    runs-on: ubuntu-latest
    needs: [build]
    strategy:
      matrix:
        package: ['wheel', 'sdist', 'archive']
    steps:
      - name: Download sdist and wheel artifacts
        if: matrix.package != 'archive'
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      - name: Download git archive artifact
        if: matrix.package == 'archive'
        uses: actions/download-artifact@v4
        with:
          name: archive
          path: archive/
      - uses: actions/setup-python@v5
        with:
          python-version: 3
      - name: Display Python version
        run: python -c "import sys; print(sys.version)"
      - name: Update pip
        run: pip install --upgrade pip
      - name: Install wheel
        if: matrix.package == 'wheel'
        run: pip install dist/nibabel-*.whl
      - name: Install sdist
        if: matrix.package == 'sdist'
        run: pip install dist/nibabel-*.tar.gz
      - name: Install archive
        if: matrix.package == 'archive'
        run: pip install archive/nibabel-archive.tgz
      - run: python -c 'import nibabel; print(nibabel.__version__)'
      - name: Install minimum test dependencies
        run: pip install nibabel[test]
      - name: Run tests
        run: pytest --doctest-modules --doctest-plus -v --pyargs nibabel -n auto

  test:
    # Check each OS, all supported Python, minimum versions and latest releases
    runs-on: ${{ matrix.os }}
    continue-on-error: ${{ matrix.dependencies == 'pre' }}
    strategy:
      fail-fast: false
      matrix:
        os: ['ubuntu-latest', 'windows-latest', 'macos-13', 'macos-latest']
        python-version: ["3.9", "3.10", "3.11", "3.12"]
        architecture: ['x64', 'x86', 'arm64']
        dependencies: ['full', 'pre']
        include:
          # Basic dependencies only
          - os: ubuntu-latest
            python-version: 3.9
            dependencies: 'none'
          # Absolute minimum dependencies
          - os: ubuntu-latest
            python-version: 3.9
            dependencies: 'min'
          # NoGIL
          - os: ubuntu-latest
            python-version: '3.13-dev'
            dependencies: 'dev'
        exclude:
          # x86 for Windows + Python<3.12
          - os: ubuntu-latest
            architecture: x86
          - os: macos-13
            architecture: x86
          - os: macos-latest
            architecture: x86
          - python-version: '3.12'
            architecture: x86
          # arm64 is available for macos-14+
          - os: ubuntu-latest
            architecture: arm64
          - os: windows-latest
            architecture: arm64
          - os: macos-13
            architecture: arm64
          # x64 is not available for macos-14+
          - os: macos-latest
            architecture: x64
          # Drop pre tests for macos-13
          - os: macos-13
            dependencies: pre
          # Drop pre tests for SPEC-0-unsupported Python versions
          - python-version: '3.9'
            dependencies: pre
          - python-version: '3.10'
            dependencies: pre

    env:
      DEPENDS: ${{ matrix.dependencies }}
      ARCH: ${{ !contains(fromJSON('["none", "min"]'), matrix.dependencies) && matrix.architecture }}

    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
          fetch-depth: 0
      - name: Set up Python ${{ matrix.python-version }}
        if: "!endsWith(matrix.python-version, '-dev')"
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          architecture: ${{ matrix.architecture }}
          allow-prereleases: true
      - name: Set up Python ${{ matrix.python-version }}
        if: endsWith(matrix.python-version, '-dev')
        uses: deadsnakes/action@v3.2.0
        with:
          python-version: ${{ matrix.python-version }}
          nogil: true
      - name: Display Python version
        run: python -c "import sys; print(sys.version)"
      - name: Install tox
        run: |
          python -m pip install --upgrade pip
          python -m pip install tox tox-gh-actions
      - name: Show tox config
        run: tox c
      - name: Run tox
        run: tox -vv --exit-and-dump-after 1200
      - uses: codecov/codecov-action@v4
        if: ${{ always() }}
        with:
          files: cov.xml
          token: ${{ secrets.CODECOV_TOKEN }}
      - name: Upload pytest test results
        uses: actions/upload-artifact@v4
        with:
          name: pytest-results-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.dependencies }}-${{ matrix.architecture }}
          path: test-results.xml
        if: ${{ always() }}

  checks:
    runs-on: 'ubuntu-latest'
    continue-on-error: true
    strategy:
      matrix:
        check: ['style', 'doctest', 'typecheck', 'spellcheck']

    steps:
      - uses: actions/checkout@v4
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: 3
      - name: Display Python version
        run: python -c "import sys; print(sys.version)"
      - name: Show tox config
        run: pipx run tox c
      - name: Show tox config (this call)
        run: pipx run tox c -e ${{ matrix.check }}
      - name: Run check
        run: pipx run tox -e ${{ matrix.check }}

  publish:
    runs-on: ubuntu-latest
    environment: "Package deployment"
    needs: [test, test-package]
    permissions:
      # Required for trusted publishing
      id-token: write
    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      - uses: pypa/gh-action-pypi-publish@release/v1
