NOTE
OPAM 1.1 is no longer supported. Documentation for OPAM 1.2 is available here.
Creating OPAM packages
In this tutorial, you will learn how to package an OCaml library or software for OPAM. The first section will introduce you the ounit OPAM package. Albeit simple, it is a real example of what an OPAM package is, and actually most packages in the OPAM repository are that simple. The second section will be a comprehensive guide illustrated by more complicated examples of real packaging cases.
An important thing to keep in mind is that OPAM is (at least for now) only a package manager, as opposed to — for example — the oasis/odb suite, which includes a build system (oasis) as well as a package manager (odb). This means that OPAM cannot help you to actually build your ocaml software or library, to do so, you need to use dedicated tools such as ocamlbuild, oasis, omake and others. OPAM packages are just built the same way you would build the software yourself (with shell commands).
The ounit OPAM package
Let get started by learning from the ounit OPAM package. This is a widely used OCaml library to create unit tests for OCaml projects, and is a perfect example of a minimalistic yet complete package.
By reading about ounit on the upstream website,
you learn that it is a library to create unit tests, that its latest version
is 1.1.2, and that it depends on ocamlfind.
You know the URL you can download its source tarball, and you must compute the md5sum of
this source tarball by running md5sum ounit-1.1.2.tar.gz
(or, on some operating systems, md5
instead of md5sum
).
You also learn that to build and install it, you have to:
make build
make install
And that’s all the information you need to build an OPAM package.
A minimum OPAM package is a directory containing three files: descr
, opam
, and url
.
The name of the directory defines the package name and version: <package-name>.<package-version>
.
In our case, the directory will be ounit.1.1.2
and contain the following files:
packages/<package-name>/<package-name>.<package-version>/descr
Unit testing framework inspired by the JUnit tool and the HUnit tool
packages/<package-name>/<package-name>.<package-version>/opam
opam-version: "1"
maintainer: "contact@ocamlpro.com"
build: [
[make "build"]
[make "install"]
]
remove: [
["ocamlfind" "remove" "oUnit"]
]
depends: ["ocamlfind"]
packages/<package-name>/<package-name>.<package-version>/url
archive: "http://forge.ocamlcore.org/frs/download.php/886/ounit-1.1.2.tar.gz"
checksum: "14e4d8ee551004dbcc1607f438ef7d83"
Notes
descr
This file is pure text. Its first line will be used as a short
description of the package, it is what is displayed for each package
when you do opam list
. The whole text contained in there is
displayed when you do opam search <package>
. Therefore you should
put a short meaningful description on the first line, and a long
description starting from the second line.
opam
The full ABNF specification of the syntax for opam files is
available in OPAM
developer manual.
In this file,
opam-version
MUST be 1
, and you should put your email in the
maintainer
field. build
has OCaml type string list list
, and
contains the build instructions. Here,
make build
make install
gets translated into
build: [
[make "build"]
[make "install"]
]
You should adapt this to the required commands to build your package,
and each line contains the shell commands corresponding to a
string list
. Note that make
is a special variable which will be
automatically translated to either make
on linux and OSX or gmake
on BSD systems.
The remove
field follows the same syntax as the build
field. The
depends
field is a string list
of dependencies, with each dependency
being another OPAM package. Here ounit depends only on ocamlfind.
url
This file contains at least one archive
line containing the URL of
the source package, and optionally a checksum
line that must contain
the MD5 sum of the source package if it is present. It is good practice
to systematically add a checksum line to your packages, unless the
source package has no fixed version (the typical example being a
source package hosted on github with no tags). This checksum will be
checked when creating and installing the package.
The URL can also contain a single git
or darcs
field instead of archive
,
which points to GIT or DARCS repository URL. This will be checked out and
updated every time opam update
is run, which is useful for development
packages.
Testing custom OPAM packages
The easiest way to test your new packages is to set-up a local repository for testing purposes.
$ mkdir -p /tmp/testing
$ opam repo add testing /tmp/testing
These commands add a new (currently empty) repository named
testing
(you can pick an other name if your prefer) which will
contain what is in /tmp/testing
(Remark: you can also clone the
official git repository if you don't want to start from a fresh one,
this will work as well).
You can now check that this new repository exists:
$ opam repo # eq. to 'opam repo list'
This command displays the list of repositories, with testing
having the highest priority (you can use opam repo priority
to
change the relative repository priorities later).
Now it is time to populate /tmp/testing/packages
with your new package
files. For instance, if you want to test the version 1.1.3
of ounit
,
you have to create /tmp/testing/packages/ounit.1.1.3/{opam,descr,url}
following the guidelines defined above.
To take this changes into account, update your testing repository:
$ opam update testing
If everything is fine, OPAM should tell you than a new version of ounit
is
available. If this is the case, you can install it by doing:
$ opam install ounit.1.1.3
Remark: you can use opam-admin
to simulate the creation of OPAM
package archives done on opam.ocaml.org
:
$ cd /tmp/testing && opam-admin check && opam-admin make -g ounit
This command will:
- Check that your metadata are well-formed.
- Download the upstream archive, and generate the correct checksum (because of `-g);
- Create the archive
archives/ounit.1.1.3+opam.tar.gz
containing the content of the upstream archive + the files inpackages/ounit/ounit.1.1.3/files/
- Create
urls.txt
andindex.tar.gz
at the root of your repository, which will let you host it as an HTTP remote.
If archives/ounit.1.1.3+opam.tar.gz
exists, OPAM will use it
directly instead of downloading the archive upstream.
If (i) the basic installation and (ii) the archive creation work, you are in good shape to submit your new package upstream (see below).
Advanced OPAM packaging guide
This section will be as comprehensive as possible on the art of creating OPAM packages, but in case of ambiguities, the ABNF syntax documentation has priority.
Since everything has already be said about the descr
file and almost
everything about the url
file, this section is mostly about the
opam
files.
OPAM variables
OPAM maintains a set of variables (key value pairs) that can be used
in opam
files and that will be substituted by their values on
package creation. The list of variables that can be used in opam
files can be displayed by doing opam config list
. The
following example shows the build
section of package ocamlnet that
use the variable bin
:
build: [
["./configure" "--bindir" bin]
[make "all"]
[make "opt"]
[make "install"]
]
In this case, bin
will be substituted by the value of the bin
variable. In case you need to substitue a substring, you can use
"--bindir=%{bin}%"
: here %{bin}%
will be substituted by the
value of bin
.
Optional dependencies
We mentioned the ability to specify optional dependencies for packages. If a package has optional dependencies, they will not be installed automatically, but will be taken into account if they are present before the installation. If the optional dependency is not present, but are subsequently installed, then the depending package will also be recompiled to take advantage of the newly installed library.
Let us see how it works via the following example, which shows the opam
file of the lwt package:
opam-version: "1"
maintainer: "contact@ocamlpro.com"
build: [
["./configure"
"--%{conf-libev:enable}%-libev"
"--%{react:enable}%-react"
"--%{ssl:enable}%-ssl"
"--%{base-unix:enable}%-unix"
"--%{base-unix:enable}%-extra"
"--%{base-threads:enable}%-preemptive"]
[make "build"]
[make "install"]
]
remove: [
["ocamlfind" "remove" "lwt"]
]
depends: ["ocamlfind"]
depopts: ["base-threads" "base-unix" "conf-libev" "ssl" "react"]
Notice the new depopts
field, which contains the list of
optional dependencies, specified in the same format as the
depends
field.
Also notice a new syntax for substitutions of the form
%{<package>:enable}%
. If package is
installed, this pattern will be replaced by enable
, otherwise by
disable
. This ease the building of lines of type ./configure
--enable-<feature1> --disable-<feature2>
.
Version constraints
If a package depends (respectively optionally depends) on a specific
version of a package, this can be specified by using the following
syntax for the field depends
(respectively depopts
) of the opam
file:
depends: [ "ocamlfind" "re" "uri" "ounit" ]
depopts: [ "async" {= "108.00.02"} "lwt" "mirage-net" ]
The above example shows two fields of the opam
file of package
cohttp, that optionally depends of the version 108.00.02 of package
async.
The OPAM specification document specifies the format used by the
depends
and depopts
fields. As there is much more to say about
version constraints, you should read it to learn how to write very
fine grained version constraints.
Manually installing binaries or libaries
Most of the time, when a package is built, binaries and/or libraries
that it provides are installed by the package's build
commands. However, for various reasons, sometimes a source package's
build commands do not allow you to install all the files you would
like or do not install them with the name or path you would like. Opam
allows you to control installed files precisely by providing a
.install
file. For example, the package xmlm has this following
opam
file:
opam-version: "1"
maintainer: "contact@ocamlpro.com"
build: [
["ocaml" "setup.ml" "-configure" "--prefix" prefix]
["ocaml" "setup.ml" "-build"]
["ocaml" "setup.ml" "-install"]
]
remove: [
["ocamlfind" "remove" "xmlm"]
]
depends: ["ocamlfind"]
and has additional file files/xmlm.install
:
bin: ["_build/test/xmltrip.native" {"xmltrip"}]
This has the semantics: “install the file of path
_build/test/xmltrip.native
relative to the root of the source
package into the directory returned by the command opam config var
bin
under the name xmltrip
”. If the source filename starts by ?
,
the installation will not fail if the file is not present.
Thus, this additional file gets installed and removed by opam, in
addition to those installed by the ocaml setup.ml -install
and
removed by the ocamlfind remove xmlm
commands.
The bin
section installs files in opam's bin
directory. You can
also define a list of files to be installed in the sections: lib
,
toplevel
, share
, doc
, misc
, stublibs
, and man
. For
example, to install additional library files, you can have the
section:
lib: [ "META" "lib/foo.cmi" "lib/foo.cmo" "lib/foo.cmx" ]
Note the destination name in curly braces can be omitted if no modification to the filename is necessary.
Ideally, the .install
file should be dynamically created by the
package build system at the root of the project and should be named
$(OPAM_PACKAGE_NAME).install
($OPAM_PACKAGE_NAME
is automatically
set by OPAM). If it comprehensively lists every file that should be
installed, then the build
section of the package's opam
file
should exclude a make install
(or equivalent) command, and the
remove
section should be omitted.
Aside from dynamically generating this file, it is also possible to
include a static version in a package under the files
sub-directory,
as is the case for the xmlm example above. This is less ideal than
dynamically generating it, but is supported because package developers
may find it easier to generate this file manually than figuring out
how to dynamically generate it with their build system.
For comprehensive information about this facility refer to the Section 1.2.5 of OPAM developer manual
Compiler version constraints
Some packages require a specific OCaml version to work and thus can
only be installed under specific compiler versions. To specify such a
constraint, you can add a field ocaml-version: [ <
"4.00.0" ]
(or available: [ ocaml-version < "4.00.0" ]
for OPAM 1.1 on) to the opam
file. This particular constraint implies
that the package cannot be built or installed under OCaml 4.00.0 or
later.
Patching sources
You can instruct OPAM to apply patches to the source code before building a package. To do so, you have to add a patches field to the opam file, the syntax being of the form patches: ["bugfix1.patch" "bugfix2.patch"]
, where bugfix1.patch and bugfix2.patch are two files existing in the directory files
. Before building such a package, OPAM will substitute any opam variable (of the form %{variable}%
) to their respective values and apply the resulting patches to the source code. Only then will the package be built. For more information, please look at packages including patches, such as dbm.1.0
.
Git / Darcs packages
It is possible to use a git repository instead of an archive file in
url
files. To do so, you need to use the following syntax:
git: "<url>"
<url>
being any url that git knows how to clone. For git packages,
OPAM has the following behaviour:
When installing a git package, OPAM will use git to clone its url and use it as the package source
When updating packages, OPAM will do a
git fetch
in order to have the last patches available for git packagesWhen upgrading packages, OPAM will merge the last changes before rebuilding and upgrading the packages
You can specify a branch other than the default by suffixing the url with
#<branch>
, eg.git: git@github.com:me/project.git#next
If you host your project on Github, you may
instead use github’s functionality to create a tarball from a git
repository. It is generally available at
https://github.com/<your-id>/<your-project>/tarball/<branch-or-tag>
. You
can use this url to create “normal” — non-git packages from git
repositories hosted on github.
Note that git packages will normally never be included in the default OPAM repository, and are mainly an aid for developers who use OPAM in their development process. If you plan to do that, please have a look at the Developing with OPAM tutorial.
Darcs packages
Darcs repositories are supported as well. OPAM behaves the
same way it does with git repositories, as described above. You just need to
specify the repository url using the following syntax in url
files:
darcs: "<url>"
Where to go from here
Although this tutorial covered most packaging cases, there are still
packages that requires more tuning that what have been described
above. If you find yourself stuck trying to package a software or a
library, please read the OPAM
developer manual
(you will find it in the
doc
directory in the OPAM tarball) and/or read existing OPAM
package descriptions for inspiration.
Including packages to the official OPAM repository
This section will help you getting started with the process of submitting packages to the official OPAM repository. This repository is available at url [http://opam.ocaml.org], but its content is generated from a git repository hosted on github.
To submit a package for inclusion in the official repository, all you
have to do is to fork opam-repository
on github, commit a patch
containing the package(s) you want to include, and open a pull request
for it.
If the above sentence makes no sense for you, you probably don’t know about either git or github (or both). git is a distributed version control system, very popular at the time we write those lines. Getting started with git is definitely a topic outside the scope of this short tutorial, but you can read about it on git’s documentation page.
Github is a web frontend to git. It allows users to store public git repositories, and provides additional convenient features over git such as “pull requests” that automatise the process of sharing patches with others. You can learn how to use it here.
If you cannot (or do not want to) use github but still want to
contribute to packages, you can try sending a mail to
contact@ocamlpro.com
with your patches to opam-repository
. As this
method requires manual intervention from our not very big OPAM team,
it should only be used if the first method is not an option for
you. It might as well take more time for your request to be processed
in this case.