Engineering-Guides

Eine Build-Prüfung für iOS und Mac Catalyst einrichten

Eine Build-Prüfung für iOS und Mac Catalyst einrichten

Wenn ein ursprünglich nur für iPhone und iPad vorgesehenes Projekt Mac Catalyst aktiviert, liegt die Annahme nahe: „Wenn es für iOS kompiliert, sollte auch der Mac-Build funktionieren.“ In automatisierten Jobs auf einem Cloud-Mac treten Probleme jedoch meist bei Plattformdeklarationen von Abhängigkeiten, der Ressourcenzuordnung, der bedingten Kompilierung und gemeinsam genutzten Caches auf. Es reicht daher nicht, einen vorhandenen Befehl lediglich um eine destination zu ergänzen. Beide Plattformen sollten als Build-Produkte behandelt werden, die denselben Quellcode verwenden, aber unabhängig voneinander validiert werden.

Zuerst die tatsächliche Unterstützung beider Ziele prüfen

Aktivieren Sie zunächst Mac Catalyst in den Target-Einstellungen von Xcode und prüfen Sie anschließend, ob die entsprechende Änderung an der Projektdatei eingecheckt wurde. Verlassen Sie sich nicht nur auf das Häkchen in der grafischen Oberfläche. Automatisierte Jobs sollten den endgültigen Wert direkt aus den Build-Einstellungen auslesen:

xcodebuild \
  -project DemoApp.xcodeproj \
  -scheme DemoApp \
  -showBuildSettings |
grep -E 'SUPPORTS_MACCATALYST|PRODUCT_BUNDLE_IDENTIFIER|SDKROOT'

SUPPORTS_MACCATALYST muss auf YES stehen. Wenn das Projekt einen Workspace verwendet, ersetzen Sie -project durch -workspace. Führen Sie danach xcodebuild -showdestinations aus und prüfen Sie, ob das gemeinsam genutzte scheme sowohl eine iOS Simulator- als auch eine Mac Catalyst-destination anbietet. Kann die Befehlszeile das scheme nicht finden, kontrollieren Sie zuerst, ob es als Shared markiert ist, statt wiederholt die destination-Zeichenfolge zu ändern.

Prüfen Sie außerdem für jedes Swift Package, jede Binärabhängigkeit und jedes interne Modul, welche Plattformen unterstützt werden. Dass ein Paket unter iOS kompiliert, bedeutet nicht automatisch, dass es Mac Catalyst als unterstützte Plattform deklariert. Bei Binärabhängigkeiten ohne verfügbaren Quellcode muss das XCFramework einen passenden Catalyst-Slice enthalten. Fehlt dieser Slice, wird der Fehler erst beim Linken sichtbar.

Caches und Ergebnisverzeichnisse isolieren

iOS und Catalyst erzeugen unterschiedliche Modul-Caches, Zwischenobjekte und Link-Artefakte. Wenn Continuous-Integration-Jobs dasselbe DerivedData-Verzeichnis verwenden, kann ein Fehler von der Ausführungsreihenfolge abhängen und nach einer Bereinigung vorübergehend verschwinden. Verwenden Sie deshalb feste, plattformspezifische Verzeichnisse und archivieren Sie auch die jeweiligen Ergebnis-Bundles:

set -euo pipefail

xcodebuild \
  -workspace DemoApp.xcworkspace \
  -scheme DemoApp \
  -destination 'generic/platform=iOS Simulator' \
  -derivedDataPath build/derived-ios \
  -resultBundlePath build/results-ios.xcresult \
  CODE_SIGNING_ALLOWED=NO \
  build-for-testing

xcodebuild \
  -workspace DemoApp.xcworkspace \
  -scheme DemoApp \
  -destination 'platform=macOS,variant=Mac Catalyst,arch=arm64' \
  -derivedDataPath build/derived-catalyst \
  -resultBundlePath build/results-catalyst.xcresult \
  CODE_SIGNING_ALLOWED=NO \
  build-for-testing

