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

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

run-name: build-tensorstore - ${{ inputs.version || '0.1.85' }}

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

permissions:
contents: read

env:
TENSORSTORE_VERSION: ${{ inputs.version || '0.1.85' }}
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64
# bazelisk.py has no riscv64 entry in determine_bazel_filename(), so bootstrap a bazel
# binary from the dist archive instead. Bazel fixed riscv64 bootstrapping in 8.2.0
# (bazelbuild/bazel#25745); tensorstore's own .bazelversion (8.5.1) is past that, so no
# rules_python/rules_java patches are needed.
BAZEL_VERSION: '8.5.1'

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

bazel:
needs: [setup]
name: Bootstrap bazel (riscv64)
runs-on: ubuntu-24.04-riscv
timeout-minutes: 180

steps:
- name: Restore bazel binary
id: cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: bazel-bin
key: bazel-${{ env.BAZEL_VERSION }}-manylinux_riscv64

- name: Bootstrap bazel ${{ env.BAZEL_VERSION }}
if: steps.cache.outputs.cache-hit != 'true'
run: |
mkdir -p bazel-bin
docker run --rm -i --network=host \
-v "${GITHUB_WORKSPACE}:/work" \
-w /work \
-e BAZEL_VERSION="${BAZEL_VERSION}" \
"${MANYLINUX_RISCV64_IMAGE}" \
bash <<'SCRIPT'
set -eux

dnf install -y --setopt=install_weak_deps=False java-21-openjdk-devel zip unzip
JAVA_HOME="$(dirname "$(dirname "$(readlink -f "$(command -v javac)")")")"
export JAVA_HOME

mkdir -p /tmp/bazel-src
cd /tmp/bazel-src
curl -fsSLo dist.zip "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-dist.zip"
unzip -q dist.zip

EXTRA_BAZEL_ARGS="--tool_java_runtime_version=local_jdk" bash ./compile.sh
install -m 0755 output/bazel /work/bazel-bin/bazel
SCRIPT

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: bazel-${{ env.BAZEL_VERSION }}-riscv64
path: bazel-bin/bazel
if-no-files-found: error

build_wheels:
name: Build tensorstore ${{ inputs.version || '0.1.85' }} manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: 1440
needs: [setup, bazel]

