Build Haskell Completely From Nix

Hey all. Bold idea I’m piecing together, and just wanted ideas and input on how to do it.

I’m no Nix expert, but I know there’s certainly a ton of capability behind it… as soon as one understands it. But from what I DO see, we have all the capabilities to build Haskell applications from just Nix, and Nix has all the pieces but not quite assembled yet.

I should also clarify some things: I mean doing this without Cabal or Stack. No *.cabal, no stack.yaml, just with Haskell files and GHC from Nix. Also, not with Flakes. I’d ideally want a VCS-pulled build to be achievable by only installing Nix, running nix-build, and configuring nothing in-between. Or all development dependencies available in a nix-shell, so a developer for such an application doesn’t need to install anything besides Nix and an IDE (or optionally configure nix-shell with vscode inside it)

But why? Why not. Nix has a lot of Haskell packages available (appropriately from the haskellPackages attr), and of course has building capabilities, so why defer to Cabal and Stack to also manage dependencies and build instructions?

In looking into this, I found this article on building Haskell using Make, which looks like an absolute pain, but it does give a little direction towards just nix-building an Haskell application. We just have to pass things into GHC in the buildPhase of a derivation. Not nearly as graceful, but this is sorta what Cabal is doing under the hood (sans Nix), no?

So for a single ./app/Main.hs file, this works:

let
    rootDir = <path to your project root from this file>;
    testConfig = {
        name = "TestHaskellApp";
        version = "0.0.1";
        main = "${rootDir}/app/Main.hs";
    };
in
pkgs.stdenv.mkDerivation {
  pname = testConfig.name;
  version = testConfig.version;
  src = ./.;
  buildInputs = [
    pkgs.nix
    pkgs.nixfmt
    pkgs.bash
    pkgs.curl
    pkgs.toybox
    pkgs.man
    pkgs.ghc
  ]; # A lot of this is just shared with a mkShell func in shell.nix. This probably only needs ghc.
  buildPhase = ''
    mkdir -p ./.tmp
    ${pkgs.ghc}/bin/ghc \
      -outputdir ./.tmp/ \
      -hidir ./.tmp/ \
      -odir ./.tmp/ \
      -dumpdir ./.tmp/ \
      -tmpdir ./.tmp/ \
      -o ./.tmp/${testConfig.name} \
      --make ${testConfig.main}
    cp ./.tmp/${testConfig.name} $out
    rm -r ./.tmp/
  '';
}

But something like this could easily become a nix function, where you give it basically all the info you’d give a .cabal file right now: name, version, build-depends, local-depends, main-is, all that sort of stuff.

One thing I’m looking to understand is how I can turn a Nixpkgs-Haskell-package into an input into a nix-only build. Data.Aeson, for instance. Aeson exists from <nixpkgs>.haskellPackages.aeson, now just need the appropriate bits to feed into the GHC build (*.hs, *.hi, *.o, or something else?). I also do have to piece together local dependencies, but I suspect that’s much easier than the aforementioned bit and I’ll probably figure it out. I also need to figure out using alternate preludes from just GHC to incorporate that in there.

1 Like

I’m not that familiar with it, but in case you haven’t seen it, there’s snack, which is at least somewhat similar to what you’re trying to do:

Maybe you’d be able to take some inspiration from this.

2 Likes

While your nix-only approach seems plausible, and maybe in some cases it is even desirable, building stuff in such way loses an important aspect of stack and cabal - they are crossplatform. So if you ever want to build things on windows, you still need your *.cabal (or package.yaml if you use hpack). And here go all our efforts - we still need to maintain both these files and the nix build script.

Nix as a native tool on windows is not happening anytime soon, of that I am sure.

2 Likes