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 =
    stdenv.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"
    ];
}

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.