-
Notifications
You must be signed in to change notification settings - Fork 237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Why is haskell.nix so complex (OR, how can we reduce it)? #1855
Comments
@L-as thanks for starting this discussion. Yes, we do want to slim it down as well. Adding all of
that is a valid question, though the answer is definitely not that we have a culture of working around upstream. IOG is in fact very involved in upstream GHC development. If you look at the patches, you'll see most of them are for 8.10, and that's effectively a dead compiler at this point. The likelihood of getting any significant 8.10.8 is virtually zero. Due to the changes between 8.10, and what's currently in HEAD of GHC, it's also non-trivial to upstream 8.10 fixes into HEAD, even though we'd like to it's a non-trivial time investment when 8.10 is (still) our production compiler :-/ We could probably drop a significant amount of logic, if we supported only Hadrian only GHCs, and dropped everything before. That does would make haskell.nix much less useful for a lot of projects still on 8.10. There is a fairly long discussion around GHCs stability here. A lot of complexity in haskell.nix comes from its ability to cross compile far beyond what stock haskell toolchains can do, due to the power of nixpkgs. If we rip all of that out, we can probably go back to the haskell infra in nixpkgs but would lose the component level granularity. I've also written something about how I see haskell.nix on reddit a few days ago. Regarding different granularity (module level), this is what I believe https://github.com/tweag/rules_haskell does for bezel, and what https://github.com/nmattia/snack tried to do. Am I willing to throw most of haskell.nix away if we can? Absolutely. I don't like complexity or code for that matter. If it can be done simpler, I'm all for it. If nix evolves to allow us to do that. By all means let's do that! I have no emotional attachment to haskell.nix. It's a tool to get a job done. And it does get that job done fairly well, despite its sharp edges. We can probably improve on a few things in haskell.nix, and especially on the complexity of IFDs. All of this is time intensive. Haskell.nix has been an iterative approach to continuously improve in a somewhat organic way towards new challenges. The most recent being support for custom hackages in the form of CHaP, and now also revision support for that. All of this sadly piles on complexity. haskell.nix has for the most part tried to defer complexity to other tools (e.g. let cabal solve the build plan; and use cabal's Setup.hs to configure/build packages and components). When possible it strives to not go down the NIH route. The compiler derivations are a slight departure from this, but mostly due to us not wanting to be reliant on nixpkgs providing the compiler we need, and not changing the expressions in ways that break haskell.nix. The idea here is that haskell.nix is orthogonal to nixpkgs. Lastly, GHC (and cabal), have (sadly) quite a bit of knowledge about linking necessities. The fact that it's already replicated in cabal, and cabal doesn't always just defer to GHC for this, can lead to complications, and replicating that in yet another layer looks (to me), like not an improvement, but making it even more brittle then all this already is. If you have some ideas where we could easily drop some code/complexity from haskell.nix, please do not hesitate to propose doing that! |
This was the primary motivation for my organization to move from upstream Nixpkgs' Haskell machinery to Haskell.nix. Moritz, Hamish, and all of the other contributors have made a massive investment in ensuring that native support for aarch64, x86-to-aarch64 cross support, and mingw-w64 cross support work well. For whatever reason, upstream Nixpkgs seems to not care as much about this functionality, and I often found it broken and struggled to get any fixes I had to make upstreamed. Haskell.nix's GHC patches are an important part of this improved support, especially since my organization is also begrudgingly still stuck on GHC 8.10.7. I am extremely grateful for this; a lot of my and my colleagues time used to be spent keeping our fork of upstream Nixpkgs' Haskell machinery doing what we wanted, now we just use Haskell.nix and it (mostly) just works. Another important benefit to mention here is that Haskell.nix provides a very nice (although scantly documented) library of Nix functions for its underlying machinery. This too was very valuable for me, since my organization has a perhaps unique approach to Haskell development. We have almost 100 internal (closed source) Haskell projects used across our company, and they all have to work together. To achieve this, we build everything in the company against a fixed stackage snapshot, to which we add this set of ~100 internal packages (plus a handful of version overrides to make certain hairy upstream packages work). We build all the Hoogle docs for this whole ecosystem, CI pushes it to an internal server so we can query it, and the whole setup provides a Debian-like dependency management model. I can add any other Haskell library made by anyone else in the company to my project without thinking about it; everything will be ABI-compatible because we're all using the same dependency versions. I only had to write about ~1000 Nix LOC to get all this working, using Haskell.nix as a library of sorts.
Doing everything you mention here in a way that's guaranteed to be reproducible is a tall order. In fact, I'm surprised that it only takes ~20,000 Nix LOC to make something that works as well as Haskell.nix does. Upstream Nixpkgs' toolchain bootstrapping machinery is also quite complex, but you're right to say that Haskell.nix is more complex than this. This extra complexity comes from these sources:
This last point is, in my opinion, the least sane, but it's not a problem with Haskell.nix. A big problem with TH is that it makes an assumption that's very, very difficult for cross-compilation to deal with: TH code is assumed to run on the target platform, not the host platform. Haskell.nix deals with this in an ingenious (or harebrained, depending on your perspective) way: it runs a remote iserv in QEMU. In fact, if I remember correctly, Moritz was the one who made remote iserv possible in GHC in the first place.
Where should we be looking for inspiration? Who has a toolchain that provides all of the guarantees of Nix, with the convenience of Haskell.nix (I don't have to write any Nix once everything's setup, IFD takes care of everything), but with more fun? |
Just a minor correction: I didn't come up with What I did was only fix some word size assumptions, such that cross word size communication did work; and then build iserv-proxy around it, such that it supports cross compilation better. haskell.nix really only ties this all together with a lot of automation. Ultimately haskell.nix wouldn't exist without all the contributors over the years who have helped! |
None at all in the Haskell ecosystem! For what it's worth, having to write Nix code is OK IMO, but it should be easy to understand. A simpler system would fix most of the problems I have I think. |
@L-as here is were we two disagree I guess:
Lastly, yes we try to merge as much stuff upstream, but this is hard for many reasons. Production codebases staying on an effectively End Of Line compiler. And ultimately also just the time to do it :( Here is what I think haskell.nix should be: {
description = "simple haskell.nix flake";
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 }: flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; inherit (haskellNix) config; overlays = [ haskellNix.overlay ]; };
myHsPkgs = pkgs.haskell-nix.project { src = ./.; compiler-nix-name = "ghc8107"; };
in rec {
packages.myComponent = myHsPkgs.flake.packages."<pkg>:<type>:<component>";
hydraJobs = packages;
});
} that's about as much nix code as I'm willing to have for a haskell project. Now if we add in packaging, and cross compilation, it will need a bit more. But that's mostly |
Wrt. GHC, if we do the base proposal by Ericson, it would likely be heavily aleviated. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
haskell/cabal#9089 I think will help a lot with this. Cabal doing something similar, even if it is not exactly the same, will excise a lot of bugs. Likewise, I really hope we could build GHC with Haskell.nix without Hadrian too. Such dogfooding would also excise a lot of bugs. |
@Ericson2314 Happy to chat in private if you like but I think there's lots to do, both in cabal and in haskell.nix. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
cloc . --exclude-dir=materialized
giveshaskell.nix is a Nix framework only for Haskell, yet it's so complex. ~20k lines of Nix, ~50k lines of patches, etc.
There's a reason for every single line of code (obviously), yet, looking at it from the big picture, it's not clear
why we need so much code to compile Haskell using GHC.
The patches are obviously not needed, every single patch that is still necessary should be upstreamed.
Perhaps there was a culture of working around upstream rather than attempting to fix upstream?
Without being deeply intimate with most of the codebase, could haskell.nix be simpler if we
Other projects for reference:
My motivation is that it's quite frustrating to deal with building Haskell code. There's no good solution really. The above two projects don't fix the problem either, they use the code from Nixpkgs which is quite complex too.
If we think about the problem from first principles, we want to be able to compile Haskell modules, that depends on other Haskell modules (perhaps from the same project or another project), and eventually turn that into either a (shared) library or executable.
That library or executable might need other dependencies, some of which are necessary because of Haskell dependencies (c-bits, pkg-config, etc.). We additionally want the (obvious) ability to cross-compile.
Cross-compilation is mainly complicated because GHC only supports targetting one platform at a time (IIRC) like GCC, etc.. Nixpkgs has also had to workaround this issue resulting in complicated code for GCC, resulting in the build-host-target platform triple, when really it ought to be just build-host.
If rather than using Nix to orchestrate Cabal and GHC, could we not attempt to make Cabal nix-native as also mentioned in NixOS/rfcs#134 (comment) (this might necessarily involve using ca-derivations and dynamic-derivations)?
In the end, I honestly don't think there's much code to reuse from the current haskell.nix, but I doubt anyone is satisfied with the status quo. It is not fun to use haskell.nix, or to work with Haskell, because of the tooling.
The text was updated successfully, but these errors were encountered: