From 9011250f992119170242cf17a67834c67b91bcdb Mon Sep 17 00:00:00 2001 From: Pratik Bhadane Date: Tue, 24 Mar 2026 01:00:17 +0530 Subject: [PATCH] chore: update packaging and CI workflow for PyPI releases Enhance the documentation in PACKAGING.md and PLATFORMS.md to clarify the supported Python versions and platforms for wheel and source distribution releases. Update the CI workflow in CI.yml to include separate jobs for building wheels on Linux, macOS, and Windows, as well as a job for building the source distribution. Ensure that the publish job verifies the presence of expected distributions before publishing to PyPI. --- .github/workflows/CI.yml | 152 +++++++++++++++++++++++++++++++++++---- PACKAGING.md | 9 +++ PLATFORMS.md | 14 ++-- RELEASE.md | 22 +++++- 4 files changed, 175 insertions(+), 22 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 15dbf01..682e716 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -130,32 +130,108 @@ jobs: # Keep release jobs here because trusted-publisher configuration points to # CI.yml specifically. # ------------------------------------------------------------------------- - build-wheels: - name: Build wheels (${{ matrix.os }}) - runs-on: ${{ matrix.os }} + build-wheels-linux: + name: Build wheels (linux / py${{ matrix.python-version }}) + runs-on: ubuntu-latest if: github.event_name == 'release' && github.event.action == 'published' strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v6 - - name: Build wheels + - name: Build wheel uses: PyO3/maturin-action@v1 with: command: build - args: --release --out dist - manylinux: auto - # Build for both Intel and Apple Silicon on macOS - target: ${{ matrix.os == 'macos-latest' && 'universal2-apple-darwin' || '' }} + args: --release --out dist --compatibility pypi -i python${{ matrix.python-version }} + manylinux: "2_17" - name: Upload wheels as artifact uses: actions/upload-artifact@v7 with: - name: wheels-${{ matrix.os }} - path: dist + name: wheels-linux-py${{ matrix.python-version }} + path: dist/*.whl + + build-wheels-macos: + name: Build wheels (macos / py${{ matrix.python-version }}) + runs-on: macos-latest + if: github.event_name == 'release' && github.event.action == 'published' + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13"] + + steps: + - uses: actions/checkout@v6 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Build universal2 wheel + uses: PyO3/maturin-action@v1 + with: + command: build + args: --release --out dist --compatibility pypi -i python + target: universal2-apple-darwin + + - name: Upload wheels as artifact + uses: actions/upload-artifact@v7 + with: + name: wheels-macos-py${{ matrix.python-version }} + path: dist/*.whl + + build-wheels-windows: + name: Build wheels (windows / py${{ matrix.python-version }}) + runs-on: windows-latest + if: github.event_name == 'release' && github.event.action == 'published' + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13"] + + steps: + - uses: actions/checkout@v6 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Build wheel + uses: PyO3/maturin-action@v1 + with: + command: build + args: --release --out dist --compatibility pypi -i python + + - name: Upload wheels as artifact + uses: actions/upload-artifact@v7 + with: + name: wheels-windows-py${{ matrix.python-version }} + path: dist/*.whl + + build-sdist: + name: Build source distribution + runs-on: ubuntu-latest + if: github.event_name == 'release' && github.event.action == 'published' + steps: + - uses: actions/checkout@v6 + + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + + - name: Upload sdist as artifact + uses: actions/upload-artifact@v7 + with: + name: sdist + path: dist/*.tar.gz # ------------------------------------------------------------------------- # Publish to PyPI @@ -163,7 +239,11 @@ jobs: publish: name: Publish to PyPI runs-on: ubuntu-latest - needs: build-wheels + needs: + - build-wheels-linux + - build-wheels-macos + - build-wheels-windows + - build-sdist if: github.event_name == 'release' && github.event.action == 'published' environment: name: pypi @@ -178,6 +258,52 @@ jobs: merge-multiple: true path: dist + - name: Download source distribution + uses: actions/download-artifact@v8 + with: + name: sdist + path: dist + + - name: Verify distribution coverage + run: | + python3 - <<'PY' + import fnmatch + import sys + from pathlib import Path + + dist = Path("dist") + files = sorted(p.name for p in dist.iterdir() if p.is_file()) + print("Distributions:") + for name in files: + print(f" - {name}") + + expected = [ + "ferro_ta-*-cp310-cp310-manylinux*_x86_64.whl", + "ferro_ta-*-cp311-cp311-manylinux*_x86_64.whl", + "ferro_ta-*-cp312-cp312-manylinux*_x86_64.whl", + "ferro_ta-*-cp313-cp313-manylinux*_x86_64.whl", + "ferro_ta-*-cp310-cp310-win_amd64.whl", + "ferro_ta-*-cp311-cp311-win_amd64.whl", + "ferro_ta-*-cp312-cp312-win_amd64.whl", + "ferro_ta-*-cp313-cp313-win_amd64.whl", + "ferro_ta-*-cp310-cp310-macosx*_universal2.whl", + "ferro_ta-*-cp311-cp311-macosx*_universal2.whl", + "ferro_ta-*-cp312-cp312-macosx*_universal2.whl", + "ferro_ta-*-cp313-cp313-macosx*_universal2.whl", + "ferro_ta-*.tar.gz", + ] + + missing = [ + pattern for pattern in expected + if not any(fnmatch.fnmatch(name, pattern) for name in files) + ] + if missing: + print("Missing expected distributions:") + for pattern in missing: + print(f" - {pattern}") + sys.exit(1) + PY + - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 @@ -207,7 +333,7 @@ jobs: sbom: name: Generate SBOM (Python + Rust) runs-on: ubuntu-latest - needs: build-wheels + needs: publish if: github.event_name == 'release' && github.event.action == 'published' permissions: contents: write diff --git a/PACKAGING.md b/PACKAGING.md index d8385e3..1d2cd4e 100644 --- a/PACKAGING.md +++ b/PACKAGING.md @@ -5,6 +5,15 @@ This document describes how ferro-ta is packaged and published. ## PyPI (pip) Wheels are built by CI on release (see [RELEASE.md](RELEASE.md)). +Release publishing currently targets CPython 3.10, 3.11, 3.12, and 3.13 on: + +- Linux x86_64 (`manylinux_2_17` / `manylinux2014`) +- macOS universal2 (covers Intel and Apple Silicon) +- Windows x86_64 + +Each release also publishes a source distribution (`sdist`) so compatible +environments outside the wheel matrix can still build from source. + Publishing uses PyPI Trusted Publishing via GitHub OIDC; no long-lived PyPI API token is required. diff --git a/PLATFORMS.md b/PLATFORMS.md index a760e58..25b8055 100644 --- a/PLATFORMS.md +++ b/PLATFORMS.md @@ -20,13 +20,14 @@ Pre-compiled wheels are published to PyPI for the following targets: | OS | Architecture | Notes | |---------|-----------------|-------| -| Linux | x86_64 (manylinux2014 / `manylinux_2_17`) | Default CI runner | -| Linux | aarch64 | Built via maturin cross-compilation | -| macOS | x86_64 | Intel | -| macOS | arm64 | Apple Silicon | -| macOS | universal2 | Intel + Apple Silicon fat binary | +| Linux | x86_64 (manylinux2014 / `manylinux_2_17`) | Pre-compiled wheel | +| macOS | universal2 | One wheel covers Intel + Apple Silicon | | Windows | x86_64 | | +Wheel releases target CPython 3.10, 3.11, 3.12, and 3.13. A source +distribution is also published so other compatible environments can build from +source. + > **Note:** Python 3.14+ is not yet tested. Set > `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1` to attempt a build on a newer > interpreter and report any issues. @@ -39,8 +40,7 @@ Pre-compiled wheels are published to PyPI for the following targets: pip install ferro-ta ``` -No C-compiler required — pre-compiled wheels are available for all platforms -listed above. +No C-compiler required on the wheel targets listed above. ### conda / conda-forge diff --git a/RELEASE.md b/RELEASE.md index dd92b2e..ee551ab 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -15,6 +15,14 @@ For the packaging and release overview, see [PACKAGING.md](PACKAGING.md). | **npm (WASM)** | Workflow `wasm-publish`| | **crates.io** | CI job `publish-cratesio` | +PyPI releases are expected to include: + +- Wheels for CPython 3.10, 3.11, 3.12, and 3.13 +- Linux x86_64 (`manylinux_2_17`) +- macOS universal2 +- Windows x86_64 +- One source distribution (`sdist`) + --- ## Pre-release checklist @@ -128,7 +136,7 @@ git push origin v0.2.0 4. Paste the changelog section for `v0.2.0` into the release notes. 5. Click **Publish release**. -Publishing the release triggers the CI `build-wheels` and `publish` jobs +Publishing the release triggers the CI wheel build jobs, `build-sdist`, and `publish` automatically (the workflow responds to `release: published`). The PyPI upload uses Trusted Publishing via GitHub OIDC, so no `PYPI_API_TOKEN` secret is used. @@ -136,7 +144,7 @@ uses Trusted Publishing via GitHub OIDC, so no `PYPI_API_TOKEN` secret is used. ## Step 7 — Monitor CI and verify PyPI -1. Watch the **Actions** tab: `build-wheels` → `publish` (PyPI), `publish-cratesio` (crates.io), and the **wasm-publish** workflow (npm). +1. Watch the **Actions** tab: the release wheel jobs, `build-sdist`, `publish` (PyPI), `publish-cratesio` (crates.io), and the **wasm-publish** workflow (npm). 2. After the `publish` job succeeds, verify the package is live: ```bash @@ -144,6 +152,16 @@ pip install ferro-ta==0.2.0 python -c "import ferro_ta; print(ferro_ta.__version__ if hasattr(ferro_ta,'__version__') else 'ok')" ``` +For version-specific verification, also check at least one install on each +supported Python line, for example: + +```bash +uv venv --python 3.13 .venv-313 +. .venv-313/bin/activate +uv pip install ferro-ta==0.2.0 +python -c "import ferro_ta; print(ferro_ta.SMA([1.0, 2.0, 3.0], 2))" +``` + 3. If anything fails: fix the issue, bump to a patch version (`0.2.1`), and repeat. ---