I’m trying to understand the dune build system and how to ‘import’ modules into OCaml files.
The way I understood, initially, was that in a dune file like this:
(libraries logs ipaddr lwt result fmt mirage-time mirage-random
mirage-protocols mirage-stack mirage-net ethernet))
Then if I need to use this I’d do
filename1.ml is a file in the same folder as that dune project file, and that contains a
However if we go into the dune file of the ipv4 part of the code:
(libraries logs mirage-protocols ipaddr cstruct rresult tcpip
tcpip.udp mirage-random mirage-clock randomconv lru)
(preprocess (pps ppx_cstruct))
In order to use it, I should do
However, this code uses it like this:
module I = Ipv4.Make(E)(Clock)(OS.Time)
Ipv4 come from?
Some of the time, your initial understanding will be correct: a file
foo.ml inside a library with name
lib will be accessible to your code at
Lib.Foo. This is because Dune provides a feature called “wrapping” which keeps all of the files in a single top-level module named after the library. There are two (?) exceptions to this:
(wrapped false) is specified in the library
dune file (as is the case here), this mechanism is disabled. The library may provide more than one top-level module, and they can be named arbitrarily. Sure enough, if we consult the documentation for
mirage-tcpip, we see that the library
tcpip.ipv4 is exporting a whole bunch of top-level modules. At the time when this code was written,
Ipv4 was one of them (it isn’t any more).
If the library contains a file with the same name – i.e. library
lib contains a file
lib.ml – this module becomes the wrapper module. Only modules in the signature of
lib.ml will be accessible outside. This is often used to hide
utils.ml files and other things that you don’t want to export to users of your library.
In general: use Dune’s wrapping and (2) if you want to hide top-level files or shrink their interface. As you’ve discovered, the alternatives are confusing for end-users and can lead to namespace collisions between different libraries.