Files
ghostty/pkg/harfbuzz/build.zig
Chris Marchesi fe9cf6a266 build: fully transition away from cImport/addTranslateC
This migrates all remaining uses of cImport (and addTranslateC for good
measure) to using translate-c for C translation, ensuring that we are
ready for when cImport is removed from the language, and also that all
sources of C translation are using the same snapshot of the external
package (when can then be updated when we need to fix something).

A couple of notes:

* A few options have been added to support the new translations, namely
  the ability to link libraries (passed through to linkLibrary on the
  Translator side) and whether or not to initialize default values
  (looks like cImport did this without a way to control it, but
  translate-c does not do it by default).

* Using the new library linking option actually simplifies the process
  of translating a number of the C packages as we have been shipping the
  necessary headers for these packages already with the applicable
  libraries. For some of the more complex translation processes though,
  we still include the appropriate directories directly.
2026-09-15 11:01:24 -07:00

293 lines
10 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;
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
else
.static,
.coretext = coretext_enabled,
.freetype = if (freetype_enabled)
.{
.dependency = freetype_dep,
.link_mode = if (b.systemIntegrationOption("freetype", .{})) .dynamic 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 {
// 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 AddImportToModuleOptions = struct {
const LinkMode = enum {
static,
dynamic,
};
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 => lib.root_module.linkSystemLibrary("freetype2", dynamic_link_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;
}
};