Die Signierung wird hier zunächst deaktiviert, damit sich die Prüfung auf Quellcode, Abhängigkeiten und Linkbarkeit konzentriert. Archivierung und Signierung für die Auslieferung gehören in nachgelagerte Jobs. So werden Fehler bei der Plattformkompatibilität und Fehler in der Signierungskonfiguration nicht in demselben Protokoll vermischt.

Das vollständige Löschen von ~/Library/Developer/Xcode/DerivedData sollte nicht als Standardlösung dienen. Löschen Sie gezielt nur das Verzeichnis des aktuellen Jobs. Dadurch bleiben andere Jobs auf demselben Rechner unbeeinflusst, und der Fehlerzustand kann weiterhin untersucht werden.

Plattformunterschiede zentral kapseln

Bei Catalyst unterscheiden sich der UI-Lebenszyklus, Menüs, das Fensterverhalten und einige Systemfunktionen von iOS. Bündeln Sie die bedingte Kompilierung in einer Anpassungsschicht, statt targetEnvironment(macCatalyst) über den gesamten Anwendungscode zu verteilen.

enum PlatformLayout {
    static var usesDesktopNavigation: Bool {
        #if targetEnvironment(macCatalyst)
        return true
        #else
        return false
        #endif
    }
}

Plattformspezifische APIs benötigen zusätzlich eine Verfügbarkeitsprüfung. Die bedingte Kompilierung beschreibt lediglich das aktuelle Build-Ziel; sie garantiert nicht, dass die API auch in der zur Laufzeit verwendeten Systemversion vorhanden ist. Definieren Sie für gemeinsame Protokolle zunächst eine stabile Schnittstelle und implementieren Sie diese anschließend getrennt für iOS und Catalyst. So lassen sich die Verhaltensweisen beider Plattformen direkt mit Unit-Tests prüfen.

Auch Ressourcen müssen separat kontrolliert werden. Schriftarten, Datenschutzbeschreibungen, Lokalisierungsressourcen und Datenmodelle können aufgrund einer fehlenden Auswahl bei Target Membership nur in einem der beiden Produkte landen. Prüfen Sie nach dem Build im Produktverzeichnis, ob die erforderlichen Dateien vorhanden sind, statt erst zur Laufzeit eine leere Oberfläche zu entdecken.

Prüfpunkt iOS Mac Catalyst
Abhängigkeiten linkbar Simulator-Slice verfügbar Catalyst-Slice verfügbar
Ressourcenzuordnung Im App-Bundle vorhanden Im Catalyst-App-Bundle vorhanden
Bedingte Kompilierung Mobiler Zweig Desktop-Anpassungszweig
Ergebnisnachweis Eigenständiges xcresult Eigenständiges xcresult

Fehler in klar zuordenbare Phasen aufteilen

Ein Job für zwei Ziele sollte nicht nur einen allgemeinen Status „Build fehlgeschlagen“ liefern. Teilen Sie den Ablauf nacheinander in Abhängigkeitsauflösung, Kompilierung ohne Signierung, Unit-Tests, Archivierung und Abnahme für die Auslieferung auf. Die nächste Phase sollte erst beginnen, wenn die vorherige erfolgreich war. Benennen Sie auch die Protokolle nach Plattform.

Bei Kompilierungsfehlern zuerst vier Signale prüfen

Bei No such module prüfen Sie zuerst die Plattformdeklaration der Abhängigkeit und das Build-Ziel. Bei einer nicht passenden Architektur kontrollieren Sie die Binär-Slices, statt sofort global ausgeschlossene Architekturen hinzuzufügen. Bei einer unavailable API ergänzen Sie in der Anpassungsschicht die bedingte Kompilierung und die Verfügbarkeitsprüfung. Bei doppelten oder fehlenden Ressourcen kontrollieren Sie Copy Bundle Resources und Target Membership.

