Plasma GitLab Archive
Projects Blog Knowledge

KNOWLEDGE BASE ON CAMLCITY.ORG: omake

KB 001: OMake Recipes

How to use the OMake build utility best - by Gerd Stolpmann, 2012-11-19

The OMake utility is easy to use for simple projects: After "omake --install" it drops a template OMakefile into the current directory, and you simply need to fill it out to get started. But what if you need more than what is provided in the template? Here, I've collected a number of recipes for advanced configurations.

Available recipes:

Starting point: A simple OMakefile for a program could look like:

USE_OCAMLFIND = true
OCAMLPACKS = lwt

FILES[] =
    foo
    bar

.DEFAULT: $(OCamlProgram myprog, $(FILES))
For a library, just use
.DEFAULT: $(OCamlLibrary mylib, $(FILES))
instead of the OCamlProgram line. The following recipes are, in some sense, variations of these simple patterns.

Normally, you just only set variables to control the build. Some of the variables only affect the linker, some only the compiler, and a number both (here the important ones):

Variable Compiler? Linker? Comment
USE_OCAMLFIND X X true/false - whether to use findlib
OCAMLINCLUDES X X search path for compiled modules and archives, also used by the dependency scanner
OCAMLFINDFLAGS X X additional flags to pass to ocamlfind
OCAMLPACKS X X findlib packages to include in build
BYTE_ENABLED X X true/false - whether bytecode compiler is enabled
NATIVE_ENABLED X X true/false - whether nativecode compiler is enabled
OCAMLCFLAGS X X flags to pass to "ocamlc"
OCAMLOPTFLAGS X X flags to pass to "ocamlopt"
OCAMLFLAGS X X flags to pass to "ocamlc" and "ocamlopt"
OCAML_BYTE_LINK_FLAGS X flags to pass to "ocamlc" when linking an executable
OCAML_NATIVE_LINK_FLAGS X flags to pass to "ocamlopt" when linking an executable
OCAML_LINK_FLAGS X flags to pass to both "ocamlc" and "ocamlopt" when linking executables
OCAML_LIBS X library archives of the project to link into executables
OCAML_OTHER_LIBS X library archives outside the project to link into executables
OCAML_LIB_FLAGS X extra flags when linking libraries

Building multiple programs or libraries

OMake allows it to split an OMakefile into sections. Each section can define its own set of variables, and its own build rules. A section defines a scope for variables: a section sees the variables of the surrounding section, but modifications to variables are local to the section, and not automatically propagated to the surrounding section (unless you "export" definitions - not covered here).

For example, this OMakefile builds two programs from a different set of files, and links different libraries into the programs:

USE_OCAMLFIND = true
OCAMLPACKS = lwt

section
    FILES[] =
        foo
        bar

    .DEFAULT: $(OCamlProgram myprog1, $(FILES))

section
    FILES[] =
        bar
        baz

    .DEFAULT: $(OCamlProgram myprog2, $(FILES))
Basically, it makes sense to set linking-related variables that are interpreted by OCamlProgram and OCamlLibrary, like OCAML_LIBS (see "Starting point" for a list of variables interpreted at link time).

What does not work here is to set variables that (only or also) affect the compiler (i.e. "ocamlc -c" and "ocamlopt -c"). If you e.g. set OCAMLINCLUDES or OCAMLPACKS within a section, there will be no effect on compilation (only on linking). Below you find another recipe that shows how to do that, but this simple recipe here does not allow it.


Building in multiple directories

Given you have a directory hierarchy
  root
   +--- dir1
   +--- dir2
and you want to build in all three directories. How can we describe this?

Solution 1: Multiple OMakefiles

Here, only root contains OMakeroot, and all three directories have their own OMakefile. Also, you need to "chain" the files: In root/OMakefile, there needs to be a .SUBDIRS directive, like:

# Before .SUBDIRS: Put here definitions that are visible in the whole project:
USE_OCAMLFIND = true
OCAMLPACKS = lwt

.SUBDIRS: dir1 dir2

# After .SUBDIRS: Add here definitions that are only applied in the root dir:
FILES[] =
    mainprog

OCAMLINCLUDES = dir1 dir2
OCAML_LIBS = dir1/lib1 dir2/lib2

