diff --git a/.github/workflows/build-ray.yml b/.github/workflows/build-ray.yml new file mode 100644 index 000000000..18072e203 --- /dev/null +++ b/.github/workflows/build-ray.yml @@ -0,0 +1,304 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +# +# This workflow is based on: https://github.com/ray-project/ray/blob/master/python/build-wheel-manylinux2014.sh +--- +name: Build ray wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'ray version to build (git tag without leading ray-, e.g. 2.58.0)' + required: true + default: '2.58.0' + pull_request: + paths: + - '.github/workflows/build-ray.yml' + - 'patches/ray/**' + +run-name: build-ray - ${{ inputs.version || '2.58.0' }} + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || '2.58.0' }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +env: + RAY_VERSION: ${{ inputs.version || '2.58.0' }} + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + # Upstream fetches this version through bazelisk, which publishes no riscv64 + # binary, so it is bootstrapped from the dist archive instead. + BAZEL_VERSION: '7.5.0' + # versions bazel 7.5.0's MODULE.bazel pins; both need a riscv64 fix below + RULES_PYTHON_VERSION: '0.33.2' + RULES_JAVA_VERSION: '7.6.5' + # ci/build/build-manylinux-forge.sh pins this node for the dashboard build. + NODE_VERSION: '14.21.3' + +jobs: + dashboard: + name: Build ray dashboard + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + # The dashboard is a React app: arch-independent output, and node.js has no riscv64 release. + - name: Checkout ray ${{ inputs.version || '2.58.0' }} + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: ray-project/ray + ref: ray-${{ env.RAY_VERSION }} + fetch-depth: 1 + persist-credentials: false + + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Build dashboard + working-directory: python/ray/dashboard/client + run: | + npm ci + npm run build + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ray-dashboard-${{ env.RAY_VERSION }} + path: python/ray/dashboard/client/build + if-no-files-found: error + + bazel: + name: Bootstrap bazel (riscv64) + runs-on: ubuntu-24.04-riscv + timeout-minutes: 720 + + steps: + - name: Restore bazel binary + id: cache + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + 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}" \ + -e RULES_PYTHON_VERSION="${RULES_PYTHON_VERSION}" \ + -e RULES_JAVA_VERSION="${RULES_JAVA_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 + + # rules_python 0.33.2's PLATFORMS has no riscv64 entry, aborting the bootstrap + # (bazelbuild/bazel#23018). Any linux entry is a safe stand-in: the toolchain it names + # is never selected on a riscv64 host. Fixed in bazel 8.2.0; the 7.x backport is open. + mkdir -p /tmp/rules_python + curl -fsSLo /tmp/rules_python.tar.gz "https://github.com/bazel-contrib/rules_python/releases/download/${RULES_PYTHON_VERSION}/rules_python-${RULES_PYTHON_VERSION}.tar.gz" + tar -xzf /tmp/rules_python.tar.gz -C /tmp/rules_python --strip-components=1 + sed -i 's|fail("No platform declared for host OS {} on arch {}".format(os_name, arch))|return "x86_64-unknown-linux-gnu"|' \ + /tmp/rules_python/python/private/toolchains_repo.bzl + + # rules_java 7.x maps riscv64 to a stray-colon include path, so libcpu_profiler.so + # can't find jni_md.h. Fixed in rules_java 8.x, never backported. + mkdir -p /tmp/rules_java + curl -fsSLo /tmp/rules_java.tar.gz "https://github.com/bazelbuild/rules_java/releases/download/${RULES_JAVA_VERSION}/rules_java-${RULES_JAVA_VERSION}.tar.gz" + tar -xzf /tmp/rules_java.tar.gz -C /tmp/rules_java + sed -i 's|\[":include/linux"\]|["include/linux"]|g' /tmp/rules_java/toolchains/BUILD + + 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 \ + --override_module=rules_python=/tmp/rules_python \ + --override_module=rules_java=/tmp/rules_java" \ + 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 ray ${{ inputs.version || '2.58.0' }} manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 1440 # 24h + needs: [bazel, dashboard] + + steps: + - name: Checkout ray ${{ inputs.version || '2.58.0' }} + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: ray-project/ray + ref: ray-${{ env.RAY_VERSION }} + path: ray + fetch-depth: 1 + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + path: python-wheels + fetch-depth: 1 + persist-credentials: false + + - name: Patch ray source + working-directory: ray + run: git apply ../python-wheels/patches/ray/${{ env.RAY_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: Download dashboard + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ray-dashboard-${{ env.RAY_VERSION }} + path: ray/python/ray/dashboard/client/build + + - 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/ \ + "${MANYLINUX_RISCV64_IMAGE}" \ + bash <<'SCRIPT' 2>&1 | tee build.log + 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 + install -m 0755 /work/bazel-bin/bazel /usr/local/bin/bazel + git config --global --add safe.directory '*' + + export BAZEL_PATH=/usr/local/bin/bazel + # A multi-hour build's default curses progress output is large enough that + # GitHub drops the job log, taking the failure with it. + # rules_foreign_cc ships no prebuilt cmake for riscv64 and its from-source + # bootstrap rejects the CXX=gcc bazel hands it, so use the image's own cmake. + export BAZEL_ARGS="--curses=no --show_progress_rate_limit=60 --extra_toolchains=@rules_foreign_cc//toolchains:preinstalled_cmake_toolchain" + export RAY_INSTALL_JAVA=0 + export RAY_DISABLE_EXTRA_CPP=1 + # //:gen_redis_pkg needs a perl toolchain rules_perl has no riscv64 entry for; its two + # binaries are referenced only by ray/_private/test_utils.py, never at runtime. + export RAY_BUILD_REDIS=0 + # Upstream sets RAY_BUILD_ENV per interpreter; it is an --action_env, + # so varying it rebuilds the whole C++ core once per wheel. + export RAY_BUILD_ENV=manylinux_riscv64 + + cd /work/ray + sed -i "s/{{RAY_COMMIT_SHA}}/$(git rev-parse HEAD)/g" python/ray/_version.py + + for python in cp312-cp312 cp313-cp313 cp314-cp314; do + # both exclusions are staged before the loop and are untracked, so a plain + # clean would take them: the dashboard artifact and the added rules_boost patch. + git clean -f -f -x -d -e python/ray/dashboard/client -e thirdparty/patches/boost-riscv64-context.patch + "/opt/python/${python}/bin/pip" install -q cython==3.0.12 setuptools==80.9.0 + # bazel actions run with a fixed PATH (--incompatible_strict_action_env), + # so the interpreter they compile against is picked up from here. + ln -sf "/opt/python/${python}/bin/python3" /usr/local/bin/python3 + # grpc's @local_config_python re-resolves Python.h only when a var in its `environ` + # changes, so without this every _raylet.so uses the first interpreter's headers. + export PYTHON3_BIN_PATH="/opt/python/${python}/bin/python3" + ( + cd python + PATH="/opt/python/${python}/bin:$PATH" \ + "/opt/python/${python}/bin/python" -m pip wheel -w dist . --no-deps + ) + # Mirrors upstream's rename: the wheels are built in the + # manylinux_2_39 image, but `pip wheel` tags them linux_riscv64. + for wheel in python/dist/*.whl; do + mv "${wheel}" "/work/wheelhouse/$(basename "${wheel}" | sed 's/-linux_riscv64\.whl$/-manylinux_2_39_riscv64.whl/')" + done + done + SCRIPT + + - name: Upload build log + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ray-${{ env.RAY_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: ray-${{ env.RAY_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/ \ + --shm-size=1g \ + "${MANYLINUX_RISCV64_IMAGE}" \ + bash <<'SCRIPT' + set -eux + + # /work holds the ray checkout, whose root is importable as a + # namespace package and would shadow the installed wheel. + cd /tmp + + for python in cp312-cp312 cp313-cp313 cp314-cp314; do + "/opt/python/${python}/bin/pip" install /work/wheelhouse/ray-*-"${python%%-*}"-*.whl + "/opt/python/${python}/bin/python" - <<'PY' + import ray + import ray._raylet + + assert ray._raylet.__file__.endswith(".so"), ray._raylet.__file__ + + ray.init(num_cpus=1, include_dashboard=False) + + + @ray.remote + def double(x): + return 2 * x + + + assert ray.get([double.remote(i) for i in range(4)]) == [0, 2, 4, 6] + ray.shutdown() + PY + done + SCRIPT + + publish: + name: Publish ray ${{ inputs.version || '2.58.0' }} to GitLab + needs: [build_wheels] + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Publish wheels and open docs PR + uses: riseproject-dev/python-wheels/actions/publish-wheels@main + with: + artifact-pattern: ray-${{ env.RAY_VERSION }}-*-manylinux_riscv64 + gitlab-username: ${{ vars.GITLAB_DEPLOY_USER }} + gitlab-token: ${{ secrets.GITLAB_DEPLOY_TOKEN }} + gitlab-project-id: ${{ vars.GITLAB_PROJECT_ID }} + gh-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/patches/ray/2.58.0/0001-Build-with-a-manylinux-interpreter-not-a-hermetic-CPython.patch b/patches/ray/2.58.0/0001-Build-with-a-manylinux-interpreter-not-a-hermetic-CPython.patch new file mode 100644 index 000000000..7db6734b2 --- /dev/null +++ b/patches/ray/2.58.0/0001-Build-with-a-manylinux-interpreter-not-a-hermetic-CPython.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Tue, 25 Aug 2026 22:30:00 +0200 +Subject: [PATCH] Build with a manylinux interpreter, not a hermetic CPython 3.10 + +WORKSPACE calls python_register_toolchains(python_version = "3.10") and then +load()s @python3_10 at the top level, so every bazel invocation downloads a +python-build-standalone interpreter for the host platform before anything is +analysed. The rules_python version ray resolves declares no riscv64 platform, +so that load fails with + + No platform declared for host OS linux on arch riscv64 + +Point the py310 runtime and pip_parse at the manylinux image's own CPython +3.10 instead. It has to stay 3.10 rather than follow the interpreter each +wheel is built for: the pip vendored by that rules_python imports distutils, +which 3.12 removed. + +Upstream-Status: Inappropriate [riscv64 build environment; upstream's own fix would be a rules_python bump, python-build-standalone does publish riscv64 CPython now] +--- +diff --git a/WORKSPACE b/WORKSPACE +index 927840e..c70da8c 100644 +--- a/WORKSPACE ++++ b/WORKSPACE +@@ -51,15 +51,6 @@ load("@hedron_compile_commands//:workspace_setup.bzl", "hedron_compile_commands_ + + hedron_compile_commands_setup() + +-load("@rules_python//python:repositories.bzl", "python_register_toolchains") +- +-python_register_toolchains( +- name = "python3_10", +- python_version = "3.10", +- register_toolchains = False, +-) +- +-load("@python3_10//:defs.bzl", python310 = "interpreter") + load("@rules_python//python/pip_install:repositories.bzl", "pip_install_dependencies") + + pip_install_dependencies() +@@ -69,7 +60,7 @@ load("@rules_python//python:pip.bzl", "pip_parse") + # For CI scripts use only; not for ray testing. + pip_parse( + name = "py_deps_py310", +- python_interpreter_target = python310, ++ python_interpreter = "/opt/python/cp310-cp310/bin/python3", + requirements_lock = "//release:requirements_py310.txt", + ) + +diff --git a/bazel/BUILD.bazel b/bazel/BUILD.bazel +index 9d422c1..cb3c32d 100644 +--- a/bazel/BUILD.bazel ++++ b/bazel/BUILD.bazel +@@ -1,4 +1,3 @@ +-load("@python3_10//:defs.bzl", python310 = "interpreter") + load("@py_deps_py310//:requirements.bzl", ci_require = "requirement") + load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_runtime", "py_runtime_pair") + +@@ -72,7 +71,7 @@ config_setting( + + py_runtime( + name = "py310_runtime", +- interpreter = python310, ++ interpreter_path = "/opt/python/cp310-cp310/bin/python3", + python_version = "PY3", + visibility = ["//visibility:private"], + ) diff --git a/patches/ray/2.58.0/0002-Build-boost.context-s-riscv64-assembly.patch b/patches/ray/2.58.0/0002-Build-boost.context-s-riscv64-assembly.patch new file mode 100644 index 000000000..f4cb6d763 --- /dev/null +++ b/patches/ray/2.58.0/0002-Build-boost.context-s-riscv64-assembly.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Thu, 27 Aug 2026 15:12:28 +0200 +Subject: [PATCH] Build boost.context's riscv64 assembly + +Boost.Context implements its stack switching primitives -- jump_fcontext, +make_fcontext, ontop_fcontext -- in hand-written assembly, one file per +(architecture, ABI, object format). rules_boost selects them in +BOOST_CTX_ASM_SOURCES, which enumerates aarch64, arm, ppc64, x86_64, the +Apple targets and Windows, and ends in "//conditions:default": []. + +riscv64 hits that default, so @boost//:context is compiled with no assembly +at all. Nothing fails while building: the three symbols are simply left +undefined in _raylet.so, which links @boost//:fiber, and the failure only +appears the first time the extension is loaded -- + + OSError: .../ray/_raylet.so: undefined symbol: jump_fcontext + +Boost has shipped libs/context/src/asm/{jump,make,ontop}_riscv64_sysv_elf_gas.S +since 1.71.0, and ray pins 1.81.0, so the sources are already present in the +archive. Add the linux_riscv64 config_setting and point the select at them, +exactly as every other ELF target does. + +Carried as one more entry in ray's existing thirdparty/patches list because +rules_boost is pinned as a third-party archive; the fix itself belongs in +nelhage/rules_boost, whose master branch still has no riscv64 branch in this +select. + +Upstream-Status: To upstream [the change is to nelhage/rules_boost, which ray pins by commit hash; ray itself can only carry it as a patch, and rules_boost has no riscv64 support to bump to] +--- + bazel/ray_deps_setup.bzl | 1 + + .../patches/boost-riscv64-context.patch | 30 +++++++++++++++++++ + 2 files changed, 31 insertions(+) + create mode 100644 thirdparty/patches/boost-riscv64-context.patch + +diff --git a/bazel/ray_deps_setup.bzl b/bazel/ray_deps_setup.bzl +index bbf3c84..c1d5710 100644 +--- a/bazel/ray_deps_setup.bzl ++++ b/bazel/ray_deps_setup.bzl +@@ -242,6 +242,7 @@ def ray_deps_setup(): + sha256 = "490d11425393eed068966a4990ead1ff07c658f823fd982fddac67006ccc44ab", + patches = [ + "//thirdparty/patches:boost-headers.patch", ++ "//thirdparty/patches:boost-riscv64-context.patch", + ], + patch_args = ["-p1"], + ) +diff --git a/thirdparty/patches/boost-riscv64-context.patch b/thirdparty/patches/boost-riscv64-context.patch +new file mode 100644 +index 0000000..bae3ec6 +--- /dev/null ++++ b/thirdparty/patches/boost-riscv64-context.patch +@@ -0,0 +1,30 @@ ++diff --git BUILD.boost BUILD.boost ++--- a/BUILD.boost +++++ b/BUILD.boost ++@@ -37,6 +37,14 @@ config_setting( ++ ) ++ ++ config_setting( +++ name = "linux_riscv64", +++ constraint_values = [ +++ "@platforms//os:linux", +++ "@platforms//cpu:riscv64", +++ ], +++) +++ +++config_setting( ++ name = "linux_x86_64", ++ constraint_values = [ ++ "@platforms//os:linux", ++@@ -111,6 +119,11 @@ BOOST_CTX_ASM_SOURCES = selects.with_or({ ++ "libs/context/src/asm/make_ppc64_sysv_elf_gas.S", ++ "libs/context/src/asm/ontop_ppc64_sysv_elf_gas.S", ++ ], +++ ":linux_riscv64": [ +++ "libs/context/src/asm/jump_riscv64_sysv_elf_gas.S", +++ "libs/context/src/asm/make_riscv64_sysv_elf_gas.S", +++ "libs/context/src/asm/ontop_riscv64_sysv_elf_gas.S", +++ ], ++ ":linux_x86_64": [ ++ "libs/context/src/asm/jump_x86_64_sysv_elf_gas.S", ++ "libs/context/src/asm/make_x86_64_sysv_elf_gas.S",