Haskell · Cabal · gestão de pacotes
Como se adiciona uma dependência no Cabal?
A resposta curta é que uma dependência não se adiciona com um comando, mas com uma linha num ficheiro. Um pacote Haskell declara aquilo de que precisa no seu ficheiro .cabal, sob build-depends, e tudo o resto decorre dessa declaração.
Vale a pena dizê-lo com clareza, porque quem chega do npm ou do Cargo procura um comando install que altere um manifesto, e depois pergunta-se porque nada do que instalou está visível para o compilador.
O campo que realmente importa: build-depends
Abra o seu ficheiro .cabal e encontre a estrofe daquilo que está a construir: uma library, um executable ou uma test-suite. Cada uma traz o seu próprio build-depends, e é esse o ponto que mais escapa.
library
exposed-modules: MyApp.Core
build-depends: base ^>=4.18
, containers ^>=0.6
, text ^>=2.0
default-language: Haskell2010 Adicionar um pacote é acrescentar uma linha aqui. A vírgula no início da linha é uma convenção, não uma exigência: existe para que acrescentar ou remover uma linha toque apenas numa.
As dependências são de cada estrofe. Um pacote de que a sua suite de testes precisa não pertence ao build-depends da biblioteca, e um pacote de que a sua biblioteca precisa não fica automaticamente disponível para o seu executável, a menos que este dependa da própria biblioteca. É mais estrito do que na maioria dos ecossistemas, e é deliberado: mantém as ferramentas de teste fora daquilo que os seus utilizadores têm de compilar.
O que o operador caret significa mesmo
O ^>= que se vê por toda a parte não é decorativo. É uma abreviatura ligada à Package Versioning Policy de Haskell, a PVP, e compreendê-la elimina quase toda a adivinhação sobre limites.
Sob a PVP, uma versão lê-se A.B.C.D, em que A.B em conjunto formam a versão maior. As alterações que quebram incrementam A ou B. As adições que não podem quebrar código existente incrementam C. Assim, ^>=1.2.3 significa pelo menos 1.2.3 e abaixo da maior seguinte, o que se expande para >=1.2.3 && <1.3.
A consequência apanha muita gente desprevenida: em Haskell, passar de 1.2 para 1.3 é um salto maior, não menor. Se trouxer o modelo mental do versionamento semântico, onde só o primeiro número é maior, escreverá limites bem mais largos do que pretendia.
Porque o cabal update vem primeiro
O Cabal resolve dependências contra uma cópia local do índice de pacotes do Hackage. Se essa cópia estiver desatualizada, um pacote publicado na semana passada simplesmente não existe para a sua máquina, e obtém um erro de resolução que se lê como se o nome do pacote estivesse errado.
cabal update
cabal build Execute cabal update quando um pacote que sabe existir não for encontrado, e após qualquer intervalo longo entre sessões. É a primeira coisa a tentar e não custa nada.
Ler uma falha de resolução sem pânico
Quando o resolvedor não consegue satisfazer todas as restrições ao mesmo tempo, comunica o conflito em vez de escolher em silêncio. A mensagem é densa, mas diz algo concreto: duas das suas dependências querem intervalos incompatíveis de uma terceira.
As opções honestas são poucas, e convém conhecê-las antes de recorrer a uma flag:
Aliviar um limite que foi você a pôr. Se o limite demasiado estreito está no seu próprio ficheiro .cabal, alargá-lo é legítimo, desde que compile e teste depois, em vez de o presumir.
Escolher outra versão do pacote que está a adicionar. Muitas vezes uma versão mais antiga da nova dependência encaixa nas restrições que o projeto já tem.
Usar --allow-newer com consciência e a título temporário. Diz ao resolvedor para ignorar os limites superiores declarados por outros pacotes. Esses limites foram escritos por mantenedores que tinham uma razão, por isso trate-o como um diagnóstico que mostra onde está o conflito real, não como uma correção que se faz commit.
Fixar aquilo que resolveu
A resolução escolhe versões num dado momento. Para tornar esse momento reproduzível, fixe-o:
cabal freeze Isto escreve um ficheiro cabal.project.freeze que regista as versões exatas escolhidas. Faça commit dele quando quiser uma compilação que se comporte da mesma forma no mês seguinte, que é a expectativa habitual para uma aplicação. As bibliotecas são o caso oposto: devem permanecer flexíveis para que quem depende delas possa resolver livremente.
Para um projeto que abranja vários pacotes, ou que precise de uma dependência de código-fonte ausente do Hackage, essa configuração pertence ao cabal.project e não ao ficheiro .cabal. Manter os dois distintos é a forma mais clara de se orientar: o ficheiro .cabal descreve um pacote, o cabal.project descreve como compilar um conjunto deles.
Em resumo
Adicione uma dependência acrescentando uma linha a build-depends, na estrofe que dela precisa de facto. Limite-a com ^>=, lembrando que sob a PVP os dois primeiros números formam em conjunto a versão maior. Execute cabal update antes de culpar um pacote por não existir. Fixe as aplicações, deixe as bibliotecas soltas.
Se ainda está a escolher ferramentas, a nossa comparação entre Stack e Cabal trata dessa decisão, e o guia de instalação do GHCup monta a toolchain que este artigo assume já instalada.
O campo build-depends, a expansão do operador caret e a regra da versão maior A.B seguem o guia de utilizador do Cabal e a Package Versioning Policy de Haskell, verificados no momento da escrita. A disponibilidade dos comandos varia com a sua versão do cabal-install: execute cabal --version e consulte o guia da sua versão antes de depender de um subcomando específico.