Empaqueter les artefacts Mac sans perdre leurs métadonnées

DevOps et CI/CD ·~7 min de lecture

Empaqueter les artefacts Mac sans perdre leurs métadonnées

Un build peut se terminer correctement sur un Mac dans le cloud, puis présenter des problèmes une fois l’artefact téléchargé sur la machine de livraison : scripts devenus non exécutables, liens symboliques transformés en fichiers ordinaires ou échec de la validation d’un paquet d’application. La cause se situe souvent non pas dans la compilation, mais dans la dernière opération de compression et d’extraction. Outre leur contenu et leur chemin, les fichiers macOS peuvent porter des attributs étendus, des listes de contrôle d’accès, des fourchettes de ressources et des relations de liens symboliques. Si le processus de livraison vérifie uniquement que « l’archive s’ouvre », ces altérations risquent d’atteindre l’étape de publication.

Définir d’abord ce qui doit être préservé

Ne compressez pas directement l’ensemble de l’espace de travail. Créez d’abord un répertoire de préparation contenant uniquement les éléments à livrer, puis définissez explicitement les métadonnées qui font partie du contrat de l’artefact.

Les éléments habituels comprennent les paquets d’application, les frameworks, les bibliothèques dynamiques, les outils en ligne de commande, les modèles de configuration et les fichiers de symboles. Les répertoires de cache, les fichiers temporaires de test, les répertoires du système de gestion de versions et les journaux locaux ne doivent pas y être inclus. Le répertoire de préparation évite également que les tâches de build continuent à modifier le répertoire source pendant l’empaquetage.

set -euo pipefail

ROOT="$PWD"
SOURCE="$ROOT/build/Release"
STAGE="$ROOT/artifacts/stage"
ARCHIVE="$ROOT/artifacts/release.zip"

rm -rf "$STAGE"
mkdir -p "$STAGE"
ditto "$SOURCE/MyProduct.app" "$STAGE/MyProduct.app"
ditto "$SOURCE/MyFramework.framework" "$STAGE/MyFramework.framework"

Le premier contrôle doit relever le nombre de fichiers ordinaires, de répertoires et de liens symboliques. Ces derniers sont particulièrement susceptibles d’être déréférencés lors d’un passage entre différents outils, ce qui peut modifier la taille de l’archive ou invalider des chemins relatifs.

find "$STAGE" -type f | wc -l
find "$STAGE" -type d | wc -l
find "$STAGE" -type l -print

Inventorier les métadonnées macOS avant l’empaquetage

Utilisez ls -le@ pour examiner les permissions, les ACL et les attributs étendus, puis xattr pour afficher les attributs des éléments importants. Ne considérez pas automatiquement tout attribut inconnu comme superflu.

ls -le@ "$STAGE"
xattr -lr "$STAGE" > "$ROOT/artifacts/xattr-before.txt"
find "$STAGE" -type l -exec sh -c '
  for item do
    printf "%s -> %s\n" "$item" "$(readlink "$item")"
  done
' sh {} + > "$ROOT/artifacts/symlinks-before.txt"

N’utilisez pas xattr -cr comme solution universelle. Un nettoyage récursif peut faire disparaître temporairement certaines erreurs visibles, mais il supprime aussi des informations utiles au diagnostic. Il faut d’abord établir un inventaire et identifier l’origine des attributs, puis limiter toute suppression aux attributs dont l’inutilité a été confirmée.

Vérifiez également les bits de permission des fichiers exécutables. Un script exécutable dans le dépôt ne le reste pas nécessairement après sa copie dans le répertoire de préparation. Vous pouvez établir une référence avec find "$STAGE" -type f -perm -111. Lorsqu’un script doit impérativement être exécutable, inscrivez cette exigence dans le script de validation au lieu de compter sur la mémoire d’un opérateur.

Créer une archive livrable avec ditto

ditto est fourni avec macOS et convient au traitement des paquets d’application et des répertoires de frameworks. Lors de la création du fichier ZIP, conservez les ressources et les attributs étendus ainsi que le niveau du répertoire de préparation, afin de fournir au destinataire une structure racine stable.

rm -f "$ARCHIVE"
ditto -c -k \
  --sequesterRsrc \
  --keepParent \
  "$STAGE" \
  "$ARCHIVE"

shasum -a 256 "$ARCHIVE" > "$ARCHIVE.sha256"
du -h "$ARCHIVE"

Le nom de l’archive et celui du répertoire racine qu’elle contient doivent rester fixes. N’intégrez pas d’horodatage, de nom de machine ou de chemin absolu vers un espace de travail temporaire dans les noms de répertoires : les scripts en aval seraient alors obligés de deviner le point d’entrée. Si le pipeline requiert un identifiant unique, placez le numéro de commit dans l’enregistrement externe de l’artefact plutôt que de modifier l’organisation interne de l’archive.

Pour les fichiers provenant de sources non contrôlées, recherchez également les sauts de ligne, les caractères invisibles et les combinaisons Unicode anormales dans les noms avant de les copier dans le répertoire de préparation. Les espaces ordinaires peuvent être gérés sans risque, mais les scripts doivent toujours placer les variables entre guillemets doubles afin d’empêcher le Shell de découper les chemins.

Extraire dans un répertoire indépendant et comparer

La validation ne doit pas extraire l’archive par-dessus le répertoire d’origine, car d’anciens fichiers pourraient masquer des éléments manquants. Utilisez systématiquement un répertoire neuf et confiez l’extraction à ditto.

VERIFY="$ROOT/artifacts/verify"

rm -rf "$VERIFY"
mkdir -p "$VERIFY"
ditto -x -k "$ARCHIVE" "$VERIFY"

