98 lines
4.6 KiB
Bash
Executable File
98 lines
4.6 KiB
Bash
Executable File
#!/bin/bash
|
|
# Build Edmund.app — a standalone macOS application bundle.
|
|
# Usage: ./scripts/build-app.sh
|
|
# Output: build/Edmund.app (ready to drag into /Applications)
|
|
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
|
|
APP_NAME="Edmund"
|
|
BUNDLE="build/${APP_NAME}.app"
|
|
# The executable target is "edmd" (see Package.swift); the binary keeps that name
|
|
# inside the bundle even though the app presents as "Edmund".
|
|
EXECUTABLE="edmd"
|
|
|
|
echo "Building release binary..."
|
|
swift build -c release 2>&1 | tail -3
|
|
|
|
echo "Creating ${APP_NAME}.app bundle..."
|
|
rm -rf "$BUNDLE"
|
|
mkdir -p "${BUNDLE}/Contents/MacOS"
|
|
mkdir -p "${BUNDLE}/Contents/Resources"
|
|
|
|
cp ".build/release/${EXECUTABLE}" "${BUNDLE}/Contents/MacOS/${EXECUTABLE}"
|
|
cp Info.plist "${BUNDLE}/Contents/"
|
|
cp Resources/AppIcon.icns "${BUNDLE}/Contents/Resources/AppIcon.icns"
|
|
|
|
# Compile the asset catalog so the app's AccentColor (our brown) is available.
|
|
# macOS uses it only when the user's system accent is "Multicolor"; a specific
|
|
# system accent still wins, which is the behavior we want.
|
|
# `actool` ships with full Xcode, not the Command Line Tools, so fall back to
|
|
# Xcode.app's copy when xcode-select points at the CLT.
|
|
echo "Compiling asset catalog..."
|
|
ACTOOL="$(xcrun --find actool 2>/dev/null || echo /Applications/Xcode.app/Contents/Developer/usr/bin/actool)"
|
|
"$ACTOOL" Resources/Assets.xcassets \
|
|
--compile "${BUNDLE}/Contents/Resources" \
|
|
--platform macosx \
|
|
--minimum-deployment-target 14.0 \
|
|
--output-partial-info-plist "$(mktemp)" \
|
|
>/dev/null
|
|
|
|
# Embed Sparkle.framework so the installed bundle is self-contained.
|
|
# SwiftPM links Sparkle but doesn't copy the framework (which carries the XPC
|
|
# helpers and Autoupdate.app) into the bundle; without this the updater crashes
|
|
# on the first check because it can't locate its helper processes.
|
|
echo "Embedding Sparkle.framework..."
|
|
mkdir -p "${BUNDLE}/Contents/Frameworks"
|
|
SPARKLE_FW="$(find .build -type d -name 'Sparkle.framework' | grep -v '\.dSYM' | head -1)"
|
|
if [ -z "$SPARKLE_FW" ]; then
|
|
echo "Error: Sparkle.framework not found in .build. Run 'swift build -c release' first." >&2
|
|
exit 1
|
|
fi
|
|
cp -R "$SPARKLE_FW" "${BUNDLE}/Contents/Frameworks/"
|
|
|
|
# Fix the rpath so the binary resolves @rpath/Sparkle.framework at the bundle-
|
|
# relative path above rather than the build-artifacts path that won't exist once
|
|
# the app is installed.
|
|
install_name_tool -add_rpath "@executable_path/../Frameworks" \
|
|
"${BUNDLE}/Contents/MacOS/${EXECUTABLE}" 2>/dev/null || true
|
|
|
|
# Code sign the bundle as a properly *sealed* bundle — not just the binary.
|
|
#
|
|
# Why this matters: at install time Sparkle re-validates the downloaded update's
|
|
# Apple code signature (SUUpdateValidator). Even with a valid EdDSA signature, if
|
|
# the bundle reports as code-signed but fails SecStaticCodeCheckValidity, Sparkle
|
|
# rejects the update as "improperly signed and could not be validated." Signing
|
|
# only the standalone binary (as we used to) produces a bundle with no
|
|
# _CodeSignature seal, which fails that check — so every Sparkle update was
|
|
# rejected.
|
|
#
|
|
# Sign inside-out: Sparkle.framework first (its nested XPC helpers must be signed
|
|
# before macOS will launch them), then the whole .app. We seal the app while its
|
|
# root contains only Contents/, because codesign refuses to seal a bundle that
|
|
# has extra items at the .app root ("unsealed contents present in the bundle
|
|
# root"). The SwiftMath resource bundle has to live at the .app root at runtime
|
|
# (see below), so we copy it in *after* sealing. That leaves one unsealed item at
|
|
# the root, which `codesign --verify` (CLI) and --strict flag — but Sparkle's
|
|
# actual check is non-strict (SecStaticCodeCheckValidityWithErrors with
|
|
# kSecCSCheckAllArchitectures), which tolerates it. Verified end-to-end.
|
|
echo "Code signing..."
|
|
codesign --force --deep --sign - "${BUNDLE}/Contents/Frameworks/Sparkle.framework"
|
|
codesign --force --deep --sign - --identifier "com.i7t5.edmd" "$BUNDLE"
|
|
|
|
# SwiftPM dependencies that ship resources (SwiftMath's math fonts) emit a
|
|
# per-target bundle next to the binary. SwiftMath's generated Bundle.module
|
|
# accessor looks for it at Bundle.main.bundleURL — i.e. the .app root — and only
|
|
# otherwise at a hardcoded absolute .build path that doesn't exist once the app
|
|
# is installed. So it must sit at the .app root; copy it in *after* signing (it
|
|
# can't be sealed there — see above) so the bundle's own seal stays valid.
|
|
# Without this the app crashes the moment it renders any LaTeX.
|
|
echo "Copying SwiftPM resource bundles..."
|
|
for bundle in .build/release/*.bundle; do
|
|
[ -e "$bundle" ] && cp -R "$bundle" "${BUNDLE}/"
|
|
done
|
|
|
|
echo ""
|
|
echo "Done: ${BUNDLE}"
|
|
echo "To install: cp -R ${BUNDLE} /Applications/"
|