diff --git a/build.zig b/build.zig index 5204b3d87..d09cc177d 100644 --- a/build.zig +++ b/build.zig @@ -2,6 +2,7 @@ const std = @import("std"); const assert = std.debug.assert; const builtin = @import("builtin"); const buildpkg = @import("src/build/main.zig"); +const translate_c = @import("translate_c"); /// App version from build.zig.zon. const app_zon_version = @import("build.zig.zon").version; @@ -437,21 +438,12 @@ fn addGhosttyH( target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, ) void { - const translate_c = b.lazyImport(@This(), "translate_c") orelse return; - const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse return; - - const translated: translate_c.Translator = .init(translate_c_dep, .{ - .c_source_file = b.addWriteFiles().add( - "hb_c.h", - \\#include - , - ), + translate_c.addImportToModule(b, "ghostty.h", module, .{ + .source = .{ .includes = .{ .files = &.{ + .{ .path = "ghostty.h" }, + } } }, .target = target, .optimize = optimize, - .link_libc = true, - }); - - translated.addSystemIncludePath(b.path("include")); - - module.addImport("ghostty.h", translated.mod); + .system_include_paths = &.{b.path("include")}, + }) catch unreachable; } diff --git a/build.zig.zon b/build.zig.zon index 1c299c4b3..7e40c97c0 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,15 +5,9 @@ .fingerprint = 0x64407a2a0b4147e5, .minimum_zig_version = "0.16.0", .dependencies = .{ - // External translate-c - // NOTE: We might not need to keep this around forever, but we need it - // for at least the 0.16.0 release cycle since we need fixes in - // Aro/translate-c itself. - .translate_c = .{ - .lazy = true, - .url = "https://codeberg.org/vancluever/translate-c/archive/4e879eb8aba615de112eabd1231ea6e01920cead.tar.gz", - .hash = "translate_c-0.0.0-Q_BUWhVNBwDOEcIqub4VFPJPB6D9dgwzUMHTX5KWr8Xr", - }, + // Our higher-level C translation helper (this also helps ensure we + // only need to maintain one external dependency point) + .translate_c = .{ .path = "./pkg/translate-c" }, // Zig libs .libxev = .{ diff --git a/pkg/gtk4-layer-shell/build.zig b/pkg/gtk4-layer-shell/build.zig index 38dfd2c8a..59fcf6a81 100644 --- a/pkg/gtk4-layer-shell/build.zig +++ b/pkg/gtk4-layer-shell/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const translate_c = @import("translate_c"); const version = @import("build.zig.zon").version; @@ -19,36 +20,32 @@ pub fn build(b: *std.Build) !void { }); translate: { - const translate_c = b.lazyImport(@This(), "translate_c") orelse break :translate; - const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse break :translate; - const Translator = translate_c.Translator; + const link_system_libs: []const []const u8, const include_paths = + if (b.systemIntegrationOption("gtk4-layer-shell", .{})) + .{ &.{ "gtk4", "gtk4-layer-shell-0" }, &.{} } + else + .{ + &.{"gtk4"}, + paths: { + // local deps (non-system layer-shell/wayland) + const deps = try LocalDeps.get(b) orelse break :translate; + break :paths &.{ + deps.upstream.path("include"), + deps.upstream.path("src"), + deps.client_header_directory, + }; + }, + }; - const link_system_libs_full: [2]Translator.LinkSystemLib = .{ - .{ .name = "gtk4", .options = dynamic_link_opts }, - .{ .name = "gtk4-layer-shell-0", .options = dynamic_link_opts }, - }; - - const headers = Translator.init(translate_c_dep, .{ - .c_source_file = b.addWriteFiles().add("c.h", - \\#include - ), + try translate_c.addImportToModule(b, "c", module, .{ + .source = .{ .includes = .{ + .files = &.{.{ .path = "gtk4-layer-shell.h" }}, + } }, .target = target, .optimize = optimize, - .link_system_libs = if (b.systemIntegrationOption("gtk4-layer-shell", .{})) - &link_system_libs_full - else - link_system_libs_full[0..1], + .link_system_libs = link_system_libs, + .include_paths = include_paths, }); - - if (!b.systemIntegrationOption("gtk4-layer-shell", .{})) { - // local deps (non-system layer-shell/wayland) - const deps = try LocalDeps.get(b) orelse break :translate; - headers.addIncludePath(deps.upstream.path("include")); - headers.addIncludePath(deps.upstream.path("src")); - headers.addIncludePath(deps.client_header_directory); - } - - module.addImport("c", headers.mod); } if (!b.systemIntegrationOption("gtk4-layer-shell", .{})) { diff --git a/pkg/gtk4-layer-shell/build.zig.zon b/pkg/gtk4-layer-shell/build.zig.zon index fd9d0cf2a..104744091 100644 --- a/pkg/gtk4-layer-shell/build.zig.zon +++ b/pkg/gtk4-layer-shell/build.zig.zon @@ -4,6 +4,8 @@ .fingerprint = 0x4b96f9483c6feeb1, .paths = .{""}, .dependencies = .{ + .translate_c = .{ .path = "../translate-c" }, + .gtk4_layer_shell = .{ .url = "https://deps.files.ghostty.org/gtk4-layer-shell-1.1.0.tar.gz", .hash = "N-V-__8AALiNBAA-_0gprYr92CjrMj1I5bqNu0TSJOnjFNSr", @@ -14,10 +16,5 @@ .hash = "N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S", .lazy = true, }, - .translate_c = .{ - .url = "https://codeberg.org/vancluever/translate-c/archive/4e879eb8aba615de112eabd1231ea6e01920cead.tar.gz", - .hash = "translate_c-0.0.0-Q_BUWhVNBwDOEcIqub4VFPJPB6D9dgwzUMHTX5KWr8Xr", - .lazy = true, - }, }, } diff --git a/pkg/harfbuzz/build.zig b/pkg/harfbuzz/build.zig index 5c78b262e..4652c8565 100644 --- a/pkg/harfbuzz/build.zig +++ b/pkg/harfbuzz/build.zig @@ -1,5 +1,6 @@ const std = @import("std"); const apple_sdk = @import("apple_sdk"); +const translate_c = @import("translate_c"); const root_build_container = @This(); @@ -18,12 +19,35 @@ pub fn build(b: *std.Build) !void { .search_strategy = .mode_first, }; - const freetype = b.dependency("freetype", .{ + const freetype_dep = b.dependency("freetype", .{ .target = target, .optimize = optimize, .@"enable-libpng" = true, }); + const harfbuzz_c_builder: HarfBuzzC = .{ + .builder = b, + .options = .{ + .target = target, + .optimize = optimize, + .harfbuzz = if (b.systemIntegrationOption("harfbuzz", .{})) + .{ .dynamic = dynamic_link_opts } + else + .static, + .coretext = coretext_enabled, + .freetype = if (freetype_enabled) + .{ + .dependency = freetype_dep, + .link_mode = if (b.systemIntegrationOption("freetype", .{})) + .{ .dynamic = dynamic_link_opts } + else + .static, + } + else + null, + }, + }; + const module = harfbuzz: { const module = b.addModule("harfbuzz", .{ .root_source_file = b.path("main.zig"), @@ -31,7 +55,7 @@ pub fn build(b: *std.Build) !void { .optimize = optimize, .imports = if (target.result.os.tag.isDarwin()) &.{ - .{ .name = "freetype", .module = freetype.module("freetype") }, + .{ .name = "freetype", .module = freetype_dep.module("freetype") }, .{ .name = "macos", .module = b.dependency("macos", .{ .target = target, .optimize = optimize }) @@ -40,22 +64,11 @@ pub fn build(b: *std.Build) !void { } else &.{ - .{ .name = "freetype", .module = freetype.module("freetype") }, + .{ .name = "freetype", .module = freetype_dep.module("freetype") }, }, }); - try HarfBuzzC.addImportToModule(b, module, .{ - .target = target, - .optimize = optimize, - .harfbuzz = if (b.systemIntegrationOption("harfbuzz", .{})) .{ .dynamic = dynamic_link_opts } else .static, - .coretext = coretext_enabled, - .freetype = if (freetype_enabled) ft: { - break :ft if (b.systemIntegrationOption("freetype", .{})) - .{ .dynamic = dynamic_link_opts } - else - .static; - } else null, - }); + try harfbuzz_c_builder.addImportToModule(module); const options = b.addOptions(); options.addOption(bool, "coretext", coretext_enabled); @@ -74,120 +87,11 @@ pub fn build(b: *std.Build) !void { test_step.dependOn(&tests_run.step); if (!b.systemIntegrationOption("harfbuzz", .{})) { - const lib = try buildLib(b, .{ - .target = target, - .optimize = optimize, - - .coretext_enabled = coretext_enabled, - .freetype_enabled = freetype_enabled, - - .dynamic_link_opts = dynamic_link_opts, - }); - + const lib = try harfbuzz_c_builder.buildLib(); test_exe.root_module.linkLibrary(lib); } } -fn buildLib(b: *std.Build, options: anytype) !*std.Build.Step.Compile { - const target = options.target; - const optimize = options.optimize; - - const coretext_enabled = options.coretext_enabled; - const freetype_enabled = options.freetype_enabled; - - const freetype = b.dependency("freetype", .{ - .target = target, - .optimize = optimize, - .@"enable-libpng" = true, - }); - - const lib = b.addLibrary(.{ - .name = "harfbuzz", - .root_module = b.createModule(.{ - .target = target, - .optimize = optimize, - .link_libc = true, - // On MSVC, we must not use linkLibCpp because Zig unconditionally - // passes -nostdinc++ and then adds its bundled libc++/libc++abi - // include paths, which conflict with MSVC's own C++ runtime - // headers. The MSVC SDK include directories (added via linkLibC) - // contain both C and C++ headers, so linkLibCpp is not needed. - .link_libcpp = target.result.abi != .msvc, - }), - .linkage = .static, - }); - - if (target.result.os.tag.isDarwin()) { - try apple_sdk.addPaths(b, lib); - } - - const dynamic_link_opts = options.dynamic_link_opts; - - var flags: std.ArrayList([]const u8) = .empty; - defer flags.deinit(b.allocator); - try flags.appendSlice(b.allocator, &.{ - "-DHAVE_STDBOOL_H", - }); - // Disable ubsan for MSVC: Zig's ubsan runtime cannot be bundled - // on Windows (LNK4229), leaving __ubsan_handle_* unresolved when - // the static archive is consumed by an external linker. - if (target.result.abi == .msvc) { - try flags.appendSlice(b.allocator, &.{ - "-fno-sanitize=undefined", - "-fno-sanitize-trap=undefined", - }); - } - if (target.result.os.tag != .windows) { - try flags.appendSlice(b.allocator, &.{ - "-DHAVE_UNISTD_H", - "-DHAVE_SYS_MMAN_H", - "-DHAVE_PTHREAD=1", - }); - } - - // Freetype - _ = b.systemIntegrationOption("freetype", .{}); // So it shows up in help - if (freetype_enabled) { - try flags.appendSlice(b.allocator, &.{ - "-DHAVE_FREETYPE=1", - - // Let's just assume a new freetype - "-DHAVE_FT_GET_VAR_BLEND_COORDINATES=1", - "-DHAVE_FT_SET_VAR_BLEND_COORDINATES=1", - "-DHAVE_FT_DONE_MM_VAR=1", - "-DHAVE_FT_GET_TRANSFORM=1", - }); - - if (b.systemIntegrationOption("freetype", .{})) { - lib.root_module.linkSystemLibrary("freetype2", dynamic_link_opts); - } else { - lib.root_module.linkLibrary(freetype.artifact("freetype")); - } - } - - if (coretext_enabled) { - try flags.appendSlice(b.allocator, &.{"-DHAVE_CORETEXT=1"}); - lib.root_module.linkFramework("CoreText", .{}); - } - - if (b.lazyDependency("harfbuzz", .{})) |upstream| { - lib.root_module.addIncludePath(upstream.path("src")); - lib.root_module.addCSourceFile(.{ - .file = upstream.path("src/harfbuzz.cc"), - .flags = flags.items, - }); - lib.installHeadersDirectory( - upstream.path("src"), - "", - .{ .include_extensions = &.{".h"} }, - ); - } - - b.installArtifact(lib); - - return lib; -} - const HarfBuzzC = struct { const AddImportToModuleOptions = struct { const LinkMode = union(enum) { @@ -199,161 +103,193 @@ const HarfBuzzC = struct { optimize: std.builtin.OptimizeMode, harfbuzz: LinkMode, coretext: bool, - freetype: ?LinkMode, + freetype: ?struct { + dependency: *std.Build.Dependency, + link_mode: LinkMode, + }, }; - fn fmtInclude(w: *std.Io.Writer, name: []const u8, mode: AddImportToModuleOptions.LinkMode) !void { - if (mode == .dynamic) { - try w.print("#include <{s}>\n", .{name}); - } else { - try w.print("#include \"{s}\"\n", .{name}); - } + builder: *std.Build, + options: AddImportToModuleOptions, + + fn appendInclude( + list: *std.ArrayList(translate_c.Options.IncludeFile), + name: []const u8, + mode: AddImportToModuleOptions.LinkMode, + ) void { + list.appendAssumeCapacity(.{ + .path = name, + .type = switch (mode) { + .static => .user, + .dynamic => .system, + }, + }); } - fn addImportToModule( - b: *std.Build, - module: *std.Build.Module, - options: AddImportToModuleOptions, - ) !void { - // TODO: There's a decent amount of duplication here right now. - // Basically we want to mirror what we're passing in buildLib to make - // sure that the translation is generated as correct as possible. - // Eventually, we want to try and unravel this as much as we can, to - // the point that ultimately all C flags and even link options are - // self-contained in the translation artifact. - // - // This is a bit tricky right now as there are situations where we - // provide a static library built straight off of the C file, hence the - // duplication. - // - // NOTE: This function de-allocates nothing as b.allocator is an arena - // (unfortunately not documented, but a cursory search in various - // communities or the issue trackers should turn up confirmation). + fn includeFiles(self: *const HarfBuzzC) ![]translate_c.Options.IncludeFile { + var len: usize = 1; + if (self.options.coretext) len += 1; + if (self.options.freetype != null) len += 1; + var includes_builder: std.ArrayList(translate_c.Options.IncludeFile) = + try .initCapacity(self.builder.allocator, len); - const translate_c = b.lazyImport(root_build_container, "translate_c") orelse return; - const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse return; + appendInclude(&includes_builder, "hb.h", self.options.harfbuzz); + if (self.options.coretext) appendInclude(&includes_builder, "hb-coretext.h", self.options.harfbuzz); + if (self.options.freetype != null) appendInclude(&includes_builder, "hb-ft.h", self.options.harfbuzz); - const c_source = c_source: { - var source_builder: std.Io.Writer.Allocating = .init(b.allocator); - try fmtInclude(&source_builder.writer, "hb.h", options.harfbuzz); + return includes_builder.items; + } - if (options.coretext) { - try fmtInclude(&source_builder.writer, "hb-coretext.h", options.harfbuzz); - } + fn systemLibs(self: *const HarfBuzzC) ![][]const u8 { + var len: usize = 1; + if (self.options.harfbuzz == .dynamic) len += 1; + if (self.options.freetype != null and self.options.freetype.?.link_mode == .dynamic) len += 1; + var libs_builder: std.ArrayList([]const u8) = try .initCapacity(self.builder.allocator, len); - if (options.freetype != null) { - try fmtInclude(&source_builder.writer, "hb-ft.h", options.harfbuzz); - } + if (self.options.harfbuzz == .dynamic) + libs_builder.appendAssumeCapacity("harfbuzz"); + if (self.options.freetype != null and self.options.freetype.?.link_mode == .dynamic) + libs_builder.appendAssumeCapacity("freetype2"); - break :c_source source_builder.written(); - }; + return libs_builder.items; + } - // Assemble system libs - const system_libs = libs: { - var libs_builder: std.ArrayList(translate_c.Translator.LinkSystemLib) = .empty; - if (options.harfbuzz == .dynamic) - try libs_builder.append(b.allocator, .{ .name = "harfbuzz", .options = options.harfbuzz.dynamic }); - if (options.freetype) |ft| { - if (ft == .dynamic) - try libs_builder.append(b.allocator, .{ .name = "freetype2", .options = ft.dynamic }); - } + fn includePaths(self: *const HarfBuzzC) ![]std.Build.LazyPath { + const hb_upstream: ?*std.Build.Dependency = if (self.options.harfbuzz == .static) + self.builder.lazyDependency("harfbuzz", .{}) + else + null; - break :libs libs_builder.items; - }; + const ft_upstream: ?*std.Build.Dependency = + if (self.options.freetype) |ft| ft: { + if (ft.link_mode == .static) break :ft ft.dependency.builder.lazyDependency("freetype", .{}); + break :ft null; + } else null; - // Assemble flags - const flags = flags: { - var flag_builder: std.ArrayList([]const u8) = .empty; - try flag_builder.appendSlice(b.allocator, &.{ - "-DHAVE_STDBOOL_H", - }); - // Disable ubsan for MSVC: Zig's ubsan runtime cannot be bundled - // on Windows (LNK4229), leaving __ubsan_handle_* unresolved when - // the static archive is consumed by an external linker. - if (options.target.result.abi == .msvc) { - try flag_builder.appendSlice(b.allocator, &.{ - "-fno-sanitize=undefined", - "-fno-sanitize-trap=undefined", - }); - } - if (options.target.result.os.tag != .windows) { - try flag_builder.appendSlice(b.allocator, &.{ - "-DHAVE_UNISTD_H", - "-DHAVE_SYS_MMAN_H", - "-DHAVE_PTHREAD=1", - }); - } + var len: usize = 0; + if (hb_upstream != null) len += 1; + if (ft_upstream != null) len += 1; + var paths_builder: std.ArrayList(std.Build.LazyPath) = try .initCapacity(self.builder.allocator, len); - // Freetype flags/non-system include paths - if (options.freetype != null) { - try flag_builder.appendSlice(b.allocator, &.{ - "-DHAVE_FREETYPE=1", + if (hb_upstream) |upstream| paths_builder.appendAssumeCapacity(upstream.path("src")); + if (ft_upstream) |upstream| paths_builder.appendAssumeCapacity(upstream.path("include")); - // Let's just assume a new freetype - "-DHAVE_FT_GET_VAR_BLEND_COORDINATES=1", - "-DHAVE_FT_SET_VAR_BLEND_COORDINATES=1", - "-DHAVE_FT_DONE_MM_VAR=1", - "-DHAVE_FT_GET_TRANSFORM=1", - }); - } + return paths_builder.items; + } - // Coretext - if (options.coretext) { - try flag_builder.appendSlice(b.allocator, &.{"-DHAVE_CORETEXT=1"}); - try flag_builder.appendSlice(b.allocator, &.{"-fblocks"}); - } + fn frameworks(self: *const HarfBuzzC) []const []const u8 { + return if (self.options.coretext) &.{"CoreText"} else &.{}; + } - break :flags flag_builder.items; - }; - - const hb_c: translate_c.Translator = .init(translate_c_dep, .{ - .c_source_file = b.addWriteFiles().add( - "hb_c.h", - c_source, - ), - .target = options.target, - .optimize = options.optimize, - .link_libc = true, - .link_system_libs = system_libs, - .libc_file = if (options.target.result.os.tag.isDarwin()) libc_file: { - switch (try @import("apple_sdk").pathsForTarget(b, options.target.result)) { - inline else => |paths| break :libc_file paths.libc, - } - } else null, - .extra_args = flags, + fn flags(self: *const HarfBuzzC) ![][]const u8 { + var flag_builder: std.ArrayList([]const u8) = .empty; + try flag_builder.appendSlice(self.builder.allocator, &.{ + "-DHAVE_STDBOOL_H", }); - - if (options.harfbuzz == .static) { - if (b.lazyDependency("harfbuzz", .{})) |upstream| { - hb_c.addIncludePath(upstream.path("src")); - } + // Disable ubsan for MSVC: Zig's ubsan runtime cannot be bundled + // on Windows (LNK4229), leaving __ubsan_handle_* unresolved when + // the static archive is consumed by an external linker. + if (self.options.target.result.abi == .msvc) { + try flag_builder.appendSlice(self.builder.allocator, &.{ + "-fno-sanitize=undefined", + "-fno-sanitize-trap=undefined", + }); + } + if (self.options.target.result.os.tag != .windows) { + try flag_builder.appendSlice(self.builder.allocator, &.{ + "-DHAVE_UNISTD_H", + "-DHAVE_SYS_MMAN_H", + "-DHAVE_PTHREAD=1", + }); } - // Freetype non-system include paths - if (options.freetype) |freetype_enabled| { - if (freetype_enabled == .static) { - const ft_dep = b.dependency("freetype", .{ - .target = options.target, - .optimize = options.optimize, - .@"enable-libpng" = true, - }); + // Freetype flags/non-system include paths + if (self.options.freetype != null) { + try flag_builder.appendSlice(self.builder.allocator, &.{ + "-DHAVE_FREETYPE=1", - if (ft_dep.builder.lazyDependency( - "freetype", - .{}, - )) |freetype_lazy_dep| { - hb_c.addIncludePath(freetype_lazy_dep.path("include")); - } - } + // Let's just assume a new freetype + "-DHAVE_FT_GET_VAR_BLEND_COORDINATES=1", + "-DHAVE_FT_SET_VAR_BLEND_COORDINATES=1", + "-DHAVE_FT_DONE_MM_VAR=1", + "-DHAVE_FT_GET_TRANSFORM=1", + }); } // Coretext - if (options.coretext) { - // NOTE: We should not necessarily need to add this directly to C - // translation, so we just add it to the module. - hb_c.mod.linkFramework("CoreText", .{}); + if (self.options.coretext) { + try flag_builder.appendSlice(self.builder.allocator, &.{"-DHAVE_CORETEXT=1"}); } - module.addImport("hb_c", hb_c.mod); + return flag_builder.items; + } + + fn addImportToModule( + self: *const HarfBuzzC, + module: *std.Build.Module, + ) !void { + try translate_c.addImportToModule(self.builder, "hb_c", module, .{ + .source = .{ .includes = .{ .files = try self.includeFiles() } }, + .target = self.options.target, + .optimize = self.options.optimize, + .link_system_libs = try self.systemLibs(), + .include_paths = try self.includePaths(), + .link_frameworks = self.frameworks(), + .extra_args = try self.flags(), + }); + } + + fn buildLib(self: *const HarfBuzzC) !*std.Build.Step.Compile { + const target = self.options.target; + const optimize = self.options.optimize; + + const lib = self.builder.addLibrary(.{ + .name = "harfbuzz", + .root_module = self.builder.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime + // headers. The MSVC SDK include directories (added via linkLibC) + // contain both C and C++ headers, so linkLibCpp is not needed. + .link_libcpp = target.result.abi != .msvc, + }), + .linkage = .static, + }); + + // Freetype + if (self.options.freetype) |ft| { + switch (ft.link_mode) { + .dynamic => |opts| lib.root_module.linkSystemLibrary("freetype2", opts), + .static => { + lib.root_module.linkLibrary(ft.dependency.artifact("freetype")); + }, + } + } + + // CoreText stuff + for (self.frameworks()) |framework| lib.root_module.linkFramework(framework, .{}); + if (target.result.os.tag.isDarwin()) { + try apple_sdk.addPaths(self.builder, lib); + } + + if (self.builder.lazyDependency("harfbuzz", .{})) |upstream| { + lib.root_module.addIncludePath(upstream.path("src")); + lib.root_module.addCSourceFile(.{ + .file = upstream.path("src/harfbuzz.cc"), + .flags = try self.flags(), + }); + lib.installHeadersDirectory( + upstream.path("src"), + "", + .{ .include_extensions = &.{".h"} }, + ); + } + + self.builder.installArtifact(lib); + + return lib; } }; diff --git a/pkg/harfbuzz/build.zig.zon b/pkg/harfbuzz/build.zig.zon index b3f3c238d..1722321e8 100644 --- a/pkg/harfbuzz/build.zig.zon +++ b/pkg/harfbuzz/build.zig.zon @@ -4,12 +4,6 @@ .fingerprint = 0xbd60917cd18865d8, .paths = .{""}, .dependencies = .{ - .translate_c = .{ - .url = "https://codeberg.org/vancluever/translate-c/archive/4e879eb8aba615de112eabd1231ea6e01920cead.tar.gz", - .hash = "translate_c-0.0.0-Q_BUWhVNBwDOEcIqub4VFPJPB6D9dgwzUMHTX5KWr8Xr", - .lazy = true, - }, - // harfbuzz/harfbuzz .harfbuzz = .{ .url = "https://deps.files.ghostty.org/harfbuzz-11.0.0.tar.xz", @@ -20,5 +14,6 @@ .freetype = .{ .path = "../freetype" }, .macos = .{ .path = "../macos" }, .apple_sdk = .{ .path = "../apple-sdk" }, + .translate_c = .{ .path = "../translate-c" }, }, } diff --git a/pkg/macos/build.zig b/pkg/macos/build.zig index 5569baad0..f6b6f2e52 100644 --- a/pkg/macos/build.zig +++ b/pkg/macos/build.zig @@ -1,6 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); const apple_sdk = @import("apple_sdk"); +const translate_c = @import("translate_c"); const Framework = struct { const Tag = enum { all, macos }; @@ -26,34 +27,28 @@ const extra_headers = [_][]const u8{ "os/signpost.h", }; -const framework_header_fmt = "#include <{s}/{s}>\n"; -const extra_header_fmt = "#include <{s}>\n"; - -fn cSourceLen(tag: Framework.Tag) usize { +fn includeFiles(b: *std.Build, tag: Framework.Tag) ![]translate_c.Options.IncludeFile { var len: usize = 0; for (frameworks) |framework| { if (tag != .macos and framework.tag == .macos) continue; - for (framework.headers) |h| len += std.fmt.count(framework_header_fmt, .{ framework.name, h }); + len += framework.headers.len; } - for (extra_headers) |h| len += std.fmt.count(extra_header_fmt, .{h}); - return len; -} + len += extra_headers.len; + var includes_builder: std.ArrayList(translate_c.Options.IncludeFile) = + try .initCapacity(b.allocator, len); -fn genCSource(comptime tag: Framework.Tag) [cSourceLen(tag):0]u8 { - const len = cSourceLen(tag); - var buf: [len:0]u8 = undefined; - var writer: std.Io.Writer = .fixed(&buf); for (frameworks) |framework| { if (tag != .macos and framework.tag == .macos) continue; - for (framework.headers) |h| writer.print(framework_header_fmt, .{ framework.name, h }) catch unreachable; + for (framework.headers) |h| { + const path = try std.fmt.allocPrint(b.allocator, "{s}/{s}", .{ framework.name, h }); + includes_builder.appendAssumeCapacity(.{ .path = path }); + } } - for (extra_headers) |h| writer.print(extra_header_fmt, .{h}) catch unreachable; - buf[len] = 0; - return buf; -} -const c_source_macos = genCSource(.macos); -const c_source_other = genCSource(.all); + for (extra_headers) |h| includes_builder.appendAssumeCapacity(.{ .path = h }); + + return includes_builder.items; +} fn linkFrameworks(tag: Framework.Tag, module: *std.Build.Module) !void { for (frameworks) |framework| { @@ -72,28 +67,14 @@ pub fn build(b: *std.Build) !void { .optimize = optimize, }); - translate: { - const translate_c = b.lazyImport(@This(), "translate_c") orelse break :translate; - const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse break :translate; - const macos_c: translate_c.Translator = .init(translate_c_dep, .{ - .c_source_file = b.addWriteFiles().add( - "macos_c.h", - if (target.result.os.tag == .macos) &c_source_macos else &c_source_other, - ), - .target = target, - .optimize = optimize, - .libc_file = if (target.result.os.tag.isDarwin()) libc_file: { - switch (try apple_sdk.pathsForTarget(b, target.result)) { - inline else => |paths| break :libc_file paths.libc, - } - } else null, - }); - - // Blocks need to be enabled to use MacOS headers - macos_c.run.addArg("-fblocks"); - - module.addImport("macos_c", macos_c.mod); - } + try translate_c.addImportToModule(b, "macos_c", module, .{ + .source = .{ .includes = .{ .files = try includeFiles( + b, + if (target.result.os.tag == .macos) .macos else .all, + ) } }, + .target = target, + .optimize = optimize, + }); const lib = b.addLibrary(.{ .name = "macos", diff --git a/pkg/macos/build.zig.zon b/pkg/macos/build.zig.zon index f70cb8020..b29f41bf5 100644 --- a/pkg/macos/build.zig.zon +++ b/pkg/macos/build.zig.zon @@ -5,10 +5,6 @@ .paths = .{""}, .dependencies = .{ .apple_sdk = .{ .path = "../apple-sdk" }, - .translate_c = .{ - .url = "https://codeberg.org/vancluever/translate-c/archive/4e879eb8aba615de112eabd1231ea6e01920cead.tar.gz", - .hash = "translate_c-0.0.0-Q_BUWhVNBwDOEcIqub4VFPJPB6D9dgwzUMHTX5KWr8Xr", - .lazy = true, - }, + .translate_c = .{ .path = "../translate-c" }, }, } diff --git a/pkg/translate-c/build.zig b/pkg/translate-c/build.zig new file mode 100644 index 000000000..01cff419b --- /dev/null +++ b/pkg/translate-c/build.zig @@ -0,0 +1,218 @@ +//! This is a wrapper package for our use of translate-c. It provides helpers +//! for short-hand addition of the translation of C files and headers, along +//! with lower-level control of the process a la the standard external +//! translate-c package. + +const std = @import("std"); +const apple_sdk = @import("apple_sdk"); +pub const Translator = @import("translate_c").Translator; + +/// Options for translation. +pub const Options = struct { + /// Describes the specification for a single include file. + pub const IncludeFile = struct { + /// Describes the type of an include file. + pub const Type = enum { + /// A system include, included as ``. + system, + + /// A user-defined include, included as `"file.h"`. + user, + }; + + /// The path to the include. Should be either a base path or a relative + /// path, depending on what is expected via translation based on the + /// library directory structure. + path: []const u8, + + /// The type of include file. + type: Type = .system, + }; + + /// The subject of the translation. + source: union(enum) { + /// The subject is an on-disk path and will be passed through directly + /// for translation. + file: std.Build.LazyPath, + + /// The subject is a collection of include files. These files will be + /// included (in order) as system includes (e.g., `#include `). + includes: struct { + /// The name of the generated source file in cache. If not + /// specified, will be inferred from the operation, usually the + /// name of the import (e.g., `c.h` if the import name was "c"). + generated_name: ?[]const u8 = null, + + /// The files to include. + files: []const IncludeFile, + }, + }, + + /// The target to perform translation as. + target: std.Build.ResolvedTarget, + + /// The optimization mode to perform translation as. + optimize: std.builtin.OptimizeMode, + + /// Whether or not to link in libc. Generally you want this. + link_libc: bool = true, + + /// The system libraries to link against. These will likely line up to + /// whatever you are translating. + /// + /// These system libraries are always linked against preferred-dynamic with + /// a fallback to static. + link_system_libs: []const []const u8 = &.{}, + + /// Any additional include paths. These will be added using `-I` to the + /// translation process, and made available to the translated code, in the + /// order they are specified. + include_paths: []const std.Build.LazyPath = &.{}, + + /// Any additional system include paths. These will be added using + /// `-isystem` to the translation process, and made available to the + /// translated code, in the order they are specified. + system_include_paths: []const std.Build.LazyPath = &.{}, + + /// If supplied, these frameworks will be linked to the underlying + /// generated Zig module via `linkFramework` in the order they are + /// received. It does not affect translation. + /// + /// You likely don't need this if you are not building for an Apple + /// platform. + link_frameworks: []const []const u8 = &.{}, + + /// Supply an external libc file. The expected format here is exactly what + /// you would get if you ran `zig libc` and can be used if the toolchain on + /// a particular target has a hard time auto-detecting these paths. + libc_file: union(enum) { + /// Auto-detect if we are targeting Darwin in the target options, and + /// if we are, generate a libc file to use here automatically. This + /// ensures that translation can correctly locate a MacOS SDK versus + /// the Zig-supplied generic Darwin headers. + detect_darwin, + + /// Supply a direct file for use here. + direct: ?std.Build.LazyPath, + } = .detect_darwin, + + /// Extra arguments passed to Aro. Use this if you need to pass along extra + /// compiler flags to the translation process to make sure the headers are + /// pre-processed correctly before translation. + extra_args: []const []const u8 = &.{}, + + /// The name of this dependency in the caller's build.zig.zon file. If you + /// name the dependency anything else other than `translate_c`, change this + /// to match. + dependency_name: []const u8 = "translate_c", +}; + +/// Creates a translation step and adds the result as import referred to by +/// `name` to the module defined by `module`, making all translated objects +/// available to the module behind the import name. +pub fn addImportToModule( + b: *std.Build, + name: []const u8, + module: *std.Build.Module, + options: Options, +) !void { + var init_opts = options; + if (init_opts.source == .includes and init_opts.source.includes.generated_name == null) { + init_opts.source.includes.generated_name = try std.fmt.allocPrint( + b.allocator, + "{s}.h", + .{name}, + ); + } + const translated = try init(b, init_opts); + module.addImport(name, translated.mod); +} + +/// Mainly serves as a pass-through for the independent translate-c +/// `Translator.init`, but also adds additional paths before returning. +/// +/// Unless you need the actual translation object for more complex build +/// chains, it's recommended to use the higher-level methods such as +/// `addImportToModule`. +pub fn init(b: *std.Build, options: Options) !Translator { + const translated = try initTranslator(b, options); + for (options.include_paths) |path| translated.addIncludePath(path); + for (options.system_include_paths) |path| translated.addSystemIncludePath(path); + for (options.link_frameworks) |framework| translated.mod.linkFramework(framework, .{}); + return translated; +} + +/// Mainly serves as a pass-through for the independent translate-c +/// `Translator.init`. Unless you need the actual translation object for more +/// complex build chains, it's recommended to use the higher-level methods such +/// as `addImportToModule`. +pub fn initTranslator(b: *std.Build, options: Options) !Translator { + const this_dep = b.dependency(options.dependency_name, .{}); + const translate_c_dep = this_dep.builder.dependency("translate_c", .{}); + return .init(translate_c_dep, .{ + .c_source_file = switch (options.source) { + .file => |f| f, + .includes => |includes| b.addWriteFiles().add( + includes.generated_name orelse "c.h", + try buildSource(b, includes.files), + ), + }, + .target = options.target, + .optimize = options.optimize, + .link_libc = options.link_libc, + .link_system_libs = try marshalSystemLibs(b, options.link_system_libs), + .libc_file = switch (options.libc_file) { + .detect_darwin => if (options.target.result.os.tag.isDarwin()) libc_file: { + switch (try apple_sdk.pathsForTarget(this_dep.builder, options.target.result)) { + inline else => |paths| break :libc_file paths.libc, + } + } else null, + .direct => |libc_file| libc_file, + }, + .extra_args = options.extra_args, + }); +} + +/// Marshals linked system libraries into the `Translator.LinkSystemLib` +/// format, which includes the link options for each library. +/// +/// All system libraries linked this way are linked dynamic-preferred with a +/// fallback to static. +/// +/// Note that this uses the builder arena and as such does not need to be freed. +fn marshalSystemLibs(b: *std.Build, libs: []const []const u8) ![]Translator.LinkSystemLib { + var result: std.ArrayList(Translator.LinkSystemLib) = .empty; + try result.ensureTotalCapacityPrecise(b.allocator, libs.len); + for (libs) |name| { + result.appendAssumeCapacity(.{ + .name = name, + .options = .{ + .preferred_link_mode = .dynamic, + .search_strategy = .mode_first, + }, + }); + } + + return result.items; +} + +/// Builds the source for a set of `IncludeFile`s. +/// +/// Note that this uses the builder arena and as such does not need to be freed. +fn buildSource(b: *std.Build, files: []const Options.IncludeFile) ![]const u8 { + var source_builder: std.Io.Writer.Allocating = .init(b.allocator); + for (files) |file| try fmtInclude(&source_builder.writer, file); + return source_builder.written(); +} + +fn fmtInclude(w: *std.Io.Writer, file: Options.IncludeFile) !void { + if (file.type == .system) { + try w.print("#include <{s}>\n", .{file.path}); + } else { + try w.print("#include \"{s}\"\n", .{file.path}); + } +} + +pub fn build(b: *std.Build) void { + _ = b; +} diff --git a/pkg/translate-c/build.zig.zon b/pkg/translate-c/build.zig.zon new file mode 100644 index 000000000..ef04cdd57 --- /dev/null +++ b/pkg/translate-c/build.zig.zon @@ -0,0 +1,19 @@ +.{ + .name = .translate_c, + .version = "0.0.1", + .minimum_zig_version = "0.16.0", + .paths = .{ + "build.zig", + "build.zig.zon", + }, + .fingerprint = 0x1c84f6452d0e8347, + .dependencies = .{ + .apple_sdk = .{ + .path = "../apple-sdk", + }, + .translate_c = .{ + .url = "https://codeberg.org/vancluever/translate-c/archive/4e879eb8aba615de112eabd1231ea6e01920cead.tar.gz", + .hash = "translate_c-0.0.0-Q_BUWhVNBwDOEcIqub4VFPJPB6D9dgwzUMHTX5KWr8Xr", + }, + }, +} diff --git a/pkg/wuffs/build.zig b/pkg/wuffs/build.zig index 2ae45585f..82cfec3b0 100644 --- a/pkg/wuffs/build.zig +++ b/pkg/wuffs/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const translate_c = @import("translate_c"); // All the C macros defined so that the header matches the build. const defines = [_][]const u8{ @@ -14,25 +15,6 @@ const defines = [_][]const u8{ "WUFFS_CONFIG__MODULE__ZLIB", }; -// Generated C code, includes the macros above. Designed to mimic old c.zig. -// TODO: is this still needed, or are the -D flags enough? -const wuffs_c_source = wuffs_c_source: { - const include: []const u8 = "#include "; - const len = len: { - var len: usize = 0; - for (defines) |d| len += std.fmt.count("#define {s}\n", .{d}); - len += std.fmt.count("{s}\n", .{include}); - break :len len; - }; - - var buf: [len:0]u8 = undefined; - var writer: std.Io.Writer = .fixed(&buf); - for (defines) |d| writer.print("#define {s}\n", .{d}) catch unreachable; - writer.print("{s}\n", .{include}) catch unreachable; - buf[len] = 0; - break :wuffs_c_source buf; -}; - pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); @@ -52,46 +34,66 @@ pub fn build(b: *std.Build) !void { const windows = target.result.os.tag == .windows; translate: { - const translate_c = b.lazyImport(@This(), "translate_c") orelse break :translate; - const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse break :translate; - const wuffs_c: translate_c.Translator = .init(translate_c_dep, .{ - .c_source_file = b.addWriteFiles().add("wuffs_c.h", &wuffs_c_source), + const wuffs_dep = b.lazyDependency("wuffs", .{}) orelse break :translate; + const include_paths: []const std.Build.LazyPath = switch (windows) { + true => &.{wuffs_dep.path("release/c")}, + + // Wuffs only needs stdlib.h and string.h from libc, and only for + // a handful of declarations. We provide minimal versions of these + // headers so that wuffs can be translated and compiled without + // libc, notably for freestanding targets (wasm32) but this also + // avoids requiring an Apple SDK for translate-c on macOS. + false => &.{ b.path("include"), wuffs_dep.path("release/c") }, + }; + + // Split up macro flags so that we can add them to translation + const macro_flags = macro_flags: { + var flag_builder: std.ArrayList([]const u8) = try .initCapacity(b.allocator, defines.len); + inline for (defines) |key| { + flag_builder.appendAssumeCapacity("-D" ++ key); + } + break :macro_flags flag_builder.items; + }; + + // Larger flag set for C file build within module + const c_flags = c_flags: { + var len: usize = macro_flags.len + 1; + if (windows) len += 2; + var flag_builder: std.ArrayList([]const u8) = try .initCapacity(b.allocator, len); + + flag_builder.appendAssumeCapacity("-DWUFFS_IMPLEMENTATION"); + + // Disable ubsan on Windows to avoid undefined __ubsan_handle_* + // references: Zig's ubsan runtime can't be bundled on Windows + // (its /exclude-symbols directives break the MSVC linker), so + // these handlers would go unresolved. This affects both the + // MSVC and GNU ABIs. + if (windows) { + flag_builder.appendAssumeCapacity("-fno-sanitize=undefined"); + flag_builder.appendAssumeCapacity("-fno-sanitize-trap=undefined"); + } + + for (macro_flags) |f| flag_builder.appendAssumeCapacity(f); + + break :c_flags flag_builder.items; + }; + + const wuffs_c = try translate_c.init(b, .{ + .source = .{ .includes = .{ + .generated_name = "wuffs_c.h", + .files = &.{.{ .path = "wuffs-v0.4.c" }}, + } }, .target = target, .optimize = optimize, + .include_paths = include_paths, .link_libc = windows, + .extra_args = macro_flags, }); - // Wuffs only needs stdlib.h and string.h from libc, and only for - // a handful of declarations. We provide minimal versions of these - // headers so that wuffs can be translated and compiled without - // libc, notably for freestanding targets (wasm32) but this also - // avoids requiring an Apple SDK for translate-c on macOS. - if (!windows) wuffs_c.addIncludePath(b.path("include")); - - var flags: std.ArrayList([]const u8) = .empty; - defer flags.deinit(b.allocator); - try flags.append(b.allocator, "-DWUFFS_IMPLEMENTATION"); - - // Disable ubsan on Windows to avoid undefined __ubsan_handle_* - // references: Zig's ubsan runtime can't be bundled on Windows - // (its /exclude-symbols directives break the MSVC linker), so - // these handlers would go unresolved. This affects both the - // MSVC and GNU ABIs. - if (windows) { - try flags.append(b.allocator, "-fno-sanitize=undefined"); - try flags.append(b.allocator, "-fno-sanitize-trap=undefined"); - } - inline for (defines) |key| { - try flags.append(b.allocator, "-D" ++ key); - } - - if (b.lazyDependency("wuffs", .{})) |wuffs_dep| { - wuffs_c.addIncludePath(wuffs_dep.path("release/c")); - wuffs_c.mod.addCSourceFile(.{ - .file = wuffs_dep.path("release/c/wuffs-v0.4.c"), - .flags = flags.items, - }); - } + wuffs_c.mod.addCSourceFile(.{ + .file = wuffs_dep.path("release/c/wuffs-v0.4.c"), + .flags = c_flags, + }); module.addImport("wuffs_c", wuffs_c.mod); } diff --git a/pkg/wuffs/build.zig.zon b/pkg/wuffs/build.zig.zon index 2813fb775..89156e9b0 100644 --- a/pkg/wuffs/build.zig.zon +++ b/pkg/wuffs/build.zig.zon @@ -3,11 +3,7 @@ .version = "0.0.0", .fingerprint = 0x67c0c059de921c4f, .dependencies = .{ - .translate_c = .{ - .lazy = true, - .url = "https://codeberg.org/vancluever/translate-c/archive/4e879eb8aba615de112eabd1231ea6e01920cead.tar.gz", - .hash = "translate_c-0.0.0-Q_BUWhVNBwDOEcIqub4VFPJPB6D9dgwzUMHTX5KWr8Xr", - }, + .translate_c = .{ .path = "../translate-c" }, // google/wuffs .wuffs = .{ diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index 86cfecc05..008005558 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -10,6 +10,7 @@ const UnicodeTables = @import("UnicodeTables.zig"); const GhosttyFrameData = @import("GhosttyFrameData.zig"); const DistResource = @import("GhosttyDist.zig").Resource; const gtk_helpers = @import("gtk.zig"); +const translate_c = @import("translate_c"); config: *const Config, @@ -666,25 +667,15 @@ pub fn add( step.root_module.linkSystemLibrary("gtk4", dynamic_link_opts); // We need to translate gio headers too - gio_translate: { - // translate-c stuff - const translate_c = b.lazyImport(@import("../../build.zig"), "translate_c") orelse - break :gio_translate; - const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse - break :gio_translate; - const translated: translate_c.Translator = .init(translate_c_dep, .{ - .c_source_file = b.addWriteFiles().add("gio_c.h", - \\#include - \\#include - ), - .target = target, - .optimize = optimize, - .link_system_libs = &.{ - .{ .name = "gio-2.0", .options = dynamic_link_opts }, - }, - }); - step.root_module.addImport("gio_c", translated.mod); - } + try translate_c.addImportToModule(b, "gio_c", step.root_module, .{ + .source = .{ .includes = .{ .files = &.{ + .{ .path = "gio/gio.h" }, + .{ .path = "gio/gunixfdlist.h" }, + } } }, + .target = target, + .optimize = optimize, + .link_system_libs = &.{"gio-2.0"}, + }); } switch (self.config.app_runtime) { @@ -729,41 +720,22 @@ fn addGtkNg( step.root_module.addImport(name, gobject.module(module)); } } - gtk_adw_translate: { - // translate-c stuff - const translate_c = b.lazyImport(@import("../../build.zig"), "translate_c") orelse break :gtk_adw_translate; - const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse break :gtk_adw_translate; - const Translator = translate_c.Translator; - { - // GTK headers - const translated: Translator = .init(translate_c_dep, .{ - .c_source_file = b.addWriteFiles().add("gtk_c.h", - \\#include - ), - .target = target, - .optimize = optimize, - .link_system_libs = &.{ - .{ .name = "gtk4", .options = dynamic_link_opts }, - }, - }); - step.root_module.addImport("gtk_c", translated.mod); - } - { - // Adwaita headers - const translated: Translator = .init(translate_c_dep, .{ - .c_source_file = b.addWriteFiles().add("adw_c.h", - \\#include - ), - .target = target, - .optimize = optimize, - .link_system_libs = &.{ - .{ .name = "libadwaita-1", .options = dynamic_link_opts }, - }, - }); - step.root_module.addImport("adw_c", translated.mod); - } - } + // GTK C translation + try translate_c.addImportToModule(b, "gtk_c", step.root_module, .{ + .source = .{ .includes = .{ .files = &.{.{ .path = "gtk/gtk.h" }} } }, + .target = target, + .optimize = optimize, + .link_system_libs = &.{"gtk4"}, + }); + + // Adwaita C translation + try translate_c.addImportToModule(b, "adw_c", step.root_module, .{ + .source = .{ .includes = .{ .files = &.{.{ .path = "adwaita.h" }} } }, + .target = target, + .optimize = optimize, + .link_system_libs = &.{"libadwaita-1"}, + }); if (self.config.x11) { step.root_module.linkSystemLibrary("X11", dynamic_link_opts); @@ -869,24 +841,19 @@ fn addGtkNg( step.root_module.linkSystemLibrary("wayland-client", dynamic_link_opts); } - ghostty_resources_translate: { + { // Get our gresource c/h files and add them to our build. const dist = gtkNgDistResources(b); - const translate_c = b.lazyImport(@import("../../build.zig"), "translate_c") orelse - break :ghostty_resources_translate; - const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse - break :ghostty_resources_translate; - const translated: translate_c.Translator = .init(translate_c_dep, .{ - .c_source_file = b.addWriteFiles().add("c.h", - \\#include - ), + const translated = try translate_c.init(b, .{ + .source = .{ .includes = .{ + .generated_name = "ghostty_gtk_resources_c.h", + .files = &.{.{ .path = "ghostty_resources.h" }}, + } }, .target = target, .optimize = optimize, - .link_system_libs = &.{ - .{ .name = "glib-2.0", .options = dynamic_link_opts }, - }, + .link_system_libs = &.{"glib-2.0"}, + .include_paths = &.{dist.resources_h.path(b).dirname()}, }); - translated.addIncludePath(dist.resources_h.path(b).dirname()); translated.mod.addCSourceFile(.{ .file = dist.resources_c.path(b), .flags = &.{} }); step.root_module.addImport("ghostty_gtk_resources", translated.mod); } @@ -1037,22 +1004,14 @@ pub fn gtkNgDistResources( .link_libc = true, }), }); - adw_translate: { - // Adwaita headers - const translate_c = b.lazyImport(@import("../../build.zig"), "translate_c") orelse break :adw_translate; - const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse break :adw_translate; - const translated: translate_c.Translator = .init(translate_c_dep, .{ - .c_source_file = b.addWriteFiles().add("adw_c.h", - \\#include - ), - .target = b.graph.host, - .optimize = .Debug, - .link_system_libs = &.{ - .{ .name = "libadwaita-1", .options = dynamic_link_opts }, - }, - }); - blueprint_exe.root_module.addImport("adw_c", translated.mod); - } + + // Adwaita headers + translate_c.addImportToModule(b, "adw_c", blueprint_exe.root_module, .{ + .source = .{ .includes = .{ .files = &.{.{ .path = "adwaita.h" }} } }, + .target = b.graph.host, + .optimize = .Debug, + .link_system_libs = &.{"libadwaita-1"}, + }) catch unreachable; for (gresource.blueprints) |bp| { const blueprint_run = b.addRunArtifact(blueprint_exe);