Oakmini Engineering Notes

Xcode-Versionen für reproduzierbare Cloud-Mac-Builds fixieren

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

Xcode-Versionen für reproduzierbare Cloud-Mac-Builds fixieren

Wenn auf demselben Cloud Mac gleichzeitig ein stabiler Branch, der reguläre Entwicklungs-Branch und ein Branch zur Upgrade-Validierung laufen, wird häufig nicht der Code übersehen, sondern die gerade aktive Xcode-Version. Ändert ein Auftrag die globale Auswahl, können nachfolgende Pipelines unbemerkt einen anderen Swift-Compiler oder ein anderes SDK verwenden. Typische Folgen: Der Build funktioniert lokal, schlägt aber auf dem Remote-System fehl – oder derselbe Commit erzeugt am nächsten Tag ein anderes Ergebnis.

Die Toolchain als Projektabhängigkeit behandeln

Xcode sollte nicht nur als Programmsymbol auf dem Rechner betrachtet, sondern ebenso eindeutig dokumentiert werden wie eine Sprachlaufzeit. Verwenden Sie versionierte Verzeichnisse, damit eine neue Installation die vorherige Version nicht direkt überschreibt:

/Applications/Xcode_16.1.app
/Applications/Xcode_16.2.app
/Applications/Xcode_16.3.app

Ermitteln Sie zunächst die vorhandenen Installationen und die aktuelle globale Auswahl:

find /Applications -maxdepth 1 -name 'Xcode*.app' -print
xcode-select --print-path
xcodebuild -version
xcrun --find swift
swift --version

Dokumentiert werden sollten mindestens die Xcode-Version, die Build-Nummer, die Swift-Version und das vom Projekt verwendete SDK. Ein Hinweis wie „neueste Version verwenden“ reicht weder zur Reproduktion älterer Builds noch zur Klärung, ob ein Fehler durch den Code oder durch eine geänderte Toolchain verursacht wurde.

Das Fixieren einer Version soll Upgrades nicht verhindern. Es macht sie vielmehr zu einer nachvollziehbaren und rückgängig zu machenden technischen Änderung.

Xcode passend zum Gültigkeitsbereich auswählen

Die Auswahlmethode sollte sich nach dem Gültigkeitsbereich des Auftrags richten. Ein globaler Standard eignet sich für die manuelle Administration, während Umgebungsvariablen auf Auftragsebene für die Automatisierung besser geeignet sind.

Methode Gültigkeitsbereich Empfohlener Einsatz Risiko bei Parallelbetrieb
xcode-select Gesamter Host Administration durch eine Person, einheitliche Standardversion Beeinflusst andere Aufträge
DEVELOPER_DIR Aktueller Prozess und Unterprozesse CI, Skripte, parallele Projekte Gering
Temporäre Zuweisung vor einem Befehl Einzelner Befehl Schnelle Validierung Gering

Wenn der globale Standard geändert werden muss, führen Sie folgenden Befehl aus:

sudo xcode-select --switch /Applications/Xcode_16.2.app/Contents/Developer

In CI-Umgebungen sollte dieser Befehl nicht wiederholt ausgeführt werden. Setzen Sie stattdessen zu Beginn des Auftrags:

export DEVELOPER_DIR="/Applications/Xcode_16.2.app/Contents/Developer"
xcodebuild -version
xcrun --sdk iphoneos --show-sdk-version

DEVELOPER_DIR wird an Unterprozesse vererbt. Dadurch beziehen nachfolgende Aufrufe von xcrun, swift und xcodebuild ihre Werkzeuge aus demselben Developer-Verzeichnis. Verschiedene Pipelines können jeweils eigene Pfade setzen, ohne um den globalen Zustand zu konkurrieren.

Unbemerkte Abweichungen mit einem Preflight-Skript verhindern

Es genügt nicht, die Version lediglich im Protokoll auszugeben. Der Preflight-Check sollte den Auftrag bei einer Abweichung sofort abbrechen, statt das Problem erst nach mehreren Minuten Kompilierzeit sichtbar werden zu lassen. Das folgende Skript fixiert Xcode 16.2 und prüft das App-Verzeichnis, die tatsächlich verwendete Version, das SDK sowie den Swift-Pfad:

#!/bin/bash
set -euo pipefail

XCODE_APP="/Applications/Xcode_16.2.app"
EXPECTED_VERSION="16.2"

test -x "$XCODE_APP/Contents/Developer/usr/bin/xcodebuild"
export DEVELOPER_DIR="$XCODE_APP/Contents/Developer"

ACTUAL_VERSION="$(xcodebuild -version | awk 'NR == 1 {print $2}')"
test "$ACTUAL_VERSION" = "$EXPECTED_VERSION"

echo "developer=$DEVELOPER_DIR"
echo "xcode=$ACTUAL_VERSION"
echo "swift=$(xcrun --find swift)"
echo "sdk=$(xcrun --sdk iphoneos --show-sdk-version)"

xcodebuild -checkFirstLaunchStatus

Legen Sie das Skript im Repository ab, beispielsweise unter ci/verify-toolchain.sh. So wird die Versionsanforderung gemeinsam mit dem Code geprüft. Aktualisieren Sie bei einem Upgrade sowohl das Skript als auch die Projektdokumentation. Diese Änderungshistorie ist zuverlässiger als manuelle Eingriffe auf dem Host.

Wenn das Projekt eine bestimmte SDK-Version voraussetzt, kann zusätzlich eine exakte Prüfung ergänzt werden. Leiten Sie das SDK nicht aus der Xcode-Hauptversion ab, da verschiedene Releases derselben Versionsreihe unterschiedliche SDKs enthalten können.

