Date Tags ocaml

Today I struggled once again to build a simple library with ocamlbuild, so once for all I decided to write something about it. I’m sure next time, googling for an answer I’ll find this post and shake my head in despair :)

The library in question is parmap written by Roberto Di Cosmo to speed up computations on modern multi processors computers. We want to build everything: cma, cmxa and cmxs. Moreover, we want to build a shared library that contains stubs for a couple of bindings to C functions. On top of that, I want to use configure to make sure that the platform I’m using supports a specific syscall.

We start by copying ocaml.m4, the more or less standard autoconf macros for OCaml ( source here ). I prefer to copy this file in my source tree as I don’t want to impose to download it from a website to compile my library. This file ends up in the directory m4. Then to use it, I need to invoke aclocal as follows aclocal -I m4. This will take care of making the m4 macros known to the configure script.

Next step is the configure script. I need check for the standard ocaml utils, if extlib is known by ocamlfind and if the function sched_setaffinity is available on the system. This is the standard autoconf way to detect if a specific function is available. Together with the AC_CONFIG_HEADERS([config.h]) call, this will define the variable HAVE_DECL_SCHED_SETAFFINITY to 1 in the file config.h that can be later used in the C source code.

AC_INIT(parmap, 0.9.4, roberto@dicosmo.org)

AC_PROG_OCAML
if test "$OCAMLC" = "no"; then
 AC_MSG_ERROR([You must install the OCaml compiler])
fi

AC_PROG_CAMLP4
AC_SUBST(CAMLP4O)
if test "$CAMLP4" = "no"; then
 AC_MSG_ERROR([You must install the Camlp4 pre-processor])
fi

AC_PROG_FINDLIB
AC_SUBST(OCAMLFIND)
if test "$OCAMLFIND" = "no"; then
 AC_MSG_ERROR([You must install OCaml findlib (the ocamlfind command)])
fi

AC_CHECK_OCAML_PKG([extlib])
if test "$OCAML_PKG_extlib" = "no"; then
 AC_MSG_ERROR([Please install OCaml findlib module 'extlib'.])
fi

AC_HEADER_STDC
AC_CHECK_HEADERS([sched.h],,AC_MSG_ERROR([missing sched.h]))
AC_CHECK_DECLS([sched_setaffinity], [], [], [[
                #define _GNU_SOURCE 1
                #include <sched.h>
                ]])

AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([Makefile])
AC_OUTPUT

Once this is done I’ve to generate the config .h.in with autoreconf and then run autoconf to generate my configure script. If this go through, you should be able to run your configure script as usual. So far so good. Now it time to convince ocamlbuild to play nice with us.

