Parallele Xcode-Branches auf einem Cloud-Mac sauber isolieren

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

Parallele Xcode-Branches auf einem Cloud-Mac sauber isolieren

Während der Release-Branch noch die Regressionstests durchläuft, kann ein Produktionsfehler plötzlich einen Hotfix auf Basis eines älteren Commits erforderlich machen. Gleichzeitig muss der Feature-Branch weiterhin gebaut werden. Wer dafür im selben Verzeichnis wiederholt git switch ausführt, riskiert, dass nicht verfolgte Dateien, Zustände von Build-Skripten und Xcode-Zwischenprodukte unbemerkt zwischen den Aufgaben vermischt werden. Robuster ist es, auf einem dedizierten Cloud-Mac nur eine Git-Objektdatenbank vorzuhalten und mit Git Worktree für jede Aufgabe ein eigenes Arbeitsverzeichnis anzulegen.

Worktree beschleunigt nicht den Branch-Wechsel, sondern isoliert Zustände

Bei einer herkömmlichen Lösung mit mehreren Verzeichnissen wird das Repository meist mehrfach geklont. Bei großen Codebasen vervielfacht das den Speicherbedarf für Git-Objekte sowie den Zeitaufwand für Downloads und Wartung. Worktrees teilen sich die zugrunde liegende Objektdatenbank, besitzen aber jeweils einen eigenen ausgecheckten Stand, einen eigenen Index und einen eigenen HEAD. Damit eignen sie sich für die parallele Arbeit an Releases, Hotfixes und Features.

Xcode-Ausgaben werden dadurch jedoch nicht automatisch isoliert. Schreiben mehrere Worktrees weiterhin in das standardmäßige DerivedData-Verzeichnis, können sich Indizes, Modul-Caches und Zwischendateien gegenseitig überschreiben. Deshalb müssen sowohl die Quellverzeichnisse als auch die Build-Ausgaben getrennt werden.

Bereich Automatisch isoliert Empfehlung
Versionierter Quellcode Ja Für jede Aufgabe einen eigenen Branch verwenden
Nicht verfolgte Dateien Ja Zugangsdaten nicht im Repository-Verzeichnis ablegen
Git-Objekte Nein Gemeinsame Nutzung vermeidet redundanten Speicherverbrauch
DerivedData Nein Für jeden Worktree einen eigenen Pfad festlegen
Protokolle und Ergebnispakete Nein Verzeichnisse mit Branch-Kennung verwenden

Der Nutzen von Worktree liegt nicht in schnelleren Einzel-Builds, sondern darin, unsichtbare Zustandskonflikte zwischen parallelen Aufgaben zu vermeiden.

Erst die Verzeichnisstruktur festlegen, dann Worktrees anlegen

Das Haupt-Repository, die Worktrees und die Build-Artefakte sollten in drei gleichrangigen Bereichen liegen. Durch diese Trennung bleiben zu archivierende Testergebnisse erhalten, selbst wenn ein Worktree entfernt wird.

~/projects/mobile-app
~/worktrees/release-3.4
~/worktrees/hotfix-3.4.1
~/build-data/release-3.4
~/build-data/hotfix-3.4.1

Zunächst werden im Haupt-Repository die Remote-Referenzen aktualisiert. Anschließend lassen sich die Worktrees anlegen:

cd "$HOME/projects/mobile-app"
git fetch --prune
mkdir -p "$HOME/worktrees" "$HOME/build-data"

git worktree add "$HOME/worktrees/release-3.4" release/3.4
git worktree add -b hotfix/3.4.1 \
  "$HOME/worktrees/hotfix-3.4.1" origin/release/3.4
git worktree list

git worktree add verhindert standardmäßig, dass derselbe Branch gleichzeitig in zwei Worktrees ausgecheckt ist. Dieser Schutz sollte nicht mit erzwungenen Optionen umgangen werden. Soll lediglich ein älterer Commit geprüft werden, bietet sich ein Worktree mit detached HEAD an:

git worktree add --detach "$HOME/worktrees/audit-build" 8f32c1a

Branches stabil auf Verzeichnisnamen abbilden

Branch-Namen enthalten häufig / und können deshalb nicht unverändert als Name eines einzelnen Verzeichnisses verwendet werden. Automatisierungsskripte sollten Schrägstriche und Leerzeichen in Bindestriche umwandeln, den ursprünglichen Branch-Namen für Git-Befehle aber beibehalten. Leere Parameter müssen ebenfalls abgewiesen werden, damit ein Skript nicht versehentlich in das Stammverzeichnis oder ein gemeinsam genutztes Verzeichnis schreibt.

Für jeden Xcode-Job einen eigenen Ausgabepfad verwenden

Im Worktree legt -derivedDataPath den Speicherort der Zwischenprodukte fest. Mit -resultBundlePath werden Diagnoseinformationen zu Tests oder Builds gespeichert. Da der Pfad des Ergebnispakets vor der Ausführung noch nicht existieren darf, lässt sich mit einem Zeitstempel ein eindeutiger Name erzeugen.

set -euo pipefail

BRANCH="${1:?branch required}"
SAFE_NAME="$(printf '%s' "$BRANCH" | tr '/ ' '--')"
WORKTREE="$HOME/worktrees/$SAFE_NAME"
OUTPUT="$HOME/build-data/$SAFE_NAME"
STAMP="$(date '+%Y%m%d-%H%M%S')"

mkdir -p "$OUTPUT/logs" "$OUTPUT/results"

cd "$WORKTREE"
xcodebuild \
  -workspace MobileApp.xcworkspace \
  -scheme MobileApp \
  -configuration Debug \
  -derivedDataPath "$OUTPUT/DerivedData" \
  -resultBundlePath "$OUTPUT/results/$STAMP.xcresult" \
  build 2>&1 | tee "$OUTPUT/logs/$STAMP.log"

