Skip to content

Commit b6420ce

Browse files
committed
build(packaging): switch wheel build to maturin, ship Rust binary alongside Python source
Replaces hatchling with maturin so the Python wheel ships *both* the existing `mergify_cli/` package and the new Rust binary as a `bindings = "bin"` console-script. The two installs side-by-side in the same venv layout pip already produces: <venv>/bin/mergify # Rust binary (this commit's # entry point) <venv>/bin/python3 # interpreter pip set up <venv>/lib/.../site-packages/ # Python source + deps The shim no longer has to embed the Python source or bootstrap a Python interpreter — pip already installed both alongside the binary. `mergify-py-shim` shrinks from ~250 lines (include_dir + atomic file-locked extraction + cache management) to ~130 lines: locate the sibling `python3`, exec `python3 -m mergify_cli`, return the exit code. Drops `dirs`, `fs2`, `include_dir` deps. A new `MERGIFY_PYTHON_EXE` env var lets `cargo build` developers point at any Python interpreter that has the package on `sys.path`; the no-sibling-python failure mode now produces a targeted `PythonNotFound` with the exact path it tried. This unlocks the "delete the Python copy when porting to Rust" rule: each future port PR removes its Python implementation in the same change, so there's never two parallel copies of the same command. Drift goes from "watch for it" to "structurally impossible" — no compat-test snapshot maintenance, no flag-by-flag mirroring. Phase 6 (curl-installable static binary, no Python at all) drops this crate entirely. Trade-offs: - Wheels are now platform-tagged (per maturin `bindings = "bin"`). The release workflow must build a per-platform matrix; the existing `uv build` single-platform path will need an update before the next PyPI publish (separate PR). - Version handling: `dynamic = ["version"]` flows from `crates/mergify-cli/Cargo.toml`'s `[package] version` instead of hatch-vcs reading git tags. Release tooling must bump the Cargo version to match the tag (also addressed in the release-workflow follow-up). Verified locally: `uvx maturin build --release` produces `target/wheels/mergify_cli-*-macosx_*.whl`. Installing into a fresh `uv venv` lands `mergify` at `<venv>/bin/mergify` (Mach-O arm64) and the Python source under `site-packages/mergify_cli/`. Both `mergify --help` (shimmed → Python click help) and `mergify config validate` (native → Rust impl) work. Change-Id: I8f6aa397e8736aa9313c033e19e5244c34038512
1 parent f6a87c8 commit b6420ce

7 files changed

Lines changed: 275 additions & 359 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ jobs:
1717
with:
1818
python-version: 3.14
1919

20+
# The maturin build-backend invokes cargo at sync time, so
21+
# every Python-using job needs the Rust toolchain on PATH.
22+
- name: Install Rust toolchain
23+
run: |
24+
rustup toolchain install stable --profile minimal
25+
rustup default stable
26+
- uses: Swatinem/rust-cache@v2
27+
2028
- uses: astral-sh/setup-uv@v8.1.0
2129
with:
2230
enable-cache: true
@@ -35,6 +43,12 @@ jobs:
3543
with:
3644
python-version: 3.14
3745

46+
- name: Install Rust toolchain
47+
run: |
48+
rustup toolchain install stable --profile minimal
49+
rustup default stable
50+
- uses: Swatinem/rust-cache@v2
51+
3852
- uses: astral-sh/setup-uv@v8.1.0
3953
with:
4054
enable-cache: true
@@ -71,7 +85,7 @@ jobs:
7185
run: cargo build --release
7286

7387
test:
74-
timeout-minutes: 10
88+
timeout-minutes: 15
7589
strategy:
7690
matrix:
7791
os: [ubuntu-24.04, windows-2025, macos-15]
@@ -83,6 +97,17 @@ jobs:
8397
with:
8498
python-version: ${{ matrix.python }}
8599

100+
- name: Install Rust toolchain
101+
run: |
102+
rustup toolchain install stable --profile minimal
103+
rustup default stable
104+
- uses: Swatinem/rust-cache@v2
105+
with:
106+
# The matrix runs the same target six times. Keying the
107+
# cache on (os, python) avoids cross-pollution while still
108+
# letting cargo reuse fetched crates within each shard.
109+
key: ${{ matrix.os }}-py${{ matrix.python }}
110+
86111
- uses: astral-sh/setup-uv@v8.1.0
87112
with:
88113
enable-cache: true

.github/workflows/release.yml

Lines changed: 104 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,38 @@
11
name: upload release to PyPI
2+
3+
# Builds platform-tagged wheels (one per supported target) plus an
4+
# sdist, then publishes everything to PyPI in a single
5+
# Trusted-Publisher upload. The wheel version is stamped from the
6+
# git tag (`$GITHUB_REF`) so the existing tag-driven release UX
7+
# stays the same as the pre-port hatch-vcs flow.
8+
#
9+
# Targets: Linux x64/arm64 (manylinux), macOS x64/arm64,
10+
# Windows x64. Add new targets here when a new platform is on the
11+
# support matrix.
12+
213
on:
314
release:
415
types:
516
- published
617

