Gestion de dépendances et conflits — CUDF 1/3
Nous allons détailler au cours d’une série de trois articles la problématique des gestions de dépendances et de la résolution de conflits. Nous commençons par la base: la norme qui va permettre une meilleure gestion des dépendances et conflits.
La gestion de dépendences et conflits
Le développement logiciel commence, maintenant, toujours par une phase de sélection de briques logicielles fournissant une ou plusieurs des fonctionnalités requises par le produit que l’on souhaite développer.
Une fois la sélection effectuée, on doit alors voir si toutes les briques choisies forment un mur qui tient la route ou qui s’écroule. C’est à ce moment là que peuvent apparaître les conflits entre dépendences.
Il existe des outils permettant d’effectuer cette gestion pour Java (Maven ou Ivy), pour Debian (aptitude par exemple) et ainsi de suite. Cependant, il n’existe pas de gestionnaire commun pour tous les environnements. Ce que nous souhaitons c’est avoir un outil unique permettant de définir des méta-données communes à tous ces environnements.
Nous pensons que la norme CUDF peut remplir ce rôle.
CUDF
Définition
CUDF est l’acronyme de Commons Upgradeability Description Format. Il s’agit d’un format de fichier permettant la description d’une installation, mise à jour ou désinstallation d’un ou plusieurs modules informatiques.
Pour l’idée de départ, il s’agit de gérer de manière globale les dépendances d’une unité binaire. Ce principe est utilisé pour le système de paquets de Debian mais également pour les plugins d’Eclipse. Avec une gestion globale, on s’assure d’avoir la même résolution à Paris et à Tokyo mais également entre votre machine Windows et le client sous MacOS.
Un document CUDF permet donc de décrire l’ensemble des binaires (les dépendances) nécessaires au bon fonctionnement d’un projet, ainsi que les possibles interactions entre tous ces binaires.
Cette norme a été créée par Ralf Treinen et Stefano Zacchiroli pour le projet Mancoosi.
Le format
Le fichier CUDF est un fichier en texte brut. On peut le rapprocher d’un fichier “properties” dans le sens où il est globalement sous la forme clé: valeur.
La structure du fichier est composée de deux parties:
- le préambule
- la contenu CUDF
Le préambule
Le préambule spécifie les informations globales concernant le document CUDF. Il est optionnel, mais peut être utilisé pour définir de nouveaux types de propriétés ou déclarer le checksum de l’univers.
Voici un exemple :
preamble: property: state : enum [ stable , testing , unstable ] = [ stable ] , bugs : int = [0], description : string = ["no description"] univ-checksum: 8c6d8b4d0cf7027cd523ad095d6408b4901ac31c
Ainsi nous avons la possibilité de rajouter dans la description d’un paquet :
- sa description sous forme d’une chaîne de caractères
- son état qui peut prendre une valeur entre stable, testing et unstable
- un nombre de bugs connu sur ce paquet
Les éléments qui sont alors définis pour un fichier CUDF particulier ne sont pas utilisés par défaut pour effectuer une analyse ou gestion des dépendances ; mais cela peut permettre de transporter des informations supplémentaires qui seront alors utilisées par votre outil.
La description d’unité logicielle
Le contenu CUDF peut être composé de trois parties distinctes :
- Un univers qui décrit tous les paquets connus par le gestionnaire de paquets.
- Une liste de tous les paquets installés. Cette liste de statuts est contenue dans l’univers.
- Une requête de l’utilisateur représentant les changements qu’il a demandés au gestionnaire de paquets.
La description d’un _package_ est faite de la sorte :
- Le nom du paquet : package (obligatoire et doit être la première propriété)
- La version : version (obligatoire)
- S’il est installé : installed
- Les dépendances du paquet : depends
- Les paquets qui produisent des conflits avec le paquet courant : conflicts
- Les fonctionnalités que le paquet peut proposer : provides
Voici un exemple de description d’unité logicielle :
package: bash version: 1 provides: bash package: bash version: 2 conflicts: bash < 2 provides: bash package: zsh version: 1 conflicts: bash = 1 provides: zsh
Version
Il faut faire attention à une chose ici : la version ne correspond pas à la version marketing de votre unité logicielle (ie 3.01.402-RC1). Il s’agit ici d’une version permettant à CUDF de trouver les liens entre les unités logicielles (dépendences, conflits..).
Le problème n’est pas simplement la notation, c’est surtout pour la gestion de range de version qui peut arriver si l’unité A n’est plus compatible à partir de la version 1.30-RC1-beta de l’unité B. De plus, les montées de version peuvent être différentes entre debian, java, .net… Et comme CUDF se veut compatible avec toutes les technologies, il faut prendre en compte que dans un cas la 1.0-RC1 est avant la 1.0 alors que ce n’est pas le schéma pour d’autres technologies.
Provides
Le point qui est intéressant, c’est la description d’une fonctionnalité apportée par une unité. Ainsi, si on se trouve dans le cas suivant :
- A, en version 1, qui offre la fonctionnalité f1, est installé
- B, en version 1, qui offre la fonctionnalité f1 en conflit avec A version 1, est disponible
- C, en version 1, dépend de B, doit être installé
- D, en version 1, nécessitant la fonctionnalité f1, doit être installé
CUDF nous permet de déterminer que A doit être désinstallé car C a besoin de B, et D sera satisfait car D aura la fonctionnalité f1 via B. Voici un schéma représentatif de ce cas:
La requête
La requête décrit les changements voulus de l’utilisateur par rapport aux paquets installés listés juste avant. Elle est composée de plusieurs propriétés :
- install
- remove
- upgrade
Les propriétés install, remove, et upgrade sont très similaires. Elles spécifient respectivement au solveur les différents paquets à installer, supprimer, ou mettre à jour. Dans tous les cas la version peut être spécifiée. Par contre la mise à jour pose quelques contraintes. Tous les paquets qui sont mis à jour doivent exister au préalable et ne peuvent être remplacés par une version antérieure.
Voici un exemple de document CUDF :
preamble: property: suite : enum [ stable , testing , unstable ] = [ stable ] , bugs : int = [0] , installed-size : posint , description : string = ["no description"] univ-checksum: 8c6d8b4d0cf7027cd523ad095d6408b4901ac31c package: m4 version: 3 installed: true depends: libc6 >= 8 package: openssl version: 11 installed: true depends: libc6 >= 18, libssl0.9.8 >= 8, zlib1g >= 1 conflicts: ssleay < 1 package: wesnoth version: 1 depends: libc6 >= 8, libfreetype6 >= 4, libfribidi0 >= 1, libgcc1 >= 6, libsdl-image1.2 >= 2, libsdl-mixer1.2 >= 1, libsdl-net1.2, libsdl1.2debian >= 3, libstdc++6 >= 5, libx11-6 , zlib1g >= 5, wesnoth-data = 1 package: postfix version: 2 installed: true provides: mail-transport-agent conflicts: mail-transport-agent package: exim version: 3 provides: mail-transport-agent conflicts: mail-transport-agent request: install: exim, ssleay < 1 upgrade: libsdl1.2debian
Conclusion
Cette nouvelle norme apporte un grand nombre d’opportunités dans le monde de la résolution de dépendances. Car si l’article se concentre plutôt sur des exemples de paquets Linux, cette norme a un tel niveau d’abstraction qu’elle peut être utilisée avec n’importe quel gestionnaire de dépendances.
Nous avons donc, dans le cadre du projet DORM, implémenté dans Ivy et Archiva de nouveaux modules afin de réaliser le support de cette norme. Ces implémentations seront décrites dans plusieurs articles à venir.