#!/usr/bin/env bash
|
#
|
# build-local-macos-universal.sh - Build a locally signed universal macOS DMG.
|
#
|
# The script reuses the existing per-architecture local build script, then
|
# combines the app binary and bundled dylibs with lipo before re-signing.
|
#
|
set -euo pipefail
|
|
APP_NAME="PrivateVoice Dictation"
|
DEFAULT_LOCAL_IDENTITY="PrivateVoice Local Code Signing"
|
LEGACY_LOCAL_IDENTITY="VoiceSnap Local Code Signing"
|
LOCAL_IDENTITY="${PRIVATEVOICE_LOCAL_SIGN_IDENTITY:-${VOICESNAP_LOCAL_SIGN_IDENTITY:-}}"
|
KEYCHAIN="${PRIVATEVOICE_LOCAL_KEYCHAIN:-${VOICESNAP_LOCAL_KEYCHAIN:-$HOME/Library/Keychains/login.keychain-db}}"
|
LOCAL_BUILD_FLAVOR="${PRIVATEVOICE_LOCAL_BUILD_FLAVOR:-local}"
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
INFO_PLIST="$PROJECT_DIR/build/darwin/Info.plist"
|
ENTITLEMENTS="$PROJECT_DIR/build/darwin/entitlements.plist"
|
VERSION="$(defaults read "$INFO_PLIST" CFBundleShortVersionString 2>/dev/null || echo "2.1.1")"
|
BUILD_ID="$(defaults read "$INFO_PLIST" CFBundleVersion 2>/dev/null || date +%Y%m%d.%H%M)"
|
|
ARM_BUILD_DIR="$PROJECT_DIR/build/local/arm64"
|
X86_BUILD_DIR="$PROJECT_DIR/build/local/x86_64"
|
BUILD_DIR="$PROJECT_DIR/build/local/universal"
|
ARM_APP="$ARM_BUILD_DIR/$APP_NAME.app"
|
X86_APP="$X86_BUILD_DIR/$APP_NAME.app"
|
APP_BUNDLE="$BUILD_DIR/$APP_NAME.app"
|
DMG_PATH="$BUILD_DIR/$APP_NAME-$VERSION-build$BUILD_ID-universal-$LOCAL_BUILD_FLAVOR.dmg"
|
COMPAT_DMG_PATH="$BUILD_DIR/$APP_NAME-$VERSION-universal-$LOCAL_BUILD_FLAVOR.dmg"
|
|
step() {
|
echo ""
|
echo "==> $1"
|
}
|
|
fail() {
|
echo "ERROR: $1" >&2
|
exit 1
|
}
|
|
resolve_local_identity() {
|
if [[ -z "$LOCAL_IDENTITY" ]]; then
|
if security find-identity -v -p codesigning "$KEYCHAIN" | grep -Fq "$DEFAULT_LOCAL_IDENTITY"; then
|
LOCAL_IDENTITY="$DEFAULT_LOCAL_IDENTITY"
|
elif security find-identity -v -p codesigning "$KEYCHAIN" | grep -Fq "$LEGACY_LOCAL_IDENTITY"; then
|
LOCAL_IDENTITY="$LEGACY_LOCAL_IDENTITY"
|
else
|
fail "Local signing identity not found; run build-local-macos.sh first"
|
fi
|
fi
|
|
if ! security find-identity -v -p codesigning "$KEYCHAIN" | grep -Fq "$LOCAL_IDENTITY"; then
|
fail "Local signing identity not available to codesign: $LOCAL_IDENTITY"
|
fi
|
}
|
|
sign_item() {
|
local item="$1"
|
codesign --force \
|
--options runtime \
|
--timestamp=none \
|
--entitlements "$ENTITLEMENTS" \
|
--sign "$LOCAL_IDENTITY" \
|
"$item"
|
}
|
|
step "Building arm64 app"
|
"$SCRIPT_DIR/build-local-macos.sh" arm64
|
|
step "Building x86_64 app"
|
"$SCRIPT_DIR/build-local-macos.sh" x86_64
|
|
step "Preflight checks"
|
[[ -d "$ARM_APP" ]] || fail "arm64 app not found at $ARM_APP"
|
[[ -d "$X86_APP" ]] || fail "x86_64 app not found at $X86_APP"
|
[[ -f "$ENTITLEMENTS" ]] || fail "entitlements.plist not found at $ENTITLEMENTS"
|
resolve_local_identity
|
|
step "Assembling universal app"
|
rm -rf "$BUILD_DIR"
|
mkdir -p "$BUILD_DIR"
|
ditto "$ARM_APP" "$APP_BUNDLE"
|
|
lipo -create \
|
"$ARM_APP/Contents/MacOS/$APP_NAME" \
|
"$X86_APP/Contents/MacOS/$APP_NAME" \
|
-output "$APP_BUNDLE/Contents/MacOS/$APP_NAME.universal"
|
mv "$APP_BUNDLE/Contents/MacOS/$APP_NAME.universal" "$APP_BUNDLE/Contents/MacOS/$APP_NAME"
|
|
for arm_dylib in "$ARM_APP"/Contents/Frameworks/*.dylib; do
|
dylib_name="$(basename "$arm_dylib")"
|
x86_dylib="$X86_APP/Contents/Frameworks/$dylib_name"
|
out_dylib="$APP_BUNDLE/Contents/Frameworks/$dylib_name"
|
out_tmp="$out_dylib.universal"
|
[[ -f "$x86_dylib" ]] || fail "Matching x86_64 dylib not found: $x86_dylib"
|
lipo -create "$arm_dylib" "$x86_dylib" -output "$out_tmp"
|
mv "$out_tmp" "$out_dylib"
|
done
|
|
step "Code signing universal app"
|
for dylib in "$APP_BUNDLE"/Contents/Frameworks/*.dylib; do
|
sign_item "$dylib"
|
done
|
sign_item "$APP_BUNDLE/Contents/MacOS/$APP_NAME"
|
sign_item "$APP_BUNDLE"
|
|
step "Verifying universal app"
|
codesign -vvv --deep --strict "$APP_BUNDLE"
|
lipo -archs "$APP_BUNDLE/Contents/MacOS/$APP_NAME"
|
|
step "Creating universal DMG"
|
DMG_TEMP="$BUILD_DIR/dmg-staging"
|
rm -rf "$DMG_TEMP" "$DMG_PATH" "$COMPAT_DMG_PATH"
|
mkdir -p "$DMG_TEMP"
|
cp -R "$APP_BUNDLE" "$DMG_TEMP/"
|
ln -s /Applications "$DMG_TEMP/Applications"
|
hdiutil create -volname "$APP_NAME" \
|
-srcfolder "$DMG_TEMP" \
|
-ov -format UDZO \
|
"$DMG_PATH"
|
rm -rf "$DMG_TEMP"
|
|
step "Signing universal DMG"
|
codesign --force --timestamp=none --sign "$LOCAL_IDENTITY" "$DMG_PATH"
|
hdiutil verify "$DMG_PATH"
|
cp "$DMG_PATH" "$COMPAT_DMG_PATH"
|
|
step "Universal local build complete"
|
echo " App: $APP_BUNDLE"
|
echo " DMG: $DMG_PATH"
|
echo " Compatibility DMG: $COMPAT_DMG_PATH"
|
echo " Signing identity: $LOCAL_IDENTITY"
|