Zwei Tage vor einem Release wird kurzfristig ein zusätzlicher Remote Mac bereitgestellt. Das Auschecken des Codes dauert nur wenige Minuten, doch der erste Xcode-Build zieht sich scheinbar endlos hin. Der zweite Durchlauf ist zwar schneller, aber nach einem Branch-Wechsel tauchen veraltete Ressourcen, fehlerhafte Indizes oder erneut aufgelöste Abhängigkeiten auf. Meist liegt das Problem nicht an der Leistung des Rechners, sondern daran, dass DerivedData, heruntergeladene Swift-Pakete und Build-Ergebnisse gemeinsam in den Standardverzeichnissen liegen. Dadurch lässt sich weder zuverlässig feststellen, ob der Cache greift, noch können relevante Daten vor Ablauf der Mietdauer gezielt exportiert werden.
Eine vergleichbare Build-Baseline festlegen
Bevor der Cache optimiert wird, müssen zunächst alle relevanten Variablen feststehen. Dokumentieren Sie die Ausgabe von xcodebuild -version, die macOS-Version, den Commit-Hash, das Scheme, die Build-Konfiguration und die Zielplattform. Aktualisieren Sie während der Tests weder Abhängigkeiten noch Xcode und ändern Sie auch keine Compiler-Optionen. Andernfalls sind die gemessenen Laufzeiten nicht miteinander vergleichbar.
Führen Sie mindestens drei Testreihen aus:
- Löschen Sie das projektspezifische DerivedData und führen Sie anschließend einen Cold Build aus.
- Ändern Sie keinen Code und führen Sie mit demselben Befehl einen Warm Build aus.
- Ändern Sie eine gewöhnliche Swift-Datei und führen Sie danach einen inkrementellen Build aus.
Speichern Sie für jeden Durchlauf Startzeit, Endzeit, Exit-Code und Ergebnis-Bundle. Die Fortschrittsanzeige der grafischen Oberfläche eignet sich zur Beobachtung, aber nicht für präzise Vergleiche. Automatisierte Messungen sollten einheitlich über xcodebuild erfolgen.
Ein Cache soll nicht einen einzelnen Build möglichst schnell erscheinen lassen, sondern bei identischen Eingaben für vorhersehbare Laufzeiten und Ergebnisse sorgen.
Caches nach Aufgabenbereichen trennen
Im Arbeitsverzeichnis sollten vier getrennte Pfadgruppen angelegt werden: Quellcode, DerivedData, heruntergeladene Swift-Pakete sowie Ergebnisse und Protokolle. Mehrere Projekte sollten sich kein gemeinsames DerivedData teilen. Ebenso dürfen sich Debug- und Release-Konfigurationen nicht gegenseitig überschreiben. Bei vielen Projekten können zusätzlich die Xcode-Hauptversion sowie eine Branch- oder Commit-Kennung in die Verzeichnisstruktur aufgenommen werden.
| Verzeichnis | Gespeicherte Inhalte | Empfohlene Strategie |
|---|---|---|
Source |
Git-Arbeitsbaum | Für jedes Projekt getrennt |
DerivedData |
Indizes und Zwischenprodukte | Nach Version, Scheme und Konfiguration isolieren |
SourcePackages |
Downloads und Checkouts von Swift-Paketen | Wiederverwenden, solange die Lockdatei unverändert bleibt |
Results |
Ergebnis-Bundles und Protokolle | Nach Build-Zeitpunkt archivieren |
Das Verzeichnis für Swift-Pakete kann wiederverwendet werden, sofern die Lockdatei der Abhängigkeiten identisch ist. Hat sich die Lockdatei geändert, sollte die reguläre Auflösung der Abhängigkeiten zunächst vollständig abgeschlossen werden. Kopieren Sie nicht einfach ein altes Checkout-Verzeichnis in das neue Projekt. DerivedData reagiert deutlich empfindlicher auf Änderungen der Toolchain. Nach einem Xcode-Wechsel sollte deshalb ein neues Verzeichnis angelegt werden, statt das bisherige Verzeichnis zu überschreiben und weiterzuverwenden.
Builds mit einem festen Befehl ausführen
Das folgende Skript legt alle wichtigen Pfade explizit fest. Ersetzen Sie Workspace, Scheme und Zielplattform durch die tatsächlichen Werte des Projekts. Der Pfad des Ergebnis-Bundles darf vor der Ausführung noch nicht vorhanden sein. Daher entfernt das Skript zunächst ein eventuell vorhandenes Verzeichnis gleichen Namens.
#!/bin/bash
set -euo pipefail
ROOT="${HOME}/BuildWorkspace"
SCHEME="Application"
CONFIGURATION="Release"
DERIVED="${ROOT}/DerivedData/${SCHEME}-${CONFIGURATION}"
PACKAGES="${ROOT}/SourcePackages"
RESULT="${ROOT}/Results/${SCHEME}.xcresult"
LOG="${ROOT}/Results/${SCHEME}.log"
mkdir -p "${DERIVED}" "${PACKAGES}" "${ROOT}/Results"
rm -rf "${RESULT}"
xcodebuild \
-workspace "${ROOT}/Source/Application.xcworkspace" \
-scheme "${SCHEME}" \
-configuration "${CONFIGURATION}" \
-destination "generic/platform=iOS" \
-derivedDataPath "${DERIVED}" \
-clonedSourcePackagesDirPath "${PACKAGES}" \
-resultBundlePath "${RESULT}" \
build | tee "${LOG}"
Führen Sie zunächst xcodebuild -list im Terminal aus und prüfen Sie, ob der Workspace das gewünschte Scheme tatsächlich bereitstellt. Verwendet das Projekt eine Projektdatei statt eines Workspace, ersetzen Sie -workspace durch -project. Übergeben Sie niemals beide Optionen gleichzeitig.
Eine stabile Kennung für das Cache-Verzeichnis erzeugen
Der Cache-Schlüssel sollte mindestens die Xcode-Version, die Prüfsumme der Lockdatei, das Scheme und die Konfiguration enthalten. Der Branch-Name kann ergänzt werden, sollte aber nie die einzige Kennung sein, da sich die Abhängigkeiten innerhalb desselben Branches ändern können.
XCODE_KEY="$(xcodebuild -version | shasum -a 256 | cut -c1-12)"
PACKAGE_KEY="$(shasum -a 256 Package.resolved | cut -c1-12)"
printf '%s-%s-%s-%s
' "${XCODE_KEY}" "${PACKAGE_KEY}" "${SCHEME}" "${CONFIGURATION}"
Enthält der Workspace mehrere Dateien namens Package.resolved, muss ausdrücklich die Datei ausgewählt werden, die tatsächlich am Build beteiligt ist. Wenn keine Lockdatei vorhanden ist, sollte kein fester leerer Schlüssel erzeugt werden. Andernfalls würden unterschiedliche Abhängigkeitsstände im selben Verzeichnis landen.
Unnötige Neukompilierungen phasenweise untersuchen
Ist auch ein Warm Build weiterhin langsam, sollte zunächst ermittelt werden, in welcher Phase die Zeit verloren geht. Wird die Auflösung der Abhängigkeiten wiederholt ausgeführt, prüfen Sie, ob ein Skript die Lockdatei neu schreibt und ob die Zugriffsrechte des Paketverzeichnisses stimmen. Läuft die Kompilierung immer wieder vollständig an, kontrollieren Sie Compiler-Bedingungen, automatisch erzeugte Dateien und Skripte in den Build-Phasen. Treten Auffälligkeiten beim Linken auf, prüfen Sie, ob bei jedem Durchlauf Versionsinformationen oder eingebettete Inhalte verändert werden.
Am häufigsten fehlen bei Skriptphasen deklarierte Ein- und Ausgaben. Sobald ein Skript bei jedem Lauf den Zeitstempel einer generierten Datei verändert, gelten alle nachgelagerten Targets als neu zu erstellen. Das Skript sollte die Datei deshalb nur ersetzen, wenn sich ihr Inhalt tatsächlich geändert hat. Zusätzlich müssen in der Xcode-Build-Phase präzise Ein- und Ausgabepfade eingetragen werden.
Das „Löschen aller Caches“ sollte nicht zum festen Arbeitsschritt werden. Damit lässt sich lediglich feststellen, ob der Cache verunreinigt ist, nicht aber, wodurch die Verunreinigung verursacht wurde. Verschieben Sie zuerst nur das DerivedData des Projekts. Besteht das Problem weiterhin, nehmen Sie anschließend das Verzeichnis der Swift-Pakete in den Blick. So bleiben Diagnoseinformationen erhalten und es müssen nicht bei jedem Versuch sämtliche Abhängigkeiten erneut heruntergeladen werden.
Vor Ablauf der Mietdauer nur wirklich relevante Daten sichern
Beenden Sie vor dem Export alle laufenden Builds und prüfen Sie, ob die Protokolle vollständig geschrieben wurden. Archivieren Sie anschließend die Lockdateien der Abhängigkeiten, benötigte Downloads von Swift-Paketen, Ergebnis-Bundles und Projektskripte. Beim Quellcode sollte der eingecheckte Stand im Repository maßgeblich sein. Nicht eingecheckte Änderungen im Arbeitsbaum müssen gesondert geprüft werden.
Es ist nicht empfehlenswert, das gesamte DerivedData langfristig aufzubewahren. Das Verzeichnis ist meist groß und enthält Indizes, Objektdateien sowie Zwischenprodukte, die an eine bestimmte Toolchain gebunden sind. Wird beim nächsten Einsatz eine andere Xcode-Version verwendet, ist eine Neuerstellung in der Regel zuverlässiger. Vertrauliche Zertifikate, private Schlüssel, Token und temporäre Umgebungsdateien gehören nicht in das Cache-Archiv. Bereinigen Sie zunächst den Shell-Verlauf, temporäre Schlüsselbunde und vom Projekt erzeugte Zugangsdaten. Kontrollieren Sie danach die Inhaltsliste des Exportpakets.
Bewahren Sie abschließend eine kurze Dokumentation auf: Toolchain-Version, Cache-Schlüssel, letzter erfolgreicher Commit, Build-Befehl, exportierte Pfade und ausdrücklich nicht exportierte Pfade. Einen neuen Cloud Mac anhand dieser Angaben wiederherzustellen, ist wesentlich reproduzierbarer als das gesamte Benutzerverzeichnis zu kopieren.
Häufig gestellte Fragen
Sollte DerivedData zwischen verschiedenen Xcode-Versionen geteilt werden?
Nein. Trennen Sie die Verzeichnisse mindestens nach Xcode-Version, Projekt, Scheme und Konfiguration. Nach einem Toolchain-Wechsel sollte DerivedData neu erzeugt werden.
Welche Cache-Daten sollte man vor Mietende exportieren?
Priorität haben Sperrdateien der Abhängigkeiten, wiederverwendbare Swift-Paket-Downloads, Ergebnis-Bundles und Diagnoseprotokolle. Objektdateien und Indizes sind groß und stark an die Toolchain gebunden.
Behebt das Löschen aller Caches langsame Builds?
Es kann einen beschädigten Cache bestätigen, ist aber keine dauerhafte Lösung. Messen Sie Abhängigkeitsauflösung, Kompilierung, Linken und Tests getrennt und bereinigen Sie nur den betroffenen Bereich.
ArmMacs Cloud Mac
Dedizierte physische Knoten projektbezogen nutzen
Wählen Sie Chip, Arbeitsspeicher, Speicher, Mietdauer und verfügbare Knoten fest aus. Sobald Bestand verfügbar ist, beginnt der Bereitstellungsprozess.