Posts tagged "nix":
Patching in Nix
Today I wanted to move one of my Haskell projects to GHC 9.2.4 and found that
envy didn't compile due to an upper bound on its dependency on bytestring
, it
didn't allow 0.11.*
.
After creating a PR I decided I didn't want to wait for upstream so instead I
started looking into options for patching the source of a derivation of a
package from Hackage. In the past I've written about building Haskell packages
from GitHub and an older one were I used callHackageDirect
to build Haskell
packages from Hackage. I wasn't sure how to patch up a package from Hackage
though, but after a bit of digging through haskell-modules I found appendPatch.
The patch wasn't too hard to put together once I recalled the name of the patch
queue tool I used regularly years ago, quilt. I put the resulting patch in the
nix
folder I already had, and the full override ended up looking like this
... hl = haskell.lib; hsPkgs = haskell.packages.ghc924; extraHsPkgs = hsPkgs.override { overrides = self: super: { envy = hl.appendPatch (self.callHackageDirect { pkg = "envy"; ver = "2.1.0.0"; sha256 = "sha256-yk8ARRyhTf9ImFJhDnVwaDiEQi3Rp4yBvswsWVVgurg="; } { }) ./nix/envy-fix-deps.patch; }; }; ...
Simple nix flake for Haskell development
Recently I've moved over to using flakes in my Haskell development projects. It took me a little while to arrive at a pattern a flake for Haskell development that I like. I'm hoping sharing it might help others when doing the same change
{ inputs = { nixpkgs.url = "github:nixos/nixpkgs"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: with nixpkgs.legacyPackages.${system}; let t = lib.trivial; hl = haskell.lib; name = "project-name"; project = devTools: # [1] let addBuildTools = (t.flip hl.addBuildTools) devTools; in haskellPackages.developPackage { root = lib.sourceFilesBySuffices ./. [ ".cabal" ".hs" ]; name = name; returnShellEnv = !(devTools == [ ]); # [2] modifier = (t.flip t.pipe) [ addBuildTools hl.dontHaddock hl.enableStaticLibraries hl.justStaticExecutables hl.disableLibraryProfiling hl.disableExecutableProfiling ]; }; in { packages.pkg = project [ ]; # [3] defaultPackage = self.packages.${system}.pkg; devShell = project (with haskellPackages; [ # [4] cabal-fmt cabal-install haskell-language-server hlint ]); }); }
The main issue I ran into is getting a development shell out of
haskellPackages.developPackage
, it requires returnShellEnv
to be true
.
Something that isn't too easy to find out. This means that the only solution
I've found to getting a development shell is to have separate expressions for
building and getting a shell. In the above flake the build expression, [3],
passes an empty list of development tools, the argument devTools
at [1],
while the development shell expression, [4], passes in a list of tools needed
for development only. The decision of whether the expression is for building or
for a development shell, [2], then looks at the list of development tools
passed in.
ZSH, Nix, and completions
TIL that ZSH completions that come with Nix packages end up in
~/.nix-profile/share/zsh/vendor-completions/
and that folder is not added to
$FPATH
by the init script that comes with Nix.
After modifying the bit in ~/.zshenv
it now looks like this
if [[ -f ~/.nix-profile/etc/profile.d/nix.sh ]]; then source ~/.nix-profile/etc/profile.d/nix.sh export fpath=(~/.nix-profile/share/zsh/vendor-completions ${fpath}) fi
First contribution to nixpkgs.haskellPackages
Nothing much to be proud of, but yesterday I found out that servant-docs was marked broken in nixpkgs even though it builds just fine and this morning I decided to do something about it.
So, with the help of a post on the NixOS discourse I put together my first PR.
Nix shell, direnv and XDG_DATA_DIRS
A few weeks ago I noticed that I no longer could use
haskell-hoogle-lookup-from-website
in Emacs. After a bit of experimentation I
found that the reason was that I couldn't use xdg-open
in a Nix shell.
Yesterday I finally got around to look into further.
It's caused by direnv
overwriting XDG_DATA_DIRS
rather than appending to it.
Of course someone already reported a bug already.
The workaround is to use
use nix --keep XDG_DATA_DIRS
Haskell, Nix and using packages from GitHub
The other day I bumped into what turned out to be a bug in Amazonka where
sockets weren't closed in a timely fashion and thus the process ran out of file
descriptors. Some more digging and an issue later I found that a fix most likely
already in place (mine was possibly a duplicate of an older issue). Now I only
had to verify if that was the case by using the most recent, and unreleased code
on the develop
branch of Amazonka.
My first thought was to attempt to instruct Cabal to build the bits of Amazonka
I need by putting a few source-repository-package
stanzas in my config. That
quickly started to look like a bit of a rabbit hole, so I decided to use Nix
instead. After finding the perfect SO post and looking up yet again how to do
overrides for Haskell I ran cabal2nix
for the three packages I need:
cabal2nix --no-haddock --no-check --subpath amazonka \ git://github.com/brendanhay/amazonka.git > amazonka.nix cabal2nix --no-haddock --no-check --subpath core \ git://github.com/brendanhay/amazonka.git > amazonka-core.nix cabal2nix --no-haddock --no-check --subpath amazonka-sqs \ git://github.com/brendanhay/amazonka.git > amazonka-sqs.nix
The relevant part of the old Nix expression looked like this:
thePkg = haskellPackages.developPackage { root = lib.cleanSource ./.; name = name; modifier = (t.flip t.pipe) [hl.dontHaddock hl.enableStaticLibraries hl.justStaticExecutables hl.disableLibraryProfiling hl.disableExecutableProfiling]; };
After adding the overrides it looked like this
hp = haskellPackages.override { overrides = self: super: { amazonka-core = self.callPackage ./amazonka-core.nix {}; amazonka = self.callPackage ./amazonka.nix {}; amazonka-sqs = self.callPackage ./amazonka-sqs.nix {}; }; }; thePkg = hp.developPackage { root = lib.cleanSource ./.; name = name; modifier = (t.flip t.pipe) [hl.dontHaddock hl.enableStaticLibraries hl.justStaticExecutables hl.disableLibraryProfiling hl.disableExecutableProfiling]; };
After a somewhat longer-than-usual build I could verify that I had indeed bumped into the same issue and my issue was a duplicate.
Better Nix setup for Spacemacs
In an earlier post I documented my setup for getting Spacemacs/Emacs to work with Nix. I've since found a much more elegant solution based on
- direnv, and
- emacs-direnv
No more Emacs packages for Nix and no need to defining functions that wrap
executables in an invocation of nix-shell
.
There's a nice bonus too, with this setup I don't need to run nix-shell
, which
always drops me at a bash prompt, instead I get a working setup in my shell of
choice.
Setting up direnv
The steps for setting up direnv
depends a bit on your setup, but luckily I
found the official instructions for installing direnv
to be very clear and
easy to follow. There's not much I can add to that.
Setting up Spacemacs
Since emacs-direnv
isn't included by default in Spacemacs I needed to do a bit
of setup. I opted to create a layer for it, rather than just drop it in the list
dotspacemacs-additional-packages
. Yes, a little more complicated, but not
difficult and I nurture an intention of submitting the layer for inclusion in
Spacemacs itself at some point. I'll see where that goes.
For now, I put the following in the file
~/.emacs.d/private/layers/direnv/packages.el
:
(defconst direnv-packages '(direnv)) (defun direnv/init-direnv () (use-package direnv :init (direnv-mode)))
Setting up the project folders
In each project folder I then add the file .envrc
containing a single line:
use_nix
Then I either run direnv allow
from the command line, or run the
function direnv-allow
after opening the folder in Emacs.
Using it
It's as simple as moving into the folder in a shell – all required envvars are set up on entry and unset on exit.
In Emacs it's just as simple, just open a file in a project and the envvars are set. When switching to a buffer outside the project the envvars are unset.
There is only one little caveat, nix-build
doesn't work inside a Nix shell. I
found out that running
IN_NIX_SHELL= nix-build
does work though.
My ghcide build for Nix
I was slightly disappointed to find out that not all packages on Hackage that are marked as present in Nix(pkgs) actually are available. Quite a few of them are marked broken and hence not installable. One of these packages is ghcide.
There are of course expressions available for getting a working ghcide
executable installed, like ghcide-nix. However, since I have rather simple needs
for my Haskell projects I thought I'd play with my own approach to it.
What I care about is:
- availability of the development tools I use, at the moment it's mainly
ghcide
but I'm planning on making use of ormolu in the near future - pre-built packages
- ease of use
So, I put together ghcide-for-nix. It's basically just a constumized Nixpkgs
where the packages needed to un-break ghcide
are present.
Usage is a simple import
away:
import (builtins.fetchGit { name = "ghcide-for-nix"; url = https://github.com/magthe/ghcide-for-nix; rev = "927a8caa62cece60d9d66dbdfc62b7738d61d75f"; })
and it'll give you a superset of Nixpkgs. Pre-built packages are available on Cachix.
It's not sophisticated, but it's rather easy to use and suffices for my purposes.
Nix setup for Spacemacs
Edit 2020-06-22: I've since found a better setup for this.
When using ghcide
and LSP, as I wrote about in my post on Haskell, ghcide, and
Spacemacs, I found myself ending up recompiling a little too often. This pushed
me to finally start looking at Nix. After a bit of a fight I managed to
get ghcide from Nix,
which brought me the issue of setting up Spacemacs. Inspired by a gist from
Samuel Evans-Powell and a guide to setting up an environment for Reflex by
Thales Macedo Garitezi I ended up with the following setup:
(defun dotspacemacs/layers () (setq-default ... dotspacemacs-additional-packages '( nix-sandbox nix-haskell-mode ... ) ... ))
(defun dotspacemacs/user-config () ... (add-hook 'haskell-mode-hook #'lsp) (add-hook 'haskell-mode-hook 'nix-haskell-mode) (add-hook 'haskell-mode-hook (lambda () (setq-local flycheck-executable-find (lambda (cmd) (nix-executable-find (nix-current-sandbox) cmd))) (setq-local flycheck-command-wrapper-function (lambda (argv) (apply 'nix-shell-command (nix-current-sandbox) argv))) (setq-local haskell-process-wrapper-function (lambda (argv) (apply 'nix-shell-command (nix-current-sandbox) argv))) (setq-local lsp-haskell-process-wrapper-function (lambda (argv) `("nix-shell" "-I" "." "--command" "ghcide --lsp" ,(nix-current-sandbox)))))) (add-hook 'haskell-mode-hook (lambda () (flycheck-add-next-checker 'lsp-ui '(warning . haskell-stack-ghc)))) ... )
It seems to work, but please let me know if you have suggestions for improvements.