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
132 changes: 132 additions & 0 deletions .github/workflows/build-drjit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# Based on the `build_wheels` job of
# https://github.com/mitsuba-renderer/drjit/blob/v1.5.0/.github/workflows/wheels.yml
name: Build drjit wheels (riscv64)

on:
workflow_dispatch:
inputs:
version:
description: 'Version glob to (re)build; empty builds every version of docs/packages/drjit.yaml not released yet'
required: false
default: ''
pull_request:
branches: [main]
paths:
- '.github/workflows/build-drjit.yml'
- 'docs/packages/drjit.yaml'
- 'patches/drjit/**'
push:
branches: [main]
paths:
- '.github/workflows/build-drjit.yml'
- 'docs/packages/drjit.yaml'
- 'patches/drjit/**'

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

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

env:
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64

jobs:
setup:
uses: $/.github/workflows/_setup.yml
with:
package: drjit
version: ${{ inputs.version }}

build_wheels:
needs: [setup]
if: needs.setup.outputs.versions != '[]'
name: Build drjit ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: 180
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
python: ["cp39", "cp310", "cp311", "cp312", "cp313", "cp314"]

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

steps:
- name: Checkout drjit v${{ env.DRJIT_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: mitsuba-renderer/drjit
ref: v${{ env.DRJIT_VERSION }}
submodules: recursive
persist-credentials: false

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

# DRJIT_ENABLE_LLVM/DRJIT_ENABLE_CUDA both resolve their backend by
# dlopen()-ing libLLVM.so/libcuda.so at runtime (DRJIT_DYNAMIC_LLVM,
# DRJIT_DYNAMIC_CUDA), so the build itself needs neither an LLVM install
# nor the CUDA toolkit; ext/drjit-core/include/drjit-core/intrin.h and
# src/lock.h just assume every non-aarch64 target is x86.
- name: Patch drjit source
run: git apply python-wheels/patches/drjit/${{ env.DRJIT_VERSION }}/00*.patch

- uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
env:
CIBW_ARCHS: riscv64
CIBW_BUILD: ${{ matrix.python }}-manylinux_riscv64
CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }}
# nanothread/drjit-core/drjit each set their own *_NATIVE_FLAGS cache
# var to -march=haswell for a scikit-build/non-aarch64 build; riscv64
# GCC rejects it outright ("ISA string must begin with rv32 or rv64").
CIBW_ENVIRONMENT: >-
SKBUILD_CMAKE_ARGS='-DDRJIT_NATIVE_FLAGS=;-DDRJIT_CORE_NATIVE_FLAGS=;-DNANOTHREAD_NATIVE_FLAGS='

