Un même commit peut être archivé avec succès sur le Mac d’un développeur, puis également sur un Mac dans le cloud ou dans un pipeline nocturne, tout en produisant deux artefacts dont les empreintes diffèrent. Il ne faut pas en conclure trop vite que le cache de Xcode est en cause. La signature, l’heure de compression, le code source généré, l’ordre des sorties de scripts et les chemins absolus peuvent tous se retrouver dans l’artefact. Une méthode plus efficace consiste à créer deux répertoires de build totalement isolés sur un même nœud ArmVMS, à exécuter successivement deux archivages sans signature, puis à comparer les différences en allant de l’enveloppe vers le contenu.
Définir d’abord le périmètre de la reproductibilité
Il est difficile d’obtenir des IPA signés strictement identiques octet par octet, car les données de signature, les profils de provisionnement et les métadonnées d’empaquetage peuvent varier. La vérification du projet doit donc être divisée en deux niveaux :
- Couche de contenu : avec le même commit, la même chaîne d’outils, les mêmes fichiers de verrouillage des dépendances et les mêmes paramètres de build, les ressources de l’application et les exécutables générés doivent être identiques.
- Couche de livraison : la méthode d’exportation, la configuration de signature, les déclarations d’autorisations et les fichiers inclus dans le paquet doivent respecter les exigences de publication, sans imposer une empreinte identique pour l’ensemble de l’IPA.
Commencez par démontrer que le contenu non signé de l’application est stable, puis examinez la signature et l’exportation. Comparer uniquement l’empreinte globale de deux IPA permet de constater qu’elles sont différentes, mais pas d’identifier l’origine de l’écart.
Avant de commencer, consignez l’empreinte du commit, le chemin de Xcode, le SDK, le Scheme, la Configuration ainsi que les fichiers de verrouillage tels que Package.resolved et Podfile.lock. L’espace de travail doit être propre, et les fichiers générés non validés doivent également être inclus dans la vérification.
Exécuter deux archivages dans des répertoires isolés
Les deux builds ne doivent pas partager le même DerivedData, faute de quoi le second pourrait réutiliser des artefacts intermédiaires laissés par le premier. Le script ci-dessous fixe l’environnement linguistique et le fuseau horaire, attribue un répertoire distinct à chaque archivage et désactive la signature du code :
#!/bin/zsh
set -euo pipefail
export LC_ALL=C
export LANG=C
export TZ=UTC
ROOT="$PWD"
SCHEME="ExampleApp"
for RUN in A B; do
rm -rf "$ROOT/.repro/$RUN"
mkdir -p "$ROOT/.repro/$RUN"
xcodebuild archive \
-workspace "$ROOT/ExampleApp.xcworkspace" \
-scheme "$SCHEME" \
-configuration Release \
-destination "generic/platform=iOS" \
-derivedDataPath "$ROOT/.repro/$RUN/DerivedData" \
-archivePath "$ROOT/.repro/$RUN/App.xcarchive" \
CODE_SIGNING_ALLOWED=NO \
COMPILER_INDEX_STORE_ENABLE=NO
done
Si le projet doit lire des variables d’environnement pendant une phase de script, transmettez explicitement des valeurs stables au lieu d’hériter de réglages temporaires provenant du Shell interactif. Les deux archivages doivent également utiliser le même DEVELOPER_DIR afin d’éviter tout changement du chemin Xcode par défaut.
Réduire le périmètre grâce à l’inventaire des fichiers
Ne commencez pas par une comparaison hexadécimale des binaires. Vérifiez d’abord que les deux .app contiennent les mêmes chemins :
APP_A=".repro/A/App.xcarchive/Products/Applications/ExampleApp.app"
APP_B=".repro/B/App.xcarchive/Products/Applications/ExampleApp.app"
find "$APP_A" -type f | sed "s#^$APP_A/##" | LC_ALL=C sort > /tmp/files-a.txt
find "$APP_B" -type f | sed "s#^$APP_B/##" | LC_ALL=C sort > /tmp/files-b.txt
diff -u /tmp/files-a.txt /tmp/files-b.txt
Un nombre de fichiers différent indique généralement qu’une phase Run Script, un générateur de ressources ou une règle de copie dépend d’entrées instables. Une fois les chemins identiques, calculez l’empreinte de chaque fichier en excluant les répertoires de signature connus pour varier :
(
cd "$APP_A"
find . -type f ! -path "./_CodeSignature/*" -print0 |
sort -z | xargs -0 shasum -a 256
) > /tmp/hash-a.txt
(
cd "$APP_B"
find . -type f ! -path "./_CodeSignature/*" -print0 |
sort -z | xargs -0 shasum -a 256
) > /tmp/hash-b.txt
diff -u /tmp/hash-a.txt /tmp/hash-b.txt
| Emplacement du premier écart | Éléments à vérifier en priorité |
|---|---|
| Info.plist | Date, nom d’hôte, chemin ou numéro de pipeline ajouté automatiquement |
| Assets.car | Ressources d’entrée, version de la chaîne d’outils et ordre des répertoires de ressources |
| Exécutable | Paramètres de compilation, code source généré, fichiers objets et ordre d’édition des liens |
| Framework | Fichiers de verrouillage des dépendances, versions des dépendances binaires et scripts d’intégration |
| Texte ou JSON | Collections non triées, identifiants aléatoires et formats régionaux |
Examiner en détail la configuration et les fichiers Mach-O
Deux listes de propriétés peuvent différer au niveau des octets uniquement parce que l’ordre de leurs clés n’est pas le même. Après avoir copié les fichiers, normalisez-les avec plutil -convert xml1, puis effectuez une comparaison textuelle. Ne modifiez pas directement les archives d’origine, car cela altérerait les éléments de preuve nécessaires aux vérifications suivantes.
Si les exécutables diffèrent, commencez par examiner leur UUID :
dwarfdump --uuid "$APP_A/ExampleApp"
dwarfdump --uuid "$APP_B/ExampleApp"
xcodebuild \
-workspace ExampleApp.xcworkspace \
-scheme ExampleApp \
-configuration Release \
-showBuildSettings > /tmp/build-settings.txt
Des UUID différents indiquent seulement que les résultats de l’édition des liens ne sont pas identiques ; ils ne démontrent pas automatiquement que le code métier a changé. Vérifiez ensuite OTHER_SWIFT_FLAGS, OTHER_LDFLAGS, les chemins de recherche, la cible de déploiement et les chemins réels des outils. Si le code généré intègre la date courante, un répertoire temporaire ou le résultat d’un parcours non ordonné de dictionnaire, toutes les compilations en aval divergeront également.
Portez une attention particulière à #filePath, aux mappages de débogage et aux chemins absolus de l’espace de travail présents dans les sorties de scripts. Lorsque le répertoire de travail de la CI change, un chemin d’extraction uniforme ou une configuration appropriée du mappage des préfixes de débogage peut réduire ces écarts. Il ne faut toutefois pas supprimer des informations nécessaires au débogage dans le seul but d’obtenir des empreintes identiques.
Transformer la vérification en contrôle maintenable
Le contrôle ne doit pas se limiter à imposer que « les empreintes des deux IPA soient identiques ». Une stratégie plus robuste consiste à conserver un rapport des différences et à appliquer des décisions distinctes selon leur niveau :
Différences qui doivent bloquer le build
Pour un même commit, l’absence de fichiers, la modification du contenu des ressources ou des déclarations d’autorisations, un changement de version d’un Framework intégré ou l’instabilité de l’exécutable principal avec des entrées identiques doivent immédiatement bloquer le build. Les deux archives doivent alors être conservées.
Différences pouvant faire l’objet d’un examen séparé
Le répertoire de signature, les journaux d’exportation et les horodatages d’empaquetage explicitement identifiés relèvent de la couche de livraison et peuvent être examinés séparément. Les chemins autorisés à varier doivent toutefois être délimités précisément : une règle générale telle que « ignorer tous les plist » est à proscrire.
Après correction, recréez deux répertoires vides pour répéter la vérification au lieu de réutiliser l’ancien DerivedData. Sur les projets volumineux, il n’est pas nécessaire d’effectuer un double archivage à chaque commit : ce contrôle peut être déclenché lorsque la version de Xcode, les fichiers de verrouillage des dépendances, les générateurs, les phases Run Script ou la configuration de publication changent. Le résultat n’est pas une règle d’empreinte fragile, mais une chaîne de preuves de build capable d’indiquer à quel niveau chaque différence apparaît.
Questions fréquentes
Pourquoi ne pas comparer uniquement les hachages de deux IPA signés ?
La signature, le profil et l’heure d’empaquetage peuvent varier normalement. Comparez d’abord le contenu non signé, puis contrôlez séparément l’export.
Des UUID Mach-O différents prouvent-ils un changement du code ?
Non. L’ordre des objets, le code généré, les arguments de compilation ou les chemins d’outils peuvent aussi modifier le résultat de l’édition de liens.
Faut-il lancer ce contrôle à chaque commit ?
Pour un grand projet, exécutez-le surtout lors des changements de chaîne d’outils, de fichiers de verrouillage, de scripts ou de configuration de livraison.
Choisissez un nœud physique Apple Silicon dédié pour votre prochaine compilation
Louez ArmVMS M4 ou ArmVMS M4 Pro à la journée, à la semaine, au mois ou au trimestre. Vérifiez chaque configuration, nœud et option avant de créer votre commande.