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