• en

Making plugins

Getting started

An OCamlbuild plugin is a single Ocaml file, named "myocamlbuild.ml", which must be in the root directory of your project. Before building any target, this file will be compiled, linked to and executed by OCamlbuild. This means that your plugin will have access to some parts of OCamlbuild, namely the plugin API.

Hello, world!

Before actually using the API, let's show that the plugin is indeed executed by OCamlbuild. Write a simple myocamlbuild.ml such as:

print_string "Hello, world!\n"

Now execute OCamlbuild. You should get something like this:

$ ocamlbuild
Finished, 1 target (0 cached) in 00:00:00.
Hello, world!
Finished, 0 targets (0 cached) in 00:00:00.

This shows that your plugin has been compiled, and then executed.

The dispatch function

The dispatch function is the most important function of the API. It allows you to control when your code will be executed. In practice, it takes a function as an argument and executes it with a different parameter at the beginning and at the end of the following stages:

  • hygiene (checking that the source tree is clean),
  • options (parsing the command line options),
  • rules (adding the default rules to the system).

For demonstration purposes, try the following plugin, which prints the value of the -ocamlc option before, and after the "options" stage:

open Ocamlbuild_plugin (* open the main API module *)
 
let () =
  dispatch begin function
  | Before_options ->
      Printf.printf "Before options, the -ocamlc option is set to %s.\n"
        (Command.string_of_command_spec !Options.ocamlc)
  | After_options ->
      Printf.printf "After options, The -ocamlc option is set to %s.\n"
        (Command.string_of_command_spec !Options.ocamlc)
  | _ -> ()
  end

Now if you run Ocamlbuild, you can see when the option is applied:

$ ocamlbuild -ocamlc bla
Before options, the -ocamlc option is set to <virtual OCAMLC>.
After options, The -ocamlc option is set to bla.

Usually, you will want your code to be executed after the "rules" stage (i.e., using the After_rules hook). But for some needs, you will use other hooks, for instance:

  • Before_options: to change the default value of some options
  • After_options: to enforce the value of some options
  • Before_hygiene: to tag or untag some files as precious or non hygienic

Basic operations

To start slowly, let's see how to write a plugin which does what you can do without a plugin. This will allow us to better understand how plugins are plugged into OCamlbuild.

Change options

You can use a plugin to set the value of an OCamlbuild option such as -no-hygiene or -ocamlc. This way you won't have to write the option every time on the command line.

To set an option, simply set the option reference. All these references are defined in the Options module of the API.

Remember that if you change the reference Before_options, the value set in your plugin can still be overridden by the command line. If you want to force the value of an option you have to do it After_options. See the example in the previous section.

Manage tags

Your plugin can replace your _tags file(s). Indeed, a plugin can read or change the tags of a file. This is usually done After_rules.

List the tags of a file

To list the tags of a file, use the API function tags_of_pathname. For example, a useful debugging function might be:

let print_info f =
  Format.fprintf Format.std_formatter
    "@[<hv 2>Tags for file %s:@ %a@]@." f
    Tags.print (tags_of_pathname f)

Tag a file

To add tags to a file, use the API function tag_file. For example, to tag the file bla.ml with the tag use_unix:

tag_file "bla.ml" ["use_unix"]

Untag a file

To remove tags from a file, use the same API tag_file with a minus before the tag's name. For example, to remove the tag use_unix from the file bla.ml:

tag_file "bla.ml" ["-use_unix"]

Example

If you dispatch the following code after the rules After_rules:

print_info "bla.ml";
tag_file "bla.ml" ["test"];
print_info "bla.ml";
tag_file "bla.ml" ["-test"; "-ocaml"];
print_info "bla.ml";

You will get the following result:

Tags for file bla.ml:
  extension:ml, file:bla.ml, ocaml, quiet, traverse, use_graph, use_nums,
  use_str, use_unix
Tags for file bla.ml:
  extension:ml, file:bla.ml, ocaml, quiet, test, traverse, use_graph,
  use_nums, use_str, use_unix
