Journal d’ingénierie

Réparer une base de build Xcode endommagée sur un Mac dans le cloud

Réparer une base de build Xcode endommagée sur un Mac dans le cloud

Après plusieurs jours de fonctionnement continu d’un nœud de build distant, Xcode peut soudainement échouer alors que le code du projet n’a pas changé. Le journal indique alors que build.db ne peut pas être ouverte, que le format de l’image disque est incorrect ou que la base de données est endommagée, et relancer le build le bloque au même stade. Supprimer l’intégralité de DerivedData est certes rapide, mais cette opération efface aussi l’index, le cache des modules et les artefacts réutilisables. Les builds suivants sont alors plus lents, tandis que les éléments les plus utiles au diagnostic disparaissent.

Sur un Mac NUMACS dans le cloud, une procédure plus fiable consiste à stabiliser le point d’entrée du projet, conserver les journaux, vérifier qu’aucun processus de build n’utilise les fichiers, contrôler l’intégrité de la base de données, puis recréer uniquement le répertoire XCBuildData du projet concerné.

Vérifier que la panne provient bien de la base de données de build

Ne vous fiez pas uniquement à la dernière ligne affichée dans l’interface de Xcode. Commencez par lancer un build en ligne de commande depuis le même répertoire, avec le même scheme et la même configuration, puis enregistrez la sortie complète :

set -o pipefail
mkdir -p "$HOME/build-evidence"

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Debug \
  -destination 'generic/platform=iOS Simulator' \
  build 2>&1 | tee "$HOME/build-evidence/xcodebuild.log"

status=${PIPESTATUS[0]}
echo "$status" > "$HOME/build-evidence/exit-code.txt"
exit "$status"

Recherchez en priorité les termes build.db, database, malformed, disk image et XCBuildData. Si le journal désigne clairement une erreur de compilation du code source, un échec de résolution des dépendances ou un manque d’espace disque, corrigez d’abord le problème correspondant. Toutes les erreurs de build ne doivent pas être attribuées à la base de données.

Avant toute réparation, conservez au minimum le journal complet, le code de sortie et l’heure de l’erreur. Une fois le nettoyage effectué, l’état initial de la base de données est généralement impossible à reproduire.

Localiser le DerivedData réellement utilisé par le projet

Un même Mac dans le cloud peut contenir plusieurs répertoires DerivedData correspondant à des projets portant le même nom. Se fier à la date de modification des dossiers risque de conduire à la suppression du mauvais répertoire. Demandez plutôt à xcodebuild de renvoyer les réglages de build actifs, puis déduisez la racine du projet à partir de BUILD_DIR.

BUILD_DIR=$(
  xcodebuild \
    -workspace App.xcworkspace \
    -scheme App \
    -configuration Debug \
    -showBuildSettings |
  awk -F ' = ' '$1 ~ /^[[:space:]]*BUILD_DIR$/ {print $2; exit}'
)

DERIVED_ROOT=$(dirname "$(dirname "$BUILD_DIR")")
XCBD="$DERIVED_ROOT/Build/Intermediates.noindex/XCBuildData"

printf 'DerivedData: %s
XCBuildData: %s
' \
  "$DERIVED_ROOT" "$XCBD"
test -d "$XCBD"

Si le pipeline utilise -derivedDataPath, réutilisez directement ce chemin explicite au lieu d’analyser le répertoire utilisateur. Le script produira ainsi le même résultat dans une session interactive et dans une tâche sans surveillance.

Consigner les fichiers concernés

Avant le nettoyage, relevez la taille et la date de modification de la base de données et des fichiers associés :

find "$XCBD" -maxdepth 1 -type f \
  -exec stat -f '%Sm %z %N' -t '%Y-%m-%dT%H:%M:%S%z' {} \; \
  > "$HOME/build-evidence/xcbuilddata-files.txt"

Cet inventaire permet de déterminer si la base de données a été réécrite juste avant l’échec. Il facilite également la comparaison des incidents récurrents afin de vérifier s’ils concernent le même chemin.

Écarter toute utilisation par un processus et inspecter build.db en lecture seule

Une base de données utilisée par un build actif ne doit pas être supprimée directement. Commencez par vérifier les processus concernés et les descripteurs de fichiers ouverts :

pgrep -alf 'Xcode|xcodebuild|XCBBuildService' || true
lsof "$XCBD/build.db" || true

Si un build appartenant à la tâche en cours est toujours actif, arrêtez-le d’abord avec le mécanisme d’annulation normal du pipeline, puis attendez la fin de ses processus enfants. Ne terminez pas indistinctement tous les processus portant le même nom sur la machine : d’autres répertoires de travail peuvent encore exécuter des tâches valides.

Après avoir confirmé que le fichier n’est plus utilisé, vérifiez la base SQLite en mode lecture seule :

sqlite3 "file:$XCBD/build.db?mode=ro" \
  'PRAGMA quick_check;'

