ona/build.zig

323 lines
7.3 KiB
Zig

const builtin = @import("builtin");
const std = @import("std");
const ImportList = std.ArrayList(struct {
name: []const u8,
module: *std.Build.Module
});
const Project = struct {
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
imports: ImportList,
pub fn find_demos(self: Project, b: *std.Build) !void {
const demos = b.step("demos", "Build demos");
var dir = try std.fs.cwd().openDir("demos/", .{
.iterate = true,
});
defer {
dir.close();
}
var entries = try dir.walk(b.allocator);
defer {
entries.deinit();
}
while (try entries.next()) |entry| {
if (entry.kind != .file or !std.mem.endsWith(u8, entry.path, ".zig")) {
continue;
}
const source_path = try sub_path(.{"demos", entry.basename});
var path_buffer = [_:0]u8{0} ** 255;
const demo = b.addExecutable(.{
.name = try std.fmt.bufPrint(&path_buffer, "{s}.out", .{std.fs.path.stem(entry.basename)}),
.root_source_file = b.path(source_path.bytes()),
.target = self.target,
.optimize = self.optimize,
});
for (self.imports.items) |import| {
demo.root_module.addImport(import.name, import.module);
}
demos.dependOn(&b.addInstallArtifact(demo, .{
.dest_dir = .{
.override = .{
.custom = "../demos/",
},
},
}).step);
}
}
pub fn find_tests(self: Project, b: *std.Build) !void {
const tests = b.step("test", "Build and run tests");
for (self.imports.items) |import| {
tests.dependOn(&b.addRunArtifact(b.addTest(.{
.root_source_file = import.module.root_source_file.?,
.target = self.target,
.optimize = self.optimize,
})).step);
}
}
pub fn add_module(self: *Project, b: *std.Build, comptime name: []const u8, options: std.Build.Module.CreateOptions) !*std.Build.Module {
const cwd = std.fs.cwd();
var corrected_options = options;
if (corrected_options.root_source_file == null) {
corrected_options.root_source_file = b.path("src/" ++ name ++ "/" ++ name ++ ".zig");
}
if (corrected_options.target == null) {
corrected_options.target = self.target;
}
if (corrected_options.optimize == null) {
corrected_options.optimize = self.optimize;
}
const module = b.addModule(name, corrected_options);
try self.imports.append(.{
.name = name,
.module = module
});
// TODO: Probably want to make it search the same path as any explicit root_source_path override for shaders.
const shaders_path = "src/" ++ name ++ "/shaders/";
var shaders_dir = cwd.openDir(shaders_path, .{.iterate = true}) catch |open_error| {
return switch (open_error) {
error.FileNotFound, error.NotDir => module,
else => open_error,
};
};
defer {
shaders_dir.close();
}
var shaders_entries = try shaders_dir.walk(b.allocator);
defer {
shaders_entries.deinit();
}
while (try shaders_entries.next()) |entry| {
if (entry.kind != .file) {
continue;
}
const is_shader_file = std.mem.endsWith(u8, entry.path, ".frag") or std.mem.endsWith(u8, entry.path, ".vert");
if (!is_shader_file) {
continue;
}
var binary_buffer = [_:0]u8{0} ** 255;
const binary_name = try std.fmt.bufPrint(&binary_buffer, "{s}.spv", .{entry.path});
const full_source_path = try sub_path(.{shaders_path, entry.path});
const full_binary_path = try sub_path(.{shaders_path, binary_name});
const glslang_validator_args = [_][]const u8{
"glslangValidator",
"-V",
full_source_path.bytes(),
"-o",
full_binary_path.bytes(),
};
shaders_dir.access(binary_name, .{.mode = .read_only}) catch {
const output = b.run(&glslang_validator_args);
std.log.info("{s}", .{output[0 .. output.len - 1]});
continue;
};
if ((try shaders_dir.statFile(entry.path)).mtime > (try shaders_dir.statFile(binary_name)).mtime) {
const output = b.run(&glslang_validator_args);
std.log.info("{s}", .{output[0 .. output.len - 1]});
continue;
}
}
return module;
}
};
const SubPath = struct {
buffer: [max]u8 = [_]u8{0} ** max,
unused: u8 = max,
pub const max = 255;
pub fn append(self: *SubPath, component: []const u8) !void {
const used = max - self.unused;
if (used != 0 and self.buffer[used - 1] != '/') {
if (component.len > self.unused) {
return error.PathTooBig;
}
@memcpy(self.buffer[used .. (used + component.len)], component);
self.unused -= @intCast(component.len);
} else {
const required_len = component.len + 1;
if (required_len > self.unused) {
return error.PathTooBig;
}
@memcpy(self.buffer[used .. (used + component.len)], component);
self.buffer[component.len] = '/';
self.unused -= @intCast(required_len);
}
}
pub fn bytes(self: *const SubPath) [:0]const u8 {
return @ptrCast(self.buffer[0 .. max - self.unused]);
}
};
pub fn build(b: *std.Build) !void {
var project = Project{
.imports = ImportList.init(b.allocator),
.target = b.standardTargetOptions(.{}),
.optimize = b.standardOptimizeOption(.{}),
};
const sokol_dependency = b.dependency("sokol", .{
.target = project.target,
.optimize = project.optimize,
});
const ona_module = try project.add_module(b, "ona", .{});
const input_module = try project.add_module(b, "input", .{});
const coral_module = try project.add_module(b, "coral", .{
.imports = &.{
.{
.name = "sokol",
.module = sokol_dependency.module("sokol"),
},
.{
.name = "ona",
.module = ona_module,
},
.{
.name = "input",
.module = input_module,
},
},
});
coral_module.addIncludePath(b.path("ext/"));
coral_module.linkLibrary(spirv_cross: {
const dir = "ext/spirv-cross/";
const sources = [_][]const u8{
"spirv_cross.cpp",
"spirv_parser.cpp",
"spirv_cross_parsed_ir.cpp",
"spirv_cfg.cpp",
"spirv_glsl.cpp",
"spirv_msl.cpp",
"spirv_hlsl.cpp",
"spirv_reflect.cpp",
"spirv_cross_util.cpp",
"spirv_cross_c.cpp",
};
const lib = b.addStaticLibrary(.{
.name = "spirvcross",
.target = project.target,
.optimize = project.optimize,
});
switch (lib.rootModuleTarget().abi) {
.msvc => lib.linkLibC(),
else => lib.linkLibCpp(),
}
inline for (sources) |src| {
lib.addCSourceFile(.{
.file = b.path(dir ++ src),
.flags = &.{
"-fstrict-aliasing",
"-DSPIRV_CROSS_C_API_GLSL",
"-DSPIRV_CROSS_C_API_HLSL",
"-DSPIRV_CROSS_C_API_MSL",
},
});
}
break: spirv_cross lib;
});
coral_module.linkSystemLibrary("SDL2", .{
.needed = true,
.preferred_link_mode = .dynamic,
});
coral_module.link_libc = true;
try project.find_tests(b);
try project.find_demos(b);
}
fn sub_path(components: anytype) !SubPath {
var path = comptime try std.BoundedArray(u8, SubPath.max).init(0);
const Components = @TypeOf(components);
switch (@typeInfo(Components)) {
.Struct => |@"struct"| {
if (!@"struct".is_tuple) {
@compileError("`components` must be a tuple");
}
const last_component_index = components.len - 1;
inline for (components, 0 .. components.len) |component, i| {
path.appendSlice(component) catch {
return error.PathTooBig;
};
if (i < last_component_index and !std.mem.endsWith(u8, component, "/")) {
path.append('/') catch {
return error.PathTooBig;
};
}
}
},
else => {
@compileError("`components` cannot be a " ++ @typeName(Components));
}
}
return .{
.unused = SubPath.max - path.len,
.buffer = path.buffer,
};
}