Dans une file d’attente CI distante, le cas le plus difficile à gérer n’est pas l’échec, mais l’annulation. Lorsqu’un développeur arrête une tâche depuis l’interface, l’exécuteur commence généralement par envoyer le signal TERM au script. Si celui-ci ne l’intercepte pas, les processus xcodebuild, les tests ou les collecteurs de journaux en cours d’exécution peuvent continuer à occuper l’espace de travail. La tâche suivante rencontre alors un fichier de verrouillage, un simulateur déjà utilisé ou un paquet de résultats incomplet. Le problème paraît aléatoire, alors qu’il provient d’un arrêt incomplet de l’exécution précédente.
Commencer par définir les critères d’une annulation terminée
La disparition d’une tâche de la file d’attente ne signifie pas que ses ressources ont été libérées. Un critère de fin exploitable doit couvrir au moins quatre points :
- Le processus de compilation principal a reçu le signal d’arrêt et s’est terminé.
- Le script a enregistré la véritable cause de l’arrêt au lieu de la classer systématiquement comme un échec de compilation.
- Les journaux déjà produits et le fichier
xcresultont été conservés. - Aucun processus en arrière-plan ni montage temporaire appartenant à cette tâche ne subsiste dans l’espace de travail.
L’annulation fait partie des chemins normaux du pipeline. Si la logique de nettoyage n’est testée qu’en cas d’arrêt anormal, vous découvrirez probablement qu’elle ne fonctionne pas au moment où la charge est la plus forte.
Commencez par vérifier quel signal l’exécuteur envoie réellement. Une tâche de test peut contenir une sonde qui se contente d’enregistrer les signaux, sans inclure de variables d’environnement sensibles. Ne vous fiez pas à un comportement par défaut supposé et n’utilisez pas l’envoi immédiat de KILL comme stratégie d’annulation : ce signal ne laisse pas aux processus le temps de fermer leurs bases de données, de vider leurs journaux ou de finaliser leurs paquets de résultats.
Intercepter TERM et suivre le processus principal
Le script de compilation doit enregistrer explicitement le PID de xcodebuild. À la réception de TERM ou de INT, il ne doit arrêter que les processus créés par la tâche en cours, sans recourir à un killall sans périmètre. Le squelette Bash suivant écrit les journaux et les résultats dans un répertoire propre à chaque exécution :
#!/bin/bash
set -u
run_id="${CI_RUN_ID:-manual-$(date +%s)}"
run_dir="$PWD/.ci-runs/$run_id"
result_path="$run_dir/TestResults.xcresult"
log_path="$run_dir/xcodebuild.log"
status_path="$run_dir/status.txt"
child_pid=""
cancelled=0
mkdir -p "$run_dir"
on_cancel() {
cancelled=1
if [[ -n "$child_pid" ]] && kill -0 "$child_pid" 2>/dev/null; then
kill -TERM "$child_pid" 2>/dev/null || true
fi
}
trap on_cancel TERM INT
xcodebuild \
-workspace Example.xcworkspace \
-scheme Example \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-resultBundlePath "$result_path" \
test >"$log_path" 2>&1 &
child_pid=$!
wait "$child_pid"
build_status=$?
if [[ "$cancelled" -eq 1 ]]; then
printf 'cancelled\n' >"$status_path"
exit 130
fi
printf 'finished:%s\n' "$build_status" >"$status_path"
exit "$build_status"
Le gestionnaire de signal ne supprime pas directement le répertoire. Il se contente de transmettre le signal, tandis que le flux principal exécute toujours wait. Le script peut ainsi distinguer un échec normal d’une annulation manuelle. Le code de sortie 130 sert couramment à signaler une interruption, mais vérifiez d’abord comment votre système CI le convertit en statut d’annulation.
Accorder un délai d’arrêt limité au processus
Certains tests peuvent ne pas réagir à TERM. En production, le script peut vérifier le PID toutes les secondes après l’envoi du signal et attendre, par exemple, 20 secondes avant d’envoyer KILL. Ce délai de grâce doit être borné, sans quoi l’annulation pourrait monopoliser l’exécuteur indéfiniment. À l’inverse, ne le réduisez pas à une ou deux secondes : xcodebuild peut être en train d’écrire le paquet de résultats.
Séparer la conservation des preuves du nettoyage des caches
L’erreur la plus fréquente consiste à exécuter directement rm -rf "$run_dir" dans un trap cleanup EXIT. Le répertoire est alors propre, mais les journaux nécessaires au diagnostic disparaissent avec lui. Il est plus sûr de répartir le contenu en deux catégories :
| Contenu | Traitement après annulation |
|---|---|
xcodebuild.log, xcresult, fichier d’état |
Conserver et archiver |
| Répertoires temporaires créés par cette tâche | Supprimer après vérification du chemin |
| Cache de dépendances partagé | Ne pas supprimer dans le gestionnaire d’annulation |
| Simulateur et données dérivées | Traiter selon la stratégie d’isolation des tâches |
L’archivage des journaux doit également tolérer l’absence de fichiers qui n’ont pas encore été créés. Utilisez [[ -e "$result_path" ]] pour effectuer la vérification, au lieu de transformer l’absence du paquet de résultats en une seconde erreur. Si le téléversement risque de prendre du temps, appliquez-lui un délai d’expiration distinct et veillez à ce qu’un échec de téléversement ne remplace pas le code de sortie initial de la compilation.
Il est conseillé de placer les répertoires temporaires dans un répertoire parent fixe et d’inclure le numéro d’exécution dans leur chemin. Avant toute suppression, vérifiez à la fois le préfixe et le chemin réel afin qu’une variable vide n’élargisse pas le périmètre de suppression :
safe_remove_run_dir() {
local target="$1"
local root="$PWD/.ci-tmp"
[[ -n "$target" ]] || return 1
[[ "$target" == "$root/"* ]] || return 1
[[ -d "$target" ]] || return 0
rm -rf -- "$target"
}
Rechercher les processus restants plutôt que tout arrêter globalement
Une fois la tâche terminée, vous pouvez commencer par enregistrer un instantané des processus :
ps -axo pid,ppid,state,etime,command >"$run_dir/processes-after.txt"
pgrep -P "$$" >"$run_dir/direct-children.txt" 2>/dev/null || true
pgrep -P ne répertorie que les processus enfants directs et ne couvre donc pas tous les descendants. Une approche plus fiable consiste à lancer chaque processus auxiliaire depuis le script et à enregistrer son PID. Par exemple, les processus de transfert des journaux, les agents de test et les redirections de ports peuvent être inscrits dans un tableau, puis vérifiés un par un pendant le nettoyage. N’arrêtez pas tous les processus portant le même nom sur la machine : même un Mac mini physique dédié peut exécuter en parallèle des processus de longue durée lancés volontairement par un développeur.
Si le pipeline autorise l’exécution parallèle, l’espace de travail, le répertoire de résultats, le groupe d’appareils du simulateur et le répertoire temporaire doivent tous inclure l’identifiant de la tâche. Une annulation propre ne gère que le cycle de vie des processus ; elle ne peut pas empêcher les écrasements provoqués par plusieurs tâches partageant un même répertoire modifiable.
Valider le chemin d’annulation avec trois types d’exercices
Avant la mise en production, effectuez au moins trois exercices. Pour le premier, annulez la tâche pendant la résolution des dépendances et vérifiez que le gestionnaire de paquets et les processus de journalisation se terminent. Pour le deuxième, annulez-la pendant la compilation et assurez-vous que la tâche suivante ne réutilise pas la base de données de compilation par erreur. Pour le troisième, annulez-la pendant l’exécution des tests, puis vérifiez que le paquet de résultats reste lisible et qu’aucun processus lié au simulateur ne continue à occuper le groupe d’appareils de cette tâche.
Pour chaque exercice, contrôlez les points suivants :
- L’interface CI indique que le résultat a été annulé, et non qu’il s’agit d’un échec de test ordinaire.
- Le processus principal s’est arrêté pendant le délai de grâce et toute escalade après expiration du délai a été enregistrée.
- La fin du journal indique à quelle étape l’annulation s’est produite.
- Lorsqu’un paquet de résultats existe, il peut être lu avec
xcrun xcresulttool. - La tâche suivante utilise un nouveau répertoire d’exécution et démarre normalement.
- Le script de nettoyage peut être relancé sans erreur et sans supprimer les données d’autres tâches.
Pour les pipelines CI de longue durée exécutés sur les Mac cloud de RentMini, ajoutez également ces exercices d’annulation à la liste de validation appliquée après chaque mise à jour de l’exécuteur. Il suffit de vérifier dans la console les nœuds et configurations actuellement disponibles ; le script lui-même ne doit dépendre ni d’une ville, ni du nom d’une machine, ni d’un chemin d’espace de travail fixe. L’objectif final n’est pas d’obtenir des répertoires visuellement impeccables, mais de garantir que chaque arrêt reste délimité et documenté, et que la compilation suivante reparte d’un état clairement défini.
Questions fréquentes
Faut-il envoyer immédiatement kill -9 lors d’une annulation CI ?
Non. Envoyez d’abord TERM au processus principal et accordez-lui un délai limité pour fermer ses fichiers et finaliser les journaux. KILL ne doit intervenir qu’après expiration de ce délai.
Quels fichiers conserver après l’annulation d’un build Xcode ?
Conservez le journal xcodebuild, tout paquet xcresult déjà produit, le statut final et un instantané des processus. Le nettoyage des caches ne doit pas supprimer ces éléments.
Préparez votre prochain build sur un Mac mini physique dédié
Choisissez la configuration M4, la durée de location et le nœud. Chaque commande correspond à un appareil physique dédié ; la disponibilité réelle est indiquée en temps réel dans la console.