From 71a7681ec9190b3b473a36fe6695e3b0d49581fa Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Tue, 13 Oct 2026 14:31:28 +0000 Subject: [PATCH 1/3] scikit-bio: Add version 0.7.3 --- .github/workflows/build-scikit-bio.yml | 113 +++++++++++++++++++++++++ docs/packages/scikit-bio.yaml | 5 ++ 2 files changed, 118 insertions(+) create mode 100644 .github/workflows/build-scikit-bio.yml create mode 100644 docs/packages/scikit-bio.yaml diff --git a/.github/workflows/build-scikit-bio.yml b/.github/workflows/build-scikit-bio.yml new file mode 100644 index 00000000000..d23e5155150 --- /dev/null +++ b/.github/workflows/build-scikit-bio.yml @@ -0,0 +1,113 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on the `build_wheels` job of +# https://github.com/scikit-bio/scikit-bio/blob/0.7.3/.github/workflows/wheels.yml +name: Build scikit-bio wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/scikit-bio.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-scikit-bio.yml' + - 'docs/packages/scikit-bio.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-scikit-bio.yml' + - 'docs/packages/scikit-bio.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: scikit-bio + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build scikit-bio ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + # Matches upstream's own CIBW_BUILD: no free-threaded wheels are published. + python: + - "cp310" + - "cp311" + - "cp312" + - "cp313" + - "cp314" + + env: + SCIKIT_BIO_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout scikit-bio ${{ env.SCIKIT_BIO_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: scikit-bio/scikit-bio + ref: ${{ env.SCIKIT_BIO_VERSION }} + persist-credentials: false + + - name: Build wheels + 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 }} + CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + CIBW_TEST_COMMAND: python -m skbio.test + + - name: Check the C extensions made it into the wheel + run: | + python3 - wheelhouse/*.whl <<'EOF' + import sys, zipfile + for whl in sys.argv[1:]: + names = [n for n in zipfile.ZipFile(whl).namelist() if n.endswith(".so")] + assert len(names) == 7, (whl, names) + print(whl, "ok") + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: scikit-bio-${{ env.SCIKIT_BIO_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish scikit-bio ${{ 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: scikit-bio-${{ matrix.version }}-*-manylinux_riscv64 diff --git a/docs/packages/scikit-bio.yaml b/docs/packages/scikit-bio.yaml new file mode 100644 index 00000000000..1d96dde4665 --- /dev/null +++ b/docs/packages/scikit-bio.yaml @@ -0,0 +1,5 @@ +package-name: scikit-bio +source-code: https://github.com/scikit-bio/scikit-bio +license: BSD-3-Clause +versions: +- version: 0.7.3 From 4c0f3611535a60ca46def0035aa7f410e0853bbb Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 23 Oct 2026 18:03:39 +0000 Subject: [PATCH 2/3] scikit-bio: Use static OpenMP schedule and build cp312+ only The cp312 job segfaulted in tree/tests/test_me.py::test_bme_real, in skbio/tree/_me.py's _bme calling _bal_avgdist_nest, whose prange asks for schedule="dynamic". That compiles to libgomp's GOMP_loop_nonmonotonic_dynamic_* path, which faults on the riscv64 runners (#617). Patch the two dynamic pranges in skbio/tree/_c_me.pyx to schedule="static"; no other extension uses a non-static schedule. The cp310 and cp311 jobs failed installing the wheel's runtime dependencies: h5py and biom-format have riscv64 wheels for cp312+ only, in our registry and on PyPI, and our scipy for them stops at 1.15.2, so pip fell back to a scipy sdist and stopped on missing OpenBLAS. Drop both interpreters until those dependencies are published for them. The extensions build with -fopenmp, so auditwheel vendors the image's libgomp into scikit_bio.libs/. Add the gpl_sources job for gcc and pass its artifact to publish. --- .github/workflows/build-scikit-bio.yml | 50 ++++++++++++++++- ...tic-for-the-dynamic-OpenMP-loops-in-.patch | 55 +++++++++++++++++++ 2 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 patches/scikit-bio/0.7.3/0001-use-schedule-static-for-the-dynamic-OpenMP-loops-in-.patch diff --git a/.github/workflows/build-scikit-bio.yml b/.github/workflows/build-scikit-bio.yml index d23e5155150..5ef15720252 100644 --- a/.github/workflows/build-scikit-bio.yml +++ b/.github/workflows/build-scikit-bio.yml @@ -17,11 +17,13 @@ on: paths: - '.github/workflows/build-scikit-bio.yml' - 'docs/packages/scikit-bio.yaml' + - 'patches/scikit-bio/**' push: branches: [main] paths: - '.github/workflows/build-scikit-bio.yml' - 'docs/packages/scikit-bio.yaml' + - 'patches/scikit-bio/**' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -51,9 +53,8 @@ jobs: matrix: version: ${{ fromJSON(needs.setup.outputs.versions) }} # Matches upstream's own CIBW_BUILD: no free-threaded wheels are published. + # No cp310/cp311: the h5py and biom-format runtime deps have riscv64 wheels for cp312+ only. python: - - "cp310" - - "cp311" - "cp312" - "cp313" - "cp314" @@ -69,6 +70,15 @@ jobs: ref: ${{ env.SCIKIT_BIO_VERSION }} persist-credentials: false + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Patch scikit-bio source + run: git apply python-wheels/patches/scikit-bio/${{ env.SCIKIT_BIO_VERSION }}/*.patch + - name: Build wheels uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 with: @@ -95,9 +105,41 @@ jobs: path: wheelhouse/*.whl if-no-files-found: error + gpl_sources: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + name: Collect GPL sources (gcc) for scikit-bio ${{ matrix.version }} + runs-on: ubuntu-24.04-riscv + + env: + SCIKIT_BIO_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # The extensions build with -fopenmp, so auditwheel vendors the image's libgomp. + - uses: ./actions/collect-gpl-sources + with: + image: ${{ env.MANYLINUX_RISCV64_IMAGE }} + packages: gcc + output: gpl-sources.tar + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: scikit-bio-${{ env.SCIKIT_BIO_VERSION }}-gpl-sources + path: gpl-sources.tar + if-no-files-found: error + publish: name: Publish scikit-bio ${{ matrix.version }} - needs: [setup, build_wheels] + needs: [setup, build_wheels, gpl_sources] if: needs.setup.outputs.versions != '[]' strategy: fail-fast: false @@ -111,3 +153,5 @@ jobs: app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }} with: artifact-pattern: scikit-bio-${{ matrix.version }}-*-manylinux_riscv64 + gpl-sources-artifact: scikit-bio-${{ matrix.version }}-gpl-sources + gpl-sources-description: gcc diff --git a/patches/scikit-bio/0.7.3/0001-use-schedule-static-for-the-dynamic-OpenMP-loops-in-.patch b/patches/scikit-bio/0.7.3/0001-use-schedule-static-for-the-dynamic-OpenMP-loops-in-.patch new file mode 100644 index 00000000000..9339018b6cd --- /dev/null +++ b/patches/scikit-bio/0.7.3/0001-use-schedule-static-for-the-dynamic-OpenMP-loops-in-.patch @@ -0,0 +1,55 @@ +From 7641767e67e2affd82ba59131e59e02be8435c74 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Fri, 23 Oct 2026 17:48:26 +0000 +Subject: [PATCH] use schedule(static) for the dynamic OpenMP loops in + _c_me.pyx + +Upstream-Status: Inappropriate [works around a libgomp defect on the riscv64 runners, riseproject-dev/python-wheels#617] + +libgomp's dynamic and guided work-share schedules fault on the riscv64 +machines these wheels are built and tested on, while schedule(static) +is clean (see the LightGBM and healpy patches in this repository). + +The cp312 job segfaulted in tree/tests/test_me.py::test_bme_real, with +the Python stack ending at skbio/tree/_me.py's _bme calling +_bal_avgdist_nest. That function's only parallel loop is a +prange(..., schedule="dynamic"), which compiles to +GOMP_loop_nonmonotonic_dynamic_start/next. The cp313 and cp314 jobs ran +the same test green, as expected of this intermittent fault. + +_c_me.pyx has exactly two dynamic pranges, in _bal_avgdist_nest and +_bal_min_branch_p; every other prange in scikit-bio leaves the schedule +unset, which GCC compiles as static. Both loops do independent work per +chunk of clades and write disjoint outputs, so the schedule only changes +which thread takes which chunk, not the result. Moving both to static +leaves the extension with no calls into libgomp's dynamic or guided +iterators. Revert once the runners' toolchain is fixed. +--- + skbio/tree/_c_me.pyx | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/skbio/tree/_c_me.pyx b/skbio/tree/_c_me.pyx +index 1a7b239..4b6096b 100644 +--- a/skbio/tree/_c_me.pyx ++++ b/skbio/tree/_c_me.pyx +@@ -1031,7 +1031,7 @@ def _bal_min_branch_p( + + # Parallel phase: Do calculations within chunks of clades. + +- for ichu in prange(nchu, nogil=True, schedule="dynamic"): ++ for ichu in prange(nchu, nogil=True, schedule="static"): + for icla in range(chunks[ichu], chunks[ichu + 1]): + i = clades[icla] + min_i = roots[i] +@@ -2256,7 +2256,7 @@ def _bal_avgdist_nest( + # number of branches from insertion point to any node + cdef Py_ssize_t deg + +- for ichu in prange(nchu, nogil=True, schedule="dynamic"): ++ for ichu in prange(nchu, nogil=True, schedule="static"): + iseg = chusegs[ichu] # next segment index + seg = segs[iseg] + lvl = lvls[iseg - 1] if iseg else -1 # current segment's level +-- +2.43.0 + From 55ef76346bf06f80c6b58be9d6d53d5615eb4269 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 25 Oct 2026 17:51:47 +0000 Subject: [PATCH 3/3] scikit-bio: prefer registry pandas wheel in test phase All three build legs (cp312/cp313/cp314) failed identically: the wheel itself built fine, but installing it pulled in scikit-bio's unpinned pandas>=1.5.0 runtime dependency, which pip resolved to PyPI's newest release (3.1.0). Our registry only hosts pandas up to 3.0.6 for these interpreters, so pip fell back to the 3.1.0 sdist, which fails to build on riscv64: pandas' SIMD moments code calls xsimd::load_unaligned/ reduce_add/broadcast, none of which exist in xsimd's generic/scalar riscv64 arch. CIBW_TEST_ENVIRONMENT: PIP_PREFER_BINARY=1 (test-phase only, mirroring gotcha 354) makes pip prefer our registry's older wheel-backed pandas over the newer sdist-only one. --- .github/workflows/build-scikit-bio.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build-scikit-bio.yml b/.github/workflows/build-scikit-bio.yml index 5ef15720252..acaa5df3809 100644 --- a/.github/workflows/build-scikit-bio.yml +++ b/.github/workflows/build-scikit-bio.yml @@ -87,6 +87,12 @@ jobs: env: CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + # Without this, pip resolves the runtime dep pandas>=1.5.0 to PyPI's newest + # release, which our registry hasn't built yet and whose sdist fails on + # riscv64 (xsimd has no generic/scalar load_unaligned/reduce_add/broadcast + # there). PIP_PREFER_BINARY makes it take our registry's older wheel-backed + # pandas instead (gotcha 354; test-phase only, per gotcha 12). + CIBW_TEST_ENVIRONMENT: PIP_PREFER_BINARY=1 CIBW_TEST_COMMAND: python -m skbio.test - name: Check the C extensions made it into the wheel