542 lines
18 KiB
Bash
Executable File
542 lines
18 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
resolve_package_signing_mode() {
|
|
local requested="${CODEXBAR_SIGNING:-adhoc}"
|
|
case "$requested" in
|
|
adhoc|identity) ;;
|
|
*)
|
|
echo "ERROR: Unsupported CODEXBAR_SIGNING: $requested (expected adhoc or identity)" >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
SIGNING_MODE="$requested"
|
|
}
|
|
|
|
CONF=${1:-release}
|
|
ALLOW_LLDB=${CODEXBAR_ALLOW_LLDB:-0}
|
|
SIGNING_MODE=
|
|
resolve_package_signing_mode
|
|
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
|
cd "$ROOT"
|
|
LOWER_CONF=$(printf "%s" "$CONF" | tr '[:upper:]' '[:lower:]')
|
|
case "$LOWER_CONF" in
|
|
debug|release) ;;
|
|
*)
|
|
echo "ERROR: Unsupported build configuration: $CONF (expected debug or release)" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Load version info
|
|
source "$ROOT/version.env"
|
|
source "$ROOT/Scripts/package_product_paths.sh"
|
|
source "$ROOT/Scripts/sparkle_signing_paths.sh"
|
|
|
|
# Clean build only when explicitly requested (slower).
|
|
if [[ "${CODEXBAR_FORCE_CLEAN:-0}" == "1" ]]; then
|
|
if [[ -d "$ROOT/.build" ]]; then
|
|
if command -v trash >/dev/null 2>&1; then
|
|
if ! trash "$ROOT/.build"; then
|
|
echo "WARN: trash .build failed; continuing with swift package clean." >&2
|
|
fi
|
|
else
|
|
rm -rf "$ROOT/.build" || echo "WARN: rm -rf .build failed; continuing with swift package clean." >&2
|
|
fi
|
|
fi
|
|
swift package clean >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
# Build for host architecture by default; allow overriding via ARCHES (e.g., "arm64 x86_64" for universal).
|
|
ARCH_LIST=( ${ARCHES:-} )
|
|
if [[ ${#ARCH_LIST[@]} -eq 0 ]]; then
|
|
HOST_ARCH=$(uname -m)
|
|
case "$HOST_ARCH" in
|
|
arm64) ARCH_LIST=(arm64) ;;
|
|
x86_64) ARCH_LIST=(x86_64) ;;
|
|
*) ARCH_LIST=("$HOST_ARCH") ;;
|
|
esac
|
|
fi
|
|
|
|
patch_keyboard_shortcuts() {
|
|
local util_path="$ROOT/.build/checkouts/KeyboardShortcuts/Sources/KeyboardShortcuts/Utilities.swift"
|
|
if [[ ! -f "$util_path" ]]; then
|
|
return 0
|
|
fi
|
|
if grep -q "keyboardShortcutsSafeBundle" "$util_path"; then
|
|
return 0
|
|
fi
|
|
|
|
chmod +w "$util_path" || true
|
|
python3 - "$util_path" <<'PY'
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
path = Path(sys.argv[1])
|
|
text = path.read_text()
|
|
if ".keyboardShortcutsSafeBundle" in text:
|
|
sys.exit(0)
|
|
|
|
text = text.replace(
|
|
'NSLocalizedString(self, bundle: .module, comment: self)',
|
|
'NSLocalizedString(self, bundle: .keyboardShortcutsSafeBundle, comment: self)',
|
|
)
|
|
|
|
inject = """
|
|
private extension Bundle {
|
|
/// Safe lookup that avoids the fatal trap in the autogenerated `Bundle.module`
|
|
/// when the resource bundle is not placed at the bundle root.
|
|
static let keyboardShortcutsSafeBundle: Bundle = {
|
|
#if os(macOS)
|
|
if let url = Bundle.main.url(forResource: "KeyboardShortcuts_KeyboardShortcuts", withExtension: "bundle"),
|
|
let bundle = Bundle(url: url) {
|
|
return bundle
|
|
}
|
|
|
|
let rootURL = Bundle.main.bundleURL.appendingPathComponent("KeyboardShortcuts_KeyboardShortcuts.bundle")
|
|
if let bundle = Bundle(url: rootURL) {
|
|
return bundle
|
|
}
|
|
#endif
|
|
|
|
let devURL = URL(fileURLWithPath: #file)
|
|
.deletingLastPathComponent() // Utilities.swift
|
|
.deletingLastPathComponent() // KeyboardShortcuts
|
|
.deletingLastPathComponent() // Sources
|
|
.appendingPathComponent("KeyboardShortcuts_KeyboardShortcuts.bundle")
|
|
if let bundle = Bundle(url: devURL) {
|
|
return bundle
|
|
}
|
|
|
|
return Bundle.main
|
|
}()
|
|
}
|
|
"""
|
|
|
|
marker = "}\n\n\nextension Data {"
|
|
if marker not in text:
|
|
raise SystemExit("Marker not found in Utilities.swift; patch failed.")
|
|
|
|
text = text.replace(marker, "}\n\n" + inject + "\n\nextension Data {")
|
|
path.write_text(text)
|
|
PY
|
|
}
|
|
|
|
KEYBOARD_SHORTCUTS_UTIL="$ROOT/.build/checkouts/KeyboardShortcuts/Sources/KeyboardShortcuts/Utilities.swift"
|
|
if [[ ! -f "$KEYBOARD_SHORTCUTS_UTIL" ]]; then
|
|
swift build -c "$CONF" --arch "${ARCH_LIST[0]}"
|
|
fi
|
|
patch_keyboard_shortcuts
|
|
|
|
# Resolve SwiftPM's current output path without relying on a fixed build-system layout.
|
|
# The output variable keeps the per-arch cache in this shell instead of losing it to
|
|
# command substitution.
|
|
swiftpm_bin_path() {
|
|
local arch="$1"
|
|
local output_var="$2"
|
|
local cache_var="SWIFTPM_BIN_PATH_${arch//[^A-Za-z0-9]/_}"
|
|
if [[ -z "${!cache_var+set}" ]]; then
|
|
local resolved
|
|
if ! resolved=$(codexbar_swiftpm_bin_path "$CONF" "$arch"); then
|
|
return 1
|
|
fi
|
|
printf -v "$cache_var" '%s' "$resolved"
|
|
fi
|
|
printf -v "$output_var" '%s' "${!cache_var}"
|
|
}
|
|
|
|
binary_has_arch() {
|
|
local binary="$1"
|
|
local arch="$2"
|
|
[[ -f "$binary" ]] && lipo -archs "$binary" 2>/dev/null | tr ' ' '\n' | grep -qx "$arch"
|
|
}
|
|
|
|
# SwiftBuild can reuse one output directory for sequential per-arch builds. Snapshot
|
|
# each fresh slice before the next build can replace it.
|
|
PRODUCT_STAGE_ROOT="$ROOT/.build/package-products/$LOWER_CONF"
|
|
rm -rf "$PRODUCT_STAGE_ROOT"
|
|
|
|
stage_build_products() {
|
|
local arch="$1"
|
|
local bin_dir stage_dir name product
|
|
swiftpm_bin_path "$arch" bin_dir
|
|
|
|
stage_dir="$PRODUCT_STAGE_ROOT/$arch"
|
|
mkdir -p "$stage_dir"
|
|
for name in CodexBar CodexBarCLI CodexBarClaudeWatchdog; do
|
|
if ! product=$(codexbar_require_product_file "$bin_dir" "$name" "$arch"); then
|
|
return 1
|
|
fi
|
|
if ! binary_has_arch "$product" "$arch"; then
|
|
echo "ERROR: ${product} does not contain required architecture: ${arch}" >&2
|
|
return 1
|
|
fi
|
|
cp "$product" "$stage_dir/$name"
|
|
done
|
|
if [[ -d "$bin_dir/CodexBar.dSYM" ]]; then
|
|
cp -R "$bin_dir/CodexBar.dSYM" "$stage_dir/"
|
|
fi
|
|
}
|
|
|
|
for ARCH in "${ARCH_LIST[@]}"; do
|
|
swift build -c "$CONF" --arch "$ARCH"
|
|
stage_build_products "$ARCH"
|
|
done
|
|
|
|
APP_FINAL="$ROOT/CodexBar.app"
|
|
APP_STAGE="$ROOT/.build/package/CodexBar.app"
|
|
rm -rf "$APP_STAGE"
|
|
APP="$APP_STAGE"
|
|
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources" "$APP/Contents/Frameworks"
|
|
mkdir -p "$APP/Contents/Helpers" "$APP/Contents/PlugIns"
|
|
|
|
# Convert new .icon bundle to .icns if present (macOS 14+/IconStudio export)
|
|
ICON_SOURCE="$ROOT/Icon.icon"
|
|
ICON_TARGET="$ROOT/Icon.icns"
|
|
if [[ -f "$ICON_SOURCE" ]]; then
|
|
iconutil --convert icns --output "$ICON_TARGET" "$ICON_SOURCE"
|
|
fi
|
|
|
|
BUNDLE_ID="com.steipete.codexbar"
|
|
FEED_URL="https://raw.githubusercontent.com/steipete/CodexBar/main/appcast.xml"
|
|
AUTO_CHECKS=true
|
|
if [[ "$LOWER_CONF" == "debug" ]]; then
|
|
BUNDLE_ID="com.steipete.codexbar.debug"
|
|
FEED_URL=""
|
|
AUTO_CHECKS=false
|
|
fi
|
|
if [[ "$SIGNING_MODE" == "adhoc" ]]; then
|
|
FEED_URL=""
|
|
AUTO_CHECKS=false
|
|
fi
|
|
WIDGET_BUNDLE_ID="${BUNDLE_ID}.widget"
|
|
APP_TEAM_ID="${APP_TEAM_ID:-Y5PE65HELJ}"
|
|
APP_GROUP_ID="${APP_TEAM_ID}.com.steipete.codexbar"
|
|
if [[ "$BUNDLE_ID" == *".debug"* ]]; then
|
|
APP_GROUP_ID="${APP_TEAM_ID}.com.steipete.codexbar.debug"
|
|
fi
|
|
ENTITLEMENTS_DIR="$ROOT/.build/entitlements"
|
|
APP_ENTITLEMENTS="${ENTITLEMENTS_DIR}/CodexBar.entitlements"
|
|
WIDGET_ENTITLEMENTS="${ENTITLEMENTS_DIR}/CodexBarWidget.entitlements"
|
|
mkdir -p "$ENTITLEMENTS_DIR"
|
|
if [[ "$ALLOW_LLDB" == "1" && "$LOWER_CONF" != "debug" ]]; then
|
|
echo "ERROR: CODEXBAR_ALLOW_LLDB requires debug configuration" >&2
|
|
exit 1
|
|
fi
|
|
cat > "$APP_ENTITLEMENTS" <<PLIST
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>com.apple.security.application-groups</key>
|
|
<array>
|
|
<string>${APP_GROUP_ID}</string>
|
|
</array>
|
|
$(if [[ "$ALLOW_LLDB" == "1" ]]; then echo " <key>com.apple.security.get-task-allow</key><true/>"; fi)
|
|
</dict>
|
|
</plist>
|
|
PLIST
|
|
cat > "$WIDGET_ENTITLEMENTS" <<PLIST
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>com.apple.security.app-sandbox</key>
|
|
<true/>
|
|
<key>com.apple.security.application-groups</key>
|
|
<array>
|
|
<string>${APP_GROUP_ID}</string>
|
|
</array>
|
|
</dict>
|
|
</plist>
|
|
PLIST
|
|
BUILD_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
|
|
|
cat > "$APP/Contents/Info.plist" <<PLIST
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>CFBundleName</key><string>CodexBar</string>
|
|
<key>CFBundleDisplayName</key><string>CodexBar</string>
|
|
<key>CFBundleIdentifier</key><string>${BUNDLE_ID}</string>
|
|
<key>CFBundleExecutable</key><string>CodexBar</string>
|
|
<key>CFBundlePackageType</key><string>APPL</string>
|
|
<key>CFBundleShortVersionString</key><string>${MARKETING_VERSION}</string>
|
|
<key>CFBundleVersion</key><string>${BUILD_NUMBER}</string>
|
|
<key>LSMinimumSystemVersion</key><string>14.0</string>
|
|
<key>LSUIElement</key><true/>
|
|
<key>CFBundleIconFile</key><string>Icon</string>
|
|
<key>NSHumanReadableCopyright</key><string>© 2026 Peter Steinberger. MIT License.</string>
|
|
<key>SUFeedURL</key><string>${FEED_URL}</string>
|
|
<key>SUPublicEDKey</key><string>AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI=</string>
|
|
<key>SUEnableAutomaticChecks</key><${AUTO_CHECKS}/>
|
|
<key>CodexBuildTimestamp</key><string>${BUILD_TIMESTAMP}</string>
|
|
<key>CodexGitCommit</key><string>${GIT_COMMIT}</string>
|
|
<key>CodexBarTeamID</key><string>${APP_TEAM_ID}</string>
|
|
</dict>
|
|
</plist>
|
|
PLIST
|
|
|
|
# Resolve a built binary from the fresh per-arch snapshot or SwiftPM's reported directory.
|
|
resolve_binary_path() {
|
|
local name="$1"
|
|
local arch="$2"
|
|
local bin_dir candidate
|
|
swiftpm_bin_path "$arch" bin_dir
|
|
if ! candidate=$(codexbar_resolve_staged_or_reported_file \
|
|
"$PRODUCT_STAGE_ROOT" "$bin_dir" "$name" "$arch"); then
|
|
return 1
|
|
fi
|
|
if ! binary_has_arch "$candidate" "$arch"; then
|
|
echo "ERROR: ${candidate} does not contain required architecture: ${arch}" >&2
|
|
return 1
|
|
fi
|
|
echo "$candidate"
|
|
}
|
|
|
|
verify_binary_arches() {
|
|
local binary="$1"; shift
|
|
local expected=("$@")
|
|
local actual
|
|
actual=$(lipo -archs "$binary")
|
|
local actual_count expected_count
|
|
actual_count=$(wc -w <<<"$actual" | tr -d ' ')
|
|
expected_count=${#expected[@]}
|
|
if [[ "$actual_count" -ne "$expected_count" ]]; then
|
|
echo "ERROR: $binary arch mismatch (expected: ${expected[*]}, actual: ${actual})" >&2
|
|
exit 1
|
|
fi
|
|
for arch in "${expected[@]}"; do
|
|
if [[ "$actual" != *"$arch"* ]]; then
|
|
echo "ERROR: $binary missing arch $arch (have: ${actual})" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
install_binary() {
|
|
local name="$1"
|
|
local dest="$2"
|
|
local binaries=()
|
|
for arch in "${ARCH_LIST[@]}"; do
|
|
local src
|
|
if ! src=$(resolve_binary_path "$name" "$arch"); then
|
|
exit 1
|
|
fi
|
|
binaries+=("$src")
|
|
done
|
|
if [[ ${#ARCH_LIST[@]} -gt 1 ]]; then
|
|
lipo -create "${binaries[@]}" -output "$dest"
|
|
else
|
|
cp "${binaries[0]}" "$dest"
|
|
fi
|
|
chmod +x "$dest"
|
|
verify_binary_arches "$dest" "${ARCH_LIST[@]}"
|
|
}
|
|
|
|
strip_release_binary() {
|
|
local binary="$1"
|
|
if [[ "$LOWER_CONF" != "release" ]]; then
|
|
return 0
|
|
fi
|
|
if [[ ! -f "$binary" ]]; then
|
|
return 0
|
|
fi
|
|
xcrun strip -x "$binary"
|
|
}
|
|
|
|
ensure_widget_extension_project() {
|
|
local spec="$ROOT/WidgetExtension/project.yml"
|
|
local project_dir="$ROOT/WidgetExtension/CodexBarWidgetExtension.xcodeproj"
|
|
if [[ -f "$project_dir/project.pbxproj" ]]; then
|
|
return
|
|
fi
|
|
if ! command -v xcodegen >/dev/null 2>&1; then
|
|
echo "ERROR: Missing ${project_dir}; install xcodegen or restore the generated project." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# The tracked project is authoritative. Regenerating it during packaging records the checkout
|
|
# directory's spelling in a package file reference and leaves release worktrees dirty.
|
|
xcodegen generate --spec "$spec" --project "$ROOT/WidgetExtension" --quiet
|
|
}
|
|
|
|
build_widget_extension() {
|
|
local xcode_conf="Release"
|
|
if [[ "$LOWER_CONF" == "debug" ]]; then
|
|
xcode_conf="Debug"
|
|
fi
|
|
|
|
ensure_widget_extension_project
|
|
|
|
local derived_dir="$ROOT/.build/xcode-widget-extension-${LOWER_CONF}"
|
|
local project_dir="$ROOT/WidgetExtension/CodexBarWidgetExtension.xcodeproj"
|
|
local build_log="$derived_dir/xcodebuild.log"
|
|
local timeout_seconds="${CODEXBAR_WIDGET_EXTENSION_TIMEOUT_SECONDS:-900}"
|
|
local archs="${ARCH_LIST[*]}"
|
|
|
|
mkdir -p "$derived_dir"
|
|
echo "Building CodexBarWidget Xcode extension (${xcode_conf}, ${archs})." >&2
|
|
xcodebuild \
|
|
-project "$project_dir" \
|
|
-scheme CodexBarWidgetExtension \
|
|
-configuration "$xcode_conf" \
|
|
-destination "generic/platform=macOS" \
|
|
-derivedDataPath "$derived_dir" \
|
|
-skipPackageUpdates \
|
|
-disableAutomaticPackageResolution \
|
|
-skipMacroValidation \
|
|
-skipPackagePluginValidation \
|
|
CODEXBAR_WIDGET_BUNDLE_ID="$WIDGET_BUNDLE_ID" \
|
|
CODEXBAR_TEAM_ID="$APP_TEAM_ID" \
|
|
MARKETING_VERSION="$MARKETING_VERSION" \
|
|
CURRENT_PROJECT_VERSION="$BUILD_NUMBER" \
|
|
CODE_SIGNING_ALLOWED=NO \
|
|
ARCHS="$archs" \
|
|
ONLY_ACTIVE_ARCH=NO \
|
|
build >"$build_log" 2>&1 &
|
|
|
|
local xcodebuild_pid=$!
|
|
local elapsed=0
|
|
while kill -0 "$xcodebuild_pid" 2>/dev/null; do
|
|
if [[ "$elapsed" -ge "$timeout_seconds" ]]; then
|
|
kill "$xcodebuild_pid" 2>/dev/null || true
|
|
wait "$xcodebuild_pid" 2>/dev/null || true
|
|
tail -80 "$build_log" >&2 || true
|
|
echo "ERROR: Timed out building CodexBarWidget extension after ${timeout_seconds}s" >&2
|
|
exit 1
|
|
fi
|
|
sleep 5
|
|
elapsed=$((elapsed + 5))
|
|
if (( elapsed > 0 && elapsed % 60 == 0 )); then
|
|
echo "Still building CodexBarWidget extension (${elapsed}s)..." >&2
|
|
fi
|
|
done
|
|
if ! wait "$xcodebuild_pid"; then
|
|
tail -120 "$build_log" >&2 || true
|
|
echo "ERROR: Failed to build CodexBarWidget extension" >&2
|
|
exit 1
|
|
fi
|
|
|
|
local appex="$derived_dir/Build/Products/${xcode_conf}/CodexBarWidget.appex"
|
|
if [[ ! -f "$appex/Contents/MacOS/CodexBarWidget" ]]; then
|
|
echo "ERROR: Missing Xcode-built CodexBarWidget.appex at ${appex}" >&2
|
|
exit 1
|
|
fi
|
|
echo "$appex"
|
|
}
|
|
|
|
install_widget_extension() {
|
|
local src_appex
|
|
src_appex="$(build_widget_extension)"
|
|
local widget_app="$APP/Contents/PlugIns/CodexBarWidget.appex"
|
|
rm -rf "$widget_app"
|
|
mkdir -p "$APP/Contents/PlugIns"
|
|
cp -R "$src_appex" "$widget_app"
|
|
verify_binary_arches "$widget_app/Contents/MacOS/CodexBarWidget" "${ARCH_LIST[@]}"
|
|
}
|
|
|
|
install_binary "CodexBar" "$APP/Contents/MacOS/CodexBar"
|
|
strip_release_binary "$APP/Contents/MacOS/CodexBar"
|
|
# Ship CodexBarCLI alongside the app for easy symlinking.
|
|
install_binary "CodexBarCLI" "$APP/Contents/Helpers/CodexBarCLI"
|
|
strip_release_binary "$APP/Contents/Helpers/CodexBarCLI"
|
|
# Watchdog helper: ensures `claude` probes die when CodexBar crashes/gets killed.
|
|
install_binary "CodexBarClaudeWatchdog" "$APP/Contents/Helpers/CodexBarClaudeWatchdog"
|
|
strip_release_binary "$APP/Contents/Helpers/CodexBarClaudeWatchdog"
|
|
install_widget_extension
|
|
strip_release_binary "$APP/Contents/PlugIns/CodexBarWidget.appex/Contents/MacOS/CodexBarWidget"
|
|
|
|
swiftpm_bin_path "${ARCH_LIST[0]}" PREFERRED_BUILD_DIR
|
|
|
|
# Embed Sparkle.framework
|
|
SPARKLE_SOURCE=$(codexbar_require_product_directory "$PREFERRED_BUILD_DIR" Sparkle.framework packaging)
|
|
cp -R "$SPARKLE_SOURCE" "$APP/Contents/Frameworks/"
|
|
chmod -R a+rX "$APP/Contents/Frameworks/Sparkle.framework"
|
|
install_name_tool -add_rpath "@executable_path/../Frameworks" "$APP/Contents/MacOS/CodexBar"
|
|
# Re-sign Sparkle and all nested components with the selected package identity.
|
|
SPARKLE="$APP/Contents/Frameworks/Sparkle.framework"
|
|
if [[ "$SIGNING_MODE" == "adhoc" ]]; then
|
|
CODESIGN_ID="-"
|
|
CODESIGN_ARGS=(--force --sign "$CODESIGN_ID")
|
|
elif [[ "$ALLOW_LLDB" == "1" ]]; then
|
|
CODESIGN_ID="-"
|
|
CODESIGN_ARGS=(--force --sign "$CODESIGN_ID")
|
|
else
|
|
CODESIGN_ID="${APP_IDENTITY:-Developer ID Application: Peter Steinberger (Y5PE65HELJ)}"
|
|
CODESIGN_ARGS=(--force --timestamp --options runtime --sign "$CODESIGN_ID")
|
|
fi
|
|
function resign() { codesign "${CODESIGN_ARGS[@]}" "$1"; }
|
|
# Validate Sparkle's nested layout before signing so framework layout drift fails clearly.
|
|
SPARKLE_SIGNING_TARGETS=$(codexbar_sparkle_signing_targets "$SPARKLE")
|
|
while IFS= read -r SPARKLE_TARGET; do
|
|
resign "$SPARKLE_TARGET"
|
|
done <<<"$SPARKLE_SIGNING_TARGETS"
|
|
|
|
if [[ -f "$ICON_TARGET" ]]; then
|
|
cp "$ICON_TARGET" "$APP/Contents/Resources/Icon.icns"
|
|
fi
|
|
|
|
# Bundle app resources (provider icons, etc.).
|
|
APP_RESOURCES_DIR="$ROOT/Sources/CodexBar/Resources"
|
|
if [[ -d "$APP_RESOURCES_DIR" ]]; then
|
|
cp -R "$APP_RESOURCES_DIR/." "$APP/Contents/Resources/"
|
|
fi
|
|
if [[ ! -f "$APP/Contents/Resources/Icon-classic.icns" ]]; then
|
|
echo "ERROR: Missing Icon-classic.icns in app bundle resources." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# SwiftPM resource bundles (e.g. KeyboardShortcuts) are emitted next to the built binary.
|
|
shopt -s nullglob
|
|
SWIFTPM_BUNDLES=("${PREFERRED_BUILD_DIR}/"*.bundle)
|
|
shopt -u nullglob
|
|
if [[ ${#SWIFTPM_BUNDLES[@]} -gt 0 ]]; then
|
|
for bundle in "${SWIFTPM_BUNDLES[@]}"; do
|
|
bundle_name="$(basename "$bundle")"
|
|
cp -R "$bundle" "$APP/Contents/Resources/"
|
|
done
|
|
fi
|
|
if [[ ! -d "$APP/Contents/Resources/KeyboardShortcuts_KeyboardShortcuts.bundle" ]]; then
|
|
echo "ERROR: Missing KeyboardShortcuts SwiftPM resource bundle (Settings → Keyboard shortcut will crash)." >&2
|
|
echo "Expected: ${PREFERRED_BUILD_DIR}/KeyboardShortcuts_KeyboardShortcuts.bundle" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Ensure contents are writable before stripping attributes and signing.
|
|
chmod -R u+w "$APP"
|
|
|
|
# Strip extended attributes to prevent AppleDouble (._*) files that break code sealing
|
|
xattr -cr "$APP"
|
|
find "$APP" -name '._*' -delete
|
|
|
|
# Sign helper binaries if present
|
|
if [[ -f "${APP}/Contents/Helpers/CodexBarCLI" ]]; then
|
|
codesign "${CODESIGN_ARGS[@]}" "${APP}/Contents/Helpers/CodexBarCLI"
|
|
fi
|
|
if [[ -f "${APP}/Contents/Helpers/CodexBarClaudeWatchdog" ]]; then
|
|
codesign "${CODESIGN_ARGS[@]}" "${APP}/Contents/Helpers/CodexBarClaudeWatchdog"
|
|
fi
|
|
|
|
# Sign widget extension if present
|
|
if [[ -d "${APP}/Contents/PlugIns/CodexBarWidget.appex" ]]; then
|
|
codesign "${CODESIGN_ARGS[@]}" \
|
|
--entitlements "$WIDGET_ENTITLEMENTS" \
|
|
"$APP/Contents/PlugIns/CodexBarWidget.appex/Contents/MacOS/CodexBarWidget"
|
|
codesign "${CODESIGN_ARGS[@]}" \
|
|
--entitlements "$WIDGET_ENTITLEMENTS" \
|
|
"$APP/Contents/PlugIns/CodexBarWidget.appex"
|
|
fi
|
|
|
|
# Finally sign the app bundle itself
|
|
codesign "${CODESIGN_ARGS[@]}" \
|
|
--entitlements "$APP_ENTITLEMENTS" \
|
|
"$APP"
|
|
|
|
rm -rf "$APP_FINAL"
|
|
mv "$APP" "$APP_FINAL"
|
|
APP="$APP_FINAL"
|
|
echo "Created $APP"
|