graph-sp 2026.1.1

High-performance DAG execution engine with Python bindings
Documentation
name: Build and Publish Python Wheels

on:
  push:
    branches:
      - main
      - master
  workflow_dispatch:

jobs:
  check-and-tag:
    name: Generate Version Tag
    runs-on: ubuntu-latest
    permissions:
      contents: write
    outputs:
      version: ${{ steps.generate_version.outputs.version }}
      should_publish: ${{ steps.generate_version.outputs.should_publish }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      
      - name: Generate version tag
        id: generate_version
        run: |
          # Get current year
          YEAR=$(date +%Y)
          
          # Get all tags for current year
          LATEST_TAG=$(git tag -l "${YEAR}.*" | sort -V | tail -n 1)
          
          if [ -z "$LATEST_TAG" ]; then
            # No tags for this year, start with v1
            VERSION="${YEAR}.1"
          else
            # Extract version number and increment
            VERSION_NUM=$(echo $LATEST_TAG | cut -d'.' -f2)
            NEW_VERSION_NUM=$((VERSION_NUM + 1))
            VERSION="${YEAR}.${NEW_VERSION_NUM}"
          fi
          
          echo "version=${VERSION}" >> $GITHUB_OUTPUT
          echo "should_publish=true" >> $GITHUB_OUTPUT
          echo "Generated version: ${VERSION}"
          
          # Create and push the tag
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git tag "${VERSION}"
          git push origin "${VERSION}"

  build-wheels:
    name: Build wheels on ${{ matrix.os }}
    needs: check-and-tag
    if: needs.check-and-tag.outputs.should_publish == 'true'
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.12'
      
      - name: Install Rust
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          profile: minimal
          override: true
      
      - name: Install maturin
        run: pip install maturin
      
      - name: Build wheels (Linux)
        if: runner.os == 'Linux'
        env:
          PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1
        run: |
          docker run --rm -v $(pwd):/io -e PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 ghcr.io/pyo3/maturin build --release -o dist --interpreter python3.12
      
      - name: Build wheels (Windows)
        if: runner.os == 'Windows'
        env:
          PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1
        run: |
          maturin build --release -o dist --interpreter python
      
      - name: Build wheels (macOS)
        if: runner.os == 'macOS'
        env:
          PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1
        run: |
          maturin build --release -o dist --interpreter python3.12
      
      - name: Upload wheels
        uses: actions/upload-artifact@v4
        with:
          name: wheels-${{ matrix.os }}
          path: dist/*.whl

  build-sdist:
    name: Build source distribution
    needs: check-and-tag
    if: needs.check-and-tag.outputs.should_publish == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.12'
      
      - name: Install Rust
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          profile: minimal
          override: true
      
      - name: Install maturin
        run: pip install maturin
      
      - name: Build sdist
        run: maturin sdist -o dist
      
      - name: Upload sdist
        uses: actions/upload-artifact@v4
        with:
          name: sdist
          path: dist/*.tar.gz

  publish:
    name: Publish to PyPI
    needs: [check-and-tag, build-wheels, build-sdist]
    runs-on: ubuntu-latest
    if: needs.check-and-tag.outputs.should_publish == 'true'
    
    steps:
      - name: Download all wheels
        uses: actions/download-artifact@v4
        with:
          pattern: wheels-*
          path: dist
          merge-multiple: true
      
      - name: Download sdist
        uses: actions/download-artifact@v4
        with:
          name: sdist
          path: dist
      
      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          password: ${{ secrets.PYPI_API_TOKEN }}
          skip-existing: true

  publish-crate:
    name: Publish crate to crates.io
    needs: check-and-tag
    runs-on: ubuntu-latest
    if: needs.check-and-tag.outputs.should_publish == 'true'
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4

      - name: Install Rust
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          profile: minimal
          override: true

      - name: Commit Cargo.lock if changed
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add Cargo.lock || true
          if ! git diff --staged --quiet; then
            git commit -m "chore: update Cargo.lock" || true
            git push origin HEAD:${{ github.ref }} || true
          fi

      - name: Publish to crates.io
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
        run: |
          # Publish the crate; requires a crates.io token in secrets.CRATES_IO_TOKEN
          # WARNING: --allow-dirty will publish even if there are uncommitted changes
          # This may result in a published crate that isn't reproducible from the
          # repository state. Use only if you understand the risk.
          cargo publish --manifest-path Cargo.toml --allow-dirty