Auch innerhalb desselben Worktrees können zwei Jobs gleichzeitig laufen. Eine Trennung nur nach Branch reicht dann nicht aus. Zusätzlich sollte eine Job-ID in den Pfad aufgenommen werden, beispielsweise release-3.4/job-17/DerivedData. Archivierungsjobs benötigen außerdem einen eigenen Archivpfad, damit ein später gestarteter Job nicht die Ergebnisse des vorherigen überschreibt.

Modul-Caches nicht unbedacht gemeinsam nutzen

Ein gemeinsamer Cache wirkt zunächst platzsparend. Verwenden zwei Branches jedoch unterschiedliche Compiler-Optionen, Toolchains oder Generierungsskripte, ist ein fälschlich wiederverwendeter Zustand oft schwerer zu diagnostizieren als ein vollständiger Neuaufbau. Zunächst sollte mit einer vollständig isolierten Konfiguration eine stabile Ausgangsbasis geschaffen werden. Erst wenn Werkzeugversionen, Lock-Dateien der Abhängigkeiten und Build-Parameter übereinstimmen, kann die gemeinsame Nutzung schreibgeschützter Download-Ressourcen geprüft werden. Das gesamte DerivedData-Verzeichnis sollte nicht direkt geteilt werden.

Vor dem Parallelbetrieb vier Abnahmeschritte durchführen

Bevor ein Worktree erstmals für einen Build eingesetzt wird, sollten Commit, Arbeitsverzeichnisstatus und tatsächlich verwendete Build-Einstellungen protokolliert werden:

git rev-parse HEAD
git status --porcelain
xcodebuild -version
xcodebuild \
  -workspace MobileApp.xcworkspace \
  -scheme MobileApp \
  -showBuildSettings > "$OUTPUT/build-settings.txt"

git status --porcelain sollte keine Ausgabe erzeugen. Sind generierte Dateien vorhanden, ist zunächst zu prüfen, ob sie ignoriert werden sollten. Das Problem darf nicht einfach mit einem Bereinigungsbefehl verdeckt werden. Anschließend werden gleichzeitig zwei risikoarme Debug-Builds gestartet. Dabei ist zu kontrollieren, ob Protokolle, Ergebnispakete und DerivedData jeweils in den vorgesehenen Verzeichnissen landen.

Bei der Abnahme sind mindestens folgende Punkte zu prüfen:

  1. Die von beiden Jobs protokollierten Commit-Hashes stimmen mit den Ziel-Branches überein.
  2. Das Löschen von DerivedData eines Jobs beeinträchtigt den anderen Job nicht.
  3. Die Namen von Ergebnispaketen, Protokollen und Archiven sind eindeutig.
  4. Aus einem Fehlerprotokoll lassen sich Worktree, Commit und Build-Konfiguration ermitteln.
  5. Arbeitsspeicher und freier Festplattenspeicher reichen für die parallelen Jobs aus.

Worktrees und Build-Artefakte sicher entfernen

Vor dem Löschen wird im betreffenden Worktree git status --short ausgeführt. Außerdem muss sichergestellt sein, dass kein xcodebuild-Prozess mehr aktiv ist. Aufzubewahrende Protokolle, Ergebnispakete und Archive sind aus temporären Verzeichnissen zu verschieben. Danach werden im Haupt-Repository folgende Befehle ausgeführt:

cd "$HOME/projects/mobile-app"
git worktree remove "$HOME/worktrees/hotfix-3.4.1"
git worktree prune
git worktree list

Enthält das Verzeichnis nicht übernommene Änderungen, verweigert git worktree remove den Vorgang. --force sollte nicht zur routinemäßigen Bereinigung verwendet werden. Änderungen müssen zuerst committet, an einer eindeutig festgelegten Stelle zwischengespeichert oder nach manueller Prüfung bewusst verworfen werden. Auch Build-Artefakte sollten gezielt nach Aufgabenverzeichnis gelöscht werden. Platzhalter mit unklarem Geltungsbereich sind zu vermeiden.

Im Dauerbetrieb kann ein Worktree-Verzeichnis in das Aufgabensystem aufgenommen werden. Darin werden Branch, zuständige Person, Erstellungszeitpunkt, Ausgabeverzeichnis und geplanter Freigabezeitpunkt dokumentiert. So belegen veraltete Verzeichnisse nicht dauerhaft SSD-Speicher, während weiterhin benötigte Hotfix-Umgebungen vor versehentlichem Löschen geschützt bleiben.

Häufig gestellte Fragen

Kann derselbe Branch in zwei Git Worktrees ausgecheckt werden?

Standardmäßig nicht. Für parallele Änderungen werden getrennte Branches benötigt; für reine Prüfungen eignet sich ein Worktree im detached Zustand.

Sollten mehrere Worktrees dasselbe DerivedData-Verzeichnis verwenden?

Nein. Gleichzeitige Builds können Indizes, Modul-Caches und Zwischenprodukte überschreiben. Jeder Worktree sollte einen eigenen Pfad erhalten.

Was muss vor dem Entfernen eines Worktrees geprüft werden?

Es dürfen keine ungesicherten Änderungen oder laufenden xcodebuild-Prozesse vorhanden sein. Benötigte Archive, Ergebnisse und Protokolle müssen zuvor gesichert werden.

Dedizierter physischer Knoten

Eine reproduzierbare Umgebung auf demselben Cloud-Mac behalten

Wählen Sie Modell, Region und Mietdauer fest aus und erledigen Sie Entwicklungs- oder Automatisierungsaufgaben mit vollständiger grafischer macOS-Oberfläche und Kommandozeile.

Cloud-Mac jetzt mieten