From 886197b2e7add31fda81e3be2aa7978fcdadb4e7 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Thu, 15 Oct 2026 04:45:49 +0000 Subject: [PATCH 1/3] quadrants: port to riscv64 Genesis-AI's fork of taichi: a scikit-build-core C++ compiler with an LLVM JIT CPU backend. Compared with the parked taichi entry, two of taichi's three walls are gone here: quadrants targets LLVM 22.1.0 (opaque pointers; RuntimeDyldELF has real RISC-V relocation handling since LLVM 20, confirmed directly against llvmorg-22.1.0 source: 42 riscv hits, a real resolveRISCVRelocation()) and finds clang via clang-22/21/20. The remaining wall is quadrants having no riscv64 CPU arch and no riscv64 LLVM to build against: - patches/quadrants/1.3.3/0001 adds a riscv64 CPU arch alongside x64/arm64 everywhere they're handled (CXXFlags, the arch enum, rhi/arch.cpp, and four scattered "is this a CPU arch" checks in the LLVM codegen/transforms). - 0002/0003 patch the CI build scripts: no riscv64 LLVM/sccache release exists to download, so setup_llvm() expects LLVM_DIR to already point at the LLVM we build ourselves, setup_sccache() no-ops (ccache from the manylinux image covers it instead), CUDA/ AMDGPU/Vulkan (no riscv64 backend for any of them) are turned off, and the dev dependency group is trimmed to what the build actually needs (full --group dev pulls pyright, which has no riscv64 wheel or sdist). build-quadrants.yml builds LLVM+clang 22.1.0 from source once (RISCV target only, matching build-libclang.yml's from-source precedent but also linking OrcJIT/ExecutionEngine/RuntimeDyld, which libclang.so doesn't need), then reuses it across the cp310/cp311/cp312/cp313 matrix - mirroring upstream's own manylinux_wheel.yml scripts (1_prerequisites.sh/2_build.sh/3_check.sh) almost verbatim, pointed at our own LLVM instead of their quadrants-sdk-builds releases. GCC (not our built clang) still compiles the bulk of the project, same as upstream; clang only compiles the LLVM JIT's runtime.cpp bitcode, and the main library links the static LLVM libraries GCC-side, which is an ordinary cross-compiler-ABI link on Linux. No gpl_sources job: the build doesn't statically link libstdc++ into the wheel (that flag is MinGW-only in QuadrantsCXXFlags.cmake), and glibc-based manylinux auditwheel doesn't bundle it either. --- .github/workflows/build-quadrants.yml | 169 ++++++++++++++++ docs/packages/quadrants.yaml | 5 + .../1.3.3/0001-Add-a-riscv64-CPU-arch.patch | 181 ++++++++++++++++++ ...LLVM-sccache-manylinux-build-scripts.patch | 112 +++++++++++ ...up-install-and-drop-submodule-reinit.patch | 68 +++++++ 5 files changed, 535 insertions(+) create mode 100644 .github/workflows/build-quadrants.yml create mode 100644 docs/packages/quadrants.yaml create mode 100644 patches/quadrants/1.3.3/0001-Add-a-riscv64-CPU-arch.patch create mode 100644 patches/quadrants/1.3.3/0002-CI-support-riscv64-in-the-LLVM-sccache-manylinux-build-scripts.patch create mode 100644 patches/quadrants/1.3.3/0003-CI-trim-riscv64-dev-group-install-and-drop-submodule-reinit.patch diff --git a/.github/workflows/build-quadrants.yml b/.github/workflows/build-quadrants.yml new file mode 100644 index 000000000000..03ea1f48f10b --- /dev/null +++ b/.github/workflows/build-quadrants.yml @@ -0,0 +1,169 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +# +# This workflow is based on: https://github.com/Genesis-Embodied-AI/quadrants/blob/v1.3.3/.github/workflows/manylinux_wheel.yml +name: Build quadrants wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/quadrants.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-quadrants.yml' + - 'docs/packages/quadrants.yaml' + - 'patches/quadrants/**' + push: + branches: [main] + paths: + - '.github/workflows/build-quadrants.yml' + - 'docs/packages/quadrants.yaml' + - 'patches/quadrants/**' + +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 + LLVM_VERSION: 22.1.0 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: quadrants + version: ${{ inputs.version }} + + build_llvm: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build LLVM ${{ env.LLVM_VERSION }} for quadrants (riscv64) + runs-on: ubuntu-24.04-riscv + container: ${{ env.MANYLINUX_RISCV64_IMAGE }} + timeout-minutes: 1440 # full LLVM+clang compile from source + + steps: + # quadrants-sdk-builds (download_llvm.py's own release bucket) ships no riscv64 LLVM; + # build LLVM+clang 22.1.0 ourselves with only the RISCV target and the components + # quadrants's QuadrantsCore.cmake links (Core/ExecutionEngine/OrcJIT/RuntimeDyld/...). + # No CUDA/AMDGPU/Vulkan backend exists for riscv64 (patches/quadrants disables them), so + # NVPTX/AMDGPU targets are left out too. + - name: Build and install LLVM+clang + run: | + set -euxo pipefail + dnf -y install ninja-build lld + curl -fsSL -o llvm-project.src.tar.xz \ + "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/llvm-project-${LLVM_VERSION}.src.tar.xz" + tar xf llvm-project.src.tar.xz + cmake -S "llvm-project-${LLVM_VERSION}.src/llvm" -B build -G Ninja \ + -DLLVM_ENABLE_PROJECTS=clang \ + -DLLVM_TARGETS_TO_BUILD=RISCV \ + -DBUILD_SHARED_LIBS=OFF \ + -DLLVM_ENABLE_ZLIB=OFF \ + -DLLVM_ENABLE_ZSTD=OFF \ + -DLLVM_ENABLE_TERMINFO=OFF \ + -DLLVM_ENABLE_LIBXML2=OFF \ + -DLLVM_INCLUDE_TESTS=OFF \ + -DLLVM_INCLUDE_EXAMPLES=OFF \ + -DLLVM_INCLUDE_BENCHMARKS=OFF \ + -DLLVM_USE_LINKER=lld \ + -DLLVM_PARALLEL_LINK_JOBS=1 \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="$(pwd)/llvm-install" + cmake --build build --target install -j"$(nproc)" + tar czf llvm-install.tar.gz llvm-install + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: quadrants-llvm-${{ env.LLVM_VERSION }}-riscv64 + path: llvm-install.tar.gz + if-no-files-found: error + retention-days: 1 + + build_wheels: + needs: [setup, build_llvm] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + python: ["cp310", "cp311", "cp312", "cp313"] + name: Build quadrants ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + container: ${{ env.MANYLINUX_RISCV64_IMAGE }} + timeout-minutes: 360 + + env: + QUADRANTS_VERSION: ${{ matrix.version }} + PIP_EXTRA_INDEX_URL: https://pypi.riseproject.dev/simple/ + + steps: + - name: Checkout quadrants v${{ env.QUADRANTS_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: Genesis-Embodied-AI/quadrants + ref: v${{ env.QUADRANTS_VERSION }} + submodules: recursive + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Patch quadrants source + run: git apply python-wheels/patches/quadrants/${{ env.QUADRANTS_VERSION }}/*.patch + + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: quadrants-llvm-${{ env.LLVM_VERSION }}-riscv64 + + - name: Unpack LLVM, select the interpreter + run: | + tar xzf llvm-install.tar.gz + echo "LLVM_DIR=$(pwd)/llvm-install" >> "$GITHUB_ENV" + echo "/opt/python/${{ matrix.python }}-${{ matrix.python }}/bin" >> "$GITHUB_PATH" + + - name: Build and check wheel + run: | + bash .github/workflows/scripts_new/manylinux_wheel/1_prerequisites.sh + bash .github/workflows/scripts_new/manylinux_wheel/2_build.sh + bash .github/workflows/scripts_new/manylinux_wheel/3_check.sh + + - name: Smoke-test wheel + run: | + rm -f dist/*.whl + cp wheelhouse/*.whl dist/ + bash .github/workflows/scripts_new/manylinux_wheel/4_install_wheel.sh + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: quadrants-${{ matrix.version }}-${{ matrix.python }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish quadrants ${{ 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: quadrants-${{ matrix.version }}-*-manylinux_riscv64 diff --git a/docs/packages/quadrants.yaml b/docs/packages/quadrants.yaml new file mode 100644 index 000000000000..af00d984a3ff --- /dev/null +++ b/docs/packages/quadrants.yaml @@ -0,0 +1,5 @@ +package-name: quadrants +source-code: https://github.com/Genesis-Embodied-AI/quadrants +license: Apache-2.0 +versions: +- version: 1.3.3 diff --git a/patches/quadrants/1.3.3/0001-Add-a-riscv64-CPU-arch.patch b/patches/quadrants/1.3.3/0001-Add-a-riscv64-CPU-arch.patch new file mode 100644 index 000000000000..0d6d2c26de75 --- /dev/null +++ b/patches/quadrants/1.3.3/0001-Add-a-riscv64-CPU-arch.patch @@ -0,0 +1,181 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: RISE Project CI +Date: Thu, 15 Oct 2026 00:00:00 +0000 +Subject: [PATCH] Add a riscv64 CPU arch + +Quadrants' CPU-arch enum (quadrants/inc/archs.inc.h) only lists x64 and +arm64; cmake/QuadrantsCXXFlags.cmake FATAL_ERRORs on any other +CMAKE_SYSTEM_PROCESSOR, and rhi/arch.cpp's host_arch()/ +default_simd_width()/arch_is_cpu()/arch_uses_llvm() hit +RHI_NOT_IMPLEMENTED or silently exclude riscv64 from the CPU-backend +checks scattered through the LLVM codegen (program/extension.cpp, +runtime/llvm/llvm_context.cpp, runtime/llvm/adstack_lazy_claim/ +bound_eval.cpp, transforms/make_mesh_block_local.cpp). Add a riscv64 +case everywhere x64/arm64 are handled as CPU archs, so the LLVM CPU +JIT backend (orc::RTDyldObjectLinkingLayer on Linux) is reachable on +riscv64. default_simd_width() uses 4, matching arm64's conservative +choice since RV64GC has no fixed-width SIMD without RVV. The nanobind +enum binding in quadrants/python/export_lang.cpp regenerates from +archs.inc.h via the same PER_ARCH macro, so quadrants.riscv64 needs no +separate binding. + +Upstream-Status: To upstream + +Signed-off-by: RISE Project CI +--- + cmake/QuadrantsCXXFlags.cmake | 5 ++++- + quadrants/inc/archs.inc.h | 1 + + quadrants/program/extension.cpp | 3 +++ + quadrants/rhi/arch.cpp | 11 +++++++++-- + .../runtime/llvm/adstack_lazy_claim/bound_eval.cpp | 2 +- + quadrants/runtime/llvm/llvm_context.cpp | 2 +- + quadrants/transforms/make_mesh_block_local.cpp | 6 +++--- + 7 files changed, 22 insertions(+), 8 deletions(-) + +diff --git a/cmake/QuadrantsCXXFlags.cmake b/cmake/QuadrantsCXXFlags.cmake +index e4eb937..c78fbab 100644 +--- a/cmake/QuadrantsCXXFlags.cmake ++++ b/cmake/QuadrantsCXXFlags.cmake +@@ -132,12 +132,15 @@ elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64" OR "${CMAKE_SYSTEM_PROCES + elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQD_ARCH_x86") + set(ARCH "x86") ++elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "riscv64") ++ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQD_ARCH_riscv64") ++ set(ARCH "riscv64") + else() + message(FATAL_ERROR "Unknown processor type ${CMAKE_SYSTEM_PROCESSOR}") + endif() + set(HOST_ARCH ${ARCH} CACHE INTERNAL "Host arch") + +-if (LINUX AND CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") ++if (LINUX AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|riscv64)$") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -latomic") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -latomic") + endif() +diff --git a/quadrants/inc/archs.inc.h b/quadrants/inc/archs.inc.h +index 919673a..7b60fa6 100644 +--- a/quadrants/inc/archs.inc.h ++++ b/quadrants/inc/archs.inc.h +@@ -3,6 +3,7 @@ + // CPU archs + PER_ARCH(x64) // a.k.a. AMD64/x86_64 + PER_ARCH(arm64) // a.k.a. Aarch64, WIP ++PER_ARCH(riscv64) // RV64GC + PER_ARCH(js) // Javascript, N/A + PER_ARCH(python) // Python + +diff --git a/quadrants/program/extension.cpp b/quadrants/program/extension.cpp +index 9a51bc2..90cf6cc 100644 +--- a/quadrants/program/extension.cpp ++++ b/quadrants/program/extension.cpp +@@ -13,6 +13,9 @@ bool is_extension_supported(Arch arch, Extension ext) { + {Arch::arm64, + {Extension::sparse, Extension::quant, Extension::quant_basic, Extension::data64, Extension::adstack, + Extension::assertion, Extension::mesh}}, ++ {Arch::riscv64, ++ {Extension::sparse, Extension::quant, Extension::quant_basic, Extension::data64, Extension::adstack, ++ Extension::assertion, Extension::mesh}}, + {Arch::cuda, + {Extension::sparse, Extension::quant, Extension::quant_basic, Extension::data64, Extension::adstack, + Extension::bls, Extension::assertion, Extension::mesh}}, +diff --git a/quadrants/rhi/arch.cpp b/quadrants/rhi/arch.cpp +index b9a4822..db48549 100644 +--- a/quadrants/rhi/arch.cpp ++++ b/quadrants/rhi/arch.cpp +@@ -39,7 +39,8 @@ Arch arch_from_name(const std::string &arch_name) { + + // Assuming a processor is either a CPU or a GPU. DSP/TPUs not considered. + bool arch_is_cpu(Arch arch) { +- if (arch == Arch::x64 || arch == Arch::arm64 || arch == Arch::js || arch == Arch::python) { ++ if (arch == Arch::x64 || arch == Arch::arm64 || arch == Arch::riscv64 || arch == Arch::js || ++ arch == Arch::python) { + return true; + } else { + return false; +@@ -59,7 +60,8 @@ bool arch_is_metal(Arch arch) { + } + + bool arch_uses_llvm(Arch arch) { +- return (arch == Arch::x64 || arch == Arch::arm64 || arch == Arch::cuda || arch == Arch::amdgpu); ++ return (arch == Arch::x64 || arch == Arch::arm64 || arch == Arch::riscv64 || arch == Arch::cuda || ++ arch == Arch::amdgpu); + } + + bool arch_is_gpu(Arch arch) { +@@ -76,6 +78,9 @@ Arch host_arch() { + #endif + #if defined(QD_ARCH_ARM) + return Arch::arm64; ++#endif ++#if defined(QD_ARCH_riscv64) ++ return Arch::riscv64; + #endif + RHI_NOT_IMPLEMENTED + } +@@ -91,6 +96,8 @@ int default_simd_width(Arch arch) { + return 32; + } else if (arch == Arch::arm64) { + return 4; ++ } else if (arch == Arch::riscv64) { ++ return 4; + } else { + RHI_NOT_IMPLEMENTED; + return -1; +diff --git a/quadrants/runtime/llvm/adstack_lazy_claim/bound_eval.cpp b/quadrants/runtime/llvm/adstack_lazy_claim/bound_eval.cpp +index 0b62054..4849e62 100644 +--- a/quadrants/runtime/llvm/adstack_lazy_claim/bound_eval.cpp ++++ b/quadrants/runtime/llvm/adstack_lazy_claim/bound_eval.cpp +@@ -38,7 +38,7 @@ uint32_t LlvmRuntimeExecutor::publish_per_task_bound_count_cpu(std::size_t task_ + // Default to UINT32_MAX (no clamp); only override on a successful host evaluation. The codegen-emitted bounds clamp + // at the float LCA-block claim site stays inert when the slot holds UINT32_MAX, so this fall-through is a no-op that + // preserves the existing behaviour. +- if (config_.arch != Arch::x64 && config_.arch != Arch::arm64) { ++ if (config_.arch != Arch::x64 && config_.arch != Arch::arm64 && config_.arch != Arch::riscv64) { + return std::numeric_limits::max(); + } + if (!ad_stack.bound_expr.has_value()) { +diff --git a/quadrants/runtime/llvm/llvm_context.cpp b/quadrants/runtime/llvm/llvm_context.cpp +index d116503..ef7070c 100644 +--- a/quadrants/runtime/llvm/llvm_context.cpp ++++ b/quadrants/runtime/llvm/llvm_context.cpp +@@ -725,7 +725,7 @@ std::unique_ptr QuadrantsLLVMContext::module_from_file(const std:: + } + + // CPU-specific intrinsics +- if (arch_ == Arch::x64 || arch_ == Arch::arm64) { ++ if (arch_ == Arch::x64 || arch_ == Arch::arm64 || arch_ == Arch::riscv64) { + auto func = module->getFunction("cpu_clock_i64"); + if (func) { + func->deleteBody(); +diff --git a/quadrants/transforms/make_mesh_block_local.cpp b/quadrants/transforms/make_mesh_block_local.cpp +index 69d713f..60724b0 100644 +--- a/quadrants/transforms/make_mesh_block_local.cpp ++++ b/quadrants/transforms/make_mesh_block_local.cpp +@@ -110,7 +110,7 @@ void MakeMeshBlockLocal::replace_global_ptrs(SNode *snode) { + } + + // in the cpu backend, atomic op in body block could be demoted to non-atomic +- if (config_.arch != Arch::x64 && config_.arch != Arch::arm64) { ++ if (config_.arch != Arch::x64 && config_.arch != Arch::arm64 && config_.arch != Arch::riscv64) { + return; + } + std::vector atomic_ops; +@@ -144,7 +144,7 @@ Stmt *MakeMeshBlockLocal::create_xlogue(Stmt *start_val, + Stmt *idx = block_->push_back(mapping_data_type_); + [[maybe_unused]] Stmt *init_val = block_->push_back(idx, start_val); + Stmt *block_dim_val; +- if (config_.arch == Arch::x64 || config_.arch == Arch::arm64) { ++ if (config_.arch == Arch::x64 || config_.arch == Arch::arm64 || config_.arch == Arch::riscv64) { + block_dim_val = block_->push_back(TypedConstant(1)); + } else { + block_dim_val = block_->push_back(TypedConstant{offload_->block_dim}); +@@ -272,7 +272,7 @@ void MakeMeshBlockLocal::fetch_mapping( + std::function)> mapping_callback_handler, + std::function attr_callback_handler) { + Stmt *thread_idx_stmt; +- if (config_.arch == Arch::x64 || config_.arch == Arch::arm64) { ++ if (config_.arch == Arch::x64 || config_.arch == Arch::arm64 || config_.arch == Arch::riscv64) { + thread_idx_stmt = block_->push_back(TypedConstant(0)); + } else { + thread_idx_stmt = block_->push_back(offload_); // Equivalent to CUDA threadIdx diff --git a/patches/quadrants/1.3.3/0002-CI-support-riscv64-in-the-LLVM-sccache-manylinux-build-scripts.patch b/patches/quadrants/1.3.3/0002-CI-support-riscv64-in-the-LLVM-sccache-manylinux-build-scripts.patch new file mode 100644 index 000000000000..9f267451d12d --- /dev/null +++ b/patches/quadrants/1.3.3/0002-CI-support-riscv64-in-the-LLVM-sccache-manylinux-build-scripts.patch @@ -0,0 +1,112 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: RISE Project CI +Date: Thu, 15 Oct 2026 00:05:00 +0000 +Subject: [PATCH] CI: support riscv64 in the LLVM/sccache/manylinux build + scripts + +quadrants-sdk-builds (the GitHub release bucket .github/workflows/ +scripts/qd_build/llvm.py downloads a prebuilt LLVM 22.1.0 from) ships +only linux-x86_64/linux-aarch64/macos-arm64/windows-amd64 archives, and +mozilla/sccache's v0.10.0 release has no riscv64 binary either. On +riscv64, setup_llvm() now expects the caller to build LLVM+clang 22.1.0 +itself and export LLVM_DIR before build.py runs, and setup_sccache() +skips the compiler-launcher wrapping instead of trying to download a +binary that doesn't exist (CMakeLists.txt's own find_program(ccache) +still caches the per-interpreter rebuilds when the image has ccache +installed). + +scripts_new/manylinux_wheel/2_build.sh's PLATFORM allowlist rejects +every arch but x86_64/aarch64; add riscv64. There is no riscv64 +CUDA/AMDGPU/Vulkan backend (the LLVM CPU JIT is the only backend +riscv64 has), so turn those three off instead of upstream's CI default +of on, and skip QD_BUILD_TESTS (a CPU-only riscv64 leg doesn't need +the GPU-aware cpp test binary) and the gcc-toolset-14 include-path +workaround (that SCL package is x86_64/aarch64-only on the manylinux +images; riscv64 needs no such workaround since it only ever uses the +clang we ourselves just built). + +Upstream-Status: Inappropriate [no riscv64 LLVM/sccache release exists to point at; this wires our own from-source riscv64 LLVM build in instead] + +Signed-off-by: RISE Project CI +--- + .github/workflows/scripts/qd_build/llvm.py | 7 +++++++ + .github/workflows/scripts/qd_build/sccache.py | 8 ++++++++ + .../scripts_new/manylinux_wheel/2_build.sh | 20 ++++++++++++------- + 3 files changed, 28 insertions(+), 7 deletions(-) + +diff --git a/.github/workflows/scripts/qd_build/llvm.py b/.github/workflows/scripts/qd_build/llvm.py +index dbb9891..487c487 100644 +--- a/.github/workflows/scripts/qd_build/llvm.py ++++ b/.github/workflows/scripts/qd_build/llvm.py +@@ -28,6 +28,13 @@ def setup_llvm() -> str: + ) + + match (u.system, u.machine): ++ case ("Linux", "riscv64"): ++ # quadrants-sdk-builds ships no riscv64 LLVM; the caller builds LLVM itself and ++ # points LLVM_DIR at the install prefix before this runs. ++ out = os.environ.get("LLVM_DIR") ++ if not out: ++ raise RuntimeError("LLVM_DIR must be set to a prebuilt LLVM install on riscv64") ++ return out + case ("Linux", "x86_64"): + out = get_cache_home() / f"llvm-{llvm_version}-x86-{build_version}" + url = release_url_template.format(platform="linux-x86_64") +diff --git a/.github/workflows/scripts/qd_build/sccache.py b/.github/workflows/scripts/qd_build/sccache.py +index b5561c7..cce2b1e 100644 +--- a/.github/workflows/scripts/qd_build/sccache.py ++++ b/.github/workflows/scripts/qd_build/sccache.py +@@ -30,6 +30,14 @@ def setup_sccache() -> Command: + else: + raise RuntimeError(f"Unsupported platform: {u.system} {u.machine}") + ++ if (u.system, u.machine) == ("Linux", "riscv64"): ++ # mozilla/sccache publishes no riscv64 binary (v0.10.0 release assets checked). Skip the ++ # compiler-launcher wrapping (entry.py's action_wheel still calls the returned command ++ # with --start-server / -s, so it must accept and ignore arbitrary args) and let ccache ++ # (installed from the manylinux image, found by CMakeLists.txt's own find_program) cache ++ # the per-interpreter rebuilds instead. ++ return sh.bake("true") ++ + if not exe.exists(): + if (u.system, u.machine) == ("Linux", "x86_64"): + url = "https://github.com/mozilla/sccache/releases/download/v0.10.0/sccache-v0.10.0-x86_64-unknown-linux-musl.tar.gz" +diff --git a/.github/workflows/scripts_new/manylinux_wheel/2_build.sh b/.github/workflows/scripts_new/manylinux_wheel/2_build.sh +index 3c030bb..3561a10 100644 +--- a/.github/workflows/scripts_new/manylinux_wheel/2_build.sh ++++ b/.github/workflows/scripts_new/manylinux_wheel/2_build.sh +@@ -3,7 +3,7 @@ + set -ex + + PLATFORM=$(uname -m) +-if [[ ! "$PLATFORM" =~ ^(x86_64|aarch64)$ ]]; then ++if [[ ! "$PLATFORM" =~ ^(x86_64|aarch64|riscv64)$ ]]; then + echo "Unsupported architecture: $PLATFORM" + exit 1 + fi +@@ -18,13 +18,19 @@ echo "Detected platform: $PLATFORM" + # Add Taichi LLVM toolchain to PATH + export PATH="$PWD/taichi-llvm-15.0.7-linux-${PLATFORM}/bin:$PATH" + +-# Taichi build options +-export CMAKE_ARGS="-DQD_WITH_VULKAN:BOOL=ON -DQD_WITH_CUDA:BOOL=ON -DQD_WITH_AMDGPU:BOOL=ON -DQD_BUILD_TESTS:BOOL=ON" ++if [[ "$PLATFORM" == "riscv64" ]]; then ++ # No riscv64 CUDA/AMDGPU/Vulkan backend exists; the LLVM CPU JIT is the only one riscv64 ++ # needs (gotcha "Add a riscv64 CPU arch" patch). ++ export CMAKE_ARGS="-DQD_WITH_VULKAN:BOOL=OFF -DQD_WITH_CUDA:BOOL=OFF -DQD_WITH_AMDGPU:BOOL=OFF -DQD_BUILD_TESTS:BOOL=OFF" ++else ++ # Taichi build options ++ export CMAKE_ARGS="-DQD_WITH_VULKAN:BOOL=ON -DQD_WITH_CUDA:BOOL=ON -DQD_WITH_AMDGPU:BOOL=ON -DQD_BUILD_TESTS:BOOL=ON" + +-# GCC toolset include paths +-inc_base="/opt/rh/gcc-toolset-14/root/usr/include/c++/14" +-extra="$inc_base:$inc_base/${PLATFORM}-redhat-linux:$inc_base/backward" ++ # GCC toolset include paths ++ inc_base="/opt/rh/gcc-toolset-14/root/usr/include/c++/14" ++ extra="$inc_base:$inc_base/${PLATFORM}-redhat-linux:$inc_base/backward" + +-export CPLUS_INCLUDE_PATH="${CPLUS_INCLUDE_PATH:+$CPLUS_INCLUDE_PATH:}$extra" ++ export CPLUS_INCLUDE_PATH="${CPLUS_INCLUDE_PATH:+$CPLUS_INCLUDE_PATH:}$extra" ++fi + + ./build.py wheel diff --git a/patches/quadrants/1.3.3/0003-CI-trim-riscv64-dev-group-install-and-drop-submodule-reinit.patch b/patches/quadrants/1.3.3/0003-CI-trim-riscv64-dev-group-install-and-drop-submodule-reinit.patch new file mode 100644 index 000000000000..19469741e348 --- /dev/null +++ b/patches/quadrants/1.3.3/0003-CI-trim-riscv64-dev-group-install-and-drop-submodule-reinit.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: RISE Project CI +Date: Thu, 15 Oct 2026 00:10:00 +0000 +Subject: [PATCH] CI: trim riscv64's dev-group install and drop its submodule + re-init + +pyproject.toml's dev dependency group (installed wholesale by +1_prerequisites.sh) pulls in pyright, which ships no riscv64 wheel and +has no sdist to fall back to (it wraps a prebuilt Node binary per +platform) -- `pip install --group dev` has no partial-success mode, so +one unresolvable package fails the whole install. On riscv64, install +only what build.py's `pip wheel --no-build-isolation` actually needs: +build-system.requires (scikit-build-core, nanobind, numpy, +setuptools_scm), the cmake/ninja it drives them with, and +psutil/requests (qd_build's own non-stdlib imports). Install ccache +too: setup_sccache() (patched earlier) no-ops on riscv64, and +CMakeLists.txt's own find_program(ccache) is what caches the +per-interpreter rebuilds instead. + +The hardcoded `git config --global --add safe.directory +/__w/quadrants/quadrants` assumes upstream's own checkout path; our +workflow checks quadrants out under python-wheels' own job workspace, +not /__w/quadrants/quadrants, and actions/checkout already ran with +submodules: recursive, so the follow-up `git submodule update` is +both redundant and pointed at a path that doesn't exist here. Skip +both on riscv64. + +Upstream-Status: Inappropriate [riscv64 has no pyright wheel, and our workflow checks the repo out at a different path than upstream's own CI does] + +Signed-off-by: RISE Project CI +--- + .../manylinux_wheel/1_prerequisites.sh | 21 +++++++++++++++---- + 1 file changed, 17 insertions(+), 4 deletions(-) + +diff --git a/.github/workflows/scripts_new/manylinux_wheel/1_prerequisites.sh b/.github/workflows/scripts_new/manylinux_wheel/1_prerequisites.sh +index 03ca845..76157e2 100644 +--- a/.github/workflows/scripts_new/manylinux_wheel/1_prerequisites.sh ++++ b/.github/workflows/scripts_new/manylinux_wheel/1_prerequisites.sh +@@ -3,12 +3,25 @@ + set -ex + + pip install -U pip +-pip install --group dev ++if [[ "$(uname -m)" == "riscv64" ]]; then ++ # --group dev also pulls pyright, which ships no riscv64 wheel and no sdist to fall back ++ # to (it wraps a prebuilt Node binary); install only what `pip wheel --no-build-isolation` ++ # below actually needs: build.py's own [build-system].requires, plus the cmake/ninja it ++ # drives them with, plus psutil/requests (qd_build's own non-stdlib imports). ++ pip install "cmake<4.0.0" ninja "nanobind>=2.0.0,<2.14.0" "scikit-build-core>=0.10" \ ++ "setuptools_scm>=6.0" "numpy>=2.0.0" psutil requests ++ yum install -y ccache # setup_sccache() no-ops on riscv64; CMakeLists.txt's own ++ # find_program(ccache) picks this up as the compiler launcher instead. ++else ++ pip install --group dev ++fi + + yum install -y git wget +-# Note: following depends on the name of the repo: +-git config --global --add safe.directory /__w/quadrants/quadrants +-git submodule update --init --jobs 2 ++if [[ "$(uname -m)" != "riscv64" ]]; then ++ # Note: following depends on the name of the repo: ++ git config --global --add safe.directory /__w/quadrants/quadrants ++ git submodule update --init --jobs 2 ++fi + + LLVM_DIR=$(python download_llvm.py | tail -n 1) + export PATH=${LLVM_DIR}/bin:$PATH From ac6a6d82e4e7030db7587f6687a45b87d6239d0e Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Thu, 15 Oct 2026 04:50:02 +0000 Subject: [PATCH 2/3] build-quadrants.yml: fix job-level container/name using the env context jobs..container and jobs..name don't have the env context available (only github/needs/strategy/matrix/inputs/vars), so ${{ env.MANYLINUX_RISCV64_IMAGE }} in container: and ${{ env.LLVM_VERSION }} in a job name failed workflow validation outright (zero jobs scheduled, run marked failed). Hardcode the image literal in both container blocks, matching build-openmm.yml/build-syntaqlite.yml, and drop the now-unused MANYLINUX_RISCV64_IMAGE workflow env (LLVM_VERSION stays - run/with contexts do have env available). --- .github/workflows/build-quadrants.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-quadrants.yml b/.github/workflows/build-quadrants.yml index 03ea1f48f10b..4d95e5c79b12 100644 --- a/.github/workflows/build-quadrants.yml +++ b/.github/workflows/build-quadrants.yml @@ -32,7 +32,6 @@ permissions: contents: read env: - MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 LLVM_VERSION: 22.1.0 jobs: @@ -45,9 +44,10 @@ jobs: build_llvm: needs: [setup] if: needs.setup.outputs.versions != '[]' - name: Build LLVM ${{ env.LLVM_VERSION }} for quadrants (riscv64) + name: Build LLVM 22.1.0 for quadrants (riscv64) runs-on: ubuntu-24.04-riscv - container: ${{ env.MANYLINUX_RISCV64_IMAGE }} + container: + image: quay.io/pypa/manylinux_2_39_riscv64 timeout-minutes: 1440 # full LLVM+clang compile from source steps: @@ -98,7 +98,8 @@ jobs: python: ["cp310", "cp311", "cp312", "cp313"] name: Build quadrants ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 runs-on: ubuntu-24.04-riscv - container: ${{ env.MANYLINUX_RISCV64_IMAGE }} + container: + image: quay.io/pypa/manylinux_2_39_riscv64 timeout-minutes: 360 env: From c0e9143da8438bf976dfc6d477497c68d7fcdb06 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Sun, 25 Oct 2026 18:01:05 +0000 Subject: [PATCH 3/3] patches/quadrants: pin cmake to the riscv64-wheel version we host All four riscv64 build legs (cp310-cp313) failed identically in 1_prerequisites.sh's `pip install "cmake<4.0.0" ...` with: -- Could NOT find OpenSSL ... CMake Error at Utilities/cmcurl/CMakeLists.txt:772 (message): Could not find OpenSSL. Install an OpenSSL development package ... ERROR: Failed building wheel for cmake With PIP_EXTRA_INDEX_URL set, pip resolves the highest version satisfying a constraint across both the public index and ours, and an sdist-only candidate still counts as resolvable. PyPI's cmake publishes no riscv64 wheel for any release (checked its JSON: only macOS/Windows/manylinux x86_64/aarch64/ppc64le/s390x/armv7l and musllinux), and the newest release under "<4.0.0" at the time of this PR, 3.31.10, is newer than the 3.31.6 we mirror -- so pip picked 3.31.10's sdist over our riscv64 wheel and tried to build CMake itself from source, which fails configuring its bundled cmcurl for lack of OpenSSL dev headers in the manylinux_riscv64 image. Pin to "cmake==3.31.6", the exact version docs/packages/cmake.yaml publishes a manylinux_2_39_riscv64 wheel for (same fix shape as patches/ouster-sdk/1.0.1/0002-build-use-a-cmake-release-with-a-riscv64-wheel.patch, CLAUDE.md gotcha 30: "the version has to line up, not just the name"). --- ...up-install-and-drop-submodule-reinit.patch | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/patches/quadrants/1.3.3/0003-CI-trim-riscv64-dev-group-install-and-drop-submodule-reinit.patch b/patches/quadrants/1.3.3/0003-CI-trim-riscv64-dev-group-install-and-drop-submodule-reinit.patch index 19469741e348..4797ad8a1316 100644 --- a/patches/quadrants/1.3.3/0003-CI-trim-riscv64-dev-group-install-and-drop-submodule-reinit.patch +++ b/patches/quadrants/1.3.3/0003-CI-trim-riscv64-dev-group-install-and-drop-submodule-reinit.patch @@ -25,20 +25,32 @@ submodules: recursive, so the follow-up `git submodule update` is both redundant and pointed at a path that doesn't exist here. Skip both on riscv64. -Upstream-Status: Inappropriate [riscv64 has no pyright wheel, and our workflow checks the repo out at a different path than upstream's own CI does] +cmake is pinned to the exact 3.31.6 release we publish a riscv64 +wheel for (CLAUDE.md gotcha 30). With PIP_EXTRA_INDEX_URL set, pip +resolves the highest version satisfying a constraint across *both* +indexes and treats a sdist-only candidate as resolvable -- PyPI's +cmake ships no riscv64 wheel for any release, so an unpinned +"cmake<4.0.0" lets pip pick a newer PyPI-only sdist (3.31.10 as of +this port) over our wheel and build CMake itself from source. That +source build FATAL_ERRORs configuring its bundled cmcurl ("Could NOT +find OpenSSL ... Could not find OpenSSL. Install an OpenSSL +development package"), since the manylinux_riscv64 image carries no +OpenSSL dev headers. + +Upstream-Status: Inappropriate [riscv64 has no pyright wheel, our workflow checks the repo out at a different path than upstream's own CI does, and PyPI's cmake ships no riscv64 wheel for any release] Signed-off-by: RISE Project CI --- - .../manylinux_wheel/1_prerequisites.sh | 21 +++++++++++++++---- - 1 file changed, 17 insertions(+), 4 deletions(-) + .../manylinux_wheel/1_prerequisites.sh | 27 +++++++++++++++++---- + 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/workflows/scripts_new/manylinux_wheel/1_prerequisites.sh b/.github/workflows/scripts_new/manylinux_wheel/1_prerequisites.sh -index 03ca845..76157e2 100644 +index 03ca845..eea10bb 100644 --- a/.github/workflows/scripts_new/manylinux_wheel/1_prerequisites.sh +++ b/.github/workflows/scripts_new/manylinux_wheel/1_prerequisites.sh -@@ -3,12 +3,25 @@ +@@ -3,12 +3,31 @@ set -ex - + pip install -U pip -pip install --group dev +if [[ "$(uname -m)" == "riscv64" ]]; then @@ -46,14 +58,20 @@ index 03ca845..76157e2 100644 + # to (it wraps a prebuilt Node binary); install only what `pip wheel --no-build-isolation` + # below actually needs: build.py's own [build-system].requires, plus the cmake/ninja it + # drives them with, plus psutil/requests (qd_build's own non-stdlib imports). -+ pip install "cmake<4.0.0" ninja "nanobind>=2.0.0,<2.14.0" "scikit-build-core>=0.10" \ ++ # Pinned to the exact version we publish a riscv64 wheel for: with PIP_EXTRA_INDEX_URL ++ # pip resolves the highest version across *both* indexes, and PyPI's cmake has no ++ # riscv64 wheel for any release (not even the one we mirror) -- an unpinned "cmake<4.0.0" ++ # lets pip land on a newer PyPI-only sdist (3.31.10 at the time of writing) and build ++ # CMake itself from source, which FATAL_ERRORs configuring its bundled cmcurl because ++ # this image carries no OpenSSL development headers. ++ pip install "cmake==3.31.6" ninja "nanobind>=2.0.0,<2.14.0" "scikit-build-core>=0.10" \ + "setuptools_scm>=6.0" "numpy>=2.0.0" psutil requests + yum install -y ccache # setup_sccache() no-ops on riscv64; CMakeLists.txt's own + # find_program(ccache) picks this up as the compiler launcher instead. +else + pip install --group dev +fi - + yum install -y git wget -# Note: following depends on the name of the repo: -git config --global --add safe.directory /__w/quadrants/quadrants @@ -63,6 +81,6 @@ index 03ca845..76157e2 100644 + git config --global --add safe.directory /__w/quadrants/quadrants + git submodule update --init --jobs 2 +fi - + LLVM_DIR=$(python download_llvm.py | tail -n 1) export PATH=${LLVM_DIR}/bin:$PATH