Auch Build-Skripte können plattformspezifische Annahmen enthalten. Wenn ein Skript das SDK beispielsweise fest auf iphoneos setzt, verwendet der Catalyst-Job einen falschen Pfad. Skripte sollten die von Xcode bereitgestellten Werte PLATFORM_NAME, SDKROOT und TARGET_BUILD_DIR verwenden und bei einer unbekannten Plattform sofort beendet werden.

Checkliste vor dem Zusammenführen einführen

Die minimale Build-Prüfung muss gewährleisten, dass beide Ziele in einem sauberen Verzeichnis ihre Abhängigkeiten auflösen und ohne Signierung kompilieren können. Für jedes Ziel ist eine eigene .xcresult-Datei zu speichern. Die Unit-Tests der Kernmodule sollten die Schnittstellen der Plattformanpassung abdecken. Für Funktionen rund um Fenster, Menüs oder Drag-and-drop sind zusätzlich Catalyst-spezifische Tests vorzusehen.

Prüfen Sie vor dem Commit die folgenden Punkte in dieser Reihenfolge:

  • Das scheme ist freigegeben, und beide destination-Einträge lassen sich über die Befehlszeile auflisten.
  • iOS und Catalyst verwenden getrennte DerivedData-Verzeichnisse.
  • Die Abhängigkeiten unterstützen ausdrücklich beide Plattformen, und alle erforderlichen Binär-Slices sind vorhanden.
  • Plattformabfragen sind in der Anpassungsschicht gebündelt, und gemeinsame Logik ist nicht doppelt implementiert.
  • Die wesentlichen Ressourcen sind in beiden Produkten enthalten.
  • Fehlerprotokolle, Ergebnis-Bundles und Snapshots der Build-Einstellungen werden nach Plattform archiviert.
  • Die Kompilierungsprüfung und der Job für Archivierung und Signierung werden getrennt ausgeführt.

Führen Sie die Pipeline bei der ersten Einrichtung auf G-Mini oder in einer anderen Remote-Build-Umgebung zweimal hintereinander aus und vertauschen Sie anschließend die Reihenfolge der beiden Ziele. Ändert sich das Ergebnis mit der Reihenfolge, prüfen Sie zuerst gemeinsam genutzte Caches, temporäre Skriptverzeichnisse und nicht bereinigte generierte Dateien. Eine stabile Pipeline für zwei Ziele zeichnet sich nicht dadurch aus, dass beide Builds gelegentlich gleichzeitig erfolgreich sind, sondern durch konsistente Ergebnisse aus einem sauberen Workspace und in jeder beliebigen Ausführungsreihenfolge.

Häufig gestellte Fragen

Sollten iOS und Mac Catalyst dasselbe DerivedData verwenden?

Für kurze lokale Versuche ist das möglich, in CI sollten die Verzeichnisse getrennt sein. So bleiben Zwischenprodukte isoliert und Fehler lassen sich gezielt bereinigen.

Muss die erste Prüfung bereits alle Tests ausführen?

Nein. Beginnen Sie mit einer Kompilierung ohne Signierung und ausgewählten Unit-Tests für beide Ziele. UI-Tests, Archive und Signaturprüfungen folgen später.

Was ist bei einem Catalyst-Fehler zuerst zu prüfen?

Prüfen Sie Catalyst-Unterstützung, Abhängigkeiten, bedingte Kompilierung, Target Membership der Ressourcen und plattformspezifische Build Phases.

Exklusiver physischer Knoten

Cloud-Mac für die nächste Build-Warteschlange vorbereiten

Vergleichen Sie zwei M4-Konfigurationen an fünf Standorten und wählen Sie die passende Tages-, Wochen-, Monats- oder Quartalsmiete für Ihren Arbeitszyklus.

Cloud-Mac-Tarif auswählen