.DEFAULT: $(OCamlProgram myprog, $(FILES))
Now, put into dir1/OMakefile and dir2/OMakefile the definitions that are local to these directories, e.g.
FILES[] =
    file1
    files
OCAMLFLAGS = -g
OCAMLPACKS += netstring

.DEFAULT: $(OCamlLibrary lib1, $(FILES))
Basically, the definitions in the sub-OMakefiles have their own scoping environment, as if they were encapsulated in a section. The scoping rules for directories and for sections are not identical, though. In particular, you can also define in a sub-OMakefile variables that affect the compilation, like OCAMLFLAGS, OCAMLPACKS, or OCAMLINCLUDES.

Solution 2: .SUBDIRS with body

Sometimes, you don't want to put OMakefiles into subdirectories, or the OMakefiles in several directories would be identical. To handle this case, it is allowed to put the definitions for the subdirectory into the body of .SUBDIRS (i.e. indented):

# Before .SUBDIRS: Put here definitions that are visible in the whole project:
USE_OCAMLFIND = true
OCAMLPACKS = lwt

.SUBDIRS: dir1 dir2
    .DEFAULT: $(OCamlProgram myprog, main)
This works exactly as if you had written the definitions in the body into separate OMakefiles in the subdirs.

How to enable camlp4

If you use ocamlfind, this is very simple, just set
OCAMLFINDFLAGS += -syntax camlp4o
OCAMLPACKS += camlp4
(or use "-syntax camlp4r" if you prefer the revised syntax). In order to activate a custom preprocessor written for camlp4, like xstrp4, just add this findlib package to OCAMLPACKS
OCAMLPACKS += xstrp4
Note that the variable OCAMLFINDFLAGS not only affects ocamlc and ocamlopt, but also ocamldep, i.e. the dependency scanning is also done with camlp4 and the activated syntax extensions.

Some custom preprocessors accept command-line arguments. For example, you can define macros on the command-line with the macro preprocessor:

OCAMLPACKS += camlp4.macro               # the macro preprocessor
OCAMLFINDFLAGS += $(mapprefix -ppopt, -D NAME=value)
The strange "mapprefix" expression just prepends -ppopt to every space-separated part of the second argument - well, in the end, camlp4 is called with -D NAME=value appended to the command-line.

If you don't trust in ocamlfind, you can also directly set the preprocessor command, e.g.

OCAMLFLAGS += -pp camlp4of
but custom extensions like xstrp4 can then not be enabled by simply adding the package to OCAMLPACKS. Instead, you would have to find out the right options for camlp4of, and add them to the command passed with -pp.

As camlp4 reduces the compilation speed tremendously, you often want to enable it only for a few files, but not for the whole project. See the following recipe how to do this.


Sections with deviating compiler flags

As already explained above, the section construct normally does not allow to modify compiler flags, but only linker flags. You may for example want to do this to invoke a preprocessor only for certain source files.

Note that you do not need this technique if you just set the compiler flags in a sub-OMakefile to different values than in the main OMakefile, because OMake already handles this case automatically.

The compiler flags have an effect on the standard build rules, which are defined by pattern rules like

%.cmo: %.ml
    $(OCamlC) -c $<
(the actual standard definitions are a lot more complicated), where OCamlC is a variable that also expands the compiler flags like OCAMLINCLUDES. Normally, the build rules just take the values these flags have at the end of the OMakefile. If you now want to force OMake to take the values set in a certain section, the trick is to recall the build rule within the section. Here is an example:
section
    OCAMLINCLUDES = additional_include_dir

    foo.cmi:
    foo.cmo:
    foo.cmx:
    foo.o:
This means that the module foo is compiled with the changed compiler flags. Note that we omit the bodies of the build rules - which OMake interprets to leave the body unchanged (i.e. take it from the pattern), and only to apply the current set of variables to the old body.

One application is to instruct OMake to compile certain modules with a preprocessor. The following defines a function doing this:

Camlp4o(module) =
    section
        OCAMLPACKS += camlp4
        OCAMLFINDFLAGS += -syntax camlp4o
        $(module).cmi:
        $(module).cmo:
        $(module).o:
        $(module).cmx:
You can call this function for every module as in
Camlp4o(mod1)
Camlp4o(mod2)
to enable camlp4 just for these modules.

Note that you need to name the modules explicitly that get a deviating set of compiler flags. You can, however, use an iteration to apply the deviating set to all modules you deal with in a section:

