Version Conflicts in Haskell Dependencies
In the previous chapter we have created a trivial Haskell project with no
dependency other than base
. Let's look at what happen when our Haskell
project have some dependencies, which happen to conflict with the
dependencies available in nixpkgs
.
We first define a new Haskell project, haskell-project-v2:
cabal-version: 2.4
name: haskell-project
version: 0.1.0.0
license: ISC
build-type: Simple
executable hello
main-is: Main.hs
build-depends: base >=4.13
, yaml == 0.11.3.0
default-language: Haskell2010
We add a new dependency yaml
and explicitly requiring version 0.11.3.0
.
At the time of writing, the latest version of yaml
available on
Hackage is 0.11.5.0
.
However let's pretend there are
breaking changes
in 0.11.5.0
, and we only support 0.11.3.0
.
Nixpkgs
Let's try to follow our previous approach to define our Haskell derivation
using callCabal2nix
. If we do that and try to build it, we would
run into an error:
$ nix-build 05-package-management/haskell-project-v2/nix/01-nixpkgs-conflict/
building '/nix/store/3gab06pwqjc16wdqhj5akxk21g1z0qnx-cabal2nix-haskell-project.drv'...
these derivations will be built:
/nix/store/242x69pl2la3lb201qd57rghisrwclpy-haskell-project-0.1.0.0.drv
building '/nix/store/242x69pl2la3lb201qd57rghisrwclpy-haskell-project-0.1.0.0.drv'...
...
Setup: Encountered missing or private dependencies:
yaml ==0.11.3.0
builder for '/nix/store/242x69pl2la3lb201qd57rghisrwclpy-haskell-project-0.1.0.0.drv' failed with exit code 1
error: build of '/nix/store/242x69pl2la3lb201qd57rghisrwclpy-haskell-project-0.1.0.0.drv' failed
Why is that so?
If we try to enter Nix shell, it will still succeed. But if we try to build
our Haskell project in Nix shell, we will find out that the package yaml
has to be explicitly built by cabal:
$ nix-shell 05-package-management/haskell-project-v2/nix/01-nixpkgs-conflict/shell.nix
[nix-shell]$ cd 05-package-management/haskell-project-v2/haskell/
[nix-shell]$ cabal --dry-run build all
Resolving dependencies...
Build profile: -w ghc-8.10.2 -O1
In order, the following would be built (use -v for more details):
- yaml-0.11.3.0 (lib) (requires build)
- haskell-project-0.1.0.0 (exe:hello) (first run)
Problem with Mono-versioning
If we look into
nixpkgs source code,
we can in fact see that the version of yaml
available in nixpkgs is the latest,
0.11.5.0
.
As discussed earlier, with the mono-versioning approach by nixpkgs, there is exactly one version of each package available. Mono-versioning conflicts can happen when we need packages that are either older or newer than the version provided by nixpkgs.
In theory we could switch to a version of nixpkgs that has yaml-0.11.3.0
,
however we would then have to buy into the versions of other Haskell packages
available at that time.
Overriding Versions
Nixpkgs provides a workaround for mono-versioning conflicts, by using the
override pattern.
We can override the version of yaml
to the one we want as follows:
let
sources = import ../sources.nix {};
nixpkgs = import sources.nixpkgs {};
hsLib = nixpkgs.haskell.lib;
hsPkgs-original = nixpkgs.haskell.packages.ghc8102;
hsPkgs = hsPkgs-original.override {
overrides = hsPkgs-old: hsPkgs-new: {
yaml = hsPkgs-new.callHackage
"yaml" "0.11.3.0" {};
};
};
src = builtins.path {
name = "haskell-project-src";
path = ../../haskell;
filter = path: type:
let
basePath = builtins.baseNameOf path;
in
basePath != "dist-newstyle"
;
};
project = hsPkgs.callCabal2nix "haskell-project" src;
in
hsPkgs.callPackage project {}
Essentially, we refer to the original haskell package set provided as
hsPkgs-original
, and we call hsPkgs-original.override
to produce
a new package set hsPkgs
with yaml
overridden to 0.11.3.0
.
Using callHackage
, we can fetch the version of yaml
from the
Hackage snapshot in nixpkgs. With that we can just provide the
string "0.11.3.0"
to specify the version that we want. Note
however that this only works if the version can be found in the
given Hackage snapshot, which may be outdated over time.
An issue with overriding dependencies this way is that the override
affects the entire Haskell package set. This means that all other
Haskell packages that depend on yaml
will also get 0.11.3.0
instead of 0.11.5.0
. As a result, this may have the unintended
ripple effect of breaking other Haskell packages that we depends
on.
Building Overridden Package
If we try to build our Haskell derivation with overridden yaml
,
it would work this time:
$ nix-build 05-package-management/haskell-project-v2/nix/02-nixpkgs-override/
these derivations will be built:
/nix/store/l0v04zz36b5s5r3qc2jisvggyc0gkj5w-remove-references-to.drv
/nix/store/bg5z6b7m24fxqn8qq2l2w8c0w30wkbp3-yaml-0.11.3.0.drv
/nix/store/p5sr404mzr8bnqqprv72lxczdr9cnnim-haskell-project-0.1.0.0.drv
...
/nix/store/0m0mr11ncii3z4zkn9z0xkwk4nswprqm-haskell-project-0.1.0.0
We can also enter the Nix shell to verify that this time, cabal will
not try to build yaml
for us:
$ nix-shell 05-package-management/haskell-project-v2/nix/02-nixpkgs-override/shell.nix
[nix-shell]$ cd 05-package-management/haskell-project-v2/haskell/
[nix-shell]$ cabal --dry-run build all
Resolving dependencies...
Build profile: -w ghc-8.10.2 -O1
In order, the following would be built (use -v for more details):
- haskell-project-0.1.0.0 (exe:hello) (first run)
Haskell.nix
In comparison with the mono-versioned nixpkgs, Haskell.nix is much more flexible in allowing any version of Haskell packages that are supported by cabal. So we can leave the Nix project unchanged and still build it successfully:
$ nix-build 05-package-management/haskell-project-v2/nix/03-haskell.nix/
trace: No index state specified, using the latest index state that we know about (2020-12-04T00:00:00Z)!
building '/nix/store/v4pf9jffq0dh6xang25qviwb77947s7s-plan-to-nix-pkgs.drv'...
Using index-state 2020-12-04T00:00:00Z
Warning: The package list for 'hackage.haskell.org-at-2020-12-04T000000Z' is
18603 days old.
Run 'cabal update' to get the latest list of available packages.
Warning: Requested index-state2020-12-04T00:00:00Z is newer than
'hackage.haskell.org-at-2020-12-04T000000Z'! Falling back to older state
(2020-12-03T20:14:57Z).
Resolving dependencies...
Build profile: -w ghc-8.10.2 -O1
In order, the following would be built (use -v for more details):
- base-compat-0.11.2 (lib) (requires download & build)
- base-orphans-0.8.3 (lib) (requires download & build)
...
- aeson-1.5.4.1 (lib) (requires download & build)
- yaml-0.11.3.0 (lib) (requires download & build)
- haskell-project-0.1.0.0 (exe:hello) (first run)
these derivations will be built:
these derivations will be built:
...
/nix/store/y8vbr0b6y8bzgmadj0rfjp3d2rzx5wgs-yaml-lib-yaml-0.11.3.0-config.drv
/nix/store/fhmib5kqsxl82r1z23mm59njw2dn0c8v-yaml-lib-yaml-0.11.3.0-ghc-8.10.2-env.drv
/nix/store/j837v0cxk9dxqpxfjfngii007hq8wn3w-yaml-lib-yaml-0.11.3.0.drv
/nix/store/nxwvfjaj40adyq002khld7ngnq3wggn7-haskell-project-exe-hello-0.1.0.0-config.drv
/nix/store/y27wbd58f5d1k3lzbzpr5qcc4pgqrxg2-haskell-project-exe-hello-0.1.0.0-ghc-8.10.2-env.drv
/nix/store/b4i7xhnha8007zqxd4gidsf7xyy338an-haskell-project-exe-hello-0.1.0.0.drv
...
/nix/store/phm2jk6xnvxsgp640r66cwgipc62kbc5-haskell-project-exe-hello-0.1.0.0
$ /nix/store/phm2jk6xnvxsgp640r66cwgipc62kbc5-haskell-project-exe-hello-0.1.0.0/bin/hello
Hello, Haskell!