From e672b2961d3e2b47326329d219f7dca4f8293f87 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 12 Oct 2026 20:18:09 +0000 Subject: [PATCH 1/5] polars-ols: Add version 0.3.5 --- .github/workflows/build-polars-ols.yml | 124 +++++++++++++++++++++++++ docs/packages/polars-ols.yaml | 5 + 2 files changed, 129 insertions(+) create mode 100644 .github/workflows/build-polars-ols.yml create mode 100644 docs/packages/polars-ols.yaml diff --git a/.github/workflows/build-polars-ols.yml b/.github/workflows/build-polars-ols.yml new file mode 100644 index 000000000000..9bdce43d769a --- /dev/null +++ b/.github/workflows/build-polars-ols.yml @@ -0,0 +1,124 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on the `linux` and `linux_tests` jobs of +# https://github.com/azmyrajab/polars_ols/blob/v0.3.5/.github/workflows/CI.yml +name: Build polars-ols wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/polars-ols.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-polars-ols.yml' + - 'docs/packages/polars-ols.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-polars-ols.yml' + - 'docs/packages/polars-ols.yaml' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +env: + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + # rust-toolchain.toml floats on "nightly"; this is the nightly upstream's 0.3.5 + # wheels were built with (2024-08-25), which its pinned crates still compile on. + RUST_TOOLCHAIN: nightly-2024-08-25 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: polars-ols + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build polars-ols ${{ matrix.version }} cp38-abi3-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 360 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + + env: + POLARS_OLS_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout polars_ols v${{ env.POLARS_OLS_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: azmyrajab/polars_ols + ref: v${{ env.POLARS_OLS_VERSION }} + persist-credentials: false + + - name: Build and test wheel + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + output-dir: wheelhouse/ + env: + # abi3-py38 gives one wheel, built on cp312 and re-tested on cp313. cp314: + # statsmodels has no riscv64 cp314 wheel; cp314t: pyo3 0.21 has no free-threading. + # musllinux is dropped: rustup.rs ships no riscv64 musl toolchain. + CIBW_BUILD: cp312-manylinux_riscv64 cp313-manylinux_riscv64 + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + CIBW_BEFORE_ALL_LINUX: >- + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | + sh -s -- -y --profile minimal --default-toolchain ${{ env.RUST_TOOLCHAIN }} + CIBW_ENVIRONMENT_LINUX: >- + PATH="$PATH:$HOME/.cargo/bin" + RUSTUP_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }} + RUSTFLAGS="-Dwarnings" + PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + CIBW_TEST_REQUIRES: polars numpy pytest statsmodels scikit-learn + CIBW_TEST_SOURCES: tests + CIBW_TEST_COMMAND: >- + python -c "import polars_ols._polars_ols as m; assert m.__file__.endswith('.so'), m.__file__" && + python -m pytest -v tests + + - name: Check the licence made it into the wheel + run: | + python3 - wheelhouse/*.whl <<'EOF' + import sys, zipfile + for whl in sys.argv[1:]: + assert "-cp38-abi3-manylinux_" in whl and whl.endswith("_riscv64.whl"), whl + names = zipfile.ZipFile(whl).namelist() + assert any(n.endswith("dist-info/licenses/LICENSE") for n in names), names + print(whl, "ok") + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: polars-ols-${{ env.POLARS_OLS_VERSION }}-cp38-abi3-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish polars-ols ${{ 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: polars-ols-${{ matrix.version }}-*-manylinux_riscv64 diff --git a/docs/packages/polars-ols.yaml b/docs/packages/polars-ols.yaml new file mode 100644 index 000000000000..b272adf6f645 --- /dev/null +++ b/docs/packages/polars-ols.yaml @@ -0,0 +1,5 @@ +package-name: polars-ols +source-code: https://github.com/azmyrajab/polars_ols +license: MIT +versions: +- version: 0.3.5 From 9b2b04a433a2722c1a7c515878cfb46d60013272 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 16 Oct 2026 00:39:25 +0000 Subject: [PATCH 2/5] polars-ols: skip build-system.requires to avoid a from-source polars-runtime-32 pyproject.toml's [build-system] requires pins "polars>=0.20.6", which maturin never actually needs (its Rust polars dependency comes from crates.io, and get_requires_for_build_wheel() returns no extra requirements). Resolving it anyway pulls the newest polars (2.0.0), whose polars-runtime-32 wheel build falls back to source on riscv64 and needs a newer Rust edition than the pinned nightly-2024-08-25 toolchain provides. Switch the cibuildwheel frontend to "pip; args: --no-build-isolation" and preinstall only maturin, so build-system.requires is never resolved. --- .github/workflows/build-polars-ols.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build-polars-ols.yml b/.github/workflows/build-polars-ols.yml index 9bdce43d769a..c6ffd32015d0 100644 --- a/.github/workflows/build-polars-ols.yml +++ b/.github/workflows/build-polars-ols.yml @@ -78,6 +78,13 @@ jobs: CIBW_BEFORE_ALL_LINUX: >- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain ${{ env.RUST_TOOLCHAIN }} + # gotcha 76: pyproject.toml's build-system.requires pins an unused "polars" + # (maturin builds the Rust polars crate from crates.io, never imports the + # Python package), which resolves to polars-runtime-32 and falls back to a + # from-source build needing a newer Rust than RUST_TOOLCHAIN. Skip resolving + # build-system.requires entirely instead of patching the pin. + CIBW_BEFORE_BUILD_LINUX: pip install "maturin>=1.0,<2.0" + CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" CIBW_ENVIRONMENT_LINUX: >- PATH="$PATH:$HOME/.cargo/bin" RUSTUP_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }} From 64ea930ee0cad1a56b0b3f122a398702abc042dc Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Thu, 22 Oct 2026 06:40:18 +0000 Subject: [PATCH 3/5] polars-ols: build faer and pulp without "nightly" on riscv64 Cargo.toml enables pulp's and faer's "nightly" features on every non-ARM target. On riscv64, pulp 0.18.22's "nightly" re-exports core::arch::riscv64 without enabling the riscv_ext_intrinsics feature gate, so the build fails on nightly-2024-08-25 with: error[E0658]: use of unstable library feature 'riscv_ext_intrinsics' --> pulp-0.18.22/src/core_arch/mod.rs:24:13 Patch Cargo.toml so riscv64 gets faer and pulp without "nightly" (as upstream already does for ARM), keeping faer's other features. The dependency graph is unchanged, so the committed Cargo.lock still resolves with --locked. --- .github/workflows/build-polars-ols.yml | 12 +++++ docs/packages/polars-ols.yaml | 1 + ...-and-pulp-without-nightly-on-riscv64.patch | 51 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 patches/polars-ols/0.3.5/0001-Cargo-build-faer-and-pulp-without-nightly-on-riscv64.patch diff --git a/.github/workflows/build-polars-ols.yml b/.github/workflows/build-polars-ols.yml index c6ffd32015d0..7a72133fd11f 100644 --- a/.github/workflows/build-polars-ols.yml +++ b/.github/workflows/build-polars-ols.yml @@ -17,11 +17,13 @@ on: paths: - '.github/workflows/build-polars-ols.yml' - 'docs/packages/polars-ols.yaml' + - 'patches/polars-ols/**' push: branches: [main] paths: - '.github/workflows/build-polars-ols.yml' - 'docs/packages/polars-ols.yaml' + - 'patches/polars-ols/**' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -65,6 +67,16 @@ jobs: ref: v${{ env.POLARS_OLS_VERSION }} persist-credentials: false + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + sparse-checkout: patches/polars-ols + persist-credentials: false + + - name: Apply patches + run: git apply -v python-wheels/patches/polars-ols/${{ env.POLARS_OLS_VERSION }}/*.patch + - name: Build and test wheel uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 with: diff --git a/docs/packages/polars-ols.yaml b/docs/packages/polars-ols.yaml index b272adf6f645..297c7cf59b66 100644 --- a/docs/packages/polars-ols.yaml +++ b/docs/packages/polars-ols.yaml @@ -3,3 +3,4 @@ source-code: https://github.com/azmyrajab/polars_ols license: MIT versions: - version: 0.3.5 + patched: true diff --git a/patches/polars-ols/0.3.5/0001-Cargo-build-faer-and-pulp-without-nightly-on-riscv64.patch b/patches/polars-ols/0.3.5/0001-Cargo-build-faer-and-pulp-without-nightly-on-riscv64.patch new file mode 100644 index 000000000000..3591defa8d8e --- /dev/null +++ b/patches/polars-ols/0.3.5/0001-Cargo-build-faer-and-pulp-without-nightly-on-riscv64.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Thu, 22 Oct 2026 00:00:00 +0000 +Subject: [PATCH] Cargo: build faer and pulp without "nightly" on riscv64 + +Every non-ARM target enables pulp's and faer's "nightly" features. +faer's "nightly" turns on faer-entity/gemm/gemm-common "nightly", which +all end up enabling pulp/nightly. On riscv64, pulp 0.18.22 then does + + #[cfg(all(feature = "nightly", target_arch = "riscv64"))] + pub use core::arch::riscv64::*; + +without adding #![feature(riscv_ext_intrinsics)], so the crate fails +to compile on the nightly toolchain: + + error[E0658]: use of unstable library feature 'riscv_ext_intrinsics' + --> pulp-0.18.22/src/core_arch/mod.rs:24:13 + +pulp has no riscv64 SIMD paths for "nightly" to switch on (its +re-export is never used on riscv64), so on riscv64 build faer and pulp +without "nightly", as Cargo.toml already does for ARM. faer keeps the +same "std", "rayon", "serde", "rand", "npy" features. The dependency +graph is unchanged, so Cargo.lock still resolves with --locked; on +x86_64 the feature set is identical to before. + +Upstream-Status: To upstream [not submitted from this automated port run; needs a pull request against azmyrajab/polars_ols] + +Signed-off-by: Ludovic Henry +--- + Cargo.toml | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/Cargo.toml b/Cargo.toml +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -40,7 +40,12 @@ ndarray-linalg = {version = "*"} + faer = { version = "*" } # don't allow nightly for ARM due to issues with NEON support + faer-ext = { version = "*", features = ["ndarray"] } + +-[target.'cfg(not(target_arch = "arm"))'.dependencies] ++[target.'cfg(target_arch = "riscv64")'.dependencies] ++pulp = { version = "0.18.22" } # pulp's "nightly" re-exports core::arch::riscv64 without enabling riscv_ext_intrinsics ++faer = { version = "*", features = ["std", "rayon", "serde", "rand", "npy"] } ++faer-ext = { version = "*", features = ["ndarray"] } ++ ++[target.'cfg(not(any(target_arch = "arm", target_arch = "riscv64")))'.dependencies] + pulp = { version = "0.18.22", features = ["nightly"] } + faer = { version = "*", features = ["std", "rayon", "serde", "rand", "npy", "nightly"] } + faer-ext = { version = "*", features = ["ndarray"] } +-- +2.43.0 From 7026e3e36a750f8405e8266e2d8b554321807f0a Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Thu, 22 Oct 2026 09:45:18 +0000 Subject: [PATCH 4/5] polars-ols: install upstream's test requirements and fix a test helper for current polars Test with polars[pandas,pyarrow] as tests/requirements-test.txt does: statsmodels' formula API and the statistics test go through DataFrame.to_pandas(), which needs pyarrow. Cap polars below 2.0 at test time: the 0.3.5 suite calls Expr.flatten, removed in polars 2.0. Patch the _make_data test helper to return float values from its map_elements(return_dtype=Float64) UDF; polars 1.36 and later no longer cast the Int64 group column implicitly, which broke test_non_contiguous. --- .github/workflows/build-polars-ols.yml | 4 +- ...ts-cast-insert_nulls-values-to-float.patch | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 patches/polars-ols/0.3.5/0002-tests-cast-insert_nulls-values-to-float.patch diff --git a/.github/workflows/build-polars-ols.yml b/.github/workflows/build-polars-ols.yml index 7a72133fd11f..837340fa72f7 100644 --- a/.github/workflows/build-polars-ols.yml +++ b/.github/workflows/build-polars-ols.yml @@ -102,7 +102,9 @@ jobs: RUSTUP_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }} RUSTFLAGS="-Dwarnings" PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ - CIBW_TEST_REQUIRES: polars numpy pytest statsmodels scikit-learn + # tests/requirements-test.txt, minus maturin/pyperf; the 0.3.5 suite calls + # Expr.flatten, removed in polars 2.0. + CIBW_TEST_REQUIRES: polars[pandas,pyarrow]<2 numpy pytest statsmodels scikit-learn CIBW_TEST_SOURCES: tests CIBW_TEST_COMMAND: >- python -c "import polars_ols._polars_ols as m; assert m.__file__.endswith('.so'), m.__file__" && diff --git a/patches/polars-ols/0.3.5/0002-tests-cast-insert_nulls-values-to-float.patch b/patches/polars-ols/0.3.5/0002-tests-cast-insert_nulls-values-to-float.patch new file mode 100644 index 000000000000..2a158af319d9 --- /dev/null +++ b/patches/polars-ols/0.3.5/0002-tests-cast-insert_nulls-values-to-float.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Thu, 22 Oct 2026 00:00:00 +0000 +Subject: [PATCH] tests: cast insert_nulls values to float + +_make_data(add_missing=True) runs every column, including the Int64 +"group" column, through map_elements(insert_nulls, +return_dtype=pl.Float64), and insert_nulls hands back the original int. +polars up to 1.35 silently cast those ints to Float64; polars 1.36 and +later reject them while building the Series: + + polars.exceptions.SchemaError: unexpected value while building + Series of type Float64; found value of type Int64: 0 + +so test_non_contiguous fails before reaching polars-ols. The oldest +polars available for riscv64 is 1.39, so no installable version still +accepts it. Return float(val), which is the cast older polars did +implicitly; the generated data is unchanged. + +Upstream-Status: To upstream [not submitted from this automated port run; needs a pull request against azmyrajab/polars_ols] + +Signed-off-by: Ludovic Henry +--- + tests/test_ols.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/test_ols.py b/tests/test_ols.py +--- a/tests/test_ols.py ++++ b/tests/test_ols.py +@@ -42,7 +42,7 @@ + if add_missing: + + def insert_nulls(val): +- return None if rng.random() < 0.1 else val ++ return None if rng.random() < 0.1 else float(val) + + columns = missing_columns or df.columns + df = df.with_columns( From 706951496028b5d1eb7eccdab7d5842e6148157b Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Fri, 23 Oct 2026 15:40:32 +0000 Subject: [PATCH 5/5] polars-ols: install numpy, pandas and other compiled test deps as wheels only The test-requires install resolved PyPI's newest numpy (2.5.4) and pandas (3.1.0), which have no riscv64 wheel and are newer than the registry's, so pip built both from sdist. numpy took 20 minutes; pandas 3.1.0 then failed to compile its xsimd-based moments_simd.hpp on riscv64: error: 'load_unaligned' is not a member of 'xsimd' Set PIP_ONLY_BINARY for the compiled test dependencies in the test environment only, so they resolve to the registry's riscv64 wheels (numpy 2.4.3, pandas 3.0.6, pyarrow 26.0.0, scipy 1.17.1, statsmodels 0.15.0, scikit-learn 1.9.1, polars-runtime-32 1.44.2). --- .github/workflows/build-polars-ols.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-polars-ols.yml b/.github/workflows/build-polars-ols.yml index 837340fa72f7..2d5dc786f547 100644 --- a/.github/workflows/build-polars-ols.yml +++ b/.github/workflows/build-polars-ols.yml @@ -102,6 +102,8 @@ jobs: RUSTUP_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }} RUSTFLAGS="-Dwarnings" PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + # gotcha 67: PyPI's newer numpy/pandas sdists outrun the registry; pandas 3.1.0 fails to compile on riscv64. + CIBW_TEST_ENVIRONMENT: PIP_ONLY_BINARY=numpy,pandas,pyarrow,scipy,statsmodels,scikit-learn,polars-runtime-32 # tests/requirements-test.txt, minus maturin/pyperf; the 0.3.5 suite calls # Expr.flatten, removed in polars 2.0. CIBW_TEST_REQUIRES: polars[pandas,pyarrow]<2 numpy pytest statsmodels scikit-learn