Symbolische Links in Cloud-Mac-CI prüfen

Symbolische Links in Cloud-Mac-CI prüfen

Dasselbe Projekt lässt sich auf einem Entwickler-Mac problemlos archivieren, während die Cloud-Mac-CI eine fehlende Ressource meldet. Eine der am häufigsten übersehenen Ursachen sind symbolische Links. Eine Datei, die im Repository unauffällig erscheint, kann tatsächlich auf ein lokales Verzeichnis, ein nicht eingechecktes Ziel oder einen Pfad außerhalb des Workspace verweisen. Xcode greift oft erst beim Kopieren von Ressourcen, Ausführen von Skripten oder Verpacken von Abhängigkeiten darauf zu. Dadurch tritt der Fehler erst in einer späten Build-Phase auf und erscheint im Protokoll häufig lediglich als gewöhnliches „Datei nicht gefunden“.

Zuerst zulässige Links definieren

Bevor ein Prüfsystem geschrieben wird, muss die Richtlinie eindeutig feststehen. Für kontinuierliche Builds reichen in der Regel drei Standardregeln: Links müssen relative Pfade verwenden, ihre aufgelösten Ziele müssen vorhanden sein und innerhalb des aktuellen Workspace liegen. Interne Links, die von bestimmten Werkzeugen erzeugt werden, können ausgenommen werden. Solche Ausnahmen sollten jedoch immer auf vollständige relative Pfade beschränkt bleiben.

Ziel einer Zugangsprüfung für symbolische Links ist nicht, Links vollständig zu verbieten, sondern implizite Abhängigkeiten von der Verzeichnisstruktur eines bestimmten Rechners zu beseitigen.

So kann Config/current.json -> release.json zuverlässig zusammen mit dem Repository ausgecheckt werden. SDK/current -> /Users/dev/SDK überträgt dagegen den lokalen Zustand eines Entwicklerrechners in die Pipeline. Auch ../Shared/file lässt sich nicht allein anhand des Pfadtexts beurteilen: Das Ziel kann sich noch innerhalb des Repository befinden, aber ebenso den von der CI bereitgestellten Arbeitsbereich verlassen. Deshalb muss der Pfad zuerst aufgelöst und normalisiert werden, bevor seine Grenzen geprüft werden.

Prüfkriterium Zulässig Zu blockieren
Linkform Relativer Pfad innerhalb des Repository Absoluter Pfad
Zielstatus Ziel ist vorhanden und lesbar Defekter oder zyklischer Link
Pfadgrenze Aufgelöstes Ziel liegt im Workspace Verweist aus dem Workspace heraus
Ausnahmeregel Vollständiger relativer Pfad Allgemeines Verzeichnispräfix

Linktyp über den Git-Index bestätigen

Modus statt Dateiendung prüfen

Git speichert symbolische Links mit dem Modus 120000; das Linkziel wird als Dateiinhalt abgelegt. Listen Sie die Links zuerst direkt aus dem Index auf, damit gewöhnliche Textdateien und erst während des Builds erzeugte Links nicht in die Prüfung geraten.

git ls-files -s | awk '$1 == "120000" { print $4 }'
git config --show-origin --get core.symlinks || true
git status --porcelain=v1

Unter macOS muss das ausgecheckte Objekt tatsächlich ein symbolischer Link sein. Falls der Repository-Status plötzlich zeigt, dass ein Link durch eine gewöhnliche Datei ersetzt wurde, sollten zunächst Verpackungs-, Entpack- und Synchronisierungsschritte darauf geprüft werden, ob sie den Dateityp verändert haben. Die Zugangsprüfung sollte nach einem sauberen Checkout außerdem einmal git diff --exit-code ausführen. So lässt sich feststellen, ob ein Initialisierungsskript den im Index gespeicherten Linktext unbemerkt geändert hat.

Nicht verfolgte Ziele erkennen

Dass der Link selbst eingecheckt wurde, bedeutet nicht, dass auch sein Ziel versioniert ist. Für jeden von Git verfolgten Link muss geprüft werden, ob das Ziel existiert. Liegt es innerhalb des Repository, lässt sich mit git ls-files --error-unmatch feststellen, ob es ebenfalls der Versionskontrolle unterliegt. Wird das Ziel planmäßig erst beim Build erzeugt, sollte die Prüfung hinter den Generierungsschritt verschoben werden. Gleichzeitig ist für die Phase davor eine präzise Liste der Ziele zu führen, die vorübergehend fehlen dürfen.

Defekte und grenzüberschreitende Links per Skript blockieren

Das folgende Skript prüft das angegebene Verzeichnis rekursiv. Es weist absolute Links, nicht auflösbare Ziele und Ziele zurück, die nach der Auflösung außerhalb des Stammverzeichnisses liegen. Speichern Sie es unter ci/check_symlinks.py und führen Sie es nach der Installation der Abhängigkeiten, aber vor dem Aufruf von xcodebuild aus.

import os
import sys
from pathlib import Path

root = Path(sys.argv[1] if len(sys.argv) > 1 else ".").resolve()
failures = []

for path in root.rglob("*"):
    if not path.is_symlink():
        continue

    raw = os.readlink(path)
    relative = path.relative_to(root)

    if os.path.isabs(raw):
        failures.append((relative, "absolute", raw))
        continue

    try:
        target = path.resolve(strict=True)
    except (FileNotFoundError, RuntimeError, OSError) as error:
        failures.append((relative, "broken", str(error)))
        continue

    try:
        target.relative_to(root)
    except ValueError:
        failures.append((relative, "outside", str(target)))

