162 lines
5.6 KiB
Markdown
162 lines
5.6 KiB
Markdown
+++
|
|
date = "2022-07-21"
|
|
draft = false
|
|
path = "/blog/nix-hls-for-deps"
|
|
tags = ["haskell", "nix"]
|
|
title = "Setting up dev environments with Haskell Language Server and Nix"
|
|
+++
|
|
|
|
A problem I sometimes have is dealing with a bunch of independently maintained
|
|
but very interdependent libraries, for which I would like proper IDE
|
|
functionality cross-project. Another problem I sometimes have is that I want to
|
|
make a contribution to some library but setting up an IDE for it is a pain in
|
|
the neck.
|
|
|
|
# YOLO method (putting it all in a workspace with your app and not telling Nix) {#yolo-method}
|
|
|
|
Often it is easiest to partially bypass Nix while doing development on
|
|
dependencies. This means that Nix provides the compiler but the dependency
|
|
you're working on *and everything below it* is then owned by Cabal.
|
|
|
|
This is achieved by adding the dependencies into `packages:` inside
|
|
`cabal.project` or `cabal.project.local`. Then they are part of the workspace,
|
|
and cabal can do partial recompilation on them as desired.
|
|
|
|
For instance, if your application depends on persistent and esqueleto and you
|
|
want to hack on persistent, you would have to do the following:
|
|
|
|
* Clone persistent and esqueleto into subdirectories under the app (no need to
|
|
make them submodules)
|
|
* Put something like the following into `cabal.project` or
|
|
`cabal.project.local`:
|
|
|
|
```cabalproject
|
|
packages:
|
|
esqueleto
|
|
persistent/persistent
|
|
persistent/persistent-*
|
|
```
|
|
|
|
Sometimes, there will be an error that says that some files are missing. This
|
|
is because Cabal decided to try to rebuild the Nix version of the package in
|
|
question, and could not find source code. The solution to this is to clone the
|
|
package with such an error and add it to `cabal.project` as before.
|
|
|
|
You can set up HLS by running `gen-hie` (from the `implicit-hie` package) and
|
|
then put the results into `hie.yaml`.
|
|
|
|
Sometimes HLS won't have loaded up the dependency yet, for some reason and
|
|
might have some weird import errors. This is solved by opening a file from that
|
|
dependency.
|
|
|
|
This is kind of a hack, but this method works really really well for large
|
|
codebases as it preserves the ability to do incremental builds on changes of
|
|
dependencies, while also mostly using dependencies from Nix caches.
|
|
|
|
# Standalone workspace
|
|
|
|
It's possible to create a workspace to work on two or more libraries. This
|
|
leverages Nix relatively nicely: pulls in dependencies as expected, and so on.
|
|
|
|
Let's do this to Persistent and Esqueleto, two database libraries. As I
|
|
later learned, this was rather annoying due to having a large number of
|
|
packages.
|
|
|
|
First, make a directory to do this in, and clone the dependencies into this
|
|
directory. It's important this *not* be a git repo, because Nix gets confused
|
|
with nested git repos, and it would be silly to have to set up submodules to do
|
|
a little hacking ([apparently they don't work at all anyway][submodules-oops]).
|
|
|
|
[submodules-oops]: https://github.com/NixOS/nix/pull/5284
|
|
|
|
```sh
|
|
$ mkdir deps && cd deps
|
|
$ gh repo clone yesodweb/persistent
|
|
$ gh repo clone bitemyapp/esqueleto
|
|
```
|
|
|
|
Then initialize the flake with just a `flake.nix` with `nix flake init`, with a
|
|
[flake template]:
|
|
|
|
```
|
|
$ nix flake init -t github:lf-/flake-templates#haskell.flakeNix
|
|
```
|
|
|
|
[flake template]: https://github.com/lf-/flake-templates/tree/main/haskell
|
|
|
|
Make a `cabal.project` so cabal considers it a multi package project properly:
|
|
|
|
```cabal
|
|
packages:
|
|
esqueleto
|
|
persistent/persistent
|
|
persistent/persistent-*
|
|
```
|
|
|
|
Edit the `flake.nix` to add both of the packages in the workspace to the
|
|
overlay and to the `packages` of the `devShell`:
|
|
|
|
```nix
|
|
{
|
|
# ...
|
|
outputs = { self, nixpkgs, flake-utils }:
|
|
let
|
|
# ...
|
|
out = system:
|
|
let
|
|
# ...
|
|
in
|
|
{
|
|
# ...
|
|
devShells.default =
|
|
let
|
|
haskellPackages = pkgs.haskell.packages.${ghcVer};
|
|
in
|
|
haskellPackages.shellFor {
|
|
packages = p: with pkgs.haskell.packages.${ghcVer}; [
|
|
persistent
|
|
esqueleto
|
|
persistent-qq
|
|
persistent-template
|
|
persistent-mysql
|
|
persistent-test
|
|
persistent-postgresql
|
|
persistent-sqlite
|
|
];
|
|
withHoogle = true;
|
|
buildInputs = with haskellPackages; [
|
|
haskell-language-server
|
|
fourmolu
|
|
ghcid
|
|
cabal-install
|
|
];
|
|
};
|
|
};
|
|
in
|
|
flake-utils.lib.eachDefaultSystem out // {
|
|
# this stuff is *not* per-system
|
|
overlays = {
|
|
default = makeHaskellOverlay (
|
|
prev: hfinal: hprev:
|
|
let
|
|
hlib = prev.haskell.lib;
|
|
# this uses overrideSrc because of funniness with the deps of
|
|
# persistent-sqlite
|
|
makeLocal = n: hlib.overrideSrc hprev.${n} { src = ./persistent + "/${n}"; };
|
|
in
|
|
{
|
|
persistent = makeLocal "persistent";
|
|
persistent-test = makeLocal "persistent-test";
|
|
persistent-sqlite = makeLocal "persistent-sqlite";
|
|
persistent-postgresql = makeLocal "persistent-postgresql";
|
|
persistent-mysql = makeLocal "persistent-mysql";
|
|
persistent-qq = makeLocal "persistent-qq";
|
|
persistent-template = makeLocal "persistent-template";
|
|
esqueleto = hprev.callCabal2nix "esqueleto" ./esqueleto/esqueleto.cabal {};
|
|
}
|
|
);
|
|
};
|
|
};
|
|
}
|
|
```
|
|
|