section
    FILES[] =
        foo
        bar
        baz
    CAMLP4_FILES[] =
        bar
        baz
    OCAML_LIBS[] =
        mylib1
        mylib2

    foreach(f, $(CAMLP4_FILES))
        Camlp4o($(f))

    .DEFAULT: $(OCamlProgram myprog1, $(FILES))

Not limited to camlp4: This is, of course, not limited to requiring camlp4. Another example it to increase the inlining value just for a certain file:

Inline(module,limit) =
    section
        OCAMLOPTFLAGS += -inline $(limit)
        $(module).cmi:
        $(module).cmo:
        $(module).o:
        $(module).cmx:

Inline(mod1, 10)
Inline(mod2, 15)

Handling dependencies between project libraries

Imagine you build a number of libraries, either in separate OMakefiles or in separate sections. For example, let's assume this directory hierarchy:
root
 +--- directory1    # containing lib1
 +--- directory2    # containing lib2
It is very simple to just use a library while building another (or building a program). E.g. if you need lib1 to build lib2, just set in directory2/OMakefile
OCAMLINCLUDES[] =
    ../directory1
to make the library available. OCAMLINCLUDES is a far-reaching variable: the directories enumerated in it are not only added as -I flags in the compiler commands to run, but also considered for dependency scanning. That means when a source file in directory1 is changed, the modules that depend on it in directory2 are automatically found out, and added to the list of things to recompile.

If you build a program using the libraries, you have to do a tiny additional step: In the OMakefile building the program also set OCAML_LIBS in addition to OCAMLINCLUDES. OCAML_LIBS needs to be set to the paths to the library archives (i.e. to the cma/cmxa files, but omit the suffix). For example, if there are programs to build in root/OMakefile, set

OCAMLINCLUDES[] =
    directory1
    directory2
OCAML_LIBS[] =
    directory1/archive1
    directory2/archive2
to link these archives while building the programs.

Many dependencies between project libraries

OMake will automatically take care of the dependencies between the libraries, and even between individual files. However, there is a disadvantage of doing only this: OMake does not provide a means of defining indirect dependencies well. For example, if you have lib1 using lib2, and the latter uses lib3, you need to list lib3 as a prerequisite for lib1, even if there is no direct relationship. For large projects with dozens of libraries this turns out to be a big problem, because such "expanded" dependencies are unmaintainable.

Solution 1: Define the dependencies only in the main OMakefile.

The idea is that there is a paragraph at the beginning of the main OMakefile describing the dependencies, e.g. if we have a directory hierarchy

root
 +--- directory1       # contains lib1
 +--- directory2       # contains lib2
 +--- directory3       # contains lib3
 +--- directory4       # build something else
we can define a number of variables in root/OMakefile as
LIB3_INCLUDES[] =
    $(dir directory3)
LIB2_INCLUDES[] =
    $(dir directory2)
    $(LIB3_INCLUDES)
LIB1_INCLUDES[] =
    $(dir directory1)
    $(LIB2_INCLUDES)
and in the sub-OMakefile for a certain library we set only OCAMLINCLUDES to one of the prepared variables, e.g. if lib4 needs lib1, we set in directory4/OMakefile for lib4:
OCAMLINCLUDES = $(LIB1_INCLUDES)

Why did we call the function dir while setting the LIB*_INCLUDES variables? Remember that we use the variable LIB1_INCLUDES in a subdirectory, so e.g. directory2 is here actually ../directory2. If we invoke dir, the variable magically rembers where it was defined, and if it is recalled from a different directory, OMake automatically adjusts the path.

(Detail: You might have noticed that LIB1_INCLUDES is actually an array of an array. This does not harm us here, because when OMake expands such structured variables to command-line arguments, it just flattens them to a list of arguments.)

For building programs, we also need the library archives. We can use the same pattern as for the include path:

LIB3_ARCHIVES[] =
    $(file directory3/archive3)
LIB2_ARCHIVES[] =
    $(file directory2/archive2)
    $(LIB3_ARCHIVES)
LIB1_ARCHIVES[] =
    $(file directory1/archive1)
    $(LIB2_ARCHIVES)
(Note that we need here to call file instead of dir).

For creating an executable linking in lib1 and all predecessors, just use

