diff --git a/.github/workflows/build-pandas.yml b/.github/workflows/build-pandas.yml index 64e2c2226361..47b1268d22ac 100644 --- a/.github/workflows/build-pandas.yml +++ b/.github/workflows/build-pandas.yml @@ -88,8 +88,14 @@ jobs: CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} # CIBW_ENVIRONMENT replaces pandas's [tool.cibuildwheel] environment # table wholesale, so its own LDFLAGS has to be restated here. + # pandas 3.1's scalar fallback (any CPU other than x86_64/aarch64) + # calls xsimd::load_unaligned/reduce_add/broadcast on xsimd::common, + # but xsimd 14.2 defines XSIMD_NO_SUPPORTED_ARCHITECTURE on a plain + # riscv64 target (no RVV) and then omits xsimd_api.hpp; the emulated + # arch makes xsimd keep the API. CIBW_ENVIRONMENT: >- LDFLAGS=-Wl,--strip-all + CXXFLAGS=-DXSIMD_WITH_EMULATED=1 PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ PIP_ONLY_BINARY=numpy,hypothesis # cibuildwheel copies the source into a fresh container per build, and @@ -98,11 +104,14 @@ jobs: # git describe reads when the meson build resolves the version (gotcha # 315). CIBW_BEFORE_BUILD replaces pandas's [tool.cibuildwheel] # before-build wholesale, so re-run it here, then commit and re-tag in - # the same container before the build reads the version. + # the same container before the build reads the version. pandas 3.1 + # dropped the script, so skip it when absent. CIBW_BEFORE_BUILD: >- + if [ -f {package}/scripts/cibw_before_build.sh ]; then PACKAGE_DIR={package} bash {package}/scripts/cibw_before_build.sh && git -C {package} -c user.name=ci -c user.email=ci@localhost commit -am "cibw before-build" && - git -C {package} tag -f v${{ env.PANDAS_VERSION }} + git -C {package} tag -f v${{ env.PANDAS_VERSION }}; + fi - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: diff --git a/docs/packages/pandas.yaml b/docs/packages/pandas.yaml index 07a9ec70596c..0153fd3399a7 100644 --- a/docs/packages/pandas.yaml +++ b/docs/packages/pandas.yaml @@ -201,3 +201,4 @@ versions: - filename: pandas-3.0.6-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl sha256: 4bc771048b8e7ac4191168e180177e373be9adc5911fc3a4277e22eb25bced61 requires-python: '>=3.11' +- version: 3.1.0 diff --git a/patches/pandas/3.1.0/0001-disable-nan-test-on-riscv64.patch b/patches/pandas/3.1.0/0001-disable-nan-test-on-riscv64.patch new file mode 100644 index 000000000000..a580bcf7c3a1 --- /dev/null +++ b/patches/pandas/3.1.0/0001-disable-nan-test-on-riscv64.patch @@ -0,0 +1,48 @@ +From 5e254d628784dd803ff9b492b06db8118b4bbaee Mon Sep 17 00:00:00 2001 +From: Julien Stephan +Date: Thu, 26 Mar 2026 13:41:01 +0100 +Subject: [PATCH] disable nan test on riscv64 + +Upstream-Status: To upstream [To send when we will add riscv64 support upstream] +Signed-off-by: Julien Stephan +--- + pandas/tests/tools/test_to_datetime.py | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/pandas/tests/tools/test_to_datetime.py b/pandas/tests/tools/test_to_datetime.py +index c61137d..2db1b74 100644 +--- a/pandas/tests/tools/test_to_datetime.py ++++ b/pandas/tests/tools/test_to_datetime.py +@@ -11,6 +11,7 @@ from datetime import ( + ) + from decimal import Decimal + import locale ++import platform + import re + import zoneinfo + +@@ -1864,6 +1865,10 @@ class TestToDatetimeUnit: + tm.assert_index_equal(result, expected) + + # with a nan! ++ # skip test on riscv64 ++ if platform.machine() == 'riscv64': ++ return ++ + result = pd.to_datetime(np.array([item, np.nan]), unit=unit, cache=cache) + assert result.isna()[1] + tm.assert_index_equal(result[:1], expected) +@@ -4743,6 +4748,10 @@ def test_to_datetime_lxml_elementunicoderesult_with_format(cache): + + + def test_to_datetime_missing_component_no_runtime_warning(): ++ # skip test on riscv64 ++ if platform.machine() == 'riscv64': ++ return ++ + df = pd.DataFrame( + { + "year": [2023, 2023], +-- +2.54.0 + diff --git a/patches/pandas/3.1.0/0002-make-nan-to-int64-casts-explicit.patch b/patches/pandas/3.1.0/0002-make-nan-to-int64-casts-explicit.patch new file mode 100644 index 000000000000..441557437c99 --- /dev/null +++ b/patches/pandas/3.1.0/0002-make-nan-to-int64-casts-explicit.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Wed, 28 Oct 2026 19:35:17 +0000 +Subject: [PATCH] core: make NaN to int64 casts explicit for + to_datetime/to_timedelta + +Casting NaN to int64 is undefined behaviour in C and the result is +architecture dependent: INT64_MIN (iNaT) on x86, 0 on ARM and INT64_MAX +on riscv64. to_datetime(unit=...) and to_timedelta/to_datetime assembly +with float columns relied on the x86 result and, on riscv64, tried to +convert INT64_MAX seconds/hours before the NaN positions were masked, +raising OutOfBounds errors. Set the masked positions to iNaT explicitly. + +Upstream-Status: To upstream [To send when we will add riscv64 support upstream] +Signed-off-by: Ludovic Henry +--- + pandas/core/arrays/timedeltas.py | 3 +++ + pandas/core/tools/datetimes.py | 3 +++ + 2 files changed, 6 insertions(+) + +diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py +index b24e372..56cb4d3 100644 +--- a/pandas/core/arrays/timedeltas.py ++++ b/pandas/core/arrays/timedeltas.py +@@ -1312,6 +1312,9 @@ def sequence_to_td64ns( + # (which results in 'us' unit instead of 'ns') + with np.errstate(invalid="ignore"): + int_data = data.astype(np.int64) ++ # NaN -> int64 is undefined behaviour in C: x86 yields INT64_MIN (iNaT), ++ # ARM yields 0 and riscv64 yields INT64_MAX. Make it explicit. ++ int_data[mask] = iNaT + # On ARM, float-to-int64 overflow saturates to INT64_MAX + # instead of wrapping, which makes the data == int_data + # check pass incorrectly for OOB values like float(2**63). +diff --git a/pandas/core/tools/datetimes.py b/pandas/core/tools/datetimes.py +index 1dc48d2..4412ff1 100644 +--- a/pandas/core/tools/datetimes.py ++++ b/pandas/core/tools/datetimes.py +@@ -601,6 +601,9 @@ def _to_datetime_with_unit( + mask = np.isnan(arg) + with np.errstate(invalid="ignore"): + int_values = arg.astype(np.int64) ++ # NaN -> int64 is undefined behaviour in C: x86 yields INT64_MIN (iNaT), ++ # ARM yields 0 and riscv64 yields INT64_MAX. Make it explicit. ++ int_values[mask] = iNaT + # On ARM, float-to-int64 overflow saturates to INT64_MAX + # instead of wrapping, which makes the arg == int_values + # check pass incorrectly for OOB values like float(2**63). +-- +2.43.0 +