↑↑ Home ↑ Haskell ↑ Root

Installing cabal globally

This is a brief description of how to configure cabal, the command-line interface to the Haskell package management system, so that it acts like other package managers. Out of the box, cabal seems designed for personal use by each user on a system separately. This may be because it was conceived on and for personal PCs or for historical reasons.

Before we start, what exactly am I trying to do? Usually package managers (such as those of Linux distributions) have the following characteristics:

The first of these is a restriction, but a reasonable one on multi-user systems. The second helps keep the use of the administrator account down, and allows users to find out about installed packages. The third is necessary for installed packages to be used and avoids the waste of space of duplicate installs.

cabal default behaviour

By default, cabal keeps its information, including its configuration file and the package database, in the subdirectory .cabal in the home directory of the user by whom it is run. This is so for the root user as well as normal users. The subdirectory is created when cabal is first run. The configuration file, config, contains a commented-out list of options, with default values if applicable. The default value for the option user-install is True, meaning that packes are installed locally by each user. If you install packages with the root user, the default settings make them unusable to everybody (unless you program in Haskell with the administrator account, which is not advisable).

Adaption to canonical package manager behaviour

Because we want to allow only the system administrator to install CABAL packages, we do the following as the root user. (This tutorial was written for Linux and other UNIX-like systems. But other OSs that support file systems with symbolic links presumably allow a similar solution.) First we have cabal generate its data directory by running:

cabal update

This creates the .cabal subdirectory, the configuration file .cabal/config and an up-to-date package database. In order to make this information accessible to all, we move it to a global directory: I chose /usr/local/share/cabal. An alternative might be to make root's home directory readable for everybody, but this is not usually allowed, which suggests that there are reasons against it. Besides the installed packages will also be put into that directory, and one would not want to burden root's home directory with so much data.

mkdir -p /usr/local/share/cabal
mv ~/.cabal/* /usr/local/share/cabal
chmod 644 /usr/local/share/cabal/packages/hackage.haskell.org/*

The last command, chmod, serves to make the package database readable to all users. Apparently, by default cabal restricts even read access to the user that generated it. Now the "personal" cabal directory of the root user has to be replaced by a link to the global one:

rmdir ~/.cabal
ln -s /usr/local/share/cabal ~/.cabal

This makes the local cabal directory the same as the newly created global one. In addition, the following changes to the configuration file have to be made, which is now located at /usr/local/share/cabal/config:

remote-repo-cache: /usr/local/share/cabal/packages
user-install: False
build-summary: /usr/local/share/cabal/logs/build.log

The most important of these is user-install: False, which causes packages to be installed under /usr/local. (To be safe, check that the prefix entry under install-dirs global is indeed /usr/local.) The other changes to config move a cache and log file to the global cabal directory we created.

Now users should be able to access the global package database. Create a link to the global cabal directory for each user and test access:

cabal list | less

This should display a long package list, the same that is displayed for root.

The only thing that remains to be done is work around a further peculiarity of cabal, namely that it installs packages so they are readable only to the user who installed them. The most comfortable solution is to define the following shell function (in root's .bashrc) and to use it instead of "cabal install":

function cabinstall()
  cabal install "$@"
  chmod -R go+rX /usr/local/share/cabal/packages