First we need to write a small myocamlbuild file to set few dependencies and compilation flags. The first rule we add is a dependency to convince ocamlbuild to copy the config.h file in the _build directory. Without this rule, ocamlbuild is unable to figure out the dependency just by looking at the C source file (I’ve found a [bug http://caml.inria.fr/mantis/view.php?id=5107] on the inria bts about this problem).

The second rule adds a couple of compilations options to compile our C code. I think this can be done on a file basis, but since these options make no harm, I use to compile all my C objects.

The third rule is there to make aware ocamlbuild that is I want to compile a library that uses libparmap, I must specify that libparmap is a dynamically linked library. The forth rule is an analogues rule for native libraries.

The fifth and sixth rule are one to compile the .a and then to link it. This is need to correctly generate cmxs objects.

open Ocamlbuild_plugin ;;

let _ = dispatch begin function
  | After_rules ->
      dep  ["compile"; "c"] ["config.h"];

      flag ["compile"; "c"] & S[ A"-ccopt"; A"-D_GNU_SOURCE"; A"-ccopt"; A"-fPIC" ];

      flag ["link"; "library"; "ocaml"; "byte"; "use_libparmap"] &
        S[A"-dllib"; A"-lparmap_stubs";];
      flag ["link"; "library"; "ocaml"; "native"; "use_libparmap"] &
          S[A"-cclib"; A"-lparmap_stubs"];
      dep ["link"; "ocaml"; "use_libparmap"] ["libparmap_stubs.a"];
      flag ["link"; "ocaml"; "link_libparmap"] (A"libparmap_stubs.a");

  | _ -> ()
end

But of course this is only part of the ocamlbuild configuration. We also need to specify how to build these libraries and what to include. To build the stubs library we specify a .clib file in which we tell to ocamlbuild the C objects it has to link together.

This is accomplish with by adding the file libparmap_stubs.clib :

$cat libparmap_stubs.clib
bytearray_stubs.o
setcore_stubs.o

To build parmap.cm{x,}a and parmap.cmxs I need to other files, respectively :

$cat parmap.mllib
Parmap
Bytearray

$cat parmap.mldylib
Parmap
Bytearray

and at last we write the _tags file to correctly associate different flags to each component.

$cat _tags 
<*>: annot
<parmap.cm{x,}a>: use_libparmap
<parmap.cmxs>: link_libparmap
<*.{ml,mli}>: package(extlib), package(unix), package(bigarray)

This should build all you goodies in one go :

$ocamlbuild -use-ocamlfind parmap.cma  parmap.cmxa  parmap.cmxs parmap.a -classic-display
/usr/bin/ocamlfind ocamlopt -I /usr/lib/ocaml/ocamlbuild unix.cmxa /usr/lib/ocaml/ocamlbuild/ocamlbuildlib.cmxa myocamlbuild.ml /usr/lib/ocaml/ocamlbuild/ocamlbuild.cmx -o myocamlbuild
/usr/bin/ocamlfind ocamlc -ccopt -D_GNU_SOURCE -ccopt -fPIC -c bytearray_stubs.c
/usr/bin/ocamlfind ocamlc -ccopt -D_GNU_SOURCE -ccopt -fPIC -c setcore_stubs.c
/usr/bin/ocamlmklib -o parmap_stubs bytearray_stubs.o setcore_stubs.o
/usr/bin/ocamlfind ocamldep -package bigarray -package extlib -package unix -modules parmap.mli > parmap.mli.depends
/usr/bin/ocamlfind ocamlc -c -annot -package bigarray -package extlib -package unix -o parmap.cmi parmap.mli
/usr/bin/ocamlfind ocamldep -package bigarray -package extlib -package unix -modules parmap.ml > parmap.ml.depends
/usr/bin/ocamlfind ocamldep -package bigarray -package extlib -package unix -modules bytearray.mli > bytearray.mli.depends
/usr/bin/ocamlfind ocamldep -package bigarray -package extlib -package unix -modules setcore.mli > setcore.mli.depends
/usr/bin/ocamlfind ocamlc -c -annot -package bigarray -package extlib -package unix -o bytearray.cmi bytearray.mli
/usr/bin/ocamlfind ocamlc -c -annot -package bigarray -package extlib -package unix -o setcore.cmi setcore.mli
/usr/bin/ocamlfind ocamldep -package bigarray -package extlib -package unix -modules bytearray.ml > bytearray.ml.depends
/usr/bin/ocamlfind ocamlc -c -annot -package bigarray -package extlib -package unix -o parmap.cmo parmap.ml
/usr/bin/ocamlfind ocamlc -c -annot -package bigarray -package extlib -package unix -o bytearray.cmo bytearray.ml
/usr/bin/ocamlfind ocamlc -a -dllib -lparmap_stubs bytearray.cmo parmap.cmo -o parmap.cma
/usr/bin/ocamlfind ocamlopt -c -annot -package bigarray -package extlib -package unix -o bytearray.cmx bytearray.ml
/usr/bin/ocamlfind ocamlopt -c -annot -package bigarray -package extlib -package unix -o parmap.cmx parmap.ml
/usr/bin/ocamlfind ocamlopt -a -cclib -lparmap_stubs bytearray.cmx parmap.cmx -o parmap.cmxa
/usr/bin/ocamlfind ocamlopt -shared libparmap_stubs.a bytearray.cmx parmap.cmx -o parmap.cmxs

Relevant files: http://gitorious.org/parmap/parmap/blobs/pipes/myocamlbuild.ml http://gitorious.org/parmap/parmap/blobs/pipes/configure.ac http://gitorious.org/parmap/parmap/blobs/pipes/Makefile.in  http://gitorious.org/parmap/parmap/blobs/pipes/_tags

All the source code is in Git parmap - in the pipes branch. If I’m doing something wrong, or not in the standard way, please tell !!!