Guide d’ingénierie

Intégrité des artefacts iOS sur un Mac cloud

Intégrité des artefacts iOS sur un Mac cloud

Le pipeline indique une réussite, mais l’IPA téléchargé ne correspond pas au commit qui a déclenché la tâche. Autre scénario, plus dangereux encore : après l’interruption d’un téléversement, une tâche ultérieure récupère un fichier qui n’a été écrit qu’à moitié. Sur un Mac cloud, la compilation, l’exportation et le téléversement sont généralement pris en charge par des scripts distincts. Si le seul critère de livraison est un code de sortie égal à 0, ces deux types de problème ont peu de chances d’être détectés pendant la tâche concernée.

La solution ne consiste pas à ajouter une commande de compression, mais à définir l’artefact comme un ensemble indivisible : l’IPA, le manifeste SHA-256, le contexte de compilation et les résultats de vérification doivent être générés ensemble, puis rendus accessibles uniquement après la réussite de tous les contrôles.

Définir d’abord l’artefact livrable

Chaque tâche utilise un BUILD_ID unique. Le répertoire de livraison doit contenir au minimum les éléments suivants :

Fichier Rôle Critère d’acceptation
App.ipa Artefact d’installation et de distribution Empreinte conforme et commande d’exportation réussie
SHA256SUMS Manifeste d’intégrité du contenu shasum -a 256 -c se termine avec succès
build-context.tsv Traçabilité de l’origine de la compilation Commit, version de Xcode et identifiant de tâche non vides
export.log Diagnostic des échecs d’exportation Sortie complète conservée avec des droits d’accès restreints

Le contexte de compilation doit provenir de variables déjà déterminées par le pipeline, et non d’une branche déduite à la volée dans le script. Il faut au minimum enregistrer le hash complet du commit, l’identifiant de la tâche, le Scheme, la version de Xcode et l’heure de génération. L’horodatage sert uniquement à rapprocher les journaux ; l’identité de l’artefact doit toujours être établie à partir du hash du commit et de la somme de contrôle.

Une empreinte permet de prouver qu’un fichier n’a pas changé entre deux points de contrôle, mais pas qu’il provient initialement d’un processus fiable. La vérification de la signature du code et le contexte de compilation sont tous deux indispensables.

Vérifier l’archive avant l’exportation

Commencez par vérifier que l’archive existe, puis localisez le .app qu’elle contient et contrôlez sa signature de code. N’utilisez pas --deep pour signer à nouveau l’application : il s’agit ici uniquement de vérifier la structure de signature existante. Si l’application comprend des extensions, la commande de vérification doit couvrir le code imbriqué et tout échec doit bloquer le processus.

L’archive, le répertoire d’exportation et le répertoire final doivent se trouver sur le même volume de travail. Le dernier renommage de répertoire pourra ainsi être atomique. Sur un Mac cloud MiniRent, chaque tâche exécutée en parallèle doit également disposer de son propre répertoire racine, afin d’éviter que deux tâches partagent un chemin fixe tel que out/latest.

Un script directement adaptable

set -euo pipefail

: "${BUILD_ID:?BUILD_ID is required}"
: "${GIT_COMMIT:?GIT_COMMIT is required}"
: "${SCHEME:?SCHEME is required}"

ROOT="${ARTIFACT_ROOT:-$PWD/out}"
ARCHIVE="$ROOT/input/App.xcarchive"
EXPORT_OPTIONS="$ROOT/input/ExportOptions.plist"
STAGE="$ROOT/.stage-$BUILD_ID"
FINAL="$ROOT/releases/$BUILD_ID"

rm -rf "$STAGE"
mkdir -p "$STAGE" "$(dirname "$FINAL")"
trap 'rm -rf "$STAGE"' EXIT

test -d "$ARCHIVE"
test -f "$EXPORT_OPTIONS"
test ! -e "$FINAL"

APP_PATH="$(find "$ARCHIVE/Products/Applications" -maxdepth 1 -name '*.app' -print -quit)"
test -n "$APP_PATH"
codesign --verify --deep --strict --verbose=2 "$APP_PATH"

xcodebuild -exportArchive \
  -archivePath "$ARCHIVE" \
  -exportPath "$STAGE/export" \
  -exportOptionsPlist "$EXPORT_OPTIONS" \
  >"$STAGE/export.log" 2>&1

IPA_PATH="$(find "$STAGE/export" -maxdepth 1 -name '*.ipa' -print -quit)"
test -n "$IPA_PATH"
cp "$IPA_PATH" "$STAGE/App.ipa"

DIGEST="$(shasum -a 256 "$STAGE/App.ipa" | awk '{print $1}')"
printf '%s  %s
' "$DIGEST" "App.ipa" >"$STAGE/SHA256SUMS"

XCODE_VERSION="$(xcodebuild -version | paste -sd ' ' -)"
printf 'build_id	%s
commit	%s
scheme	%s
xcode	%s
' \
  "$BUILD_ID" "$GIT_COMMIT" "$SCHEME" "$XCODE_VERSION" \
  >"$STAGE/build-context.tsv"

(
  cd "$STAGE"
  shasum -a 256 -c SHA256SUMS
)

mv "$STAGE" "$FINAL"

(
  cd "$FINAL"
  shasum -a 256 -c SHA256SUMS
)

