Guide d’ingénierie

Optimiser le cache Xcode sur un Mac cloud distant

Optimiser le cache Xcode sur un Mac cloud distant

Deux jours avant une mise en production, une nouvelle machine Mac distante est ajoutée en urgence. Le clonage du code ne prend que quelques minutes, mais le premier build Xcode semble interminable. Le deuxième est certes plus rapide, mais un changement de branche fait réapparaître d’anciennes ressources, des problèmes d’indexation ou une nouvelle résolution des dépendances. Le problème tient généralement moins aux performances de la machine qu’au mélange de DerivedData, des paquets Swift téléchargés et des résultats de build dans les répertoires par défaut. Il devient alors impossible de vérifier si le cache est réellement utilisé ou d’en extraire sélectivement les éléments utiles avant la fin de la période de location.

Définir une base de référence comparable pour les builds

Avant d’optimiser le cache, commencez par figer les variables. Consignez la sortie de xcodebuild -version, la version de macOS, le hash du commit, le Scheme, la configuration de build et la plateforme cible. Pendant les tests, évitez de mettre à jour les dépendances, de changer de version de Xcode ou de modifier les options de compilation, faute de quoi les durées obtenues ne seront pas comparables.

Exécutez au minimum trois séries de tests :

  1. Effectuez un build à froid après avoir supprimé le DerivedData propre au projet.
  2. Sans modifier le code, relancez exactement la même commande pour effectuer un build à chaud.
  3. Modifiez un fichier Swift ordinaire, puis effectuez un build incrémental.

Pour chaque exécution, enregistrez l’heure de début, l’heure de fin, le code de sortie et le bundle de résultats. La barre de progression de l’interface graphique est utile pour suivre l’avancement, mais pas pour effectuer des comparaisons précises. Pour automatiser les mesures, utilisez systématiquement xcodebuild.

L’objectif d’un cache n’est pas de rendre un build ponctuel aussi rapide que possible, mais d’obtenir des durées et des résultats prévisibles à partir d’entrées identiques.

Séparer les caches selon leur fonction

Il est recommandé de créer quatre catégories de répertoires dans l’espace de travail : le code source, DerivedData, les paquets Swift téléchargés et les résultats avec leurs journaux. Plusieurs projets ne doivent pas partager le même DerivedData, et les configurations Debug et Release ne doivent pas s’écraser mutuellement. Si vous gérez de nombreux projets, vous pouvez également intégrer la version majeure de Xcode et un identifiant de branche ou de commit.

Répertoire Contenu conservé Stratégie recommandée
Source Arbre de travail Git Un répertoire distinct par projet
DerivedData Index et artefacts intermédiaires Isolation par version, Scheme et configuration
SourcePackages Téléchargements et checkouts des paquets Swift Réutilisation tant que le fichier de verrouillage reste inchangé
Results Bundles de résultats et journaux Archivage par date et heure de build

Le répertoire des paquets Swift peut être réutilisé à condition que le fichier de verrouillage des dépendances soit identique. S’il a changé, laissez d’abord la résolution des dépendances se terminer normalement au lieu de copier directement un ancien répertoire de checkouts dans le nouveau projet. DerivedData est plus sensible à la chaîne d’outils : après un changement de version de Xcode, créez un nouveau répertoire plutôt que de continuer à utiliser l’ancien en l’écrasant.

Exécuter les builds avec une commande stable

Le script suivant rend explicites tous les chemins importants. Remplacez l’espace de travail, le Scheme et la plateforme cible par les valeurs correspondant au projet. Le chemin du bundle de résultats ne doit pas exister avant l’exécution ; le script supprime donc d’abord tout répertoire portant le même nom.

#!/bin/bash
set -euo pipefail

ROOT="${HOME}/BuildWorkspace"
SCHEME="Application"
CONFIGURATION="Release"
DERIVED="${ROOT}/DerivedData/${SCHEME}-${CONFIGURATION}"
PACKAGES="${ROOT}/SourcePackages"
RESULT="${ROOT}/Results/${SCHEME}.xcresult"
LOG="${ROOT}/Results/${SCHEME}.log"

mkdir -p "${DERIVED}" "${PACKAGES}" "${ROOT}/Results"
rm -rf "${RESULT}"

xcodebuild \
  -workspace "${ROOT}/Source/Application.xcworkspace" \
  -scheme "${SCHEME}" \
  -configuration "${CONFIGURATION}" \
  -destination "generic/platform=iOS" \
  -derivedDataPath "${DERIVED}" \
  -clonedSourcePackagesDirPath "${PACKAGES}" \
  -resultBundlePath "${RESULT}" \
  build | tee "${LOG}"

Commencez par exécuter xcodebuild -list dans le terminal afin de vérifier que l’espace de travail expose bien le Scheme attendu. Si le projet utilise un fichier de projet plutôt qu’un espace de travail, remplacez -workspace par -project. Ne transmettez jamais les deux options simultanément.

Générer un identifiant stable pour le cache

