VMArm Techniknotizen

iOS On-Demand Resources auf einem Cloud-Mac bauen und prüfen

iOS On-Demand Resources auf einem Cloud-Mac bauen und prüfen

Wenn Levels, hochauflösende Texturen oder Offline-Modelle eines iOS-Projekts in On-Demand Resources ausgelagert werden, fällt das Installationspaket in der Regel deutlich kleiner aus. Ein erfolgreicher Build bedeutet jedoch nicht automatisch, dass die Ressourcen auch verfügbar sind. Die häufigsten Probleme sind keine Compilerfehler, sondern fehlende Tag-Zuweisungen, versehentlich vollständig eingebettete Ressourcen in Test-Builds oder Assetpacks im Release-Artefakt, die nicht zu den Anforderungen im Code passen. Ein Cloud-Mac eignet sich dafür, diese Prüfungen als separaten Job zu automatisieren: Jede Archivierung beginnt in einem sauberen Arbeitsbereich, Manifest und Prüfsummen werden aufbewahrt, anschließend wird ein realer Ressourcenabruf ausgeführt.

Zuerst überprüfbare Abnahmekriterien definieren

Die ODR-Abnahme sollte mindestens drei Ebenen abdecken: Projektkonfiguration, exportierte Artefakte und Laufzeitanforderungen. Wer nur eine dieser Ebenen prüft, lässt blinde Flecken zurück.

Auf Projektebene muss sichergestellt werden, dass On-Demand Resources für das Target aktiviert ist und jedes Tag in den Ressourcenverzeichnissen einem klaren Zweck dient. Auf Artefaktebene werden .assetpack-Pakete, Manifestdateien und Datei-Hashes geprüft. Auf Laufzeitebene werden der erste Abruf, die Freigabe der Ressourcen und ein erneuter Abruf getestet.

„Die App startet“ reicht als Abnahmekriterium für ODR nicht aus. Selbst vollständig fehlende Level-Ressourcen verhindern den Startbildschirm nicht, solange noch nicht auf sie zugegriffen wird.

Für jedes Tag sollten vier Angaben festgehalten werden: verantwortliche Person, auslösende Seite, erwarteter Ressourcentyp und Kennzeichnung, ob die Ressource für die Erstinstallation erforderlich ist. Diese Informationen gehören in ein gewöhnliches Textmanifest im Repository und nicht ausschließlich in die Xcode-Oberfläche.

Paketgranularität mit stabilen Tags steuern

Tags sollten nicht eins zu eins an die Anzahl der Ordner gekoppelt werden. Eine zu feine Aufteilung erzeugt zahlreiche kleine Pakete und häufige Abrufe. Eine zu grobe Aufteilung zwingt Benutzer dagegen, für eine einzelne Seite eine ganze Gruppe nicht benötigter Ressourcen herunterzuladen. Robuster ist eine Aufteilung entlang der Benutzerpfade, etwa level-01, tutorial-audio und model-basic. Dabei müssen die im Code verwendeten Abrufnamen exakt mit den Tags der Ressourcenverzeichnisse übereinstimmen.

Tag-Manifest als verpflichtende Prüfung verwenden

Die zulässigen Tags können in einem Manifest im Repository gespeichert werden:

level-01
level-02
tutorial-audio
model-basic

Das Build-Skript extrahiert die Tags aus der Projektdatei oder den generierten Ressourceninformationen, sortiert sie und vergleicht sie mit diesem Manifest. Ein neues Tag muss immer mit einer Änderung des Manifests einhergehen. Beim Entfernen eines Tags müssen außerdem die Aufrufe von NSBundleResourceRequest im Code geprüft werden. So werden Tippfehler bereits beim Zusammenführen und nicht erst zur Laufzeit erkannt.

