How to expose a function to a file in ocsigen?

Hi !
Writing some pgocaml/database related code in a file (as required), I realized that I had difficulties exposing a simple function from another file. Here is what I tried:

(* inside a function in a file: *)
let hstore_opt = Utils.one_of_opt_db hstores_opt in

(* inside utils.eliom: *)
let filter_none_db l =
  let rec skip_none acc_l = function
    | None :: tl -> skip_none acc_l tl
    | Some e :: tl -> skip_none (e::acc_l) tl
    | [] -> acc_l
  skip_none [] l
let one_of_opt_db l =
    match filter_none_db l with
    | a :: _ -> Some a
    | [] -> None

It lead me to following error message:

File "", line 240, characters 19-38:
240 |   let hstore_opt = Utils.one_of_opt_db hstores_opt in
Error: Unbound value Utils.one_of_opt_db
Makefile.os:235: recipe for target '_server/administrative_book_db.cmo' failed

And it is the same if I replace [%%shared ... ] by [%%server ... ] (and even if I had the same declarations in a [%%client ... ] block just after).

It is very surprising to me for three reasons:

1/ as in ocsigen-start, this file starts with open Os_db and uses function full_transaction_block without any problem.

2/ I expose a lot of types declared in shared section from another module without problem

3/ I exposed already a log function from utils.eliom. This function is a bit special, I wrote it this way so that I could called it the same way from client and server side:

let%server log s =
  let s = " # log: "^s in
  let%lwt _ = Lwt_unix.write_string
         (String.length s)
  Lwt.return ()

let%server log_from_client =
    [%json: string]
    (Os_session.Opt.connected_rpc (fun userid s -> log s))

let%client log_from_client = ~%log_from_client

let%client log = ~%log_from_client

(I tried to expose one_of_opt_db the same way with an rpc, but I could not serialize polymorphic type 'a option list to json)
I can of course simply write those functions inside the file, but I find it a bit frustrating to do this way if I want to split this file later.
What do you think I am doing wrong?
(I use eliom 6.11.0 and ocsigen-start 2.16.1)
Thanks a lot for your help!


As you already found out, there’s nothing preventing you from having a file depending on an .eliom file. That being said, it might be the case that utils.eliom depends, in some indirect way, on your file. If this is the case, you can confirm that by taking a look at the .depend file (lying in the root directory of project) or putting the two functions that you exposed in a new .eliom file.

Hope this helps


Hello @Zzull,
I think I spotted a bug in ocsigen-start build system thanks to your help. Here I explain my investigation:
In the .depend file, I could see that my file and my utils.eliom file was not depending on each other:

_server/administrative_book_db.cmo : _server/type.cmo
_server/administrative_book_db.cmx : _server/type.cmx
_client/utils.cmo :
_client/utils.cmx :
_client/utils.cmo : _server/utils.type_mli
_client/utils.cmx : _server/utils.type_mli
_server/utils.cmo :
_server/utils.cmx :
_server/utils.cmo : _server/utils.type_mli
_server/utils.cmx : _server/utils.type_mli

I was not surprised that utils.eliom file did not depend on file, but the opposite surprised me a lot because I make calls to Utils.log inside file without any compile time nor runtime problem.
I decided to move definitions of filter_none_db and one_of_opt_db into another file (utils_bis.eliom), and I got a compiler error:

File "", line 367, characters 12-35:
367 |   let s_l = Utils_bis.filter_none_db s_l_o in
Error: Unbound module Utils_bis

So I deduced that make test.byte does not automatically make aware a file of other modules.
I tried a local open let open Util_bis in, but it did not change anything.
As I import values from a module called Type (from type.eliom) with a global opening at the beginning of the file, I tried a global open open Utils_bis at the beginning, and then it worked!

Although the problem was solved, I wanted to understand why I could open only part on Utils module into file. Here is my further investigation:
I checked .depend file. It stated that _db_file was now depending on utils_bis.eliom. Then I removed open Utils_bis and the beginning of file, I saved, I compiled, and… everything was fine: no compile time nor runtime error while I was using Utils_bis module. I checked .depend file that stated that file did not depend on utils_bis.eliom anymore.

I began to understand that .depend file for file keeps only track of dependencies for modules that are globally opened. Then if a global open is removed, dependency is removed from .depend, but previously built interface for file remain (that is why I could import an “old” log function from utils.eliom, but not a new one).

To confirm this hypothesis, I added a small function to utils_bis.eliom:

let%shared do_nothing () = ()

and I tried to call it inside my

let () = Utils_bis.do_nothing () in

And I got error:

Error: Unbound value Utils_bis.do_nothing

That was solved again by a open Utils_bis at the beginning of file, that I could remove after one succesful compilation.

So to sum it up:

  • a file cannot be aware of values in another .eliom file without a (possibly removed in the past) global open.
  • there is a problem of synchronization between .depend file and built interface that expose modules to others (“real dependencies”) for files.

Again thanks for your help,
Have a good day !

[EDIT : I could not reproduce the bug on a fresh ocsigen-start project, so I did not open an issue about it on github]

You definitely put your finger on something fishy in Makefile.os. And your answer made me remember that I actually did stumble on something similar too. And the ‘solution’ (which isn’t one because it’s not fixing the Makefile) was to make distclean the project.

1 Like

Thank you @Zzull for sharing your tip ! I just tried make distclean but it did not remove the strange behavior. For now I am ok with the “fix” I found, and maybe one day I will explore Makefile.os.

Best !