Engineering-Notizen von MiniDebug

Eine Größenkontrolle für iOS-Builds auf dem Cloud-Mac einrichten

Eine Größenkontrolle für iOS-Builds auf dem Cloud-Mac einrichten

Eine scheinbar harmlose Funktionszusammenführung lässt das Archiv plötzlich um mehr als zehn Megabyte anwachsen, obwohl im Commit weder große Bilder noch eindeutig neue Abhängigkeiten zu finden sind. Wer die IPA erst vor der Veröffentlichung manuell prüft, entdeckt das Problem meist erst, nachdem es bereits in mehrere Branches gelangt ist. Zuverlässiger ist es, in einer fest definierten Cloud-Mac-Build-Umgebung eine Größenbaseline zu hinterlegen und nach jeder Archivierung App, ausführbare Hauptdatei und dynamische Frameworks automatisch getrennt zu messen. Wird ein Grenzwert überschritten, wird die Zusammenführung gestoppt.

Zunächst vergleichbare Messgrößen definieren

Für die „Paketgröße“ gibt es mindestens drei unterschiedliche Messgrößen, die nicht in derselben Kurve vermischt werden dürfen.

Metrik Messobjekt Hauptzweck
Entpackte App-Größe .app-Verzeichnis innerhalb des .xcarchive Zuwachs bei Ressourcen, Frameworks und Lokalisierungsdateien erkennen
Größe des Hauptprogramms Mach-O-Datei, auf die CFBundleExecutable verweist Änderungen an Code, statischen Bibliotheken und Symbolen erkennen
IPA-Größe Exportierte komprimierte Datei Entwicklung der tatsächlichen Downloadgröße beobachten

Für die Größenkontrolle sollten vor allem die ersten beiden Werte maßgeblich sein. Die IPA-Größe hängt von Kompressionsrate und Dateianordnung ab. Selbst kleine Änderungen an denselben Ressourcen können das Kompressionsergebnis vergrößern oder verkleinern. Debugsymbole befinden sich im Verzeichnis dSYMs des Archivs und dürfen nicht zum Installationspaket für Benutzer gezählt werden. Sie sollten jedoch separat archiviert werden, damit sich spätere Probleme untersuchen lassen.

Größenunterschiede sind nur bei identischen Build-Bedingungen aussagekräftig. Xcode-Pfad, Konfiguration, Zielplattform, Exportverfahren und Compileroptionen müssen fest vorgegeben sein.

Archiv mit einem festen Befehl erzeugen

Zunächst werden die Abhängigkeiten in einem sauberen Arbeitsverzeichnis aufgelöst. Anschließend wird ein Release-Archiv für ein physisches Gerät erzeugt. Workspace-Name und Scheme werden als Job-Parameter übergeben, damit keine projektspezifischen Angaben fest im Skript hinterlegt sind.

set -euo pipefail

WORKSPACE="${WORKSPACE:?missing WORKSPACE}"
SCHEME="${SCHEME:?missing SCHEME}"
OUT="${OUT:-$PWD/build-size}"
ARCHIVE="$OUT/App.xcarchive"

rm -rf "$OUT"
mkdir -p "$OUT"

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

Wenn während der Archivierung zwingend signaturbezogene Skripte ausgeführt werden müssen, sollte die Codesignierung nicht erzwungen deaktiviert werden. Verwenden Sie stattdessen die bereits vorhandene, kontrollierte Projektkonfiguration. Entscheidend für die Größenkontrolle ist die Übereinstimmung mit dem produktiven Build – nicht ein universeller Befehl für jedes Repository. Bei der erstmaligen Einrichtung muss außerdem geprüft werden, ob die Release-Konfiguration irrtümlich Testressourcen, Diagnosebibliotheken oder Adressen von Entwicklungsservern enthält.

App, ausführbare Datei und Frameworks getrennt messen

Das folgende Skript sucht im Archiv nach der einzigen App, liest den Namen des Hauptprogramms aus und gibt maschinenlesbare TSV-Daten aus. du -sk eignet sich für fortlaufende Vergleiche des Verzeichnisumfangs, während stat die exakte Bytezahl des Hauptprogramms liefert.

set -euo pipefail

ARCHIVE="${1:?usage: measure.sh path/to/App.xcarchive}"
APP_ROOT="$ARCHIVE/Products/Applications"
APP_PATH="$(find "$APP_ROOT" -maxdepth 1 -type d -name '*.app' -print -quit)"

test -n "$APP_PATH"
EXECUTABLE="$(/usr/libexec/PlistBuddy \
  -c 'Print :CFBundleExecutable' "$APP_PATH/Info.plist")"

printf "kind	name	bytes
"
APP_KB="$(du -sk "$APP_PATH" | awk '{print $1}')"
printf "app	%s	%s
" "$(basename "$APP_PATH")" "$((APP_KB * 1024))"
printf "executable	%s	%s
" "$EXECUTABLE" \
  "$(stat -f '%z' "$APP_PATH/$EXECUTABLE")"

FRAMEWORKS="$APP_PATH/Frameworks"
if test -d "$FRAMEWORKS"; then
  find "$FRAMEWORKS" -maxdepth 1 -type d -name '*.framework' -print0 |
  while IFS= read -r -d '' item; do
    kb="$(du -sk "$item" | awk '{print $1}')"
    printf "framework	%s	%s
" "$(basename "$item")" "$((kb * 1024))"
  done
fi

Der Bericht sollte als Artefakt des jeweiligen Builds gespeichert werden, statt lediglich eine Gesamtsumme auszugeben. Bei einer Größenregression können Reviewer so unmittelbar erkennen, ob der Zuwachs vom Hauptprogramm, von einem bestimmten Framework oder von Ressourcen im gesamten App-Verzeichnis stammt.

