Skip to content

Commit febe9a1

Browse files
committed
Support emscripten/pygbag in the meson buildconfig
1 parent 2a7de5c commit febe9a1

File tree

6 files changed

+106
-28
lines changed

6 files changed

+106
-28
lines changed

.github/workflows/build-emsdk.yml

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,35 +36,22 @@ jobs:
3636
runs-on: ubuntu-22.04
3737
env:
3838
# pin SDK version to the latest, update manually
39-
SDK_VERSION: 3.1.32.0
40-
SDK_ARCHIVE: python3.11-wasm-sdk-Ubuntu-22.04.tar.lz4
39+
SDK_VERSION: 3.1.61.12bi
40+
SDK_ARCHIVE: python3.13-wasm-sdk-Ubuntu-22.04.tar.lz4
4141
SDKROOT: /opt/python-wasm-sdk
42+
PYBUILD: 3.13
4243

4344
steps:
4445
- uses: actions/checkout@v5.0.0
4546

46-
- name: Regen with latest cython (using system python3)
47-
run: |
48-
pip3 install cython==3.0.10
49-
python3 setup.py cython_only
50-
5147
- name: Install python-wasm-sdk
5248
run: |
5349
sudo apt-get install lz4
54-
echo https://github.com/pygame-web/python-wasm-sdk/releases/download/$SDK_VERSION/$SDK_ARCHIVE
5550
curl -sL --retry 5 https://github.com/pygame-web/python-wasm-sdk/releases/download/$SDK_VERSION/$SDK_ARCHIVE | tar xvP --use-compress-program=lz4
56-
# do not let SDL1 interfere
57-
rm -rf /opt/python-wasm-sdk/emsdk/upstream/emscripten/cache/sysroot/include/SDL
5851
working-directory: /opt
5952

6053
- name: Build WASM with emsdk
61-
run: |
62-
${SDKROOT}/python3-wasm setup.py build -j$(nproc)
63-
64-
- name: Generate libpygame.a static binaries archive
65-
run: |
66-
mkdir -p dist
67-
SYS_PYTHON=python3 /opt/python-wasm-sdk/emsdk/upstream/emscripten/emar rcs dist/libpygame.a $(find build/temp.wasm32-*/ | grep o$)
54+
run: ${SDKROOT}/python3-wasm dev.py build --wheel
6855

6956
# Upload the generated files under github actions assets section
7057
- name: Upload dist
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[host_machine]
2+
system = 'emscripten'
3+
cpu_family = 'wasm64'
4+
cpu = 'wasm'
5+
endian = 'little'
6+
7+
[binaries]
8+
c = '/opt/python-wasm-sdk/emsdk/upstream/emscripten/emcc'
9+
cpp = '/opt/python-wasm-sdk/emsdk/upstream/emscripten/em++'
10+
ar = '/opt/python-wasm-sdk/emsdk/upstream/bin/llvm-ar'
11+
strip = '/opt/python-wasm-sdk/emsdk/upstream/bin/llvm-strip'
12+
exe_wrapper = 'node'
13+
pkgconfig_path = '/opt/python-wasm-sdk/devices/x86_64/usr/bin/pkgconfig'

dev.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import re
1212
import subprocess
1313
import sys
14+
import sysconfig
1415
from enum import Enum
1516
from pathlib import Path
1617
from typing import Any, Union
@@ -35,6 +36,19 @@
3536
# We assume this script works with any pip version above this.
3637
PIP_MIN_VERSION = "23.1"
3738

39+
# we will assume dev.py wasm builds are made for pygbag.
40+
host_gnu_type = sysconfig.get_config_var("HOST_GNU_TYPE")
41+
if isinstance(host_gnu_type, str) and "wasm" in host_gnu_type:
42+
wasm = "wasi" if "wasi" in host_gnu_type else "emscripten"
43+
else:
44+
wasm = ""
45+
46+
wasm_cross_file = (source_tree / "buildconfig" / f"meson-cross-{wasm}.ini").resolve()
47+
wasm_args = [
48+
f"-Csetup-args=--cross-file={wasm_cross_file}",
49+
"-Csetup-args=-Demscripten_type=pygbag",
50+
]
51+
3852

