Engineering-Leitfaden

Prüfbare iOS-Build-Artefakte auf Cloud-Macs

Prüfbare iOS-Build-Artefakte auf Cloud-Macs

Eine Pipeline meldet Erfolg, doch die heruntergeladene IPA lässt sich nicht dem Commit zuordnen, der den Build ausgelöst hat. Noch riskanter ist ein anderer Fall: Nach einem abgebrochenen Upload übernimmt ein Folgejob eine nur teilweise geschriebene Datei. Auf Cloud-Macs werden Build, Export und Upload häufig von getrennten Skripten ausgeführt. Gilt lediglich ein Befehls-Exitcode von 0 als Auslieferungskriterium, lassen sich beide Probleme während des betroffenen Jobs nur schwer erkennen.

Die Lösung besteht nicht in einem zusätzlichen Komprimierungsbefehl. Stattdessen muss das Artefakt als unteilbare Einheit definiert werden: IPA, SHA-256-Manifest, Build-Kontext und Prüfergebnis müssen gemeinsam erzeugt und dürfen erst nach erfolgreichem Abschluss aller Prüfungen veröffentlicht werden.

Auslieferbares Artefakt zuerst definieren

Jeder Job verwendet eine eindeutige BUILD_ID. Das Auslieferungsverzeichnis enthält mindestens die folgenden Dateien:

Datei Zweck Abnahmekriterium
App.ipa Artefakt für Installation und Verteilung Prüfsumme stimmt überein, Exportbefehl war erfolgreich
SHA256SUMS Manifest zur Prüfung der Inhaltsintegrität shasum -a 256 -c wird erfolgreich beendet
build-context.tsv Herkunft des Builds nachverfolgen Commit, Xcode und Jobkennung sind nicht leer
export.log Exportfehler diagnostizieren Vollständige Ausgabe speichern und Zugriffsrechte beschränken

Der Build-Kontext muss aus Variablen stammen, die bereits von der Pipeline festgelegt wurden. Das Skript sollte nicht versuchen, den Branch nachträglich zu erraten. Mindestens zu erfassen sind der vollständige Commit-Hash, die Jobkennung, das Scheme, die Xcode-Version und der Erstellungszeitpunkt. Der Zeitpunkt dient nur dazu, Protokolle einander zuzuordnen. Maßgeblich für die Identität des Artefakts bleiben Commit-Hash und Prüfsumme.

Eine Prüfsumme kann belegen, dass sich eine Datei zwischen zwei Prüfpunkten nicht verändert hat. Sie beweist jedoch nicht, dass die Datei ursprünglich aus einem vertrauenswürdigen Prozess stammt. Sowohl die Prüfung der Codesignatur als auch der Build-Kontext sind unverzichtbar.

Archiv vor dem Export prüfen

Prüfen Sie zunächst, ob das Archiv vorhanden ist. Ermitteln Sie anschließend die darin enthaltene .app und validieren Sie deren Codesignatur. Verwenden Sie --deep nicht zum erneuten Signieren. Ziel ist hier ausschließlich die Prüfung der vorhandenen Signaturstruktur. Enthält die App Erweiterungen, muss der Prüfbefehl auch verschachtelten Code erfassen. Jeder Fehler ist als blockierende Bedingung zu behandeln.

Archiv, Exportverzeichnis und endgültiges Verzeichnis müssen auf demselben Arbeitsvolume liegen. Nur dann kann das abschließende Umbenennen des Verzeichnisses atomar erfolgen. Auch bei der Ausführung auf einem MiniRent Cloud-Mac benötigt jeder parallele Job ein eigenes Stammverzeichnis. Zwei Jobs dürfen keinen festen Pfad wie out/latest gemeinsam verwenden.

Direkt anpassbares Skript

set -euo pipefail

: "${BUILD_ID:?BUILD_ID is required}"
: "${GIT_COMMIT:?GIT_COMMIT is required}"
: "${SCHEME:?SCHEME is required}"

ROOT="${ARTIFACT_ROOT:-$PWD/out}"
ARCHIVE="$ROOT/input/App.xcarchive"
EXPORT_OPTIONS="$ROOT/input/ExportOptions.plist"
STAGE="$ROOT/.stage-$BUILD_ID"
FINAL="$ROOT/releases/$BUILD_ID"

