Deux pipelines d’archivage iOS s’exécutent simultanément sur un Mac dans le cloud : l’un corrige un incident en production, tandis que l’autre prépare la livraison habituelle. Tous deux lisent la même valeur de CURRENT_PROJECT_VERSION dans le projet et produisent finalement deux artefacts qui portent le même numéro de build, mais contiennent un code différent. Le problème n’apparaît généralement qu’au moment de la soumission au système de publication. À ce stade, les noms de fichiers et l’historique des commits suffisent rarement à déterminer quelle archive conserver.
La gestion des numéros de build ne consiste pas simplement à « ajouter un automatiquement ». Chaque artefact distribuable doit permettre de répondre à trois questions : qui a attribué le numéro, à quel commit correspond-il et quelle valeur figure réellement dans l’archive ?
Distinguer d’abord le numéro de version du numéro de build
MARKETING_VERSION correspond à la version visible par les utilisateurs, par exemple 3.8.0. CURRENT_PROJECT_VERSION est le numéro de build et doit être une valeur croissante composée uniquement de chiffres. Ces deux valeurs n’ont pas le même cycle de vie. Il ne faut donc pas écrire directement un tag de commit dans les deux champs.
| Champ | Exemple | Moment de la modification | Usage principal |
|---|---|---|---|
MARKETING_VERSION |
3.8.0 |
Changement de version du produit | Identifier la version fonctionnelle |
CURRENT_PROJECT_VERSION |
18427 |
Chaque archive distribuable | Distinguer les artefacts d’une même version |
| Commit Git | a1b2c3d |
Chaque commit | Retrouver le code source |
| Numéro de pipeline | 5821 |
Chaque exécution de tâche | Retrouver l’enregistrement d’exécution |
Le dépôt du projet peut conserver un numéro de version stable, mais plusieurs exécuteurs ne doivent pas modifier et valider simultanément le numéro de build. Lorsque des tâches concurrentes effectuent chacune une séquence « lecture, incrémentation, écriture », elles peuvent obtenir le même résultat, même si toutes se terminent correctement.
Le numéro de build doit être considéré comme une entrée du pipeline, et non comme le résultat d’une modification du code source. Le code source définit la manière de l’utiliser ; le système d’orchestration garantit son unicité.
Mettre en place une source unique de numéros
Choisir une séquence croissante de manière monotone
Les archives destinées à la publication doivent obtenir leur entier auprès d’une source centralisée, par exemple le numéro global d’exécution du système de pipeline ou une séquence attribuée atomiquement par une tâche interne de coordination. La numérotation doit respecter trois conditions :
- Aucun doublon pour une même cible de publication ;
- Tout nouveau numéro est supérieur aux numéros déjà soumis ;
- Le numéro permet de retrouver le commit, la branche et l’exécution correspondante.
git rev-list --count HEAD convient aux projets à branche unique dont l’historique n’est jamais réécrit. En revanche, cette méthode n’est pas adaptée aux clones superficiels, aux rebases ni aux multiples branches de publication. Des branches différentes peuvent obtenir le même compteur, et une réécriture de l’historique peut faire reculer la valeur. Cette approche peut servir pour des builds internes de débogage, mais elle ne doit pas devenir l’unique source de vérité d’un processus de publication complexe.
Dans les tâches parallèles d’OpsVM, la couche d’orchestration peut d’abord générer BUILD_SEQUENCE, puis transmettre cette valeur au nœud de build concerné. Les nœuds se contentent de consommer le numéro : ils ne se font pas concurrence pour l’obtenir et ne le réécrivent pas.
Refuser les données invalides dès l’entrée de la tâche
#!/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
La validation des entrées doit précéder l’installation des dépendances et la compilation. Ainsi, une variable manquante ne produit pas, plusieurs minutes plus tard, une archive impossible à distribuer.
Injecter le numéro dans la commande d’archivage
Le pipeline n’a pas besoin de modifier project.pbxproj. Surcharger directement les réglages de build à la fin de la commande xcodebuild évite de salir le dépôt et facilite la reproduction à partir des journaux.
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
Les surcharges doivent figurer dans la même commande d’archivage. Cela évite que les tests utilisent un numéro tandis que l’archivage relit ensuite la valeur par défaut du projet. Si le projet contient des extensions, il faut également vérifier que l’application principale et les extensions héritent des mêmes réglages. Sauf exigence explicite des règles de publication, il ne faut pas générer un numéro distinct pour chaque target.
Avant l’archivage, il est possible de contrôler les réglages effectivement résolus :
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-showBuildSettings |
awk '/MARKETING_VERSION|CURRENT_PROJECT_VERSION/ { print }'
Cette étape sert principalement à détecter les surcharges inattendues des paramètres de ligne de commande par des scripts, des fichiers de configuration ou des réglages propres à une target.
Ne pas se fier aux journaux : contrôler directement l’archive
Le succès de la commande indique uniquement que l’archivage est terminé. Il ne garantit pas que les valeurs attendues ont été intégrées à l’application finale. Le script de validation doit lire le fichier Info.plist de l’application principale dans le .xcarchive, puis comparer ses valeurs aux entrées du pipeline.
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
Une fois la validation réussie, le numéro de version, le numéro de build, le hash complet du commit, la somme de contrôle de l’archive et l’identifiant d’exécution du pipeline doivent être inscrits dans un même manifeste en texte brut, puis conservés avec l’artefact. Un nom de fichier lisible est pratique, mais il ne remplace ni le manifeste ni les champs stockés dans l’archive.
Gérer les nouvelles tentatives, les branches et la concurrence
Fixer les règles de nouvelle tentative au niveau du pipeline
Si l’échec survient avant la compilation et qu’aucune archive n’a été générée, la même tâche peut être relancée avec son numéro d’origine. En revanche, dès qu’une archive a été créée, téléversée ou transmise à une étape ultérieure, la nouvelle tentative doit obtenir un nouveau numéro. L’ancien numéro reste dans les enregistrements et ne doit pas être recyclé dans le seul but de conserver une suite sans interruption.
Lorsque plusieurs branches de publication partagent la même cible de publication, elles doivent également partager le même espace de numérotation. Faire commencer séparément chaque branche à 1 peut sembler ordonné, mais provoque des collisions après leur fusion. Le nom de la branche, le hash du commit et le numéro de version indiquent l’origine ; le numéro de build sert uniquement à garantir l’unicité et l’ordre croissant.
Liste de contrôle minimale
- Le numéro est attribué atomiquement par une source centralisée ;
- Les nœuds de build lisent uniquement le numéro et ne modifient pas les fichiers du projet ;
- La commande d’archivage transmet explicitement les deux champs de version ;
- L’héritage entre l’application principale et les extensions a été vérifié ;
- Après une exécution réussie, les champs internes de l’archive sont lus et comparés ;
- Chaque artefact conserve la correspondance avec le commit, la tâche et la somme de contrôle ;
- Une tâche en échec ayant déjà produit un artefact ne restitue pas son numéro ;
- Après avoir confirmé dans la console la configuration actuellement disponible, les nœuds parallèles exécutent uniquement le build et n’attribuent aucun numéro.
Une fois ces contraintes appliquées, le numéro de build n’est plus une valeur modifiée à la dernière minute avant la publication. Il devient un index stable reliant le code source, les enregistrements d’exécution et l’artefact final. En cas de retour arrière ou de conflit entre des tâches parallèles, l’équipe peut d’abord retrouver l’archive grâce à son numéro, puis revenir au commit et à l’exécution de pipeline correspondants sans ambiguïté.
Questions fréquentes
Le nombre de commits Git peut-il servir de numéro de build iOS ?
Oui pour un historique linéaire non réécrit. Avec des branches de publication, des clones superficiels ou des rebases, utilisez plutôt un entier croissant attribué par le CI.
Faut-il réutiliser le numéro lors d’une relance du pipeline ?
Seulement si l’exécution précédente n’a produit ni archive ni artefact publié. Sinon, attribuez un nouveau numéro et conservez le lien avec le même commit.
Déployer un workflow Mac dans le cloud avec OpsVM
Choisissez parmi trois configurations Apple Silicon et six nœuds disponibles à la vente. La disponibilité réelle est confirmée en temps réel par la console.