3953
class Colors(Enum):
4054
RESET = "\033[0m"
@@ -189,7 +203,11 @@ def check_module_in_constraint(mod: str, constraint: str):
189203

190204
class Dev:
191205
def __init__(self) -> None:
192-
self.py: Path = Path(sys.executable)
206+
self.py: Path = (
207+
Path(os.environ["SDKROOT"]) / "python3-wasm"
208+
if wasm
209+
else Path(sys.executable)
210+
)
193211
self.args: dict[str, Any] = {}
194212

195213
self.deps: dict[str, set[str]] = {
@@ -233,6 +251,14 @@ def cmd_build(self):
233251
]
234252

235253
if not wheel_dir:
254+
if wasm:
255+
pprint(
256+
"Editable builds are not supported on WASM as of now. "
257+
"Pass --wheel to do a regular build",
258+
Colors.RED,
259+
)
260+
sys.exit(1)
261+
236262
# editable install
237263
if not quiet:
238264
install_args.append("-Ceditable-verbose=true")
@@ -259,6 +285,9 @@ def cmd_build(self):
259285
if sanitize:
260286
install_args.append(f"-Csetup-args=-Db_sanitize={sanitize}")
261287

288+
if wasm:
289+
install_args.extend(wasm_args)
290+
262291
info_str = (
263292
f"with {debug=}, {lax=}, {sdl3=}, {stripped=}, {coverage=} and {sanitize=}"
264293
)
@@ -484,6 +513,12 @@ def prep_env(self):
484513
# set PATH to give high priority to executables in the python bin folder
485514
# this is where the binaries for meson/ninja/cython/sphinx/etc are installed
486515
os.environ["PATH"] = f"{self.py.parent}{os.pathsep}{os.environ.get('PATH', '')}"
516+
if wasm:
517+
for wasm_path in [
518+
self.py.parent / "devices" / "emsdk" / "usr" / "bin",
519+
self.py.parent / "emsdk" / "upstream" / "emscripten",
520+
]:
521+
os.environ["PATH"] = f"{wasm_path}{os.pathsep}{os.environ['PATH']}"
487522

488523
pprint("Checking pip version")
489524
pip_v = cmd_run([self.py, "-m", "pip", "-V"], capture_output=True)
@@ -497,6 +532,10 @@ def prep_env(self):
497532
pprint("pip version is too old or unknown, attempting pip upgrade")
498533
pip_install(self.py, ["-U", "pip"])
499534

535+
if wasm:
536+
# dont try to install any deps on WASM
537+
return
538+
500539
deps = self.deps.get(self.args["command"], set())
501540
ignored_deps = self.args["ignore_dep"]
502541
deps_filtered = deps.copy()

meson.build

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,7 @@ elif host_machine.system() == 'android'
3434
'However it may be added in the future',
3535
)
3636
elif host_machine.system() == 'emscripten'
37-
plat = 'emscripten'
38-
error(
39-
'The meson buildconfig of pygame-ce does not support emscripten for now. ',
40-
'However it may be added in the future',
41-
)
37+
plat = 'emscripten-@0@'.format(get_option('emscripten_type'))
4238
else
4339
# here it one of: cygwin, dragonfly, freebsd, gnu, haiku, netbsd, openbsd, sunos
4440
plat = 'unix'
@@ -80,7 +76,7 @@ pg_dir = py.get_install_dir() / pg
8076

8177
sdl_api = get_option('sdl_api')
8278
sdl = 'SDL@0@'.format(sdl_api)
83-
sdl_mixer = '@0@_mixer'.format(sdl)
79+
sdl_mixer = (plat == 'emscripten-pygbag') ? '@0@_mixer_ogg'.format(sdl) : '@0@_mixer'.format(sdl)
8480
sdl_ttf = '@0@_ttf'.format(sdl)
8581
sdl_image = '@0@_image'.format(sdl)
8682

