on
The Haskell Cabal and Cross Compilation
Once you want to tap into the vast ecosystem of haskell libraries, you will run into cabal and hackage in one way or the other (stack and stackage build upon cabal as well). Over the last few days we set up the Raspberry Pi, built the Raspbian SDK and the Haskell cross compiler. Today we will look at what cabal is, and how to use it for cross compilation.
Cabal, cabal-install, cabal and Hackage
The *C*ommon *A*rchitecture for *B*uilding *A*pplications and
*L*ibraries consists of Cabal the
library. Cabal the library contains the logic how to build a haskell
package from a =.cabal= file. cabal-install is the cabal package, that
provides the cabal
command.
Hackage finally is the haskell
package repository.
As an end user you will mostly deal with cabal
the command line
interface. This will also take care of downloading dependencies and
building them as required.
Cross Compiling with =cabal=
cabal is not yet very cross compilation agnostic. As cross compilation has been mostly a niche, cross compiling packages with cabal needs some hand-holding.
By default cabal
will use a non-prefixed toolchain, which results in
the library being compiled for the build machine. In the cross
compilation setting, we want to compile for the host machine. That is
the machine that will host the final binary, and not the machine that
builds the binary.
Luckily cabal
provides the necessary arguments to pass in the
toolchain we want to use (I’ve seen this first in the
ghc-ios-scripts/)/.
For our arm-linux-gnueabihf-ghc
we
built
yesterday we want to effectively call cabal
with the following
arguments:
--builddir=dist/arm-linux-gnueabihf
to put any build product into a separate directory for each architecture. And
--with-ghc=arm-linux-gnueabihf-ghc
--with-ghc-pkg=arm-linux-gnueabihf-ghc-pkg
--with-gcc=arm-linux-gnueabihf-clang
--with-ld=arm-linux-gnueabihf-ld
to teach cabal about our toolchain. cabal may call hsc2hs
as well,
passing
--hsc2hs-options=--cross-compile
when compiling ensures that hsc2hs
operates
in cross compilation mode.
Finally cabal
may invoke configure
. Therefore we pass
--configure-option=--host=arm-linux-gnueabihf
as well, when running cabal configure
or cabal install
, so that
configure scripts can rely on the proper --host
flag.
Passing all this by hand is truly annoying. Therefore let us create a wrapper script for this (again credit for this goes to ghc-ios-scripts/, where I saw this approach first — we will use a slightly modified version here)./
Let us create a script called cabal-wrapper
with the following
content:
#!/bin/bash
name=${0##*/}
cmd=${name##*-}
target=${name%-*}
fcommon="--builddir=dist/${target}"
fcompile=" --with-ghc=${target}-ghc"
fcompile+=" --with-ghc-pkg=${target}-ghc-pkg"
fcompile+=" --with-gcc=${target}-clang"
fcompile+=" --with-ld=${target}-ld"
fcompile+=" --hsc2hs-options=--cross-compile"
fconfig="--disable-shared --configure-option=--host=${target}"
case $1 in
configure|install) flags="${fcommon} ${fcompile} ${fconfig}" ;;
build) flags="${fcommon} ${fcompile}" ;;
list|info|update) flags="" ;;
"") flags="" ;;
*) flags=$fcommon ;;
esac
exec $cmd $flags "$@"
Note: for simplicity we do not wrap cabal
’s new
nix-style
local build commands like new-build
, and new-install
.
Simply creating a symbolic link
ln -s cabal-wrapper arm-linux-gnueabihf-cabal
provides us with arm-linux-gnueabihf-cabal
which nicely fits in with
the rest of the toolchain we built so far. And we can use
arm-linux-gnueabihf-cabal
just like cabal
to build or install
haskell packages to use with the cross compiler.
Be Aware of build-type: Custom
Cabal provides an escape hatch for to support custom build types, for
when the build-type: Simple
is not sufficient. Unfortunately this
requires the Setup.hs
to be built by cabal and run, and cabal default
to using the ghc
is knows about to compile the Setup.hs
. This then
leads to a Setup.hs
that is compiled with the cross compiling ghc
(here: arm-linux-gnueabihf-ghc
), and can only be run on the host,
but not on the build machine. Ideally cabal would be cross compilation
aware and compile the Setup.hs
with the compiler that targets the
build machine.
As quite a few packages use build-type: Custom
for the purpose of
supporting doctest
or haddock
. Often times just rewriting
build-type: Custom
to build-type: Simple
can make a package succeed
to compile for cross compilation. (An alternative solution, that tries
to respect the =build-type: Custom=/, can be found in the/
cabal-custom
script, again from the
ghc-ios-scripts/)/
Be Aware of Template Haskell
Template Haskell provides interesting challenges for cross compilation. For a long time Template Haskell was simply not an option for cross compilation. While there are evil splicer and ZeroTH, which try to work around Template Haskell, there has been no proper Template Haskell support. However GHCJS does support Template Haskell and is a cross compiler as well, and the same approach can be taken with GHC. Cross compiling and Template Haskell will the the main topic for next week.