Another simple flake for Haskell development

I’ve been converting my Haskell projects to flake and would love to get some feedback on the pattern I’ve arrived at.

I put a post about it at Simple nix flake for Haskell development. If you don’t want to follow that link the relevant bit is

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      with nixpkgs.legacyPackages.${system};
      let
        t = lib.trivial;
        hl = haskell.lib;

        name = "project-name";

        project = devTools:
          let addBuildTools = (t.flip hl.addBuildTools) devTools;
          in haskellPackages.developPackage {
            root = lib.sourceFilesBySuffices ./. [ ".cabal" ".hs" ];
            name = name;
            returnShellEnv = !(devTools == [ ]);

            modifier = (t.flip t.pipe) [
              addBuildTools
              hl.dontHaddock
              hl.enableStaticLibraries
              hl.justStaticExecutables
              hl.disableLibraryProfiling
              hl.disableExecutableProfiling
            ];
          };

      in {
        packages.pkg = project [ ];

        defaultPackage = self.packages.${system}.walloper;

        devShell = project (with haskellPackages; [
          cabal-fmt
          cabal-install
          haskell-language-server
          hlint
        ]);
      });
}
3 Likes

I like it; it’s easy to understand. I’m getting the following error, though. I’m using your expression as is; only replacing "project-name" with "hello-haskell-flake".

$ nix build
warning: Git tree '/home/amy/github/hello-haskell-flake' is dirty
warning: creating lock file '/home/amy/github/hello-haskell-flake/flake.lock'
warning: Git tree '/home/amy/github/hello-haskell-flake' is dirty
error: attribute 'walloper' missing

       at /nix/store/l868qm2zjpl3bip6cx8kivpjf466fcs8-source/flake.nix:36:26:

           35|
           36|         defaultPackage = self.packages.${system}.walloper;
             |                          ^
           37|
(use '--show-trace' to show detailed location information)

Also, I would like to be able to use flakes as NixOS modules. I believe that just requires adding something like this (changing hello to the package name, of course).

      nixosModules.hello =
        { pkgs, ... }:
        {
          nixpkgs.overlays = [ self.overlay ];

          environment.systemPackages = [ pkgs.hello ];

          #systemd.services = { ... };
        };

I figured out that defaultPackage should have the name of the package I’m building, so I changed it as follows:

        defaultPackage = self.packages.${system}.hello-haskell-flake;

But I’m basically getting the same error, just with a different name.

$ nix build
warning: Git tree '/home/amy/github/hello-haskell-flake' is dirty
error: attribute 'hello-haskell-flake' missing

       at /nix/store/b6r6cxfhma6wvgging1g2kckg5v6yqfk-source/flake.nix:36:26:

           35|
           36|         defaultPackage = self.packages.${system}.hello-haskell-flake;
             |                          ^
           37|
(use '--show-trace' to show detailed location information)

Ah, damn it. That was an oversight on my side. The default package should relate to packages.pkg, i.e. in the example above it should be defaultPackage = self.packages.${system}.pkg;

The full, and now hopefully correct, file would be

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      with nixpkgs.legacyPackages.${system};
      let
        t = lib.trivial;
        hl = haskell.lib;

        name = "project-name";

        project = devTools: # [1]
          let addBuildTools = (t.flip hl.addBuildTools) devTools;
          in haskellPackages.developPackage {
            root = lib.sourceFilesBySuffices ./. [ ".cabal" ".hs" ];
            name = name;
            returnShellEnv = !(devTools == [ ]); # [2]

            modifier = (t.flip t.pipe) [
              addBuildTools
              hl.dontHaddock
              hl.enableStaticLibraries
              hl.justStaticExecutables
              hl.disableLibraryProfiling
              hl.disableExecutableProfiling
            ];
          };

      in {
        packages.pkg = project [ ]; # [3]

        defaultPackage = self.packages.${system}.pkg;

        devShell = project (with haskellPackages; [ # [4]
          cabal-fmt
          cabal-install
          haskell-language-server
          hlint
        ]);
      });
}

I hope that fixes your issue.

That does fix the error!

However, it seems to have the same issue as the default Haskell template.

 nix flake show github:mhwombat/hello-haskell-flake
github:mhwombat/hello-haskell-flake/408d723baba37be72b69c44488c6621d685bc385
├───defaultPackage
error: cannot build '/nix/store/faz0ibrbnfa0c8zskmxfvxirpw205b18-cabal2nix-hello-haskell-flake.drv' during evaluation because the option 'allow-import-from-derivation' is disabled
(use '--show-trace' to show detailed location information)

I really like the way you’ve written it, though. I find it easier to understand than the template.

I feel like that is more of an issue with flakes, rather than an issue with the default Haskell template:

https://github.com/NixOS/nix/pull/5253

You can see that there was a bunch of push-back from that, mostly from Haskellers.

I have a little more explanation here: "'allow-import-from-derivation' is disabled" error · Issue #34 · purenix-org/purenix · GitHub

@cdepillabout , I was intrigued by cabal2nixWithoutIFD. I understand it’s a proof-of-concept, but could it be a solution to this problem, at least for Haskell?

Sort of.

This is a big simplification, but the way I see it, there are two “camps” of people in the Nix community: Pro-IFD and Anti-IFD.

  • Pro-IFD: These are people that generally think IFD should be used for packaging things. Most Haskellers fall into this camp (given tools like haskell.nix and callCabal2nix), as well as users of the other FOO2nix tools.

    Using IFD can often really simplify your day-to-day maintenance of Nix code. I fall into this camp, and I personally feel like it is an anti-pattern to try to do Haskell develop with Nix and not rely on something like haskell.nix or callCabal2nix.

  • Anti-IFD: These are people that think Nix should try to evolve without relying on the IFD functionality. There are a bunch of good reasons for not allowing IFD. You see evidence of this mindset with Nixpkgs/Hydra not allowing IFD, and flakes not allowing IFD by default for things like nix flake show.

I’m not sure if there are many people trying to work on unifying these two camps. (Although one person that comes to mind is @Ericson2314 with a bunch of his recent RFCs: Pull requests · NixOS/rfcs · GitHub).

cabal2nixWithoutIFD (and more generally, PureNix) is an attempt to replicate most of the functionality of cabal2nix, but implemented entirely in Nix. The idea came from poetry2nix, which works basically the same. This type of approach gives you all the benefits of something like cabal2nix, but without requiring IFD.

Although, in practice, cabal2nix relies on Cabal-the-library, which is incredibly complex. Porting the necessary functionality from Cabal to cabal2nixWithoutIFD would be quite an undertaking. My guess is that cabal2nixWithoutIFD will forever stay a neat proof-of-concept, and never become a tool that is actually used.

(edit: The above explanation paints people as either being pro-IFD or anti-IFD, but in practice almost everyone sees the benefits of IFD, and understands the drawbacks. Most people are firmly in the middle.)

6 Likes

Thank you so much for that explanation! I’m very interested in this issue now.

I’m trying an approach that I expect will probably fail, but will be a good learning experience for me. I am using cabal build directly in a flake, with a configuration that tells it not to try to connect to the Internet. For the moment, I have a simple Haskell program with no dependencies on anything except base. I’m just curious to see how far I’ll get.

1 Like