macOS- und Xcode-Ausgaben in Cloud-Mac-CI zuverlässig parsen

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

macOS- und Xcode-Ausgaben in Cloud-Mac-CI zuverlässig parsen

Wenn dasselbe CI-Skript in einem interaktiven Terminal korrekt läuft, auf einem unbeaufsichtigten Cloud-Mac jedoch das falsche Scheme auswählt, liegt die Ursache häufig nicht bei Xcode selbst. Meist behandelt das Skript für Menschen bestimmte Ausgaben wie eine stabile Schnittstelle. Systemsprache, Werkzeugversion, die Position eingefügter Warnungen oder sogar ein zusätzliches Leerzeichen können grep, cut und Annahmen über feste Zeilennummern unbrauchbar machen. Robuster ist es, bevorzugt JSON oder plist zu lesen und Exit-Code, Rohausgabe und Parsergebnis getrennt zu speichern.

Instabile Textauswertung aufspüren

Suchen Sie zunächst nach besonders riskanten Kombinationen in der Pipeline:

grep -RInE 'xcodebuild|simctl|system_profiler|sw_vers' ci scripts \
  | grep -E 'grep|awk|sed|cut|tail|head'

Nicht jede Pipeline ist automatisch fehlerhaft. Riskant wird es, wenn ein Skript von natürlichsprachlichem Text abhängt, etwa nach „Build Settings“ sucht, die zweite Spalte hinter einem Doppelpunkt übernimmt oder voraussetzt, dass das Zielgerät immer in der dritten Zeile steht. LC_ALL=C verringert zwar sprachbedingte Unterschiede, fixiert aber weder die Feldreihenfolge noch Warnmeldungen oder das Format zukünftiger Versionen.

Wenn eine Befehlsausgabe in erster Linie für Menschen gedacht ist, sollte sie nicht ohne Weiteres als stabile Maschinenschnittstelle behandelt werden.

Bei der Prüfung sollte jeder Lesevorgang drei Fragen eindeutig beantworten: Welches Feld wird benötigt, stellt der Befehl ein strukturiertes Format bereit und soll ein fehlendes Feld zum Abbruch führen oder durch einen ausdrücklich definierten Standardwert ersetzt werden? Leere Zeichenfolgen dürfen nicht unbemerkt in nachfolgende Archivierungsschritte gelangen.

Build-Einstellungen als JSON-Vertrag behandeln

xcodebuild -showBuildSettings -json kann die Build-Einstellungen des Ziels direkt zurückgeben. Prüfen Sie zuerst den Exit-Code des Befehls und parsen Sie anschließend die Datei. Die Standardfehlerausgabe darf nicht mit dem JSON vermischt werden.

set -euo pipefail

mkdir -p artifacts/environment

if ! xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Release \
  -showBuildSettings \
  -json \
  > artifacts/environment/build-settings.json \
  2> artifacts/environment/build-settings.stderr; then
  cat artifacts/environment/build-settings.stderr >&2
  exit 1
fi

python3 - <<'PY'
import json
from pathlib import Path

path = Path("artifacts/environment/build-settings.json")
if not path.exists() or path.stat().st_size == 0:
    raise SystemExit("build settings output is empty")

items = json.loads(path.read_text(encoding="utf-8"))
if len(items) != 1:
    raise SystemExit(f"expected one target, got {len(items)}")

settings = items[0].get("buildSettings", {})
required = ["PRODUCT_BUNDLE_IDENTIFIER", "SDKROOT", "CONFIGURATION_BUILD_DIR"]
missing = [key for key in required if not settings.get(key)]
if missing:
    raise SystemExit("missing build settings: " + ", ".join(missing))

print(settings["PRODUCT_BUNDLE_IDENTIFIER"])
PY

Enthält der Workspace mehrere Targets, darf die Array-Länge nicht erzwungen auf eins geprüft werden. Wählen Sie das gewünschte Ziel stattdessen über das Feld target aus und brechen Sie ab, wenn kein oder mehr als ein Eintrag passt. So liest das Skript nach dem Hinzufügen eines Extension-Targets nicht unbemerkt die falsche Konfiguration.

Geräteauswahl mit simctl-JSON eindeutig machen

Simulatorlisten eignen sich besonders schlecht für eine zeilenbasierte Auswertung. Gerätenamen können Leerzeichen enthalten, und gleichnamige Geräte können zu unterschiedlichen Runtimes gehören. Speichern Sie zunächst das vollständige Ergebnis und filtern Sie anschließend nach Runtime, Name und Verfügbarkeit:

xcrun simctl list -j devices available \
  > artifacts/environment/simulators.json

python3 - <<'PY'
import json
from pathlib import Path

data = json.loads(
    Path("artifacts/environment/simulators.json").read_text(encoding="utf-8")
)
matches = []
for runtime, devices in data.get("devices", {}).items():
    if not runtime.startswith("com.apple.CoreSimulator.SimRuntime.iOS-"):
        continue
    for device in devices:
        if device.get("isAvailable") and device.get("name") == "iPhone 16":
            matches.append((runtime, device["udid"]))

if len(matches) != 1:
    raise SystemExit(f"expected one matching simulator, got {len(matches)}")

print(matches[0][1])
PY

Produktionsskripte sollten außerdem die erwartete Runtime-Version in der Repository-Konfiguration festhalten, anstatt automatisch die „neueste“ auszuwählen. Eine automatische Auswahl der neuesten Version führt dazu, dass Aktualisierungen von Systemkomponenten unmittelbar die Testbasis verändern. Beim abschließenden Testlauf ist die Übergabe der UDID weniger mehrdeutig als die alleinige Angabe des Gerätenamens.

