浏览代码

[build.zig]: a concrete approach to build for web with zig-build (#5157)

HaxSam 1 周之前
父节点
当前提交
0d29d8d4b1
共有 2 个文件被更改,包括 219 次插入250 次删除
  1. 214 248
      build.zig
  2. 5 2
      build.zig.zon

+ 214 - 248
build.zig

@@ -14,58 +14,80 @@ comptime {
         @compileError("Raylib requires zig version " ++ min_ver);
 }
 
-fn setDesktopPlatform(raylib: *std.Build.Step.Compile, platform: PlatformBackend) void {
-    switch (platform) {
-        .glfw => raylib.root_module.addCMacro("PLATFORM_DESKTOP_GLFW", ""),
-        .rgfw => raylib.root_module.addCMacro("PLATFORM_DESKTOP_RGFW", ""),
-        .sdl => raylib.root_module.addCMacro("PLATFORM_DESKTOP_SDL", ""),
-        .android => raylib.root_module.addCMacro("PLATFORM_ANDROID", ""),
-        else => {},
-    }
-}
+pub const emsdk = struct {
+    const zemscripten = @import("zemscripten");
 
-fn createEmsdkStep(b: *std.Build, emsdk: *std.Build.Dependency) *std.Build.Step.Run {
-    if (builtin.os.tag == .windows) {
-        return b.addSystemCommand(&.{emsdk.path("emsdk.bat").getPath(b)});
-    } else {
-        return b.addSystemCommand(&.{emsdk.path("emsdk").getPath(b)});
+    pub fn shell(b: *std.Build) std.Build.LazyPath {
+        return b.dependency("raylib", .{}).path("src/shell.html");
     }
-}
 
-fn emSdkSetupStep(b: *std.Build, emsdk: *std.Build.Dependency) !?*std.Build.Step.Run {
-    const dot_emsc_path = emsdk.path(".emscripten").getPath(b);
-    const dot_emsc_exists = !std.meta.isError(std.fs.accessAbsolute(dot_emsc_path, .{}));
-
-    if (!dot_emsc_exists) {
-        const emsdk_install = createEmsdkStep(b, emsdk);
-        emsdk_install.addArgs(&.{ "install", "latest" });
-        const emsdk_activate = createEmsdkStep(b, emsdk);
-        emsdk_activate.addArgs(&.{ "activate", "latest" });
-        emsdk_activate.step.dependOn(&emsdk_install.step);
-        return emsdk_activate;
-    } else {
-        return null;
+    pub const FlagsOptions = struct {
+        optimize: std.builtin.OptimizeMode,
+        asyncify: bool = true,
+    };
+
+    pub fn emccDefaultFlags(allocator: std.mem.Allocator, options: FlagsOptions) zemscripten.EmccFlags {
+        var emcc_flags = zemscripten.emccDefaultFlags(allocator, .{
+            .optimize = options.optimize,
+            .fsanitize = true,
+        });
+
+        if (options.asyncify)
+            emcc_flags.put("-sASYNCIFY", {}) catch unreachable;
+
+        return emcc_flags;
     }
-}
 
-// Adapted from Not-Nik/raylib-zig
-fn emscriptenRunStep(b: *std.Build, emsdk: *std.Build.Dependency, examplePath: []const u8) !*std.Build.Step.Run {
-    const dot_emsc_path = emsdk.path("upstream/emscripten/cache/sysroot/include").getPath(b);
-    // If compiling on windows , use emrun.bat.
-    const emrunExe = switch (builtin.os.tag) {
-        .windows => "emrun.bat",
-        else => "emrun",
+    pub const SettingsOptions = struct {
+        optimize: std.builtin.OptimizeMode,
+        es3: bool = true,
+        emsdk_allocator: zemscripten.EmsdkAllocator = .emmalloc,
     };
-    var emrun_run_arg = try b.allocator.alloc(u8, dot_emsc_path.len + emrunExe.len + 1);
-    defer b.allocator.free(emrun_run_arg);
 
-    if (b.sysroot == null) {
-        emrun_run_arg = try std.fmt.bufPrint(emrun_run_arg, "{s}" ++ std.fs.path.sep_str ++ "{s}", .{ emsdk.path("upstream/emscripten").getPath(b), emrunExe });
-    } else {
-        emrun_run_arg = try std.fmt.bufPrint(emrun_run_arg, "{s}" ++ std.fs.path.sep_str ++ "{s}", .{ dot_emsc_path, emrunExe });
+    pub fn emccDefaultSettings(allocator: std.mem.Allocator, options: SettingsOptions) zemscripten.EmccSettings {
+        var emcc_settings = zemscripten.emccDefaultSettings(allocator, .{
+            .optimize = options.optimize,
+            .emsdk_allocator = options.emsdk_allocator,
+        });
+
+        if (options.es3)
+            emcc_settings.put("FULL_ES3", "1") catch unreachable;
+        emcc_settings.put("USE_GLFW", "3") catch unreachable;
+        emcc_settings.put("EXPORTED_RUNTIME_METHODS", "['requestFullscreen']") catch unreachable;
+
+        return emcc_settings;
+    }
+
+    pub fn emccStep(b: *std.Build, raylib: *std.Build.Step.Compile, wasm: *std.Build.Step.Compile, options: zemscripten.StepOptions) *std.Build.Step {
+        const activate_emsdk_step = zemscripten.activateEmsdkStep(b);
+
+        const emsdk_dep = b.dependency("emsdk", .{});
+        raylib.root_module.addIncludePath(emsdk_dep.path("upstream/emscripten/cache/sysroot/include"));
+        wasm.root_module.addIncludePath(emsdk_dep.path("upstream/emscripten/cache/sysroot/include"));
+
+        const emcc_step = zemscripten.emccStep(b, wasm, options);
+        emcc_step.dependOn(activate_emsdk_step);
+
+        return emcc_step;
+    }
+
+    pub fn emrunStep(
+        b: *std.Build,
+        html_path: []const u8,
+        extra_args: []const []const u8,
+    ) *std.Build.Step {
+        return zemscripten.emrunStep(b, html_path, extra_args);
+    }
+};
+
+fn setDesktopPlatform(raylib: *std.Build.Step.Compile, platform: PlatformBackend) void {
+    switch (platform) {
+        .glfw => raylib.root_module.addCMacro("PLATFORM_DESKTOP_GLFW", ""),
+        .rgfw => raylib.root_module.addCMacro("PLATFORM_DESKTOP_RGFW", ""),
+        .sdl => raylib.root_module.addCMacro("PLATFORM_DESKTOP_SDL", ""),
+        .android => raylib.root_module.addCMacro("PLATFORM_ANDROID", ""),
+        else => {},
     }
-    const run_cmd = b.addSystemCommand(&.{ emrun_run_arg, examplePath });
-    return run_cmd;
 }
 
 /// A list of all flags from `src/config.h` that one may override
@@ -99,10 +121,20 @@ const config_h_flags = outer: {
     break :outer flags[0..i].*;
 };
 
-pub fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, options: Options) !*std.Build.Step.Compile {
+fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, options: Options) !*std.Build.Step.Compile {
     var raylib_flags_arr: std.ArrayList([]const u8) = .empty;
     defer raylib_flags_arr.deinit(b.allocator);
 
+    const raylib = b.addLibrary(.{
+        .name = "raylib",
+        .linkage = options.linkage,
+        .root_module = b.createModule(.{
+            .optimize = optimize,
+            .target = target,
+            .link_libc = true,
+        }),
+    });
+
     try raylib_flags_arr.appendSlice(
         b.allocator,
         &[_][]const u8{
@@ -113,7 +145,7 @@ pub fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize:
         },
     );
 
-    if (options.shared) {
+    if (options.linkage == .dynamic) {
         try raylib_flags_arr.appendSlice(
             b.allocator,
             &[_][]const u8{
@@ -159,16 +191,6 @@ pub fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize:
         try raylib_flags_arr.appendSlice(b.allocator, &config_h_flags);
     }
 
-    const raylib = b.addLibrary(.{
-        .name = "raylib",
-        .linkage = if (options.shared) .dynamic else .static,
-        .root_module = b.createModule(.{
-            .target = target,
-            .optimize = optimize,
-        }),
-    });
-    raylib.linkLibC();
-
     // No GLFW required on PLATFORM_DRM
     if (options.platform != .drm) {
         raylib.addIncludePath(b.path("src/external/glfw/include"));
@@ -210,22 +232,22 @@ pub fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize:
                 .rgfw, .sdl, .drm, .android => {},
             }
 
-            raylib.linkSystemLibrary("winmm");
-            raylib.linkSystemLibrary("gdi32");
-            raylib.linkSystemLibrary("opengl32");
+            raylib.root_module.linkSystemLibrary("winmm", .{});
+            raylib.root_module.linkSystemLibrary("gdi32", .{});
+            raylib.root_module.linkSystemLibrary("opengl32", .{});
 
             setDesktopPlatform(raylib, options.platform);
         },
         .linux => {
             if (options.platform == .drm) {
                 if (options.opengl_version == .auto) {
-                    raylib.linkSystemLibrary("GLESv2");
+                    raylib.root_module.linkSystemLibrary("GLESv2", .{});
                     raylib.root_module.addCMacro("GRAPHICS_API_OPENGL_ES2", "");
                 }
 
-                raylib.linkSystemLibrary("EGL");
-                raylib.linkSystemLibrary("gbm");
-                raylib.linkSystemLibrary2("libdrm", .{ .use_pkg_config = .force });
+                raylib.root_module.linkSystemLibrary("EGL", .{});
+                raylib.root_module.linkSystemLibrary("gbm", .{});
+                raylib.root_module.linkSystemLibrary("libdrm", .{ .use_pkg_config = .force });
 
                 raylib.root_module.addCMacro("PLATFORM_DRM", "");
                 raylib.root_module.addCMacro("EGL_NO_X11", "");
@@ -260,12 +282,12 @@ pub fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize:
                 const androidAsmPath = try std.fs.path.join(b.allocator, &.{ androidIncludePath, "/asm-generic" });
                 const androidGluePath = try std.fs.path.join(b.allocator, &.{ androidNdkPathString, "/sources/android/native_app_glue/" });
 
-                raylib.addLibraryPath(.{ .cwd_relative = androidLibPath });
+                raylib.root_module.addLibraryPath(.{ .cwd_relative = androidLibPath });
                 raylib.root_module.addLibraryPath(.{ .cwd_relative = androidApiSpecificPath });
-                raylib.addSystemIncludePath(.{ .cwd_relative = androidIncludePath });
-                raylib.addSystemIncludePath(.{ .cwd_relative = androidArchIncludePath });
-                raylib.addSystemIncludePath(.{ .cwd_relative = androidAsmPath });
-                raylib.addSystemIncludePath(.{ .cwd_relative = androidGluePath });
+                raylib.root_module.addSystemIncludePath(.{ .cwd_relative = androidIncludePath });
+                raylib.root_module.addSystemIncludePath(.{ .cwd_relative = androidArchIncludePath });
+                raylib.root_module.addSystemIncludePath(.{ .cwd_relative = androidAsmPath });
+                raylib.root_module.addSystemIncludePath(.{ .cwd_relative = androidGluePath });
 
                 var libcData: std.ArrayList(u8) = .empty;
                 var aw: std.Io.Writer.Allocating = .fromArrayList(b.allocator, &libcData);
@@ -289,15 +311,15 @@ pub fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize:
 
                 if (options.linux_display_backend == .X11 or options.linux_display_backend == .Both) {
                     raylib.root_module.addCMacro("_GLFW_X11", "");
-                    raylib.linkSystemLibrary("GLX");
-                    raylib.linkSystemLibrary("X11");
-                    raylib.linkSystemLibrary("Xcursor");
-                    raylib.linkSystemLibrary("Xext");
-                    raylib.linkSystemLibrary("Xfixes");
-                    raylib.linkSystemLibrary("Xi");
-                    raylib.linkSystemLibrary("Xinerama");
-                    raylib.linkSystemLibrary("Xrandr");
-                    raylib.linkSystemLibrary("Xrender");
+                    raylib.root_module.linkSystemLibrary("GLX", .{});
+                    raylib.root_module.linkSystemLibrary("X11", .{});
+                    raylib.root_module.linkSystemLibrary("Xcursor", .{});
+                    raylib.root_module.linkSystemLibrary("Xext", .{});
+                    raylib.root_module.linkSystemLibrary("Xfixes", .{});
+                    raylib.root_module.linkSystemLibrary("Xi", .{});
+                    raylib.root_module.linkSystemLibrary("Xinerama", .{});
+                    raylib.root_module.linkSystemLibrary("Xrandr", .{});
+                    raylib.root_module.linkSystemLibrary("Xrender", .{});
                 }
 
                 if (options.linux_display_backend == .Wayland or options.linux_display_backend == .Both) {
@@ -309,9 +331,9 @@ pub fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize:
                         @panic("`wayland-scanner` not found");
                     };
                     raylib.root_module.addCMacro("_GLFW_WAYLAND", "");
-                    raylib.linkSystemLibrary("EGL");
-                    raylib.linkSystemLibrary("wayland-client");
-                    raylib.linkSystemLibrary("xkbcommon");
+                    raylib.root_module.linkSystemLibrary("EGL", .{});
+                    raylib.root_module.linkSystemLibrary("wayland-client", .{});
+                    raylib.root_module.linkSystemLibrary("xkbcommon", .{});
                     waylandGenerate(b, raylib, "wayland.xml", "wayland-client-protocol");
                     waylandGenerate(b, raylib, "xdg-shell.xml", "xdg-shell-client-protocol");
                     waylandGenerate(b, raylib, "xdg-decoration-unstable-v1.xml", "xdg-decoration-unstable-v1-client-protocol");
@@ -327,25 +349,25 @@ pub fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize:
         },
         .freebsd, .openbsd, .netbsd, .dragonfly => {
             try c_source_files.append(b.allocator, "rglfw.c");
-            raylib.linkSystemLibrary("GL");
-            raylib.linkSystemLibrary("rt");
-            raylib.linkSystemLibrary("dl");
-            raylib.linkSystemLibrary("m");
-            raylib.linkSystemLibrary("X11");
-            raylib.linkSystemLibrary("Xrandr");
-            raylib.linkSystemLibrary("Xinerama");
-            raylib.linkSystemLibrary("Xi");
-            raylib.linkSystemLibrary("Xxf86vm");
-            raylib.linkSystemLibrary("Xcursor");
+            raylib.root_module.linkSystemLibrary("GL", .{});
+            raylib.root_module.linkSystemLibrary("rt", .{});
+            raylib.root_module.linkSystemLibrary("dl", .{});
+            raylib.root_module.linkSystemLibrary("m", .{});
+            raylib.root_module.linkSystemLibrary("X11", .{});
+            raylib.root_module.linkSystemLibrary("Xrandr", .{});
+            raylib.root_module.linkSystemLibrary("Xinerama", .{});
+            raylib.root_module.linkSystemLibrary("Xi", .{});
+            raylib.root_module.linkSystemLibrary("Xxf86vm", .{});
+            raylib.root_module.linkSystemLibrary("Xcursor", .{});
 
             setDesktopPlatform(raylib, options.platform);
         },
         .macos => {
             // Include xcode_frameworks for cross compilation
             if (b.lazyDependency("xcode_frameworks", .{})) |dep| {
-                raylib.addSystemFrameworkPath(dep.path("Frameworks"));
-                raylib.addSystemIncludePath(dep.path("include"));
-                raylib.addLibraryPath(dep.path("lib"));
+                raylib.root_module.addSystemFrameworkPath(dep.path("Frameworks"));
+                raylib.root_module.addSystemIncludePath(dep.path("include"));
+                raylib.root_module.addLibraryPath(dep.path("lib"));
             }
 
             // On macos rglfw.c include Objective-C files.
@@ -355,26 +377,18 @@ pub fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize:
                 .flags = raylib_flags_arr.items,
             });
             _ = raylib_flags_arr.pop();
-            raylib.linkFramework("Foundation");
-            raylib.linkFramework("CoreServices");
-            raylib.linkFramework("CoreGraphics");
-            raylib.linkFramework("AppKit");
-            raylib.linkFramework("IOKit");
+            raylib.root_module.linkFramework("Foundation", .{});
+            raylib.root_module.linkFramework("CoreServices", .{});
+            raylib.root_module.linkFramework("CoreGraphics", .{});
+            raylib.root_module.linkFramework("AppKit", .{});
+            raylib.root_module.linkFramework("IOKit", .{});
 
             setDesktopPlatform(raylib, options.platform);
         },
         .emscripten => {
-            if (b.lazyDependency("emsdk", .{})) |dep| {
-                if (try emSdkSetupStep(b, dep)) |emSdkStep| {
-                    raylib.step.dependOn(&emSdkStep.step);
-                }
-
-                raylib.addIncludePath(dep.path("upstream/emscripten/cache/sysroot/include"));
-            }
-
             raylib.root_module.addCMacro("PLATFORM_WEB", "");
             if (options.opengl_version == .auto) {
-                raylib.root_module.addCMacro("GRAPHICS_API_OPENGL_ES2", "");
+                raylib.root_module.addCMacro("GRAPHICS_API_OPENGL_ES3", "");
             }
         },
         else => {
@@ -396,9 +410,9 @@ pub fn addRaygui(b: *std.Build, raylib: *std.Build.Step.Compile, raygui_dep: *st
     raylib.step.dependOn(&gen_step.step);
 
     const raygui_c_path = gen_step.add("raygui.c", "#define RAYGUI_IMPLEMENTATION\n#include \"raygui.h\"\n");
-    raylib.addCSourceFile(.{ .file = raygui_c_path });
-    raylib.addIncludePath(raygui_dep.path("src"));
-    raylib.addIncludePath(raylib_dep.path("src"));
+    raylib.root_module.addCSourceFile(.{ .file = raygui_c_path });
+    raylib.root_module.addIncludePath(raygui_dep.path("src"));
+    raylib.root_module.addIncludePath(raylib_dep.path("src"));
 
     raylib.installHeader(raygui_dep.path("src/raygui.h"), "raygui.h");
 }
@@ -410,7 +424,7 @@ pub const Options = struct {
     rtext: bool = true,
     rtextures: bool = true,
     platform: PlatformBackend = .glfw,
-    shared: bool = false,
+    linkage: std.builtin.LinkMode = .static,
     linux_display_backend: LinuxDisplayBackend = .Both,
     opengl_version: OpenglVersion = .auto,
     android_ndk: []const u8 = "",
@@ -428,7 +442,7 @@ pub const Options = struct {
             .rtext = b.option(bool, "rtext", "Compile with text support") orelse defaults.rtext,
             .rtextures = b.option(bool, "rtextures", "Compile with textures support") orelse defaults.rtextures,
             .rshapes = b.option(bool, "rshapes", "Compile with shapes support") orelse defaults.rshapes,
-            .shared = b.option(bool, "shared", "Compile as shared library") orelse defaults.shared,
+            .linkage = b.option(std.builtin.LinkMode, "linkage", "Compile as shared or static library") orelse defaults.linkage,
             .linux_display_backend = b.option(LinuxDisplayBackend, "linux_display_backend", "Linux display backend to use") orelse defaults.linux_display_backend,
             .opengl_version = b.option(OpenglVersion, "opengl_version", "OpenGL version to use") orelse defaults.opengl_version,
             .config = b.option([]const u8, "config", "Compile with custom define macros overriding config.h") orelse &.{},
@@ -475,14 +489,7 @@ pub const PlatformBackend = enum {
 };
 
 pub fn build(b: *std.Build) !void {
-    // Standard target options allows the person running `zig build` to choose
-    // what target to build for. Here we do not override the defaults, which
-    // means any target is allowed, and the default is native. Other options
-    // for restricting supported target set are available.
     const target = b.standardTargetOptions(.{});
-    // Standard optimization options allow the person running `zig build` to select
-    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
-    // set a preferred release mode, allowing the user to decide how to optimize.
     const optimize = b.standardOptimizeOption(.{});
 
     const lib = try compileRaylib(b, target, optimize, Options.getOptions(b));
@@ -505,29 +512,6 @@ pub fn build(b: *std.Build) !void {
     examples.dependOn(try addExamples("textures", b, target, optimize, lib));
 }
 
-fn waylandGenerate(
-    b: *std.Build,
-    raylib: *std.Build.Step.Compile,
-    comptime protocol: []const u8,
-    comptime basename: []const u8,
-) void {
-    const waylandDir = "src/external/glfw/deps/wayland";
-    const protocolDir = b.pathJoin(&.{ waylandDir, protocol });
-    const clientHeader = basename ++ ".h";
-    const privateCode = basename ++ "-code.h";
-
-    const client_step = b.addSystemCommand(&.{ "wayland-scanner", "client-header" });
-    client_step.addFileArg(b.path(protocolDir));
-    raylib.addIncludePath(client_step.addOutputFileArg(clientHeader).dirname());
-
-    const private_step = b.addSystemCommand(&.{ "wayland-scanner", "private-code" });
-    private_step.addFileArg(b.path(protocolDir));
-    raylib.addIncludePath(private_step.addOutputFileArg(privateCode).dirname());
-
-    raylib.step.dependOn(&client_step.step);
-    raylib.step.dependOn(&private_step.step);
-}
-
 fn addExamples(
     comptime module: []const u8,
     b: *std.Build,
@@ -537,9 +521,8 @@ fn addExamples(
 ) !*std.Build.Step {
     const all = b.step(module, "All " ++ module ++ " examples");
     const module_subpath = b.pathJoin(&.{ "examples", module });
-    const module_resources = b.pathJoin(&.{ module_subpath, "resources@resources" });
     var dir = try std.fs.cwd().openDir(b.pathFromRoot(module_subpath), .{ .iterate = true });
-    defer if (comptime builtin.zig_version.minor >= 12) dir.close();
+    defer dir.close();
 
     var iter = dir.iterate();
     while (try iter.next()) |entry| {
@@ -551,20 +534,21 @@ fn addExamples(
         // zig's mingw headers do not include pthread.h
         if (std.mem.eql(u8, "core_loading_thread", name) and target.result.os.tag == .windows) continue;
 
+        const exe_mod = b.createModule(.{
+            .target = target,
+            .optimize = optimize,
+        });
+        exe_mod.addCSourceFile(.{ .file = b.path(path), .flags = &.{} });
+        exe_mod.linkLibrary(raylib);
+
+        const run_step = b.step(name, name);
+
         if (target.result.os.tag == .emscripten) {
-            const exe_lib = b.addLibrary(.{
+            const wasm = b.addLibrary(.{
                 .name = name,
                 .linkage = .static,
-                .root_module = b.createModule(.{
-                    .target = target,
-                    .optimize = optimize,
-                }),
+                .root_module = exe_mod,
             });
-            exe_lib.addCSourceFile(.{
-                .file = b.path(path),
-                .flags = &.{},
-            });
-            exe_lib.linkLibC();
 
             if (std.mem.eql(u8, name, "rlgl_standalone")) {
                 //TODO: Make rlgl_standalone example work
@@ -575,139 +559,121 @@ fn addExamples(
                 continue;
             }
 
-            exe_lib.linkLibrary(raylib);
-
-            // Include emscripten for cross compilation
-            if (b.lazyDependency("emsdk", .{})) |emsdk_dep| {
-                if (try emSdkSetupStep(b, emsdk_dep)) |emSdkStep| {
-                    exe_lib.step.dependOn(&emSdkStep.step);
-                }
-
-                exe_lib.addIncludePath(emsdk_dep.path("upstream/emscripten/cache/sysroot/include"));
-
-                // Create the output directory because emcc can't do it.
-                const emccOutputDirExample = b.pathJoin(&.{ emccOutputDir, name, std.fs.path.sep_str });
-                const mkdir_command = switch (builtin.os.tag) {
-                    .windows => b.addSystemCommand(&.{ "cmd.exe", "/c", "if", "not", "exist", emccOutputDirExample, "mkdir", emccOutputDirExample }),
-                    else => b.addSystemCommand(&.{ "mkdir", "-p", emccOutputDirExample }),
-                };
-
-                const emcc_exe = switch (builtin.os.tag) {
-                    .windows => "emcc.bat",
-                    else => "emcc",
-                };
-
-                const emcc_exe_path = b.pathJoin(&.{ emsdk_dep.path("upstream/emscripten").getPath(b), emcc_exe });
-                const emcc_command = b.addSystemCommand(&[_][]const u8{emcc_exe_path});
-                emcc_command.step.dependOn(&mkdir_command.step);
-                const emccOutputDirExampleWithFile = b.pathJoin(&.{ emccOutputDir, name, std.fs.path.sep_str, emccOutputFile });
-                emcc_command.addArgs(&[_][]const u8{
-                    "-o",
-                    emccOutputDirExampleWithFile,
-                    "-sFULL-ES3=1",
-                    "-sUSE_GLFW=3",
-                    "-sSTACK_OVERFLOW_CHECK=1",
-                    "-sEXPORTED_RUNTIME_METHODS=['requestFullscreen']",
-                    "-sASYNCIFY",
-                    "-O0",
-                    "--emrun",
-                    "--preload-file",
-                    module_resources,
-                    "--shell-file",
-                    b.path("src/shell.html").getPath(b),
-                });
-
-                const link_items: []const *std.Build.Step.Compile = &.{
-                    raylib,
-                    exe_lib,
-                };
-                for (link_items) |item| {
-                    emcc_command.addFileArg(item.getEmittedBin());
-                    emcc_command.step.dependOn(&item.step);
-                }
-
-                const run_step = try emscriptenRunStep(b, emsdk_dep, emccOutputDirExampleWithFile);
-                run_step.step.dependOn(&emcc_command.step);
-                run_step.addArg("--no_browser");
-                const run_option = b.step(name, name);
+            const emcc_flags = emsdk.emccDefaultFlags(b.allocator, .{ .optimize = optimize });
+            const emcc_settings = emsdk.emccDefaultSettings(b.allocator, .{ .optimize = optimize });
+
+            const install_dir: std.Build.InstallDir = .{ .custom = "htmlout" };
+            const emcc_step = emsdk.emccStep(b, raylib, wasm, .{
+                .optimize = optimize,
+                .flags = emcc_flags,
+                .settings = emcc_settings,
+                .shell_file_path = b.path("src/shell.html"),
+                .embed_paths = &.{
+                    .{
+                        .src_path = b.pathJoin(&.{ module_subpath, "resources" }),
+                        .virtual_path = "resources",
+                    },
+                },
+                .install_dir = install_dir,
+            });
 
-                run_option.dependOn(&run_step.step);
+            const html_filename = try std.fmt.allocPrint(b.allocator, "{s}.html", .{wasm.name});
+            const emrun_step = emsdk.emrunStep(
+                b,
+                b.getInstallPath(install_dir, html_filename),
+                &.{"--no_browser"},
+            );
+            emrun_step.dependOn(emcc_step);
 
-                all.dependOn(&emcc_command.step);
-            }
+            run_step.dependOn(emrun_step);
+            all.dependOn(emcc_step);
         } else {
-            const exe = b.addExecutable(.{
-                .name = name,
-                .root_module = b.createModule(.{
-                    .target = target,
-                    .optimize = optimize,
-                }),
-            });
-            exe.addCSourceFile(.{ .file = b.path(path), .flags = &.{} });
-            exe.linkLibC();
-
             // special examples that test using these external dependencies directly
             // alongside raylib
             if (std.mem.eql(u8, name, "rlgl_standalone")) {
-                exe.addIncludePath(b.path("src"));
-                exe.addIncludePath(b.path("src/external/glfw/include"));
+                exe_mod.addIncludePath(b.path("src"));
+                exe_mod.addIncludePath(b.path("src/external/glfw/include"));
                 if (!hasCSource(raylib.root_module, "rglfw.c")) {
-                    exe.addCSourceFile(.{ .file = b.path("src/rglfw.c"), .flags = &.{} });
+                    exe_mod.addCSourceFile(.{ .file = b.path("src/rglfw.c"), .flags = &.{} });
                 }
             }
             if (std.mem.eql(u8, name, "raylib_opengl_interop")) {
-                exe.addIncludePath(b.path("src/external"));
+                exe_mod.addIncludePath(b.path("src/external"));
             }
 
-            exe.linkLibrary(raylib);
-
             switch (target.result.os.tag) {
                 .windows => {
-                    exe.linkSystemLibrary("winmm");
-                    exe.linkSystemLibrary("gdi32");
-                    exe.linkSystemLibrary("opengl32");
+                    exe_mod.linkSystemLibrary("winmm", .{});
+                    exe_mod.linkSystemLibrary("gdi32", .{});
+                    exe_mod.linkSystemLibrary("opengl32", .{});
 
-                    exe.root_module.addCMacro("PLATFORM_DESKTOP", "");
+                    exe_mod.addCMacro("PLATFORM_DESKTOP", "");
                 },
                 .linux => {
-                    exe.linkSystemLibrary("GL");
-                    exe.linkSystemLibrary("rt");
-                    exe.linkSystemLibrary("dl");
-                    exe.linkSystemLibrary("m");
-                    exe.linkSystemLibrary("X11");
+                    exe_mod.linkSystemLibrary("GL", .{});
+                    exe_mod.linkSystemLibrary("rt", .{});
+                    exe_mod.linkSystemLibrary("dl", .{});
+                    exe_mod.linkSystemLibrary("m", .{});
+                    exe_mod.linkSystemLibrary("X11", .{});
 
-                    exe.root_module.addCMacro("PLATFORM_DESKTOP", "");
+                    exe_mod.addCMacro("PLATFORM_DESKTOP", "");
                 },
                 .macos => {
-                    exe.linkFramework("Foundation");
-                    exe.linkFramework("Cocoa");
-                    exe.linkFramework("OpenGL");
-                    exe.linkFramework("CoreAudio");
-                    exe.linkFramework("CoreVideo");
-                    exe.linkFramework("IOKit");
-
-                    exe.root_module.addCMacro("PLATFORM_DESKTOP", "");
+                    exe_mod.linkFramework("Foundation", .{});
+                    exe_mod.linkFramework("Cocoa", .{});
+                    exe_mod.linkFramework("OpenGL", .{});
+                    exe_mod.linkFramework("CoreAudio", .{});
+                    exe_mod.linkFramework("CoreVideo", .{});
+                    exe_mod.linkFramework("IOKit", .{});
+
+                    exe_mod.addCMacro("PLATFORM_DESKTOP", "");
                 },
                 else => {
                     @panic("Unsupported OS");
                 },
             }
 
+            const exe = b.addExecutable(.{
+                .name = name,
+                .root_module = exe_mod,
+            });
+
             const install_cmd = b.addInstallArtifact(exe, .{});
 
             const run_cmd = b.addRunArtifact(exe);
             run_cmd.cwd = b.path(module_subpath);
             run_cmd.step.dependOn(&install_cmd.step);
 
-            const run_step = b.step(name, name);
             run_step.dependOn(&run_cmd.step);
-
             all.dependOn(&install_cmd.step);
         }
     }
     return all;
 }
 
+fn waylandGenerate(
+    b: *std.Build,
+    raylib: *std.Build.Step.Compile,
+    comptime protocol: []const u8,
+    comptime basename: []const u8,
+) void {
+    const waylandDir = "src/external/glfw/deps/wayland";
+    const protocolDir = b.pathJoin(&.{ waylandDir, protocol });
+    const clientHeader = basename ++ ".h";
+    const privateCode = basename ++ "-code.h";
+
+    const client_step = b.addSystemCommand(&.{ "wayland-scanner", "client-header" });
+    client_step.addFileArg(b.path(protocolDir));
+    raylib.root_module.addIncludePath(client_step.addOutputFileArg(clientHeader).dirname());
+
+    const private_step = b.addSystemCommand(&.{ "wayland-scanner", "private-code" });
+    private_step.addFileArg(b.path(protocolDir));
+    raylib.root_module.addIncludePath(private_step.addOutputFileArg(privateCode).dirname());
+
+    raylib.step.dependOn(&client_step.step);
+    raylib.step.dependOn(&private_step.step);
+}
+
 fn hasCSource(module: *std.Build.Module, name: []const u8) bool {
     for (module.link_objects.items) |o| switch (o) {
         .c_source_file => |c| if (switch (c.file) {

+ 5 - 2
build.zig.zon

@@ -1,6 +1,6 @@
 .{
     .name = .raylib,
-    .version = "5.5.0",
+    .version = "5.6.0-dev",
     .minimum_zig_version = "0.15.1",
 
     .fingerprint = 0x13035e5cb8bc1ac2, // Changing this has security and trust implications.
@@ -14,7 +14,10 @@
         .emsdk = .{
             .url = "git+https://github.com/emscripten-core/emsdk#4.0.9",
             .hash = "N-V-__8AAJl1DwBezhYo_VE6f53mPVm00R-Fk28NPW7P14EQ",
-            .lazy = true,
+        },
+        .zemscripten = .{
+            .url = "git+https://github.com/zig-gamedev/zemscripten#3fa4b778852226c7346bdcc3c1486e875a9a6d02",
+            .hash = "zemscripten-0.2.0-dev-sRlDqApRAACspTbAZnuNKWIzfWzSYgYkb2nWAXZ-tqqt",
         },
     },