diff --git a/build.zig b/build.zig index 2775042a3..d0b4da0d0 100644 --- a/build.zig +++ b/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const fs = std.fs; const Builder = std.build.Builder; const LibExeObjStep = std.build.LibExeObjStep; const glfw = @import("vendor/mach/glfw/build.zig"); @@ -15,6 +16,11 @@ pub fn build(b: *std.build.Builder) !void { "tracy", "Enable Tracy integration (default true in Debug)", ) orelse (mode == .Debug); + const conformance = b.option( + []const u8, + "conformance", + "Name of the conformance app to run with 'run' option.", + ); const exe_options = b.addOptions(); exe_options.addOption(bool, "tracy_enabled", tracy); @@ -50,8 +56,18 @@ pub fn build(b: *std.build.Builder) !void { // exe.addIncludeDir("vendor/stb"); // exe.addCSourceFile("src/stb/stb.c", &.{}); - const run_cmd = exe.run(); - run_cmd.step.dependOn(b.getInstallStep()); + // Conformance apps + var conformance_exes = try conformanceSteps(b, target, mode); + defer conformance_exes.deinit(); + + // Build our run step, which runs the main app by default, but will + // run a conformance app if `-Dconformance` is set. + const run_exe = if (conformance) |name| + conformance_exes.get(name) orelse return error.InvalidConformance + else + exe; + const run_cmd = run_exe.run(); + run_cmd.step.dependOn(&run_exe.step); if (b.args) |args| { run_cmd.addArgs(args); } @@ -68,3 +84,48 @@ pub fn build(b: *std.build.Builder) !void { lib_tests.addCSourceFile("vendor/glad/src/gl.c", &.{}); test_step.dependOn(&lib_tests.step); } + +fn conformanceSteps( + b: *std.build.Builder, + target: std.zig.CrossTarget, + mode: std.builtin.Mode, +) !std.StringHashMap(*LibExeObjStep) { + var map = std.StringHashMap(*LibExeObjStep).init(b.allocator); + + // Open the directory ./conformance + const c_dir_path = root() ++ "/conformance"; + var c_dir = try fs.openDirAbsolute(c_dir_path, .{ .iterate = true }); + defer c_dir.close(); + + // Go through and add each as a step + var c_dir_it = c_dir.iterate(); + while (try c_dir_it.next()) |entry| { + // Get the index of the last '.' so we can strip the extension. + const index = std.mem.lastIndexOfScalar(u8, entry.name, '.') orelse continue; + if (index == 0) continue; + + // Name of the conformance app and full path to the entrypoint. + const name = entry.name[0..index]; + const path = try fs.path.join(b.allocator, &[_][]const u8{ + c_dir_path, + entry.name, + }); + + // Executable builder. We install all conformance tests so that + // `zig build` verifies they work. + const c_exe = b.addExecutable(name, path); + c_exe.setTarget(target); + c_exe.setBuildMode(mode); + c_exe.install(); + + // Store the mapping + try map.put(name, c_exe); + } + + return map; +} + +/// Path to the directory with the build.zig. +fn root() []const u8 { + return std.fs.path.dirname(@src().file) orelse unreachable; +} diff --git a/conformance/ansi_ri.zig b/conformance/ansi_ri.zig new file mode 100644 index 000000000..991804295 --- /dev/null +++ b/conformance/ansi_ri.zig @@ -0,0 +1,12 @@ +//! Reverse Index (RI) - ESC M +const std = @import("std"); + +pub fn main() !void { + const stdout = std.io.getStdOut().writer(); + try stdout.print("A\nB\nC", .{}); + try stdout.print("\x1BM", .{}); + try stdout.print("D\n\n", .{}); + + // const stdin = std.io.getStdIn().reader(); + // _ = try stdin.readByte(); +} diff --git a/conformance/ansi_ri_top.zig b/conformance/ansi_ri_top.zig new file mode 100644 index 000000000..a8094d2bd --- /dev/null +++ b/conformance/ansi_ri_top.zig @@ -0,0 +1,10 @@ +//! Reverse Index (RI) - ESC M at the top of the screen. +const std = @import("std"); + +pub fn main() !void { + const stdout = std.io.getStdOut().writer(); + try stdout.print("\x1B[H", .{}); // move to top-left + try stdout.print("\x1B[J", .{}); // clear screen + try stdout.print("\x1BM", .{}); + try stdout.print("D\n", .{}); +}