Using Nix to Fetch C Libraries!
In the last couple weeks, we've started getting our feet wet with the Nix package manager. Last time we used cabal2nix
to convert a purely Cabal project into a Nix project. This demonstrated how we could get our Haskell dependencies from the Nix store if we wanted.
But one of the virtues of Nix is that it has much more to it than Haskell packages! Sometimes, we might be using a package that has a trickier dependency, like a C library. We can't capture these in our Stack or Cabal files all that well. Nix however, can install these dependencies as easily as Haskell packages!
This week, we'll see a couple simple ways Nix can do this. The first will be a simple addition to our Nix file. But the second will be more of a hybrid integration with Stack! We'll get our Haskell dependencies from Stack, but others from Nix! It's actually a very simple process!
Nix is a more advanced system for managing our projects. But for some more basic knowledge, you should download our Beginners Checklist. This will acquaint you with some more basic tools to get started with. You can also read our Liftoff Series for a more detailed walkthrough!
Using a C Library
Let's suppose we're doing some task with Linear Programming in Haskell. The GNU Linear Programming Kit (GLPK) is a useful tool for this task. And if we want to interact with the kit through Haskell, we'd want to use the glpk-hs
library. If we create a new Stack project, we can add it as a dependency in our .cabal
file. We'll also need the package and one of its dependencies as extra-deps
in stack.yaml
:
extra-deps:
- glpk-hs-0.7
- gasp-1.2.0.0
When we do stack build
, it will appear to download the packages fine, but then we'll get a strange error:
Missing dependency on a foreign library:
* Missing (or bad) C library: glpk
The Haskell GLPK library uses the C library as a dependency for running some of its tasks. So if we don't have this library installed, we can't proceed. And unfortunately, our normal ways of using Stack don't let us install C libraries.
The normal solution to this would be to download the C library, compile it, and install it. But this is a tedious process. The more dependencies like this you have, the harder it is for anyone to use your library. They'll have a lot more steps involved in setting themselves up to use it or contribute. And it can be difficult to replicate all these same steps on a remote server machine. This can make continuous integration and deployment more painful.
Nix to the Rescue!
While Stack and Cabal aren't great with C libraries, Nix handles them perfectly well! If you take a look at the source repository for glpk-hs
, you can read the default.nix
file. There's a line in there specifying the C dependency on glpk
! Instead of going in the libraryHaskellDepends
section, we have a new line. The librarySystemDepends
field tells us the system packages our library depends on.
{ ... }:
mkDerivation {
...
libraryHaskellDepends = [array base containers deepseq gasp mtl];
librarySystemDepends = [glpk];
...
}
As long as a user of this code has Nix, all they have to do is nix-build release.nix
, and they'll get this C library! It doesn't matter if they're on their own machine or a remote server!
Using Stack and Nix
Using librarySystemDepends
is one way of adding system dependencies. But, this workflow forces us to use Nix for our Haskell dependencies as well. To make our little GLPK project work we would make a full nix file with glpk-hs
as a Haskell dependency and glpk
as a system dependency.
What if we want to use Stack for our Haskell packages and Nix for external packages like C libraries? There's a way to do this with Stack! It's actually quite simple. We'll have our stack.yaml
file set up as we did before. Then we'll add these lines to it:
nix:
enable: true
packages: [glpk]
By including a nix
section with enable: true
, Stack knows we want to use Nix. We can then list the system packages we want to retrieve. If we use this approach, our project will build without needing any extra work for glpk
.
As another example, suppose we want to use the snappy
compression library. But once again, including this package as an extra-dep
won't give you the required C library. But if you also include it via Nix, then everything will work!
-- stack.yaml
nix:
enable: true
packages: [snappy]
extra-deps:
- snappy-0.2.0.2
The Stack/Nix combination works by running Stack commands within a nix-shell
. So there are some other fields we can add to the nix
section that can customize the behavior of this. For example nix-shell-options
allows us to pass other options to the command. The path
field allows us to specify a specific Nix path. And instead of providing a packages
list, we can also provide a full nix-shell
file. This gives us the chance to add more configurations if we want.
Conclusion
That's how simple it can be to use Stack with Nix! Next week, we'll wrap up our study of Nix and package managers by exploring how to use GHCJS! This is an awesome Haskell project that lets us compile our Haskell into Javascript to use in web pages. You need to know the basics of Nix before trying it out though!
Appendix: Haskell and Tensor Flow!
If you've read our Machine Learning Series you know how irritating C dependencies can be. Following our Haskell TensorFlow guide involves installing 3 separate C libraries! Hopefully I'll soon be able to provide a guide with an alternative Nix-based approach. It depends on whether Nix has the right versions of the various libraries!