Creating and publishing opam packages
An opam package is defined by a <pkgname>.opam
, or simply opam
file,
containing its metadata. This short guide will get you through writing this
definition, testing it, and publishing to the
opam-repository.
Creating a package definition file
For complete documentation of the format, see the manual.
If your project does not yet have a package definition, get to the root of its source, and then either
- run
opam pin .
to create and edit a template, and test your definition right away, - or create a
<pkgname>.opam
file and edit it by hand.
The file follows a simple field: <value>
format:
opam-version: "2.0"
name: "project"
version: "0.1"
synopsis: "One-line description"
description: """
Longer description
"""
maintainer: "Name <email>"
authors: "Name <email>"
license: ""
homepage: ""
bug-reports: ""
dev-repo: ""
depends: [ "ocaml" "ocamlfind" ]
build: [
["./configure" "--prefix=%{prefix}%"]
[make]
]
install: [make "install"]
The depends:
, build:
and install:
are the most important fields here. If
your project uses dune
, skip install:
and
use:
build: ["dune" "build" "-p" name "-j" jobs]
See below for more on the format.
It is recommended to use opam lint
to check the
validity of your opam file.
Testing
Make sure you have committed everything if using a version-control system
(e.g. git add *.opam && git commit
), and just run
opam install .
from the root of your project. This will attempt to install the newly-defined package so you can check it goes as expected.
Publishing
Publishing is done using Github's pull-request mechanism, which allows automated checks to be run, and discussion to happen with the maintainers before your contribution is merged. You will need a Github account.
Submitting is most easily done using the opam-publish
plugin. Run opam
publish --help
for more options.
If the project is hosted on Github
First tag the project. Assuming this is version 0.1:
git tag -a 0.1
git push origin 0.1
Alternatively, you can create a release using the web UI (https://github.com/USER/REPO/releases/new).
Then just run opam publish
from your source directory and follow the steps.
If not
Assuming your release is available as an archive at
https://foo.bar/project-0.1.tar.gz
, run:
opam publish https://foo.bar/project-0.1.tar.gz .
from your source directory. The final .
argument indicates to search for
package definitions in the current directory rather than in the archive.
opam publish
can be re-run any number of times to update an existing submission, or propose changes to an already released package.To fill the pull request,
opam publish
clones opam repository over ssh, you need to have your ssh keys registered in your Github account.
Without opam-publish
First, you will need to add a section in the following format to the package definition, to specify where to retrieve the source of the package:
url {
src: "https://address/of/project.1.0.tar.gz"
checksum: "md5=3ffed1987a040024076c08f4a7af9b21"
}
Then get to https://github.com/ocaml/opam-repository and select Fork
on the
top-right. Clone the resulting repository, add your package definition, and
push back, as such:
git clone git@github.com:USER/opam-repository
cd opam-repository
cp OPAM_FILE packages/NAME/NAME.VERSION/opam
git add packages
git commit
git push origin HEAD:add-pkg-NAME
Then, back to the web UI, Github should propose to file a pull-request for your
newly pushed branch. If not, select the new pull request
button on the left.
The file format in more detail
The basics
The opam-version
and maintainer
fields are mandatory; you should
remove the others rather than leave them empty.
synopsis
should be a one-line description of what your package does, used in listings. It is recommended to also add adescription
field for a longer explanation (hint: you may delimit long strings with triple-quotation mark delimiters"""
to avoid escaping issues). Since opam 2.0.1, linting requires to have at least synopsis or description filled.- You'll probably be the
maintainer
for now, so give a way to contact you in case your package needs maintenance. - Most interesting is the
build
field, that tells opam how to compile the project. Each element of the list is a single command in square brackets, containing arguments either as a string ("./configure"
) or a variable name (make
, defined by default to point at the chosen "make" command -- think$(MAKE)
in Makefiles).%{prefix}%
is another syntax to replace variables within strings.opam config list
(oropam var
with opam 2.1.0) will give you a list of available variables.build
instructions shouldn't need to write outside of the package's source directory. install
is similar tobuild
, but tells opam how to install. The example above should indeed beinstall: [ [make "install"] ]
, but the extra square brackets are optional when there is a single element. This field can be skipped if your package generates a<pkgname>.install
file, like is the case when usingdune
.depends
should be a list of existing opam package names that your package relies on to build and run. You'll be guaranteed those are there when you execute thebuild
instructions, and won't be changed or removed while your package is installed. If contributing to the default repository at https://opam.ocaml.org, it is quite unlikely that you don't need at least"ocaml"
there.
Note: when running local shell scripts during e.g.
build:
, it is preferable to usebuild: ["sh" "-exc" "./SCRIPT"]
than call the script directly.
A few other fields are available, but that should be enough to get started. Like
install
, most fields may contain lists in square brackets rather than a single
element: maintainer
, author
, homepage
, bug-reports
, license
and
depends
. You may add a remove
field, but since opam 2.0, removal of
installed files is done automatically, so that should only be needed if your
install
modified existing files.
Advanced usage
This is just a very short introduction, don't be afraid to consult the reference for details and more:
Version constraints: an optional version constraint can be added after any package name in
depends
: simply write"package" {>= "3.2"}
. Warning, versions are strings too, don't forget the quotes.Formulas: depends are by default a conjunction (all of them are required), but you can use the logical "and"
&
and "or"|
operators, and even group with parentheses. The same is true for version constraints:("pkg1" & "pkg2") | "pkg3" {>= "3.2" & != "3.7"}
.Build depends: you may add, among others, the key
build
in the version constraints, e.g."package" {build & >= "3.2"}
, to indicate that there is no run-time dependency to this package: it is required but won't trigger rebuilds of your package when changed.OS constraints: The
available
field is a formula that determines your package's availability based on the operating system or other global opam variables. For example:available: [ os != "macos" ]
Conflicts: some packages just can't coexist. The
conflicts
field is a list of packages, with optional version constraints. See alsoconflict-class
for families of incompatible packages.Optional dependencies: they change the way your package builds, but are not mandatory. The
depopts
field is a package formula likedepends
. simple list of package names. If you require specific versions, add aconflicts
field with the ones that won't work.Variables: you can get a list of predefined variables that you can use in your opam rules with
opam config list
(oropam var
with opam 2.1.0).Filters: dependencies, commands and single command arguments may need to be omitted depending on the environment. This uses the same optional argument syntax as above, postfix curly braces, with boolean conditions:
["./configure" "--with-foo" {ocaml-version > "3.12"} "--prefix=%{prefix}%"] [make "byte"] { !ocaml:native } [make "native"] { ocaml:native }