From c3998a1302689d621f452d9e3ea00b746f81d05e Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 5 Oct 2026 15:52:11 +0000 Subject: [PATCH 1/4] Add graspologic-native 1.3.1 riscv64 wheel build graspologic-native ships an unconditional pyo3 abi3-py39 feature (Cargo.toml), so its PyPI cp39 tag is just the abi3 floor, not a stale Python-version ceiling: one wheel covers every GIL-ful interpreter, with a separate build for the free-threaded cp314t leg. The crate graph has no -sys dependencies and no C toolchain requirement. --- .../workflows/build-graspologic-native.yml | 142 ++++++++++++++++++ docs/packages/graspologic-native.yaml | 5 + 2 files changed, 147 insertions(+) create mode 100644 .github/workflows/build-graspologic-native.yml create mode 100644 docs/packages/graspologic-native.yaml diff --git a/.github/workflows/build-graspologic-native.yml b/.github/workflows/build-graspologic-native.yml new file mode 100644 index 00000000000..099ef01d312 --- /dev/null +++ b/.github/workflows/build-graspologic-native.yml @@ -0,0 +1,142 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on the `build` job of +# https://github.com/graspologic-org/graspologic-native/blob/v1.3.1/.github/workflows/CI.yml +name: Build graspologic-native wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/graspologic-native.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-graspologic-native.yml' + - 'docs/packages/graspologic-native.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-graspologic-native.yml' + - 'docs/packages/graspologic-native.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 + MUSLLINUX_RISCV64_IMAGE: quay.io/pypa/musllinux_1_2_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: graspologic-native + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build graspologic-native ${{ matrix.version }} ${{ matrix.tag }}-${{ matrix.platform }}_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 90 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + # pyo3's abi3-py39 feature (Cargo.toml) is on unconditionally, so one + # abi3 wheel serves every GIL-ful interpreter; pyo3 disables abi3 + # under Py_GIL_DISABLED, giving the free-threaded build its own wheel. + # The riscv64 musllinux image ships no free-threaded interpreter, so + # cp314t is manylinux-only. + include: + # Built on cp39 -- the oldest interpreter the abi3 tag claims -- and + # re-tested on the newer ones via find_compatible_wheel. + - tag: cp39-abi3 + platform: manylinux + build: >- + cp39-manylinux_riscv64 cp310-manylinux_riscv64 + cp311-manylinux_riscv64 cp312-manylinux_riscv64 + cp313-manylinux_riscv64 cp314-manylinux_riscv64 + - tag: cp39-abi3 + platform: musllinux + build: >- + cp39-musllinux_riscv64 cp310-musllinux_riscv64 + cp311-musllinux_riscv64 cp312-musllinux_riscv64 + cp313-musllinux_riscv64 cp314-musllinux_riscv64 + - tag: cp314t + platform: manylinux + build: cp314t-manylinux_riscv64 + + env: + GRASPOLOGIC_NATIVE_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout graspologic-native v${{ env.GRASPOLOGIC_NATIVE_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: graspologic-org/graspologic-native + ref: v${{ env.GRASPOLOGIC_NATIVE_VERSION }} + persist-credentials: false + + - name: Stage the licence beside graspologic-native's pyproject.toml + # maturin globs LICEN[CS]E* relative to the pyproject directory, which + # in this workspace is packages/pyo3/ -- so upstream's published wheels + # carry no licence text at all. + run: cp LICENSE packages/pyo3/ + + - name: Build wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + package-dir: packages/pyo3 + output-dir: wheelhouse/ + env: + CIBW_BUILD: ${{ matrix.build }} + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + CIBW_MUSLLINUX_RISCV64_IMAGE: ${{ env.MUSLLINUX_RISCV64_IMAGE }} + # graspologic-native ships no [tool.cibuildwheel], so the Rust + # toolchain its maturin backend needs is installed in-container here. + CIBW_BEFORE_ALL_LINUX: >- + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + CIBW_ENVIRONMENT_LINUX: >- + PATH="$PATH:$HOME/.cargo/bin" + PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + CIBW_TEST_SOURCES: >- + packages/pyo3/tests + packages/network_partitions/tests + CIBW_TEST_COMMAND: >- + cd packages/pyo3 && + python -c "import graspologic_native as m, importlib.metadata as md; + assert m.graspologic_native.__file__.endswith('.so'), m.graspologic_native.__file__; + assert any(str(p).endswith('licenses/LICENSE') for p in md.files('graspologic-native'))" && + python -m unittest + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: graspologic-native-${{ env.GRASPOLOGIC_NATIVE_VERSION }}-${{ matrix.tag }}-${{ matrix.platform }}_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish graspologic-native ${{ 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: graspologic-native-${{ matrix.version }}-*_riscv64 diff --git a/docs/packages/graspologic-native.yaml b/docs/packages/graspologic-native.yaml new file mode 100644 index 00000000000..283665b837a --- /dev/null +++ b/docs/packages/graspologic-native.yaml @@ -0,0 +1,5 @@ +package-name: graspologic-native +source-code: https://github.com/graspologic-org/graspologic-native +license: MIT +versions: +- version: 1.3.1 From 0b29fcf5e11086accabc41aaa0703be7ee026dcf Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 5 Oct 2026 16:08:58 +0000 Subject: [PATCH 2/4] Fix matrix fan-out for graspologic-native wheels The three include entries shared no key with the base version dimension, so GitHub Actions applied them in sequence to the same single combination instead of fanning out, leaving only the last entry's tag/platform/build. Explicit platform/tag dimensions with an exclude, and CIBW_BUILD computed via format(), produce all three legs. --- .../workflows/build-graspologic-native.yml | 31 ++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build-graspologic-native.yml b/.github/workflows/build-graspologic-native.yml index 099ef01d312..7d90878f97c 100644 --- a/.github/workflows/build-graspologic-native.yml +++ b/.github/workflows/build-graspologic-native.yml @@ -54,26 +54,12 @@ jobs: # pyo3's abi3-py39 feature (Cargo.toml) is on unconditionally, so one # abi3 wheel serves every GIL-ful interpreter; pyo3 disables abi3 # under Py_GIL_DISABLED, giving the free-threaded build its own wheel. - # The riscv64 musllinux image ships no free-threaded interpreter, so - # cp314t is manylinux-only. - include: - # Built on cp39 -- the oldest interpreter the abi3 tag claims -- and - # re-tested on the newer ones via find_compatible_wheel. - - tag: cp39-abi3 - platform: manylinux - build: >- - cp39-manylinux_riscv64 cp310-manylinux_riscv64 - cp311-manylinux_riscv64 cp312-manylinux_riscv64 - cp313-manylinux_riscv64 cp314-manylinux_riscv64 - - tag: cp39-abi3 - platform: musllinux - build: >- - cp39-musllinux_riscv64 cp310-musllinux_riscv64 - cp311-musllinux_riscv64 cp312-musllinux_riscv64 - cp313-musllinux_riscv64 cp314-musllinux_riscv64 - - tag: cp314t - platform: manylinux - build: cp314t-manylinux_riscv64 + platform: [manylinux, musllinux] + tag: [cp39-abi3, cp314t] + exclude: + # The riscv64 musllinux image ships no free-threaded interpreter. + - platform: musllinux + tag: cp314t env: GRASPOLOGIC_NATIVE_VERSION: ${{ matrix.version }} @@ -98,7 +84,10 @@ jobs: package-dir: packages/pyo3 output-dir: wheelhouse/ env: - CIBW_BUILD: ${{ matrix.build }} + # Built on cp39 -- the oldest interpreter the abi3 tag claims -- and + # re-tested on the newer ones via find_compatible_wheel. + CIBW_BUILD: >- + ${{ matrix.tag == 'cp39-abi3' && format('cp39-{0}_riscv64 cp310-{0}_riscv64 cp311-{0}_riscv64 cp312-{0}_riscv64 cp313-{0}_riscv64 cp314-{0}_riscv64', matrix.platform) || format('cp314t-{0}_riscv64', matrix.platform) }} CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} CIBW_MUSLLINUX_RISCV64_IMAGE: ${{ env.MUSLLINUX_RISCV64_IMAGE }} # graspologic-native ships no [tool.cibuildwheel], so the Rust From 936d0a005c02eaf9fd3bc30c547e4070c6b7709d Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 5 Oct 2026 17:06:10 +0000 Subject: [PATCH 3/4] Pin numpy/scipy test installs to our registry wheels pip's default resolver preferred a newer public-PyPI numpy/scipy release over our registry's riscv64 wheel and fell back to building from sdist; scipy's sdist then failed on the test venv's missing OpenBLAS. PIP_ONLY_BINARY keeps the resolution on the wheels our registry actually ships for riscv64. --- .github/workflows/build-graspologic-native.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build-graspologic-native.yml b/.github/workflows/build-graspologic-native.yml index 7d90878f97c..9e2116a6965 100644 --- a/.github/workflows/build-graspologic-native.yml +++ b/.github/workflows/build-graspologic-native.yml @@ -94,9 +94,13 @@ jobs: # toolchain its maturin backend needs is installed in-container here. CIBW_BEFORE_ALL_LINUX: >- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + # numpy and scipy are runtime requirements only our registry has for + # riscv64; only-binary keeps a newer PyPI release from winning the + # resolution and then compiling from sdist. CIBW_ENVIRONMENT_LINUX: >- PATH="$PATH:$HOME/.cargo/bin" PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + PIP_ONLY_BINARY=numpy,scipy CIBW_TEST_SOURCES: >- packages/pyo3/tests packages/network_partitions/tests From 092d1e3acfd5212e26add7e795e9e1f77f79bfdc Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 5 Oct 2026 17:27:26 +0000 Subject: [PATCH 4/4] Drop musllinux: no riscv64 numpy/scipy wheel on our registry numpy and scipy are runtime dependencies of the built wheel, and neither has a musllinux_riscv64 build in this registry yet, so pip fell back to their manylinux riscv64 wheel inside the musl test container; numpy's compiled extension then failed to load against musl's dynamic linker. Manylinux is unaffected and keeps both legs. --- .github/workflows/build-graspologic-native.yml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-graspologic-native.yml b/.github/workflows/build-graspologic-native.yml index 9e2116a6965..4f4dc3a38ca 100644 --- a/.github/workflows/build-graspologic-native.yml +++ b/.github/workflows/build-graspologic-native.yml @@ -32,7 +32,6 @@ permissions: env: MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 - MUSLLINUX_RISCV64_IMAGE: quay.io/pypa/musllinux_1_2_riscv64 jobs: setup: @@ -44,7 +43,7 @@ jobs: build_wheels: needs: [setup] if: needs.setup.outputs.versions != '[]' - name: Build graspologic-native ${{ matrix.version }} ${{ matrix.tag }}-${{ matrix.platform }}_riscv64 + name: Build graspologic-native ${{ matrix.version }} ${{ matrix.tag }}-manylinux_riscv64 runs-on: ubuntu-24.04-riscv timeout-minutes: 90 strategy: @@ -54,12 +53,7 @@ jobs: # pyo3's abi3-py39 feature (Cargo.toml) is on unconditionally, so one # abi3 wheel serves every GIL-ful interpreter; pyo3 disables abi3 # under Py_GIL_DISABLED, giving the free-threaded build its own wheel. - platform: [manylinux, musllinux] tag: [cp39-abi3, cp314t] - exclude: - # The riscv64 musllinux image ships no free-threaded interpreter. - - platform: musllinux - tag: cp314t env: GRASPOLOGIC_NATIVE_VERSION: ${{ matrix.version }} @@ -87,16 +81,17 @@ jobs: # Built on cp39 -- the oldest interpreter the abi3 tag claims -- and # re-tested on the newer ones via find_compatible_wheel. CIBW_BUILD: >- - ${{ matrix.tag == 'cp39-abi3' && format('cp39-{0}_riscv64 cp310-{0}_riscv64 cp311-{0}_riscv64 cp312-{0}_riscv64 cp313-{0}_riscv64 cp314-{0}_riscv64', matrix.platform) || format('cp314t-{0}_riscv64', matrix.platform) }} + ${{ matrix.tag == 'cp39-abi3' && 'cp39-manylinux_riscv64 cp310-manylinux_riscv64 cp311-manylinux_riscv64 cp312-manylinux_riscv64 cp313-manylinux_riscv64 cp314-manylinux_riscv64' || 'cp314t-manylinux_riscv64' }} CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} - CIBW_MUSLLINUX_RISCV64_IMAGE: ${{ env.MUSLLINUX_RISCV64_IMAGE }} # graspologic-native ships no [tool.cibuildwheel], so the Rust # toolchain its maturin backend needs is installed in-container here. CIBW_BEFORE_ALL_LINUX: >- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y # numpy and scipy are runtime requirements only our registry has for # riscv64; only-binary keeps a newer PyPI release from winning the - # resolution and then compiling from sdist. + # resolution and then compiling from sdist. musllinux is dropped: + # our registry has no musllinux_riscv64 wheel for either, so the + # installed wheel's own runtime deps can't resolve there. CIBW_ENVIRONMENT_LINUX: >- PATH="$PATH:$HOME/.cargo/bin" PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ @@ -113,7 +108,7 @@ jobs: - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: graspologic-native-${{ env.GRASPOLOGIC_NATIVE_VERSION }}-${{ matrix.tag }}-${{ matrix.platform }}_riscv64 + name: graspologic-native-${{ env.GRASPOLOGIC_NATIVE_VERSION }}-${{ matrix.tag }}-manylinux_riscv64 path: wheelhouse/*.whl if-no-files-found: error