1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
const std = @import("std");
// Unified build: `zig build` orchestrates the whole desktop stack.
// 1. raylib -> vendor/raylib/src/libraylib.a (make, once)
// 2. RmlUi -> vendor/rmlui/build-static/librmlui.a (cmake, once)
// 3. mruby -> vendor/mruby/build/host/lib/libmruby.a (rake, every build:
// embeds our Rl::/Rml:: bindings, so it tracks binding changes)
// 4. compile src/main.c and link everything.
//
// The vendored libs (1,2) are guarded so they only build when missing; mruby (3)
// runs every time (rake is itself incremental). See BUILDING.md.
//
// Desktop window/GL backend is raylib's SDL2 backend (PLATFORM_DESKTOP_SDL),
// not GLFW: GLFW 3.4's Wayland drag-and-drop is broken (crashes on drag) and
// its X11 path segfaults on WSLg. SDL is robust on both real Wayland (labwc)
// and WSLg. Requires SDL2 dev installed system-wide. Web uses Emscripten/GLFW.
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// --- dependency build steps (system commands) ------------------------------
// Desktop raylib -> build/desktop/libraylib.a. raylib shares .o files in src/
// across platforms, so `make clean` first to avoid picking up wasm objects
// from a prior web build. Guarded on the desktop lib so it only builds once.
//
// We use raylib's SDL backend (PLATFORM_DESKTOP_SDL) instead of the default
// GLFW backend. GLFW 3.4 (vendored in raylib 6.0) has broken drag-and-drop on
// Wayland (the wl_data_offer source_actions/action listeners are NULL ->
// libwayland wl_abort()s when a file is dragged over the window), and its X11
// backend segfaults on WSLg (Mesa GLX). SDL's own window/egl/drag-drop code is
// mature on both real Wayland (labwc) and WSLg, so one backend covers both
// targets with no vendor patches. Requires SDL2 dev installed system-wide
// (`pacman -S sdl2` / `sdl2-compat`). Web still uses Emscripten/GLFW (separate).
const raylib_lib = b.addSystemCommand(&.{
"sh", "-c",
"r=\"$PWD\"; mkdir -p \"$r/build/desktop\"; " ++
"[ -f \"$r/build/desktop/libraylib.a\" ] || (" ++
"cd vendor/raylib/src && make clean >/dev/null 2>&1; " ++
"make PLATFORM=PLATFORM_DESKTOP_SDL RAYLIB_LIBTYPE=STATIC " ++
"SDL_INCLUDE_PATH=/usr/include/SDL2 SDL_LIBRARY_PATH=/usr/lib -j4 " ++
"RAYLIB_RELEASE_PATH=\"$r/build/desktop\")",
});
// flecs (ECS): compile the single-file amalgamation to a static lib once.
const flecs_lib = b.addSystemCommand(&.{
"sh", "-c",
"r=\"$PWD\"; mkdir -p \"$r/build/desktop\"; " ++
"[ -f \"$r/build/desktop/libflecs.a\" ] || (" ++
"cc -c -O2 -std=gnu99 -DNDEBUG -I vendor/flecs/distr " ++
"vendor/flecs/distr/flecs.c -o build/desktop/flecs.o && " ++
"ar rcs build/desktop/libflecs.a build/desktop/flecs.o)",
});
// Jolt Physics via joltc (C API). CMake builds libjoltc.a + libJolt.a, which
// we then MERGE into a single build/desktop/libjoltphysics.a. The merge is
// important: libmruby (Jolt:: bindings) -> libjoltc -> libJolt is a 3-archive
// chain that lld's single pass won't resolve; one combined archive (like
// libflecs.a) resolves all cross-references. Jolt v5.5.0 is taken locally from
// vendor/JoltPhysics; profiler/debug-renderer are disabled to stay lean.
const jolt_lib = b.addSystemCommand(&.{
"sh", "-c",
"mkdir -p build/desktop; [ -f build/desktop/libjoltphysics.a ] || (" ++
"[ -f vendor/joltc/build-static/lib/libjoltc.a ] || (" ++
"cmake -S vendor/joltc -B vendor/joltc/build-static " ++
"-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DJPH_BUILD_SHARED=OFF " ++
"-DJPH_SAMPLES=OFF -DJPH_TESTS=OFF -DJPH_INSTALL=OFF " ++
// LTO OFF: Jolt defaults to GCC -flto, whose GIMPLE-bytecode objects
// lld (zig's linker) cannot link. OFF produces native objects.
"-DINTERPROCEDURAL_OPTIMIZATION=OFF " ++
"-DDEBUG_RENDERER_IN_DEBUG_AND_RELEASE=OFF -DDEBUG_RENDERER_IN_DISTRIBUTION=OFF " ++
"-DPROFILER_IN_DEBUG_AND_RELEASE=OFF && " ++
"cmake --build vendor/joltc/build-static --target joltc -j4); " ++
"rm -rf build/desktop/jolt_obj && mkdir -p build/desktop/jolt_obj && " ++
"(cd build/desktop/jolt_obj && " ++
"ar x ../../../vendor/joltc/build-static/lib/libjoltc.a && " ++
"ar x ../../../vendor/joltc/build-static/lib/libJolt.a && " ++
"ar rcs ../libjoltphysics.a *.o) && rm -rf build/desktop/jolt_obj)",
});
const rmlui_lib = b.addSystemCommand(&.{
"sh", "-c",
"[ -f vendor/rmlui/build-static/librmlui.a ] || (" ++
"cmake -S vendor/rmlui -B vendor/rmlui/build-static " ++
"-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF " ++
"-DRMLUI_SAMPLES=OFF -DRMLUI_LUA_BINDINGS=OFF -DRMLUI_FONT_ENGINE=freetype && " ++
"cmake --build vendor/rmlui/build-static --target rmlui_core -j4)",
});
// mruby (with our mrbgems). Prepend the user gem bin so `rake` resolves to the
// Linux gem (not a Windows rake on /mnt/c under WSL). Target the .a path so
// mruby's own CLI tools (which don't link raylib) aren't built.
const mruby_lib = b.addSystemCommand(&.{
"sh", "-c",
"export PATH=\"$(ruby -e 'puts Gem.user_dir')/bin:$PATH\"; " ++
"export JAMSTACK_ROOT=\"$PWD\"; " ++
"export MRUBY_CONFIG=\"$PWD/build_config.rb\"; " ++
"cd vendor/mruby && rake \"$JAMSTACK_ROOT/vendor/mruby/build/host/lib/libmruby.a\"",
});
// --- the game executable ---------------------------------------------------
const exe = b.addExecutable(.{
.name = "game",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
exe.root_module.addCSourceFile(.{
.file = b.path("src/main.c"),
.flags = &.{ "-std=c11", "-Wall", "-DMRB_INT64" },
});
// mruby headers + static lib (embeds the Rl::/Rml:: bindings mrbgems)
exe.root_module.addIncludePath(b.path("vendor/mruby/include"));
exe.root_module.addObjectFile(b.path("vendor/mruby/build/host/lib/libmruby.a"));
// raylib static lib (desktop)
exe.root_module.addIncludePath(b.path("vendor/raylib/src"));
exe.root_module.addObjectFile(b.path("build/desktop/libraylib.a"));
// flecs static lib (desktop). libmruby.a holds the Flecs:: bindings that
// reference these symbols, so it must precede this in link order (it does).
exe.root_module.addIncludePath(b.path("vendor/flecs/distr"));
exe.root_module.addObjectFile(b.path("build/desktop/libflecs.a"));
// Jolt Physics (C++): single merged archive (joltc C API + Jolt impl).
exe.root_module.addIncludePath(b.path("vendor/joltc/include"));
exe.root_module.addObjectFile(b.path("build/desktop/libjoltphysics.a"));
// RmlUi static lib (C++) + its deps. libmruby.a (above) holds the Rml::
// bindings that reference these symbols, so it must precede this in link order.
exe.root_module.addObjectFile(b.path("vendor/rmlui/build-static/librmlui.a"));
exe.root_module.linkSystemLibrary("freetype", .{});
// RmlUi is built with GNU libstdc++; link it directly. (Do NOT use
// linkSystemLibrary("stdc++") — zig 0.16 substitutes its own LLVM libc++,
// which lacks the libstdc++ ABI symbols RmlUi needs.)
exe.root_module.addObjectFile(.{ .cwd_relative = "/usr/lib/libstdc++.so" });
// GCC unwinder (_Unwind_Resume) — mruby is built with C++ exceptions enabled
// (MRB_USE_CXX_EXCEPTION) because a C++ mrbgem (rmlui) is present.
exe.root_module.addObjectFile(.{ .cwd_relative = "/usr/lib/libgcc_s.so.1" });
// system libraries needed by raylib (desktop SDL2 backend) and mruby
exe.root_module.linkSystemLibrary("SDL2", .{});
exe.root_module.linkSystemLibrary("GL", .{});
exe.root_module.linkSystemLibrary("m", .{});
exe.root_module.linkSystemLibrary("pthread", .{});
exe.root_module.linkSystemLibrary("dl", .{});
exe.root_module.linkSystemLibrary("rt", .{});
// link only after the dependency libs are built
exe.step.dependOn(&raylib_lib.step);
exe.step.dependOn(&flecs_lib.step);
exe.step.dependOn(&jolt_lib.step);
exe.step.dependOn(&rmlui_lib.step);
exe.step.dependOn(&mruby_lib.step);
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
run_cmd.setCwd(b.path(".")); // so game/main.rb resolves
if (b.args) |args| run_cmd.addArgs(args);
const run_step = b.step("run", "Build and run the game");
run_step.dependOn(&run_cmd.step);
}
|