Skip to content

Conversation

ankith26
Copy link
Member

This is my take on @pmp-p 's PR at pmp-p#12

In its current state, this PR can build a static build (libpygame.a) on emscripten just like the old buildconfig. It then makes a custom wheel with this static build of src_c and the pure python contents of src_py.

@pmp-p I would appreciate your review on this PR + help with testing it. If this PR works out, pygbag would need to be updated accordingly to handle the wheel file this PR can build.

I have tried to keep things generic here, keeping in mind possible extensibility of this work to re-supporting pyodide builds (with help from pyodide maintainers).

@ankith26 ankith26 requested a review from a team as a code owner September 14, 2025 11:18
Copy link
Contributor

coderabbitai bot commented Sep 14, 2025

📝 Walkthrough

Walkthrough

Adds Emscripten/WASM build support: updates CI to use python-wasm SDK and build via the SDK's python3-wasm, introduces a Meson cross file and an emscripten_type option, extends dev.py to detect and drive WASM builds, adds an emscripten static-library path in Meson, and adjusts a FreeType include.

Changes

Cohort / File(s) Change Summary
CI workflow (WASM SDK build)
.github/workflows/build-emsdk.yml
Switches CI to python-wasm SDK (SDK_VERSION -> 3.1.61.12bi, new archive, PYBUILD=3.13), removes cython-only regen and SDL cache cleanup, drops echo of download URL, and runs ${SDKROOT}/python3-wasm dev.py build --wheel instead of prior emsdk build steps; artifact upload remains.
Meson cross-config
buildconfig/meson-cross-emscripten.ini
Adds Meson cross-compilation config for Emscripten (wasm64): maps emcc/em++, llvm-ar/llvm-strip, node as exe_wrapper, and sets pkgconfig_path.
Meson options & platform branching
meson_options.txt, meson.build
Adds emscripten_type option (pygbag/generic), replaces the previous Emscripten guard with emscripten-<type> platform string, conditionally selects SDL_mixer variant, adjusts include/lib base dirs and static link flag (-DBUILD_STATIC) per type, expands wasm library search paths, skips docs/strip and portmidi resolution for emscripten builds.
src C build layout for Emscripten
src_c/meson.build
Adds an emscripten-specific branch that builds a single static library pygame from base C and cython SDL2 sources with SDL_image/mixer/ttf and freetype deps and installs it to pg_dir; retains per-module extension builds for non-emscripten.
Tooling: dev build script
dev.py
Detects WASM via HOST_GNU_TYPE, exposes host_gnu_type, wasm, wasm_cross_file, wasm_args; selects ${SDKROOT}/python3-wasm as interpreter when targeting WASM, injects emsdk tools into PATH, appends cross-file args for Meson, forbids editable (non-wheel) builds on WASM, and skips dependency installation on WASM. Also extends Colors enum with additional members.
C source include fix
src_c/_freetype.c
Changes FreeType header include from "freetype.h" to "freetype/ft_freetype.h".

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant GHA as GitHub Actions
  participant SDK as python-wasm SDK
  participant Dev as dev.py
  participant Meson as Meson/Ninja
  participant Ems as Emscripten toolchain
  participant Art as Artifact Store

  GHA->>SDK: Install python-wasm SDK (3.1.61.12bi)
  GHA->>Dev: Run ${SDKROOT}/python3-wasm dev.py build --wheel
  Dev->>Dev: Detect WASM (HOST_GNU_TYPE) and set wasm_args/cross-file
  Dev->>SDK: Use ${SDKROOT}/python3-wasm as interpreter
  Dev->>Meson: Configure build with cross file + emscripten_type
  Meson->>Ems: Invoke emcc/em++ (wasm64 target)
  Ems-->>Meson: Produce wasm/static artifacts
  Meson-->>Dev: Build complete (wheel)
  Dev-->>GHA: Provide artifact(s)
  GHA->>Art: Upload artifact(s)
