Ein Swift Package funktioniert in der lokalen Vorschau einwandfrei, zeigt nach der Einbindung in den Archivierungsjob auf einem Cloud Mac jedoch leere Symbole oder Standardtexte an oder kann JSON-Dateien nicht mehr lesen. Das größte Problem ist dabei meist kein Compilerfehler, sondern eine Pipeline, die weiterhin erfolgreich durchläuft. SwiftPM stellt zwar Bundle.module bereit, prüft aber weder, ob das Team alle Ressourcen vollständig deklariert hat, noch ob die verwendeten Namen übereinstimmen. Ebenso wenig garantiert es, dass die Ressourcen tatsächlich im finalen App-Bundle gelandet sind. Deshalb sollten Ressourcen als Bestandteil des Build-Artefakts abgenommen werden, statt ihr Fehlen erst festzustellen, wenn das Testteam die betreffende Ansicht öffnet.
Die drei möglichen Stellen eines Ressourcenverlusts bestimmen
Vor der Fehlersuche muss zunächst geklärt werden, auf welcher Ebene das Problem auftritt. Auf der ersten Ebene liegt die Datei im Quellverzeichnis, wurde im Target der Package.swift jedoch nicht als Ressource deklariert. Auf der zweiten Ebene wurde bereits ein .bundle erzeugt, der aufrufende Code verwendet aber das Bundle der Hauptanwendung. Auf der dritten Ebene befindet sich die Datei zwar im App-Bundle, doch Groß- und Kleinschreibung des Dateinamens, Unterverzeichnis oder Lokalisierungsverzeichnis stimmen nicht mit der Laufzeitabfrage überein.
Es empfiehlt sich, einen minimalen Reproduktionspfad vorzuhalten, der jeweils ein Bild, eine JSON-Datei und eine lokalisierte Zeichenfolge lädt. Schlagen alle drei Ressourcentypen gleichzeitig fehl, sollte zuerst die Bundle-Auswahl geprüft werden. Betrifft der Fehler nur eine einzelne Datei, sind zunächst Ressourcenregel und Pfad zu kontrollieren.
„Erfolgreich kompiliert“ bedeutet lediglich, dass der Compiler die aktuellen Eingaben akzeptiert hat. Es beweist nicht, dass alle zur Laufzeit benötigten Dateien im auszuliefernden Artefakt enthalten sind.
In der Package.swift sollte ausdrücklich zwischen .process und .copy gewählt werden. Bilder, Ressourcenverzeichnisse und lokalisierte Inhalte werden üblicherweise mit .process verarbeitet. .copy ist nur für Dateien vorgesehen, deren ursprüngliche Verzeichnisstruktur oder Bytefolge erhalten bleiben muss. Das gesamte Repository-Verzeichnis sollte nicht pauschal in das Ressourcen-Bundle kopiert werden, da dadurch auch Testdaten, temporäre Ausgaben oder Konfigurationen in die Auslieferung gelangen können, die dort nicht hingehören.
Den Ressourcenvertrag mit einer Manifestdatei festschreiben
Bei der Ressourcenprüfung reicht es nicht, lediglich die Anzahl der Dateien zu vergleichen. Wird eine Ressource hinzugefügt, eine alte entfernt oder eine Datei umbenannt, kann die Gesamtzahl unverändert bleiben. Robuster ist eine im Repository verwaltete Datei ci/expected-resources.txt, in der jede Zeile einen Pfad relativ zum Stammverzeichnis des Ressourcen-Bundles enthält:
Assets.car
Defaults/config.json
en.lproj/Localizable.strings
zh-Hans.lproj/Localizable.strings
Das Manifest sollte ausschließlich Dateien enthalten, die zur Laufzeit zwingend vorhanden sein müssen. Bei Bedarf heruntergeladene Inhalte, Test-Fixtures und Ressourcen für Entwicklungsvorschauen gehören nicht hinein. Bei einer Umbenennung von Ressourcen müssen die Dateiänderung und die Anpassung des Manifests im selben Review erscheinen. So können Reviewer unmittelbar erkennen, wie sich der Auslieferungsvertrag geändert hat.
Auch das Ressourcenverzeichnis selbst sollte in Gegenrichtung geprüft werden: Für Dateien, die im Quellcode vorhanden, aber nicht deklariert sind, sollte eine Warnung ausgegeben werden. Fehlt dagegen eine laut Manifest erforderliche Datei im Build-Ergebnis, muss der Job sofort fehlschlagen. Die erste Prüfung hilft beim Bereinigen des Repositorys, die zweite schützt das tatsächliche Auslieferungsergebnis.
Das App-Bundle direkt nach dem Build prüfen
Jeder Job sollte ein eigenes DerivedData-Verzeichnis verwenden. Andernfalls kann ein altes Bundle aus einem parallel laufenden Job dazu führen, dass die Prüfung fälschlicherweise erfolgreich ist. Das folgende Skript verwendet beispielhaft einen Simulator-Build. In der tatsächlichen Pipeline müssen lediglich das Scheme und das Suchmuster für den Bundle-Namen an den realen Modulnamen des Projekts angepasst werden.
set -euo pipefail
DERIVED="${RUNNER_TEMP:-/tmp}/resource-check-${BUILD_ID:-local}"
rm -rf "$DERIVED"
xcodebuild \
-scheme ExampleApp \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath "$DERIVED" \
build
APP=$(find "$DERIVED/Build/Products" -type d -name 'ExampleApp.app' -print -quit)
test -n "$APP"
COUNT=$(find "$APP" -type d -name '*FeatureKit*.bundle' | wc -l | tr -d ' ')
test "$COUNT" = "1"
BUNDLE=$(find "$APP" -type d -name '*FeatureKit*.bundle' -print -quit)
while IFS= read -r item; do
test -z "$item" && continue
test -e "$BUNDLE/$item" || {
printf 'missing resource: %s\n' "$item" >&2
exit 1
}
done < ci/expected-resources.txt
Das Skript stellt bewusst zuerst sicher, dass genau ein Bundle vorhanden ist. Würde einfach der erste Treffer verwendet, könnten alte Artefakte, Test-Bundles oder gleichnamige Module den eigentlichen Fehler verdecken. Geprüft werden muss außerdem das Bundle innerhalb der .app und nicht ein Zwischenverzeichnis unter DerivedData, denn nur Ersteres bildet das finale Einbettungsergebnis ab.
Häufige Fehlerquellen in verbindliche Gates überführen
Unterschiedliche Ressourcentypen erfordern unterschiedliche Abnahmeverfahren. Eine pauschale Prüfung mit test -e genügt nicht.
| Ressourcentyp | Mindestprüfung | Typischer Fehler |
|---|---|---|
| JSON | Datei ist vorhanden und kann geparst werden | Leere oder syntaktisch ungültige Datei wurde kopiert |
| Lokalisierte Zeichenfolgen | Verzeichnis der Zielsprache ist vorhanden und Schlüssel können gelesen werden | Falsch benanntes Sprachverzeichnis oder Rückfall auf den Standardtext |
| Bilder und Farben | Kompiliertes Ressourcenartefakt ist vorhanden und kann in einer Smoke-Test-Ansicht geladen werden | Abweichende Groß- und Kleinschreibung oder irrtümliche Verwendung des Haupt-Bundles |
| Vorlagendateien | Hash oder maßgebliche Felder werden geprüft | Der Generierungsschritt hat die Repository-Version überschrieben |
Unter macOS sind gängige Arbeitsvolumes nicht zwischen Groß- und Kleinschreibung unterscheidend. Ein Fehler wie IconDark statt icondark kann deshalb auf einem Entwicklungsrechner unbemerkt bleiben. Statt sich auf die zufällige Fehlertoleranz des Dateisystems zu verlassen, sollten die Pfade aus dem Manifest extrahiert und unter Beachtung der Groß- und Kleinschreibung exakt mit den tatsächlichen Pfaden verglichen werden. JSON-Dateien müssen zusätzlich von einem Parser geprüft werden. Für Property Lists sollte plutil -lint ausgeführt werden, und bei Zeichenfolgenverzeichnissen sind mindestens die erforderlichen Sprachen und zentralen Schlüssel zu kontrollieren.
Verwendet das Package ein Generierungs-Plug-in, muss zunächst sichergestellt werden, dass der Generierungsschritt vor der Ressourcenprüfung ausgeführt wird. Das Ausgabeverzeichnis ist außerdem auf einen temporären, ausschließlich diesem Job zugeordneten Pfad zu beschränken. Nutzen mehrere Jobs dasselbe Generierungsverzeichnis, kann ein Job Dateien aus einem anderen Branch lesen und dadurch einen nur schwer reproduzierbaren Scheinerfolg erzeugen.
Nachweise sichern und Jobs reproduzierbar halten
Bei einem Fehler sollte nicht das gesamte DerivedData-Verzeichnis hochgeladen werden. Es ist groß, enthält viele irrelevante Daten und kann Pfadinformationen umfassen, die nicht langfristig gespeichert werden sollten. Ein zweckmäßigeres Nachweispaket besteht aus dem Ressourcenmanifest, dem tatsächlichen Dateibaum des Bundles, dem letzten Abschnitt des Build-Protokolls, den Parserfehlern und der Kennung des aktuellen Commits. Der Dateibaum lässt sich wie folgt erzeugen:
find "$BUNDLE" -print \
| sed "s#^$BUNDLE/##" \
| LC_ALL=C sort \
> resource-bundle-tree.txt
Vor der Archivierung müssen Zugriffstoken, Benutzerverzeichnisse und temporäre Zugangsdaten aus Protokollen und Dateibäumen entfernt werden. Nach Abschluss des Jobs sind das separate DerivedData-Verzeichnis und die temporären Generierungsverzeichnisse zu löschen, damit der nächste Lauf wieder mit leeren Verzeichnissen beginnt. Für parallele Runner auf DPLYMAC gilt dasselbe Prinzip: Download-Caches dürfen wiederverwendet werden, nicht gekennzeichnete Build-Ausgaben dagegen nicht.
Das abschließende Gate muss drei Fragen beantworten: Was wird laut Deklaration benötigt, was befindet sich tatsächlich im finalen App-Bundle und kann die Laufzeit die Ressource unter ihrem echten Namen lesen? Wenn für jede dieser drei Ebenen ein überprüfbares Ergebnis vorliegt, werden fehlende SwiftPM-Ressourcen von sporadischen Oberflächenfehlern zu gewöhnlichen Build-Fehlern, die bereits beim Commit eindeutig lokalisiert werden können.
Häufig gestellte Fragen
Warum kann ein SwiftPM-Paket erfolgreich bauen, aber eine Ressource zur Laufzeit nicht finden?
Ein erfolgreicher Build bestätigt weder die Schreibweise des Laufzeitnamens noch den endgültigen Ablageort. Deshalb müssen die erzeugte .app und das Resource-Bundle direkt geprüft werden.
Soll die Prüfung den Quellbaum oder nur das gebaute Produkt untersuchen?
Beides ist erforderlich. Im Quellbaum fallen nicht deklarierte Dateien auf, während das gebaute Produkt ihre Verarbeitung und Einbettung in das Lieferergebnis bestätigt.
Geprüfte Workflows auf exklusiven physischen Knoten ausführen
Wählen Sie je nach Aufgabe M4 oder M4 Pro, den Zielknoten und den Abrechnungszeitraum. Prüfen Sie Konfiguration und Zusatzoptionen vor der Bestellung.