OCAMLINCLUDES = $(LIB1_INCLUDES)
OCAML_LIBS = $(LIB1_ARCHIVES)
If the library archives have all the same name, you can also do:
OCAMLINCLUDES = $(LIB1_INCLUDES)
OCAML_LIBS = $(addsuffix /archivename, $(LIB1_INCLUDES))
If the archives have the same name as the directories:
mapname(name) =
    value $(name)/$(basename $(name))

OCAMLINCLUDES = $(LIB1_INCLUDES)
OCAML_LIBS = $(foreach $(mapname), $(LIB1_INCLUDES))

Solution 2 (advanced): Extract the dependency data from META files.

Here we assume that we have already META files in the subdirectories. We want to use ocamlfind to look up paths and archive names:

root
 +--- directory1       # contains lib1
 |     +---- META
 +--- directory2       # contains lib2
 |     +---- META
 +--- directory3       # contains lib3
 |     +---- META
 +--- directory4       # contains lib4
       +---- META
The first thing to do is to set OCAMLPATH so it contains the root directory: Into root/OMakefile put:
setenv(OCAMLPATH, $(absname .):$(getenv OCAMLPATH,$(string)))

(Details: absname returns the absolute path name of the argument. $(string) is just the empty string. In OMake there is no good way of writing the empty string as a quoted literal.)

Now we can refer to the libraries as packages. The package names are just the directory names, e.g. "directory3" would mean lib3. It is, however, not sufficient to just add these names to OCAMLPACKS, because OMake would then not consider these libraries as part of the project, but as packages outside the project.

The solution is to still set OCAMLINCLUDES in the sub-OMakefiles like in solution 1. This time, however, we call ocamlfind for getting the values. To do so, define in root/OMakefile before the .SUBDIRS line

all_dirs = $(subdirs .)

get_project_findlib_dirs(pkg) =
    dirs = $(shella ocamlfind query -r $(pkg))
    fdirs =
    foreach (d, $(dirs))
        if $(mem $(dir $(d)), $(all_dirs))
            fdirs += $(d)
            export
        export
    value $(fdirs)

get_project_findlib_archives(pkg,pred) =
    dirs = $(shella ocamlfind query -format %d/%a -predicates $(pred) -r $(pkg))
    fdirs =
    foreach (d, $(dirs))
        if $(mem $(dir $(d)), $(all_dirs))
            fdirs += $(d)
            export
        export
    value $(fdirs)
Note that these functions only output directories and archives inside the project.

