How to get llvm-hs building on MacOSX in nixpkgs

We’re using llvm-hs at work. However, there is a problem building it on MacOSX.

Through some trail-and-error, I was able to get it building. It was somewhat complicated, so I wanted to create a post here that explained how to do it.

From what I could tell, llvm-hs needs to be built in an environment with the LLVM version it expects (so llvm-hs-8.0.0 needs LLVM-8 available). However, GHC is built with clang-7 (from LLVM-7), and this seems to mess up llvm-hs-8.0.0 when building.

In order to work around this, I had to recompile GHC with using clang-8. Because GHC is a compiler and interacts with the cross-compilation infrastructure, it was somewhat complicated figuring out how to do this.

Here’s the derivation I came up with to get llvm-hs-8.0.0 building on MacOSX:

# This derivation shows how to build llvm-hs-8.0.0 on MacOSX with a recent
# version of nixpkgs.
#
# llvm-hs is needs to be built in an environment with a specific version of
# llvm and related tools.  For instance, llvm-hs-8.0.0 needs to be built in
# an environment with LLVM-8 and clang-8.
#
# The difficulty of doing this is that the stdenv on darwin uses clang-7 by
# default, and GHC is built using this stdenv.  GHC sets the C compiled used to
# build it as a propagatedBuildInput.  This interferes with building llvm-hs.
#
# The trick is to rebuild GHC with the same version of clang provided by the
# version of LLVM used by llvm-hs.  In our case, llvm-hs-8.0.0 requires LLVM-8,
# so we rebuilt GHC with clang-8 from the LLVM-8 stdenv.

let
  nixpkgs = builtins.fetchTarball {
    # nixpkgs master as of 2019/11/12.
    url = "https://github.com/NixOS/nixpkgs/archive/2041381185a3794b658928506faf575091fa3088.tar.gz";
    sha256 = "1dqznlhxf6w23db8apvjp98b9xdw9vqzqq4pii6i6b134v9nmkxr";
  };
in

with import nixpkgs {};
with haskell.lib;

let
  # This is the stdenv we will be using everywhere below.  You can see it comes
  # from LLVM-8, which means it uses clang-8.
  #
  # Note that I don't just create an overlay setting the default stdenv to be
  # llvmPackages_8.stdenv because I don't want to recompile absolutely
  # everything, just Haskell-related things.
  stdenvUsingLLVM8 = llvmPackages_8.stdenv;

  # This is GHC rebuilt with LLVM-8.
  #
  # This is somewhat complicated because GHC can be setup to cross compile
  # binaries, which means it has to be careful whether it gets things from
  # buildPackages, targetPackages, or pkgsBuildTarget.
  #
  # By default, GHC sets the C compiler (clang in our case) as a
  # propagatedBuildInput.  If we don't override clang to come from llvm-8, then
  # this messes up llvm-hs when we try to compile it.  It is really important
  # that GHC only see clang-8.
  #
  # I don't really know that much about cross-compilation in nixpkgs, so there
  # may be an easier way to write this.
  ghcUsingLLVM8 = haskell.compiler.ghc865.override {
    stdenv = stdenvUsingLLVM8;
    llvmPackages = llvmPackages_8;
    buildLlvmPackages = buildPackages.llvmPackages_8;
    targetPackages = targetPackages.extend (self: super: {
      stdenv = stdenvUsingLLVM8;
    });
    pkgsBuildTarget = pkgsBuildTarget.extend (self: super: {
      targetPackages = super.targetPackages.extend (sself: ssuper: {
        stdenv = stdenvUsingLLVM8;
      });
    });
  };

  # This is a Haskell package set using our GHC from above and a stdenv with
  # only references to clang-8.
  haskellPackagesUsingLLVM8 = haskellPackages.override {
    # buildHaskellPackages needs to be overridden.  If you don't override it, it becomes an environment
    # with a version of GHC that is not built with clang-8.
    #
    # Originally I tried to override it like the following.  This also seemed to work:
    # 
    # buildHaskellPackages = buildPackages.haskell.packages.ghc865.override {
    #   stdenv = stdenvUsingLLVM8;
    #   ghc = ghcUsingLLVM8;
    # };
    #
    # However, through trial-and-error I also figured out that it could just be this recursive package set.
    # I'm not sure why this works and doesn't go into an infinite recursion error. 
    buildHaskellPackages = haskellPackagesUsingLLVM8;
    stdenv = stdenvUsingLLVM8;
    ghc = ghcUsingLLVM8;
  };

  llvm-hs =
    let
      drv = haskellPackagesUsingLLVM8.llvm-hs.override {
        # The llvm-config executable is a build tool (similar to pkg-config but
        # specific for LLVM).  It comes from the llvm_8 package.
        llvm-config = llvm_8;
      };
    in
    overrideCabal drv (oldArgs: {
      # Apparently something links to libxml2.  Not sure what requires this.
      librarySystemDepends = (oldArgs.librarySystemDepends or []) ++ [
        libxml2
      ];
      # llvm-hs has a Cabal flag called `shared-llvm`.  This is enabled by
      # default, and it tries to link to dynamic shared libraries provided by
      # LLVM instead of static libraries.
      #
      # However, apparently the LLVM maintainers don't like the shared-library
      # approach, so they recommend only using static libraries.  LLVM-8 in
      # nixpkgs only provides static libraries, so the `shared-llvm` flag must
      # be disabled.
      configureFlags = (oldArgs.configureFlags or []) ++ [
        "-f-shared-llvm"
      ];
      # llvm-hs tests fail on MacOSX.  I'm not sure if this has been reported
      # upstream.
      doCheck = false;
    });