Auch den Build-Befehl explizit festlegen

Führen Sie nach erfolgreichem Preflight-Check den Build über einen eindeutig festgelegten Einstiegspunkt aus:

export DEVELOPER_DIR="/Applications/Xcode_16.2.app/Contents/Developer"

xcodebuild \
  -workspace Example.xcworkspace \
  -scheme Example \
  -configuration Release \
  -derivedDataPath "$PWD/.derived-data" \
  CODE_SIGNING_ALLOWED=NO \
  build

Workspace, Scheme, Konfiguration und Pfad für Derived Data werden hier explizit angegeben. CODE_SIGNING_ALLOWED=NO eignet sich nur für Kompilierungsprüfungen, die keine Signierung benötigen. Für Archive oder Geräte-Builds muss der bereits im Projekt eingerichtete sichere Signierungsprozess verwendet werden; dieser Parameter darf dort nicht unverändert übernommen werden.

Neue Versionen parallel validieren, bevor umgestellt wird

Behalten Sie nach der Installation einer neuen Xcode-Version zunächst die alte Version bei und führen Sie die Upgrade-Validierung in einem separaten Auftrag aus. Die erstmalige Einrichtung kann durch einen Administrator erfolgen:

sudo DEVELOPER_DIR="/Applications/Xcode_16.3.app/Contents/Developer" \
  xcodebuild -runFirstLaunch

Führen Sie anschließend für denselben Commit jeweils einen Lauf mit der alten und der neuen Version aus. Vergleichen Sie, ob die Kompilierung erfolgreich ist, wie viele Tests ausgeführt werden, ob sich Warnungen ändern sowie welche Architekturen und Archivmetadaten die Artefakte enthalten. Löschen Sie das alte Verzeichnis nicht gleich zu Beginn. Andernfalls fehlt bei inkompatiblen Abhängigkeiten oder Compileroptionen ein schneller Rückweg.

Zur Upgrade-Abnahme gehört außerdem die Prüfung, ob das Projektformat automatisch umgeschrieben wurde. Ändert die neue Xcode-Version Projektdateien, sollten diese Anpassungen als separater Commit geprüft und nicht mit fachlichen Codeänderungen vermischt werden. So lassen sich automatische Migrationen, aktualisierte Abhängigkeiten und tatsächliche Funktionsänderungen klar voneinander unterscheiden.

Häufige Fallstricke und Checkliste für die Übergabe

Ein häufiger Fehler besteht darin, nur xcode-select umzustellen und dabei ein noch gesetztes DEVELOPER_DIR in der Auftragsumgebung zu übersehen. Die Umgebungsvariable hat Vorrang; der im Protokoll angezeigte globale Pfad muss daher nicht dem tatsächlich vom Auftrag verwendeten Pfad entsprechen. Ein weiterer Fehler ist, die Anwendung einfach Xcode.app zu nennen. Wird sie bei einem Upgrade überschrieben, lässt sich später nicht mehr feststellen, welche Version ältere Aufträge verwendet haben. Ebenfalls problematisch ist es, nur den Compiler zu prüfen, nicht aber das SDK und den von xcrun aufgelösten Pfad. Dadurch können unbeabsichtigt Werkzeuge aus einer anderen Installation einfließen.

Prüfen Sie vor der Übergabe folgende Punkte in dieser Reihenfolge:

  1. Das Xcode-App-Verzeichnis enthält eine eindeutige Versionsnummer, und die vorherige Version ist weiterhin nutzbar.
  2. Die erwartete Version ist im Repository dokumentiert und hängt nicht vom Gedächtnis einzelner Personen ab.
  3. Jeder CI-Auftrag setzt DEVELOPER_DIR separat.
  4. Der Preflight-Check prüft Xcode, Swift, SDK und Werkzeugpfade.
  5. Der Build-Befehl legt Workspace, Scheme und Konfiguration explizit fest.
  6. Die neue Version wird erst nach erfolgreicher Prüfung von Kompilierung, Tests und Artefakten als Standard gesetzt.
  7. Parallele Aufträge ändern das globale xcode-select nicht.

Nach diesen Schritten lässt sich der Toolchain-Zustand des Cloud Mac anhand der Protokolle nachvollziehen. Treten Unterschiede zwischen Builds auf, kann zunächst eine Versionsabweichung ausgeschlossen werden. Erst danach müssen Abhängigkeiten, Code und Auftragskonfiguration untersucht werden, statt mehrere Variablen gleichzeitig auf Verdacht zu ändern.

Häufig gestellte Fragen

Können parallele CI-Jobs unterschiedliche Xcode-Versionen verwenden?

Ja. Jeder Job setzt sein eigenes DEVELOPER_DIR. Der globale Pfad von xcode-select darf während paralleler Ausführungen nicht geändert werden.

Reicht die Ausgabe von xcodebuild -version zur Kontrolle aus?

Nein. Zusätzlich sollten DEVELOPER_DIR, SDK- und Swift-Version sowie die von xcrun aufgelösten Werkzeugpfade geprüft werden.

Wie wird eine neue Xcode-Version sicher eingeführt?

Sie wird unter einem versionierten Namen installiert und vorbereitet. Danach laufen Preflight, Build und Tests bei weiterhin verfügbarer Vorgängerversion.

Dedizierter physischer Mac-mini-Knoten erforderlich

Verfügbare Konfigurationen, Knoten und feste Abrechnungszeiträume anzeigen und die Schritte des Artikels in einer dauerhaft nutzbaren Cloud-Mac-Umgebung umsetzen.

Mac mini jetzt mieten