Derselbe Commit läuft lokal erfolgreich durch, verursacht auf einem dauerhaft betriebenen Cloud-Mac jedoch sporadisch den Fehler module file was created by a different version of the compiler. Bei einer Wiederholung des Builds verschwindet das Problem mitunter wieder. Solche Fehler entstehen normalerweise nicht durch zufällige Änderungen am Swift-Quellcode, sondern weil unterschiedliche Toolchains, SDKs oder parallele Jobs auf denselben Cache für vorkompilierte Module zugreifen. Das vollständige Löschen aller DerivedData kann die Symptome zwar vorübergehend beseitigen, vernichtet jedoch zugleich die wichtigsten Diagnoseinformationen und verhindert nicht, dass der Cache erneut verunreinigt wird.
Zuerst prüfen, ob der Modulcache die Fehlerquelle ist
Clang kompiliert System-Header und Objective-C-Module zu PCM-Dateien; auch der Swift-Compiler verwaltet einen eigenen Modulcache. Sobald Compiler, SDK, Architektur oder Build-Parameter beim Erzeugen des Caches von denen des aktuellen Jobs abweichen, kann das Laden der Module fehlschlagen.
Suchen Sie zunächst im vollständigen Protokoll nach diesen Merkmalen:
| Merkmal im Protokoll | Wahrscheinlichere Ursache | Erster Prüfpunkt |
|---|---|---|
different version of the compiler |
Xcode-Version wurde gewechselt | xcodebuild -version |
PCM file is out of date |
SDK oder Zeitstempel von Header-Dateien haben sich geändert | Aktueller SDK-Pfad |
could not build module |
Ein vorgelagertes Modul ist beschädigt | Erstes fehlgeschlagenes Modul |
| Derselbe Job läuft nach einer Wiederholung erfolgreich durch | Parallele Schreibzugriffe oder gemeinsam genutzter Cache | Eigentümer des Cache-Verzeichnisses |
Beschränken Sie sich nicht auf die letzte Fehlermeldung. Ein fehlgeschlagenes Laden von Modulen kann Dutzende Folgefehler auslösen. Suchen Sie weiter vorn im Protokoll nach der ersten Stelle, an der module, .pcm oder ModuleCache erscheint.
Sichern Sie vor dem Leeren des Caches das Fehlerprotokoll, die Xcode-Buildnummer und die tatsächlich verwendeten Build-Einstellungen. Ohne diese Informationen lässt sich selbst nach einem erfolgreichen Build nicht feststellen, ob die Ursache eine abweichende Toolchain, ein durch konkurrierende Zugriffe verunreinigter Cache oder eine Beschädigung des Caches selbst war.
Vergleichbare Diagnosedaten für fehlgeschlagene Jobs sichern
Erfassen Sie auf dem Cloud-Mac von VMKeep zunächst das Entwicklerverzeichnis, das der Befehl tatsächlich auflöst. Verlassen Sie sich nicht nur auf die Xcode-Version, die laut Skript verwendet werden soll.
set -o pipefail
mkdir -p artifacts/diagnostics
xcode-select -p | tee artifacts/diagnostics/developer-dir.txt
xcodebuild -version | tee artifacts/diagnostics/xcode-version.txt
xcrun --sdk iphoneos --show-sdk-path | tee artifacts/diagnostics/sdk-path.txt
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-showBuildSettings \
> artifacts/diagnostics/build-settings.txt
Prüfen Sie anschließend in dem fehlgeschlagenen Befehl die Werte von SDKROOT, ARCHS, DEVELOPER_DIR, CLANG_MODULE_CACHE_PATH und SWIFT_MODULE_CACHE_PATH. Wird der Job über sudo, eine Remote-Sitzung oder einen Scheduler gestartet, müssen außerdem der ausführende Benutzer und HOME verglichen werden. Wird dasselbe Skript unter einem anderen Benutzer ausgeführt, ändert sich auch der Standardspeicherort des Caches.
Einstiegspunkt der Toolchain fest vorgeben
Das Build-Skript sollte DEVELOPER_DIR explizit setzen und unmittelbar vor der eigentlichen Kompilierung einmal xcodebuild -version ausführen. Weicht die Version von der erwarteten Version ab, muss der Job sofort beendet werden. Andernfalls schreibt er mit der falschen Toolchain in einen gemeinsam genutzten Cache. Nach einem Xcode-Upgrade sollte ein neuer Cache-Schlüssel erzeugt werden, statt das bisherige Verzeichnis weiterzuverwenden.
Cache-Grenzen auf einzelne Jobs beschränken
Ein zuverlässiger Verzeichnisschlüssel enthält mindestens die Xcode-Buildnummer, das SDK, die Architektur und eine Job-ID. Ein Branch-Name darf nicht unverändert als Pfad verwendet werden; Sonderzeichen müssen zuvor ersetzt werden. Das folgende Verfahren weist jedem Job eigene DerivedData- und Modulcache-Verzeichnisse zu, während heruntergeladene Abhängigkeiten in einem separaten Verzeichnis verbleiben.
set -euo pipefail
JOB_KEY="$(printf '%s' "${CI_JOB_ID:-local}" | tr -cs 'A-Za-z0-9._-' '-')"
XCODE_BUILD="$(xcodebuild -version | awk '/Build version/{print $3}')"
SDK_BUILD="$(xcrun --sdk iphoneos --show-sdk-build-version)"
CACHE_KEY="${XCODE_BUILD}-${SDK_BUILD}-arm64-${JOB_KEY}"
ROOT="$HOME/BuildCaches/$CACHE_KEY"
DERIVED="$ROOT/DerivedData"
MODULES="$ROOT/Modules"
PACKAGES="$HOME/BuildCaches/SourcePackages"
mkdir -p "$DERIVED" "$MODULES/clang" "$MODULES/swift" "$PACKAGES"
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-destination 'generic/platform=iOS' \
-derivedDataPath "$DERIVED" \
-clonedSourcePackagesDirPath "$PACKAGES" \
CLANG_MODULE_CACHE_PATH="$MODULES/clang" \
SWIFT_MODULE_CACHE_PATH="$MODULES/swift" \
build
Langfristig gepflegte Branches können ihren eigenen stabilen Schlüssel wiederverwenden. Kurzlebige Merge Requests sollten dagegen pro Job isoliert werden. Zwei gleichzeitig laufende xcodebuild-Prozesse dürfen nicht in dasselbe Modulverzeichnis schreiben. Ebenso sollte der Modulcache nicht als allgemeines Archiv behandelt werden, das sich über verschiedene Toolchains hinweg wiederherstellen lässt.
Gezielt bereinigen statt alles zurückzusetzen
Sobald der Modulcache als Fehlerquelle bestätigt ist, beenden Sie zunächst alle Build-Prozesse, die das betreffende Verzeichnis verwenden. Bereinigen Sie danach schrittweise vom kleinsten zum größten Bereich:
- Löschen Sie
Modules/clangundModules/swiftdes aktuellen Jobs. - Behalten Sie
SourcePackagesbei und führen Sie die Abhängigkeitsauflösung sowie den Build erneut aus. - Falls der Index oder Zwischenprodukte weiterhin auf alte Module verweisen, löschen Sie zusätzlich die DerivedData des aktuellen Jobs.
- Falls nur ein einzelnes Drittanbietermodul fehlschlägt, prüfen Sie, ob dessen Binärartefakte die verwendete Architektur und das aktuelle SDK unterstützen. Wiederholtes Leeren des Caches darf kein Kompatibilitätsproblem verdecken.
Die Bereinigung muss auf das berechnete Job-Verzeichnis begrenzt bleiben. Ein Befehl wie rm -rf ~/Library/Developer/Xcode/DerivedData/* beeinträchtigt gleichzeitig andere Jobs und kann aus einem lokal begrenzten Fehler einen Kaltstart der gesamten Maschine machen.
Versehentliches Löschen verwendeter Verzeichnisse verhindern
Prüfen Sie beim Entfernen alter Caches anhand von Job-Sperren oder Laufzeitaufzeichnungen, ob das Verzeichnis noch verwendet wird. Löschen Sie es erst danach entsprechend dem Zeitpunkt der letzten Nutzung. Verlassen Sie sich nicht allein auf das Erstellungsdatum des Verzeichnisses, da stabile Branches einen früher angelegten Cache über längere Zeit wiederverwenden können. Das Bereinigungsskript sollte außerdem verifizieren, dass der Zielpfad innerhalb des erwarteten Stammverzeichnisses liegt. Bei einer leeren Variablen oder einer fehlgeschlagenen Pfadberechnung muss es sofort abbrechen.
Cache-Konsistenz als Build-Prüfung etablieren
Die Pipeline sollte Abweichungen letztlich selbstständig sichtbar machen. Speichern Sie bei jedem Build die Xcode-Buildnummer, die SDK-Buildnummer, die Architektur, den Cache-Schlüssel und den ausführenden Benutzer. Archivieren Sie nach Abschluss des Jobs das Fehlerprotokoll zusammen mit diesen Angaben. Legen Sie beim Upgrade der Toolchain einen neuen Namensraum an und entfernen Sie alte Caches erst, nachdem sich der neue Build-Pfad als stabil erwiesen hat.
Zusätzlich kann ein Validierungsjob eingerichtet werden, der keinen Modulcache wiederherstellt. Schlägt der reguläre Job fehl, während der saubere Job erfolgreich durchläuft, liegt die Ursache wahrscheinlich in der Cache-Schicht. Scheitern beide Jobs, sollte die Untersuchung bei Quellcode, Abhängigkeiten oder Build-Einstellungen fortgesetzt werden. So wird weder jeder Fehler vorschnell dem Cache zugeschrieben noch auf zufälligen Erfolg durch wiederholte Ausführung gehofft.
Der Modulcache ist letztlich ein Bestandteil der Compiler-Eingaben. Werden Versionen, Verzeichniseigentum und Grenzen für parallele Zugriffe im Skript festgehalten, wird eine PCM-Invalidierung von einem sporadischen Fehler zu einem lokalisierbaren und kontrolliert behebbaren Build-Ereignis.
Häufig gestellte Fragen
Muss bei einem PCM-Fehler der gesamte DerivedData-Ordner weg?
Zunächst nicht. Sichern Sie Protokoll und Build-Einstellungen und löschen Sie nur ModuleCache.noindex des betroffenen Auftrags. Erst bei ebenfalls inkonsistenten Indizes und Produkten wird dessen DerivedData neu erstellt.
Dürfen mehrere Xcode-Versionen denselben Modulcache verwenden?
Das ist nicht empfehlenswert. Der Cache-Schlüssel sollte Xcode-Buildnummer, SDK, Architektur und Auftragskennung enthalten, damit kein inkompatibles PCM wiederverwendet wird.
Diesen Workflow auf einem Cloud-Mac weiter validieren
Wählen Sie aus drei Apple-Silicon-Konfigurationen die passende Kombination aus Arbeitsspeicher, Speicher, Knoten und Mietdauer.