Haskell · Cabal · gestion de paquets
Comment ajoute-t-on une dépendance avec Cabal ?
La réponse courte est qu’on n’ajoute pas une dépendance avec une commande, mais avec une ligne dans un fichier. Un paquet Haskell déclare ce dont il a besoin dans son fichier .cabal, sous build-depends, et tout le reste découle de cette déclaration.
Cela mérite d’être dit clairement, parce que les personnes venant de npm ou de Cargo cherchent une commande install qui modifierait un manifeste, puis se demandent pourquoi rien de ce qu’elles ont installé n’est visible par le compilateur.
Le champ qui compte vraiment : build-depends
Ouvrez votre fichier .cabal et trouvez la strophe correspondant à ce que vous construisez : une library, un executable ou une test-suite. Chacune porte son propre build-depends, et c’est le point qui échappe le plus souvent.
library
exposed-modules: MyApp.Core
build-depends: base ^>=4.18
, containers ^>=0.6
, text ^>=2.0
default-language: Haskell2010 Ajouter un paquet consiste à ajouter une ligne ici. La virgule en début de ligne est une convention, pas une obligation : elle existe pour qu’ajouter ou retirer une ligne n’en modifie qu’une seule.
Les dépendances sont propres à chaque strophe. Un paquet dont votre suite de tests a besoin n’a rien à faire dans le build-depends de la bibliothèque, et un paquet nécessaire à votre bibliothèque n’est pas automatiquement disponible pour votre exécutable, sauf si celui-ci dépend de la bibliothèque elle-même. C’est plus strict que dans la plupart des écosystèmes, et c’est délibéré : cela garde l’outillage de test hors de ce que vos utilisateurs doivent compiler.
Ce que signifie vraiment l’opérateur caret
Le ^>= que l’on voit partout n’est pas décoratif. C’est un raccourci lié à la Package Versioning Policy de Haskell, la PVP, et le comprendre supprime l’essentiel des approximations sur les bornes.
Sous la PVP, une version se lit A.B.C.D, où A.B pris ensemble forment la version majeure. Les changements cassants incrémentent A ou B. Les ajouts qui ne peuvent pas casser du code existant incrémentent C. Ainsi ^>=1.2.3 signifie au moins 1.2.3, et en dessous de la majeure suivante, ce qui se développe en >=1.2.3 && <1.3.
La conséquence surprend : en Haskell, passer de 1.2 à 1.3 est un saut majeur, pas mineur. Si vous gardez le modèle mental du versionnage sémantique, où le premier nombre seul est majeur, vous écrirez des bornes bien plus larges que voulu.
Pourquoi cabal update passe en premier
Cabal résout les dépendances à partir d’une copie locale de l’index des paquets Hackage. Si cette copie est périmée, un paquet publié la semaine dernière n’existe tout simplement pas du point de vue de votre machine, et vous obtenez une erreur de résolution qui se lit comme si le nom du paquet était faux.
cabal update
cabal build Lancez cabal update quand un paquet dont vous savez qu’il existe reste introuvable, et après toute longue interruption entre deux sessions. C’est la première chose à essayer, et cela ne coûte rien.
Lire un échec de résolution sans paniquer
Quand le solveur ne peut pas satisfaire toutes les contraintes à la fois, il signale le conflit plutôt que de choisir en silence. Le message est dense, mais il dit quelque chose de précis : deux de vos dépendances veulent des plages incompatibles d’une troisième.
Les options honnêtes sont peu nombreuses, et il vaut mieux les connaître avant d’attraper une option en ligne de commande :
Relâcher une borne que vous avez posée vous-même. Si la borne trop étroite se trouve dans votre propre fichier .cabal, l’élargir est légitime, à condition de compiler et de tester ensuite plutôt que de le supposer.
Choisir une autre version du paquet que vous ajoutez. Souvent, une version plus ancienne de la nouvelle dépendance s’accorde avec les contraintes déjà présentes dans votre projet.
Utiliser --allow-newer en connaissance de cause, et temporairement. Cette option demande au solveur d’ignorer les bornes supérieures déclarées par d’autres paquets. Ces bornes ont été écrites par des mainteneurs qui avaient une raison : traitez-la comme un diagnostic qui montre où est le vrai conflit, pas comme un correctif que l’on commite.
Figer ce que vous avez résolu
La résolution choisit des versions à un instant donné. Pour rendre cet instant reproductible, figez-le :
cabal freeze Cela écrit un fichier cabal.project.freeze consignant les versions exactes retenues. Commitez-le quand vous voulez une compilation qui se comporte pareil le mois prochain, ce qui est l’attente habituelle pour une application. Les bibliothèques relèvent du cas inverse : elles doivent rester souples pour que ceux qui en dépendent puissent résoudre librement.
Pour un projet couvrant plusieurs paquets, ou qui a besoin d’une dépendance source absente de Hackage, cette configuration appartient à cabal.project et non au fichier .cabal. Garder les deux distincts est la façon la plus claire de rester orienté : le fichier .cabal décrit un paquet, cabal.project décrit comment en compiler un ensemble.
En bref
Ajoutez une dépendance en ajoutant une ligne à build-depends, dans la strophe qui en a réellement besoin. Bornez-la avec ^>=, en vous rappelant que sous la PVP les deux premiers nombres forment ensemble la version majeure. Lancez cabal update avant d’accuser un paquet de ne pas exister. Figez les applications, laissez les bibliothèques souples.
Si vous en êtes encore au choix de l’outillage, notre comparaison entre Stack et Cabal traite cette décision, et le guide d’installation GHCup met en place la chaîne d’outils que cet article suppose déjà installée.
Le champ build-depends, le développement de l’opérateur caret et la règle de la majeure A.B suivent le guide utilisateur de Cabal et la Package Versioning Policy Haskell, vérifiés au moment de la rédaction. La disponibilité des commandes varie selon votre version de cabal-install : lancez cabal --version et consultez le guide de votre version avant de vous appuyer sur une sous-commande précise.