diff --git a/linux/rocknix/README.md b/linux/rocknix/README.md index 81cd763..79bd28b 100644 --- a/linux/rocknix/README.md +++ b/linux/rocknix/README.md @@ -143,9 +143,9 @@ Confirmed on-device (ROCKNIX `next`, `OS_NAME="ROCKNIX"` in `/etc/os-release`): ### pygame -ROCKNIX ships Python 3.13 but **no `pip` and no `pygame`**. The bundle vendors a self-contained -`pygame-ce` (Community Edition) 2.5.7 cp313 aarch64 manylinux wheel (its own SDL 2.32.10 in -`pygame_ce.libs`, built with both the wayland and kmsdrm video drivers — no reliance on system +ROCKNIX ships a system Python but **no `pip` and no `pygame`**. The bundle vendors a +self-contained `pygame-ce` (Community Edition) 2.5.7 aarch64 manylinux wheel (its own SDL 2.32.10 +in `pygame_ce.libs`, built with both the wayland and kmsdrm video drivers — no reliance on system SDL). The package still imports as `pygame` (pygame-ce is API-compatible). The vanilla `pygame` 2.6.1 wheel used before segfaulted inside SDL's native wayland/EGL init on @@ -154,20 +154,28 @@ RK3326 devices with the `libmali` GPU blob (issue #55, and an earlier Discord re device class run fine using `pygame-ce`/`pygame_sdl2` builds instead of the vanilla wheel, so we switched the vendored build rather than adding more launch-script workarounds. +Which CPython the wheel has to match is not fixed: stable ROCKNIX ships 3.13, nightlies since +20260822 ship 3.14, and a bundle carrying only one of them dies at startup on the other with +`No module named 'pygame.base'` (issue #127), because the `.so` files are ABI-tagged and an +interpreter never sees extension modules built for another minor version. The vendor directory +therefore holds **every supported ABI at once**: `base.cpython-313-aarch64-linux-gnu.so` and +`base.cpython-314-aarch64-linux-gnu.so` sit side by side and each interpreter picks its own. The +wheels' pure-Python files and their `pygame_ce.libs` payload are byte-identical across ABIs of the +same release, so only the extra `.so` files add to the installer. + The vendored payload is **not** checked in (see `.gitignore`). Populate it before building: ```bash -# downloads a prebuilt cp313 aarch64 manylinux pygame-ce wheel, no build toolchain needed -cd linux/rocknix/vendor -pip download pygame-ce --platform manylinux_2_17_aarch64 --python-version 313 \ - --implementation cp --abi cp313 --only-binary=:all: --no-deps -d . -unzip pygame_ce-*-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl -# leaves vendor/pygame and vendor/pygame_ce.libs in place +./linux/rocknix/fetch_vendor.sh ``` -> Note: the vendored `.so` files are cp313-specific, and the `pygame_ce.libs` directory name is -> baked into each `.so`'s RPATH (`$ORIGIN/../pygame_ce.libs`) — don't rename it. If ROCKNIX moves -> to a different Python minor version, re-vendor a matching wheel. +That downloads the prebuilt cp313 and cp314 wheels (no build toolchain needed) and merges them +into `vendor/pygame` + `vendor/pygame_ce.libs`. To support a further Python version, add it to +`PY_VERSIONS` in `fetch_vendor.sh` and `REQUIRED_PY_VERSIONS` in `build_bundle.sh`; the build +refuses to run if a required ABI is missing from the vendor directory. + +> Note: the `pygame_ce.libs` directory name is baked into each `.so`'s RPATH +> (`$ORIGIN/../pygame_ce.libs`), so don't rename it. ## Build diff --git a/linux/rocknix/build_bundle.sh b/linux/rocknix/build_bundle.sh index 04f960c..846d5a9 100755 --- a/linux/rocknix/build_bundle.sh +++ b/linux/rocknix/build_bundle.sh @@ -11,12 +11,24 @@ VERSION="${RAOFFLINEPROXY_APP_VERSION:-1.11.1-alpha1}" INSTALLER_PATH="${DIST_DIR}/RAOfflineProxy-Rocknix-v${VERSION}-Install.sh" TEMP_TARBALL="${DIST_DIR}/.raofflineproxy-rocknix-bundle.tar.gz" +# ROCKNIX builds ship different CPython minor versions (3.13 through nightly's +# 3.14), and pygame's extension modules only import into the interpreter whose +# ABI tag they carry, so the vendored pygame must cover every one of them. +REQUIRED_PY_VERSIONS=(313 314) + if [ ! -d "${SCRIPT_DIR}/vendor/pygame" ] || [ ! -d "${SCRIPT_DIR}/vendor/pygame_ce.libs" ]; then - echo "Missing vendored pygame-ce. Populate linux/rocknix/vendor/{pygame,pygame_ce.libs}" >&2 - echo "from a pygame-ce cp313 aarch64 manylinux wheel before building." >&2 + echo "Missing vendored pygame-ce. Run linux/rocknix/fetch_vendor.sh before building." >&2 exit 1 fi +for py_version in "${REQUIRED_PY_VERSIONS[@]}"; do + if [ ! -f "${SCRIPT_DIR}/vendor/pygame/base.cpython-${py_version}-aarch64-linux-gnu.so" ]; then + echo "Vendored pygame-ce has no cpython-${py_version} extension modules." >&2 + echo "Re-run linux/rocknix/fetch_vendor.sh before building." >&2 + exit 1 + fi +done + TARGET="aarch64-linux-gnu.2.17" OUT_DIR="${SCRIPT_DIR}/native" \ "${LINUX_DIR}/build_rchash.sh" diff --git a/linux/rocknix/fetch_vendor.sh b/linux/rocknix/fetch_vendor.sh new file mode 100755 index 0000000..50c0f83 --- /dev/null +++ b/linux/rocknix/fetch_vendor.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Downloads the ROCKNIX pygame vendor directory: prebuilt pygame-ce aarch64 +# manylinux wheels, one per CPython ABI a ROCKNIX build may ship. +# +# ROCKNIX has no pip and no pygame, so the bundle carries its own. The wheels' +# extension modules are ABI-tagged (base.cpython-313-... vs +# base.cpython-314-...), so several ABIs live side by side in one pygame +# directory: each interpreter only imports the .so carrying its own tag. The +# pure-Python files and the pygame_ce.libs payload are byte-identical across +# ABIs of the same release, so the merge costs only the extra .so files. +# +# Usage: +# linux/rocknix/fetch_vendor.sh + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +VENDOR_DIR="${SCRIPT_DIR}/vendor" +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "${TMP_DIR}"' EXIT + +PYGAME_CE_VERSION="2.5.7" +# Keep in sync with build_bundle.sh's REQUIRED_PY_VERSIONS. +PY_VERSIONS=(313 314) + +rm -rf "${VENDOR_DIR}/pygame" "${VENDOR_DIR}/pygame_ce.libs" +mkdir -p "${VENDOR_DIR}" + +for py_version in "${PY_VERSIONS[@]}"; do + abi="cp${py_version}" + echo "Downloading pygame-ce ${PYGAME_CE_VERSION} ${abi} aarch64..." + python3 -m pip download "pygame-ce==${PYGAME_CE_VERSION}" \ + --platform manylinux_2_17_aarch64 --python-version "${py_version}" \ + --implementation cp --abi "${abi}" --only-binary=:all: --no-deps \ + -d "${TMP_DIR}" >/dev/null + + # Each .so's RPATH is $ORIGIN/../pygame_ce.libs, so both directories have to + # land next to each other under vendor/ under those exact names. + unzip -oq "${TMP_DIR}/pygame_ce-${PYGAME_CE_VERSION}-${abi}-${abi}-"*aarch64.whl \ + "pygame/*" "pygame_ce.libs/*" -d "${VENDOR_DIR}" +done + +find "${VENDOR_DIR}/pygame" -name "__pycache__" -type d -prune -exec rm -rf {} + + +echo "" +for py_version in "${PY_VERSIONS[@]}"; do + count="$(find "${VENDOR_DIR}/pygame" -name "*.cpython-${py_version}-aarch64-linux-gnu.so" | wc -l | tr -d ' ')" + echo "vendor/pygame contains ${count} cpython-${py_version} extension modules." +done