Zwei iOS-Archivierungspipelines laufen gleichzeitig auf einem Mac in der Cloud: Eine behebt ein Problem in der Produktionsumgebung, die andere bereitet das reguläre Release vor. Beide lesen denselben Wert für CURRENT_PROJECT_VERSION aus dem Projekt und erzeugen am Ende zwei Artefakte mit identischer Buildnummer, aber unterschiedlichem Code. Meist fällt das Problem erst bei der Übermittlung an das Veröffentlichungssystem auf. Dann lässt sich anhand von Dateinamen und Commit-Verlauf kaum noch feststellen, welches Artefakt behalten werden soll.
Beim Verwalten von Buildnummern geht es nicht darum, eine Zahl lediglich „automatisch um eins zu erhöhen“. Jedes auslieferbare Artefakt muss drei Fragen beantworten können: Wer hat die Nummer vergeben, zu welchem Commit gehört sie und welcher Wert befindet sich tatsächlich im Archiv?
Versionsnummer und Buildnummer zuerst trennen
MARKETING_VERSION ist die für Benutzer sichtbare Version, beispielsweise 3.8.0. CURRENT_PROJECT_VERSION ist die Buildnummer und sollte ein aufsteigender Wert sein, der ausschließlich aus Ziffern besteht. Beide haben unterschiedliche Lebenszyklen. Ein Commit-Tag sollte daher nicht direkt in beide Felder geschrieben werden.
| Feld | Beispiel | Zeitpunkt der Änderung | Hauptzweck |
|---|---|---|---|
MARKETING_VERSION |
3.8.0 |
Änderung der Produktversion | Funktionsversion kennzeichnen |
CURRENT_PROJECT_VERSION |
18427 |
Bei jedem auslieferbaren Archiv | Artefakte derselben Version unterscheiden |
| Git-Commit | a1b2c3d |
Bei jedem Commit | Quellcode zuordnen |
| Pipeline-Nummer | 5821 |
Bei jeder Job-Ausführung | Ausführungsprotokoll zuordnen |
Das Projekt-Repository kann eine stabile Versionsnummer enthalten. Die Buildnummer sollte jedoch nicht von mehreren Runnern gleichzeitig geändert und zurückgeschrieben werden. Wenn parallele Jobs jeweils „lesen, um eins erhöhen, zurückschreiben“ ausführen, können sie selbst bei erfolgreichem Abschluss dieselbe Nummer erhalten.
Die Buildnummer sollte als Eingabe der Pipeline behandelt werden, nicht als Ergebnis einer Quellcodeänderung. Der Quellcode legt fest, wie die Nummer verwendet wird; das Orchestrierungssystem stellt ihre Eindeutigkeit sicher.
Eine eindeutige Nummernquelle einrichten
Eine monoton steigende Sequenz wählen
Produktionsarchive sollten ihre Ganzzahl von einer zentralen Quelle beziehen, etwa von der globalen Ausführungsnummer des Pipeline-Systems oder von einer Sequenz, die ein interner Koordinierungsjob atomar vergibt. Die Nummerierung muss drei Bedingungen erfüllen:
- Keine Wiederholung innerhalb desselben Veröffentlichungsziels;
- Jede neue Nummer ist größer als bereits übermittelte Nummern;
- Commit, Branch und Job-Ausführung lassen sich anhand der Nummer zurückverfolgen.
git rev-list --count HEAD eignet sich für Projekte mit einem einzelnen Branch, deren Historie nicht umgeschrieben wird. Für flache Klone, Rebases oder mehrere Release-Branches ist der Befehl ungeeignet. Unterschiedliche Branches können denselben Zählerstand erreichen, und nach einer umgeschriebenen Historie kann die Zahl zurückgehen. Für interne Debug-Builds ist dieses Verfahren möglich, in komplexen Release-Prozessen darf es jedoch nicht als alleinige maßgebliche Quelle dienen.
Bei parallelen Jobs auf OpsVM kann zunächst die Orchestrierungsebene BUILD_SEQUENCE erzeugen und den Wert anschließend an den jeweiligen Build-Knoten übergeben. Die Knoten verwenden die Nummer nur; sie konkurrieren weder um ihre Vergabe noch schreiben sie sie zurück.
Ungültige Eingaben direkt am Job-Einstieg ablehnen
#!/bin/zsh
set -euo pipefail
: "${BUILD_SEQUENCE:?BUILD_SEQUENCE is required}"
: "${RELEASE_VERSION:?RELEASE_VERSION is required}"
if [[ ! "$BUILD_SEQUENCE" =~ ^[0-9]+$ ]]; then
print -u2 "BUILD_SEQUENCE must contain digits only"
exit 64
fi
if [[ ! "$RELEASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
print -u2 "RELEASE_VERSION must use major.minor.patch"
exit 64
fi
Die Eingabeprüfung sollte vor der Installation von Abhängigkeiten und vor der Kompilierung erfolgen. So führt eine fehlende Variable nicht erst nach mehreren Minuten zu einem Archiv, das sich nicht ausliefern lässt.
Nummer beim Archivieren injizieren
Die Pipeline muss project.pbxproj nicht verändern. Werden die Build-Einstellungen direkt am Ende des xcodebuild-Befehls überschrieben, entstehen weder geänderte Repository-Dateien noch unnötige Schwierigkeiten bei der Reproduktion anhand der Logs.
archive_path="$PWD/output/App.xcarchive"
result_path="$PWD/output/Archive.xcresult"
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-destination "generic/platform=iOS" \
-archivePath "$archive_path" \
-resultBundlePath "$result_path" \
MARKETING_VERSION="$RELEASE_VERSION" \
CURRENT_PROJECT_VERSION="$BUILD_SEQUENCE" \
clean archive
Die Überschreibungen müssen im selben Archivierungsbefehl stehen. Andernfalls können Tests eine Nummer verwenden, während die Archivierung erneut den Standardwert des Projekts einliest. Enthält das Projekt Erweiterungen, muss außerdem geprüft werden, ob Haupt-App und Erweiterungen dieselben Einstellungen erben. Sofern die Release-Vorgaben nichts anderes verlangen, sollte nicht für jedes Target eine eigene Nummer erzeugt werden.
Vor der Archivierung können zunächst die aufgelösten Einstellungen geprüft werden:
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-showBuildSettings |
awk '/MARKETING_VERSION|CURRENT_PROJECT_VERSION/ { print }'
Damit lassen sich vor allem unerwartete Überschreibungen der Befehlszeilenparameter durch Skripte, Konfigurationsdateien oder Target-spezifische Einstellungen erkennen.
Nicht dem Log vertrauen, sondern das Archiv prüfen
Ein erfolgreich beendeter Befehl bedeutet lediglich, dass die Archivierung abgeschlossen wurde. Er garantiert nicht, dass die vorgesehenen Werte in die endgültige App übernommen wurden. Das Prüfsystem sollte deshalb die Info.plist der Haupt-App im .xcarchive lesen und die enthaltenen Werte mit den Pipeline-Eingaben vergleichen.
app_path="$(find "$archive_path/Products/Applications" \
-maxdepth 1 -name '*.app' -type d -print -quit)"
if [[ -z "$app_path" ]]; then
print -u2 "Application bundle not found"
exit 65
fi
plist="$app_path/Info.plist"
actual_version=$(/usr/libexec/PlistBuddy \
-c "Print :CFBundleShortVersionString" "$plist")
actual_build=$(/usr/libexec/PlistBuddy \
-c "Print :CFBundleVersion" "$plist")
[[ "$actual_version" == "$RELEASE_VERSION" ]] || exit 66
[[ "$actual_build" == "$BUILD_SEQUENCE" ]] || exit 67
Nach erfolgreicher Prüfung sollten Versionsnummer, Buildnummer, vollständiger Commit-Hash, Prüfsumme des Archivs und Kennung der Pipeline-Ausführung gemeinsam in einer einfachen Textdatei erfasst und zusammen mit dem Artefakt gespeichert werden. Ein gut lesbarer Dateiname ist hilfreich, ersetzt aber weder diese Datei noch die im Archiv enthaltenen Felder.
Wiederholungen, Branches und Parallelität behandeln
Wiederholungsregeln auf Pipeline-Ebene festlegen
Tritt ein Fehler vor der Kompilierung auf und wurde noch kein Archiv erzeugt, kann derselbe Job mit der ursprünglichen Nummer erneut ausgeführt werden. Wurde bereits ein Archiv erstellt, hochgeladen oder weiterverarbeitet, muss eine neue Nummer bezogen werden. Die alte Nummer bleibt in den Aufzeichnungen erhalten und darf nicht wiederverwendet werden, nur um eine lückenlose Zahlenfolge zu erhalten.
Wenn mehrere Release-Branches dasselbe Veröffentlichungsziel verwenden, müssen sie auch denselben Nummernraum teilen. Für jeden Branch separat bei 1 zu beginnen, wirkt zwar übersichtlich, führt nach dem Zusammenführen aber zu Kollisionen. Branchname, Commit-Hash und Versionsnummer beschreiben die Herkunft; die Buildnummer ist ausschließlich für Eindeutigkeit und aufsteigende Reihenfolge zuständig.
Minimale Checkliste
- Die Nummer wird atomar von einer zentralen Quelle vergeben;
- Build-Knoten lesen die Nummer nur und ändern keine Projektdateien;
- Der Archivierungsbefehl übergibt beide Versionsfelder explizit;
- Die Vererbung zwischen Haupt-App und Erweiterungen wurde geprüft;
- Nach erfolgreicher Archivierung werden die internen Felder gelesen und verglichen;
- Für jedes Artefakt wird die Zuordnung zu Commit, Job und Prüfsumme gespeichert;
- Fehlgeschlagene Jobs, die bereits ein Artefakt erzeugt haben, geben ihre Nummer nicht wieder frei;
- Parallele Knoten bestätigen in der Konsole die aktuell auswählbare Konfiguration und übernehmen nur den Build, nicht die Nummernvergabe.
Sind diese Regeln umgesetzt, ist die Buildnummer keine kurzfristig vor dem Release geänderte Zahl mehr. Sie wird zu einem stabilen Index, der Quellcode, Ausführungsprotokoll und endgültiges Artefakt miteinander verbindet. Bei einem Rollback oder einer Kollision zwischen parallelen Jobs kann das Team zuerst das Archiv über die Nummer finden und anschließend eindeutig zum zugehörigen Commit und zur Pipeline-Ausführung zurückkehren.
Häufig gestellte Fragen
Eignet sich die Anzahl der Git-Commits als iOS-Buildnummer?
Nur bei linearer Historie ohne Rebase und ohne flache Klone. Für mehrere Release-Zweige ist ein zentral vergebener, monoton steigender CI-Wert sicherer.
Darf ein fehlgeschlagener CI-Lauf dieselbe Buildnummer erneut verwenden?
Ja, wenn noch kein Archiv oder ausgeliefertes Artefakt entstanden ist. Andernfalls sollte der neue Lauf eine neue Nummer erhalten und auf denselben Commit verweisen.
Cloud-Mac-Workflows mit OpsVM bereitstellen
Wählen Sie aus drei Apple-Silicon-Konfigurationen und sechs verfügbaren Knoten. Maßgeblich ist der in der Konsole in Echtzeit angezeigte Status.