build-macos-app.sh publiziert und platziert jetzt zusätzlich zur Hauptapp die MCP-Bridge-Binary (Contents/MacOS/LehrerApp.McpBridge), inklusive Universal-Binary-Handling und eigenständiger Ad-hoc-Signierung vor dem Bundle-weiten codesign --deep-Schritt. Publish/Lipo/Kopier-Logik dafür in eine wiederverwendbare place_binary()-Funktion extrahiert. Nur auf Bash-Syntax geprüft (kein macOS in dieser Session verfügbar) - noch nicht auf echtem macOS gebaut/verifiziert, siehe TODO.md 4.5.27. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
166 lines
7.6 KiB
Bash
Executable File
166 lines
7.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ============================================================
|
|
# build-macos-app.sh
|
|
# Baut LehrerApp.Desktop als natives macOS .app-Bundle, inklusive
|
|
# LehrerApp.McpBridge als Companion-Binary im selben Bundle
|
|
# (Contents/MacOS/LehrerApp.McpBridge neben Contents/MacOS/LehrerApp.Desktop) -
|
|
# siehe TODO.md 4.5.25 zur Architektur-Begründung (Bridge muss im selben
|
|
# Bundle liegen, damit sie ihren Pfad relativ zur Hauptapp ermitteln und sich
|
|
# selbst in die MCP-Server-Config eines KI-Clients eintragen kann).
|
|
# ============================================================
|
|
|
|
# ---- Konfiguration (bei Bedarf anpassen) --------------------
|
|
PROJECT_PATH="LehrerApp.Desktop" # Pfad zum .csproj / Projektordner
|
|
APP_NAME="LehrerApp" # Name des App-Bundles (LehrerApp.app)
|
|
EXECUTABLE_NAME="LehrerApp.Desktop" # Name der Binary aus dotnet publish (= AssemblyName des Projekts!)
|
|
BRIDGE_PROJECT_PATH="LehrerApp.McpBridge" # Pfad zum Bridge-.csproj / Projektordner
|
|
BRIDGE_EXECUTABLE_NAME="LehrerApp.McpBridge" # Name der Bridge-Binary (= AssemblyName des Bridge-Projekts!)
|
|
BUNDLE_ID="de.deinname.lehrerapp" # CFBundleIdentifier
|
|
VERSION="1.0.0" # CFBundleShortVersionString
|
|
MIN_MACOS="12.0" # LSMinimumSystemVersion
|
|
ICON_PNG="icon.png" # Quell-Icon (min. 512x512 oder 1024x1024)
|
|
RUNTIME_IDS=("osx-arm64" "osx-x64") # Architekturen, für die gebaut wird
|
|
UNIVERSAL_BUILD=true # true = zusätzlich Universal Binary via lipo
|
|
CODESIGN_ADHOC=true # true = Ad-hoc-Signierung am Ende
|
|
|
|
# ---- Ableitungen ---------------------------------------------
|
|
BUILD_DIR="./build"
|
|
APP_BUNDLE="${BUILD_DIR}/${APP_NAME}.app"
|
|
CONTENTS_DIR="${APP_BUNDLE}/Contents"
|
|
MACOS_DIR="${CONTENTS_DIR}/MacOS"
|
|
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
|
|
|
|
echo "=== 1/7: Alte Build-Artefakte entfernen ==="
|
|
rm -rf "${BUILD_DIR}"
|
|
mkdir -p "${BUILD_DIR}"
|
|
|
|
echo "=== 2/7: dotnet publish für jede Architektur (Hauptapp + Bridge) ==="
|
|
for RID in "${RUNTIME_IDS[@]}"; do
|
|
echo " -> Publish Hauptapp für ${RID}"
|
|
dotnet publish "${PROJECT_PATH}" \
|
|
-c Release \
|
|
-r "${RID}" \
|
|
--self-contained true \
|
|
-p:PublishSingleFile=false \
|
|
-o "${BUILD_DIR}/publish/${RID}"
|
|
|
|
echo " -> Publish McpBridge für ${RID}"
|
|
dotnet publish "${BRIDGE_PROJECT_PATH}" \
|
|
-c Release \
|
|
-r "${RID}" \
|
|
--self-contained true \
|
|
-p:PublishSingleFile=false \
|
|
-o "${BUILD_DIR}/publish-bridge/${RID}"
|
|
done
|
|
|
|
echo "=== 3/7: App-Bundle-Struktur anlegen (Hauptapp + Bridge) ==="
|
|
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
|
|
|
|
# Kopiert den Publish-Output einer Architektur (oder beider via lipo zu einem Universal Binary)
|
|
# nach MACOS_DIR. Für Hauptapp und Bridge wiederverwendet - beide sind self-contained-Publishes
|
|
# derselben TFM, gemeinsame .NET-Runtime-Dateien werden beim zweiten Aufruf einfach mit sich
|
|
# selbst überschrieben (keine Laufzeit-Duplikate im fertigen Bundle vermieden, nur der
|
|
# Publish-Ordner enthält sie doppelt - siehe TODO.md 4.5.25 zur noch offenen
|
|
# Runtime-Dedup-Optimierung, für v1 bewusst nicht angegangen).
|
|
place_binary() {
|
|
local publish_root="$1"
|
|
local executable_name="$2"
|
|
|
|
if [ "${UNIVERSAL_BUILD}" = true ] && [ "${#RUNTIME_IDS[@]}" -ge 2 ]; then
|
|
local src_a="${publish_root}/${RUNTIME_IDS[0]}/${executable_name}"
|
|
local src_b="${publish_root}/${RUNTIME_IDS[1]}/${executable_name}"
|
|
rsync -a --exclude "${executable_name}" "${publish_root}/${RUNTIME_IDS[0]}/" "${MACOS_DIR}/"
|
|
lipo -create -output "${MACOS_DIR}/${executable_name}" "${src_a}" "${src_b}"
|
|
else
|
|
cp -r "${publish_root}/${RUNTIME_IDS[0]}/"* "${MACOS_DIR}/"
|
|
fi
|
|
|
|
if [ ! -f "${MACOS_DIR}/${executable_name}" ]; then
|
|
echo "FEHLER: Erwartete Executable '${executable_name}' nicht in ${MACOS_DIR} gefunden."
|
|
echo "Tatsächlicher Inhalt von ${publish_root}/${RUNTIME_IDS[0]}/:"
|
|
ls "${publish_root}/${RUNTIME_IDS[0]}/"
|
|
echo "-> Passe den entsprechenden EXECUTABLE_NAME im Skript-Kopf an."
|
|
exit 1
|
|
fi
|
|
chmod +x "${MACOS_DIR}/${executable_name}"
|
|
}
|
|
|
|
echo " -> Hauptapp"
|
|
place_binary "${BUILD_DIR}/publish" "${EXECUTABLE_NAME}"
|
|
echo " -> Bridge"
|
|
place_binary "${BUILD_DIR}/publish-bridge" "${BRIDGE_EXECUTABLE_NAME}"
|
|
|
|
echo "=== 4/7: Icon konvertieren (.icns) ==="
|
|
if [ -f "${ICON_PNG}" ]; then
|
|
ICONSET_DIR="${BUILD_DIR}/icon.iconset"
|
|
mkdir -p "${ICONSET_DIR}"
|
|
|
|
sips -z 16 16 "${ICON_PNG}" --out "${ICONSET_DIR}/icon_16x16.png" > /dev/null
|
|
sips -z 32 32 "${ICON_PNG}" --out "${ICONSET_DIR}/icon_16x16@2x.png" > /dev/null
|
|
sips -z 32 32 "${ICON_PNG}" --out "${ICONSET_DIR}/icon_32x32.png" > /dev/null
|
|
sips -z 64 64 "${ICON_PNG}" --out "${ICONSET_DIR}/icon_32x32@2x.png" > /dev/null
|
|
sips -z 128 128 "${ICON_PNG}" --out "${ICONSET_DIR}/icon_128x128.png" > /dev/null
|
|
sips -z 256 256 "${ICON_PNG}" --out "${ICONSET_DIR}/icon_128x128@2x.png" > /dev/null
|
|
sips -z 256 256 "${ICON_PNG}" --out "${ICONSET_DIR}/icon_256x256.png" > /dev/null
|
|
sips -z 512 512 "${ICON_PNG}" --out "${ICONSET_DIR}/icon_256x256@2x.png" > /dev/null
|
|
sips -z 512 512 "${ICON_PNG}" --out "${ICONSET_DIR}/icon_512x512.png" > /dev/null
|
|
sips -z 1024 1024 "${ICON_PNG}" --out "${ICONSET_DIR}/icon_512x512@2x.png" > /dev/null
|
|
|
|
iconutil -c icns "${ICONSET_DIR}" -o "${RESOURCES_DIR}/icon.icns"
|
|
ICON_KEY="<key>CFBundleIconFile</key><string>icon.icns</string>"
|
|
else
|
|
echo " -> Kein '${ICON_PNG}' gefunden, App-Bundle wird ohne Icon erstellt."
|
|
ICON_KEY=""
|
|
fi
|
|
|
|
echo "=== 5/7: Info.plist schreiben ==="
|
|
cat > "${CONTENTS_DIR}/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>CFBundleExecutable</key><string>${EXECUTABLE_NAME}</string>
|
|
<key>CFBundleIdentifier</key><string>${BUNDLE_ID}</string>
|
|
<key>CFBundlePackageType</key><string>APPL</string>
|
|
<key>CFBundleShortVersionString</key><string>${VERSION}</string>
|
|
<key>CFBundleVersion</key><string>${VERSION}</string>
|
|
<key>LSMinimumSystemVersion</key><string>${MIN_MACOS}</string>
|
|
<key>NSHighResolutionCapable</key><true/>
|
|
${ICON_KEY}
|
|
</dict>
|
|
</plist>
|
|
PLIST
|
|
|
|
# Die Bridge ist bewusst NICHT CFBundleExecutable - sie wird nie von Finder/launchd gestartet,
|
|
# sondern ausschließlich vom KI-Client als Kindprozess über ihren vollen Pfad
|
|
# (Contents/MacOS/LehrerApp.McpBridge). Kein eigener Eintrag im Info.plist nötig.
|
|
|
|
echo "=== 6/7: Bridge-Binary einzeln signieren ==="
|
|
if [ "${CODESIGN_ADHOC}" = true ]; then
|
|
# Getrennt vom Bundle-weiten --deep-Signieren in Schritt 7: die Bridge ist eine zweite, lose
|
|
# liegende Mach-O-Executable direkt in Contents/MacOS (kein eigenes .framework/.bundle) - explizit
|
|
# zuerst signieren statt sich allein auf die Rekursion von --deep zu verlassen, damit die
|
|
# Bridge auch dann ein gültiges Signat trägt, falls --deep das nicht selbst abdeckt.
|
|
codesign --force --sign - "${MACOS_DIR}/${BRIDGE_EXECUTABLE_NAME}"
|
|
echo " -> Bridge ad-hoc signiert."
|
|
else
|
|
echo " -> Signierung übersprungen."
|
|
fi
|
|
|
|
echo "=== 7/7: Bundle-weit signieren ==="
|
|
if [ "${CODESIGN_ADHOC}" = true ]; then
|
|
codesign --force --deep --sign - "${APP_BUNDLE}"
|
|
echo " -> Ad-hoc signiert (nur lokal lauffähig, Gatekeeper-Warnung bei Weitergabe)."
|
|
else
|
|
echo " -> Signierung übersprungen."
|
|
fi
|
|
|
|
echo ""
|
|
echo "Fertig! App-Bundle liegt unter: ${APP_BUNDLE}"
|
|
echo "Test mit: open '${APP_BUNDLE}'"
|
|
echo "Bridge-Binary für die MCP-Server-Config eines KI-Clients: ${APP_BUNDLE}/Contents/MacOS/${BRIDGE_EXECUTABLE_NAME}"
|