Engineering-Dossier

Eine belastbare Baseline für die Xcode-Analyse auf dem Cloud Mac

Eine belastbare Baseline für die Xcode-Analyse auf dem Cloud Mac

Wenn ein Team xcodebuild analyze in einen Cloud Mac integriert, führt das meist nicht sofort zu null Fehlern. Stattdessen erscheinen auf einmal Dutzende historisch gewachsener Diagnosen. Wird die Gesamtzahl der Warnungen unmittelbar als Fehlerbedingung verwendet, bleibt die Pipeline dauerhaft rot. Werden die Protokolle dagegen nur als Artefakte abgelegt, prüft sie auf Dauer niemand. Praktikabler ist es, zunächst eine geprüfte Baseline aufzubauen und anschließend bei jedem Merge ausschließlich neu hinzugekommene Probleme zu berücksichtigen.

Eingaben des Analysejobs fest vorgeben

Die statische Analyse hängt von der Projektkonfiguration, den Build-Bedingungen und den tatsächlich kompilierten Quelldateien ab. Vor der Analyse müssen daher die Xcode-Auswahl, der Workspace, das Scheme, die Konfiguration und die Zielplattform festgelegt werden. Außerdem muss CI dieselben Lock-Dateien für Abhängigkeiten verwenden wie der Archivierungsjob. Persönliche Schemes einzelner Entwickler sind ebenso ungeeignet wie Skripte, die automatisch die „neueste“ Toolchain auswählen.

Der Analysejob sollte ein eigenes Verzeichnis verwenden, damit er DerivedData nicht gleichzeitig mit Build- oder Testjobs verändert:

set -o pipefail

ROOT="$PWD"
OUT="$ROOT/ci-artifacts/analyze"
DERIVED="$ROOT/.derived/analyze"
RESULT="$OUT/AppAnalyze.xcresult"

rm -rf "$OUT" "$DERIVED"
mkdir -p "$OUT" "$DERIVED"

xcodebuild analyze \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Debug \
  -destination 'generic/platform=iOS' \
  -derivedDataPath "$DERIVED" \
  -resultBundlePath "$RESULT" \
  CODE_SIGNING_ALLOWED=NO \
  | tee "$OUT/xcodebuild.log"

CODE_SIGNING_ALLOWED=NO eignet sich nur für Analyseziele, die ohne Signierung abgeschlossen werden können. Wenn Projektskripte ausdrücklich signierungsbezogene Build-Einstellungen auslesen, sollten zunächst ihre Eingabegrenzen korrigiert werden, statt blind weitere überschreibende Parameter hinzuzufügen.

Eine Baseline ist keine Liste von Problemen, die ignoriert werden dürfen. Sie ist eine Momentaufnahme bekannter Tatsachen während der Migration. Neue Probleme müssen weiterhin sofort zum Fehlschlag führen; für bestehende Probleme braucht es dagegen Verantwortliche und einen Bereinigungsplan.

Vollständige Ergebnisse statt nur warnings speichern

Ein direktes grep -c warning: ist bequem, eignet sich aber nicht als dauerhafte Qualitätsschranke. Pfade, Zeilennummern und Formate im Protokoll können sich ändern, und dieselbe Diagnose kann mehrfach ausgegeben werden. CI sollte deshalb sowohl die vollständige .xcresult-Datei als auch das ursprüngliche Protokoll aufbewahren und aus den strukturierten Ergebnissen eine Diagnoseliste für den Vergleich erzeugen.

Die Unterbefehle von xcresulttool können sich zwischen Xcode-Versionen ändern. Daher sollte zunächst die Hilfe gespeichert und das Parsing-Skript bei jeder Aktualisierung des Node-Images erneut geprüft werden:

xcrun xcresulttool help > "$OUT/xcresulttool-help.txt"
xcrun xcresulttool get \
  --legacy \
  --path "$RESULT" \
  --format json > "$OUT/xcresult.json"