in
llvm-hs

I just wanted to leave this in a google-able location so that other people might be able to find it if they are having trouble getting it working.

I doubt you actually need to recompile GHC at all. The version of Clang used by GHC should not prohibit you from linking binaries to different versions of LLVM. It’s just a compiler, after all. So I would guess you could just do haskellPackages.llvm-hs.override { llvm-config = llvm_8; }

1 Like

Thanks for the response.

When trying to debug this problem, the first thing I tried was something simple like haskellPackages.llvm-hs.override { llvm-config = llvm_8; }. This may work on Linux, but on OSX I got an error like the following:

building '/nix/store/np44cams3gdzgzpd4l147cbl1as2mkia-llvm-hs-8.0.0.drv'...
setupCompilerEnvironmentPhase
Build with /nix/store/kdmykixl5nafbygjp5i8a6b4iclmfm1l-ghc-8.6.5.
...
configuring
configureFlags: --verbose --prefix=/nix/store/s5ch06rx368hr0zyljavdgk7zdc2s12z-llvm-hs-8.0.0 --libdir=$prefix/lib/$compiler --libsubdir=$abi/$libname --docdir=/nix/store/x6z0j4nzwk75c44animmqkqhrgfx434l-llvm-hs-8.0.0-doc/share/doc/llvm-hs-8.0.0 --with-gcc=clang --package-db=/private/var/folders/1b/r0tb60rs58jbyrp6fcbhy62h0000gn/T/nix-build-llvm-hs-8.0.0.drv-1/package.conf.d --ghc-option=-j4 --disable-split-objs --enable-library-profiling --profiling-detail=exported-functions --disable-profiling --enable-shared --disable-coverage --enable-static --disable-executable-dynamic --disable-tests --disable-benchmarks --enable-library-vanilla --disable-library-for-ghci --extra-include-dirs=/nix/store/bd214rhhg6qv1xsgkc9jnbxm910qv0a5-libc++-7.1.0/include --extra-lib-dirs=/nix/store/bd214rhhg6qv1xsgkc9jnbxm910qv0a5-libc++-7.1.0/lib --extra-include-dirs=/nix/store/1rhn6hj0949jhj9wgl75f8ziy6086x9c-libc++abi-7.1.0/include --extra-lib-dirs=/nix/store/1rhn6hj0949jhj9wgl75f8ziy6086x9c-libc++abi-7.1.0/lib --extra-include-dirs=/nix/store/ch1p8kfbsnx99y6br2zmzxsfdqrcjwm2-compiler-rt-7.1.0-dev/include --extra-lib-dirs=/nix/store/ig3yd5wmmqfgacnp5s33fyn2zndackr6-compiler-rt-7.1.0/lib --extra-lib-dirs=/nix/store/zprassvcbzabdrhjczy6h6y681xzi30a-ncurses-6.1-20190112/lib --extra-lib-dirs=/nix/store/bi25vgdgvw1kc3rc76fiwfahqf2hjf4n-libffi-3.2.1/lib --extra-lib-dirs=/nix/store/ifkq8kfmvwf3mf71grxa2r90z6pl1gpi-gmp-6.1.2/lib --extra-include-dirs=/nix/store/93bg0xf2kg402g0zy69y2ggz354ww7ps-libiconv-osx-10.12.6/include --extra-lib-dirs=/nix/store/93bg0xf2kg402g0zy69y2ggz354ww7ps-libiconv-osx-10.12.6/lib --extra-framework-dirs=/nix/store/vhk8p8r2z3hphhk8sm4pzvxjqlai6f4z-swift-corefoundation/Library/Frameworks
Using Parsec parser
/nix/store/jcfdrgixjp3msp2bn97ckhqbd6nx357q-llvm-8.0.1/bin/llvm-config --version
...
/nix/store/jcfdrgixjp3msp2bn97ckhqbd6nx357q-llvm-8.0.1/bin/llvm-config --libdir
Configuring llvm-hs-8.0.0...
Flags chosen: debug=False, shared-llvm=True
Dependency array >=0.4.0.0: using array-0.5.3.0
...
Dependency utf8-string >=0.3.7: using utf8-string-1.0.1.1
error: error reading '/private/var/folders/1b/r0tb60rs58jbyrp6fcbhy62h0000gn/T/nix-build-llvm-hs-8.0.0.drv-1/13498-0.c'
1 error generated.
`cc' failed in phase `C Compiler'. (Exit code: 1)
builder for '/nix/store/np44cams3gdzgzpd4l147cbl1as2mkia-llvm-hs-8.0.0.drv' failed with exit code 1
error: build of '/nix/store/np44cams3gdzgzpd4l147cbl1as2mkia-llvm-hs-8.0.0.drv' failed