Le fichier ExportOptions.plist utilisé par le script doit provenir d’une configuration versionnée dans le dépôt. N’inscrivez jamais dans les journaux ou les fichiers de contexte des mots de passe, des clés privées, des jetons temporaires ni l’ensemble complet des variables d’environnement.

Isoler les artefacts incomplets grâce à une publication atomique

Le principe essentiel consiste à terminer toutes les écritures et vérifications dans un répertoire temporaire masqué, puis à exposer le répertoire final au moyen d’une seule commande mv. Les consommateurs analysent uniquement releases/ et n’accèdent pas aux répertoires .stage-* ; ils ne peuvent donc pas récupérer un fichier encore en cours de génération.

Cette garantie ne vaut qu’au sein d’un même système de fichiers. Si le répertoire de publication est monté sur un autre volume, mv peut se réduire à une copie suivie d’une suppression. La bonne méthode consiste alors à créer un répertoire temporaire sur le volume cible, à y copier tous les fichiers, à relancer la vérification des empreintes côté cible, puis à renommer le répertoire au sein de ce même volume. Lorsqu’un stockage objet ou un service d’artefacts ne propose pas de renommage de répertoire, téléversez d’abord les objets sous des clés temporaires contenant l’identifiant de la tâche, vérifiez-les, puis créez un petit marqueur d’achèvement. Les consommateurs doivent contrôler ce marqueur avant de lire les artefacts.

Ne pas écraser latest

Un répertoire latest permet facilement à une nouvelle tentative d’écraser un résultat réussi plus récent. Une approche fiable consiste à conserver les artefacts sous un BUILD_ID immuable, puis à gérer séparément un fichier pointeur contenant uniquement l’identifiant de la tâche cible. Avant de mettre à jour ce pointeur, vérifiez que le répertoire cible a passé tous les contrôles. Un retour arrière ne doit lui aussi modifier que le pointeur, jamais les artefacts historiques.

Faire échouer le processus à la bonne étape

Une erreur de signature doit interrompre le processus avant l’exportation, une erreur d’exportation doit rester confinée au répertoire temporaire et une erreur d’empreinte doit bloquer la publication. Une nouvelle vérification après le téléversement permet quant à elle de détecter les modifications causées par le transfert, le disque ou la sélection du mauvais fichier par un script.

Parmi les erreurs courantes figurent la sélection du « premier fichier » du répertoire d’exportation sans filtrer son extension, le partage d’un même chemin de sortie entre plusieurs Scheme, ou encore la réutilisation, lors d’une nouvelle tentative, du répertoire temporaire laissé par l’exécution précédente. Le script doit nettoyer au démarrage le répertoire de staging propre à la tâche, sans jamais utiliser de caractères génériques trop larges qui pourraient supprimer les répertoires d’autres tâches.

Les journaux doivent eux aussi respecter des limites précises. Conservez l’état de sortie complet et toute la sortie de xcodebuild, mais évitez d’imprimer des données d’environnement sensibles. Avant d’envoyer une demande d’assistance, ne fournissez que la plage temporelle utile et supprimez les jetons du dépôt, les identifiants internes présents dans les chemins des éléments de signature ainsi que les identifiants de connexion.

Liste de contrôle avant livraison

Avant que le pipeline ne soit marqué comme réussi, vérifiez chaque point :

  1. L’App de l’archive a passé une vérification stricte de sa signature de code.
  2. L’IPA a été exporté depuis l’archive de la tâche en cours, sans réutiliser un ancien répertoire.
  3. GIT_COMMIT contient le hash complet du commit et correspond à l’événement déclencheur.
  4. SHA256SUMS a été vérifié une fois dans le répertoire de staging et une fois dans le répertoire final.
  5. Le répertoire final porte l’identifiant unique de la tâche et ne peut pas être écrasé par une nouvelle tentative.
  6. Les consommateurs lisent uniquement les répertoires publiés ou les objets accompagnés d’un marqueur d’achèvement.
  7. Les journaux permettent d’identifier l’étape en échec sans contenir d’identifiants ni le contenu des éléments de signature.

Ce processus n’élimine pas tous les échecs de compilation, mais il sépare deux questions vérifiables : « la compilation a-t-elle réussi ? » et « l’artefact livré est-il digne de confiance ? ». Une fois les limites de l’archivage, de la vérification, du contexte et de la publication clairement établies, les nouvelles tentatives et les retours arrière portent sur des objets bien définis, sans dépendre d’un répertoire partagé en perpétuelle évolution.

Questions fréquentes

Une empreinte SHA-256 prouve-t-elle qu’un artefact iOS vient d’une machine fiable ?

Non. Elle prouve seulement que le fichier n’a pas changé entre deux contrôles. Il faut aussi vérifier la signature du code et conserver le commit, la version de Xcode, l’identifiant du job et le manifeste.

Pourquoi publier depuis un répertoire temporaire avant de le renommer ?

Sur un même système de fichiers, le renommage rend visible le répertoire complet en une seule opération. Entre deux volumes, copiez d’abord, refaites la vérification, puis renommez dans le volume de destination.

Mac cloud dédié

Exécutez vos builds sur un nœud physique dédié

Choisissez MiniRents M4 ou MiniRents M4 Pro à la journée, à la semaine, au mois ou au trimestre, puis sélectionnez un nœud selon votre équipe et l’emplacement de votre dépôt de code. Chaque appareil est une machine physique dédiée, et non une machine virtuelle.

Choisir un modèle et commander