Akzeptiert die aktuelle Toolchain --legacy nicht, muss der Befehl entsprechend der Hilfe dieser Version angepasst werden. Die Änderung am Parser und das Toolchain-Upgrade gehören dabei in denselben Merge Request. Schlägt das Skript fehl, darf es nicht ersatzweise Warnungen im Protokoll zählen und den Job trotzdem freigeben. Andernfalls wird die Qualitätsschranke unbemerkt wirkungslos.

Für jede Diagnose sollten die folgenden Felder gespeichert werden:

Feld Zweck
Regel- oder Problemtyp Unterscheidung von Kategorien wie Nullzeiger und Ressourcenlecks
Repository-relativer Pfad Verhindert, dass das Arbeitsverzeichnis eines Nodes in die Baseline gelangt
Funktions- oder Symbolname Erleichtert die Zuordnung auch nach Änderungen der Zeilennummer
Normalisierte Meldung Entfernt temporäre Verzeichnisse und instabile Zahlen
Schweregrad Ermöglicht risikobasierte Behandlungsregeln

Eine prüfbare Baseline mit stabilen Fingerprints erstellen

Ein Diagnose-Fingerprint sollte weder absolute Pfade noch DerivedData-Pfade oder eine einzelne Zeilennummer enthalten. Eine robustere Kombination lautet „Problemtyp + Repository-relativer Pfad + Symbolname + normalisierte Meldung“. Die Zeilennummer kann zur Anzeige gespeichert werden, sollte aber nicht Teil des Primärschlüssels sein. Sonst würde bereits eine zusätzliche Zeile am Anfang einer Datei dazu führen, dass alle bestehenden Probleme als neu erkannt werden.

Übermäßige Zusammenfassung bei der Normalisierung vermeiden

Präfixe des Arbeitsverzeichnisses, temporäre UUIDs und aufeinanderfolgende Leerzeichen können entfernt werden. Variablennamen, Aufrufnamen oder Ressourcentypen müssen dagegen erhalten bleiben. Werden zwei unterschiedliche Fehler in derselben Datei auf denselben Fingerprint reduziert, kann ein später hinzugekommenes Problem von der bestehenden Baseline verdeckt werden.

Für die Baseline empfiehlt sich sortiertes JSON Lines mit einem Datensatz pro Zeile. Die Datei sollte in die Versionsverwaltung aufgenommen werden:

{"fingerprint":"sha256:…","type":"AnalyzeWarning","path":"Sources/Cache.swift","symbol":"load()","message":"Potential leak of an object"}

Beim erstmaligen Aufbau der Baseline müssen die Code-Verantwortlichen jeden Eintrag einzeln prüfen. Dabei ist mindestens sicherzustellen, dass die Diagnose tatsächlich aus dem aktuellen Hauptbranch stammt, vorerst nicht unmittelbar behoben werden kann, bereits ein Tracking-Eintrag existiert und weder Parsing-Fehler noch Duplikate in die Baseline aufgenommen wurden.

In Merge Requests nur die Mengendifferenz vergleichen

Nachdem jeder Job eine current.jsonl erzeugt hat, wird die Menge ihrer Fingerprints mit baseline.jsonl verglichen. current - baseline enthält neue Probleme und muss den Job fehlschlagen lassen. baseline - current enthält Probleme, die bereits verschwunden sind. In diesem Fall sollten die Verantwortlichen aufgefordert werden, die entsprechenden Baseline-Einträge zu löschen, statt ungültige Datensätze dauerhaft beizubehalten.

Die Ausgabe der Qualitätsschranke sollte knapp und unmittelbar nutzbar sein. Für jedes neue Problem werden Typ, relativer Pfad, Zeilennummer, Symbol und Meldung ausgegeben, ergänzt um den Speicherort des vollständigen xcresult-Artefakts. Das Konsolenprotokoll sollte nur eine Zusammenfassung zeigen, damit Tausende Zeilen Analyseverlauf die tatsächlichen Unterschiede nicht verdecken.

