From f364c48d163794eafd8aab506c2163f13a071c47 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 21 Sep 2026 04:06:41 +0000 Subject: [PATCH] simpleitk: Add version 2.5.6 Builds SimpleITK's SWIG Python bindings and the ITK 5.4.7 C++ world behind them for riscv64, the same two-stage recipe upstream's own Linux wheel job runs in Utilities/Distribution/manylinux/imagefiles/cmd.sh: the SuperBuild compiles Lua, PCRE2, SWIG 4.4.1, ITK and the static SimpleITK core, then Wrapping/Python is configured against that build tree and packaged with setup.py. No cibuildwheel, no scikit-build, and no ITK dependency wheel - upstream's wheel statically links its own ITK build into _SimpleITK.so. The interpreter set follows the wheels upstream publishes rather than this repo's default: dist/CMakeLists.txt passes --py-limited-api=cp311, so the limited-API wheel is tagged cp311-abi3 and is built on cp311 to match the tag it carries, covering cp312 through cp314; cp314t is a second build because free-threaded interpreters cannot use the stable ABI. Nothing in ITK or SimpleITK is architecture-specific: every x86 intrinsic in the vendored trees (Eigen, vnl, openjpeg, zlib-ng) sits behind its own ISA gate. The exception is ITK's vendored zlib-ng, which turns its RVV kernels on by default on riscv and probes them with a vsetvli that SIGILLs on this runner hardware, so the build passes WITH_RVV=OFF - which needs one patch, because SuperBuild/External_ITK.cmake forwards no WITH_* variable to the ITK sub-build. Testing runs upstream's Python unit tests from Testing/Unit/Python against the installed wheel; BUILD_TESTING is off in both CMake stages, which skips the C++ test binaries and their ExternalData downloads. --- .github/workflows/build-simpleitk.yml | 232 ++++++++++++++++++ docs/packages/simpleitk.yaml | 6 + ...rd-WITH_-cache-variables-to-the-ITK-.patch | 37 +++ 3 files changed, 275 insertions(+) create mode 100644 .github/workflows/build-simpleitk.yml create mode 100644 docs/packages/simpleitk.yaml create mode 100644 patches/simpleitk/2.5.6/0001-SuperBuild-forward-WITH_-cache-variables-to-the-ITK-.patch diff --git a/.github/workflows/build-simpleitk.yml b/.github/workflows/build-simpleitk.yml new file mode 100644 index 0000000000..73f4b00e19 --- /dev/null +++ b/.github/workflows/build-simpleitk.yml @@ -0,0 +1,232 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow mirrors the two-stage recipe SimpleITK's own Linux wheel job runs in +# Utilities/Distribution/manylinux/imagefiles/cmd.sh (driven by Package.yml's +# package-docker job): the SuperBuild compiles Lua, PCRE2, SWIG, ITK and the SimpleITK +# core, then Wrapping/Python is configured against that build tree once per interpreter +# and packaged with setup.py - no cibuildwheel and no scikit-build. +name: Build simpleitk wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/simpleitk.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-simpleitk.yml' + - 'docs/packages/simpleitk.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-simpleitk.yml' + - 'docs/packages/simpleitk.yaml' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: simpleitk + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build simpleitk ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 1440 # 24h: ITK plus ~380 generated filter translation units is + # ~1h30m of compile on upstream's own 4-core x86 runners + # (open.cdash.org, project SimpleITK), and these 4-core + # riscv64 runners are several times slower per core. + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + # Wrapping/Python/dist/CMakeLists.txt hardcodes --py-limited-api=cp311, so the + # limited-API wheel is tagged cp311-abi3 and must be built on cp311 (gotcha 96); + # it covers cp312-cp314, and free-threaded builds cannot use the stable ABI. + python: ["cp311", "cp314t"] + + env: + SIMPLEITK_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout SimpleITK v${{ matrix.version }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: SimpleITK/SimpleITK + ref: v${{ env.SIMPLEITK_VERSION }} + fetch-depth: 0 # Version.cmake derives the wheel version with git describe + persist-credentials: false + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + # Lets -DWITH_RVV=OFF below reach ITK's vendored zlib-ng, whose RVV feature probe + # SIGILLs on this runner hardware (gotchas 272/279). + - name: Patch SimpleITK source + run: git apply python-wheels/patches/simpleitk/${{ env.SIMPLEITK_VERSION }}/00*.patch + + - name: Write container script + run: | + mkdir -p build + cat > riscv64-build-and-test.sh <<'EOF' + #!/bin/bash + set -e -x + + case "${PYTHON_TAG}" in + *t) python_prefix="/opt/python/${PYTHON_TAG%t}-${PYTHON_TAG}" ;; + *) python_prefix="/opt/python/${PYTHON_TAG}-${PYTHON_TAG}" ;; + esac + python_exe="${python_prefix}/bin/python" + + case "$1" in + build) + dnf install -y --setopt=install_weak_deps=False cmake + + # ITK's vendored third-party trees declare cmake_minimum_required below 3.5, + # which the image's own cmake 4.x - ahead of /usr/bin on PATH - rejects + # (gotcha 358). + cmake=/usr/bin/cmake + + "${cmake}" -S /simpleitk/SuperBuild -B /build/core \ + -DCMAKE_BUILD_TYPE:STRING=Release \ + -DBUILD_SHARED_LIBS:BOOL=OFF \ + -DBUILD_EXAMPLES:BOOL=OFF \ + -DBUILD_TESTING:BOOL=OFF \ + -DWRAP_DEFAULT:BOOL=OFF \ + -DSimpleITK_BUILD_DISTRIBUTE:BOOL=ON \ + -DSimpleITK_BUILD_STRIP:BOOL=ON \ + -DITK_C_OPTIMIZATION_FLAGS:STRING= \ + -DITK_CXX_OPTIMIZATION_FLAGS:STRING= \ + -DWITH_RVV:BOOL=OFF + "${cmake}" --build /build/core --parallel "$(nproc)" + find /build/core -name '*.o' -delete + + limited_api=ON + case "${PYTHON_TAG}" in *t) limited_api=OFF ;; esac + + # SimpleITK_PYTHON_USE_VIRTUALENV would have the dist target pip install + # "numpy<2.5" into a fresh venv, which has no riscv64 wheel anywhere and would + # be compiled from source; VIRTUAL_PYTHON_EXECUTABLE points it at the + # interpreter we build for instead. + "${cmake}" -S /simpleitk/Wrapping/Python -B /build/python \ + -DCMAKE_BUILD_TYPE:STRING=Release \ + -DCMAKE_C_FLAGS:STRING=-fvisibility=hidden \ + -DCMAKE_CXX_FLAGS:STRING="-fvisibility=hidden -fvisibility-inlines-hidden" \ + -DCMAKE_MODULE_PATH:PATH=/simpleitk \ + -DCMAKE_PREFIX_PATH:PATH=/build/core \ + -DSWIG_EXECUTABLE:FILEPATH=/build/core/Swig/bin/swig \ + -DSWIG_DIR:PATH=/build/core/Swig \ + -DBUILD_TESTING:BOOL=OFF \ + -DSimpleITK_BUILD_DISTRIBUTE:BOOL=ON \ + -DSimpleITK_BUILD_STRIP:BOOL=ON \ + -DSimpleITK_PYTHON_WHEEL:BOOL=ON \ + -DSimpleITK_PYTHON_EGG:BOOL=OFF \ + -DSimpleITK_PYTHON_USE_LIMITED_API:BOOL="${limited_api}" \ + -DSimpleITK_PYTHON_USE_VIRTUALENV:BOOL=OFF \ + -DVIRTUAL_PYTHON_EXECUTABLE:FILEPATH="${python_exe}" \ + -DPython_EXECUTABLE:FILEPATH="${python_exe}" \ + -DPython_INCLUDE_DIR:PATH="$(find -L "${python_prefix}/include" -name Python.h -printf '%h\n')" + + "${python_exe}" -m pip install -U setuptools wheel + "${cmake}" --build /build/python --parallel "$(nproc)" + "${cmake}" --build /build/python --target dist + + wheel=$(find /build/python -name '*.whl') + auditwheel show "${wheel}" + auditwheel repair -w /build/wheelhouse "${wheel}" + ;; + test) + "${python_exe}" -m venv /tmp/venv + . /tmp/venv/bin/activate + pip install -U pip + pip install numpy + pip install /build/wheelhouse/*.whl + + mkdir -p /tmp/tests/output + cp /simpleitk/Testing/Unit/Python/*.py /tmp/tests/ + cd /tmp/tests + python -c "from SimpleITK import _SimpleITK; assert _SimpleITK.__file__.endswith('.so'), _SimpleITK.__file__" + python -c "import SimpleITK; print(SimpleITK.Version())" + for test in sitkImageTests sitkBasicFilterTests sitkTransformTests \ + sitkImageIndexingTest sitkNumpyArrayConversionTest \ + sitkGetArrayViewFromImageTest sitkProcessObjectTest \ + sitkImageReadWrite sitkFlatStaticMethod sitkGetGDCMSeriesIDs \ + ConcurrentImageRead; do + python "${test}.py" + done + python IOTest.py /tmp/tests/output + ;; + *) + echo "usage: $0 " >&2 + exit 1 + ;; + esac + EOF + + - name: Build wheel + run: | + podman run -t \ + --log-driver=none \ + --network=host \ + -v "${PWD}":/simpleitk \ + -v "${PWD}/build":/build \ + --workdir /simpleitk \ + -e PYTHON_TAG="${{ matrix.python }}" \ + --pull=newer \ + "${MANYLINUX_RISCV64_IMAGE}" \ + bash /simpleitk/riscv64-build-and-test.sh build + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: simpleitk-${{ env.SIMPLEITK_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: build/wheelhouse/*.whl + if-no-files-found: error + + - name: Test wheel + run: | + podman run -t \ + --log-driver=none \ + --network=host \ + -v "${PWD}":/simpleitk \ + -v "${PWD}/build":/build \ + --workdir /simpleitk \ + -e PYTHON_TAG="${{ matrix.python }}" \ + -e PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ \ + -e PIP_ONLY_BINARY=numpy \ + "${MANYLINUX_RISCV64_IMAGE}" \ + bash /simpleitk/riscv64-build-and-test.sh test + + publish: + name: Publish simpleitk ${{ matrix.version }} + needs: [setup, build_wheels] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + secrets: + app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }} + with: + artifact-pattern: simpleitk-${{ matrix.version }}-*-manylinux_riscv64 diff --git a/docs/packages/simpleitk.yaml b/docs/packages/simpleitk.yaml new file mode 100644 index 0000000000..e4bfd42216 --- /dev/null +++ b/docs/packages/simpleitk.yaml @@ -0,0 +1,6 @@ +package-name: simpleitk +source-code: https://github.com/SimpleITK/SimpleITK +license: Apache-2.0 +versions: +- version: 2.5.6 + patched: true diff --git a/patches/simpleitk/2.5.6/0001-SuperBuild-forward-WITH_-cache-variables-to-the-ITK-.patch b/patches/simpleitk/2.5.6/0001-SuperBuild-forward-WITH_-cache-variables-to-the-ITK-.patch new file mode 100644 index 0000000000..f8933a1715 --- /dev/null +++ b/patches/simpleitk/2.5.6/0001-SuperBuild-forward-WITH_-cache-variables-to-the-ITK-.patch @@ -0,0 +1,37 @@ +From 6cc39b5660aa8f02803d22eec2cf022ffb9e461b Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Mon, 21 Sep 2026 04:03:14 +0000 +Subject: [PATCH] SuperBuild: forward WITH_* cache variables to the ITK + external project + +External_ITK.cmake forwards the ITK_*, ITKV3/4, FFTW, GDCM_, NIFTI_ and +Module_ variables of the SuperBuild cache into the ITK sub-build, but not +the WITH_* ones. ITK's vendored zlib-ng (Modules/ThirdParty/ZLIB) is added +with a plain add_subdirectory(), so all of its architecture-kernel toggles +are ordinary WITH_* options of the ITK build - and with nothing forwarding +them, a SuperBuild caller has no way to turn one off. + +riscv64 needs WITH_RVV=OFF: zlib-ng enables its RVV kernels by default on +riscv (option(WITH_RVV ... ON) once BASEARCH_RISCV_FOUND), and its +riscv_features.c probe confirms the kernel's HWCAP report by executing +vsetvli itself, which raises SIGILL on the riscv64 hardware this wheel is +built and tested on - crashing any process that reaches deflate/inflate, +i.e. every compressed NIfTI/NRRD/MetaImage or HDF5 transform read. + +Upstream-Status: Inappropriate [riscv64 build-host workaround; the real defect is zlib-ng's RVV probe trusting an instruction it is testing for, and nothing in SimpleITK's own code is wrong] +--- + SuperBuild/External_ITK.cmake | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/SuperBuild/External_ITK.cmake b/SuperBuild/External_ITK.cmake +index 0eadf34..3bfd58b 100644 +--- a/SuperBuild/External_ITK.cmake ++++ b/SuperBuild/External_ITK.cmake +@@ -45,6 +45,7 @@ foreach (_varName ${_varNames}) + OR _varName MATCHES "^GDCM_" + OR _varName MATCHES "^NIFTI_" + OR _varName MATCHES "^Module_" ++ OR _varName MATCHES "^WITH_" + OR _varName STREQUAL "TBB_DIR") + message( STATUS "Passing variable \"${_varName}=${${_varName}}\" to ITK external project.") + list(APPEND ITK_VARS ${_varName})