Nuke GLFW, make zig build run on macOS build the Xcode project (#7815)

This PR does two things.

1. Build system improvements to make developing on macOS more enjoyable 
2. Delete the GLFW apprt

## Build System Improvements (macOS)

On macOS, there are a few major improvements:

* `zig build` now produces a full macOS app bundle and copies it into
`zig-out`
* `zig build run` now builds the macOS app and runs it, streaming logs
directly into the terminal
* `-Demit-macos-app` can control whether app bundle is created
* `-Dxcframework-target` can be set to one of `native` or `universal` to
control whether the xcframework uses only your target machines arch or
creates a universal one with macOS and iOS. This defaults to `native`
for the `run` command and `universal` for all others.
* The general flow of the `build.zig` file was improved to be more
declarative

## Nuke GLFW

> This deletes the GLFW apprt from the Ghostty codebase.
> 
> The GLFW apprt was the original apprt used by Ghostty (well, before
> Ghostty even had the concept of an "apprt" -- it was all just a single
> application then). It let me iterate on the core terminal features,
> rendering, etc. without bothering about the UI. It was a good way to
get
> started. But it has long since outlived its usefulness.
> 
> We've had a stable GTK apprt for Linux (and Windows via WSL) and a
> native macOS app via libghostty for awhile now. The GLFW apprt only
> remained within the tree for a few reasons:
> 
>   1. Primarily, it provided a faster feedback loop on macOS because
>      building the macOS app historically required us to hop out of the
>      zig build system and into Xcode, which is slow and cumbersome.
> 
>   2. It was a convenient way to narrow whether a bug was in the
> core Ghostty codebase or in the apprt itself. If a bug was in both
>      the glfw and macOS app then it was likely in the core.
> 
>   3. It provided us a way on macOS to test OpenGL.
> 
> All of these reasons are no longer valid. Respectively:
> 
> 1. Our Zig build scripts now execute the `xcodebuild` CLI directly and
>      can open the resulting app, stream logs, etc. This is the same
> experience we have on Linux. (Xcode has always been a dependency of
>      building on macOS in general, so this is not cumbersome.)
> 
>   2. We have a healthy group of maintainers, many of which have access
>      to both macOS and Linux, so we can quickly narrow down bugs
>      regardless of the apprt.
> 
> 3. Our OpenGL renderer hasn't been compatible with macOS for some time
>      now, so this is no longer a useful feature.
> 
> At this point, the GLFW apprt is just a burden. It adds complexity
> across the board, and some people try to run Ghostty with it in the
real
> world and get confused when it doesn't work (it's always been lacking
in
> features and buggy compared to the other apprts).
> 
> So, it's time to say goodbye. Its bittersweet because it is a big part
> of Ghostty's history, but we've grown up now and it's time to move on.
> Thank you, goodbye.
> 
> (NOTE: If you are a user of the GLFW apprt, then please fork the
project
> prior to this commit or start a new project based on it. We've warned
> against using it for a very, very long time now.)
This commit is contained in:
Mitchell Hashimoto
2025-07-05 13:49:24 -07:00
committed by GitHub
75 changed files with 407 additions and 22551 deletions

View File