Tags for file bla.ml:
  extension:ml, file:bla.ml, quiet, traverse, use_graph, use_nums, use_str,
  use_unix

Declare libraries

If you don't like to use the -lib option, you can write a plugin which will use your libraries automatically.

Standard libraries

Libraries such as unix, num, str or bigarray are already declared in OCamlbuild, so all you have to do is tag your files using use_unix, use_num and so on using the tag_file API.

Other libraries

See Using an external library if your libraries are installed, or Using internal libraries otherwise. You might also prefer Using ocamlfind with ocamlbuild.

Advanced operations

These operations cannot be done without a plugin. Because one cannot imagine everything you'll want to do, you might not find exactly what you need here. Be sure to read the API documentation, to experiment with stuff and, why not, to read the OCamlbuild source.

Add or modify rules

Before reading this section, be sure you understand the solver mechanism.

Rules

Once you know how rules are handled by OCamlbuild, all you have to know is that adding a rule is done using the "rule" function of the API.

Usually, all you have to do is call the "rule" function with these arguments:

  • The name of the rule. It should be unique. This is the name that appears when using the "-documentation" or -verbose options.
  • ~dep: the dependency (use ~deps if there are more than 1)
  • ~prod: the production (use ~prods if there are more than 1)
  • The action of the rule, i.e. a function which returns the command to use to produce the productions. This command may have holes (see the T constructor of type Command.spec).
File names

The ~deps and ~prod parameters don't have to be exact path names. For example, with ~dep="%.ml" and ~prod="%.byte", you can produce bla.byte from bla.ml, or foo.byte from foo.ml, or... If you need more than one % you can use %(name) where name is any identifier. For example, the file why.not.ml matches the pattern %(bla).%(foo).ml with %(bla)=why and %(foo)=not.

The action of the rules takes two arguments. The first one is usually called env and is used to replace your % in a string. For example, with the previous example, env "%(foo).%(bla).plop" returns "not.why.plop".

Commands

Your action returns a command (see module Command) which should build the productions of the rule.

There are some pre-defined commands in the Ocamlbuild_plugin module, such as usual Unix commands (cp, mv, ...).

Commands may come with tag holes that will be filled depending on the tags. For instance:

let ml = env "%.ml" in
Cmd(S[ocamlc; A "-c";
      T(tags_of_pathname ml ++ "ocaml" ++ "compile" ++ "byte"); A ml])

Usually, the executed command will be (if env "%.ml" = "bla.ml"):

ocamlc -c bla.ml

But say the file bla.ml is tagged with thread. There is a flag rule which says that the tags ocaml, compile and thread together should produce the -thread option. In this case, the executed command becomes:

ocamlc -c -thread bla.ml
Dynamic dependencies

The second argument of the action is usually called build and is used to build new dependencies that you couldn't write in the ~deps parameter. It takes a conjunction of alternative targets. For instance, build [["a"; "b"]; ["c"]] will try to build two targets: "a" and "c", or "b" and "c".

The value returned by build tells you which files have been built. For instance, build [["a"; "b"]; ["c"]] may return [Good "a"; Bad exn]. This means that "a" has been built, but that "c" couldn't be built. You should raise the exn exception if you can't do without "c".

The Outcome module has some handy functions to help you with the return values of build.

Examples

Tags and flags

You can also use the dep and flag functions. The dep function allows you to automatically build some dependencies when a file is tagged with some given tags, and the flag function allows you to associate command options with tags (to fill the holes of a command). For instance, here is how to declare that tags "ocaml", "compile" and "thread" should become "-thread":

flag ["ocaml"; "compile"; "thread"] (A "-thread")

Examples:

There is no direct support to link external modules with your plugin. For now the only way to achieve this is to invoke ocamlbuild as follows (the example uses the Str module) :

ocamlbuild -ocamlc 'ocamlc str.cma' -ocamlopt 'ocamlopt str.cmxa'