rm -rf "$STAGE"
mkdir -p "$STAGE" "$(dirname "$FINAL")"
trap 'rm -rf "$STAGE"' EXIT

test -d "$ARCHIVE"
test -f "$EXPORT_OPTIONS"
test ! -e "$FINAL"

APP_PATH="$(find "$ARCHIVE/Products/Applications" -maxdepth 1 -name '*.app' -print -quit)"
test -n "$APP_PATH"
codesign --verify --deep --strict --verbose=2 "$APP_PATH"

xcodebuild -exportArchive \
  -archivePath "$ARCHIVE" \
  -exportPath "$STAGE/export" \
  -exportOptionsPlist "$EXPORT_OPTIONS" \
  >"$STAGE/export.log" 2>&1

IPA_PATH="$(find "$STAGE/export" -maxdepth 1 -name '*.ipa' -print -quit)"
test -n "$IPA_PATH"
cp "$IPA_PATH" "$STAGE/App.ipa"

DIGEST="$(shasum -a 256 "$STAGE/App.ipa" | awk '{print $1}')"
printf '%s  %s
' "$DIGEST" "App.ipa" >"$STAGE/SHA256SUMS"

XCODE_VERSION="$(xcodebuild -version | paste -sd ' ' -)"
printf 'build_id	%s
commit	%s
scheme	%s
xcode	%s
' \
  "$BUILD_ID" "$GIT_COMMIT" "$SCHEME" "$XCODE_VERSION" \
  >"$STAGE/build-context.tsv"

(
  cd "$STAGE"
  shasum -a 256 -c SHA256SUMS
)

mv "$STAGE" "$FINAL"

(
  cd "$FINAL"
  shasum -a 256 -c SHA256SUMS
)

Die im Skript verwendete ExportOptions.plist muss aus einer versionskontrollierten Konfiguration des Repositorys stammen. Passwörter, private Schlüssel, temporäre Token und der vollständige Satz der Umgebungsvariablen dürfen weder in Protokolle noch in Kontextdateien geschrieben werden.

Unvollständige Artefakte durch atomare Veröffentlichung isolieren

Entscheidend ist, sämtliche Schreib- und Prüfvorgänge zunächst in einem verborgenen temporären Verzeichnis abzuschließen. Erst danach wird das endgültige Verzeichnis mit einem einzigen mv sichtbar gemacht. Leser durchsuchen ausschließlich releases/ und greifen nicht auf .stage-* zu. Dadurch können sie keine Datei übernehmen, die noch erzeugt wird.

Diese Garantie gilt nur innerhalb desselben Dateisystems. Ist das Veröffentlichungsverzeichnis auf einem anderen Volume eingebunden, kann mv zu einem Kopier- und Löschvorgang werden. Richtig ist es, auf dem Zielvolume ein temporäres Verzeichnis anzulegen, alle Dateien dorthin zu kopieren, die Hashprüfung am Ziel erneut auszuführen und das Verzeichnis abschließend innerhalb des Zielvolumes umzubenennen. Bei Objektspeichern oder Artefaktdiensten ohne Semantik zum Umbenennen von Verzeichnissen können die Dateien zunächst unter temporären Schlüsseln mit der Jobkennung hochgeladen werden. Nach der Prüfung wird eine kleine Abschlussmarkierung geschrieben. Verbraucher müssen diese Markierung prüfen, bevor sie das Artefakt lesen.

latest nicht überschreiben

Bei Verwendung von latest kann ein Wiederholungsjob ein neueres erfolgreiches Ergebnis überschreiben. Robuster ist es, Artefakte unveränderlich unter ihrer BUILD_ID zu speichern und separat eine Zeigerdatei zu verwalten, die ausschließlich die Kennung des Zieljobs enthält. Vor dem Aktualisieren des Zeigers muss geprüft werden, ob das Zielverzeichnis alle Prüfungen bestanden hat. Auch bei einem Rollback wird nur der Zeiger geändert; historische Artefakte bleiben unverändert.

Fehler in der richtigen Phase stoppen

