Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions .github/workflows/build-polars-ols.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# 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'
- '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 }}
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: 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:
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 }}
# 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 }}
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
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
6 changes: 6 additions & 0 deletions docs/packages/polars-ols.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package-name: polars-ols
source-code: https://github.com/azmyrajab/polars_ols
license: MIT
versions:
- version: 0.3.5
patched: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
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 <git@ludovic.dev>
---
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
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 <git@ludovic.dev>
---
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(
Loading