diff --git a/build.zig b/build.zig index 21f6a0523..96f5d41aa 100644 --- a/build.zig +++ b/build.zig @@ -37,42 +37,7 @@ const app_version = std.SemanticVersion{ .major = 0, .minor = 1, .patch = 0 }; pub fn build(b: *std.Build) !void { const optimize = b.standardOptimizeOption(.{}); - const target = target: { - var result = b.standardTargetOptions(.{}); - - // On macOS, we specify a minimum supported version. This is important - // to set since header files will use this to determine the availability - // of certain APIs and I believe it is also encoded in the Mach-O - // binaries. - if (result.query.os_version_min == null) { - switch (result.result.os.tag) { - // The lowest supported version of macOS is 12.x because - // this is the first version to support Apple Silicon so it is - // the earliest version we can virtualize to test (I only have - // an Apple Silicon machine for macOS). - .macos => { - result.query.os_version_min = .{ .semver = .{ - .major = 12, - .minor = 0, - .patch = 0, - } }; - }, - - // iOS 17 picked arbitrarily - .ios => { - result.query.os_version_min = .{ .semver = .{ - .major = 17, - .minor = 0, - .patch = 0, - } }; - }, - - else => {}, - } - } - - break :target result; - }; + const target = b.standardTargetOptions(.{}); const wasm_target: WasmTarget = .browser; @@ -455,92 +420,38 @@ pub fn build(b: *std.Build) !void { // On Mac we can build the embedding library. This only handles the macOS lib. if (builtin.target.isDarwin() and target.result.os.tag == .macos) { - // Modify our build configuration for macOS builds. - const macos_config: BuildConfig = config: { - var copy = config; - - // Always static for the macOS app because we want all of our - // dependencies in a fat static library. - copy.static = true; - - break :config copy; - }; - - const static_lib_aarch64 = lib: { - const lib = b.addStaticLibrary(.{ - .name = "ghostty", - .root_source_file = .{ .path = "src/main_c.zig" }, - .target = b.resolveTargetQuery(.{ - .cpu_arch = .aarch64, - .os_tag = .macos, - .os_version_min = target.query.os_version_min, - }), - .optimize = optimize, - }); - lib.bundle_compiler_rt = true; - lib.linkLibC(); - lib.root_module.addOptions("build_options", exe_options); - - // Create a single static lib with all our dependencies merged - var lib_list = try addDeps(b, lib, macos_config); - try lib_list.append(lib.getEmittedBin()); - const libtool = LibtoolStep.create(b, .{ - .name = "ghostty", - .out_name = "libghostty-aarch64-fat.a", - .sources = lib_list.items, - }); - libtool.step.dependOn(&lib.step); - b.default_step.dependOn(libtool.step); - - break :lib libtool; - }; - - const static_lib_x86_64 = lib: { - const lib = b.addStaticLibrary(.{ - .name = "ghostty", - .root_source_file = .{ .path = "src/main_c.zig" }, - .target = b.resolveTargetQuery(.{ - .cpu_arch = .x86_64, - .os_tag = .macos, - .os_version_min = target.query.os_version_min, - }), - .optimize = optimize, - }); - lib.bundle_compiler_rt = true; - lib.linkLibC(); - lib.root_module.addOptions("build_options", exe_options); - - // Create a single static lib with all our dependencies merged - var lib_list = try addDeps(b, lib, macos_config); - try lib_list.append(lib.getEmittedBin()); - const libtool = LibtoolStep.create(b, .{ - .name = "ghostty", - .out_name = "libghostty-x86_64-fat.a", - .sources = lib_list.items, - }); - libtool.step.dependOn(&lib.step); - b.default_step.dependOn(libtool.step); - - break :lib libtool; - }; - - const static_lib_universal = LipoStep.create(b, .{ - .name = "ghostty", - .out_name = "libghostty.a", - .input_a = static_lib_aarch64.output, - .input_b = static_lib_x86_64.output, - }); - static_lib_universal.step.dependOn(static_lib_aarch64.step); - static_lib_universal.step.dependOn(static_lib_x86_64.step); + // Create the universal macOS lib. + const macos_lib_step, const macos_lib_path = try createMacOSLib( + b, + optimize, + config, + exe_options, + ); // Add our library to zig-out const lib_install = b.addInstallLibFile( - static_lib_universal.output, - "libghostty.a", + macos_lib_path, + "libghostty-macos.a", ); b.getInstallStep().dependOn(&lib_install.step); - // Copy our ghostty.h to include + // Create the universal iOS lib. + const ios_lib_step, const ios_lib_path = try createIOSLib( + b, + optimize, + config, + exe_options, + ); + + // Add our library to zig-out + const ios_lib_install = b.addInstallLibFile( + ios_lib_path, + "libghostty-ios.a", + ); + b.getInstallStep().dependOn(&ios_lib_install.step); + + // Copy our ghostty.h to include. The header file is shared by + // all embedded targets. const header_install = b.addInstallHeaderFile( "include/ghostty.h", "ghostty.h", @@ -552,72 +463,23 @@ pub fn build(b: *std.Build) !void { const xcframework = XCFrameworkStep.create(b, .{ .name = "GhosttyKit", .out_path = "macos/GhosttyKit.xcframework", - .library = static_lib_universal.output, - .headers = .{ .path = "include" }, + .libraries = &.{ + .{ + .library = macos_lib_path, + .headers = .{ .path = "include" }, + }, + .{ + .library = ios_lib_path, + .headers = .{ .path = "include" }, + }, + }, }); - xcframework.step.dependOn(static_lib_universal.step); + xcframework.step.dependOn(ios_lib_step); + xcframework.step.dependOn(macos_lib_step); + xcframework.step.dependOn(&header_install.step); b.default_step.dependOn(xcframework.step); } - // iOS - if (builtin.target.isDarwin() and target.result.os.tag == .ios) { - const ios_config: BuildConfig = config: { - var copy = config; - copy.static = true; - break :config copy; - }; - - const lib = b.addStaticLibrary(.{ - .name = "ghostty", - .root_source_file = .{ .path = "src/main_c.zig" }, - .target = b.resolveTargetQuery(.{ - .cpu_arch = .aarch64, - .os_tag = .ios, - .os_version_min = target.query.os_version_min, - }), - .optimize = optimize, - }); - lib.bundle_compiler_rt = true; - lib.linkLibC(); - lib.root_module.addOptions("build_options", exe_options); - - // Create a single static lib with all our dependencies merged - var lib_list = try addDeps(b, lib, ios_config); - try lib_list.append(lib.getEmittedBin()); - const libtool = LibtoolStep.create(b, .{ - .name = "ghostty", - .out_name = "libghostty-ios-fat.a", - .sources = lib_list.items, - }); - libtool.step.dependOn(&lib.step); - b.default_step.dependOn(libtool.step); - - // Add our library to zig-out - const lib_install = b.addInstallLibFile( - libtool.output, - "libghostty.a", - ); - b.getInstallStep().dependOn(&lib_install.step); - - // Copy our ghostty.h to include - const header_install = b.addInstallHeaderFile( - "include/ghostty.h", - "ghostty.h", - ); - b.getInstallStep().dependOn(&header_install.step); - - // // The xcframework wraps our ghostty library so that we can link - // // it to the final app built with Swift. - // const xcframework = XCFrameworkStep.create(b, .{ - // .name = "GhosttyKit", - // .out_path = "macos/GhosttyKit.xcframework", - // .library = libtool.output, - // .headers = .{ .path = "include" }, - // }); - // xcframework.step.dependOn(libtool.step); - // b.default_step.dependOn(xcframework.step); - } - // wasm { // Build our Wasm target. @@ -748,6 +610,173 @@ pub fn build(b: *std.Build) !void { } } +/// Returns the minimum OS version for the given OS tag. This shouldn't +/// be used generally, it should only be used for Darwin-based OS currently. +fn osVersionMin(tag: std.Target.Os.Tag) ?std.Target.Query.OsVersion { + return switch (tag) { + // The lowest supported version of macOS is 12.x because + // this is the first version to support Apple Silicon so it is + // the earliest version we can virtualize to test (I only have + // an Apple Silicon machine for macOS). + .macos => .{ .semver = .{ + .major = 12, + .minor = 0, + .patch = 0, + } }, + + // iOS 17 picked arbitrarily + .ios => .{ .semver = .{ + .major = 17, + .minor = 0, + .patch = 0, + } }, + + // This should never happen currently. If we add a new target then + // we should add a new case here. + else => @panic("unhandled os version min os tag"), + }; +} + +/// Creates a universal macOS libghostty library and returns the path +/// to the final library. +/// +/// The library is always a fat static library currently because this is +/// expected to be used directly with Xcode and Swift. In the future, we +/// probably want to change this because it makes it harder to use the +/// library in other contexts. +fn createMacOSLib( + b: *std.Build, + optimize: std.builtin.OptimizeMode, + config: BuildConfig, + exe_options: *std.Build.Step.Options, +) !struct { *std.Build.Step, std.Build.LazyPath } { + // Modify our build configuration for macOS builds. + const macos_config: BuildConfig = config: { + var copy = config; + + // Always static for the macOS app because we want all of our + // dependencies in a fat static library. + copy.static = true; + + break :config copy; + }; + + const static_lib_aarch64 = lib: { + const lib = b.addStaticLibrary(.{ + .name = "ghostty", + .root_source_file = .{ .path = "src/main_c.zig" }, + .target = b.resolveTargetQuery(.{ + .cpu_arch = .aarch64, + .os_tag = .macos, + .os_version_min = osVersionMin(.macos), + }), + .optimize = optimize, + }); + lib.bundle_compiler_rt = true; + lib.linkLibC(); + lib.root_module.addOptions("build_options", exe_options); + + // Create a single static lib with all our dependencies merged + var lib_list = try addDeps(b, lib, macos_config); + try lib_list.append(lib.getEmittedBin()); + const libtool = LibtoolStep.create(b, .{ + .name = "ghostty", + .out_name = "libghostty-aarch64-fat.a", + .sources = lib_list.items, + }); + libtool.step.dependOn(&lib.step); + b.default_step.dependOn(libtool.step); + + break :lib libtool; + }; + + const static_lib_x86_64 = lib: { + const lib = b.addStaticLibrary(.{ + .name = "ghostty", + .root_source_file = .{ .path = "src/main_c.zig" }, + .target = b.resolveTargetQuery(.{ + .cpu_arch = .x86_64, + .os_tag = .macos, + .os_version_min = osVersionMin(.macos), + }), + .optimize = optimize, + }); + lib.bundle_compiler_rt = true; + lib.linkLibC(); + lib.root_module.addOptions("build_options", exe_options); + + // Create a single static lib with all our dependencies merged + var lib_list = try addDeps(b, lib, macos_config); + try lib_list.append(lib.getEmittedBin()); + const libtool = LibtoolStep.create(b, .{ + .name = "ghostty", + .out_name = "libghostty-x86_64-fat.a", + .sources = lib_list.items, + }); + libtool.step.dependOn(&lib.step); + b.default_step.dependOn(libtool.step); + + break :lib libtool; + }; + + const static_lib_universal = LipoStep.create(b, .{ + .name = "ghostty", + .out_name = "libghostty.a", + .input_a = static_lib_aarch64.output, + .input_b = static_lib_x86_64.output, + }); + static_lib_universal.step.dependOn(static_lib_aarch64.step); + static_lib_universal.step.dependOn(static_lib_x86_64.step); + + return .{ + static_lib_universal.step, + static_lib_universal.output, + }; +} + +/// Create an Apple iOS/iPadOS build. +fn createIOSLib( + b: *std.Build, + optimize: std.builtin.OptimizeMode, + config: BuildConfig, + exe_options: *std.Build.Step.Options, +) !struct { *std.Build.Step, std.Build.LazyPath } { + const ios_config: BuildConfig = config: { + var copy = config; + copy.static = true; + break :config copy; + }; + + const lib = b.addStaticLibrary(.{ + .name = "ghostty", + .root_source_file = .{ .path = "src/main_c.zig" }, + .optimize = optimize, + .target = b.resolveTargetQuery(.{ + .cpu_arch = .aarch64, + .os_tag = .ios, + .os_version_min = osVersionMin(.ios), + }), + }); + lib.bundle_compiler_rt = true; + lib.linkLibC(); + lib.root_module.addOptions("build_options", exe_options); + + // Create a single static lib with all our dependencies merged + var lib_list = try addDeps(b, lib, ios_config); + try lib_list.append(lib.getEmittedBin()); + const libtool = LibtoolStep.create(b, .{ + .name = "ghostty", + .out_name = "libghostty-ios-fat.a", + .sources = lib_list.items, + }); + libtool.step.dependOn(&lib.step); + + return .{ + libtool.step, + libtool.output, + }; +} + /// Used to keep track of a list of file sources. const LazyPathList = std.ArrayList(std.Build.LazyPath); diff --git a/src/build/XCFrameworkStep.zig b/src/build/XCFrameworkStep.zig index a611edc4b..823e5aac4 100644 --- a/src/build/XCFrameworkStep.zig +++ b/src/build/XCFrameworkStep.zig @@ -15,6 +15,12 @@ pub const Options = struct { /// The path to write the framework out_path: []const u8, + /// The libraries to bundle + libraries: []const Library, +}; + +/// A single library to bundle into the xcframework. +pub const Library = struct { /// Library file (dylib, a) to package. library: LazyPath, @@ -41,10 +47,12 @@ pub fn create(b: *std.Build, opts: Options) *XCFrameworkStep { const run = RunStep.create(b, b.fmt("xcframework {s}", .{opts.name})); run.has_side_effects = true; run.addArgs(&.{ "xcodebuild", "-create-xcframework" }); - run.addArg("-library"); - run.addFileArg(opts.library); - run.addArg("-headers"); - run.addFileArg(opts.headers); + for (opts.libraries) |lib| { + run.addArg("-library"); + run.addFileArg(lib.library); + run.addArg("-headers"); + run.addFileArg(lib.headers); + } run.addArg("-output"); run.addArg(opts.out_path); break :run run;