GHC Bindists on FreeBSD 12

Posted on January 6, 2019

FreeBSD 12 introduced a major breaking change, namely 64 bit inodes. This requires ports and packages to be compiled on a FreeBSD 12 system in order to work properly. Using a GHC binary built on an older OS version will likely result in strange errors related to filesystem access, such as the one shown below.

    Cabal-simple_mPHDZzAJ_1.24.2.0_ghc-8.0.2: No cabal file found.
    Please create a package description file <pkgname>.cabal

One might be tempted to think that stack setup or something similar would fix this issue. This is however not always the case. If you require a certain GHC version (e.g., due to your chosen resolver), then stack will download and install a pre-compiled GHC bindist. While stack recently got support for detecting whether or not an ino64-enabled build of GHC is required, the number of available bindists is rather small. In case you want stack to install one of the ino64-supported GHC flavors, you’re probably good to go: you might want to provide --ghc-build=ino64 depending on your stack version. However, what if there is no bindist for the GHC version you’re after? This blog post is about creating your own GHC bindists on FreeBSD 12. I’ll be using ghc-8.0.2 as an example here, because there’s no working bindist for this version and it appears to be required for installing cabal-install from stack. For instance, stack install cabal-install fails for me:

host ghc% stack install --ghc-build ino64 cabal-install           
No setup information found for ghc-8.0.2 on your platform.
This probably means a GHC bindist has not yet been added for OS key 'freebsd64-ino64'.
Supported versions: ghc-8.4.3, ghc-8.4.4, ghc-8.6.2

So this blogpost will be about how to build your own GHC bindists on FreeBSD.

When compiling GHC, you need an already existing GHC as bootstrap compiler. The lang/ghc port achieves this by downloading a GHC compiled on an older OS version and then patches its runtime so that the bootstap compiler will run sufficiently well for compiling GHC. The Makefile shows how to do this:

The key point here is that a special library is used, which wraps a number of functions like readdir_r, stat, lstat etc., so that they behave like before the ino64 change. This library (wrap.o) is simply appended to all of the libCffi*.a files of the GHC runtime (this is what the ar and ranlib commands do). For the sake of completeness, the following code shows wrap.c, stolenborrowed from the lang/ghc port:

You can cc -c -o wrap.o wrap.c in order to get the object file required by the ar and ranlib commands above.

In order to redirect all invocations of stat to wrap_stat etc., you then simply tell the hacked GHC version to make use of the following linker flags: -Wl,--wrap=readdir_r,--wrap=stat,--wrap=lstat,--wrap=fstat,--wrap=mknod. The linker flags can be set in the lib/ghc-VERSION/settings file of the hacked GHC bindist.

The rest is rather simple: download a GHC bindist for FreeBSD (any version) that allows you to compile our actually desired GHC version. If you for instance want to build ghc-8.0.2, you’ll need to use an older compiler version for bootstrapping as newer versions (such as ghc-8.4.4) won’t work due to changes in the base system. After downloading the correct version of your bootstrap compiler, patch its runtime as shown above. Then download, configure and build your actual GHC as described in the documentation. In order to make it use the hacked version of the bootstrap compiler binary, cou need to supply --with-ghc=/path/to/hacked/bindist/ghc (e.g., something like --with-ghc=~/.stack/programs/x86_64-freebsd/ghc-8.0.2/bin/ghc) to the ./configure command. Also, you should be aware that certain GHC versions might require further arguments to ./configure. For example, version 8.0.2 can’t be built without --disable-large-address-space as described here.

After configuring GHC, you can now gmake, followed by gmake binary-dist. Depending on the speed of the system you’re building on, you might want to grab one or more drinks now. Congratulations! You now have a GHC bindist. In order to teach stack where to find your bindist, you should adjust your ~/.stack/config.yaml like this:

Now that you have a working GHC bindist, you could also use it as a bootstrap compiler to build other GHC versions. This prevents you from having to use the wrap hack described above.

And that’s it. Have fun!