471 lines
16 KiB
Zig
471 lines
16 KiB
Zig
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,
|
|
|
|
code_static_linking: ?bool = null,
|
|
code_dynamic_linking: ?bool = null,
|
|
has_imgui: ?bool = null,
|
|
has_tracy: ?bool = null,
|
|
win32_has_console: ?bool = null,
|
|
win32_png_icon: ?std.Build.LazyPath = null,
|
|
asset_hot_reload: ?bool = null,
|
|
asset_dir: std.Build.LazyPath,
|
|
};
|
|
|
|
pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
|
|
const b = opts.dep_engine.builder;
|
|
|
|
const isWasm = opts.target.result.cpu.arch.isWasm();
|
|
const isDebug = (opts.optimize == .Debug);
|
|
|
|
var has_tracy = false;
|
|
var code_dynamic_linking = false;
|
|
var asset_hot_reload = false;
|
|
if (!isWasm) {
|
|
code_dynamic_linking = opts.code_dynamic_linking orelse isDebug;
|
|
has_tracy = opts.has_tracy orelse isDebug;
|
|
asset_hot_reload = opts.asset_hot_reload orelse isDebug;
|
|
}
|
|
|
|
const has_imgui = opts.has_imgui orelse isDebug;
|
|
const code_static_linking = opts.code_static_linking orelse true;
|
|
|
|
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, "code_dynamic_linking", code_dynamic_linking);
|
|
build_options.addOption(bool, "code_static_linking", code_static_linking);
|
|
build_options.addOption(bool, "asset_hot_reload", asset_hot_reload);
|
|
if (asset_hot_reload) {
|
|
build_options.addOptionPath("asset_dir", opts.asset_dir);
|
|
}
|
|
|
|
const engine_lib = b.createModule(.{
|
|
.root_source_file = b.path("src/game_lib/root.zig")
|
|
});
|
|
engine_lib.addOptions("build_options", build_options);
|
|
|
|
const dep_stb = b.dependency("stb", .{});
|
|
engine_lib.addImport("stb_image", dep_stb.module("stb_image"));
|
|
|
|
opts.root_module.addImport("engine_lib", engine_lib);
|
|
|
|
const engine_module = createEngineModule(b, .{
|
|
.target = opts.target,
|
|
.optimize = opts.optimize,
|
|
.has_tracy = has_tracy,
|
|
.has_imgui = has_imgui
|
|
});
|
|
engine_module.root_module.addImport("engine_lib", engine_lib);
|
|
if (code_static_linking) {
|
|
engine_module.root_module.addImport("game", opts.root_module);
|
|
}
|
|
|
|
// TODO: Fix windows cross compile build.
|
|
// Fails to build asset bundler, because engine module is built for target and not host
|
|
const assset_bundler_tool = buildAssetBundler(
|
|
b, b.graph.host, .ReleaseSafe,
|
|
engine_module.root_module,
|
|
opts.root_module
|
|
);
|
|
|
|
const asset_bundler_step = b.addRunArtifact(assset_bundler_tool);
|
|
asset_bundler_step.addDirectoryArg(opts.asset_dir);
|
|
const assets_bundle_file = asset_bundler_step.addOutputFileArg("assets.bin");
|
|
|
|
const runtime_mod = b.createModule(.{
|
|
.root_source_file = b.path("src/runtime/main.zig"),
|
|
.target = opts.target,
|
|
.optimize = opts.optimize,
|
|
});
|
|
runtime_mod.addImport("engine", engine_module.root_module);
|
|
if (!asset_hot_reload) {
|
|
// TODO: Add compression on asset bundle
|
|
runtime_mod.addAnonymousImport("asset_bundle", .{
|
|
.root_source_file = assets_bundle_file
|
|
});
|
|
}
|
|
|
|
var run_cmd_step: *std.Build.Step = undefined;
|
|
if (isWasm) {
|
|
const build_step = buildWasm(b, .{
|
|
.name = "index",
|
|
.root_module = runtime_mod,
|
|
.dep_sokol = engine_module.dep_sokol,
|
|
.outer_builder = outer_builder
|
|
});
|
|
outer_builder.getInstallStep().dependOn(build_step);
|
|
|
|
const dep_sokol = engine_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_mod.link_libc = true;
|
|
const exe = buildNative(b, .{
|
|
.name = opts.name,
|
|
.root_module = runtime_mod,
|
|
.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 (code_dynamic_linking) {
|
|
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("--game-lib-path");
|
|
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 install_assets_bin = outer_builder.addInstallFileWithDir(assets_bundle_file, .bin, "assets.bin");
|
|
const assets_step = outer_builder.step("assets", "Build assets file");
|
|
assets_step.dependOn(&install_assets_bin.step);
|
|
}
|
|
}
|
|
|
|
const EngineModule = 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 createEngineModule(b: *std.Build, opts: EngineModule.Options) EngineModule {
|
|
const mod = b.createModule(.{
|
|
.root_source_file = b.path("src/engine/root.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/engine/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 EngineModule{
|
|
.root_module = mod,
|
|
.dep_sokol = dep_sokol
|
|
};
|
|
}
|
|
|
|
fn buildAssetBundler(
|
|
b: *std.Build,
|
|
target: std.Build.ResolvedTarget,
|
|
optimize: std.builtin.OptimizeMode,
|
|
engine: *std.Build.Module,
|
|
game: *std.Build.Module
|
|
) *std.Build.Step.Compile {
|
|
const mod = b.createModule(.{
|
|
.target = target,
|
|
.optimize = optimize,
|
|
.root_source_file = b.path("tools/asset-bundler.zig"),
|
|
});
|
|
mod.addImport("engine", engine);
|
|
mod.addImport("game", game);
|
|
// mod.addImport("asset_bundle", b.createModule(.{
|
|
// .root_source_file = b.path("src/runtime/asset_bundle.zig")
|
|
// }));
|
|
|
|
return b.addExecutable(.{
|
|
.name = "asset-bundler",
|
|
.root_module = mod,
|
|
});
|
|
}
|
|
|
|
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/engine/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("\"");
|
|
}
|
|
};
|