Une fois l’archivage terminé sur un nœud CI, les artefacts doivent être transmis aux tâches de test, de publication ou de conservation. La solution la plus évidente consiste à utiliser directement un partage SMB comme espace de travail afin que plusieurs Mac cloud puissent lire et écrire dans le même projet. Cette approche peut fonctionner temporairement avec un petit dépôt, mais dès qu’une instabilité réseau, un retard de verrouillage de fichiers ou une perte de montage survient, les fichiers intermédiaires de Xcode risquent d’être corrompus. Les résultats peuvent même être écrits dans un répertoire local vide portant le même nom.
Une séparation plus fiable consiste à réserver SMB à la réception des instantanés d’entrée et des artefacts finaux. L’extraction du code source, la résolution des dépendances, DerivedData et les fichiers temporaires doivent tous rester sur le disque local.
Séparer d’abord les trois catégories de répertoires
Chaque tâche nécessite au minimum trois chemins qui ne doivent jamais être utilisés de manière interchangeable :
- Zone d’entrée en lecture seule : conserve les instantanés du code source ou les paquets de dépendances à version fixe.
- Espace de travail local : contient le code source extrait, DerivedData, les résultats des tests et les fichiers temporaires.
- Zone de livraison distante : ne conserve que les archives validées, les résumés de journaux et les manifestes.
Deux tâches concurrentes ne doivent jamais partager le même DerivedData. Même lorsqu’elles compilent le même commit, l’index, le cache des modules et la base de données de compilation peuvent être modifiés simultanément. Il est recommandé d’intégrer l’identifiant de la tâche au chemin, par exemple ~/ci-work/$RUN_ID, puis de nettoyer ce répertoire à la fin de la tâche conformément à la politique de conservation.
Un partage réseau doit être considéré comme une frontière de livraison, et non comme un substitut transparent au disque local. La réussite du build et celle du transfert doivent également constituer deux états indépendants.
Confier la gestion du montage à un processus permanent
Une tâche sans surveillance ne doit pas inclure directement un nom d’utilisateur et un mot de passe dans ses arguments de ligne de commande. Il est préférable qu’un processus permanent contrôlé monte SMB au préalable et lise les identifiants depuis un trousseau distinct ou une configuration à accès restreint. Les tâches de build ordinaires ne reçoivent alors que les droits d’écriture minimaux sur le répertoire cible.
Au démarrage d’une tâche, vérifier uniquement l’existence du répertoire ne suffit pas. Après une déconnexion SMB, le point de montage peut redevenir un simple répertoire local. Il faut au minimum vérifier le type de système de fichiers, les droits d’écriture et effectuer un aller-retour avec un petit fichier :
set -euo pipefail
SHARE_ROOT="/Volumes/ci-artifacts"
PROBE="$SHARE_ROOT/.probe-${RUN_ID}"
fs_type="$(stat -f '%T' "$SHARE_ROOT")"
test "$fs_type" = "smbfs"
test -w "$SHARE_ROOT"
printf '%s\n' "$RUN_ID" > "$PROBE"
test "$(cat "$PROBE")" = "$RUN_ID"
rm -f "$PROBE"
La tâche doit être interrompue dès qu’une de ces étapes échoue. Il ne faut pas créer automatiquement le point de montage puis poursuivre l’exécution, car une simple déconnexion du partage pourrait alors aboutir à un « build réussi » dont les artefacts ne se trouvent que sur la machine locale.
Limiter les opérations de build au disque local
Synchronisez d’abord le commit fixé dans un répertoire temporaire local, puis lancez la résolution des dépendances et le build. Le répertoire de la tâche, DerivedData et le paquet de résultats doivent tous inclure un identifiant de tâche unique afin d’éviter que des tâches parallèles ne s’écrasent mutuellement.
LOCAL_ROOT="$(mktemp -d "$TMPDIR/vmcache-ci.XXXXXX")"
trap 'rm -rf "$LOCAL_ROOT"' EXIT
rsync -a --delete "$SOURCE_SNAPSHOT/" "$LOCAL_ROOT/repo/"
xcodebuild \
-workspace "$LOCAL_ROOT/repo/App.xcworkspace" \
-scheme App \
-configuration Release \
-derivedDataPath "$LOCAL_ROOT/DerivedData" \
-resultBundlePath "$LOCAL_ROOT/TestResults.xcresult" \
build
Ici, SOURCE_SNAPSHOT doit correspondre à un commit immuable, et non à un répertoire actif que d’autres tâches peuvent encore modifier. Si l’entrée provient d’un volume partagé, vérifiez d’abord l’identifiant du commit ou le manifeste avant de lancer la synchronisation.
Ne pas mettre en cache l’intégralité de DerivedData
Copier l’intégralité de DerivedData entre les tâches est généralement contre-productif : le cache est volumineux, tandis que les chemins internes et les paramètres de compilation peuvent changer. Une approche plus facile à maintenir consiste à ne mettre en cache que les répertoires de téléchargement des dépendances dont la réutilisation est explicitement sûre, puis à intégrer la version de Xcode, l’architecture et l’empreinte du fichier de verrouillage dans la clé de cache. Si la clé ne correspond pas, le cache doit être régénéré sans tenter de réutilisation approximative.
Effectuer une livraison atomique à l’aide d’un nom temporaire
Copier directement un fichier sous son nom définitif expose un artefact incomplet. Une tâche en aval peut commencer à le lire dès qu’il apparaît, alors que le transfert est encore en cours. L’ordre correct est le suivant : créer le paquet localement, calculer son empreinte localement, le copier sur le volume partagé sous un nom temporaire, le valider sur ce volume, puis le renommer dans le même répertoire.
ARTIFACT="$LOCAL_ROOT/App-release.zip"
ditto -c -k --norsrc "$LOCAL_ROOT/DerivedData/Build/Products/Release" "$ARTIFACT"
REMOTE_DIR="$SHARE_ROOT/releases/$GIT_COMMIT"
REMOTE_TMP="$REMOTE_DIR/.App-release.zip.${RUN_ID}.partial"
REMOTE_FINAL="$REMOTE_DIR/App-release.zip"
mkdir -p "$REMOTE_DIR"
cp "$ARTIFACT" "$REMOTE_TMP"
local_hash="$(shasum -a 256 "$ARTIFACT" | awk '{print $1}')"
remote_hash="$(shasum -a 256 "$REMOTE_TMP" | awk '{print $1}')"
test "$local_hash" = "$remote_hash"
mv "$REMOTE_TMP" "$REMOTE_FINAL"
printf '%s %s\n' "$remote_hash" "App-release.zip" \
> "$REMOTE_DIR/SHA256SUMS.${RUN_ID}"
Pour que le renommage soit atomique, le fichier temporaire et le fichier définitif doivent se trouver sur le même volume partagé, de préférence dans le même répertoire. N’écrivez pas d’abord le fichier localement avant d’utiliser mv entre deux volumes : cette opération se transforme alors en copie suivie d’une suppression.
Rendre les échecs identifiables et nettoyables
Après un échec de transfert, les résultats locaux du build doivent être conservés pendant une durée contrôlée afin de permettre une nouvelle tentative, mais la tâche ne doit pas être marquée comme entièrement livrée. Il est recommandé d’enregistrer séparément les états build_completed, artifact_verified et publish_completed. Les tâches en aval ne doivent accepter que le dernier état.
Les fichiers .partial ayant dépassé leur durée de conservation doivent également être supprimés régulièrement. La tâche de nettoyage doit vérifier le format du nom de fichier et la date de modification, et ne traiter que le suffixe temporaire sans parcourir ni supprimer les artefacts définitifs. Si le volume partagé passe en lecture seule, présente des incohérences de somme de contrôle ou se déconnecte à répétition, la file de publication doit être suspendue. Il faut d’abord préserver les résultats locaux et les informations de diagnostic du montage.
Sur les Mac cloud de VMCache, l’essentiel de cette mise en œuvre ne réside pas dans une commande de montage particulière, mais dans la clarté des frontières : le volume réseau assure les échanges, le disque local prend en charge les builds et un nom de fichier définitif désigne uniquement un artefact dont la validation est terminée. Ainsi, même en cas d’incident SMB temporaire, l’environnement de build et l’état de livraison ne sont pas confondus.
Questions fréquentes
Peut-on placer DerivedData directement sur un partage SMB ?
Ce n’est pas recommandé. DerivedData produit de nombreux petits fichiers, verrous et changements de métadonnées. Conservez-le sur le disque local et réservez SMB aux entrées versionnées et aux artefacts terminés.
Comment empêcher la lecture d’un artefact encore en cours de copie ?
Copiez-le sous un nom temporaire sur le partage, écrivez sa somme de contrôle et son manifeste, puis renommez-le dans le même répertoire. Les consommateurs ne doivent surveiller que les noms définitifs.
Que doit faire la CI si le partage semble monté mais reste inaccessible ?
Elle doit vérifier le type de système de fichiers, tester l’écriture et relire un petit fichier. En cas d’échec, le job s’arrête au lieu d’utiliser silencieusement un répertoire local portant le même nom.
Choisissez un Mac cloud pour compiler, tester et exécuter des inférences MLX
Choisissez votre M4, votre mémoire, votre stockage, votre nœud et votre durée selon vos besoins. Chaque location correspond à une machine physique dédiée, et non à une machine virtuelle.