Versteckte lokale Abhängigkeiten mit einem sauberen Checkout finden

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

Versteckte lokale Abhängigkeiten mit einem sauberen Checkout finden

Dasselbe Xcode-Projekt lässt sich im langjährig genutzten Entwicklungsverzeichnis kompilieren, meldet nach dem Kopieren in einen neuen VPSGit Cloud-Mac-Arbeitsbereich jedoch fehlende Dateien, nicht gefundene Skripte oder leere Konfigurationswerte. In diesem Fall sollten nicht zuerst alte Caches übertragen werden. Entscheidend ist vielmehr die Prüfung, ob ein Build ausschließlich anhand eines bestimmten Commits, einer eindeutig deklarierten Toolchain und der von der Pipeline eingespeisten Parameter abgeschlossen werden kann.

Welche Zustände ein sauberer Checkout isolieren muss

Das Löschen von DerivedData schließt lediglich eine Cache-Kategorie aus. Es beweist nicht, dass das Projekt keine lokalen Abhängigkeiten besitzt. In einem über längere Zeit genutzten Arbeitsbereich sammeln sich meist zusätzlich nicht eingecheckte Dateien, Konfigurationen im Benutzerverzeichnis, global installierte Werkzeuge, Shell-Initialisierungsskripte und absolute Pfade zu Dateien außerhalb des Repositorys an.

Mindestens die folgenden fünf Ebenen müssen isoliert werden:

Das Ziel der Abnahme mit einem sauberen Checkout besteht nicht darin, den Build „noch einmal gründlicher zu bereinigen“. Sie soll beweisen, dass kein Zustand unbemerkt verwendet wird, der weder im Versionsverwaltungssystem noch in einer Abhängigkeitsliste oder in den Pipeline-Parametern erfasst ist.

Für die erste Fehlersuche sollte ein Vergleichspaar erhalten bleiben: Der Build im ursprünglichen Arbeitsbereich ist erfolgreich, der Build im isolierten Arbeitsbereich schlägt fehl. Beide müssen denselben Commit, dasselbe Scheme, dieselbe Configuration und dasselbe Destination verwenden. Nur dann besitzt die Abweichung diagnostischen Wert.

Einen temporären isolierten Arbeitsbereich erstellen

Das folgende Skript erstellt aus einem vorhandenen Repository einen Klon ohne lokale Hardlinks, checkt den angegebenen Commit aus und legt Benutzerverzeichnis, temporäres Verzeichnis und Build-Cache vollständig in einem temporären Verzeichnis ab. Projektname und Scheme werden als Parameter übergeben, damit Umgebungsunterschiede nicht fest im Skript verankert werden.

#!/bin/bash
set -euo pipefail

SOURCE_REPO="${1:?source repository required}"
REVISION="${2:?revision required}"
PROJECT="${3:?project path required}"
SCHEME="${4:?scheme required}"

ROOT="$(mktemp -d "${TMPDIR:-/tmp}/clean-checkout.XXXXXX")"
trap 'rm -rf "$ROOT"' EXIT

git clone --no-local --no-checkout "$SOURCE_REPO" "$ROOT/repo"
git -C "$ROOT/repo" checkout --detach "$REVISION"
git -C "$ROOT/repo" submodule update --init --recursive

mkdir -p "$ROOT/home" "$ROOT/tmp" "$ROOT/DerivedData"
export HOME="$ROOT/home"
export TMPDIR="$ROOT/tmp"
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"

cd "$ROOT/repo"
xcodebuild \
  -project "$PROJECT" \
  -scheme "$SCHEME" \
  -configuration Debug \
  -derivedDataPath "$ROOT/DerivedData" \
  CODE_SIGNING_ALLOWED=NO \
  build | tee "$ROOT/build.log"