Loading
sequenceDiagram
  autonumber
  participant User as Developer
  participant Dev as dev.py
  participant SysPy as System Python
  participant PyWasm as ${SDKROOT}/python3-wasm
  participant Meson as Meson/Ninja

  User->>Dev: Invoke dev.py build [--wheel]
  alt WASM target
    Dev->>PyWasm: Select SDK interpreter
    Dev->>Dev: Inject emsdk tools into PATH
    Dev->>Meson: Pass cross-file + wasm_args
    note right of Dev: Editable builds disallowed on WASM
  else Non-WASM target
    Dev->>SysPy: Use system interpreter
    Dev->>Meson: Standard args
  end
  Meson-->>User: Build artifacts
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Support emscripten/pygbag in the meson buildconfig" is concise and accurately summarizes the primary change in the changeset—adding emscripten/pygbag support to the Meson build configuration and related build files—so it clearly communicates the main intent to reviewers. It is specific, avoids noise, and would be understandable in history.
Description Check ✅ Passed The PR description directly describes the core outcome (building a static lib and a custom wheel for emscripten/pygbag), references the originating work, and requests review/testing, so it is relevant to the changeset and not off-topic. Given this check's lenient standard, the description satisfies the requirement by relating to the changeset.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ankith26-meson-wasm

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
dev.py (4)

39-51: Guard WASM cross-file/args and handle unsupported WASI explicitly

Computing wasm_cross_file and wasm_args unconditionally risks pointing to a non-existent file (e.g., meson-cross-.ini when not WASM, or meson-cross-wasi.ini which doesn’t exist here). Build will fail silently later.

  • Only compute/use these when wasm == 'emscripten'.
  • Fail fast for wasm == 'wasi' (or allow selecting type via a flag/env).

Apply this diff:

-host_gnu_type = sysconfig.get_config_var("HOST_GNU_TYPE")
-if isinstance(host_gnu_type, str) and "wasm" in host_gnu_type:
-    wasm = "wasi" if "wasi" in host_gnu_type else "emscripten"
-else:
-    wasm = ""
-
-wasm_cross_file = (source_tree / "buildconfig" / f"meson-cross-{wasm}.ini").resolve()
-wasm_args = [
-    f"-Csetup-args=--cross-file={wasm_cross_file}",
-    "-Csetup-args=-Demscripten_type=pygbag",
-]
+host_gnu_type = sysconfig.get_config_var("HOST_GNU_TYPE")
+if isinstance(host_gnu_type, str) and "wasm" in host_gnu_type:
+    wasm = "wasi" if "wasi" in host_gnu_type else "emscripten"
+else:
+    wasm = ""
+
+wasm_args: list[str] = []
+if wasm == "emscripten":
+    wasm_cross_file = (source_tree / "buildconfig" / "meson-cross-emscripten.ini").resolve()
+    wasm_args = [
+        f"-Csetup-args=--cross-file={wasm_cross_file}",
+        "-Csetup-args=-Demscripten_type=pygbag",
+    ]
+elif wasm == "wasi":
+    pprint("WASI is not supported by this buildconfig yet. Please use emscripten.", Colors.RED)
+    sys.exit(1)

206-211: Fail gracefully when SDKROOT is missing in WASM mode

Accessing os.environ["SDKROOT"] will KeyError if the environment isn’t set correctly, leading to a cryptic crash.

Apply this diff:

-        self.py: Path = (
-            Path(os.environ["SDKROOT"]) / "python3-wasm"
-            if wasm
-            else Path(sys.executable)
-        )
+        if wasm:
+            sdkroot = os.environ.get("SDKROOT")
+            if not sdkroot:
+                pprint("SDKROOT is not set but WASM was detected. Set SDKROOT to your python-wasm SDK root.", Colors.RED)
+                sys.exit(1)
+            self.py = Path(sdkroot) / "python3-wasm"
+        else:
+            self.py = Path(sys.executable)

516-522: PATH injection: skip non-existent dirs and preserve order

Prepending blindly may add junk paths on custom layouts. Also, prefer emscripten upstream dir before devices bin.

Apply this diff:

-        if wasm:
-            for wasm_path in [
-                self.py.parent / "devices" / "emsdk" / "usr" / "bin",
-                self.py.parent / "emsdk" / "upstream" / "emscripten",
-            ]:
-                os.environ["PATH"] = f"{wasm_path}{os.pathsep}{os.environ['PATH']}"
+        if wasm:
+            for wasm_path in [
+                self.py.parent / "emsdk" / "upstream" / "emscripten",
+                self.py.parent / "devices" / "emsdk" / "usr" / "bin",
+            ]:
+                if wasm_path.exists():
+                    os.environ["PATH"] = f"{wasm_path}{os.pathsep}{os.environ['PATH']}"

535-538: Skip pip version check/upgrade on WASM

In SDK environments this can be slow or fail; since you don’t install deps on WASM, you can skip the pip check/upgrade too.

Apply this diff:

-        pprint("Checking pip version")
-        pip_v = cmd_run([self.py, "-m", "pip", "-V"], capture_output=True)
+        if not wasm:
+            pprint("Checking pip version")
+            pip_v = cmd_run([self.py, "-m", "pip", "-V"], capture_output=True)

