Help understanding templates#haskell-nix

I don’t really understand how the default haskell.nix template works. There are a few different factors conspiring to make it incomprehensible:

flake.nix:

{
  # This is a template created by `hix init`
  inputs.haskellNix.url = "github:input-output-hk/haskell.nix";
  inputs.nixpkgs.follows = "haskellNix/nixpkgs-unstable";
  inputs.flake-utils.url = "github:numtide/flake-utils";
  outputs = { self, nixpkgs, flake-utils, haskellNix }:
    let
      supportedSystems = [
        "x86_64-linux"
        "x86_64-darwin"
        "aarch64-linux"
        "aarch64-darwin"
      ];
    in
      flake-utils.lib.eachSystem supportedSystems (system:
      let
        overlays = [ haskellNix.overlay
          (final: prev: {
            hixProject =
              final.haskell-nix.hix.project {
                src = ./.;
                evalSystem = "x86_64-linux";
              };
          })
        ];
        pkgs = import nixpkgs { inherit system overlays; inherit (haskellNix) config; };
        flake = pkgs.hixProject.flake {};
      in flake // {
        legacyPackages = pkgs;

        packages.default = flake.packages."hello:exe:hello";
      });

  # --- Flake Local Nix Configuration ----------------------------
  nixConfig = {
    # This sets the flake to use the IOG nix cache.
    # Nix should ask for permission before using it,
    # but remove it here if you do not want it to.
    extra-substituters = ["https://cache.iog.io"];
    extra-trusted-public-keys = ["hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ="];
    allow-import-from-derivation = "true";
  };
}
  1. I initialize it using nix flake init --template templates#haskell-nix --impure. However, in the flake.nix, it says
# This is a template created by `hix init`

This is a lie! I have never heard of hix, don’t have it installed, and certainly didn’t use it to initialise this template. It seems as though hix is some kind of command line tool. I don’t understand why it’s being referenced in the template - it feels like a leaky abstraction. Do I need to now go and learn how to use this command line tool in order to understand how to use the flake I just initialized?

  1. I don’t know where to look for documentation.
        overlays = [ haskellNix.overlay
          (final: prev: {
            hixProject =
              final.haskell-nix.hix.project {
                src = ./.;
                evalSystem = "x86_64-linux";
              };
          })
        ];

I’m a bit hazy on how overlays work, so I don’t entirely understand what final.haskell-nix is or how it relates to haskellNix. It seems like the template is using a function haskell-nix.hix.project. Where can I find documentation on this? The haskell.nix library reference doesn’t include any information on a hix attribute, although it does mention a project, is that the same thing? How would I know if it were?

  1. nix flake show doesn’t work.
⮞ nix flake show
path:/home/james/tmp/haskell-nix-template?lastModified=1690453162&narHash=sha256-ali7UKFmjtNSlTOQyvuduWharsgmfKtDWKsbxnY%2f+C0=
trace: No index state specified for haskell-project, using the latest index state that we know about (2023-03-01T00:00:00Z)!
error: cannot build '/nix/store/6kjv10ghqs2jwfbq6zlszp64i21xb9is-haskell-project-plan-to-nix-pkgs.drv' during evaluation because the option 'allow-import-from-derivation' is disabled
(use '--show-trace' to show detailed location information)

It seems as though there are some github issues relating to this (haskell.nix/issues/1711, nix/issues/4265)

The actual impetus behind this post was that I couldn’t figure out how to get the flake to provide a haskell library as well as an executable. I don’t really understand where the “hello:exe:hello” string comes from, or what its syntax is - what strings are valid to put between the colons. I tried the following:

⮞ git diff
diff --git a/flake.nix b/flake.nix
index d2b07fc..d96fb9a 100644
--- a/flake.nix
+++ b/flake.nix
@@ -29,6 +29,7 @@
         legacyPackages = pkgs;

         packages.default = flake.packages."hello:exe:hello";
+        packages.lib = flake.packages."hello:lib:hello-lib";
       });

   # --- Flake Local Nix Configuration ----------------------------
diff --git a/hello.cabal b/hello.cabal
index 0b7f34e..794af3f 100644
--- a/hello.cabal
+++ b/hello.cabal
@@ -14,7 +14,8 @@ stability: stable
 homepage: http://www.haskell.org/hello/
 synopsis: Hello World, an example package
 category: Console, Text
-cabal-version: >= 1.6
+-- need this because "Internal libraries only supported with per-component builds."
+cabal-version: >= 1.8
 build-type: Simple

 Description:
@@ -37,3 +38,9 @@ executable hello

   if flag(threaded)
      ghc-options: -threaded
+
+library hello-lib
+       exposed-modules: Lib
+       build-depends: base >= 4.2 && < 5
+       hs-source-dirs: src
+

Then nix build executes fine, but the only file in result is bin/hello - no library. The reason I was trying to run nix flake show was to try to understand what the provided outputs are - but no joy. How would I find that out, and how do I add a library to the outputs?

Overall, I think this flake.nix could do with a few more explanatory comments and documentation links for newer users like myself.

It is indeed a leaky abstraction. You do not need to learn that tool, it is just a 100 lines long bash-script that uses that template internally. hix init is basically just an alias for nix flake init --template templates#haskell-nix --impure.

2: I don’t know much about that, but I assume it is the same thing as that project. Here
is the source code for that field in the overlay.

3: Yes, indeed. I believe nix flake show --allow-import-from-derivation should work. If not you can use

nix repl
> :lf .

and then use tab-completion to browse the flake. I also believe that once you’ve built it, it should have the IFD result cached, so nix flake show should work out of the box, but I’m not sure about that.

This is cabal (or stack?) syntax. It is documented here for cabal and here for stack. I don’t know if there is a way to get cabal to list which are available, but stack can do so with the hidden stack ide targets command. You first need to run stack init to create a stack.yaml file for that to work.

1 Like

But I believe the real solution to getting nix flake show to work well is to materialize the derivation, which also makes things faster.

My code looked like this after following the instructions

              final.haskell-nix.hix.project {
                src = ./.;
                evalSystem = "x86_64-linux";
                index-state = "2023-03-01T00:00:00Z";
                materialized = ./hello.materialized;
              };

I then got a message like

 Materialized nix used for haskell-project-plan-to-nix-pkgs is missing. To fix check you are in the right directory and run: /nix/store/n9l5jd1dqs9zi0mk9fd8fv0dcy0lb4vb-generateMaterialized hello.materialized

Following those instructions

/nix/store/n9l5jd1dqs9zi0mk9fd8fv0dcy0lb4vb-generateMaterialized hello.materialized

I get a hello.materialized directory with nix files and nix flake show evaluates quickly.


As to why you don’t get a library as intended, the issue is in your cabal file.

Changing

library hello-lib
       exposed-modules: Lib
       build-depends: base >= 4.2 && < 5
       hs-source-dirs: src

to

library
       exposed-modules: Lib
       build-depends: base >= 4.2 && < 5
       hs-source-dirs: src

it will work as intended. As the error message you got before indicated, what you made is an internal library. For historical reasons a cabal file can export multiple executables, but only one library. If you want multiple libraries, you’ll need multiple cabal files (and optionally a cabal.project or stack.yaml file to link them together).

1 Like

I really appreciate the comprehensive answer!

I also discovered today that using haskell-nix.hix.project instead of haskell-nix.project makes it use the nix/hix.nix file that was created by the template to load some additional settings, so be aware of that.

1 Like