Leere Ergebnismengen nicht ignorieren

„Kein passendes Gerät gefunden“ muss ausdrücklich als Fehler gelten. Bevor vorübergehend ein Simulator erstellt wird, ist zu prüfen, ob die benötigte Runtime tatsächlich vorhanden ist. Nach Abschluss des Jobs dürfen nur die von diesem Job angelegten Geräte gelöscht werden; ein gemeinsam genutzter Gerätebestand darf nicht bereinigt werden. Parallele Jobs sollten jeweils einen eigenen -derivedDataPath und einen separaten CoreSimulator-Gerätebestand verwenden.

Hostdaten aus plist lesen

Die macOS-Versionsinformationen lassen sich direkt aus der System-plist lesen. Dadurch entfällt das Parsen der Beschriftungen in der Ausgabe von sw_vers:

SYSTEM_PLIST=/System/Library/CoreServices/SystemVersion.plist

product_version=$(
  /usr/libexec/PlistBuddy -c 'Print :ProductVersion' "$SYSTEM_PLIST"
)
build_version=$(
  /usr/libexec/PlistBuddy -c 'Print :ProductBuildVersion' "$SYSTEM_PLIST"
)

printf '%s\n' "$product_version" > artifacts/environment/macos-version.txt
printf '%s\n' "$build_version" > artifacts/environment/macos-build.txt
xcodebuild -version > artifacts/environment/xcode-version.txt

Beim Lesen einer Projekt-plist sollte plutil -extract KEY raw -o - FILE bevorzugt werden. Wenn ein Feld fehlen kann, prüfen Sie es zunächst mit plutil -type KEY FILE und lassen Sie das Skript entscheiden, ob das Fehlen zulässig ist. XML darf nicht mit regulären Ausdrücken verarbeitet werden: Eine plist kann binär vorliegen, und Zeichenfolgen können Escape-Sequenzen enthalten.

Eine diagnostizierbare Grenze für die Datenerfassung schaffen

Ein strukturiertes Format bedeutet nicht, dass der zugrunde liegende Befehl zwangsläufig erfolgreich war. Ein zuverlässiger Erfassungsschritt sollte mindestens vier Arten von Nachweisen aufbewahren: den Exit-Code des Befehls, die Standardfehlerausgabe, die unveränderte Rohdatei und das reduzierte Parsergebnis. Die Rohdatei dient zur nachträglichen Analyse des Werkzeugverhaltens, während nachfolgende Jobs das reduzierte Ergebnis verwenden.

Jeder Datensammler sollte möglichst nur für ein Objekt zuständig sein und seine Ausgabe zunächst in eine temporäre Datei schreiben. Erst wenn Syntax und sämtliche Pflichtfelder erfolgreich geprüft wurden, wird die endgültige Datei mit mv atomar ersetzt. So bleibt bei einem Befehlsabbruch kein unvollständiges JSON zurück.

Prüfliste vor dem Commit

Vor dem Zusammenführen von Pipeline-Änderungen sollten mindestens die folgenden Punkte geprüft werden:

  • Das Skript extrahiert keine Felder anhand fester Zeilennummern oder natürlichsprachlicher Beschriftungen.
  • Standardausgabe und Standardfehlerausgabe werden in getrennte Dateien geschrieben.
  • JSON und plist werden vor dem Einlesen syntaktisch validiert.
  • Die Array-Filterung behandelt sowohl null als auch mehrere Treffer ausdrücklich.
  • Xcode-Version, macOS-Buildnummer und die UDID des ausgewählten Geräts können zusammen mit dem Job archiviert werden.
  • Protokolle enthalten ausschließlich Fakten zur Umgebung und geben keine Token, Zugangsdaten oder vollständigen Umgebungsvariablen aus.
  • Nach einer Aktualisierung der Werkzeugversionen wird zuerst die Datenerfassung validiert, bevor reguläre Build-Jobs wieder freigegeben werden.

Das Ziel besteht nicht darin, jeden Befehl auf JSON umzustellen, sondern eine klare Grenze zu schaffen: Maschinen verarbeiten strukturierte Daten, Menschen lesen Diagnoseprotokolle. Wenn tatsächlich keine strukturierte Schnittstelle verfügbar ist, sollten dennoch die Befehlsversion fixiert, die Sprachumgebung isoliert und die Anzahl der Spalten geprüft werden. Ein Parserfehler muss als Fehler behandelt werden, statt mit einem scheinbar plausiblen leeren Wert fortzufahren.

Häufig gestellte Fragen

Reicht es aus, LC_ALL=C zu setzen?

Nein. Das reduziert Sprachunterschiede, fixiert aber weder Feldreihenfolge und Leerzeichen noch Warnungen oder Formatänderungen zwischen Werkzeugversionen. JSON oder plist bleiben erforderlich.

Welche Daten sollten bei einem Befehlsfehler erhalten bleiben?

Bewahren Sie Exitcode, stderr, die ursprüngliche JSON- oder plist-Datei, die Xcode-Version und den macOS-Build auf. Prüfen Sie Dateien vor dem Parsen auf Inhalt und gültige Syntax.

Dedizierter Mac

Verlagere deine nächste Entwicklungsaufgabe auf einen Cloud-Mac

Wähle Berry M4 oder Berry M4 Pro, prüfe die fünf Standorte, die Mietdauer und Zusatzoptionen und gib anschließend die Bestellung auf.

Konfiguration wählen und bestellen