summaryrefslogtreecommitdiff
path: root/src/macos/build-app.sh
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-08 10:44:56 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commitc3c8bbd8d8add99088c774c54bc1acf1e39ec895 (patch)
treea602212f59134909532093765a80c87f219c6c4b /src/macos/build-app.sh
parent8aafc3fa24c7475a07259eb06cd5d217f510df98 (diff)
downloadpardes-c3c8bbd8d8add99088c774c54bc1acf1e39ec895.tar.gz
pardes-c3c8bbd8d8add99088c774c54bc1acf1e39ec895.zip
a native macOS backend: libpardes plus an AppKit shell
Adds -Dplatform=macos, a fourth backend beside tty, gui and web. Zig keeps the core, the ptys, every effect and the worker threads; Swift owns NSApplication, the window, input translation, and drawing the cell grid with CoreText. They meet at a hand-written C ABI in src/macos/pardes.h, built as a static library the app links. The ABI is src/web.zig's boundary with the wasm removed, because both hosts are the same animal: someone else owns the clock, feeds events in through flat functions, and reads one packed cell buffer out. The browser proved the shape. The one divergence is that the browser has no processes and forwards every effect to JavaScript, whereas forkpty is right here, so src/macos.zig performs them — spawn, write, resize_pty, save_file, new_file, write_dump, open_link, set_clipboard. lsp, pipe and watch are answered with nothing and marked; the core already tolerates that, since the browser answers none of them either. This deliberately inverts ghostty's split, which was studied first and is written up in docs/ghostty-macos-notes.md. Ghostty hands Zig a bare NSView*, installs its own CALayer and owns the frame clock; Swift never renders. Pardes does the opposite because its frame is already a cell grid and CoreText draws one natively — the alternative is a second hand-rolled glyph atlas, which is what most of gui.zig's 4,300 lines already are. It would also have been written blind: the Swift half cannot be compiled here. What makes the scaffold verifiable rather than dead code is that the Zig half is ordinary POSIX and builds and tests on Linux. Borrowing ghostty's best trick, build.zig translate-C's the header into the test build and src/macos.zig asserts every constant, struct layout, and exported function's arity and widths against it. That guard earned its place immediately: pardes_scroll grew a cell coordinate after the Swift view had been written against the older form. Skipped, and named as the upgrade path in docs/macos.md: the Xcode project, xcframework, lipo and codesigning ghostty needs. All four exist for distribution; a dev build is a swiftc invocation and a directory with a plist. The Swift app is a scaffold and says so — every uncertain API spelling carries an UNVERIFIED marker, and no part of it has been compiled. tty is unaffected: 75/75 snapshot scripts and both unit suites pass.
Diffstat (limited to 'src/macos/build-app.sh')
-rwxr-xr-xsrc/macos/build-app.sh45
1 files changed, 45 insertions, 0 deletions
diff --git a/src/macos/build-app.sh b/src/macos/build-app.sh
new file mode 100755
index 00000000..6734f8d3
--- /dev/null
+++ b/src/macos/build-app.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+# Assemble pardes.app from libpardes.a and the Swift sources. Run it through
+# `zig build macos-app -Dplatform=macos`, or by hand with the install prefix as
+# $1 once `zig build -Dplatform=macos` has produced the library.
+#
+# There is no Xcode project on purpose. An .app is a directory with a plist and
+# a binary in it, swiftc ships with the Command Line Tools, and a hand-written
+# pbxproj would be a second build system to keep in step for no gain at this
+# stage. What Xcode buys — an xcframework of universal slices, codesigning,
+# notarization, a DMG — is distribution machinery; see docs/macos.md for the
+# upgrade path when that day comes.
+set -eu
+
+root=$(cd "$(dirname "$0")/../.." && pwd)
+out=${1:-"$root/zig-out"}
+app="$out/pardes.app"
+lib="$out/lib/libpardes.a"
+
+[ -f "$lib" ] || { echo "missing $lib — run: zig build -Dplatform=macos" >&2; exit 1; }
+command -v swiftc >/dev/null || { echo "swiftc not found (needs macOS + Command Line Tools)" >&2; exit 1; }
+
+rm -rf "$app"
+mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources"
+cp "$root/src/macos/Info.plist" "$app/Contents/Info.plist"
+
+# -import-objc-header rather than a module map: the header is consumed straight
+# from the source tree, so there is nothing to stage and nothing to keep in
+# sync. A module map is what an xcframework needs, and there isn't one.
+#
+# -lc++ because ghostty-vt pulls in simdutf and highway, which are C++. The Zig
+# side bundles compiler_rt/ubsan_rt into the archive (see build.zig), so the
+# C++ runtime is the only thing left for this link to supply.
+# -target is not optional. Without it swiftc uses the host triple, so
+# LC_BUILD_VERSION records whatever macOS built the thing and dyld refuses to
+# launch it on anything older — the Info.plist's LSMinimumSystemVersion is a
+# claim, not the enforcement. It is also what turns on the availability
+# diagnostics that catch a post-13 API before a user does.
+swiftc -O -target "$(uname -m)-apple-macos13.0" \
+ -import-objc-header "$root/src/macos/pardes.h" \
+ -o "$app/Contents/MacOS/pardes" \
+ "$root"/src/macos/Sources/*.swift \
+ "$lib" -lc++ \
+ -framework AppKit -framework CoreText -framework CoreGraphics
+
+echo "built $app"