Drei Fehlermodi getrennt behandeln

Das Analyseskript muss mindestens die folgenden Zustände unterscheiden:

  1. xcodebuild analyze selbst schlägt fehl, etwa wegen fehlender Abhängigkeiten oder eines nicht verfügbaren Schemes;
  2. das Parsen der Ergebnisse schlägt fehl, beispielsweise weil sich nach einem Toolchain-Upgrade die JSON-Struktur geändert hat;
  3. die Analyse ist erfolgreich, findet aber neue Diagnosen außerhalb der Baseline.

Die ersten beiden Fälle sind Infrastruktur- oder Konfigurationsfehler und dürfen nicht als „null neue Fehler“ erscheinen. Der Job darf nur erfolgreich sein, wenn sowohl Analyse als auch Parsing abgeschlossen wurden und die Mengendifferenz leer ist.

Die Baseline kontinuierlich verkleinern

Bleibt eine Baseline über lange Zeit unverändert, wird sie letztlich zu einer weiteren, nicht gepflegten Ignorierliste. Verantwortliche können pro Modul festgelegt werden, damit sie beim Bearbeiten betroffener Dateien auch Diagnosen in der Umgebung beheben. Alternativ lässt sich für jede Iteration die Bereinigung einer kleinen Zahl besonders riskanter Einträge einplanen. Nach einer Korrektur muss der entsprechende Baseline-Datensatz gelöscht werden. Der nächste Lauf prüft dann, ob das Problem tatsächlich verschwunden ist.

Für die tägliche Kontrolle empfiehlt sich folgende Reihenfolge:

  • Prüfen, dass Toolchain, Scheme, Konfiguration und Zielplattform nicht abgewichen sind;
  • sicherstellen, dass das Analyseverzeichnis von anderen Jobs isoliert ist;
  • prüfen, dass xcresult, Protokoll und normalisierte Liste vollständig erzeugt wurden;
  • stichprobenartig kontrollieren, dass Fingerprints keine absoluten Pfade oder temporären Kennungen enthalten;
  • neue Diagnosen müssen den Merge blockieren, verschwundene Diagnosen müssen auf eine Verkleinerung der Baseline hinweisen;
  • bei Toolchain-Upgrades die Baseline zunächst in einem separaten Branch neu erstellen und die Unterschiede prüfen.

Bei solchen Jobs auf OakVM kommt es nicht darauf an, einen aggressiveren Schwellenwert zu wählen. Entscheidend ist vielmehr, dass Toolchain, Skripte und Ergebnisformat auf demselben physischen Node nachvollziehbar bleiben. Solange Parsing-Fehler nicht als Erfolg behandelt werden und Änderungen an der Baseline stets geprüft werden müssen, wird aus einem leicht übersehenen langen Protokoll eine stabile, inkrementelle Qualitätsschranke für die statische Analyse.

Häufig gestellte Fragen

Warum sollte die Analyse nicht sofort null Warnungen verlangen?

Bei gewachsenen Projekten macht eine große Altlast den Check dauerhaft rot und wird anschließend ignoriert. Eine Baseline hält bekannte Befunde fest, während neue Probleme sofort blockiert werden.

Welche Artefakte sollte der CI-Lauf aufbewahren?

Bewahre das vollständige xcresult, das Build-Protokoll, die normalisierte Diagnoseliste und die verwendete Baseline auf. Damit bleibt jeder fehlgeschlagene Vergleich nachvollziehbar.

OAKVM BUILD NODE

Den nächsten Build auf einem dedizierten physischen Knoten ausführen

Wählen Sie OakVM M4 oder OakVM M4 Pro und mieten Sie einen Cloud-Mac ohne Virtualisierung – tage-, wochen-, monats- oder quartalsweise. Der aktuelle Verfügbarkeitsstatus des Knotens wird in Echtzeit von der Konsole angezeigt.

Konfiguration auswählen und mieten