haskell-flake icon indicating copy to clipboard operation
haskell-flake copied to clipboard

Near-impossible to specify `extra-libraries` not existing in Nixpkgs

Open JaSpa opened this issue 2 years ago • 2 comments

Problem description

The repository JaSpa/haskell-flakes-with-extra-libraries demonstrates the problem.

I want to write a Haskell executable depending on the gvc library distributed with Graphviz. To instruct GHC/cabal about the link-time requirement I add extra-libraries: gvc to the package description in example.cabal.

Main.hs contains a simple executable which calls two functions from the mentioned library and prints their results. The script run-outside-of-nix.sh demonstrates that the executable is functional. It uses the global GHC/cabal and Graphviz obtained through nix.

Broken haskell-flakes setup

Building through the bare-bones haskell-flakes setup does not work (complete error message below). It complains about a required argument gvc which was not provided. But this makes sense. cabal2nix translates the extra-libraries: gvc into an argument { …, gvc }: … but there is no gvc package in Nixpkgs.

nix error message
error: anonymous function at /nix/store/95jn322pl7yhy3ggz04qrcvhgl33y0bc-cabal2nix-example/default.nix:1:1 called without required argument 'gvc'

       at /nix/store/2ayipj5kgy67231fvbl8agsl591kanw8-source/pkgs/development/haskell-modules/make-package-set.nix:97:40:

           96|       # this wraps the `drv` function to add `scope` and `overrideScope` to the result.
           97|       drvScope = allArgs: ensureAttrs (drv allArgs) // {
             |                                        ^
           98|         inherit scope;
(use '--show-trace' to show detailed location information)
cabal2nix output
{ mkDerivation, base, gvc, lib }:
mkDerivation {
  pname = "example";
  version = "0.1.0.0";
  src = ./.;
  isLibrary = false;
  isExecutable = true;
  executableHaskellDepends = [ base ];
  executableSystemDepends = [ gvc ];
  license = "unknown";
  mainProgram = "example";
}

Working ordinary Nixpkgs setup

I defined a flake package “ordinary” in flake.nix which demonstrates that this is not a problem when using the ordinary Nixpkgs infrastructure: callCabal2nix is called with the argument { gvc = pkgs.graphviz; }. nix-build .#ordinary succeeds and produces a runnable binary.

Workaround

There exists a workaround. It is possible to define gvc as an alias for Graphviz using a Nixpkgs overlay. This exists commented out in flake.nix. Enabling these lines allows the haskell-flakes package to build successfully. However, using Nixpkgs overlays is quite un-ergonomic in conjunction with flake-parts.

{
    _module.args.pkgs = import inputs.nixpkgs {
      inherit system;
      overlays = [
        (final: prev: {gvc = final.graphviz;})
      ];
    };
    haskellProjects.default = {};
}

Achieving this through haskell-flakes

Is there a way to achieve this using haskell-flakes? I tried various incantations using either packages or settings but ultimately I don't think this can work with the current version.

https://github.com/srid/haskell-flake/blob/ddc704f3f62d3d3569ced794b534e8fd065c379c/nix/build-haskell-package.nix#L37 The empty { } argument is the place where the { gvc = pkgs.graphviz; } would have to appear.

There are two ways I can imagine how this might work in the future: packages.<name>.custom could specify any kind of package. These packages don't go through callCabal2nix/callHackage but instead are added to the package set as-is. Instead of adding a “global” alias for Graphviz it could then be scoped to haskellProjects:

{
    haskellProjects.default = {
        packages = {
            gvc.custom = pkgs.graphviz;
        };
    };
}

However, this still is quite coarse. Maybe instead there could be a new setting args which takes the place of { } mentioned above. (It's unclear to me if this would fit with haskell-flakes architecture.)

{
    haskellProjects.default = {
        settings.example.args = {
            gvc = pkgs.graphviz;
        };
    };
}

JaSpa avatar Sep 14 '23 18:09 JaSpa

I think it is worth solving the more general problem here -- by allowing the user to pass any arguments (default being {}) to callCabal2nix. Perhaps like this:

    haskellProjects.default = {
        packages.example.source = {
          cabal2nix-args = {
            gvc = pkgs.graphviz;
          };
       };
    };

Note that source is an overloaded option ― it can be either a source path or hackage version.

https://github.com/srid/haskell-flake/blob/ddc704f3f62d3d3569ced794b534e8fd065c379c/nix/modules/project/packages/default.nix#L63-L69

As you can see above, we call callCabal2nix only in the former case, and thus this option cabal2nix-args makes sense only in that context. So we want to figure out the API first, before implementing it (the implementation itself should be straightforward). Let's also consider this in the context of a potential 3rd overloaded type, hackage-direct.

cc @roberth in case I missed anything.

srid avatar Sep 14 '23 19:09 srid