|
1 | 1 | #!/usr/bin/env bash |
2 | | -# Build the Swift helper for arm64 + x86_64 and lipo into a universal |
3 | | -# Mach-O at vendor/swrag-helper-darwin-universal. |
| 2 | +# Build the Swift helper for arm64 + x86_64 and assemble a code-signed |
| 3 | +# .app bundle at vendor/swrag-helper.app/. Also emit a deterministic |
| 4 | +# tarball of the bundle at vendor/swrag-helper.app.tar — the compiled |
| 5 | +# swrag CLI embeds the tarball via `with { type: "file" }` and |
| 6 | +# extracts it to a per-user cache dir on first use. |
4 | 7 | # |
5 | | -# Idempotent: re-running with no source changes is a near-noop because |
6 | | -# SPM caches builds per-arch. Fail clearly when the swift toolchain is |
7 | | -# missing — Apple's Command Line Tools provide it; we don't fall back |
8 | | -# to a brittle "install Xcode" path. |
| 8 | +# Why a bundle? |
9 | 9 | # |
10 | | -# Layout: |
| 10 | +# * macOS TCC tracks raw binaries by (absolute path + checksum). |
| 11 | +# Both change on every brew upgrade because the per-user cache |
| 12 | +# dir suffix encodes the file size. Result: Screen Recording / |
| 13 | +# Microphone grants are revoked on every release. |
| 14 | +# |
| 15 | +# * A code-signed .app is tracked by (bundle identifier + sign |
| 16 | +# checksum). Ad-hoc sign with `--sign -` is sufficient for TCC |
| 17 | +# persistence; we don't need (and can't economically afford) a |
| 18 | +# Developer ID certificate. |
| 19 | +# |
| 20 | +# * UNUserNotificationCenter (used by the `notify` subcommand) |
| 21 | +# refuses to register a delegate or post alerts outside of a |
| 22 | +# real bundle. The .app is a hard prerequisite for the native |
| 23 | +# start-recording banner. |
| 24 | +# |
| 25 | +# Idempotent: re-running with no source changes is fast because SPM |
| 26 | +# caches builds per-arch. |
| 27 | +# |
| 28 | +# Layout produced: |
11 | 29 | # swift-helper/.build/{arm64-apple-macosx,x86_64-apple-macosx}/release/SwragHelper |
12 | 30 | # ↓ lipo |
13 | | -# vendor/swrag-helper-darwin-universal |
| 31 | +# vendor/swrag-helper.app/Contents/MacOS/swrag-helper (universal) |
| 32 | +# vendor/swrag-helper.app/Contents/Info.plist (verbatim copy) |
| 33 | +# vendor/swrag-helper.app.tar (tarball of .app) |
14 | 34 | set -euo pipefail |
15 | 35 |
|
16 | 36 | REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" |
17 | 37 | PKG_DIR="$REPO_ROOT/swift-helper" |
18 | 38 | VENDOR_DIR="$REPO_ROOT/vendor" |
19 | | -OUT="$VENDOR_DIR/swrag-helper-darwin-universal" |
| 39 | +APP_DIR="$VENDOR_DIR/swrag-helper.app" |
| 40 | +APP_TAR="$VENDOR_DIR/swrag-helper.app.tar" |
20 | 41 |
|
21 | 42 | if ! command -v swift >/dev/null 2>&1; then |
22 | 43 | echo "[swift-helper] error: 'swift' not found on PATH." >&2 |
@@ -44,11 +65,54 @@ if [[ ! -x "$X64_BIN" ]]; then |
44 | 65 | exit 1 |
45 | 66 | fi |
46 | 67 |
|
47 | | -echo "[swift-helper] lipo into $OUT" |
48 | | -lipo -create -output "$OUT" "$ARM_BIN" "$X64_BIN" |
49 | | -chmod +x "$OUT" |
| 68 | +# Assemble bundle from scratch. We do `rm -rf` rather than overwriting |
| 69 | +# in place so a previous-run with different layout never lingers. |
| 70 | +rm -rf "$APP_DIR" |
| 71 | +mkdir -p "$APP_DIR/Contents/MacOS" |
| 72 | + |
| 73 | +echo "[swift-helper] lipo into $APP_DIR/Contents/MacOS/swrag-helper" |
| 74 | +lipo -create -output "$APP_DIR/Contents/MacOS/swrag-helper" "$ARM_BIN" "$X64_BIN" |
| 75 | +chmod +x "$APP_DIR/Contents/MacOS/swrag-helper" |
| 76 | + |
| 77 | +cp "$PKG_DIR/Resources/Info.plist" "$APP_DIR/Contents/Info.plist" |
| 78 | + |
| 79 | +# Ad-hoc sign. `--force` lets us re-sign over the existing identity |
| 80 | +# every build (the bundle is fresh, but defensive); `--deep` walks |
| 81 | +# nested executables — we only have one, but future-proofs against |
| 82 | +# adding helper sub-binaries. `--sign -` is the magic ad-hoc identity: |
| 83 | +# no Developer ID required, but produces a stable signature checksum |
| 84 | +# tied to the binary contents + Info.plist that TCC uses to identify |
| 85 | +# the bundle across upgrades. |
| 86 | +echo "[swift-helper] codesign --sign - $APP_DIR" |
| 87 | +codesign --force --deep --sign - "$APP_DIR" |
| 88 | +codesign --verify --deep --strict "$APP_DIR" 2>/dev/null || { |
| 89 | + echo "[swift-helper] error: codesign verification failed" >&2 |
| 90 | + exit 1 |
| 91 | +} |
| 92 | + |
| 93 | +# Tarball the bundle for embedding in the swrag CLI binary. We use |
| 94 | +# `-C` so paths in the tarball start at `swrag-helper.app/...` (not |
| 95 | +# `vendor/swrag-helper.app/...`), which keeps extraction simple on |
| 96 | +# the runtime side. Plain tar (not gzip) — the swrag CLI is already |
| 97 | +# inside a gzipped release tarball, so an inner gzip layer would |
| 98 | +# just waste CPU. |
| 99 | +echo "[swift-helper] tar -> $APP_TAR" |
| 100 | +( cd "$VENDOR_DIR" && tar -cf "$APP_TAR" swrag-helper.app ) |
| 101 | + |
| 102 | +# Clean up the legacy raw binary path from v0.8.x so a partial-state |
| 103 | +# checkout (or a tap formula that still points at the old path) fails |
| 104 | +# loudly instead of silently running the previous version. |
| 105 | +LEGACY_RAW="$VENDOR_DIR/swrag-helper-darwin-universal" |
| 106 | +if [[ -e "$LEGACY_RAW" ]]; then |
| 107 | + echo "[swift-helper] removing legacy raw binary at $LEGACY_RAW" |
| 108 | + rm -f "$LEGACY_RAW" |
| 109 | +fi |
50 | 110 |
|
51 | | -size=$(stat -f%z "$OUT") |
52 | | -echo "[swift-helper] wrote $OUT ($size bytes)" |
| 111 | +app_size=$(du -sh "$APP_DIR" | awk '{print $1}') |
| 112 | +tar_size=$(stat -f%z "$APP_TAR") |
| 113 | +echo "[swift-helper] bundle: $APP_DIR ($app_size on disk)" |
| 114 | +echo "[swift-helper] tar: $APP_TAR ($tar_size bytes)" |
53 | 115 | echo "[swift-helper] archs:" |
54 | | -lipo -info "$OUT" |
| 116 | +lipo -info "$APP_DIR/Contents/MacOS/swrag-helper" |
| 117 | +echo "[swift-helper] codesign:" |
| 118 | +codesign -dvv "$APP_DIR" 2>&1 | sed 's/^/ /' |
0 commit comments