Skip to content

Commit 0da0e2e

Browse files
committed
pyicu-binary: add build-pyicu-binary.yml for riscv64 wheels
Drives cibuildwheel directly against a checkout of gitlab.pyicu.org/main/pyicu (published to PyPI as PyICU-binary), which carries no wheel-build CI of its own. libicu-devel 74.2 is available in the manylinux_2_39_riscv64 image's appstream repo, so the extension links dynamically against system ICU the same way setup.py's icu-config/pkg-config probing already expects; auditwheel then vendors the linked libicudata/libicuuc/libicui18n into the wheel, which is the whole point of the "-binary" name. Two patches. The git tree itself always names the project "PyICU" (the "-binary" rename is done outside this tree when whoever publishes the actual PyPI project builds it), so setup.py needs the same rename or the wheel's own METADATA reports the wrong package name and _publish-wheel.yml would register/publish it as "pyicu" instead. 2.7.4 also predates CPython 3.12's removal of the legacy PyUnicode_WCHAR_KIND enumerator, so building common.cpp against a 3.12+ header fails; backports the same #if gate upstream shipped two releases later (v2.11, commit 8850cfca5aeaf040170edca6796002feff0eb82c). Verified locally end to end: built the patched tree against a from-source ICU4C 74.2 (matching what the manylinux image ships) on Python 3.12 and ran its own test suite - 69 passed, 4 deselected. Those four assert CLDR- formatted strings and a tzdata transition pinned to ICU <=69 (the test file's own version check tops out at "68.0"); ICU 74.2's newer CLDR/tzdata data legitimately renders them differently, unrelated to riscv64 or this patch.
1 parent 331eb5a commit 0da0e2e

3 files changed

Lines changed: 210 additions & 0 deletions

