Files
ghostty/pkg/harfbuzz/build.zig
Chris Marchesi c0c5473daf build: refactoring our use of translate-c
This commit refactors our use of translate-c, in preparation for larger
removal of cImport and better co-ordination between building of C
dependencies and translation of headers.

The major update is the creation of an internal helper package that
wraps our use of the external translate-c library. This allows us to not
only have better shorthand and a data-driven, declarative approach to C
translation (versus the otherwise more imperative approach), it also
funnels the external dependency into a single package instead of
spreading it out among what will be an increasingly larger amount of
places as dependencies in "pkg/" get updated.

It also includes some refactors, namely to the harfbuzz package, which
has had its individual settings refactored into helpers to allow for the
settings to be better shared between translation and the build of the
c-based static library.

Wuffs has also had a bit of a refactor too so that we don't generate a
file with all of the macro defines in it - we just send these in as "-D"
flags now.
2026-09-10 11:17:32 -07:00

296 lines
11 KiB
Zig

const std = @import("std");
const apple_sdk = @import("apple_sdk");
const translate_c = @import("translate_c");
const root_build_container = @This();
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const coretext_enabled = b.option(bool, "enable-coretext", "Build coretext") orelse false;
const freetype_enabled = b.option(bool, "enable-freetype", "Build freetype") orelse true;
// For dynamic linking, we prefer dynamic linking and to search by
// mode first. Mode first will search all paths for a dynamic library
// before falling back to static.
const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{
.preferred_link_mode = .dynamic,
.search_strategy = .mode_first,
};
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"),
.target = target,
.optimize = optimize,
.imports = if (target.result.os.tag.isDarwin())
&.{
.{ .name = "freetype", .module = freetype_dep.module("freetype") },
.{
.name = "macos",
.module = b.dependency("macos", .{ .target = target, .optimize = optimize })
.module("macos"),
},
}
else
&.{
.{ .name = "freetype", .module = freetype_dep.module("freetype") },
},
});
try harfbuzz_c_builder.addImportToModule(module);
const options = b.addOptions();
options.addOption(bool, "coretext", coretext_enabled);
options.addOption(bool, "freetype", freetype_enabled);
module.addOptions("build_options", options);
break :harfbuzz module;
};
const test_exe = b.addTest(.{
.name = "test",
.root_module = module,
});
const tests_run = b.addRunArtifact(test_exe);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&tests_run.step);
if (!b.systemIntegrationOption("harfbuzz", .{})) {
const lib = try harfbuzz_c_builder.buildLib();
test_exe.root_module.linkLibrary(lib);
}
}
const HarfBuzzC = struct {
const AddImportToModuleOptions = struct {
const LinkMode = union(enum) {
static,
dynamic: std.Build.Module.LinkSystemLibraryOptions,
};
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
harfbuzz: LinkMode,
coretext: bool,
freetype: ?struct {
dependency: *std.Build.Dependency,
link_mode: LinkMode,
},
};
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 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);
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);
return includes_builder.items;
}
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 (self.options.harfbuzz == .dynamic)
libs_builder.appendAssumeCapacity("harfbuzz");
if (self.options.freetype != null and self.options.freetype.?.link_mode == .dynamic)
libs_builder.appendAssumeCapacity("freetype2");
return libs_builder.items;
}
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;
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;
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);
if (hb_upstream) |upstream| paths_builder.appendAssumeCapacity(upstream.path("src"));
if (ft_upstream) |upstream| paths_builder.appendAssumeCapacity(upstream.path("include"));
return paths_builder.items;
}
fn frameworks(self: *const HarfBuzzC) []const []const u8 {
return if (self.options.coretext) &.{"CoreText"} else &.{};
}
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",
});
// 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 flags/non-system include paths
if (self.options.freetype != null) {
try flag_builder.appendSlice(self.builder.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",
});
}
// Coretext
if (self.options.coretext) {
try flag_builder.appendSlice(self.builder.allocator, &.{"-DHAVE_CORETEXT=1"});
}
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;
}
};