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
- Decken die Slices sowohl echte Geräte als auch alle benötigten Simulator-Architekturen ab?
- Wird die Prüfsumme unmittelbar nach dem Packen berechnet, und bleibt das zip danach unverändert?
- Wird das Versionsverzeichnis im Cache nur einmal geschrieben und nicht überschrieben?
- Stimmt die Checksum in der Downstream-
Package.swiftmit der tatsächlichen Datei überein? - Wurden Altversionen ohne Abruf seit 90 Tagen bereinigt?
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.