Using OCaml packages with ocamlbuild: a recipe

by: Matthieu Lemerre  tags: ocaml and build  published: 20 December 2012

Using OCaml packages with ocamlbuild can raise different error messages that are difficult to trace; there is a documentation ( http://brion.inria.fr/gallium/index.php/Ocamlbuild_and_module_packs ) that helps, but does not provide a step-by-step guide with common pitfalls, so here is one.

Compilation of bytecode files

First create a project with the following files:

File: plu/bla.ml

let bla = 3;;

File: plu/bli.ml

let bli = Bla.bla;;

File: plu.mlpack

plu/Bla
plu/Bli

File: use.ml

Plu.Bli.bli

Note: all the files do not have to be in a directory named plu/; this is just my convention.

Try to compile:

ocamlbuild use.byte
/usr/bin/ocamlc -c -o use.cmo use.ml
+ /usr/bin/ocamlc -c -o use.cmo use.ml
File "use.ml", line 1, characters 0-11:
Error: Unbound module Plu
Command exited with code 2.

Compilation will not work, probably because by default Ocamlbuild does not traverse directories. It will work if you use the -r option to ocamlbuild, or create a (even empty) _tags file at the root of the project, that will allow ocamlbuild to traverse the directories:

ocamlbuild use.byte
/usr/bin/ocamldep -modules plu/bla.ml > plu/bla.ml.depends
/usr/bin/ocamldep -modules plu/bli.ml > plu/bli.ml.depends
/usr/bin/ocamlc -c -I plu -o plu/bla.cmo plu/bla.ml
/usr/bin/ocamlc -c -I plu -o plu/bli.cmo plu/bli.ml
/usr/bin/ocamlc -pack plu/bla.cmo plu/bli.cmo -o plu.cmo
/usr/bin/ocamlc -c -o use.cmo use.ml
/usr/bin/ocamlc plu.cmo use.cmo -o use.byte

Notice that ocamlbuild has added -I plu when compiling modules of the Plu package; so for instance, Bli can access Bla.

This odd behaviour can be the cause of many headaches, so I prefer to document it here (and I filed a bug report about this issue here).

Also note that if you make a mistake in the plu.mlpack file, for instance an error in the name of the directory or in that of the module, ocamlbuild will fail without much warning. It will fail with a compile error, as before; it may also fail at the linking step, if you make a mistake in the plu.mlpack file, but you had successfully compiled a previous version. This problem can also be painful to track, so I hope this help someone.

ocamlbuild use.byte
/usr/bin/ocamlc use.cmo -o use.byte
+ /usr/bin/ocamlc use.cmo -o use.byte
File "_none_", line 1, characters 0-1:
Error: Error while linking use.cmo:
Reference to undefined global `Plu'
Command exited with code 2.

Adding native compilation

You cannot pack a set of .cmx files (resulting from compilation of a file) if you did not specified that the .cmx can be packed when you compile the file. Failure to do that result in an error like this:

ocamlbuild use.native
/usr/bin/ocamldep -modules use.ml > use.ml.depends
/usr/bin/ocamldep -modules plu/bla.ml > plu/bla.ml.depends
/usr/bin/ocamldep -modules plu/bli.ml > plu/bli.ml.depends
/usr/bin/ocamlc -c -I plu -o plu/bla.cmo plu/bla.ml
/usr/bin/ocamlc -c -I plu -o plu/bli.cmo plu/bli.ml
/usr/bin/ocamlc -pack plu/bla.cmo plu/bli.cmo -o plu.cmo
/usr/bin/ocamlc -c -o use.cmo use.ml
/usr/bin/ocamlopt -c -I plu -o plu/bla.cmx plu/bla.ml
/usr/bin/ocamlopt -c -I plu -o plu/bli.cmx plu/bli.ml
touch plu.mli  ;\
  if  /usr/bin/ocamlopt -pack -I plu plu/bla.cmx plu/bli.cmx -o plu.cmx  ;\
  then  rm -f plu.mli  ; else  rm -f plu.mli  ; exit 1; fi
+ touch plu.mli  ;\
  if  /usr/bin/ocamlopt -pack -I plu plu/bla.cmx plu/bli.cmx -o plu.cmx  ;\
  then  rm -f plu.mli  ; else  rm -f plu.mli  ; exit 1; fi
File "plu.cmx", line 1, characters 0-1:
Error: File plu/bla.cmx
was not compiled with the `-for-pack Plu' option
Command exited with code 1.

The lines:

/usr/bin/ocamlopt -c -I plu -o plu/bla.cmx plu/bla.ml
/usr/bin/ocamlopt -c -I plu -o plu/bli.cmx plu/bli.ml

Need to be compiled with the -for-pack Plu option. This is achieved by a simple change in the _tags file: just fill it with

<plu/*.cmx>: for-pack(Plu)

And now everything works:

ocamlbuild use.native
/usr/bin/ocamldep -modules use.ml > use.ml.depends
/usr/bin/ocamldep -modules plu/bla.ml > plu/bla.ml.depends
/usr/bin/ocamldep -modules plu/bli.ml > plu/bli.ml.depends
/usr/bin/ocamlc -c -I plu -o plu/bla.cmo plu/bla.ml
/usr/bin/ocamlc -c -I plu -o plu/bli.cmo plu/bli.ml
/usr/bin/ocamlc -pack plu/bla.cmo plu/bli.cmo -o plu.cmo
/usr/bin/ocamlc -c -o use.cmo use.ml
/usr/bin/ocamlopt -c -for-pack Plu -I plu -o plu/bla.cmx plu/bla.ml
/usr/bin/ocamlopt -c -for-pack Plu -I plu -o plu/bli.cmx plu/bli.ml
touch plu.mli  ;\
  if  /usr/bin/ocamlopt -pack -I plu plu/bla.cmx plu/bli.cmx -o plu.cmx  ;\
  then  rm -f plu.mli  ; else  rm -f plu.mli  ; exit 1; fi
/usr/bin/ocamlopt -c -o use.cmx use.ml
/usr/bin/ocamlopt plu.cmx use.cmx -o use.native

I will update this post if/when I find more weird error messages using packages with ocamlbuild; or if you find such error, just tell me!

Update: linking problem when refering to modules outside of the package

I discovered a new problem with ocamlbuild. The problems occur when you put code in a library which is included, and referenced only inside of a package; in that case ocamlbuild forgets to link with the library. The problem is well described in this mail. The author of that mail also provides a ocamlbuild plugin that works around the problem.

This seems to be a classical bug, and there is a pending bug report for it here.