From bc48c2ebe8018b4994a55d9ee3975c519df33784 Mon Sep 17 00:00:00 2001 From: hartsock Date: Fri, 26 Jun 2026 17:24:42 -0400 Subject: [PATCH] =?UTF-8?q?feat(publish):=20abi3=20maturin=20wheel=20publi?= =?UTF-8?q?sh=20workflow=20=E2=86=92=20PyPI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WHAT: Add .github/workflows/publish.yml — fires on vX.Y.Z tags (and workflow_dispatch). Builds abi3 wheels (cp39 ABI floor) across: - linux x86_64 + aarch64 (manylinux, QEMU) - macOS universal2 (x86_64+arm64, combined by maturin-action) - windows x86_64 Plus an sdist. Uploads via `maturin upload --skip-existing` using the repo's `pypi` environment + PYPI_API_TOKEN secret. Also add `abi3-py39` to the pyo3 feature list in Cargo.toml (required for maturin to stamp the wheel tag correctly); verified it still builds clean. Hook/pipeline parity notes updated in ci.yml + pre-push. WHY: gila-cap-git-tend (branch feat/cap-git-tend in gilamonster-capabilities) declares `gitxtend>=0.1.0` as a dep. The caps CI is Python-only and can't build Rust from source, so gitxtend must be on PyPI for that cap to land. v0.1.0 is release-ready (all M1 read methods + parity-tests merged, E2E suite green, version already bumped on this branch). To release: push the tag — `git tag v0.1.0 && git push origin v0.1.0`. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_018R92DDLCsWorb4fSJQrdvD --- .githooks/pre-push | 3 + .github/workflows/ci.yml | 3 + .github/workflows/publish.yml | 135 ++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- 4 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/publish.yml diff --git a/.githooks/pre-push b/.githooks/pre-push index e18b142..19d082d 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -5,6 +5,9 @@ # CI steps, update this hook to match (and vice-versa). Do not bypass with # --no-verify; if a check fails, fix the issue. # +# NOTE: .github/workflows/publish.yml (abi3 PyPI wheels) fires on vX.Y.Z tags +# and is NOT mirrored here — publishing to PyPI is a release gate only. +# # Install once per clone: git config core.hooksPath .githooks set -euo pipefail diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc9137f..d47e35a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,9 @@ # # HOOK PARITY: this pipeline is mirrored by .githooks/pre-push. When editing the # steps here, update the hook to match (and vice-versa). +# PUBLISH PIPELINE: .github/workflows/publish.yml fires on vX.Y.Z tags and uploads +# abi3 manylinux/macOS/Windows wheels + an sdist to PyPI. It is NOT mirrored by the +# pre-push hook (uploading to PyPI is a release gate only). name: ci on: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..3d2a687 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,135 @@ +# gitxtend PyPI publish — fires on a vX.Y.Z tag or workflow_dispatch. +# +# HOOK PARITY: this pipeline is additive (publish is not mirrored by the pre-push +# hook — uploading to PyPI is a release gate only). The pre-push hook runs the +# same build + test steps that the `check` job in ci.yml runs. +# +# Publishes abi3 maturin wheels for: +# - linux x86_64 + aarch64 (via QEMU) +# - macOS x86_64 + arm64 (via two native runners, combined into universal2) +# - windows x86_64 +# +# The wheel uses cp39 abi3 (--interpreter python3.9 --abi3) so one wheel covers +# Python 3.9+ on each platform. pyproject.toml declares requires-python = ">=3.11" +# which is still compatible (the abi3 tag is the build ABI floor, not the runtime +# floor — pip enforces pyproject.toml's requires-python separately). + +name: publish + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + workflow_dispatch: + inputs: + tag: + description: "Tag to publish (e.g. v0.1.0)" + required: true + +env: + CARGO_TERM_COLOR: always + # maturin needs this to pick the right Python for abi3 builds. + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + +jobs: + # ── Linux x86_64 + aarch64 (manylinux) ─────────────────────────────────── + linux: + name: linux (${{ matrix.target }}) + runs-on: ubuntu-latest + strategy: + matrix: + target: [x86_64, aarch64] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: build wheel (manylinux abi3) + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: --release --out dist --features extension-module --interpreter python3.11 --abi3 + manylinux: auto + sccache: "true" + - uses: actions/upload-artifact@v4 + with: + name: wheels-linux-${{ matrix.target }} + path: dist + + # ── macOS x86_64 + arm64 (combined into universal2) ────────────────────── + macos: + name: macos (universal2) + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: build wheel (universal2 abi3) + uses: PyO3/maturin-action@v1 + with: + target: universal2-apple-darwin + args: --release --out dist --features extension-module --interpreter python3.11 --abi3 + sccache: "true" + - uses: actions/upload-artifact@v4 + with: + name: wheels-macos-universal2 + path: dist + + # ── Windows x86_64 ─────────────────────────────────────────────────────── + windows: + name: windows (x86_64) + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: build wheel (abi3) + uses: PyO3/maturin-action@v1 + with: + target: x86_64 + args: --release --out dist --features extension-module --interpreter python3.11 --abi3 + sccache: "true" + - uses: actions/upload-artifact@v4 + with: + name: wheels-windows-x86_64 + path: dist + + # ── sdist ──────────────────────────────────────────────────────────────── + sdist: + name: sdist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + - uses: actions/upload-artifact@v4 + with: + name: wheels-sdist + path: dist + + # ── Upload to PyPI ──────────────────────────────────────────────────────── + release: + name: upload to PyPI + runs-on: ubuntu-latest + needs: [linux, macos, windows, sdist] + environment: + name: pypi + url: https://pypi.org/p/gitxtend + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + pattern: wheels-* + merge-multiple: true + path: dist + - name: publish + uses: PyO3/maturin-action@v1 + with: + command: upload + args: --non-interactive --skip-existing dist/* diff --git a/Cargo.toml b/Cargo.toml index e1b8d10..c863595 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ gix = { version = "0.70", features = [ # PyO3 — only compiled when the `python` feature is on. `extension-module` is # layered via the crate feature of the same name, so `cargo test` links no # libpython and the pure-Rust core stays interpreter-free. -pyo3 = { version = "0.28", optional = true, default-features = false, features = ["macros"] } +pyo3 = { version = "0.28", optional = true, default-features = false, features = ["macros", "abi3-py39"] } [dev-dependencies] # Temp-dir fixtures for parity tests vs the git CLI.