The error is the following part:

error: error reading '/private/var/folders/1b/r0tb60rs58jbyrp6fcbhy62h0000gn/T/nix-build-llvm-hs-8.0.0.drv-1/13498-0.c'
1 error generated.
`cc' failed in phase `C Compiler'. (Exit code: 1)

After a bunch of debugging, I figured out that this is caused because the Setup.hs for LLVM sets DYLD_LIBRARY_PATH to the output of llvm-config-8 --libdir:

The following happens:

  • In cabal configure, Cabal tries to configure the ld on the system and see if it accepts the -x flag.
  • Cabal calls out to ghc to compile a C file.
  • ghc calls out to clang-7 to compile the C file.
  • clang-7 fails for some reason because of DYLD_LIBRARY_PATH is set to the lib/ directory for LLVM-8. It makes sense that this would cause a problem, but I didn’t actually figure out what the underlying problem was.

It is possible to patch the Setup.hs file from llvm-hs to not set DYLD_LIBRARY_PATH:

overrideCabal (haskellPackages.llvm-hs.override { llvm-config = llvm_8; }) (oldAttrs: {
  preCompileBuildDriver = oldAttrs.preCompileBuildDriver or "" + ''
    substituteInPlace Setup.hs --replace "addToLdLibraryPath libDir" "pure ()"
  '';
})

When you run this, it… actually works…

Hmm, I’m almost certain I tested this simple solution yesterday, and it wasn’t working :-/ I’m not sure how I could have messed this up.

Well, anyway, thanks for suggesting the simple solution so that I ran through everything again to try to figure it out.

In conclusion, for anyone else looking at this, the short code snippet above is all that is needed to get llvm-hs compiling on macosx. I’ll send a PR. However, if you want to compile GHC with a different version of clang, the example from the first post is what is needed.