Project Description

Configuration files used by drom are in TOML syntax. See also Learn TOML in Y minutes for a very quick introduction.

A project is composed of the project itself, and a set of packages (libraries and executables), among which the main package shares the same name as the project. An opam package is generated for each package in the project.

The drom.toml Project File

To get a feeling of what is in this file, we can look at the one used for drom itself:

[project]
drom-version = "0.1.0"

[project]
drom-version = "0.1.0"

[project]
authors = ["Fabrice Le Fessant <fabrice.le_fessant@origin-labs.com>", "Léo Andrès <leo.andres@ocamlpro.com>"]
copyright = "OCamlPro SAS & Origin Labs SAS"
edition = "4.10.0"
github-organization = "ocamlpro"
license = "LGPL2"
min-edition = "4.07.0"
mode = "binary"
name = "drom"
synopsis = "The drom tool is a wrapper over opam/dune in an attempt to provide a cargo-like user experience"
version = "0.2.0"
windows-ci = true

# keys that you could also define:
# build-profile = "...build-profile..."
# odoc-target = "...odoc-target..."
# sphinx-target = "...sphinx-target..."
# archive = "...archive..."
# dev-repo = "...dev-repo..."
# bug-reports = "...bug-reports..."
# doc-api = "...doc-api..."
# doc-gen = "...doc-gen..."
# homepage = "...homepage..."

[project]
description = """
The drom tool is a wrapper over opam/dune in an attempt to provide a cargo-like
user experience. It can be used to create full OCaml projects with
sphinx and odoc documentation. It has specific knowledge of Github and
will generate files for Github Actions CI and Github pages.
"""

[project]
skip = "sphinx/about.rst src/drom_lib/main.ml sphinx/index.rst CHANGES.md test/expect-tests/test.ml ocamlformat"

[dependencies]

[tools.ocamlformat]
for-test = true
[tools.odoc]
for-doc = true
[tools.ppx_expect]
for-test = true
[tools.ppx_inline_test]
for-test = true

[profile.dev]
ocaml-flags = "-w +a-4-40-41-42-44"
[profile.release]
ocaml-flags = "-w -a"
[project]
generators = ["ocamllex", "menhir"]
pack-modules = true
skip-dirs = ["drom-test"]

[[package]]
dir = "src/drom"

[[package]]
dir = "src/drom_lib"

Notice that every package contains only the directory where the sources are located, in which drom expects to find a file package.toml describing the package itself.

The [project] table

This table describes the main project. For multi-package projects, these fields are used as default if the corresponding fields are not specified in sub-packages.

Here is a short description of the fields:

  • name: the name of the project and of its main package.
  • version: the current version of the project
  • authors: the list of authors of the project, to display in opam files and in the documentation
  • copyright: the name of the copyright holder for the project
  • edition: the version of OCaml to be used by default, when building locally or on the CI
  • github-organization: the organization owning the project on Github. This field is used to compute the URLs for the documentation.
  • kind: specify whether packages should be programs ("program") or libraries ("library") by default.
  • license: the identifier of the license. See the section about licenses. drom knows a few license and may automatically generate the corresponding files.
  • min-edition: the minimal version of OCaml with which the project can be built. The CI will start at least one job with this version.
  • mode: the target of the compilation. It is either "binary" for an executable, or javascript for a file generated by js_of_ocaml.
  • synopsis: a very short description of the project. opam requires this field to start with an uppercase letter and not end with a dot.
  • description: a long description of the project.
  • pack-modules: a bool indicating whether modules should be packed. true by default. It corresponds to the wrapped field of dune.

Some fields are computed automatically if github-organization is specified, but can be overriden:

  • archive: the URL from where the sources can be downloaded. Can contain ${version} instead of the version.
  • dev-repo: the location of the VCS for the project. They are often specified with git+https://.
  • bug-reports: the URL where issues can be created.
  • doc-api: the URL where the documentation of the API can be found.
  • doc-gen: the URL where the general documentation can be found.
  • homepage: the URL of the project main homepage

