Gestion sémantique de version 2.0.0
En bref
Étant donné un numéro de version MAJEUR.MINEUR.CORRECTIF, il faut incrémenter :
le numéro de version MAJEUR quand il y a des changements non rétrocompatibles,
le numéro de version MINEUR quand il y a des changements rétrocompatibles,
le numéro de version de CORRECTIF quand il y a des corrections d’anomalies rétrocompatibles
Des libellés supplémentaires peuvent être ajoutés pour les versions de pré-livraison et pour des méta-données de construction sous forme d’extension du format MAJEURE.MINEURE.CORRECTIF.
Introduction
Dans le monde de la gestion des logiciels, il existe un endroit redouté appelé “l’enfer des dépendances” (de l’anglais “dependency hell”). Plus votre système se développe et plus vous intégrez de composants dans votre logiciel, plus vous êtes susceptible de vous trouver un jour dans cette abîme de désespoir.
Dans les systèmes comportant de nombreuses dépendances, publier une nouvelle version d’un composant peut vite devenir un cauchemar. Si les règles de dépendance sont trop strictes, vous risquez de verrouiller vos versions (incapacité de mettre à jour un composant sans avoir à publier une nouvelle version de chaque composant qui en dépend). Si les règles de dépendances sont trop lâches, vous allez inévitablement subir la promiscuité de version (supposer une compatibilité avec plus de futures versions que raisonnable). L’enfer des dépendances est l’endroit où vous vous trouvez lorsque vous êtes bloqué dans une version et/ou qu’une incompatibilité de version vous empêche d’avancer sans risque dans votre projet.
Comme solution à ce problème, je propose un ensemble de règles et d’exigences simples qui dictent la façon dont les numéros de version sont attribués et incrémentés. Ces règles sont basées mais pas nécessairement limitées à des pratiques très répandues aussi bien dans les domaines du logiciel privé que du logiciel libre. Pour que ce système fonctionne, vous devez d’abord déclarer une API publique. Il peut s’agir d’un document ou de règles imposées par le code lui- même. Quoiqu’il en soit, il est important que cette API soit claire et précise. Une fois prête, vous communiquez ses modifications par des incrémentations successives de son numéro de version. Considérons le format de version X.Y.Z où X, Y et Z identifient la version (Majeure.Mineure.Corrective). Les corrections qui n’affectent pas l’API incrémentent le dernier identifiant qui est l’identifiant de version de correction. Lors d’ajouts ou de modifications rétrocompatibles de l’API, il faut incrémenter l’identifiant de version mineure. Enfin, pour des modifications non rétrocompatibles, il faut incrémenter l’identifiant de version majeure.
J’appelle ce système “gestion sémantique de version”. Avec ce système, les numéros de version, et la façon dont ils changent, donnent du sens au code sous-jacent et à ce qui a été modifié d’une version à l’autre.
Spécification de la gestion sémantique de version (SemVer)
Les mots clés “DOIT”, “NE DOIT PAS”, “OBLIGATOIRE”, “DEVRA”, “NE DEVRA PAS”, “DEVRAIT”, “NE DEVRAIT PAS”, “RECOMMANDÉ”, “PEUT”, et “OPTIONNEL” dans ce document doivent être interprétés comme décrit dans la RFC 2119.
Tout logiciel utilisant la gestion sémantique de version DOIT déclarer une
API publique. Cette
API peut être déclarée dans le code lui-même ou dans un document. Dans tous les cas, elle doit être précise et claire.
Un numéro de version standard DOIT prendre la forme X.Y.Z où X, Y et Z sont des entiers non négatifs et NE DOIVENT PAS être préfixés par des zéros. X représente l’identifiant de version majeure, Y représente l’identifiant de version mineure et Z l’identifiant de version de correction. Chaque élément DOIT s’incrémenter numériquement. Exemple : 1.9.0 → 1.10.0 → 1.11.0.
Une fois qu’un composant est publié, le contenu de sa version NE DOIT PAS être modifié. Toute modification DOIT être publiée dans une nouvelle version.
L’identifiant de version majeure zéro (0.y.z) est destiné au développement initial. Tout ou partie peut être modifié à tout moment. L’API publique ne devrait pas être considérée comme stable.
La version 1.0.0 définit l’API publique. La façon dont le numéro de version est incrémenté après cette publication est dépendante de cette
API publique et de ses évolutions.
L’identifiant de version de correction Z (x.y.Z | x > 0) DOIT être incrémenté si seules des corrections rétrocompatibles sont introduites. Une correction est définie comme un changement interne qui corrige un comportement incorrect.
L’identifiant de version mineure Y (x.Y.z | x > 0) DOIT être incrémenté si de nouvelles fonctionnalités rétrocompatibles sont introduites dans l’API publique. Il DOIT être incrémenté si une fonctionnalité de l’API publique est marquée comme obsolète. Il PEUT être incrémenté si de nouvelles fonctionnalités ou améliorations substantielles sont introduites dans le code privé. Il PEUT inclure dans le même temps des corrections. L’identifiant de version de correction DOIT être remis à zéro lorsque l’identifiant de version mineure est incrémenté.
L’identifiant de version majeur X (X.y.z | X > 0) DOIT être incrémenté si des changements non rétrocompatibles sont introduits dans l’API publique. Cela PEUT inclure dans le même temps des changements mineurs et des corrections. Les identifiants de version mineure et de correction DOIVENT être remis à zéro quand l’identifiant de version majeure est incrémenté.
Une version de pré-livraison PEUT être notée par l’ajout d’un trait d’union et d’une série d’identifiants séparés par des points suivant immédiatement l’identifiant de version de correction. Les identifiants DOIVENT être composés uniquement de caractères alphanumériques
ASCII et de traits d’union [0-9A-Za-z-]. Les identifiants NE DOIVENT PAS être vides. Les identifiants numériques NE DOIVENT PAS être préfixés par des zéros. Les versions de pré-livraison précèdent la version normale associée (version de pré-livraison < version normale). Une version de pré-livraison indique que la version n’est pas stable et ne satisfait pas forcément les exigences de compatibilité associées à une version normale. Exemples : 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92.
Les méta-données de construction PEUVENT être notées par l’ajout d’un signe “plus” et d’une série d’identifiants séparés par des points suivant immédiatement l’identifiant de version de correction ou de pré-livraison. Les identifiants DOIVENT être composés uniquement de caractères alphanumériques
ASCII et de traits d’union [0-9A-Za-z-]. Les identifiants NE DOIVENT PAS être vides. Les méta-données de construction DEVRAIENT être ignorées dans l’ordre des versions. Autrement dit, deux versions qui diffèrent seulement par leurs informations de construction ont la même priorité. Exemples : 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85.
La priorité définit la façon dont sont ordonnées les versions entre elles. La priorité DOIT être calculée en séparant les identifiants de versions entre majeures, mineures, de correction et de pré-livraison, en suivant cet ordre (les informations de construction n’entrent pas en compte dans la comparaison). La priorité est déterminée par la première différence apparaissant dans la comparaison de chacun de ces identifiants dans l’ordre : majeur, mineur et correctif. Ces identifiants sont toujours comparés numériquement. Exemple : 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1 Lorsque ces identifiants sont identiques, une version de pré-livraison est moins prioritaire qu’une version normale. Exemple : 1.0.0-alpha < 1.0.0. La priorité pour deux versions de pré-livraison ayant les mêmes identifiants de version majeure, mineure et de correction DOIT être déterminée en comparant chaque identifiant séparé par un point de la gauche vers la droite jusqu’à ce qu’une différence soit trouvée, comme suit : les identifiants composés uniquement de chiffres sont comparés numériquement et les identifiants contenant des lettres ou des traits d’union sont comparés dans l’ordre
ASCII. Les identifiants numériques sont toujours moins prioritaires que les identifiants non numériques (identifiants numériques < identifiants non-numériques). Un ensemble de champs plus long est prioritaire par rapport à un ensemble de champs plus court si tous les identifiants précédents sont identiques. Exemple : 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.
Pourquoi utiliser la gestion sémantique de version ?
Il ne s’agit pas d’une idée nouvelle ou révolutionnaire. En fait, vous faites probablement déjà quelque chose d’approchant. Et le problème vient du fait que “quelque chose de proche” n’est pas suffisant. Sans conformité avec une méthode formelle, les numéros de versions deviennent inutilisables pour la gestion de vos dépendances. En donnant un nom et une définition claire aux idées exposées ci-dessus, il devient facile de communiquer vos intentions aux utilisateurs de votre logiciel. Une fois que vos intentions sont claires, une spécification souple (mais pas “trop” souple) des dépendances peut être réalisée.
Un exemple simple permet de montrer comment la gestion sémantique de version peut faire de l’enfer des dépendances, une chose du passé. Considérons une bibliothèque appelée “CamionDePompier”. Elle nécessite un composant appelé “Échelle” dont la version est gérée sémantiquement. Lorsque la librairie CamionDePompier est créée, Échelle en est à sa version 3.1.0. Et puisque CamionDePompier utilise des fonctionnalités qui ont été introduites en 3.1.0, vous pouvez spécifier, sans ne courir aucun risque, une dépendance vers Échelle plus grande ou égale à 3.1.0 mais inférieure à 4.0.0. Maintenant, lorsque les versions 3.1.1 et 3.2.0 de Échelle seront disponibles, vous pourrez les publier dans votre système de gestion de dépendances en sachant qu’elles seront compatibles avec les logiciels existants qui en dépendent.
En tant que développeur responsable, bien entendu, vous voudrez vérifier que toute mise à jour de composant fonctionne comme annoncée. Dans la réalité, les choses ne sont pas forcément toujours très cohérentes ; il n’y a donc rien d’autre à faire que de rester vigilant. Ce que vous pouvez cependant faire est de laisser la gestion sémantique de version vous fournir une manière saine de publier et mettre à jour vos composants et ainsi ne pas avoir besoin de déployer de nouvelles versions de vos sous-composants vous permettant ainsi d’économiser du temps et du souci.
Si tout cela vous semble intéressant, tout ce que vous avez à faire pour commencer à utiliser la gestion sémantique de version est de déclarer que vous le faites et d’en suivre les règles. Ajoutez ensuite un lien vers ce site web dans votre README pour que d’autres puissent en connaître les règles et en bénéficier.