steps:
- name: Checkout tensorstore v${{ env.TENSORSTORE_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: google/tensorstore
ref: v${{ env.TENSORSTORE_VERSION }}
path: tensorstore
fetch-depth: 1
persist-credentials: false

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

- name: Patch tensorstore source
working-directory: tensorstore
run: git apply ../python-wheels/patches/tensorstore/${{ env.TENSORSTORE_VERSION }}/00*.patch

- name: Download bazel
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: bazel-${{ env.BAZEL_VERSION }}-riscv64
path: bazel-bin

- name: Build wheels
run: |
mkdir -p wheelhouse
set -o pipefail
docker run --rm -i --network=host \
-v "${GITHUB_WORKSPACE}:/work" \
-w /work \
-e PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ \
-e PIP_ONLY_BINARY=numpy \
-e TENSORSTORE_VERSION="${TENSORSTORE_VERSION}" \
"${MANYLINUX_RISCV64_IMAGE}" \
bash <<'SCRIPT' 2>&1 | tee build.log
set -eux

dnf install -y --setopt=install_weak_deps=False java-21-openjdk-devel
JAVA_HOME="$(dirname "$(dirname "$(readlink -f "$(command -v javac)")")")"
export JAVA_HOME

install -m 0755 /work/bazel-bin/bazel /usr/local/bin/bazel
git config --global --add safe.directory '*'
# git apply leaves the tree dirty, so setuptools_scm reads it as a
# post-release dev build (0.1.85 -> 0.1.85.post1.dev0) unless pinned.
export SETUPTOOLS_SCM_PRETEND_VERSION_FOR_TENSORSTORE="${TENSORSTORE_VERSION}"

# setup.py invokes `sys.executable -u $TENSORSTORE_BAZELISK`, so the bootstrapped
# native bazel binary needs a thin python shim rather than direct assignment.
cat > /usr/local/bin/bazelisk_shim.py <<'PY'
import os
import sys
os.execv("/usr/local/bin/bazel", ["/usr/local/bin/bazel"] + sys.argv[1:])
PY

export TENSORSTORE_BAZELISK=/usr/local/bin/bazelisk_shim.py
export TENSORSTORE_BAZEL_STARTUP_OPTIONS="--output_user_root=/work/bazel-output"
# A multi-hour build's default curses progress output is large enough that
# GitHub drops the job log, taking the failure with it. --local_ram_resources
# keeps analysis/compile action concurrency from oversubscribing memory on the
# shared riscv64 runner pool (other jobs run concurrently on the same hosts).
export TENSORSTORE_BAZEL_BUILD_OPTIONS="--curses=no --show_progress_rate_limit=60 --local_ram_resources=HOST_RAM*.5"

cd /work/tensorstore

for python in cp311-cp311 cp312-cp312 cp313-cp313 cp314-cp314 cp314-cp314t; do
"/opt/python/${python}/bin/pip" install -q -r third_party/pypa/wheel_requirements_frozen.txt
PATH="/opt/python/${python}/bin:$PATH" \
"/opt/python/${python}/bin/python" -m pip wheel -w /work/wheelhouse . --no-deps --no-build-isolation
done

for wheel in /work/wheelhouse/*.whl; do
mv "${wheel}" "$(dirname "${wheel}")/$(basename "${wheel}" | sed 's/-linux_riscv64\.whl$/-manylinux_2_39_riscv64.whl/')"
done
SCRIPT

- name: Upload build log
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: tensorstore-${{ env.TENSORSTORE_VERSION }}-build-log
path: build.log

# Uploaded before the smoke test so a failing wheel is still available to inspect.
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: tensorstore-${{ env.TENSORSTORE_VERSION }}-wheels-manylinux_riscv64
path: wheelhouse/*.whl
if-no-files-found: error
compression-level: 0

- name: Test wheels
run: |
docker run --rm -i --network=host \
-v "${GITHUB_WORKSPACE}:/work" \
-e PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ \
"${MANYLINUX_RISCV64_IMAGE}" \
bash <<'SCRIPT'
set -eux

for python in cp311-cp311 cp312-cp312 cp313-cp313 cp314-cp314 cp314-cp314t; do
pytag="${python%%-*}"
# PIP_ONLY_BINARY stops pip at the newest numpy/ml_dtypes our registry has a
# riscv64 wheel for, instead of resolving a newer sdist-only release.
PIP_ONLY_BINARY=numpy,ml_dtypes "/opt/python/${python}/bin/pip" install \
/work/wheelhouse/tensorstore-*-"${pytag}"-*.whl \
pytest pytest-asyncio cloudpickle numpy ml_dtypes
# /work holds the tensorstore checkout, whose root would shadow the installed
# wheel if pytest ran from there (python/tensorstore is importable as-is).
(cd /tmp && "/opt/python/${python}/bin/python" -m pytest \
/work/tensorstore/python/tensorstore/tests -vv -s --asyncio-mode=auto)
done
SCRIPT

publish:
name: Publish tensorstore ${{ inputs.version || '0.1.85' }}
needs: [setup, build_wheels]
permissions:
contents: write
pull-requests: write
uses: $/.github/workflows/_publish-wheel.yml
with:
artifact-pattern: tensorstore-${{ inputs.version || '0.1.85' }}-*-manylinux_riscv64
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
From 721c7c44fa91bc8ac74682c16896458df70938b7 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Sun, 13 Sep 2026 04:40:02 +0200
Subject: [PATCH] Add riscv64 support to the dav1d Bazel BUILD wrapper

dav1d.BUILD.bazel's select()s for deps/TMPL_SRCS/DAV1D_SRCS/TEXTUAL_HDRS/
CONFIG_H only branch on arm64/ppc/x86_64, so building //python/tensorstore
for any other cpu (riscv64 included) fails bazel's analysis phase outright:
"doesn't match this configuration. Would a default condition help?".

dav1d has shipped riscv64 (rv64gcv) assembly since 1.4.0 under src/riscv/,
gated on ARCH_RISCV/ARCH_RV64 in config.h exactly like the other arches'
asm is gated on their own ARCH_* defines (src/cpu.c, src/*_tmpl.c). This
adds a riscv64 branch mirroring arm64's shape (raw .S sources, no _tmpl.c
split, asm.S as a textual header) instead of falling back to a slower
plain-C build, plus a //conditions:default on the three selects that only
varied by "is there extra arch-specific work" (empty on every arch this
project doesn't build asm for anyway).

Upstream-Status: To upstream [not submitted from this automated port run; needs a pull request against google/tensorstore]
---
.../org_videolan_dav1d/dav1d.BUILD.bazel | 34 +++++++++++++++++++
1 file changed, 34 insertions(+)

diff --git a/third_party/org_videolan_dav1d/dav1d.BUILD.bazel b/third_party/org_videolan_dav1d/dav1d.BUILD.bazel
index a8c504d..b371088 100644
--- a/third_party/org_videolan_dav1d/dav1d.BUILD.bazel
+++ b/third_party/org_videolan_dav1d/dav1d.BUILD.bazel
@@ -30,6 +30,7 @@ cc_library(
"@platforms//cpu:arm64": [],
"@platforms//cpu:ppc": [],
"@platforms//cpu:x86_64": [":dav1d_asm_x86"],
+ "//conditions:default": [],
}),
)

@@ -53,6 +54,13 @@ PPC_ASM_SRCS = glob(
],
)

+# Mirrors upstream's src/meson.build riscv branch: cpu.c plus the riscv64 .S
+# sources, with no _tmpl.c split (dav1d has none for this arch).
+RISCV_ASM_SRCS = glob([
+ "src/riscv/*.c",
+ "src/riscv/64/*.S",
+])
+
# TMPL_SRCS are compiled for BITDEPTH=8 and BITDEPTH=16
TMPL_SRCS = glob(["src/*_tmpl.c"]) + select({
"@platforms//cpu:arm64": glob(
@@ -67,6 +75,7 @@ TMPL_SRCS = glob(["src/*_tmpl.c"]) + select({
["src/x86/*_tmpl.c"],
allow_empty = True,
),
+ "//conditions:default": [],
})

DAV1D_SRCS = glob(
@@ -78,10 +87,12 @@ DAV1D_SRCS = glob(
}) + select({
"@platforms//cpu:arm64": ARM_ASM_SRCS,
"@platforms//cpu:ppc": PPC_ASM_SRCS,
+ "@platforms//cpu:riscv64": RISCV_ASM_SRCS,
"@platforms//cpu:x86_64": glob(
["src/x86/*.c"],
exclude = ["src/x86/*_tmpl.c"],
),
+ "//conditions:default": [],
})

TEXTUAL_HDRS = select({
@@ -96,6 +107,11 @@ TEXTUAL_HDRS = select({
"src/*.h",
"src/ppc/*.h",
]),
+ "@platforms//cpu:riscv64": glob([
+ "src/*.h",
+ "src/riscv/*.h",
+ "src/riscv/asm.S",
+ ]),
"@platforms//cpu:x86_64": glob([
"src/*.h",
"src/x86/*.h",
@@ -326,6 +342,23 @@ PPC_CONFIG_H = [
"",
]

+# dav1d has shipped riscv64 (rv64gcv) asm since 1.4.0 (src/riscv), gated on
+# ARCH_RISCV/ARCH_RV64 the same way the other arches gate their asm dispatch.
+RISCV64_CONFIG_H = [
+ "#define ARCH_AARCH64 0",
+ "#define ARCH_ARM 0",
+ "#define ARCH_X86 0",
+ "#define ARCH_X86_32 0",
+ "#define ARCH_X86_64 0",
+ "#define ARCH_PPC64LE 0",
+ "#define ARCH_RISCV 1",
+ "#define ARCH_RV32 0",
+ "#define ARCH_RV64 1",
+ "",
+ "#define HAVE_ASM 1",
+ "",
+]
+
CONFIG_END_H = [
"",
"#define CONFIG_16BPC 1",
@@ -358,6 +391,7 @@ CONFIG_H = CONFIG_START_H + select({
"@platforms//cpu:x86_64": X86_64_CONFIG_H,
"@platforms//cpu:arm64": ARM64_CONFIG_H,
"@platforms//cpu:ppc": PPC_CONFIG_H,
+ "@platforms//cpu:riscv64": RISCV64_CONFIG_H,
}) + select({
"@platforms//os:macos": DARWIN_CONFIG_H,
"//conditions:default": [],
--
2.50.1 (Apple Git-155)

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
From 3a0c8bc6475937122508cac4443fff01fb707136 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Sun, 13 Sep 2026 05:18:10 +0200
Subject: [PATCH] Add a riscv64 default to libaom's header select()

libaom.BUILD.bazel's LIBAOM_HEADERS select() only branches on arm64/x86_64/
ppc, so building //python/tensorstore (which pulls in libaom transitively
through the avif image driver) fails bazel analysis on riscv64 the same way
dav1d's BUILD.bazel did.

Unlike dav1d, this wrapper's own _PATTERN_UNSUPPORTED list already excludes
"**/riscv/**" and "**/*_rvv*" headers, and GENERATED_CONFIGS already maps
"//conditions:default" to a pre-generated "generic" (non-SIMD) aom_config.h/
rtcd.h set. So riscv64 already gets a working, intentionally-generic config
elsewhere in this file; LIBAOM_HEADERS was simply missing the matching
default branch. This adds one, built from the same exclude-every-arch
pattern the generic config pairs with.

Upstream-Status: To upstream [not submitted from this automated port run; needs a pull request against google/tensorstore]
---
third_party/org_aomedia_aom/libaom.BUILD.bazel | 8 ++++++++
1 file changed, 8 insertions(+)

diff --git a/third_party/org_aomedia_aom/libaom.BUILD.bazel b/third_party/org_aomedia_aom/libaom.BUILD.bazel
index a7e1788..80ca56d 100644
--- a/third_party/org_aomedia_aom/libaom.BUILD.bazel
+++ b/third_party/org_aomedia_aom/libaom.BUILD.bazel
@@ -83,6 +83,14 @@ LIBAOM_HEADERS = select({
allow_empty = True,
exclude = _PATTERN_UNSUPPORTED + _PATTERN_X86 + _PATTERN_ARM,
),
+ # _PATTERN_UNSUPPORTED already excludes riscv/rvv; every other arch (this
+ # port's riscv64 included) gets the same generic, non-SIMD header set the
+ # GENERATED_CONFIGS "//conditions:default" -> "generic" entry below pairs with.
+ "//conditions:default": glob(
+ include = LIBAOM_HEADERS_PATTERN,
+ allow_empty = True,
+ exclude = _PATTERN_UNSUPPORTED + _PATTERN_PPC + _PATTERN_X86 + _PATTERN_ARM,
+ ),
})

cc_library(
--
2.50.1 (Apple Git-155)

Loading
Loading