Semantic Versioning : structurez vos versions pour faciliter les mises à jour
Le Semantic Versioning est la convention qui permet à Composer et à npm de décider seuls si une mise à jour peut s'installer sans casser votre application.
C'est un standard aujourd'hui incontournable pour organiser la gestion de dépendances.
2.0.0Version de la spécificationPubliée en juin 2013, et inchangée depuis.
1.0.0Première version numérotéePubliée en 2011 par Tom Preston-Werner, cofondateur de GitHub.
13 ansSans révisionUne convention stabilisée : contrairement à un référentiel de sécurité, ce qu'elle décrit ne bouge pas avec le temps.
Un numéro de version n'a de valeur que s'il engage quelqu'un
Pourquoi cette convention existe, et où nous la tenons.
Avant les gestionnaires de paquets, mettre à jour une librairie voulait dire lire son code pour deviner ce qui avait changé. À mesure qu'un projet accumulait des dépendances qui dépendaient elles-mêmes les unes des autres, plus personne ne savait quelle combinaison de versions fonctionnait ensemble. C'est le problème que la convention a résolu, en donnant à chaque chiffre une signification que les machines peuvent lire.
Ce qui en découle est passé dans le quotidien de tous les projets : une commande de mise à jour installe seule les correctifs de sécurité disponibles, sans qu'un développeur ait à vérifier une par une les dizaines de librairies concernées. Cela ne fonctionne que parce que le numéro engage l'éditeur sur ce qu'il a le droit de casser.
Notre ligne tient en deux phrases. Nous appliquons la convention là où d'autres projets consomment notre code, parce qu'elle est alors le seul contrat qu'ils lisent. Et nous ne faisons jamais confiance au numéro seul, parce qu'une convention déclarative ne remplace pas une chaîne de tests.
LA CONVENTION
Majeure, mineure, correctif : ce que dit un numéro de version
La quasi-totalité des langages et frameworks web suivent le Semantic Versioning. Trois chiffres, et un impact précis sur votre code pour chacun.
7
Majeure
.
2
Mineure
.
1
Correctif
Version majeure
Changements incompatibles avec la version précédente. C'est le seul endroit où un
BC break est autorisé : certains appels de votre code devront être adaptés
avant de déployer.
Version mineure
Nouvelles fonctionnalités ajoutées sans casser l'existant.
Rétrocompatible : aucune adaptation de code prévue, et c'est ici que s'annoncent
les dépréciations à venir.
Correctif
Corrections de bugs et de failles de sécurité uniquement, aucune fonctionnalité
nouvelle. C'est le vecteur principal des correctifs de sécurité :
à appliquer sans attendre.
0.4.2 : la convention se
met en pause
Tant que le premier chiffre vaut zéro, la spécification autorise explicitement n'importe
quel changement, à n'importe quel moment. Une mise à jour de
0.4.2 vers
0.5.0 peut donc tout casser sans
rien violer. Le numéro ne promet plus rien : c'est au projet qui l'installe de lire ce que
la version change.
7.3.0-rc.1 : une version
qui n'est pas encore sortie
Le suffixe après le tiret désigne une pré-version : bêta, release candidate. Elle
précède la version finale du même numéro et n'est jamais installée par défaut. Elle sert
à tester une majeure à venir sur une copie du projet, ce qui est exactement le premier
geste d'une migration bien préparée.
LA CONFUSION LA PLUS COURANTE
La version ne précise pas la durée de support
Une version plus récente n'est pas forcément une version plus sûre et plus maintenue.
Le Semantic Versioning décrit une compatibilité, pas un calendrier. Il dit qu'une mineure n'a pas le droit de casser votre code. Il ne dit rien de la durée pendant laquelle cette version continuera de recevoir des correctifs de sécurité, ni de la date à laquelle elle cessera d'en recevoir.
Cette seconde question relève de la politique de publication de l'éditeur, écrite dans sa documentation officielle. PHP publie son calendrier de support, Symfony le sien, avec des versions LTS maintenues plus longtemps que les autres. Ces dates n'ont aucun rapport avec le numéro : une version peut être la plus récente publiée et sortir du support dans six mois.
La conséquence pratique est directe. Un projet à jour au sens de la convention, sans aucune dépendance en retard de majeure, peut parfaitement tourner sur des versions dont plus aucune ne reçoit de correctif.
C'est pour cela que nous avons développé notre propre outil de suivi des versions : il confronte les versions réellement installées aux calendriers publiés par les éditeurs, et présente au client un calendrier de support clair, où l'on voit d'un seul coup d'oeil ce qui est déjà derrière soi et ce qui court encore.
BC break : autorisé en version majeure mais interdit partout ailleurs !
BC break signifie Backward Compatibility break ou rupture de compatibilité en français. C'est le cœur de la convention : tout le reste en découle.
Un BC break est une modification qui rend incompatible du code qui fonctionnait
jusque-là : une méthode renommée, un argument devenu obligatoire, une valeur de retour
qui change de nature. La règle de la convention est stricte, et c'est ce qui la rend
utile : ces changements ne sont autorisés qu'en version majeure. Une
mineure ou un correctif qui casse la compatibilité viole le standard.
Toutes les ruptures ne se voient pas au premier lancement. Une méthode supprimée
provoque une erreur immédiate, facile à repérer. Un comportement modifié à signature
identique, un arrondi, un tri, une valeur par défaut, ne fait rien planter du tout :
l'application continue de tourner et produit des résultats différents. C'est le cas le
plus coûteux, et celui que seule une chaîne de tests automatisés peu détecter.
La convention ne se contente pas d'interdire la rupture ailleurs qu'en majeure : elle
organise son annonce. C'est le mécanisme des dépréciations, et c'est
lui qui transforme une migration en travail planifiable.
Ce qu'un BC break donne sur un projet réel
Une méthode renommée : le code s'arrête, l'erreur est immédiate et localisée
Un argument devenu obligatoire : les appels incomplets échouent au premier passage
Une interface qui change de signature : tout ce qui l'implémente doit suivre
Un comportement modifié sans changement de signature : rien ne plante, les résultats changent
Une option de configuration supprimée : le service ne démarre plus, souvent en production
LE MÉCANISME DES DÉPRÉCIATIONS
Comment annoncer une suppression de code, avant de la déployer
Une API ne disparaît jamais du jour au lendemain chez un éditeur sérieux. La convention impose de la déprécier d'abord pour laisser le temps de s'organiser.
1. L'annonce, en version mineure
L'éditeur marque une méthode ou une option @deprecated et publie une version mineure. Rien ne casse : le code existant continue de fonctionner exactement comme avant.
2. L'avertissement, à l'exécution
Le framework signale chaque usage déprécié dans les logs de développement. C'est la liste de travail : elle dit précisément quels fichiers devront être adaptés.
3. L'adaptation, au fil de l'eau
Le code se met à jour pendant que l'ancienne API fonctionne encore, en parallèle des développements métier. C'est la fenêtre que la convention offre, et elle dure des mois.
4. La suppression, en version majeure
L'ancienne API disparaît. Un projet qui a suivi les trois étapes précédentes n'a plus rien à faire ; un projet qui les a ignorées découvre tout le travail d'un coup.
Ce que la spécification demande, littéralement
Sur la question de la dépréciation, la spécification est explicite : avant de supprimer
une fonctionnalité dans une version majeure, l'éditeur doit publier au moins une version mineure qui contient la dépréciation, afin que les projets qui l'utilisent aient le temps de basculer.
C'est ce qui rend une migration majeure planifiable des mois à l'avance. Les
avertissements de dépréciation remontés par le framework en développement ne sont pas du
bruit : ils sont la liste de travail de la prochaine migration, disponible avant même que
la version majeure soit publiée. La gestion des dépréciations peut se traiter par
anticipation dans le cadre de la maintenance technique de vos projets.
Nos bonnes pratiques
Comment nous utilisons le versionnement sémantique
SemVer est une convention pour la gestion des dépendances, elle propose un cadre adapté lorsque l'on veut partager du code.
Chez SmartBooster, nous devons utiliser des dépendances codées par d'autres, gérer nos propres librairies open-source comme interne et gérer les projets de nos clients qui utilisent ses dépendances.
Voilà les bonnes pratiques que nous suivons pour éviter de nous perdre dans de la compléxité inutile.
Nos librairies partagées suivent la convention à la lettre
Un tag par version, un changelog qui l'explique : Nos bundles open source et nos librairies internes, sont tagués version par versionavec un changelog explicatif. C'est ce qui permet aux développeurs de suivre les mises à jour.
Une rupture attend toujours la majeure suivante : Ce qui doit disparaître est d'abord déprécié dans une mineure, supprimé seulement à la majeure. C'est ce qui permet aux projets qui en dépendent d'adapter leur code au fil de l'eau, sans chantier d'urgence.
Les applications client n'ont pas de versions SemVer
Un fichier revision.txt écrit à chaque déploiement : Il porte le hash complet du commit déployé et la date du build, à la racine publique de l'application. Une erreur remontée par la supervision ou une question sur un comportement observé mardi matin se relient au code exact qui tournait.
Un suivi industrialisé : Le fichier est produit par une cible make appelée à chaque déploiement, dans le socle Docker que nous utilisons sur nos projets Symfony : make/deploy.mk. Elle tient en quatre lignes, elle est publique, et c'est tout ce que coûte la traçabilité d'une application déployée en continu.
La traçabilité passe par le ticket, pas par le numéro
Le ticket en tête du message de commit : Nos commits s'écrivent au format [ID du ticket] : description, et la description d'une correction commence par fix. Un commit renvoie donc à la demande qui l'a provoqué, avec son contexte et sa recette.
La nature du travail portée par la branche : fix/ pour une correction, hotfix/ quand le bug est en production et que le correctif ne peut pas attendre. Le détail est sur notre fiche Git.
Nous ne faisons jamais confiance au numéro seul
La convention n'est opposable à personne : Rien ne sanctionne un éditeur qui casse la compatibilité dans une mineure, et les versions 0.x se dispensent de la règle par construction. Toute montée passe donc par la chaîne de tests, puis par l'intégration et la recette.
Plus aucune librairie instable : Nous l'avons appris avant Composer, quand les dépendances s'installaient depuis leurs dépôts Git : la suite de bundles Symfony CMF demandait ses propres correctifs à chaque mise à jour. Depuis, des briques matures et peu nombreuses, dont l'éditeur tient sa convention et publie son calendrier.
OUTILLAGE
semantic-release : le versionnage automatique
semantic-release automatise entièrement la publication : il lit les messages de commit depuis la dernière version,
en déduit s'il faut incrémenter le correctif, la mineure ou la majeure, pose le tag, rédige
le changelog et publie le paquet. Plus aucune décision humaine sur le numéro.
Cela suppose que les messages de commit soient écrits pour être lus par une machine, dans
la convention Conventional Commits.
Il impose une autre convention de commit
L'outil déduit le numéro des messages de commit, qui doivent alors suivre Conventional Commits (feat:, fix:, BREAKING CHANGE:). Nous ne suivons pas cette convention : nos messages relient un commit à un ticket, pas à un incrément de version.
Il publie à chaque fusion
En déploiement continu, cela produit des dizaines de versions par mois. Un changelog que personne ne lit n'est pas une traçabilité, c'est du bruit qu'il faudra trier le jour où on en aura besoin.
Il calcule un numéro sans lecteur
Sur une application développée pour un seul client, aucune machine et aucune équipe extérieure ne consulte ce numéro avant d'installer quoi que ce soit. Le calcul est juste, il ne sert à personne.
Nous avons choisi de ne pas l'utiliser car nous privilégions un historique GIT fortement lié à nos tickets de
gestion de projet avec des messages plus rapidement compréhensibles lors des revues de code et analyses de code
pour retrouver la raison d'une erreur. Pour nous, il n'est simplement pas adapté à notre contexte.
FAQ
Les réponses à vos questions
Et si vous ne trouvez pas ce que vous cherchez, nous serons ravis de vous répondre en direct lors d'un rendez-vous entre humains !
La convention est explicite : avant la 1.0.0, tout peut changer à tout moment, y compris dans une version mineure. Un 0.x n'est donc pas interdit, mais il déplace le risque sur vous : c'est à vous de lire ce que chaque mise à jour change, puisque le numéro ne le dit plus. Nous l'acceptons quand la librairie est petite, remplaçable et isolée derrière notre propre code. Nous le refusons sur une brique structurante, celle qu'on ne peut plus retirer au bout de deux ans.
Oui, et cela arrive. Le Semantic Versioning est une convention déclarative, pas une règle opposable : personne ne vérifie qu'un éditeur la respecte, et rien ne le sanctionne s'il s'en écarte. C'est la raison pour laquelle nous ne mettons jamais à jour une dépendance sans que la chaîne de tests et les environnements d'intégration et de recette aient tourné derrière. Le numéro oriente le travail, il ne le remplace pas.
Un numéro de version sert à informer celui qui va installer le logiciel : il lui dit s'il peut mettre à jour sans risque. Sur une application développée pour un seul client, déployée en continu par nos soins, ce lecteur n'existe pas. Le numéro n'informerait personne et le débat sur le chiffre à incrémenter consommerait du temps sans produire de valeur. Ce qui compte réellement, savoir quel code tourne en production à un instant donné, se règle par le commit déployé.
Chaque déploiement écrit un fichier revision.txt à la racine publique de l'application. Il contient le hash complet du commit déployé et la date du build. Ce fichier est accessible sur l'application elle-même : il suffit de l'ouvrir pour savoir exactement quel code s'exécute. C'est ce qui permet de relier une erreur remontée par la supervision, une question posée un mardi matin ou une demande d'audit à une ligne de code précise.
Non, et c'est la confusion la plus fréquente sur le sujet. La convention décrit ce qu'une mise à jour est autorisée à casser, rien d'autre. La durée pendant laquelle une version reçoit encore des correctifs de sécurité relève de la politique de publication de l'éditeur, écrite dans sa documentation officielle : c'est là que se lisent les dates de fin de support et le rythme des versions LTS, jamais dans le numéro.
Elle est sans adaptation de code prévue, ce qui n'est pas la même chose que sans risque. Une version mineure ajoute des fonctionnalités, corrige des bugs, et peut modifier un comportement à la marge sans que cela soit considéré comme une rupture par l'éditeur. Sur un projet outillé, ces mises à jour s'appliquent en continu et la chaîne de tests confirme que rien n'a bougé. Sur un projet sans tests, la même mise à jour demande une recette manuelle : le coût ne vient pas de la version, il vient de l'absence de filet.