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.
This commit is contained in:
+139
-13
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
+7
-7
@@ -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
|
||||
|
||||
|
||||
+20
-2
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user