From 6dac1664bd7250bd3b02ff8faa50d09a36209155 Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Sat, 20 Dec 2025 14:36:59 -0500 Subject: [PATCH] Fix windows and alpine aarch64 --- .github/workflows/build.yml | 40 ++++++++++++++++++++++++---------- ARCHITECTURE.md | 4 ++-- Justfile | 12 +++++----- builder/v8_build.py | 37 ++++++++++++++++--------------- src/py_mini_racer/__about__.py | 2 +- src/py_mini_racer/_dll.py | 7 +++--- 6 files changed, 62 insertions(+), 40 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index feddc1b7..ce6dedcb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,6 +61,9 @@ jobs: - name: macos-aarch64 os: macos-15 target_cpu: arm64 + - name: linux-aarch64-musl-compat + os: ubuntu-24.04 + target_cpu: arm64 steps: - uses: extractions/setup-just@v3 @@ -86,6 +89,11 @@ jobs: - name: Build dll with v8 run: just build-dll "--out-path=_dll" "--target-cpu=${{ matrix.config.target_cpu }}" + if: matrix.config.name != 'linux-aarch64-musl-compat' + + - name: Build dll with v8 + run: just build-dll "--out-path=_dll" "--target-cpu=${{ matrix.config.target_cpu }}" --aarch64-musl-compat + if: matrix.config.name == 'linux-aarch64-musl-compat' - uses: actions/upload-artifact@v4 with: @@ -134,10 +142,28 @@ jobs: path: src/py_mini_racer - run: just build-wheel + if: matrix.config.os != 'windows-11-arm' + + # By default, uv still uses the x86_64 python in emulation on Windows. + # https://github.com/astral-sh/uv/issues/12906 + # Override this behavior to build an actual aarch64 wheel: + - run: UV_PYTHON=arm64 just build-wheel + if: matrix.config.os == 'windows-11-arm' - name: Test wheel + if: matrix.config.os != 'windows-11-arm' run: just test-matrix + # As above; uv on Windows aarch64 likes to do emulation by default + # (and uv has no 3.10 Python build): + - name: Test wheel + if: matrix.config.os == 'windows-11-arm' + run: | + uv run --python cpython-3.11-windows-aarch64-none pytest tests + uv run --python cpython-3.12-windows-aarch64-none pytest tests + uv run --python cpython-3.13-windows-aarch64-none pytest tests + uv run --python cpython-3.14-windows-aarch64-none pytest tests + - uses: actions/upload-artifact@v4 with: name: wheels-${{ matrix.config.name }} @@ -157,7 +183,7 @@ jobs: dll: linux-x64_64 - name: alpine-aarch64 image: ubuntu-22.04-arm - dll: linux-aarch64 + dll: linux-aarch64-musl-compat container: image: alpine @@ -182,11 +208,7 @@ jobs: shell: /bin/sh {0} run: | apk update - apk add bash curl gcompat libgcc - - curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=/usr/local/bin sh - - curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin + apk add bash curl gcompat libgcc uv just - uses: actions/checkout@v3 with: @@ -213,15 +235,11 @@ jobs: # confuses uv into thinking we're on a glibc Linux and thus it rejects our # musl wheel. uv sync --python 3.10 - LD_PRELOAD="/lib/libgcompat.so.0" uv run --frozen --python 3.10 pytest tests uv sync --python 3.11 - LD_PRELOAD="/lib/libgcompat.so.0" uv run --frozen --python 3.11 pytest tests uv sync --python 3.12 - LD_PRELOAD="/lib/libgcompat.so.0" uv run --frozen --python 3.12 pytest tests uv sync --python 3.13 - LD_PRELOAD="/lib/libgcompat.so.0" uv run --frozen --python 3.13 pytest tests uv sync --python 3.14 - LD_PRELOAD="/lib/libgcompat.so.0" uv run --frozen --python 3.14 pytest tests + LD_PRELOAD="/lib/libgcompat.so.0" just test-matrix --frozen release: name: Create GitHub release diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ea93f303..fcb05209 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -261,8 +261,8 @@ Alpine without extensive patching (or just dispensing with the V8 build system entirely!). So instead we build a on Ubuntu with glibc and then put that into a wheel on Alpine. To -use the resulting wheel, you must `apk add gcompat` and add the environment variable -`LD_PRELOAD="/lib/libgcompat.so.0"`. +use the resulting wheel, you must `apk add gcompat libgcc` and add the environment +variable `LD_PRELOAD="/lib/libgcompat.so.0"`. ### Build V8 _with_ our frontend (`v8_py_frontend`) as a snuck-in component diff --git a/Justfile b/Justfile index b67ff8a5..f9ced569 100644 --- a/Justfile +++ b/Justfile @@ -69,12 +69,12 @@ build: build-dll build-wheel test: uv run pytest tests -test-matrix: - uv run --python 3.10 pytest tests - uv run --python 3.11 pytest tests - uv run --python 3.12 pytest tests - uv run --python 3.13 pytest tests - uv run --python 3.14 pytest tests +test-matrix *args: + uv run --python 3.10 {{args}} pytest tests + uv run --python 3.11 {{args}} pytest tests + uv run --python 3.12 {{args}} pytest tests + uv run --python 3.13 {{args}} pytest tests + uv run --python 3.14 {{args}} pytest tests git-config-global: git config --global core.symlinks true diff --git a/builder/v8_build.py b/builder/v8_build.py index b8076844..521e92d3 100644 --- a/builder/v8_build.py +++ b/builder/v8_build.py @@ -108,16 +108,14 @@ def run(*args: str, cwd: Path) -> None: env = environ.copy() env["PATH"] = pathsep.join([str(get_depot_tools_path()), environ["PATH"]]) + + # The v8 build tries to download its own toolchain for Windows... just use the local + # one: env["DEPOT_TOOLS_WIN_TOOLCHAIN"] = "0" # vpython is V8's Python environment manager; it downloads Python binaries - # dynamically. This doesn't work on Alpine (because it downloads a glibc binary, - # but needs a musl binary), so let's just disable it on all environments: + # dynamically. Let's just use uv's: env["VPYTHON_BYPASS"] = "manually managed python not supported by chrome operations" - # Goma is a remote build system which we aren't using. depot_tools/autoninja.py - # tries to run the goma client, which is checked into depot_tools as a glibc binary. - # This fails on musl (on Alpine), so let's just disable the thing: - env["GOMA_DISABLED"] = "1" check_call(args, env=env, cwd=cwd) @@ -137,7 +135,7 @@ def ensure_depot_tools() -> None: ) # depot_tools will auto-update when we run various commands. This creates extra - # dependencies, e.g., on goma (which has trouble running on Alpine due to musl). + # dependencies. # We just created a fresh single-use depot_tools checkout. There is no reason to # update it, so let's just disable that functionality: (get_depot_tools_path() / ".disable_auto_update").write_text("") @@ -244,6 +242,13 @@ def run_build(build_dir: Path, args: Args) -> None: "v8_custom_deps": '"//custom_deps/mini_racer"', } + if args.aarch64_musl_compat: + # Avoid "Error relocating /PyMiniRacer/src/py_mini_racer/libmini_racer.so: + # unsupported relocation type 1032" + # when running ubuntu-built aarch64 shared objects on Alpine: + # https://gitlab.alpinelinux.org/alpine/aports/-/issues/16210 + opts["v8_enable_partition_alloc"] = "false" + if is_linux() and target_cpu != get_local_v8_target_cpu(): run( executable, @@ -252,16 +257,7 @@ def run_build(build_dir: Path, args: Args) -> None: cwd=get_v8_path(), ) - args_text = "\n".join(f"{n}={v}" for n, v in opts.items()) - args_gn = f"""\ -# This file is auto-generated by v8_build.py -{args_text} -""" - - LOGGER.info(f"Writing args.gn:\n{args_gn}") # noqa: G004 - - build_dir.mkdir(parents=True, exist_ok=True) - Path(build_dir / "args.gn").write_text(args_gn, encoding="utf-8") + args_text = " ".join(f"{n}={v}" for n, v in opts.items()) # Now generate Ninja build files: run( @@ -270,6 +266,7 @@ def run_build(build_dir: Path, args: Args) -> None: "gen", str(build_dir), "--check", + f"--args={args_text}", cwd=get_v8_path(), ) @@ -292,6 +289,7 @@ class Args: v8_revision: str fetch_only: bool skip_fetch: bool + aarch64_musl_compat: bool def build_v8(args: Args) -> None: @@ -342,5 +340,10 @@ def clean_v8(out_path: Path) -> None: parser.add_argument("--v8-revision", default=V8_VERSION) parser.add_argument("--fetch-only", action="store_true", help="Only fetch V8") parser.add_argument("--skip-fetch", action="store_true", help="Do not fetch V8") + parser.add_argument( + "--aarch64-musl-compat", + action="store_true", + help="Make a compat build for aarch64 musl", + ) args = parser.parse_args() build_v8(Args(**vars(args))) diff --git a/src/py_mini_racer/__about__.py b/src/py_mini_racer/__about__.py index 8cd53a66..1b8428c0 100644 --- a/src/py_mini_racer/__about__.py +++ b/src/py_mini_racer/__about__.py @@ -2,4 +2,4 @@ __author__ = "bpcreech" __email__ = "mini-racer@bpcreech.com" -__version__ = "0.12.4" +__version__ = "0.13.0" diff --git a/src/py_mini_racer/_dll.py b/src/py_mini_racer/_dll.py index a16f19b3..53e37bdb 100644 --- a/src/py_mini_racer/_dll.py +++ b/src/py_mini_racer/_dll.py @@ -228,10 +228,11 @@ def _open_dll(flags: Iterable[str]) -> Iterator[ctypes.CDLL]: # Find the dll and its external dependency files: meipass = getattr(sys, "_MEIPASS", None) if meipass is not None: - # We are running under PyInstaller + # We are running under PyInstaller. + # See https://github.com/bpcreech/PyMiniRacer/issues/78 meipass_path = Path(meipass) - dll_path = meipass_path / dll_filename - icu_data_path = meipass_path / _ICU_DATA_FILENAME + dll_path = meipass_path / "py_mini_racer" / dll_filename + icu_data_path = meipass_path / "py_mini_racer" / _ICU_DATA_FILENAME else: dll_path = _open_resource_file(dll_filename, exit_stack) icu_data_path = _open_resource_file(_ICU_DATA_FILENAME, exit_stack)