diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3778975..02377dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,7 +37,7 @@ jobs: - name: Build with latest zig & vk.xml run: | - zig build -Dvulkan-registry=./vk.xml + zig build -Dexample-registry=./examples/vk.xml - name: Archive vk.xml uses: actions/upload-artifact@v2 diff --git a/README.md b/README.md index e6fda32..c10794c 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,44 @@ pub fn build(b: *Builder) void { ``` This reads vk.xml, parses its contents, and renders the Vulkan bindings to "vk.zig", which is then formatted and placed in `zig-cache`. The resulting file can then be added to an executable by using `addModule`, after which the bindings will be made available to the executable under the name passed to `getModule`. +### Generation with the package manager from build.zig +There is also support for adding this project as a dependency through zig package manager in its current form. In order to do this, add this repo as a dependency in your build.zig.zon: +```zig +.{ + // -- snip -- + .dependencies = .{ + // -- snip -- + .vulkan_zig = .{ + .url = "https://github.com/Snektron/vulkan-zig/archive/.tar.gz", + .hash = "", + }, + }, +} +``` +And then in your build.zig file, you'll need to add a line like this to your build function: +```zig +const vkzig_dep = b.dependency("vulkan_zig", .{ + .registry = b.pathFromRoot("path/to/vk.xml"), +}); +const vkzig_bindings = vkzig_dep.module("vulkan-zig"); +exe.addModule("vulkan-zig", vkzig_bindings); +``` +That will allow you to `@import("vulkan-zig")` in your executable's source. + +### Manual generation with the package manager from build.zig +In the event you have a specific need for it, the generator executable is made available through the dependency, allowing you to run the executable as a build step in your own build.zig file. +Doing so should look a bit like this: +```zig +const vkzig_dep = b.dependency("vulkan_zig", .{}); // passing the registry argument here not necessary when using the executable directly +const vkzig_generator = vkzig_dep.artifact("generator"); + +vkzig_generator.addArg("path/to/vk.xml"); +const vkzig_src = vkzig_generator.addOutputFileArg("vk.zig"); // this is the FileSource representing the generated bindings + +const vkzig_bindings = b.createModule("vulkan-zig", .{ .source_file = vkzig_src }); +exe.addModule("vulkan-zig", vkzig_bindings); +``` + ### Function & field renaming Functions and fields are renamed to be more or less in line with [Zig's standard library style](https://ziglang.org/documentation/master/#Style-Guide): * The vk prefix is removed everywhere diff --git a/build.zig b/build.zig index 99b565f..376045f 100644 --- a/build.zig +++ b/build.zig @@ -5,15 +5,36 @@ const Step = std.build.Step; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const vk_xml_path: ?[]const u8 = b.option([]const u8, "registry", "Override the path to the Vulkan registry"); + // using the package manager, this artifact can be obtained by the user + // through `b.dependency(, .{}).artifact("generator")`. + // with that, the user need only `.addArg("path/to/vk.xml")`, and then obtain + // a file source to the generated code with `.addOutputArg("vk.zig")` const generator_exe = b.addExecutable(.{ - .name = "vulkan-zig-generator", + .name = "generator", .root_source_file = .{ .path = "generator/main.zig" }, .target = target, .optimize = optimize, }); generator_exe.install(); + // or they can skip all that, and just make sure to pass `.registry = "path/to/vk.xml"` to `b.dependency`, + // and then obtain the module directly via `.module("vulkan-zig")`. + if (vk_xml_path) |path| { + const generate_cmd = b.addRunArtifact(generator_exe); + + if (!std.fs.path.isAbsolute(path)) @panic("Make sure to assign an absolute path to the `registry` option (see: std.Build.pathFromRoot).\n"); + generate_cmd.addArg(path); + + b.addModule(.{ + .name = "vulkan-zig", + .source_file = generate_cmd.addOutputFileArg("vk.zig"), + }); + } + + // remaindure of the script is for local testing + const triangle_exe = b.addExecutable(.{ .name = "triangle", .root_source_file = .{ .path = "examples/triangle.zig" }, @@ -24,9 +45,8 @@ pub fn build(b: *std.Build) void { triangle_exe.linkLibC(); triangle_exe.linkSystemLibrary("glfw"); - const vk_xml_path = b.option([]const u8, "vulkan-registry", "Override the path to the Vulkan registry") orelse "examples/vk.xml"; - - const gen = vkgen.VkGenerateStep.create(b, vk_xml_path, "vk.zig"); + const example_registry = b.option([]const u8, "example-registry", "Override the path to the Vulkan registry used for the examples") orelse "examples/vk.xml"; + const gen = vkgen.VkGenerateStep.create(b, example_registry); triangle_exe.addModule("vulkan", gen.getModule()); const shaders = vkgen.ShaderCompileStep.create( @@ -38,8 +58,10 @@ pub fn build(b: *std.Build) void { shaders.add("triangle_frag", "examples/shaders/triangle.frag", .{}); triangle_exe.addModule("shaders", shaders.getModule()); - const triangle_run_cmd = triangle_exe.run(); + const triangle_run_cmd = b.addRunArtifact(triangle_exe); triangle_run_cmd.step.dependOn(b.getInstallStep()); + triangle_run_cmd.condition = .always; + const triangle_run_step = b.step("run-triangle", "Run the triangle example"); triangle_run_step.dependOn(&triangle_run_cmd.step); diff --git a/generator/build_integration.zig b/generator/build_integration.zig index 3252a31..8a9c44c 100644 --- a/generator/build_integration.zig +++ b/generator/build_integration.zig @@ -28,10 +28,7 @@ pub const ShaderCompileStep = struct { hasher.update(arg); } for (self.watched_files) |file_path| { - const full_path = std.fs.path.join(b.allocator, &.{ - b.build_root, - file_path, - }) catch unreachable; + const full_path = b.build_root.join(b.allocator, &.{file_path}) catch unreachable; const source = std.fs.cwd().readFileAlloc( b.allocator, @@ -113,10 +110,7 @@ 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 = std.fs.path.join(self.b.allocator, &.{ - self.b.build_root, - src, - }) catch unreachable; + const full_source_path = self.b.build_root.join(self.b.allocator, &.{src}) catch unreachable; self.shaders.append(.{ .name = name, .source_path = full_source_path, @@ -176,9 +170,12 @@ pub const ShaderCompileStep = struct { var shaders_file_contents = std.ArrayList(u8).init(self.b.allocator); const shaders_out = shaders_file_contents.writer(); - const shaders_dir = try std.fs.path.join( + const shaders_dir = try self.b.build_root.join( self.b.allocator, - &.{ self.b.build_root, self.b.cache_root, cache_dir }, + &.{try self.b.cache_root.join( + self.b.allocator, + &.{cache_dir}, + )}, ); try cwd.makePath(shaders_dir); diff --git a/generator/vulkan/build_integration.zig b/generator/vulkan/build_integration.zig index 6beab76..bae6d1c 100644 --- a/generator/vulkan/build_integration.zig +++ b/generator/vulkan/build_integration.zig @@ -11,31 +11,22 @@ const Step = Build.Step; pub const GenerateStep = struct { step: Step, builder: *Build, - + generated_file: std.build.GeneratedFile, /// The path to vk.xml spec_path: []const u8, - generated_file: std.build.GeneratedFile, - /// Initialize a Vulkan generation step, for `builder`. `spec_path` is the path to /// vk.xml, relative to the project root. The generated bindings will be placed at /// `out_path`, which is relative to the zig-cache directory. - pub fn create(builder: *Build, spec_path: []const u8, out_path: []const u8) *GenerateStep { + pub fn create(builder: *Build, spec_path: []const u8) *GenerateStep { const self = builder.allocator.create(GenerateStep) catch unreachable; - const full_out_path = path.join(builder.allocator, &[_][]const u8{ - builder.build_root, - builder.cache_root, - out_path, - }) catch unreachable; - self.* = .{ .step = Step.init(.custom, "vulkan-generate", builder.allocator, make), .builder = builder, - .spec_path = spec_path, .generated_file = .{ .step = &self.step, - .path = full_out_path, }, + .spec_path = spec_path, }; return self; } @@ -44,13 +35,13 @@ pub const GenerateStep = struct { /// root. Typically, the location of the LunarG SDK root can be retrieved by querying for the VULKAN_SDK /// environment variable, set by activating the environment setup script located in the SDK root. /// `builder` and `out_path` are used in the same manner as `init`. - pub fn createFromSdk(builder: *Build, sdk_path: []const u8, out_path: []const u8) *GenerateStep { + pub fn createFromSdk(builder: *Build, sdk_path: []const u8, output_name: []const u8) *GenerateStep { const spec_path = std.fs.path.join( builder.allocator, &[_][]const u8{ sdk_path, "share/vulkan/registry/vk.xml" }, ) catch unreachable; - return create(builder, spec_path, out_path); + return create(builder, spec_path, output_name); } /// Returns the module with the generated budings, with name `module_name`. @@ -73,7 +64,27 @@ pub const GenerateStep = struct { const self = @fieldParentPtr(GenerateStep, "step", step); const cwd = std.fs.cwd(); + var man = self.builder.cache.obtain(); + defer man.deinit(); + const spec = try cwd.readFileAlloc(self.builder.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 digest = man.final(); + const output_file_path = try self.builder.cache_root.join( + self.builder.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); generate(self.builder.allocator, spec, out_buffer.writer()) catch |err| switch (err) { @@ -101,10 +112,16 @@ pub const GenerateStep = struct { const tree = try std.zig.Ast.parse(self.builder.allocator, src, .zig); std.debug.assert(tree.errors.len == 0); // If this triggers, vulkan-zig produced invalid code. - var formatted = try tree.render(self.builder.allocator); + const formatted = try tree.render(self.builder.allocator); - const dir = path.dirname(self.generated_file.path.?).?; - try cwd.makePath(dir); - try cwd.writeFile(self.generated_file.path.?, formatted); + const output_dir_path = std.fs.path.dirname(output_file_path).?; + cwd.makePath(output_dir_path) catch |err| { + std.debug.print("unable to make path {s}: {s}\n", .{ output_dir_path, @errorName(err) }); + return err; + }; + + try cwd.writeFile(output_file_path, formatted); + self.generated_file.path = output_file_path; + try man.writeManifest(); } };