Build Wheels & Publish #7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Build pre-compiled wheels for all platforms and publish to PyPI. | |
| # Triggered on release publish or version tag push. | |
| # Tests must pass before wheels are built and published. | |
| name: Build Wheels & Publish | |
| on: | |
| release: | |
| types: [published] | |
| push: | |
| tags: | |
| - 'v*' | |
| # Allow manual trigger for testing the workflow | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| # ── Gate: run tests first ────────────────────────────────────── | |
| test: | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| python-version: ['3.10', '3.12'] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Install package (builds C backend) | |
| run: | | |
| pip install pytest pandas | |
| pip install -e ".[all]" | |
| - name: Run tests | |
| run: pytest tests/ -x -q | |
| # ── Build wheels for all platforms ───────────────────────────── | |
| # macOS needs two runners: macos-13 (Intel) for x86_64, macos-latest (M-series) for arm64. | |
| # cibuildwheel cannot cross-compile C extensions across architectures. | |
| build_wheels: | |
| needs: test | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| cibw_archs: "x86_64 aarch64" | |
| artifact_name: "wheels-linux" | |
| - os: macos-latest | |
| cibw_archs: "arm64" | |
| artifact_name: "wheels-macos-arm64" | |
| - os: windows-latest | |
| cibw_archs: "AMD64" | |
| artifact_name: "wheels-windows" | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.12' | |
| # Linux aarch64 wheels need QEMU emulation | |
| - name: Set up QEMU | |
| if: runner.os == 'Linux' | |
| uses: docker/setup-qemu-action@v3 | |
| with: | |
| platforms: arm64 | |
| # Pre-download virtualenv.pyz into cibuildwheel's cache dir using | |
| # authenticated GitHub API to avoid HTTP 429 rate limits. | |
| - name: Pre-cache virtualenv for cibuildwheel | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| VENV_VERSION="20.27.1" | |
| VENV_FILE="virtualenv-${VENV_VERSION}.pyz" | |
| # Determine cibuildwheel cache directory per OS | |
| if [ "$RUNNER_OS" = "macOS" ]; then | |
| CACHE_DIR="$HOME/Library/Caches/cibuildwheel" | |
| elif [ "$RUNNER_OS" = "Windows" ]; then | |
| CACHE_DIR="$LOCALAPPDATA/cibuildwheel/Cache" | |
| else | |
| CACHE_DIR="$HOME/.cache/cibuildwheel" | |
| fi | |
| mkdir -p "$CACHE_DIR" | |
| DEST="$CACHE_DIR/$VENV_FILE" | |
| if [ ! -f "$DEST" ]; then | |
| echo "Downloading virtualenv.pyz with authenticated GitHub API..." | |
| curl -sSL --retry 3 --retry-delay 5 \ | |
| -H "Authorization: Bearer $GH_TOKEN" \ | |
| -H "Accept: application/octet-stream" \ | |
| "https://github.com/pypa/get-virtualenv/raw/refs/tags/${VENV_VERSION}/public/virtualenv.pyz" \ | |
| -o "$DEST" | |
| echo "Downloaded to $DEST ($(wc -c < "$DEST") bytes)" | |
| else | |
| echo "virtualenv.pyz already cached at $DEST" | |
| fi | |
| - name: Build wheels | |
| uses: pypa/cibuildwheel@v2.22 | |
| env: | |
| CIBW_ARCHS: ${{ matrix.cibw_archs }} | |
| - name: Upload wheel artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: ./wheelhouse/*.whl | |
| # ── Build source distribution ────────────────────────────────── | |
| build_sdist: | |
| needs: test | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.12' | |
| - name: Build sdist | |
| run: | | |
| pip install build | |
| python -m build --sdist | |
| - name: Upload sdist artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sdist | |
| path: dist/*.tar.gz | |
| # ── Publish to PyPI ──────────────────────────────────────────── | |
| publish: | |
| needs: [build_wheels, build_sdist] | |
| runs-on: ubuntu-latest | |
| # Only publish on release or tag push (not manual dispatch) | |
| if: github.event_name == 'release' || startsWith(github.ref, 'refs/tags/v') | |
| steps: | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: dist | |
| merge-multiple: true | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| user: __token__ | |
| password: ${{ secrets.PYPI_API_TOKEN }} |