diff --git a/build.zig b/build.zig index 78977a8c9..d8e8bd86a 100644 --- a/build.zig +++ b/build.zig @@ -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(); diff --git a/src/build/GhosttyLibVt.zig b/src/build/GhosttyLibVt.zig index d5e76b9de..55f22e232 100644 --- a/src/build/GhosttyLibVt.zig +++ b/src/build/GhosttyLibVt.zig @@ -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, diff --git a/src/build/GhosttyZig.zig b/src/build/GhosttyZig.zig index 3f3db95c6..8d5b78fb4 100644 --- a/src/build/GhosttyZig.zig +++ b/src/build/GhosttyZig.zig @@ -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,