Cross compilation

Cross compilation of Haskell projects involves building a version of GHC that outputs code for the target platform, and providing builds of all library dependencies for that platform.

First, understand how to cross-compile a normal package from Nixpkgs. Matthew Bauer's Beginners' guide to cross compilation in Nixpkgs is a useful resource.

Using an example from the guide, this builds GNU Hello for a Raspberry Pi:

nix build -f '<nixpkgs>' pkgsCross.raspberryPi.hello

We will use the same principle in Haskell.nix — replacing the normal package set pkgs with a cross-compiling package set pkgsCross.raspberryPi.

Raspberry Pi example

This is an example of using Haskell.nix to build the Bench command-line utility, which is a Haskell program.

{ pkgs ? import <nixpkgs> {} }:
let
  haskellNix = import (builtins.fetchTarball https://github.com/input-output-hk/haskell.nix/archive/master.tar.gz);
  native = haskellNix { inherit pkgs; };
in
  native.haskellPackages.bench.components.exes.bench

Now switch the package set as in the previous example:

{ pkgs ? import <nixpkgs> {} }:
let
  haskellNix = import (builtins.fetchTarball https://github.com/input-output-hk/haskell.nix/archive/master.tar.gz);
  raspberryPi = haskellNix { pkgs = pkgs.pkgsCross.raspberryPi; };
in
  raspberryPi.haskellPackages.bench.components.exes.bench

You should be prepared for a long wait because it first needs to build GHC, before building all the Haskell dependencies of Bench. If all of these dependencies compiled successfully, I would be very surprised!

Hint:

The above example won't build, but you can try and see, if you like. It will fail on clock-0.7.2, which needs a patch to build.

To fix the build problems, you must add extra configuration to the package set. Your project will have a mkStackPkgSet or mkCabalProjectPkgSet. It is there where you must add module options for setting compiler flags, adding patches, and so on.

Note:

Note that haskell.nix will automatically use qemu to emulate the target when necessary to run Template Haskell splices.

Static executables with Musl libc

Another application of cross-compiling is to produce fully static binaries for Linux. For information about how to do that with the Nixpkgs Haskell infrastructure (not Haskell.nix), see nh2/static‑haskell‑nix. Vaibhav Sagar's linked blog post is also very informative.

{ pkgs ? import <nixpkgs> {} }:
let
  haskellNix = import (builtins.fetchTarball https://github.com/input-output-hk/haskell.nix/archive/master.tar.gz);
  musl64 = haskellNix { pkgs = pkgs.pkgsCross.musl64; };
in
  musl64.haskellPackages.bench.components.exes.bench

This example will build Bench linked against Musl libc. However the executable will still be dynamically linked. To get fully static executables you must add package overrides to:

  1. Disable dynamic linking
  2. Provide static versions of system libraries. (For more details, see Vaibhav's article).
{
  packages.bench.components.exes.bench.configureFlags =
    lib.optionals stdenv.hostPlatform.isMusl [
      "--disable-executable-dynamic"
      "--disable-shared"
      "--ghc-option=-optl=-pthread"
      "--ghc-option=-optl=-static"
      "--ghc-option=-optl=-L${gmp6.override { withStatic = true; }}/lib"
      "--ghc-option=-optl=-L${zlib.static}/lib"
    ];
}

Note: Licensing

Note that if copyleft licensing your program is a problem for you, then you need to statically link with integer-simple rather than integer-gmp. However, at present, Haskell.nix does not provide an option for this.

How to cross-compile your project

Set up your project Haskell package set.

# default.nix
{ pkgs ? import <nixpkgs> {}}:
let
  # Import the Haskell.nix library,
  haskell = import (builtins.fetchTarball "https://github.com/input-output-hk/haskell.nix/archive/master.tar.gz") {
    inherit pkgs;
  };

  # Instantiate a package set using the generated file.
  pkgSet = haskell.mkCabalProjectPkgSet {
    plan-pkgs = import ./pkgs.nix;
    pkg-def-extras = [];
    modules = [
      {
        # You will need to put build fixes here.
      }
    ];
  };
in
  pkgSet.config.hsPkgs

Apply that package set to the Nixpkgs cross package sets that you are interested in.

We are going to expand the pkgs.pkgsCross shortcut to be more explicit.

let
  pkgs = import <nixpkgs> {}
in {
  shortcut = pkgs.pkgsCross.SYSTEM;
  actual = import <nixpkgs> { crossSystem = pkgs.lib.systems.examples.SYSTEM; };
}

In the above example, for any SYSTEM, shortcut and actual are the same package set.

# release.nix
let
  myProject = import ./default.nix;

  pkgsNative = import <nixpkgs> {};
  pkgsRaspberryPi = import <nixpkgs> {
    crossSystem = pkgsNative.lib.systems.examples.raspberryPi;
  };

  native = myProject { pkgs = pkgsNative; };
  crossRaspberryPi = myProject { pkgs = pkgsRaspberryPi; };

in {
  my-project-native = native.my-project.components.exes.my-project;
  my-project-raspberry-pi = crossRaspberryPi.my-project.components.exes.my-project;
}

Try to build it, and apply fixes to the modules list, until there are no errors left.