Tag-Namen sollten ausschließlich stabile ASCII-Zeichen enthalten. Leerzeichen, uneinheitliche Groß- und Kleinschreibung sowie temporäre Versionsnummern sind zu vermeiden. Aktualisierungen von Ressourcen sollten über Inhaltsversionen oder Prüfsummen abgebildet werden, statt fortlaufend nicht wartbare Namen wie level-final-v2-new anzulegen.

Bedingungen für Archivierung und Export festlegen

Für denselben Commit müssen stets derselbe Workspace, dasselbe Scheme, die Release-Konfiguration und dieselben Exportoptionen verwendet werden. Der Job auf einem VMArm-Knoten kann das Ausgabeverzeichnis des aktuellen Durchlaufs vorab leeren. Globale Caches sollten jedoch nicht pauschal gelöscht werden, da sich sonst nur schwer feststellen lässt, ob Änderungen von den Ressourcen oder von der Umgebung verursacht wurden.

set -euo pipefail

: "${WORKSPACE:?WORKSPACE is required}"
: "${SCHEME:?SCHEME is required}"

ROOT="$PWD"
ARCHIVE_PATH="$ROOT/out/Game.xcarchive"
EXPORT_DIR="$ROOT/out/export"

rm -rf "$ARCHIVE_PATH" "$EXPORT_DIR"
mkdir -p "$EXPORT_DIR"

xcodebuild \
  -workspace "$WORKSPACE" \
  -scheme "$SCHEME" \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  -archivePath "$ARCHIVE_PATH" \
  clean archive

xcodebuild \
  -exportArchive \
  -archivePath "$ARCHIVE_PATH" \
  -exportPath "$EXPORT_DIR" \
  -exportOptionsPlist "$ROOT/ci/ExportOptions.plist"

Für den lokalen Abnahmepfad kann embedOnDemandResourcesAssetPacksInBundle auf true gesetzt werden, um zunächst die Paketaufteilung und die Abruflogik zu verifizieren. Der Produktionspfad muss dagegen die Exportkonfiguration der tatsächlichen Verteilungsmethode verwenden und darf die lokale Einbettungseinstellung nicht übernehmen. Die Artefakte beider Pfade müssen in getrennten Verzeichnissen gespeichert werden, damit ein Test-Build nicht das zur Veröffentlichung vorgesehene Paket überschreibt.

Artefakte auf drei Ebenen prüfen

Auf der ersten Ebene wird geprüft, ob die Dateien vorhanden sind. Auf der zweiten Ebene wird die Lesbarkeit der plist-Dateien kontrolliert. Auf der dritten Ebene werden stabile Prüfsummen für alle Ressourcendateien erzeugt, damit sich zwei Builds desselben Commits vergleichen lassen.

set -euo pipefail

ARCHIVE_PATH="$PWD/out/Game.xcarchive"
EXPORT_DIR="$PWD/out/export"
REPORT_DIR="$PWD/out/report"

mkdir -p "$REPORT_DIR"

find "$ARCHIVE_PATH" "$EXPORT_DIR" \
  -name '*.assetpack' \
  -print | LC_ALL=C sort > "$REPORT_DIR/assetpacks.txt"

find "$ARCHIVE_PATH" "$EXPORT_DIR" \
  -name 'AssetPackManifest.plist' \
  -exec plutil -lint {} \; > "$REPORT_DIR/plist-lint.txt"

find "$ARCHIVE_PATH" "$EXPORT_DIR" \
  -type f \
  \( -name '*.car' -o -name '*.plist' -o -name '*.assetpack' \) \
  -exec shasum -a 256 {} \; |
  LC_ALL=C sort > "$REPORT_DIR/checksums.txt"

test -s "$REPORT_DIR/assetpacks.txt"

Falls es sich bei .assetpack tatsächlich um ein Verzeichnis handelt, berechnet der letzte Hash-Befehl nicht direkt die Prüfsummen seiner Inhalte. Deshalb müssen auch die regulären Dateien innerhalb dieses Verzeichnisses in die Prüfung einbezogen werden. Der Bericht sollte den Commit-Hash, die Xcode-Version, das Scheme und den Hash der Exportkonfigurationsdatei enthalten. Signaturschlüssel, Tokens oder vollständige Pfade zu Zugangsdaten dürfen nicht aufgenommen werden.

