XCFrameworks auf einem Cloud Mac bauen und verteilen: Binärcache für Multi-Repo-Integration

DevOps & CI/CD ·ca. 5 Min. Lesezeit

XCFrameworks auf einem Cloud Mac bauen und verteilen: Binärcache für Multi-Repo-Integration

XCFrameworks auf einem Cloud Mac bauen und verteilen: Binärcache für Multi-Repo-Integration

Letzte Woche wurden in drei Repositories gleichzeitig Commits gepusht, und in der CI wurde dasselbe interne Audio-Processing-Framework dreimal neu kompiliert – jedes Mal mit einem Archive-Lauf von fast acht Minuten. Als der dritte Build dann mit einer abgelaufenen Signaturzertifikat-Meldung fehlschlug, fragte ein Kollege im Bereitschaftsdienst im Chat: „Das Ding wurde doch gestern erst gebaut, warum muss jedes Repo das nochmal selbst machen?" Diese Frage trifft den Kern eines Problems, das viele Teams haben: Ein gemeinsam genutztes Binärframework sollte eigentlich nur einmal gebaut werden – aber ohne Caching-Mechanismus wird daraus doppelte Arbeit, bei der jede Pipeline unabhängig ihr eigenes Ding macht.

Auf einem dedizierten Cloud Mac von HireVPS haben wir diesen Workflow neu aufgesetzt: Ein Multi-Slice-XCFramework wird zentral gebaut, signiert, mit Prüfsumme versehen und landet dann in einem schlanken lokalen Binärcache. Mehrere Repositories laden das Artefakt direkt herunter, statt es jeweils selbst neu zu bauen. Im Folgenden die konkrete Vorgehensweise.

Warum man XCFrameworks selbst cachen sollte

Der Swift Package Manager unterstützt binaryTarget, um direkt auf ein vorkompiliertes XCFramework zu verweisen – das ist an sich nichts Neues. Damit das aber wirklich funktioniert, muss das Artefakt vertrauenswürdig, nachvollziehbar und für mehrere Parteien gemeinsam nutzbar sein. Baut jede Repo-CI ihren eigenen Archive-Lauf, erhält man in Wirklichkeit mehrere Binärdateien, die theoretisch identisch, praktisch aber nie wirklich auf Übereinstimmung geprüft sind. Gibt es dann einen kleinen Unterschied in der Build-Umgebung – etwa ein Xcode-Minor-Update –, wird die Fehlersuche mühsam. Nur zentrale Kompilierung und einheitliche Verteilung garantieren, dass alle Downstream-Konsumenten exakt dasselbe Artefakt erhalten.

Erfahrungswert: Jedes interne Framework, das von 3 oder mehr Repositories referenziert wird, lohnt sich für einen eigenen Binärcache. Bei weniger als 3 Referenzen können die Kosten für wiederholte Builds unter dem Aufwand für die Cache-Pflege liegen.

Der Ressourcenvorteil eines Cloud Mac

Dieser Workflow stellt zwei harte Anforderungen an die Maschine: Erstens braucht man eine dedizierte macOS-Umgebung (GUI und Kommandozeile), um einen vollständigen xcodebuild archive-Lauf durchzuführen. Zweitens erzeugt das Kompilieren mehrerer Architektur-Slices erhebliche Disk-I/O-Last – auf gemeinsam genutzten Maschinen bremsen sich Nachbar-Jobs schnell gegenseitig aus. Bei HireVPS mietet man tageweise eine M4- oder M4-Pro-Maschine, wählt einen Standort und erhält anschließend eigene SSH-Zugangsdaten mit sofort nutzbaren Root-Rechten. Man muss sich keine CPU-Zeitscheiben mit anderen teilen, und auch ein mehrstündiger Multi-Architektur-Build wird nicht durch fremde Jobs unterbrochen. Welche Maschine und welcher Standort aktuell verfügbar sind, lässt sich einfach im Control Panel prüfen.

Multi-Slice-XCFramework bauen

Ein XCFramework, das sowohl auf echten Geräten als auch im Simulator funktionieren soll, braucht mindestens zwei Slices: ios-arm64 (echtes Gerät) und ios-arm64-simulator (Simulator auf Apple Silicon). Nutzt das Team noch einen Intel-Mac für Simulator-Tests, sollte man ios-x86_64-simulator ergänzen – sonst meldet diese Maschine bei jedem Testlauf einen Architektur-Mismatch.

Beide Slices getrennt archivieren

xcodebuild archive \
  -scheme AudioCore \
  -destination "generic/platform=iOS" \
  -archivePath build/AudioCore-iOS.xcarchive \
  SKIP_INSTALL=NO \
  BUILD_LIBRARY_FOR_DISTRIBUTION=YES

xcodebuild archive \
  -scheme AudioCore \
  -destination "generic/platform=iOS Simulator" \
  -archivePath build/AudioCore-iOSSim.xcarchive \
  SKIP_INSTALL=NO \
  BUILD_LIBRARY_FOR_DISTRIBUTION=YES

Zu einem XCFramework zusammenführen

xcodebuild -create-xcframework \
  -framework build/AudioCore-iOS.xcarchive/Products/Library/Frameworks/AudioCore.framework \
  -framework build/AudioCore-iOSSim.xcarchive/Products/Library/Frameworks/AudioCore.framework \
  -output build/AudioCore.xcframework

