Motivation
haskell.nix is an infrastructure based on nix to build Haskell code.
It provides a way to build cabal-install and Stack based projects using nix,
reading the cabal.project or stack.yaml files used by those tools, hence reducing
the amount of nix code that needs to be maintained and making it easy to continue
using cabal-install and Stack as well.
In the rest of this page we motivate haskell.nix by comparing it to:
- Stack and cabal-install build tools
- nixpkgs Haskell infrastructure for
nix
Comparison with Stack and cabal-install
Using haskell.nix instead of Stack or cabal-install gives us:
- deterministic and hermetic builds
- distributed caching
- precise selection of the toolchain (GHC...) to use (which only
Stackallows to some extent) - precise selection of the native libraries to use (using
nix), if any
In addition, haskell.nix has better support for cross-compilation (e.g.
compiling Haskell code on Linux that will be run on Windows). It does this by
carefully distinguishing the GHC compiler for the build platform (used to
compile Cabal's Setup.hs files for Linux in our example) and the GHC
compiler for the host platform (GHC cross-compiler targeting Windows in our
example).
By design haskell.nix reuses configuration files from other tools and converts
them into nix expressions:
.cabalfilesStack'sstack.yamlcabal-install'scabal.project...
As such it doesn't require more work from you if your projects already build
with Stack or cabal-install.
haskell.nix can also be used to provide developer environments including
common Haskell tools: GHC, cabal-install, HLS (Haskell Language Server), hlint,
etc. With these environments, you don't need to use ghcup nor to pass programs
explicitly (e.g. as in cabal -w ghc-9.2.2). See devx.
Comparison with nixpkgs
To properly compare with nixpkgs we need to get more into the technical details
of both solutions.
Cross compilation
haskell.nix has more maintainable support for cross-compilation (e.g.
compiling Haskell code on a Linux machine to produce a program that runs on
Windows).
Both nixpkgs and haskell.nix rely on tools to convert .cabal files into
nix expressions. .cabal files can contain conditionals (e.g. os(windows)) to
conditionally build modules, pass flags to the compiler, etc.
The difference is that:
nixpkgsgenerates a differentnixexpression for each os/arch/flags configuration.haskell.nixgenerates a singlenixexpression that exposes the conditionals tonix.
The drawback of the nixpkgs approach is that managing so many different nix
expressions for a single .cabal file becomes a maintenance burden over time.
Performance: build-type
When haskell.nix converts a .cabal file into a nix expression, it keeps
track of the build-type value. All the .cabal files that use build-type: simple reuse the same Setup program that is built once and cached.
Dependencies: package sets
Not all Haskell packages work well together. As it is cumbersome to pinpoint every package version explicitly, it is common to rely on curated sets of packages: packages that are known to work well together to some extent (e.g. Stackage snapshots).
-
nixpkgsprovides its own curated set of packages which might or might not work for the project we work on. -
haskell.nixallows any form of package set.
First hackage.nix exposes the
nix expressions of every revision of every package from Hackage.
As the Hackage index is an ever growing repository of Haskell packages,
haskell.nix supports pinning the Hackage index to a specific revision
and letting Cabal's solver resolve the dependencies in a reproducible way.
An alternative is to start with a curated package set. For example,
stackage.nix exposes the
nix expressions of every Stackage Snapshot.
In addition, it is possible to explicitly specify a package version and revision, or even to fetch its sources (e.g. using Git).
Granularity and performance: per component level control
Haskell packages can contain several components: libraries, executables, testsuites...
nixpkgsmostly considers package as a whole.haskell.nixuses component granularity for dependencies.
The nixpkgs approach leads to some issues:
-
building only a specific component (e.g. an executable) in a package is tricky to do
-
dependencies of the different components are mixed up: this can lead to cyclic dependencies that
nixcan't solve. For example, packageunicodeexposeslib-unicodeandtest-unicodeexecutable, wheretest-unicodedepends onlib-printfrom packageprint, which itself depends onlib-unicode. Component-wise, dependencies aren't cyclic, however, package-wise, they are. -
build times: the Haskell builder in nixpkgs builds a package sequentially, first the library then the executables and finally the tests. It then executes the tests before the package is considered done. The upshot of this is that packages are only considered done if the test-suites passed. The downside is that if you have to compile multiple packages the likelihood of them failing is low, you have unnecessarily serialized your build. In a more aggressive setting libraries could start building as early as their dependent libraries are built. Of course they will have to be invalidated later should the test-suites of their dependencies fail, but this way we can make use of parallel building. In an ideal scenario this will reduce build times close to the optimum.
More logic in nix
The cabal2nix tool has a resolver that resolves system dependencies
and licenses to values in nixpkgs. This logic ends up being a simple
dictionary lookup and therefore can be a simple nix expression. This also
offloads some of the work the cabal to nix translation tool needs to
do into nix, and as such if changes are necessary (or needed to be
performed ad hoc) there is no need to rebuild the conversion tool and
subsequently mark every derived expression as out of date.
Decoupling
Finally, by treating haskell.nix and nixpkgs as separate entities we
can decouple the Haskell packages and infrastructure from the nixpkgs
package set, and rely on it to provide us with system packages while
staying up to date with Haskell packages from hackage while retaining
a stable (or known to be good) nixpkgs revision.