for item in failures:
    print("\t".join(map(str, item)))

sys.exit(1 if failures else 0)

Der Aufruf lautet:

python3 ci/check_symlinks.py "$PWD"

Pfadgrenzen dürfen nicht mit einem einfachen Zeichenfolgenpräfix verglichen werden. /tmp/job-10 beginnt zwar als Zeichenfolge mit /tmp/job-1, ist aber kein Unterverzeichnis davon. Solche Fehlentscheidungen lassen sich nur vermeiden, indem normalisierte Pfade anhand ihrer Verzeichnishierarchie verglichen werden.

Vor und nach dem Build prüfen

Eine Prüfung des Quellcodes findet nur Probleme, die bereits im Repository vorhanden sind. Paketmanager, Codegeneratoren und benutzerdefinierte Build Phases können jedoch weitere Links erzeugen. Deshalb sind zwei Prüfzeitpunkte empfehlenswert.

Workspace vor dem Build prüfen

Scannen Sie nach dem Auflösen der Abhängigkeiten und der Codegenerierung den gesamten Workspace. Ausgenommen werden dürfen nur Cache-Verzeichnisse, die eindeutig zur internen Implementierung eines Werkzeugs gehören und nicht in das Build-Artefakt übernommen werden. Auch diese Ausschlussregeln müssen fest im Repository gespeichert sein und dürfen nicht durch temporäre Umgebungsvariablen auf dem Runner beliebig erweitert werden. Erfassen Sie anschließend die vorhandenen Links, damit später nachvollzogen werden kann, welche während des Builds hinzugekommen sind.

find "$PWD" -type l -print | LC_ALL=C sort > "$TMPDIR/symlinks-before.txt"
python3 ci/check_symlinks.py "$PWD"

Build-Artefakte nach dem Build prüfen

Führen Sie dasselbe Skript nach Abschluss der Archivierung erneut für die exportierte .app oder das Stammverzeichnis des entpackten Artefakts aus. Ein zulässiger Link muss gleichzeitig zwei Bedingungen erfüllen: Sein Ziel liegt weiterhin innerhalb des Artefakts und sein Pfad steht auf der Freigabeliste. Enthält ein Drittanbieter-Framework tatsächlich interne Links, sollten konkrete Pfade freigegeben werden, nicht das gesamte Verzeichnis Frameworks.

Vergleichen Sie anschließend die Linklisten vor und nach dem Build. Wurde ein neuer Link von einer Skriptphase angelegt, muss er sich einem konkreten Generierungsbefehl zuordnen lassen. Nicht erklärbare neue Einträge sollten den Release zunächst blockieren; der Erzeuger lässt sich danach über das Build-Protokoll ermitteln.

Häufige Fehlalarme behandeln und eine Zugangsprüfung etablieren

Die häufigsten Fehlalarme entstehen durch temporäre Verzeichnisse, Abhängigkeits-Caches und interne Archivstrukturen. Verkleinern Sie zuerst den Scan-Stamm, definieren Sie danach präzise Ausnahmen und erwägen Sie erst zuletzt den Ausschluss ganzer Verzeichnisse. Nur weil eine Abhängigkeit einen Link erzeugt, darf nicht der gesamte Abhängigkeitsbaum übersprungen werden. Andernfalls bleiben dort auch echte defekte Links verborgen.

Bei einem Fehlschlag muss die Prüfung mindestens den relativen Linkpfad, den Fehlertyp und das aufgelöste Ziel ausgeben. Der Exit-Code des Prüfscripts ist unverändert an die CI weiterzugeben. Nach der Korrektur muss die Prüfung mit einem vollständig neuen Checkout wiederholt werden; ein erneuter Lauf in einem Workspace, in dem bereits Initialisierungsskripte ausgeführt wurden, reicht nicht aus. Die endgültige Checkliste lässt sich auf sechs Punkte reduzieren: korrekter Git-Modus, versioniertes Ziel, relativer Linkpfad, kein Verlassen des Workspace nach der Auflösung, erklärbare während des Builds hinzugefügte Links und exakt passende Archiv-Ausnahmen. Sind diese sechs Bedingungen erfüllt, werden symbolische Links von einem sporadischen Umgebungsproblem zu einem überprüfbaren technischen Vertrag.

Häufig gestellte Fragen

Warum funktioniert ein Link lokal, aber nicht auf dem Cloud-Mac-Runner?

Häufig verweist er auf einen lokalen absoluten Pfad, ein nicht versioniertes Ziel oder eine fest angenommene Checkout-Position. Prüfen Sie Linktext und aufgelöstes Ziel gemeinsam.

Sollten alle symbolischen Links im Repository verboten werden?

Nein. Stabile relative Links innerhalb des Repositorys sind vertretbar. Abgelehnt werden sollten defekte, absolute und aus dem Workspace führende Links.

Wie werden legitime Links in einem Archiv freigegeben?

Das aufgelöste Ziel muss innerhalb des Archivstamms liegen. Danach wird nur der exakte relative Pfad freigegeben; breite Präfixregeln sind zu vermeiden.

Nächste Aufgabe ausführen

Wählen Sie den passenden Cloud-Mac für Ihre Workloads

Prüfen Sie Chip, Arbeitsspeicher, Speicherplatz, Mietdauer und Standort und bestellen Sie einen exklusiven physischen Apple-Silicon-Server.

Modell auswählen und bestellen