ca·bal noun: the artifices and intrigues of a group of persons secretly united in a plot
The undocumented or obscure artifices and intrigues of Cabal. And these are just some important and useful ones!
Cabal has a user guide! Did you know? Link
However, it still doesn't tell you everything…
.cabal/config defaults want you to never write in Haskell! Or never read docs and never profile. Look at these lines:
-- library-profiling: False ... -- documentation: False
This is an expensive error. One day, you will want library documentation on your own hard disk. One day, you will need profiling. When the day comes, you will have to distract yourself and reinstall a lot of packages. Reinstalling is slow or unsafe: pick one.
To correct this error, uncomment and change the lines:
library-profiling: True ... documentation: True
(This paragraph assumes that you have hscolour.) Even this does not enable
hyperlinking from docs to coloured source code. In cabal-install ≤ 1.18, there
is no config line for it, you must manually type cabal install
--haddock-hyperlink-source
every time, every bloody time. In cabal-install
≥ 1.20, the relevant config section is:
-- requires cabal-install ≥ 1.20 haddock ... hyperlink-source: True ...
OK, you have now adjusted your config, and ready to bite the bullet and reinstall. Here is the slow, safe, scorched earth policy. Do not succumb to the sunk cost fallacy.
Understand my removing packages. In fact, understand the whole thing.
We will be erasing all user packages. Slow, safe, scorched earth policy. Do not succumb to the sunk cost fallacy.
Therefore, before erasing, do jot down which packages not already from GHC and Haskell Platform (if you have it) that you will want back after erasing.
Do not jot down packages from GHC and Haskell Platform installers—they already come with docs, profiling, dynamic linking, everything. Only cabal-install disables everything, like it hates Haskell programmers.
Erase: erase the appropriate metadata directory $HOME/.ghc/arch-ghcversion
Install back: Use cabal install
to install back the packages
you jotted down. Do not use multiple commands. Use one single command for the whole
roster, so that cabal-install can compute one single coherent set. Example:
cabal install --haddock-hyperlink-source lens netwire hxt hxt-http
(If you don't have hscolour, --haddock-hyperlink-source
does not
apply.)
Co-existing multiple versions or instances of a library is problematic, see my the pigeon-drop con. There is no such thing as “upgrade”; there is only reinstall and install more.
To guard against multiple versions or instances of bytestring and containers
(say) during cabal install
, add these flags:
--constraint="bytestring installed" --constraint="containers installed"
or insert these lines into $HOME/.cabal/config:
constraint: bytestring installed constraint: containers installed
A complete list for Haskell Platform is at my Haskell Platform article.
.cabal/config defaults optimization to
-- optimization: True
This means passing -O to GHC. Do you want -O2 instead? Without command line options? You can change the config line to:
optimization: 2
That's right, it does not have to be boolean! The complete list of valid values are: False, false (will cause warning), 0, True, true (will cause warning), 1, 2.
Cabal normally tells GHC to use Haskell 98. This is why some files are accepted when using GHC by hand but rejected when using Cabal. To correct this error: In project.cabal files, under each library section and executable section, add this undocumented field
default-language: Haskell2010
As a result of using this setting, it is also a good idea to request Cabal version 1.10 or higher in the global (top) section:
cabal-version: >=1.10
So here is how it goes in a sample project.cabal file:
name: dummy version: 1.1 cabal-version: >= 1.10 build-type: Simple author: Albert Y. C. Lai category: Dummy synopsis: Example executable and example library. executable dummy main-is: dummy.hs build-depends: base >= 4 default-language: Haskell2010 library exposed-modules: Dummy build-depends: base >= 4 default-language: Haskell2010
Its significance is exemplified by the following file, which uses Haskell 2010 new features left right and centre. As said, it is normally accepted by GHC, but normally rejected via Cabal, unless you add the above line.
import Data.Set(empty, minView) -- foreign declarations foreign import ccall abort :: IO () -- pattern guards f s | Just (x, s1) <- minView s = x | Nothing <- minView s = "empty" -- do and if-then-else main = do if False then abort else putStrLn (f empty)
And just what more undocumented valid fields can you use in project.cabal files? This brings us to…
To find out more undocumented valid fields in project.cabal, deliberately
add an invalid field (and value) under the desired section, then run
cabal configure
.
Version constraints can mix &&
(higher precedence) and
||
(lower precedence), and also use parentheses to override
precedences. Example:
base ==10.* || <9 && (<3 || >4) || ==12.*
Since Cabal 1.20 and GHC 7.10, you can restrict which modules from a package you can use with renaming, or you can add aliases, right in the build-depends field.
Example of just adding a few module aliases:
build-depends: base with (Data.List as List, System.IO as IO), containers
Example of allowing only a few modules, with some renaming:
build-depends: base, containers (Data.Set, Data.Map.Strict as M)
This much is actually in the user guide. What they forgot to mention is: where do you also specify version constraints? Answer:
build-depends: base >= 4.8 && <4.9 with (Data.List as List, System.IO as IO), containers >=0.5 && < 0.6 (Data.Set, Data.Map.Strict as M)
You can host additional package repositories on your file system! And yes that's plural, you can have more than one.
Let's say one repository is at /home/trebla/must-have, and it has these packages: doggie 1.0, kitteh 1.1, kitteh 1.2. Here is the expected file system layout in /home/trebla/must-have:
doggie/1.0/doggie-1.0.tar.gz
kitteh/1.1/kitteh-1.1.tar.gz
kitteh/1.2/kitteh-1.2.tar.gz
00-index.tar, which tars up (order unimportant):
doggie/1.0/doggie.cabal
kitteh/1.1/kitteh.cabal
kitteh/1.2/kitteh.cabal
preferred-versions
This file is optional. It can contain version preferences, so that some
packages default to not-the-newest versions. It has the same semantics as
--preference
on the command line. Here is an example:
-- this is a comment doggie < 2 || >= 5 kitteh > 1 && < 6
It is ok to have other files for your own convenience.
This file system layout is different from remote repositories described next, and it is not just because 00-index.tar is uncompressed here. Don't you love gratuitous inconsistencies?
And here is the line in .cabal/config to use that repository:
local-repo: /home/trebla/must-have
You can use many local repositories. Just add more local-repo
lines.
cabal-install will add the file 00-index.cache to the repositories for its own use. You do not have to worry about maintaining it. Just keep your 00-index.tar up to date, and cabal-install will do the right thing.
You do not need to run cabal update
for changes in local
repositories.
You can also host and/or use additional remote package repositories! And yes that's also plural, you can have more than one.
Let's say one repository is at http://aminal-libs.com/kittehdoggie/, and it has these packages: doggie 1.0, kitteh 1.1, kitteh 1.2. Here are the expected relative paths of files:
package/doggie-1.0.tar.gz
package/kitteh-1.1.tar.gz
package/kitteh-1.2.tar.gz
00-index.tar.gz, which tars up and compresses (order unimportant):
doggie/1.0/doggie.cabal
kitteh/1.1/kitteh.cabal
kitteh/1.2/kitteh.cabal
preferred-versions
This file is optional. It can contain version preferences, so that some
packages default to not-the-newest versions. It has the same semantics as
--preference
on the command line. Here is an example:
-- this is a comment doggie < 2 || >= 5 kitteh > 1 && < 6
It is ok to have other files for your own convenience.
This layout is different from local repositories described previously, and it is not just because 00-index.tar.gz is compressed here. Furthermore it is different from Hackage: cabal-install has special code to detect the URL “hackage.haskell.org/packages/archive” and compute paths differently. Don't you love gratuitous inconsistencies?
And here is the line in .cabal/config to use that repository. First you have to choose a name; it is up to you and it just has to be unique. Let's say you choose “catdog”. Then the line is:
remote-repo: catdog:http://aminal-libs.com/kittehdoggie
You can use many remote repositories. Just add more remote-repo
lines and give them names. (In fact, you already have one for Hackage.) And they
don't have to be http://, they can also be file://.
You need to run cabal update
for remote repositories. This
downloads and caches 00-index.tar.gz. Explore .cabal/packages to see what is
cached there.
Many people use this idiom in a source tree to build and install:
cabal configure cabal build cabal install
This idiom is an urban legend, i.e., a popular error. The 3rd step
cabal install
re-does the 1st and 2nd steps, among other
things. Redundant work looks harmless so far, until one day you need
non-default options:
cabal configure --prefix=/nondefault --flags=debug cabal build cabal install
Whee! Your laborously entered options are lost on deaf ears. The 3rd
step cabal install
without options re-does cabal
configure
without options. Your options are erased.
The correct idiom is:
cabal configure --prefix=/nondefault --flags=debug cabal build [cabal haddock [--hyperlink-source]] cabal copy cabal register
or simply
cabal install --prefix=/nondefault --flags=debug [--enable-documentation [--haddock-hyperlink-source]]
This concludes the practical advice. But the story doesn't end here.
The urban legend probably arises from this analogy:
Makefile | Setup.hs |
---|---|
./configure make make install |
Setup configure Setup build Setup install |
And you would think that it would carry over to cabal
, no?
No:
Setup install
does not repeat Setup configure
or Setup build
Setup
defaults to --global
, cabal
defaults to --user
(see my SICP)Don't you love gratuitous inconsistencies?
So why did they choose the name cabal install
for the all-in-one
command? Probably because of another analogy:
Debian | Cabal |
---|---|
apt-get update apt-get install that-package apt-get upgrade |
cabal update cabal install that-package cabal upgrade (deprecated) |
The cabal
program tries too hard to use the Makefile analogy and
the Debian analogy together. Analogies are like drugs.
Cabal's default of whether to build dynamic libraries is a very complicated story depending on Cabal version, GHC version, and OS.
Cabal ≤ 1.16 | Cabal ≥ 1.18 | |||||||
---|---|---|---|---|---|---|---|---|
GHC ≤ 7.6 | False | False | ||||||
GHC ≥ 7.8 | False |
|
This is because:
Starting with GHC 7.8, the interpreter and Template Haskell call the OS's dynamic library loader to load libraries. (Previously, they used a homebrew loader to load static libraries.) Except for Windows, waiting for one final missing piece.
Therefore, all libraries have to have dynamic versions built. Cabal knows to do this starting with 1.18. (Cabal 1.16 could not have known this.)
The full story is at link.
This complicated story is clearly a transitional pain only. Eventually, no one will use Cabal ≤ 1.16 anymore (already, many people are at 1.20); eventually, no one will use Windows anymore the Windows version will enable dynamic libraries too. When that day comes, no one will run into any problem.
The transitional pain will come to pass. Meanwhile, it can show up as mysterious linker “cannot find” error messages, even when you don't use ghci or Template Haskell. Here is how:
Install GHC 7.8 alone (i.e., not as part of Haskell Platform).
Still using cabal-install 1.16, install some libraries directly or indirectly. This causes libraries to be installed without dynamic versions.
The most treacherous way to install some libraries indirectly, behind your back:
cabal install cabal-install
This brings in: HTTP, mtl, network, parsec, random, stm, text, zlib, a 2nd version of Cabal (the library part), and a 2nd version of transformer.
Then using a newer cabal-install, install some libraries that depend on those installed above. For example install async now, which depends on stm above.
The newer cabal-install tries to build a dynamic version of async. This requires linking against a dynamic version of stm. But a dynamic version of stm is absent.
/usr/bin/ld: cannot find -lHSstm-2.4.3-ghc7.8.3
Or, when you use ghci and try to use a library:
Prelude> import Control.Concurrent.STM <no location info>: Could not find module ‘Control.Concurrent.STM’ Perhaps you haven't installed the "dyn" libraries for package ‘stm-2.4.3’? Use -v to see a list of the files searched for.
You have to rebuild those libraries that don't have dynamic versions.
An ounce of prevention is better than a pound of cure, for those who learn from history. Here are some safe practices that have averted problems in the past, avert this problem today, and will avert more problems in the future:
Stick to Haskell Platform. You would never have a chance to use the mismatched combination of GHC 7.8 and cabal-install 1.16.
Piecemeal upgrade and the ensuing trouble-shooting is the full-time job of a system administrator. Do you have time for the full-time job of a system administrator?
Whenever you upgrade cabal-install, build it in a sandbox, then copy out the exe for actual use. Throw away the collateral, behind-your-back libraries; they have caused other problems in the past.
If you don't already have a sandboxing tool, you can do it by hand, see my article.
I have more Haskell Notes and Examples