Si le résultat est ok, aucun dommage structurel manifeste n’a été détecté dans la base. Revenez alors au journal pour examiner les autorisations, l’espace disponible et la configuration du projet. Ne passez à la recréation que si la commande renvoie un code différent de zéro, ne parvient pas à lire le fichier ou signale une erreur d’intégrité.

Résultat de la vérification Étape suivante
lsof indique un build actif Arrêter normalement la tâche, puis vérifier de nouveau
quick_check renvoie ok Conserver la base et rechercher une autre cause
Aucun processus actif et échec du contrôle d’intégrité Recréer le XCBuildData concerné
Le chemin n’existe pas Vérifier le workspace, le scheme et les paramètres de DerivedData

Recréer uniquement XCBuildData

Commencez par déplacer le répertoire endommagé vers le dossier de collecte des preuves plutôt que de le supprimer définitivement. Le déplacement doit avoir lieu sur le même système de fichiers afin de limiter le temps de copie :

STAMP=$(date '+%Y%m%d-%H%M%S')
QUARANTINE="$HOME/build-evidence/XCBuildData-$STAMP"

mv "$XCBD" "$QUARANTINE"
mkdir -p "$(dirname "$XCBD")"

Relancez ensuite le build avec exactement les mêmes paramètres. Xcode créera un nouveau répertoire XCBuildData ainsi qu’un nouveau fichier build.db, tandis que les autres répertoires de DerivedData resteront intacts.

set -o pipefail

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Debug \
  -destination 'generic/platform=iOS Simulator' \
  build 2>&1 | tee "$HOME/build-evidence/recovery-build.log"

Si cette recréation minimale échoue encore, n’élargissez pas immédiatement le périmètre de suppression. Comparez d’abord les anciens et les nouveaux journaux pour vérifier si l’erreur concerne toujours la base de données. Si l’erreur porte désormais sur les dépendances, les autorisations ou le code source, la panne de base de données est résolue et il faut traiter ce nouveau problème séparément.

Valider la réparation avec deux builds et prévenir les récidives

Un seul build réussi ne suffit pas à prouver que l’état est stable. Le premier build sert à générer la nouvelle base de données ; le second, effectué en mode incrémental, vérifie que celle-ci peut de nouveau être lue et mise à jour. Les deux exécutions doivent utiliser les mêmes workspace, scheme, configuration, destination et chemin DerivedData. Enregistrez séparément leurs journaux et leurs codes de sortie.

Lors de la validation, contrôlez les points suivants :

  1. Un nouveau fichier build.db a été créé et la commande quick_check en lecture seule renvoie ok.
  2. Le build initial et le build incrémental se terminent tous les deux correctement.
  3. Les journaux ne contiennent plus d’erreur de format de base de données ni d’erreur de lecture de XCBuildData.
  4. Les artefacts de build proviennent de la configuration et du répertoire de destination attendus.
  5. Les éléments collectés lors de l’échec n’ont pas été supprimés avant la confirmation du rétablissement.

La prévention ne consiste pas à vider régulièrement les caches, mais à attribuer un chemin DerivedData explicite à chaque répertoire de travail et à faire en sorte que l’annulation d’une tâche attende bien l’arrêt des processus enfants du build. Les nœuds d’exécution doivent aussi surveiller en permanence l’espace disque disponible afin d’éviter qu’une écriture interrompue par manque de place ne laisse une base de données incomplète.

Si le même chemin est endommagé à plusieurs reprises, consignez les arrêts anormaux, l’état du disque et la version de Xcode avant chaque incident. N’ajoutez pas la commande de nettoyage comme étape préalable systématique de tous les builds. Réinitialiser le cache à chaque exécution masque la cause réelle du problème et annule les bénéfices des builds incrémentaux.

La procédure peut finalement être ramenée à quatre actions : conserver les preuves, confirmer le chemin, effectuer une vérification en lecture seule et procéder à une recréation minimale. N’envisagez de supprimer une plus grande partie de DerivedData que si ces étapes ne permettent toujours pas de rétablir le build. Cette méthode réduit le temps de récupération tout en préservant suffisamment d’informations pour identifier la cause d’une récidive.

Questions fréquentes

Faut-il supprimer tout DerivedData après une erreur build.db ?

Non, pas en première intention. Arrêtez les builds concernés, conservez les journaux, puis supprimez seulement XCBuildData pour laisser Xcode reconstruire sa base.

Comment distinguer une corruption d'un fichier encore utilisé ?

Vérifiez avec lsof si un processus garde build.db ouvert, puis lancez SQLite quick_check en lecture seule. Sans processus actif, un échec indique une corruption probable.

Comment valider que la réparation est stable ?

Exécutez un build propre puis un build incrémental avec les mêmes workspace, scheme, configuration et chemin DerivedData, en conservant les résultats.

Numacs Mac dans le cloud

Externalisez vos tâches de build sur une station de travail physique dédiée

Deux configurations Apple Silicon sont proposées sur des machines physiques dédiées, jamais virtualisées. Louez-les à la journée, à la semaine, au mois ou au trimestre, avec des nœuds à Singapour, Tokyo, Séoul et Hong Kong.

Choisir un appareil et commander