Rendre les Git Submodule reproductibles sur un Mac cloud CI

Rendre les Git Submodule reproductibles sur un Mac cloud CI

Un projet iOS peut être cloné sans difficulté sur une machine de développement, puis se bloquer au niveau des sous-modules une fois transféré vers un Mac cloud CI : le dépôt principal est bien extrait, mais un composant privé renvoie une erreur d’autorisation, une dépendance imbriquée reste sur un ancien commit ou un clone superficiel ne trouve pas l’objet demandé. Le problème vient généralement non pas de Xcode, mais d’un processus d’extraction qui ne distingue pas clairement la « branche courante du dépôt principal » du « commit de sous-module enregistré par le dépôt principal ».

Définir d’abord le périmètre de reproductibilité

Dans le dépôt principal, Git Submodule enregistre un chemin, une adresse distante et un commit précis. L’objectif d’un build reproductible est qu’un même commit du dépôt principal fournisse toujours les mêmes objets de sous-modules, et non qu’il suive à chaque exécution le dernier état de leurs branches.

Commencez par enregistrer une référence dans l’environnement de développement :

git submodule status --recursive
git config --file .gitmodules --get-regexp 'submodule\..*\.path'
git config --file .gitmodules --get-regexp 'submodule\..*\.url'

Au début d’une ligne d’état, - indique que le sous-module n’a pas encore été initialisé, + que le commit du répertoire de travail ne correspond pas à celui enregistré par le dépôt principal, et U qu’un conflit de fusion est présent. Dans un pipeline CI, aucun de ces trois états ne doit être considéré comme normal.

N’exécutez pas git submodule update --remote dans une tâche de build. Cette commande suit la branche configurée et peut donc récupérer des dépendances différentes à des moments différents pour un même commit du dépôt principal.

Les adresses relatives facilitent la migration de dépôts au sein d’un même périmètre d’hébergement, mais elles sont résolues à partir de l’adresse distante du dépôt principal. Si une tâche réécrit temporairement origin, exécutez git submodule sync --recursive pendant l’initialisation, puis affichez les adresses réellement résolues après les avoir expurgées des données sensibles.

Créer un point d’entrée unique pour l’extraction

Évitez de mélanger clone --recurse-submodules, des commandes de récupération lancées manuellement depuis chaque répertoire et des commandes correctives ajoutées au script de build. Il est préférable que la CI récupère d’abord le dépôt principal, puis traite tous les niveaux depuis un point d’entrée unique :

set -euo pipefail

git submodule sync --recursive
git submodule update --init --recursive --jobs 4

expected_file="$(mktemp)"
actual_file="$(mktemp)"
trap 'rm -f "$expected_file" "$actual_file"' EXIT

git ls-tree -r HEAD |
  awk '$2 == "commit" { print $3, $4 }' |
  sort > "$expected_file"

git submodule status --recursive |
  sed -E 's/^[ +\-U]([0-9a-f]+) ([^ ]+).*/\1 \2/' |
  sort > "$actual_file"

diff -u "$expected_file" "$actual_file"

Ici, --jobs 4 sert uniquement à limiter le nombre de requêtes réseau parallèles ; il ne constitue pas une recommandation de performance universelle. Les conditions réseau des nœuds, les limites imposées par les serveurs distants et le nombre de sous-modules varient. Commencez avec une faible concurrence, puis ajustez-la en fonction du taux d’échec.

Avant le build, le script doit également exécuter git diff --submodule=log --exit-code. Si une étape d’installation modifie discrètement le répertoire de travail d’un sous-module, la tâche doit échouer immédiatement au lieu de poursuivre l’archivage avec un code source indéterminé.

Utiliser les clones superficiels avec prudence

Les clones superficiels réduisent le volume transféré, mais ils expliquent aussi fréquemment pourquoi « le commit existe bien sur le serveur distant, mais la CI ne le trouve pas ». L’objet de sous-module enregistré par le dépôt principal peut être antérieur à la limite actuelle de l’historique superficiel, ou n’être accessible que par l’historique d’une branche supprimée.

Scénario Stratégie recommandée Traitement en cas d’échec
Commit du sous-module proche de la tête de branche Utiliser une extraction à profondeur limitée Augmenter la profondeur, puis réessayer
Étendue historique non prévisible Extraire intégralement le sous-module Ne pas tenter de deviner une profondeur fixe
Sous-modules imbriqués sur plusieurs niveaux Vérifier les commits niveau par niveau Afficher le chemin en échec et l’ID de l’objet
Objet distant devenu inaccessible Corriger les références du dépôt Ne pas masquer le problème en changeant de branche

