nix-tools
is a set of haskell to nix tools to integrate haskell
projects into nix build environements.
Why (yet) another conversion to nix? cabal2nix
has served the nix
community well for a long time. It does have short comings that can’t
be easily overcome without significantly altering cabal2nix
. These
are among others:
- hard coded flags / operating system upon invocation
- package resolution in cabal2nix instead of nix.
Ideally we’d convert cabal files into nix expression and retain almost everything from the cabal file and let nix deal with the expression afterwards.
nix-tools
explores this space of a very simple .cabal
conversion
into a nix
expression and then doing the lifting in nix.
Given a stack
or cabal
project. We will walk through the
necessary steps to turn it into a nix expression. That is you can ask
nix build
to build the project and the result will be you library or
executables.
You can install nix-tools
with cabal
or nix
. For cabal
nix-tools $ cabal new-build
should be sufficient. And place the executables somewhere into
./dist-newstyle
. You can usually find the executable quite reliably
with:
nix-tools $ find . -type f -name "cabal-to-nix"
To install with nix
, simply run
nix-tools $ nix build nix-tools
after a while the executables should be in ./result/bin
Given a stack.yaml
in you project directory, running:
stack-project $ $(find /path/to/nix-tools/ -type f -name "stack-to-nix") stack.yaml > .stack-pkgs.nix
should generate .stack.nix
containing all the expressions for all
packages references from the stack.yaml
and .stack-pkgs
should
list them.
So how do we make actual use of .stack-pkgs.nix
?
First we will create a new file called pkgs.nix
which will take
the expressions from .stack-pkgs.nix
and turn them into a package set
that we can use with nixpkgs
.
{ pkgs ? import <nixpkgs> {}
}:
let
overrideWith = override: default:
let
try = builtins.tryEval (builtins.findFile builtins.nixPath override);
in if try.success then
builtins.trace "using search host <${override}>" try.value
else
default;
in
let
# all packages from hackage as nix expressions
hackage = import (overrideWith "hackage"
(pkgs.fetchFromGitHub { owner = "angerman";
repo = "hackage.nix";
rev = "20ad44ecdd5475c8adbe0e129638f729a26ca120";
sha256 = "0bh0p58i9w9nw2mcjgx6j9qyi6x5xg8pn5x37a696kw1bgwm8wzn"; }))
;
# a different haskell infrastructure
haskell = import (overrideWith "haskell"
(pkgs.fetchFromGitHub { owner = "angerman";
repo = "haskell.nix";
rev = "3b2cff33565e31e31a8a33eb5ebfa20a19aa70d6";
sha256 = "1hjqppxh9vmvlfbfpkg7gcijjhq4hhlx4xah87ma0w1nw7vk7nda"; }))
hackage;
# the set of all stackage snapshots
stackage = import (overrideWith "stackage"
(pkgs.fetchFromGitHub { owner = "angerman";
repo = "stackage.nix";
rev = "385609120d6f20a67f79e5120a93b4524d8c8862";
sha256 = "1l3k5qpbj6w2mg6rgmg0af2jk0bq1wwrijrn66grbw7kbbi4h9nx"; }))
{ inherit pkgs hackage haskell; };
# our packages
stack-pkgs = import ./.stack-pkgs.nix;
# pick the repsective stackage version here
# and augment them with out packages
stackPackages = stackage.${stack-pkgs.resolver} {
extraDeps = hsPkgs: (stack-pkgs.extraDeps hsPkgs
// stack-pkgs.packages hsPkgs); };
in stackPackages
Finally we’ll define the default.nix
driver:
{ pkgs ? import <nixpkgs> {} }:
let
base = import ./pkgs.nix { inherit pkgs; };
overlays = [ (self: super: with pkgs.haskell.lib; {
# you can explicilty mark packages as dontCheck.
# e.g. if they depend on doctest or lead to cyclic
# dependencies.
#
# aeson = dontCheck super.aeson;
}) ];
in
builtins.foldl' (pkgs: overlay: pkgs.extend overlay) base overlays;
The cabal example is quite similar to the stack
example. We will
utilize the plan.json
from cabal
new-build
.
Given a cabal
project we’ll run:
cabal-project $ cabal new-configure # this will generate the plan.json file
cabal-project $ $(find /path/to/nix-tools/ -type f -name "plan-to-nix") ./dist-newstyle/cache/plan.json > plan.nix
cabal-project $ $(find /path/to/nix-tools/ -type f -name "cabal-to-nix") PROJECT.cabal > PROJECT.nix
At this point we now have the package set as cabal
has computed it
in plan.nix
, and the projects cabal file translated into a nix
expression in PROJECT.nix
.
Similar to the stack project we need glue code to turn it into a buildable nix-expression:
Again we’ll create a pkgs.nix
file:
{ pkgs ? import <nixpkgs> {}
}:
let
overrideWith = override: default:
let
try = builtins.tryEval (builtins.findFile builtins.nixPath override);
in if try.success then
builtins.trace "using search host <${override}>" try.value
else
default;
in
let
# all packages from hackage as nix expressions
hackage = import (overrideWith "hackage"
(pkgs.fetchFromGitHub { owner = "angerman";
repo = "hackage.nix";
rev = "20ad44ecdd5475c8adbe0e129638f729a26ca120";
sha256 = "0bh0p58i9w9nw2mcjgx6j9qyi6x5xg8pn5x37a696kw1bgwm8wzn"; }))
;
# a different haskell infrastructure
haskell = import (overrideWith "haskell"
(pkgs.fetchFromGitHub { owner = "angerman";
repo = "haskell.nix";
rev = "fe5c13d8b72cf5335e5b6f71ffa3828def716736";
sha256 = "183i77jn2bg1669hpjikm28hgznwdzndjpgwvm3mdpx4kkp4ambk"; }))
hackage;
# our packages
plan = import ./plan.nix;
# create the packageset based on the nixpkgs and plan information.
# note that thehaskell packages will be picked from hackage as
# defined above, and not from the packages within nixpks. However
# all system dependencies will come from nixpkgs.
pkgSet = haskell.mkPkgSet pkgs plan;
packages = pkgSet {
extraDeps = hsPkgs: { PROJECT = ./PROJECT.nix; }; };
in packages
Note, that this is slightly different from the one for stack
due to
the reuse of the package-set.nix
from the stackage.nix
repository. This is a bug, and should be streamlined in the future.
The default.nix
will look identical. However, expect to have to
mark a few more packages as dontCheck
{ pkgs ? import <nixpkgs> {} }:
let
base = import ./pkgs.nix { inherit pkgs; };
overlays = [ (self: super: with pkgs.haskell.lib; {
# you can explicilty mark packages as dontCheck.
# e.g. if they depend on doctest or lead to cyclic
# dependencies.
#
# aeson = dontCheck super.aeson;
# you can also set cabal flag like so:
#
# cassava = super.cassava.override { flags = { bytestring--lt-0_10_4 = false; }; };
}) ];
in
builtins.foldl' (pkgs: overlay: pkgs.extend overlay) base overlays;
cabal-to-nix
(not to be confused with [[https://github.com/nixos/cabal2nix][cabal2nix]]
) translates Cabal’s
GenericPackageDescription
into a nix
expression that is parameterized over
flags
, system
and compiler
, allowing the expression to change depending
on the flags/system/compiler provided.
It does **consierably** less than cabal2nix
.
To produce a nix representation of a cabal file, simply call
$ cabal-to-nix some.cabal > some-cabal.nix
To produce a cabal2nix
compatible expression the nix/driver.nix
can
be used:
with import <nixpkgs> {};
let pkg = import ./nix/driver.nix { cabalexpr = import ./some-cabal.nix; pkgs = pkgs; };
in pkgs.haskellPackages.callPackage pkg {}
To produce a nix expression for each item int he all-cabal-hashes
folder use:
$ hashes-to-nix all-cabal-hashes > all-cabal-hashes.nix
To produce a nix expression for a given lts/nightly set:
#+BEING_SRC sh
$ lts-to-nix
To produce a nix expression from a stack.yaml file
#+BEING_SRC sh $ stack-to-nix stack.yaml > stack-pkgs.nix