CODE_SIGNING_ALLOWED=NO eignet sich nur zur Prüfung von Kompilierungsschritten, die keine Signierung erfordern. Muss das Ziel archiviert oder signiert werden, sollte CI die erforderlichen Materialien sicher bereitstellen. Es dürfen nicht lediglich Dateien aus einem persönlichen Benutzerverzeichnis referenziert werden, um das Skript erfolgreich auszuführen.

Fehlende Abhängigkeiten möglichst früh erkennen

Nach dem Klonen sollte zunächst git status --porcelain ausgeführt werden. Die Ausgabe muss leer sein. Anschließend sind der Status der Submodule und die Vollständigkeit der Large-File-Objekte zu prüfen. Enthält das Projekt Schritte zur Codegenerierung, müssen zwei Arten von Artefakten klar unterschieden werden: Quellcode und Version des Generators müssen festgeschrieben sein; die generierten Ergebnisse werden je nach Teamkonvention eingecheckt oder während des Builds reproduzierbar erzeugt.

~/bin, /usr/local/bin oder das Desktop-Verzeichnis eines Teammitglieds dürfen nicht als provisorische Suchpfade hinzugefügt werden. Hilfswerkzeuge gehören in ein Werkzeugverzeichnis des Repositorys, in eine versionsgebundene Paketverwaltungsliste oder müssen bei der Initialisierung des Knotens in einer festgelegten Version installiert werden.

Versteckte Eingaben anhand fehlgeschlagener Builds ermitteln

Schlägt der isolierte Build fehl, ist zuerst nach dem ersten tatsächlichen Fehler zu suchen und nicht nach der Zusammenfassung am Ende des Protokolls. Typische Hinweise lassen sich nach Eingabetyp einordnen:

Fehlermeldung Häufige Ursache Lösungsansatz
No such file or directory Nicht eingecheckte Datei oder absoluter Pfad Datei in das Repository aufnehmen und einen repositoryrelativen Pfad verwenden
command not found Abhängigkeit von einer interaktiven Shell oder einem globalen Werkzeug Werkzeugversion deklarieren und PATH explizit festlegen
Konfigurationswert ist leer Parameter existiert nur in der lokalen Konfiguration Über CI einspeisen und bei Fehlen sofort abbrechen
Modul ist im alten Verzeichnis sichtbar Ein gemeinsam genutzter Cache verdeckt eine fehlende Abhängigkeit Abhängigkeitsdeklaration korrigieren, nicht den alten Cache kopieren
Fehlerhafte Skriptberechtigung Das Ausführungsbit wurde nicht eingecheckt Dateimodus korrigieren und erneut committen

Mit den folgenden Befehlen lässt sich prüfen, ob der aktuelle Commit die Zieldatei und ihre Berechtigungen tatsächlich enthält:

git ls-tree -r HEAD -- path/to/file
git status --short
git diff --summary HEAD

Verweist der fehlerhafte Pfad auf /Users/irgendein-name/, wurde meist ein absoluter Pfad in der Projektkonfiguration, einem Skript oder einer generierten Datei gespeichert. Korrigiert werden muss die Quellkonfiguration, die diesen Pfad erzeugt. Auf dem neuen Knoten ein gleichnamiges Verzeichnis anzulegen, ist keine Lösung.

Die Umgebung einschränken, ohne künstliche Fehler zu erzeugen

Mit env -i lassen sich Umgebungsabhängigkeiten schnell aufdecken. Der Befehl kann jedoch auch für den Build erforderliche Werte wie PATH, Gebietsschema und temporäres Verzeichnis entfernen. Zuverlässiger ist es, zunächst die vom aktuellen Build tatsächlich gelesenen Variablen zu erfassen und daraus eine Positivliste zu erstellen.

Beibehalten werden sollten HOME, TMPDIR, PATH, DEVELOPER_DIR, LANG sowie die von der Pipeline explizit eingespeisten fachlichen Parameter. Für jede benutzerdefinierte Variable sollte eine Pflichtprüfung verwendet werden, zum Beispiel:

: "${API_BASE_URL:?API_BASE_URL must be provided by CI}"
: "${BUILD_FLAVOR:?BUILD_FLAVOR must be provided by CI}"