@@ -229,7 +225,20 @@ if plat == 'win' and host_machine.cpu_family().startswith('x86')
229225
install_data(dlls, install_dir: pg_dir, install_tag: 'pg-tag')
230226

231227
else
232-
bases = ['/usr/local', '/usr', '/opt/homebrew', '/opt/local']
228+
if plat == 'emscripten-pygbag'
229+
bases = [
230+
'/opt/python-wasm-sdk/emsdk/upstream/emscripten/cache/sysroot',
231+
'/opt/python-wasm-sdk/devices/emsdk/usr'
232+
]
233+
add_global_arguments(['-DBUILD_STATIC'], language: 'c')
234+
elif plat == 'emscripten-generic'
235+
# TODO: check if these bases are valid in pyodide
236+
bases = ['/usr/local', '/usr', '/opt/local']
237+
add_global_arguments(['-DBUILD_STATIC'], language: 'c')
238+
else
239+
bases = ['/usr/local', '/usr', '/opt/homebrew', '/opt/local']
240+
endif
241+
233242
foreach inc_dir : bases
234243
foreach sub_inc : [
235244
'',
@@ -244,7 +253,7 @@ else
244253
endforeach
245254

246255
foreach lib_dir : bases
247-
foreach sub_lib : ['lib', 'lib64']
256+
foreach sub_lib : ['lib', 'lib64', 'lib/wasm32-emscripten/pic', 'lib/wasm32-emscripten']
248257
full_lib = lib_dir / sub_lib
249258
if fs.exists(full_lib)
250259
pg_lib_dirs += full_lib
@@ -312,7 +321,7 @@ if not freetype_dep.found()
312321
endif
313322

314323
portmidi_dep = dependency('portmidi', required: false)
315-
if not portmidi_dep.found()
324+
if not portmidi_dep.found() and not plat.startswith('emscripten')
316325
portmidi_dep = declare_dependency(
317326
include_directories: pg_inc_dirs,
318327
dependencies: cc.find_library(
@@ -436,7 +445,7 @@ endif
436445
subdir('src_c')
437446
subdir('src_py')
438447

439-
if not get_option('stripped')
448+
if not get_option('stripped') and not plat.startswith('emscripten')
440449
# run make_docs and make docs
441450
if not fs.is_dir('docs/generated')
442451
make_docs = files('buildconfig/make_docs.py')

meson_options.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,7 @@ option('coverage', type: 'boolean', value: false)
4040

4141
# Controls whether to use SDL3 instead of SDL2. The default is to use SDL2
4242
option('sdl_api', type: 'integer', min: 2, max: 3, value: 2)
43+
44+
# Specify the type of emscripten build being done.
45+
# TODO: add a 'pyodide' option here if it requires specialised code
46+
option('emscripten_type', type: 'combo', choices: ['pygbag', 'generic'], value: 'generic')

src_c/meson.build

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,27 @@
1+
if plat.startswith('emscripten') # emscripten build
2+
3+
base_files = ['base.c', 'bitmask.c', 'SDL_gfx/SDL_gfxPrimitives.c']
4+
cython_files = [
5+
'cython/pygame/_sdl2/audio.pyx',
6+
'cython/pygame/_sdl2/mixer.pyx',
7+
'cython/pygame/_sdl2/sdl2.pyx',
8+
'cython/pygame/_sdl2/video.pyx',
9+
'pgcompat.c'
10+
]
11+
12+
# make one big static build on emscripten
13+
pygame = static_library(
14+
'pygame',
15+
base_files + cython_files,
16+
# c_args: warnings_error,
17+
dependencies: pg_base_deps + [sdl_image_dep, sdl_mixer_dep, sdl_ttf_dep, freetype_dep],
18+
install: true,
19+
install_dir: pg_dir,
20+
install_tag: 'pg-tag'
21+
)
22+
23+
else # regular build
24+
125
# first the "required" modules
226

327
base = py.extension_module(
@@ -452,3 +476,5 @@ if portmidi_dep.found()
452476
subdir: pg,
453477
)
454478
endif
479+
480+
endif # emscripten

0 commit comments

Comments
 (0)