build: emit xcframework for libghostty-vt on macOS

On Darwin targets, the build now automatically produces a universal
(arm64 + x86_64) XCFramework at lib/ghostty-vt.xcframework under
the install prefix. This bundles the fat static library with headers
so consumers using Xcode or Swift PM can link libghostty-vt directly.
This commit is contained in:
Mitchell Hashimoto
2026-04-06 14:02:06 -07:00
parent f7a9e313cd
commit 05fb57dd40
3 changed files with 111 additions and 2 deletions

View File

@@ -152,6 +152,18 @@ pub fn build(b: *std.Build) !void {
).step);
}
// libghostty-vt xcframework (Apple only, universal binary)
if (config.target.result.os.tag.isDarwin()) {
const universal = try buildpkg.GhosttyLibVt.initStaticAppleUniversal(
b,
&config,
&deps,
&mod,
);
const xcframework = universal.xcframework();
b.getInstallStep().dependOn(xcframework.step);
}
// Helpgen
if (config.emit_helpgen) deps.help_strings.install();

View File

@@ -4,9 +4,12 @@ const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const RunStep = std.Build.Step.Run;
const Config = @import("Config.zig");
const GhosttyZig = @import("GhosttyZig.zig");
const LibtoolStep = @import("LibtoolStep.zig");
const LipoStep = @import("LipoStep.zig");
const SharedDeps = @import("SharedDeps.zig");
const XCFrameworkStep = @import("XCFrameworkStep.zig");
/// The step that generates the file.
step: *std.Build.Step,
@@ -99,6 +102,45 @@ pub fn initShared(
return initLib(b, zig, .dynamic);
}
/// Build a macOS universal (arm64 + x86_64) static library using lipo.
pub fn initStaticAppleUniversal(
b: *std.Build,
cfg: *const Config,
deps: *const SharedDeps,
zig: *const GhosttyZig,
) !GhosttyLibVt {
const aarch64_zig = try zig.retarget(
b,
cfg,
deps,
Config.genericMacOSTarget(b, .aarch64),
);
const x86_64_zig = try zig.retarget(
b,
cfg,
deps,
Config.genericMacOSTarget(b, .x86_64),
);
const aarch64 = try initStatic(b, &aarch64_zig);
const x86_64 = try initStatic(b, &x86_64_zig);
const universal = LipoStep.create(b, .{
.name = "ghostty-vt",
.out_name = "libghostty-vt.a",
.input_a = aarch64.output,
.input_b = x86_64.output,
});
return .{
.step = universal.step,
.artifact = universal.step,
.kind = .static,
.output = universal.output,
.dsym = null,
.pkg_config = null,
};
}
fn initLib(
b: *std.Build,
zig: *const GhosttyZig,
@@ -294,6 +336,23 @@ fn requiresPrivate(b: *std.Build) []const u8 {
return "";
}
/// Create an XCFramework bundle from the static library.
pub fn xcframework(
lib_vt: *const GhosttyLibVt,
) *XCFrameworkStep {
assert(lib_vt.kind == .static);
const b = lib_vt.step.owner;
return XCFrameworkStep.create(b, .{
.name = "ghostty-vt",
.out_path = b.pathJoin(&.{ b.install_prefix, "lib/ghostty-vt.xcframework" }),
.libraries = &.{.{
.library = lib_vt.output,
.headers = b.path("include/ghostty"),
.dsym = null,
}},
});
}
pub fn install(
self: *const GhosttyLibVt,
step: *std.Build.Step,

View File

@@ -24,6 +24,44 @@ pub fn init(
b: *std.Build,
cfg: *const Config,
deps: *const SharedDeps,
) !GhosttyZig {
return initInner(b, cfg, deps, "ghostty-vt", "ghostty-vt-c");
}
/// Create a new GhosttyZig with modules retargeted to a different
/// architecture. Used to produce universal (fat) binaries on macOS.
pub fn retarget(
self: *const GhosttyZig,
b: *std.Build,
cfg: *const Config,
deps: *const SharedDeps,
target: std.Build.ResolvedTarget,
) !GhosttyZig {
_ = self;
const retargeted_config = try b.allocator.create(Config);
retargeted_config.* = cfg.*;
retargeted_config.target = target;
const retargeted_deps = try b.allocator.create(SharedDeps);
retargeted_deps.* = try deps.retarget(b, target);
// Use unique module names to avoid collisions with the original target.
const arch_name = @tagName(target.result.cpu.arch);
return initInner(
b,
retargeted_config,
retargeted_deps,
b.fmt("ghostty-vt-{s}", .{arch_name}),
b.fmt("ghostty-vt-c-{s}", .{arch_name}),
);
}
fn initInner(
b: *std.Build,
cfg: *const Config,
deps: *const SharedDeps,
vt_name: []const u8,
vt_c_name: []const u8,
) !GhosttyZig {
// Terminal module build options
var vt_options = cfg.terminalOptions(.lib);
@@ -37,7 +75,7 @@ pub fn init(
return .{
.vt = try initVt(
"ghostty-vt",
vt_name,
b,
cfg,
deps,
@@ -46,7 +84,7 @@ pub fn init(
),
.vt_c = try initVt(
"ghostty-vt-c",
vt_c_name,
b,
cfg,
deps,