Ressourcenverzeichnisse weiter aufschlüsseln

Wenn die App insgesamt größer wird, während Hauptprogramm und Frameworks stabil bleiben, können Assets.car, Lokalisierungsverzeichnisse, Offline-Datendateien und Medienressourcen jeweils separat mit stat oder du gemessen werden. Dateien, die doppelt erscheinen, sollten nicht vorschnell gelöscht werden. Zunächst muss geklärt werden, ob sie von unterschiedlichen Targets, Regeln für On-Demand Resources oder vom Lokalisierungsprozess erzeugt werden.

Fehlalarme mit zwei Grenzwerten reduzieren

Ein rein prozentualer Grenzwert reagiert bei kleinen Komponenten zu empfindlich. Ein ausschließlich fester Byte-Grenzwert kann dagegen das kontinuierliche Wachstum großer Apps übersehen. Der zulässige Zuwachs lässt sich daher wie folgt definieren:

allowed = max(8 MiB, baseline_app_bytes × 3%)
failed  = current_app_bytes - baseline_app_bytes > allowed

Die Werte 8 MiB und 3% sind lediglich Ausgangswerte für die Einführung und kein allgemeingültiger Standard. Erfassen Sie zunächst die Schwankungen mehrerer regulärer Archivierungen und passen Sie die Werte anschließend an das Projekt an. Für das Hauptprogramm und einzelne Frameworks sollten kleinere, jeweils eigene Grenzwerte gelten. Andernfalls kann eine neu hinzugefügte Abhängigkeit in der Gesamtgröße der App untergehen.

Die Baseline-Datei muss gemeinsam mit dem Quellcode geprüft werden, darf aber nicht automatisch durch einen fehlgeschlagenen Job überschrieben werden. Ein sinnvoller Ablauf sieht so aus: Der Job gibt die Differenz aus, die Entwickler begründen die Ursache, die Reviewer bestätigen, dass die Änderung den Anforderungen entspricht, und die Baseline wird abschließend innerhalb derselben Änderung aktualisiert. Nur so lässt sich zwischen einem bewusst akzeptierten Zuwachs und dem bloßen Zurücksetzen von Zahlen unterscheiden, um die Prüfung zu bestehen.

Häufige Ursachen für ungewöhnliches Wachstum untersuchen

Wächst das Hauptprogramm, sollten zuerst statische Abhängigkeiten, generische Instanziierungen, mehrfaches Linken und Build-Bedingungen geprüft werden. Bei größer gewordenen Frameworks sind Abhängigkeitsversionen und Einbettungsverfahren zu kontrollieren. Bei einem Zuwachs der Ressourcen sollten Originalbilder, Schriftarten, Audio- und Videodateien sowie doppelte Lokalisierungsinhalte untersucht werden.

Außerdem sind vier häufige Fehler zu vermeiden:

  1. Ergebnisse aus unterschiedlichen Xcode-Umgebungen direkt miteinander vergleichen.
  2. Simulator-Builds und Archive für physische Geräte vermischen.
  3. Nur die Gesamtgröße speichern, nicht aber Komponentendetails und Commit-Kennung.
  4. Nach einer fehlgeschlagenen Größenkontrolle automatisch den Grenzwert erhöhen oder die Baseline überschreiben.

Der Abschlussbericht sollte mindestens die Commit-Kennung, die Archivkonfiguration, die Gesamtgröße der App, die Größe des Hauptprogramms, die größten Frameworks, den Zuwachs gegenüber der Baseline und das Prüfergebnis enthalten. Auch nach erfolgreicher Prüfung muss der Bericht aufbewahrt werden, damit sich langsames, aber kontinuierliches Wachstum erkennen lässt. Schlägt die Prüfung fehl, werden Archiv und Detaildaten ebenfalls gespeichert und in derselben Umgebung erneut untersucht, statt sich auf einen lokalen Neubuild durch die Entwickler zu verlassen.

Häufig gestellte Fragen

Warum reicht der Vergleich der fertigen IPA-Datei nicht aus?

Eine IPA-Datei ist komprimiert. Ihre Größe hängt deshalb auch von Ressourceninhalten, Dateireihenfolge und Archivierungsverhalten ab. Das unkomprimierte App-Bundle und die Hauptprogrammdatei sind stabilere Kennzahlen.

Wie wird ein sinnvoller Grenzwert für Größenregressionen festgelegt?

Erfassen Sie zunächst mehrere normale Releases und kombinieren Sie danach einen absoluten Spielraum mit einem relativen Prozentsatz. Die Beispielwerte 8 MiB und 3 Prozent müssen an die Projekthistorie angepasst werden.

Darf ein Abhängigkeitsupdate die Basislinie automatisch ersetzen?

Nein. Prüfen Sie zuerst, welche Symbole, Ressourcen oder Frameworks hinzugekommen sind und ob der Zuwachs beabsichtigt ist. Die neue Basislinie wird erst nach der Prüfung zusammen mit der begründenden Änderung übernommen.

Exklusiver physischer Mac

Den nächsten iOS-Build auf einem MiniDebug M4 ausführen.

M4, 16 GB RAM und 256 GB SSD sind standardmäßig verfügbar. Die Miete ist tage-, wochen-, monats- oder quartalsweise möglich. Zur Auswahl stehen fünf Standorte: Singapur, Tokio in Japan, Seoul in Südkorea, Hongkong und die US-Ostküste. Die tatsächliche Verfügbarkeit wird in Echtzeit von der Konsole angezeigt.

Standort auswählen und bestellen