ocamlocaml-dune

How do I include an OCaml module that is not called from the main module in an executable with dune?


I want to include a module in an OCaml program where the module is not directly called by the main (or any other) module, but rather does something during initialization. How do I do that with dune? It seems that dune only includes modules that are explicitly invoked, even when I list other modules in a modules stanza.

Example of dune not doing what I want:

Mod1.ml:

let mod1 () : unit =
  Printf.printf "in mod1\n"

let () =
  mod1 ()

Mod2.ml:

let mod2 () : unit =
  Printf.printf "in mod2\n"

let () =
  mod2 ()

dune:

(executable
  (name Mod1)
  (modules
    Mod1
    Mod2
  )
)

dune-project

(lang dune 3.3)

Compiling and running:

$ dune exec ./Mod1.exe
in mod1

My goal is to also see in mod2 above output.

Example that works with direct ocamlc invocation:

This is what I would do if not using dune:

$ ocamlc -o both.exe Mod1.ml Mod2.ml
$ ./both.exe
in mod1
in mod2

Adding an explicit call

If I change Mod1.ml to:

let mod1 () : unit =
  Printf.printf "in mod1\n"

let () =
  mod1 ();
  Mod2.mod2 ()    (* added *)

Then both modules get included:

$ dune exec ./Mod1.exe
in mod2
in mod1
in mod2

But my goal is include Mod2 in the executable without explicitly invoking it from Mod1.

empty_module_interface_if_absent does not help

In the documentation for the executable stanza, it says:

(empty_module_interface_if_absent) causes the generation of empty interfaces for every module that does not have an interface file already. Useful when modules are used solely for their side-effects. This field is available since the 3.0 version of the Dune language.

However, this does not help. With dune file:

(executable
  (name Mod1)
  (empty_module_interface_if_absent)   ; added
  (modules
    Mod1
    Mod2
  )
)

and reverting to the original Mod1.ml, the behavior is the same:

$ dune exec ./Mod1.exe
in mod1

Versions, etc.

$ ocamlc --version
4.14.0

$ dune --version
3.3.1

$ opam --version
2.1.2

$ uname -a
Linux mint201vm 5.4.0-58-generic #64-Ubuntu SMP Wed Dec 9 08:16:25 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

Solution

  • You can pack your modules in a library and then link this library to your main executable using the linkall option. E.g., using your concrete example,

    (executable
     (name mod1)
     (modules mod1)
     (libraries libmod2)
     (flags -linkall))
    
    (library
     (name libmod2)
     (modules mod2))
    

    But it is more convenient to pack all modules into a library and move the executable into a separate folder. You can create an empty file for the executable main module or do some initialization in it, e.g.,

    $ tree
    .
    ├── dune
    ├── dune-project
    ├── mod1.ml
    ├── mod2.ml
    └── run
        ├── dune
        └── main.ml
    $ cat dune
    (library
     (name impl))
    
    $ cat run/dune
    (executable
     (name main)
     (libraries impl)
     (flags -linkall))
    
    $ dune exec ./run/main.exe
    in mod2
    in mod1
    

    Either way, note that by default dune packs modules in a library to prevent name-clashing with other libraries (since OCaml has a flat namespace for compilation units). Therefore to access a module Mod2 in the library Libmod2 you have to say Libmod2.Mod2 (if you're outside of the library). If you want to disable this packing, use (wrapped false) in the library stanza.