A thin layer over the existing Haskell infrastructure in nixpkgs which adds all of the tools needed for interactive development. Here are some of the features that you might find the most useful:
-
An interactive development environment (via
nix-shell
) that includes: -
Easy to use system for overriding Haskell packages (e.g., use a package not on Hackage, fix a package marked as broken, etc.) without having to write tons of Nix code.
-
Works seamlessly with direnv, lorri, and niv if you already have those tools installed.
-
Switch GHC versions by passing an argument to
nix-build
ornix-shell
. -
Strip dependencies from a package so you can deploy just a binary without tons of other packages coming along for the ride.
-
Build fully static binaries that don't require any system libraries or the nix store (Linux only).
-
Create an interactive development environment without adding nix-hs as a project dependency.
-
Fetch pre-built tools from the binary cache.
Create a default.nix
file that looks something like this:
{ pkgs ? import <nixpkgs> {}
}:
let
nix-hs =
import (fetchGit "https://github.com/pjones/nix-hs.git") { inherit pkgs; };
in nix-hs {
cabal = ./test/hello-world/hello-world.cabal;
}
And a shell.nix
that looks like this:
# Load an interactive environment:
(import ./. {}).interactive
That's it! Now nix-build
and nix-shell
just work!
In addition to the cabal
argument to the nix-hs
function, there
are other ways to control how your package is built.
If you have a flag defined in your package's cabal file you can enable
it using the flags
argument:
nix-hs {
cabal = ./mypackage.cabal;
flags = [ "someflagname" ];
}
If one of your package's dependencies can't be built you can try overriding it:
nix-hs {
cabal = ./mypackage.cabal;
overrides = lib: self: super: with lib; {
pipes-text = unBreak (dontCheck (doJailbreak super.pipes-text));
};
}
In the example above, the overrides
function takes three arguments:
-
lib
: An attribute set of library functions. These are the functions provided by thepkgs.haskellPackages.lib
set plus a few more that you might find useful such as:unBreak
: Remove thebroken
flag from a packagecompilerName
: The nixpkgs name of the Haskell compiler being used (e.g.ghc884
)pkgs
: The full package set, after overriding
-
self
: The final set of Haskell packages after applying all overrides. This refers to the future version of the package set so if you're not careful you can fall into infinite recursion. When in doubt usesuper
instead. -
super
: The set of Haskell packages that are being modified. Use this attribute set to refer to existing Haskell packages. You can also usesuper
to access useful functions such ascallCabal2nix
andcallHackageDirect
.
The overrides
function should return an attribute set of Haskell
packages. The set of returned packages will be merged into the final
set used to build your package.
If you have a project that contains multiple Cabal packages you can
build them all with a single default.nix
. The cabal
argument to
nix-hs
can either be a path to a Cabal file or an attribute set of
Cabal files:
nix-hs {
cabal = {
package1 = ./package1/package1.cabal;
package2 = ./package1/package2.cabal;
};
}
The best way to let your text editor and shell use the environment
created from Nix is to use direnv. Here's an example .envrc
file:
# Use lorri if it's installed, otherwise load shell.nix:
if has lorri; then
eval "$(lorri direnv)"
else
use nix
fi
# Reload if these files change:
watch_file $(find . -name '*.cabal' -o -name '*.nix')
NOTE: Make sure you have a shell.nix
file that exposes the
interactive
attribute of the derivation, like the example above.
If you don't want to use nix-hs
to control your default.nix
you
can still use it for building an interactive development environment.
Just clone this repository and use the nix/shell/default.nix
file.
For example, to drop into an interactive shell:
$ nix-shell /path/to/nix-hs/nix/shell
Or
$ nix-shell --argstr compiler 8.8.3 /path/to/nix-hs/nix/shell
Even better, use direnv so your normal shell and text editor can
see all the installed development tools. Here's an example .envrc
file:
use nix /path/to/nix-hs/nix/shell
The derivation generated by the nix-hs
function makes it easy to
access a "binary only" derivation. This is perfect for deployments or
Docker containers where you don't want to bring along all of your
package's dependencies (including GHC).
The bin
attribute of the derivation gives you access to this binary
only derivation. For example, to create a docker container put the
following in docker.nix
:
{ pkgs ? import <nixpkgs> { }
}:
let
mypackage = (import ./. { inherit pkgs; }).bin;
in pkgs.dockerTools.buildImage {
name = "mypackage";
tag = "latest";
config = {
Cmd = [ "${mypackage}/bin/hello" ];
};
}
It's possible to build fully static binaries using the static-haskell-nix project. Here are some things you should keep in mind:
-
Every upstream dependency needs to be rebuilt so it links with musl instead of glibc, including GHC and its dependencies. This can take a very long time so you might want to consider using the binary cache.
-
Ensuring that all packages in nixpkgs build with musl is not a priority and is sometimes broken. You'll often need to pin nixpkgs to a specific commit off master. The best way to find a working commit ID for nixpkgs is to see what's being used to build the
survey
portion of static-haskell-nix. -
As of June 5, 2020 a patch needs to be applied to nixpkgs so that we can bootstrap a statically compiled GHC.
nix-hs
will automatically apply this patch to the nixpkgs set that you give it.
With that out of the way, let's talk about how to actually build a
static binary. Most of the work has already been done for you and
it's likely that all you'll need to do is set the
enableFullyStaticExecutables
argument to true
when calling the
nix-hs
function.
You may also need to link with static libraries created by the
static-haskell-nix project. If your package fails to build due to
missing static libraries use the staticBuildInputs
argument to
nix-hs
to add more buildInputs
.
Here's a complete example:
{ pkgs ? import <nixpkgs> { } }:
let
nix-hs =
import (fetchGit "https://github.com/pjones/nix-hs.git") { inherit pkgs; };
in nix-hs {
cabal = ./test/hello-world/hello-world.cabal;
enableFullyStaticExecutables = true;
staticBuildInputs = static: with static; [ zlib_both ];
}
If you don't want to spend all day compiling the tools needed to build
your Haskell package and its development environment you can use the
nix-hs
cache on Cachix.
The cache is populated after each git push
via a GitHub
action and even includes
the statically compiled versions of GHC needed for building fully
static binaries.
NOTE: Due to disk space limitations the cache is limited to the current LTS version of GHC.