const std = @import("std"); const sokol = @import("sokol"); pub fn build(b: *std.Build) !void { _ = b; // autofix } const InitOptions = struct { name: []const u8, root_module: *std.Build.Module, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, dep_engine: *std.Build.Dependency, statically_linked: ?bool = null, hot_reload: ?bool = null, has_imgui: ?bool = null, has_tracy: ?bool = null, win32_has_console: ?bool = null, win32_png_icon: ?std.Build.LazyPath = null }; pub fn init(outer_builder: *std.Build, opts: InitOptions) void { const b = opts.dep_engine.builder; const isWasm = opts.target.result.cpu.arch.isWasm(); var has_tracy = false; var hot_reload = false; if (!isWasm) { hot_reload = opts.hot_reload orelse (opts.optimize == .Debug); has_tracy = opts.has_tracy orelse (opts.optimize == .Debug); } const has_imgui = opts.has_imgui orelse (opts.optimize == .Debug); const statically_linked = opts.statically_linked orelse !hot_reload; var build_options = b.addOptions(); build_options.addOption(bool, "has_imgui", has_imgui); build_options.addOption(bool, "has_tracy", has_tracy); build_options.addOption(bool, "hot_reload", hot_reload); build_options.addOption(bool, "statically_linked", statically_linked); const engine_lib = b.createModule(.{ .root_source_file = b.path("src/lib/root.zig") }); engine_lib.addOptions("build_options", build_options); opts.root_module.addImport("engine", engine_lib); const runtime_module = createRuntimeModule(b, .{ .target = opts.target, .optimize = opts.optimize, .has_tracy = has_tracy, .has_imgui = has_imgui }); runtime_module.root_module.addImport("lib", engine_lib); if (statically_linked) { runtime_module.root_module.addImport("game", opts.root_module); } var run_cmd_step: *std.Build.Step = undefined; if (isWasm) { const build_step = buildWasm(b, .{ .name = "index", .root_module = runtime_module.root_module, .dep_sokol = runtime_module.dep_sokol, .outer_builder = outer_builder }); outer_builder.getInstallStep().dependOn(build_step); const dep_sokol = runtime_module.dep_sokol; const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{}); const emrun_step = sokol.emRunStep(outer_builder, .{ .name = "index", .emsdk = dep_emsdk }); emrun_step.step.dependOn(build_step); run_cmd_step = &emrun_step.step; // TODO: Create a zip archive of all of the files. Would be useful for easier itch.io upload } else { runtime_module.root_module.link_libc = true; const exe = buildNative(b, .{ .name = opts.name, .root_module = runtime_module.root_module, .win32_has_console = opts.win32_has_console, .win32_png_icon = opts.win32_png_icon }); outer_builder.installArtifact(exe); { const run_cmd = outer_builder.addRunArtifact(exe); if (outer_builder.args) |args| { run_cmd.addArgs(args); } if (hot_reload) { opts.root_module.resolved_target = opts.target; opts.root_module.optimize = opts.optimize; const game_lib = b.addLibrary(.{ .name = "game", .root_module = opts.root_module, .linkage = .dynamic, }); const install_game_lib = b.addInstallArtifact(game_lib, .{}); run_cmd.addArg(b.getInstallPath(.lib, game_lib.out_lib_filename)); run_cmd.step.dependOn(&install_game_lib.step); var game_lib_step = outer_builder.step("game-lib", "Build game dynamic library"); game_lib_step.dependOn(&install_game_lib.step); } run_cmd_step = &run_cmd.step; } } const run_step = outer_builder.step("run", "Run game"); run_step.dependOn(run_cmd_step); } const RuntimeModule = struct { root_module: *std.Build.Module, dep_sokol: *std.Build.Dependency, const Options = struct { target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, has_imgui: bool, has_tracy: bool, }; }; fn createRuntimeModule(b: *std.Build, opts: RuntimeModule.Options) RuntimeModule { const mod = b.createModule(.{ .root_source_file = b.path("src/runtime/main.zig"), .target = opts.target, .optimize = opts.optimize, .link_libc = true }); const dep_sokol = b.dependency("sokol", .{ .target = opts.target, .optimize = opts.optimize, .with_sokol_imgui = opts.has_imgui, }); mod.linkLibrary(dep_sokol.artifact("sokol_clib")); mod.addImport("sokol", dep_sokol.module("sokol")); if (opts.has_imgui) { if (b.lazyDependency("cimgui", .{ .target = opts.target, .optimize = opts.optimize, })) |dep_cimgui| { const cimgui = b.lazyImport(@This(), "cimgui").?; const cimgui_conf = cimgui.getConfig(false); mod.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 = opts.target, .optimize = opts.optimize, .tracy_enable = opts.has_tracy, .tracy_only_localhost = true }); if (opts.has_tracy) { mod.linkLibrary(dep_tracy.artifact("tracy")); } mod.addImport("tracy", dep_tracy.module("tracy")); const dep_stb = b.dependency("stb", .{}); mod.addImport("stb_image", dep_stb.module("stb_image")); mod.addImport("stb_vorbis", dep_stb.module("stb_vorbis")); const dep_fontstash_c = b.dependency("fontstash_c", .{}); mod.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, opts.target.result)) { .d3d11 => cflags.appendAssumeCapacity("-DSOKOL_D3D11"), .metal => cflags.appendAssumeCapacity("-DSOKOL_METAL"), .gl => cflags.appendAssumeCapacity("-DSOKOL_GLCORE"), .gles3 => cflags.appendAssumeCapacity("-DSOKOL_GLES3"), .wgpu => cflags.appendAssumeCapacity("-DSOKOL_WGPU"), else => @panic("unknown sokol backend"), } mod.addIncludePath(dep_sokol_c.path("util")); mod.addCSourceFile(.{ .file = b.path("src/runtime/fontstash/sokol_fontstash_impl.c"), .flags = cflags.items }); } // TODO: // const sdl = b.dependency("sdl", Run game.{ // .optimize = optimize, // .target = target, // .linkage = .static, // .default_target_config = !isWasm // }); // mod_main.linkLibrary(sdl.artifact("SDL3")); // if (isWasm) { // // TODO: Define buid config for wasm // } return RuntimeModule{ .root_module = mod, .dep_sokol = dep_sokol }; } fn buildPngToIconTool( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, ) *std.Build.Step.Compile { const mod = b.createModule(.{ .target = target, .optimize = optimize, .root_source_file = b.path("tools/png-to-icon.zig"), }); const dep_stb = b.dependency("stb", .{}); mod.addImport("stb_image", dep_stb.module("stb_image")); return b.addExecutable(.{ .name = "png-to-icon", .root_module = mod, }); } const BuildNativeOptions = struct { name: []const u8, root_module: *std.Build.Module, win32_has_console: ?bool = null, win32_png_icon: ?std.Build.LazyPath = null }; fn buildNative(b: *std.Build, opts: BuildNativeOptions) *std.Build.Step.Compile { const exe = b.addExecutable(.{ .name = opts.name, .root_module = opts.root_module }); const target = opts.root_module.resolved_target.?; if (target.result.os.tag == .windows) { const optimize = opts.root_module.optimize.?; const has_console = opts.win32_has_console orelse (optimize == .Debug); exe.subsystem = if (has_console) .Console else .Windows; if (opts.win32_png_icon) |win32_png_icon| { const png_to_icon_tool = buildPngToIconTool(b, b.graph.host, .ReleaseSafe); const png_to_icon_step = b.addRunArtifact(png_to_icon_tool); png_to_icon_step.addFileArg(win32_png_icon); 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); } } return exe; } const BuildWasmOptions = struct { name: []const u8, root_module: *std.Build.Module, dep_sokol: *std.Build.Dependency, outer_builder: *std.Build }; fn buildWasm(b: *std.Build, opts: BuildWasmOptions) *std.Build.Step { opts.root_module.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 = opts.name, .root_module = opts.root_module, }); const dep_sokol = opts.dep_sokol; const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{}); patchWasmIncludeDirs( opts.root_module, dep_emsdk.path("upstream/emscripten/cache/sysroot/include"), &(dep_sokol.artifact("sokol_clib").step) ); const link_step = sokol.emLinkStep(opts.outer_builder, .{ .lib_main = main_lib, .target = opts.root_module.resolved_target.?, .optimize = opts.root_module.optimize.?, .emsdk = dep_emsdk, .use_webgl2 = true, .use_emmalloc = true, .use_filesystem = false, .shell_file_path = b.path("src/runtime/shell.html"), }) catch unreachable; return &link_step.step; } 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); } } 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("\""); } };