ripex 0.3.1

Multi-language structural parsing and fact extraction.
Documentation
name: Python Release

on:
  push:
    tags:
      - "v*"
  workflow_dispatch:
    inputs:
      version:
        description: "Version to release (without the leading v)"
        required: true
        default: "0.3.1"
        type: string
      publish:
        description: "Enable PyPI publication"
        required: false
        default: false
        type: boolean

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always
  RELEASE_VERSION: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }}

concurrency:
  group: python-release-${{ github.ref }}
  cancel-in-progress: false

jobs:
  validate:
    name: Validate Python release version
    runs-on: ubuntu-latest
    timeout-minutes: 20
    permissions:
      contents: read
    steps:
      # actions/checkout v4.2.2
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262

      - name: Install Python 3.12
        # actions/setup-python v5.4.0
        uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
        with:
          python-version: '3.12'

      - name: Install Rust 1.85.0
        # dtolnay/rust-toolchain
        uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
        with:
          toolchain: 1.85.0

      - name: Validate package versions
        env:
          RELEASE_VERSION_INPUT: ${{ env.RELEASE_VERSION }}
        shell: bash
        run: |
          set -euo pipefail
          expected="${RELEASE_VERSION_INPUT#v}"
          [[ "$expected" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
            echo "Release version must be a semantic X.Y.Z value: $expected" >&2
            exit 1
          }
          root_version="$(
            cargo metadata --locked --no-deps --format-version 1 |
              python3 -c 'import json, sys; print(next(p["version"] for p in json.load(sys.stdin)["packages"] if p["name"] == "ripex"))'
          )"
          py_cargo_version="$(
            cargo metadata --manifest-path bindings/python/Cargo.toml --locked --no-deps --format-version 1 |
              python3 -c 'import json, sys; print(next(p["version"] for p in json.load(sys.stdin)["packages"] if p["name"] == "ripex-python"))'
          )"
          pyproject_version="$(
            python3 -c '
          import tomllib
          with open("bindings/python/pyproject.toml", "rb") as f:
              print(tomllib.load(f)["project"]["version"])
          '
          )"
          for pair in \
            "root ripex $root_version" \
            "python-cargo ripex-python $py_cargo_version" \
            "pyproject ripex $pyproject_version"; do
            read -r kind package version <<<"$pair"
            test "$version" = "$expected" || {
              echo "$kind package $package is $version, expected $expected" >&2
              exit 1
            }
          done

  linux:
    name: Build Linux Wheels (${{ matrix.target }})
    needs: validate
    runs-on: ubuntu-latest
    timeout-minutes: 45
    permissions:
      contents: read
    strategy:
      fail-fast: false
      matrix:
        target: [x86_64, aarch64]
    steps:
      # actions/checkout v4.2.2
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262

      - name: Install Python 3.12
        # actions/setup-python v5.4.0
        uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
        with:
          python-version: '3.12'

      - name: Build Linux wheel via maturin-action
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          args: --release --out dist --manifest-path bindings/python/Cargo.toml
          manylinux: auto

      - name: Upload wheel artifact
        # actions/upload-artifact v4.6.0
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
        with:
          name: python-wheels-linux-${{ matrix.target }}
          path: dist/*.whl
          if-no-files-found: error
          retention-days: 14

  macos:
    name: Build macOS Wheels (${{ matrix.target }})
    needs: validate
    runs-on: ${{ matrix.os }}
    timeout-minutes: 45
    permissions:
      contents: read
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: macos-14
            target: x86_64
          - os: macos-14
            target: aarch64
    steps:
      # actions/checkout v4.2.2
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262

      - name: Install Python 3.12
        # actions/setup-python v5.4.0
        uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
        with:
          python-version: '3.12'

      - name: Install Rust 1.85.0
        # dtolnay/rust-toolchain
        uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
        with:
          toolchain: 1.85.0

      - name: Build macOS wheel via maturin-action
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          args: --release --out dist --manifest-path bindings/python/Cargo.toml

      - name: Upload wheel artifact
        # actions/upload-artifact v4.6.0
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
        with:
          name: python-wheels-macos-${{ matrix.target }}
          path: dist/*.whl
          if-no-files-found: error
          retention-days: 14

  windows:
    name: Build Windows Wheels (${{ matrix.target }})
    needs: validate
    runs-on: windows-latest
    timeout-minutes: 45
    permissions:
      contents: read
    strategy:
      fail-fast: false
      matrix:
        target: [x64]
    steps:
      # actions/checkout v4.2.2
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262

      - name: Install Python 3.12
        # actions/setup-python v5.4.0
        uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
        with:
          python-version: '3.12'

      - name: Install Rust 1.85.0
        # dtolnay/rust-toolchain
        uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
        with:
          toolchain: 1.85.0

      - name: Build Windows wheel via maturin-action
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          args: --release --out dist --manifest-path bindings/python/Cargo.toml

      - name: Upload wheel artifact
        # actions/upload-artifact v4.6.0
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
        with:
          name: python-wheels-windows-${{ matrix.target }}
          path: dist/*.whl
          if-no-files-found: error
          retention-days: 14

  sdist:
    name: Build Python Source Distribution (sdist)
    needs: validate
    runs-on: ubuntu-latest
    timeout-minutes: 20
    permissions:
      contents: read
    steps:
      # actions/checkout v4.2.2
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262

      - name: Install Python 3.12
        # actions/setup-python v5.4.0
        uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
        with:
          python-version: '3.12'

      - name: Install Rust 1.85.0
        # dtolnay/rust-toolchain
        uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
        with:
          toolchain: 1.85.0

      - name: Build sdist via maturin-action
        uses: PyO3/maturin-action@v1
        with:
          command: sdist
          args: --out dist --manifest-path bindings/python/Cargo.toml

      - name: Upload sdist artifact
        # actions/upload-artifact v4.6.0
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
        with:
          name: python-sdist
          path: dist/*
          if-no-files-found: error
          retention-days: 14

  release:
    name: Release Python Packages
    needs: [linux, macos, windows, sdist]
    runs-on: ubuntu-latest
    timeout-minutes: 20
    permissions:
      contents: read
      id-token: write
    steps:
      # actions/checkout v4.2.2
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262

      - name: Download all built artifacts
        # actions/download-artifact v4.1.8
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
        with:
          pattern: python-*
          path: dist
          merge-multiple: true

      - name: Display package directory contents
        shell: bash
        run: |
          ls -la dist/

      - name: Publish to PyPI
        if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && env.PYPI_API_TOKEN != ''
        env:
          PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        uses: PyO3/maturin-action@v1
        with:
          command: upload
          args: --non-interactive --skip-existing dist/*