After recently setting up a new machine and going through the exercise of setting up a development environment for the nth time, I was frustrated by having to install and configure all of my plugins and dependencies to support auto-completion, syntax highlighting, and the other niceties I’ve come to enjoy with Vim. Looking for an alternative, I was drawn to Visual Studio Code (VS Code) with the VSCodeVim extension. Combining Vim key-bindings with Code’s excellent extension marketplace, I was able to recreate and in some cases improve the development environment I enjoyed as a Vim user in VS Code. This blog post continues that discovery by setting up a brand new OCaml project and development environment in VS Code.

Setting up VS Code

OCaml has a rich set of tools for development like merlin for code completion and ocp-indent for code formatting. These tools come with Vim and Emacs integration, but lack support for Visual Studio Code out of the box. Some third-party extensions in the VS Code marketplace support OCaml, but many are deprecated or are targeted towards ReasonML and the Reason compiler chain. What to do? The ocaml-lsp project is a promising implementation of a language server for OCaml, and the extension OCaml Platform makes use of this language server in Visual Studio Code. This combination seems to be the best maintained option for OCaml on VSCode.

Start by installing ocaml-lsp on your development machine through opam:

$ opam pin add ocaml-lsp-server https://github.com/ocaml/ocaml-lsp.git
$ opam install ocaml-lsp-server

Next, install the OCaml Platform extension through the VS Code marketplace. Lastly, go to the OCaml Platform extension settings and manually set the location of the ocamllsp binary, which should be part of your ~\.opam installation. Mine was set to /Users/kevinsookocheff/.opam/default/bin/ocamllsp. Now, when editing OCaml files, the OCaml Platform extension will automatically run the OCaml Language Server, providing syntax highlighting, jump to definition, and error highlighting.

This is all we need to do to setup VS Code for OCaml development. Next, let’s get the Dune system set up so that we can develop and publish our code.

Setting up a new OCaml project using the Dune build system

Dune is a build system for OCaml. A typical dune project will have a dune-project file and one or more <package>.opam files at the root level, and additional dune files for libraries, executables, and tests.

Start by creating a new folder hello_ocaml to house our project:

$ mkdir ~/hello_ocaml

Dune Project Layout

Done projects contain a file called dune-project in the root directory. The contents of that file name the project you are building and the dune syntax version.

(lang dune 2.5)
(name hello_ocaml)

Next, create a directory bin to hold your source code, and create a file main.ml within that directory:

$ cd hello_ocaml
$ mkdir bin
$ touch ~/bin/main.ml

The main.ml file can simply print “Hello OCaml!”. This is the OCaml application that Dune will build for us.

let () =
    print_endline "Hello Ocaml!"

Building the Project

Given this scaffold, we can build the project using the dune build command from the project root. Doing so will create a main.exe file in the default directory (all Dune builds %mdash; even non-Windows — use the .exe extension).

$ dune build
$ tree
.
|-- _build
|   |-- default
|   |   |-- bin
|   |   |   |-- dune
|   |   |   |-- main.exe
|   |   |   `-- main.ml
|   |   `-- dune-project
|   `-- log
|-- bin
|   |-- dune
|   `-- main.ml
`-- dune-project

You can run the executable as you would any other:

$ ./_build/default/bin/main.exe
Hello Ocaml!

Using Libraries

We can add libraries to organize our project using the same method we just looked at for binaries. First, create a lib directory and an initial library file.

$ mkdir ~/hello_ocaml/lib
$ touch ~/hello_ocaml/lib/hello_ocaml.ml

Our library code will be fairly simple. It just removes the hard-coded message from the main executable and puts it in a library. (In the following sections bold-face font is the file you are working on, followed by the contents of that file).

./lib/hello_ocaml.ml

let message = "Hello OCaml!"

to use this library, we need to declare the dune file for it:

./lib/dune

(library
    (public_name hello_ocaml))

and then update our main.ml and bin/dune files to use the library:

./bin/dune

We also need to update the dune file to reference the library we added:

(executables
    (names main)
    (libraries hello_ocaml))

./bin/main.ml

Modules in OCaml are referred to by the file name. We can use our library module in main.ml by referencing it. Dune makes this module part of our environment when we add the libraries stanza to the dune file in this directory. Modules are required to have an upper-case first letter, so we have to start with an upper-case to use the module.

let () =
    print_endline Hello_ocaml.message

Lastly, let’s build our project:

$ dune build
File "lib/dune", line 2, characters 17-28:
2 |     (public_name hello_ocaml))
                     ^^^^^^^^^^^
Error: You cannot declare items to be installed without adding a
<package>.opam file at the root of your project.
To declare elements to be installed as part of package "hello_ocaml", add a
"hello_ocaml.opam" file at the root of your project.
Root of the project as discovered by dune: .

Libraries need a .opam file to before they can be used. We can declare one for our project at the root level using the minimum Opam integration required for Dune projects. This minimum simply adds a build clause to say “use Dune to build this project”. The name and jobs portions of this line are parameters that get set from Opam during an installation.

./hello_ocaml.opam

opam-version: "2.0"
version: "1.0"
maintainer: "kevin@sookocheff.com"
authors: ["Kevin Sookocheff"]
homepage: "https://github.com/soofaloofa/hello_ocaml"
bug-reports: "https://github.com/soofaloofa/hello_ocaml/issues"
dev-repo: "git+https://github.com/soofaloofa/hello_ocaml.git"
license: "Apache-2.0"
build: [
    ["dune" "build" "-p" name "-j" jobs]
]

Now we can build and run our project.

$ dune build
$ ./_build/default/bin/main.exe
Hello OCaml!

Now we have a project that builds, an executable, uses a local library, and that is integrated with VS Code. Happy coding!