From 0da0e2ebbdcb3ce6c6a664cc2bf9c820ca8b2b6d Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Tue, 8 Sep 2026 08:14:20 +0200 Subject: [PATCH] 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. --- .github/workflows/build-pyicu-binary.yml | 121 ++++++++++++++++++ ...-deprecated-PyUnicode_WCHAR_KIND-cas.patch | 53 ++++++++ ...p-rename-the-project-to-PyICU-binary.patch | 36 ++++++ 3 files changed, 210 insertions(+) create mode 100644 .github/workflows/build-pyicu-binary.yml create mode 100644 patches/pyicu-binary/2.7.4/0001-common-gate-the-deprecated-PyUnicode_WCHAR_KIND-cas.patch create mode 100644 patches/pyicu-binary/2.7.4/0002-setup-rename-the-project-to-PyICU-binary.patch diff --git a/.github/workflows/build-pyicu-binary.yml b/.github/workflows/build-pyicu-binary.yml new file mode 100644 index 000000000..a1b044808 --- /dev/null +++ b/.github/workflows/build-pyicu-binary.yml @@ -0,0 +1,121 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# pyicu-binary is PyICU's own gitlab.pyicu.org/main/pyicu tree (setup.py: +# name="PyICU-binary"), which carries no wheel-build CI of its own. +name: Build pyicu-binary wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'pyicu-binary version to build (git tag without leading v, e.g. 2.7.4)' + required: true + default: '2.7.4' + pull_request: + paths: + - '.github/workflows/build-pyicu-binary.yml' + - 'patches/pyicu-binary/**' + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || '2.7.4' }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + # `inputs.version` is empty on pull_request events; default to 2.7.4 there. + PYICU_BINARY_VERSION: ${{ inputs.version || '2.7.4' }} + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + + build_wheels: + needs: [setup] + name: Build pyicu-binary ${{ inputs.version || '2.7.4' }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + python: ["cp312", "cp313", "cp314", "cp314t"] + + steps: + # gitlab.pyicu.org isn't github.com, so actions/checkout can't reach it. + - name: Checkout pyicu v${{ env.PYICU_BINARY_VERSION }} + run: | + git clone --depth 1 --branch "v${{ env.PYICU_BINARY_VERSION }}" \ + https://gitlab.pyicu.org/main/pyicu.git pyicu + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Apply riscv64 patches + working-directory: pyicu + run: git apply -v ../python-wheels/patches/pyicu-binary/${{ env.PYICU_BINARY_VERSION }}/*.patch + + - name: Build wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + package-dir: pyicu + output-dir: wheelhouse/ + only: ${{ matrix.python }}-manylinux_riscv64 + env: + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + # setup.py links ICU dynamically via icu-config/pkg-config; auditwheel + # then vendors libicudata/libicuuc/libicui18n into the wheel (the + # "-binary" in the name), which is what makes it self-contained. Its + # Unicode License requires the notice to travel with the binary. + CIBW_BEFORE_ALL_LINUX: >- + dnf -y install libicu-devel && + cp /usr/share/licenses/libicu/LICENSE {package}/LICENSE.icu + CIBW_TEST_REQUIRES: pytest six + CIBW_TEST_SOURCES: pyicu/test + # 2.7.4's own tests hardcode CLDR-formatted strings and a tzdata + # transition pinned to ICU <=69 (test_DateTimeParserGenerator's + # own ICU_VERSION check tops out at "68.0"); manylinux_2_39 + # links 74.2, whose newer CLDR/tzdata legitimately renders these + # four differently (e.g. narrow-no-break-space in "5:30 PM", + # Fiji's DST rule having no further transitions). Not a riscv64 + # or code defect - deselect rather than chase upstream's CLDR pin. + CIBW_TEST_COMMAND: >- + python -c "import icu; print('linked ICU', icu.ICU_VERSION)" && + python -m pytest pyicu/test + --deselect pyicu/test/test_DateTimeParserGenerator.py::TestDateTimePatternGenerator::testAddPattern + --deselect pyicu/test/test_DateTimeParserGenerator.py::TestDateTimePatternGenerator::testGetBestPattern + --deselect pyicu/test/test_DateTimeParserGenerator.py::TestDateTimePatternGenerator::testReplaceFieldType + --deselect pyicu/test/test_TimeZone.py::TestTimeZone::testTransition + + - name: Check the wheel bundles ICU and its licence + run: | + python3 - wheelhouse/*.whl <<'EOF' + import sys, zipfile + for whl in sys.argv[1:]: + names = zipfile.ZipFile(whl).namelist() + assert any(n.split("/")[-1].startswith("_icu") and n.endswith(".so") for n in names), (whl, names) + assert any(".libs/" in n and "icu" in n.lower() for n in names), (whl, names) + assert any(n.endswith(".dist-info/licenses/LICENSE.icu") for n in names), (whl, names) + print(whl, "OK") + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: pyicu-binary-${{ env.PYICU_BINARY_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish pyicu-binary ${{ inputs.version || '2.7.4' }} + needs: [setup, build_wheels] + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + with: + artifact-pattern: pyicu-binary-${{ inputs.version || '2.7.4' }}-*-manylinux_riscv64 diff --git a/patches/pyicu-binary/2.7.4/0001-common-gate-the-deprecated-PyUnicode_WCHAR_KIND-cas.patch b/patches/pyicu-binary/2.7.4/0001-common-gate-the-deprecated-PyUnicode_WCHAR_KIND-cas.patch new file mode 100644 index 000000000..b840b10e6 --- /dev/null +++ b/patches/pyicu-binary/2.7.4/0001-common-gate-the-deprecated-PyUnicode_WCHAR_KIND-cas.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Tue, 8 Sep 2026 00:00:00 +0000 +Subject: [PATCH] common: gate the deprecated PyUnicode_WCHAR_KIND case out on + Python 3.12+ + +Upstream-Status: Backport [https://gitlab.pyicu.org/main/pyicu/-/commit/8850cfca5aeaf040170edca6796002feff0eb82c] + +First released in v2.11 (an earlier attempt at the same fix is +e7a54d1, tightened to this final form the next day by 8850cfc). + +CPython 3.12 removed the legacy PyUnicode_WCHAR_KIND enumerator along +with the rest of the pre-3.3 "legacy Unicode" C API +(Include/cpython/unicodeobject.h), so building this file against a +3.12+ header fails with: + + common.cpp: In function '...PyObject_AsUnicodeString(...)': + common.cpp:358:16: error: 'PyUnicode_WCHAR_KIND' was not declared + in this scope; did you mean 'PyUnicode_4BYTE_KIND'? + +Every other PyUnicode_KIND case already predates the switch this +enumerator lives in, so the fix is the same one upstream shipped two +releases after 2.7.4: only compile the WCHAR_KIND arm on interpreters +old enough to still declare it. + +Signed-off-by: Ludovic Henry +--- + common.cpp | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/common.cpp b/common.cpp +index 601f1c3..98f86ff 100644 +--- a/common.cpp ++++ b/common.cpp +@@ -355,6 +355,7 @@ EXPORT UnicodeString &PyObject_AsUnicodeString(PyObject *object, + PyUnicode_READY(object); + + switch (PyUnicode_KIND(object)) { ++#if PY_VERSION_HEX < 0x030c0000 + case PyUnicode_WCHAR_KIND: { // this code path should be deprecated + if (SIZEOF_WCHAR_T == sizeof(UChar)) + { +@@ -381,7 +382,7 @@ EXPORT UnicodeString &PyObject_AsUnicodeString(PyObject *object, + } + break; + } +- ++#endif + case PyUnicode_1BYTE_KIND: { + Py_ssize_t len = PyUnicode_GET_LENGTH(object); + Py_UCS1 *pchars = PyUnicode_1BYTE_DATA(object); +-- +2.43.0 diff --git a/patches/pyicu-binary/2.7.4/0002-setup-rename-the-project-to-PyICU-binary.patch b/patches/pyicu-binary/2.7.4/0002-setup-rename-the-project-to-PyICU-binary.patch new file mode 100644 index 000000000..da9cb2c5c --- /dev/null +++ b/patches/pyicu-binary/2.7.4/0002-setup-rename-the-project-to-PyICU-binary.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Tue, 8 Sep 2026 00:00:00 +0000 +Subject: [PATCH] setup: rename the project to PyICU-binary + +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] + +The PyPI project this repo is porting, and the one pypi.riseproject.dev +is meant to mirror, is "PyICU-binary" (see PyPI's own PyICU-binary +sdist, whose setup.py carries this exact name= override) - the plain +git checkout the workflow builds from stays "PyICU" throughout its +history, including at the v2.7.4 tag. Without this the built wheel's +own METADATA reports Name: pyicu, and _publish-wheel.yml derives the +published package name straight from that field, so the workflow would +register and publish the wrong package. + +Signed-off-by: Ludovic Henry +--- + setup.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/setup.py b/setup.py +index 3d85f87..ff78c9f 100644 +--- a/setup.py ++++ b/setup.py +@@ -246,7 +246,7 @@ if sys.version_info < (2, 4): + return iterable + + +-setup(name="PyICU", ++setup(name="PyICU-binary", + description='Python extension wrapping the ICU C++ API', + long_description=open('README.md').read(), + long_description_content_type="text/markdown", +-- +2.43.0