coldwa.st
Tutte le guideProgrammazioneWebDatiStrumentiDatabaseHaskellConcettiCabal e buildToolchainCompilatorePrestazioniEditor e HLS

Haskell · Cabal · gestione dei pacchetti

Come si aggiunge una dipendenza in Cabal?

Di ColdwastAggiornato il 4 agosto 20267 min di lettura#haskell#cabal#pacchetti
Alte pile di libri usati che riempiono il corridoio di una libreria
Pile di libri usati ingombrano il corridoio di una libreria, accatastate più in alto degli scaffali sul fondo.

La risposta breve è che una dipendenza non si aggiunge con un comando, ma con una riga in un file. Un pacchetto Haskell dichiara ciò di cui ha bisogno nel suo file .cabal, sotto build-depends, e tutto il resto discende da quella dichiarazione.

Vale la pena dirlo chiaramente, perché chi arriva da npm o da Cargo cerca un comando install che modifichi un manifest, e poi si chiede perché nulla di quanto installato risulti visibile al compilatore.

Il campo che conta davvero: build-depends

Apri il tuo file .cabal e individua la strofa relativa a ciò che stai costruendo: una library, un executable o una test-suite. Ognuna porta il proprio build-depends, ed è il punto che sfugge più spesso.

library
  exposed-modules:  MyApp.Core
  build-depends:    base ^>=4.18
                  , containers ^>=0.6
                  , text ^>=2.0
  default-language: Haskell2010

Aggiungere un pacchetto significa aggiungere una riga qui. La virgola a inizio riga è una convenzione, non un obbligo: serve perché aggiungere o togliere una riga ne tocchi una sola.

Le dipendenze valgono per singola strofa. Un pacchetto che serve alla tua suite di test non appartiene al build-depends della libreria, e un pacchetto che serve alla tua libreria non diventa automaticamente disponibile al tuo eseguibile, a meno che questo non dipenda dalla libreria stessa. È più rigido che nella maggior parte degli ecosistemi, ed è voluto: tiene gli strumenti di test fuori da ciò che i tuoi utenti devono compilare.

Che cosa significa davvero l’operatore caret

Il ^>= che si vede ovunque non è un ornamento. È una scorciatoia legata alla Package Versioning Policy di Haskell, la PVP, e capirla elimina quasi tutte le supposizioni sui vincoli.

Sotto la PVP una versione si legge A.B.C.D, dove A.B presi insieme formano la versione maggiore. I cambiamenti che rompono incrementano A o B. Le aggiunte che non possono rompere il codice esistente incrementano C. Quindi ^>=1.2.3 significa almeno 1.2.3 e sotto la maggiore successiva, che si espande in >=1.2.3 && <1.3.

La conseguenza spiazza: in Haskell, passare da 1.2 a 1.3 è un salto maggiore, non minore. Se porti con te il modello mentale del versionamento semantico, dove solo il primo numero è maggiore, scriverai vincoli molto più larghi di quanto intendessi.

Tavolette di legno identiche montate in piccole strutture, ogni strato appoggiato su quello sottostante
Tavolette di legno identiche montate in piccole strutture, ogni strato appoggiato su quello sottostante.

Perché cabal update viene prima

Cabal risolve le dipendenze rispetto a una copia locale dell’indice dei pacchetti di Hackage. Se quella copia è vecchia, un pacchetto pubblicato la settimana scorsa semplicemente non esiste per la tua macchina, e ottieni un errore di risoluzione che si legge come se il nome del pacchetto fosse sbagliato.

cabal update
cabal build

Esegui cabal update quando un pacchetto che sai esistere non viene trovato, e dopo ogni lunga pausa tra due sessioni. È la prima cosa da provare e non costa nulla.

Leggere un errore di risoluzione senza allarmarsi

Quando il risolutore non riesce a soddisfare tutti i vincoli insieme, segnala il conflitto invece di scegliere in silenzio. Il messaggio è denso, ma dice qualcosa di preciso: due delle tue dipendenze vogliono intervalli incompatibili di una terza.

Le opzioni oneste sono poche, ed è bene conoscerle prima di ricorrere a un flag:

Allentare un vincolo che hai posto tu. Se il vincolo troppo stretto è nel tuo file .cabal, allargarlo è legittimo, a patto di compilare e testare dopo, invece di darlo per scontato.

Scegliere un’altra versione del pacchetto che stai aggiungendo. Spesso una release più vecchia della nuova dipendenza si accorda con i vincoli che il progetto ha già.

Usare --allow-newer con cognizione e in via temporanea. Dice al risolutore di ignorare i limiti superiori dichiarati da altri pacchetti. Quei limiti li hanno scritti manutentori che avevano una ragione, quindi trattalo come una diagnosi che mostra dove sta il conflitto reale, non come una correzione da committare.

Fissare ciò che hai risolto

La risoluzione sceglie le versioni in un dato momento. Per rendere quel momento riproducibile, fissalo:

cabal freeze

Questo scrive un file cabal.project.freeze che registra le versioni esatte scelte. Committalo quando vuoi una build che si comporti allo stesso modo il mese prossimo, che è l’aspettativa consueta per un’applicazione. Le librerie sono il caso opposto: devono restare flessibili perché chi ne dipende possa risolvere liberamente.

Per un progetto che copre più pacchetti, o che ha bisogno di una dipendenza sorgente assente da Hackage, quella configurazione appartiene a cabal.project e non al file .cabal. Tenere distinti i due è il modo più chiaro per orientarsi: il file .cabal descrive un pacchetto, cabal.project descrive come compilarne un insieme.

In breve

Aggiungi una dipendenza inserendo una riga in build-depends, nella strofa che ne ha davvero bisogno. Vincolala con ^>=, ricordando che sotto la PVP i primi due numeri formano insieme la versione maggiore. Esegui cabal update prima di accusare un pacchetto di non esistere. Fissa le applicazioni, lascia libere le librerie.

Se stai ancora scegliendo gli strumenti, il nostro confronto tra Stack e Cabal affronta quella decisione, e la guida all’installazione di GHCup predispone la toolchain che questo articolo dà per installata.

Il campo build-depends, l’espansione dell’operatore caret e la regola della versione maggiore A.B seguono la guida utente di Cabal e la Package Versioning Policy di Haskell, verificate al momento della stesura. La disponibilità dei comandi varia con la versione di cabal-install: esegui cabal --version e consulta la guida della tua versione prima di affidarti a un sottocomando specifico.