From ad73f49ec561bd295b103ddeedb0b3b1f449863e Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Tue, 13 Oct 2026 16:08:11 +0000 Subject: [PATCH 1/3] Port scipp 26.8.0 to riscv64 Multi-dimensional labeled-array library with a pybind11/scikit-build-core C++ core. lib/cmake/scipp-cpm.cmake fetches and builds llnl/units, Boost headers, Eigen3, GoogleTest (test-only) and oneTBB from source for every wheel build, so the port needs no system TBB/Eigen/Boost package; oneTBB 2023.1.0 already builds and publishes for riscv64 in this registry, confirming the toolchain handles it. license-files is an explicit PEP 639 list, so the vendored units/Boost/Eigen3/TBB licences (none of which ship in any published wheel) need widening that list rather than just dropping files at the root. --- .github/workflows/build-scipp.yml | 143 ++++++++++++++++++ docs/packages/scipp.yaml | 5 + ...iles-to-cover-vendored-CPM-dependenc.patch | 41 +++++ 3 files changed, 189 insertions(+) create mode 100644 .github/workflows/build-scipp.yml create mode 100644 docs/packages/scipp.yaml create mode 100644 patches/scipp/26.8.0/0001-Widen-license-files-to-cover-vendored-CPM-dependenc.patch diff --git a/.github/workflows/build-scipp.yml b/.github/workflows/build-scipp.yml new file mode 100644 index 000000000000..385e8bac08ae --- /dev/null +++ b/.github/workflows/build-scipp.yml @@ -0,0 +1,143 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# Mirrors the Linux leg of upstream's .github/workflows/wheel.yml (called from +# pr_and_main.yml/release.yml): a scikit-build-core/CMake/pybind11 build, with +# lib/cmake/scipp-cpm.cmake fetching llnl/units, Boost headers, Eigen3, GoogleTest and +# oneTBB at configure time. GoogleTest is test-only and never installed into the wheel; +# units/Boost/Eigen3 are statically compiled in, and oneTBB's libtbb.so ships inside the +# wheel (scipp/lib64/libtbb.so). musllinux is skipped upstream too (`skip = ["*musllinux*"]` +# in pyproject.toml). +name: Build scipp wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/scipp.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-scipp.yml' + - 'docs/packages/scipp.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-scipp.yml' + - 'docs/packages/scipp.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: scipp + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build scipp ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 180 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + python: ["cp311", "cp312", "cp313", "cp314", "cp314t"] + + env: + SCIPP_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout scipp/scipp v${{ env.SCIPP_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: scipp/scipp + ref: ${{ env.SCIPP_VERSION }} + fetch-depth: 0 # setuptools_scm needs history to determine the version + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Patch scipp source + run: git apply python-wheels/patches/scipp/${{ env.SCIPP_VERSION }}/00*.patch + + # pyproject.toml's license-files is an explicit ["LICENSE"] list (patched to also + # match LICENSE.*), so scikit-build-core's default licence glob never runs. The CPM + # dependencies ship no licence in upstream's own wheel; stage the ones actually + # compiled or linked into it at the root (GoogleTest is test-only). + - name: Stage vendored dependency licences + run: | + set -euxo pipefail + curl -fsSL --retry 5 "https://raw.githubusercontent.com/llnl/units/v0.13.1/LICENSE" -o LICENSE.units + curl -fsSL --retry 5 "https://raw.githubusercontent.com/boostorg/boost/boost-1.90.0/LICENSE_1_0.txt" -o LICENSE.boost + curl -fsSL --retry 5 "https://gitlab.com/libeigen/eigen/-/raw/3.4.0/COPYING.MPL2" -o LICENSE.eigen + curl -fsSL --retry 5 "https://raw.githubusercontent.com/uxlfoundation/oneTBB/v2023.0.0/LICENSE.txt" -o LICENSE.tbb + + - uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + output-dir: wheelhouse/ + only: ${{ matrix.python }}-manylinux_riscv64 + env: + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + # pip resolves cmake>=3.28 (our registry's newest is 4.0.3); CPM-fetched + # dependencies' own cmake_minimum_required predates CMake 4's floor. + CIBW_ENVIRONMENT: >- + CMAKE_POLICY_VERSION_MINIMUM=3.5 + PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + CIBW_TEST_EXTRAS: test + CIBW_TEST_REQUIRES: scipy graphviz pooch hypothesis + CIBW_TEST_COMMAND: >- + python -c "import importlib.metadata as m; f = {p.name for p in m.files('scipp') if '.dist-info/licenses/' in str(p)}; assert f == {'LICENSE', 'LICENSE.units', 'LICENSE.boost', 'LICENSE.eigen', 'LICENSE.tbb'}, f" + && pytest --ignore {project}/tests/plotting --ignore {project}/tests/hypothesis/ --ignore {project}/tests/experimental/ {project}/tests + # numpy/scipy have no riscv64 wheel on public PyPI; keep pip on our registry's copies. + CIBW_TEST_ENVIRONMENT: PIP_ONLY_BINARY=numpy,scipy + + - name: Check wheel contents + run: | + python3 - wheelhouse/*.whl <<'EOF' + import sys, zipfile + path = sys.argv[1] + names = zipfile.ZipFile(path).namelist() + exts = [n for n in names if n.endswith(".so")] + assert exts, "no compiled extension in wheel" + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: scipp-${{ env.SCIPP_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish scipp ${{ 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: scipp-${{ matrix.version }}-*-manylinux_riscv64 diff --git a/docs/packages/scipp.yaml b/docs/packages/scipp.yaml new file mode 100644 index 000000000000..981afaf64739 --- /dev/null +++ b/docs/packages/scipp.yaml @@ -0,0 +1,5 @@ +package-name: scipp +source-code: https://github.com/scipp/scipp +license: 'BSD-3-Clause, Third Party: Apache-2.0 (oneTBB), BSL-1.0 (Boost), MPL-2.0 (Eigen3), BSD-3-Clause (units)' +versions: +- version: 26.8.0 diff --git a/patches/scipp/26.8.0/0001-Widen-license-files-to-cover-vendored-CPM-dependenc.patch b/patches/scipp/26.8.0/0001-Widen-license-files-to-cover-vendored-CPM-dependenc.patch new file mode 100644 index 000000000000..c1ab5660cc57 --- /dev/null +++ b/patches/scipp/26.8.0/0001-Widen-license-files-to-cover-vendored-CPM-dependenc.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Tue, 13 Oct 2026 00:00:00 +0200 +Subject: [PATCH] Widen license-files to cover vendored CPM dependencies + +pyproject.toml declares the PEP 639 license-files = ["LICENSE"] as an +explicit list, so scikit-build-core's default LICEN[CS]E*/COPYING*/ +NOTICE*/AUTHORS* glob never runs and only that one path is ever +collected. The wheel statically links several dependencies that +lib/cmake/scipp-cpm.cmake fetches and builds from source for every +wheel build (llnl/units, Boost headers, Eigen3) and dynamically links +oneTBB's built libtbb.so into the wheel itself (visible in the +published wheel as scipp/lib64/libtbb.so). None of those licences ship +in any published wheel; verified against the real scipp-26.8.0 wheels +on PyPI, which have the same gap. + +Widening the explicit list to also match LICENSE.* lets the build add +LICENSE.units/LICENSE.boost/LICENSE.eigen/LICENSE.tbb at the project +root with no other packaging change (GoogleTest, the only other CPM +dependency, is test-only and never installed into the wheel). + +Upstream-Status: To upstream [not yet submitted; this port may only touch riseproject-dev/python-wheels] +--- + pyproject.toml | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/pyproject.toml b/pyproject.toml +index eb38d37a6..b29e5cc93 100644 +--- a/pyproject.toml ++++ b/pyproject.toml +@@ -11,7 +11,7 @@ name = "scipp" + description = "Multi-dimensional data arrays with labeled dimensions" + readme = "README.md" + license = "BSD-3-Clause" +-license-files = ["LICENSE"] ++license-files = ["LICENSE", "LICENSE.*"] + authors = [{ name = "Scipp contributors (https://github.com/scipp)" }] + classifiers = [ + "Intended Audience :: Science/Research", +-- +2.43.0 From 43526216a06fb3cec2ac5be8db9558c712fdbb67 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 23 Oct 2026 17:58:10 +0000 Subject: [PATCH 2/3] scipp: Pin version, raise timeout and keep test deps binary-only The riscv64 build of one interpreter takes ~2h55m, so every job hit the 180-minute timeout during the test step. The patched checkout is dirty, so setuptools_scm named the wheel 26.8.1.dev0+g.d; pin it with SETUPTOOLS_SCM_PRETEND_VERSION_FOR_SCIPP and drop the now-unneeded full history fetch. The test venv also pulled the matplotlib 3.11.2 sdist (and would do the same for pillow and, on cp314t, hypothesis), so extend PIP_ONLY_BINARY to the native test dependencies our registry provides. --- .github/workflows/build-scipp.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-scipp.yml b/.github/workflows/build-scipp.yml index 385e8bac08ae..db86dccdbf3d 100644 --- a/.github/workflows/build-scipp.yml +++ b/.github/workflows/build-scipp.yml @@ -50,7 +50,7 @@ jobs: if: needs.setup.outputs.versions != '[]' name: Build scipp ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 runs-on: ubuntu-24.04-riscv - timeout-minutes: 180 + timeout-minutes: 360 strategy: fail-fast: false matrix: @@ -66,7 +66,6 @@ jobs: with: repository: scipp/scipp ref: ${{ env.SCIPP_VERSION }} - fetch-depth: 0 # setuptools_scm needs history to determine the version persist-credentials: false - name: Checkout python-wheels @@ -97,17 +96,20 @@ jobs: env: CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} # pip resolves cmake>=3.28 (our registry's newest is 4.0.3); CPM-fetched - # dependencies' own cmake_minimum_required predates CMake 4's floor. + # dependencies' own cmake_minimum_required predates CMake 4's floor. The + # patched (dirty) checkout would otherwise read as 26.8.1.dev0+g. CIBW_ENVIRONMENT: >- CMAKE_POLICY_VERSION_MINIMUM=3.5 + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_SCIPP=${{ env.SCIPP_VERSION }} PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ CIBW_TEST_EXTRAS: test CIBW_TEST_REQUIRES: scipy graphviz pooch hypothesis CIBW_TEST_COMMAND: >- python -c "import importlib.metadata as m; f = {p.name for p in m.files('scipp') if '.dist-info/licenses/' in str(p)}; assert f == {'LICENSE', 'LICENSE.units', 'LICENSE.boost', 'LICENSE.eigen', 'LICENSE.tbb'}, f" && pytest --ignore {project}/tests/plotting --ignore {project}/tests/hypothesis/ --ignore {project}/tests/experimental/ {project}/tests - # numpy/scipy have no riscv64 wheel on public PyPI; keep pip on our registry's copies. - CIBW_TEST_ENVIRONMENT: PIP_ONLY_BINARY=numpy,scipy + # These have no (or lagging) riscv64 wheels on public PyPI; keep pip on our + # registry's copies instead of building a newer sdist in the test venv. + CIBW_TEST_ENVIRONMENT: PIP_ONLY_BINARY=numpy,scipy,matplotlib,contourpy,kiwisolver,pillow,psutil,hypothesis - name: Check wheel contents run: | From 5368d6397781a8f07fcbcbacb222f402d4a55843 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 25 Oct 2026 18:02:10 +0000 Subject: [PATCH 3/3] scipp: pin pybind11<3 to fix int-scalar dtype corruption cp312/cp314 CI failed 22 of upstream's own tests with DTypeError (add_equals, copy, concat, ... "does not support dtypes 'int64', 'float64'"). Reproduced against the official x86_64 PyPI wheel: those same operations work there, so this isn't a riscv64 codegen issue. Root cause: pyproject.toml's build-system.requires pins pybind11 to no version, so pip resolves the newest at build time. pybind11 3.x changed overload resolution for a plain Python int against overloads registered as (double, int64_t) in that order -- exactly how bind_operators.h registers Variable's scalar arithmetic. 2.x picks int64_t for the int; 3.x now picks double, silently promoting int-valued scalars/results to float64 throughout scipp's Python layer. Confirmed with a minimal pybind11 2.13.6 vs 3.1.0 repro using the same overload order. --- .github/workflows/build-scipp.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/build-scipp.yml b/.github/workflows/build-scipp.yml index db86dccdbf3d..7e712b6e343f 100644 --- a/.github/workflows/build-scipp.yml +++ b/.github/workflows/build-scipp.yml @@ -77,6 +77,18 @@ jobs: - name: Patch scipp source run: git apply python-wheels/patches/scipp/${{ env.SCIPP_VERSION }}/00*.patch + # pyproject.toml's build-system.requires pins pybind11 to no version at all, so + # pip resolves whatever is newest at build time. pybind11 3.x changed overload + # resolution for a plain Python int against overloads of (double, int64_t) + # registered in that order (as scipp's bind_operators.h does for Variable's + # scalar arithmetic): 2.x picks int64_t, 3.x now picks double. That silently + # promotes int-valued scalars/results to float64 throughout scipp's Python + # layer (copy, add_equals, concat, ...), which is what actually failed 22 of + # upstream's own tests here, not a riscv64 codegen issue (confirmed: those + # same operations behave correctly against the official x86_64 PyPI wheel). + - name: Pin pybind11 below the major that reorders numeric overload resolution + run: echo 'pybind11<3' >> build-constraints.txt + # pyproject.toml's license-files is an explicit ["LICENSE"] list (patched to also # match LICENSE.*), so scikit-build-core's default licence glob never runs. The CPM # dependencies ship no licence in upstream's own wheel; stage the ones actually @@ -98,9 +110,13 @@ jobs: # pip resolves cmake>=3.28 (our registry's newest is 4.0.3); CPM-fetched # dependencies' own cmake_minimum_required predates CMake 4's floor. The # patched (dirty) checkout would otherwise read as 26.8.1.dev0+g. + # pip >= 26 ignores PIP_CONSTRAINT inside isolated build environments -- + # PIP_BUILD_CONSTRAINT is the replacement, and it reaches nested builds. CIBW_ENVIRONMENT: >- CMAKE_POLICY_VERSION_MINIMUM=3.5 SETUPTOOLS_SCM_PRETEND_VERSION_FOR_SCIPP=${{ env.SCIPP_VERSION }} + PIP_CONSTRAINT=build-constraints.txt + PIP_BUILD_CONSTRAINT=build-constraints.txt PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ CIBW_TEST_EXTRAS: test CIBW_TEST_REQUIRES: scipy graphviz pooch hypothesis