Another important field is the skip field:

  • skip contains a space-separated list of files, that should not be modified anymore by drom. By default, drom will generate any missing file, and upgrade any file that was previously generated by drom and not modified since then. drom will print a warning everytime it finds a modified file. This option can be used to avoid this warning, or to prevent drom from recreating an unwanted file. drom generates a file _drom/maximum-skip-field.txt showing all the values that can appear in this field.

    There is an exception for drom.toml and package.toml files, that are only modified when the user configuration has been changed, a changing option passed to drom project, or the --upgrade option to benefit from a more recent version of drom.

    skip can also contains tags: such tags can be used to reference several files, related to the tag itself. For example, current skeletons use the following tags: code (source files), docs (documentation), sphinx (sphinx documentation), test (test files), github (github pages and workflows). More tags may be defined in the future.

    To detect modifications of its files, drom generates a file .drom in the project. This file should be committed in the repository with the files, so that drom can always use it to detect changes.

    Every time drom skips a file because it has been modified, it saves the new version in the _drom/skipped/ directory.

    For example, if drom outputs:

    [...]
    Skipping modified file dune-project
    [...]
    

    You can use the command:

    $ diff dune-project _drom/skipped/dune-project
    

    to see the differences. if the differences are meaningless, you may then decide to promote the new file by removing your file and restarting drom.

The [dependencies] table

This section contains dependencies with their versions. These dependencies are for libraries only, as they will be added in the generated dune files.

For example:

ez_config = "0.1.0"

means that the project should be linked with ez_config with a version compatible with 0.1.0. drom uses semantic versioning, so it means version >= 0.1.0 and version < 1.0.0. If the version is not formatted as X.Y.Z, drom will only generate a constraint version >= V.

Versions can also be specified with some other formats:

  • Following opam conventions : ">0.1.0" for example
  • No constraint specified, with the empty string ""
  • Using the string "version" meaning that the version of the dependency should be the same one as the package

It is possible to specify more information than the version, in which case the dependency object should be seen as a record containing the fields:

  • version for the version of the dependency
  • libname for the name of the dependency that should be used as a dependency in dune files
  • for-test for a boolean specifying if the dependency is only needed for tests (with-test in opam files)
  • for-doc for a boolean specifying if the dependency is only needed for tests (with-doc in opam files)

For example:

[dependencies]
ez-config = { version = "0.1.0", libname = "ez_config" }

or equivalently:

[dependencies.ez-config]
version = "0.1.0"
libanem = "ez_config"

meaning that the project depends on the opam package ez-config, and that it should be linked with the corresponding library ez_config.

The [tools] table

This section contains dependencies that will appear in the opam files, but not as libraries in the dune files.

The [package] table

The package table has one entry per package (library or program) included in the project. One of the packages must have the same name as the project.

The most important field of the package description in the drom.toml file is the dir field. drom expects to find a package.toml file in that directory with more information on the packages.

Yet, it is possible to inline these fields also in the drom.toml file, but drom.toml will move them to their package.toml file if you ask it to upgrade the file (using drom project --upgrade). You can check the list of available fields in the documentation of package.toml.

The package.toml Files

Every package source directory (as specified by the dir field of each [[package]] entry in the drom.toml file) can contain a package.toml file.

For example:

$ less src/drom_lib/package.toml
gen-version = "version.ml"
kind = "library"
name = "drom_lib"
pack-modules = true

[dependencies]
directories = "0.2"
ez_cmdliner = "0.1.0"
ez_config = "0.1.0"
ez_file = "0.2.0"
opam-file-format = "2.1.1"
toml = "5.0.0"

[fields]
dune-libraries = "bigarray"

