initial generate implementation

This commit is contained in:
Jonathan Marler
2026-02-08 05:38:29 -07:00
parent d03bf26fc8
commit d5efcb4260
8 changed files with 3003 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
*.zig text eol=lf
*.zon text eol=lf
+2
View File
@@ -0,0 +1,2 @@
.zig-cache/
zig-out/
+5
View File
@@ -0,0 +1,5 @@
# win32json - generate branch
Generates the JSON win32 metadata files.
Run `zig build` to generate/install the JSON files to `zig-out`.
+69
View File
@@ -0,0 +1,69 @@
const std = @import("std");
const Build = std.Build;
const version = "25.0.28-preview";
pub fn build(b: *Build) !void {
const metadata_nupkg_file = blk: {
const download_winmd_nupkg = b.addSystemCommand(&.{
"curl",
"https://www.nuget.org/api/v2/package/Microsoft.Windows.SDK.Win32Metadata/" ++ version,
"--location",
"--output",
});
break :blk download_winmd_nupkg.addOutputFileArg("win32metadata.nupkg");
};
const metadata_nupkg_out = blk: {
const zipcmdline = b.dependency("zipcmdline", .{
.target = b.graph.host,
});
const unzip_exe = zipcmdline.artifact("unzip");
const unzip_metadata = b.addRunArtifact(unzip_exe);
unzip_metadata.addFileArg(metadata_nupkg_file);
unzip_metadata.addArg("-d");
break :blk unzip_metadata.addOutputDirectoryArg("nupkg");
};
const winmd = metadata_nupkg_out.path(b, "Windows.Win32.winmd");
const gen_step = b.step("gen", "Generate JSON files (in .zig-cache)");
const winmd_dep = b.dependency("winmd", .{});
const gen_out_dir = blk: {
const exe = b.addExecutable(.{
.name = "genjson",
.root_module = b.createModule(.{
.root_source_file = b.path("src/genjson.zig"),
.target = b.graph.host,
}),
});
exe.root_module.addImport("winmd", winmd_dep.module("winmd"));
const run = b.addRunArtifact(exe);
run.addFileArg(winmd);
const out_dir = run.addOutputDirectoryArg(".");
gen_step.dependOn(&run.step);
break :blk out_dir;
};
b.installDirectory(.{
.source_dir = gen_out_dir,
.install_dir = .prefix,
.install_subdir = ".",
});
const test_step = b.step("test", "Run all the tests");
{
const validate_exe = b.addExecutable(.{
.name = "validate",
.root_module = b.createModule(.{
.root_source_file = b.path("src/validate.zig"),
.target = b.graph.host,
.optimize = .Debug,
}),
});
const validate = b.addRunArtifact(validate_exe);
validate.addDirectoryArg(gen_out_dir);
validate.expectExitCode(0);
test_step.dependOn(&validate.step);
}
}
+22
View File
@@ -0,0 +1,22 @@
.{
.name = .win32jsongenerate,
.fingerprint = 0xf9e0adc32fc8efe2,
.version = "0.0.0",
.zig_version = "0.15.2",
.minimum_zig_version = "0.15.0",
.dependencies = .{
.zipcmdline = .{
.url = "git+https://github.com/marler8997/zipcmdline#a6de65875023c52d2f56f21a31c6487266f8105f",
.hash = "zipcmdline-0.0.0-DFii07hmAwCFNhtMpml64Pj_hr8kN4WesClygZErQHsk",
},
.winmd = .{
.url = "git+https://github.com/marler8997/winmd-zig#d0d8daff01b4fb1a6324c79a855c34ced5bbc8d2",
.hash = "winmd-0.0.0-2QIrG4MpAQCCh0ReC0jZKAps9Frn3jbbR--W7szyGAOS",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}
+2629
View File
File diff suppressed because it is too large Load Diff
+214
View File
@@ -0,0 +1,214 @@
pub fn apiPatchMap(al: std.mem.Allocator) error{OutOfMemory}!std.StringHashMapUnmanaged(ApiPatches) {
var api_patches: std.StringHashMapUnmanaged(ApiPatches) = .{};
{
const api = try addApiPatches(al, &api_patches, "System.Console");
try api.func(al, .{ .name = "WriteConsoleA", .optional_params = .copy(&.{
.{ .name = "lpReserved" },
}) });
try api.func(al, .{ .name = "WriteConsoleW", .optional_params = .copy(&.{
.{ .name = "lpReserved" },
}) });
}
{
const api = try addApiPatches(al, &api_patches, "System.Memory");
try api.func(al, .{ .name = "CreateFileMappingA", .optional_return = .{} });
try api.func(al, .{ .name = "CreateFileMappingW", .optional_return = .{} });
try api.func(al, .{ .name = "MapViewOfFile", .optional_return = .{} });
try api.func(al, .{ .name = "MapViewOfFileEx", .optional_return = .{} });
}
{
const api = try addApiPatches(al, &api_patches, "UI.WindowsAndMessaging");
try api.func(al, .{ .name = "CreateWindowExA", .optional_return = .{} });
try api.func(al, .{ .name = "CreateWindowExW", .optional_return = .{} });
try api.func(al, .{ .name = "ShowWindow", .optional_params = .copy(&.{
.{ .name = "hWnd" },
}) });
try api.struct_(al, .{ .name = "WNDCLASSA", .optional_fields = .copy(&.{
.{ .name = "hIcon" },
.{ .name = "hCursor" },
.{ .name = "hbrBackground" },
.{ .name = "lpszMenuName" },
}) });
try api.struct_(al, .{ .name = "WNDCLASSW", .optional_fields = .copy(&.{
.{ .name = "hIcon" },
.{ .name = "hCursor" },
.{ .name = "hbrBackground" },
.{ .name = "lpszMenuName" },
}) });
}
{
const api = try addApiPatches(al, &api_patches, "Graphics.Gdi");
try api.func(al, .{ .name = "CreateFontA", .optional_return = .{} });
try api.func(al, .{ .name = "CreateFontW", .optional_return = .{} });
}
return api_patches;
}
pub fn verifyApiPatches(api_patch_map: *const std.StringHashMapUnmanaged(ApiPatches)) void {
var it = api_patch_map.iterator();
while (it.next()) |entry| {
entry.value_ptr.verify();
}
}
fn addApiPatches(
al: std.mem.Allocator,
api_patches: *std.StringHashMapUnmanaged(ApiPatches),
name: []const u8,
) error{OutOfMemory}!*ApiPatches {
const entry = try api_patches.getOrPut(al, name);
if (entry.found_existing) std.debug.panic("api '{s}' patched multiple times", .{name});
entry.value_ptr.* = .{
.name = name,
.func_map = .{},
.struct_map = .{},
};
return entry.value_ptr;
}
pub const ApiPatches = struct {
name: []const u8,
func_map: std.StringHashMapUnmanaged(FuncPatches),
struct_map: std.StringHashMapUnmanaged(StructPatches),
pub const none: ApiPatches = .{
.name = "",
.func_map = .{},
.struct_map = .{},
};
pub fn verify(api_patches: *const ApiPatches) void {
{
var it = api_patches.func_map.iterator();
while (it.next()) |entry| {
entry.value_ptr.verify(api_patches.name);
}
}
{
var it = api_patches.struct_map.iterator();
while (it.next()) |entry| {
entry.value_ptr.verify(api_patches.name);
}
}
}
pub fn func(
api_patches: *ApiPatches,
al: std.mem.Allocator,
func_patches: FuncPatches,
) error{OutOfMemory}!void {
const entry = try api_patches.func_map.getOrPut(al, func_patches.name);
if (entry.found_existing) std.debug.panic("api '{s}' function '{s}' patched multiple times", .{ api_patches.name, func_patches.name });
entry.value_ptr.* = func_patches;
}
pub fn struct_(
api_patches: *ApiPatches,
al: std.mem.Allocator,
struct_patches: StructPatches,
) error{OutOfMemory}!void {
const entry = try api_patches.struct_map.getOrPut(al, struct_patches.name);
if (entry.found_existing) std.debug.panic("api '{s}' struct '{s}' patched multiple times", .{ api_patches.name, struct_patches.name });
entry.value_ptr.* = struct_patches;
}
pub fn finish(api_patches: *ApiPatches) void {
_ = api_patches;
}
};
fn BoundedArray(comptime T: type, comptime max: usize) type {
return struct {
count: std.math.IntFittingRange(0, max),
buffer: [max]T,
const Self = @This();
pub const empty: Self = .{ .count = 0, .buffer = undefined };
pub fn copy(source: []const T) Self {
if (source.len > max) std.debug.panic("slice {} too long (max {})", .{ source.len, max });
var result: Self = .{ .count = @intCast(source.len), .buffer = undefined };
@memcpy(result.buffer[0..source.len], source);
return result;
}
pub fn slice(self: *const Self) []const T {
return self.buffer[0..self.count];
}
pub fn sliceMut(self: *Self) []T {
return self.buffer[0..self.count];
}
};
}
pub const StructPatches = struct {
name: []const u8,
optional_fields: BoundedArray(NamedOptional, 4) = .empty,
pub const none: StructPatches = .{ .name = "" };
pub fn verify(patches: *const StructPatches, api_name: []const u8) void {
for (patches.optional_fields.slice()) |f| if (!f.applied) std.debug.panic(
"api '{s}' struct '{s}' field '{s}' optional not applied",
.{ api_name, patches.name, f.name },
);
}
pub fn queryOptionalField(patches: *StructPatches, name: []const u8) bool {
for (patches.optional_fields.sliceMut()) |*p| {
if (std.mem.eql(u8, name, p.name)) {
std.debug.assert(!p.applied);
p.applied = true;
return true;
}
}
return false;
}
};
pub const FuncPatches = struct {
name: []const u8,
optional_return: ?OptionalReturn = null,
optional_params: BoundedArray(NamedOptional, 1) = .empty,
pub const none: FuncPatches = .{ .name = "" };
pub fn verify(patches: *const FuncPatches, api_name: []const u8) void {
if (patches.optional_return) |r| if (!r.applied) std.debug.panic(
"api '{s}' function '{s}' optional return not applied",
.{ api_name, patches.name },
);
for (patches.optional_params.slice()) |p| if (!p.applied) std.debug.panic(
"api '{s}' function '{s}' param '{s}' optional not applied",
.{ api_name, patches.name, p.name },
);
}
pub fn queryOptionalReturn(patches: *FuncPatches) bool {
const optional_return: *OptionalReturn = &(patches.optional_return orelse return false);
std.debug.assert(!optional_return.applied);
optional_return.applied = true;
return true;
}
pub fn queryOptionalParam(patches: *FuncPatches, name: []const u8) bool {
for (patches.optional_params.sliceMut()) |*p| {
if (std.mem.eql(u8, name, p.name)) {
std.debug.assert(!p.applied);
p.applied = true;
return true;
}
}
return false;
}
};
const OptionalReturn = struct {
applied: bool = false,
};
const NamedOptional = struct {
name: []const u8,
applied: bool = false,
};
pub fn oom(e: error{OutOfMemory}) noreturn {
@panic(@errorName(e));
}
const std = @import("std");
+60
View File
@@ -0,0 +1,60 @@
const std = @import("std");
pub fn main() !void {
var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const arena = arena_instance.allocator();
const all_args = try std.process.argsAlloc(arena);
if (all_args.len != 2) @panic("unexpected number of cmdline args");
const json_path = all_args[1];
std.log.info("verifying JSON files in: {s}", .{json_path});
var file_count: u32 = 0;
var json_dir = try std.fs.cwd().openDir(json_path, .{ .iterate = true });
defer json_dir.close();
var it = json_dir.iterate();
while (try it.next()) |entry| {
switch (entry.kind) {
.file => {},
else => |kind| std.debug.panic(
"unexpected directory entry kind '{t}' for '{s}' in '{s}'",
.{ kind, entry.name, json_path },
),
}
std.debug.assert(std.mem.endsWith(u8, entry.name, ".json"));
try checkFile(arena, json_path, json_dir, entry.name);
file_count += 1;
}
std.log.info("verified {} files", .{file_count});
}
fn checkFile(allocator: std.mem.Allocator, dir_path: []const u8, dir: std.fs.Dir, sub_path: []const u8) !void {
const content = blk: {
var file = dir.openFile(sub_path, .{}) catch |err| {
std.log.err("open '{s}/{s}' failed with {s}", .{ dir_path, sub_path, @errorName(err) });
std.process.exit(0xff);
};
defer file.close();
break :blk try file.readToEndAlloc(allocator, std.math.maxInt(usize));
};
defer allocator.free(content);
var diagnostics = std.json.Diagnostics{};
var scanner = std.json.Scanner.initCompleteInput(allocator, content);
defer scanner.deinit();
scanner.enableDiagnostics(&diagnostics);
const json = std.json.parseFromTokenSourceLeaky(
std.json.Value,
allocator,
&scanner,
.{},
) catch |err| {
std.log.err(
"{s}{c}{s}:{}:{}: {s}",
.{
dir_path, std.fs.path.sep, sub_path,
diagnostics.getLine(), diagnostics.getColumn(), @errorName(err),
},
);
std.process.exit(0xff);
};
_ = json;
}