-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
381 lines (334 loc) · 16.2 KB
/
Copy pathMakefile
File metadata and controls
381 lines (334 loc) · 16.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
.PHONY: build run clean ghostty ghostty-linux ironmark monaco setup setup-linux release test app sign notarize zip zip-from-app dmg dmg-from-app dist lint format
UNAME := $(shell uname -s)
# ── Configuration ────────────────────────────────────────────────────────────
APP_NAME := Boo
BUNDLE_ID := com.boo.app
VERSION := $(shell /usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Boo/App/Info.plist 2>/dev/null || echo "0.1.0")
BUILD_DIR := .build
APP_BUNDLE := $(BUILD_DIR)/$(APP_NAME).app
ZIP_NAME := $(APP_NAME)-$(VERSION).zip
DMG_NAME := $(APP_NAME)-$(VERSION).dmg
# Signing (set via environment or CI secrets)
SIGNING_IDENTITY ?=
APPLE_ID ?=
APPLE_TEAM_ID ?=
APPLE_PASSWORD ?=
# ── GhosttyKit (macOS) ──────────────────────────────────────────────────────
# Xcode 26 beta ships macOS SDK .tbd stubs with only arm64e (no arm64), which
# breaks zig 0.15's linker. The CLT macOS SDK still includes arm64. This
# wrapper intercepts `xcrun --sdk macosx --show-sdk-path` to return the CLT
# SDK while letting all other xcrun calls (iOS SDK, metal, etc.) pass through.
XCRUN_WRAPPER_DIR := $(BUILD_DIR)/xcrun-wrapper
$(XCRUN_WRAPPER_DIR)/xcrun:
@mkdir -p $(XCRUN_WRAPPER_DIR)
@CLT_SDK=$$(DEVELOPER_DIR=/Library/Developer/CommandLineTools xcrun --sdk macosx --show-sdk-path 2>/dev/null); \
XCODE_SDK=$$(xcrun --sdk macosx --show-sdk-path 2>/dev/null); \
XCODE_TBD="$$XCODE_SDK/usr/lib/libSystem.B.tbd"; \
NEED_WRAPPER=false; \
if [ -n "$$CLT_SDK" ] && [ "$$CLT_SDK" != "$$XCODE_SDK" ] && [ -f "$$XCODE_TBD" ]; then \
TOP_TARGETS=$$(head -4 "$$XCODE_TBD" | grep '^targets:'); \
if echo "$$TOP_TARGETS" | grep -q 'arm64e-macos' && \
! echo "$$TOP_TARGETS" | grep -qE '[ ,]arm64-macos[ ,\]]'; then \
NEED_WRAPPER=true; \
fi; \
fi; \
if $$NEED_WRAPPER; then \
echo "==> Xcode macOS SDK missing arm64 stubs; using CLT SDK for zig"; \
printf '#!/bin/bash\n\
sdk_is_macosx=false; has_show_sdk_path=false\n\
for arg in "$$@"; do\n\
case "$$arg" in macosx) sdk_is_macosx=true ;; --show-sdk-path) has_show_sdk_path=true ;; esac\n\
done\n\
if $$sdk_is_macosx && $$has_show_sdk_path; then echo "%s"; exit 0; fi\n\
exec /usr/bin/xcrun "$$@"\n' "$$CLT_SDK" > $(XCRUN_WRAPPER_DIR)/xcrun; \
else \
printf '#!/bin/bash\nexec /usr/bin/xcrun "$$@"\n' > $(XCRUN_WRAPPER_DIR)/xcrun; \
fi
@chmod +x $(XCRUN_WRAPPER_DIR)/xcrun
# The static archive GhosttyKit emits and Package.swift links against. Upstream has
# renamed this before (it used to carry a `-fat` suffix), and the failure mode is a
# bare `ld: library not found` from a clean checkout while a stale local copy keeps
# working — so keep this the single source of truth and assert it after building.
GHOSTTY_LIB_NAME = ghostty-internal
GHOSTTY_LIB = Vendor/ghostty/macos/GhosttyKit.xcframework/macos-arm64/lib$(GHOSTTY_LIB_NAME).a
# Initialize Ghostty submodule and build GhosttyKit xcframework (macOS)
ghostty: $(XCRUN_WRAPPER_DIR)/xcrun
@if [ ! -f Vendor/ghostty/build.zig ]; then \
echo "==> Initializing Ghostty submodule..."; \
git submodule update --init --depth 1 Vendor/ghostty; \
fi
@$(MAKE) ghostty-patches
@PATCH_HASH=$$(cat Vendor/patches/*.patch 2>/dev/null | shasum -a 256 | cut -d' ' -f1); \
STAMP=Vendor/ghostty/macos/.patches-stamp; \
if [ ! -f $(GHOSTTY_LIB) ] \
|| [ "$$(cat $$STAMP 2>/dev/null)" != "$$PATCH_HASH" ]; then \
echo "==> Building GhosttyKit..."; \
cd Vendor/ghostty && PATH="$(CURDIR)/$(XCRUN_WRAPPER_DIR):$$PATH" zig build -Demit-xcframework=true -Dxcframework-target=native -Demit-macos-app=false -Doptimize=ReleaseFast; \
echo "$$PATCH_HASH" > "$(CURDIR)/$$STAMP"; \
else \
echo "==> GhosttyKit already built"; \
fi
@if ! cmp -s Vendor/ghostty/include/ghostty.h CGhostty/include/ghostty.h; then \
echo "==> Syncing CGhostty/include/ghostty.h from Vendor/ghostty"; \
cp Vendor/ghostty/include/ghostty.h CGhostty/include/ghostty.h; \
fi
@if [ ! -f $(GHOSTTY_LIB) ]; then \
echo "ERROR: expected $(GHOSTTY_LIB) after building GhosttyKit."; \
echo " Upstream may have renamed the archive. Emitted instead:"; \
ls Vendor/ghostty/macos/GhosttyKit.xcframework/macos-arm64/*.a 2>/dev/null || echo " (none)"; \
echo " Update GHOSTTY_LIB_NAME in the Makefile and the matching"; \
echo " .linkedLibrary(...) in Package.swift."; \
exit 1; \
fi
# Apply local Ghostty patches (Vendor/patches/*.patch). Idempotent: a patch
# that no longer applies forward but applies in reverse is already in.
ghostty-patches:
@for p in Vendor/patches/*.patch; do \
[ -f "$$p" ] || continue; \
if git -C Vendor/ghostty apply --check "../../$$p" 2>/dev/null; then \
echo "==> Applying $$p"; \
git -C Vendor/ghostty apply "../../$$p"; \
elif git -C Vendor/ghostty apply --check --reverse "../../$$p" 2>/dev/null; then \
echo "==> $$p already applied"; \
else \
echo "ERROR: $$p does not apply to Vendor/ghostty" >&2; \
exit 1; \
fi; \
done
# Build the libghostty-vt WebAssembly module used by the Remote Control web
# UI and copy it into the app resources. Run after updating Vendor/ghostty.
ghostty-wasm: $(XCRUN_WRAPPER_DIR)/xcrun ghostty-patches
@echo "==> Building ghostty-vt.wasm..."
@cd Vendor/ghostty && PATH="$(CURDIR)/$(XCRUN_WRAPPER_DIR):$$PATH" zig build -Demit-lib-vt -Dtarget=wasm32-freestanding -Doptimize=ReleaseSmall
@cp Vendor/ghostty/zig-out/bin/ghostty-vt.wasm Boo/Resources/RemoteControl/ghostty-vt.wasm
@echo "==> Copied to Boo/Resources/RemoteControl/ghostty-vt.wasm"
# ── Ghostty GTK (Linux) ─────────────────────────────────────────────────────
# Build Ghostty GTK executable for Linux
ghostty-linux:
@if [ ! -f Vendor/ghostty/build.zig ]; then \
echo "==> Initializing Ghostty submodule..."; \
git submodule update --init --depth 1 Vendor/ghostty; \
fi
@if [ ! -f Vendor/ghostty/zig-out/bin/ghostty ]; then \
echo "==> Building Ghostty GTK..."; \
cd Vendor/ghostty && zig build -Dapp-runtime=gtk -Doptimize=ReleaseFast; \
else \
echo "==> Ghostty GTK already built"; \
fi
# ── Ironmark (macOS) ────────────────────────────────────────────────────────
# Build ironmark static library for markdown rendering
ironmark:
@if [ ! -f Vendor/ironmark/Cargo.toml ]; then \
echo "==> Initializing ironmark submodule..."; \
git submodule update --init --depth 1 Vendor/ironmark; \
fi
@echo "==> Building ironmark..."
@cd Vendor/ironmark && \
CARGO_TARGET_DIR=target-macos15 \
MACOSX_DEPLOYMENT_TARGET=15.0 RUSTFLAGS="-C link-arg=-mmacosx-version-min=15.0" \
cargo build --release --target aarch64-apple-darwin && \
mkdir -p macos-arm64 && \
cp target-macos15/aarch64-apple-darwin/release/libironmark.a macos-arm64/
# Build bundled Monaco editor assets with Vite
monaco:
cd Boo/Resources/MonacoSource && CI=true pnpm install --frozen-lockfile && pnpm build
monaco-lint:
cd Boo/Resources/MonacoSource && CI=true pnpm install --frozen-lockfile && pnpm lint
monaco-format:
cd Boo/Resources/MonacoSource && CI=true pnpm install --frozen-lockfile && pnpm format
# ── Build ────────────────────────────────────────────────────────────────────
# Full setup (auto-detects platform)
ifeq ($(UNAME),Darwin)
setup: ghostty ironmark build
else
setup: setup-linux
endif
# Linux setup: build Boo with GTK4 sidebar + VTE terminal
setup-linux: build-linux
# Build Boo (macOS — requires GhosttyKit)
build: monaco
swift build
@# Bundle Ghostty resources next to the executable so shell integration works
@for dir in .build/debug .build/arm64-apple-macosx/debug; do \
if [ -d "$$dir" ]; then \
mkdir -p "$$dir/ghostty-resources"; \
rsync -a --delete Vendor/ghostty/zig-out/share/ghostty/ "$$dir/ghostty-resources/ghostty/"; \
rsync -a --delete Vendor/ghostty/zig-out/share/terminfo/ "$$dir/ghostty-resources/terminfo/"; \
fi; \
done
@echo "==> Ghostty resources bundled"
# Build Boo for Linux (GTK4 + VTE terminal with sidebar)
build-linux: monaco
swift build
@echo "==> Linux build complete"
# Build and run as a proper .app bundle so macOS menu/shortcut wiring works
run: build
@mkdir -p .build/debug/Boo.app/Contents/MacOS
@mkdir -p .build/debug/Boo.app/Contents/Resources
@mkdir -p .build/debug/Boo.app/Contents/Frameworks
@cp .build/debug/BooApp .build/debug/Boo.app/Contents/MacOS/Boo
@cp Boo/App/Info.plist .build/debug/Boo.app/Contents/
@/usr/libexec/PlistBuddy -c "Delete SUFeedURL" .build/debug/Boo.app/Contents/Info.plist 2>/dev/null || true
@/usr/libexec/PlistBuddy -c "Delete SUPublicEDKey" .build/debug/Boo.app/Contents/Info.plist 2>/dev/null || true
@for bundle in .build/debug/*.bundle; do \
if [ -d "$$bundle" ]; then \
rsync -a "$$bundle" .build/debug/Boo.app/Contents/Resources/; \
fi; \
done
@for dir in .build/debug .build/arm64-apple-macosx/debug; do \
if [ -d "$$dir" ]; then \
for framework in "$$dir"/*.framework; do \
if [ -d "$$framework" ]; then \
rsync -a "$$framework" .build/debug/Boo.app/Contents/Frameworks/; \
fi; \
done; \
fi; \
done
@rsync -a .build/debug/ghostty-resources/ .build/debug/Boo.app/Contents/Resources/ 2>/dev/null || true
@rsync -a Resources/shell-integration .build/debug/Boo.app/Contents/Resources/
@codesign --force --deep --sign - --entitlements Boo/App/Boo.entitlements .build/debug/Boo.app 2>/dev/null || true
@pkill -x Boo 2>/dev/null || true; for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do pkill -0 -x Boo 2>/dev/null || break; sleep 0.02; done
open .build/debug/Boo.app
# Release build
ifeq ($(UNAME),Darwin)
release: monaco
swift build -c release
@for dir in .build/release .build/arm64-apple-macosx/release; do \
if [ -d "$$dir" ]; then \
mkdir -p "$$dir/ghostty-resources"; \
rsync -a --delete Vendor/ghostty/zig-out/share/ghostty/ "$$dir/ghostty-resources/ghostty/"; \
rsync -a --delete Vendor/ghostty/zig-out/share/terminfo/ "$$dir/ghostty-resources/terminfo/"; \
fi; \
done
@echo "==> Release build complete"
else
release: monaco
swift build -c release
@echo "==> Release build complete"
endif
# Run tests (macOS only — tests depend on AppKit types)
test: monaco
swift test --disable-swift-testing
# ── App Bundle (macOS) ───────────────────────────────────────────────────────
# Find the release binary (SPM may use different output dirs)
RELEASE_BIN = $(shell \
if [ -f .build/release/BooApp ]; then echo .build/release/BooApp; \
elif [ -f .build/arm64-apple-macosx/release/BooApp ]; then echo .build/arm64-apple-macosx/release/BooApp; \
fi)
# Create macOS .app bundle from release build
app: release
@echo "==> Creating $(APP_NAME).app bundle..."
@rm -rf "$(APP_BUNDLE)"
@mkdir -p "$(APP_BUNDLE)/Contents/MacOS"
@mkdir -p "$(APP_BUNDLE)/Contents/Resources"
@mkdir -p "$(APP_BUNDLE)/Contents/Frameworks"
@cp "$(RELEASE_BIN)" "$(APP_BUNDLE)/Contents/MacOS/$(APP_NAME)"
@strip "$(APP_BUNDLE)/Contents/MacOS/$(APP_NAME)"
@cp Boo/App/Info.plist "$(APP_BUNDLE)/Contents/"
@RESOURCE_BUILD_DIR="$$(dirname "$(RELEASE_BIN)")"; \
for bundle in "$$RESOURCE_BUILD_DIR"/*.bundle; do \
if [ -d "$$bundle" ]; then \
rsync -a "$$bundle" "$(APP_BUNDLE)/Contents/Resources/"; \
fi; \
done
@for dir in .build/release .build/arm64-apple-macosx/release; do \
if [ -d "$$dir" ]; then \
for framework in "$$dir"/*.framework; do \
if [ -d "$$framework" ]; then \
rsync -a "$$framework" "$(APP_BUNDLE)/Contents/Frameworks/"; \
fi; \
done; \
fi; \
done
@rsync -a Vendor/ghostty/zig-out/share/ghostty/ "$(APP_BUNDLE)/Contents/Resources/ghostty/"
@rsync -a Vendor/ghostty/zig-out/share/terminfo/ "$(APP_BUNDLE)/Contents/Resources/terminfo/"
@if [ -f assets/AppIcon.icns ]; then \
cp assets/AppIcon.icns "$(APP_BUNDLE)/Contents/Resources/"; \
fi
@echo "==> $(APP_BUNDLE) created"
# ── Linux Packaging ─────────────────────────────────────────────────────────
# Create a tar.gz archive for Linux distribution
tarball: release
@echo "==> Creating Linux tarball..."
@mkdir -p "$(BUILD_DIR)/boo-$(VERSION)-linux"
@cp .build/release/BooApp "$(BUILD_DIR)/boo-$(VERSION)-linux/boo"
@if [ -f CLinuxGTK/boo.css ]; then \
cp CLinuxGTK/boo.css "$(BUILD_DIR)/boo-$(VERSION)-linux/"; \
fi
@cd "$(BUILD_DIR)" && tar czf "boo-$(VERSION)-linux-$$(uname -m).tar.gz" "boo-$(VERSION)-linux/"
@rm -rf "$(BUILD_DIR)/boo-$(VERSION)-linux"
@echo "==> $(BUILD_DIR)/boo-$(VERSION)-linux-$$(uname -m).tar.gz created"
# ── Code Signing (macOS) ────────────────────────────────────────────────────
sign:
@if [ -z "$(SIGNING_IDENTITY)" ]; then \
echo "ERROR: SIGNING_IDENTITY not set. Export it or pass via make sign SIGNING_IDENTITY='...'"; \
exit 1; \
fi
@echo "==> Signing $(APP_BUNDLE)..."
codesign --force --deep --options runtime --sign "$(SIGNING_IDENTITY)" \
--entitlements Boo/App/Boo.entitlements \
"$(APP_BUNDLE)"
@echo "==> Signed"
# ── Notarization (macOS) ────────────────────────────────────────────────────
notarize:
@if [ -z "$(APPLE_ID)" ] || [ -z "$(APPLE_TEAM_ID)" ] || [ -z "$(APPLE_PASSWORD)" ]; then \
echo "ERROR: Set APPLE_ID, APPLE_TEAM_ID, and APPLE_PASSWORD"; \
exit 1; \
fi
@echo "==> Submitting for notarization..."
@ditto -c -k --keepParent "$(APP_BUNDLE)" "$(BUILD_DIR)/$(APP_NAME)-notarize.zip"
xcrun notarytool submit "$(BUILD_DIR)/$(APP_NAME)-notarize.zip" \
--apple-id "$(APPLE_ID)" \
--team-id "$(APPLE_TEAM_ID)" \
--password "$(APPLE_PASSWORD)" \
--wait
xcrun stapler staple "$(APP_BUNDLE)"
@rm -f "$(BUILD_DIR)/$(APP_NAME)-notarize.zip"
@echo "==> Notarized and stapled"
# ── DMG Packaging (macOS) ───────────────────────────────────────────────────
zip: app zip-from-app
zip-from-app:
@echo "==> Creating $(ZIP_NAME)..."
@rm -f "$(BUILD_DIR)/$(ZIP_NAME)"
@ditto -c -k --keepParent "$(APP_BUNDLE)" "$(BUILD_DIR)/$(ZIP_NAME)"
@echo "==> $(BUILD_DIR)/$(ZIP_NAME) created"
dmg: app dmg-from-app
dmg-from-app:
@echo "==> Creating $(DMG_NAME)..."
@rm -f "$(BUILD_DIR)/$(DMG_NAME)"
@mkdir -p "$(BUILD_DIR)/dmg-staging"
@cp -R "$(APP_BUNDLE)" "$(BUILD_DIR)/dmg-staging/"
@ln -sf /Applications "$(BUILD_DIR)/dmg-staging/Applications"
@hdiutil create -volname "$(APP_NAME)" \
-srcfolder "$(BUILD_DIR)/dmg-staging" \
-ov -format UDZO \
"$(BUILD_DIR)/$(DMG_NAME)"
@rm -rf "$(BUILD_DIR)/dmg-staging"
@echo "==> $(BUILD_DIR)/$(DMG_NAME) created"
# Full macOS distribution pipeline
dist: app
@if [ -n "$(SIGNING_IDENTITY)" ]; then \
$(MAKE) sign; \
else \
echo "==> Skipping signing (SIGNING_IDENTITY not set)"; \
fi
@if [ -n "$(APPLE_ID)" ] && [ -n "$(APPLE_TEAM_ID)" ] && [ -n "$(APPLE_PASSWORD)" ]; then \
$(MAKE) notarize; \
else \
echo "==> Skipping notarization (Apple credentials not set)"; \
fi
$(MAKE) zip-from-app
# ── Lint & Format ────────────────────────────────────────────────────────────
lint:
$(MAKE) monaco-lint
swift-format lint --strict --recursive Boo/ BooApp/ Tests/
format:
$(MAKE) monaco-format
swift-format format --in-place --recursive Boo/ BooApp/ Tests/
# ── Cleanup ──────────────────────────────────────────────────────────────────
clean:
swift package clean
rm -rf .build
clean-ghostty:
rm -rf Vendor/ghostty/macos/GhosttyKit.xcframework
rm -rf Vendor/ghostty/.zig-cache Vendor/ghostty/zig-out