[Nix-dev] Map Cabal Files To Nix Without Information Loss

Peter Simons simons at cryp.to
Sun Oct 18 21:16:10 CEST 2015


The Problem
-----------

Our implementation of cabal2nix loses important information, because its
mapping from Cabal to Nix is too simplistic. Cabal supports conditional
values that depend on

 - the OS and architecture on which the build takes place,
 - the compiler version,
 - the respective versions of the build dependencies, and
 - Cabal flags.

But we don't capture those conditional settings in the generate Nix
file. Instead, we resolve those conditions with a set of hard-coded
assumptions that work well for Linux/x86_64 using GHC 7.10.2, but other
build environments often fail to compile the expression because some
essential information is missing.


The Situation Today
-------------------

Consider the Cabal file for conduit-1.2.5 [2]. Among other things, it
says:

    Library
      Build-depends:       base              >= 4.3   && < 5
                         , resourcet         >= 1.1   && < 1.2
                         , exceptions        >= 0.6
                         , lifted-base       >= 0.1
                         , transformers-base >= 0.4.1 && < 0.5
                         , transformers      >= 0.2.2 && < 0.5
                         , mtl
                         , mmorph
      if !impl(ghc>=7.9)
        build-depends:   void                >= 0.5.5

The library depends on the module Data.Void, which GHC's base library
contains starting with version 7.9.x, but older versions of GHC don't
have that module, so the build needs an additional 3rd-party library,
void, to provide that code. The Nix build expression for conduit,
however, does not capture that information. cabal2nix omits void from
the list of dependencies entirely:

    libraryHaskellDepends = [
      base exceptions lifted-base mmorph mtl resourcet transformers
      transformers-base
    ];

The conversion function [1] used by cabal2nix doesn't translate the
conditional from Cabal into Nix. Instead, the function resolves all
conditionals [3], assuming that

- the build will take place with the same architecture, OS, and compiler
  version as were used to compile cabal2nix,

- all Haskell dependencies are available in all versions, and

- all Cabal flags have their default value unless specified otherwise in
  a hard-coded list [4].

For the majority of packages, that heuristic produces build expressions
that work fine, but In case of conduit the result won't compile with
compilers prior to GHC 7.10.x, and thus a manually configured override
is necessary to fix the build [5].

The lack of support for conditionals is particularly annoying when it
comes to packages that make extensive use of Cabal flags, like
git-annex. There are numerous flags that determine the feature set
supported by the build, i.e. whether to include the "assistant" or not.
Now, Stackage package sets like LTS Haskell do contain git-annex, but
they don't contain all the dependencies that are necessary to build the
assistant. Consequently, git-annex ought to compile with "-f-assistant"
within a context of Stackage. At the same time, we want to provide the
assistant feature to Nixpkgs users -- and we can, because we have all of
Hackage, not just Stackage!

So what we do is this: our generated build expression for git-annex by
default includes the assistant, and that is the build
"gitAndTools.git-annex" refers to. Then we define variants of that build
without the assistant [5], and these are the ones
"haskell.packages.lts-x_y.git-annex" refers to. Now, we define that
variant by setting the Cabal flag "-f-assistant", but that override does
not change the fact that all those dependencies needed only to build the
assistant are still listed as build inputs in the Nix expression for
git-annex! Cabal won't use these dependencies during the build, but Nix
will create them and include them into the build environment
nonetheless, which defeats the purpose of having that flag in the first
place.


Possible Improvements
---------------------

Nix expressions must capture the complete logic that the underlying
Cabal file specifies. In case of the conduit example, the generated code
should say:

    libraryHaskellDepends = [
      base exceptions lifted-base mmorph mtl resourcet transformers
      transformers-base
    ] ++ stdenv.lib.optional (stdenv.lib.versionOlder ghc.version "7.9") void;

Furthermore, Cabal flags should be mapped to boolean function arguments
that enable/disable the underlying feature and thereby modify how the
Nix expression evaluates, so that no extraneous build inputs occur.


[1]: https://github.com/NixOS/cabal2nix/blob/master/distribution-nixpkgs/src/Distribution/Nixpkgs/Haskell/FromCabal.hs#L36
[2]: http://hackage.haskell.org/package/conduit-1.2.5/conduit.cabal
[3]: http://hackage.haskell.org/package/Cabal-1.22.4.0/docs/Distribution-PackageDescription-Configuration.html#v:finalizePackageDescription
[4]: https://github.com/NixOS/cabal2nix/blob/master/distribution-nixpkgs/src/Distribution/Nixpkgs/Haskell/FromCabal/Flags.hs
[5]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/configuration-ghc-7.8.x.nix#L142
[5]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/configuration-common.nix#L66-L80



More information about the nix-dev mailing list