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

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

permissions:
contents: read

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

build_dll:
name: Build v8 dll for mini-racer ${{ matrix.version }}-riscv64
needs: [setup]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
env:
MINI_RACER_VERSION: ${{ matrix.version }}
runs-on: ubuntu-24.04-riscv
timeout-minutes: 2880 # 48h: a from-scratch monolithic v8 build got to 1970/2176 in 23h30m
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 ${{ matrix.version }} wheel for riscv64
needs: [setup, build_dll]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
env:
MINI_RACER_VERSION: ${{ matrix.version }}
runs-on: ubuntu-24.04-riscv
# Four pytest runs on a 4-core runner, gated behind a day-long build_dll:
# a re-run to widen a default timeout would cost that day again.
timeout-minutes: 720
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 ${{ matrix.version }}
needs: [setup, build_wheel]
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: mini-racer-${{ matrix.version }}-py3-manylinux_riscv64
5 changes: 5 additions & 0 deletions docs/packages/mini-racer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package-name: mini-racer
source-code: https://github.com/bpcreech/PyMiniRacer
license: ISC
versions:
- version: 0.14.1
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