Nach Abschluss des Builds wird ein zip-Archiv erstellt – das ist das finale, zu verteilende Artefakt. Danach sollte man es nicht mehr manuell verändern.

Signieren und Prüfsumme bilden

Auch interne Frameworks sollte man signieren, damit Downstream-Konsumenten die Herkunft verifizieren können:

codesign --sign "Apple Distribution: Your Team" \
  --timestamp \
  build/AudioCore.xcframework

zip -r AudioCore-1.4.0.xcframework.zip build/AudioCore.xcframework

Nach dem Signieren und Packen berechnet man mit dem in SwiftPM integrierten Befehl die Prüfsumme – dieser Wert muss anschließend in die Package.swift des Downstream-Repos eingetragen werden:

swift package compute-checksum AudioCore-1.4.0.xcframework.zip

Der häufigste Grund für eine fehlgeschlagene Prüfsummen-Verifikation ist, dass der zip-Inhalt nach dem Packen nochmal verändert wurde, oder dass ein anderes Kompressionstool zu einer anderen internen Dateireihenfolge geführt hat. Das Prinzip ist einfach: Sobald die Prüfsumme berechnet ist, wird diese zip-Datei eingefroren und nicht mehr angefasst.

Lokalen Binärcache aufsetzen

Man braucht keinen komplexen Dienst – ein Verzeichnisbaum, der statische Dateien zum Download bereitstellt, reicht völlig aus:

Ebene Inhalt Beispielpfad
Framework-Name Ein Verzeichnis pro internem Framework /cache/AudioCore/
Versionsnummer Ein Unterverzeichnis pro Version /cache/AudioCore/1.4.0/
Artefakt zip-Paket + Prüfsummen-Textdatei AudioCore-1.4.0.xcframework.zip, checksum.txt

In der Package.swift des Downstream-Repos wird es so referenziert:

.binaryTarget(
    name: "AudioCore",
    url: "https://cache.internal.example/AudioCore/1.4.0/AudioCore-1.4.0.xcframework.zip",
    checksum: "Im vorherigen Schritt berechnete Prüfsumme"
)

Für die Speicherplanung reicht es bei einem mittelgroßen Team, 3-4 Kern-Frameworks mit jeweils den letzten 10 Versionen aufzubewahren. Ein einzelnes gepacktes Archiv liegt meist zwischen 20 und 80 MB, die Gesamtmenge lässt sich auf wenige GB begrenzen. Alte Versionen, die seit über 90 Tagen nicht mehr abgerufen wurden, kann man regelmäßig bereinigen.

In Multi-Repo-CI-Pipelines integrieren

Die CI der jeweiligen Downstream-Repos führt kein xcodebuild archive mehr aus, sondern ruft nur noch swift package resolve auf, um die angegebene Version herunterzuladen – die Zeit sinkt von mehreren Minuten Kompilierzeit auf wenige Sekunden Download. Für eine neue Version legt man im Cache-Verzeichnis einfach ein neues Versionsverzeichnis an und aktualisiert die entsprechende Checksum-Referenz im jeweiligen Repo – am Cache-Server-Code selbst muss nichts geändert werden.

Fallstrick: Mehrere Repositories sollten niemals gleichzeitig auf ein veränderliches Tag wie „latest" verweisen. Der Prüfsummen-Mechanismus setzt voraus, dass URL und Inhalt eindeutig einander zugeordnet sind – erlaubt man das Überschreiben der Datei unter derselben Versionsnummer, verliert die Integritätsprüfung von SwiftPM ihren Sinn.

Checkliste

Häufig gestellte Fragen

Welche Slices braucht ein XCFramework für Gerät und Simulator gleichzeitig?

Mindestens ios-arm64 für echte Geräte und ios-arm64-simulator für Apple-Silicon-Simulatoren; läuft in eurem Team noch ein Intel Mac mit Simulator, kommt ios-x86_64-simulator dazu — sonst schlägt der Test dort sofort mit Architekturfehler fehl.

Warum schlägt die Checksum-Prüfung des binaryTarget fehl?

Meist wurde die zip-Datei nach dem Packen nochmal manuell verändert, oder ein anderes Kompressionstool hat die interne Dateireihenfolge geändert; korrekt ist, swift package compute-checksum genau einmal auf die final zu verteilende zip-Datei anzuwenden und diese danach nicht mehr anzurühren.

Wie viel Speicher sollte ein lokaler Binärcache-Server einplanen?

Für ein mittelgroßes Team, das die letzten 10 Versionen von 3-4 Kernframeworks behält, liegt eine komprimierte Datei meist zwischen 20-80MB — insgesamt reichen wenige GB; am besten auf einer eigenen Partition der SSD des Cloud Mac ablegen und Versionen ohne Zugriff seit 90+ Tagen regelmäßig löschen.

HireVPS Cloud Mac

Testen Sie noch heute einen dedizierten Cloud Mac mini

Tagesweise mietbar, SSH/VNC-Zugangsdaten in nur 2 Minuten, Konfiguration jederzeit upgradbar.

Jetzt Mac mini mieten