Dadurch führen fehlende Konfigurationswerte bereits beim Start des Skripts zum Abbruch, statt erst spät im Kompiliervorgang einen mehrdeutigen Fehler auszulösen. Bei sensiblen Werten darf nur geprüft werden, ob sie vorhanden sind; sie dürfen nicht im Protokoll ausgegeben werden. Build-Skripte sollten außerdem keine Konfiguration einer interaktiven Shell einlesen, da diese Dateien bei nicht interaktiven Aufgaben möglicherweise gar nicht geladen werden.

Die Abnahme in die reguläre Pipeline integrieren

Ein vollständig unabhängiger Klon erhöht den Speicherbedarf und verlängert den Checkout. Deshalb muss er nicht jeden kleinen Commit blockieren. Die Prüfung kann in zwei Stufen aufgeteilt werden: Reguläre Aufgaben verwenden für schnelles Feedback einen wiederverwendeten Arbeitsbereich; die Aufgabe mit sauberem Checkout läuft beim Zusammenführen in zentrale Branches, nach Änderungen an Abhängigkeiten oder Xcode sowie vor einer Veröffentlichung.

Für eine erfolgreiche Abnahme müssen alle folgenden Bedingungen erfüllt sein:

  1. Der angegebene Commit lässt sich vollständig in einen unabhängigen Klon auschecken.
  2. Der Arbeitsbaum weist weder vor noch nach dem Build unerwartete Änderungen auf.
  3. Der Build liest weder aus dem ursprünglichen Arbeitsbereich noch aus persönlichen Benutzerverzeichnissen.
  4. Für alle Hilfswerkzeuge existiert eine nachvollziehbare Versionsquelle.
  5. Auch nach dem Leeren von DerivedData wird das erwartete Artefakt erzeugt.
  6. Fehlerprotokoll und Umgebungsübersicht ermöglichen die Reproduktion, enthalten jedoch keine sensiblen Werte.

Wenn ein sauberer Checkout fehlschlägt, während der reguläre Build erfolgreich ist, muss dies als Mangel in der Abhängigkeitsdeklaration behandelt werden. Zuerst sind die Grenzen der Eingaben zu korrigieren; erst danach sollte über die Wiederherstellung von Caches gesprochen werden. Wird diese Abnahme dauerhaft beibehalten, werden die Übernahme durch neue Knoten, parallele Skalierung und Toolchain-Upgrades von einem Glücksspiel beim Maschinenwechsel zu einem reproduzierbaren technischen Prozess.

Häufig gestellte Fragen

Warum reicht git clean für diese Prüfung nicht aus?

git clean entfernt nur nicht versionierte Dateien im aktuellen Arbeitsbaum. HOME-Einstellungen, globale Werkzeuge, gemeinsame Caches, Keychain-Inhalte und externe absolute Pfade bleiben erhalten.

Was sollte nach einem fehlgeschlagenen sauberen Build zuerst geprüft werden?

Vergleichen Sie zuerst Eingabepfade und Umgebungsvariablen des fehlgeschlagenen Befehls. Prüfen Sie danach Submodule, große Dateien, Codegeneratoren, Konfigurationen und deklarierte Hilfswerkzeuge.

Muss die vollständige Prüfung bei jedem Commit laufen?

Nein. Ein kurzer Test kann im normalen Ablauf bleiben. Die vollständige Isolation eignet sich vor Releases, nach Xcode- oder Abhängigkeitsupdates und bei wichtigen Zusammenführungen in den Hauptzweig.

Exklusiver physischer Knoten

Konfigurieren Sie einen Cloud-Mac für Ihren nächsten Build- oder Inferenz-Job

Wählen Sie aus drei Apple-Silicon-Konfigurationen, sechs Knoten und festen Laufzeiten. Die tatsächliche Verfügbarkeit wird in Echtzeit in der Konsole angezeigt.

Cloud-Mac konfigurieren