Signaturfehler müssen vor dem Export zum Abbruch führen, Exportfehler im temporären Verzeichnis und Hashfehler vor der Veröffentlichung. Eine erneute Prüfung nach dem Upload dient dazu, Änderungen zu erkennen, die durch die Übertragung, den Datenträger oder die Auswahl einer falschen Datei im Skript verursacht wurden.

Zu den häufigen Fehlern gehört, aus dem Exportverzeichnis die „erste Datei“ zu übernehmen, ohne die Dateierweiterung einzuschränken. Weitere typische Probleme sind ein gemeinsamer Ausgabepfad für mehrere Schemes sowie die Wiederverwendung eines temporären Verzeichnisses aus einem vorherigen Versuch. Das Skript muss zu Beginn das Staging-Verzeichnis des aktuellen Jobs bereinigen. Es darf jedoch keine weit gefassten Platzhalter verwenden, die Verzeichnisse anderer Jobs löschen könnten.

Auch für Protokolle sind klare Grenzen erforderlich. Der vollständige Exitstatus und die Ausgabe von xcodebuild müssen erhalten bleiben, ohne sensible Umgebungsdaten auszugeben. Vor einer Supportanfrage sollte nur der relevante Zeitraum extrahiert werden. Repository-Token, interne Kennungen in Pfaden zu Signaturmaterial und Zugangsdaten für Verbindungen sind zu entfernen.

Checkliste vor der Auslieferung

Prüfen Sie jeden der folgenden Punkte, bevor die Pipeline als erfolgreich markiert wird:

  1. Die App im Archiv hat eine strikte Prüfung der Codesignatur bestanden.
  2. Die IPA wurde aus dem Archiv des aktuellen Jobs exportiert und nicht aus einem alten Verzeichnis übernommen.
  3. GIT_COMMIT ist der vollständige Commit-Hash und stimmt mit dem auslösenden Datensatz überein.
  4. SHA256SUMS wurde jeweils einmal im Staging-Verzeichnis und im endgültigen Verzeichnis geprüft.
  5. Das endgültige Verzeichnis trägt eine eindeutige Jobkennung und wird bei Wiederholungen nicht überschrieben.
  6. Verbraucher lesen ausschließlich veröffentlichte Verzeichnisse oder Objekte mit Abschlussmarkierung.
  7. Das Protokoll ermöglicht die Zuordnung zur fehlgeschlagenen Phase, enthält aber weder Zugangsdaten noch Inhalte des Signaturmaterials.

Dieses Verfahren verhindert nicht jeden Buildfehler. Es trennt jedoch die Fragen „War der Build erfolgreich?“ und „Ist das ausgelieferte Artefakt vertrauenswürdig?“ in zwei unabhängig prüfbare Probleme. Sind die Grenzen für Archivierung, Prüfung, Kontext und Veröffentlichung festgelegt, beziehen sich Wiederholungen und Rollbacks nur noch auf eindeutig bestimmte Objekte und nicht mehr auf ein gemeinsam genutztes Verzeichnis, dessen Inhalt sich fortlaufend ändert.

Häufig gestellte Fragen

Beweist eine SHA-256-Prüfsumme die vertrauenswürdige Herkunft eines iOS-Artefakts?

Nein. Sie belegt nur, dass sich die Datei zwischen zwei Prüfzeitpunkten nicht verändert hat. Zusätzlich sind Codesignatur, Commit, Xcode-Version, Jobkennung und das zugehörige Manifest zu prüfen und aufzubewahren.

Warum erfolgt die Freigabe über ein temporäres Verzeichnis mit anschließendem Umbenennen?

Auf demselben Dateisystem macht das Umbenennen nur den vollständigen Stand sichtbar. Bei einem Wechsel des Volumes wird zuerst kopiert, am Ziel erneut geprüft und erst dort umbenannt.

Dedizierte Cloud-Macs

Build-Aufgaben auf einem dedizierten physischen Knoten ausführen

Wählen Sie MiniRents M4 oder MiniRents M4 Pro zur Tages-, Wochen-, Monats- oder Quartalsmiete und wählen Sie den Knoten passend zu Ihrem Team und dem Standort Ihres Code-Repositorys. Die Geräte sind dedizierte physische Rechner, keine virtuellen Maschinen.

Modell auswählen und bestellen