Ein über Jahre gepflegtes Xcode-Projekt hat häufig bereits Dutzende Build-Warnungen angesammelt. Wird jetzt einfach SWIFT_TREAT_WARNINGS_AS_ERRORS aktiviert, lässt sich der Hauptbranch sofort nicht mehr bauen. Bleibt dagegen alles unverändert, gehen neue Warnungen im bestehenden Rauschen unter. Praktikabler ist es, auf einem Cloud Mac eine geprüfte Warnungsbaseline festzuhalten und das CI nur solche Einträge ablehnen zu lassen, die durch die aktuelle Änderung neu hinzukommen.
Grenzen des Gates festlegen
Ein Warnungs-Gate darf nicht einfach alle Vorkommen von warning: im Protokoll zählen. Auch Downloads von Abhängigkeiten, Build-Skripte und Systemwerkzeuge können dieselbe Zeichenfolge ausgeben. Eine direkte Zählung ist daher weder stabil noch zeigt sie Entwicklern, welche Datei geändert werden muss.
Es empfiehlt sich, jede Warnung als „repository-relativer Pfad + Warnungstext“ zu normalisieren und Zeilen- sowie Spaltennummern, temporäre Verzeichnisse und Zeitstempel zu entfernen. Dadurch erzeugt das Verschieben von Code keine neue Signatur, während das Umbenennen einer Datei oder eine Änderung des Warnungstextes weiterhin eine Prüfung auslöst.
| Inhalt | Teil der Signatur | Grund |
|---|---|---|
| Repository-relativer Pfad | Ja | Kennzeichnet die verantwortliche Datei |
| Warnungstext | Ja | Unterscheidet die Art des Problems |
| Zeilen- und Spaltennummer | Nein | Ändern sich bei Codeanpassungen leicht |
| DerivedData-Pfad | Nein | Kann bei jedem Auftrag anders sein |
| Warnungen externer Abhängigkeiten | Standardmäßig nein | Können vom Team meist nicht direkt behoben werden |
Die Baseline steht für „derzeit bekannte und akzeptierte technische Schulden“. Sie bedeutet nicht, dass diese Warnungen korrekt sind. Wird eine bestehende Warnung entfernt, sollte die Baseline entsprechend verkleinert werden.
Reproduzierbare Warnungssignaturen erzeugen
Zunächst müssen Workspace, Scheme, Konfiguration und SDK festgelegt werden. Wenn lokale Entwicklungsrechner Debug verwenden, das CI aber Release, unterscheiden sich die bedingt kompilierten Codepfade und die resultierenden Baselines sind nicht sinnvoll vergleichbar. Das folgende Skript verlangt, dass Workspace und Scheme explizit übergeben werden, und speichert die vollständige Ausgabe.
#!/bin/bash
set -u
WORKSPACE="${WORKSPACE:?set WORKSPACE}"
SCHEME="${SCHEME:?set SCHEME}"
ROOT="$(git rev-parse --show-toplevel)"
OUT="$ROOT/.ci-artifacts"
DERIVED="$OUT/DerivedData"
mkdir -p "$OUT"
rm -rf "$DERIVED"
set +e
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration Release \
-sdk iphoneos \
-derivedDataPath "$DERIVED" \
CODE_SIGNING_ALLOWED=NO \
build 2>&1 | tee "$OUT/xcodebuild.log"
BUILD_STATUS=${PIPESTATUS[0]}
set -e
grep -F "$ROOT/" "$OUT/xcodebuild.log" \
| grep " warning: " \
| sed "s#${ROOT}/##" \
| sed -E 's#:[0-9]+:[0-9]+: warning: # | #' \
| LC_ALL=C sort -u \
> "$OUT/warnings.current"
exit "$BUILD_STATUS"
CODE_SIGNING_ALLOWED=NO eignet sich für Aufträge, die ausschließlich die Kompilierung prüfen. Für Pipelines, die Archive erstellen oder Signaturen validieren, sollte diese Einstellung nicht unverändert übernommen werden. Build-Fehler und Warnungsregressionen müssen außerdem getrennt behandelt werden: Schlägt xcodebuild selbst fehl, sollte dessen Exit-Code Vorrang haben, statt den tatsächlichen Fehler mit einer leeren Warnungsdatei zu überdecken.
Skriptausgaben und mehrzeilige Diagnosen behandeln
Einige Run-Script-Phasen geben eigene Warnungen aus, deren Format nicht unbedingt Quellcodekoordinaten enthält. Werden diese Skripte vom Team gepflegt, kann für sie ein festes Präfix vereinbart und dafür eine zweite Parsing-Regel angelegt werden. Ein breit gefasster regulärer Ausdruck für sämtliche Ausgaben sollte vermieden werden, da sonst auch Netzwerkhinweise und Benachrichtigungen über Werkzeug-Upgrades in die Baseline gelangen.
Zusätzliche Hinweiszeilen von Swift enthalten üblicherweise kein warning: und eignen sich nicht als eigenständige Signatur. Sie sollten jedoch im vollständigen Protokoll erhalten bleiben. Das Gate dient der schnellen Entscheidung, während das vollständige Protokoll den Kontext nachvollziehbar macht. Beides kann einander nicht ersetzen.
Erste Baseline prüfen und committen
Die erstmals erzeugte Datei warnings.current sollte nicht automatisch von einem Auftrag als Baseline übernommen werden. Zunächst müssen Verantwortliche anhand der Pfade zugewiesen, Duplikate entfernt und abgeleitete Dateien, Quellcode von Abhängigkeiten sowie temporäre Verzeichnisse ausgeschlossen werden. Nach Abschluss der Prüfung können folgende Befehle ausgeführt werden:
mkdir -p .ci
LC_ALL=C sort -u .ci-artifacts/warnings.current > .ci/xcode-warnings.baseline
git add .ci/xcode-warnings.baseline
git commit -m "Add reviewed Xcode warning baseline"
Die Baseline muss zusammen mit dem Code versioniert werden. Jeder Commit, der sie vergrößert, sollte die neuen Signaturen und deren Begründung ausweisen. Ein fehlgeschlagener Auftrag darf neue Warnungen nicht automatisch „lernen“, da sich das Gate sonst genau dann selbst umgeht, wenn es eingreifen müsste.
Bei einem Xcode-Upgrade kann sich die Formulierung von Compilerdiagnosen ändern. Der richtige Ablauf besteht darin, auf einem separaten Branch einen vollständigen Build auszuführen, echte neue Probleme von reinen Textänderungen zu unterscheiden und anschließend die Baseline einmalig zu aktualisieren sowie die Toolchain-Version zu dokumentieren. Der Vergleichslogik sollten stattdessen keine beliebig großzügigen unscharfen Regeln hinzugefügt werden.
Im CI nur neue Warnungen blockieren
Sind sowohl das aktuelle Ergebnis als auch die Baseline sortiert, lassen sich mit comm alle Signaturen ermitteln, die nur im aktuellen Build vorkommen:
BASELINE=".ci/xcode-warnings.baseline"
CURRENT=".ci-artifacts/warnings.current"
NEW=".ci-artifacts/warnings.new"
test -f "$BASELINE"
LC_ALL=C sort -u "$BASELINE" -o "$BASELINE"
LC_ALL=C sort -u "$CURRENT" -o "$CURRENT"
LC_ALL=C comm -13 "$BASELINE" "$CURRENT" > "$NEW"
if test -s "$NEW"; then
printf '%s
' "New Xcode warnings detected:"
cat "$NEW"
exit 42
fi
xcodebuild.log, warnings.current und warnings.new sollten gemeinsam archiviert werden. Der Exit-Code 42 ist lediglich eine teaminterne Konvention. Entscheidend ist, „Build fehlgeschlagen“ und „neue Warnungen“ als unterschiedliche Ursachen darzustellen. Erst wenn Entwickler Pfad und Warnungstext sehen, können sie das Problem innerhalb einer Feedback-Schleife beheben.
Wenn mehrere Aufträge parallel verschiedene Targets bauen, sollten ihre Ergebnisse separat erzeugt und erst anschließend zusammengeführt, sortiert und dedupliziert werden. Mehrere Prozesse dürfen nicht gleichzeitig in dieselbe Datei schreiben, da abgeschnittene oder ineinander verschachtelte Ausgaben sporadische Fehlentscheidungen verursachen können.
Die Baseline kontinuierlich verkleinern statt dauerhaft einzufrieren
Nach Einführung des Gates muss bei jeder Behebung einer alten Warnung auch die entsprechende Zeile aus der Baseline entfernt werden. Zusätzlich kann eine nicht blockierende Prüfung nach Einträgen suchen, die noch in der Baseline stehen, im aktuellen Build aber bereits verschwunden sind. So werden Beitragende daran erinnert, diese Einträge direkt mit zu bereinigen. Dadurch schrumpft die Baseline nur in eine Richtung, statt zu einer unverständlichen historischen Liste zu werden.
Vor dem stabilen Betrieb sollten außerdem vier Punkte geprüft werden: Verwenden CI und lokale Umgebung dieselbe Xcode-Version? Enthält das Scheme die erwarteten Targets? Beeinflusst die Gebietsschema-Einstellung die Werkzeugausgabe? Deckt der Repository-Pfadfilter den gesamten eigenen Quellcode ab? Auch das Ausführungsverzeichnis auf OnceMac-Knoten sollte bei jedem Auftrag fest angelegt werden, damit weder Protokolle noch DerivedData aus einem vorherigen Build wiederverwendet werden.
Sobald die Baseline auf null gesunken ist, kann der Differenzvergleich entfernt und die Compilerstrategie „Warnungen als Fehler“ aktiviert werden. Bis dahin bietet das Baseline-Gate einen realistischen Übergang: Bestehende technische Schulden werden nicht verborgen, neue dürfen jedoch nicht weiter anwachsen.
Häufig gestellte Fragen
Warum werden Warnungen nicht sofort als Fehler behandelt?
Bei vielen Altbefunden würden sämtliche Builds sofort scheitern. Eine Baseline stoppt zunächst nur neue Warnungen; nach dem Abbau des Bestands kann die strengere Compileroption aktiviert werden.
Führen geänderte Zeilennummern zu Fehlalarmen?
Nicht, wenn Zeilen- und Spaltennummern vor dem Vergleich entfernt werden. Der Schlüssel sollte nur aus relativem Quellpfad und Warnungstext bestehen.
Sollen Warnungen externer Abhängigkeiten aufgenommen werden?
In der Regel nein. Gefiltert werden nur verwaltete Quellen im Projektverzeichnis; interne Abhängigkeiten lassen sich bei Bedarf über ausdrücklich definierte Pfade ergänzen.
Führe deinen nächsten Build auf einem dedizierten physischen Knoten aus.
Wähle Chip, Arbeitsspeicher, Speicher, Knoten und Laufzeit und erhalte einen Cloud-Mac mit Ressourcen, die nicht mit anderen Kunden geteilt werden.