build: add iOS slices to lib-vt xcframework

Add iOS device and simulator slices to the xcframework, gated on
SDK availability via std.zig.LibCInstallation.findNative. Refactor
AppleLibs from a struct with named fields to an EnumMap keyed by
ApplePlatform so that adding new platforms only requires extending
the enum and its sdk_platforms table.

tvOS, watchOS, and visionOS are listed as not yet supported due to
Zig stdlib limitations (missing PATH_MAX, mcontext fields).
This commit is contained in:
Mitchell Hashimoto
2026-04-06 14:31:18 -07:00
parent e1a0e40ec4
commit 9b281cde43
2 changed files with 84 additions and 18 deletions

View File

@@ -156,13 +156,13 @@ pub fn build(b: *std.Build) !void {
// Only when building on macOS (not cross-compiling) since
// xcodebuild is required.
if (builtin.os.tag.isDarwin() and config.target.result.os.tag.isDarwin()) {
const universal = try buildpkg.GhosttyLibVt.initStaticAppleUniversal(
const apple_libs = try buildpkg.GhosttyLibVt.initStaticAppleUniversal(
b,
&config,
&deps,
&mod,
);
const xcframework = universal.xcframework();
const xcframework = buildpkg.GhosttyLibVt.xcframework(&apple_libs, b);
b.getInstallStep().dependOn(xcframework.step);
}

View File

@@ -102,13 +102,39 @@ pub fn initShared(
return initLib(b, zig, .dynamic);
}
/// Build a macOS universal (arm64 + x86_64) static library using lipo.
/// Apple platform targets for xcframework slices.
pub const ApplePlatform = enum {
macos_universal,
ios,
ios_simulator,
// tvOS, watchOS, and visionOS are not yet supported by Zig's
// standard library (missing PATH_MAX, mcontext fields, etc.).
/// Platforms that have device + simulator pairs, gated on SDK detection.
const sdk_platforms = [_]struct {
os_tag: std.Target.Os.Tag,
device: ApplePlatform,
simulator: ApplePlatform,
}{
.{ .os_tag = .ios, .device = .ios, .simulator = .ios_simulator },
};
};
/// Static libraries for each Apple platform, keyed by `ApplePlatform`.
pub const AppleLibs = std.EnumMap(ApplePlatform, GhosttyLibVt);
/// Build static libraries for all available Apple platforms.
/// Always builds a macOS universal (arm64 + x86_64) fat binary.
/// Additional platforms are included if their SDK is detected.
pub fn initStaticAppleUniversal(
b: *std.Build,
cfg: *const Config,
deps: *const SharedDeps,
zig: *const GhosttyZig,
) !GhosttyLibVt {
) !AppleLibs {
var result: AppleLibs = .{};
// macOS universal (arm64 + x86_64)
const aarch64_zig = try zig.retarget(
b,
cfg,
@@ -121,7 +147,6 @@ pub fn initStaticAppleUniversal(
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, .{
@@ -130,15 +155,38 @@ pub fn initStaticAppleUniversal(
.input_a = aarch64.output,
.input_b = x86_64.output,
});
return .{
result.put(.macos_universal, .{
.step = universal.step,
.artifact = universal.step,
.kind = .static,
.output = universal.output,
.dsym = null,
.pkg_config = null,
};
});
// Additional Apple platforms, each gated on SDK availability.
for (ApplePlatform.sdk_platforms) |p| {
const target_query: std.Target.Query = .{
.cpu_arch = .aarch64,
.os_tag = p.os_tag,
.os_version_min = Config.osVersionMin(p.os_tag),
};
if (detectAppleSDK(b.resolveTargetQuery(target_query).result)) {
const dev_zig = try zig.retarget(b, cfg, deps, b.resolveTargetQuery(target_query));
result.put(p.device, try initStatic(b, &dev_zig));
const sim_zig = try zig.retarget(b, cfg, deps, b.resolveTargetQuery(.{
.cpu_arch = .aarch64,
.os_tag = p.os_tag,
.os_version_min = Config.osVersionMin(p.os_tag),
.abi = .simulator,
.cpu_model = .{ .explicit = &std.Target.aarch64.cpu.apple_a17 },
}));
result.put(p.simulator, try initStatic(b, &sim_zig));
}
}
return result;
}
fn initLib(
@@ -336,13 +384,11 @@ fn requiresPrivate(b: *std.Build) []const u8 {
return "";
}
/// Create an XCFramework bundle from the static library.
/// Create an XCFramework bundle from Apple platform static libraries.
pub fn xcframework(
lib_vt: *const GhosttyLibVt,
apple_libs: *const AppleLibs,
b: *std.Build,
) *XCFrameworkStep {
assert(lib_vt.kind == .static);
const b = lib_vt.step.owner;
// Generate a headers directory with a module map for Swift PM.
// We can't use include/ directly because it contains a module map
// for GhosttyKit (the macOS app library).
@@ -359,18 +405,38 @@ pub fn xcframework(
\\}
\\
);
const headers = wf.getDirectory();
var libraries: [AppleLibs.len]XCFrameworkStep.Library = undefined;
var lib_count: usize = 0;
for (std.enums.values(ApplePlatform)) |platform| {
if (apple_libs.get(platform)) |lib| {
libraries[lib_count] = .{
.library = lib.output,
.headers = headers,
.dsym = null,
};
lib_count += 1;
}
}
return XCFrameworkStep.create(b, .{
.name = "ghostty-vt",
.out_path = b.pathJoin(&.{ b.install_prefix, "lib/ghostty-vt.xcframework" }),
.libraries = &.{.{
.library = lib_vt.output,
.headers = wf.getDirectory(),
.dsym = null,
}},
.libraries = libraries[0..lib_count],
});
}
/// Returns true if the Apple SDK for the given target is installed.
fn detectAppleSDK(target: std.Target) bool {
_ = std.zig.LibCInstallation.findNative(.{
.allocator = std.heap.page_allocator,
.target = &target,
.verbose = false,
}) catch return false;
return true;
}
pub fn install(
self: *const GhosttyLibVt,
step: *std.Build.Step,