diff --git a/.github/workflows/build-mini-racer.yml b/.github/workflows/build-mini-racer.yml new file mode 100644 index 0000000000..8115366ec9 --- /dev/null +++ b/.github/workflows/build-mini-racer.yml @@ -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-" (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 diff --git a/docs/packages/mini-racer.yaml b/docs/packages/mini-racer.yaml new file mode 100644 index 0000000000..3982616b83 --- /dev/null +++ b/docs/packages/mini-racer.yaml @@ -0,0 +1,5 @@ +package-name: mini-racer +source-code: https://github.com/bpcreech/PyMiniRacer +license: ISC +versions: +- version: 0.14.1 diff --git a/patches/mini-racer/0.14.1/0001-builder-recognize-riscv64-as-a-native-v8_target_cpu.patch b/patches/mini-racer/0.14.1/0001-builder-recognize-riscv64-as-a-native-v8_target_cpu.patch new file mode 100644 index 0000000000..c97d7a6f58 --- /dev/null +++ b/patches/mini-racer/0.14.1/0001-builder-recognize-riscv64-as-a-native-v8_target_cpu.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +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 +--- + 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) + diff --git a/patches/mini-racer/0.14.1/0002-setup-give-riscv64-wheels-their-own-manylinux-platfo.patch b/patches/mini-racer/0.14.1/0002-setup-give-riscv64-wheels-their-own-manylinux-platfo.patch new file mode 100644 index 0000000000..0be1e64221 --- /dev/null +++ b/patches/mini-racer/0.14.1/0002-setup-give-riscv64-wheels-their-own-manylinux-platfo.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +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 +--- + 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 diff --git a/patches/mini-racer/0.14.1/0003-builder-use-the-riscv64-linux-gnu-gcc_toolchain-not-.patch b/patches/mini-racer/0.14.1/0003-builder-use-the-riscv64-linux-gnu-gcc_toolchain-not-.patch new file mode 100644 index 0000000000..06d8e73827 --- /dev/null +++ b/patches/mini-racer/0.14.1/0003-builder-use-the-riscv64-linux-gnu-gcc_toolchain-not-.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +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 +--- + 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, diff --git a/patches/mini-racer/0.14.1/0004-builder-serialize-gclient-sync-on-riscv64.patch b/patches/mini-racer/0.14.1/0004-builder-serialize-gclient-sync-on-riscv64.patch new file mode 100644 index 0000000000..2a03fa40fd --- /dev/null +++ b/patches/mini-racer/0.14.1/0004-builder-serialize-gclient-sync-on-riscv64.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +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 +--- + 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) diff --git a/patches/mini-racer/0.14.1/0005-builder-skip-reclient-siso-CIPD-packages-on-riscv64.patch b/patches/mini-racer/0.14.1/0005-builder-skip-reclient-siso-CIPD-packages-on-riscv64.patch new file mode 100644 index 0000000000..41c4c7defa --- /dev/null +++ b/patches/mini-racer/0.14.1/0005-builder-skip-reclient-siso-CIPD-packages-on-riscv64.patch @@ -0,0 +1,116 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sun, 13 Sep 2026 05:38:02 +0200 +Subject: [PATCH] builder: skip reclient/siso CIPD packages on riscv64 + +v8's DEPS fetches two CIPD packages unconditioned on riscv64: +buildtools/reclient (infra/rbe/client, the remote build execution +client) and third_party/siso/cipd (the siso build tool). Their +conditions exclude s390x/ppc64/zos and arm64-on-Linux, but not +riscv64, and CI confirmed neither package is published for +linux-riscv64: + + no such package: infra/rbe/client/linux-riscv64 + no such package: build/siso/linux-riscv64 + +Both are optional build accelerators that plain gn gen + ninja never +asks for. gn and ninja share the exact same guard clause and *do* +have riscv64 packages (verified against +https://chrome-infra-packages.appspot.com/p/gn/gn/linux-riscv64 and +the ninja/cpython3 3pp packages), so this can't be fixed by +overriding gclient's host_cpu var globally -- that would also stop gn +and ninja from being fetched. custom_deps can't target the two +packages by name either: CipdDependency.run() never consults the +Dependency.url a custom_deps override would change, only +should_process (derived from the DEPS-declared condition). + +Instead, let the first sync attempt run to completion (it always +clones v8 in full before it gets to resolving CIPD packages, so this +isn't wasted work), patch the two conditions out of the now-checked- +out DEPS on failure, and retry: gclient re-resolves "v8@{revision}" +to the same commit we are already on, so its git step is a no-op +(unless --reset/--force, neither of which we pass) and the patch +survives into the retry's dependency processing. + +Upstream-Status: To upstream [not yet submitted to bpcreech/PyMiniRacer] + +Signed-off-by: Ludovic Henry +--- + builder/v8_build.py | 55 +++++++++++++++++++++++++++++++++++++++------ + 1 file changed, 48 insertions(+), 7 deletions(-) + +diff --git a/builder/v8_build.py b/builder/v8_build.py +index 4c8f3f4..37a1f15 100644 +--- a/builder/v8_build.py ++++ b/builder/v8_build.py +@@ -10,7 +10,7 @@ from platform import machine + from re import match + from shlex import join as shlexjoin + from shutil import copyfile, rmtree +-from subprocess import check_call ++from subprocess import CalledProcessError, check_call + from sys import executable, platform + from typing import TYPE_CHECKING + +@@ -182,14 +182,55 @@ solutions = [ + ] + + 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. ++ # 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()) ++ try: ++ run(*gclient_args, cwd=get_workspace_path()) ++ except CalledProcessError: ++ # v8 is already fully cloned by this point (gclient always ++ # fetches the solution itself before resolving CIPD packages): ++ # two of its DEPS entries, buildtools/reclient ++ # (infra/rbe/client) and third_party/siso/cipd (the siso build ++ # tool), are unconditioned on riscv64 (their conditions exclude ++ # s390x/ppc64/zos, and arm64 on Linux, but not riscv64) even ++ # though neither package is published for linux-riscv64. Both ++ # are optional build accelerators that plain gn gen + ninja ++ # never asks for. gn and ninja share the exact same guard ++ # clause and *do* have riscv64 packages, so this can't be fixed ++ # by overriding gclient's host_cpu var (it would also disable ++ # gn/ninja); patch the two conditions out of the checkout ++ # instead. ++ deps_file = get_v8_path() / "DEPS" ++ if not deps_file.exists(): ++ raise ++ ++ text = deps_file.read_text() ++ for condition in ( ++ '(host_os == "linux" or host_os == "mac" or ' ++ 'host_os == "win") and host_cpu != "s390x" and ' ++ 'host_os != "zos" and host_cpu != "ppc64" and ' ++ '(host_cpu != "arm64" or host_os == "mac")', ++ 'not build_with_chromium and host_cpu != "s390x" and ' ++ 'host_os != "zos" and host_cpu != "ppc64"', ++ ): ++ text = text.replace( ++ condition, f'{condition} and host_cpu != "riscv64"', 1 ++ ) ++ deps_file.write_text(text) ++ ++ # Retrying re-resolves "v8@{revision}" to the same commit we ++ # are already sitting on, so gclient's git step is a no-op (it ++ # only touches the working tree on mismatch, or with ++ # --reset/--force, neither of which we pass) and the DEPS ++ # patch survives into this second sync's dependency processing. ++ run(*gclient_args, cwd=get_workspace_path()) ++ else: ++ run(*gclient_args, cwd=get_workspace_path()) + + link_name = get_v8_path() / "custom_deps" / "mini_racer" + link_name.unlink(missing_ok=True) diff --git a/patches/mini-racer/0.14.1/0006-builder-retry-the-DEPS-patch-without-revision.patch b/patches/mini-racer/0.14.1/0006-builder-retry-the-DEPS-patch-without-revision.patch new file mode 100644 index 0000000000..88886d3ca0 --- /dev/null +++ b/patches/mini-racer/0.14.1/0006-builder-retry-the-DEPS-patch-without-revision.patch @@ -0,0 +1,137 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sun, 13 Sep 2026 06:38:56 +0200 +Subject: [PATCH] builder: retry the DEPS patch without --revision + +CI proved the retry itself wrong: gclient still passed +--revision v8@branch-heads/14.4 on the second sync, so it re-resolved +and re-checked-out the v8 solution as normal (branch-heads/14.4 is a +branch name, not a pinned hash, so the current_revision fast path +never applied) -- which refused to proceed over our uncommitted DEPS +edit: + + You have uncommitted changes. + cd into v8, run git status to see changes, + and commit, stash, or reset. + +Drop --revision from the retry instead. The .gclient's +"managed: False" for the v8 solution only takes effect when there is +no revision override for it (gclient.py: 'if not revision_override and +not self.managed: revision_override = "unmanaged"'), which then skips +its git step entirely ("unmanaged solution; skipping v8") rather than +touching the working tree -- leaving the patch in place for the +retry's DEPS reprocessing. + +Upstream-Status: To upstream [not yet submitted to bpcreech/PyMiniRacer] + +Signed-off-by: Ludovic Henry +--- + builder/v8_build.py | 88 +++++++++++++++++++++++---------------------- + 1 file changed, 45 insertions(+), 43 deletions(-) + +diff --git a/builder/v8_build.py b/builder/v8_build.py +index 37a1f15..aeaf0a3 100644 +--- a/builder/v8_build.py ++++ b/builder/v8_build.py +@@ -177,8 +177,6 @@ solutions = [ + executable, + str(get_depot_tools_path() / "gclient.py"), + "sync", +- "--revision", +- f"v8@{revision}", + ] + + if machine().lower() == "riscv64": +@@ -189,47 +187,51 @@ solutions = [ + # from concurrent workers racing to fetch it), so do the same here. + gclient_args += ["--jobs", "1"] + +- try: +- run(*gclient_args, cwd=get_workspace_path()) +- except CalledProcessError: +- # v8 is already fully cloned by this point (gclient always +- # fetches the solution itself before resolving CIPD packages): +- # two of its DEPS entries, buildtools/reclient +- # (infra/rbe/client) and third_party/siso/cipd (the siso build +- # tool), are unconditioned on riscv64 (their conditions exclude +- # s390x/ppc64/zos, and arm64 on Linux, but not riscv64) even +- # though neither package is published for linux-riscv64. Both +- # are optional build accelerators that plain gn gen + ninja +- # never asks for. gn and ninja share the exact same guard +- # clause and *do* have riscv64 packages, so this can't be fixed +- # by overriding gclient's host_cpu var (it would also disable +- # gn/ninja); patch the two conditions out of the checkout +- # instead. +- deps_file = get_v8_path() / "DEPS" +- if not deps_file.exists(): +- raise +- +- text = deps_file.read_text() +- for condition in ( +- '(host_os == "linux" or host_os == "mac" or ' +- 'host_os == "win") and host_cpu != "s390x" and ' +- 'host_os != "zos" and host_cpu != "ppc64" and ' +- '(host_cpu != "arm64" or host_os == "mac")', +- 'not build_with_chromium and host_cpu != "s390x" and ' +- 'host_os != "zos" and host_cpu != "ppc64"', +- ): +- text = text.replace( +- condition, f'{condition} and host_cpu != "riscv64"', 1 +- ) +- deps_file.write_text(text) +- +- # Retrying re-resolves "v8@{revision}" to the same commit we +- # are already sitting on, so gclient's git step is a no-op (it +- # only touches the working tree on mismatch, or with +- # --reset/--force, neither of which we pass) and the DEPS +- # patch survives into this second sync's dependency processing. +- run(*gclient_args, cwd=get_workspace_path()) +- else: ++ try: ++ run(*gclient_args, "--revision", f"v8@{revision}", cwd=get_workspace_path()) ++ except CalledProcessError: ++ if machine().lower() != "riscv64": ++ raise ++ ++ # v8 is already fully cloned by this point (gclient always fetches ++ # the solution itself before resolving CIPD packages): two of its ++ # DEPS entries, buildtools/reclient (infra/rbe/client) and ++ # third_party/siso/cipd (the siso build tool), are unconditioned on ++ # riscv64 (their conditions exclude s390x/ppc64/zos, and arm64 on ++ # Linux, but not riscv64) even though neither package is published ++ # for linux-riscv64. Both are optional build accelerators that ++ # plain gn gen + ninja never asks for. gn and ninja share the exact ++ # same guard clause and *do* have riscv64 packages, so this can't ++ # be fixed by overriding gclient's host_cpu var (it would also ++ # disable gn/ninja); patch the two conditions out of the checkout ++ # instead. ++ deps_file = get_v8_path() / "DEPS" ++ if not deps_file.exists(): ++ raise ++ ++ text = deps_file.read_text() ++ for condition in ( ++ '(host_os == "linux" or host_os == "mac" or ' ++ 'host_os == "win") and host_cpu != "s390x" and ' ++ 'host_os != "zos" and host_cpu != "ppc64" and ' ++ '(host_cpu != "arm64" or host_os == "mac")', ++ 'not build_with_chromium and host_cpu != "s390x" and ' ++ 'host_os != "zos" and host_cpu != "ppc64"', ++ ): ++ text = text.replace( ++ condition, f'{condition} and host_cpu != "riscv64"', 1 ++ ) ++ deps_file.write_text(text) ++ ++ # Retry *without* --revision: "branch-heads/14.4" isn't a pinned ++ # hash, so passing it again makes gclient re-resolve and re-check- ++ # out the v8 solution as normal, which refuses to proceed over our ++ # uncommitted DEPS edit ("You have uncommitted changes"). Our ++ # .gclient marks the v8 solution "managed: False", which gclient ++ # only honors (skipping its git step entirely, "unmanaged ++ # solution; skipping v8") when there is *no* revision override for ++ # it -- leaving our patch in place for this sync's DEPS ++ # reprocessing. + run(*gclient_args, cwd=get_workspace_path()) + + link_name = get_v8_path() / "custom_deps" / "mini_racer" diff --git a/patches/mini-racer/0.14.1/0007-builder-use_sysroot-false-use_custom_libcxx-false-en.patch b/patches/mini-racer/0.14.1/0007-builder-use_sysroot-false-use_custom_libcxx-false-en.patch new file mode 100644 index 0000000000..a2a554d54c --- /dev/null +++ b/patches/mini-racer/0.14.1/0007-builder-use_sysroot-false-use_custom_libcxx-false-en.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sun, 13 Sep 2026 08:09:44 +0200 +Subject: [PATCH] builder: use_sysroot=false, use_custom_libcxx=false, + enable_rust=false on riscv64 + +CI got past the DEPS/CIPD fixes and into an actual ninja build for the +first time, which surfaced three independent problems: + +* use_sysroot defaults to Chromium's bundled + build/linux/debian_trixie_riscv64-sysroot, meant for *cross*-compiling + from an amd64 host. On a native riscv64 build host it fails + ('bits/wordsize.h: No such file or directory' -- its usr/include + lacks the multiarch subdirectory our riscv64-linux-gnu- toolprefix + trick expects from a real cross toolchain's built-in search paths). + Use the host's own headers/libc, which are already the right ones. + +* v8's custom (LLVM) libc++ requires GCC 15+ to build with GCC + (third_party/libc++/src/include/__configuration/compiler.h); our + manylinux_2_39_riscv64 image's GCC is older. Use system libstdc++, + which is what a GCC build normally pairs with. + +* Something still invokes third_party/rust-toolchain/bin/rustc despite + enable_rust defaulting to build_with_chromium (false for our + standalone checkout), and it segfaults on riscv64 + ('rustc --print=cfg --target=riscv64gc-unknown-linux-gnu' died with + SIGSEGV) -- like Chromium's clang, the prebuilt Rust toolchain is + published for amd64/arm64/mac hosts only. Disable Rust explicitly. + +Upstream-Status: To upstream [not yet submitted to bpcreech/PyMiniRacer] + +Signed-off-by: Ludovic Henry +--- + builder/v8_build.py | 24 ++++++++++++++++++++++++ + 1 file changed, 24 insertions(+) + +diff --git a/builder/v8_build.py b/builder/v8_build.py +index aeaf0a3..c82341e 100644 +--- a/builder/v8_build.py ++++ b/builder/v8_build.py +@@ -310,6 +310,30 @@ def run_build(build_dir: Path, args: Args) -> None: + # build/toolchain/linux/BUILD.gn: + opts["is_clang"] = "false" + ++ # use_sysroot defaults to fetching and building against Chromium's ++ # bundled build/linux/debian_trixie_riscv64-sysroot, meant for ++ # *cross*-compiling from an amd64 host; on a native riscv64 build ++ # host it fails outright (its usr/include lacks the multiarch ++ # bits/wordsize.h our riscv64-linux-gnu- toolprefix trick expects, ++ # since it isn't a real cross toolchain with matching built-in ++ # search paths). Use the host's own headers/libc instead, which are ++ # already the right ones for a native build: ++ opts["use_sysroot"] = "false" ++ ++ # v8's custom (LLVM) libc++ requires GCC 15+ to build with GCC ++ # (//third_party/libc++/src/include/__configuration/compiler.h); ++ # our manylinux_2_39_riscv64 image's GCC is older. Use the system ++ # libstdc++ instead, which is what a GCC build normally pairs with: ++ opts["use_custom_libcxx"] = "false" ++ ++ # enable_rust defaults to build_with_chromium (false for our ++ # standalone v8 checkout), but the prebuilt third_party/rust-toolchain ++ # is, like Chromium's clang, published for amd64/arm64/mac hosts only; ++ # something still invokes it for our riscv64 build and segfaults ++ # trying to run it, so disable Rust explicitly rather than rely on a ++ # default this pinned build/rust.gni revision may compute differently: ++ opts["enable_rust"] = "false" ++ + if is_linux() and target_cpu != get_local_v8_target_cpu(): + run( + executable, diff --git a/patches/mini-racer/0.14.1/0008-builder-also-disable-v8_enable_temporal_support-on-r.patch b/patches/mini-racer/0.14.1/0008-builder-also-disable-v8_enable_temporal_support-on-r.patch new file mode 100644 index 0000000000..5997a03a55 --- /dev/null +++ b/patches/mini-racer/0.14.1/0008-builder-also-disable-v8_enable_temporal_support-on-r.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sun, 13 Sep 2026 09:04:26 +0200 +Subject: [PATCH] builder: also disable v8_enable_temporal_support on riscv64 + +enable_rust=false alone doesn't work: gn gen failed with + + ERROR at //build/rust/gni_impl/rust_target.gni:41:3: Assertion failed. + assert(enable_rust) + See //build/rust/rust_static_library.gni:222:3: whence it was called. + See //build/rust/cargo_crate.gni:252:5: whence it was called. + See //third_party/rust/temporal_capi/v0_1/BUILD.gn:11:1: whence it was called. + See //third_party/rust/temporal_capi/BUILD.gn:33:19: which caused the + file to be included. + +v8_enable_temporal_support (gni/v8.gni) unconditionally depends on +//third_party/rust/temporal_capi via the v8_maybe_temporal group in +BUILD.gn, and its own default expression already excludes ppc64 and +s390x by name, with the comment "some architectures don't have Rust +toolchains in Chromium" -- riscv64 is simply missing from that list. +Temporal is runtime-flag-gated even when built in (needs +--harmony-temporal per gni/v8.gni's own comment), so disabling it +drops no default-on behavior. + +Upstream-Status: To upstream [not yet submitted to bpcreech/PyMiniRacer] + +Signed-off-by: Ludovic Henry +--- + builder/v8_build.py | 17 +++++++++++------ + 1 file changed, 11 insertions(+), 6 deletions(-) + +diff --git a/builder/v8_build.py b/builder/v8_build.py +index c82341e..0cc9bb3 100644 +--- a/builder/v8_build.py ++++ b/builder/v8_build.py +@@ -326,12 +326,17 @@ def run_build(build_dir: Path, args: Args) -> None: + # libstdc++ instead, which is what a GCC build normally pairs with: + opts["use_custom_libcxx"] = "false" + +- # enable_rust defaults to build_with_chromium (false for our +- # standalone v8 checkout), but the prebuilt third_party/rust-toolchain +- # is, like Chromium's clang, published for amd64/arm64/mac hosts only; +- # something still invokes it for our riscv64 build and segfaults +- # trying to run it, so disable Rust explicitly rather than rely on a +- # default this pinned build/rust.gni revision may compute differently: ++ # v8_enable_temporal_support (gni/v8.gni) already excludes ppc64 and ++ # s390x by name, with the comment "some architectures don't have ++ # Rust toolchains in Chromium" -- riscv64 is simply missing from ++ # that list. It pulls in //third_party/rust/temporal_capi ++ # unconditionally when true (v8_maybe_temporal in BUILD.gn), and the ++ # prebuilt third_party/rust-toolchain (like Chromium's clang) isn't ++ # usable on riscv64 (it segfaulted: 'rustc --print=cfg ++ # --target=riscv64gc-unknown-linux-gnu' died with SIGSEGV). ++ # Temporal is runtime-flag-gated even when built in (needs ++ # --harmony-temporal), so this drops no default-on behavior: ++ opts["v8_enable_temporal_support"] = "false" + opts["enable_rust"] = "false" + + if is_linux() and target_cpu != get_local_v8_target_cpu(): diff --git a/patches/mini-racer/0.14.1/0009-builder-patch-a-GCC-Clang-source-incompatibility-in-.patch b/patches/mini-racer/0.14.1/0009-builder-patch-a-GCC-Clang-source-incompatibility-in-.patch new file mode 100644 index 0000000000..6c2dc281eb --- /dev/null +++ b/patches/mini-racer/0.14.1/0009-builder-patch-a-GCC-Clang-source-incompatibility-in-.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sun, 13 Sep 2026 10:39:42 +0200 +Subject: [PATCH] builder: patch a GCC/Clang source incompatibility in + unicode.h + +CI got past gn gen and almost an hour into the actual ninja build, +then failed compiling with GCC (is_clang=false, see above): + + ../../src/strings/unicode.h:218:13: error: explicit specialization + in non-namespace scope 'class unibrow::Utf8' + ../../src/strings/unicode.h:219:10: error: template-id + 'WriteLeadingAscii' in declaration of primary template + +unibrow::Utf8::WriteLeadingAscii's two explicit specializations are +declared a second time inside the class body in unicode.h; clang +accepts this as an extension, GCC correctly rejects it per the +standard (explicit specializations of a member template must be +declared at namespace scope). The out-of-line definitions in +unicode.cc are already namespace-scope ('namespace unibrow { template +<> size_t Utf8::WriteLeadingAscii(...) {...} }') and +standard-conformant on their own, so the in-class declarations are +both redundant and non-portable. Wire up the previously-unused +apply_patch() to drop them for a riscv64 (GCC) build. + +Upstream-Status: To upstream [not yet submitted to bpcreech/PyMiniRacer] + +Signed-off-by: Ludovic Henry +--- + ...iscv64-gcc-unicode-write-leading-ascii.patch | 17 +++++++++++++++++ + builder/v8_build.py | 10 ++++++++++ + 2 files changed, 27 insertions(+) + create mode 100644 builder/0001-riscv64-gcc-unicode-write-leading-ascii.patch + +diff --git a/builder/0001-riscv64-gcc-unicode-write-leading-ascii.patch b/builder/0001-riscv64-gcc-unicode-write-leading-ascii.patch +new file mode 100644 +index 0000000..6efd1ce +--- /dev/null ++++ b/builder/0001-riscv64-gcc-unicode-write-leading-ascii.patch +@@ -0,0 +1,17 @@ ++--- src/strings/unicode.h 2026-09-13 10:38:02 +++++ src/strings/unicode.h 2026-09-13 10:38:02 ++@@ -214,14 +214,6 @@ ++ ++ template ++ static size_t WriteLeadingAscii(const Char* src, char* dest, size_t size); ++- ++- template <> ++- size_t WriteLeadingAscii(const uint8_t* src, char* dest, ++- size_t size); ++- ++- template <> ++- size_t WriteLeadingAscii(const uint16_t* src, char* dest, ++- size_t size); ++ ++ // Encode the given characters as Utf8 into the provided output buffer. ++ struct EncodingResult { +diff --git a/builder/v8_build.py b/builder/v8_build.py +index 0cc9bb3..77cbb87 100644 +--- a/builder/v8_build.py ++++ b/builder/v8_build.py +@@ -393,6 +393,16 @@ def build_v8(args: Args) -> None: + if args.fetch_only: + return + ++ if machine().lower() == "riscv64": ++ # GCC (used instead of clang on riscv64, see is_clang=false above) ++ # rejects unibrow::Utf8::WriteLeadingAscii's explicit specializations ++ # declared inside the class body ("explicit specialization in ++ # non-namespace scope 'class unibrow::Utf8'"); clang accepts it as ++ # an extension. The out-of-line definitions in unicode.cc are ++ # already namespace-scope and standard-conformant on their own, so ++ # the in-class declarations are both redundant and non-portable: ++ apply_patch("0001-riscv64-gcc-unicode-write-leading-ascii.patch") ++ + build_dir = get_v8_path() / "out.gn" / "build" + + run_build(build_dir, args) diff --git a/patches/mini-racer/0.14.1/0010-builder-fold-in-a-second-GCC-compat-fix-keep-ninja-g.patch b/patches/mini-racer/0.14.1/0010-builder-fold-in-a-second-GCC-compat-fix-keep-ninja-g.patch new file mode 100644 index 0000000000..4303241138 --- /dev/null +++ b/patches/mini-racer/0.14.1/0010-builder-fold-in-a-second-GCC-compat-fix-keep-ninja-g.patch @@ -0,0 +1,144 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sun, 13 Sep 2026 13:54:30 +0200 +Subject: [PATCH] builder: fold in a second GCC compat fix, keep ninja going on + riscv64 + +CI got past the unicode.h fix and ~46% (1011/2176) into the ninja +build before hitting another GCC-only warning-as-error: + + ../../src/bigint/mul-karatsuba.cc:51:38: error: comparison of + integer expressions of different signedness: 'uint32_t' and 'int' + [-Werror=sign-compare] + if (shift >= 2 && (len & additive) < (1 << (shift - 2))) { + +'1 << (shift - 2)' is int, 'len & additive' is uint32_t; clang doesn't +flag this particular comparison, GCC does. Cast it explicitly, the +same way the line above already assigns '1 << shift' into a uint32_t. + +Rename the accumulating patch to 0001-riscv64-gcc-compat.patch (was +scoped to the unicode.h fix alone) since this is evidently a class of +issue, not a one-off, and pass ninja -k 1000 on riscv64 so one build +surfaces as many of these as it can find, instead of one discovery per +multi-hour CI cycle. + +Upstream-Status: To upstream [not yet submitted to bpcreech/PyMiniRacer] + +Signed-off-by: Ludovic Henry +--- + builder/0001-riscv64-gcc-compat.patch | 28 ++++++++++++++++++ + ...cv64-gcc-unicode-write-leading-ascii.patch | 17 ----------- + builder/v8_build.py | 29 ++++++++++++++----- + 3 files changed, 49 insertions(+), 25 deletions(-) + create mode 100644 builder/0001-riscv64-gcc-compat.patch + delete mode 100644 builder/0001-riscv64-gcc-unicode-write-leading-ascii.patch + +diff --git a/builder/0001-riscv64-gcc-compat.patch b/builder/0001-riscv64-gcc-compat.patch +new file mode 100644 +index 0000000..ee8eb3a +--- /dev/null ++++ b/builder/0001-riscv64-gcc-compat.patch +@@ -0,0 +1,28 @@ ++--- src/bigint/mul-karatsuba.cc 2026-09-13 13:52:35 +++++ src/bigint/mul-karatsuba.cc 2026-09-13 13:52:35 ++@@ -48,7 +48,7 @@ ++ // Round up, unless we're only just above the threshold. This smoothes ++ // the steps by which time goes up as input size increases. ++ uint32_t additive = ((1 << shift) - 1); ++- if (shift >= 2 && (len & additive) < (1 << (shift - 2))) { +++ if (shift >= 2 && (len & additive) < static_cast(1 << (shift - 2))) { ++ return len; ++ } ++ return ((len + additive) >> shift) << shift; ++--- src/strings/unicode.h 2026-09-13 13:52:35 +++++ src/strings/unicode.h 2026-09-13 13:52:35 ++@@ -215,14 +215,6 @@ ++ template ++ static size_t WriteLeadingAscii(const Char* src, char* dest, size_t size); ++ ++- template <> ++- size_t WriteLeadingAscii(const uint8_t* src, char* dest, ++- size_t size); ++- ++- template <> ++- size_t WriteLeadingAscii(const uint16_t* src, char* dest, ++- size_t size); ++- ++ // Encode the given characters as Utf8 into the provided output buffer. ++ struct EncodingResult { ++ size_t bytes_written; +diff --git a/builder/0001-riscv64-gcc-unicode-write-leading-ascii.patch b/builder/0001-riscv64-gcc-unicode-write-leading-ascii.patch +deleted file mode 100644 +index 6efd1ce..0000000 +--- a/builder/0001-riscv64-gcc-unicode-write-leading-ascii.patch ++++ /dev/null +@@ -1,17 +0,0 @@ +---- src/strings/unicode.h 2026-09-13 10:38:02 +-+++ src/strings/unicode.h 2026-09-13 10:38:02 +-@@ -214,14 +214,6 @@ +- +- template +- static size_t WriteLeadingAscii(const Char* src, char* dest, size_t size); +-- +-- template <> +-- size_t WriteLeadingAscii(const uint8_t* src, char* dest, +-- size_t size); +-- +-- template <> +-- size_t WriteLeadingAscii(const uint16_t* src, char* dest, +-- size_t size); +- +- // Encode the given characters as Utf8 into the provided output buffer. +- struct EncodingResult { +diff --git a/builder/v8_build.py b/builder/v8_build.py +index 77cbb87..a5457e7 100644 +--- a/builder/v8_build.py ++++ b/builder/v8_build.py +@@ -361,12 +361,23 @@ def run_build(build_dir: Path, args: Args) -> None: + ) + + # Finally, actually do the build: +- run( ++ ninja_args = [ + executable, + str(get_depot_tools_path() / "ninja.py"), + # "-vv", # too much spam for GitHub Actions + "-C", + str(build_dir), ++ ] ++ if target_cpu == "riscv64": ++ # This is the first time this codebase has been compiled with GCC ++ # for this target in anger, so there is a long tail of small ++ # GCC-vs-clang source incompatibilities to find (see apply_patch() ++ # calls above). Keep going past failures so one build surfaces as ++ # many of them as possible, instead of one per multi-hour CI cycle: ++ ninja_args += ["-k", "1000"] ++ ++ run( ++ *ninja_args, + str(Path("custom_deps") / "mini_racer"), + cwd=get_v8_path(), + ) +@@ -395,13 +406,15 @@ def build_v8(args: Args) -> None: + + if machine().lower() == "riscv64": + # GCC (used instead of clang on riscv64, see is_clang=false above) +- # rejects unibrow::Utf8::WriteLeadingAscii's explicit specializations +- # declared inside the class body ("explicit specialization in +- # non-namespace scope 'class unibrow::Utf8'"); clang accepts it as +- # an extension. The out-of-line definitions in unicode.cc are +- # already namespace-scope and standard-conformant on their own, so +- # the in-class declarations are both redundant and non-portable: +- apply_patch("0001-riscv64-gcc-unicode-write-leading-ascii.patch") ++ # is stricter than clang about a few things in v8's C++, e.g. an ++ # explicit specialization declared inside a class body ++ # (unibrow::Utf8::WriteLeadingAscii in unicode.h -- the out-of-line ++ # definitions in unicode.cc are already namespace-scope and ++ # standard-conformant on their own) and a signed/unsigned ++ # comparison under -Werror=sign-compare (bigint/mul-karatsuba.cc). ++ # Both are narrow, mechanical fixes; accumulate any more of this ++ # kind we hit in the same patch: ++ apply_patch("0001-riscv64-gcc-compat.patch") + + build_dir = get_v8_path() / "out.gn" / "build" + diff --git a/patches/mini-racer/0.14.1/0011-builder-stop-treating-GCC-only-warnings-as-errors-pa.patch b/patches/mini-racer/0.14.1/0011-builder-stop-treating-GCC-only-warnings-as-errors-pa.patch new file mode 100644 index 0000000000..c974ea5405 --- /dev/null +++ b/patches/mini-racer/0.14.1/0011-builder-stop-treating-GCC-only-warnings-as-errors-pa.patch @@ -0,0 +1,181 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Sat, 19 Sep 2026 10:00:00 +0200 +Subject: [PATCH] builder: stop treating GCC-only warnings as errors; patch + highway's EMU128 opt-out + +The -k 1000 build did its job: one 24h run collected every remaining +failure instead of one per cycle. There were six, and they split into +two kinds. + +Five are pure -Werror diagnostics GCC emits and clang does not: +-Wuninitialized (53 of them in wasm/baseline/liftoff-compiler.cc), +-Wdangling-reference (compiler/js-call-reducer.cc, +compiler/js-typed-lowering.cc -- both false positives: the reference +is to data owned by the node's operator, not by the temporary node +wrapper), -Wsign-compare (maglev/maglev-graph-builder.cc) and +-Wparentheses (heap/collection-barrier.cc). Chasing these one cast at +a time is unbounded work at a day per discovery, and V8 never promised +to be warning-clean under GCC: config("chromium_code") in +build/config/compiler/BUILD.gn adds -Werror unconditionally, while +config("no_chromium_code") pointedly restricts it to clang because +"GCC may emit unsuppressible warnings" (crbug.com/589724). Set the +declared treat_warnings_as_errors=false gn arg, whose own comment says +it exists to be overridden "for Chromium builds on Linux that could +use a different version of the compiler", and drop the mul-karatsuba +cast that only existed to silence one of these. + +The sixth is a real compile error, and not in V8's own code: +src/json/json-stringifier.cc's AppendStringSIMD asks +third_party/highway for a hw::FixedTag, but highway +resolved to HWY_SCALAR, whose tags hold one lane ("static assertion +failed: Too many lanes"). The cause is highway's EMU128 opt-out, which +at the revision V8 pins (84379d1c) disables EMU128 for every GCC older +than 16.0 -- ours is 14.3.1 -- leaving HWY_SCALAR as the fallback. +That is wrong on RISC-V specifically: with no V extension in the +baseline, EMU128 is the only fallback that can provide fixed-width +tags at all. Highway has since fixed this upstream, carving RISC-V out +with a GCC 14 floor and naming V8's JSON stringifier as the breakage; +backport that carve-out. + +Also drop ninja -k 1000, which has now served its purpose: with the +warning class gone, keeping the build going past a genuine error only +converts a fast failure into a 24h timeout. + +https://github.com/riseproject-dev/python-wheels/actions/runs/34755707688/job/103719604859 + +Upstream-Status: To upstream [not yet submitted to bpcreech/PyMiniRacer] + +Signed-off-by: Ludovic Henry +--- + builder/0001-riscv64-gcc-compat.patch | 34 ++++++++++++++--------- + builder/v8_build.py | 51 ++++++++++++++++++++--------------- + 2 files changed, 51 insertions(+), 34 deletions(-) + +diff --git a/builder/0001-riscv64-gcc-compat.patch b/builder/0001-riscv64-gcc-compat.patch +index ee8eb3a..ee00c0f 100644 +--- a/builder/0001-riscv64-gcc-compat.patch ++++ b/builder/0001-riscv64-gcc-compat.patch +@@ -1,16 +1,5 @@ +---- src/bigint/mul-karatsuba.cc 2026-09-13 13:52:35 +-+++ src/bigint/mul-karatsuba.cc 2026-09-13 13:52:35 +-@@ -48,7 +48,7 @@ +- // Round up, unless we're only just above the threshold. This smoothes +- // the steps by which time goes up as input size increases. +- uint32_t additive = ((1 << shift) - 1); +-- if (shift >= 2 && (len & additive) < (1 << (shift - 2))) { +-+ if (shift >= 2 && (len & additive) < static_cast(1 << (shift - 2))) { +- return len; +- } +- return ((len + additive) >> shift) << shift; +---- src/strings/unicode.h 2026-09-13 13:52:35 +-+++ src/strings/unicode.h 2026-09-13 13:52:35 ++--- src/strings/unicode.h 2026-09-19 00:00:00 +++++ src/strings/unicode.h 2026-09-19 00:00:00 + @@ -215,14 +215,6 @@ + template + static size_t WriteLeadingAscii(const Char* src, char* dest, size_t size); +@@ -26,3 +15,22 @@ + // Encode the given characters as Utf8 into the provided output buffer. + struct EncodingResult { + size_t bytes_written; ++--- third_party/highway/src/hwy/detect_targets.h 2026-09-19 00:00:00 +++++ third_party/highway/src/hwy/detect_targets.h 2026-09-19 00:00:00 ++@@ -393,8 +393,15 @@ ++ // remains with 13.2, see #1683. This is separate from HWY_BROKEN_TARGETS ++ // because it affects the fallback target, which must always be enabled. If 1, ++ // we instead choose HWY_SCALAR even without HWY_COMPILE_ONLY_SCALAR being set. +++// RISC-V has no native SIMD baseline unless the V extension is enabled, so +++// there EMU128 is the only fallback able to provide fixed-width tags: +++// substituting HWY_SCALAR does not merely lose speed, it makes +++// FixedTag with N > 1 fail to compile, which breaks downstream users +++// such as V8's JSON stringifier. Keep the blanket GCC opt-out, but do not +++// extend it past GCC 14 on RISC-V, where the bug has not been observed. ++ #if !defined(HWY_BROKEN_EMU128) // allow overriding ++-#if (HWY_COMPILER_GCC_ACTUAL && HWY_COMPILER_GCC_ACTUAL < 1600) || \ +++#if (HWY_COMPILER_GCC_ACTUAL && \ +++ HWY_COMPILER_GCC_ACTUAL < (HWY_ARCH_RISCV ? 1400 : 1600)) || \ ++ defined(HWY_NO_LIBCXX) ++ #define HWY_BROKEN_EMU128 1 ++ #else +diff --git a/builder/v8_build.py b/builder/v8_build.py +index a5457e7..b7a5c0d 100644 +--- a/builder/v8_build.py ++++ b/builder/v8_build.py +@@ -339,6 +339,20 @@ def run_build(build_dir: Path, args: Args) -> None: + opts["v8_enable_temporal_support"] = "false" + opts["enable_rust"] = "false" + ++ # V8 is only warning-clean under clang, which is what it gates its ++ # -Werror on everywhere but here: config("chromium_code") in ++ # build/config/compiler/BUILD.gn adds -Werror for any compiler, while ++ # config("no_chromium_code") deliberately restricts it to clang ++ # ("GCC may emit unsuppressible warnings", crbug.com/589724). GCC ++ # flags a long tail of diagnostics clang does not (-Wuninitialized, ++ # -Wdangling-reference, -Wsign-compare, -Wparentheses in five ++ # different translation units so far, all of them benign or outright ++ # false positives), and each one costs a day of CI to discover. The ++ # declared gn arg exists for exactly this case -- "allow overriding ++ # this e.g. for Chromium builds on Linux that could use a different ++ # version of the compiler" (build/config/compiler/compiler.gni): ++ opts["treat_warnings_as_errors"] = "false" ++ + if is_linux() and target_cpu != get_local_v8_target_cpu(): + run( + executable, +@@ -361,23 +375,12 @@ def run_build(build_dir: Path, args: Args) -> None: + ) + + # Finally, actually do the build: +- ninja_args = [ ++ run( + executable, + str(get_depot_tools_path() / "ninja.py"), + # "-vv", # too much spam for GitHub Actions + "-C", + str(build_dir), +- ] +- if target_cpu == "riscv64": +- # This is the first time this codebase has been compiled with GCC +- # for this target in anger, so there is a long tail of small +- # GCC-vs-clang source incompatibilities to find (see apply_patch() +- # calls above). Keep going past failures so one build surfaces as +- # many of them as possible, instead of one per multi-hour CI cycle: +- ninja_args += ["-k", "1000"] +- +- run( +- *ninja_args, + str(Path("custom_deps") / "mini_racer"), + cwd=get_v8_path(), + ) +@@ -405,15 +408,21 @@ def build_v8(args: Args) -> None: + return + + if machine().lower() == "riscv64": +- # GCC (used instead of clang on riscv64, see is_clang=false above) +- # is stricter than clang about a few things in v8's C++, e.g. an +- # explicit specialization declared inside a class body +- # (unibrow::Utf8::WriteLeadingAscii in unicode.h -- the out-of-line +- # definitions in unicode.cc are already namespace-scope and +- # standard-conformant on their own) and a signed/unsigned +- # comparison under -Werror=sign-compare (bigint/mul-karatsuba.cc). +- # Both are narrow, mechanical fixes; accumulate any more of this +- # kind we hit in the same patch: ++ # Two things GCC (used instead of clang on riscv64, see is_clang=false ++ # above) genuinely cannot compile, as opposed to merely warns about ++ # (those are handled by treat_warnings_as_errors=false): ++ # ++ # * unibrow::Utf8::WriteLeadingAscii's explicit specializations are ++ # declared a second time inside the class body in src/strings/ ++ # unicode.h; clang takes that as an extension, GCC rejects it ++ # ("explicit specialization in non-namespace scope"). The ++ # out-of-line definitions in unicode.cc are already namespace-scope ++ # and standard-conformant on their own. ++ # ++ # * third_party/highway resolves to HWY_SCALAR here, and ++ # src/json/json-stringifier.cc's AppendStringSIMD asks it for a ++ # hw::FixedTag, which the scalar target cannot provide ++ # ("static assertion failed: Too many lanes"). + apply_patch("0001-riscv64-gcc-compat.patch") + + build_dir = get_v8_path() / "out.gn" / "build"