game-2026-01-18/build.zig
2026-01-18 07:06:01 +02:00

305 lines
10 KiB
Zig

const std = @import("std");
const sokol = @import("sokol");
const builtin = @import("builtin");
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const has_imgui = b.option(bool, "imgui", "ImGui integration") orelse (optimize == .Debug);
var has_tracy = b.option(bool, "tracy", "Tracy integration") orelse (optimize == .Debug);
const has_console = b.option(bool, "console", "Show console (Window only)") orelse (optimize == .Debug);
const isWasm = target.result.cpu.arch.isWasm();
if (isWasm) {
has_tracy = false;
}
const mod_main = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.link_libc = true
});
const dep_sokol = b.dependency("sokol", .{
.target = target,
.optimize = optimize,
.with_sokol_imgui = has_imgui,
});
mod_main.linkLibrary(dep_sokol.artifact("sokol_clib"));
mod_main.addImport("sokol", dep_sokol.module("sokol"));
if (has_imgui) {
if (b.lazyDependency("cimgui", .{
.target = target,
.optimize = optimize,
})) |dep_cimgui| {
const cimgui = b.lazyImport(@This(), "cimgui").?;
const cimgui_conf = cimgui.getConfig(false);
mod_main.addImport("cimgui", dep_cimgui.module(cimgui_conf.module_name));
dep_sokol.artifact("sokol_clib").addIncludePath(dep_cimgui.path(cimgui_conf.include_dir));
}
}
const dep_tracy = b.dependency("tracy", .{
.target = target,
.optimize = optimize,
.tracy_enable = has_tracy,
.tracy_only_localhost = true
});
if (has_tracy) {
mod_main.linkLibrary(dep_tracy.artifact("tracy"));
}
mod_main.addImport("tracy", dep_tracy.module("tracy"));
const dep_tiled = b.dependency("tiled", .{});
mod_main.addImport("tiled", dep_tiled.module("tiled"));
const dep_stb = b.dependency("stb", .{});
mod_main.addImport("stb_image", dep_stb.module("stb_image"));
mod_main.addImport("stb_vorbis", dep_stb.module("stb_vorbis"));
const dep_fontstash_c = b.dependency("fontstash_c", .{});
mod_main.addIncludePath(dep_fontstash_c.path("src"));
const dep_sokol_c = b.dependency("sokol_c", .{});
{
var cflags_buffer: [64][]const u8 = undefined;
var cflags = std.ArrayListUnmanaged([]const u8).initBuffer(&cflags_buffer);
switch (sokol.resolveSokolBackend(.auto, target.result)) {
.d3d11 => try cflags.appendBounded("-DSOKOL_D3D11"),
.metal => try cflags.appendBounded("-DSOKOL_METAL"),
.gl => try cflags.appendBounded("-DSOKOL_GLCORE"),
.gles3 => try cflags.appendBounded("-DSOKOL_GLES3"),
.wgpu => try cflags.appendBounded("-DSOKOL_WGPU"),
else => @panic("unknown sokol backend"),
}
mod_main.addIncludePath(dep_sokol_c.path("util"));
mod_main.addCSourceFile(.{
.file = b.path("src/engine/fontstash/sokol_fontstash_impl.c"),
.flags = cflags.items
});
}
// TODO:
// const sdl = b.dependency("sdl", .{
// .optimize = optimize,
// .target = target,
// .linkage = .static,
// .default_target_config = !isWasm
// });
// mod_main.linkLibrary(sdl.artifact("SDL3"));
// if (isWasm) {
// // TODO: Define buid config for wasm
// }
var options = b.addOptions();
options.addOption(bool, "has_imgui", has_imgui);
options.addOption(bool, "has_tracy", has_tracy);
mod_main.addOptions("build_options", options);
// from here on different handling for native vs wasm builds
if (target.result.cpu.arch.isWasm()) {
try buildWasm(b, .{
.name = "sokol_template",
.mod_main = mod_main,
.dep_sokol = dep_sokol,
});
} else {
try buildNative(b, "sokol_template", mod_main, has_console);
}
}
fn buildNative(b: *std.Build, name: []const u8, mod: *std.Build.Module, has_console: bool) !void {
const exe = b.addExecutable(.{
.name = name,
.root_module = mod
});
const target = mod.resolved_target.?;
if (target.result.os.tag == .windows) {
exe.subsystem = if (has_console) .Console else .Windows;
const png_to_icon_tool = b.addExecutable(.{
.name = "png-to-icon",
.root_module = b.createModule(.{
.target = b.graph.host,
.root_source_file = b.path("tools/png-to-icon.zig"),
}),
});
const dep_stb_image = b.dependency("stb_image", .{});
png_to_icon_tool.root_module.addImport("stb_image", dep_stb_image.module("stb_image"));
const png_to_icon_step = b.addRunArtifact(png_to_icon_tool);
png_to_icon_step.addFileArg(b.path("src/assets/icon.png"));
const icon_file = png_to_icon_step.addOutputFileArg("icon.ico");
const add_icon_step = AddExecutableIcon.init(exe, icon_file);
exe.step.dependOn(&add_icon_step.step);
}
b.installArtifact(exe);
{
const run_step = b.step("run", "Run game");
const run_cmd = b.addRunArtifact(exe);
run_step.dependOn(&run_cmd.step);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
}
{
const exe_tests = b.addTest(.{
.root_module = exe.root_module,
});
const run_exe_tests = b.addRunArtifact(exe_tests);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_exe_tests.step);
}
}
const BuildWasmOptions = struct {
name: []const u8,
mod_main: *std.Build.Module,
dep_sokol: *std.Build.Dependency,
};
fn patchWasmIncludeDirs(
module: *std.Build.Module,
path: std.Build.LazyPath,
depend_step: *std.Build.Step
) void {
if (module.link_libc != null and module.link_libc.?) {
// need to inject the Emscripten system header include path into
// the cimgui C library otherwise the C/C++ code won't find
// C stdlib headers
module.addSystemIncludePath(path);
}
for (module.import_table.values()) |imported_module| {
patchWasmIncludeDirs(imported_module, path, depend_step);
}
for (module.link_objects.items) |link_object| {
if (link_object != .other_step) {
continue;
}
const lib = link_object.other_step;
if (&lib.step == depend_step) {
continue;
}
if (lib.root_module.link_libc != null and lib.root_module.link_libc.?) {
// need to inject the Emscripten system header include path into
// the cimgui C library otherwise the C/C++ code won't find
// C stdlib headers
lib.root_module.addSystemIncludePath(path);
// all C libraries need to depend on the sokol library, when building for
// WASM this makes sure that the Emscripten SDK has been setup before
// C compilation is attempted (since the sokol C library depends on the
// Emscripten SDK setup step)
lib.step.dependOn(depend_step);
}
patchWasmIncludeDirs(lib.root_module, path, depend_step);
}
}
fn buildWasm(b: *std.Build, opts: BuildWasmOptions) !void {
opts.mod_main.sanitize_c = .off;
// build the main file into a library, this is because the WASM 'exe'
// needs to be linked in a separate build step with the Emscripten linker
const main_lib = b.addLibrary(.{
.name = "index",
.root_module = opts.mod_main,
});
const dep_emsdk = opts.dep_sokol.builder.dependency("emsdk", .{});
patchWasmIncludeDirs(
opts.mod_main,
dep_emsdk.path("upstream/emscripten/cache/sysroot/include"),
&(opts.dep_sokol.artifact("sokol_clib").step)
);
// create a build step which invokes the Emscripten linker
const link_step = try sokol.emLinkStep(b, .{
.lib_main = main_lib,
.target = opts.mod_main.resolved_target.?,
.optimize = opts.mod_main.optimize.?,
.emsdk = dep_emsdk,
.use_webgl2 = true,
.use_emmalloc = true,
.use_filesystem = false,
.shell_file_path = b.path("src/engine/shell.html"),
});
// attach to default target
b.getInstallStep().dependOn(&link_step.step);
// ...and a special run step to start the web build output via 'emrun'
const run = sokol.emRunStep(b, .{ .name = "index", .emsdk = dep_emsdk });
run.step.dependOn(&link_step.step);
b.step("run", "Run game").dependOn(&run.step);
// TODO: Create a zip archive of all of the files. Would be useful for easier itch.io upload
}
const AddExecutableIcon = struct {
obj: *std.Build.Step.Compile,
step: std.Build.Step,
icon_file: std.Build.LazyPath,
resource_file: std.Build.LazyPath,
fn init(obj: *std.Build.Step.Compile, icon_file: std.Build.LazyPath) *AddExecutableIcon {
const b = obj.step.owner;
const self = b.allocator.create(AddExecutableIcon) catch @panic("OOM");
self.obj = obj;
self.step = std.Build.Step.init(.{
.id = .custom,
.name = "add executable icon",
.owner = b,
.makeFn = make
});
self.icon_file = icon_file;
icon_file.addStepDependencies(&self.step);
const write_files = b.addWriteFiles();
self.resource_file = write_files.add("resource-file.rc", "");
self.step.dependOn(&write_files.step);
self.obj.addWin32ResourceFile(.{
.file = self.resource_file,
});
return self;
}
fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void {
const b = step.owner;
const self: *AddExecutableIcon = @fieldParentPtr("step", step);
const resource_file = try std.fs.cwd().createFile(self.resource_file.getPath(b), .{ });
defer resource_file.close();
const relative_icon_path = try std.fs.path.relative(
b.allocator,
self.resource_file.dirname().getPath(b),
self.icon_file.getPath(b)
);
std.mem.replaceScalar(u8, relative_icon_path, '\\', '/');
try resource_file.writeAll("IDI_ICON ICON \"");
try resource_file.writeAll(relative_icon_path);
try resource_file.writeAll("\"");
}
};