From 80358655962cdd997f17f08d4f32c8e59c5c9430 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 13 Feb 2023 22:20:33 -0800 Subject: [PATCH] build: ghostty lib, framework, build into app --- build.zig | 33 +++++++++++-- include/ghostty.h | 16 +++++++ include/module.modulemap | 4 ++ macos/.gitignore | 1 + macos/Package.swift | 10 +++- macos/Sources/Ghostty/entry.swift | 6 ++- src/build/XCFrameworkStep.zig | 78 +++++++++++++++++++++++++++++++ src/main_c.zig | 11 +++++ 8 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 include/ghostty.h create mode 100644 include/module.modulemap create mode 100644 src/build/XCFrameworkStep.zig create mode 100644 src/main_c.zig diff --git a/build.zig b/build.zig index 6dbd83ee3..c7af4f357 100644 --- a/build.zig +++ b/build.zig @@ -22,6 +22,7 @@ const tracylib = @import("pkg/tracy/build.zig"); const system_sdk = @import("vendor/mach/libs/glfw/system_sdk.zig"); const WasmTarget = @import("src/os/wasm/target.zig").Target; const SwiftBuildStep = @import("src/build/SwiftBuildStep.zig"); +const XCFrameworkStep = @import("src/build/XCFrameworkStep.zig"); // Do a comptime Zig version requirement. The required Zig version is // somewhat arbitrary: it is meant to be a version that we feel works well, @@ -132,17 +133,41 @@ pub fn build(b: *std.build.Builder) !void { b.installFile("dist/macos/Ghostty.icns", "Ghostty.app/Contents/Resources/Ghostty.icns"); } - // Mac App based on Swift - { + // c lib + const static_lib = lib: { + const static_lib = b.addStaticLibrary("ghostty", "src/main_c.zig"); + static_lib.setBuildMode(mode); + static_lib.setTarget(target); + static_lib.install(); + static_lib.linkLibC(); + b.default_step.dependOn(&static_lib.step); + break :lib static_lib; + }; + + // On Mac we can build the app. + const macapp = b.step("macapp", "Build macOS app"); + if (builtin.target.isDarwin()) { + // 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 = .{ .generated = &static_lib.output_lib_path_source }, + .headers = .{ .path = "include" }, + }); + xcframework.step.dependOn(&static_lib.step); + macapp.dependOn(&xcframework.step); + + // Build our swift app const swift_build = SwiftBuildStep.create(b, .{ .product = "Ghostty", .target = target, .optimize = mode, .cwd = .{ .path = "macos" }, }); - - const macapp = b.step("macapp", "Build macOS app"); macapp.dependOn(&swift_build.step); + + // Build our app bundle macapp.dependOn(&b.addInstallFileWithDir( .{ .generated = &swift_build.bin_path }, .prefix, diff --git a/include/ghostty.h b/include/ghostty.h new file mode 100644 index 000000000..2dedabefd --- /dev/null +++ b/include/ghostty.h @@ -0,0 +1,16 @@ +#ifndef GHOSTTY_H +#define GHOSTTY_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +uint64_t ghostty_hello(void); + +#ifdef __cplusplus +} +#endif + +#endif /* GHOSTTY_H */ diff --git a/include/module.modulemap b/include/module.modulemap new file mode 100644 index 000000000..917796d89 --- /dev/null +++ b/include/module.modulemap @@ -0,0 +1,4 @@ +module GhosttyKit { + umbrella header "ghostty.h" + export * +} diff --git a/macos/.gitignore b/macos/.gitignore index 3b2981208..538cdd3dd 100644 --- a/macos/.gitignore +++ b/macos/.gitignore @@ -2,6 +2,7 @@ /.build /Packages /*.xcodeproj +/*.xcframework xcuserdata/ DerivedData/ .swiftpm/config/registries.json diff --git a/macos/Package.swift b/macos/Package.swift index d85ec4af0..f436448ae 100644 --- a/macos/Package.swift +++ b/macos/Package.swift @@ -10,10 +10,18 @@ let package = Package( // SwiftUI .macOS(.v11), ], + products: [ + .executable( + name: "Ghostty", + targets: ["Ghostty"]), + ], dependencies: [], targets: [ .executableTarget( name: "Ghostty", - dependencies: []), + dependencies: ["GhosttyKit"]), + .binaryTarget( + name: "GhosttyKit", + path: "GhosttyKit.xcframework"), ] ) diff --git a/macos/Sources/Ghostty/entry.swift b/macos/Sources/Ghostty/entry.swift index 2829f7fa3..72829efd7 100644 --- a/macos/Sources/Ghostty/entry.swift +++ b/macos/Sources/Ghostty/entry.swift @@ -1,10 +1,14 @@ import SwiftUI +import GhosttyKit @main struct MyApp: App { + @State private var num = ghostty_hello() + var body: some Scene { WindowGroup { - ContentView() + Text(String(num)) + .font(.largeTitle) } } } diff --git a/src/build/XCFrameworkStep.zig b/src/build/XCFrameworkStep.zig new file mode 100644 index 000000000..8522810df --- /dev/null +++ b/src/build/XCFrameworkStep.zig @@ -0,0 +1,78 @@ +//! A zig builder step that runs "swift build" in the context of +//! a Swift project managed with SwiftPM. This is primarily meant to build +//! executables currently since that is what we build. +const XCFrameworkStep = @This(); + +const std = @import("std"); +const Step = std.build.Step; +const GeneratedFile = std.build.GeneratedFile; + +pub const Options = struct { + /// The name of the xcframework to create. + name: []const u8, + + /// The path to write the framework + out_path: []const u8, + + /// Library file (dylib, a) to package. + library: std.build.FileSource, + + /// Path to a directory with the headers. + headers: std.build.FileSource, +}; + +step: Step, +builder: *std.build.Builder, + +/// See Options +name: []const u8, +out_path: []const u8, +library: std.build.FileSource, +headers: std.build.FileSource, + +pub fn create(builder: *std.build.Builder, opts: Options) *XCFrameworkStep { + const self = builder.allocator.create(XCFrameworkStep) catch @panic("OOM"); + self.* = .{ + .step = Step.init(.custom, builder.fmt( + "xcframework {s}", + .{opts.name}, + ), builder.allocator, make), + .builder = builder, + .name = opts.name, + .out_path = opts.out_path, + .library = opts.library, + .headers = opts.headers, + }; + return self; +} + +fn make(step: *Step) !void { + const self = @fieldParentPtr(XCFrameworkStep, "step", step); + + // TODO: use the zig cache system when it is in the stdlib + // https://github.com/ziglang/zig/pull/14571 + const output_path = self.out_path; + + // We use a RunStep here to ease our configuration. + { + const run = std.build.RunStep.create(self.builder, self.builder.fmt( + "xcframework delete {s}", + .{self.name}, + )); + run.addArgs(&.{ "rm", "-rf", output_path }); + try run.step.make(); + } + { + const run = std.build.RunStep.create(self.builder, self.builder.fmt( + "xcframework {s}", + .{self.name}, + )); + run.addArgs(&.{ + "xcodebuild", "-create-xcframework", + "-library", self.library.getPath(self.builder), + "-headers", self.headers.getPath(self.builder), + "-output", output_path, + }); + try run.step.make(); + } +} diff --git a/src/main_c.zig b/src/main_c.zig new file mode 100644 index 000000000..723698fb6 --- /dev/null +++ b/src/main_c.zig @@ -0,0 +1,11 @@ +// This is the main file for the C API. The C API is used to embed Ghostty +// within other applications. Depending on the build settings some APIs +// may not be available (i.e. embedding into macOS exposes various Metal +// support). +const std = @import("std"); +const builtin = @import("builtin"); + +// We're just testing right now. +export fn ghostty_hello() u64 { + return 42; +}