From 8a0f1f9a59a9c1a8d249f57e76eca8ef32a3da7d Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 2 Oct 2026 04:15:14 +0000 Subject: [PATCH 1/2] river: Add version 0.26.1 river compiles one PyO3 extension (river._river_rust) with maturin and publishes no riscv64 wheel. Build it from the 0.26.1 tag with upstream's own [tool.cibuildwheel] config, as its pypi.yml does: one native wheel per interpreter (cp312-cp315) plus the cp311-abi3 fallback wheel built on its floor interpreter with --features abi3. The native legs also run upstream's pytest suite (code-quality.yml's no-pandas job) against the installed wheel. cp315 keeps upstream's own test-skip; numpy and scipy have no cp315 riscv64 wheel either. --- .github/workflows/build-river.yml | 160 ++++++++++++++++++++++++++++++ docs/packages/river.yaml | 5 + 2 files changed, 165 insertions(+) create mode 100644 .github/workflows/build-river.yml create mode 100644 docs/packages/river.yaml diff --git a/.github/workflows/build-river.yml b/.github/workflows/build-river.yml new file mode 100644 index 00000000000..1e924db274e --- /dev/null +++ b/.github/workflows/build-river.yml @@ -0,0 +1,160 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on the `build_linux_wheels` and `build_linux_abi3_wheels` jobs of +# https://github.com/online-ml/river/blob/0.26.1/.github/workflows/pypi.yml +# and the `tests` job of +# https://github.com/online-ml/river/blob/0.26.1/.github/workflows/code-quality.yml +name: Build river wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/river.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-river.yml' + - 'docs/packages/river.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-river.yml' + - 'docs/packages/river.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: river + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build river ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 240 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + # Upstream ships no free-threaded wheel; cp311 is served by the abi3 wheel below. + python: ["cp312", "cp313", "cp314", "cp315"] + + env: + RIVER_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout river ${{ env.RIVER_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: online-ml/river + ref: ${{ env.RIVER_VERSION }} + persist-credentials: false + + - name: Build and test 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 }} + # build[uv] needs a host uv for the audit step, which the riscv64 runner lacks. + CIBW_BUILD_FRONTEND: build + CIBW_ENVIRONMENT_LINUX: >- + PATH="$HOME/.cargo/bin:$PATH" + PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + CIBW_TEST_ENVIRONMENT: PIP_PREFER_BINARY=1 + CIBW_TEST_REQUIRES: >- + pytest scikit-learn sqlalchemy sympy gymnasium polars pyarrow + CIBW_TEST_SOURCES: tests conftest.py pyproject.toml + # test_docs.py needs the docs/ parser tooling, not the wheel; the memory + # ceiling in test_memory_usage_class is calibrated to CPython 3.13 object + # sizes and fails on 3.12 on x86_64 as well. + CIBW_TEST_COMMAND: >- + python -c "import river.anomaly, river.datasets, river.drift, river.stream, river.tree" && + python -c "import river._river_rust as m; assert m.__file__.endswith('.so'), m.__file__" && + python -m pytest tests + --ignore=tests/test_docs.py + --deselect "tests/tree/test_trees.py::test_memory_usage_class[dataset2-model2]" + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: river-${{ env.RIVER_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + build_abi3_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build river ${{ matrix.version }} cp311-abi3-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + + env: + RIVER_VERSION: ${{ matrix.version }} + MATURIN_PEP517_ARGS: "--features abi3" + + steps: + - name: Checkout river ${{ env.RIVER_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: online-ml/river + ref: ${{ env.RIVER_VERSION }} + persist-credentials: false + + - name: Build and test wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + output-dir: wheelhouse/ + only: cp311-manylinux_riscv64 + env: + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + CIBW_BUILD_FRONTEND: build + CIBW_ENVIRONMENT_PASS_LINUX: MATURIN_PEP517_ARGS + CIBW_ENVIRONMENT_LINUX: >- + PATH="$HOME/.cargo/bin:$PATH" + PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + # scipy 1.16/1.17 still accept 3.11 but have no riscv64 wheel; take the registry's 1.15. + CIBW_TEST_ENVIRONMENT: PIP_PREFER_BINARY=1 + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: river-${{ env.RIVER_VERSION }}-cp311-abi3-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish river ${{ matrix.version }} + needs: [setup, build_wheels, build_abi3_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: river-${{ matrix.version }}-*-manylinux_riscv64 diff --git a/docs/packages/river.yaml b/docs/packages/river.yaml new file mode 100644 index 00000000000..380cc0aeeeb --- /dev/null +++ b/docs/packages/river.yaml @@ -0,0 +1,5 @@ +package-name: river +source-code: https://github.com/online-ml/river +license: BSD-3-Clause +versions: +- version: 0.26.1 From 7e6625e0420828376c80a012f8bfc886c8cf9a7e Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 2 Oct 2026 05:29:06 +0000 Subject: [PATCH 2/2] river: download the test datasets before running pytest tests/anomaly/test_hst.py::test_missing_features is a doctest that iterates datasets.CreditCard(). On first use a river dataset downloads itself with verbose=True, so "Downloading ..." and "Uncompressing into ..." land in the doctest's output and it fails. It failed this way on cp312, cp313 and cp314, and reproduces on x86_64 with an empty ~/river_data; with the data already present it passes. Upstream's tests job avoids this by running `make download-datasets` before pytest, so run that target's command here too. --- .github/workflows/build-river.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-river.yml b/.github/workflows/build-river.yml index 1e924db274e..6808df4e1e4 100644 --- a/.github/workflows/build-river.yml +++ b/.github/workflows/build-river.yml @@ -88,6 +88,7 @@ jobs: CIBW_TEST_COMMAND: >- python -c "import river.anomaly, river.datasets, river.drift, river.stream, river.tree" && python -c "import river._river_rust as m; assert m.__file__.endswith('.so'), m.__file__" && + python -c "from river import datasets, bandit; datasets.Elec2().download(); datasets.SMSSpam().download(); datasets.CreditCard().download(); datasets.Music().download(); datasets.CriteoAds().download(); bandit.datasets.NewsArticles().download()" && python -m pytest tests --ignore=tests/test_docs.py --deselect "tests/tree/test_trees.py::test_memory_usage_class[dataset2-model2]"