diff --git a/.github/workflows/build-skia-python.yml b/.github/workflows/build-skia-python.yml new file mode 100644 index 000000000..1cc7e4d2c --- /dev/null +++ b/.github/workflows/build-skia-python.yml @@ -0,0 +1,149 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on the `build_wheels` job of: +# https://github.com/kyamagu/skia-python/blob/v144.0.post2/.github/workflows/ci.yml +name: Build skia-python wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'skia-python version to build (git tag without leading v, e.g. 144.0.post2)' + required: true + default: '144.0.post2' + pull_request: + paths: + - '.github/workflows/build-skia-python.yml' + - 'patches/skia-python/**' + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || '144.0.post2' }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + # `inputs.version` is empty on pull_request events; default to 144.0.post2 there. + SKIA_PYTHON_VERSION: ${{ inputs.version || '144.0.post2' }} + GN_REVISION: 0625da07ece7b1a374ec0719b6f40ebe3d0950c1 + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + + build_wheels: + needs: [setup] + name: Build skia-python ${{ inputs.version || '144.0.post2' }} cp312/cp313/cp314/cp314t-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 1440 # full Skia (Chromium's 2D graphics engine) compile from source + + steps: + - name: Checkout skia-python v${{ env.SKIA_PYTHON_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: kyamagu/skia-python + ref: v${{ env.SKIA_PYTHON_VERSION }} + submodules: true + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Patch skia-python source + run: git apply python-wheels/patches/skia-python/${{ env.SKIA_PYTHON_VERSION }}/*.patch + + - name: Build wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + output-dir: wheelhouse/ + env: + CIBW_ARCHS: riscv64 + CIBW_BUILD: cp312-manylinux_riscv64 cp313-manylinux_riscv64 cp314-manylinux_riscv64 cp314t-manylinux_riscv64 + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + CIBW_SKIP: "*musllinux*" + # Reimplements scripts/build_Linux.sh: its `yum install ... ninja-build gn` + # comes from EPEL, which the riscv64 manylinux image doesn't carry (gotcha + # 51), so ninja comes from its own riscv64 PyPI wheel and gn is bootstrapped + # from source (gn.googlesource.com/gn's build/gen.py has no arch-specific + # logic). Everything after that mirrors the upstream script unchanged. + CIBW_BEFORE_ALL_LINUX: | + set -e + dnf install -y --enablerepo=crb \ + fontconfig-devel mesa-libGL-devel mesa-libEGL-devel libglvnd-devel \ + mesa-libGLES-devel libglvnd mesa-libGLES mesa-libEGL libglvnd-egl \ + mesa-dri-drivers expat-devel libxml2-devel libstdc++-static + python3 -m pip install ninja + # Full clone, not --depth 1: build/gen.py's GenerateLastCommitPosition + # runs `git describe --match initial-commit` unconditionally whenever + # .git exists, which needs the full history back to that root tag. + git clone https://gn.googlesource.com/gn /tmp/gn + git -C /tmp/gn checkout ${{ env.GN_REVISION }} + # build/gen.py defaults CXX to clang++ on any non-MSVC/AIX/Windows + # host when CXX isn't set; this image has no clang, only GCC. + # --allow-warnings: gn's own source only gets clang -Werror + # coverage upstream; GCC's -Wcomment fires on an ASCII-art + # diagram in a doc-comment (src/gn/header_checker.h). + (cd /tmp/gn && CXX=g++ python3 build/gen.py --allow-warnings && ninja -C out gn) + install -m 0755 /tmp/gn/out/gn /usr/local/bin/gn + # BUILD.gn's `skia.h` action hardcodes rebase_path("//bin/gn") -- + # the path our patched bin/fetch-gn would have populated -- so PATH + # alone isn't enough; put the same binary there too. + install -D -m 0755 /tmp/gn/out/gn skia/bin/gn + cd skia + patch -R -p1 < ../patch/0001-Make-SkPath-immutable-on-GN-build.patch + patch -R -p1 < ../patch/0001-Disable-legacy-PNG-encoding-decoding-in-SkPicture.patch + patch -p1 < ../patch/skia-m144-minimize-download.patch + patch -p1 < ../patch/skia-m132-colrv1-freetype.diff + patch -p1 < ../patch/skia-m132-egl-runtime.diff + python3 tools/git-sync-deps + gn gen out/Release --args=" + is_official_build=true + skia_enable_svg=true + skia_use_vulkan=true + skia_use_system_libjpeg_turbo=false + skia_use_system_libwebp=false + skia_use_system_libpng=false + skia_use_system_icu=false + skia_use_system_harfbuzz=false + skia_use_system_freetype2=false + extra_cflags_cc=[\"-frtti\"] + extra_ldflags=[\"-lrt\"] + " + ninja -C out/Release + cd .. + CIBW_BEFORE_BUILD: pip install pybind11 numpy + CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + CIBW_TEST_REQUIRES: pytest pillow glfw + # moderngl (used only by test_GrDirectContext_MakeGL_EGL) has no riscv64 wheel; + # test_GrDirectContext_MakeGL_GLX has no display-unavailable skip at all, unlike + # the fixture-based 'gpu'-parametrized tests 0001-*.patch covers. No xvfb-run: + # Rocky 10 ships no Xvfb (nor Xvnc/weston) at all, so there's no display to run + # one under. -k, not --deselect {project}/...: gotcha 14 -- a path-based + # --deselect silently no-ops since pytest reports nodeids relative to its + # rootdir, not the absolute {project} path. + CIBW_TEST_COMMAND: >- + python -m pytest {project}/tests + -k "not test_GrDirectContext_MakeGL_EGL and not test_GrDirectContext_MakeGL_GLX" + CIBW_REPAIR_WHEEL_COMMAND_LINUX: "auditwheel repair --exclude libEGL.so.1 -w {dest_dir} {wheel}" + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: skia-python-${{ env.SKIA_PYTHON_VERSION }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + publish: + name: Publish skia-python ${{ inputs.version || '144.0.post2' }} + needs: [setup, build_wheels] + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + with: + artifact-pattern: skia-python-${{ inputs.version || '144.0.post2' }}-*-manylinux_riscv64 diff --git a/patches/skia-python/144.0.post2/0001-Skip-GPU-context-tests-when-glfw.init-fails-not-just.patch b/patches/skia-python/144.0.post2/0001-Skip-GPU-context-tests-when-glfw.init-fails-not-just.patch new file mode 100644 index 000000000..16c8c2375 --- /dev/null +++ b/patches/skia-python/144.0.post2/0001-Skip-GPU-context-tests-when-glfw.init-fails-not-just.patch @@ -0,0 +1,43 @@ +From ba4f377e460a8611d1ca7ff6015306913bcfd714 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Thu, 10 Sep 2026 13:07:37 +0200 +Subject: [PATCH] Skip GPU-context tests when glfw.init() fails, not just when + glfw is missing + +opengl_context's try/except only catches ImportError (glfw not +installed) and UserWarning; glfw_context() raises a plain RuntimeError +when glfw.init() returns False, which happens whenever no display +server (X11/Wayland) is reachable, not only when glfw itself is +absent. That RuntimeError propagates uncaught, erroring every test +that depends on the 'gpu'-parametrized surface/context fixtures +instead of the graceful pytest.skip('OpenGL is not available') the +rest of this fixture already falls back to. + +Rocky Linux 10 (the riscv64 manylinux image's base) ships no virtual +X server at all: xorg-x11-server-Xvfb, tigervnc-server and weston are +all absent from BaseOS/AppStream/CRB/Extras on every architecture, not +just riscv64 (RHEL 10 dropped Xvfb outright, same shape as its SDL2 +removal) -- so no DISPLAY is ever available to build a real GL context +in that container, and every test using 'gpu' hits this exact path. + +Upstream-Status: Inappropriate [Rocky 10, this repo's riscv64 CI base image, ships no Xvfb/Xvnc/weston at all -- specific to our infra, not a defect other environments with a display server would hit] +--- + tests/conftest.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/conftest.py b/tests/conftest.py +index 0658d2a..af22987 100644 +--- a/tests/conftest.py ++++ b/tests/conftest.py +@@ -46,7 +46,7 @@ def opengl_context(request): + return + except ImportError: + logger.warning('glfw not found') +- except UserWarning as e: ++ except (RuntimeError, UserWarning) as e: + logger.exception(e) + pytest.skip('GLFW error') + +-- +2.50.1 (Apple Git-155) + diff --git a/patches/skia-python/144.0.post2/0002-Skip-the-CIPD-gn-download-when-a-gn-is-already-on-PA.patch b/patches/skia-python/144.0.post2/0002-Skip-the-CIPD-gn-download-when-a-gn-is-already-on-PA.patch new file mode 100644 index 000000000..5c75c0722 --- /dev/null +++ b/patches/skia-python/144.0.post2/0002-Skip-the-CIPD-gn-download-when-a-gn-is-already-on-PA.patch @@ -0,0 +1,37 @@ +From 7a8dff07c5fd64343665d6b7d17994d6ee3a5b87 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Thu, 10 Sep 2026 17:30:42 +0200 +Subject: [PATCH] Skip the CIPD gn download when a gn is already on PATH + +bin/fetch-gn's CPU map (platform.machine().lower() -> {aarch64, +amd64, arm64, x86_64}) has no riscv64 entry, and there's no prebuilt +gn published for riscv64 on Google's CIPD to add one for -- so this +KeyErrors outright on any riscv64 host, unconditionally, with no way +to opt out (tools/git-sync-deps calls it with no skip flag, unlike +its --skip-emsdk sibling). When a gn is already resolvable via PATH, +skip the download entirely instead of trying to fetch one: nothing +downstream of git-sync-deps reads skia/bin/gn or third_party/gn/gn +directly, they're only ever invoked through PATH. + +Upstream-Status: Inappropriate [riscv64 has no prebuilt gn published on Google's CIPD to add a CPU-map entry for; this only ever fires when PATH already resolves one, which is specific to how this repo builds skia-python for that architecture] +--- + bin/fetch-gn | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/skia/bin/fetch-gn b/skia/bin/fetch-gn +index ebdc31c4..c425d70e 100755 +--- a/skia/bin/fetch-gn ++++ b/skia/bin/fetch-gn +@@ -17,6 +17,9 @@ from urllib.request import urlopen + + os.chdir(os.path.join(os.path.dirname(__file__), os.pardir)) + ++if shutil.which('gn'): ++ sys.exit(0) ++ + gnzip = os.path.join(tempfile.mkdtemp(), 'gn.zip') + with open(gnzip, 'wb') as f: + OS = {'darwin': 'mac', 'linux': 'linux', 'linux2': 'linux', 'win32': 'windows'}[sys.platform] +-- +2.50.1 (Apple Git-155) +