playbook/antigravity-awesome-skills/skills/macos-spm-app-packaging/assets/templates/package_app.sh

207 lines
6.0 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
CONF=${1:-release}
ROOT=$(cd "$(dirname "$0")/.." && pwd)
cd "$ROOT"
APP_NAME=${APP_NAME:-MyApp}
BUNDLE_ID=${BUNDLE_ID:-com.example.myapp}
MACOS_MIN_VERSION=${MACOS_MIN_VERSION:-14.0}
MENU_BAR_APP=${MENU_BAR_APP:-0}
SIGNING_MODE=${SIGNING_MODE:-}
APP_IDENTITY=${APP_IDENTITY:-}
if [[ -f "$ROOT/version.env" ]]; then
source "$ROOT/version.env"
else
MARKETING_VERSION=${MARKETING_VERSION:-0.1.0}
BUILD_NUMBER=${BUILD_NUMBER:-1}
fi
ARCH_LIST=( ${ARCHES:-} )
if [[ ${#ARCH_LIST[@]} -eq 0 ]]; then
HOST_ARCH=$(uname -m)
ARCH_LIST=("$HOST_ARCH")
fi
for ARCH in "${ARCH_LIST[@]}"; do
swift build -c "$CONF" --arch "$ARCH"
done
APP="$ROOT/${APP_NAME}.app"
rm -rf "$APP"
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources" "$APP/Contents/Frameworks"
# Convert Icon.icon to Icon.icns if present (requires iconutil).
ICON_SOURCE="$ROOT/Icon.icon"
ICON_TARGET="$ROOT/Icon.icns"
if [[ -f "$ICON_SOURCE" ]]; then
iconutil --convert icns --output "$ICON_TARGET" "$ICON_SOURCE"
fi
LSUI_VALUE="false"
if [[ "$MENU_BAR_APP" == "1" ]]; then
LSUI_VALUE="true"
fi
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>${APP_NAME}</string>
<key>CFBundleDisplayName</key><string>${APP_NAME}</string>
<key>CFBundleIdentifier</key><string>${BUNDLE_ID}</string>
<key>CFBundleExecutable</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>
<key>LSMinimumSystemVersion</key><string>${MACOS_MIN_VERSION}</string>
<key>LSUIElement</key><${LSUI_VALUE}/>
<key>CFBundleIconFile</key><string>Icon</string>
<key>BuildTimestamp</key><string>${BUILD_TIMESTAMP}</string>
<key>GitCommit</key><string>${GIT_COMMIT}</string>
</dict>
</plist>
PLIST
build_product_path() {
local name="$1"
local arch="$2"
case "$arch" in
arm64|x86_64) echo ".build/${arch}-apple-macosx/$CONF/$name" ;;
*) echo ".build/$CONF/$name" ;;
esac
}
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
src=$(build_product_path "$name" "$arch")
if [[ ! -f "$src" ]]; then
echo "ERROR: Missing ${name} build for ${arch} at ${src}" >&2
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[@]}"
}
install_binary "$APP_NAME" "$APP/Contents/MacOS/$APP_NAME"
# Bundle app resources (if any).
APP_RESOURCES_DIR="$ROOT/Sources/$APP_NAME/Resources"
if [[ -d "$APP_RESOURCES_DIR" ]]; then
cp -R "$APP_RESOURCES_DIR/." "$APP/Contents/Resources/"
fi
# SwiftPM resource bundles are emitted next to the built binary.
PREFERRED_BUILD_DIR="$(dirname "$(build_product_path "$APP_NAME" "${ARCH_LIST[0]}")")"
shopt -s nullglob
SWIFTPM_BUNDLES=("${PREFERRED_BUILD_DIR}/"*.bundle)
shopt -u nullglob
if [[ ${#SWIFTPM_BUNDLES[@]} -gt 0 ]]; then
for bundle in "${SWIFTPM_BUNDLES[@]}"; do
cp -R "$bundle" "$APP/Contents/Resources/"
done
fi
# Embed frameworks if any exist in the build folder.
FRAMEWORK_DIRS=(".build/$CONF" ".build/${ARCH_LIST[0]}-apple-macosx/$CONF")
for dir in "${FRAMEWORK_DIRS[@]}"; do
if compgen -G "${dir}/*.framework" >/dev/null; then
cp -R "${dir}/"*.framework "$APP/Contents/Frameworks/"
chmod -R a+rX "$APP/Contents/Frameworks"
install_name_tool -add_rpath "@executable_path/../Frameworks" "$APP/Contents/MacOS/$APP_NAME"
break
fi
done
if [[ -f "$ICON_TARGET" ]]; then
cp "$ICON_TARGET" "$APP/Contents/Resources/Icon.icns"
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
ENTITLEMENTS_DIR="$ROOT/.build/entitlements"
DEFAULT_ENTITLEMENTS="$ENTITLEMENTS_DIR/${APP_NAME}.entitlements"
mkdir -p "$ENTITLEMENTS_DIR"
APP_ENTITLEMENTS=${APP_ENTITLEMENTS:-$DEFAULT_ENTITLEMENTS}
if [[ ! -f "$APP_ENTITLEMENTS" ]]; then
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>
<!-- Add entitlements here if needed. -->
</dict>
</plist>
PLIST
fi
if [[ "$SIGNING_MODE" == "adhoc" || -z "$APP_IDENTITY" ]]; then
CODESIGN_ARGS=(--force --sign "-")
else
CODESIGN_ARGS=(--force --timestamp --options runtime --sign "$APP_IDENTITY")
fi
# Sign embedded frameworks and their nested binaries before the app bundle.
sign_frameworks() {
local fw
for fw in "$APP/Contents/Frameworks/"*.framework; do
if [[ ! -d "$fw" ]]; then
continue
fi
while IFS= read -r -d '' bin; do
codesign "${CODESIGN_ARGS[@]}" "$bin"
done < <(find "$fw" -type f -perm -111 -print0)
codesign "${CODESIGN_ARGS[@]}" "$fw"
done
}
sign_frameworks
codesign "${CODESIGN_ARGS[@]}" \
--entitlements "$APP_ENTITLEMENTS" \
"$APP"
echo "Created $APP"