Good setup to work with reason-cli and opam


How to properly configure the editor for someone using both ReasonML installed from the reason-cli and OCaml from opam? This question arises when we are doing both native and bucklescript development.

First thing to remember is that all the editor plugins that really need configuration are based on merlin (as far as I know). More precisely on the ocamlmerlin binary. And they will either take the first ocamlmerlin binary they will find in the path, or the binary from the currently selected opam switch.

The configuration presented bellow allows us to properly configure our environment prior to launch Emacs (with tuareg or reason-mode), vscode, vim, … Once this is done, we can launch our editor from the shell with the expected environment and it will pickup the good version of merlin. In theory it is also possible to launch our editor from our OS launcher rather than from the editor, but then we have to be sure to load the good environment globally. And it doesn't correspond to the usual workflow when mixing native and bucklescript development. So I will not try to get to this point.

I am using yarn rather than npm because I am more familiar with it and the global installation system seems easier to manage. But I guess it is possible to reach the same state with npm.

Also I am using opam2 because it provides nice new features over opam1 and is already very stable.

Putting merlin in the environment

Once we have installed reason-cli globally using yarn, we will have the ocamlmerlin binary in a path like $HOME/.yarn/bin. We can get this path from the command yarn global bin.

For opam, the binary will be in $HOME/.opam/SWITCH/bin. And this can be added to our path by calling eval $(opam env --sw=SWITCH).

So what we want to do is just to add one of those two paths to the environment and then launch our editor.

By hand in bash, it would be something like this for yarn:

PATH="$(yarn global bin):$PATH" emacs

And like this for opam:

PATH="$(opam env --switch=SWITCH):$PATH" emacs

But it is troublesome to do this every time we want to launch our editor. It is actually very troublesome because we have to prefix all the commands that are using binaries from yarn or opam.

So we want to update our environment globally for the current shell. This can be done with export. But it is even easier to use a small utility like direnv.

Automatic environment setup using direnv

direnv behavior is pretty simple. It will look for a .envrc file in the current directory or any of its parents. This .envrc file is actually a bash script that is sourced by direnv once found. Every time we change directory, it will look for a .envrc and update our environment.

The configuration of direnv for each shell is a bit different. I will not detail it here. It is better to check the direnv documentation.

The important parts are what to put in the .envrc file and where to put this file.

The answer to where is at the root of our project.

And the content of the file is actually very short.

For a project using opam (so targeting native or bytecode compilation):

eval $(opam env --shell=bash --inplace-path --set-switch --readonly --switch=SWITCH)

If we are using a local switch, the --switch=SWITCH part can be removed.

For a project using reason-cli (targeting javascript by using bucklescript), the configuration is not much longer:

PATH_add $(yarn global bin)

PATH_add is a function provided by direnv.

Then we need to execute direnv allow to tell direnv we know about the .envrc in the directory of our project and it is legitimate. This is only needed when we create a new .envrc.

Once all this is done, our .envrc should be automatically used by direnv. We can check if it works by executing which ocamlmerlin and making sure this is actually the merlin binary we want to use.

Launching the editor

We are now ready to launch the editor. To do so, we need to be in the project directory so that direnv does it jobs. And then execute emacs, code ., vim, atom or any other command corresponding to our favorite editor. That's all.


Using direnv allows us to use reason-cli or opam for a project. Or even both. But it also has other benefits. One of them very valuable is to reuse the same switch for multiple directories. This is very convenient while using git worktree.

Note concerning emacs

I am not sure how all the editors plugins are working. But for Emacs by default the merlin package will try to look for the ocamlmerlin binary in opam. Even if the environment has not been populated by opam env. This behavior is not the one expected here. It would take merlin from opam even if we added the reason-cli's merlin to our path. It is not hard to change this behavior though. Just add this line to Emacs configuration:

(setq merlin-command "ocamlmerlin")

The whole Emacs configuration I am using for reason and OCaml is available in this GitHub repository. It also makes the configuration of reason-mode more robust. For someone using use-package, it should automatically install everything that it required.

There is a direnv package for Emacs that could allow us to skip configuration of the environment prior to launch Emacs. But using this plugin rather than setting up the environment first has a big disadvantage. As soon as we will jump to a file which is not in a sub-directory of the .envrc file, merlin will not be in the path. If we use merlin-locate on a function or value coming from a library installed by opam it will work. But it is not possible to use merlin in the file we landed in.

Note that this problem doesn't exist while using yarn or local switches with opam because all the installed libraries will be in sub-directories or .envrc.


  1. Install direnv
  2. Go to the root of the project
  3. If using reason-cli, execute

    echo 'PATH_add $(yarn global bin)' | tee .envrc
  4. If using opam, execute

    echo 'eval $(opam env --shell=bash --inplace-path --set-switch --readonly --switch=SWITCH)' | tee .envrc
  5. Execute direnv allow
  6. Launch editor
Louis Roché 2018-04-15 Sun 00:00 Emacs 25.1.1 (Org mode 9.2.1)