Maybe I did something really wrong in my life, because I constantly run into Cabal Hell. That feeling of powerlessness is refreshing, though depressing. And I hate it the most when I just need to install an executable from Hackage, like
But hey, we are software engineers after all. So I decided to write a little helper script to avoid world destruction and get desired executable in my
Important note. In the last few years Haskell community did a great job in order to fight the Cabal Hell. And thanks to Stack and Nix-style Local Builds my solution is no longer required. I keep it just for historical reference (and to keep my blog relatively busy).
The feeling of powerlessness one has when Cabal does not do what one wanted and one does not know how to fix it.
What is the difficulty caused by Cabal-install?
The main difficulty with Cabal is otherwise known as ‘dependency hell’, in which the cabal-install does not manage to install a desired package for a reason or another, leading to large amount of manual work. As an example of this difficulty, consider a case where the user wishes to install packages A and B. Both of these work with package C, but not with the same version of C.
I need to confess. Sometimes I solve Cabal Hell by using this method with
rm -rf. Cabal Hell is like cancer - it’s very hard to cure this disease without ruining your environment (in our case - packages database). But with Cabal Hell comes one good thing - you can use some tools in order to prevent this bizarre to happen with you. For such purposes, you can use cabal sandboxes, Stackage or nixos. Probably there are some other handy solutions or tools, but this is all I know.
Stackage is great, but it doesn’t work for me very well because sometimes I need to install ‘heavy’ packages that are not on
Stackage. Also, I work on a reliably fast computer, so I don’t mind to waste thirty seconds more on the compilation. Safety is more preferable. As for
nixos – I haven’t tried it yet. But I know that it helps to find compilation problems very good. So actually, many thanks to the people that made
I think that sandboxes are really great. Usually, I install globally only commonly used packages. Everything else comes via sandboxes. Sometimes the project I am working on has dependencies that can’t be installed from Hackage. In such cases I use
So I don’t need to install such dependencies globally. And if this dependency is very heavy and problem-bringing, then it can save my global packages database.
But you use Haskell not only for writing libraries (funny, isn’t it?). Sometimes you need to install some executables. So here comes the ‘executables’ part.
Usually, I install executables by using the following sequence of commands:
$ cd path/to/cabal/project $ cabal sandbox init $ cabal install --only-dependencies $ cabal install $ cp .cabal-sandbox/bin/executable ~/.bin/executable
This works because, executables are usually completely stand-alone, so you can build them in a sandbox and then move them to any location of your choice. This approach helps to keep the system (or user) wide packages database clean and free from conflicts. I move executable into
~/.bin (but make sure that
~/.bin is in
$PATH), because when something breaks in my packages database I want to keep these executables (they made nothing bad!).
But it’s very boring to call this commands every time I want to install any executables, so I wrote a simple
fish function that installs executable from
.cabal file in the current directory for you.
function cabal-install-bin -d "Install executables from .cabal file in current directory" # set some color settings set -l error_color red set -l msg_color blue # get cabal file in current directory set -l cb *.cabal set -l c (count *.cabal) # we expect only 1 cabal file to be existing if test c -ne 1 set_color $error_color if test c -eq 0 echo "Couldn' find cabal file in (pwd)" else echo "Found $c cabal files. Think about it!" end set_color normal return 1 end set_color $msg_color echo "Using $cb" # check if sandbox is not created yet if test ! \( -e .cabal-sandbox \) -o ! \( -e cabal.sandbox.config \) echo "It looks like there is no sandbox, so creating one" set_color normal # create sandbox cabal sandbox init end # todo add support of multiple executables set -l name (cabal info *.cabal | sed -ne "s/ *Executables: *\(.*\)/\1/p") # check that the name is not empty if test ! \( -n $name \) set_color $error_color echo "Couldn't find any executable in cabal file" set_color normal return 1 end set_color $msg_color echo "Found executables: $name" echo "Installing dependencies" set_color normal # first we want to install dependencies # we could just ~cabal install~ # but I find separate installation # more satisfying cabal install --only-dependencies if test $status -ne 0 return 1 end set_color $msg_color echo "Building application" set_color normal # install package cabal install if test $status -ne 0 return 1 end set_color $msg_color echo "Copying $name to ~/.bin" set_color normal # now copy executable to ~/.bing cp ".cabal-sandbox/bin/$name" "$HOME/.bin/$name" end
But for situations when I don’t care about package sources and it’s available on hackage, I wrote another function (that reuses
function cabal-unpack-and-install-bin -a package -d "Unpack and install specified executable package from cabal." set -l current_dir (pwd) cd $TMPDIR set -l dir $package* if test (count $dir) -ne 0 echo "Found $TMPDIR$dir" echo "Looks like the package already unpacked in \$TMPDIR" cd $current_dir return 1 end cabal unpack $package if test $status -ne 0 cd $current_dir return 1 end set -l dir $package* cd $TMPDIR/$dir cabal-install-bin cd $TMPDIR rm -rf $dir cd $current_dir end
It just downloads sources of a single package to the
$TMPDIR (you might want to change this to something different, depending on your system), then installs executable (using
cabal-install-bin function) and removes sources dir. Useful, isn’t it?
You can grab the latest version of these function on GitHub.
Happy Haskell coding!