156 lines
6.0 KiB
Bash
Executable File
156 lines
6.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Assemble a macOS .app bundle for a Nucleic build channel.
|
|
#
|
|
# scripts/package-app.sh {dev|beta|rc|stable}
|
|
#
|
|
# Wraps the channel's bare SwiftPM executable in a proper .app: writes Info.plist,
|
|
# ships the SwiftPM resource bundles (GRDB / SwiftTerm) inside Contents/Resources,
|
|
# and ad-hoc code-signs so it launches locally. The channels build to distinct
|
|
# app names + bundle IDs, so they coexist:
|
|
#
|
|
# channel product (process) app bundle bundle id
|
|
# dev nucleic-local Nucleic Dev.app com.abkslm.nucleic.dev
|
|
# beta nucleic-beta Nucleic Beta.app com.abkslm.nucleic.beta
|
|
# rc nucleic-rc Nucleic RC.app com.abkslm.nucleic.rc
|
|
# stable nucleic Nucleic.app com.abkslm.nucleic
|
|
#
|
|
# Env overrides:
|
|
# NUCLEIC_CONFIG build configuration (default: release)
|
|
# NUCLEIC_VERSION marketing version / CFBundleShortVersionString (default: 0.1.0)
|
|
# NUCLEIC_SIGN_ID codesign identity (default: "-", ad-hoc). Set to a Developer ID
|
|
# to sign for distribution (notarization/TestFlight is separate).
|
|
#
|
|
# An app icon is picked up automatically if present at Resources/AppIcon.icns
|
|
# (or per-channel Resources/AppIcon-<channel>.icns).
|
|
|
|
set -euo pipefail
|
|
|
|
CHANNEL="${1:-dev}"
|
|
CONFIG="${NUCLEIC_CONFIG:-release}"
|
|
MARKETING_VERSION="${NUCLEIC_VERSION:-0.1.0}"
|
|
SIGN_ID="${NUCLEIC_SIGN_ID:--}"
|
|
|
|
case "$CHANNEL" in
|
|
dev) PRODUCT=nucleic-local; APP_NAME="Nucleic Dev"; BUNDLE_ID="com.abkslm.nucleic.dev" ;;
|
|
beta) PRODUCT=nucleic-beta; APP_NAME="Nucleic Beta"; BUNDLE_ID="com.abkslm.nucleic.beta" ;;
|
|
rc) PRODUCT=nucleic-rc; APP_NAME="Nucleic RC"; BUNDLE_ID="com.abkslm.nucleic.rc" ;;
|
|
stable) PRODUCT=nucleic; APP_NAME="Nucleic"; BUNDLE_ID="com.abkslm.nucleic" ;;
|
|
*) echo "usage: $0 {dev|beta|rc|stable}" >&2; exit 2 ;;
|
|
esac
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
cd "$ROOT"
|
|
|
|
# iCloud sync drops "conflict copies" beside source files — "Foo 2.swift", "Foo 3.swift"
|
|
# — that are byte-identical to the original. SwiftPM globs the whole Sources/Tests tree,
|
|
# so each copy compiles as a second definition and the build dies with "invalid
|
|
# redeclaration". Sweep them before building. We only touch our own source trees, and
|
|
# only files whose name is "<base> <n>.swift" with an existing "<base>.swift" alongside,
|
|
# so a deliberately-named file is never removed.
|
|
clean_icloud_dupes() {
|
|
local dupe base removed=0
|
|
while IFS= read -r -d '' dupe; do
|
|
base="${dupe% [0-9].swift}.swift"
|
|
[ -f "$base" ] || continue
|
|
rm -f "$dupe"
|
|
echo " removed iCloud conflict copy: ${dupe#"$ROOT/"}"
|
|
removed=$((removed + 1))
|
|
done < <(find "$ROOT/Sources" "$ROOT/Tests" -type f -name '* [0-9].swift' -print0 2>/dev/null)
|
|
[ "$removed" -gt 0 ] && echo "▸ Cleaned $removed iCloud conflict cop$([ "$removed" -eq 1 ] && echo y || echo ies) before build"
|
|
return 0
|
|
}
|
|
clean_icloud_dupes
|
|
|
|
echo "▸ Building $PRODUCT (channel=$CHANNEL, config=$CONFIG)"
|
|
NUCLEIC_CHANNEL="$CHANNEL" swift build -c "$CONFIG" --build-system native --product "$PRODUCT"
|
|
BIN_DIR="$(NUCLEIC_CHANNEL="$CHANNEL" swift build -c "$CONFIG" --build-system native --product "$PRODUCT" --show-bin-path)"
|
|
|
|
BUILD_NUMBER="$(git rev-list --count HEAD 2>/dev/null || echo 1)"
|
|
COMMIT="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
|
|
|
APP="$ROOT/dist/$APP_NAME.app"
|
|
CONTENTS="$APP/Contents"
|
|
echo "▸ Assembling $APP"
|
|
rm -rf "$APP"
|
|
mkdir -p "$CONTENTS/MacOS" "$CONTENTS/Resources"
|
|
|
|
# Executable — keep the channel's product name so the OS process name is preserved
|
|
# even when launched from the bundle.
|
|
cp "$BIN_DIR/$PRODUCT" "$CONTENTS/MacOS/$PRODUCT"
|
|
|
|
# SwiftPM resource bundles must ship inside the app — `Bundle.module` resolves them
|
|
# via Bundle.main.resourceURL (= Contents/Resources).
|
|
shopt -s nullglob
|
|
for b in "$BIN_DIR"/*.bundle; do
|
|
cp -R "$b" "$CONTENTS/Resources/"
|
|
done
|
|
shopt -u nullglob
|
|
|
|
# Optional icon.
|
|
ICON_PLIST=""
|
|
ICON_SRC=""
|
|
[ -f "$ROOT/Resources/AppIcon-$CHANNEL.icns" ] && ICON_SRC="$ROOT/Resources/AppIcon-$CHANNEL.icns"
|
|
[ -z "$ICON_SRC" ] && [ -f "$ROOT/Resources/AppIcon.icns" ] && ICON_SRC="$ROOT/Resources/AppIcon.icns"
|
|
if [ -n "$ICON_SRC" ]; then
|
|
cp "$ICON_SRC" "$CONTENTS/Resources/AppIcon.icns"
|
|
ICON_PLIST="
|
|
<key>CFBundleIconFile</key>
|
|
<string>AppIcon</string>"
|
|
fi
|
|
|
|
cat > "$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>CFBundleDevelopmentRegion</key>
|
|
<string>en</string>
|
|
<key>CFBundleExecutable</key>
|
|
<string>$PRODUCT</string>
|
|
<key>CFBundleIdentifier</key>
|
|
<string>$BUNDLE_ID</string>
|
|
<key>CFBundleInfoDictionaryVersion</key>
|
|
<string>6.0</string>
|
|
<key>CFBundleName</key>
|
|
<string>$APP_NAME</string>
|
|
<key>CFBundleDisplayName</key>
|
|
<string>$APP_NAME</string>
|
|
<key>CFBundlePackageType</key>
|
|
<string>APPL</string>
|
|
<key>CFBundleShortVersionString</key>
|
|
<string>$MARKETING_VERSION</string>
|
|
<key>CFBundleVersion</key>
|
|
<string>$BUILD_NUMBER</string>$ICON_PLIST
|
|
<key>LSMinimumSystemVersion</key>
|
|
<string>14.0</string>
|
|
<key>NSHighResolutionCapable</key>
|
|
<true/>
|
|
<key>NSPrincipalClass</key>
|
|
<string>NSApplication</string>
|
|
<key>NSSupportsAutomaticTermination</key>
|
|
<true/>
|
|
<key>NSSupportsSuddenTermination</key>
|
|
<true/>
|
|
</dict>
|
|
</plist>
|
|
PLIST
|
|
|
|
printf 'APPL????' > "$CONTENTS/PkgInfo"
|
|
|
|
# Strip extended attributes (resource forks / Finder info) that iCloud and Finder
|
|
# attach to copied files — codesign rejects them with "resource fork, Finder
|
|
# information, or similar detritus not allowed". SwiftPM ships some resource files
|
|
# read-only, so make the tree writable first or xattr -c gets EACCES.
|
|
chmod -R u+w "$APP"
|
|
xattr -cr "$APP"
|
|
|
|
echo "▸ Code-signing (identity: $SIGN_ID)"
|
|
codesign --force --options runtime --timestamp=none --sign "$SIGN_ID" "$APP"
|
|
codesign --verify --verbose=1 "$APP"
|
|
|
|
echo "✓ $APP"
|
|
echo " bundle id $BUNDLE_ID"
|
|
echo " version $MARKETING_VERSION (build $BUILD_NUMBER, commit $COMMIT)"
|
|
echo " open with: open \"$APP\""
|