And guard the subsequent pip upgrade block under if not wasm:.

src_c/meson.build (3)

1-22: Emscripten static lib path: good direction; confirm .pyx in static_library works across Meson versions

Building a single static pygame is correct for pygbag. Two follow-ups:

  • Meson cython support with .pyx inside static_library() varies by Meson/Cython versions; please confirm CI uses a Meson that treats cython as a first-class language for non-py.extension_module targets.
  • Consider moving 'pgcompat.c' out of cython_files to base_files to keep typed lists clean.

Proposed tidy:

-base_files = ['base.c', 'bitmask.c', 'SDL_gfx/SDL_gfxPrimitives.c']
-cython_files = [
+base_files = ['base.c', 'bitmask.c', 'SDL_gfx/SDL_gfxPrimitives.c', 'pgcompat.c']
+cython_files = [
     'cython/pygame/_sdl2/audio.pyx',
     'cython/pygame/_sdl2/mixer.pyx',
     'cython/pygame/_sdl2/sdl2.pyx',
     'cython/pygame/_sdl2/video.pyx',
-    'pgcompat.c'
 ]

12-21: Optional deps: tie inclusion to options to avoid link-time surprises

You always add sdl_image_dep, sdl_mixer_dep, sdl_ttf_dep, freetype_dep. When options are disabled or libs absent, some toolchains produce empty/“disabler” deps that still end up in the list. Safer to filter by get_option(...) or *_dep.found().

Apply this diff:

-    dependencies: pg_base_deps + [sdl_image_dep, sdl_mixer_dep, sdl_ttf_dep, freetype_dep],
+    dependencies: pg_base_deps
+        + (get_option('image') ? [sdl_image_dep] : [])
+        + (get_option('mixer') ? [sdl_mixer_dep] : [])
+        + (get_option('font') ? [sdl_ttf_dep, freetype_dep] : []),

23-24: Style: place comment on its own line

Meson allows trailing comments, but else # regular build is brittle in diffs. Non-blocking nit.

-else  # regular build
+else
+  # regular build
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a6e7fc and febe9a1.

📒 Files selected for processing (6)
  • .github/workflows/build-emsdk.yml (1 hunks)
  • buildconfig/meson-cross-emscripten.ini (1 hunks)
  • dev.py (7 hunks)
  • meson.build (6 hunks)
  • meson_options.txt (1 hunks)
  • src_c/meson.build (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • meson_options.txt
  • buildconfig/meson-cross-emscripten.ini
  • .github/workflows/build-emsdk.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: arm64
  • GitHub Check: x86_64
  • GitHub Check: i686
  • GitHub Check: x86_64
  • GitHub Check: aarch64
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.13.5)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.14.0rc1)
  • GitHub Check: debug_coverage (ubuntu-24.04, 3.9.23)
  • GitHub Check: x86
  • GitHub Check: AMD64
  • GitHub Check: Debian (Bookworm - 12) [armv6]
  • GitHub Check: Debian (Bookworm - 12) [armv7]
  • GitHub Check: Debian (Bookworm - 12) [s390x]
  • GitHub Check: Debian (Bookworm - 12) [ppc64le]
🔇 Additional comments (8)
dev.py (2)

253-261: Nice: clear error for editable installs on WASM

Good user-facing guard; avoids a confusing partial install.


288-290: Plumb WASM args only when set

This is safe now, but after guarding wasm_args above, ensure we only extend when non-empty.

Suggested tiny tweak:

-        if wasm:
+        if wasm_args:
             install_args.extend(wasm_args)
src_c/meson.build (1)

480-481: LGTM: explicit endif comment

Helpful for long files.

meson.build (5)

36-38: Good: make emscripten build selectable via option

plat = 'emscripten-@0@' sets the ground for multiple flavors. LGTM.


79-80: Mixer variant name for pygbag: confirm library name

Using SDL2_mixer_ogg is reasonable for pygbag SDKs; ensure the PC/lib name matches the shipped artifact to avoid fallback failures.

If the actual lib is SDL2_mixer with built-in OGG, consider using a cross property or option to switch names rather than hard-coding.


255-262: Nice: include wasm-specific lib dirs

Including lib/wasm32-emscripten and /pic improves resolution across SDK variants. LGTM.


323-333: Good: skip portmidi resolution on emscripten

Avoids unnecessary lookups and simplifies the wasm build.


448-464: Docs/tests/stubs gating for emscripten is appropriate

Prevents cross-environment build steps from running where they can’t execute. LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant