From a76efd50f1676121d5df89e713da286ec9307b62 Mon Sep 17 00:00:00 2001 From: zhenghaoz Date: Tue, 27 Oct 2026 14:33:03 +0800 Subject: [PATCH] ci: publish Python CPU wheels and sdist to PyPI --- .github/workflows/release.yml | 95 +++++++++++++++++++++++++++++++++++ python/README.md | 29 ++++++++++- 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fec64ce..ad77bde 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,101 @@ permissions: contents: write jobs: + build-python-sdist: + runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install packaging tools + run: python -m pip install build packaging twine + - name: Set Python package version from release tag + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + python - <<'PY' + import os + from pathlib import Path + import tomllib + from packaging.version import Version + + version = str(Version(os.environ['RELEASE_TAG'])) + path = Path('python/pyproject.toml') + text = path.read_text() + current = tomllib.loads(text)['project']['version'] + old = f'version = "{current}"' + assert text.count(old) == 1, 'Expected exactly one package version' + path.write_text(text.replace(old, f'version = "{version}"')) + PY + - name: Build source distribution + run: python -m build python --sdist --outdir dist + - name: Check source distribution + run: python -m twine check --strict dist/* + - uses: actions/upload-artifact@v4 + with: + name: python-sdist + path: dist/*.tar.gz + if-no-files-found: error + + build-python-wheels: + needs: build-python-sdist + permissions: + contents: read + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, ubuntu-24.04-arm, macos-15, windows-2022] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/download-artifact@v4 + with: + name: python-sdist + path: sdist + - name: Extract source distribution + shell: bash + run: | + mkdir python-dist + tar -xzf sdist/*.tar.gz --strip-components=1 -C python-dist + - name: Build and test CPU wheels from source distribution + uses: pypa/cibuildwheel@v4.3.4 + with: + package-dir: python-dist + output-dir: dist + env: + CIBW_BUILD: 'cp39-* cp310-* cp311-* cp312-* cp313-* cp314-*' + CIBW_SKIP: '*-musllinux_* *-win32' + CIBW_ARCHS: native + CIBW_ENVIRONMENT: CMAKE_BUILD_PARALLEL_LEVEL=4 + CIBW_TEST_REQUIRES: pytest + CIBW_TEST_COMMAND: python -m pytest {package}/tests -v + - uses: actions/upload-artifact@v4 + with: + name: python-wheels-${{ matrix.os }} + path: dist/*.whl + if-no-files-found: error + + publish-pypi: + needs: [build-python-sdist, build-python-wheels] + runs-on: ubuntu-24.04 + environment: + name: pypi + url: https://pypi.org/p/clap-cpp + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + pattern: python-* + merge-multiple: true + path: dist + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + build-cpu: strategy: fail-fast: false diff --git a/python/README.md b/python/README.md index c74711d..410bd7e 100644 --- a/python/README.md +++ b/python/README.md @@ -80,5 +80,30 @@ python -m build python The source distribution includes the pinned native sources and third-party licenses; a wheel can be rebuilt from that archive without a Git checkout or CMake-time downloads. Build dependencies must still be available from the Python -package index or a local wheelhouse. Wheel publishing and GPU support are out of -scope for this initial binding. +package index or a local wheelhouse. GPU support is not included. + +## PyPI releases + +Publishing a GitHub release runs `.github/workflows/release.yml`. The release tag +must be a valid Python package version (for example `v0.2.0` or `v0.2.0rc1`); +its normalized version replaces the source package's development version in the +build only. The workflow builds one source distribution, then builds and tests +CPython 3.9–3.14 CPU wheels from that archive for Linux x86-64/ARM64, macOS ARM64 +and Windows x86-64. All Python builds must pass before anything is published. +Native executable release jobs remain independent. + +Before the first release, configure a PyPI Trusted Publisher (or pending +publisher for a new project) with: + +- Project: `clap-cpp` +- GitHub owner: `gorse-io` +- Repository: `clap.cpp` +- Workflow: `release.yml` +- Environment: `pypi` + +Create the matching GitHub `pypi` environment and configure any desired approval +rules. Publishing uses GitHub OIDC; no PyPI API token secret is required. Release +candidates are published as PyPI prereleases when the tag uses a prerelease +version; the GitHub prerelease checkbox alone does not change the version. +Previously published package files cannot be overwritten: use a new version for +changed artifacts.