diff --git a/build.zig b/build.zig index 80af88488..4bd6e0b46 100644 --- a/build.zig +++ b/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,52 +75,53 @@ 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; - - // Build the xcframework - const xcframework = try buildpkg.GhosttyXCFramework.init(b, &deps); - xcframework.install(); - - // The xcframework build always installs resources because our - // macOS xcode project contains references to them. - resources.install(); - i18n.install(); - - // If we aren't emitting docs we need to emit a placeholder so - // our macOS xcodeproject builds. - if (!config.emit_docs) { - var wf = b.addWriteFiles(); - const path = "share/man/.placeholder"; - 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); + 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; } - } - // Run runs the Ghostty exe - { - const run_cmd = b.addRunArtifact(exe.exe); - if (b.args) |args| run_cmd.addArgs(args); + assert(config.target.result.os.tag.isDarwin()); + if (!config.emit_xcframework) break :none; - // 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 the xcframework + const xcframework = try buildpkg.GhosttyXCFramework.init(b, &deps); + xcframework.install(); + + // The xcframework build always installs resources because our + // macOS xcode project contains references to them. + resources.install(); + i18n.install(); + + // If we aren't emitting docs we need to emit a placeholder so + // our macOS xcodeproject builds. + if (!config.emit_docs) { + var wf = b.addWriteFiles(); + const path = "share/man/.placeholder"; + const placeholder = wf.add(path, "emit-docs not true so no man pages"); + b.getInstallStep().dependOn(&b.addInstallFile(placeholder, path).step); + } + + // 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 diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 418005927..efc09ede9 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -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) { diff --git a/src/build/GhosttyXcodebuild.zig b/src/build/GhosttyXcodebuild.zig new file mode 100644 index 000000000..83ab0aed3 --- /dev/null +++ b/src/build/GhosttyXcodebuild.zig @@ -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); +} diff --git a/src/build/XCFrameworkStep.zig b/src/build/XCFrameworkStep.zig index 823e5aac4..8a0d5dc67 100644 --- a/src/build/XCFrameworkStep.zig +++ b/src/build/XCFrameworkStep.zig @@ -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); diff --git a/src/build/main.zig b/src/build/main.zig index 3154d395f..f25ce1c23 100644 --- a/src/build/main.zig +++ b/src/build/main.zig @@ -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");