Tom Smeding's "blog"

ad/
bugs/

About cabal install --lib

TL;DR: Don't use it, add the library to your package-name.cabal or package.yaml instead, or use a cabal script. After you learn more about the downsides, you can reconsider. See the "What to do instead" section below.


Suppose you are new to Haskell, or at least new to the current (2023) Haskell tooling, and would like to install a program written in Haskell. For example, say you would like to install a Haskell formatter, say fourmolu, and find that installing Haskell packages uses a tool called cabal. Hopeful, you try:

cabal install fourmolu

and, if you are patient, this may well succeed and give you a fourmolu executable.

So now you want to write some Haskell! But you want to use a library, say brick, for making a terminal user interface (TUI). So you go:

cabal install brick

which seems to proceed as before, compiling a bunch of dependencies. (Note that in the past, this was a common way to install Haskell libraries for use in your own code, and quite a number of READMEs of older libraries still recommend this command.) But at the end it prints this warning: (as of cabal-install 3.10.1.0)

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: Installation might not be completed as desired! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
The command "cabal install [TARGETS]" doesn't expose libraries.
* You might have wanted to add them as dependencies to your package. In this
case add "brick" to the build-depends field(s) of your package's .cabal file.
* You might have wanted to add them to a GHC environment. In this case use
"cabal install --lib brick". The "--lib" flag is provisional: see
https://github.com/haskell/cabal/issues/6481 for more information.

which looks scary, using the same kind of @@@@ banner as ssh reporting a possible man-in-the-middle attack (a changed host key, really). But you want to use this library, after all, and you're just working in a single .hs file and aren't planning on creating a "package". So you try the second suggestion:

cabal install --lib brick      # note, don't try this at home

and that seems to work -- and if you had let the previous cabal install brick command run to completion, it doesn't even seem to do much. That much is true: it hasn't done much, but what it has done is probably not what you wanted.

For example, let's try to sanity-check our Haskell installation and start a REPL:

$ ghci
Loaded package environment from /home/tom/.ghc/x86_64-linux-9.4.7/environments/default
GHCi, version 9.4.7: https://www.haskell.org/ghc/  :? for help
ghci> 1 + 2

<interactive>:1:3: error:
    Variable not in scope: (+) :: t0 -> t1 -> t
ghci> print "hi"

<interactive>:3:1: error:
    Variable not in scope: print :: base-4.17.2.0:GHC.Base.String -> t
ghci>

I mean, that doesn't look good, does it?

And if that did not scare you enough, suppose that in the future, you want to use a newer version of brick and try to install that using cabal install --lib brick again. What you'll see is this:

$ cabal install --lib brick-1.9
Error: cabal: Packages requested to install already exist in environment file
at /home/tom/.ghc/x86_64-linux-9.4.7/environments/default. Overwriting them
may break other packages. Use --force-reinstalls to proceed anyway. Packages:
brick

(I simulated the situation by installing an older version instead. I can't time-travel, unfortunately.)

Another thing that would fail is trying to install a package that is incompatible with the version of brick you have now "installed". I don't have a good example for this post because I couldn't find a neat pair of incompatible packages that didn't have many other dependencies, but I hope you'll trust me that this will result in the well-known (to seasoned haskellers) cabal dependency resolution errors.

What happened?

Note the line printed by ghci:

Loaded package environment from /home/tom/.ghc/x86_64-linux-9.4.7/environments/default

This file is the "GHC environment" from above that cabal wrote to. It now contains this:

clear-package-db
global-package-db
package-db /home/tom/.cabal/store/ghc-9.4.7/package.db
package-id brick-1.10-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696

This means that when starting ghci, these, and no others, are the packages that are in scope:

$ ghci
Loaded package environment from /home/tom/.ghc/x86_64-linux-9.4.7/environments/default
GHCi, version 9.4.7: https://www.haskell.org/ghc/  :? for help
ghci> :show packages
active package flags:
  -package-id brick-1.10-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696

This does not include base; this is what produced the broken ghci above (which couldn't find (+) nor print).

You can "fix" this:

ghci> :set -package base
package flags have changed, resetting and loading new packages...
ghci> 1 + 2
3
ghci> :show packages
active package flags:
  -package base
  -package-id brick-1.10-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696

and you can even make that change permanent with cabal install --lib base, which adds (in my case) a line package-id base-4.17.2.0 to the aforementioned default file.

In short, a GHC environment file was created by cabal that contains a list of packages in scope for ghc when you're not using cabal, or outside the context of a project. You need to either manage this file manually or through some helper tool, and you will need to, because cabal won't resolve conflicts for you. You're back to manual, imperative management of dependencies.

Furthermore, this way of installing dependencies is fundamentally separate from the code that uses those dependencies, and there is just one such global list. So if you have multiple Haskell programs that you'd like to work globally, outside of a project, their dependency sets had better be compatible, or things will break.

How to fix the situation

If you got yourself in a pickle due to an unintended use of cabal install --lib, you can undo its effects (apart from having used some disk space in compiling the packages in question) by removing the default file mentioned above. This is in ~/.ghc/architecture-OS-ghcversion/environments/default.

As mentioned, the compiled packages are still around (in ~/.cabal/store/ghc-version/), but removing those is tricky -- do not try it, cabal likes to maintain its own consistent set of packages in the "store". Removing the entire store folder for a particular GHC version is safe, however -- even though this does of course mean that you may need to recompile a lot of things later. :)

What to do instead

Create a project! The intended mode of operation of the modern Haskell tooling, that is cabal or stack, is to always work inside of a project. Often, "project" basically means "package", but you can have projects with multiple packages in them (using a cabal.project file, see the docs).

Creating a package is easily done using cabal init --simple inside a fresh directory. If you like to be asked more questions, you can also opt for cabal init instead. Then, you can declaratively add dependencies in the build-depends field of your executable/library in the package-name.cabal file that was generated. Put a comma (,) between the package names in the build-depends field.

Note that if you selected "Library" and "yes" for generating a test suite (the default option), there will be two build-depends blocks in your package-name.cabal file, one for each component. A package (a single thing in the package repository, should you decide to upload it to Hackage at some point) can contain multiple components: possibly one public library, as well as zero or more executables, test suites, or benchmarks. (There is also the concept of an "internal library", for which see the documentation, but don't worry about that.)

You can start writing code in the app/Main.hs file (or src/MyLib.hs file if you selected Library) and run using cabal run (or build using cabal build in the case of a library). cabal will automatically ensure that a consistent set of versions is compiled and made available, if at all possible. You can also add version bounds to your dependencies if you want to apply some proper software engineering principles.

(If you want to use stack instead of cabal, try their getting started guide.)

An even lighter-weight alternative

An alternative to creating a project is to make a cabal script: this allows you to effectively make a self-contained project inside a single Haskell file. You specify the dependencies in a special comment block at the top of the file. See the documentation for more details.

Created: , last updated: