GHC Bindists on FreeBSD 12
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 bsd.ghc.mk
shows how to do this:
${CC} ${CFLAGS} -c -o ${BOOT_DIR}/wrap.o ${PATCHDIR}/wrap.c
for x in ${BOOT_DIR}/rts/dist/build/libCffi*.a; do \
${AR} q $$x ${BOOT_DIR}/wrap.o; ${RANLIB} $$x; \
done
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:
/* -*- ugly-hack-mode -*- */
/* $FreeBSD: head/lang/ghc/files/wrap.c 441844 2017-05-27 12:09:22Z kib $ */
#include <sys/stat.h>
struct old_dirent;
struct old_stat;
__asm(".symver old_readdir_r, readdir_r@FBSD_1.0");
int old_readdir_r(void *dirp, struct old_dirent *entry,
struct old_dirent **result);
__asm(".symver old_stat, stat@FBSD_1.0");
int old_stat(const char * restrict path, struct old_stat * restrict sb);
__asm(".symver old_lstat, lstat@FBSD_1.0");
int old_lstat(const char * restrict path, struct old_stat * restrict sb);
__asm(".symver old_fstat, fstat@FBSD_1.0");
int old_fstat(int fd, struct old_stat *sb);
__asm(".symver old_mknod, mknod@FBSD_1.0");
int old_mknod(const char *path, mode_t mode, uint32_t dev);
int
__wrap_readdir_r(void *dirp, struct old_dirent *entry,
struct old_dirent **result)
{
return (old_readdir_r(dirp, entry, result));
}
int
__wrap_stat(const char * restrict path, struct old_stat * restrict sb)
{
return (old_stat(path, sb));
}
int
__wrap_lstat(const char * restrict path, struct old_stat * restrict sb)
{
return (old_lstat(path, sb));
}
int
__wrap_fstat(int fd, struct old_stat *sb)
{
return (old_fstat(fd, sb));
}
int
__wrap_mknod(const char *path, mode_t mode, uint32_t dev)
{
return (old_mknod(path, mode, dev));
}
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!