improve error handling of generator tool

This now also prints more detailed error messages,
similar to the build step.
This commit is contained in:
Robin Voetter
2024-01-06 15:09:04 +01:00
parent 2b6d7691f1
commit cebfcc6045
2 changed files with 65 additions and 38 deletions

View File

@@ -1,23 +1,19 @@
const std = @import("std"); const std = @import("std");
const generator = @import("vulkan/generator.zig"); const generator = @import("vulkan/generator.zig");
const usage = fn invalidUsage(prog_name: []const u8, comptime fmt: []const u8, args: anytype) noreturn {
\\Usage: {s} [options] <spec xml path> <output zig source> std.log.err(fmt, args);
\\Options: std.log.err("see {s} --help for usage", .{prog_name});
\\-h --help show this message and exit. std.process.exit(1);
\\-a --api <api> Generate API for 'vulkan' or 'vulkansc'. Defaults to 'vulkan'. }
\\
;
pub fn main() !void {
const stderr = std.io.getStdErr();
pub fn main() void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit(); defer arena.deinit();
const allocator = arena.allocator(); const allocator = arena.allocator();
var args = try std.process.argsWithAllocator(allocator); var args = try std.process.argsWithAllocator(allocator);
const prog_name = args.next() orelse return error.ExecutableNameMissing; const prog_name = args.next() orelse "vulkan-zig-generator";
var maybe_xml_path: ?[]const u8 = null; var maybe_xml_path: ?[]const u8 = null;
var maybe_out_path: ?[]const u8 = null; var maybe_out_path: ?[]const u8 = null;
@@ -26,7 +22,7 @@ pub fn main() !void {
while (args.next()) |arg| { while (args.next()) |arg| {
if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
@setEvalBranchQuota(2000); @setEvalBranchQuota(2000);
try stderr.writer().print( std.io.getStdOut().writer().print(
\\Utility to generate a Zig binding from the Vulkan XML API registry. \\Utility to generate a Zig binding from the Vulkan XML API registry.
\\ \\
\\The most recent Vulkan XML API registry can be obtained from \\The most recent Vulkan XML API registry can be obtained from
@@ -34,64 +30,98 @@ pub fn main() !void {
\\and the most recent LunarG Vulkan SDK version can be found at \\and the most recent LunarG Vulkan SDK version can be found at
\\$VULKAN_SDK/x86_64/share/vulkan/registry/vk.xml. \\$VULKAN_SDK/x86_64/share/vulkan/registry/vk.xml.
\\ \\
\\Usage: {s} [options] <spec xml path> <output zig source>
\\Options:
\\-h --help show this message and exit.
\\-a --api <api> Generate API for 'vulkan' or 'vulkansc'. Defaults to 'vulkan'.
\\ \\
++ usage, ,
.{prog_name}, .{prog_name},
); ) catch |err| {
std.log.err("failed to write to stdout: {s}", .{@errorName(err)});
std.process.exit(1);
};
return; return;
} else if (std.mem.eql(u8, arg, "-a") or std.mem.eql(u8, arg, "--api")) { } else if (std.mem.eql(u8, arg, "-a") or std.mem.eql(u8, arg, "--api")) {
const api_str = args.next() orelse { const api_str = args.next() orelse {
try stderr.writer().print("Error: {s} expects argument <api>\n", .{arg}); invalidUsage(prog_name, "{s} expects argument <api>", .{arg});
return;
}; };
api = std.meta.stringToEnum(generator.Api, api_str) orelse { api = std.meta.stringToEnum(generator.Api, api_str) orelse {
try stderr.writer().print("Error: Invalid api '{s}'", .{api_str}); invalidUsage(prog_name, "invalid api '{s}'", .{api_str});
return;
}; };
} else if (maybe_xml_path == null) { } else if (maybe_xml_path == null) {
maybe_xml_path = arg; maybe_xml_path = arg;
} else if (maybe_out_path == null) { } else if (maybe_out_path == null) {
maybe_out_path = arg; maybe_out_path = arg;
} else { } else {
try stderr.writer().print("Error: Superficial argument '{s}'\n", .{arg}); invalidUsage(prog_name, "superficial argument '{s}'", .{arg});
return;
} }
} }
const xml_path = maybe_xml_path orelse { const xml_path = maybe_xml_path orelse {
try stderr.writer().print("Error: Missing required argument <spec xml path>\n" ++ usage, .{prog_name}); invalidUsage(prog_name, "missing required argument <spec xml path>", .{});
return;
}; };
const out_path = maybe_out_path orelse { const out_path = maybe_out_path orelse {
try stderr.writer().print("Error: Missing required argument <output zig source>\n" ++ usage, .{prog_name}); invalidUsage(prog_name, "missing required argument <output zig source>", .{});
return;
}; };
const cwd = std.fs.cwd(); const cwd = std.fs.cwd();
const xml_src = cwd.readFileAlloc(allocator, xml_path, std.math.maxInt(usize)) catch |err| { const xml_src = cwd.readFileAlloc(allocator, xml_path, std.math.maxInt(usize)) catch |err| {
try stderr.writer().print("Error: Failed to open input file '{s}' ({s})\n", .{ xml_path, @errorName(err) }); std.log.err("failed to open input file '{s}' ({s})", .{ xml_path, @errorName(err) });
return; std.process.exit(1);
}; };
var out_buffer = std.ArrayList(u8).init(allocator); var out_buffer = std.ArrayList(u8).init(allocator);
try generator.generate(allocator, api, xml_src, out_buffer.writer()); generator.generate(allocator, api, xml_src, out_buffer.writer()) catch |err| switch (err) {
try out_buffer.append(0); error.InvalidXml => {
std.log.err("invalid vulkan registry - invalid xml", .{});
std.log.err("please check that the correct vk.xml file is passed", .{});
std.process.exit(1);
},
error.InvalidRegistry => {
std.log.err("invalid vulkan registry - registry is valid xml but contents are invalid", .{});
std.log.err("please check that the correct vk.xml file is passed", .{});
std.process.exit(1);
},
error.UnhandledBitfieldStruct => {
std.log.err("unhandled struct with bit fields detected in vk.xml", .{});
std.log.err("this is a bug in vulkan-zig", .{});
std.log.err("please make a bug report at https://github.com/Snektron/vulkan-zig/issues/", .{});
std.process.exit(1);
},
error.OutOfMemory => @panic("oom"),
};
out_buffer.append(0) catch @panic("oom");
const src = out_buffer.items[0 .. out_buffer.items.len - 1 :0]; const src = out_buffer.items[0 .. out_buffer.items.len - 1 :0];
const tree = try std.zig.Ast.parse(allocator, src, .zig); const tree = std.zig.Ast.parse(allocator, src, .zig) catch |err| switch (err) {
const formatted = try tree.render(allocator); error.OutOfMemory => @panic("oom"),
};
if (tree.errors.len > 0) {
std.log.err("generated invalid zig code", .{});
std.log.err("this is a bug in vulkan-zig", .{});
std.log.err("please make a bug report at https://github.com/Snektron/vulkan-zig/issues/", .{});
std.process.exit(1);
}
const formatted = tree.render(allocator) catch |err| switch (err) {
error.OutOfMemory => @panic("oom"),
};
defer allocator.free(formatted); defer allocator.free(formatted);
if (std.fs.path.dirname(out_path)) |dir| { if (std.fs.path.dirname(out_path)) |dir| {
cwd.makePath(dir) catch |err| { cwd.makePath(dir) catch |err| {
try stderr.writer().print("Error: Failed to create output directory '{s}' ({s})\n", .{ dir, @errorName(err) }); std.log.err("failed to create output directory '{s}' ({s})", .{ dir, @errorName(err) });
return; std.process.exit(1);
}; };
} }
cwd.writeFile(out_path, formatted) catch |err| { cwd.writeFile(out_path, formatted) catch |err| {
try stderr.writer().print("Error: Failed to write to output file '{s}' ({s})\n", .{ out_path, @errorName(err) }); std.log.err("failed to write to output file '{s}' ({s})", .{ out_path, @errorName(err) });
return; std.process.exit(1);
}; };
} }

