How to have nested libraries? Confused about dune etc

2.6k views Asked by At

I have an OCaml project using dune

Following advice in basic tutorials I have a dir structure like:

bin/
    cli.ml
    dune
lib/
    dune
    ...
    <various>.ml

The number of files in my lib dir is growing and I would like to have another level of namespacing.

I want sub dirs like:

lib/
    utils/
        dune
        ...
        <various>.ml
    some_other_domain/
        dune
        ...
        <various>.ml
    dune
    ...
    <various>.ml

And I want to be able to open them like Lib.Utils.Whatever

I assume this must be possible?

I tried making a dune file under lib/utils like:

(library
  (name utils)
  (libraries ...))

...but open Lib.Utils.Whatever doesn't seem to work.

I found the subdir stanza ...but if I add that to lib/dune and define utils as a subdir library then I don't get the namespacing... I have to open Utils rather than open Lib.Utils

1

There are 1 answers

4
Lhooq On BEST ANSWER

It's actually a bit strange to call them "nested libraries" since you want to call them with Lib.Utils.Whatever. Utils, here, is a sub-module of Lib. Here's what I was able to do if it can help you:

.
├── bin
│   ├── cli.ml
│   └── dune
├── dune-project
├── lib
│   ├── dune
│   ├── lib.ml
│   ├── suba
│   │   └── suba.ml
│   └── subb
│       └── subb.ml

With

bin/cli.ml

let () =
  Lib.Suba.a ();
  Lib.Subb.b ()

bin/dune

(executable
 (name cli)
 (libraries lib)
)

lib/dune

(include_subdirs unqualified)

(library
 (name lib)
  (modules suba subb)
)

(If you include your modules like this, you'll have to use them with these exact names, another way of gaining control is to add the following file and remove the (modules suba subb) line:

lib/lib.ml

(* here you can give the name you want -- say A -- and use it in bin with Lib.A *)
module Suba = Suba
module Subb = Subb

(to summarise:

  • either your dune file is containing (modules suba subb)
    • if sub-directory is containing multiple files, you need to put all the files you're using inside your (modules ...) stanza or the compiler won't be able to use them
  • or a lib.ml file where each module you want to export should be included with module MyName = AModule (and only the ones you want to export)
    • with this solution, the modules you don't want to export don't need to be explicitly included in the lib.ml file, the compiler will use them if needed )

sub{a|b}/{a|b}.ml

let {a|b} () = Format.eprintf "{A|B}@."

suba.ml and subb.ml are used as sub-modules of lib and can be used with Lib.Suba.a() as you can see in cli.ml


Notice that this forbids you from giving the exact same name to two files since the directories will all be flatten in the parent directory so you can't have something like:

.
├── bin
│   ├── cli.ml
│   └── dune
├── dune-project
├── lib
│   ├── dune
│   ├── lib.ml
│   ├── suba
│   │   └── lib.ml
│   └── subb
│       └── lib.ml

because (include_subdirs unqualified) will make it look like

.
├── bin
│   ├── cli.ml
│   └── dune
├── dune-project
├── lib
│   ├── dune
│   ├── lib.ml
│   ├── lib.ml
|   └── lib.ml

and dune won't be able to know which lib.ml file to use.


[EDIT] If you want one directory per library you just have to remove the dune file at the root of lib and create one for each subdirectories:

.
├── bin
│   ├── cli.ml
│   └── dune
├── dune-project
├── lib
│   ├── suba
│   │   ├── dune
│   │   └── suba.ml
│   └── subb
│       ├── dune
│       └── subb.ml

The only changes are:

bin/cli.ml

let () =
  Suba.a (); (* no more Lib.(...) *)
  Subb.b ()

bin/dune

(executable
 (name cli)
 (libraries suba subb)
)

lib/dune has been removed

lib/sub{a|b}/dune

(library
 (name sub{a|b})
)

And in that case, multiple files inside different directories can have the same name.