Skip to content
Open
154 changes: 154 additions & 0 deletions .github/workflows/build-mini-racer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# Based on the `build-dll`/`build-wheel` jobs of
# https://github.com/bpcreech/PyMiniRacer/blob/v0.14.1/.github/workflows/build.yml
name: Build mini-racer wheels (riscv64)

on:
workflow_dispatch:
inputs:
version:
description: 'mini-racer version to build (git tag without leading v)'
required: true
default: '0.14.1'
pull_request:
paths:
- '.github/workflows/build-mini-racer.yml'
- 'patches/mini-racer/**'

run-name: build-mini-racer - ${{ inputs.version || '0.14.1' }}

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

permissions:
contents: read

env:
MINI_RACER_VERSION: ${{ inputs.version || '0.14.1' }}

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

build_dll:
name: Build v8 dll for mini-racer ${{ inputs.version || '0.14.1' }}-riscv64
needs: [setup]
runs-on: ubuntu-24.04-riscv
timeout-minutes: 1440 # 24h: a from-scratch monolithic v8 build
container:
image: quay.io/pypa/manylinux_2_39_riscv64

steps:
- name: Checkout mini-racer v${{ env.MINI_RACER_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: bpcreech/PyMiniRacer
ref: v${{ env.MINI_RACER_VERSION }}
persist-credentials: false

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

- name: Patch mini-racer source
run: git apply python-wheels/patches/mini-racer/${{ env.MINI_RACER_VERSION }}/*.patch

- name: Install build dependencies
run: dnf install -y git curl which patch

- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
# the v8 build still doesn't work with a system Python > 3.11 (upstream
# build.yml pins the same version):
python-version: '3.11'
activate-environment: true
enable-cache: false

- name: Provide a riscv64-linux-gnu- prefixed toolchain
run: |
mkdir -p "${RUNNER_TEMP}/riscv64-toolchain/bin"
for tool in gcc g++ ar as ld nm objcopy objdump ranlib readelf strip; do
real=$(command -v "${tool}" || true)
[ -n "${real}" ] && ln -sf "${real}" "${RUNNER_TEMP}/riscv64-toolchain/bin/riscv64-linux-gnu-${tool}"
done
echo "${RUNNER_TEMP}/riscv64-toolchain/bin" >> "${GITHUB_PATH}"

- name: Build dll with v8
run: |
uv sync --no-install-project
uv run --no-project builder/v8_build.py --out-path=_dll --target-cpu=riscv64

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: mini-racer-${{ env.MINI_RACER_VERSION }}-dll-riscv64
path: _dll
if-no-files-found: error

build_wheel:
name: Build and test mini-racer ${{ inputs.version || '0.14.1' }} wheel for riscv64
needs: [setup, build_dll]
runs-on: ubuntu-24.04-riscv
container:
image: quay.io/pypa/manylinux_2_39_riscv64

steps:
- name: Checkout mini-racer v${{ env.MINI_RACER_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: bpcreech/PyMiniRacer
ref: v${{ env.MINI_RACER_VERSION }}
persist-credentials: false

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

- name: Patch mini-racer source
run: git apply python-wheels/patches/mini-racer/${{ env.MINI_RACER_VERSION }}/*.patch

- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: mini-racer-${{ env.MINI_RACER_VERSION }}-dll-riscv64
path: src/py_mini_racer

- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
python-version: '3.12'
activate-environment: true
enable-cache: false

- name: Build wheel
run: uv build

# The wheel is "py3-none-<platform>" (one v8 build serves every interpreter);
# test it against each one the image ships, since uv has no riscv64 pythons to download.
- name: Test wheel
run: |
for py in /opt/python/cp312-cp312 /opt/python/cp313-cp313 /opt/python/cp314-cp314 /opt/python/cp314t-cp314t; do
"${py}/bin/pip" install dist/*.whl pytest
"${py}/bin/python" -m pytest tests
"${py}/bin/pip" uninstall -y mini-racer
done

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: mini-racer-${{ env.MINI_RACER_VERSION }}-py3-manylinux_riscv64
path: dist/*
if-no-files-found: error

publish:
name: Publish mini-racer ${{ inputs.version || '0.14.1' }}
needs: [setup, build_wheel]
permissions:
contents: write
pull-requests: write
uses: $/.github/workflows/_publish-wheel.yml
with:
artifact-pattern: mini-racer-${{ inputs.version || '0.14.1' }}-py3-manylinux_riscv64
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Sun, 13 Sep 2026 03:48:55 +0200
Subject: [PATCH 1/2] builder: recognize riscv64 as a native v8_target_cpu

get_local_v8_target_cpu() maps platform.machine() to a v8 target_cpu
string for every architecture PyMiniRacer's build.yml matrix builds
(x86/x64/arm/arm64/s390x/ppc64), but not riscv64: on a native riscv64
host it raises UnknownArchError before the --target-cpu override in
run_build() is even consulted, since that override still calls this
function to decide whether to cross-install a sysroot.

Upstream-Status: To upstream [not yet submitted to bpcreech/PyMiniRacer]

Signed-off-by: Ludovic Henry <git@ludovic.dev>
---
builder/v8_build.py | 2 ++
1 file changed, 2 insertions(+)

diff --git a/builder/v8_build.py b/builder/v8_build.py
index 090bb8c..cbcbce0 100644
--- a/builder/v8_build.py
+++ b/builder/v8_build.py
@@ -61,6 +61,8 @@ def get_local_v8_target_cpu() -> str:
return "s390x"
if m == "ppc64":
return "ppc64"
+ if m == "riscv64":
+ return "riscv64"

raise UnknownArchError(m)

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Sun, 13 Sep 2026 03:48:55 +0200
Subject: [PATCH 2/2] setup: give riscv64 wheels their own manylinux platform
tag

_get_platform_tag() only special-cases aarch64 among non-x86_64
manylinux tags; every other architecture, riscv64 included, falls
through to the final "return manylinux_2_27_x86_64", mislabeling a
riscv64 build as x86_64. Unlike x86_64/aarch64, v8's sysroot fetcher
has no riscv64 sysroot to bundle (build/linux/sysroot_scripts), so a
riscv64 build cannot claim the same backward-compatible manylinux_2_27
floor those get; it is only as portable as the glibc of the image it
was built in, so report that instead.

Upstream-Status: To upstream [not yet submitted to bpcreech/PyMiniRacer]

Signed-off-by: Ludovic Henry <git@ludovic.dev>
---
setup.py | 6 ++++++
1 file changed, 6 insertions(+)

diff --git a/setup.py b/setup.py
index 380195c..48df240 100644
--- a/setup.py
+++ b/setup.py
@@ -30,6 +30,12 @@ def _get_platform_tag() -> str:
if tag.endswith("_aarch64"):
return "manylinux_2_27_aarch64"

+ # Unlike x86_64/aarch64, v8's sysroot fetcher has no riscv64 sysroot to
+ # bundle, so the wheel is only as portable as the glibc it was built
+ # against; report that instead of silently mislabeling it x86_64:
+ if tag.endswith("_riscv64"):
+ return "manylinux_2_39_riscv64"
+
return "manylinux_2_27_x86_64"

return tag
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Sun, 13 Sep 2026 03:51:38 +0200
Subject: [PATCH] builder: use the riscv64-linux-gnu- gcc_toolchain, not v8's
clang

Chromium's prebuilt clang toolchain (third_party/llvm-build, fetched by
gclient's DEPS from the chromium-browser-clang GCS bucket) is only
published for amd64/arm64/mac hosts; the Linux object is
unconditioned on host_cpu (only on host_os == "linux"), so gclient
sync happily downloads an x86_64 clang binary on a riscv64 host that
can never execute it. v8's own build/toolchain/linux/BUILD.gn already
defines a gcc_toolchain("riscv64") for exactly this situation (see
https://github.com/riscv-collab/v8/wiki/Cross-compiled-Build); passing
is_clang=false for a riscv64 target routes gn at that toolchain instead
of the missing clang one.

Upstream-Status: To upstream [not yet submitted to bpcreech/PyMiniRacer]

Signed-off-by: Ludovic Henry <git@ludovic.dev>
---
builder/v8_build.py | 7 +++++++
1 file changed, 7 insertions(+)

diff --git a/builder/v8_build.py b/builder/v8_build.py
index cbcbce0..7a08487 100644
--- a/builder/v8_build.py
+++ b/builder/v8_build.py
@@ -251,6 +251,13 @@ def run_build(build_dir: Path, args: Args) -> None:
# https://gitlab.alpinelinux.org/alpine/aports/-/issues/16210
opts["v8_enable_partition_alloc"] = "false"

+ if target_cpu == "riscv64":
+ # Chromium's prebuilt clang (third_party/llvm-build) is only published
+ # for amd64/arm64/mac hosts, so a riscv64 build must instead use the
+ # riscv64-linux-gnu- gcc_toolchain already wired into
+ # build/toolchain/linux/BUILD.gn:
+ opts["is_clang"] = "false"
+
if is_linux() and target_cpu != get_local_v8_target_cpu():
run(
executable,
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Sun, 13 Sep 2026 04:20:09 +0200
Subject: [PATCH] builder: serialize gclient sync on riscv64

depot_tools' own gclient.py already forces --jobs=1 for 32-bit arm
boards ("Some arm boards have issues with parallel sync"); a riscv64
CI runner hits the same class of failure during 'gclient sync':
concurrent sync workers race to bootstrap the shared gsutil binary
under a flock()-based lockfile, and lose it with

lockfile.LockError: ... (err: [Errno 11] Resource temporarily unavailable)

Serialize the sync on riscv64 the same way upstream already does for
arm.

Upstream-Status: To upstream [not yet submitted to bpcreech/PyMiniRacer]

Signed-off-by: Ludovic Henry <git@ludovic.dev>
---
builder/v8_build.py | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/builder/v8_build.py b/builder/v8_build.py
index 7a08487..4c8f3f4 100644
--- a/builder/v8_build.py
+++ b/builder/v8_build.py
@@ -173,14 +173,23 @@ solutions = [
"""
)

- run(
+ gclient_args = [
executable,
str(get_depot_tools_path() / "gclient.py"),
"sync",
"--revision",
f"v8@{revision}",
- cwd=get_workspace_path(),
- )
+ ]
+
+ if machine().lower() == "riscv64":
+ # depot_tools' own gclient.py already serializes sync ("-j1") on 32-bit
+ # arm boards because parallel syncs are flaky there; riscv64 boards hit
+ # the same class of failure (a "gsutil" bootstrap lockfile.LockError:
+ # [Errno 11] Resource temporarily unavailable from concurrent workers
+ # racing to fetch it), so do the same here.
+ gclient_args += ["--jobs", "1"]
+
+ run(*gclient_args, cwd=get_workspace_path())

link_name = get_v8_path() / "custom_deps" / "mini_racer"
link_name.unlink(missing_ok=True)
Loading
Loading