Si le clone superficiel doit être conservé, commencez par essayer :

git submodule update --init --recursive --depth 50

En cas d’échec, ne remplacez pas directement cette commande par --remote. Accédez au chemin signalé, exécutez git fetch --deepen=100, puis vérifiez l’objet avec git cat-file -e <commit>^{commit}. S’il reste introuvable, il faut contrôler la conservation de l’objet sur le serveur distant ou le commit enregistré par le dépôt principal, plutôt que d’ajouter des tentatives aléatoires.

Isoler les identifiants des sous-modules privés

Les sous-modules peuvent relever de périmètres d’autorisation différents. La méthode la plus sûre consiste à créer une configuration Git temporaire pour chaque tâche, à ne la transmettre que pendant la durée de vie du processus et à ne pas modifier la configuration globale du nœud.

git_config="$(mktemp)"
chmod 600 "$git_config"
trap 'rm -f "$git_config"' EXIT

git config --file "$git_config" credential.helper ""
git config --file "$git_config" protocol.version 2

GIT_CONFIG_GLOBAL="$git_config" \
git submodule update --init --recursive

Les données d’authentification doivent être fournies par des variables protégées de la CI ou par des fichiers temporaires. Elles ne doivent jamais être inscrites dans .gitmodules, les adresses distantes, les arguments de commande ou les journaux de build. À la fin de la tâche, vérifiez au minimum :

git config --global --list
git remote -v
git submodule foreach --recursive 'git remote -v'

Les sorties doivent être expurgées avant leur consultation. Si des identifiants ont été intégrés à une URL, supprimer le fichier temporaire ne suffit pas : il faut également restaurer les adresses distantes et vérifier l’historique du shell, les paquets de diagnostic et les répertoires de cache.

Transformer les erreurs en éléments de diagnostic exploitables

Un échec de sous-module ne doit pas se résumer à la mention « code de sortie 128 ». Enregistrez le commit du dépôt principal, le chemin en échec, l’objet attendu, l’objet réel, le nom d’hôte résolu, la profondeur d’extraction et l’étape concernée, sans consigner le nom d’utilisateur, le jeton ni l’adresse d’authentification complète.

L’ordre de diagnostic peut être standardisé comme suit :

  1. Utilisez git ls-tree HEAD <path> pour confirmer l’objet attendu par le dépôt principal.
  2. Vérifiez l’adresse finale à partir de .gitmodules et de la configuration Git locale.
  3. Utilisez git cat-file -e pour déterminer si l’objet existe déjà localement.
  4. Validez les droits d’accès au serveur distant avec une authentification en lecture seule.
  5. Contrôlez la limite du clone superficiel et l’état des sous-modules imbriqués.
  6. Nettoyez le répertoire de travail, puis relancez le même script d’extraction.

Enfin, archivez la sortie de git submodule status --recursive avec les métadonnées du build. Elle est peu volumineuse, mais permet d’identifier précisément les commits de dépendances utilisés. Les tâches exécutées sur les nœuds SDKMac doivent suivre le même principe : figer d’abord les sources en entrée, puis lancer Xcode ou une autre chaîne d’outils, afin de maintenir une séparation nette entre les erreurs d’extraction et les erreurs de compilation.

Questions fréquentes

Pourquoi un commit de Submodule figé peut-il rester introuvable ?

Le dépôt parent ne conserve que l’identifiant de l’objet. Il faut aussi vérifier l’URL, les droits, la profondeur du clone et la présence de cet objet dans le dépôt distant.

Faut-il utiliser git submodule update --remote dans la CI ?

Non pour une construction reproductible. Cette option suit une branche configurée et peut sélectionner des dépendances différentes pour un même commit parent selon la date d’exécution.

Comment supprimer les identifiants des Submodule après la tâche ?

Injectez-les via une configuration temporaire propre à la tâche, supprimez celle-ci avec trap, puis contrôlez la configuration Git, les URL distantes et les journaux.

Nœud physique dédié

Choisissez un Mac cloud pour vos builds continus

Comparez les configurations, les régions et les quatre cycles de facturation de SDKMac M4 et SDKMac M4 Pro, puis créez votre commande.

Choisir une formule de location