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>.opamfile 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.
Using opam-publish with a project hosted on Github
First tag the project. Assuming this is version 0.1:
git tag -a 0.1
git push origin 0.1Alternatively, 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.
Using opam-publish with a project not hosted on Github
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 publishcan 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 publishclones opam repository over ssh, you need to have your ssh keys registered in your Github account.
Publishing 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: "sha256=a42b21dc49a05be658391e63fe64c055d52ec67c0812cf6801f1de0f22733ce3"
}
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.
synopsisshould be a one-line description of what your package does, used in listings. It is recommended to also add adescriptionfield 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
maintainerfor now, so give a way to contact you in case your package needs maintenance. - Most interesting is the
buildfield, 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 varwith opam 2.1.0) will give you a list of available variables.buildinstructions shouldn't need to write outside of the package's source directory. installis 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>.installfile, like is the case when usingdune.dependsshould 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 thebuildinstructions, 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
buildin 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
availablefield 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
conflictsfield is a list of packages, with optional version constraints. See alsoconflict-classfor families of incompatible packages.Optional dependencies: they change the way your package builds, but are not mandatory. The
depoptsfield is a package formula likedepends. simple list of package names. If you require specific versions, add aconflictsfield 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 varwith 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 }


