From cca23762282437266524d62d8000fe8f27665ed5 Mon Sep 17 00:00:00 2001 From: peterino2 Date: Sat, 7 Mar 2026 10:41:05 -0800 Subject: [PATCH] updates to sdl-parserscrap --- README.md | 8 ++++ build.zig | 108 +++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 87 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index e19390b..6bebc21 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,14 @@ You can specify which repo to use with `-Dsdl-url=` the git refs will `-Dofficial` will use libsdl org's github repo. +If you already have SDL headers locally, use `-DtargetDir=` instead of fetching from git: + +``` +zig build generate -DtargetDir=D:/path/to/SDL +``` + +`targetDir` may point at an SDL checkout root (`include/SDL3`) or directly at a directory containing the SDL headers. Generated outputs go to `/api` and `/json`, and parser scratch directories (`tmp`, `debug`, `archive`) are rooted there as well. + ## Parser Usage building the parser for standalone use or in your own scripts diff --git a/build.zig b/build.zig index 9a29aa8..0e93880 100644 --- a/build.zig +++ b/build.zig @@ -1,5 +1,21 @@ const std = @import("std"); +fn resolveHeaderRoot(allocator: std.mem.Allocator, target_dir: []const u8) ![]const u8 { + const include_sdl3 = try std.fs.path.join(allocator, &.{ target_dir, "include", "SDL3" }); + defer allocator.free(include_sdl3); + if (std.fs.cwd().access(include_sdl3, .{})) { + return try allocator.dupe(u8, "include/SDL3"); + } else |_| {} + + const sdl3_dir = try std.fs.path.join(allocator, &.{ target_dir, "SDL3" }); + defer allocator.free(sdl3_dir); + if (std.fs.cwd().access(sdl3_dir, .{})) { + return try allocator.dupe(u8, "SDL3"); + } else |_| {} + + return try allocator.dupe(u8, "."); +} + 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"; @@ -47,8 +63,9 @@ fn addFetchSdlStep(b: *std.Build) *std.Build.Step { const ArchiveStep = struct { step: std.Build.Step, + base_dir: ?[]const u8, - pub fn create(b: *std.Build) *std.Build.Step { + pub fn create(b: *std.Build, base_dir: ?[]const u8) *std.Build.Step { const self = b.allocator.create(ArchiveStep) catch @panic("OOM"); self.* = .{ .step = std.Build.Step.init(.{ @@ -57,36 +74,58 @@ const ArchiveStep = struct { .owner = b, .makeFn = make, }), + .base_dir = base_dir, }; return &self.step; } fn make(step: *std.Build.Step, options: std.Build.Step.MakeOptions) !void { - _ = step; _ = options; + const self: *ArchiveStep = @fieldParentPtr("step", step); + const cwd = std.fs.cwd(); - const json_exists = if (cwd.access("json", .{})) true else |_| false; - const api_exists = if (cwd.access("api", .{})) true else |_| false; + const json_path = if (self.base_dir) |base_dir| + try std.fs.path.join(step.owner.allocator, &.{ base_dir, "json" }) + else + try step.owner.allocator.dupe(u8, "json"); + defer step.owner.allocator.free(json_path); + + const api_path = if (self.base_dir) |base_dir| + try std.fs.path.join(step.owner.allocator, &.{ base_dir, "api" }) + else + try step.owner.allocator.dupe(u8, "api"); + defer step.owner.allocator.free(api_path); + + const archive_root = if (self.base_dir) |base_dir| + try std.fs.path.join(step.owner.allocator, &.{ base_dir, "archive", "generate" }) + else + try step.owner.allocator.dupe(u8, "archive/generate"); + defer step.owner.allocator.free(archive_root); + + const json_exists = if (cwd.access(json_path, .{})) true else |_| false; + const api_exists = if (cwd.access(api_path, .{})) true else |_| false; if (!json_exists and !api_exists) return; const timestamp = std.time.timestamp(); - var buf: [64]u8 = undefined; - const archive_path = try std.fmt.bufPrint(&buf, "archive/generate/{d}", .{timestamp}); + const timestamp_dir = try std.fmt.allocPrint(step.owner.allocator, "{d}", .{timestamp}); + defer step.owner.allocator.free(timestamp_dir); + const archive_path = try std.fs.path.join(step.owner.allocator, &.{ archive_root, timestamp_dir }); + defer step.owner.allocator.free(archive_path); 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); + const json_dest = try std.fs.path.join(step.owner.allocator, &.{ archive_path, "json" }); + defer step.owner.allocator.free(json_dest); + try cwd.rename(json_path, json_dest); } if (api_exists) { - var api_dest_buf: [128]u8 = undefined; - const api_dest = try std.fmt.bufPrint(&api_dest_buf, "{s}/api", .{archive_path}); - try cwd.rename("api", api_dest); + const api_dest = try std.fs.path.join(step.owner.allocator, &.{ archive_path, "api" }); + defer step.owner.allocator.free(api_dest); + try cwd.rename(api_path, api_dest); } } }; @@ -117,22 +156,29 @@ const MakeDirStep = struct { }; pub fn generateApi(b: *std.Build, parser_exe: *std.Build.Step.Compile, fetch_sdl_step: *std.Build.Step) void { - // Archive existing json/ and api/ directories before regenerating - const archive_step = ArchiveStep.create(b); - fetch_sdl_step.dependOn(archive_step); + const target_dir = b.option([]const u8, "targetDir", "Parse SDL headers from an existing local directory and generate api/ there instead of fetching git"); + const basedir = b.option([]const u8, "basedir", "Working directory for the parser to execute in"); + const effective_basedir = target_dir orelse basedir; + const header_root_suffix = if (target_dir) |dir| resolveHeaderRoot(b.allocator, dir) catch @panic("OOM") else null; - // Write a build marker file after fetch to enable caching + // Archive existing json/ and api/ directories before regenerating + const archive_step = ArchiveStep.create(b, effective_basedir); + + const generation_root = if (target_dir != null) archive_step else fetch_sdl_step; + if (target_dir == null) { + fetch_sdl_step.dependOn(archive_step); + } + + // Write a build marker file after the source selection step 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); - - const basedir = b.option([]const u8, "basedir", "Working directory for the parser to execute in"); + wf.step.dependOn(generation_root); // Create basedir if specified - const basedir_step = if (basedir) |dir| MakeDirStep.create(b, dir) else &wf.step; - if (basedir != null) + const basedir_step = if (effective_basedir) |dir| MakeDirStep.create(b, dir) else &wf.step; + if (effective_basedir != null) basedir_step.dependOn(&wf.step); // All public SDL3 API headers (53 total) @@ -193,17 +239,21 @@ pub fn generateApi(b: *std.Build, parser_exe: *std.Build.Step.Compile, fetch_sdl }; const regenerate_step = b.step("generate", "Regenerate bindings from SDL headers"); - - const header_path = "sdl3/include/SDL3"; + const header_root = if (target_dir) |dir| + b.fmt("{s}/{s}", .{ dir, header_root_suffix.? }) + else + "sdl3/include/SDL3"; + const output_root = if (target_dir) |dir| b.fmt("{s}/api", .{dir}) else "api"; + const json_root = if (target_dir) |dir| b.fmt("{s}/json", .{dir}) else "json"; 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=api/{s}.zig", .{header_info.output})); + regenerate.addArg(b.fmt("{s}/{s}", .{ header_root, header_info.header })); + regenerate.addArg(b.fmt("--output={s}/{s}.zig", .{ output_root, header_info.output })); regenerate.addArg(timestamp_arg); - if (basedir) |dir| { + if (effective_basedir) |dir| { regenerate.addArg(b.fmt("--basedir={s}", .{dir})); } // regenerate.addArg(b.fmt("--output=api/{s}.zig --mocks=mocks/{s}.c", .{ header_info.output, header_info.output })); @@ -211,10 +261,10 @@ pub fn generateApi(b: *std.Build, parser_exe: *std.Build.Step.Compile, fetch_sdl 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(b.fmt("{s}/{s}", .{ header_root, header_info.header })); + regenerateJson.addArg(b.fmt("--generate-json={s}/{s}.json", .{ json_root, header_info.output })); regenerateJson.addArg(timestamp_arg); - if (basedir) |dir| { + if (effective_basedir) |dir| { regenerateJson.addArg(b.fmt("--basedir={s}", .{dir})); } regenerateJson.step.dependOn(basedir_step);