- name: Check wheel contents
run: |
python3 - wheelhouse/*.whl <<'EOF'
import sys, zipfile
path = sys.argv[1]
names = zipfile.ZipFile(path).namelist()
exts = [n for n in names if n.endswith(".so")]
assert exts and any("_drjit_ext" in n for n in exts), exts
licences = {n.split(".dist-info/licenses/", 1)[1] for n in names
if ".dist-info/licenses/" in n and not n.endswith("/")}
assert licences == {
"LICENSE",
"ext/drjit-core/ext/lz4/LICENSE",
"ext/drjit-core/ext/robin_map/LICENSE",
}, licences
EOF

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

publish:
name: Publish drjit ${{ 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: drjit-${{ matrix.version }}-*-manylinux_riscv64
6 changes: 6 additions & 0 deletions docs/packages/drjit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package-name: drjit
source-code: https://github.com/mitsuba-renderer/drjit
license: BSD-3-Clause
versions:
- version: 1.5.0
patched: true
64 changes: 64 additions & 0 deletions patches/drjit/1.5.0/0001-build-drjit-core-on-riscv64.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Mon, 28 Sep 2026 00:00:00 +0000
Subject: [PATCH] build the LLVM/CUDA-JIT-free parts of drjit-core on riscv64

Both DRJIT_ENABLE_LLVM and DRJIT_ENABLE_CUDA already resolve their backend
at runtime via dlopen (DRJIT_DYNAMIC_LLVM/DRJIT_DYNAMIC_CUDA default ON on
Linux), so building drjit-core needs neither an LLVM install nor the CUDA
toolkit. Two small preprocessor gaps nonetheless make the plain C++ build
itself fail on any architecture that is neither x86 nor aarch64:

1. drjit-core/intrin.h assumes "not aarch64" means "must be x86" and
unconditionally includes <immintrin.h>:

fatal error: immintrin.h: No such file or directory

Every intrinsic actually gated behind this header is itself gated behind
a real compiler-predefined macro (__F16C__, __AVX__, etc.), so on riscv64
none of it is ever reached -- the header just needs to stop trying to
pull in x86-only system headers on non-x86 targets.

2. lock.h's lock_pause() has the same "aarch64 or must be x86" assumption
and falls into __builtin_ia32_pause(), an x86-only GCC/Clang builtin:

error: '__builtin_ia32_pause' was not declared in this scope

lock_pause() is only ever an optional spin-wait hint (see the
"Unknown OS/processor" fallback a few lines above in the same file for
thread_id()), so riscv64 (and any other non-x86, non-aarch64 target)
can safely fall through to a no-op.

Upstream-Status: To upstream [not yet submitted; drjit does not otherwise support/test riscv64 today, so this needs its own PR against mitsuba-renderer/drjit-core rather than being folded into an unrelated one]

Signed-off-by: Ludovic Henry <git@ludovic.dev>
---
diff --git a/ext/drjit-core/include/drjit-core/intrin.h b/ext/drjit-core/include/drjit-core/intrin.h
--- a/ext/drjit-core/include/drjit-core/intrin.h
+++ b/ext/drjit-core/include/drjit-core/intrin.h
@@ -15,6 +15,10 @@
# if defined(__aarch64__)
# include <arm_neon.h>
# include <arm_fp16.h>
+# elif !defined(__i386__) && !defined(__x86_64__)
+ // Neither x86 nor aarch64: no intrinsics header needed. Every intrinsic
+ // gated behind this header is itself gated behind a real compiler flag
+ // macro (__F16C__, __AVX__, etc.) that is never defined here.
# elif !defined(DRJIT_DISABLE_AVX512) || !defined(__AVX512F__)
# include <immintrin.h>
# else
diff --git a/ext/drjit-core/src/lock.h b/ext/drjit-core/src/lock.h
--- a/ext/drjit-core/src/lock.h
+++ b/ext/drjit-core/src/lock.h
@@ -102,8 +102,10 @@
#endif
#elif defined(__aarch64__)
__asm__ __volatile__("yield");
- #else
+ #elif defined(__i386__) || defined(__x86_64__)
__builtin_ia32_pause();
+ #else
+ // No portable spin-wait hint on this architecture.
#endif
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Mon, 28 Sep 2026 00:00:00 +0000
Subject: [PATCH] package licences of vendored lz4, xxHash and robin_map

ext/drjit-core/ext/lz4/{lz4,xxhash}.c compile straight into libdrjit-core.so,
and ext/drjit-core/ext/robin_map's header-only hash map is compiled into
both libdrjit-core.so and the _drjit_ext Python extension, but the wheel
only ships drjit's own top-level LICENSE (confirmed against the released
PyPI wheel's dist-info/licenses/). lz4/xxHash are BSD-2-Clause (Yann
Collet) and robin_map is MIT (Tessil), both requiring their own copyright
notice to travel with binary redistributions.

pyproject.toml declares no `license-files`, so scikit-build-core falls
back to its own root-only `LICEN[CS]E*` glob and never reaches these two
vendored, nested LICENSE files that already exist in the tree (as git
submodules). Listing all three explicitly is enough: scikit-build-core
copies each listed path into dist-info/licenses/<same relative path>
unchanged (see build/wheel.py's `wheel_dirs["metadata"] / "licenses" / x`),
so this only adds files, it does not change how the existing top-level
LICENSE is packaged.

Upstream-Status: To upstream [not yet submitted; same gap on every architecture upstream already ships, not riscv64-specific]

Signed-off-by: Ludovic Henry <git@ludovic.dev>
---
diff --git a/pyproject.toml b/pyproject.toml
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -14,6 +14,11 @@ dependencies = [
"typing_extensions;python_version<\"3.11\""
]
requires-python = ">=3.8"
+license-files = [
+ "LICENSE",
+ "ext/drjit-core/ext/lz4/LICENSE",
+ "ext/drjit-core/ext/robin_map/LICENSE",
+]
authors = [
{ name = "Wenzel Jakob", email = "wenzel.jakob@epfl.ch" },
{ name = "Christian Döring", email = "christian.doering@tum.de" },
Loading