718
jobs:
8-
pypi-publish:
9-
name: upload release to PyPI
10-
runs-on: ubuntu-latest
11-
environment: release
12-
permissions:
13-
id-token: write
14-
contents: write
19+
build-wheels:
20+
name: build wheel (${{ matrix.target }})
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
include:
25+
- os: ubuntu-24.04
26+
target: x86_64-unknown-linux-gnu
27+
- os: ubuntu-24.04
28+
target: aarch64-unknown-linux-gnu
29+
- os: macos-15
30+
target: x86_64-apple-darwin
31+
- os: macos-15
32+
target: aarch64-apple-darwin
33+
- os: windows-2025
34+
target: x86_64-pc-windows-msvc
35+
runs-on: ${{ matrix.os }}
1536
steps:
1637
- uses: actions/checkout@v6.0.2
1738
with:
@@ -22,13 +43,84 @@ jobs:
2243
with:
2344
python-version: 3.14
2445

25-
- uses: astral-sh/setup-uv@v8.1.0
46+
- name: Stamp wheel version from git tag
47+
shell: bash
48+
run: |
49+
set -eu
50+
VERSION="${GITHUB_REF#refs/tags/}"
51+
# `sed -i.bak`/rm form works on both BSD (macOS) and GNU
52+
# (Linux) sed without divergent flag handling. On Windows
53+
# the runner ships GNU sed via Git Bash, which honours the
54+
# same syntax.
55+
sed -i.bak -E "s/^version = \".*\"$/version = \"${VERSION}\"/" pyproject.toml
56+
rm -f pyproject.toml.bak
57+
echo "Stamped pyproject.toml:"
58+
grep '^version = ' pyproject.toml
59+
60+
- name: Build wheel
61+
uses: PyO3/maturin-action@v1
62+
with:
63+
target: ${{ matrix.target }}
64+
# `manylinux: auto` picks the most-compatible glibc the
65+
# target supports; `--strip` shaves debug symbols off the
66+
# bundled binary.
67+
args: --release --strip --out target/wheels
68+
manylinux: auto
69+
sccache: true
70+
71+
- uses: actions/upload-artifact@v4
72+
with:
73+
name: wheel-${{ matrix.target }}
74+
path: target/wheels/*.whl
75+
76+
build-sdist:
77+
name: build sdist
78+
runs-on: ubuntu-24.04
79+
steps:
80+
- uses: actions/checkout@v6.0.2
81+
with:
82+
fetch-depth: 0
83+
fetch-tags: true
84+
85+
- name: Stamp wheel version from git tag
86+
shell: bash
87+
run: |
88+
set -eu
89+
VERSION="${GITHUB_REF#refs/tags/}"
90+
sed -i.bak -E "s/^version = \".*\"$/version = \"${VERSION}\"/" pyproject.toml
91+
rm -f pyproject.toml.bak
92+
93+
- uses: PyO3/maturin-action@v1
94+
with:
95+
command: sdist
96+
args: --out target/wheels
97+
98+
- uses: actions/upload-artifact@v4
99+
with:
100+
name: wheel-sdist
101+
path: target/wheels/*.tar.gz
102+
103+
publish:
104+
name: publish to PyPI
105+
runs-on: ubuntu-24.04
106+
needs:
107+
- build-wheels
108+
- build-sdist
109+
environment: release
110+
permissions:
111+
id-token: write
112+
contents: write
113+
steps:
114+
- uses: actions/download-artifact@v4
26115
with:
27-
enable-cache: true
28-
version-file: requirements-uv.txt
116+
pattern: wheel-*
117+
merge-multiple: true
118+
path: dist
29119

30-
- name:
31-
run: uv build
120+
- name: Confirm collected artifacts
121+
run: ls -la dist
32122

33123
- name: Publish package distributions to PyPI
34124
uses: pypa/gh-action-pypi-publish@release/v1
125+
with:
126+
packages-dir: dist

Cargo.lock

Lines changed: 1 addition & 102 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/mergify-py-shim/Cargo.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,10 @@ description = "Embedded Python fallback for un-ported mergify-cli commands."
1010
publish = false
1111

1212
[dependencies]
13-
dirs = "6.0"
14-
fs2 = "0.4"
15-
include_dir = "0.7"
1613
thiserror = "2.0"
1714

1815
[dev-dependencies]
19-
tempfile = "3.14"
16+
temp-env = "0.3"
2017

2118
[lints]
2219
workspace = true

0 commit comments

Comments
 (0)