const std = @import("std"); fn addFetchSdlStep(b: *std.Build) *std.Build.Step { const default_sdl_url = "git@github.com:castholm/SDL.git"; const official_sdl_url = "git@github.com:libsdl-org/SDL.git"; const sdl_url_option = b.option([]const u8, "sdl-url", "URL to fetch SDL3 from (use 'official' for libsdl-org/SDL)") orelse default_sdl_url; const sdl_url = if (std.mem.eql(u8, sdl_url_option, "official") or b.option(bool, "official", "use the official sdl path of git@github.com:libsdl-org/SDL.git (same as setting -Dsdl-url=official)") != null) official_sdl_url else sdl_url_option; const clean_sdl = b.option(bool, "clean", "Delete sdl3/ directory before fetching") orelse false; const sdl_checkout = b.option([]const u8, "ref", "the git ref to check out after fetching sdl, this can be tag, commit, branch..."); const fetch_step = b.step("fetch-sdl", "Fetch SDL3 source from git"); const sdl_exists = if (std.fs.cwd().access("sdl3", .{})) true else |_| false; var end_step: *std.Build.Step = undefined; if (clean_sdl) { const remove_dir = b.addRemoveDirTree(b.path("sdl3")); const fetch_sdl = b.addSystemCommand(&.{ "git", "clone", sdl_url, "sdl3" }); fetch_sdl.step.dependOn(&remove_dir.step); end_step = &fetch_sdl.step; // fetch_step.dependOn(&fetch_sdl.step); } else if (sdl_exists) { const echo_msg = b.addSystemCommand(&.{ "echo", "sdl already fetched" }); end_step = &echo_msg.step; // fetch_step.dependOn(&echo_msg.step); } else { const fetch_sdl = b.addSystemCommand(&.{ "git", "clone", sdl_url, "sdl3" }); end_step = &fetch_sdl.step; } const checkout_step = if (sdl_checkout) |ref| &b.addSystemCommand(&.{ "git", "-C", "sdl3", "checkout", ref }).step else end_step; fetch_step.dependOn(checkout_step); return fetch_step; } const ArchiveStep = struct { step: std.Build.Step, pub fn create(b: *std.Build) *std.Build.Step { const self = b.allocator.create(ArchiveStep) catch @panic("OOM"); self.* = .{ .step = std.Build.Step.init(.{ .id = .custom, .name = "archive generate outputs", .owner = b, .makeFn = make, }), }; return &self.step; } fn make(step: *std.Build.Step, options: std.Build.Step.MakeOptions) !void { _ = step; _ = options; const cwd = std.fs.cwd(); const json_exists = if (cwd.access("json", .{})) true else |_| false; const v2_exists = if (cwd.access("v2", .{})) true else |_| false; if (!json_exists and !v2_exists) return; const timestamp = std.time.timestamp(); var buf: [64]u8 = undefined; const archive_path = try std.fmt.bufPrint(&buf, "archive/generate/{d}", .{timestamp}); try cwd.makePath(archive_path); if (json_exists) { var json_dest_buf: [128]u8 = undefined; const json_dest = try std.fmt.bufPrint(&json_dest_buf, "{s}/json", .{archive_path}); try cwd.rename("json", json_dest); } if (v2_exists) { var v2_dest_buf: [128]u8 = undefined; const v2_dest = try std.fmt.bufPrint(&v2_dest_buf, "{s}/v2", .{archive_path}); try cwd.rename("v2", v2_dest); } } }; pub fn generateApi(b: *std.Build, parser_exe: *std.Build.Step.Compile, fetch_sdl_step: *std.Build.Step) void { // Archive existing json/ and v2/ directories before regenerating const archive_step = ArchiveStep.create(b); fetch_sdl_step.dependOn(archive_step); // Write a build marker file after fetch to enable caching const timestamp = std.time.timestamp(); const wf = b.addWriteFiles(); const marker_file = wf.add(".buildmarker", b.fmt("{d}", .{timestamp})); _ = marker_file; wf.step.dependOn(fetch_sdl_step); // All public SDL3 API headers (53 total) // Skipped: assert, thread, hidapi, mutex, tray (not core APIs or problematic) const headers_to_generate = [_]struct { header: []const u8, output: []const u8 }{ // .{ .header = "SDL_asyncio.h", .output = "asyncio" }, // .{ .header = "SDL_atomic.h", .output = "atomic" }, .{ .header = "SDL_audio.h", .output = "audio" }, .{ .header = "SDL_blendmode.h", .output = "blendmode" }, .{ .header = "SDL_camera.h", .output = "camera" }, .{ .header = "SDL_clipboard.h", .output = "clipboard" }, // .{ .header = "SDL_cpuinfo.h", .output = "cpuinfo" }, .{ .header = "SDL_dialog.h", .output = "dialog" }, .{ .header = "SDL_endian.h", .output = "endian" }, .{ .header = "SDL_error.h", .output = "error" }, // .{ .header = "SDL_events.h", .output = "events" }, .{ .header = "SDL_filesystem.h", .output = "filesystem" }, .{ .header = "SDL_gamepad.h", .output = "gamepad" }, .{ .header = "SDL_gpu.h", .output = "gpu" }, // .{ .header = "SDL_guid.h", .output = "guid" }, .{ .header = "SDL_haptic.h", .output = "haptic" }, // .{ .header = "SDL_hidapi.h", .output = "hidapi" }, // Skipped: not core API .{ .header = "SDL_hints.h", .output = "hints" }, .{ .header = "SDL_init.h", .output = "init" }, // .{ .header = "SDL_iostream.h", .output = "iostream" }, // Skipped: complex I/O API .{ .header = "SDL_joystick.h", .output = "joystick" }, // .{ .header = "SDL_keyboard.h", .output = "keyboard" }, .{ .header = "SDL_keycode.h", .output = "keycode" }, .{ .header = "SDL_loadso.h", .output = "loadso" }, // .{ .header = "SDL_locale.h", .output = "locale" }, // .{ .header = "SDL_log.h", .output = "log" }, .{ .header = "SDL_messagebox.h", .output = "messagebox" }, // .{ .header = "SDL_metal.h", .output = "metal" }, .{ .header = "SDL_misc.h", .output = "misc" }, .{ .header = "SDL_mouse.h", .output = "mouse" }, // .{ .header = "SDL_mutex.h", .output = "mutex" }, // Skipped: not core API // .{ .header = "SDL_opengl.h", .output = "opengl" }, // .{ .header = "SDL_pen.h", .output = "pen" }, .{ .header = "SDL_pixels.h", .output = "pixels" }, // .{ .header = "SDL_power.h", .output = "power" }, // .{ .header = "SDL_process.h", .output = "process" }, .{ .header = "SDL_properties.h", .output = "properties" }, .{ .header = "SDL_rect.h", .output = "rect" }, .{ .header = "SDL_render.h", .output = "render" }, // .{ .header = "SDL_scancode.h", .output = "scancode" }, .{ .header = "SDL_sensor.h", .output = "sensor" }, .{ .header = "SDL_storage.h", .output = "storage" }, .{ .header = "SDL_surface.h", .output = "surface" }, .{ .header = "SDL_system.h", .output = "system" }, // .{ .header = "SDL_thread.h", .output = "thread" }, // Skipped: not core API .{ .header = "SDL_time.h", .output = "time" }, .{ .header = "SDL_timer.h", .output = "timer" }, .{ .header = "SDL_touch.h", .output = "touch" }, // .{ .header = "SDL_tray.h", .output = "tray" }, // Skipped: not core API .{ .header = "SDL_version.h", .output = "version" }, .{ .header = "SDL_video.h", .output = "video" }, // .{ .header = "SDL_vulkan.h", .output = "vulkan" }, // Skipped: Vulkan interop }; const regenerate_step = b.step("generate", "Regenerate bindings from SDL headers"); const header_path = "sdl3/include/SDL3"; const timestamp_arg = b.fmt("--timestamp={d}", .{timestamp}); for (headers_to_generate) |header_info| { const regenerate = b.addRunArtifact(parser_exe); regenerate.addFileArg(b.path(b.fmt("{s}/{s}", .{ header_path, header_info.header }))); regenerate.addArg(b.fmt("--output=v2/{s}.zig", .{header_info.output})); regenerate.addArg(timestamp_arg); // regenerate.addArg(b.fmt("--output=v2/{s}.zig --mocks=mocks/{s}.c", .{ header_info.output, header_info.output })); regenerate.step.dependOn(&wf.step); regenerate_step.dependOn(®enerate.step); const regenerateJson = b.addRunArtifact(parser_exe); regenerateJson.addFileArg(b.path(b.fmt("{s}/{s}", .{ header_path, header_info.header }))); regenerateJson.addArg(b.fmt("--generate-json=json/{s}.json", .{header_info.output})); regenerateJson.addArg(timestamp_arg); regenerateJson.step.dependOn(&wf.step); regenerate_step.dependOn(®enerateJson.step); } } pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const fetch_sdl_step = addFetchSdlStep(b); // Parser executable const parser_exe = b.addExecutable(.{ .name = "sdl-parser", .root_module = b.createModule(.{ .root_source_file = b.path("src/parser.zig"), .target = target, .optimize = optimize, }), }); b.installArtifact(parser_exe); generateApi(b, parser_exe, fetch_sdl_step); // Run command const run_cmd = b.addRunArtifact(parser_exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } const run_step = b.step("run", "Run the SDL3 header parser"); run_step.dependOn(&run_cmd.step); // Test mocks generation target const test_mocks_cmd = b.addRunArtifact(parser_exe); test_mocks_cmd.step.dependOn(b.getInstallStep()); const test_header_path = b.path("test_small.h"); const test_output = b.path("zig-out/test_small.zig"); const test_mocks = b.path("zig-out/test_small_mock.c"); test_mocks_cmd.addArg(test_header_path.getPath(b)); test_mocks_cmd.addArg(b.fmt("--output={s}", .{test_output.getPath(b)})); test_mocks_cmd.addArg(b.fmt("--mocks={s}", .{test_mocks.getPath(b)})); const test_mocks_step = b.step("test-mocks", "Test mock generation with test_small.h"); test_mocks_step.dependOn(&test_mocks_cmd.step); // Tests const parser_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("src/parser.zig"), .target = target, .optimize = optimize, }), }); const run_tests = b.addRunArtifact(parser_tests); const test_step = b.step("test", "Run parser tests"); test_step.dependOn(&run_tests.step); }