Skip to content

Commit e7545ff

Browse files
authored
pact-python-ffi: add build-pact-python-ffi.yml for riscv64 wheels (#1683)
* **Package**: `pact-python-ffi` * **Version**: `0.5.4.1` * **Source**: https://github.com/pact-foundation/pact-python * **Docs**: https://pact.io Compiles the `pact_ffi` CFFI extension bound to `libpact_ffi`, the Rust core shared library published by pact-foundation/pact-reference. Upstream publishes no riscv64 wheel. Mirrors [upstream's `release-ffi.yml`](https://github.com/pact-foundation/pact-python/blob/pact-python-ffi/0.5.4.1/.github/workflows/release-ffi.yml). **Differs from upstream** - Builds `libpact_ffi` from pact-reference source instead of downloading it - pact-reference ships no riscv64 release asset. - Drops musllinux - rustup.rs ships no riscv64 musl toolchain. **Matrix**: cp312-abi3 only, matching upstream's own single-interpreter release job; abi3 covers cp313/cp314, and upstream ships no free-threaded wheel for this package. **Testing** - same as upstream **License**: OK **Patches** - `0001-recognize-riscv64-in-the-hatch-build-hook.patch` - To upstream [blocked: pact-reference has no riscv64 libpact_ffi release yet]. Without it, `_lib_url` raises `UnsupportedPlatformError` on every architecture once `packaging>=26.3` reorders `sys_tags()`, and has no riscv64 case regardless. Reproduces on any architecture.
1 parent ba90050 commit e7545ff

2 files changed

Lines changed: 212 additions & 0 deletions

File tree

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# SPDX-FileCopyrightText: 2026 The RISE Project
2+
# SPDX-License-Identifier: MIT
3+
---
4+
# This workflow is based on the `build-wheels` job of
5+
# https://github.com/pact-foundation/pact-python/blob/pact-python-ffi/0.5.4.1/.github/workflows/release-ffi.yml
6+
name: Build pact-python-ffi wheels (riscv64)
7+
8+
on:
9+
workflow_dispatch:
10+
inputs:
11+
version:
12+
description: 'pact-python-ffi version to build (git tag suffix, e.g. 0.5.4.1)'
13+
required: true
14+
default: '0.5.4.1'
15+
pull_request:
16+
paths:
17+
- '.github/workflows/build-pact-python-ffi.yml'
18+
19+
concurrency:
20+
group: ${{ github.workflow }}-${{ inputs.version || '0.5.4.1' }}-${{ github.head_ref || github.run_id }}
21+
cancel-in-progress: true
22+
23+
permissions:
24+
contents: read # to fetch code (actions/checkout)
25+
26+
env:
27+
# `inputs.version` is empty on pull_request events; default to 0.5.4.1 there.
28+
PACT_PYTHON_FFI_VERSION: ${{ inputs.version || '0.5.4.1' }}
29+
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64
30+
31+
jobs:
32+
setup:
33+
uses: $/.github/workflows/_setup.yml
34+
35+
build_wheels:
36+
needs: [setup]
37+
name: Build pact-python-ffi ${{ inputs.version || '0.5.4.1' }} ${{ matrix.tag }}-manylinux_riscv64
38+
runs-on: ubuntu-24.04-riscv
39+
timeout-minutes: 360
40+
strategy:
41+
fail-fast: false
42+
matrix:
43+
include:
44+
# hatch_build.py tags the wheel cp<building-interpreter>-abi3, same as
45+
# upstream's own release job (CIBW_BUILD: cp310-*); building on our
46+
# registry's floor (cp312) covers cp312/cp313/cp314 through abi3.
47+
# Upstream ships no free-threaded wheel for this package.
48+
- tag: cp312-abi3
49+
build: >-
50+
cp312-manylinux_riscv64 cp313-manylinux_riscv64
51+
cp314-manylinux_riscv64
52+
53+
steps:
54+
- name: Checkout pact-python-ffi ${{ env.PACT_PYTHON_FFI_VERSION }}
55+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
56+
with:
57+
repository: pact-foundation/pact-python
58+
ref: pact-python-ffi/${{ env.PACT_PYTHON_FFI_VERSION }}
59+
persist-credentials: false
60+
61+
- name: Compute the matching pact-reference libpact_ffi tag
62+
# hatch_build.py derives this from the first 3 components of its own
63+
# version (pact-python-ffi versions itself {pact-reference version}.{N}).
64+
id: pact-reference
65+
run: echo "tag=libpact_ffi-v$(cut -d. -f1-3 <<< '${{ env.PACT_PYTHON_FFI_VERSION }}')" >> "$GITHUB_OUTPUT"
66+
67+
- name: Checkout python-wheels
68+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
69+
with:
70+
path: python-wheels
71+
persist-credentials: false
72+
73+
- name: Patch pact-python-ffi source
74+
run: git apply python-wheels/patches/pact-python-ffi/${{ env.PACT_PYTHON_FFI_VERSION }}/*.patch
75+
76+
- name: Build wheels
77+
uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
78+
with:
79+
package-dir: pact-python-ffi
80+
output-dir: wheelhouse/
81+
env:
82+
CIBW_BUILD: ${{ matrix.build }}
83+
CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }}
84+
# musllinux is dropped: rustup.rs ships no riscv64 musl toolchain.
85+
#
86+
# hatch_build.py's build hook downloads a prebuilt libpact_ffi from
87+
# pact-reference's GitHub releases, which has no riscv64 asset; build
88+
# it from source instead (gotcha 77) and stage the gzipped .so +
89+
# header at the exact path _download() already caches to, so the
90+
# patched platform check is satisfied with no network request.
91+
# aws-lc-sys (pulled in via pact-plugin-driver/pact_verifier's TLS
92+
# stack) ships prebuilt riscv64gc bindings, and cmake is already in
93+
# the manylinux image.
94+
CIBW_BEFORE_ALL_LINUX: |
95+
set -eux
96+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
97+
git clone --depth 1 --branch ${{ steps.pact-reference.outputs.tag }} \
98+
https://github.com/pact-foundation/pact-reference /tmp/pact-reference
99+
cd /tmp/pact-reference/rust
100+
"$HOME/.cargo/bin/cargo" build --release --locked -p pact_ffi
101+
mkdir -p {package}/src/pact_ffi/data
102+
gzip -c target/release/libpact_ffi.so \
103+
> {package}/src/pact_ffi/data/libpact_ffi-linux-riscv64.so.gz
104+
curl --proto '=https' --tlsv1.2 -sSfL \
105+
-o {package}/src/pact_ffi/data/pact.h \
106+
https://github.com/pact-foundation/pact-reference/releases/download/${{ steps.pact-reference.outputs.tag }}/pact.h
107+
CIBW_BUILD_FRONTEND: build
108+
# pyproject.toml's [tool.pytest] addopts always enables coverage
109+
# (--cov=pact_ffi et al), which requires pytest-cov to be installed.
110+
CIBW_TEST_REQUIRES: pytest pytest-cov
111+
CIBW_TEST_SOURCES: pact-python-ffi/tests/test_init.py pact-python-ffi/pyproject.toml
112+
# pact_ffi/__init__.py does `from pact_ffi.ffi import ffi, lib`, which
113+
# shadows the `pact_ffi.ffi` package attribute with the imported `ffi`
114+
# object (a cffi.FFI instance with no __file__) - go through
115+
# sys.modules instead to check the compiled extension module itself.
116+
CIBW_TEST_COMMAND: |
117+
cd pact-python-ffi &&
118+
python3 - <<'PYEOF' &&
119+
import sys
120+
import pact_ffi
121+
m = sys.modules['pact_ffi.ffi']
122+
assert m.__file__.endswith('.so'), m.__file__
123+
PYEOF
124+
python -m pytest -v tests/test_init.py
125+
126+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
127+
with:
128+
name: pact-python-ffi-${{ env.PACT_PYTHON_FFI_VERSION }}-${{ matrix.tag }}-manylinux_riscv64
129+
path: wheelhouse/*.whl
130+
if-no-files-found: error
131+
132+
publish:
133+
name: Publish pact-python-ffi ${{ inputs.version || '0.5.4.1' }}
134+
needs: [setup, build_wheels]
135+
permissions:
136+
contents: write
137+
pull-requests: write
138+
uses: $/.github/workflows/_publish-wheel.yml
139+
with:
140+
artifact-pattern: pact-python-ffi-${{ inputs.version || '0.5.4.1' }}-*-manylinux_riscv64
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2+
From: Ludovic Henry <git@ludovic.dev>
3+
Date: Thu, 10 Sep 2026 00:00:00 +0000
4+
Subject: [PATCH] recognize riscv64 in the hatch build hook
5+
6+
hatch_build.py downloads a prebuilt libpact_ffi from
7+
pact-foundation/pact-reference's GitHub releases and compiles the CFFI
8+
extension against it. `_lib_url` derives the target platform from
9+
`next(t.platform for t in packaging.tags.sys_tags())` and only
10+
recognizes `_aarch64`/`_x86_64` wheel-tag suffixes on a tag that starts
11+
with `"manylinux"`.
12+
13+
Two things are wrong for riscv64, confirmed from the CI log (job
14+
102765101921: the from-source build finishes in 39m24s, then `python -m
15+
build` still raises `UnsupportedPlatformError: Unsupported platform
16+
linux_riscv64`):
17+
18+
1. `packaging` 26.3 (an unpinned build dependency here, and the version
19+
actually resolved in that job) reordered `sys_tags()` so the generic
20+
`linux_<arch>` tag now comes before any `manylinux_*`/`musllinux_*`
21+
tag (packaging changelog: "Native linux_* platform tags are now
22+
ordered before manylinux and musllinux tags", packaging#160). So
23+
`_sys_tag_platform()` returns `linux_riscv64`, not a
24+
`manylinux_2_39_riscv64`-shaped tag - `startswith("manylinux")`
25+
never matches, on any architecture, once a fresh isolated build
26+
environment resolves `packaging>=26.3`.
27+
2. Even when the `manylinux` branch is reached, it has no riscv64 case.
28+
29+
pact-reference publishes no riscv64 libpact_ffi asset (there is no
30+
prebuilt binary to download), so recognizing the platform alone does
31+
not make the download succeed. Paired with this patch, the riscv64 CI
32+
job builds libpact_ffi from the pact-reference source at the matching
33+
tag and stages the gzipped .so at the exact
34+
`src/pact_ffi/data/libpact_ffi-linux-riscv64.so.gz` path `_download()`
35+
already caches to, so it is picked up without a network request.
36+
37+
Upstream-Status: To upstream [blocked: pact-reference publishes no riscv64 libpact_ffi release asset yet]
38+
39+
Signed-off-by: Ludovic Henry <git@ludovic.dev>
40+
---
41+
diff --git a/pact-python-ffi/hatch_build.py b/pact-python-ffi/hatch_build.py
42+
index 0000000..0000000 100644
43+
--- a/pact-python-ffi/hatch_build.py
44+
+++ b/pact-python-ffi/hatch_build.py
45+
@@ -209,6 +209,7 @@
46+
47+
aarch64 = ("_arm64", "_aarch64")
48+
x86_64 = ("_x86_64", "_amd64")
49+
+ riscv64 = ("_riscv64",)
50+
51+
# Simplified platform and architecture detection
52+
if wheel_platform.startswith("macosx"):
53+
@@ -233,7 +234,7 @@
54+
else:
55+
raise UnsupportedPlatformError(wheel_platform)
56+
57+
- elif wheel_platform.startswith("manylinux"):
58+
+ elif wheel_platform.startswith(("manylinux", "linux")):
59+
os, ext = "linux", "so.gz"
60+
prefix = "lib"
61+
suffix = ""
62+
@@ -241,6 +242,8 @@
63+
platform = "aarch64"
64+
elif wheel_platform.endswith(x86_64):
65+
platform = "x86_64"
66+
+ elif wheel_platform.endswith(riscv64):
67+
+ platform = "riscv64"
68+
else:
69+
raise UnsupportedPlatformError(wheel_platform)
70+
71+
--
72+
2.51.0

0 commit comments

Comments
 (0)