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 !!!