Skip to content
Merged
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
200 changes: 200 additions & 0 deletions .github/workflows/build-tables.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# This workflow is based on: https://github.com/PyTables/PyTables/blob/v3.11.1/.github/workflows/wheels.yml
name: Build tables wheels (riscv64)

on:
workflow_dispatch:
inputs:
version:
description: 'tables version to build (git tag without leading v, e.g. 3.11.1)'
required: true
default: '3.11.1'
pull_request:
paths:
- '.github/workflows/build-tables.yml'
- 'patches/tables/**'

concurrency:
group: ${{ github.workflow }}-${{ inputs.version || '3.11.1' }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

permissions:
contents: read # to fetch code (actions/checkout)

env:
# `inputs.version` is empty on pull_request events; default to 3.11.1 there.
TABLES_VERSION: ${{ inputs.version || '3.11.1' }}
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64
# Matches upstream's own `env.HDF5_VERSION` in wheels.yml ("H5Dchunk_iter needs
# at least 1.14.1"). No libaec/szip: upstream's own Linux wheel build doesn't
# enable szip either (only zlib), so this needs no szip counterpart.
HDF5_VERSION: '2.2.0'

jobs:
setup:
uses: $/.github/workflows/_setup.yml

build_wheels:
needs: [setup]
name: Build tables ${{ inputs.version || '3.11.1' }} ${{ matrix.tag }}-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: ${{ matrix.timeout }}
strategy:
fail-fast: false
matrix:
include:
# setup.py's bdist_wheel_abi3 hardcodes the wheel tag to cp311-abi3
# regardless of which Py_LIMITED_API-built interpreter compiles it, so
# the produced wheel is the same upstream ships. The build floor here is
# cp312, not cp311 like upstream: numexpr (a runtime dependency) only has
# a riscv64 build on this registry from cp312 onward (see
# build-numexpr.yml), and cibuildwheel's abi3 handling builds once on the
# lowest requested identifier and reuses that wheel to test the rest.
- tag: cp311-abi3
build: >-
cp312-manylinux_riscv64 cp313-manylinux_riscv64
cp314-manylinux_riscv64
timeout: 420
- tag: cp314t
build: cp314t-manylinux_riscv64
timeout: 180

steps:
- name: Checkout PyTables v${{ env.TABLES_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: PyTables/PyTables
ref: v${{ env.TABLES_VERSION }}
submodules: true
persist-credentials: false

- name: Checkout python-wheels
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: python-wheels
persist-credentials: false

# hdf5-blosc2/src/blosc2_filter.c is a plain vendored copy (not a git
# submodule) written against the pre-3.0 c-blosc2 API: it fails to
# compile against any current blosc2 (BLOSC2_MAX_DIM was renamed to
# B2ND_MAX_DIM in c-blosc2 3.0, which every blosc2 >= 3.2.0 bundles,
# including the one this registry publishes). See gotcha 269.
- name: Patch tables source
run: git apply python-wheels/patches/tables/${{ env.TABLES_VERSION }}/*.patch

- uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
env:
CIBW_ARCHS: riscv64
CIBW_BUILD: ${{ matrix.build }}
CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }}
# Neutralizes pyproject.toml's own before-build, which installs from
# hash-pinned lockfiles with no riscv64 wheels; build-system.requires
# (Cython, numpy, blosc2, ...) resolves normally instead.
CIBW_BEFORE_BUILD: 'true'
# zlib/bzip2-devel, then HDF5 from source, mirroring PyTables' own
# ci/github/get_hdf5.sh (Linux branch) and wheels.yml's HDF5_VERSION.
# No libaec/szip: upstream doesn't enable szip on Linux either.
# LICENSES/BZIP2.txt: bzip2, unlike zlib, isn't on the manylinux policy
# allow-list, so auditwheel vendors it into the wheel; PyTables' own
# license-files glob (LICENSES/*.txt) already covers HDF5/zlib/blosc's
# licenses but ships no bzip2 one, so add it the same way netcdf4's port
# does for its own vendored bzip2 copy.
CIBW_BEFORE_ALL_LINUX: |
set -ex
dnf -y install zlib-devel bzip2-devel
mkdir -p /tmp/hdf5
curl -fsSL "https://github.com/HDFGroup/hdf5/archive/refs/tags/$HDF5_VERSION.tar.gz" | tar xz --strip-components=1 -C /tmp/hdf5
cmake -S /tmp/hdf5 -B /tmp/hdf5/build \
-D CMAKE_BUILD_TYPE=Release \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D CMAKE_INSTALL_LIBDIR=lib \
-D BUILD_TESTING=OFF \
-D BUILD_STATIC_LIBS=OFF \
-D HDF5_BUILD_EXAMPLES=OFF \
-D HDF5_BUILD_TOOLS=OFF \
-D HDF5_BUILD_UTILS=OFF \
-D HDF5_ENABLE_ZLIB_SUPPORT=ON
cmake --build /tmp/hdf5/build -j "$(nproc)"
cmake --install /tmp/hdf5/build
ldconfig
cp /usr/share/licenses/bzip2-libs/LICENSE "{project}/LICENSES/BZIP2.txt"
CIBW_ENVIRONMENT_LINUX: >-
HDF5_DIR=/usr/local
HDF5_VERSION=${{ env.HDF5_VERSION }}
DISABLE_AVX2=true
CFLAGS=-g0
PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/
PIP_ONLY_BINARY=numpy,blosc2,numexpr
# blosc2's shared lib lives inside the separately-installed blosc2
# wheel (a runtime dependency, already on this registry); tables loads
# it via ctypes at import time (tables/__init__.py's _load_blosc2()),
# so auditwheel must not vendor a second copy of it (gotcha 17).
CIBW_REPAIR_WHEEL_COMMAND_LINUX: >-
auditwheel repair -w {dest_dir} {wheel} --exclude 'libblosc2.so*'
# Mirrors upstream's own test_wheels job (wheels.yml): no lzo in the
# check list because upstream's Linux wheels don't build with lzo
# either (their before-all doesn't install lzo-devel).
CIBW_TEST_COMMAND: >-
python -c "import tables; keys = 'zlib bzip2 blosc blosc2'.split(); missing = [key for key in keys if tables.which_lib_version(key) is None]; assert missing == [], missing" &&
python -m tables.tests.test_all -v

- name: Check the wheel ships the extensions and the bundled licences
run: |
python3 - wheelhouse/*.whl <<'EOF'
import sys, zipfile

names = zipfile.ZipFile(sys.argv[1]).namelist()
expected_mods = {
"utilsextension", "hdf5extension", "tableextension",
"_comp_lzo", "_comp_bzip2", "linkextension",
"lrucacheextension", "indexesextension",
}
# The cp311-abi3 leg's extensions are tagged "*.abi3.so"; cp314t
# can't use the stable ABI, so its own leg ships
# "*.cpython-314t-riscv64-linux-gnu.so" instead. Match on the
# module name only, not the interpreter-specific suffix. Excludes
# "tables/libblosc2.so*", the bundled runtime lib (not a
# tables-owned extension module, see CIBW_REPAIR_WHEEL_COMMAND_LINUX
# above).
found_mods = {
n.split("/", 1)[1].split(".", 1)[0]
for n in names
if n.startswith("tables/") and n.endswith(".so")
and not n.startswith("tables/lib")
}
assert found_mods == expected_mods, found_mods
licences = {
n.split(".dist-info/licenses/", 1)[1] for n in names
if ".dist-info/licenses/" in n and not n.endswith("/")
}
# Everything the wheel actually compiles in or links (patches/tables):
# c-blosc and its bundled LZ4/zlib/zstd, HDF5, h5py (credited in
# hdf5extension.pyx/array.py), and our own added BZIP2.txt. Not
# upstream's whole LICENSES/ dir - most of the rest (FastLZ, Snappy,
# Windows-only shims, the docs theme) covers content this build
# never ships.
expected_licences = {
"LICENSE.txt", "LICENSES/BLOSC.txt", "LICENSES/BZIP2.txt",
"LICENSES/H5PY.txt", "LICENSES/HDF5.txt", "LICENSES/LZ4.txt",
"LICENSES/ZLIB.txt", "LICENSES/ZSTD.txt",
}
assert licences == expected_licences, licences
EOF

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: tables-${{ env.TABLES_VERSION }}-${{ matrix.tag }}-manylinux_riscv64
path: ./wheelhouse/*.whl
if-no-files-found: error

publish:
name: Publish tables ${{ inputs.version || '3.11.1' }}
needs: [setup, build_wheels]
permissions:
contents: write
pull-requests: write
uses: $/.github/workflows/_publish-wheel.yml
with:
artifact-pattern: tables-${{ inputs.version || '3.11.1' }}-*-manylinux_riscv64
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Sun, 6 Sep 2026 00:00:00 +0000
Subject: [PATCH] Backport hdf5-blosc2 filter fixes for newer blosc2

hdf5-blosc2/src/blosc2_filter.c is a plain vendored copy (not a git
submodule) of Blosc/HDF5-Blosc2's filter plugin, frozen at whatever state
it was in when last copied into this tree. It uses BLOSC2_MAX_DIM, a
macro c-blosc2 dropped in its 3.0 release (renamed to B2ND_MAX_DIM, and
bumped from 8 to 16) as part of the B2ND API becoming first-class. Any
blosc2 Python package bundling that newer c-blosc2 -- which is what
`blosc2 >= 2.3.0` (this project's own floor in pyproject.toml) resolves
to today -- fails to compile this file:

hdf5-blosc2/src/blosc2_filter.c:48:32: error: 'BLOSC2_MAX_DIM'
undeclared (first use in this function); did you mean 'B2ND_MAX_DIM'?

Not riscv64-specific: any platform building against a current blosc2
hits the same error, which is presumably why upstream, after this v3.11.1
tag, replaced the vendored copy with a proper git submodule pinned to
Blosc/HDF5-Blosc2 (PyTables/PyTables@fc026c03871c63ea33d86f5eaffdeb55f8aa18da,
"Add submodule for hdf5-blosc2", 2026-08-16) and raised the pyproject.toml
blosc2 floor to >= 3.2.0 to match. That submodule now pins
Blosc/HDF5-Blosc2@1ca9d34b132ad9c62c91db4337088b3c676b368a.

This backports that commit's effect on blosc2_filter.c as a plain text
patch instead of a submodule swap, since converting a vendored copy into
a submodule mid-tree can't be expressed as a patch applied after
actions/checkout has already fetched submodules. Besides the
BLOSC2_MAX_DIM -> B2ND_MAX_DIM rename, the pinned commit also fixes
blosc2_filter_function() to report the compressed frame's real size
(`status`) instead of the precomputed buffer-size guess, which HDF5 >=
2.2.0 -- the version this port's own workflow builds HDF5 from -- rejects
writes against when the guess undershoots. Both hunks are carried
together since they come from the same upstream commit and this port
depends on both preconditions (a current blosc2, HDF5 2.2.0).

Upstream-Status: Backport [https://github.com/Blosc/HDF5-Blosc2/commit/1ca9d34b132ad9c62c91db4337088b3c676b368a]

Signed-off-by: Ludovic Henry <git@ludovic.dev>
---
diff --git a/hdf5-blosc2/src/blosc2_filter.c b/hdf5-blosc2/src/blosc2_filter.c
index 03bf789..f79d478 100644
--- a/hdf5-blosc2/src/blosc2_filter.c
+++ b/hdf5-blosc2/src/blosc2_filter.c
@@ -38,14 +38,14 @@
* - 4: compression level
* - 5: shuffle method
* - 6: compressor code
- * - 7: chunk rank (number of dimensions) (present if 1 < rank <= BLOSC2_MAX_DIM, for B2ND)
+ * - 7: chunk rank (number of dimensions) (present if 1 < rank <= B2ND_MAX_DIM, for B2ND)
* - 8 + i: length of chunk dimension i (0 <= i < rank)
*
* If a value is specified, all values before it must be specified too.
*
* If the chunk rank is specified, chunk dimensions must follow.
*/
-#define MAX_FILTER_VALUES (8 + BLOSC2_MAX_DIM)
+#define MAX_FILTER_VALUES (8 + B2ND_MAX_DIM)
/* Compression level default */
#define DEFAULT_CLEVEL 5
/* Shuffle default */
@@ -95,7 +95,7 @@ int register_blosc2(char **version, char **date){

3. Compute the chunk size in bytes and store it in slot 3.

- 4. If 1 < rank <= BLOSC2_MAX_DIM, store it in slot 7, and chunk dimensions in the following slots.
+ 4. If 1 < rank <= B2ND_MAX_DIM, store it in slot 7, and chunk dimensions in the following slots.
*/
herr_t blosc2_set_local(hid_t dcpl, hid_t type, hid_t space) {

@@ -157,7 +157,7 @@ herr_t blosc2_set_local(hid_t dcpl, hid_t type, hid_t space) {
fprintf(stderr, "Blosc2: Computed buffer size %d\n", bufsize);
#endif

- if (1 < ndim && ndim <= BLOSC2_MAX_DIM) {
+ if (1 < ndim && ndim <= B2ND_MAX_DIM) {
if (nelements < 5) { values[4] = DEFAULT_CLEVEL; }
if (nelements < 6) { values[5] = DEFAULT_SHUFFLE; }
if (nelements < 7) { values[6] = DEFAULT_COMPCODE; }
@@ -172,7 +172,7 @@ herr_t blosc2_set_local(hid_t dcpl, hid_t type, hid_t space) {
/* The user may be expecting more efficient storage than we can currently provide,
* so convey some information when tracing. */
BLOSC_TRACE_WARNING("Chunk rank %d exceeds B2ND build limit %d, "
- "using plain Blosc2 instead", ndim, BLOSC2_MAX_DIM);
+ "using plain Blosc2 instead", ndim, B2ND_MAX_DIM);
}

r = H5Pmodify_filter(dcpl, FILTER_BLOSC2, flags, nelements, values);
@@ -297,7 +297,7 @@ size_t blosc2_filter_function(unsigned flags, size_t cd_nelmts,

/* Filter params that are only set for B2ND */
int ndim = -1;
- int32_t chunkshape[BLOSC2_MAX_DIM];
+ int32_t chunkshape[B2ND_MAX_DIM];
size_t chunksize = typesize;
if (cd_nelmts >= 8) {
/* Get chunk shape for B2ND */
@@ -308,10 +308,10 @@ size_t blosc2_filter_function(unsigned flags, size_t cd_nelmts,
ndim);
goto failed;
}
- if (ndim > BLOSC2_MAX_DIM) {
+ if (ndim > B2ND_MAX_DIM) {
PUSH_ERR("blosc2_filter", H5E_CALLBACK,
"Chunk rank %d (filter value) exceeds B2ND build limit %d",
- ndim, BLOSC2_MAX_DIM);
+ ndim, B2ND_MAX_DIM);
goto failed;
}
if (cd_nelmts < (size_t)(8 + ndim)) {
@@ -391,12 +391,12 @@ size_t blosc2_filter_function(unsigned flags, size_t cd_nelmts,
}
blocksize = sugg_blocksize;
}
- int32_t blockdims[BLOSC2_MAX_DIM];
+ int32_t blockdims[B2ND_MAX_DIM];
cparams.blocksize = compute_b2nd_block_shape(blocksize, typesize,
ndim, chunkshape,
blockdims);

- int64_t chunkshape_l[BLOSC2_MAX_DIM];
+ int64_t chunkshape_l[B2ND_MAX_DIM];
for (int i = 0; i < ndim; i++) {
chunkshape_l[i] = chunkshape[i];
}
@@ -454,6 +454,15 @@ size_t blosc2_filter_function(unsigned flags, size_t cd_nelmts,

}

+ if (status > 0) {
+ /* The cframe returned by Blosc2 is exactly `status` bytes long. Report
+ * its real size instead of the precomputed guess: when the frame is
+ * larger than the guess (e.g. incompressible data), HDF5 >= 2.2.0
+ * rejects the write if the reported buffer size is smaller than the
+ * returned data size. */
+ outbuf_size = (size_t) status;
+ }
+
#ifdef BLOSC2_DEBUG
fprintf(stderr, "Blosc2: Compressed into %zd bytes\n", status);
#endif
@@ -504,7 +513,7 @@ size_t blosc2_filter_function(unsigned flags, size_t cd_nelmts,
"B2ND array rank (%hhd) != filter rank (%d)", array->ndim, ndim);
goto b2nd_decomp_out;
}
- int64_t start[BLOSC2_MAX_DIM], stop[BLOSC2_MAX_DIM], size = typesize;
+ int64_t start[B2ND_MAX_DIM], stop[B2ND_MAX_DIM], size = typesize;
for (int i = 0; i < array->ndim; i++) {
start[i] = 0;
stop[i] = array->shape[i];
--
2.43.0
Loading