Files

921 lines
46 KiB
Bash
Executable File

#!/bin/zsh
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2026 Vorssaint
# Builds Vorssaint, assembles the .app bundle, signs it and (with --install)
# installs it into /Applications.
#
# The bundle is staged in a temporary directory outside ~/Documents: folders synced
# by File Provider gain xattrs (com.apple.provenance etc.) that invalidate codesign.
set -euo pipefail
cd "$(dirname "$0")"
# The icon catalog and the bundle are staged in temp dirs; sweep both however
# the script ends.
ICON_TMP=""
STAGE_TMP=""
cleanup() {
[[ -n "$ICON_TMP" ]] && rm -rf "$ICON_TMP"
[[ -n "$STAGE_TMP" ]] && rm -rf "$STAGE_TMP"
return 0
}
trap cleanup EXIT
# zsh runs the EXIT trap when the script is hung up, but not when it is
# interrupted or terminated; route those through exit so a Ctrl-C partway
# into the build sweeps like any other ending.
trap 'exit 1' INT TERM HUP
# Flags: --dev builds the local-only "Vorssaint (Developer)" variant (its own
# bundle id, so it coexists with the official app); --install puts it in /Applications.
DEV=0
INSTALL=0
TEST=0
TEST_ARGS=()
for arg in "$@"; do
case "$arg" in
--dev) DEV=1 ;;
--install) INSTALL=1 ;;
--test) TEST=1 ;;
--test-suite=*) TEST=1; TEST_ARGS+=("--suite=${arg#*=}") ;;
--list-tests) TEST=1; TEST_ARGS+=(--list) ;;
esac
done
if (( DEV )); then
APP_NAME="Vorssaint (Developer)"
EXECUTABLE="VorssaintDeveloper"
APP_BUNDLE_ID="com.vorssaint.utils.dev"
BUILD_VARIANT_FLAGS=(-D VORSSAINT_DEVELOPMENT)
APP_OPTIMIZATION_FLAGS=(-Onone)
BUILD_CONFIGURATION="debug"
else
APP_NAME="Vorssaint"
EXECUTABLE="Vorssaint"
APP_BUNDLE_ID="com.vorssaint.utils"
BUILD_VARIANT_FLAGS=()
APP_OPTIMIZATION_FLAGS=(-O)
BUILD_CONFIGURATION="release"
fi
FAN_HELPER_ID="$APP_BUNDLE_ID.fan-control"
# Now Playing is read through /usr/bin/perl loading this library; see
# Sources/NowPlayingAdapter. Staged under Contents/Frameworks, signed on its own.
NOW_PLAYING_ADAPTER_ID="$APP_BUNDLE_ID.now-playing"
NOW_PLAYING_ADAPTER="libVorssaintNowPlaying.dylib"
TARGET="arm64-apple-macosx14.0"
ENTITLEMENTS="Resources/Vorssaint.entitlements"
LEGACY_IDENTITY="Vorssaint Utils Signing"
developer_id_identity() {
security find-identity -v -p codesigning 2>/dev/null \
| grep 'Developer ID Application' \
| head -1 \
| sed -E 's/.*"(.*)".*/\1/' || true
}
# A find-identity listing also names certificates codesign then rejects (an
# expired one fails the build with errSecInternalComponent), and -v excludes
# every self-signed one; ask codesign itself with a throwaway copy of /bin/echo.
legacy_identity_installed() {
local probe signed=1
# A locked keychain still lists its identities but cannot sign with them,
# and this one is locked after every reboot; unlock it before asking.
security unlock-keychain -p vorssaint-signing \
"$HOME/Library/Keychains/vorssaint-signing.keychain-db" 2>/dev/null || true
probe="$(mktemp)"
cp /bin/echo "$probe"
/usr/bin/codesign --force --strip-disallowed-xattrs --sign "$LEGACY_IDENTITY" "$probe" \
>/dev/null 2>&1 && signed=0
rm -f "$probe"
return $signed
}
# Any build that lands in /Applications needs a stable signature, not just the
# Developer one: macOS ties Accessibility and Screen Recording grants to the
# exact binary hash, so an ad-hoc rebuild orphans them while System Settings
# keeps showing them as granted, and no new prompt ever appears. A plain
# --install strands them under the released bundle id, on the app the user
# actually relies on. When no identity is installed, create the stable local one
# up front instead of falling through to ad-hoc — setup-signing.sh is free,
# offline and idempotent. Gating on the install rather than the variant keeps
# this off CI, where neither ci.yml nor release.yml passes --install.
if (( DEV || INSTALL )) && [[ -z "$(developer_id_identity)" ]] \
&& ! legacy_identity_installed; then
echo "▸ No signing identity installed; creating the stable local one…"
if ! ./Tools/setup-signing.sh; then
echo " ⚠ Tools/setup-signing.sh failed; signing ad-hoc instead." >&2
echo " Accessibility and Screen Recording grants will not survive rebuilds:" >&2
echo " System Settings will show them as granted while the app is not trusted." >&2
echo " After fixing the identity, clear the stale grant once with:" >&2
echo " tccutil reset Accessibility $APP_BUNDLE_ID" >&2
fi
fi
codesign_with_timestamp_retry() {
local attempt
for attempt in 1 2 3; do
if /usr/bin/codesign "$@"; then
return 0
fi
if (( attempt < 3 )); then
echo " Developer ID signing failed; retrying ($((attempt + 1))/3)"
sleep "$attempt"
fi
done
return 1
}
write_swift_output_file_map() {
local output_file="$1"
local object_dir="$2"
shift 2
local source artifact
{
print -r -- "{"
print -r -- " \"\": {"
print -r -- " \"swift-dependencies\": \"$object_dir/master.swiftdeps\""
print -r -- " }"
for source in "$@"; do
artifact="${source//\//__}"
artifact="${artifact%.swift}"
print -r -- ","
print -r -- " \"$source\": {"
print -r -- " \"object\": \"$object_dir/$artifact.o\","
print -r -- " \"swift-dependencies\": \"$object_dir/$artifact.swiftdeps\""
print -r -- " }"
done
print -r -- "}"
} > "$output_file"
}
finalize_installed_bundle_after_child() {
local bundle="$1"
local helper="$bundle/Contents/Library/LaunchServices/$FAN_HELPER_ID"
local adapter="$bundle/Contents/Frameworks/$NOW_PLAYING_ADAPTER"
local devid
devid="$(developer_id_identity)"
echo "▸ Finalizing installed signature…"
sleep 3
if [[ -n "$devid" ]]; then
[[ -f "$helper" ]] && codesign_with_timestamp_retry --force --strip-disallowed-xattrs \
--options runtime --timestamp --identifier "$FAN_HELPER_ID" --sign "$devid" "$helper"
[[ -f "$adapter" ]] && codesign_with_timestamp_retry --force --strip-disallowed-xattrs \
--options runtime --timestamp --identifier "$NOW_PLAYING_ADAPTER_ID" --sign "$devid" "$adapter"
codesign_with_timestamp_retry --force --strip-disallowed-xattrs --options runtime --timestamp \
--entitlements "$ENTITLEMENTS" --sign "$devid" "$bundle"
elif legacy_identity_installed; then
[[ -f "$helper" ]] && /usr/bin/codesign --force --strip-disallowed-xattrs \
--identifier "$FAN_HELPER_ID" --sign "$LEGACY_IDENTITY" "$helper"
[[ -f "$adapter" ]] && /usr/bin/codesign --force --strip-disallowed-xattrs \
--identifier "$NOW_PLAYING_ADAPTER_ID" --sign "$LEGACY_IDENTITY" "$adapter"
/usr/bin/codesign --force --strip-disallowed-xattrs --sign "$LEGACY_IDENTITY" "$bundle"
else
[[ -f "$helper" ]] && /usr/bin/codesign --force --strip-disallowed-xattrs \
--identifier "$FAN_HELPER_ID" --sign - "$helper"
[[ -f "$adapter" ]] && /usr/bin/codesign --force --strip-disallowed-xattrs \
--identifier "$NOW_PLAYING_ADAPTER_ID" --sign - "$adapter"
/usr/bin/codesign --force --strip-disallowed-xattrs --sign - "$bundle"
fi
[[ -f "$helper" ]] && /usr/bin/codesign --verify --strict "$helper"
[[ -f "$adapter" ]] && /usr/bin/codesign --verify --strict "$adapter"
/usr/bin/codesign --verify --deep --strict "$bundle"
echo "✓ Signature ready: $bundle"
}
if (( INSTALL && ! TEST )) && [[ "${VORSSAINT_INSTALL_CHILD:-0}" != "1" ]]; then
VORSSAINT_INSTALL_CHILD=1 "$0" "$@"
child_status=$?
if (( child_status != 0 )); then
exit "$child_status"
fi
finalize_installed_bundle_after_child "/Applications/$APP_NAME.app"
exit 0
fi
# Prefer the macOS 26 SDK when present: the 27 SDK turns SwiftUI property wrappers
# into macros (SwiftUIMacros plugin) that the Command Line Tools cannot load yet.
PINNED_SDK="/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk"
if [[ -n "${DEVELOPER_DIR:-}" ]]; then
SDK="$(xcrun --show-sdk-path)"
elif [[ -d "$PINNED_SDK" ]]; then
SDK="$PINNED_SDK"
else
SDK="$(xcrun --show-sdk-path)"
fi
SDK_COMPAT_FLAGS=()
VM_STATISTICS_COMPAT_FLAGS=(-I Sources/VMStatisticsCompat)
HID_EVENT_SYSTEM_FLAGS=(-I Sources/HIDEventSystem)
if [[ "$SDK" == "$PINNED_SDK" ]]; then
# Swift 6.4 can read the SDK 26 interfaces when given their compiler version.
SDK_COMPAT_FLAGS=(-Xfrontend -interface-compiler-version -Xfrontend 6.3.2)
fi
# The defaults migrations under test need a real UserDefaults suite, and every
# suite leaves an empty plist in ~/Library/Preferences. The tests already clear
# the domains, but cfprefsd writes the emptied file back out around the time the
# process that owned it exits, so only a caller that outlives the run can remove
# them. `PreferenceNamespaceTests` scans every compiled Swift test file against
# these namespaces, which keeps this sweep complete without a second list.
discard_test_preferences() {
local preferences="${1:-$HOME/Library/Preferences}" name attempt
local survivors=0 quiet_passes=0
# cfprefsd can recreate an emptied domain after the first removal. Require
# two quiet checks, but keep a hard limit so persistent failures still fail CI.
for attempt in {1..10}; do
for name in "vorss.tests." "com.vorssaint.tests."; do
rm -f "$preferences"/$name*.plist(N)
done
rm -f "$preferences/metrics-tests.plist"
sleep 0.2
survivors=$(find "$preferences" -maxdepth 1 \
\( -name "vorss.tests.*.plist" -o -name "com.vorssaint.tests.*.plist" \
-o -name "metrics-tests.plist" \) 2>/dev/null | wc -l | tr -d ' ')
if [[ "$survivors" == "0" ]]; then
quiet_passes=$((quiet_passes + 1))
if (( quiet_passes == 2 )); then return 0; fi
else
quiet_passes=0
fi
done
echo "✗ test preferences did not settle in $preferences ($survivors remaining)" >&2
return 1
}
# Standalone automated tests: pure contracts plus isolated disk, subprocess,
# keyboard-data and media fixtures. No application windows or device capture.
if (( TEST )); then
python3 Tests/generate_sources.py
TEST_OBJECT_DIR="build/objects/tests"
mkdir -p "$TEST_OBJECT_DIR"
TEST_SOURCES=(
Sources/Vorssaint/Services/Media/MediaSupport.swift
Sources/Vorssaint/Core/QuitProtectionSupport.swift
Sources/Vorssaint/Core/QuitProtectionStrings.swift
Sources/Vorssaint/Core/Defaults.swift
Sources/Vorssaint/Core/NotchStrings.swift
Sources/Vorssaint/Core/NotchTourStrings.swift
Sources/Vorssaint/Core/NotchEditorStrings.swift
Sources/Vorssaint/UI/Settings/NotchSettingsTabRow.swift
Sources/Vorssaint/Core/NotchActivityStrings.swift
Sources/Vorssaint/Services/Notch/NotchTimerSupport.swift
Sources/Vorssaint/Services/Notch/NotchAppFullscreen.swift
Sources/Vorssaint/Services/Notch/NotchTimerAlert.swift
Sources/Vorssaint/UI/Notch/NotchIslandAlert.swift
Sources/Vorssaint/Services/Notch/NotchAccessorySupport.swift
Sources/Vorssaint/Services/QuickTools/CameraPreviewSupport.swift
Sources/Vorssaint/Core/NotchMusicExtrasStrings.swift
Sources/Vorssaint/Core/NotchClipboardSizeStrings.swift
Sources/Vorssaint/Services/Notch/NotchLyricsSupport.swift
Sources/Vorssaint/Services/Notch/NotchQueueSupport.swift
Sources/Vorssaint/Services/Notch/NotchPreferredPlayer.swift
Sources/Vorssaint/Core/NotchFilesStrings.swift
Sources/Vorssaint/Core/NotchWatchStrings.swift
Sources/Vorssaint/Services/Notch/NotchWatchSupport.swift
Sources/Vorssaint/Core/NotchMascotStrings.swift
Sources/Vorssaint/Services/Notch/NotchMascotSupport.swift
Sources/Vorssaint/Services/CommandBar/CommandBarDropletMotion.swift
Sources/Vorssaint/Services/Notch/NotchFileToolsSupport.swift
Sources/Vorssaint/Services/Notch/NotchDownloadSupport.swift
Sources/Vorssaint/Services/Notch/NotchDownloadProgressObserver.swift
Sources/Vorssaint/Core/NotchCalendarStrings.swift
Sources/Vorssaint/Core/NotchNotificationStrings.swift
Sources/Vorssaint/Core/NotchGestureStrings.swift
Sources/Vorssaint/Core/NotchAgentStrings.swift
Sources/Vorssaint/Core/CodexAccountStrings.swift
Sources/Vorssaint/Services/Notch/NotchAgentSupport.swift
Sources/Vorssaint/Core/NotchLockScreenStrings.swift
Sources/Vorssaint/Services/Notch/NotchLockScreenSupport.swift
Sources/Vorssaint/Services/AgentUsage/AgentUsageModels.swift
Sources/Vorssaint/Services/AgentUsage/AgentPricing.swift
Sources/Vorssaint/Services/AgentUsage/AgentLogObject.swift
Sources/Vorssaint/Services/AgentUsage/AgentLogParser.swift
Sources/Vorssaint/Services/AgentUsage/Harness/AgentHarnessReader.swift
Sources/Vorssaint/Services/AgentUsage/Harness/ClaudeReader.swift
Sources/Vorssaint/Services/AgentUsage/Harness/CodexReader.swift
Sources/Vorssaint/Services/AgentUsage/Harness/OpenCodeReader.swift
Sources/Vorssaint/Services/AgentUsage/Harness/CopilotReader.swift
Sources/Vorssaint/Services/AgentUsage/AgentUsageSummary.swift
Sources/Vorssaint/Services/AgentUsage/AgentUsageStore.swift
Sources/Vorssaint/Services/AgentUsage/AgentUsageArchive.swift
Sources/Vorssaint/Services/AgentUsage/AgentClaudeAppUsage.swift
Sources/Vorssaint/Services/AgentUsage/AgentCLIConversation.swift
Sources/Vorssaint/Services/AgentUsage/AgentClaudeServer.swift
Sources/Vorssaint/Services/AgentUsage/AgentCodexServer.swift
Sources/Vorssaint/Services/AgentUsage/CodexAccountLimits.swift
Sources/Vorssaint/Services/AgentUsage/CodexProfileLimitsService.swift
Sources/Vorssaint/Services/AgentUsage/AgentOpenCodeReader.swift
Sources/Vorssaint/Services/Notch/NotchGestureSupport.swift
Sources/Vorssaint/Services/Notch/NotchSectionPaging.swift
Sources/Vorssaint/Services/Notch/NotchSliderEditing.swift
Sources/Vorssaint/Services/Notch/NotchNotificationSupport.swift
Sources/Vorssaint/Services/Notch/NotchNotificationReaderCore.swift
Sources/Vorssaint/Services/Notch/NotchCalendarSupport.swift
Sources/Vorssaint/Services/Notch/NotchKeepAwakeSupport.swift
Sources/Vorssaint/Services/Notch/NotchSupport.swift
Sources/Vorssaint/Services/Notch/NotchAudioLevelSupport.swift
Sources/Vorssaint/Services/Notch/NotchVolumeKeyGate.swift
Sources/Vorssaint/Services/Notch/NotchMusicSupport.swift
Sources/Vorssaint/UI/Notch/NotchDecorativeClock.swift
Sources/Vorssaint/Services/Notch/NotchBrowserArtworkSupport.swift
Sources/Vorssaint/UI/Notch/NotchEqualizerBars.swift
Sources/Vorssaint/UI/Notch/NotchScrollEdgeFade.swift
Sources/Vorssaint/UI/Notch/NotchAgentAnimationView.swift
Sources/Vorssaint/UI/WindowVisibilityReader.swift
Sources/Vorssaint/Services/Notch/NotchMusicAutomationSupport.swift
Sources/Vorssaint/Services/Notch/NotchMusicAutomation.swift
Sources/Vorssaint/Services/Notch/NotchPlaybackSource.swift
Sources/Vorssaint/Services/Notch/NotchPlaybackCommand.swift
Sources/Vorssaint/Services/Notch/NotchMusicCommandWriter.swift
Sources/Vorssaint/Core/FeatureCatalog.swift
Sources/Vorssaint/Core/FeaturePresets.swift
Sources/Vorssaint/Core/FeatureHubStrings.swift
Sources/Vorssaint/Core/ShortcutSettingsStrings.swift
Sources/Vorssaint/Core/SettingsBackupSupport.swift
Sources/Vorssaint/Core/BackupStrings.swift
Sources/Vorssaint/Core/SnippetStrings.swift
Sources/Vorssaint/Core/AlertSoundStrings.swift
Sources/Vorssaint/Core/BrightnessStrings.swift
Sources/Vorssaint/Core/MediaImageStrings.swift
Sources/Vorssaint/Core/QuickToggleStrings.swift
Sources/Vorssaint/Core/ScreenshotStrings.swift
Sources/Vorssaint/Core/RecentCaptureStrings.swift
Sources/Vorssaint/Core/RecorderStrings.swift
Sources/Vorssaint/Core/RecorderShareStrings.swift
Sources/Vorssaint/Core/CameraPreviewStrings.swift
Sources/Vorssaint/Core/WallpaperStrings.swift
Sources/Vorssaint/Services/Wallpaper/WallpaperSupport.swift
Sources/Vorssaint/Core/ScratchpadStrings.swift
Sources/Vorssaint/Core/FinderRenameStrings.swift
Sources/Vorssaint/Core/CommandBarStrings.swift
Sources/Vorssaint/Core/FeedbackStrings.swift
Sources/Vorssaint/Core/RadialMenuStrings.swift
Sources/Vorssaint/Core/MenuBarAppearanceStrings.swift
Sources/Vorssaint/Core/AppAppearance.swift
Sources/Vorssaint/Core/AppearanceStrings.swift
Sources/Vorssaint/Core/GeneralSettingsStrings.swift
Sources/Vorssaint/Core/SettingsPageStrings.swift
Sources/Vorssaint/Core/BatteryTimeStrings.swift
Sources/Vorssaint/Core/CPUCoreStrings.swift
Sources/Vorssaint/Core/MonitorLayoutStrings.swift
Sources/Vorssaint/Core/KeepAwakeStrings.swift
Sources/Vorssaint/Core/BluetoothSleepStrings.swift
Sources/Vorssaint/Core/PermissionGuideStrings.swift
Sources/Vorssaint/Core/FanControlStrings.swift
Sources/Vorssaint/Core/ConnectedDevicesStrings.swift
Sources/Vorssaint/Services/FanControl/FanControlSupport.swift
Sources/Vorssaint/Services/FanControl/FanControlResumeSupport.swift
Sources/Vorssaint/Services/Snippets/TextSnippetSupport.swift
Sources/Vorssaint/Services/RadialMenu/RadialMenuSupport.swift
Sources/Vorssaint/Services/QuickTools/ScratchpadSupport.swift
Sources/Vorssaint/Services/QuickTools/ScratchpadStore.swift
Sources/Vorssaint/Services/KillProcess/KillProcessSupport.swift
Sources/Vorssaint/Services/Recorder/RecorderSupport.swift
Sources/Vorssaint/Services/Recorder/RecorderSampleTiming.swift
Sources/Vorssaint/Services/Recorder/RecorderWriter.swift
Sources/Vorssaint/Services/Recorder/RecorderCaptureEngine.swift
Sources/Vorssaint/Core/RecorderExportStrings.swift
Sources/Vorssaint/Services/Recorder/RecorderComposer.swift
Sources/Vorssaint/Services/Recorder/RecorderComposerPlan.swift
Sources/Vorssaint/Services/Recorder/RecorderCursorSprite.swift
Sources/Vorssaint/Services/Recorder/RecorderTextRenderer.swift
Sources/Vorssaint/Services/Recorder/RecorderImageRenderer.swift
Sources/Vorssaint/Services/Recorder/RecorderExporter.swift
Sources/Vorssaint/Services/Recorder/RecorderGIFClipboard.swift
Sources/Vorssaint/Services/Recorder/RecorderGIFWriter.swift
Sources/Vorssaint/Services/Recorder/RecorderGIFWorkspace.swift
Sources/Vorssaint/Services/Recorder/RecorderComposition.swift
Sources/Vorssaint/Services/Recorder/RecordingSharingSupport.swift
Sources/Vorssaint/Services/PrivateFileStore.swift
Sources/Vorssaint/Services/Recorder/RecorderTakeStore.swift
Sources/Vorssaint/Services/Recorder/RecorderPresetImageStore.swift
Sources/Vorssaint/Services/Recorder/RecorderMotion.swift
Sources/Vorssaint/Services/Recorder/RecorderPointerTrack.swift
Sources/Vorssaint/Services/Recorder/RecorderTypingTrack.swift
Sources/Vorssaint/Services/Recorder/RecorderTimeline.swift
Sources/Vorssaint/Services/Recorder/RecorderTextOverlay.swift
Sources/Vorssaint/Services/Recorder/RecorderImageOverlay.swift
Sources/Vorssaint/Services/Recorder/RecorderBlurRegion.swift
Sources/Vorssaint/Services/Recorder/RecorderEditDocument.swift
Sources/Vorssaint/Core/AppInfo.swift
Sources/Vorssaint/Core/GlobalShortcut.swift
Sources/Vorssaint/Core/SymbolicHotKeys.swift
Sources/Vorssaint/Services/SystemShortcutTakeoverSupport.swift
Sources/Vorssaint/Core/Localization.swift
Sources/Vorssaint/Core/Localizations/Strings+*.swift
Sources/Vorssaint/Core/FeatureStrings.swift
Sources/Vorssaint/Core/KillProcessStrings.swift
Sources/Vorssaint/Core/PortManagerStrings.swift
Sources/Vorssaint/Core/WhatsAppDownloadStrings.swift
Sources/Vorssaint/Core/WhatsAppOrganizerStrings.swift
Sources/Vorssaint/Core/ReleaseNotes.swift
Sources/Vorssaint/Core/ReleaseDigest.swift
Sources/Vorssaint/Core/ReleaseChangeAliases.swift
Sources/Vorssaint/Core/WhatsNewStrings.swift
Sources/Vorssaint/Core/URLCleaning.swift
Sources/Vorssaint/Services/GeneralPasteboardAccess.swift
Sources/Vorssaint/Services/Clipboard/ClipboardHistoryWrite.swift
Sources/Vorssaint/Services/Audio/AirPlayRouteManager.swift
Sources/Vorssaint/Services/Audio/MixerRoutingSupport.swift
Sources/Vorssaint/Services/Audio/MusicLaunchSupport.swift
Sources/Vorssaint/Services/Bluetooth/BluetoothSleepSupport.swift
Sources/Vorssaint/UI/MenuPanel/MixerPercentNativeTextField.swift
Sources/Vorssaint/UI/MenuPanel/MixerAppDragSource.swift
Sources/Vorssaint/Services/Audio/BoostLimiter.swift
Sources/Vorssaint/Services/Audio/MixerRender.swift
Sources/Vorssaint/Services/Audio/PreciseVolumeRollerSupport.swift
Sources/Vorssaint/Services/DockPreview/DockPreviewSupport.swift
Sources/Vorssaint/Services/DockPreview/DockAutohideHold.swift
Sources/Vorssaint/Services/SpacesOrder/SpacesOrderHold.swift
Sources/Vorssaint/Services/Homebrew/HomebrewSupport.swift
Sources/Vorssaint/Services/Homebrew/HomebrewEnvironment.swift
Sources/Vorssaint/Services/AppUpdates/AppUpdatesSupport.swift
Sources/Vorssaint/Services/AppUpdates/AppUpdateFeedSupport.swift
Sources/Vorssaint/Core/AppUpdateStrings.swift
Sources/Vorssaint/Core/DiskImageInstallerStrings.swift
Sources/Vorssaint/Services/DiskImageInstaller/DiskImageInstallerSupport.swift
Sources/Vorssaint/UI/NonModalAlert.swift
Sources/Vorssaint/Services/Clipboard/ClipboardHistorySupport.swift
Sources/Vorssaint/Core/ColorValue.swift
Sources/Vorssaint/Services/Clipboard/ClipboardAutoClearSupport.swift
Sources/Vorssaint/Services/AutoQuit/AutoQuitSupport.swift
Sources/Vorssaint/Services/Shelf/ShelfSupport.swift
Sources/Vorssaint/Services/Shelf/ShelfFilePromiseTransfer.swift
Sources/Vorssaint/Core/ShelfPromiseDeliveryStrings.swift
Sources/Vorssaint/Services/Finder/FinderRenameSupport.swift
Sources/Vorssaint/Services/Update/UpdateInstallerSupport.swift
Sources/Vorssaint/Services/Update/UpdateServiceSupport.swift
Sources/Vorssaint/Services/InstalledApps.swift
Sources/Vorssaint/Services/LaunchAtLoginSupport.swift
Sources/Vorssaint/UI/Settings/SettingsSearchSupport.swift
Sources/Vorssaint/UI/Settings/SettingsSidebarSupport.swift
Sources/Vorssaint/UI/Settings/FeatureVisibilitySupport.swift
Sources/Vorssaint/UI/Onboarding/WhatsNewSupport.swift
Sources/Vorssaint/UI/Settings/SettingsWindow.swift
Sources/Vorssaint/Core/SettingsNavigationStrings.swift
Sources/Vorssaint/App/MenuBarSpacingSupport.swift
Sources/Vorssaint/App/MenuBarAllowanceSupport.swift
Sources/Vorssaint/App/ReopenRequestSupport.swift
Sources/Vorssaint/App/StatusItemAnchorSupport.swift
Sources/Vorssaint/Services/DockClick/DockClickSupport.swift
Sources/Vorssaint/Services/Finder/CutPasteProgressSupport.swift
Sources/Vorssaint/Services/Finder/CutPastePrivilegeSupport.swift
Sources/Vorssaint/Services/Finder/FinderPasteImageSupport.swift
Sources/Vorssaint/Services/MiddleClick/MiddleClickSupport.swift
Sources/Vorssaint/Services/MouseNavigation/MouseNavigationSupport.swift
Sources/Vorssaint/Services/MouseNavigation/MouseNavigationKeys.swift
Sources/Vorssaint/Services/MouseButtons/MouseButtonShortcutSupport.swift
Sources/Vorssaint/Services/MouseButtons/MouseSpacesGestureSupport.swift
Sources/Vorssaint/Services/MouseClickDebounce/MouseClickDebounceSupport.swift
Sources/Vorssaint/Services/MouseExceptions/MouseAppExceptionSupport.swift
Sources/Vorssaint/Services/MouseExceptions/MouseAppExceptions.swift
Sources/Vorssaint/Services/WindowServerSupport.swift
Sources/Vorssaint/Services/WindowMaximizerSupport.swift
Sources/Vorssaint/Core/MouseButtonStrings.swift
Sources/Vorssaint/Core/MouseClickDebounceStrings.swift
Sources/Vorssaint/Core/MouseExceptionStrings.swift
Sources/Vorssaint/Core/ClipboardIgnoredAppsStrings.swift
Sources/Vorssaint/Core/ClipboardRetentionStrings.swift
Sources/Vorssaint/Core/WindowLayoutIgnoredAppsStrings.swift
Sources/Vorssaint/Services/WindowLayout/WindowLayoutIgnoredApps.swift
Sources/Vorssaint/Core/WindowPreviewExclusionStrings.swift
Sources/Vorssaint/Core/WindowMaximizerExclusionStrings.swift
Sources/Vorssaint/Core/DiskExclusionStrings.swift
Sources/Vorssaint/Core/SwitcherAppRulesStrings.swift
Sources/Vorssaint/Services/QuickTools/QuickToolsSupport.swift
Sources/Vorssaint/Services/CommandBar/CommandBarSupport.swift
Sources/Vorssaint/Services/CommandBar/CommandBarPreferences.swift
Sources/Vorssaint/Services/CommandBar/CommandBarMath.swift
Sources/Vorssaint/Services/CommandBar/CommandBarUnits.swift
Sources/Vorssaint/Services/CommandBar/CommandBarColors.swift
Sources/Vorssaint/Services/CommandBar/CommandBarEmoji.swift
Sources/Vorssaint/Services/CommandBar/CommandBarLinks.swift
Sources/Vorssaint/Services/CommandBar/CommandBarDates.swift
Sources/Vorssaint/Services/CommandBar/CommandBarRowShortcuts.swift
Sources/Vorssaint/Services/CommandBar/CommandBarSystemSettingsSupport.swift
Sources/Vorssaint/Services/CommandBar/CommandBarFileSearchSupport.swift
Sources/Vorssaint/Services/CommandBar/CommandBarQueryMemory.swift
Sources/Vorssaint/Services/SpotlightNamesSupport.swift
Sources/Vorssaint/Services/QuickTools/MicMuteSupport.swift
Sources/Vorssaint/Services/QuickTools/QuickTogglesSupport.swift
Sources/Vorssaint/Services/QuickTools/ScreenshotCapturePolicy.swift
Sources/Vorssaint/Services/QuickTools/ScreenshotSupport.swift
Sources/Vorssaint/Services/QuickTools/ScreenshotCanvasSupport.swift
Sources/Vorssaint/UI/Settings/ScreenCaptureToolPicker.swift
Sources/Vorssaint/UI/Settings/MonitorToken.swift
Sources/Vorssaint/Services/QuickTools/ScreenshotRenderer.swift
Sources/Vorssaint/Services/QuickTools/ScreenshotImageLayerRendering.swift
Sources/Vorssaint/Services/QuickTools/RecentCaptureStore.swift
Sources/Vorssaint/Services/QuickTools/ScreenshotSharingSupport.swift
Sources/Vorssaint/Services/QuickTools/WindowActivationPolicy.swift
Sources/Vorssaint/Services/KeyboardDebounce/KeyboardDebounceSupport.swift
Sources/Vorssaint/Services/SuperKey/SuperKeySupport.swift
Sources/Vorssaint/Services/SuperKey/SuperKeyMappingGuard.swift
Sources/Vorssaint/Core/SuperKeyStrings.swift
Sources/Vorssaint/Core/InputSourceSelection.swift
Sources/Vorssaint/Services/SessionActivity.swift
Sources/Vorssaint/Services/SessionActivitySupport.swift
Sources/Vorssaint/Services/EventTimestamp.swift
Sources/Vorssaint/Services/OwnKeyEvent.swift
Sources/Vorssaint/Services/ScrollWheelSupport.swift
Sources/Vorssaint/Services/HorizontalWheelScrolling.swift
Sources/Vorssaint/Services/SmoothScrollSupport.swift
Sources/Vorssaint/Services/MouseAcceleration/MouseAccelerationSupport.swift
Sources/Vorssaint/Services/FocusFollowsMouse/FocusFollowsMouseSupport.swift
Sources/Vorssaint/Services/AssistiveKeyboard.swift
Sources/Vorssaint/Services/Switcher/SwitcherModels.swift
Sources/Vorssaint/Services/Switcher/WindowServerCaptureQueue.swift
Sources/Vorssaint/Services/Switcher/SwitcherSupport.swift
Sources/Vorssaint/Services/Switcher/SpaceHopSupport.swift
Sources/Vorssaint/Services/Switcher/WindowUseOrder.swift
Sources/Vorssaint/Services/Metrics/MetricFormat.swift
Sources/Vorssaint/Services/Metrics/VMStatisticsDecoder.swift
Sources/Vorssaint/Services/KeepAwakeAutomationSupport.swift
Sources/Vorssaint/Services/SudoersSupport.swift
Sources/Vorssaint/Services/Metrics/BatteryTimeSupport.swift
Sources/Vorssaint/Services/Metrics/BatteryPowerSupport.swift
Sources/Vorssaint/Services/BoundedProcessRunner.swift
Sources/Vorssaint/Services/DetachedProcess.swift
Sources/Vorssaint/Services/ShellSupport.swift
Sources/Vorssaint/Services/PortManager/PortManagerSupport.swift
Sources/Vorssaint/Services/Metrics/NetworkProcessSupport.swift
Sources/Vorssaint/Services/Metrics/NetworkSampler.swift
Sources/Vorssaint/Services/Metrics/NetworkAddressService.swift
Sources/Vorssaint/Services/Metrics/SpeedTest.swift
Sources/Vorssaint/Services/Metrics/PeripheralBatterySampler.swift
Sources/Vorssaint/Services/Metrics/PeripheralBatterySupport.swift
Sources/Vorssaint/Services/Metrics/DiskSupport.swift
Sources/Vorssaint/Services/Metrics/MonitorSamplingPolicy.swift
Sources/Vorssaint/Services/Metrics/USBDeviceSampler.swift
Sources/Vorssaint/Services/Metrics/CPUCoreSampler.swift
Sources/Vorssaint/Services/Metrics/MaxCapacityProbe.swift
Sources/Vorssaint/Services/Metrics/TemperatureSensorSelector.swift
Sources/Vorssaint/Services/Metrics/SustainedAlertGate.swift
Sources/Vorssaint/Services/WindowLayout/WindowLayoutSupport.swift
Sources/Vorssaint/Services/WindowLayout/WindowGestureSupport.swift
Sources/Vorssaint/Core/WindowDirectionalStrings.swift
Sources/Vorssaint/Core/PointerDisplayStrings.swift
Sources/Vorssaint/Core/GraphScaleStrings.swift
Sources/Vorssaint/Core/NotchLowBatteryStrings.swift
Sources/Vorssaint/Services/CleaningMode/CleaningUnlockCounter.swift
Sources/Vorssaint/Services/CleaningMode/CleaningMouseReleaseGate.swift
Sources/Vorssaint/Services/Display/ExtraBrightnessSupport.swift
Sources/Vorssaint/Services/Display/BrightnessSupport.swift
Sources/Vorssaint/Services/Display/LidDimmingSupport.swift
Sources/Vorssaint/Services/Cleaner/CleanerSupport.swift
Sources/Vorssaint/Services/Cleaner/CleanerPolicy.swift
Sources/Vorssaint/Services/Cleaner/CleanerSchedule.swift
Sources/Vorssaint/Services/Uninstall/UninstallerSupport.swift
Sources/Vorssaint/Services/ManagedDownloads/WhatsAppDownloadSupport.swift
Sources/Vorssaint/Core/SecureInputSupport.swift
Tests/*.swift
build/generated-tests/*.swift
)
TEST_OUTPUT_FILE_MAP="$TEST_OBJECT_DIR/output-file-map.json"
write_swift_output_file_map "$TEST_OUTPUT_FILE_MAP" "$TEST_OBJECT_DIR" "${TEST_SOURCES[@]}"
echo "▸ Building & running tests against $(basename "$SDK")…"
swiftc -Onone -incremental -enable-batch-mode -j "$(sysctl -n hw.logicalcpu)" \
-module-name VorssaintTests -output-file-map "$TEST_OUTPUT_FILE_MAP" \
-target "$TARGET" -sdk "$SDK" "${SDK_COMPAT_FLAGS[@]}" \
"${VM_STATISTICS_COMPAT_FLAGS[@]}" "${TEST_SOURCES[@]}" -o build/metrics-tests
test_status=0
./build/metrics-tests "${TEST_ARGS[@]}" || test_status=$?
if (( ${#TEST_ARGS} == 0 )); then
./Tests/PreferenceCleanupTests.sh || test_status=1
./Tests/UninstallSpacesTests.sh || test_status=1
./Tests/DeveloperInstallIsolationTests.sh || test_status=1
fi
discard_test_preferences || test_status=1
exit $test_status
fi
echo "▸ Compiling ($BUILD_CONFIGURATION) against $(basename "$SDK")…"
APP_SOURCES=(Sources/Vorssaint/**/*.swift)
if (( ! DEV )); then
# A release starts from an empty build directory, so nothing an earlier
# build left behind (an old icon catalog, a staged bundle) can reach it.
# Only the compiler's incremental records stay: they rebuild whatever
# changed since, and a fresh checkout has none.
find build -mindepth 1 -maxdepth 1 ! -name objects -exec rm -rf {} + 2>/dev/null || true
fi
APP_OBJECT_DIR="build/objects/$EXECUTABLE"
mkdir -p build "$APP_OBJECT_DIR"
APP_OUTPUT_FILE_MAP="$APP_OBJECT_DIR/output-file-map.json"
write_swift_output_file_map "$APP_OUTPUT_FILE_MAP" "$APP_OBJECT_DIR" "${APP_SOURCES[@]}"
# Without -j the driver compiles one file at a time, and without batch mode
# each file's compiler parses the whole module again: a clean release took a
# quarter of an hour. Batches share that work and run on every core, and the
# optimization stays per file, as before.
swiftc "${APP_OPTIMIZATION_FLAGS[@]}" -incremental -enable-batch-mode -j "$(sysctl -n hw.logicalcpu)" \
-output-file-map "$APP_OUTPUT_FILE_MAP" \
-target "$TARGET" -sdk "$SDK" "${SDK_COMPAT_FLAGS[@]}" "${VM_STATISTICS_COMPAT_FLAGS[@]}" "${HID_EVENT_SYSTEM_FLAGS[@]}" \
"${BUILD_VARIANT_FLAGS[@]}" \
"${APP_SOURCES[@]}" -o "build/$EXECUTABLE"
echo "▸ Compiling protected fan helper…"
swiftc -O -target "$TARGET" -sdk "$SDK" "${SDK_COMPAT_FLAGS[@]}" "${BUILD_VARIANT_FLAGS[@]}" \
Sources/Vorssaint/Services/FanControl/FanControlSupport.swift \
Sources/Vorssaint/Services/FanControl/FanControlXPC.swift \
Sources/Vorssaint/Services/SystemMonitor/SMCClient.swift \
Sources/Vorssaint/Services/Metrics/TemperatureSensorSelector.swift \
Sources/Vorssaint/Services/FanControl/FanControlHardware.swift \
Sources/FanControlHelper/main.swift \
-o "build/$FAN_HELPER_ID"
"build/$FAN_HELPER_ID" --selftest
echo "▸ Compiling Now Playing adapter…"
swiftc -O -target "$TARGET" -sdk "$SDK" "${SDK_COMPAT_FLAGS[@]}" -emit-library \
-module-name VorssaintNowPlaying \
Sources/NowPlayingAdapter/NowPlayingAdapter.swift \
Sources/NowPlayingAdapter/NowPlayingQueue.swift \
Sources/NowPlayingAdapter/NowPlayingSelection.swift \
Sources/Vorssaint/Services/Notch/NotchPlaybackSource.swift \
Sources/Vorssaint/Services/Notch/NotchPlaybackCommand.swift \
-o "build/$NOW_PLAYING_ADAPTER"
echo "▸ Generating app icon…"
swift Tools/MakeIcon.swift build/AppIcon.iconset
xattr -c -r build/AppIcon.iconset build/AppIcon.icns build/MenuBarIcon.png build/MenuBarIcon@2x.png build/BrandMark.png 2>/dev/null || true
ACTOOL_BIN="$(xcrun --find actool 2>/dev/null || true)"
ICON_TMP="$(mktemp -d)"
ADAPTIVE_SKIP=""
if [[ -z "$ACTOOL_BIN" ]]; then
ADAPTIVE_SKIP="actool not found (adaptive icons need Xcode 26+)"
else
echo "▸ Compiling adaptive icon catalog…"
# actool crashes on File Provider-synced paths, so compile a local copy.
ditto "Resources/Brand/AppIcon.icon" "$ICON_TMP/AppIcon.icon"
# Xcode 27 beta actool requires the --compile target directory to already exist.
mkdir -p "$ICON_TMP/catalog"
if "$ACTOOL_BIN" "$ICON_TMP/AppIcon.icon" \
--compile "$ICON_TMP/catalog" \
--app-icon AppIcon \
--platform macosx \
--target-device mac \
--minimum-deployment-target 14.0 \
--enable-on-demand-resources NO \
--output-partial-info-plist "$ICON_TMP/partial-info.plist" \
>"$ICON_TMP/actool.log" 2>&1 && [[ -s "$ICON_TMP/catalog/Assets.car" ]]; then
mv "$ICON_TMP/catalog/Assets.car" build/Assets.car
else
ADAPTIVE_SKIP="actool could not compile the catalog"
fi
fi
if [[ -n "$ADAPTIVE_SKIP" ]]; then
cp "$ICON_TMP/actool.log" build/actool-failure.log 2>/dev/null || true
echo " adaptive icon skipped: $ADAPTIVE_SKIP (Dock falls back to AppIcon.icns)"
fi
echo "▸ Assembling and signing bundle…"
STAGE_TMP="$(mktemp -d)"
STAGE="$STAGE_TMP/$APP_NAME.app"
mkdir -p "$STAGE/Contents/MacOS" "$STAGE/Contents/Resources" \
"$STAGE/Contents/Library/LaunchDaemons" "$STAGE/Contents/Library/LaunchServices"
cp "build/$EXECUTABLE" "$STAGE/Contents/MacOS/$EXECUTABLE"
cp "build/$FAN_HELPER_ID" "$STAGE/Contents/Library/LaunchServices/$FAN_HELPER_ID"
mkdir -p "$STAGE/Contents/Frameworks"
cp "build/$NOW_PLAYING_ADAPTER" "$STAGE/Contents/Frameworks/$NOW_PLAYING_ADAPTER"
cp Resources/now-playing.pl "$STAGE/Contents/Resources/now-playing.pl"
cp Resources/agent-prices.json "$STAGE/Contents/Resources/agent-prices.json"
cp Resources/com.vorssaint.utils.fan-control.plist \
"$STAGE/Contents/Library/LaunchDaemons/$FAN_HELPER_ID.plist"
cp Resources/Info.plist "$STAGE/Contents/Info.plist"
cp CHANGELOG.md "$STAGE/Contents/Resources/CHANGELOG.md"
for lproj in Resources/*.lproj(N); do
cp -R "$lproj" "$STAGE/Contents/Resources/"
done
if (( DEV )); then
# A distinct identity so the Developer build installs and runs next to the
# official app, with its own permissions, preferences and login item.
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $APP_BUNDLE_ID" "$STAGE/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleName $APP_NAME" "$STAGE/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $APP_NAME" "$STAGE/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleExecutable $EXECUTABLE" "$STAGE/Contents/Info.plist"
FAN_PLIST="$STAGE/Contents/Library/LaunchDaemons/$FAN_HELPER_ID.plist"
/usr/libexec/PlistBuddy -c "Set :Label $FAN_HELPER_ID" "$FAN_PLIST"
/usr/libexec/PlistBuddy -c "Set :BundleProgram Contents/Library/LaunchServices/$FAN_HELPER_ID" "$FAN_PLIST"
/usr/libexec/PlistBuddy -c "Delete :MachServices:com.vorssaint.utils.fan-control" "$FAN_PLIST"
/usr/libexec/PlistBuddy -c "Add :MachServices:$FAN_HELPER_ID bool true" "$FAN_PLIST"
# Stamp the source commit + build time so the running dev app shows (in About)
# exactly which code it was compiled from. Lets you verify it matches HEAD before
# testing, instead of unknowingly running a stale build. Dev-only; never shipped.
SHA="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)"
[[ -n "$(git status --porcelain 2>/dev/null)" ]] && SHA="$SHA-dirty"
/usr/libexec/PlistBuddy -c "Add :VorssaintBuildCommit string '$SHA · $(date '+%Y-%m-%d %H:%M')'" "$STAGE/Contents/Info.plist"
echo " stamped dev build: $SHA"
fi
FAN_HELPER_VERSION="$(
export LC_ALL=C
/usr/bin/shasum -a 256 \
"$STAGE/Contents/Library/LaunchServices/$FAN_HELPER_ID" \
"$STAGE/Contents/Library/LaunchDaemons/$FAN_HELPER_ID.plist" \
| /usr/bin/awk '{print $1}' | /usr/bin/shasum -a 256 \
| /usr/bin/awk '{print $1}'
)"
/usr/libexec/PlistBuddy -c "Add :VorssaintFanControlHelperVersion string '$FAN_HELPER_VERSION'" \
"$STAGE/Contents/Info.plist"
printf 'APPL????' > "$STAGE/Contents/PkgInfo"
cp build/AppIcon.icns "$STAGE/Contents/Resources/AppIcon.icns"
cp build/MenuBarIcon.png build/MenuBarIcon@2x.png build/BrandMark.png "$STAGE/Contents/Resources/"
if [[ -f build/Assets.car ]]; then
cp build/Assets.car "$STAGE/Contents/Resources/Assets.car"
fi
if [[ -d Resources/Gifs ]]; then
mkdir -p "$STAGE/Contents/Resources/Gifs"
cp Resources/Gifs/*.gif "$STAGE/Contents/Resources/Gifs/"
fi
if ! cmp -s Resources/Gifs/highlights-notch.gif "$STAGE/Contents/Resources/Gifs/highlights-notch.gif"; then
echo "Dynamic Island tour GIF is missing or differs from the bundled copy" >&2
exit 1
fi
if [[ -d Resources/Images ]]; then
mkdir -p "$STAGE/Contents/Resources/Images"
cp Resources/Images/* "$STAGE/Contents/Resources/Images/"
fi
xattr -c -r "$STAGE" 2>/dev/null || true
# Signing, in order of preference:
# 1. Developer ID Application — the real, Apple-issued identity used for
# notarized releases. Signed with the hardened runtime (required for
# notarization), the app's entitlements and a secure timestamp. Gives a
# stable, team-based designated requirement, so permissions persist across
# updates AND Gatekeeper shows no "unverified developer" warning.
# 2. "Vorssaint Utils Signing" — the legacy stable self-signed identity, kept
# as a fallback so contributors without a Developer ID still get a constant
# designated requirement across their local builds.
# 3. Ad-hoc — fresh clone with no identity at all.
DEVID="$(developer_id_identity)"
codesign_app() {
local target="$1"
if [[ -n "$DEVID" ]]; then
codesign_with_timestamp_retry --force --strip-disallowed-xattrs --options runtime --timestamp \
--entitlements "$ENTITLEMENTS" --sign "$DEVID" "$target"
elif legacy_identity_installed; then
codesign --force --strip-disallowed-xattrs --sign "$LEGACY_IDENTITY" "$target"
else
codesign --force --strip-disallowed-xattrs --sign - "$target"
fi
}
codesign_fan_helper() {
local target="$1"
if [[ -n "$DEVID" ]]; then
codesign_with_timestamp_retry --force --strip-disallowed-xattrs --options runtime --timestamp \
--identifier "$FAN_HELPER_ID" --sign "$DEVID" "$target"
elif legacy_identity_installed; then
codesign --force --strip-disallowed-xattrs --identifier "$FAN_HELPER_ID" \
--sign "$LEGACY_IDENTITY" "$target"
else
codesign --force --strip-disallowed-xattrs --identifier "$FAN_HELPER_ID" --sign - "$target"
fi
}
codesign_now_playing_adapter() {
local target="$1"
if [[ -n "$DEVID" ]]; then
codesign_with_timestamp_retry --force --strip-disallowed-xattrs --options runtime --timestamp \
--identifier "$NOW_PLAYING_ADAPTER_ID" --sign "$DEVID" "$target"
elif legacy_identity_installed; then
codesign --force --strip-disallowed-xattrs --identifier "$NOW_PLAYING_ADAPTER_ID" \
--sign "$LEGACY_IDENTITY" "$target"
else
codesign --force --strip-disallowed-xattrs --identifier "$NOW_PLAYING_ADAPTER_ID" --sign - "$target"
fi
}
sign_bundle() {
local bundle="$1"
local executable="$bundle/Contents/MacOS/$EXECUTABLE"
local helper="$bundle/Contents/Library/LaunchServices/$FAN_HELPER_ID"
local adapter="$bundle/Contents/Frameworks/$NOW_PLAYING_ADAPTER"
if [[ -n "$DEVID" ]]; then
echo " signing with Developer ID (hardened runtime): $DEVID"
elif legacy_identity_installed; then
echo " signing with legacy self-signed identity: $LEGACY_IDENTITY"
else
echo " signing ad-hoc (no identity installed — run Tools/setup-signing.sh)"
fi
[[ -f "$helper" ]] && codesign_fan_helper "$helper"
[[ -f "$adapter" ]] && codesign_now_playing_adapter "$adapter"
codesign_app "$bundle"
# If local filesystem metadata invalidates the first signature, sign once
# more. The installed Developer bundle is signed again after the final copy.
if ! codesign --verify --deep --strict "$bundle" >/dev/null 2>&1; then
echo " re-signing after filesystem metadata settled"
xattr -c -r "$bundle" 2>/dev/null || true
[[ -f "$helper" ]] && codesign_fan_helper "$helper"
[[ -f "$adapter" ]] && codesign_now_playing_adapter "$adapter"
codesign_app "$bundle"
fi
[[ -f "$executable" ]] && codesign --verify --strict "$executable"
[[ -f "$helper" ]] && codesign --verify --strict "$helper"
[[ -f "$adapter" ]] && codesign --verify --strict "$adapter"
codesign --verify --deep --strict "$bundle"
}
sign_installed_bundle() {
local bundle="$1"
wait_for_install_metadata "$bundle"
sign_bundle "$bundle"
}
sign_bundle "$STAGE"
process_is_running() {
local proc="$1"
if (( ${#proc} > 15 )); then
pgrep -f "/Contents/MacOS/$proc" >/dev/null 2>&1
else
pgrep -x "$proc" >/dev/null 2>&1
fi
}
stop_process() {
local proc="$1"
if (( ${#proc} > 15 )); then
pkill -f "/Contents/MacOS/$proc" 2>/dev/null || true
else
pkill -x "$proc" 2>/dev/null || true
fi
for _ in {1..50}; do
if ! process_is_running "$proc"; then
return 0
fi
sleep 0.1
done
echo "✗ $proc is still running — quit it and retry" >&2
return 1
}
wait_for_install_metadata() {
local bundle="$1"
local missing
for _ in {1..50}; do
missing=0
while IFS= read -r file; do
if ! xattr -p com.apple.provenance "$file" >/dev/null 2>&1; then
missing=1
break
fi
done < <(find "$bundle/Contents" -type f ! -path "*/_CodeSignature/*")
if (( missing == 0 )); then
return 0
fi
sleep 0.1
done
}
# Installed development builds only need the copy in /Applications. Retaining
# another app in each checkout pollutes application search with stale builds.
if (( DEV )); then
for old_bundle in "build/stage/$APP_NAME.app" "build/stage.noindex/$APP_NAME.app"; do
if [[ -d "$old_bundle" ]]; then
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister \
-u "$PWD/$old_bundle" >/dev/null 2>&1 || true
rm -rf "$old_bundle"
fi
done
fi
if (( !DEV || !INSTALL )); then
STAGE_DIRECTORY="build/stage"
(( DEV )) && STAGE_DIRECTORY="build/stage.noindex"
mkdir -p "$STAGE_DIRECTORY"
BUILD_STAGE="$STAGE_DIRECTORY/$APP_NAME.app"
rm -rf "$BUILD_STAGE"
ditto --noextattr --noqtn "$STAGE" "$BUILD_STAGE"
xattr -c -r "$BUILD_STAGE" 2>/dev/null || true
if ! codesign --verify --deep --strict "$BUILD_STAGE" >/dev/null 2>&1; then
if xattr -lr "$BUILD_STAGE" 2>/dev/null | grep -Eq 'com\.apple\.(FinderInfo|ResourceFork|provenance|fileprovider)'; then
echo " staging copy has local filesystem metadata; temp bundle was verified"
else
codesign --verify --deep --strict "$BUILD_STAGE"
fi
fi
echo "✓ Bundle ready: $BUILD_STAGE"
fi
if (( INSTALL )); then
echo "▸ Installing into /Applications…"
stop_process "$EXECUTABLE"
# Only an official install replaces the pre-rename official apps. The
# Developer variant must coexist with them just as it does with Vorssaint.
if (( ! DEV )); then
for legacy in "Vorss:Vorss" "Vorssaint Utils:VorssaintUtils"; do
name="${legacy%%:*}"; proc="${legacy##*:}"
if [[ -d "/Applications/$name.app" ]]; then
stop_process "$proc"
rm -rf "/Applications/$name.app"
echo " (legacy $name.app removed)"
fi
done
fi
INSTALL_DEST="/Applications/$APP_NAME.app"
rm -rf "$INSTALL_DEST"
ditto --noextattr --noqtn "$STAGE" "$INSTALL_DEST"
sign_installed_bundle "$INSTALL_DEST"
echo "✓ Installed: $INSTALL_DEST"
fi