Define the ARMv8 assembler macro for legacy AArch64 cross-compilers used by the npm and PyPI release matrices, and keep independent targets running when one fails.
175 lines
6.2 KiB
YAML
175 lines
6.2 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
fetch-depth: 2
|
|
|
|
- name: Check package version sync
|
|
run: python3 scripts/version.py --check
|
|
|
|
- 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:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- os: ubuntu-latest
|
|
target: x86_64-unknown-linux-gnu
|
|
- os: ubuntu-latest
|
|
target: aarch64-unknown-linux-gnu
|
|
docker-options: -e CFLAGS_aarch64_unknown_linux_gnu=-D__ARM_ARCH=8
|
|
# macos-13 was retired by GitHub; macos-15-intel is the remaining
|
|
# Intel runner label (available through 2027).
|
|
- os: macos-15-intel
|
|
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
with:
|
|
python-version: '3.12'
|
|
|
|
- name: Build wheel
|
|
uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
|
|
with:
|
|
target: ${{ matrix.target }}
|
|
args: --release --out dist
|
|
manylinux: auto
|
|
# The manylinux AArch64 GCC omits this macro while preprocessing
|
|
# ring's assembly. AArch64 is ARMv8 by definition.
|
|
docker-options: ${{ matrix.docker-options }}
|
|
|
|
- name: Upload wheel
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Build sdist
|
|
uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
|
|
with:
|
|
command: sdist
|
|
args: --out dist
|
|
|
|
- name: Upload sdist
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
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@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
path: dist
|
|
merge-multiple: true
|
|
|
|
- name: List artifacts
|
|
run: ls -la dist/
|
|
|
|
- name: Publish to PyPI
|
|
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # 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
|