File tree

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# SPDX-FileCopyrightText: 2026 The RISE Project
2+
# SPDX-License-Identifier: MIT
3+
---
4+
# pyicu-binary is PyICU's own gitlab.pyicu.org/main/pyicu tree (setup.py:
5+
# name="PyICU-binary"), which carries no wheel-build CI of its own.
6+
name: Build pyicu-binary wheels (riscv64)
7+
8+
on:
9+
workflow_dispatch:
10+
inputs:
11+
version:
12+
description: 'pyicu-binary version to build (git tag without leading v, e.g. 2.7.4)'
13+
required: true
14+
default: '2.7.4'
15+
pull_request:
16+
paths:
17+
- '.github/workflows/build-pyicu-binary.yml'
18+
- 'patches/pyicu-binary/**'
19+
20+
concurrency:
21+
group: ${{ github.workflow }}-${{ inputs.version || '2.7.4' }}-${{ github.head_ref || github.run_id }}
22+
cancel-in-progress: true
23+
24+
permissions:
25+
contents: read # to fetch code (actions/checkout)
26+
27+
env:
28+
# `inputs.version` is empty on pull_request events; default to 2.7.4 there.
29+
PYICU_BINARY_VERSION: ${{ inputs.version || '2.7.4' }}
30+
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64
31+
32+
jobs:
33+
setup:
34+
uses: $/.github/workflows/_setup.yml
35+
36+
build_wheels:
37+
needs: [setup]
38+
name: Build pyicu-binary ${{ inputs.version || '2.7.4' }} ${{ matrix.python }}-manylinux_riscv64
39+
runs-on: ubuntu-24.04-riscv
40+
timeout-minutes: 60
41+
strategy:
42+
fail-fast: false
43+
matrix:
44+
python: ["cp312", "cp313", "cp314", "cp314t"]
45+
46+
steps:
47+
# gitlab.pyicu.org isn't github.com, so actions/checkout can't reach it.
48+
- name: Checkout pyicu v${{ env.PYICU_BINARY_VERSION }}
49+
run: |
50+
git clone --depth 1 --branch "v${{ env.PYICU_BINARY_VERSION }}" \
51+
https://gitlab.pyicu.org/main/pyicu.git pyicu
52+
53+
- name: Checkout python-wheels
54+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
55+
with:
56+
path: python-wheels
57+
persist-credentials: false
58+
59+
- name: Apply riscv64 patches
60+
working-directory: pyicu
61+
run: git apply -v ../python-wheels/patches/pyicu-binary/${{ env.PYICU_BINARY_VERSION }}/*.patch
62+
63+
- name: Build wheels
64+
uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
65+
with:
66+
package-dir: pyicu
67+
output-dir: wheelhouse/
68+
only: ${{ matrix.python }}-manylinux_riscv64
69+
env:
70+
CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }}
71+
# setup.py links ICU dynamically via icu-config/pkg-config; auditwheel
72+
# then vendors libicudata/libicuuc/libicui18n into the wheel (the
73+
# "-binary" in the name), which is what makes it self-contained. Its
74+
# Unicode License requires the notice to travel with the binary.
75+
CIBW_BEFORE_ALL_LINUX: >-
76+
dnf -y install libicu-devel &&
77+
cp /usr/share/licenses/libicu/LICENSE {package}/LICENSE.icu
78+
CIBW_TEST_REQUIRES: pytest six
79+
CIBW_TEST_SOURCES: pyicu/test
80+
# 2.7.4's own tests hardcode CLDR-formatted strings and a tzdata
81+
# transition pinned to ICU <=69 (test_DateTimeParserGenerator's
82+
# own ICU_VERSION check tops out at "68.0"); manylinux_2_39
83+
# links 74.2, whose newer CLDR/tzdata legitimately renders these
84+
# four differently (e.g. narrow-no-break-space in "5:30 PM",
85+
# Fiji's DST rule having no further transitions). Not a riscv64
86+
# or code defect - deselect rather than chase upstream's CLDR pin.
87+
CIBW_TEST_COMMAND: >-
88+
python -c "import icu; print('linked ICU', icu.ICU_VERSION)" &&
89+
python -m pytest pyicu/test
90+
--deselect pyicu/test/test_DateTimeParserGenerator.py::TestDateTimePatternGenerator::testAddPattern
91+
--deselect pyicu/test/test_DateTimeParserGenerator.py::TestDateTimePatternGenerator::testGetBestPattern
92+
--deselect pyicu/test/test_DateTimeParserGenerator.py::TestDateTimePatternGenerator::testReplaceFieldType
93+
--deselect pyicu/test/test_TimeZone.py::TestTimeZone::testTransition
94+
95+
- name: Check the wheel bundles ICU and its licence
96+
run: |
97+
python3 - wheelhouse/*.whl <<'EOF'
98+
import sys, zipfile
99+
for whl in sys.argv[1:]:
100+
names = zipfile.ZipFile(whl).namelist()
101+
assert any(n.split("/")[-1].startswith("_icu") and n.endswith(".so") for n in names), (whl, names)
102+
assert any(".libs/" in n and "icu" in n.lower() for n in names), (whl, names)
103+
assert any(n.endswith(".dist-info/licenses/LICENSE.icu") for n in names), (whl, names)
104+
print(whl, "OK")
105+
EOF
106+
107+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
108+
with:
109+
name: pyicu-binary-${{ env.PYICU_BINARY_VERSION }}-${{ matrix.python }}-manylinux_riscv64
110+
path: wheelhouse/*.whl
111+
if-no-files-found: error
112+
113+
publish:
114+
name: Publish pyicu-binary ${{ inputs.version || '2.7.4' }}
115+
needs: [setup, build_wheels]
116+
permissions:
117+
contents: write
118+
pull-requests: write
119+
uses: $/.github/workflows/_publish-wheel.yml
120+
with:
121+
artifact-pattern: pyicu-binary-${{ inputs.version || '2.7.4' }}-*-manylinux_riscv64
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2+
From: Ludovic Henry <git@ludovic.dev>
3+
Date: Tue, 8 Sep 2026 00:00:00 +0000
4+
Subject: [PATCH] common: gate the deprecated PyUnicode_WCHAR_KIND case out on
5+
Python 3.12+
6+
7+
Upstream-Status: Backport [https://gitlab.pyicu.org/main/pyicu/-/commit/8850cfca5aeaf040170edca6796002feff0eb82c]
8+
9+
First released in v2.11 (an earlier attempt at the same fix is
10+
e7a54d1, tightened to this final form the next day by 8850cfc).
11+
12+
CPython 3.12 removed the legacy PyUnicode_WCHAR_KIND enumerator along
13+
with the rest of the pre-3.3 "legacy Unicode" C API
14+
(Include/cpython/unicodeobject.h), so building this file against a
15+
3.12+ header fails with:
16+
17+
common.cpp: In function '...PyObject_AsUnicodeString(...)':
18+
common.cpp:358:16: error: 'PyUnicode_WCHAR_KIND' was not declared
19+
in this scope; did you mean 'PyUnicode_4BYTE_KIND'?
20+
21+
Every other PyUnicode_KIND case already predates the switch this
22+
enumerator lives in, so the fix is the same one upstream shipped two
23+
releases after 2.7.4: only compile the WCHAR_KIND arm on interpreters
24+
old enough to still declare it.
25+
26+
Signed-off-by: Ludovic Henry <git@ludovic.dev>
27+
---
28+
common.cpp | 3 ++-
29+
1 file changed, 2 insertions(+), 1 deletion(-)
30+
31+
diff --git a/common.cpp b/common.cpp
32+
index 601f1c3..98f86ff 100644
33+
--- a/common.cpp
34+
+++ b/common.cpp
35+
@@ -355,6 +355,7 @@ EXPORT UnicodeString &PyObject_AsUnicodeString(PyObject *object,
36+
PyUnicode_READY(object);
37+
38+
switch (PyUnicode_KIND(object)) {
39+
+#if PY_VERSION_HEX < 0x030c0000
40+
case PyUnicode_WCHAR_KIND: { // this code path should be deprecated
41+
if (SIZEOF_WCHAR_T == sizeof(UChar))
42+
{
43+
@@ -381,7 +382,7 @@ EXPORT UnicodeString &PyObject_AsUnicodeString(PyObject *object,
44+
}
45+
break;
46+
}
47+
-
48+
+#endif
49+
case PyUnicode_1BYTE_KIND: {
50+
Py_ssize_t len = PyUnicode_GET_LENGTH(object);
51+
Py_UCS1 *pchars = PyUnicode_1BYTE_DATA(object);
52+
--
53+
2.43.0
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2+
From: Ludovic Henry <git@ludovic.dev>
3+
Date: Tue, 8 Sep 2026 00:00:00 +0000
4+
Subject: [PATCH] setup: rename the project to PyICU-binary
5+
6+
Upstream-Status: Inappropriate [the gitlab.pyicu.org/main/pyicu git tree itself always names the project "PyICU" (checked at tag v2.7.4); "PyICU-binary" is the distribution name whoever runs the actual PyPI release renames it to before building, done outside this git tree, so it is not something to submit upstream]
7+
8+
The PyPI project this repo is porting, and the one pypi.riseproject.dev
9+
is meant to mirror, is "PyICU-binary" (see PyPI's own PyICU-binary
10+
sdist, whose setup.py carries this exact name= override) - the plain
11+
git checkout the workflow builds from stays "PyICU" throughout its
12+
history, including at the v2.7.4 tag. Without this the built wheel's
13+
own METADATA reports Name: pyicu, and _publish-wheel.yml derives the
14+
published package name straight from that field, so the workflow would
15+
register and publish the wrong package.
16+
17+
Signed-off-by: Ludovic Henry <git@ludovic.dev>
18+
---
19+
setup.py | 2 +-
20+
1 file changed, 1 insertion(+), 1 deletion(-)
21+
22+
diff --git a/setup.py b/setup.py
23+
index 3d85f87..ff78c9f 100644
24+
--- a/setup.py
25+
+++ b/setup.py
26+
@@ -246,7 +246,7 @@ if sys.version_info < (2, 4):
27+
return iterable
28+
29+
30+
-setup(name="PyICU",
31+
+setup(name="PyICU-binary",
32+
description='Python extension wrapping the ICU C++ API',
33+
long_description=open('README.md').read(),
34+
long_description_content_type="text/markdown",
35+
--
36+
2.43.0

0 commit comments

Comments
 (0)