Pratiques d’ingénierie

Contrôler la cohérence d’un projet Xcode sur un Mac cloud

Contrôler la cohérence d’un projet Xcode sur un Mac cloud

Lorsque plusieurs personnes modifient simultanément un projet Xcode, les échecs les plus dangereux ne sont généralement pas les erreurs de compilation, mais les fichiers project.pbxproj endommagés qui passent malgré tout la fusion textuelle. Une référence de fichier supprimée, un Scheme non partagé ou un réglage de compilation essentiel modifié silencieusement depuis l’interface peut ne provoquer de problème qu’au moment de l’archivage. Une approche plus fiable consiste à traiter les fichiers du projet comme des entrées de compilation indépendantes sur le Mac cloud, à effectuer d’abord une série de contrôles de cohérence peu coûteux, puis seulement à lancer la résolution des dépendances, les tests et l’archivage.

Que doit contrôler la barrière de validation ?

Une barrière de validation efficace doit couvrir au moins quatre niveaux, dans un ordre précis : rechercher d’abord les résidus de fusion, valider ensuite le format du fichier, demander à Xcode d’analyser réellement le projet, puis comparer les réglages essentiels. Dès qu’un niveau échoue, le processus doit s’arrêter afin de ne pas consommer inutilement du temps de compilation.

Niveau Élément contrôlé Signification d’un échec
Texte Marqueurs de conflit, fichier vide La fusion n’est pas terminée
Structure project.pbxproj La structure plist ne peut pas être analysée
Projet Project, Target, Scheme Xcode ne peut pas construire le modèle du projet
Configuration SDK, version de déploiement, méthode de signature Les réglages ont dérivé de manière inattendue

L’absence de conflit dans git diff ne signifie pas que le projet Xcode est valide. L’outil de gestion de versions confirme uniquement que la fusion textuelle est terminée ; il ne sait pas si Xcode peut encore reconnaître les références d’objets, les Target ou les Scheme.

Le script de contrôle doit fixer son répertoire d’exécution et le chemin de Xcode, sans dépendre de l’état courant d’un Shell interactif. Si le projet contient à la fois un fichier .xcodeproj et un fichier .xcworkspace, les compilations courantes doivent utiliser le point d’entrée réellement employé, mais le fichier project.pbxproj sous-jacent doit tout de même être contrôlé séparément.

Bloquer d’abord les résidus de fusion et les erreurs de format

Le script suivant peut être enregistré dans ci/check_xcode_project.sh. L’exemple suppose que le projet s’appelle App.xcodeproj. En pratique, remplacez cette valeur au moyen d’une variable d’environnement afin de ne pas disperser le nom du projet dans plusieurs configurations CI.

#!/bin/bash
set -euo pipefail

PROJECT_PATH="${PROJECT_PATH:-App.xcodeproj}"
PBXPROJ="${PROJECT_PATH}/project.pbxproj"

test -s "$PBXPROJ" || {
  echo "project.pbxproj is missing or empty"
  exit 1
}

if grep -nE '^(<<<<<<<|=======|>>>>>>>)' "$PBXPROJ"; then
  echo "merge conflict markers found"
  exit 1
fi

plutil -lint "$PBXPROJ"
xcodebuild -list -json -project "$PROJECT_PATH" > /tmp/xcode-project-list.json
plutil -lint /tmp/xcode-project-list.json

Les marqueurs de conflit sont recherchés uniquement en début de ligne, ce qui évite les faux positifs lorsqu’un nom de fichier métier ou un commentaire contient par hasard une série de signes égal. Une fois le contrôle plutil réussi, exécutez xcodebuild -list, car seule cette commande construit réellement le modèle de projet Xcode. Si elle renvoie un code de sortie non nul, conservez la sortie d’erreur standard au lieu de tenter une « réparation » automatique ou de régénérer les fichiers du projet.

Traitement des projets Workspace

Pour un Workspace, ajoutez une validation du point d’entrée :

WORKSPACE_PATH="${WORKSPACE_PATH:-App.xcworkspace}"
xcodebuild -list -json -workspace "$WORKSPACE_PATH" \
  > /tmp/xcode-workspace-list.json
plutil -lint /tmp/xcode-workspace-list.json

Ne validez pas uniquement le Workspace en ignorant les projets sous-jacents. Le fait qu’un Workspace soit reconnu ne garantit pas que chaque fichier de projet qu’il contient soit exempt de résidus de fusion.

Valider le Scheme partagé et l’ensemble des cibles

Le Scheme requis par la CI doit être placé sous contrôle de version. Commencez par vérifier le fichier du Scheme partagé, puis confirmez son nom dans la sortie JSON de xcodebuild -list. Le Python fourni avec le système suffit pour analyser cette sortie ; aucune dépendance supplémentaire n’est nécessaire.

SCHEME_NAME="${SCHEME_NAME:-App}"
SCHEME_FILE="${PROJECT_PATH}/xcshareddata/xcschemes/${SCHEME_NAME}.xcscheme"

test -s "$SCHEME_FILE" || {
  echo "shared scheme is missing: $SCHEME_NAME"
  exit 1
}

python3 - "$SCHEME_NAME" /tmp/xcode-project-list.json <<'PY'
import json
import sys

expected = sys.argv[1]
path = sys.argv[2]

with open(path, encoding="utf-8") as handle:
    payload = json.load(handle)

schemes = payload.get("project", {}).get("schemes", [])
if expected not in schemes:
    raise SystemExit(f"expected scheme not found: {expected}")
PY