test -d "$VERIFY/stage/MyProduct.app"
test -d "$VERIFY/stage/MyFramework.framework"

find "$VERIFY/stage" -type l -exec sh -c '
  for item do
    printf "%s -> %s\n" "$item" "$(readlink "$item")"
  done
' sh {} + > "$ROOT/artifacts/symlinks-after.txt"

diff -u \
  "$ROOT/artifacts/symlinks-before.txt" \
  "$ROOT/artifacts/symlinks-after.txt"

Pour vérifier le contenu des fichiers, générez un manifeste distinct avant l’empaquetage et après l’extraction. Les chemins pouvant contenir des espaces, évitez les boucles dont les variables ne sont pas correctement protégées. Le fragment Python ci-dessous trie les éléments par chemin relatif et calcule le SHA-256 de chaque fichier ordinaire.

from hashlib import sha256
from pathlib import Path
import sys

root = Path(sys.argv[1])

for path in sorted(root.rglob("*"), key=lambda p: str(p.relative_to(root))):
    if path.is_file() and not path.is_symlink():
        digest = sha256(path.read_bytes()).hexdigest()
        print(digest, path.relative_to(root))

Exécutez-le séparément sur stage et verify/stage, puis comparez les sorties avec diff. Cette méthode permet de détecter à la fois les modifications de contenu, les fichiers manquants et les ajouts inattendus.

Transformer les contrôles de structure et de signature en barrière de validation

Si l’artefact contient un paquet d’application ou un framework signé, vérifiez de nouveau sa signature après l’extraction. Une validation effectuée uniquement avant la compression ne suffit pas, car l’outil d’archivage peut lui-même introduire le problème.

codesign --verify --deep --strict \
  "$VERIFY/stage/MyProduct.app"

codesign -d --verbose=4 \
  "$VERIFY/stage/MyProduct.app" 2>&1 |
  sed -n '1,20p'

L’option --deep convient à un contrôle final d’intégrité, mais ne remplace pas un diagnostic couche par couche. En cas d’échec, déterminez d’abord si le problème concerne l’application principale, un framework intégré ou un exécutable auxiliaire, puis lancez codesign --verify --strict --verbose=4 sur le chemin précis. Comparez également les sorties de xattr obtenues avant et après l’empaquetage afin de confirmer si les changements proviennent de l’étape d’archivage.

Il est recommandé de définir les conditions d’échec suivantes : répertoire racine différent, manifestes de fichiers divergents, cible d’un lien symbolique modifiée, perte d’un bit d’exécution nécessaire, anomalie touchant un attribut étendu essentiel ou échec de la vérification de signature. La publication doit être interrompue dès qu’une seule de ces conditions est remplie, plutôt que de recompresser l’artefact en espérant un résultat différent.

Erreurs fréquentes et liste de contrôle de livraison

L’erreur la plus courante consiste à combiner plusieurs outils : un script crée d’abord un ZIP ordinaire, puis le programme de téléversement l’extrait et le recompresse automatiquement. Plus la chaîne est longue, plus la sémantique des métadonnées devient difficile à suivre. Désignez un seul outil d’empaquetage et transmettez directement l’archive d’origine au stockage d’artefacts.

La deuxième erreur consiste à empaqueter pendant le build. Tant que le compilateur écrit encore des fichiers, le manifeste et l’archive peuvent refléter deux instants différents. Attendez que le processus de build se termine avec succès, copiez ses résultats dans un répertoire de préparation en lecture seule, puis commencez l’archivage.

La troisième erreur consiste à effectuer les tests uniquement sur la même machine. Supprimez au minimum l’ancien répertoire de validation, extrayez de nouveau l’archive et lancez les contrôles à partir des chemins qu’elle contient. Lorsque c’est possible, confiez ensuite l’exécution des mêmes scripts à une tâche indépendante. Dans les pipelines de longue durée sur BerryVM, les fichiers d’inventaire, le hachage de l’archive et les journaux de validation peuvent être conservés comme pièces du même dossier de livraison. Les journaux ne doivent toutefois contenir ni identifiants secrets ni variables d’environnement non expurgées.

La liste de contrôle finale doit rester concise : le répertoire de préparation est figé, les liens symboliques sont consignés, les attributs étendus sont inventoriés, le hachage de l’archive est généré, l’extraction indépendante est terminée, les manifestes de fichiers correspondent, les bits d’exécution sont conformes aux attentes et les contrôles de signature applicables ont réussi. Ainsi, l’empaquetage n’est plus une opération improvisée après le build, mais une véritable barrière d’ingénierie, reproductible et capable d’échouer de manière précise.

Questions fréquentes

Pourquoi zip -r ne suffit-il pas toujours pour un artefact macOS ?

Un flux ZIP générique peut perdre des branches de ressources, des attributs étendus, des ACL ou la sémantique des liens symboliques. ditto convient mieux aux bundles et frameworks.

Faut-il exécuter xattr -cr avant chaque empaquetage ?

Non. Il faut d’abord inventorier les attributs et comprendre leur rôle. Une suppression récursive peut effacer des informations utiles à la livraison ou au diagnostic.

Le SHA-256 de l’archive suffit-il pour valider la livraison ?

Non. Il prouve seulement que l’archive n’a pas changé. Il faut aussi extraire dans un dossier distinct et vérifier structure, liens, attributs et signature lorsque celle-ci s’applique.

Mac physique dédié

Confiez votre prochaine tâche de développement à un Mac dans le cloud

Choisissez Berry M4 ou Berry M4 Pro, vérifiez les cinq nœuds, la durée de location et les options supplémentaires, puis passez commande.

Choisir une configuration et commander