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!