Ein Archiv, das sowohl eine iOS-App als auch eine watchOS-App enthält, kann trotz erfolgreichem Build der Haupt-App nicht auslieferbar sein: Die Watch-App wurde nicht eingebettet, ihre Buildnummer liegt eine Version zurück, die Erweiterung verweist auf eine alte Bundle ID oder eine verschachtelte Komponente wurde nicht korrekt signiert. Solche Probleme sollten nicht erst beim Export oder Einreichen auffallen. Zuverlässiger ist es, nach jeder Erzeugung eines xcarchive auf dem Cloud-Mac direkt die Struktur des endgültigen Pakets zu prüfen und das Ergebnis als Gate in der Pipeline zu verwenden.
Warum das fertige Archiv geprüft werden muss
Projektdateien beschreiben den Sollzustand, das xcarchive ist dagegen das tatsächlich auszuliefernde Artefakt. Unterschiedliche Konfigurationsdateien, Build-Skripte und Umgebungsvariablen können Versionsnummern oder Produktkennungen überschreiben. Das bloße Auslesen der Projekteinstellungen beweist weder, dass die watchOS-App im Verzeichnis Watch der Haupt-App enthalten ist, noch dass die Erweiterung korrekt mit der umgebenden App verknüpft ist.
Als Prüfobjekt empfiehlt sich stets ein Release-Archiv und nicht ein Zwischenverzeichnis in DerivedData. Erzeugen Sie das Artefakt zunächst mit demselben Workspace, Scheme und derselben Konfiguration wie in der produktiven Pipeline:
set -euo pipefail
ROOT="$PWD"
OUT="$ROOT/out"
ARCHIVE="$OUT/Client.xcarchive"
rm -rf "$ARCHIVE"
mkdir -p "$OUT"
xcodebuild \
-workspace Client.xcworkspace \
-scheme Client \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath "$ARCHIVE" \
clean archive
clean archive eignet sich für eine grundlegende Abnahme. Die reguläre Pipeline kann entsprechend ihrer Cache-Strategie angepasst werden, doch das Prüfskript muss immer das vom aktuellen Auftrag erzeugte Archiv lesen. Es darf nicht auf irgendein „zuletzt erzeugtes“ Verzeichnis zurückgreifen.
Das Gate muss die Frage „Was enthält das Paket, das jetzt ausgeliefert werden soll?“ beantworten – nicht „Was sollte das Projekt theoretisch erzeugen?“.
Komponenten anhand der Paketstruktur erfassen
Der typische Pfad lautet Products/Applications/*.app. Das darin enthaltene Verzeichnis Watch enthält die watchOS-App, deren Verzeichnis PlugIns wiederum die Erweiterung enthält. Produktnamen sollten nicht im Skript fest codiert werden. Nach dem Umbenennen eines Targets funktionieren feste Pfade schnell nicht mehr.
IOS_APP=$(find "$ARCHIVE/Products/Applications" \
-maxdepth 1 -type d -name '*.app' -print -quit)
test -n "${IOS_APP:-}" || {
printf '%s
' "iOS app not found"
exit 1
}
WATCH_APP=$(find "$IOS_APP/Watch" \
-maxdepth 1 -type d -name '*.app' -print -quit)
test -n "${WATCH_APP:-}" || {
printf '%s
' "watchOS app not found"
exit 1
}
WATCH_EXTENSION=$(find "$WATCH_APP/PlugIns" \
-maxdepth 1 -type d -name '*.appex' -print -quit)
printf 'ios=%s
watch=%s
extension=%s
' \
"$IOS_APP" "$WATCH_APP" "${WATCH_EXTENSION:-embedded}"
Einige neuere Projekte besitzen keine separate .appex-Ebene. Eine fehlende Erweiterung ist deshalb nicht automatisch ein Fehler. Das Gate muss sich nach der im Repository festgelegten Produktstruktur richten: Bei der älteren Struktur sind alle drei Komponentenebenen erforderlich, bei einer neueren Struktur mit nur einem Target dagegen lediglich die iOS-App und die watchOS-App. Statt den Projekttyp aus den Verzeichnissen abzuleiten, kann eine kurze Liste der erwarteten Struktur im Repository hinterlegt werden.
Beziehungen zwischen Versionen und Kennungen validieren
Führen Sie zunächst für jede Info.plist den Befehl plutil -lint aus und lesen Sie anschließend CFBundleIdentifier, CFBundleShortVersionString und CFBundleVersion. Bei derselben Veröffentlichung sollten Marketingversion und Buildnummer von iOS und watchOS üblicherweise vollständig übereinstimmen. Falls das Team getrennte Buildnummern verwendet, müssen die zulässigen Regeln ausdrücklich im Skript festgehalten werden.
| Prüfpunkt | Empfohlene Regel | Risiko bei einem Fehler |
|---|---|---|
| Marketingversion | Unter iOS und watchOS identisch | Inkonsistente Versionsbeziehung im Store |
| Buildnummer | Innerhalb desselben Release-Auftrags identisch | Die Watch-App wird als älterer Build erkannt |
| Begleit-App-Kennung | Verweist auf die ID der iOS-Haupt-App | Nach der Installation kann keine Begleitbeziehung hergestellt werden |
| Zuordnung der Erweiterung | Verweist auf die ID der aktuellen watchOS-App | Erweiterung und umgebende App sind falsch zugeordnet |
read_plist() {
/usr/libexec/PlistBuddy -c "Print :$2" "$1"
}
IOS_PLIST="$IOS_APP/Info.plist"
WATCH_PLIST="$WATCH_APP/Info.plist"
plutil -lint "$IOS_PLIST" "$WATCH_PLIST"
IOS_ID=$(read_plist "$IOS_PLIST" CFBundleIdentifier)
WATCH_ID=$(read_plist "$WATCH_PLIST" CFBundleIdentifier)
IOS_VERSION=$(read_plist "$IOS_PLIST" CFBundleShortVersionString)
WATCH_VERSION=$(read_plist "$WATCH_PLIST" CFBundleShortVersionString)
IOS_BUILD=$(read_plist "$IOS_PLIST" CFBundleVersion)
WATCH_BUILD=$(read_plist "$WATCH_PLIST" CFBundleVersion)
COMPANION_ID=$(read_plist "$WATCH_PLIST" WKCompanionAppBundleIdentifier)
test "$IOS_VERSION" = "$WATCH_VERSION"
test "$IOS_BUILD" = "$WATCH_BUILD"
test "$COMPANION_ID" = "$IOS_ID"
Ersetzen Sie den exakten Vergleich nicht durch die Regel, dass die Bundle ID von watchOS mit der Bundle ID von iOS beginnen müsse. Ein Team kann explizite Kennungen verwenden, und ähnliche Präfixe beweisen keine korrekte Verknüpfung. Der erwartete Wert muss aus der Release-Konfiguration gelesen und der vollständige Inhalt von WKCompanionAppBundleIdentifier geprüft werden.
Strukturen mit Erweiterung unterstützen
Falls das Archiv eine .appex enthält, lesen Sie zusätzlich deren WKAppBundleIdentifier aus und verlangen Sie, dass der Wert mit der Bundle ID der aktuellen watchOS-App übereinstimmt. Erfassen Sie außerdem die Version und Buildnummer der Erweiterung selbst. So lässt sich verhindern, dass bei einem Target das gemeinsame Skript zur Versionspflege nicht ausgeführt wurde.
Signaturen und ausführbare Architekturen prüfen
Eine erfolgreiche Signierung der äußeren App bedeutet nicht, dass sich jede verschachtelte Komponente verifizieren lässt. Statt nur die Haupt-App rekursiv zu prüfen, sollten die tatsächlich vorhandenen Komponenten einzeln erfasst werden. Dadurch zeigt das Protokoll eindeutig, bei welchem Objekt die Prüfung fehlgeschlagen ist:
verify_component() {
local item="$1"
codesign --verify --strict --verbose=2 "$item"
codesign -d --entitlements :- "$item" >/dev/null
}
verify_component "$IOS_APP"
verify_component "$WATCH_APP"
if test -n "${WATCH_EXTENSION:-}"; then
verify_component "$WATCH_EXTENSION"
fi
Ermitteln Sie anschließend über den jeweiligen Eintrag CFBundleExecutable die Binärdatei und protokollieren Sie ihre Architekturen mit file oder lipo -archs. Das Gate muss keine bestimmte Architekturgruppe dauerhaft festschreiben, da sich Toolchain und Deployment Target ändern können. Zuverlässiger ist eine Regel, die leere ausführbare Dateien und unerwartete Simulator-Artefakte ablehnt und die aktuelle Architekturliste zur Überprüfung speichert.
Prüfen Sie die verschachtelten Komponenten außerdem auf symbolische Links, die aus dem zulässigen Bereich herausführen, doppelte Bundle IDs und zusätzliche, nicht deklarierte Erweiterungen. Solche Probleme entstehen häufig durch Kopierskripte mit zu weit gefassten Platzhaltern.
Prüfergebnisse als stabiles CI-Gate nutzen
Der Exitcode des Skripts stoppt die Ausführung, während der Bericht den Grund dafür erklärt. Für jeden Auftrag sollten die relativen Pfade der Komponenten, Bundle IDs, Versionen, Buildnummern, Architekturen, Ergebnisse der Signaturprüfung und die Prüfsumme des Archivs gespeichert werden. Private Zertifikatsschlüssel, sämtliche Umgebungsvariablen oder vollständige Pfade zu Benutzerverzeichnissen gehören nicht in den Bericht.
Es empfiehlt sich, den Ablauf in vier Schritte zu unterteilen:
- Ein eindeutiges Archivverzeichnis erzeugen und die Wiederverwendung des vorherigen Artefakts unterbinden.
- Komponenten ermitteln und mit der im Repository hinterlegten Sollstruktur vergleichen.
- Metadaten, Verknüpfungsfelder, Signaturen und ausführbare Architekturen validieren.
- Einen Text- oder JSON-Bericht ausgeben und anschließend den Exportauftrag ausführen.
Bei der erstmaligen Einführung kann das Gate einige Durchläufe lang nur protokollieren, ohne die Pipeline zu stoppen. So lässt sich bestätigen, dass sowohl ältere als auch neuere Projektstrukturen abgedeckt sind. Sobald die Regeln stabil sind, werden fehlende Komponenten, nicht übereinstimmende Versionen und Signaturfehler zu harten Fehlern. Wenn derselbe Cloud-Mac mehrere Archive parallel erzeugt, benötigt jeder Auftrag einen eigenen archivePath und ein separates Berichtsverzeichnis. Andernfalls könnte ein Auftrag die Ergebnisse eines anderen auslesen.
Das eigentliche Ziel besteht nicht darin, eine weitere formale Prüfung einzuführen. Die Pipeline soll vielmehr nachweisen können, dass Haupt-App, Watch-App und deren Erweiterung tatsächlich aus demselben Veröffentlichungskontext stammen, eindeutig miteinander verknüpft sind und das fertige Archiv in die Exportphase übergehen kann.
Häufig gestellte Fragen
Reicht eine Prüfung der Projekteinstellungen aus?
Nein. Erst das fertige Archiv enthält die tatsächlichen Ergebnisse aus Konfigurationswerten, Build-Skripten, Einbettung und Signierung.
Müssen iOS- und watchOS-Version übereinstimmen?
Für eine gemeinsame Veröffentlichung sollten Marketingversion und Buildnummer übereinstimmen. Abweichungen brauchen eine dokumentierte, maschinell geprüfte Regel.
Deine Mac-mini-Workflows dauerhaft in der Cloud ausführen
Wähle RunAMac M4, die Mietdauer und den Bereitstellungsstandort, und verlagere Entwicklungs-, Build- oder Testaufgaben auf einen dedizierten physischen Rechner.