Managing Dependencies

The OCaml ecosystem has a wealth of third-party packages that are available for use. In this section we will look into how to use them with Dune.

Adding Dependencies

Much like in regular projects, to add a library we need to add a dependency to it. For simplicity we will use the popular fmt library as an example, but any package from the package repository can be used.

To do so we update the dune-project file to add a dependency on the opam package.

dune-project
(lang dune 3.17)
(name test)

(package
  (name test)
  (depends
    (ocaml (>= 4.14))
    fmt))

Here we define the OPAM packages that we want to use, along with the version constraints these dependencies should adhere to.

dune-workspace
(lang dune 3.21)
(pkg enabled)

In this file we direct Dune to enable package management in the current workspace. The pkg stanza configures Dune to manage the declared dependencies automatically.

This configuration will take care of installing the dependencies, but we still need to add it to our build as a library as usual:

dune
(executable
  (public_name test)
  (libraries fmt))

Adding a library dependency to our dune file via the libraries stanza. This is unchanged from the usual Dune workflow.

This change will allow us to use the Fmt module in our OCaml code.

test.ml
let langs = ["OCaml"; "Rust"]

let () =
  let pp_langs = Fmt.(list ~sep:(any ", ") string) in
  Format.printf "Hello, %a!\n" pp_langs langs

We update the code to define an Fmt.t pretty-printer for the list of strings and then use it to print the value.

To build it we just call build again.

$ dune build

Dune will notice that the project depends on new packages. Thus it will re-run the internal dependency solver to find a solution for the set of packages to use. It will then use this new solution to download, build and install these dependencies automatically.

We can check the build log in _build/log and see the packages that the Dune solver has selected:

...
# Dependency solution for
# _build/.sandbox/<sandbox-hash>/_private/default/.lock/dune.lock:
# - base-unix.base
# - fmt.0.11.0
# - ocaml.5.4.0
# - ocaml-base-compiler.5.4.0
# - ocaml-compiler.5.4.0
# - ocaml-config.3
# - ocamlbuild.0.16.1+dune
# - ocamlfind.1.9.8+dune
# - topkg.1.1.1
...

Note

The list of packages being output includes all dependencies of your project, including transitive dependencies.

As we see, the code works and uses fmt to do the pretty-printing:

$ dune exec ./test.exe
Hello, OCaml, Rust!

Dependency Constraints

Packages are often only compatible with some versions of dependencies. To specify a version range, use the regular Dune dependency syntax used for opam dependencies in the dune-project file.

dune-project
(lang dune 3.17)
(name test)

(package
 (name test)
 (depends
  (ocaml (>= 4.14))
  (fmt (and (>= 0.6) (< 1.0)))))

This change ensures the fmt package to install will be compatible with our request. These constraints will be taken into account the next time the build system is ran.

dune build

Checking _build/log again reveals that our change was taken into account:

...
# Dependency solution for
# _build/.sandbox/<sandbox-hash>/_private/default/.lock/dune.lock:
# - base-unix.base
# - fmt.0.9.0
# - ocaml.5.4.0
# - ocaml-base-compiler.5.4.0
# - ocaml-compiler.5.4.0
# - ocaml-config.3
# - ocamlbuild.0.16.1+dune
# - ocamlfind.1.9.8+dune
# - topkg.1.1.1
...

Removing Dependencies

Given all dependencies are defined in the dune-project file, removing a dependency just means to remove the dependency from the depends field of your dune-project.

From then on the project will not depend on the package anymore, and in future builds the package will not be accessible as library anymore.

Note

The removed dependency might still be accessible if some other dependency of your project depends on it, thus if it is a transitive dependency.