Project Description
===================
Configuration files used by :code:`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 :code:`opam` package is generated for
each package in the project.
The :code:`drom.toml` Project File
----------------------------------
To get a feeling of what is in this file, we can look at the one used for
:code:`drom` itself::
[project]
drom-version = "0.1.0"
[project]
drom-version = "0.1.0"
[project]
authors = ["Fabrice Le Fessant ", "Léo Andrès "]
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 :code:`drom` expects to find a file \
:code:`package.toml` describing the package itself.
The :code:`[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:
* :code:`name`: the name of the project and of its main package.
* :code:`version`: the current version of the project
* :code:`authors`: the list of authors of the project, to display in
opam files and in the documentation
* :code:`copyright`: the name of the copyright holder for the project
* :code:`edition`: the version of OCaml to be used by default, when
building locally or on the CI
* :code:`github-organization`: the organization owning the project on Github.
This field is used to compute the URLs for the documentation.
* :code:`kind`: specify whether packages should be programs
(:code:`"program"`) or libraries (:code:`"library"`) by default.
* :code:`license`: the identifier of the license. See the section
about licenses. :code:`drom` knows a few license and may
automatically generate the corresponding files.
* :code:`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.
* :code:`mode`: the target of the compilation. It is either
:code:`"binary"` for an executable, or :code:`javascript` for a
file generated by :code:`js_of_ocaml`.
* :code:`synopsis`: a very short description of the
project. :code:`opam` requires this field to start with an uppercase
letter and not end with a dot.
* :code:`description`: a long description of the project.
* :code:`pack-modules`: a bool indicating whether modules should be
packed. :code:`true` by default. It corresponds to the
:code:`wrapped` field of :code:`dune`.
Some fields are computed automatically if :code:`github-organization`
is specified, but can be overriden:
* :code:`archive`: the URL from where the sources can be
downloaded. Can contain :code:`${version}` instead of the version.
* :code:`dev-repo`: the location of the VCS for the project. They are
often specified with :code:`git+https://`.
* :code:`bug-reports`: the URL where issues can be created.
* :code:`doc-api`: the URL where the documentation of the API can be found.
* :code:`doc-gen`: the URL where the general documentation can be found.
* :code:`homepage`: the URL of the project main homepage
Another important field is the :code:`skip` field:
* :code:`skip` contains a space-separated list of files, that
should not be modified anymore by :code:`drom`. By default,
:code:`drom` will generate any missing file, and upgrade any file
that was previously generated by :code:`drom` and not modified since
then. :code:`drom` will print a warning everytime it finds a
modified file. This option can be used to avoid this warning, or to
prevent :code:`drom` from recreating an unwanted file. :code:`drom`
generates a file :code:`_drom/maximum-skip-field.txt` showing all
the values that can appear in this field.
There is an exception for :code:`drom.toml` and :code:`package.toml`
files, that are only modified when the user configuration has been
changed, a changing option passed to :code:`drom project`, or the
:code:`--upgrade` option to benefit from a more recent version of
:code:`drom`.
:code:`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:`code` (source
files), :code:`docs` (documentation), :code:`sphinx` (sphinx
documentation), :code:`test` (test files), :code:`github` (github
pages and workflows). More tags may be defined in the future.
To detect modifications of its files, :code:`drom` generates a file
`.drom` in the project. This file should be committed in the
repository with the files, so that :code:`drom` can always use it to
detect changes.
Every time :code:`drom` skips a file because it has been modified,
it saves the new version in the :code:`_drom/skipped/` directory.
For example, if :code:`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 :code:`drom`.
The :code:`[dependencies]` table
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This section contains dependencies with their versions. These
dependencies are for libraries only, as they will be added in the
generated :code:`dune` files.
For example::
ez_config = "0.1.0"
means that the project should be linked with :code:`ez_config` with a
version compatible with :code:`0.1.0`. :code:`drom` uses semantic
versioning, so it means :code:`version >= 0.1.0` and :code:`version <
1.0.0`. If the version is not formatted as :code:`X.Y.Z`, :code:`drom`
will only generate a constraint :code:`version >= V`.
Versions can also be specified with some other formats:
* Following :code:`opam` conventions : :code:`">0.1.0"` for example
* No constraint specified, with the empty string :code:`""`
* Using the string :code:`"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:
* :code:`version` for the version of the dependency
* :code:`libname` for the name of the dependency that should be used
as a dependency in :code:`dune` files
* :code:`for-test` for a boolean specifying if the dependency is only
needed for tests (:code:`with-test` in :code:`opam` files)
* :code:`for-doc` for a boolean specifying if the dependency is only
needed for tests (:code:`with-doc` in :code:`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 :code:`opam` package
:code:`ez-config`, and that it should be linked with the corresponding
library :code:`ez_config`.
The :code:`[tools]` table
~~~~~~~~~~~~~~~~~~~~~~~~~
This section contains dependencies that will appear in the
:code:`opam` files, but not as libraries in the :code:`dune` files.
The :code:`[package]` table
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The :code:`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
:code:`drom.toml` file is the :code:`dir` field. :code:`drom` expects
to find a :code:`package.toml` file in that directory with more
information on the packages.
Yet, it is possible to inline these fields also in the
:code:`drom.toml` file, but :code:`drom.toml` will move them to their
:code:`package.toml` file if you ask it to upgrade the file (using
:code:`drom project --upgrade`). You can check the list of available
fields in the documentation of :code:`package.toml`.
The :code:`[fields]` table
~~~~~~~~~~~~~~~~~~~~~~~~~~
The :code:`fields` table contains fields that will be added to the
the :code:`dune-project` file. There is two fields may be used :
* :code:`dune-project-header`
* :code:`dune-project-trailer`
The first one add verbatim content to the :code:`dune-project` file, right
after the :code:`(lang dune X.Y)` line. The second one add verbatim content
at the end of file.
Using those fields may be required when using very specific :code:`dune`
build feature that are not handled by :code:`drom`. For example, using
the parser generator :code:`menhir` for non trivial cases will likely need
such tuning.
The :code:`package.toml` Files
------------------------------
Every package source directory (as specified by the :code:`dir` field
of each :code:`[[package]]` entry in the :code:`drom.toml` file) can
contain a :code:`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:
* :code:`dir`: the directory of the package sources (defaults to
:code:`src/${name}/`
* :code:`dependencies`: the :code:`opam` *library* dependencies of
this package
* :code:`generators`: the list of program for which specific
:code:`dune` targets should be generated. Defaults to an empty list,
but the project can define its own list (:code:`[ "ocamllex" ;
"ocamlyacc" ]` by default). The generator :code:`menhir` is also available.
* :code:`gen-version`: whether a :code:`version.ml` file should be
created
* :code:`kind`: the kind of the package (:code:`library` or
:code:`program`)
* :code:`name`: the name of the package (under which it will be
published in :code:`opam`
* :code:`pack`: if modules should be wrapped, the name of the wrapper
module (defaults to the capitalized name of the library otherwise)
* :code:`skeleton`: the skeleton to be used to generate the files of this
project (defaults to :code:`"program"`). See the section on skeletons
for more info.
* :code:`tools`: the :code:`opam` *tools* dependencies of this package
The following fields are also allowed, and default to the project ones
if not specified:
* :code:`authors`: the authors of the package
* :code:`description`: the full description of the package
* :code:`mode`: the mode of the package (:code:`binary` or
:code:`javascript`)
* :code:`pack-modules`: whether modules should be wrapped or not
* :code:`synopsis`: the one-line description of the package
* :code:`version`: the version of this package
For :code:`menhir` usage, you will find an optional :code:`[menhir]` talbe,
with the following fields:
* :code:`version`: the version of the menhir language configuration
* :code:`parser`: a table with the following fields:
* :code:`modules`: an array of string with all your menhir modules
* :code:`merge-into`: an optional string for the name of the merged module,
by default the last value in the :code:`modules` array if not provided
* :code:`tokens`: an optional to add the :code:`--external-tokens` flag
* :code:`flags`: an optional string array with menhir flags
* :code:`infer`: an optional boolean which adds the :code:`(infer )`
rule to the :code:`dune` file
* :code:`tokens`: an optional table that will add the `--only-tokens` flag
to the :code:`menhir` rule with the following fiels:
* :code:`modules`: a string array with all the modules to add to the rule
* :code:`flags`: an optional string array with all the flags to add to menhir
Finally, there is a table :code:`[fields]` within a
package. Currently, the following fields are used by skeletons:
* :code:`dune-libraries`: extra libraries that can be added in the
:code:`libraries` field of :code:`dune`. Typically for internal libraries.
* :code:`dune-stanzas`: extra :code:`dune` stanzas in the description
* :code:`dune-trailer`: a string that is added at the end of the
:code:`dune` file.
* :code:`opam-trailer`: a string that is added at the end of the
:code:`opam` file for the package.
In skeletons, *fields* are referenced using parens (for example
:code:`!(dune-stanzas)`).
Skeletons
---------
:code:`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 :code:`dune` and sources files
When :code:`drom` is called in a project, it generates a file
:code:`_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 :code:`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 :code:`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 :code:`.opam` file generated by default
in the :code:`virtual` skeleton, until you add other packages using
:code:`drom package XXX --new SKELETON`
* The :code:`library` skeleton contains only a library package. It
inherits from the :code:`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 :code:`program` skeleton contains both a library and a driver
packages. It inherits from the :code:`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 :code:`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 :code:`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 :code:`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 :code:`virtual` skeleton is an empty skeleton.
* The :code:`library` skeleton contains a simple library.
* The :code:`program` skeleton contains a simple program.
* The :code:`driver` skeleton contains a simple program that only
calls the main entry point of an associated library.
* The :code:`ppx_deriver` skeleton contains a simple library that defines
a *ppx_deriver*.
Note that the project :code:`program` skeleton combines a
:code:`library` package skeleton with a :code:`driver` package
skeleton.
User-specified skeletons
~~~~~~~~~~~~~~~~~~~~~~~~
Users can create their own skeletons. Check the documentation on :ref:`Contributing skeletons`