The following fields are allowed in a package definition:

  • dir: the directory of the package sources (defaults to src/${name}/
  • dependencies: the opam library dependencies of this package
  • generators: the list of program for which specific dune targets should be generated. Defaults to an empty list, but the project can define its own list ([ "ocamllex" ; "ocamlyacc" ] by default). The generator menhir is also available.
  • gen-version: whether a version.ml file should be created
  • kind: the kind of the package (library or program)
  • name: the name of the package (under which it will be published in opam
  • pack: if modules should be wrapped, the name of the wrapper module (defaults to the capitalized name of the library otherwise)
  • skeleton: the skeleton to be used to generate the files of this project (defaults to "program"). See the section on skeletons for more info.
  • tools: the opam tools dependencies of this package

The following fields are also allowed, and default to the project ones if not specified:

  • authors: the authors of the package
  • description: the full description of the package
  • mode: the mode of the package (binary or javascript)
  • pack-modules: whether modules should be wrapped or not
  • synopsis: the one-line description of the package
  • version: the version of this package

Finally, there is a table [fields] within a package. Currently, the following fields are used by skeletons:

  • dune-libraries: extra libraries that can be added in the libraries field of dune. Typically for internal libraries.
  • dune-stanzas: extra dune stanzas in the description
  • dune-trailer: a string that is added at the end of the dune file.
  • opam-trailer: a string that is added at the end of the opam file for the package.

In skeletons, fields are referenced using parens (for example !(dune-stanzas)).

Skeletons

drom generates most of the files using template files stored in skeletons on which substitutions are applied.

There are two kinds of skeletons:

  • Project skeletons are used to create and update most of the files of the project, excluding package specific files.
  • Package skeletons are used to create and update package specific files, mostly dune and sources files

When drom is called in a project, it generates a file _drom/known-skeletons.txt listing all the skeletons it knows about. Beware that, if you create a new skeleton, you will have to share it for other users to be able to use drom on the project to update generated files.

Project Skeletons

Default project skeletons are defined in the source tree in: https://github.com/OCamlPro/drom/tree/master/src/drom_lib/skeletons/projects

The following project skeletons are available by default:

  • The virtual skeleton is for a “meta” project, i.e. a project containing other packages, but whose main package (with the name of the project) does not define a program or library. This skeleton can be seen as the root of the inheritance tree between project skeletons.

    Files:

    $ drom new VIRTUAL --skeleton virtual
    └── VIRTUAL/
        ├── .drom             (drom state, do not edit)
        ├── .github/
        │   └── workflows/
        │       ├── doc-deploy.yml
        │       └── workflow.yml
        ├── .gitignore
        ├── .ocamlformat
        ├── .ocamlformat-ignore
        ├── .ocp-indent
        ├── CHANGES.md
        ├── LICENSE.md
        ├── Makefile
        ├── README.md
        ├── docs/
        │   ├── README.txt
        │   ├── doc/
        │   │   └── index.html
        │   ├── favicon.png
        │   ├── index.html
        │   ├── sphinx/
        │   │   └── index.html
        │   └── style.css
        ├── drom.toml    <────────── project config EDIT !
        ├── dune
        ├── dune-project
        ├── sphinx/
        │   ├── _static/
        │   │   └── css/
        │   │       └── fixes.css
        │   ├── about.rst
        │   ├── conf.py
        │   ├── index.rst
        │   ├── install.rst
        │   └── license.rst
        ├── src/
        │   └── VIRTUAL/
        │       └── package.toml    <────────── package config EDIT !
        └── test/
            ├── expect-tests/
            │   ├── dune
            │   └── test.ml
            ├── inline-tests/
            │   ├── dune
            │   └── test.ml
            └── output-tests/
                ├── dune
                ├── test1.expected
                ├── test2.expected
                └── test2.ml
    

    Notice that there are no .opam file generated by default in the virtual skeleton, until you add other packages using drom package XXX --new SKELETON

  • The library skeleton contains only a library package. It inherits from the virtual skeleton.

    Files:

    $ drom new LIBRARY --skeleton library
    └── LIBRARY/
        │
        .  (same as virtual )
        .
        ├── LIBRARY.opam
        ├── src/
        │   └── LIBRARY/
        │       ├── dune
        │       ├── index.mld
        │       ├── main.ml
        │       ├── package.toml    <────────── package config EDIT !
        │       └── version.mlt
        └── test/
        .
        .  (same as virtual )
    
  • The program skeleton contains both a library and a driver packages. It inherits from the virtual skeleton. It is the default project skeleton used when nothing is specified.

    Files:

    $ drom new PROGRAM --skeleton program
    └── PROGRAM/
        │
        .  (same as virtual )
        .
        ├── PROGRAM.opam
        ├── PROGRAM_lib.opam
        ├── src/
        │   ├── PROGRAM/
        │   │   ├── dune
        │   │   ├── index.mld
        │   │   ├── main.ml
        │   │   └── package.toml    <────────── package config EDIT !
        │   └── PROGRAM_lib/
        │       ├── dune
        │       ├── index.mld
        │       ├── main.ml
        │       ├── package.toml    <────────── package config EDIT !
        │       └── version.mlt
        └── test/
        .
        .  (same as virtual )
    
  • The mini-lib skeleton contains a library package, with a minimal set of files (no test, no docs, etc.) (since version 0.2.2)

    Files:

    $ drom new MINI-LIB --skeleton mini-lib
    └── MINI-LIB/
        ├── .drom             (drom state, do not edit)
        ├── .gitignore
        ├── CHANGES.md
        ├── LICENSE.md
        ├── MINI-LIB.opam
        ├── Makefile
        ├── README.md
        ├── drom.toml    <────────── project config EDIT !
        ├── dune
        ├── dune-project
        └── src/
            └── MINI-LIB/
                ├── dune
                ├── package.toml    <────────── package config EDIT !
                └── version.mlt
    
  • The mini-prg skeleton contains a program package, with a minimal set of files (no test, no docs, etc.) (since version 0.2.2)

    Files:

    $ drom new MINI-PRG --skeleton mini-prg
    └── MINI-PRG/
        ├── .drom             (drom state, do not edit)
        ├── .gitignore
        ├── CHANGES.md
        ├── LICENSE.md
        ├── MINI-PRG.opam
        ├── MINI-PRG_lib.opam
        ├── Makefile
        ├── README.md
        ├── drom.toml    <────────── project config EDIT !
        ├── dune
        ├── dune-project
        └── src/
            ├── MINI-PRG/
            │   ├── dune
            │   └── package.toml    <────────── package config EDIT !
            └── MINI-PRG_lib/
                ├── dune
                ├── package.toml    <────────── package config EDIT !
                └── version.mlt
    
  • The ppx_deriver skeleton contains a library package that defines a ppx_deriver, with a minimal example of code. (since version 0.2.2)

    Files:

    $ drom new PPX_DERIVER --skeleton ppx_deriver
    └── PPX_DERIVER/
        │
        .  (same as virtual )
        .
        ├── PPX_DERIVER.opam
        ├── src/
        │   └── PPX_DERIVER/
        │       ├── dune
        │       ├── index.mld
        │       ├── main.ml
        │       ├── package.toml    <────────── package config EDIT !
        │       └── version.mlt
        └── test/
        .
        .  (same as virtual )
    

Package Skeletons

Default package skeletons are defined in the source tree in: https://github.com/OCamlPro/drom/tree/master/src/drom_lib/skeletons/packages

The following package skeletons are available by default:

  • The virtual skeleton is an empty skeleton.
  • The library skeleton contains a simple library.
  • The program skeleton contains a simple program.
  • The driver skeleton contains a simple program that only calls the main entry point of an associated library.
  • The ppx_deriver skeleton contains a simple library that defines a ppx_deriver.

Note that the project program skeleton combines a library package skeleton with a driver package skeleton.

User-specified skeletons

Users can create their own skeletons. Check the documentation on Contributing Your Own Skeletons