Nix Quick Tips - Flake for OCaml
Tags: nix, flake, ocaml, tutorial
Published: 2023-10-17 15:26:57

**TL;DR** Here is the code for those in a hurry. I'll break down what is happening for those interested.

```nix
{
  description = "My OCaml project";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
    ocaml-overlay.url = "github:nix-ocaml/nix-overlays";
    ocaml-overlay.inputs.nixpkgs.follows = "nixpkgs";
  };
  outputs = { self, nixpkgs, flake-utils, ocaml-overlay }: 
    flake-utils.lib.eachDefaultSystem (system: 
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [
            ocaml-overlay.overlays.default
          ];
        };
      in {
        devShell = pkgs.mkShell {
          buildInputs = [
            pkgs.ocaml
            pkgs.ocamlPackages.dune_2
            pkgs.ocamlPackages.merlin
            pkgs.ocamlPackages.utop
            pkgs.ocamlPackages.dream # This isn't required, you can place any  packages you want in here.
          ];
          shellHook = ''
            eval $(opam env)
          '';
        };
      }     
    );
}

```
# Requirements

You'll need to have nix installed and experimental features enabled for use with flakes.

- [Installing  nix](https://nixos.org/download)
- [Enabling flakes](https://nixos.wiki/wiki/Flakes)

# The what and why

If you want a reproducible build, one way you can do this is with [nix](https://nixos.org/) and [nix flakes](https://nixos.wiki/wiki/Flakes). I wont go into great detail on them here (that will be for another post) but I will cover how this flake is working. 

In very broad terms, _nix_ is a functional language you can use to define declarative builds. There is also NixOS which is a Linux distribution built on nix and uses it for it's package management, but you can install nix separate from NixOS. 

A _flake_ is used for managing dependencies between nix expressions. In simple terms, you can think of a flake as its own self contained "environment" like python's virtual environment. There is more to it than that but in general that is an easy way to think about it. 

# Brief overview of flakes

A flake has two main parts, the `inputs` and the `outputs`. The `inputs` declare dependencies to pass to the `outputs`. The `outputs` define what will happen when running certain nix commands. In this example our inputs are `nixpkgs`, `flake-utils`, and `ocmal-overlay`. Our only output is the `devShell`. In this case I haven't created a build output, only a devShell used for development.

# How do I use this flake?

Simply place the flake in the root of your ocaml project and to activate the develop environment run the command `nix develop`. It will fetch all the dependencies defined in the flake then drop you in a shell with them. These dependencies are only available in this shell. If you exit it or go to another shell you'll have to run `nix develop` again. If you want to avoid running this command every time you can use [direnv](https://direnv.net/). Install direnv then create a file called `.envrc` in your project. Add the line `use flake` to the file. Now every time you `cd` into the project directory it will activate the flake. You may have to run `direnv allow` the first time around.

# Ok ok, what is the flake actually doing?

I'll go through this line by line;

```nix
description = "My OCaml project";
```
Just a description of your flake, this can be whatever.

```nix
inputs = {
```
Inputs are a required part of a flake. This defines what dependencies will be passed to use in your outputs. 

```nix
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
```
This defines a dependency `nixpkgs`. We pass the github "url" for where to fetch it. It's the format of `github:<repo-owner>/<repo-name/<branch-name>`

```nix
flake-utils.url = "github:numtide/flake-utils";
```
This is exactly what the name says. It imports a flake with some useful functionality for creating flakes. Not strictly necessary but it makes doing certain things easier. 

```nix
    ocaml-overlay.url = "github:nix-ocaml/nix-overlays";
    ocaml-overlay.inputs.nixpkgs.follows = "nixpkgs";
```
The first line is getting the ocaml _overlay_. Overlays are, as the name says, a way to overlay modifications on a package. Say you want a different version or extra functionality in a package from `nixpkgs`. You can create an overlay that takes in the original package and returns an modified one, essentially _overlaying_ your changes. This overlay adds some things we'll need to get ocaml working. The last line just tells this flake to use _our_ provided nixpkgs version in its definition. 

```nix
outputs = { self, nixpkgs, flake-utils, ocaml-overlay }:
```
Here we're defining out outputs, and what to be passed in from our inputs. If you add/remove inputs, make sure to update this to reflect that.

```nix
flake-utils.lib.eachDefaultSystem (system:
```
Our flake-utils are back! Usually you need to define the different systems you're building for in your flake. Using this flake-utils function provides us an easy way to define this by providing us a `system` constant that will be the system we're running on. (I think that's how it works, haven't dived too deeply into flake-utils. Feel free to correct me.)

```nix
 let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [
            ocaml-overlay.overlays.default
          ];
        };
      in {
```
I'm going to cover this section as one chunk. We are declaring a constant to be passed to the next expression with `let` `in` syntax. What we're defining is the `pkgs` we're going to use. We set it equal to `import nixpkgs` which essentially expands `nixpkgs` (which we get from out inputs) and applies any changes we include in the `{}`. In this case we `inherit system;` which defines our current system as the one to use for these packages. Then we define an `overlay` which is set to the one we got from out inputs, `ocaml-overlay`. Now any packages created or modified by `ocaml-overlay` are applied to our `pkgs`.

```nix
in {
        devShell = pkgs.mkShell {
          buildInputs = [
            pkgs.ocaml
            pkgs.ocamlPackages.dune_2
            pkgs.ocamlPackages.merlin
            pkgs.ocamlPackages.utop
            pkgs.ocamlPackages.dream # This isn't required, you can place any  packages you want in here.
          ];
          shellHook = ''
            eval $(opam env)
          '';
        };
      }    
```
Deep breath, we're almost done. This last chunk of code is just defining our `devShell`. This is the shell environment that will be created when we run `nix develop`. The `in {}` is simply bringing _in_ the definitions from our `let` expression (`pkgs`). 

We create our `devShell` and set it equal to `pkgs.mkShell` which is a function provided by `nixpkgs` (now named `pkgs`) to create a shell environment. We pass to it our definition of this environment. The `buildInputs` in the definition is a list of packages to include in this shell. So any packages defined in `nixpkgs` or our `ocaml-overlay` go here. Finally, we have a `shellHook` which is run before you're dropped into the shell. Any extra environment variables or scripts you  want to provide to the shell can be dropped here. In this case we're just adding `eval $(opam env)` which will add opam's environment variables to our shell that we need for working with it.

 Now you can run `nix develop` and get dropped into a shell with these dependencies and environment variables available to you after a short install process. These are only available in the project, they won't be installed system wide. Neat, right?

# And that's it!

I'm still no expert on nix or flakes. As I journey through learning them though and run into hiccups I had to figure out I'll try to provide my solutions with explanations for future learners. Let me know your thoughts and if you have a better way of doing things I've explained, let me know that too! Thanks for reading.