Other topics

This section describes some details of dune for advanced users.

META file generation

Dune uses META files from the findlib library manager in order to interoperate with the rest of the world when installing libraries. It is able to generate them automatically. However, for the rare cases where you would need a specific META file, or to ease the transition of a project to dune, it is allowed to write/generate a specific one.

In order to do that, write or setup a rule to generate a META.<package>.template file in the same directory as the <package>.opam file. Dune will generate a META.<package> file from the META.<package>.template file by replacing lines of the form # DUNE_GEN by the contents of the META it would normally generate.

For instance if you want to extend the META file generated by dune you can write the following META.foo.template file:

# DUNE_GEN
blah = "..."

Findlib integration

Dune uses META files to support external libraries. However, it doesn’t export the full power of findlib to the user, and especially it doesn’t let the user specify predicates.

The reason for this limitation is that so far they haven’t been needed, and adding full support for them would complicate things quite a lot. In particular, complex META files are often hand-written and the various features they offer are only available once the package is installed, which goes against the root ideas dune is built on.

In practice, dune interprets META files assuming the following set of predicates:

  • mt: what this means is that using a library that can be used with or without threads with dune will force the threaded version
  • mt_posix: forces the use of posix threads rather than VM threads. VM threads are deprecated and are likely to go away soon
  • ppx_driver: when a library acts differently depending on whether it is linked as part of a driver or meant to add a -ppx argument to the compiler, choose the former behavior

Dynamic loading of packages

Dune supports the findlib.dynload package from findlib that allows to dynamically load packages and their dependencies (using OCaml Dynlink module). So adding the ability for an application to have plugins just requires to add findlib.dynload to the set of library dependencies:

(library
  (name mytool)
  (public_name mytool)
  (modules ...)
)

(executable
  (name main)
  (public_name mytool)
  (libraries mytool findlib.dynload)
  (modules ...)
)

Then you could use in your application Fl_dynload.load_packages l that will load the list l of packages. The packages are loaded only once. So trying to load a package statically linked does nothing.

A plugin creator just need to link to your library:

(library
  (name mytool_plugin_a)
  (public_name mytool-plugin-a)
  (libraries mytool)
)

By choosing some naming convention, for example all the plugins of mytool should start with mytool-plugin-. You can automatically load all the plugins installed for your tool by listing the existing packages:

let () = Findlib.init ()
let () =
  let pkgs = Fl_package_base.list_packages () in
  let pkgs =
    List.filter
      (fun pkg -> 14 <= String.length pkg && String.sub pkg 0 14 = "mytool-plugin-")
      pkgs
  in
  Fl_dynload.load_packages pkgs

Classical ppx

classical ppx refers to running ppx using the -ppx compiler option, which is composed using Findlib. Even though this is useful to run some (usually old) ppx’s which don’t support drivers, dune does not support preprocessing with ppx this way. but a workaround exists using the ppxfind tool.

Profiling dune

If --trace-file FILE is passed, dune will write detailed data about internal operations, such as the timing of commands that are run by dune.

The format is compatible with Catapult trace-viewer. In particular, these files can be loaded into Chromium’s chrome://tracing. Note that the exact format is subject to change between versions.

Package version

Dune determine the version of a package by looking at the version field in the package stanza. If the version field is not set, it looks at the toplevel version field in the dune-project field. If neither are set, dune assume that we are in development mode and reads the version from the VCS if any. The way it obtains the version from the VCS in described in the build-info section.

When installing the files of a package on the system, dune automatically inserts the package version into various metadata files such as META and dune-package files.

OCaml syntax

If a dune file starts with (* -*- tuareg -*- *), then it is interpreted as an OCaml script that generates the dune file as described in the rest of this section. The code in the script will have access to a Jbuild_plugin module containing details about the build context it is executed in.

The OCaml syntax gives you an escape hatch for when the S-expression syntax is not enough. It is not clear whether the OCaml syntax will be supported in the long term as it doesn’t work well with incremental builds. It is possible that it will be replaced by just an include stanza where one can include a generated file.

Consequently you must not build complex systems based on it.

Variables for artifacts

For specific situations where one needs to refer to individual compilation artifacts, special variables (see Variables) are provided so that the user does not need to be aware of the particular naming conventions or directory layout implemented by dune.

These variables can appear wherever a Dependency specification is expected and also inside User actions. When used inside User actions, they implicitly declare a dependency on the corresponding artifact.

The variables have the form %{<ext>:<path>}, where <path> is interpreted relative to the current directory:

  • cmo:<path>, cmx:<path>, cmi:<path> expand to the path of the corresponding artifact for the module specified by <path>. The basename of <path> should be the name of a module as specified in a (modules) field.
  • cma:<path>, cmxa:<path> expands to the path of the corresponding artifact for the library specified by <path>. The basename of <path> should be the name of the library as specified in the (name) field of a library stanza (not its public name).

In each case, the expansion of the variable is a path pointing inside the build context (ie _build/<context>).

Building an ad-hoc .cmxs

In the model exposed by dune, a .cmxs target is created for each library. However, the .cmxs format itself is more flexible than that and is capable to containing arbitrary .cmxa and .cmx files.

For the specific cases where this extra flexibility is needed, one can use Variables for artifacts to write explicit rules to build .cmxs files not associated to any library.

Below is an example where we build my.cmxs containing foo.cmxa and d.cmx. Note how we use a library stanza to set up the compilation of d.cmx.

(library
 (name foo)
 (modules a b c))

(library
 (name dummy)
 (modules d))

(rule
 (targets my.cmxs)
 (action (run %{ocamlopt} -shared -o %{targets} %{cmxa:foo} %{cmx:d})))