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
262 changes: 262 additions & 0 deletions .github/workflows/build-tensorstore.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
# 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: 'Version glob to (re)build; empty builds every version of docs/packages/tensorstore.yaml not released yet'
required: false
default: ''
pull_request:
branches: [main]
paths:
- '.github/workflows/build-tensorstore.yml'
- 'docs/packages/tensorstore.yaml'
- 'patches/tensorstore/**'
push:
branches: [main]
paths:
- '.github/workflows/build-tensorstore.yml'
- 'docs/packages/tensorstore.yaml'
- 'patches/tensorstore/**'

run-name: build-tensorstore ${{ inputs.version && format('- {0}', inputs.version) || '' }}

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
# 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
with:
package: tensorstore
version: ${{ inputs.version }}

bazel:
needs: [setup]
if: needs.setup.outputs.versions != '[]'
name: Bootstrap bazel (riscv64)
runs-on: ubuntu-24.04-riscv
timeout-minutes: 720

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 ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: 720
needs: [setup, bazel]
if: needs.setup.outputs.versions != '[]'

strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
# Upstream also publishes cp311; the registry's numpy floor is 3.12. One
# interpreter per job is load-bearing here: bazel reconfigures the whole C++
# build per interpreter, so looping them in one job shares no cache and takes
# ~5h30 each (gotcha 456).
python: ["cp312", "cp313", "cp314", "cp314t"]

env:
TENSORSTORE_VERSION: ${{ matrix.version }}

steps:
- name: Checkout tensorstore v${{ matrix.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 }}/*.patch

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

- name: Build wheel
env:
PYTHON_TAG: ${{ matrix.python }}
run: |
mkdir -p wheelhouse
set -o pipefail
docker run --rm -i --network=host \
-v "${GITHUB_WORKSPACE}:/work" \
-w /work \
-e PYTHON_TAG \
-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"

case "${PYTHON_TAG}" in
*t) python_dir="/opt/python/${PYTHON_TAG%t}-${PYTHON_TAG}/bin" ;;
*) python_dir="/opt/python/${PYTHON_TAG}-${PYTHON_TAG}/bin" ;;
esac
export PATH="${python_dir}:${PATH}"
python_exe="${python_dir}/python"

cd /work/tensorstore
"${python_exe}" -m pip install -q -U auditwheel
"${python_exe}" -m pip install -q -r third_party/pypa/wheel_requirements_frozen.txt
"${python_exe}" -m pip wheel -w /tmp/wheelhouse . --no-deps --no-build-isolation
auditwheel repair --plat manylinux_2_39_riscv64 -w /work/wheelhouse /tmp/wheelhouse/*.whl
SCRIPT

- name: Upload build log
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: tensorstore-${{ env.TENSORSTORE_VERSION }}-${{ matrix.python }}-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 }}-${{ matrix.python }}-manylinux_riscv64
path: wheelhouse/*.whl
if-no-files-found: error
compression-level: 0

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

case "${PYTHON_TAG}" in
*t) python_dir="/opt/python/${PYTHON_TAG%t}-${PYTHON_TAG}/bin" ;;
*) python_dir="/opt/python/${PYTHON_TAG}-${PYTHON_TAG}/bin" ;;
esac
export PATH="${python_dir}:${PATH}"
python_exe="${python_dir}/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 "${python_exe}" -m pip install \
/work/wheelhouse/*.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 && "${python_exe}" -m pytest \
/work/tensorstore/python/tensorstore/tests -vv -s --asyncio-mode=auto)
SCRIPT

publish:
name: Publish tensorstore ${{ 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: tensorstore-${{ matrix.version }}-*-manylinux_riscv64
6 changes: 6 additions & 0 deletions docs/packages/tensorstore.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package-name: tensorstore
source-code: https://github.com/google/tensorstore
license: Apache-2.0
versions:
- version: 0.1.85
patched: true
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)

Loading
Loading