La clé de cache doit au minimum contenir la version de Xcode, l’empreinte du fichier de verrouillage des dépendances, le Scheme et la configuration. Vous pouvez y ajouter le nom de la branche, mais celui-ci ne doit pas constituer l’unique identifiant, car les dépendances peuvent avoir changé au sein d’une même branche.

XCODE_KEY="$(xcodebuild -version | shasum -a 256 | cut -c1-12)"
PACKAGE_KEY="$(shasum -a 256 Package.resolved | cut -c1-12)"
printf '%s-%s-%s-%s
' "${XCODE_KEY}" "${PACKAGE_KEY}" "${SCHEME}" "${CONFIGURATION}"

Si l’espace de travail contient plusieurs fichiers Package.resolved, sélectionnez explicitement celui qui intervient réellement dans le build. Si aucun fichier de verrouillage n’est disponible, ne générez pas une clé vide fixe : des états de dépendances différents aboutiraient alors dans le même répertoire.

Identifier les recompilations inutiles par étape

Lorsqu’un build à chaud reste lent, commencez par déterminer à quelle étape le temps est consacré. Si la résolution des dépendances se répète, vérifiez qu’aucun script ne réécrit le fichier de verrouillage et que les droits du répertoire des paquets sont corrects. Si la compilation se relance sans cesse, contrôlez les conditions de compilation, les fichiers générés automatiquement et les scripts des phases de build. Si le problème se situe à l’édition de liens, vérifiez si les informations de version ou le contenu incorporé changent à chaque exécution.

Le problème le plus fréquent concerne les phases de script qui ne déclarent ni leurs entrées ni leurs sorties. Dès qu’un script modifie à chaque exécution l’horodatage d’un fichier généré, les cibles en aval sont considérées comme devant être reconstruites. Pour corriger ce comportement, le script ne doit remplacer le fichier que lorsque son contenu a réellement changé. Il faut également renseigner avec précision les chemins d’entrée et de sortie dans la phase de build Xcode.

Ne faites pas de la « suppression de tous les caches » une étape systématique. Elle permet uniquement de savoir si le cache est contaminé, sans expliquer l’origine du problème. Commencez par déplacer uniquement le DerivedData du projet. Si le problème persiste, traitez ensuite le répertoire des paquets Swift. Cette approche préserve les éléments nécessaires au diagnostic et évite de retélécharger toutes les dépendances à chaque tentative.

Conserver uniquement les éléments réellement utiles avant l’expiration

Avant d’exporter les données, arrêtez les builds en cours et vérifiez que tous les journaux ont bien été écrits. Archivez ensuite les fichiers de verrouillage des dépendances, les téléchargements de paquets Swift nécessaires, les bundles de résultats et les scripts du projet. Pour le code source, la référence doit rester le contenu validé dans le dépôt ; examinez séparément toute modification non commitée présente dans l’arbre de travail.

Il est déconseillé de conserver l’intégralité de DerivedData sur le long terme. Ce répertoire est généralement volumineux et contient des index, des fichiers objets ainsi que des artefacts intermédiaires liés à une chaîne d’outils précise. Lors d’une prochaine utilisation avec une autre version de Xcode, les régénérer est souvent plus fiable. N’intégrez pas à l’archive du cache les certificats sensibles, les clés privées, les jetons ou les fichiers d’environnement temporaires. Nettoyez d’abord l’historique du Shell, les trousseaux temporaires et les fichiers d’identifiants générés par le projet, puis vérifiez la liste du contenu exporté.

Conservez enfin une note succincte indiquant la version de la chaîne d’outils, la clé de cache, le dernier commit ayant abouti, la commande de build, les chemins exportés et ceux qui ont explicitement été exclus. Restaurer le prochain Mac cloud à partir de ces informations permet de reproduire l’environnement bien plus facilement que de copier l’intégralité du répertoire personnel.

Questions fréquentes

Peut-on partager DerivedData entre plusieurs versions de Xcode ?

Non. Séparez les répertoires par version de Xcode, projet, schéma et configuration. Après un changement de chaîne d’outils, recréez DerivedData afin d’éviter les index et objets intermédiaires obsolètes.

Quels caches faut-il exporter avant la fin de la location ?

Exportez d’abord les fichiers de verrouillage, les téléchargements de paquets Swift réutilisables, les bundles de résultats et les journaux utiles. Les objets compilés et index sont volumineux et très liés à la chaîne d’outils.

Supprimer tous les caches corrige-t-il une compilation lente ?

Cela peut confirmer une corruption, mais ce n’est pas une stratégie quotidienne. Mesurez séparément la résolution des dépendances, la compilation, l’édition de liens et les tests, puis nettoyez uniquement l’étape concernée.

ArmMacs Cloud Mac

Utilisez un nœud physique dédié selon la durée de votre projet

Choisissez la puce, la mémoire, le stockage, la durée de location et un nœud disponible. La livraison démarre lorsque le stock est disponible.

Choisir un modèle et commander