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.