Recursive dependencies for C stubs with dune

How does a library B express its internal dependency on library A, so that a user C can use B without knowing about A?

The case at hand is ocaml-ctypes, which depends on ocaml-integers. A public header in ocaml-ctypes includes a public header from ocaml-integers (ctypes_primitives.h includes ocaml_integers.h).

Then there is luv, which uses ocaml-ctypes directly (by including ctypes_cstubs_internals.h in some C source file.), but not ocaml-integers - because that is an internal dependency of ocaml-ctypes.

Since dune is apparently not aware of the dependency of B to A, the C stubs build includes only the include path to B, the include path to A is missing.

A workaround in user C may look like this:

--- ocaml-luv-0.5.7.orig/src/c/dune
+++ ocaml-luv-0.5.7/src/c/dune
@@ -117,6 +117,7 @@ let () = Jbuild_plugin.V1.send @@ {|
  (deps (:c generate_types_step_2.c) helpers.h shims.h)
  (action (bash "\
   %{cc} %{c} \
+  -I '%{lib:integers:.}' \
   -I '%{lib:ctypes:.}' \
   -I %{ocaml_where} \
   |}^ i_option ^{| -o %{targets}")))

But, now that I look at the patch, this cmdline is all manually constructed, so little dune can actually do.

The question still stands: does dune actually know that B depends on A, and would it add the include path to A when B is used?

2 Likes

I’m far from an expert on this, but can relate what I do know.

The OCaml compiler has a built-in mechanism for libraries to record the C flags that they require, so that consumers of the library can pick them up. (See the documentation of ocamlc -a for more.) This information is stored in .cm(x)a files, and sure enough we can see the dependendency on integers is recorded in ctypes:

; ocamlobjinfo ~/.opam/default/lib/ctypes/ctypes.cmxa | head -n 4
File /home/craigfe/.opam/default/lib/ctypes/ctypes.cmxa
Extra C object files: -lctypes_stubs -lintegers_stubs -Wl,--no-as-needed
Extra C options: -I/home/craigfe/.opam/default/lib/integers
Name: Ctypes_ptr

Dune supports this mechanism, and will ensure that libraries / executables that it builds get the right includes (see the documentation of include_dirs here.). As you rightly point out, it can’t do this for opaque bash actions. I suspect a non-ad-hoc fix requires a Dune equivalent of ocamlfind query -i-format -recursive ctypes, and AFAIK this isn’t exposed in dune files yet. This is perhaps why @antron ended up hand-rolling a build rule here :slight_smile:

Thank you. This seems to be missing in my build of ctypes.

ls -1 /usr/lib64/ocaml/ctypes*/*.cmxa|xargs -n1 ocamlobjinfo|grep -w C
Extra C object files: -lctypes_foreign_stubs -L/usr/lib64/../lib64 -lffi -Wl,--no-as-needed 
Extra C options:
Extra C object files: -lctypes_stubs
Extra C options:

What are the required knobs in a dune file to express such dependency? None of the packages seem to need this feature. This command lists just two compiler core files.

for i in $(find `ocamlc -where` -name "*.cmxa")
do
 ocamlobjinfo $i | grep "^Extra C options:." && echo $i
done

The required includes are added by ocaml-ctypes own homegrown build system, that’s why it happens to work for everyone else.