└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Setting up a Haskell development environment with Nix 2 | 3 | - [Introducing Nix](#introducing-nix) 4 | - [nix-channel](#nix-channel) 5 | - [nix search](#nix-search) 6 | - [First using nix-env](#first-using-nix-env) 7 | - [Alternatively, using nix-shell](#alternatively-using-nix-shell) 8 | - [A simple Haskell environment](#a-simple-haskell-environment) 9 | - [Introducing Direnv and Lorri](#introducing-direnv-and-lorri) 10 | - [Direnv](#direnv) 11 | - [Lorri](#lorri) 12 | - [Bonus: Setting up Ghcide to work on our project](#bonus-setting-up-ghcide-to-work-on-our-project) 13 | 14 | We'll explore how to quickly setup a reliable environment to hack on our Haskell projects using the **Nix** language and its **Nixpkgs** infrastructure. 15 | As we proceed, I'll introduce some auxiliary tools we can add to our stack to streamline our workflow even further. Let's dive in! 16 | 17 | ## Introducing Nix 18 | 19 | As stated on the project's website, **\"Nix is a powerful package manager for Linux and other Unix systems that makes package management reliable and reproducible\"**. 20 | What's of interest to us here is that Nix is a tool which allows you to setup self-contained environments for all your development needs. 21 | Think of it as a more lightweight and less cumbersome alternative to Docker. 22 | 23 | Ok now let's first install it and then I'll explain how it works. 24 | 25 | The installation procedure is pretty straightforward, just paste the following in your terminal and you'll be all setup. 26 | ~~~bash 27 | curl -L https://nixos.org/nix/install | sh 28 | ~~~ 29 | You may need to reload your profile to get access to the nix commands. 30 | 31 | I'll now introduce the commands we'll use for the matter at hand here. 32 | 33 | ### nix-channel 34 | Using this command should print something like the following in your terminal. 35 | ~~~bash 36 | [romain@clevo-N141ZU:~]$ nix-channel --list 37 | nixpkgs https://nixos.org/channels/nixpkgs-unstable 38 | ~~~ 39 | What this means is that your nix installation is currently subscribed to the **nixpkgs-unstable** channel. 40 | A channel is tied to a git branch of the [Nixpkgs GitHub repository](https://github.com/NixOS/nixpkgs). 41 | Conceptually, a channel is a set of package as they are defined by the GitHub repository as of the commit it points to, which makes exploring the source for a given package convenient once you've understood how to navigate the GitHub repository. 42 | For example you can see what's the state of the **nixpkgs-unstable** channel by browsing the repository at this url [https://github.com/NixOS/nixpkgs/tree/nixpkgs-unstable](https://github.com/NixOS/nixpkgs/tree/nixpkgs-unstable).\ 43 | Beware though, your local nix installation isn't automatically synced with GitHub, you should use the command **nix-channel --update** to bring it up to date, somewhat akin to what **apt update** would do on Debian based distributions. 44 | 45 | ### nix search 46 | 47 | Now that you understand the purpose of nix channels, you'll surely want to search for available packages in a convenient manner. 48 | That's what the **nix search** command brings to the table. Let's say you'd like to install GHC, the **Glasgow Haskell Compiler**, on your machine.\ 49 | To search for the package, you'd type the following in your terminal: 50 | 51 | ~~~bash 52 | [romain@clevo-N141ZU:~]$ nix search ghc 53 | warning: using cached results; pass '-u' to update the cache 54 | * nixpkgs.ghc (ghc) 55 | The Glasgow Haskell Compiler 56 | * nixpkgs.ghcid (ghcid) 57 | GHCi based bare bones IDE 58 | * nixpkgs.vimPlugins.ghc-mod-vim (vimplugin-ghcmod-vim) 59 | * nixpkgs.vimPlugins.ghcid (vimplugin-ghcid) 60 | * nixpkgs.vimPlugins.ghcmod (vimplugin-ghcmod-vim) 61 | * nixpkgs.vimPlugins.ghcmod-vim (vimplugin-ghcmod-vim) 62 | * nixpkgs.vimPlugins.neco-ghc (vimplugin-neco-ghc) 63 | * nixpkgs.vimPlugins.necoGhc (vimplugin-neco-ghc) 64 | ~~~ 65 | As expected, the first result is the one of interest to us. 66 | 67 | I'll now demonstrate two ways in which we can get to play with this package. 68 | 69 | ### First using nix-env 70 | 71 | You can install GHC using the **nix-env -i** command. See below: 72 | ~~~bash 73 | [romain@clevo-N141ZU:~]$ nix-env -i ghc 74 | installing 'ghc-8.8.3' 75 | *** Elided output for brievity *** 76 | building '/nix/store/b4p2sf432qyfr91ixz9xnl9hw6hqvg9p-user-environment.drv'... 77 | created 52 symlinks in user environment 78 | ~~~ 79 | 80 | And sure enough we now have access to the GHC binary: 81 | ~~~bash 82 | [romain@clevo-N141ZU:~]$ whereis ghc 83 | ghc: /nix/store/f6j4lqvx3zmm05wqmpi3867srkghd4vv-user-environment/bin/ghc 84 | 85 | [romain@clevo-N141ZU:~]$ ghc --version 86 | The Glorious Glasgow Haskell Compilation System, version 8.8.3 87 | ~~~ 88 | 89 | Now you may wonder what's with the ugly paths given by the **whereis** command. It's time to take a break to explain how nix works under the hood. 90 | Nix uses a central store located at **/nix/store** where you'll find every package in use by your current installation. 91 | The files you'll find there are named like the following **0ywc4dlk8159rj7q5fhkdvm2xrjkfxy3-ghc-8.8.3** where **0ywc4dlk8159rj7q5fhkdvm2xrjkfxy3** is a hash representing every input involved in the build of that version of the package.\ 92 | You can then have multiple versions of the same package (even of the same version of the package depending on its input) installed on your machine at the same time. 93 | Those packages aren't in scope by default. What makes those available to you is the user profile Nix sets up with a clever usage of symbolic links. 94 | 95 | For example, on my machine, we can see that the **.nix-profile** file present in my home directory is a symbolic link pointing to some directory in **/nix/var**. 96 | Let's follow the trail to see where this leads us. 97 | ~~~bash 98 | [romain@clevo-N141ZU:~]$ readlink .nix-profile 99 | /nix/var/nix/profiles/per-user/romain/profile 100 | ~~~ 101 | The **.nix-profile** entry points to another symlink in the nix profiles directory. 102 | ~~~bash 103 | [romain@clevo-N141ZU:~]$ readlink /nix/var/nix/profiles/per-user/romain/profile 104 | profile-71-link 105 | ~~~ 106 | Which itself points to **profile-71-link**, aptly named because it's the 71th version of my user environment. A new version being built whenever you install, update or remove a package. 107 | ~~~bash 108 | [romain@clevo-N141ZU:~]$ readlink /nix/var/nix/profiles/per-user/romain/profile-71-link 109 | /nix/store/f6j4lqvx3zmm05wqmpi3867srkghd4vv-user-environment 110 | ~~~ 111 | This then points to our current enviroment in the store. 112 | ~~~bash 113 | [romain@clevo-N141ZU:~]$ ll /nix/store/f6j4lqvx3zmm05wqmpi3867srkghd4vv-user-environment 114 | total 20 115 | dr-xr-xr-x 2 root root 4096 1 janv. 1970 bin 116 | lrwxrwxrwx 1 root root 57 1 janv. 1970 lib -> /nix/store/yzndcg3yq2iaps4zjp8wc4fwwpi9b1pj-ghc-8.8.3/lib 117 | lrwxrwxrwx 1 root root 66 1 janv. 1970 libexec -> /nix/store/yv86xmr68ssdq2ba0yy4zd05qxpvbcf1-yarn2nix-1.0.0/libexec 118 | lrwxrwxrwx 1 root root 60 1 janv. 1970 manifest.nix -> /nix/store/lmsf8qj1zr4fi0aylk6jh9qv4q84x81i-env-manifest.nix 119 | dr-xr-xr-x 6 root root 4096 1 janv. 1970 share 120 | lrwxrwxrwx 1 root root 67 1 janv. 1970 tarballs -> /nix/store/yv86xmr68ssdq2ba0yy4zd05qxpvbcf1-yarn2nix-1.0.0/tarballs 121 | ~~~ 122 | And, at last, we get to see where the GHC binary is accessed from. 123 | ~~~bash 124 | [romain@clevo-N141ZU:~]$ ll /nix/store/f6j4lqvx3zmm05wqmpi3867srkghd4vv-user-environment/bin/ 125 | total 116 126 | lrwxrwxrwx 1 root root 61 1 janv. 1970 ghc -> /nix/store/yzndcg3yq2iaps4zjp8wc4fwwpi9b1pj-ghc-8.8.3/bin/ghc 127 | lrwxrwxrwx 1 root root 67 1 janv. 1970 ghc-8.8.3 -> /nix/store/yzndcg3yq2iaps4zjp8wc4fwwpi9b1pj-ghc-8.8.3/bin/ghc-8.8.3 128 | lrwxrwxrwx 1 root root 62 1 janv. 1970 ghci -> /nix/store/yzndcg3yq2iaps4zjp8wc4fwwpi9b1pj-ghc-8.8.3/bin/ghci 129 | lrwxrwxrwx 1 root root 68 1 janv. 1970 ghci-8.8.3 -> /nix/store/yzndcg3yq2iaps4zjp8wc4fwwpi9b1pj-ghc-8.8.3/bin/ghci-8.8.3 130 | lrwxrwxrwx 1 root root 65 1 janv. 1970 ghc-pkg -> /nix/store/yzndcg3yq2iaps4zjp8wc4fwwpi9b1pj-ghc-8.8.3/bin/ghc-pkg 131 | lrwxrwxrwx 1 root root 71 1 janv. 1970 ghc-pkg-8.8.3 -> /nix/store/yzndcg3yq2iaps4zjp8wc4fwwpi9b1pj-ghc-8.8.3/bin/ghc-pkg-8.8.3 132 | lrwxrwxrwx 1 root root 65 1 janv. 1970 haddock -> /nix/store/yzndcg3yq2iaps4zjp8wc4fwwpi9b1pj-ghc-8.8.3/bin/haddock 133 | lrwxrwxrwx 1 root root 75 1 janv. 1970 haddock-ghc-8.8.3 -> /nix/store/yzndcg3yq2iaps4zjp8wc4fwwpi9b1pj-ghc-8.8.3/bin/haddock-ghc-8.8.3 134 | lrwxrwxrwx 1 root root 63 1 janv. 1970 hp2ps -> /nix/store/yzndcg3yq2iaps4zjp8wc4fwwpi9b1pj-ghc-8.8.3/bin/hp2ps 135 | lrwxrwxrwx 1 root root 61 1 janv. 1970 hpc -> /nix/store/yzndcg3yq2iaps4zjp8wc4fwwpi9b1pj-ghc-8.8.3/bin/hpc 136 | lrwxrwxrwx 1 root root 64 1 janv. 1970 hsc2hs -> /nix/store/yzndcg3yq2iaps4zjp8wc4fwwpi9b1pj-ghc-8.8.3/bin/hsc2hs 137 | lrwxrwxrwx 1 root root 76 1 janv. 1970 markdown -> /nix/store/fngkm633j7ga5hg93amzbq1h058z4mxf-multimarkdown-4.7.1/bin/markdown 138 | lrwxrwxrwx 1 root root 80 1 janv. 1970 markdown.bat -> /nix/store/fngkm633j7ga5hg93amzbq1h058z4mxf-multimarkdown-4.7.1/bin/markdown.bat 139 | lrwxrwxrwx 1 root root 71 1 janv. 1970 mmd -> /nix/store/fngkm633j7ga5hg93amzbq1h058z4mxf-multimarkdown-4.7.1/bin/mmd 140 | lrwxrwxrwx 1 root root 75 1 janv. 1970 mmd2all -> /nix/store/fngkm633j7ga5hg93amzbq1h058z4mxf-multimarkdown-4.7.1/bin/mmd2all 141 | lrwxrwxrwx 1 root root 75 1 janv. 1970 mmd2odf -> /nix/store/fngkm633j7ga5hg93amzbq1h058z4mxf-multimarkdown-4.7.1/bin/mmd2odf 142 | lrwxrwxrwx 1 root root 76 1 janv. 1970 mmd2opml -> /nix/store/fngkm633j7ga5hg93amzbq1h058z4mxf-multimarkdown-4.7.1/bin/mmd2opml 143 | lrwxrwxrwx 1 root root 75 1 janv. 1970 mmd2pdf -> /nix/store/fngkm633j7ga5hg93amzbq1h058z4mxf-multimarkdown-4.7.1/bin/mmd2pdf 144 | lrwxrwxrwx 1 root root 75 1 janv. 1970 mmd2rtf -> /nix/store/fngkm633j7ga5hg93amzbq1h058z4mxf-multimarkdown-4.7.1/bin/mmd2rtf 145 | lrwxrwxrwx 1 root root 75 1 janv. 1970 mmd2tex -> /nix/store/fngkm633j7ga5hg93amzbq1h058z4mxf-multimarkdown-4.7.1/bin/mmd2tex 146 | lrwxrwxrwx 1 root root 81 1 janv. 1970 multimarkdown -> /nix/store/fngkm633j7ga5hg93amzbq1h058z4mxf-multimarkdown-4.7.1/bin/multimarkdown 147 | lrwxrwxrwx 1 root root 74 1 janv. 1970 ob -> /nix/store/gd99xxp41i5dllhpx341rxscgbyzlpqn-obelisk-command-0.8.0.0/bin/ob 148 | lrwxrwxrwx 1 root root 69 1 janv. 1970 patchelf -> /nix/store/58vavyggrv0s48l5fshif4c8vswlhp5x-patchelf-0.9/bin/patchelf 149 | lrwxrwxrwx 1 root root 64 1 janv. 1970 runghc -> /nix/store/yzndcg3yq2iaps4zjp8wc4fwwpi9b1pj-ghc-8.8.3/bin/runghc 150 | lrwxrwxrwx 1 root root 70 1 janv. 1970 runghc-8.8.3 -> /nix/store/yzndcg3yq2iaps4zjp8wc4fwwpi9b1pj-ghc-8.8.3/bin/runghc-8.8.3 151 | lrwxrwxrwx 1 root root 68 1 janv. 1970 runhaskell -> /nix/store/yzndcg3yq2iaps4zjp8wc4fwwpi9b1pj-ghc-8.8.3/bin/runhaskell 152 | lrwxrwxrwx 1 root root 61 1 janv. 1970 xev -> /nix/store/lzjswkiibs9yr6b47ir6h2vgbrnzp9sv-xev-1.2.3/bin/xev 153 | lrwxrwxrwx 1 root root 71 1 janv. 1970 yarn2nix -> /nix/store/yv86xmr68ssdq2ba0yy4zd05qxpvbcf1-yarn2nix-1.0.0/bin/yarn2nix 154 | lrwxrwxrwx 1 root root 64 1 janv. 1970 zola -> /nix/store/2m7llhrfgjqk064fz7pqhdcaz9x1rb59-zola-0.10.1/bin/zola 155 | ~~~ 156 | 157 | The other binaries you see here are packages I've installed in my environment using the **nix-env -i** method. 158 | You can list them using **nix-env -q**. 159 | 160 | ~~~bash 161 | [romain@clevo-N141ZU:~]$ nix-env -q 162 | ghc-8.8.3 163 | multimarkdown-4.7.1 164 | obelisk-command-0.8.0.0 165 | patchelf-0.9 166 | xev-1.2.3 167 | yarn2nix-1.0.0 168 | zola-0.10.1 169 | ~~~ 170 | 171 | To uninstall a package, use **nix-env -e**, we'll do this in order to demonstrate the second way of getting GHC in scope. 172 | ~~~bash 173 | [romain@clevo-N141ZU:~]$ nix-env -e ghc 174 | uninstalling 'ghc-8.8.3' 175 | ~~~ 176 | 177 | As a side note, if I was to install GHC again right now using **nix-env -i ghc**, it would be nearly instant as all the previously downloaded files are still present in the store. 178 | Nix just has to rebuild the user environment and set up the symlinks accordingly. 179 | 180 | Whenever you want to clean the nix store to free up some space on your machine, you can run **nix-collect-garbage -d** which will remove the files in the store which are not part of what's called a **garbage collection root**. Essentially, a **garbage collection root** is some symlink which points to packages in the nix store and which prevents them from being garbage collected. 181 | 182 | ### Alternatively, using nix-shell 183 | 184 | The Nix shell is using the same mechanics but is more intended to setup project environments or to test packages without installing them. This is what we'll use for our Haskell projects later. 185 | 186 | First a simple example of getting GHC into scope using **nix-shell -p** which essentially means ***get me into a shell with the following packages in scope***. 187 | ~~~bash 188 | [romain@clevo-N141ZU:~]$ ghc 189 | The program ‘ghc’ is currently not installed. You can install it by typing: 190 | nix-env -iA nixos.ghc 191 | 192 | [romain@clevo-N141ZU:~]$ nix-shell -p ghc 193 | 194 | [nix-shell:~]$ ghc 195 | ghc: no input files 196 | Usage: For basic information, try the `--help' option. 197 | 198 | [nix-shell:~]$ exit 199 | 200 | [romain@clevo-N141ZU:~]$ ghc 201 | The program ‘ghc’ is currently not installed. You can install it by typing: 202 | nix-env -iA nixos.ghc 203 | ~~~ 204 | Simple enough. 205 | Now what's of interest is that we can provide the packages we want to build a shell for using a nix file. That's what is commonly used in project development. 206 | When invoking **nix-shell**, Nix will look for a **shell.nix** or a **default.nix** file in this order. 207 | 208 | The canonical example of this as used in the community is building a shell for the **gnu hello** package, so let's do this. 209 | ~~~bash 210 | [romain@clevo-N141ZU:~]$ mkdir nix-shell-example && cd nix-shell-example 211 | ~~~ 212 | We'll create a **shell.nix** file with the following content. 213 | ~~~nix 214 | let 215 | pkgs = import {}; 216 | in 217 | pkgs.mkShell { 218 | buildInputs = [ 219 | pkgs.hello 220 | ]; 221 | } 222 | ~~~ 223 | Let's explain what we have here.\ 224 | First we import **\**, what this means is that we'll have access to our currently subscribed nix-channel set of packages, here **nixpkgs-unstable**, through the **pkgs** variable. 225 | Then we invoke **mkShell** which will build a shell environment for the packages given in **buildInputs**. 226 | That's the same as **nix-shell -p hello** but using declarative nix file. 227 | Here **pkgs.hello** is the same as you'd have found using **nix search hello**.\ 228 | Testing it with nix-shell gives us the expected result. 229 | ~~~bash 230 | [nix-shell:~/nix-shell-example]$ ll 231 | total 4 232 | -rw-r--r-- 1 romain users 92 juin 2 18:28 shell.nix 233 | 234 | [romain@clevo-N141ZU:~/nix-shell-example]$ nix-shell 235 | 236 | [nix-shell:~/nix-shell-example]$ hello 237 | Bonjour, le monde ! 238 | ~~~ 239 | There's one problem with our current setup though, that's the reliance on **\**, this isn't what we'd call a truly reproducible environment as it currently depends on the version of the nix channel currently in use on our machine. We'll see how to fix this with the Haskell example. 240 | 241 | ## A simple Haskell environment 242 | 243 | Ok, now that you should have a basic grasp of how Nix works, we'll see how to use it for Haskell development. 244 | 245 | We'll proceed with the following simple setup. 246 | 247 | ~~~bash 248 | [romain@clevo-N141ZU:~/Code/haskell-nix]$ tree . 249 | . 250 | ├── default.nix 251 | ├── .gitignore 252 | ├── haskell-nix.cabal 253 | ├── shell.nix 254 | └── src 255 | └── Main.hs 256 | 257 | 1 directory, 4 files 258 | ~~~ 259 | 260 | **haskell-nix.cabal** 261 | ~~~ 262 | cabal-version: >= 1.10 263 | name: haskell-nix 264 | version: 0.1.0 265 | build-type: Simple 266 | 267 | executable haskell-nix 268 | hs-source-dirs: src 269 | main-is: Main.hs 270 | default-language: Haskell2010 271 | build-depends: base 272 | ~~~ 273 | **Main.hs** 274 | ~~~haskell 275 | module Main where 276 | 277 | main :: IO () 278 | main = print "hello, world!" 279 | ~~~ 280 | Nothing too fancy here. 281 | 282 | **shell.nix** 283 | ~~~nix 284 | (import ./default.nix).shell 285 | ~~~ 286 | Now our shell.nix is merely here to access the shell attribute exposed by the default.nix which we'll now focus on. 287 | 288 | **default.nix** 289 | ~~~nix 290 | let 291 | 292 | nixpkgsRev = "0f5ce2fac0c7"; 293 | compilerVersion = "ghc865"; 294 | compilerSet = pkgs.haskell.packages."${compilerVersion}"; 295 | 296 | githubTarball = owner: repo: rev: 297 | builtins.fetchTarball { url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; }; 298 | 299 | pkgs = import (githubTarball "NixOS" "nixpkgs" nixpkgsRev) { inherit config; }; 300 | gitIgnore = pkgs.nix-gitignore.gitignoreSourcePure; 301 | 302 | config = { 303 | packageOverrides = super: let self = super.pkgs; in rec { 304 | haskell = super.haskell // { 305 | packageOverrides = self: super: { 306 | haskell-nix = super.callCabal2nix "haskell-nix" (gitIgnore [./.gitignore] ./.) {}; 307 | }; 308 | }; 309 | }; 310 | }; 311 | 312 | in { 313 | inherit pkgs; 314 | shell = compilerSet.shellFor { 315 | packages = p: [p.haskell-nix]; 316 | buildInputs = with pkgs; [ 317 | compilerSet.cabal-install 318 | ]; 319 | }; 320 | } 321 | 322 | ~~~ 323 | Now here's the meat of our example. Lets break it apart. 324 | 325 | ~~~nix 326 | nixpkgsRev = "0f5ce2fac0c7"; 327 | compilerVersion = "ghc865"; 328 | compilerSet = pkgs.haskell.packages."${compilerVersion}"; 329 | ~~~ 330 | We first introduce 3 completely arbitrary bindings. 331 | - **nixpkgsRev** here is bound to a specific commit of the the GitHub **nixpkgs** repository. This is how we achieve true reproducibility, by not depending on any nix channel and its current state. 332 | - **compilerVersion** is the version of ghc we want to use to build our package. 333 | - **compilerSet** is an alias we set to make our life easier in the remaining of the file. It points to the ghc version specific (ghc865 here) set of haskell packages in the nixpkgs repository. 334 | ~~~nix 335 | githubTarball = owner: repo: rev: 336 | builtins.fetchTarball { url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; }; 337 | ~~~ 338 | We then showcase how you can define functions in Nix as you would in your favorite language. Here, **githubTarball** is a function which, given a github owner, a github repository and a commit revision will fetch the tarball of the code from GitHub. 339 | 340 | ~~~nix 341 | pkgs = import (githubTarball "NixOS" "nixpkgs" nixpkgsRev) { inherit config; }; 342 | gitIgnore = pkgs.nix-gitignore.gitignoreSourcePure; 343 | 344 | config = { 345 | packageOverrides = super: let self = super.pkgs; in rec { 346 | haskell = super.haskell // { 347 | packageOverrides = self: super: { 348 | haskell-nix = super.callCabal2nix "haskell-nix" (gitIgnore [./.gitignore] ./.) {}; 349 | }; 350 | }; 351 | }; 352 | }; 353 | ~~~ 354 | Then you can see what differs from our previous shell example. Here we don't rely on the "magic" **\** but we are specific with which nixpkgs commit we want to build our environment from. Reproducibility again. 355 | 356 | You should also notice that we provide a specific **config** to our instantiation of pkgs. This is how we can override parts of it. Here we'll use it to add our **haskell-nix** package to the set of all haskell packages, as if it was initially a part of the package set.\ 357 | We also pull in the **gitignoreSourcePure** from the **nix-gitignore** package. This will allow us to tell Nix that the files indicated into our **.gitignore** should be excluded when building our package. 358 | 359 | Then we have the **config** binding, that's the same config that is passed to the import of nixpkgs a few lines above. 360 | The **packageOverrides** definition is a bit hairy here but that's the way to override the set of haskell packages of every version of ghc available.\ 361 | To introduce our **haskell-nix** package to the set, we rely on **callCabal2nix** which is a utility from the haskell nixpkgs infrastructure. It automatically translates our cabal file into a Nix derivation behind the scenes so that you don't have to worry about this.\ 362 | In its most simple form demonstrated here, you only have to specify the name of your package as defined in your cabal file and the path to where your cabal file is located. 363 | Here we wrap the path given with the **gitIgnore** which takes a list of **.gitignore** files as first argument. 364 | 365 | ~~~nix 366 | in { 367 | inherit pkgs; 368 | shell = compilerSet.shellFor { 369 | packages = p: [p.haskell-nix]; 370 | buildInputs = with pkgs; [ 371 | compilerSet.cabal-install 372 | ]; 373 | }; 374 | } 375 | ~~~ 376 | Now we get to the attributes exposed by our **default.nix** file, there are two of them: 377 | - **pkgs**, here **inherit pkgs;** is just another way of writing **pkgs = pkgs;** \. The key **pkgs** is what gets exposed, the value **pkgs** corresponds to the one defined in the **let** part of our file, basically the whole nixpkgs as defined from the commit **0f5ce2fac0c7** modulo addition of our **haskell-nix** package. 378 | - **shell** is what we'll use for our development purpose. The nixpkgs haskell infrastructure provides a **shellFor** utility which is essentially a **mkShell** that understands that it's actually used in a haskell context. You must provide it with an attribute set specifying the haskell packages for which you want to build a development environment for. 379 | To build our shell, we give it our freshly included **haskell-nix** package. The **p** here is a reference to the package set of our chosen ghc version. 380 | You can then use the **buildInputs** attribute to specify everything you need for your development shell. This can include any package of nixpkgs, not only haskell ones. For example, you could include **yarn** here if you're working on some javascript package. In our case, we'll include **cabal-install** since we need to use some of its facilities to hack on our project. 381 | 382 | Let's check if this works. 383 | 384 | ~~~bash 385 | [romain@clevo-N141ZU:~/Code/haskell-nix]$ nix-shell 386 | building '/nix/store/w0z0lil4lmhiiab2iiywi97v4j9gv5cm-cabal2nix-haskell-nix.drv'... 387 | installing 388 | 389 | [nix-shell:~/Code/haskell-nix]$ cabal run haskell-nix 390 | Warning: The package list for 'hackage.haskell.org' is 245 days old. 391 | Run 'cabal update' to get the latest list of available packages. 392 | Resolving dependencies... 393 | Build profile: -w ghc-8.6.5 -O1 394 | In order, the following will be built (use -v for more details): 395 | - haskell-nix-0.1.0 (exe:haskell-nix) (first run) 396 | Configuring executable 'haskell-nix' for haskell-nix-0.1.0.. 397 | Preprocessing executable 'haskell-nix' for haskell-nix-0.1.0.. 398 | Building executable 'haskell-nix' for haskell-nix-0.1.0.. 399 | [1 of 1] Compiling Main ( src/Main.hs, /home/romain/Code/haskell-nix/dist-newstyle/build/x86_64-linux/ghc-8.6.5/haskell-nix-0.1.0/x/haskell-nix/build/haskell-nix/haskell-nix-tmp/Main.o ) 400 | Linking /home/romain/Code/haskell-nix/dist-newstyle/build/x86_64-linux/ghc-8.6.5/haskell-nix-0.1.0/x/haskell-nix/build/haskell-nix/haskell-nix ... 401 | "hello, world!" 402 | ~~~ 403 | 404 | And sure enough this works. 405 | As you can see, cabal is warning me that my package list is greatly out of date here. 406 | You should not pay attention to this as Nix resolves packages through its own machinery. Here, we only use cabal for its building capabilities and not for package management. 407 | 408 | To build our package with nix, we'll introduce the **nix-build** command. If you don't pass it a **-Q** argument, it looks for a **default.nix** file in the current directory. 409 | We want to build our **haskell-nix** package which is now exposed through the overridden nixpkgs set so we'll point **nix-build** to it (you don't have to be in the shell to use **nix-build**). 410 | 411 | ~~~bash 412 | [romain@clevo-N141ZU:~/Code/haskell-nix]$ nix-build -A pkgs.haskell.packages.ghc865.haskell-nix 413 | these derivations will be built: 414 | /nix/store/kpasmmscjr2hzqrj5zizkkgdrn3r6jj1-haskell-nix-0.1.0.drv 415 | building '/nix/store/kpasmmscjr2hzqrj5zizkkgdrn3r6jj1-haskell-nix-0.1.0.drv'... 416 | setupCompilerEnvironmentPhase 417 | Build with /nix/store/89ln27rjz9xisxcfvvcbm43myd92y280-ghc-8.6.5. 418 | unpacking sources 419 | unpacking source archive /nix/store/a6wsbh4pp0nvhq2bribmm6p0yam0800a-haskell-nix 420 | source root is haskell-nix 421 | *** Elided output for brievity *** 422 | /nix/store/b9h0kd4907n790lig06cdvaawbkr0vl1-haskell-nix-0.1.0 423 | ~~~ 424 | The last line of output is the path in the nix store where our **haskell-nix** lives. 425 | We now also have a **result** in the current directory which points to it which allows us to conveniently test it. 426 | 427 | ~~~bash 428 | [romain@clevo-N141ZU:~/Code/haskell-nix]$ ./result/bin/haskell-nix 429 | "hello, world!" 430 | ~~~ 431 | That's all there is to it. 432 | We can now look how to further improve our setup. 433 | Right now, if you add a dependency in your cabal file, you'll have to exit the shell and reenter it. If you don't do this, cabal will try to fetch the new dependencies itself and bad things will happen so don't. 434 | 435 | ## Introducing Direnv and Lorri 436 | 437 | Those two will bring some nice quality of life improvements, especially when trying to integrate the various haskell IDEs in our development workflow. 438 | 439 | ### Direnv 440 | As stated on its website, **\"direnv is an extension for your shell. It augments existing shells with a new feature that can load and unload environment variables depending on the current directory\"**. To install direnv you can just use your new favorite package manager and run 441 | ~~~bash 442 | nix-env -i direnv 443 | ~~~ 444 | You then have to hook direnv into your shell. Instructions differ depending on your shell so i'll point you to [Direnv docs](https://direnv.net/docs/hook.html). 445 | To use it with our project, we need to add a **.envrc** at the root of our directory. Everytime you navigate to a directory, direnv will check if there's a **.envrc** file in it and proceed with building the environment it defines. 446 | Let's create this file and use the nix integration. 447 | ~~~ 448 | -- .envrc 449 | use_nix 450 | ~~~ 451 | For this to work, you first have to run **direnv allow** in the directory (and again anytime the **.envrc** is modified), that's a security measure to prevent arbitrary scripts from being ran without you first allowing them. 452 | That's it, direnv will now leverage nix-shell as you'd have done manually before to setup the environment. 453 | You don't have to enter the nix-shell explicitly anymore but you still have to run **direnv reload** anytime you modify your cabal file to let Nix bring the new dependency in scope. 454 | That's still a bit cumbersome so let's pull in... 455 | 456 | ### Lorri 457 | 458 | As stated on its [GitHub repository](https://github.com/target/lorri), **\"lorri is a nix-shell replacement for project development. lorri is based around fast direnv integration for robust CLI and editor integration\"**. To install it, simply run 459 | ~~~bash 460 | nix-env -i lorri 461 | ~~~ 462 | Then replace the content of your **.envrc** file with the following 463 | ~~~ 464 | -- .envrc 465 | eval "$(lorri direnv)" 466 | ~~~ 467 | For lorri to work, you have to start its daemon by invoking **lorri daemon** in a shell. You don't have to do it in the project directory as it's a system wide process. See [here](https://github.com/target/lorri/blob/master/contrib/daemon.md) if you want to start it automatically as a systemd service. 468 | Lorri will now continuously watch for changes in files referenced by your **shell.nix** file and reload the environment accordingly. A nice benefit of using lorri is that it will create garbage collection roots in your user directory **\~/.cache/lorri/gc\_roots**. What this means is that you won't lose all your project state when running **nix-collect-garbage** . This will essentially prevent you from having to download a massive amount of dependencies again when you get back to working on your project. On the flip side you have to purge those garbage roots from times to times if you don't want your machine to get cluttered. 469 | 470 | 471 | ## Bonus: Setting up Ghcide to work on our project 472 | 473 | [Ghcide](https://github.com/digital-asset/ghcide) is, along [haskell-ide-engine](https://github.com/haskell/haskell-ide-engine), one of the current possibilites to get a decent ide environment to hack on haskell code. Those will eventually be merged together into [haskell-language-server](https://github.com/haskell/haskell-language-server) in the near future. We'll use [ghcide-nix](https://github.com/cachix/ghcide-nix) so we'll follow the instructions there. 474 | First, let's install **cachix**, this will allow us to easily add binary caches to our nix installation. A binary cache is a remote server you can fetch precompiled binaries from. Nix will look them up using the hash that you see in the file names located in the nix store. This will essentially prevent you from having to compile the whole universe on your machine. By default, nix will use the nixos binary cache. Let's proceed. 475 | ~~~bash 476 | $ nix-env -iA cachix -f https://cachix.org/api/v1/install 477 | $ cachix use ghcide-nix 478 | ~~~ 479 | That's it, Nix will now look for precompiled binaries in the ghcide-nix cache too, this will come handy with what we are about to do. 480 | 481 | Alright, let's just modify our **default.nix** now. 482 | 483 | In the let part of our file, we'll add the following 484 | ~~~nix 485 | ghcide = (import (githubTarball "cachix" "ghcide-nix" "master") {})."ghcide-${compilerVersion}"; 486 | ~~~ 487 | 488 | And in the **buildInputs** of the shell, we'll add ghcide 489 | 490 | ~~~nix 491 | shell = compilerSet.shellFor { 492 | packages = p: [p.haskell-nix]; 493 | buildInputs = with pkgs; [ 494 | compilerSet.cabal-install 495 | ghcide 496 | ]; 497 | }; 498 | ~~~ 499 | 500 | That's all, you now need to let nix reload your environment. This could take a while depending on the speed of your bandwith. 501 | 502 | Let's run it just to check everything's still ok. 503 | 504 | ~~~bash 505 | [nix-shell:~/Code/haskell-nix]$ ghcide 506 | ghcide version: 0.1.0 (GHC: 8.6.5) (PATH: /nix/store/8cn9219qr3iq479qvcrn6wh5qr8x635l-ghcide-exe-ghcide-0.1.0/bin/ghcide) 507 | Ghcide setup tester in /home/romain/Code/haskell-nix. 508 | Report bugs at https://github.com/digital-asset/ghcide/issues 509 | 510 | Step 1/6: Finding files to test in /home/romain/Code/haskell-nix 511 | Found 1 files 512 | 513 | Step 2/6: Looking for hie.yaml files that control setup 514 | Found 1 cradle 515 | 516 | Step 3/6, Cradle 1/1: Implicit cradle for /home/romain/Code/haskell-nix 517 | Cradle {cradleRootDir = "/home/romain/Code/haskell-nix", cradleOptsProg = CradleAction: Default} 518 | 519 | Step 4/6, Cradle 1/1: Loading GHC Session 520 | Interface files cache dir: /home/romain/.cache/ghcide/da39a3ee5e6b4b0d3255bfef95601890afd80709 521 | 522 | Step 5/6: Initializing the IDE 523 | 524 | Step 6/6: Type checking the files 525 | 526 | Completed (1 file worked, 0 files failed) 527 | ~~~ 528 | 529 | You'll now have to setup the client part of the language server. This will depend of your ide of choice. I personally use [lsp-haskell](https://github.com/emacs-lsp/lsp-haskell) with Emacs which works fine and is not too hard to setup. 530 | 531 | Right, that'll conclude our introduction to using nix and haskell together. If you have any questions, feel free to open an issue. 532 | 533 | --------------------------------------------------------------------------------