• en

Using ocamlfind with ocamlbuild

There are many ways to make ocamlbuild use ocamlfind. The first section gives the simplest one, starting from ocamlfind 3.12. The second section gives a generic plugin which replaces the "ocamlc", "ocamldep"... commands with ocamlfind equivalents. The third shows how to call "ocamlfind query" from a plugin.

Simple tag (from 3.12)

Starting with version 3.12, the support of ocamlfind is native in ocamlbuild. To activate it, you need to pass the command line option -use-ocamlfind. Then you can add packages in the _tags file with the package(...) tag. Syntax extensions are enabled by requiring the related package and using the syntax(camlp4o) tag. Finally ocamlfind predicates can be activated with the predicate(...) tag.

# Suppose that prog.ml is the main module and
# that your project use num and str libraries.
$ cat _tags
<*.ml> or "prog.byte": package(num), package(str)
$ ocamlbuild -use-ocamlfind prog.byte --
Hello World!

To include sub-packages, use the doted notation

<*.ml>: package(lwt.unix), package(lwt.syntax), syntax(camlp4o)
"prog.byte": package(lwt.unix)

Generic plugin

Here is presented a rather generic plugin to access packages using ocamlfind.

Source

The following code should be in a file named myocamlbuild.ml, at the root of your project.

open Ocamlbuild_plugin
(* open Command -- no longer needed for OCaml >= 3.10.2 *)`

(* these functions are not really officially exported *)
let run_and_read = Ocamlbuild_pack.My_unix.run_and_read
let blank_sep_strings = Ocamlbuild_pack.Lexers.blank_sep_strings

let split s ch =
  let x = ref [] in
  let rec go s =
    let pos = String.index s ch in
    x := (String.before s pos)::!x;
    go (String.after s (pos + 1))
  in
  try go s
  with Not_found -> !x

let split_nl s = split s '\n'

let before_space s =
  try String.before s (String.index s ' ')
  with Not_found -> s`

(* this lists all supported packages *)
let find_packages () =
  List.map before_space (split_nl & run_and_read "ocamlfind list")

(* this is supposed to list available syntaxes,
   but I don't know how to do it. *)
let find_syntaxes () = ["camlp4o"; "camlp4r"]

(* ocamlfind command *)
let ocamlfind x = S[A"ocamlfind"; x]

let _ = dispatch begin function
  | Before_options ->
     (* by using Before_options one let command line options
        have an higher priority on the contrary using After_options
        will guarantee to have the higher priority *)
     (* override default commands by ocamlfind ones *)
     Options.ocamlc     := ocamlfind & A"ocamlc";
     Options.ocamlopt   := ocamlfind & A"ocamlopt";
     Options.ocamldep   := ocamlfind & A"ocamldep";
     Options.ocamldoc   := ocamlfind & A"ocamldoc";
     Options.ocamlmktop := ocamlfind & A"ocamlmktop"

  | After_rules ->
     (* When one link an OCaml library/binary/package, one should
        use -linkpkg *)
     flag ["ocaml"; "link"; "program"] & A"-linkpkg";

     (* For each ocamlfind package one inject the -package option when
        compiling, computing dependencies, generating documentation and
        linking. *)
     List.iter begin fun pkg ->
         flag ["ocaml"; "compile";  "pkg_"^pkg] & S[A"-package"; A pkg];
         flag ["ocaml"; "ocamldep"; "pkg_"^pkg] & S[A"-package"; A pkg];
         flag ["ocaml"; "doc";      "pkg_"^pkg] & S[A"-package"; A pkg];
         flag ["ocaml"; "link";     "pkg_"^pkg] & S[A"-package"; A pkg];
         flag ["ocaml"; "infer_interface"; "pkg_"^pkg] & S[A"-package"; A pkg];
       end (find_packages ());

     (* Like -package but for extensions syntax. Morover -syntax is
        useless when linking. *)
     List.iter begin fun syntax ->
         flag ["ocaml"; "compile";  "syntax_" ^ syntax]
         & S[A"-syntax"; A syntax];
         flag ["ocaml"; "ocamldep"; "syntax_" ^ syntax]
         & S[A"-syntax"; A syntax];
         flag ["ocaml"; "doc";      "syntax_" ^ syntax]
         & S[A"-syntax"; A syntax];
         flag ["ocaml"; "infer_interface"; "syntax_" ^ syntax]
         & S[A"-syntax"; A syntax];
       end (find_syntaxes ());
       
     (* The default "thread" tag is not compatible with ocamlfind.
         Indeed, the default rules add the "threads.cma" or
         "threads.cmxa" options when using this tag. When using the
         "-linkpkg" option with ocamlfind, this module will then be
         added twice on the command line.
       
        To solve this, one approach is to add the "-thread" option
        when using the "threads" package using the previous plugin.
      *)
     flag ["ocaml"; "pkg_threads"; "compile"] (S[A "-thread"]);
     flag ["ocaml"; "pkg_threads"; "link"] (S[A "-thread"]);
     flag ["ocaml"; "pkg_threads"; "infer_interface"] (S[A "-thread"])
  | _ -> ()
end

Usage

Packages to be used are supplied using the tagging system.

This plugin declares a tag pkg_<thepackagename> per package (for instance pkg_nums, pkg_str, pkg_unix,...).

Tag your source files with them if you want the package to be used a compile-time, and your .byte or .native files if you want the package to be used at link-time (usually you'll want both).

Example:

# Suppose that prog.ml is the main module and
# that your project use nums and str libraries.
$ cat _tags
<*.ml> or "prog.byte": pkg_nums, pkg_str
$ ocamlbuild prog.byte --
Hello World!

This plugin also provide a little support for the -syntax flag of ocamlfind. However the list of syntaxes are hard listed in the myocamlbuild.ml. Generally if you use packages that provide a syntax extension (like json-static), using camlp4o will suffice to trigger the extension.

Example:

$ wget http://martin.jambon.free.fr/yahoo.ml
$ cat _tags
<yahoo.\*\>`: pkg_json-static, pkg_netclient, syntax_camlp4o
$ ocamlbuild yahoo.byte -- ocaml
...

Summary

  • Tag the files that need the "foo" package with pkg_foo (sources and binaries)
  • Don't use the "thread" tag, use pkg_threads instead.

Using ocamlfind only for querying package installation directories

Maybe you do not want to invoke ocamllfind as a wrapper around the toolchain commands (ocamlc, etc.). In that case, you can still invoke ocamlfind to locate the directories in which registered packages are installed. The "ocamlfind query pkgname" command yields the installation directory of the command. Here is the ocamlbuild equivalent of $(ocamlfind query pkgname):

let ocamlfind_query pkg =
  let cmd = Printf.sprintf "ocamlfind query %s" (Filename.quote pkg) in
  Ocamlbuild_pack.My_unix.run_and_open cmd (fun ic ->
      Log.dprintf 5 "Getting Ocaml directory from command %s" cmd;
      input_line ic
    )

You can use it as follows in the After_rules section:

ocaml_lib ~extern:true ~dir:(ocamlfind_query "sexplib") "sexplib";