Beim Vergleich zweier Berichte sollte zunächst zwischen einer geänderten Dateireihenfolge und tatsächlichen Inhaltsänderungen unterschieden werden. Eine einheitliche Sortierung beseitigt Unterschiede der ersten Art. Ändern sich die Hashes für denselben Commit weiterhin, sollte geprüft werden, ob Generierungsskripte Zeitstempel, absolute Workspace-Pfade oder zufällige Kennungen eintragen.

Laufzeitprüfung und Fehlerdiagnose

Laufzeittests müssen in einem sauberen Zustand beginnen. Nach der Installation des Abnahme-Builds wird ein Tag angefordert, das nicht zur Erstinstallation gehört. Dabei werden drei Ereignisse protokolliert: Beginn des Abrufs, Freigabe des Zugriffs und Freigabe der Ressourcen. Anschließend wird die App beendet, die Testumgebung bereinigt und der Vorgang erneut ausgeführt. So lässt sich ausschließen, dass der Erfolg lediglich auf einem alten Cache beruht.

Empfohlene Reihenfolge bei der Fehlersuche

Schlägt eine Anforderung sofort fehl, sollten zuerst das Tag im Code und das Tag im Ressourcenverzeichnis verglichen werden. Bleibt eine Anforderung lange ausstehend, sind der korrekte Export des Ressourcenpakets und das Testnetzwerk zu prüfen. Funktioniert die lokale Einbettung, nicht aber der Produktionspfad, sollten insbesondere die beiden ExportOptions-Dateien und die Konfiguration des Ressourcenhostings verglichen werden. Fehlt nur ein Teil der Ressourcen, ist zu kontrollieren, ob gleichnamige Dateien mehreren Tags zugeordnet wurden oder ob die Ressourcen weiterhin zum Haupt-Bundle gehören.

Fehler dürfen nicht durch unbegrenzte Wiederholungsversuche kaschiert werden. Für Anforderungen ist ein eindeutiges Zeitlimit festzulegen. Fehlerprotokolle sollten ausschließlich Tag, Fehlerdomäne, Fehlercode und Verarbeitungsphase enthalten, jedoch keine Zugriffstokens oder sensiblen Pfade. Nach Abschluss der Abnahme werden Berichte, der Hash der Exportkonfiguration und Fehlerprotokolle aufbewahrt; temporäre Pakete und Test-Caches werden gelöscht. Treten bei der nächsten ODR-Änderung Abweichungen auf, können so direkt die Artefakte verglichen werden, anstatt erneut darüber zu rätseln, was Xcode ausgeführt hat.

Häufig gestellte Fragen

Sollten ODR-Pakete in einen Entwicklungsbuild eingebettet werden?

Für die erste lokale Prüfung ist das sinnvoll. Mit embedOnDemandResourcesAssetPacksInBundle=true werden Paketierungsfehler getrennt von Netzwerkproblemen geprüft. Der produktionsnahe Export erfolgt anschließend mit den realen Auslieferungseinstellungen.

Reicht die Größe der IPA-Datei als ODR-Prüfung aus?

Nein. Zusätzlich müssen Anzahl und Inhalt der Assetpacks, Tag-Zuordnung, AssetPackManifest.plist, Prüfsummen sowie der erste Abruf nach einer Cache-Bereinigung geprüft werden.

Dedizierter physischer Mac-Knoten

Wählen Sie einen dauerhaft verfügbaren Cloud-Mac für Ihren nächsten Xcode-Build

Prüfen Sie Chip, Arbeitsspeicher, Speicher, Abrechnungszeitraum und Knotenregion, bevor Sie mit der Konfiguration beginnen. Der tatsächlich verfügbare Status wird in Echtzeit von der Konsole angezeigt.

Cloud-Mac-Konfiguration auswählen