Files
ghostty/pkg/harfbuzz/build.zig
Chris Marchesi e8525c0fd9 Update to Zig 0.16.0
This commit represents the majority of the work necessary to upgrade
Ghostty to use Zig 0.16.0.

Key parts:

* In addition to its previous responsibilities, the global state now
  houses state for global I/O implementations and the process
  environment. It is now also utilized in the main application along
  with the C library. Where necessary, global state is isolated from key
  parts of the implementation (e.g., in libghostty subsystems), and it's
  expected that this list will grow.

* We currently manage our own C translation layer where necessary. In
  these cases, cImport has been removed in favor of the new external
  translate-c package. Due to fixes that have needed be made to properly
  translate the dependencies that were swapped out, as mentioned, we
  have had to backport fixes from the current translate-c package (and
  the upstream Arocc dependency). We will host this ourselves until Zig
  0.17.0 is released with these fixes.

* Where necessary (only a small number of cases), some stdlib code from
  0.15.2 (and even from 0.17.0) has been taken, adopted, and vendored in
  lib/compat.

Co-authored-by: Leah Amelia Chen <hi@pluie.me>
2026-07-21 12:35:05 -07:00

360 lines
13 KiB
Zig

const std = @import("std");
const apple_sdk = @import("apple_sdk");
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 = b.dependency("freetype", .{
.target = target,
.optimize = optimize,
.@"enable-libpng" = true,
});
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.module("freetype") },
.{
.name = "macos",
.module = b.dependency("macos", .{ .target = target, .optimize = optimize })
.module("macos"),
},
}
else
&.{
.{ .name = "freetype", .module = freetype.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,
});
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 buildLib(b, .{
.target = target,
.optimize = optimize,
.coretext_enabled = coretext_enabled,
.freetype_enabled = freetype_enabled,
.dynamic_link_opts = dynamic_link_opts,
});
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) {
static,
dynamic: std.Build.Module.LinkSystemLibraryOptions,
};
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
harfbuzz: LinkMode,
coretext: bool,
freetype: ?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});
}
}
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).
const translate_c = b.lazyImport(root_build_container, "translate_c") orelse return;
const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse return;
const c_source = c_source: {
var source_builder: std.Io.Writer.Allocating = .init(b.allocator);
try fmtInclude(&source_builder.writer, "hb.h", options.harfbuzz);
if (options.coretext) {
try fmtInclude(&source_builder.writer, "hb-coretext.h", options.harfbuzz);
}
if (options.freetype != null) {
try fmtInclude(&source_builder.writer, "hb-ft.h", options.harfbuzz);
}
break :c_source source_builder.written();
};
// 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 });
}
break :libs libs_builder.items;
};
// 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",
});
}
// Freetype flags/non-system include paths
if (options.freetype != null) {
try flag_builder.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",
});
}
// Coretext
if (options.coretext) {
try flag_builder.appendSlice(b.allocator, &.{"-DHAVE_CORETEXT=1"});
try flag_builder.appendSlice(b.allocator, &.{"-fblocks"});
}
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,
});
if (options.harfbuzz == .static) {
if (b.lazyDependency("harfbuzz", .{})) |upstream| {
hb_c.addIncludePath(upstream.path("src"));
}
}
// 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,
});
if (ft_dep.builder.lazyDependency(
"freetype",
.{},
)) |freetype_lazy_dep| {
hb_c.addIncludePath(freetype_lazy_dep.path("include"));
}
}
}
// 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", .{});
}
module.addImport("hb_c", hb_c.mod);
}
};