Si le dépôt contient plusieurs applications ou extensions, maintenez une liste explicite des Scheme au lieu d’accepter la simple présence d’« au moins un Scheme ». N’utilisez pas non plus les Scheme du répertoire personnel comme entrées de la CI : ces fichiers non partagés ne seront pas présents après un changement de nœud physique.

Créer un instantané des réglages de compilation essentiels

Un projet peut être analysé correctement alors que sa configuration a été modifiée par erreur. Il est recommandé de créer un instantané limité aux champs qui changent la nature du livrable, tels que PRODUCT_BUNDLE_IDENTIFIER, IPHONEOS_DEPLOYMENT_TARGET, SWIFT_VERSION, CODE_SIGN_STYLE et SUPPORTED_PLATFORMS. N’enregistrez pas la sortie complète de -showBuildSettings : elle contient des chemins et des répertoires temporaires qui généreraient beaucoup de bruit lors des comparaisons entre nœuds.

xcodebuild -project "$PROJECT_PATH" \
  -scheme "$SCHEME_NAME" \
  -configuration Release \
  -showBuildSettings |
awk -F ' = ' '
  /PRODUCT_BUNDLE_IDENTIFIER =/ ||
  /IPHONEOS_DEPLOYMENT_TARGET =/ ||
  /SWIFT_VERSION =/ ||
  /CODE_SIGN_STYLE =/ ||
  /SUPPORTED_PLATFORMS =/ {
    gsub(/^[ 	]+/, "", $1)
    print $1 " = " $2
  }
' | LC_ALL=C sort > /tmp/build-settings.current

diff -u ci/build-settings.release /tmp/build-settings.current

Après la création initiale de ci/build-settings.release, ajoutez ce fichier au contrôle de version. Lorsqu’une version de déploiement ou une méthode de signature doit être modifiée intentionnellement, examinez d’abord les différences de réglages, puis mettez à jour l’instantané dans le même changement. La CI ne doit jamais remplacer automatiquement la référence, faute de quoi toute dérive deviendrait la nouvelle norme.

Intégrer le contrôle au pipeline et gérer les faux positifs

Placez la barrière de cohérence avant le téléchargement des dépendances et la compilation complète, et attribuez au script des étapes d’échec faciles à identifier. L’ordre recommandé est le suivant : extraire le code, sélectionner une version fixe de Xcode, exécuter la barrière de validation du projet, résoudre les dépendances, compiler, tester, puis archiver. Le script doit utiliser le même point d’entrée en local et sur le Mac cloud, par exemple :

DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer" \
PROJECT_PATH="App.xcodeproj" \
SCHEME_NAME="App" \
bash ci/check_xcode_project.sh

Les faux positifs proviennent généralement de trois sources. Premièrement, un développeur a modifié un Scheme sans le partager ; la solution consiste à valider xcshareddata/xcschemes dans le dépôt, et non à le créer manuellement sur le nœud. Deuxièmement, l’instantané contient des chemins absolus ; il faut alors réduire l’ensemble des champs enregistrés. Troisièmement, différentes tâches utilisent des chemins Xcode distincts ; affichez et vérifiez xcodebuild -version avant le début du contrôle, tout en fixant DEVELOPER_DIR.

Lorsqu’un contrôle échoue, le ticket ou le journal de compilation doit conserver au minimum l’identifiant du commit, la version de Xcode, la commande en échec, la sortie d’erreur standard et le point d’entrée du projet. Ne téléversez pas l’ensemble des variables d’environnement si elles contiennent des valeurs sensibles. S’il faut changer de nœud physique, vérifiez dans la console les configurations actuellement disponibles et laissez le nouveau nœud réexécuter la barrière depuis le même dépôt, au lieu de copier l’état temporaire du projet depuis l’ancien nœud.

Liste de contrôle avant la fusion

Avant de valider la barrière, vérifiez chaque point : le script active set -euo pipefail ; le chemin du projet et le Scheme peuvent être remplacés par des variables d’environnement ; la recherche de marqueurs de conflit porte uniquement sur le fichier du projet ; le Project et le Workspace sont analysés séparément selon le point d’entrée réellement utilisé ; le Scheme partagé est placé sous contrôle de version ; l’instantané des réglages ne conserve que des champs stables ; toute mise à jour de la référence fait l’objet d’une revue humaine.

Ces contrôles ne remplacent ni la compilation ni les tests, mais ils permettent de détecter en quelques secondes des problèmes qui n’apparaissaient auparavant qu’à l’étape de l’archivage. Dès lors que les fichiers du projet deviennent des entrées explicites, révisables et reproductibles, l’équipe n’a plus besoin de se fier au fait que « le projet s’ouvre encore sur le poste d’un développeur » pour juger de l’état de santé de la branche principale.

Questions fréquentes

La commande plutil suffit-elle pour valider entièrement un projet Xcode ?

Non. plutil détecte surtout les problèmes de structure et de syntaxe. Exécutez aussi xcodebuild -list afin de vérifier que Xcode analyse le projet et trouve les schemes partagés attendus.

À quel moment faut-il exécuter ce contrôle dans la CI ?

Exécutez-le avant la compilation complète pour chaque modification, puis de nouveau sur la branche partagée. Un projet endommagé est ainsi arrêté avant l’archivage.

Comment gérer une modification volontaire des réglages de compilation ?

Relisez la différence, puis mettez explicitement à jour la référence versionnée. La CI ne doit jamais réécrire cette référence automatiquement, au risque de masquer une dérive.

Mac mini physique dédié

Lancez votre prochain build sur un nœud physique dédié.

Choisissez la puce, la mémoire, le stockage, le nœud et la durée de location pour profiter d’un Mac dans le cloud avec des ressources qui ne sont pas partagées avec d’autres clients.

Choisir une configuration et louer