(Detail: Note that the mem function is used to check whether a directory d occurs in a list of directories all_dirs. This works independently of the path by which the directories are referenced. Actually, d is normally a relative path, and all_dirs contains absolute paths. Cool, isn't it.)

How to use this: In the sub-OMakefiles that create libraries:

OCAMLPACKS += directory1 directory2
OCAMLINCLUDES = $(get_project_findlib_dirs $(OCAMLPACKS))

With the OCAMLPACKS line the project-local findlib packages are added to the build. The OCAMLINCLUDES setting is only necessary to instruct the scanner that the dependencies to these packages are also needed.

In the sub-OMakefiles that create programs (here just program):

OCAMLPACKS += directory1 directory2

program.run: $(get_project_findlib_archives $(OCAMLPACKS), byte)
program.opt: $(get_project_findlib_archives $(OCAMLPACKS), native)
You need the two "program.run"/"program.opt" lines for every program you create. The effect is that the library archives of the project-local packages are added as dependency to the build.

How to create ocamldoc documentation

The following function works well - all interfaces with mli suffix are assumed to contain docs:
public.InterfaceDoc(name, files) =
    protected.mlifiles = $(filter-exists $(addsuffix .mli, $(files)))
    protected.cmifiles = $(addsuffix .cmi, $(removesuffix $(mlifiles)))

    $(name).idoc: $(mlifiles) $(cmifiles) /.PHONY/OCamlGeneratedFilesTarget
        ocamlfind ocamldoc -dump $(name).idoc -stars \
            $(PREFIXED_OCAMLINCLUDES) -package "$(OCAMLPACKS)" \
            $(mlifiles)
    return $(name).idoc
You call this function like this in every OMakefile compiling $(FILES):
InterfaceDoc(filename, $(FILES))
This creates a file filename.idoc (idoc for interface documentation) in the current directory. This file is a binary description of the documentation, and it can be used in a final ocamldoc step (modify this as needed):
IDOCS[] =
    subdirectory1/filename1.idoc
    subdirectory2/filename2.idoc

.PHONY: html-doc
html-doc: $(IDOCS)
    mkdir -p html
    rm -rf html/*
    ocamlfind ocamldoc -d html -stars -t "My title" -html \
        $(mapprefix -load, $(IDOCS))

How to interpret command-line parameters

If you try to modify a variable by setting it on the command-line, this often does not work. For example, if you try to override BYTE_ENABLED with
$ omake BYTE_ENABLED=true
this setting is often ignored. The reason for this is that omake interprets command-line assignments just as initial assignments, but not as overriding assignments like traditional "make". If your OMakefile contains already something like
BYTE_ENABLED = $(not $(OCAMLOPT_EXISTS))
this will simply overwrite the initial setting given on the command-line.

Note that the standard build definitions, as in e.g. OCaml.om (part of the OMake deployment) also set many control variables. These settings are, however, overridable. Look at OMakeroot to see why:

open build/C
open build/OCaml
open build/LaTeX
DefineCommandVars()
.SUBDIRS: .
In English, this just means that the three standard build definition files are loaded, and first after that the command-line assignments are evaluated. Finally, the .SUBDIRS: . directive includes the OMakefile in the same directory.

So, what to do? If you have a block at the beginning of your OMakefile where you customize variables, like

BYTE_ENABLED = true
NATIVE_ENABLED = $(OCAMLOPT_EXISTS)
OCAMLPACKS = p1 p2 p3
you just call DefineCommandVars again after this block:
DefineCommandVars()
This evaluates the assignments given on the command-line again, and the caller can override your customizations.

There is also the possibility to set variables only if they aren't already set. Unfortunately, OMake does not know the popular "make" syntax "variable ?= value", and we have to do it on our own:

if $(not $(defined X))
    X = default_value
    export
There are other formulations:
var_default(name, value) =
    if $(not $(defined $(name)))
        setvar($(name), $(value))
        export
    export

var_default(X, default_value)
Another option, closer to a normal assignment:
var_default(value, name) =         # different order!
    if $(not $(defined $(name)))
        setvar($(name), $(value))
        export
    export

var_default(X)
    default_value

Putting the build objects into subdirectories

Normally, OMake puts the output files of a build into the same directories as the source files. It is possible to change this. Assume the directory hierarchy is
root
 +---- directory1_src
 +---- directory1_out
 +---- directory2_src
 +---- directory2_out
and we want that the output files of the sources in *_src appear in *_out. This is achieved by changing the .SUBDIRS line in root/OMakefile into
vmount(-l, directory1_src, directory1_out)     # "minus ell" as first arg
vmount(-l, directory2_src, directory2_out)
.SUBDIRS: directory1_out directory2_out
The effect of vmount is that the files in the source directories are automatically linked into the output directories. After that, you do not refer to the source directories anymore, but just to the output directories.

Building several versions of the same: You can use the vmount feature to build several output directories from the same source directory, and have different configurations for each output. Example:

section
    vmount(-l, directory1_src, directory1_normal)
    ENABLE_DEBUG = false
    .SUBDIRS: directory1_normal

section
    vmount(-l, directory1_src, directory1_debug)
    ENABLE_DEBUG = true
    .SUBDIRS: directory1_debug
Of course, both versions share the same OMakefile (residing in directory1_src). Because of this, we set one variable ENABLE_DEBUG differently in both versions. You can now use conditionals like
if $(ENABLE_DEBUG)
    OCAMLFINDFLAGS += $(mapprefix -ppopt, -D DEBUG)
    # set a camlp4 macro for IFDEF
in directory1_src/OMakefile to enable the debug code if the OMakefile is interpreted in the "right" output directory.

Note that the OMake developers consider vmount still as experimental.


Profile builds

A gprof profile build is enabled by passing -p to ocamlopt. In an application project, we can do this like
if $(defined ENABLE_PROF)
    OCAMLOPTFLAGS += -p
    export
If the user calls omake on the command-line as
omake ENABLE_PROF=anyvalue
the feature is enabled.

Simultaneous profile and non-profile builds: If you develop a library for the general public, you may want to provide both a non-profile and a profile build. Given the following directory layout

root
 +---- directory1_src
 +---- directory1_normal
 +---- directory1_gprof
you can use the vmount feature to enable the profile build only in directory1_gprof. (We assume here that you do not build anything in root.) In root/OMakefile replace the normal .SUBDIRS line with
section
    vmount(-l, directory1_src, directory1_normal)
    ENABLE_PROF = false
    .SUBDIRS: directory1_normal

section
    vmount(-l, directory1_src, directory1_gprof)
    ENABLE_PROF = true
    .SUBDIRS: directory1_gprof
Put all your sources into directory1_src, and leave the other two directories empty. The directory1_src/OMakefile should contain
if $(defined ENABLE_PROF)
    OCAMLOPTFLAGS += -p
    BYTE_ENABLED = false
    export
at the beginning. If you now build your projects, you get everything twice: In directory1_normal profiling is disabled, whereas in directory1_gprof it is enabled.

When installing your project as findlib library, there is the possibility to put all archive and cmi files into the same findlib directory. The cmi files should be the same in both versions, so you can take them from either version. The native archives, however, are different (both the .cmxa and the .a files). An install target (in root/OMakefile) could e.g. do

.PHONY: install
install:
    ln-or-cp directory1_gprof/myarchive.cmxa directory1_gprof/myarchive.p.cmxa 
    ln-or-cp directory1_gprof/myarchive.a directory1_gprof/myarchive.p.a 
    ocamlfind install mylib META \
        directory1_src/*.mli \
        directory1_normal/*.cmi \
        directory1_normal/myarchive.cma \
        directory1_normal/myarchive.cmxa \
        directory1_normal/myarchive.a \
        directory1_gprof/myarchive.p.cmxa \
        directory1_gprof/myarchive.p.a
and the META file would refer to the archives like
archive(byte) = "myarchive.cma"
archive(native) = "myarchive.cmxa"
archive(native,gprof) = "myarchive.p.cmxa"
This way, the profile-enabled version of your library is automatically taken when ocamlfind ocamlopt -p runs.

Note that the OMake developers consider vmount still as experimental.


Generating files

Three standard cases are already handled by OMake:
  • ocamllex: OMake automatically finds files ending in .mll, and runs ocamllex to generate to corresponding .ml files
  • ocamlyacc: Similarly, files ending in .mly are processed by ocamlyacc
  • menhir: If you set MENHIR_ENABLED=true, files ending in .mly are instead processed by menhir
If you use a different generator, you need to add rules for handling these.

Example: The atdgen utility can generate I/O serializers for given type definitions. If called like

atdgen -t foo.atd
it generates from the description foo.atd two files, namely foo_t.ml and foo_t.mli. We create this rule for it:
%_t.ml %_t.mli: %.atd
    atdgen -t $<
Note that you should describe all output files on the left side of the colon, and all input files on the right side. In the command, $< is replaced by the leftmost input file.

It most cases, adding such a rule is already fully sufficient to drive the generation, and to include the generated files into the dependency graph. OMake calls ocamldep with the -modules flag meaning that it emits the dependencies by module names and not by files. Because of that, ocamldep also prints dependencies on modules that do not yet exist as files, but first need to be generated. OMake then postprocesses these dependencies, and compares them with the left sides of the rules to check whether the files can be generated.


Compiling stub libraries

OMake contains full support for C projects. Nevertheless, for stub libraries wrapping C functions for use in OCaml some special definitions should be made.

The problem here is that OMake has its own idea what the C compiler to call is, and with which flags. With a few configurations, though, one can tell OMake to call the right C compiler with the right flags:

CC_CONFIG = $(shell $(OCAMLC) -config | grep bytecode_c_compiler:)
CC = $(nth 1, $(CC_CONFIG))
CFLAGS = $(nth-tl 2, $(CC_CONFIG))
CFLAGS += -I$(shell $(OCAMLC) -where)
These are essentially the same flags that are added when you compile a C file and use ocamlc as driver (e.g. ocamlc -c file.c).

In a native-only project you can replace "bytecode_c_compiler" with "native_c_compiler" (this avoids the -fPIC flag giving a minimal boost).

The built-in link function OCamlLibrary does not support stub libraries. Because of this we need to define our own rules. Given the following source files

foo.ml
foo.mli
foo_stubs.c
we would like to generate these outputs:
libfoo.a           # containing foo_stubs.o
libfoo.so
foo.cma            # the compiled foo.ml
foo.cmxa
The rules doing this:
OCAML_MKLIB_FLAGS = ...       # set e.g. to: -lbar

if $(BYTE_ENABLED)
    foo.cma libfoo$(EXT_LIB): foo.cmo foo_stubs$(EXT_OBJ)
        ocamlmklib -o foo foo.cmo foo_stubs$(EXT_OBJ) \
             $(OCAML_BYTE_LINK_FLAGS) $(OCAML_LINK_FLAGS) $(OCAML_MKLIB_FLAGS)

if $(NATIVE_ENABLED)
    foo.cmxa libfoo$(EXT_LIB): foo.cmx foo_stubs$(EXT_OBJ)
        ocamlmklib -custom -o foo foo.cmx foo_stubs$(EXT_OBJ) \
             $(OCAML_NATIVE_LINK_FLAGS) $(OCAML_LINK_FLAGS) $(OCAML_MKLIB_FLAGS)
Note that we do not specify that libfoo.so (or libfoo.dll on Windows) is also created, at least on many platforms.

The ":" problem

Beware that the OMake grammar has some ambiguities. For example
echo A : B
does not output "A : B", but rather defines a dependency that "echo" and "A" depend on "B". How to circumvent:
  • Put : into quotes (":") if meaningful (note that such quotes are passed down to invoked commands)
  • Put : into OMake quotes ($":"). This should always work
  • Quote : with a backslash (\:)
  • It is also no problem if the colon occurs inside $(...)
Note that the problem does not occur when you define a variable (e.g. X = A : B).

Cleaning completely

I often switch between different ocaml versions (e.g. currently between ocaml-3.12 and ocaml-4.00), and I have of a separate omake instance for each version. So far everything works great.

Sometimes, however, I compile a project with a different ocaml version than the last time. Now I get errors, even for "omake clean", e.g.:

*** omake: reading OMakefiles
*** omake error:
   File /opt/godi-4.00/lib/omake/build/C.om: line 4, characters 2-32
   unbound variable: public.OMakeVersion
The problem is that OMake caches some values between invocations, namely in the files ending in .omc.

The solutions:

  • Remove all .omc files, and .omakedb in the topmost directory
  • Call omake with flag --flush-includes

Expressions or statements?

You may already have wondered by we write
.DEFAULT: $(OCamlProgram ...)
if the program is to built by default, but only
OCamlProgram(...)
if not. Essentially, OMake distinguishes between contexts where you invoke functions as expressions returning values, and contexts where functions are only seen as statements, returning nothing.

On the outermost level, everything is a statement, e.g.

  • A statement defining a variable:
    VAR = expr
  • A statement defining a build rule:
    file.html: file.txt
        transform-txt-to-html file.txt
    
  • A conditional statement:
    if $(condition)
        statement1
    else
        statement2
    
Within statements, you can substitute arguments by using expressions. In an expression, the executable code is within $(...) parentheses:
  • Substitute the value of a variable:
    $(VAR)
  • Call a function:
    $(getenv PATH)
  • Conditional:
    $(if $(condition),$(getenv V1),$(getenv V2))
Actually, the distinction between expressions and statements is only syntactical, because statement blocks are allowed to return values:
value $(expression...)
(in function definitions you can also use return if you want to leave the function immediately). And, of course, expressions may have side effects, e.g.
$(setvar VAR, $(...))
sets VAR in the same way as the statement VAR=$(...) would have. In some sense, the duality between "statement syntax" and "expression syntax" exists to keep some compatibility with traditional "make".

Now back to our initial question. OCamlProgram is a function that defines a number of build rules, and finally returns the main targets as an array of strings. If you execute OCamlProgram as a statement, the returned array is silently dropped. If you invoke it as an expression, as in

.DEFAULT: $(OCamlProgram prog, $(FILES))
the function returns the array [| "prog"; "prog.opt"; "prog.run" |] after definining the build rules as side effect, and this is the same as if you had written
OCamlProgram(prog, $(FILES))
.DEFAULT: prog prog.opt prog.run
effectively declaring these targets as default targets.

Gerd Stolpmann works as O'Caml consultant
This web site is published by Informatikbüro Gerd Stolpmann
Powered by Caml