Du code au build : pourquoi votre ordinateur portable n'est pas le bon endroit pour compiler
Vous venez de terminer l'écriture d'une fonctionnalité. Les tests passent sur votre machine. Vous tapez go build ou npm run build, et ça fonctionne. Vous êtes prêt à déployer.
Mais quand vous poussez le même code sur le serveur, le build échoue. Ou pire, il se construit correctement sur votre portable mais plante en production à cause d'une incompatibilité de version de bibliothèque. Le code est identique, mais l'environnement est différent.
C'est à ce moment que la plupart des équipes réalisent que construire un logiciel ne se résume pas à écrire du code. Il s'agit de transformer ce code en quelque chose qui peut s'exécuter de manière fiable n'importe où.
Ce qui se passe réellement quand vous compilez
Le code écrit par des humains n'est pas directement exécutable par un serveur. Un fichier Java avec ses classes et méthodes, un fichier Go avec sa structure de packages, ou un fichier TypeScript avec ses annotations de types — tout cela est conçu pour la lisibilité humaine. Le serveur a besoin de quelque chose de totalement différent.
Le processus de traduction varie selon le langage. Pour Go ou Rust, le compilateur produit un seul fichier binaire. Pour Java, il génère du bytecode qui s'exécute sur la machine virtuelle Java. Pour TypeScript ou le JavaScript moderne, le code est transpilé et souvent minifié en fichiers compacts. Cette étape de traduction s'appelle la compilation.
Mais la compilation n'est qu'une partie de l'histoire. Les applications modernes sont rarement un seul fichier. Elles intègrent des bibliothèques tierces, des fichiers de configuration, des assets CSS, des images, et parfois des templates pour le rendu des vues. Tous ces éléments doivent être rassemblés, vérifiés et organisés dans une structure cohérente. Ce processus s'appelle le build.
Une fois que tout est collecté et compilé, le résultat doit être empaqueté dans quelque chose de portable. Le format d'empaquetage dépend du type d'application :
Le diagramme ci-dessous compare le processus de build sur un ordinateur portable par rapport à un serveur CI, montrant comment les différences d'environnement introduisent des points de défaillance.
- Les applications Java produisent des fichiers JAR ou WAR
- Les applications Go produisent un seul binaire exécutable
- Les applications Node.js produisent un dossier avec toutes les dépendances et assets
- Les applications mobiles produisent des fichiers APK pour Android ou IPA pour iOS
Ce résultat final empaqueté s'appelle un artifact. C'est la version complète et exécutable de votre code.
Pourquoi compiler sur votre ordinateur portable est une mauvaise idée
Il est tentant de compiler sur sa propre machine. C'est rapide, vous avez un contrôle total, et vous pouvez déboguer les problèmes immédiatement. Mais cette approche a un problème fondamental : la reproductibilité.
Votre ordinateur portable a un système d'exploitation spécifique, des versions spécifiques de bibliothèques installées au fil des mois ou des années, et des configurations locales dont vous ne vous souvenez peut-être même pas. Le serveur de build, la machine de votre collègue ou le serveur de production — ils ont tous des environnements différents. Un build qui fonctionne sur votre portable peut échouer partout ailleurs à cause d'une bibliothèque manquante, d'une version de compilateur différente ou d'une variable d'environnement qui n'existe que sur votre machine.
Voici un exemple concret. La même commande go build sur deux machines différentes peut produire des binaires qui se comportent différemment :
# Sur votre portable macOS :
go build -o myapp .
file myapp
# Sortie : myapp: Mach-O 64-bit executable x86_64
ls -lh myapp
# Sortie : -rwxr-xr-x 1 user staff 12M Mar 15 10:23 myapp
# Sur le serveur CI (Linux) :
go build -o myapp .
file myapp
# Sortie : myapp: ELF 64-bit LSB executable, x86-64, dynamically linked
ls -lh myapp
# Sortie : -rwxr-xr-x 1 root root 18M Mar 15 10:23 myapp
Le format binaire diffère (Mach-O vs. ELF), la taille diffère (12 Mo vs. 18 Mo), et l'édition de liens diffère. Si vous aviez compilé sur votre portable et copié le binaire sur un serveur Linux, il ne se serait tout simplement pas exécuté. Un serveur CI utilisant le même environnement à chaque fois élimine cette incompatibilité.
C'est pourquoi les systèmes de build automatisés existent. Ils exécutent le processus de build dans un environnement contrôlé et cohérent à chaque fois. Les mêmes étapes, les mêmes outils, les mêmes dépendances. Si le build réussit sur le serveur de build, vous pouvez être confiant qu'il fonctionnera lors du déploiement.
Les builds automatisés détectent également les problèmes tôt. Le système de build vérifie si toutes les bibliothèques requises sont disponibles, s'il y a des erreurs de compilation et si la structure des fichiers est correcte. Si quelque chose ne va pas, le build échoue immédiatement et le développeur reçoit un rapport clair de ce qui doit être corrigé.
Ce qu'est réellement un artifact
Un artifact est le résultat final du processus de build. C'est la chose que vous pouvez prendre et déplacer vers un serveur. Il n'a pas besoin de transformation supplémentaire. Le serveur a juste besoin de le recevoir, de le placer au bon endroit et de l'exécuter.
Pensez-y comme à un kit de repas par rapport à un repas fait maison. Quand vous cuisinez à la maison, vous avez des ingrédients bruts, vous coupez, assaisonnez et cuisez. Le résultat est un plat fini. Un artifact est ce plat fini. Vous n'avez pas besoin de couper quoi que ce soit ou d'ajuster l'assaisonnement. Vous le réchauffez et servez.
L'artifact doit être aussi autonome que possible. Pour une application Go, cela signifie un seul binaire qui inclut tout ce dont il a besoin. Pour une application Java, cela signifie un fichier JAR qui inclut toutes les bibliothèques requises. Pour une application Node.js, cela signifie un dossier avec toutes les dépendances regroupées.
Cette autonomie est cruciale car elle élimine le problème du "ça marche sur ma machine". L'artifact qui a été construit et testé dans l'environnement de build est exactement le même artifact qui est déployé en production. Rien ne change entre le build et le déploiement.
Le pipeline de build en pratique
Un processus de build automatisé typique suit ces étapes :
- Checkout : Le système de build récupère le dernier code depuis le dépôt.
- Résolution des dépendances : Il télécharge toutes les bibliothèques et packages requis.
- Compilation : Il traduit le code source en forme exécutable.
- Tests : Il exécute les tests unitaires et d'intégration sur le code compilé.
- Empaquetage : Il assemble tout dans l'artifact final.
- Stockage de l'artifact : Il sauvegarde l'artifact dans un dépôt central où les systèmes de déploiement peuvent y accéder.
Chaque étape est automatisée et journalisée. Si une étape échoue, l'ensemble du build échoue et l'équipe est notifiée. Pas de builds partiels, pas de correctifs manuels au milieu du processus.
Ce qui peut mal tourner
Même avec des builds automatisés, des choses peuvent casser. Voici les problèmes les plus courants rencontrés par les équipes :
Dépendances manquantes : Une bibliothèque disponible pendant le développement ne l'est pas dans l'environnement de build. Cela se produit généralement lorsque les versions des dépendances ne sont pas figées ou lorsque l'environnement de build n'a pas accès au réseau pour les télécharger.
Code spécifique à l'environnement : Du code qui fonctionne sur macOS mais pas sur Linux. C'est courant avec la gestion des chemins de fichiers, les appels système ou l'utilisation de variables d'environnement.
Incompatibilités de versions d'outils de build : Le serveur de build exécute une version différente du compilateur ou de l'outil de build que celle utilisée localement par les développeurs. Cela peut entraîner des différences subtiles dans la sortie.
Épuisement des ressources : Le processus de build manque de mémoire ou d'espace disque, surtout lors de la construction de grandes applications ou de l'exécution de suites de tests étendues.
Nommage incohérent des artifacts : Des artifacts sans numéros de version ou horodatages rendent impossible de savoir quelle version s'exécute où.
Liste de contrôle pratique pour votre processus de build
Avant de mettre en place votre pipeline de build, parcourez cette liste de contrôle :
- Le build s'exécute-t-il dans un environnement propre à chaque fois ?
- Toutes les versions des dépendances sont-elles explicitement définies et verrouillées ?
- Le build échoue-t-il rapidement en cas d'erreurs de compilation ?
- Les tests sont-ils exécutés dans le cadre du build, et non séparément ?
- L'artifact est-il versionné avec un identifiant unique ?
- L'artifact est-il stocké dans un dépôt central et accessible ?
- Le build peut-il être reproduit à partir de zéro à tout moment ?
L'essentiel à retenir
Compiler du code n'est pas une tâche de confort pour le développeur. C'est le moment où votre code devient un produit déployable. Automatiser ce processus avec un environnement cohérent, des étapes claires et des artifacts versionnés élimine la source la plus courante d'échecs de déploiement : la différence entre ce qui s'exécute sur votre portable et ce qui s'exécute sur le serveur.
Une fois que vous avez un processus de build fiable, la question suivante est de savoir où stocker ces artifacts pour qu'ils soient disponibles lors du déploiement. C'est là que le stockage et la gestion des artifacts entrent en jeu.