git.iko.soy/typeable/cabal2nix
on v2.12 help sign in
typeable/cabal2nix/doc/03-map-cabal-files-to-nix-without-information-loss.md
110 lines 5.3 kB history raw
1Map Cabal Files To Nix Without Information Loss
2===============================================
4The Problem
5-----------
7Our implementation of `cabal2nix` loses important information, because its
8mapping from Cabal to Nix is too simplistic. Cabal supports conditional values
9that depend on
11- the OS and architecture on which the build takes place,
12- the compiler version,
13- the respective versions of the build dependencies, and
14- Cabal flags.
16But we don't capture those conditional settings in the generate Nix file.
17Instead, we resolve those conditions with a set of hard-coded assumptions that
18work well for Linux/x86_64 using GHC 7.10.2, but other build environments often
19fail to compile the expression because some essential information is missing.
22The Situation Today
23-------------------
25Consider the Cabal file for [`conduit-1.2.5`][2]. Among other things, it says:
27 Library
28 Build-depends: base >= 4.3 && < 5
29 , resourcet >= 1.1 && < 1.2
30 , exceptions >= 0.6
31 , lifted-base >= 0.1
32 , transformers-base >= 0.4.1 && < 0.5
33 , transformers >= 0.2.2 && < 0.5
34 , mtl
35 , mmorph
36 if !impl(ghc>=7.9)
37 build-depends: void >= 0.5.5
39The library depends on the module `Data.Void`, which GHC's `base` library
40contains starting with version 7.9.x, but older versions of GHC don't have that
41module, so the build needs an additional 3rd-party library, `void`, to provide
42that code. The Nix build expression for `conduit`, however, does not capture
43that information. `cabal2nix` omits `void` from the list of dependencies
44entirely:
46 libraryHaskellDepends = [
47 base exceptions lifted-base mmorph mtl resourcet transformers
48 transformers-base
49 ];
51The [conversion function][1] used by `cabal2nix` doesn't translate the
52conditional from Cabal into Nix. Instead, the function
53[resolves all conditionals][3], assuming that
55- the build will take place with the same architecture, OS, and compiler
56version as were used to compile `cabal2nix`,
58- all Haskell dependencies are available in all versions, and
60- all Cabal flags have their default value unless specified otherwise in a
61[hard-coded list][4].
63For the majority of packages, that heuristic produces build expressions that
64work fine, but In case of `conduit` the result won't compile with compilers
65prior to GHC 7.10.x, and thus a
66[manually configured override is necessary to fix the build][5].
68The lack of support for conditionals is particularly annoying when it comes to
69packages that make extensive use of Cabal flags, like `git-annex`. There are
70numerous flags that determine the feature set supported by the build, i.e.
71whether to include the "assistant" or not. Now, Stackage package sets like LTS
72Haskell do contain `git-annex`, but they don't contain all the dependencies
73that are necessary to build the assistant. Consequently, `git-annex` ought to
74compile with `-f-assistant` within a context of Stackage. At the same time, we
75want to provide the assistant feature to Nixpkgs users --- and we can, because
76we have all of Hackage, not just Stackage!
78So what we do is this: our generated build expression for `git-annex` by
79default includes the assistant, and that is the build `gitAndTools.git-annex`
80refers to. Then we define [variants of that build without the assistant][5],
81and these are the ones `haskell.packages.lts-x_y.git-annex` refers to. Now, we
82define that variant by setting the Cabal flag `-f-assistant`, but that override
83does not change the fact that all those dependencies needed only to build the
84assistant are still listed as build inputs in the Nix expression for
85`git-annex`! Cabal won't use these dependencies during the build, but Nix will
86create them and include them into the build environment nonetheless, which
87defeats the purpose of having that flag in the first place.
89Possible Improvements
90---------------------
92Nix expressions must capture the complete logic that the underlying Cabal file
93specifies. In case of the `conduit` example, the generated code should say:
95 libraryHaskellDepends = [
96 base exceptions lifted-base mmorph mtl resourcet transformers
97 transformers-base
98 ] ++ stdenv.lib.optional (stdenv.lib.versionOlder ghc.version "7.9") void;
100Furthermore, Cabal flags should be mapped to boolean function arguments that
101enable/disable the underlying feature and thereby modify how the Nix expression
102evaluates, so that no extraneous build inputs occur.
105[1]: https://github.com/NixOS/cabal2nix/blob/master/distribution-nixpkgs/src/Distribution/Nixpkgs/Haskell/FromCabal.hs#L36
106[2]: http://hackage.haskell.org/package/conduit-1.2.5/conduit.cabal
107[3]: http://hackage.haskell.org/package/Cabal-1.22.4.0/docs/Distribution-PackageDescription-Configuration.html#v:finalizePackageDescription
108[4]: https://github.com/NixOS/cabal2nix/blob/master/distribution-nixpkgs/src/Distribution/Nixpkgs/Haskell/FromCabal/Flags.hs
109[5]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/configuration-ghc-7.8.x.nix#L142
110[5]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/configuration-common.nix#L66-L80