Un même commit peut réussir en local, puis provoquer par intermittence l’erreur module file was created by a different version of the compiler sur un Mac cloud fonctionnant en continu, avant qu’une nouvelle exécution ne réussisse à nouveau. Ce type de panne ne vient généralement pas d’une modification aléatoire du code source Swift, mais du fait que des chaînes d’outils, des SDK ou des tâches parallèles différents accèdent au même cache de modules précompilés. Supprimer l’intégralité de DerivedData peut apporter un soulagement temporaire, mais efface aussi les indices les plus précieux et n’empêche pas une nouvelle contamination.
Vérifier d’abord qu’il s’agit bien du cache de modules
Clang compile les en-têtes système et les modules Objective-C en fichiers PCM, tandis que le compilateur Swift maintient également son propre cache de modules. Si le compilateur, le SDK, l’architecture ou les options de compilation utilisés pour créer le cache diffèrent de ceux de la tâche en cours, le chargement peut échouer.
Commencez par rechercher ces indices dans les journaux complets :
| Indice dans les journaux | Cause la plus probable | Premier point à vérifier |
|---|---|---|
different version of the compiler |
Changement de version de Xcode | xcodebuild -version |
PCM file is out of date |
Modification du SDK ou de l’horodatage des en-têtes | Chemin du SDK actuel |
could not build module |
Module en amont endommagé | Premier module en échec |
| La même tâche réussit après relance | Écriture parallèle ou cache partagé | Propriétaire du répertoire de cache |
Ne conservez pas uniquement la dernière erreur. Un échec de chargement de module entraîne souvent des dizaines d’erreurs en cascade : remontez jusqu’à la première occurrence de module, .pcm ou ModuleCache.
Avant de vider le cache, conservez le journal de l’échec, le numéro de build de Xcode et les paramètres de compilation réellement appliqués. Sans ces informations, même si la compilation réussit ensuite, il sera impossible de déterminer si la cause était une dérive de la chaîne d’outils, une contamination concurrente ou une corruption du cache.
Conserver un état comparable pour la tâche en échec
Sur un Mac cloud VMKeep, commencez par enregistrer le répertoire développeur réellement résolu par les commandes, au lieu de vous fier uniquement à la version que le script est censé utiliser.
set -o pipefail
mkdir -p artifacts/diagnostics
xcode-select -p | tee artifacts/diagnostics/developer-dir.txt
xcodebuild -version | tee artifacts/diagnostics/xcode-version.txt
xcrun --sdk iphoneos --show-sdk-path | tee artifacts/diagnostics/sdk-path.txt
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-showBuildSettings \
> artifacts/diagnostics/build-settings.txt
Examinez ensuite les valeurs de SDKROOT, ARCHS, DEVELOPER_DIR, CLANG_MODULE_CACHE_PATH et SWIFT_MODULE_CACHE_PATH dans la commande ayant échoué. Si la tâche est lancée via sudo, une session distante ou un ordonnanceur, comparez également l’utilisateur d’exécution et la variable HOME : exécuter le même script sous un autre utilisateur modifie aussi l’emplacement par défaut du cache.
Fixer explicitement la chaîne d’outils
Le script de compilation doit définir explicitement DEVELOPER_DIR et exécuter xcodebuild -version avant toute compilation réelle. Si la version ne correspond pas à celle attendue, quittez immédiatement au lieu de laisser la tâche écrire dans un cache partagé avec la mauvaise chaîne d’outils. Après une mise à niveau de Xcode, générez une nouvelle clé de cache plutôt que de continuer à utiliser l’ancien répertoire.
Limiter le cache à une seule tâche
Une clé de répertoire fiable doit inclure au minimum le numéro de build de Xcode, le SDK, l’architecture et l’identifiant de la tâche. Un nom de branche ne doit pas être utilisé directement comme chemin : ses caractères spéciaux doivent d’abord être normalisés. L’approche suivante attribue à chaque tâche ses propres répertoires DerivedData et de cache de modules, tout en conservant les dépendances téléchargées dans un emplacement distinct.
set -euo pipefail
JOB_KEY="$(printf '%s' "${CI_JOB_ID:-local}" | tr -cs 'A-Za-z0-9._-' '-')"
XCODE_BUILD="$(xcodebuild -version | awk '/Build version/{print $3}')"
SDK_BUILD="$(xcrun --sdk iphoneos --show-sdk-build-version)"
CACHE_KEY="${XCODE_BUILD}-${SDK_BUILD}-arm64-${JOB_KEY}"
ROOT="$HOME/BuildCaches/$CACHE_KEY"
DERIVED="$ROOT/DerivedData"
MODULES="$ROOT/Modules"
PACKAGES="$HOME/BuildCaches/SourcePackages"
mkdir -p "$DERIVED" "$MODULES/clang" "$MODULES/swift" "$PACKAGES"
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-destination 'generic/platform=iOS' \
-derivedDataPath "$DERIVED" \
-clonedSourcePackagesDirPath "$PACKAGES" \
CLANG_MODULE_CACHE_PATH="$MODULES/clang" \
SWIFT_MODULE_CACHE_PATH="$MODULES/swift" \
build
Les branches de longue durée peuvent réutiliser leur propre clé stable, tandis que les demandes de fusion temporaires doivent être isolées par tâche. Ne laissez pas deux processus xcodebuild encore actifs écrire dans le même répertoire de modules et ne traitez pas le cache de modules comme une archive générique pouvant être restaurée entre différentes chaînes d’outils.
Nettoyer précisément au lieu de tout réinitialiser
Après avoir confirmé une défaillance du cache de modules, arrêtez d’abord les processus de compilation qui utilisent le répertoire concerné, puis intervenez progressivement, du périmètre le plus restreint au plus large :
- Supprimez les répertoires
Modules/clangetModules/swiftde la tâche en cours. - Conservez
SourcePackages, puis relancez la résolution des dépendances et la compilation. - Si l’index ou les produits intermédiaires font toujours référence aux anciens modules, supprimez également le DerivedData de la tâche en cours.
- Si un seul module tiers échoue, vérifiez que son binaire prend en charge l’architecture et le SDK actuels, au lieu de vider sans cesse le cache pour masquer un problème de compatibilité.
Le nettoyage doit rester limité au répertoire calculé pour la tâche. La commande rm -rf ~/Library/Developer/Xcode/DerivedData/* affecte simultanément les autres tâches et risque de transformer une panne locale en redémarrage à froid de toute la machine.
Éviter de supprimer un répertoire encore utilisé
Lors de la récupération des anciens caches, utilisez les verrous de tâches ou les registres d’exécution pour confirmer qu’aucun processus n’occupe encore le répertoire, puis supprimez-le en fonction de sa dernière date d’utilisation. Ne vous fiez pas uniquement à sa date de création, car une branche stable peut continuer à réutiliser un cache créé depuis longtemps. Le script de nettoyage doit également vérifier que le chemin cible se trouve sous le répertoire racine attendu et quitter immédiatement si une variable est vide ou si le calcul du chemin échoue.
Faire de la cohérence du cache une règle de validation
À terme, le pipeline doit signaler activement toute dérive. À chaque compilation, enregistrez le numéro de build de Xcode, le numéro de build du SDK, l’architecture, la clé de cache et l’utilisateur d’exécution. À la fin de la tâche, archivez ces informations avec le journal d’échec. Lors d’une mise à niveau de la chaîne d’outils, créez un nouvel espace de noms, puis ne récupérez les anciens caches qu’après avoir confirmé la stabilité du nouveau flux.
Vous pouvez également prévoir une tâche de validation qui ne restaure pas le cache de modules. Si la tâche habituelle échoue alors que la tâche propre réussit, le problème se situe au niveau du cache. Si les deux échouent, reprenez l’analyse du code source, des dépendances ou des paramètres de compilation. Cette méthode évite aussi bien d’attribuer toutes les erreurs au cache que de compter sur des relances répétées.
Le cache de modules fait fondamentalement partie des entrées du compilateur. Lorsque la version, la propriété des répertoires et les limites de concurrence sont inscrites dans les scripts, l’invalidation des PCM cesse d’être une panne aléatoire pour devenir un incident d’ingénierie localisable et récupérable.
Questions fréquentes
Faut-il supprimer tout DerivedData après une erreur PCM ?
Non, pas en première intention. Conservez le journal et les réglages, puis supprimez seulement ModuleCache.noindex pour la tâche concernée. Ne recréez son DerivedData dédié que si les index et produits sont aussi incohérents.
Plusieurs versions de Xcode peuvent-elles partager le même cache ?
Ce n’est pas recommandé. La clé du cache doit inclure le numéro de build Xcode, le SDK, l’architecture et l’identifiant de tâche afin d’éviter la réutilisation d’un PCM incompatible.
Poursuivez la validation de ce workflow sur un Mac dans le cloud
Choisissez la mémoire, le stockage, le nœud et la durée de location adaptés parmi trois configurations Apple Silicon.