diff --git a/build.zig b/build.zig index 539a0f148..311e6d6e2 100644 --- a/build.zig +++ b/build.zig @@ -140,6 +140,8 @@ pub fn build(b: *std.build.Builder) !void { static_lib.setTarget(target); static_lib.install(); static_lib.linkLibC(); + static_lib.addOptions("build_options", exe_options); + try addDeps(b, static_lib, true); b.default_step.dependOn(&static_lib.step); } @@ -150,8 +152,9 @@ pub fn build(b: *std.build.Builder) !void { const lib = b.addStaticLibrary("ghostty", "src/main_c.zig"); lib.setBuildMode(mode); lib.setTarget(try std.zig.CrossTarget.parse(.{ .arch_os_abi = "aarch64-macos" })); - lib.install(); lib.linkLibC(); + lib.addOptions("build_options", exe_options); + try addDeps(b, lib, true); b.default_step.dependOn(&lib.step); break :lib lib; }; @@ -160,8 +163,9 @@ pub fn build(b: *std.build.Builder) !void { const lib = b.addStaticLibrary("ghostty", "src/main_c.zig"); lib.setBuildMode(mode); lib.setTarget(try std.zig.CrossTarget.parse(.{ .arch_os_abi = "x86_64-macos" })); - lib.install(); lib.linkLibC(); + lib.addOptions("build_options", exe_options); + try addDeps(b, lib, true); b.default_step.dependOn(&lib.step); break :lib lib; }; @@ -368,12 +372,18 @@ fn addDeps( return; } + // If we're building a lib we have some different deps + const lib = step.kind == .lib; + + // We always require the system SDK so that our system headers are available. + // This makes things like `os/log.h` available for cross-compiling. + system_sdk.include(b, step, .{}); + // We always need the Zig packages if (enable_fontconfig) step.addModule("fontconfig", fontconfig.module(b)); step.addModule("freetype", freetype.module(b)); step.addModule("harfbuzz", harfbuzz.module(b)); step.addModule("imgui", imgui.module(b)); - step.addModule("glfw", glfw.module(b)); step.addModule("xev", libxev.module(b)); step.addModule("pixman", pixman.module(b)); step.addModule("stb_image_resize", stb_image_resize.module(b)); @@ -386,10 +396,6 @@ fn addDeps( _ = try macos.link(b, step, .{}); } - // We always statically compile glad - step.addIncludePath("vendor/glad/include/"); - step.addCSourceFile("vendor/glad/src/gl.c", &.{}); - // Tracy step.addModule("tracy", tracylib.module(b)); if (tracy) { @@ -403,13 +409,6 @@ fn addDeps( // utf8proc _ = try utf8proc.link(b, step); - // Glfw - const glfw_opts: glfw.Options = .{ - .metal = step.target.isDarwin(), - .opengl = false, - }; - try glfw.link(b, step, glfw_opts); - // Imgui, we have to do this later since we need some information const imgui_backends = if (step.target.isDarwin()) &[_][]const u8{ "glfw", "opengl3", "metal" } @@ -505,9 +504,24 @@ fn addDeps( imgui_opts.freetype.include = &freetype.include_paths; } - // Imgui - const imgui_step = try imgui.link(b, step, imgui_opts); - try glfw.link(b, imgui_step, glfw_opts); + if (!lib) { + step.addModule("glfw", glfw.module(b)); + + // We always statically compile glad + step.addIncludePath("vendor/glad/include/"); + step.addCSourceFile("vendor/glad/src/gl.c", &.{}); + + // Glfw + const glfw_opts: glfw.Options = .{ + .metal = step.target.isDarwin(), + .opengl = false, + }; + try glfw.link(b, step, glfw_opts); + + // Imgui + const imgui_step = try imgui.link(b, step, imgui_opts); + try glfw.link(b, imgui_step, glfw_opts); + } } fn benchSteps( diff --git a/include/ghostty.h b/include/ghostty.h index 2dedabefd..18940d6c5 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -7,7 +7,9 @@ extern "C" { #include -uint64_t ghostty_hello(void); +#define GHOSTTY_SUCCESS 0 + +int ghostty_init(void); #ifdef __cplusplus } diff --git a/macos/Sources/GhosttyApp.swift b/macos/Sources/GhosttyApp.swift index c05c5c1dd..53829b678 100644 --- a/macos/Sources/GhosttyApp.swift +++ b/macos/Sources/GhosttyApp.swift @@ -3,11 +3,13 @@ import GhosttyKit @main struct GhosttyApp: App { - @State private var num = ghostty_hello() + init() { + assert(ghostty_init() == GHOSTTY_SUCCESS, "ghostty failed to initialize"); + } var body: some Scene { WindowGroup { - Text(String(num)).font(.largeTitle) + Text("Hello!").font(.largeTitle) } } } diff --git a/src/App.zig b/src/App.zig index e1e01bd00..402937751 100644 --- a/src/App.zig +++ b/src/App.zig @@ -5,6 +5,7 @@ const App = @This(); const std = @import("std"); const builtin = @import("builtin"); +const assert = std.debug.assert; const Allocator = std.mem.Allocator; const apprt = @import("apprt.zig"); const Window = @import("Window.zig"); @@ -273,8 +274,7 @@ pub const Wasm = if (!builtin.target.isWasm()) struct {} else struct { const alloc = wasm.alloc; // export fn app_new(config: *Config) ?*App { - // return app_new_(config) catch |err| { - // log.err("error initializing app err={}", .{err}); + // return app_new_(config) catch |err| { log.err("error initializing app err={}", .{err}); // return null; // }; // } @@ -295,3 +295,15 @@ pub const Wasm = if (!builtin.target.isWasm()) struct {} else struct { // } // } }; + +pub const CAPI = struct { + const ProcessState = @import("main.zig").ProcessState; + var state: ?ProcessState = null; + + export fn ghostty_init() c_int { + assert(state == null); + state = undefined; + ProcessState.init(&state.?); + return 0; + } +}; diff --git a/src/main.zig b/src/main.zig index 61dc5e2ba..dad323b31 100644 --- a/src/main.zig +++ b/src/main.zig @@ -16,58 +16,88 @@ const App = @import("App.zig"); const cli_args = @import("cli_args.zig"); const Config = @import("config.zig").Config; -pub fn main() !void { - // Output some debug information right away - std.log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()}); - if (options.fontconfig) { - std.log.info("dependency fontconfig={d}", .{fontconfig.version()}); - } - std.log.info("renderer={}", .{renderer.Renderer}); - std.log.info("libxev backend={}", .{xev.backend}); - - // First things first, we fix our file descriptors - internal_os.fixMaxFiles(); - - // We need to make sure the process locale is set properly. Locale - // affects a lot of behaviors in a shell. - internal_os.ensureLocale(); - +/// ProcessState represents the global process state. There should only +/// be one of these at any given moment. This is extracted into a dedicated +/// struct because it is reused by main and the static C lib. +/// +/// ProcessState.init should be one of the first things ever called +/// when using Ghostty. Ghostty calls this for you so this is more of a note +/// for maintainers. +pub const ProcessState = struct { const GPA = std.heap.GeneralPurposeAllocator(.{}); - var gpa: ?GPA = gpa: { - // Use the libc allocator if it is available beacuse it is WAY - // faster than GPA. We only do this in release modes so that we - // can get easy memory leak detection in debug modes. - if (builtin.link_libc) { - if (switch (builtin.mode) { - .ReleaseSafe, .ReleaseFast => true, - // We also use it if we can detect we're running under - // Valgrind since Valgrind only instruments the C allocator - else => std.valgrind.runningOnValgrind() > 0, - }) break :gpa null; + gpa: ?GPA, + alloc: std.mem.Allocator, + + pub fn init(self: *ProcessState) void { + // Output some debug information right away + std.log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()}); + if (options.fontconfig) { + std.log.info("dependency fontconfig={d}", .{fontconfig.version()}); } + std.log.info("renderer={}", .{renderer.Renderer}); + std.log.info("libxev backend={}", .{xev.backend}); - break :gpa GPA{}; - }; - defer if (gpa) |*value| { - // We want to ensure that we deinit the GPA because this is - // the point at which it will output if there were safety violations. - _ = value.deinit(); - }; + // First things first, we fix our file descriptors + internal_os.fixMaxFiles(); - const alloc = alloc: { - const base = if (gpa) |*value| - value.allocator() - else if (builtin.link_libc) - std.heap.c_allocator - else - unreachable; + // We need to make sure the process locale is set properly. Locale + // affects a lot of behaviors in a shell. + internal_os.ensureLocale(); - // If we're tracing, wrap the allocator - if (!tracy.enabled) break :alloc base; - var tracy_alloc = tracy.allocator(base, null); - break :alloc tracy_alloc.allocator(); - }; + // Initialize ourself to nothing so we don't have any extra state. + self.* = .{ + .gpa = null, + .alloc = undefined, + }; + errdefer self.deinit(); + + self.gpa = gpa: { + // Use the libc allocator if it is available beacuse it is WAY + // faster than GPA. We only do this in release modes so that we + // can get easy memory leak detection in debug modes. + if (builtin.link_libc) { + if (switch (builtin.mode) { + .ReleaseSafe, .ReleaseFast => true, + + // We also use it if we can detect we're running under + // Valgrind since Valgrind only instruments the C allocator + else => std.valgrind.runningOnValgrind() > 0, + }) break :gpa null; + } + + break :gpa GPA{}; + }; + + self.alloc = alloc: { + const base = if (self.gpa) |*value| + value.allocator() + else if (builtin.link_libc) + std.heap.c_allocator + else + unreachable; + + // If we're tracing, wrap the allocator + if (!tracy.enabled) break :alloc base; + var tracy_alloc = tracy.allocator(base, null); + break :alloc tracy_alloc.allocator(); + }; + } + + pub fn deinit(self: *ProcessState) void { + if (self.gpa) |*value| { + // We want to ensure that we deinit the GPA because this is + // the point at which it will output if there were safety violations. + _ = value.deinit(); + } + } +}; + +pub fn main() !void { + var state: ProcessState = undefined; + ProcessState.init(&state); + defer state.deinit(); + const alloc = state.alloc; // Try reading our config var config = try Config.default(alloc); diff --git a/src/main_c.zig b/src/main_c.zig index 723698fb6..d00a5e62b 100644 --- a/src/main_c.zig +++ b/src/main_c.zig @@ -4,8 +4,8 @@ // support). const std = @import("std"); const builtin = @import("builtin"); +const main = @import("main.zig"); -// We're just testing right now. -export fn ghostty_hello() u64 { - return 42; -} +pub usingnamespace @import("App.zig").CAPI; + +pub const std_options = main.std_options;