* ci: add PyPI trusted publishing, abi3 wheels, bump to 0.2.1 Adds publish-pypi.yml mirroring the npm/crates.io pattern: triggers on Cargo.toml version change, builds wheels for 5 platforms via maturin, publishes with OIDC trusted publishing (no tokens). workflow_dispatch serves as a manual fallback for the first run after the PyPI project transfer. Enables pyo3 abi3-py38 so one wheel per platform covers CPython >=3.8 (previous manual uploads were cp312-only). Bumps version to 0.2.1 since PyPI already has 0.2.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(pypi): guard dispatch to main, support partial-release repair Review feedback: trusted publishing doesn't match on branch, so workflow_dispatch needed an explicit main-ref guard. Manual dispatch now always rebuilds and publishes with skip-existing so a release that failed after uploading only some wheels can be completed by re-running. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(pypi): version PyPI package from pyproject.toml, not Cargo.toml Decouple the Python package version from the crate version, matching how npm publishing keys off napi/package.json: bump [project] version in pyproject.toml manually and CI publishes on merge. Reverts the Cargo.toml bump so this PR no longer triggers a crates.io release. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: remove accidentally committed uv.lock Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ci): tolerate missing version key in parent pyproject.toml The first merge of this workflow has a parent commit where pyproject.toml still used dynamic = ["version"], so the old-version read would KeyError and the auto-publish would never fire. Treat a missing key as a change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
165 lines
5.3 KiB
YAML
165 lines
5.3 KiB
YAML
name: Publish Python package
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths: ['pyproject.toml']
|
|
# Manual fallback: re-publish the current version without a version bump
|
|
# (e.g. first run after PyPI trusted publishing is configured).
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
check-version:
|
|
name: Check version change
|
|
# Guard manual dispatches too: PyPI trusted publishing matches
|
|
# repo+workflow+environment but NOT branch, so without this a
|
|
# workflow_dispatch from any branch could publish unmerged code.
|
|
if: github.ref == 'refs/heads/main'
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
changed: ${{ steps.check.outputs.changed }}
|
|
published: ${{ steps.check.outputs.published }}
|
|
version: ${{ steps.check.outputs.version }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 2
|
|
|
|
- name: Check if version changed
|
|
id: check
|
|
run: |
|
|
NEW_VERSION=$(python3 -c 'import pathlib, tomllib; print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')
|
|
echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
|
|
|
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
# Manual dispatch always rebuilds and publishes. Combined with
|
|
# skip-existing on the publish step, this repairs partial releases
|
|
# (PyPI's version endpoint returns 200 even when only some of the
|
|
# expected wheels were uploaded).
|
|
echo "manual dispatch: publishing v$NEW_VERSION (skip-existing handles uploaded files)"
|
|
echo "changed=true" >> "$GITHUB_OUTPUT"
|
|
echo "published=false" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
# .get(): the parent commit may predate the static version field
|
|
# (pyproject.toml used dynamic = ["version"]) — treat that as a change
|
|
# so the very first merge of this workflow publishes.
|
|
OLD_VERSION=$(git show HEAD~1:pyproject.toml | python3 -c 'import sys, tomllib; print(tomllib.loads(sys.stdin.read())["project"].get("version", ""))')
|
|
echo "old=$OLD_VERSION new=$NEW_VERSION"
|
|
|
|
if [ "$NEW_VERSION" = "$OLD_VERSION" ]; then
|
|
echo "changed=false" >> "$GITHUB_OUTPUT"
|
|
echo "published=false" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
echo "changed=true" >> "$GITHUB_OUTPUT"
|
|
|
|
HTTP_STATUS=$(curl --silent --show-error --output /tmp/pypi-version.json --write-out "%{http_code}" \
|
|
"https://pypi.org/pypi/pdf-inspector/$NEW_VERSION/json")
|
|
|
|
case "$HTTP_STATUS" in
|
|
200)
|
|
echo "published=true" >> "$GITHUB_OUTPUT"
|
|
echo "pdf-inspector v$NEW_VERSION is already published to PyPI"
|
|
;;
|
|
404)
|
|
echo "published=false" >> "$GITHUB_OUTPUT"
|
|
;;
|
|
*)
|
|
cat /tmp/pypi-version.json
|
|
echo "Unexpected PyPI response: $HTTP_STATUS" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
build:
|
|
needs: check-version
|
|
if: needs.check-version.outputs.changed == 'true' && needs.check-version.outputs.published == 'false'
|
|
name: Build ${{ matrix.target }}
|
|
runs-on: ${{ matrix.os }}
|
|
strategy:
|
|
matrix:
|
|
include:
|
|
- os: ubuntu-latest
|
|
target: x86_64-unknown-linux-gnu
|
|
- os: ubuntu-latest
|
|
target: aarch64-unknown-linux-gnu
|
|
- os: macos-13
|
|
target: x86_64-apple-darwin
|
|
- os: macos-14
|
|
target: aarch64-apple-darwin
|
|
- os: windows-latest
|
|
target: x86_64-pc-windows-msvc
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.12'
|
|
|
|
- name: Build wheel
|
|
uses: PyO3/maturin-action@v1
|
|
with:
|
|
target: ${{ matrix.target }}
|
|
args: --release --out dist
|
|
manylinux: auto
|
|
|
|
- name: Upload wheel
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: wheels-${{ matrix.target }}
|
|
path: dist/*.whl
|
|
if-no-files-found: error
|
|
|
|
sdist:
|
|
needs: check-version
|
|
if: needs.check-version.outputs.changed == 'true' && needs.check-version.outputs.published == 'false'
|
|
name: Build sdist
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Build sdist
|
|
uses: PyO3/maturin-action@v1
|
|
with:
|
|
command: sdist
|
|
args: --out dist
|
|
|
|
- name: Upload sdist
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: sdist
|
|
path: dist/*.tar.gz
|
|
if-no-files-found: error
|
|
|
|
publish:
|
|
name: Publish to PyPI
|
|
needs: [check-version, build, sdist]
|
|
runs-on: ubuntu-latest
|
|
environment: pypi
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
steps:
|
|
- name: Download all artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: dist
|
|
merge-multiple: true
|
|
|
|
- name: List artifacts
|
|
run: ls -la dist/
|
|
|
|
- name: Publish to PyPI
|
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
with:
|
|
packages-dir: dist
|
|
# Tolerate already-uploaded files so a manual re-run can complete
|
|
# a release that previously failed partway through.
|
|
skip-existing: true
|