mrrc 0.7.6

A Rust library for reading, writing, and manipulating MARC bibliographic records in ISO 2709 binary format
Documentation
name: Python Release to PyPI

on:
  push:
    tags:
      - 'v*'

jobs:
  build-release-wheels:
    name: Build release wheels on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
      fail-fast: false
    steps:
      - uses: actions/checkout@v6

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

      - uses: PyO3/maturin-action@v1
        with:
          manylinux: auto
          args: --release --out dist -i python${{ matrix.python-version }}

      - name: Upload wheels
        uses: actions/upload-artifact@v7
        with:
          name: release-wheels-${{ matrix.os }}-${{ matrix.python-version }}
          path: dist

  build-cross-linux-wheels:
    name: Build cross-compiled ${{ matrix.target }} wheel (Python ${{ matrix.python-version }})
    runs-on: ubuntu-latest
    strategy:
      matrix:
        target: [aarch64-unknown-linux-gnu, i686-unknown-linux-gnu]
        python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
      fail-fast: false
    steps:
      - uses: actions/checkout@v6

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

      - uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          manylinux: auto
          args: --release --out dist -i python${{ matrix.python-version }}

      - name: Upload wheels
        uses: actions/upload-artifact@v7
        with:
          name: release-wheels-${{ matrix.target }}-${{ matrix.python-version }}
          path: dist

  validate-cross-wheels:
    name: Validate ${{ matrix.target }} wheel (Python ${{ matrix.python-version }})
    needs: build-cross-linux-wheels
    runs-on: ubuntu-latest
    strategy:
      matrix:
        target: [aarch64-unknown-linux-gnu, i686-unknown-linux-gnu]
        python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
      fail-fast: false
    steps:
      - uses: actions/download-artifact@v8
        with:
          name: release-wheels-${{ matrix.target }}-${{ matrix.python-version }}
          path: dist

      - name: Validate wheel exists with correct platform tag
        run: |
          if [ "${{ matrix.target }}" = "aarch64-unknown-linux-gnu" ]; then
            EXPECTED_TAG="aarch64"
          elif [ "${{ matrix.target }}" = "i686-unknown-linux-gnu" ]; then
            EXPECTED_TAG="i686"
          fi
          WHEEL=$(ls dist/*.whl 2>/dev/null | head -1)
          if [ -z "$WHEEL" ]; then
            echo "ERROR: No .whl file found in dist/"
            ls -la dist/
            exit 1
          fi
          echo "Found wheel: $WHEEL"
          if echo "$WHEEL" | grep -q "$EXPECTED_TAG"; then
            echo "OK: Wheel filename contains expected tag '$EXPECTED_TAG'"
          else
            echo "ERROR: Wheel filename does not contain expected tag '$EXPECTED_TAG'"
            exit 1
          fi

  test-release-wheels:
    name: Test release wheels on ${{ matrix.os }}
    needs: build-release-wheels
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
      fail-fast: false
    steps:
      - uses: actions/checkout@v6

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

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

      - uses: actions/download-artifact@v8
        with:
          name: release-wheels-${{ matrix.os }}-${{ matrix.python-version }}
          path: dist

      - name: Install wheel and test dependencies
        shell: bash
        run: |
          uv pip install --system dist/mrrc-*.whl
          uv pip install --system pytest pytest-benchmark mypy pyright

      - name: Run pytest (core tests, excludes benchmarks)
        run: |
          pytest tests/python/ -m "not benchmark" -v
        # Retry on macOS due to intermittent runner flakiness
        if: runner.os != 'macOS'

      - name: Run pytest with retry (macOS)
        if: runner.os == 'macOS'
        run: |
          max_attempts=3
          attempt=1
          while [ $attempt -le $max_attempts ]; do
            echo "Attempt $attempt of $max_attempts"
            if pytest tests/python/ -m "not benchmark" -v; then
              echo "Tests passed on attempt $attempt"
              exit 0
            fi
            echo "Attempt $attempt failed, retrying..."
            attempt=$((attempt + 1))
            sleep 5
          done
          echo "All $max_attempts attempts failed"
          exit 1
        shell: bash

      - name: Run type checking
        run: |
          mypy tests/python/ --strict || true
          pyright tests/python/ || true

  publish-pypi:
    name: Publish to PyPI
    needs: [test-release-wheels, validate-cross-wheels]
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: write
    steps:
      - uses: actions/download-artifact@v8
        with:
          pattern: release-wheels-*
          path: dist
          merge-multiple: true

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

      - uses: actions/checkout@v6
        with:
          sparse-checkout: CHANGELOG.md
          sparse-checkout-cone-mode: false

      - name: Extract release notes from CHANGELOG
        id: changelog
        run: |
          VERSION="${GITHUB_REF_NAME#v}"
          # Extract the section for this version (between its header and the next ## header)
          NOTES=$(awk "/^## \[${VERSION}\]/{found=1; next} /^## \[/{if(found) exit} found{print}" CHANGELOG.md)
          # Write to file to avoid shell quoting issues
          echo "$NOTES" > release_notes.md

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v3
        if: startsWith(github.ref, 'refs/tags/')
        with:
          files: dist/*
          body_path: release_notes.md
          draft: false
          prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }}