Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 232 additions & 0 deletions .github/workflows/build-simpleitk.yml
Original file line number Diff line number Diff line change
@@ -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 <build|test>" >&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
6 changes: 6 additions & 0 deletions docs/packages/simpleitk.yaml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
From 6cc39b5660aa8f02803d22eec2cf022ffb9e461b Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
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})
Loading