diff --git a/README.md b/README.md index c10794c..784852f 100644 --- a/README.md +++ b/README.md @@ -308,8 +308,6 @@ pub const ${name} align(@alignOf(u32)) = @embedFile("${path}").*; See [build.zig](build.zig) for a working example. ## Limitations -* Currently, the self-hosted version of Zig's cache-hash API is not yet ready for usage, which means that the bindings are regenerated every time an executable is built. - * vulkan-zig has as of yet no functionality for selecting feature levels and extensions when generating bindings. This is because when an extension is promoted to Vulkan core, its fields and commands are renamed to lose the extensions author tag (for example, VkSemaphoreWaitFlagsKHR was renamed to VkSemaphoreWaitFlags when it was promoted from an extension to Vulkan 1.2 core). This leads to inconsistencies when only items from up to a certain feature level is included, as these promoted items then need to re-gain a tag. ## Example diff --git a/build.zig b/build.zig index df1c1f2..1655225 100644 --- a/build.zig +++ b/build.zig @@ -1,6 +1,5 @@ const std = @import("std"); const vkgen = @import("generator/index.zig"); -const Step = std.build.Step; pub const ShaderCompileStep = vkgen.ShaderCompileStep; pub const VkGenerateStep = vkgen.VkGenerateStep; diff --git a/generator/build_integration.zig b/generator/build_integration.zig index 87264f3..382a6c8 100644 --- a/generator/build_integration.zig +++ b/generator/build_integration.zig @@ -1,8 +1,5 @@ const std = @import("std"); -const path = std.fs.path; -const Builder = std.build.Builder; -const Step = std.build.Step; -const GeneratedFile = std.build.GeneratedFile; +const Build = std.Build; /// Utility functionality to help with compiling shaders from build.zig. /// Invokes a shader compile command (e.g., glslc ...) for each shader @@ -23,7 +20,7 @@ pub const ShaderCompileStep = struct { /// To ensure that if compilation options change, the shader is recompiled /// properly. - fn hash(self: ShaderOptions, b: *Builder, hasher: anytype) !void { + fn hash(self: ShaderOptions, b: *Build, hasher: anytype) !void { for (self.args) |arg| { hasher.update(arg); } @@ -59,8 +56,7 @@ pub const ShaderCompileStep = struct { options: ShaderOptions, }; - step: Step, - b: *Builder, + step: Build.Step, /// The command and optional arguments used to invoke the shader compiler. compile_command: []const []const u8, @@ -73,17 +69,21 @@ pub const ShaderCompileStep = struct { /// The main Zig file that contains all the shaders. Each shader is included as /// `pub const ${name} align(@alignOf(u32))= @embedFile("${path").*;` - generated_file: GeneratedFile, + generated_file: Build.GeneratedFile, /// Create a ShaderCompileStep for `builder`. When this step is invoked by the build /// system, ` ` is invoked for each shader. /// For example, if one calls this with `create(b, "glslc", "-o")` and then /// `c.addShader("vertex", "vertex.glsl", .{})`, the command will be `glslc vertex.glsl -o ` - pub fn create(builder: *Builder, compile_command: []const []const u8, output_flag: []const u8) *ShaderCompileStep { + pub fn create(builder: *Build, compile_command: []const []const u8, output_flag: []const u8) *ShaderCompileStep { const self = builder.allocator.create(ShaderCompileStep) catch unreachable; self.* = .{ - .step = Step.init(.custom, "shaders", builder.allocator, make), - .b = builder, + .step = Build.Step.init(.{ + .id = .custom, + .name = "shaders", + .owner = builder, + .makeFn = make, + }), .compile_command = builder.dupeStrings(compile_command), .output_flag = builder.dupe(output_flag), .shaders = std.ArrayList(Shader).init(builder.allocator), @@ -94,14 +94,14 @@ pub const ShaderCompileStep = struct { } /// Returns the shaders module with name. - pub fn getModule(self: *ShaderCompileStep) *std.build.Module { - return self.b.createModule(.{ + pub fn getModule(self: *ShaderCompileStep) *Build.Module { + return self.step.owner.createModule(.{ .source_file = self.getSource(), }); } /// Returns the file source for the generated shader resource code. - pub fn getSource(self: *ShaderCompileStep) std.build.FileSource { + pub fn getSource(self: *ShaderCompileStep) Build.FileSource { return .{ .generated = &self.generated_file }; } @@ -110,7 +110,8 @@ pub const ShaderCompileStep = struct { /// This path can then be used to include the binary into an executable, for example by passing it /// to @embedFile via an additional generated file. pub fn add(self: *ShaderCompileStep, name: []const u8, src: []const u8, options: ShaderOptions) void { - const full_source_path = self.b.build_root.join(self.b.allocator, &.{src}) catch unreachable; + const b = self.step.owner; + const full_source_path = b.build_root.join(b.allocator, &.{src}) catch unreachable; self.shaders.append(.{ .name = name, .source_path = full_source_path, @@ -120,8 +121,9 @@ pub const ShaderCompileStep = struct { /// Create a hash of a shader's source contents. fn hashShaderToFileName(self: *ShaderCompileStep, shader: Shader) ![64]u8 { + const b = self.step.owner; const source = std.fs.cwd().readFileAlloc( - self.b.allocator, + b.allocator, shader.source_path, std.math.maxInt(usize), ) catch |err| switch (err) { @@ -140,7 +142,7 @@ pub const ShaderCompileStep = struct { hasher.update(source); // Not only the shader source must be the same to ensure uniqueness - // the compilation options must be the same as well! - try shader.options.hash(self.b, &hasher); + try shader.options.hash(b, &hasher); // And the compile command, too. for (self.compile_command) |cmd| { hasher.update(cmd); @@ -159,26 +161,28 @@ pub const ShaderCompileStep = struct { } /// Internal build function. - fn make(step: *Step) !void { + fn make(step: *Build.Step, progress: *std.Progress.Node) !void { + _ = progress; + const b = step.owner; const self = @fieldParentPtr(ShaderCompileStep, "step", step); const cwd = std.fs.cwd(); - var cmd = std.ArrayList([]const u8).init(self.b.allocator); + var cmd = std.ArrayList([]const u8).init(b.allocator); try cmd.appendSlice(self.compile_command); const base_cmd_len = cmd.items.len; - var shaders_file_contents = std.ArrayList(u8).init(self.b.allocator); + var shaders_file_contents = std.ArrayList(u8).init(b.allocator); const shaders_out = shaders_file_contents.writer(); - const shaders_dir = try self.b.cache_root.join( - self.b.allocator, + const shaders_dir = try b.cache_root.join( + b.allocator, &.{cache_dir}, ); try cwd.makePath(shaders_dir); for (self.shaders.items) |shader| { const shader_basename = try self.hashShaderToFileName(shader); - const shader_out_path = try std.fs.path.join(self.b.allocator, &.{ + const shader_out_path = try std.fs.path.join(b.allocator, &.{ shaders_dir, &shader_basename, }); @@ -203,7 +207,7 @@ pub const ShaderCompileStep = struct { try cmd.appendSlice(shader.options.args); try cmd.appendSlice(&.{ shader.source_path, self.output_flag, shader_out_path }); - try self.b.spawnChild(cmd.items); + try step.evalChildProcess(cmd.items); } // Generate a file name for the shaders zig source based on the contents of shaders_file_contents. @@ -214,7 +218,7 @@ pub const ShaderCompileStep = struct { hasher.update(shaders_file_contents.items); const shaders_path = try std.fs.path.join( - self.b.allocator, + b.allocator, &.{ shaders_dir, &digest(&hasher) }, ); diff --git a/generator/vulkan/build_integration.zig b/generator/vulkan/build_integration.zig index 2cc55bb..74eb754 100644 --- a/generator/vulkan/build_integration.zig +++ b/generator/vulkan/build_integration.zig @@ -1,17 +1,14 @@ const std = @import("std"); const generator = @import("generator.zig"); -const path = std.fs.path; const Build = std.Build; -const Step = Build.Step; /// build.zig integration for Vulkan binding generation. This step can be used to generate /// Vulkan bindings at compiletime from vk.xml, by providing the path to vk.xml and the output /// path relative to zig-cache. The final package can then be obtained by `package()`, the result /// of which can be added to the project using `std.Build.addModule`. pub const GenerateStep = struct { - step: Step, - builder: *Build, - generated_file: std.build.GeneratedFile, + step: Build.Step, + generated_file: Build.GeneratedFile, /// The path to vk.xml spec_path: []const u8, /// The API to generate for. @@ -25,8 +22,12 @@ pub const GenerateStep = struct { pub fn create(builder: *Build, spec_path: []const u8) *GenerateStep { const self = builder.allocator.create(GenerateStep) catch unreachable; self.* = .{ - .step = Step.init(.custom, "vulkan-generate", builder.allocator, make), - .builder = builder, + .step = Build.Step.init(.{ + .id = .custom, + .name = "vulkan-generate", + .owner = builder, + .makeFn = make, + }), .generated_file = .{ .step = &self.step, }, @@ -54,14 +55,14 @@ pub const GenerateStep = struct { } /// Returns the module with the generated budings, with name `module_name`. - pub fn getModule(self: *GenerateStep) *std.build.Module { - return self.builder.createModule(.{ + pub fn getModule(self: *GenerateStep) *Build.Module { + return self.step.owner.createModule(.{ .source_file = self.getSource(), }); } /// Returns the file source for the generated bindings. - pub fn getSource(self: *GenerateStep) std.build.FileSource { + pub fn getSource(self: *GenerateStep) Build.FileSource { return .{ .generated = &self.generated_file }; } @@ -69,34 +70,31 @@ pub const GenerateStep = struct { /// the final bindings. The resulting generated bindings are not formatted, which is why an ArrayList /// writer is passed instead of a file writer. This is then formatted into standard formatting /// by parsing it and rendering with `std.zig.parse` and `std.zig.render` respectively. - fn make(step: *Step) !void { + fn make(step: *Build.Step, progress: *std.Progress.Node) !void { + _ = progress; + const b = step.owner; const self = @fieldParentPtr(GenerateStep, "step", step); const cwd = std.fs.cwd(); - var man = self.builder.cache.obtain(); + var man = b.cache.obtain(); defer man.deinit(); - const spec = try cwd.readFileAlloc(self.builder.allocator, self.spec_path, std.math.maxInt(usize)); + const spec = try cwd.readFileAlloc(b.allocator, self.spec_path, std.math.maxInt(usize)); // TODO: Look into whether this is the right way to be doing // this - maybe the file-level caching API has some benefits I // don't understand. man.hash.addBytes(spec); - const already_exists = man.hit() catch |err| @panic(switch (err) { - inline else => |e| "Cache error: " ++ @errorName(e), - }); + const already_exists = try step.cacheHit(&man); const digest = man.final(); - const output_file_path = try self.builder.cache_root.join( - self.builder.allocator, - &.{ "o", &digest, "vk.zig" }, - ); + const output_file_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, "vk.zig" }); if (already_exists) { self.generated_file.path = output_file_path; return; } - var out_buffer = std.ArrayList(u8).init(self.builder.allocator); - generator.generate(self.builder.allocator, self.api, spec, out_buffer.writer()) catch |err| switch (err) { + var out_buffer = std.ArrayList(u8).init(b.allocator); + generator.generate(b.allocator, self.api, spec, out_buffer.writer()) catch |err| switch (err) { error.InvalidXml => { std.log.err("invalid vulkan registry - invalid xml", .{}); std.log.err("please check that the correct vk.xml file is passed", .{}); @@ -118,10 +116,10 @@ pub const GenerateStep = struct { try out_buffer.append(0); const src = out_buffer.items[0 .. out_buffer.items.len - 1 :0]; - const tree = try std.zig.Ast.parse(self.builder.allocator, src, .zig); + const tree = try std.zig.Ast.parse(b.allocator, src, .zig); std.debug.assert(tree.errors.len == 0); // If this triggers, vulkan-zig produced invalid code. - const formatted = try tree.render(self.builder.allocator); + const formatted = try tree.render(b.allocator); const output_dir_path = std.fs.path.dirname(output_file_path).?; cwd.makePath(output_dir_path) catch |err| { @@ -131,6 +129,6 @@ pub const GenerateStep = struct { try cwd.writeFile(output_file_path, formatted); self.generated_file.path = output_file_path; - try man.writeManifest(); + try step.writeManifest(&man); } };