mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
Build system can build macOS app bundle and open it
`zig build run` on macOS now builds the app bundle via the `xcodebuild` CLI and runs it. The experience for running the app is now very similar to Linux or the prior GLFW build, where the app runs, blocks the zig command, and logs to the terminal. `xcodebuild` has its own build cache system that we can't really hook into so it runs on every `zig build run` command, but it does cache and I find its actually relatively fast so I think this is a good replacement for the glfw-based system.
This commit is contained in:
69
build.zig
69
build.zig
@ -1,4 +1,5 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const builtin = @import("builtin");
|
||||
const buildpkg = @import("src/build/main.zig");
|
||||
|
||||
@ -47,6 +48,25 @@ pub fn build(b: *std.Build) !void {
|
||||
exe.install();
|
||||
resources.install();
|
||||
i18n.install();
|
||||
|
||||
// Run runs the Ghostty exe. We only do this if we are building
|
||||
// an apprt.
|
||||
{
|
||||
const run_cmd = b.addRunArtifact(exe.exe);
|
||||
if (b.args) |args| run_cmd.addArgs(args);
|
||||
|
||||
// Set the proper resources dir so things like shell integration
|
||||
// work correctly. If we're running `zig build run` in Ghostty,
|
||||
// this also ensures it overwrites the release one with our debug
|
||||
// build.
|
||||
run_cmd.setEnvironmentVariable(
|
||||
"GHOSTTY_RESOURCES_DIR",
|
||||
b.getInstallPath(.prefix, "share/ghostty"),
|
||||
);
|
||||
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
}
|
||||
|
||||
// Libghostty
|
||||
@ -55,9 +75,24 @@ pub fn build(b: *std.Build) !void {
|
||||
// heavily by Ghostty on macOS but it isn't built to be reusable yet.
|
||||
// As such, these build steps are lacking. For example, the Darwin
|
||||
// build only produces an xcframework.
|
||||
if (config.app_runtime == .none) {
|
||||
if (config.target.result.os.tag.isDarwin()) darwin: {
|
||||
if (!config.emit_xcframework) break :darwin;
|
||||
if (config.app_runtime == .none) none: {
|
||||
if (!config.target.result.os.tag.isDarwin()) {
|
||||
const libghostty_shared = try buildpkg.GhosttyLib.initShared(
|
||||
b,
|
||||
&deps,
|
||||
);
|
||||
const libghostty_static = try buildpkg.GhosttyLib.initStatic(
|
||||
b,
|
||||
&deps,
|
||||
);
|
||||
libghostty_shared.installHeader(); // Only need one header
|
||||
libghostty_shared.install("libghostty.so");
|
||||
libghostty_static.install("libghostty.a");
|
||||
break :none;
|
||||
}
|
||||
|
||||
assert(config.target.result.os.tag.isDarwin());
|
||||
if (!config.emit_xcframework) break :none;
|
||||
|
||||
// Build the xcframework
|
||||
const xcframework = try buildpkg.GhosttyXCFramework.init(b, &deps);
|
||||
@ -76,31 +111,17 @@ pub fn build(b: *std.Build) !void {
|
||||
const placeholder = wf.add(path, "emit-docs not true so no man pages");
|
||||
b.getInstallStep().dependOn(&b.addInstallFile(placeholder, path).step);
|
||||
}
|
||||
} else {
|
||||
const libghostty_shared = try buildpkg.GhosttyLib.initShared(b, &deps);
|
||||
const libghostty_static = try buildpkg.GhosttyLib.initStatic(b, &deps);
|
||||
libghostty_shared.installHeader(); // Only need one header
|
||||
libghostty_shared.install("libghostty.so");
|
||||
libghostty_static.install("libghostty.a");
|
||||
}
|
||||
}
|
||||
|
||||
// Run runs the Ghostty exe
|
||||
{
|
||||
const run_cmd = b.addRunArtifact(exe.exe);
|
||||
if (b.args) |args| run_cmd.addArgs(args);
|
||||
|
||||
// Set the proper resources dir so things like shell integration
|
||||
// work correctly. If we're running `zig build run` in Ghostty,
|
||||
// this also ensures it overwrites the release one with our debug
|
||||
// build.
|
||||
run_cmd.setEnvironmentVariable(
|
||||
"GHOSTTY_RESOURCES_DIR",
|
||||
b.getInstallPath(.prefix, "share/ghostty"),
|
||||
// Build our macOS app
|
||||
const app = try buildpkg.GhosttyXcodebuild.init(
|
||||
b,
|
||||
&config,
|
||||
&xcframework,
|
||||
);
|
||||
|
||||
// Add a run command that opens our mac app.
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
run_step.dependOn(&app.open.step);
|
||||
}
|
||||
|
||||
// Tests
|
||||
|
@ -255,6 +255,22 @@ class AppDelegate: NSObject,
|
||||
|
||||
// Setup signal handlers
|
||||
setupSignals()
|
||||
|
||||
// This is a hack used by our build scripts, specifically `zig build run`,
|
||||
// to force our app to the foreground.
|
||||
if ProcessInfo.processInfo.environment["GHOSTTY_MAC_ACTIVATE"] == "1" {
|
||||
// This never gets called until we click the dock icon. This forces it
|
||||
// activate immediately.
|
||||
applicationDidBecomeActive(.init(name: NSApplication.didBecomeActiveNotification))
|
||||
|
||||
// We run in the background, this forces us to the front.
|
||||
DispatchQueue.main.async {
|
||||
NSApp.setActivationPolicy(.regular)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
NSApp.unhide(nil)
|
||||
NSApp.arrangeInFront(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ notification: Notification) {
|
||||
|
99
src/build/GhosttyXcodebuild.zig
Normal file
99
src/build/GhosttyXcodebuild.zig
Normal file
@ -0,0 +1,99 @@
|
||||
const Ghostty = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const RunStep = std.Build.Step.Run;
|
||||
const Config = @import("Config.zig");
|
||||
const XCFramework = @import("GhosttyXCFramework.zig");
|
||||
|
||||
xcodebuild: *std.Build.Step.Run,
|
||||
open: *std.Build.Step.Run,
|
||||
|
||||
pub fn init(
|
||||
b: *std.Build,
|
||||
config: *const Config,
|
||||
xcframework: *const XCFramework,
|
||||
) !Ghostty {
|
||||
const xc_config = switch (config.optimize) {
|
||||
.Debug => "Debug",
|
||||
.ReleaseSafe,
|
||||
.ReleaseSmall,
|
||||
.ReleaseFast,
|
||||
=> "Release",
|
||||
};
|
||||
|
||||
// Our step to build the Ghostty macOS app.
|
||||
const build = build: {
|
||||
// External environment variables can mess up xcodebuild, so
|
||||
// we create a new empty environment.
|
||||
const env_map = try b.allocator.create(std.process.EnvMap);
|
||||
env_map.* = .init(b.allocator);
|
||||
|
||||
const build = RunStep.create(b, "xcodebuild");
|
||||
build.has_side_effects = true;
|
||||
build.cwd = b.path("macos");
|
||||
build.env_map = env_map;
|
||||
build.addArgs(&.{
|
||||
"xcodebuild",
|
||||
"-target",
|
||||
"Ghostty",
|
||||
"-configuration",
|
||||
xc_config,
|
||||
});
|
||||
|
||||
// We need the xcframework
|
||||
build.step.dependOn(xcframework.xcframework.step);
|
||||
|
||||
// Expect success
|
||||
build.expectExitCode(0);
|
||||
|
||||
// Capture stdout/stderr so we don't pollute our zig build
|
||||
_ = build.captureStdOut();
|
||||
_ = build.captureStdErr();
|
||||
break :build build;
|
||||
};
|
||||
|
||||
// Our step to open the resulting Ghostty app.
|
||||
const open = open: {
|
||||
const open = RunStep.create(b, "run Ghostty app");
|
||||
open.has_side_effects = true;
|
||||
open.cwd = b.path("macos");
|
||||
open.addArgs(&.{
|
||||
b.fmt(
|
||||
"build/{s}/Ghostty.app/Contents/MacOS/ghostty",
|
||||
.{xc_config},
|
||||
),
|
||||
});
|
||||
|
||||
// Open depends on the app
|
||||
open.step.dependOn(&build.step);
|
||||
|
||||
// This overrides our default behavior and forces logs to show
|
||||
// up on stderr (in addition to the centralized macOS log).
|
||||
open.setEnvironmentVariable("GHOSTTY_LOG", "1");
|
||||
|
||||
// This is hack so that we can activate the app and bring it to
|
||||
// the front forcibly even though we're executing directly
|
||||
// via the binary and not launch services.
|
||||
open.setEnvironmentVariable("GHOSTTY_MAC_ACTIVATE", "1");
|
||||
|
||||
if (b.args) |args| {
|
||||
open.addArgs(args);
|
||||
} else {
|
||||
// This tricks the app into thinking it's running from the
|
||||
// app bundle so we don't execute our CLI mode.
|
||||
open.setEnvironmentVariable("GHOSTTY_MAC_APP", "1");
|
||||
}
|
||||
|
||||
break :open open;
|
||||
};
|
||||
|
||||
return .{
|
||||
.xcodebuild = build,
|
||||
.open = open,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn install(self: *const Ghostty) void {
|
||||
const b = self.xcodebuild.step.owner;
|
||||
b.getInstallStep().dependOn(&self.xcodebuild.step);
|
||||
}
|
@ -55,6 +55,9 @@ pub fn create(b: *std.Build, opts: Options) *XCFrameworkStep {
|
||||
}
|
||||
run.addArg("-output");
|
||||
run.addArg(opts.out_path);
|
||||
run.expectExitCode(0);
|
||||
_ = run.captureStdOut();
|
||||
_ = run.captureStdErr();
|
||||
break :run run;
|
||||
};
|
||||
run_create.step.dependOn(&run_delete.step);
|
||||
|
@ -15,6 +15,7 @@ pub const GhosttyFrameData = @import("GhosttyFrameData.zig");
|
||||
pub const GhosttyLib = @import("GhosttyLib.zig");
|
||||
pub const GhosttyResources = @import("GhosttyResources.zig");
|
||||
pub const GhosttyI18n = @import("GhosttyI18n.zig");
|
||||
pub const GhosttyXcodebuild = @import("GhosttyXcodebuild.zig");
|
||||
pub const GhosttyXCFramework = @import("GhosttyXCFramework.zig");
|
||||
pub const GhosttyWebdata = @import("GhosttyWebdata.zig");
|
||||
pub const HelpStrings = @import("HelpStrings.zig");
|
||||
|
Reference in New Issue
Block a user