Der Code wurde nicht geändert, dennoch kompiliert Xcode beim zweiten Build auf einem Cloud Mac weiterhin zahlreiche Targets. Wird derselbe Commit dagegen in einem neuen Arbeitsverzeichnis gebaut, funktioniert der inkrementelle Build wieder normal. Solche Probleme müssen nicht von DerivedData ausgehen: Auch Quelldateien, generierte Dateien oder Cache-Artefakte können fehlerhafte Änderungszeiten enthalten. Das Leeren des Caches beseitigt die Symptome vorübergehend. Solange sich die Synchronisierungs- und Wiederherstellungsprozesse jedoch nicht ändern, gelangt die Zeitstempel-Drift erneut in das Arbeitsverzeichnis.
Zuerst prüfen, ob eine Zeitstempel-Drift vorliegt
Legen Sie zunächst Commit, Scheme, Xcode-Pfad und Build-Parameter fest und führen Sie anschließend zweimal hintereinander denselben Build aus. Beim ersten Durchlauf dürfen alle Zwischenartefakte vollständig erzeugt werden. Beobachten Sie beim zweiten Durchlauf, welche Tasks weiterhin ausgeführt werden. Werden dieselben Swift-Kompilierungen, Ressourcenverarbeitungen oder Skriptphasen erneut gestartet, vergleichen Sie die Ergebnisse eines sauberen Arbeitsverzeichnisses mit denen eines wiederverwendeten Arbeitsverzeichnisses.
Betrachten Sie bei der Fehlersuche nicht nur die gesamte Build-Dauer. Erfassen Sie vor allem die Eingabepfade der wiederholt ausgeführten Tasks, die Skriptausgaben und die Generierungsverzeichnisse. Prüfen Sie außerdem, ob ein Tool vor Beginn des Builds Dateien neu schreibt. Typische Anzeichen sind:
- die Änderungszeit einer Datei liegt nach der aktuellen Systemzeit;
- nach dem Entpacken eines Caches sind Ausgabedateien neuer oder älter als die Quelldateien;
- ein Generierungsskript überschreibt bei jedem Lauf eine Datei mit unverändertem Inhalt;
- ein Synchronisierungswerkzeug übernimmt fehlerhafte Zeitstempel von einem anderen Rechner;
- die Zeit des Volumes mit dem Arbeitsverzeichnis ist korrekt, die Metadaten im Archiv sind jedoch inkonsistent.
Inkrementelle Builds hängen nicht nur vom Dateiinhalt ab. Sobald die zeitliche Beziehung zwischen Ein- und Ausgaben nicht mehr stimmt, kann das Build-System bereits abgeschlossene Tasks dauerhaft als erneut auszuführende Arbeit einstufen.
Dateien mit zukünftiger Zeit und auffällige Verzeichnisse suchen
Prüfen Sie zunächst die Systemzeit und die Zeitzone. Suchen Sie anschließend nach Dateien, deren Zeit mehr als fünf Minuten in der Zukunft liegt. Eine Toleranz von fünf Minuten vermeidet Fehlmeldungen durch sehr kleine Erfassungsabweichungen und reicht zugleich aus, um eine deutliche Drift zu erkennen.
#!/bin/zsh
set -euo pipefail
root="${1:-$PWD}"
limit=$(( $(date +%s) + 300 ))
find "$root" -type f -print0 |
while IFS= read -r -d '' file; do
modified=$(stat -f '%m' "$file")
if (( modified > limit )); then
printf '%s\t%s\n' \
"$(date -r "$modified" '+%Y-%m-%d %H:%M:%S %z')" \
"$file"
fi
done
Der Scan sollte mindestens den Quellcode, die Projektdateien, Skripte, Ressourcen, Verzeichnisse für die Codegenerierung und wiederhergestellte Caches abdecken. Scannen Sie nicht sofort das gesamte Benutzerverzeichnis, da Paketmanager-Caches, Protokolle und Systemdateien eine große Menge irrelevanter Treffer erzeugen. Führen Sie für gefundene Dateien zusätzlich stat -x 文件路径 aus und prüfen Sie Änderungszeit, Metadatenänderungszeit und das zugehörige Volume.
Konzentrieren sich die Auffälligkeiten auf ein einzelnes Verzeichnis, lässt sich die Ursache meist direkt auf einen bestimmten Download-, Entpack-, Synchronisierungs- oder Codegenerierungsschritt eingrenzen. Sind sie über das gesamte Repository verteilt, sollten Sie prüfen, wie das Arbeitsverzeichnis kopiert wird und welche Initialisierungsskripte vor Beginn des Tasks ausgeführt werden.
Reparatur anhand der Ursache auswählen
Unterschiedliche Ursachen erfordern unterschiedliche Korrekturen. Sie sollten nicht pauschal mit touch kaschiert werden.
| Symptom | Häufige Ursache | Empfohlene Maßnahme |
|---|---|---|
| Die Zeit weniger Quelldateien liegt in der Zukunft | Fehlerhaftes Archiv oder fehlerhafte Synchronisierungsquelle | Dateien erneut beziehen und die Zeit des Quellsystems prüfen |
| Generierte Dateien erhalten bei jedem Lauf eine neue Zeit | Der Generator überschreibt Dateien bedingungslos | Erst bei geändertem Inhalt atomar ersetzen |
| Die zeitlichen Beziehungen im Cache-Verzeichnis sind inkonsistent | Bei der Wiederherstellung wurden fehlerhafte Metadaten übernommen | Betroffenen Cache verwerfen und den Cache-Schlüssel neu erstellen |
| Die Zeiten des gesamten Arbeitsverzeichnisses entsprechen ungefähr dem Kopierzeitpunkt | Kopierparameter verändern die Metadaten | Eine einheitliche Checkout-Methode verwenden und Synchronisierungsstrategien nicht mischen |
| Ausgaben sind dauerhaft älter als ihre Eingaben | Fehlerhafte Zeiten innerhalb des Cache-Archivs | Cache neu erzeugen und Cache-Manifest überprüfen |
Bei von Git verwaltetem Quellcode ist es in der Regel am sichersten, zunächst sicherzustellen, dass keine nicht committeten Änderungen vorhanden sind, und anschließend das betroffene Verzeichnis neu auszuchecken. Die Zeitstempel des gesamten Repositorys sollten nicht umgeschrieben werden. Generatoren sollten ihre Ausgabe zunächst in eine temporäre Datei schreiben, die Inhalte vergleichen und die Zieldatei erst danach ersetzen:
generate_config > Config.generated.swift.tmp
if ! cmp -s Config.generated.swift.tmp Config.generated.swift; then
mv Config.generated.swift.tmp Config.generated.swift
else
rm Config.generated.swift.tmp
fi
Bleibt der Inhalt unverändert, ändert sich dadurch auch die Änderungszeit der ursprünglichen Datei nicht. Von ihr abhängige Kompilierungs-Tasks werden somit nicht unnötig erneut ausgelöst.
Grenzen von Cache und Synchronisierung prüfen
Vor der Wiederherstellung eines Caches muss klar sein, welche Verzeichnisse taskübergreifend wiederverwendet werden dürfen. Build-Artefakte, Modul-Caches und Paketmanager-Caches haben unterschiedliche Lebenszyklen und sollten nicht in einem einzigen, nicht nachvollziehbaren Archiv zusammengefasst werden. Das Cache-Manifest sollte mindestens Cache-Schlüssel, Xcode-Version, Architektur, Generierungsbefehl und Dateianzahl enthalten. Führen Sie nach der Wiederherstellung zunächst eine Vorabprüfung der Zeitstempel durch, bevor der eigentliche Build beginnt.
Auch für Synchronisierungswerkzeuge muss eindeutig festgelegt werden, ob Änderungszeiten erhalten bleiben. Das Beibehalten der Zeiten unterstützt inkrementelle Entscheidungen, überträgt jedoch bei einer fehlerhaften Uhr des Quellsystems auch die falschen Werte unverändert. Werden die Zeiten nicht erhalten, können dagegen sämtliche Dateien wie gerade erst geändert erscheinen. Entscheidend ist nicht die pauschale Wahl eines bestimmten Parameters, sondern eine einzige, überprüfte Strategie für das gesamte Team, die in den Task-Skripten festgeschrieben wird.
Wird der Code als Archiv übergeben, sollte er zunächst in ein isoliertes Verzeichnis entpackt werden. Prüfen Sie dort Dateien mit zukünftigen Zeiten, die Dateianzahl und den Git-Status, bevor Sie atomar auf das endgültige Arbeitsverzeichnis umschalten. Überschreiben Sie nicht direkt ein Verzeichnis, das noch alte Zwischenartefakte enthält.
Zeitbasis am Task-Einstieg prüfen
Die Prüfung sollte letztlich vor der Abhängigkeitsauflösung und dem Xcode-Build ausgeführt werden. Werden Dateien mit zukünftigen Zeiten gefunden, muss der Task sofort fehlschlagen und eine begrenzte Anzahl von Pfaden ausgeben. Korrigieren Sie die Zeiten nicht automatisch, um den Build anschließend fortzusetzen. Eine solche automatische Korrektur würde wichtige Fehlernachweise beseitigen.
Für jeden Task sollten folgende Angaben gespeichert werden:
- die Ausgabe von
dateundsystemsetup -gettimezone; - der aktuelle Commit, der Xcode-Pfad und die SDK-Informationen;
- die Anzahl der Dateien mit zukünftiger Zeit sowie die ersten gefundenen Pfade;
- der Cache-Schlüssel und das Ergebnis der Wiederherstellung;
- die Unterschiede zwischen den Tasks des ersten und zweiten Builds.
Führen Sie nach der Korrektur drei Durchläufe mit demselben Commit aus: Der erste Lauf baut den Cache auf, der zweite überprüft das inkrementelle Verhalten und der dritte bestätigt, dass das Ergebnis nicht zufällig war. Werden im zweiten und dritten Lauf weiterhin dieselben Tasks wiederholt, verfolgen Sie deren Eingabedateien weiter, anstatt erneut alles zu bereinigen. Ziel eines kontrollierten Umgangs mit Zeitstempeln ist nicht, allen Dateien dieselbe Zeit zuzuweisen. Entscheidend ist eine nachvollziehbare und überprüfbare zeitliche Beziehung zwischen Quellcode, generierten Dateien und Caches.
Häufig gestellte Fragen
Sollte ich nach einem Fund touch auf das gesamte Repository anwenden?
Nein. Dadurch ändern sich alle Modifikationszeiten und meist wird ein großer Neuaufbau ausgelöst. Reparieren Sie nur betroffene Dateien oder checken Sie das betroffene Verzeichnis erneut aus.
Warum bleibt der saubere Build stabil, während inkrementelle Builds langsamer werden?
Inkrementelle Builds vergleichen vorhandene Ausgaben und Abhängigkeiten. Zukünftige Dateizeiten oder inkonsistente Cache-Metadaten können diesen Zustand bei jedem Lauf entwerten.
Konfigurieren Sie für die nächste Entwicklungsaufgabe einen Cloud-Mac
Wählen Sie Oak M4, Oak M4 Plus oder Oak M4 Pro und konfigurieren Sie ein Mietmodell an einem von fünf physischen Standorten entsprechend dem Standort Ihres Teams.