@@ -5,7 +5,6 @@ const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const builtin = @import("builtin");
const glfw = @import("glfw");
const objc = @import("objc");
const macos = @import("macos");
const graphics = macos.graphics;
@@ -38,11 +37,6 @@ pub const swap_chain_count = 3;
const log = std.log.scoped(.metal);
// Get native API access on certain platforms so we can do more customization.
const glfwNative = glfw.Native(.{
.cocoa = builtin.os.tag == .macos,
});
layer: IOSurfaceLayer,
/// MTLDevice
@@ -87,27 +81,6 @@ pub fn init(alloc: Allocator, opts: rendererpkg.Options) !Metal {
// Get the metadata about our underlying view that we'll be rendering to.
const info: ViewInfo = switch (apprt.runtime) {
apprt.glfw => info: {
// Everything in glfw is window-oriented so we grab the backing
// window, then derive everything from that.
const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(
opts.rt_surface.window,
).?);
const contentView = objc.Object.fromId(
nswindow.getProperty(?*anyopaque, "contentView").?,
);
const scaleFactor = nswindow.getProperty(
graphics.c.CGFloat,
"backingScaleFactor",
);
break :info .{
.view = contentView,
.scaleFactor = scaleFactor,
};
},
apprt.embedded => .{
.scaleFactor = @floatCast(opts.rt_surface.content_scale.x),
.view = switch (opts.rt_surface.platform) {

View File

@@ -5,7 +5,6 @@ const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const builtin = @import("builtin");
const glfw = @import("glfw");
const gl = @import("opengl");
const shadertoy = @import("shadertoy.zig");
const apprt = @import("../apprt.zig");
@@ -60,18 +59,6 @@ pub fn deinit(self: *OpenGL) void {
self.* = undefined;
}
/// Returns the hints that we want for this
pub fn glfwWindowHints(config: *const configpkg.Config) glfw.Window.Hints {
_ = config;
return .{
.context_version_major = MIN_VERSION_MAJOR,
.context_version_minor = MIN_VERSION_MINOR,
.opengl_profile = .opengl_core_profile,
.opengl_forward_compat = true,
.transparent_framebuffer = true,
};
}
/// 32-bit windows cross-compilation breaks with `.c` for some reason, so...
const gl_debug_proc_callconv =
@typeInfo(
@@ -172,8 +159,7 @@ fn prepareContext(getProcAddress: anytype) !void {
/// This is called early right after surface creation.
pub fn surfaceInit(surface: *apprt.Surface) !void {
// Treat this like a thread entry
const self: OpenGL = undefined;
_ = surface;
switch (apprt.runtime) {
else => @compileError("unsupported app runtime for OpenGL"),
@@ -181,8 +167,6 @@ pub fn surfaceInit(surface: *apprt.Surface) !void {
// GTK uses global OpenGL context so we load from null.
apprt.gtk => try prepareContext(null),
apprt.glfw => try self.threadEnter(surface),
apprt.embedded => {
// TODO(mitchellh): this does nothing today to allow libghostty
// to compile for OpenGL targets but libghostty is strictly
@@ -205,17 +189,12 @@ pub fn surfaceInit(surface: *apprt.Surface) !void {
pub fn finalizeSurfaceInit(self: *const OpenGL, surface: *apprt.Surface) !void {
_ = self;
_ = surface;
// For GLFW, we grabbed the OpenGL context in surfaceInit and
// we need to release it before we start the renderer thread.
if (apprt.runtime == apprt.glfw) {
glfw.makeContextCurrent(null);
}
}
/// Callback called by renderer.Thread when it begins.
pub fn threadEnter(self: *const OpenGL, surface: *apprt.Surface) !void {
_ = self;
_ = surface;
switch (apprt.runtime) {
else => @compileError("unsupported app runtime for OpenGL"),
@@ -227,21 +206,6 @@ pub fn threadEnter(self: *const OpenGL, surface: *apprt.Surface) !void {
// on the main thread. As such, we don't do anything here.
},
apprt.glfw => {
// We need to make the OpenGL context current. OpenGL requires
// that a single thread own the a single OpenGL context (if any).
// This ensures that the context switches over to our thread.
// Important: the prior thread MUST have detached the context
// prior to calling this entrypoint.
glfw.makeContextCurrent(surface.window);
errdefer glfw.makeContextCurrent(null);
glfw.swapInterval(1);
// Load OpenGL bindings. This API is context-aware so this sets
// a threadlocal context for these pointers.
try prepareContext(&glfw.getProcAddress);
},
apprt.embedded => {
// TODO(mitchellh): this does nothing today to allow libghostty
// to compile for OpenGL targets but libghostty is strictly
@@ -262,11 +226,6 @@ pub fn threadExit(self: *const OpenGL) void {
// be sharing the global bindings with other windows.
},
apprt.glfw => {
gl.glad.unload();
glfw.makeContextCurrent(null);
},
apprt.embedded => {
// TODO: see threadEnter
},

View File

@@ -1,6 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const glfw = @import("glfw");
const xev = @import("xev");
const wuffs = @import("wuffs");
const apprt = @import("../apprt.zig");
@@ -606,20 +605,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
}
};
/// Returns the hints that we want for this window.
pub fn glfwWindowHints(config: *const configpkg.Config) glfw.Window.Hints {
// If our graphics API provides hints, use them,
// otherwise fall back to generic hints.
if (@hasDecl(GraphicsAPI, "glfwWindowHints")) {
return GraphicsAPI.glfwWindowHints(config);
}
return .{
.client_api = .no_api,
.transparent_framebuffer = config.@"background-opacity" < 1,
};
}
pub fn init(alloc: Allocator, options: renderer.Options) !Self {
// Initialize our graphics API wrapper, this will prepare the
// surface provided by the apprt and set up any API-specific