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
149 changes: 149 additions & 0 deletions .github/workflows/build-skia-python.yml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
From ba4f377e460a8611d1ca7ff6015306913bcfd714 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
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)

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
From 7a8dff07c5fd64343665d6b7d17994d6ee3a5b87 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
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)