View File

@@ -300,7 +300,6 @@ fn lenToPointer(fields: Fields, len: []const u8) std.meta.Tuple(&.{ registry.Poi
} }
fn parsePointerMeta(fields: Fields, type_info: *registry.TypeInfo, elem: *xml.Element) !void { fn parsePointerMeta(fields: Fields, type_info: *registry.TypeInfo, elem: *xml.Element) !void {
var len_attribute_depth: usize = 0; var len_attribute_depth: usize = 0;
if (elem.getAttribute("len")) |lens| { if (elem.getAttribute("len")) |lens| {
@@ -327,7 +326,6 @@ fn parsePointerMeta(fields: Fields, type_info: *registry.TypeInfo, elem: *xml.El
} }
} }
var current_depth: usize = 0; var current_depth: usize = 0;
if (elem.getAttribute("optional")) |optionals| { if (elem.getAttribute("optional")) |optionals| {
@@ -343,7 +341,6 @@ fn parsePointerMeta(fields: Fields, type_info: *registry.TypeInfo, elem: *xml.El
current_type_info.pointer.is_optional = current_type_info.pointer.is_optional =
is_already_optional or mem.eql(u8, optional_str, "true"); is_already_optional or mem.eql(u8, optional_str, "true");
} else { } else {
// There is no information for this pointer, probably incorrect. // There is no information for this pointer, probably incorrect.
// Currently there is one definition where this is the case, VkCudaLaunchInfoNV. // Currently there is one definition where this is the case, VkCudaLaunchInfoNV.