This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,30 @@
Building an Ad Hoc ``.cmxs``
----------------------------
.. TODO(diataxis) howto: Building an Ad Hoc ``.cmxs``
In the model exposed by Dune, a ``.cmxs`` target is created for each
library. However, the ``.cmxs`` format itself is more flexible and is
capable to containing arbitrary ``.cmxa`` and ``.cmx`` files.
For the specific cases where this extra flexibility is needed, one can use
:ref:`variables-for-artifacts` to write explicit rules to build ``.cmxs`` files
not associated to any library.
Below is an example where we build ``my.cmxs`` containing ``foo.cmxa`` and
``d.cmx``. Note how we use a :doc:`/reference/dune/library` stanza to set
up the compilation of ``d.cmx``.
.. code:: dune
(library
(name foo)
(modules a b c))
(library
(name dummy)
(modules d))
(rule
(targets my.cmxs)
(action (run %{ocamlopt} -shared -o %{targets} %{cmxa:foo} %{cmx:d})))

View file

@ -0,0 +1,58 @@
Dynamic Loading of Packages with Findlib
========================================
.. TODO(diataxis) this is an howto
The preferred way for new development is to use :ref:`plugins`.
Dune supports the ``findlib.dynload`` package from `Findlib
<http://projects.camlcity.org/projects/findlib.html>`_ that enables
dynamically-loading packages and their dependencies (using the OCaml Dynlink module).
Adding the ability for an application to have plugins just requires adding
``findlib.dynload`` to the set of library dependencies:
.. code:: dune
(library
(name mytool)
(public_name mytool)
(modules ...)
)
(executable
(name main)
(public_name mytool)
(libraries mytool findlib.dynload)
(modules ...)
)
Use ``Fl_dynload.load_packages l`` in your application to load
the list ``l`` of packages. The packages are loaded
only once, so trying to load a package statically linked does nothing.
A plugin creator just needs to link to your library:
.. code:: dune
(library
(name mytool_plugin_a)
(public_name mytool-plugin-a)
(libraries mytool)
)
For clarity, choose a naming convention. For example, all the plugins of
``mytool`` should start with ``mytool-plugin-``. You can automatically
load all the plugins installed for your tool by listing the existing packages:
.. code:: ocaml
let () = Findlib.init ()
let () =
let pkgs = Fl_package_base.list_packages () in
let pkgs =
List.filter
(fun pkg -> 14 <= String.length pkg && String.sub pkg 0 14 = "mytool-plugin-")
pkgs
in
Fl_dynload.load_packages pkgs

View file

@ -0,0 +1,14 @@
Advanced Topics
===============
These documents describe some advanced or very specific features of Dune.
.. toctree::
:maxdepth: 1
findlib-dynamic
profiling-dune
package-version
ocaml-syntax
variables-artifacts
custom-cmxs

View file

@ -0,0 +1,21 @@
OCaml Syntax
============
.. TODO(diataxis)
- reference: files
- howto: using dynamic features
If a ``dune`` file starts with ``(* -*- tuareg -*- *)``, then it is
interpreted as an OCaml script that generates the ``dune`` file as described
in the rest of this section. The code in the script will have access to a
`Jbuild_plugin
<https://github.com/ocaml/dune/blob/master/plugin/jbuild_plugin.mli>`__
module containing details about the build context it's executed in.
The OCaml syntax gives you an escape hatch for when the S-expression
syntax is not enough. It isn't clear whether the OCaml syntax will be
supported in the long term, as it doesn't work well with incremental
builds. It is possible that it will be replaced by just an ``include``
stanza where one can include a generated file.
Consequently **you must not** build complex systems based on it.

View file

@ -0,0 +1,16 @@
Package Version
===============
.. TODO(diataxis)
- reference: environment - packages
Dune determines a package's version by looking at the ``version`` field in the
:doc:`/reference/dune-project/package`. If the version field isn't set,
it looks at the toplevel ``version`` field in the ``dune-project`` field. If
neither are set, Dune assumes that we are in development mode and reads the
version from the VCS, if any. The way it obtains the version from the VCS is
described in :ref:`the build-info section <build-info>`.
When installing the files of a package on the system, Dune
automatically inserts the package version into various metadata files
such as ``META`` and ``dune-package`` files.

View file

@ -0,0 +1,15 @@
Profiling Dune
==============
.. TODO(diataxis)
- reference: the CLI
- howto: profiling a dune build
If ``--trace-file FILE`` is passed, Dune will write detailed data about internal
operations, such as the timing of commands that Dune runs.
The format is compatible with `Catapult trace-viewer`_. In particular, these
files can be loaded into Chromium's ``chrome://tracing``. Note that the exact
format is subject to change between versions.
.. _Catapult trace-viewer: https://github.com/catapult-project/catapult/blob/master/tracing/README.md

View file

@ -0,0 +1,32 @@
.. _variables-for-artifacts:
Variables for Artifacts
-----------------------
.. TODO(diataxis) move to :doc:`../concepts/variables`
For specific situations where one needs to refer to individual compilation
artifacts, special variables (see :doc:`../concepts/variables`) are provided,
so the user doesn't need to be aware of the particular naming conventions or
directory layout implemented by Dune.
These variables can appear wherever a :doc:`../concepts/dependency-spec` is
expected and also inside :doc:`../reference/actions/index`. When used inside
:doc:`../reference/actions/index`, they implicitly declare a dependency on the
corresponding artifact.
The variables have the form ``%{<ext>:<path>}``, where ``<path>`` is
interpreted relative to the current directory:
- ``cmo:<path>``, ``cmx:<path>``, and ``cmi:<path>`` expand to the corresponding
artifact's path for the module specified by ``<path>``. The basename of
``<path>`` should be the name of a module as specified in a ``(modules)``
field.
- ``cma:<path>`` and ``cmxa:<path>`` expands to the corresponding
artifact's path for the library specified by ``<path>``. The basename of ``<path>``
should be the name of the library as specified in the ``(name)`` field of a
``library`` stanza (*not* its public name).
In each case, the expansion of the variable is a path pointing inside the build
context (i.e., ``_build/<context>``).

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -0,0 +1,111 @@
**********
Dune Cache
**********
.. TODO(diataxis) This is reference material with some explanation.
Dune implements a cache of build results that is shared across different
workspaces. Before executing a build rule, Dune looks it up in the shared
cache, and if it finds a matching entry, Dune skips the rule's execution and
restores the results in the current build directory. This can greatly speed up
builds when different workspaces share code, as well as when switching branches
or simply undoing some changes within the same workspace.
Configuration
=============
There are three ways to configure the Dune cache. Choose the one that is more
convenient for you:
* Add ``(cache <setting>)`` to your Dune configuration file
(``~/.config/dune/config`` by default).
* Set the environment variable ``DUNE_CACHE`` to ``<setting>``
* Run Dune with the ``--cache=<setting>`` flag.
Here, ``<setting>`` must be one of:
* ``disabled``: disables the Dune cache completely.
* ``enabled-except-user-rules``: enables the Dune cache, but excludes
user-written rules. This setting is a conservative choice that can avoid
breaking rules whose dependencies are not correctly specified. Currently the
default.
* ``enabled``: enables the Dune cache unconditionally.
By default, Dune stores the cache in your ``XDG_CACHE_HOME`` directory on \*nix
systems and ``%LOCALAPPDATA%\Microsoft\Windows\Temporary Internet Files\dune`` on Windows.
You can change the default location by setting the environment variable
``DUNE_CACHE_ROOT``.
Cache Storage Mode
==================
Dune supports two modes of storing and restoring cache entries: `hardlink` and
`copy`. If your file system supports hard links, we recommend that you use the
`hardlink` mode, which is generally more efficient and reliable.
The `hardlink` Mode
-------------------
By default, Dune uses hard links when storing and restoring cache entries. This
is fast and has zero disk space overhead for files that still live in a build
directory. There are two disadvantages of this mode:
* The cache storage must be on the same partition as the build tree.
* A cache entry can be corrupted by modifying the hard link that points to it
from the build directory. To reduce the risk of cache corruption, Dune
systematically removes write permissions from all build results. It is worth
noting that modifying files in the build directory is a bad practice anyway.
The `copy` Mode
---------------
If you specify ``(cache-storage-mode copy)`` in the configuration file, Dune
will copy files to and from the cache instead of using hard links. This mode is
slower and has higher disk space usage. On the positive side, it is more
portable and doesn't have the disadvantages of the `hardlink` mode (see above).
You can also set or override the storage mode via the environment variable
``DUNE_CACHE_STORAGE_MODE`` and the command line flag ``--cache-storage-mode``.
Trimming the Cache
==================
Storing all historically produced build results in the cache is infeasible, so
you'll need to occasionally trim the cache. To do that, run the ``dune cache
trim --size=BYTES`` command. This will remove the oldest used cache entries to
keep the cache overhead below the specified size. By "overhead" we mean the
cache entries whose hard link count is equal to 1, i.e., which aren't used in
any build directory. Trimming cache entries whose hard link count is greater
than 1 would not free any disk space.
Note that previous versions of Dune, cache provided a "cache daemon" that could
periodically trim the cache. The current version doesn't require an additional
daemon process, so this automated trimming functionality is no longer provided.
Reproducibility
===============
Reproducibility Check
---------------------
While the main purpose of Dune cache is to speed up build times, it can also be
used to check build reproducibility. By specifying ``(cache-check-probability
FLOAT)`` in the configuration file, or running Dune with the
``--cache-check-probability=FLOAT`` flag, you instruct Dune to re-execute
randomly chosen build rules and compare their results with those stored in the
cache. If the results differ, the rule is not reproducible, and Dune will print
out a corresponding warning.
Non-Reproducible Rules
----------------------
Some build rules are inherently not reproducible because they involve running
non-deterministic commands that, for example, depend on the current time or
download files from the Internet. To prevent Dune from caching such rules, mark
them as non-reproducible by using ``(deps (universe))``. Please see
:doc:`concepts/dependency-spec`.

View file

@ -0,0 +1,152 @@
Dependency Specification
========================
.. TODO(diataxis)
- reference - dependency spec
- reference - globbing
Dependencies in ``dune`` files can be specified using one of the following:
.. _source_tree:
- ``(:name <dependencies>)`` will bind the list of dependencies to the
``name`` variable. This variable will be available as ``%{name}`` in actions.
- ``(file <filename>)``, or simply ``<filename>``, depend on this file.
- ``(alias <alias-name>)`` depends on the construction of this alias. For
instance: ``(alias src/runtest)``.
- ``(alias_rec <alias-name>)`` depends on the construction of this
alias recursively in all children directories wherever it is
defined. For instance: ``(alias_rec src/runtest)`` might depend on
``(alias src/runtest)``, ``(alias src/foo/bar/runtest)``, etc.
- ``(glob_files <glob>)`` depends on all files matched by ``<glob>``. See the
:ref:`glob <glob>` for details.
- ``(glob_files_rec <glob>)`` is the recursive version of
``(glob_files <glob>)``. See the :ref:`glob <glob>` for details.
- ``(source_tree <dir>)`` depends on all source files in the subtree with root
``<dir>``.
- ``(universe)`` depends on everything in the universe. This is for
cases where dependencies are too hard to specify. Note that Dune
will not be able to cache the result of actions that depend on the
universe. In any case, this is only for dependencies in the
:term:`installed world`. You must still specify all dependencies that come
from the workspace.
- ``(package <pkg>)`` depends on all files installed by ``<package>``, as well
as on the transitive package dependencies of ``<package>``. This can be used
to test a command against the files that will be installed.
- ``(env_var <var>)`` depends on the value of the environment variable ``<var>``.
If this variable becomes set, becomes unset, or changes value, the target
will be rebuilt.
- ``(sandbox <config>)`` requires a particular sandboxing configuration.
``<config>`` can be one (or many) of:
- ``always``: the action requires a clean environment
- ``none``: the action must run in the build directory
- ``preserve_file_kind``: the action needs the files it reads to look
like normal files (so Dune won't use symlinks for sandboxing)
- ``(include <file>)`` read the s-expression in ``<file>`` and interpret it as
additional dependencies. The s-expression is expected to be a list of the
same constructs enumerated here.
In all these cases, the argument supports :doc:`variables`.
Named Dependencies
------------------
Dune allows a user to organize dependency lists by naming them. The user is
allowed to assign a group of dependencies a name that can later be referred to
in actions (like the ``%{deps}``, ``%{target}``, and ``%{targets}`` built in variables).
One instance where this is useful is for naming globs. Here's an
example of an imaginary bundle command:
.. code:: dune
(rule
(target archive.tar)
(deps
index.html
(:css (glob_files *.css))
(:js foo.js bar.js)
(:img (glob_files *.png) (glob_files *.jpg)))
(action
(run %{bin:bundle} index.html -css %{css} -js %{js} -img %{img} -o %{target})))
Note that a named dependency list can also include unnamed
dependencies (like ``index.html`` in the example above). Also, such
user defined names will shadow build in variables, so
``(:workspace_root x)`` will shadow the built-in ``%{workspace_root}``
variable.
.. _glob:
Glob
----
You can use globs to declare dependencies on a set of files. Note that globs
will match files that exist in the source tree as well as buildable targets, so
for instance you can depend on ``*.cmi``.
Dune supports globbing files in a single directory via ``(glob_files
...)`` and, starting with Dune 3.0, in all subdirectories recursively via ``(glob_files_rec
...)``. The glob is interpreted as follows:
- anything before the last ``/`` is taken as a literal path
- anything after the last ``/``, or everything if the glob contains no ``/``, is
interpreted using the glob syntax
Absolute paths are permitted in the ``(glob_files ...)`` term only. It's an error to pass
an absolute path (i.e., a path beginning with a ``/``) to ``(glob_files_rec ...)```.
The glob syntax is interpreted as follows:
- ``\<char>`` matches exactly ``<char>``, even if it's a special character
(``*``, ``?``, ...).
- ``*`` matches any sequence of characters, except if it comes first, in which
case it matches any character that is not ``.`` followed by anything.
- ``**`` matches any character that is not ``.`` followed by anything, except if
it comes first, in which case it matches anything.
- ``?`` matches any single character.
- ``[<set>]`` matches any character that is part of ``<set>``.
- ``[!<set>]`` matches any character that is not part of ``<set>``.
- ``{<glob1>,<glob2>,...,<globn>}`` matches any string that is matched by one of
``<glob1>``, ``<glob2>``, etc.
.. list-table:: Glob syntax examples
:header-rows: 1
* - Syntax
- Files matched
- Files not matched
* - ``x``
- ``x``
- ``y``
* - ``\*``
- ``*``
- ``x``
* - ``file*.txt``
- ``file1.txt``, ``file2.txt``
- ``f.txt``
* - ``*.txt``
- ``f.txt``
- ``.hidden.txt``
* - ``a**``
- ``aml``
- ``a.ml``
* - ``**``
- ``a/b``, ``a.b``
- (none)
* - ``a?.txt``
- ``a1.txt``, ``a2.txt``
- ``b1.txt``, ``a10.txt``
* - ``f[xyz].txt``
- ``fx.txt``, ``fy.txt``, ``fz.txt``
- ``f2.txt``, ``f.txt``
* - ``f[!xyz].txt``
- ``f2.txt``, ``fa.txt``
- ``fx.txt``, ``f.txt``
* - ``a.{ml,mli}``
- ``a.ml``, ``a.mli``
- ``a.txt``, ``b.ml``
* - ``../a.{ml,mli}``
- ``../a.ml``, ``../a.mli``
- ``a.ml``

View file

@ -0,0 +1,53 @@
Locks
=====
.. TODO(diataxis)
- howto: testing in general (note about concurrency)
- reference: locks
Given two rules that are independent, Dune will assume that their
associated actions can be run concurrently. Two rules are considered
independent if neither of them depend on the other, either directly or
through a chain of dependencies. This basic assumption allows Dune to
parallelize the build.
However, it is sometimes the case that two independent rules cannot be
executed concurrently. For instance, this can happen for more
complicated tests. In order to prevent Dune from running the
actions at the same time, you can specify that both actions take the
same lock:
.. code:: dune
(rule
(alias runtest)
(deps foo)
(locks m)
(action (run test.exe %{deps})))
(alias
(rule runtest)
(deps bar)
(locks m)
(action (run test.exe %{deps})))
Dune will make sure that the executions of ``test.exe foo`` and
``test.exe bar`` are serialized.
Although they don't live in the filesystem, lock names are interpreted as file
names. So for instance, ``(with-lock m ...)`` in ``src/dune`` and ``(with-lock
../src/m)`` in ``test/dune`` refer to the same lock.
Note also that locks are per build context. So if your workspace has two build
contexts setup, the same rule might still be executed concurrently between the
two build contexts. If you want a lock that is global to all build contexts,
simply use an absolute filename:
.. code:: dune
(rule
(alias runtest)
(deps foo)
(locks /tcp-port/1042)
(action (run test.exe %{deps})))

View file

@ -0,0 +1,22 @@
OCaml Flags
===========
In ``library``, ``executable``, ``executables``, and ``env`` stanzas,
you can specify OCaml compilation flags using the following fields:
- ``(flags <flags>)`` to specify flags passed to both ``ocamlc`` and
``ocamlopt``
- ``(ocamlc_flags <flags>)`` to specify flags passed to ``ocamlc`` only
- ``(ocamlopt_flags <flags>)`` to specify flags passed to ``ocamlopt`` only
For all these fields, ``<flags>`` is specified in the
:doc:`../reference/ordered-set-language`.
These fields all support ``(:include ...)`` forms.
The default value for ``(flags ...)`` is taken from the environment,
as a result it's recommended to write ``(flags ...)`` fields as
follows:
.. code:: dune
(flags (:standard <my options>))

View file

@ -0,0 +1,147 @@
Package Specification
=====================
.. TODO(diataxis)
- reference: packages
- howto: preparing an opam package
- tutorial: from zero to opam
Installation is the process of copying freshly built libraries,
binaries, and other files from the build directory to the system. Dune
offers two ways of doing this: via opam or directly via the ``install``
command. In particular, the installation model implemented by Dune
was copied from opam. Opam is the standard OCaml package manager.
In both cases, Dune only know how to install whole packages. A
package being a collection of executables, libraries, and other files.
In this section, we'll describe how to define a package, how to
"attach" various elements to it, and how to proceed with installing it
on the system.
.. _declaring-a-package:
Declaring a Package
-------------------
To declare a package, simply add a ``package`` stanza to your
``dune-project`` file:
.. code:: dune
(package
(name mypackage)
(synopsis "My first Dune package!")
(description "\| This is my first attempt at creating
"\| a project with Dune.
))
Once you have done this, Dune will know about the package named
``mypackage`` and you will be able to attach various elements to it.
The ``package`` stanza accepts more fields, such as dependencies.
Note that package names are in a global namespace, so the name you choose must
be universally unique. In particular, package managers never allow users to
release two packages with the same name.
.. TODO: describe this more in details
In older projects using Dune, packages were defined by manually writing a file
called ``<package-name>.opam`` at the root of the project. However, it's not
recommended to use this method in new projects, as we expect to deprecate it in
the future. The right way to define a package is with a ``package`` stanza in
the ``dune-project`` file.
See :doc:`../howto/opam-file-generation` for instructions on configuring Dune
to automatically generate ``.opam`` files based on the ``package`` stanzas.
Attaching Elements to a Package
-------------------------------
Attaching an element to a package means declaring to Dune that this
element is part of the said package. The method to attach an element
to a package depends on the kind of the element. In this subsection,
we will go through the various kinds of elements and describe how to
attach each of them to a package.
In the rest of this section, ``<prefix>`` refers to the directory in
which the user chooses to install packages. When installing via opam,
it's opam that sets this directory. When calling ``dune install``,
the installation directory is either guessed or can be manually
specified by the user. Defaults directories which replace guessing
can be set during the compilation of dune.
Sites of a Package
------------------
When packages need additional resources outside their binary, their location
could be hard to find. Moreover, some packages could add resources to another
package, e.g., in the case of plugins. These locations are called sites in
Dune. One package can define them. During execution, one site corresponds to a
list of directories. They are like layers, and the first directories have a higher
priority. Examples and precisions are available at :ref:`sites`.
Libraries
^^^^^^^^^
In order to attach a library to a package, merely add a
``public_name`` field to your library. This is the name that external
users of your libraries must use in order to refer to it. Dune
requires that a library's public name is either the name of the
package it is part of or start with the package name followed by a dot
character.
For instance:
.. code:: dune
(library
(name mylib)
(public_name mypackage.mylib))
After you have added a public name to a library, Dune will know to
install it as part of the package it is attached to. Dune installs
the library files in a directory ``<prefix>/lib/<package-name>``.
If the library name contains dots, the full directory in which the
library files are installed is ``lib/<comp1>/<comp2/.../<compn>``,
where ``<comp1>``, ``<comp2>``, ... ``<compn>`` are the dot-separated
component of the public library name. By definition, ``<comp1>`` is
always the package name.
Executables
^^^^^^^^^^^
Similar to libraries, to attach an executable to a package simply
add a ``public_name`` field to your ``executable`` stanza or a
``public_names`` field for ``executables`` stanzas. Designate this
name to match the available executables through the installed ``PATH``
(i.e., the name users must type in their shell to execute
the program), because Dune cannot guess an executable's relevant package
from its public name. It's also necessary to add a ``package`` field
unless the project contains a single package, in which case the executable
will be attached to this package.
For instance:
.. code:: dune
(executable
(name main)
(public_name myprog)
(package mypackage))
Once ``mypackage`` is installed on the system, the user will be able
to type the following in their shell:
.. code:: console
$ myprog
to execute the program.
Other Files
^^^^^^^^^^^
For all other kinds of elements, you must attach them manually via
an :doc:`/reference/dune/install` stanza.

View file

@ -0,0 +1,109 @@
Diffing and Promotion
=====================
You can use Diffing and Promotion flows to compare the outputs of your build in
the build directory with the source tree and/or copy the result of the rules
into your source tree to store the changes.
Diffing
=======
You can use the ``(diff <file1> <file2>)`` directive in a rule to compare
its output with the version in your source tree. It is useful when
your tests produce a file output and you want to make sure that output has
not changed.
.. TODO(diataxis)
- howto: diffing and promotion
- reference: diffing
``(diff <file1> <file2>)`` is very similar to ``(run diff <file1>
<file2>)``. In particular it behaves in the same way:
- When ``<file1>`` and ``<file2>`` are equal, it does nothing.
- When they are not, the differences are shown and the action fails.
However, it is different for the following reason:
- The exact command used for diff files can be configured via the
``--diff-command`` command line argument. Note that it's only
called when the files are not byte equals
- By default, it will use ``patdiff`` if it is installed. ``patdiff``
is a better diffing program. You can install it via opam with:
.. code:: console
$ opam install patdiff
- On Windows, both ``(diff a b)`` and ``(diff? a b)`` normalize
end-of-line characters before comparing the files.
- Since ``(diff a b)`` is a built-in action, Dune knows that ``a``
and ``b`` are needed, so you don't need to specify them
explicitly as dependencies.
- You can use ``(diff? a b)`` after a command that might or might not
produce ``b``, for cases where commands optionally produce a
*corrected* file
- If ``<file1>`` doesn't exist, it will compare with the empty file.
- It allows promotion. See below.
Note that ``(cmp a b)`` does no end-of-line normalization and doesn't
print a diff when the files differ. ``cmp`` is meant to be used with
binary files.
Promotion
=========
Promotion relates to copying the output of a Dune rule to your source tree.
Common uses include updating rule output after a failed diff (e.g., from a
test) or committing output to source control to cut down on dependencies
during packaging.
Promoting Test or Rule Output After Diffing
-------------------------------------------
Whenever an action ``(diff <file1> <file2>)`` or ``(diff? <file1>
<file2>)`` fails because the two files are different, Dune allows
you to promote ``<file2>`` as ``<file1>`` if ``<file1>`` is a source
file and ``<file2>`` is a generated file.
More precisely, let's consider the following Dune file:
.. code:: dune
(rule
(with-stdout-to data.out (run ./test.exe)))
(rule
(alias runtest)
(action (diff data.expected data.out)))
Where ``data.expected`` is a file committed in the source
repository. You can use the following workflow to update your test:
- Update the code of your test.
- Run ``dune runtest``. The diff action will fail and a diff will
be printed.
- Check the diff to make sure it's what you expect. This diff can be displayed
again by running ``dune promotion diff``.
- Run ``dune promote``. This will copy the generated ``data.out``
file to ``data.expected`` directly in the source tree.
You can also use ``dune runtest --auto-promote``, which will
automatically do the promotion.
Automatically Promoting Rule Output Into the Source Tree
--------------------------------------------------------
Dune rules support a ``(mode promote)`` directive that will automatically
copy their output into your source tree. This approach suits, for example, code
documentation generation flows where output needs to be committed to source
code control to enable easier browsing, or eliminate dependencies on a code
generation step during opam package installation.
More information, including customising when the source is copied, can be found
in :doc:`../reference/dune/rule`.

View file

@ -0,0 +1,76 @@
Sandboxing
==========
.. TODO(diataxis)
- explanation: sandboxing
- reference: sandboxing
The user actions that run external commands (``run``, ``bash``, ``system``)
are opaque to Dune, so Dune has to rely on manual specification of dependencies
and targets. One problem with manual specification is that it's error-prone.
It's often hard to know in advance what files the command will read,
and knowing a correct set of dependencies is very important for build
reproducibility and incremental build correctness.
To help with this problem Dune supports sandboxing.
An idealized view of sandboxing is that it runs the action in an environment
where it can't access anything except for its declared dependencies.
In practice, we have to make compromises and have some trade-offs between
simplicity, information leakage, performance, and portability.
The way sandboxing is currently implemented is that for each sandboxed action
we build a separate directory tree (sandbox directory) that mirrors the build
directory, filtering it to only contain the files that were declared as
dependencies. We run the action in that directory, and then we copy
the targets back to the build directory.
You can configure Dune to use sandboxing modes ``symlink``, ``hardlink``, or
``copy``, which determine how the individual files are populated (they will be
symlinked, hardlinked, or copied into the sandbox directory).
This approach is very simple and portable, but that comes with
certain limitations:
- The actions in the sandbox can use absolute paths to refer to anywhere outside
the sandbox. This means that only dependencies on relative paths in the build
tree can be enforced/detected by sandboxing.
- The sandboxed actions still run with full permissions of Dune itself, so
sandboxing is not a security feature. It won't prevent network access either.
- We don't erase the environment variables of the sandboxed
commands. This is something we want to change.
- Performance impact is usually small, but it can get noticeable for
fast actions with very large sets of dependencies.
Per-Action Sandboxing Configuration
-----------------------------------
Some actions may rely on sandboxing to work correctly.
For example, an action may need the input directory to contain nothing
except the input files, or the action might create temporary files that
break other build actions.
Some other actions may refuse to work with Sandboxing. For example,
if they rely on absolute path to the build directory staying fixed,
or if they deliberately use some files without declaring dependencies
(this is usually a very bad idea, by the way).
Generally it's better to improve the action so it works with or without
sandboxing (especially with), but sometimes you just can't do that.
Things like this can be described using the "sandbox" field in the dependency
specification language (see :doc:`dependency-spec`).
Global Sandboxing Configuration
-------------------------------
Dune always respects per-action sandboxing specification.
You can configure it globally to prefer a certain sandboxing mode if
the action allows it.
This is controlled by:
- ``dune --sandbox <...>`` CLI flag (see ``man dune-build``)
- ``DUNE_SANDBOX`` environment (see ``man dune-build``)
- ``(sandboxing_preference ..)`` field in the configuration file (see ``man
dune-config``)

View file

@ -0,0 +1,241 @@
Variables
=========
.. TODO(diataxis)
- reference: variables
- explanation: rule loading
Some fields can contains variables that are expanded by Dune.
The syntax of variables is as follows:
.. code::
%{var}
or, for more complex forms that take an argument:
.. code::
%{fun:arg}
In order to write a plain ``%{``, you need to write ``\%{`` in a
string.
Dune supports the following variables:
- ``project_root`` is the root of the current project. It is typically the root
of your project, and as long as you have a ``dune-project`` file there,
``project_root`` is independent of the workspace configuration.
- ``workspace_root`` is the root of the current workspace. Note that
the value of ``workspace_root`` isn't constant and depends on
whether your project is vendored or not.
- ``cc`` is the C compiler command line (list made of the compiler
name followed by its flags) that will be used to compile foreign code. For
more details about its content, please see :doc:`/reference/foreign-flags`.
- ``cxx`` is the C++ compiler command line being used in the
current build context.
- ``ocaml_bin`` is the path where ``ocamlc`` lives.
- ``ocaml`` is the ``ocaml`` binary.
- ``ocamlc`` is the ``ocamlc`` binary.
- ``ocamlopt`` is the ``ocamlopt`` binary.
- ``ocaml_version`` is the version of the compiler used in the
current build context.
- ``ocaml_where`` is the output of ``ocamlc -where``.
- ``arch_sixtyfour`` is ``true`` if using a compiler that targets a
64-bit architecture and ``false`` otherwise.
- ``null`` is ``/dev/null`` on Unix or ``nul`` on Windows.
- ``ext_obj``, ``ext_asm``, ``ext_lib``, ``ext_dll``, and ``ext_exe``
are the file extensions used for various artifacts.
- ``ext_plugin`` is ``.cmxs`` if ``natdynlink`` is supported and
``.cma`` otherwise.
- ``ocaml-config:v`` is for every variable ``v`` in the output of
``ocamlc -config``. Note that Dune processes the output
of ``ocamlc -config`` in order to make it a bit more stable across
versions, so the exact set of variables accessible this way might
not be exactly the same as what you can see in the output of
``ocamlc -config``. In particular, variables added in new OCaml versions
need to be registered in Dune before they can be used.
- ``profile`` is the profile selected via ``--profile``.
- ``context_name`` is the name of the context (``default``, or defined in the
workspace file)
- ``os_type`` is the type of the OS the build is targeting. This is
the same as ``ocaml-config:os_type``.
- ``architecture`` is the type of the architecture the build is targeting. This
is the same as ``ocaml-config:architecture``.
- ``model`` is the type of the CPU the build is targeting. This is
the same as ``ocaml-config:model``.
- ``system`` is the name of the OS the build is targeting. This is the same as
``ocaml-config:system``.
- ``ignoring_promoted_rules`` is ``true`` if
``--ignore-promoted-rules`` was passed on the command line and
``false`` otherwise.
- ``<ext>:<path>`` where ``<ext>`` is one of ``cmo``, ``cmi``, ``cma``,
``cmx``, or ``cmxa``. See :ref:`variables-for-artifacts`.
- ``env:<var>=<default`` expands to the value of the environment
variable ``<var>``, or ``<default>`` if it does not exist.
For example, ``%{env:BIN=/usr/bin}``.
Available since Dune 1.4.0.
- There are some Coq-specific variables detailed in :ref:`coq-variables`.
In addition, ``(action ...)`` fields support the following special variables:
- ``target`` expands to the one target.
- ``targets`` expands to the list of target.
- ``deps`` expands to the list of dependencies.
- ``^`` expands to the list of dependencies, separated by spaces.
- ``dep:<path>`` expands to ``<path>`` (and adds ``<path>`` as a dependency of
the action).
- ``exe:<path>`` is the same as ``<path>``, except when cross-compiling, in
which case it will expand to ``<path>`` from the host build context.
- ``bin:<program>`` expands ``<path>`` to ``program``. If ``program``
is installed by a workspace package (see :doc:`/reference/dune/install`
stanzas), the locally built binary will be used, otherwise it will be
searched in the ``<path>`` of the current build context. Note that ``(run
%{bin:program} ...)`` and ``(run program ...)`` behave in the same way.
``%{bin:...}`` is only necessary when you are using ``(bash ...)`` or
``(system ...)``.
- ``bin-available:<program>`` expands to ``true`` or ``false``, depending
on whether ``<program>`` is available or not.
- ``file-available:<path>`` expands to ``true`` or ``false``, depending on
whether the file at ``<path>`` is available in the current workspace.
- ``lib:<public-library-name>:<file>`` expands to the file's installation path
``<file>`` in the library ``<public-library-name>``. If
``<public-library-name>`` is available in the current workspace, the local
file will be used, otherwise the one from the :term:`installed world` will be
used.
- ``lib-private:<library-name>:<file>`` expands to the file's build path
``<file>`` in the library ``<library-name>``. Both public and private library
names are allowed as long as they refer to libraries within the same project.
- ``libexec:<public-library-name>:<file>`` is the same as ``lib:...``, except
when cross-compiling, in which case it will expand to the file from the host
build context.
- ``libexec-private:<library-name>:<file>`` is the same as ``lib-private:...``
except when cross-compiling, in which case it will expand to the file from the
host build context.
- ``lib-available:<library-name>`` expands to ``true`` or ``false`` depending on
whether the library is available or not. A library is available if at least
one of the following conditions holds:
- It's part the :term:`installed world`.
- It's available locally and is not optional.
- It's available locally, and all its library dependencies are
available.
- ``version:<package>`` expands to the version of the given
package. Packages defined in the current scope have priority over the
public packages. Public packages that don't install any libraries
will not be detected. How Dune determines the version
of a package is described :doc:`here <../advanced/package-version>`.
- ``read:<path>`` expands to the contents of the given file.
- ``read-lines:<path>`` expands to the list of lines in the given
file.
- ``read-strings:<path>`` expands to the list of lines in the given
file, unescaped using OCaml lexical convention.
The ``%{<kind>:...}`` forms are what allows you to write custom rules that work
transparently, whether things are installed or not.
Note that aliases are ignored by ``%{deps}``
The intent of this last form is to reliably read a list of strings
generated by an OCaml program via:
.. code:: ocaml
List.iter (fun s -> print_string (String.escaped s)) l
#. Dealing with circular dependencies introduced by variables
If you ever see Dune reporting a dependency cycle that involves a
variable such as `%{read:<path>}`, try to move `<path>` to a different
directory.
The reason you might see such dependency cycle is because Dune is
trying to evaluate the `%{read:<path>}` too early. For instance, let's
consider the following example:
.. code:: dune
(rule
(targets x)
(enabled_if %{read:y})
(action ...))
(rule
(with-stdout-to y (...)))
When Dune loads and interprets this file, it decides whether the
first rule is enabled by evaluating ``%{read:y}``. To
evaluate ``%{read:y}``, it must build ``y``. To build ``y``, it must
figure out the build rule that produces ``y``, and in order to do that, it must
first load and evaluate the above ``dune`` file. You can see how this
creates a cycle.
Some cycles might be more complex. In any case, when you see such an
error, the easiest thing to do is move the file that's being read
to a different directory, preferably a standalone one. You can use the
:doc:`/reference/dune/subdir` stanza to keep the logic self-contained in
the same ``dune`` file:
.. code:: dune
(rule
(targets x)
(enabled_if %{read:dir-for-y/y})
(action ...))
(subdir
dir-for-y
(rule
(with-stdout-to y (...))))
Expansion of Lists
------------------
Forms that expand to a list of items, such as ``%{cc}``, ``%{deps}``,
``%{targets}``, or ``%{read-lines:...}``, are suitable to be used in
``(run <prog> <arguments>)``. For instance in:
.. code:: dune
(run foo %{deps})
If there are two dependencies, ``a`` and ``b``, the produced command
will be equivalent to the shell command:
.. code:: console
$ foo "a" "b"
If you want both dependencies to be passed as a single argument,
you must quote the variable:
.. code:: dune
(run foo "%{deps}")
which is equivalent to the following shell command:
.. code:: console
$ foo "a b"
(The items of the list are concatenated with space.)
Please note: since ``%{deps}`` is a list of items, the first one may be
used as a program name. For instance:
.. code:: dune
(rule
(targets result.txt)
(deps foo.exe (glob_files *.txt))
(action (run %{deps})))
Here is another example:
.. code:: dune
(rule
(target foo.exe)
(deps foo.c)
(action (run %{cc} -o %{target} %{deps} -lfoolib)))

View file

@ -0,0 +1,174 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# dune documentation build configuration file, created by
# sphinx-quickstart on Tue Apr 11 21:24:42 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
import os
import sys
sys.path.append(os.path.abspath('exts'))
from sphinx.highlighting import lexers
from dune_lexer import DuneLexer
from opam_lexer import OpamLexer
from cram_lexer import CramLexer
lexers[DuneLexer.name] = DuneLexer(startinline=True)
lexers[OpamLexer.name] = OpamLexer()
lexers[CramLexer.name] = CramLexer()
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx_copybutton',
'sphinx_design',
'myst_parser',
]
myst_enable_extensions = ["colon_fence"]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'Dune'
copyright = u'2017 - 2025, Jérémie Dimino & the Dune maintainers'
author = u'Jérémie Dimino & the Dune maintainers'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = "en"
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = [
'_build',
'Thumbs.db',
'.DS_Store',
'dev',
'papers',
'changes',
]
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'friendly'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'furo'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'dunedoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'dune.tex', 'Dune Documentation',
u'Jérémie Dimino', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'dune', 'Dune Documentation',
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'dune', 'Dune Documentation',
author, 'dune', 'One line description of project.',
'Miscellaneous'),
]
html_context = {
'display_github': True,
'github_user': 'ocaml',
'github_repo': 'dune',
'github_version': 'main',
'conf_py_path': '/doc/',
}

View file

@ -0,0 +1,851 @@
.. _coq:
***
Coq
***
.. TODO(diataxis)
This looks like there are several components in there:
- reference info for stanzas and variables
- tutorials (the examples part)
Introduction
------------
Dune can build Coq theories and plugins with additional support for extraction
and ``.mlg`` file preprocessing.
A *Coq theory* is a collection of ``.v`` files that define Coq modules whose
names share a common prefix. The module names reflect the directory hierarchy.
Coq theories may be defined using :ref:`coq.theory<coq-theory>` stanzas, or be
auto-detected by Dune by inspecting Coq's install directories.
A *Coq plugin* is an OCaml :doc:`/reference/dune/library` that Coq can
load dynamically at runtime. Plugins are typically linked with the Coq OCaml
API.
Since Coq 8.16, plugins need to be "public" libraries in Dune's terminology,
that is to say, they must declare a ``public_name`` field.
A *Coq project* is an informal term for a
:doc:`/reference/dune-project/index` containing a collection of Coq
theories and plugins.
The ``.v`` files of a theory need not be present as source files. They may also
be Dune targets of other rules.
To enable Coq support in a Dune project, specify the :ref:`Coq language
version<coq-lang>` in the :doc:`/reference/dune-project/index` file. For
example, adding
.. code:: dune
(using coq 0.8)
to a :doc:`/reference/dune-project/index` file enables using the
``coq.theory`` stanza and other ``coq.*`` stanzas. See the :ref:`Dune Coq
language<coq-lang>` section for more details.
.. _coq-theory:
coq.theory
----------
The Coq theory stanza is very similar in form to the OCaml
:doc:`/reference/dune/library` stanza:
.. code:: dune
(coq.theory
(name <module_prefix>)
(package <package>)
(synopsis <text>)
(modules <ordered_set_lang>)
(plugins <ocaml_plugins>)
(flags <coq_flags>)
(modules_flags <flags_map>)
(coqdep_flags <coqdep_flags>)
(coqdoc_flags <coqdoc_flags>)
(stdlib <stdlib_included>)
(mode <coq_native_mode>)
(theories <coq_theories>))
The stanza builds all the ``.v`` files in the given directory and its
subdirectories if the :ref:`include-subdirs <include-subdirs-coq>` stanza is
present.
For usage of this stanza, see the :ref:`examples`.
The semantics of the fields are:
- ``<module_prefix>`` is a dot-separated list of valid Coq module names and
determines the module scope under which the theory is compiled (this
corresponds to Coq's ``-R`` option).
For example, if ``<module_prefix>`` is ``foo.Bar``, the theory modules are
named ``foo.Bar.module1``, ``foo.Bar.module2``, etc. Note that modules in the
same theory don't see the ``foo.Bar`` prefix in the same way that OCaml
``wrapped`` libraries do.
For compatibility, :ref:`Coq lang 1.0<coq-lang-1.0>` installs a theory named
``foo.Bar`` under ``foo/Bar``. Also note that Coq supports composing a module
path from different theories, thus you can name a theory ``foo.Bar`` and a
second one ``foo.Baz``, and Dune composes these properly. See an example of
:ref:`a multi-theory<example-multi-theory>` Coq project for this.
- The ``modules`` field allows one to constrain the set of modules included in
the theory, similar to its OCaml counterpart. Modules are specified in Coq
notation. That is to say, ``A/b.v`` is written ``A.b`` in this field.
- If the ``package`` field is present, Dune generates install rules for the
``.vo`` files of the theory. ``pkg_name`` must be a valid package name.
Note that :ref:`Coq lang 1.0<coq-lang-1.0>` will use the Coq legacy install
setup, where all packages share a common root namespace and install directory,
``lib/coq/user-contrib/<module_prefix>``, as is customary in the Make-based
Coq package ecosystem.
For compatibility, Dune also installs, under the ``user-contrib`` prefix, the
``.cmxs`` files that appear in ``<ocaml_plugins>``. This will be dropped in
future versions.
- ``<coq_flags>`` are passed to ``coqc`` as command-line options. ``:standard``
is taken from the value set in the ``(coq (flags <flags>))`` field in ``env``
profile. See :doc:`/reference/dune/env` for more information.
- ``<flags_map>`` is a list of pairs of valid Coq module names and a
list of ``<coq_flags>``. Note that if a module is present here, the
``:standard`` variable will be bound to the value of ``<coq_flags>``
effective for the theory. This way it is possible to override the
default flags for particular files of the theory, for example:
.. code:: dune
(coq.theory
(name Foo)
(modules_flags
(bar (:standard \ -quiet))))
It is more common to just use this field to *add* some particular
flags, but that should be done using ``(:standard <flag1> <flag2>
...)`` as to propagate the default flags. (Appeared in :ref:`Coq
lang 0.9<coq-lang>`)
- ``<coqdep_flags>`` are extra user-configurable flags passed to ``coqdep``. The
default value for ``:standard`` is empty. This field exists for transient
use-cases, in particular disabling ``coqdep`` warnings, but it should not be
used in normal operations. (Appeared in :ref:`Coq lang 0.10<coq-lang>`)
- ``<coqdoc_flags>`` are extra user-configurable flags passed to ``coqdoc``. The
default value for ``:standard`` is ``--toc``. The ``--html`` or ``--latex``
flags are passed separately depending on which mode is target. See the section
on :ref:`documentation using coqdoc<coqdoc>` for more information.
- ``<stdlib_included>`` can either be ``yes`` or ``no``, currently defaulting to
``yes``. When set to ``no``, Coq's standard library won't be visible from this
theory, which means the ``Coq`` prefix won't be bound, and
``Coq.Init.Prelude`` won't be imported by default.
- If the ``plugins`` field is present, Dune will pass the corresponding flags to
Coq so that ``coqdep`` and ``coqc`` can find the corresponding OCaml libraries
declared in ``<ocaml_plugins>``. This allows a Coq theory to depend on OCaml
plugins. Starting with ``(lang coq 0.6)``, ``<ocaml_plugins>`` must contain
public library names.
- Your Coq theory can depend on other theories --- globally installed or defined
in the current workspace --- by adding the theories names to the
``<coq_theories>`` field. Then, Dune will ensure that the depended theories
are present and correctly registered with Coq.
See :ref:`Locating Theories<locating-theories>` for more information on how
Coq theories are located by Dune.
- If Coq has been configured with ``-native-compiler yes`` or ``ondemand``, Dune
will always build the ``cmxs`` files together with the ``vo`` files. This only
works on Coq versions after 8.13 in which the option was introduced.
You may override this by specifying ``(mode native)`` or ``(mode vo)``.
Before :ref:`Coq lang 0.7<coq-lang>`, the native mode had to be manually
specified, and Coq did not use Coq's configuration
Versions of Dune < 3.7.0 would disable native compilation if the ``dev``
profile was selected.
- If the ``(mode vos)`` field is present, only Coq compiled interface files
``.vos`` will be produced for the theory. This is mainly useful in conjunction
with ``dune coq top``, since this makes the compilation of dependencies much
faster, at the cost of skipping proof checking. (Appeared in :ref:`Coq lang
0.8<coq-lang>`).
Coq Dependencies
~~~~~~~~~~~~~~~~
When a Coq file ``a.v`` depends on another file ``b.v``, Dune is able to build
them in the correct order, even if they are in separate theories. Under the
hood, Dune asks coqdep how to resolve these dependencies, which is why it is
called once per theory.
.. _coqdoc:
Coq Documentation
~~~~~~~~~~~~~~~~~
Given a :ref:`coq-theory` stanza with ``name A``, Dune will produce two
*directory targets*, ``A.html/`` and ``A.tex/``. HTML or LaTeX documentation for
a Coq theory may then be built by running ``dune build A.html`` or ``dune build
A.tex``, respectively (if the :doc:`dune file </reference/dune/index>` for the
theory is the current directory).
There are also two aliases :doc:`/reference/aliases/doc` and ``@doc-latex``
that will respectively build the HTML or LaTeX documentation when called. These
will determine whether or not Dune passes a ``--html`` or ``--latex`` flag to
``coqdoc``.
Further flags can also be configured using the ``(coqdoc_flags)`` field in the
``coq.theory`` stanza. These will be passed to ``coqdoc`` and the default value
is ``:standard`` which is ``--toc``. Extra flags can therefore be passed by
writing ``(coqdoc_flags :standard --body-only)`` for example.
.. _include-subdirs-coq:
Recursive Qualification of Modules
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you add:
.. code:: dune
(include_subdirs qualified)
to a :doc:`/reference/dune/index` file, Dune considers all the modules in
the directory and its subdirectories, adding a prefix to the module name in the
usual Coq style for subdirectories. For example, file ``A/b/C.v`` becomes the
module ``A.b.C``.
.. _locating-theories:
How Dune Locates and Builds theories
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Dune organises it's knowledge about Coq theories in 3 databases:
- Scope database: A Dune *scope* is a part of the project sharing a single
common ``dune-project`` file. In a single scope, any theory in the database
can depend on any other theory in that database as long as their visibilities
are compatible. A public theory for example cannot depend on a private
theory.
- Public theory database: The set of all scopes that Dune knows about is termed
a *workspace*. Only public theories coming from scopes are added to the
database of all public theories in the current workspace.
The public theory database allows theories to depend on theories that are in
a different scope. Thus, you can depend on theories belonging to another
:doc:`/reference/dune-project/index` as long as they share a common
scope under another :doc:`/reference/dune-project/index` file or a
:doc:`/reference/dune-workspace/index` file.
Doing so is usually as simple as placing a Coq project within the scope of
another. This process is termed *composition*. See the :ref:`interproject
composition<example-interproject-theory>` example.
Inter-project composition allows Dune to compute module dependencies using a
fine granularity. In practice, this means that Dune will only build the parts
of a depended theory that are needed by your project.
Inter-project composition has been available since :ref:`Coq lang
0.4<coq-lang>`.
- Installed theory database: If a theory cannot be found in the list of
workspace-public theories, Dune will try to locate the theory in the list of
installed locations Coq knows about.
This list is built using the output of ``coqc --config`` in order to infer
the ``COQLIB`` and ``COQPATH`` environment variables. Each path in ``COQPATH``
and ``COQLIB/user-contrib`` is used to build the database of installed
theories.
Note that, for backwards compatibility purposes, installed theories do not
have to be installed or built using Dune. Dune tries to infer the name of the
theory from the installed layout. This is ambiguous in the sense that a
file-system layout of `a/b` will provide theory names ``a`` and ``a.b``.
Resolving this ambiguity in a backwards-compatible way is not possible, but
future versions of Dune Coq support will provide a way to improve this.
Coq's standard library gets a special status in Dune. The location at
``COQLIB/theories`` will be assigned a entry with the theory name ``Coq``, and
added to the dependency list implicitly. This can be disabled with the
``(stdlib no)`` field in the ``coq.theory`` stanza.
The ``Coq`` prefix can then be used to depend on Coq's stdlib in a regular,
qualified way. We recommend setting ``(stdlib no)`` and adding ``(theories
Coq)`` explicitly.
Composition with installed theories has been available since :ref:`Coq lang
0.8<coq-lang>`.
The databases above are used to locate a theory dependencies. Note that Dune has
a complete global view of every file involved in the compilation of your theory
and will therefore rebuild if any changes are detected.
.. _public-private-theory:
Public and Private Theories
~~~~~~~~~~~~~~~~~~~~~~~~~~~
A *public theory* is a :ref:`coq-theory` stanza that is visible outside the
scope of a :doc:`/reference/dune-project/index` file.
A *private theory* is a :ref:`coq-theory` stanza that is limited to the scope
of the :doc:`/reference/dune-project/index` file it is in.
A private theory may depend on both private and public theories; however, a
public theory may only depend on other public theories.
By default, all :ref:`coq-theory` stanzas are considered private by Dune. In
order to make a private theory into a public theory, the ``(package )`` field
must be specified.
.. code:: dune
(coq.theory
(name private_theory))
(coq.theory
(name private_theory)
(package coq-public-theory))
Limitations
~~~~~~~~~~~
- ``.v`` files always depend on the native OCaml version of the Coq binary and
its plugins, unless the natively compiled versions are missing.
.. _limitation-mlpack:
- A ``foo.mlpack`` file must the present in directories of locally defined
plugins for things to work. ``coqdep``, which is used internally by Dune, will
recognize a plugin by looking at the existence of an ``.mlpack`` file, as it
cannot access (for now) Dune's library database. This is a limitation of
``coqdep``. See the :ref:`example plugin<example plugin>` or the `this
template <https://github.com/ejgallego/coq-plugin-template>`_.
This limitation will be lifted soon, as newer versions of ``coqdep`` can use
findlib's database to check the existence of OCaml libraries.
.. _coq-lang:
Coq Language Version
~~~~~~~~~~~~~~~~~~~~
The Coq lang can be modified by adding the following to a
:doc:`/reference/dune-project/index` file:
.. code:: dune
(using coq 0.8)
The supported Coq language versions (not the version of Coq) are:
- ``0.10``: Support for the ``(coqdep_flags ...)`` field.
- ``0.9``: Support for per-module flags with the ``(module_flags ...)``` field.
- ``0.8``: Support for composition with installed Coq theories;
support for ``vos`` builds.
Deprecated experimental Coq language versions are:
- ``0.1``: Basic Coq theory support.
- ``0.2``: Support for the ``theories`` field and composition of theories in the
same scope.
- ``0.3``: Support for ``(mode native)`` requires Coq >= 8.10 (and Dune >= 2.9
for Coq >= 8.14).
- ``0.4``: Support for interproject composition of theories.
- ``0.5``: ``(libraries ...)`` field deprecated in favor of ``(plugins ...)``
field.
- ``0.6``: Support for ``(stdlib no)``.
- ``0.7``: ``(mode )`` is automatically detected from the configuration of Coq
and ``(mode native)`` is deprecated. The ``dev`` profile also no longer
disables native compilation.
.. _coq-lang-1.0:
Coq Language Version 1.0
~~~~~~~~~~~~~~~~~~~~~~~~
Guarantees with respect to stability are not yet provided, but we
intend that the ``(0.8)`` version of the language becomes ``1.0``.
The ``1.0`` version of Coq lang will commit to a stable set of
functionality. All the features below are expected to reach ``1.0``
unchanged or minimally modified.
.. _coq-extraction:
coq.extraction
--------------
Coq may be instructed to *extract* OCaml sources as part of the compilation
process by using the ``coq.extraction`` stanza:
.. code:: dune
(coq.extraction
(prelude <name>)
(extracted_modules <names>)
<optional-fields>)
- ``(prelude <name>)`` refers to the Coq source that contains the extraction
commands.
- ``(extracted_modules <names>)`` is an exhaustive list of OCaml modules
extracted.
- ``<optional-fields>`` are ``flags``, ``stdlib``, ``theories``, and
``plugins``. All of these fields have the same meaning as in the
``coq.theory`` stanza.
The extracted sources can then be used in ``executable`` or ``library`` stanzas
as any other sources.
Note that the sources are extracted to the directory where the ``prelude`` file
lives. Thus the common placement for the ``OCaml`` stanzas is in the same
:doc:`/reference/dune/index` file.
**Warning**: using Coq's ``Cd`` command to work around problems with the output
directory is not allowed when using extraction from Dune. Moreover the ``Cd``
command has been deprecated in Coq 8.12.
.. _coq-pp:
coq.pp
------
Authors of Coq plugins often need to write ``.mlg`` files to extend the Coq
grammar. Such files are preprocessed with the ``coqpp`` binary. To help plugin
authors avoid writing boilerplate, we provide a ``(coq.pp ...)`` stanza:
.. code:: dune
(coq.pp
(modules <ordered_set_lang>))
This will run the ``coqpp`` binary on all the ``.mlg`` files in
``<ordered_set_lang>``.
.. _examples:
Examples of Coq Projects
------------------------
Here we list some examples of some basic Coq project setups in order.
.. _example-simple:
Simple Project
~~~~~~~~~~~~~~
Let us start with a simple project. First, make sure we have a
:doc:`/reference/dune-project/index` file with a :ref:`Coq
lang<coq-lang>` stanza present:
.. code:: dune
(lang dune 3.20)
(using coq 0.8)
Next we need a :doc:`/reference/dune/index` file with a :ref:`coq-theory`
stanza:
.. code:: dune
(coq.theory
(name myTheory))
Finally, we need a Coq ``.v`` file which we name ``A.v``:
.. code:: coq
(** This is my def *)
Definition mydef := nat.
Now we run ``dune build``. After this is complete, we get the following files:
.. code::
.
├── A.v
├── _build
│ ├── default
│ │ ├── A.glob
│ │ ├── A.v
│ │ └── A.vo
│ └── log
├── dune
└── dune-project
.. _example-multi-theory:
Multi-Theory Project
~~~~~~~~~~~~~~~~~~~~
Here is an example of a more complicated setup:
.. code::
.
├── A
│ ├── AA
│ │ └── aa.v
│ ├── AB
│ │ └── ab.v
│ └── dune
├── B
│ ├── b.v
│ └── dune
└── dune-project
Here are the :doc:`/reference/dune/index` files:
.. code:: dune
; A/dune
(include_subdirs qualified)
(coq.theory
(name A))
; B/dune
(coq.theory
(name B)
(theories A))
Notice the ``theories`` field in ``B`` allows one :ref:`coq-theory` to depend on
another. Another thing to note is the inclusion of the
:doc:`/reference/dune/include_subdirs` stanza. This allows our theory to
have :ref:`multiple subdirectories<include-subdirs-coq>`.
Here are the contents of the ``.v`` files:
.. code:: coq
(* A/AA/aa.v is empty *)
(* A/AB/ab.v *)
Require Import AA.aa.
(* B/b.v *)
From A Require Import AB.ab.
This causes a dependency chain ``b.v -> ab.v -> aa.v``. Now we might be
interested in building theory ``B``, so all we have to do is run ``dune build
B``. Dune will automatically build the theory ``A`` since it is a dependency.
.. _example-interproject-theory:
Composing Projects
~~~~~~~~~~~~~~~~~~
To demonstrate the composition of Coq projects, we can take our previous two
examples and put them in project which has a theory that depends on theories in
both projects.
.. code::
.
├── CombinedWork
│ ├── comb.v
│ └── dune
├── DeeperTheory
│ ├── A
│ │ ├── AA
│ │ │ └── aa.v
│ │ ├── AB
│ │ │ └── ab.v
│ │ └── dune
│ ├── B
│ │ ├── b.v
│ │ └── dune
│ ├── Deep.opam
│ └── dune-project
├── dune-project
└── SimpleTheory
├── A.v
├── dune
├── dune-project
└── Simple.opam
The file ``comb.v`` looks like:
.. code:: coq
(* Files from DeeperTheory *)
From A.AA Require Import aa.
(* In Coq, partial prefixes for theory names are enough *)
From A Require Import ab.
From B Require Import b.
(* Files from SimpleTheory *)
From myTheory Require Import A.
We are referencing Coq modules from all three of our previously defined
theories.
Our :doc:`/reference/dune/index` file in ``CombinedWork`` looks like:
.. code:: dune
(coq.theory
(name Combined)
(theories myTheory A B))
As you can see, there are dependencies on all the theories we mentioned.
All three of the theories we defined before were *private theories*. In order to
depend on them, we needed to make them *public theories*. See the section on
:ref:`public-private-theory`.
Composing With Installed Theories
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We can also compose with theories that are installed. If we wanted to have a
theory that depends on the Coq theory ``mathcomp.ssreflect`` we can add the
following to our stanza:
.. code:: dune
(coq.theory
(name my_mathcomp_theory)
(theories mathcomp.ssreflect))
Note that ``mathcomp`` on its own would also work, since there would be a
``matchcomp`` directory in ``user-contrib``, however it would not compose
locally with a ``coq.theory`` stanza with the ``mathcomp.ssreflect`` name (in
case one exists). So it is advisable to use the actual theory name. Dune is not
able to validate theory names that have been installed since they do not include
their Dune metadata.
Building Documentation
~~~~~~~~~~~~~~~~~~~~~~
Following from our last example, we might wish to build the HTML documentation
for ``A``. We simply do ``dune build A/A.html/``. This will produce the
following files:
.. code::
A
├── AA
│ ├── aa.glob
│ ├── aa.v
│ └── aa.vo
├── AB
│ ├── ab.glob
│ ├── ab.v
│ └── ab.vo
└── A.html
├── A.AA.aa.html
├── A.AB.ab.html
├── coqdoc.css
├── index.html
└── toc.html
We may also want to build the LaTeX documentation of the theory ``B``. For this
we can call ``dune build B/B.tex/``. If we want to build all the HTML
documentation targets, we can use the :doc:`/reference/aliases/doc` alias as in
``dune build @doc``. If we want to build all the LaTeX documentation then we
use the ``@doc-latex`` alias instead.
.. _example plugin:
Coq Plugin Project
~~~~~~~~~~~~~~~~~~
Let us build a simple Coq plugin to demonstrate how Dune can handle this setup.
.. code::
.
├── dune-project
├── src
│ ├── dune
│ ├── hello_world.ml
│ ├── my_plugin.mlpack
│ └── syntax.mlg
└── theories
├── dune
└── UsingMyPlugin.v
Our :doc:`/reference/dune-project/index` will need to have a package for
the plugin to sit in, otherwise Coq will not be able to find it.
.. code:: dune
(lang dune 3.20)
(using coq 0.8)
(package
(name my-coq-plugin)
(synopsis "My Coq Plugin")
(depends coq-core))
Now we have two directories, ``src/`` and ``theories/`` each with their own
:doc:`/reference/dune/index` file. Let us begin with the plugin
:doc:`/reference/dune/index` file:
.. code:: dune
(library
(name my_plugin)
(public_name my-coq-plugin.plugin)
(synopsis "My Coq Plugin")
(flags :standard -rectypes -w -27)
(libraries coq-core.vernac))
(coq.pp
(modules syntax))
Here we define a library using the :doc:`/reference/dune/library` stanza.
Importantly, we declared which external libraries we rely on and gave the
library a ``public_name``, as starting with Coq 8.16, Coq will identify plugins
using their corresponding findlib public name.
The :ref:`coq-pp` stanza allows ``src/syntax.mlg`` to be preprocessed, which for
reference looks like:
.. code:: ocaml
DECLARE PLUGIN "my-coq-plugin.plugin"
VERNAC COMMAND EXTEND Hello CLASSIFIED AS QUERY
| [ "Hello" ] -> { Feedback.msg_notice Pp.(str Hello_world.hello_world) }
END
Together with ``hello_world.ml``:
.. code:: ocaml
let hello_world = "hello world!"
They make up the plugin. There is one more important ingredient here and that is
the ``my_plugin.mlpack`` file, needed to signal ``coqdep`` the existence of
``my_plugin`` in this directory. An empty file suffices. See :ref:`this note on
.mlpack files<limitation-mlpack>`.
The file for ``theories/`` is a standard :ref:`coq-theory` stanza with an
included ``libraries`` field allowing Dune to see ``my-coq-plugin.plugin`` as a
dependency.
.. code:: dune
(coq.theory
(name MyPlugin)
(package my-coq-plugin)
(plugins my-coq-plugin.plugin))
Finally, our .v file will look something like this:
.. code:: coq
(* For Coq < 8.16 *)
Declare ML Module "my_plugin".
(* For Coq = 8.16 *)
Declare ML Module "my_plugin:my-coq-plugin.plugin".
(* At some point Coq 8.17 or 8.18 will transition to the syntax below, check Coq's manual *)
Declare ML Module "my-coq-plugin.plugin".
Hello.
Running ``dune build`` will build everything correctly.
.. _running-coq-top:
Running a Coq Toplevel
----------------------
Dune supports running a Coq toplevel binary such as ``coqtop``, which is
typically used by editors such as CoqIDE or Proof General to interact with Coq.
The following command:
.. code:: console
$ dune coq top <file> -- <args>
runs a Coq toplevel (``coqtop`` by default) on the given Coq file ``<file>``,
after having recompiled its dependencies as necessary. The given arguments
``<args>`` are forwarded to the invoked command. For example, this can be used
to pass a ``-emacs`` flag to ``coqtop``.
A different toplevel can be chosen with ``dune coq top --toplevel CMD <file>``.
Note that using ``--toplevel echo`` is one way to observe what options are
actually passed to the toplevel. These options are computed based on the options
that would be passed to the Coq compiler if it was invoked on the Coq file
``<file>``.
In certain situations, it is desirable to not rebuild dependencies for a ``.v``
files but still pass the correct flags to the toplevel. For this reason, a
``--no-build`` flag can be passed to ``dune coq top`` which will skip any
building of dependencies.
Limitations
~~~~~~~~~~~
* Only files that are part of a stanza can be loaded in a Coq toplevel.
* When a file is created, it must be written to the file system before the Coq
toplevel is started.
* When new dependencies are added to a file (via a Coq ``Require`` vernacular
command), it is in principle required to save the file and restart to Coq
toplevel process.
.. _coq-variables:
Coq-Specific Variables
----------------------
There are some special variables that can be used to access data about the Coq
configuration. These are:
- ``%{coq:version}`` the version of Coq.
- ``%{coq:version.major}`` the major version of Coq (e.g., ``8.15.2`` gives
``8``).
- ``%{coq:version.minor}`` the minor version of Coq (e.g., ``8.15.2`` gives
``15``).
- ``%{coq:version.suffix}`` the suffix version of Coq (e.g., ``8.15.2`` gives
``.2`` and ``8.15+rc1`` gives ``+rc1``).
- ``%{coq:ocaml-version}`` the version of OCaml used to compile Coq.
- ``%{coq:coqlib}`` the output of ``COQLIB`` from ``coqc -config``.
- ``%{coq:coq_native_compiler_default}`` the output of
``COQ_NATIVE_COMPILER_DEFAULT`` from ``coqc -config``.
See :doc:`concepts/variables` for more information on variables supported by
Dune.
.. _coq-env:
Coq Environment Fields
----------------------
The :doc:`/reference/dune/env` stanza has a ``(coq <coq_fields>)`` field
with the following values for ``<coq_fields>``:
- ``(flags <flags>)``: The default flags passed to ``coqc``. The default value
is ``-q``. Values set here become the ``:standard`` value in the
``(coq.theory (flags <flags>))`` field.
- ``(coqdep_flags <flags>)``: The default flags passed to ``coqdep``. The default
value is empty. Values set here become the ``:standard`` value in the
``(coq.theory (coqdep_flags <flags>))`` field. As noted in the documentation
of the ``(coq.theory (coqdep_flags <flags>))`` field, changing the ``coqdep``
flags is discouraged.
- ``(coqdoc_flags <flags>)``: The default flags passed to ``coqdoc``. The default
value is ``--toc``. Values set here become the ``:standard`` value in the
``(coq.theory (coqdoc_flags <flags>))`` field.

View file

@ -0,0 +1,104 @@
.. _cross-compilation:
*****************
Cross-Compilation
*****************
.. TODO(diataxis)
This can be turned into an how-to guide.
Dune allows for cross-compilation by defining build contexts with multiple
targets. Targets are specified by adding a ``targets`` field to the build
context definition.
``targets`` takes a list of target name. It can be either:
- ``native``, the native tools that can build binaries to run on the machine
doing the build
- the name of an alternative toolchain
Note that at the moment, there is no official support for cross-compilation in
OCaml. Dune supports the `opam-cross-<x>` repositories from the `OCaml-cross
organization on GitHub <https://github.com/ocaml-cross/>`_, such as:
- `opam-cross-windows <https://github.com/ocaml-cross/opam-cross-windows>`_
- `opam-cross-android <https://github.com/ocaml-cross/opam-cross-android>`_
- `opam-cross-ios <https://github.com/ocaml-cross/opam-cross-ios>`_
In particular:
- to build Windows binaries using opam-cross-windows, write ``windows`` in the
list of targets
- to build Android binaries using opam-cross-android, write ``android`` in the
list of targets
- to build IOS binaries using opam-cross-ios, write ``ios`` in the list of
targets
For example, the following workspace file defines three different targets for
the ``default`` build context:
.. code:: dune
(context (default (targets native windows android)))
This configuration defines three build contexts:
- ``default``
- ``default.windows``
- ``default.android``
Note that the ``native`` target is always implicitly added when not present;
however, ``dune build @install`` will skip this context, i.e., ``default`` will
only be used for building executables needed by the other contexts.
With such a setup, calling ``dune build @install`` will build all the packages
three times.
Note that instead of writing a ``dune-workspace`` file, you can also use the
``-x`` command line option. Passing ``-x foo`` to ``dune`` without having a
``dune-workspace`` file is the same as writing the following ``dune-workspace``
file:
.. code:: dune
(context (default (targets foo)))
If you have a ``dune-workspace`` and pass a ``-x foo`` option, ``foo`` will be
added as target of all context stanzas.
How Does it Work?
=================
In such a setup, binaries that need to be built and executed in the
``default.windows`` or ``default.android`` contexts as part of the build will
no longer be executed. Instead, all the binaries that will be executed come
from the ``default`` context. One consequence of this is that all preprocessing
(PPX or otherwise) will be done using binaries built in the ``default``
context.
To clarify this with an example, let's assume that you have the following
``src/dune`` file:
.. code:: dune
(executable (name foo))
(rule (with-stdout-to blah (run ./foo.exe)))
When building ``_build/default/src/blah``, dune will resolve ``./foo.exe`` to
``_build/default/src/foo.exe`` as expected. However, for
``_build/default.windows/src/blah`` dune will resolve ``./foo.exe`` to
``_build/default/src/foo.exe``
Assuming that the right packages are installed or that your workspace has no
external dependencies, Dune will be able to cross-compile a given package
without doing anything special.
Some packages might still have to be updated to support cross-compilation. For
instance if the ``foo.exe`` program in the previous example was using
``Sys.os_type``, it should instead take it as a command line argument:
.. code:: dune
(rule (with-stdout-to blah (run ./foo.exe -os-type %{os_type})))

View file

@ -0,0 +1,5 @@
# Developer documentation
We use `doc/dev` for storing developer documentation, including specification,
design and implementation notes. Anything that should be user-facing should go
into the `doc` folder instead.

View file

@ -0,0 +1,279 @@
# Dune cache: design and implementation notes
This document describes main ideas behind the Dune cache as well as a few
subtleties of the implementation. This is a working document and it will be
updated as part of on-going development. Some of the described features are
currently still in development or unused, in particular, here we describe
support for two types of cache entries &ndash; artifacts and values &ndash; but
Dune currently doesn't store any value entries in the cache.
The design and implementation are based on a non-trivial assumption that there
are no hash collisions, for instance, that we will never come across two files
with different contents but with the same content hash. While this assumption is
common in the world of build systems and package managers, and is highly
unlikely to be violated by chance, one can manufacture hash collisions on
purpose, especially when using a weak hash like MD5.
## What we store in the cache
The cache stores build _artifacts_ and _values_.
* An _artifact_ is a file produced by a build rule. As any file, it has a name
as well as content. Note that we treat a file's executable permission bit as
part of its content.
* A _value_ is anything else produced during a build that is not written to a
file but which is worth storing persistently between successive builds. A
common example is a string written to the standard output by a build action.
Such output-producing actions may run as part of a build rule or at an earlier
stage when generating rules. Unlike artifacts, values have no names, yet they
still have content.
## How we use the cache
The build system uses the cache to _store_ and _restore_ build artifacts and
values. Here is a typical interaction sequence for the case of artifacts:
* The build system is executing a build rule and has already identified all of
its dependencies, thereby obtaining the _rule hash_. It uses it to make a
_restore request_ to the cache.
* Now there are two cases:
- The cache successfully restores all the artifacts of the rule, placing
them into the build directory and returning their content hashes. The
build system can skip building the artifacts and can use the obtained
content hashes for deciding whether any dependent build rules need to be
rerun. **This successful scenario is the only reason we use the cache.**
- The cache fails to restore the artifacts, either because of an error or
because it doesn't have an entry for the given rule hash. In this case,
the build system needs to build the artifacts itself. On completion, it
makes a _store request_ to the cache, providing a list of build artifacts
(_file names_ in the build directory) as well as their _content hashes_.
The cache stores the artifacts and after that the build system is allowed
to continue with the build (but not before, since the cache requires
exclusive access to the artifacts in the build directory). There can be a
rare situation where the store request is declined because the given entry
is already in the cache. How could this happen? We did try to restore it
first! The reason is that the cache can be populated concurrently by
multiple build systems and by the distributed cache daemon, so there can
be a race between multiple systems adding the same entry to the cache. In
this case, the cache will perform _deduplication_ of build artifacts by
replacing them with hard links to the copies already stored in the cache
(this is an example where the exclusive access is needed).
Build values are handled similarly; the main difference is that to identify a
cache entry we use the corresponding _action hash_ rather than the _rule hash_.
The only difference between rule hashes and action hashes is that the former
include the names of the produced artifacts into the hash, while the latter
do not, since values have no names.
## Cache storage format
Let `root` stand for the cache root directory. It has three main subdirectories.
* `root/meta/v3` stores _metadata files_, one per each historically executed
build rule or value-producing action. (While this is a convenient mental
model, in reality we need to occasionally remove some outdated metadata files
to free disk space &ndash; see the section on cache trimming.)
<br/><br/>
A metadata file corresponding to a build rule is named by the rule hash and
stores file names and content hashes of all artifacts produced by the rule.
<br/><br/>
A metadata file corresponding to a value-producing action is named by the
action hash and stores the hash of the resulting value.
<br/><br/>
It is important to guarantee that rule and action hashes do not accidentally
overlap, which may happen if one simply hashes their in-memory representations
because a rule and an action might happen to be represented by the same
sequence of bytes in memory.
* `root/files/v3` is a storage for artifacts, where files named by content
hashes store the matching contents. We will create hard links to these files
from build directories and rely on the hard link count, as well as on the last
change time as useful metrics during cache trimming.
* `root/values/v3` is a storage for values. As in the case of `files`, we store
the values in the files named by their content hashes. However, these files
will always have the hard link count equal to 1, because they do not appear
anywhere in build directories. By storing them in a separate directory, we
simplify the job of the cache trimmer.
* `root/temp` contains temporary files used for atomic file operations needed
when adding new entries to the cache, as will be described below.
Note that since this document was first written, some of the above paths have
changed due to version bumps (to `v4` and beyond).
## Adding entries to the cache
To add entries to the cache, we use the functions `store_artifacts` and
`store_value` described in the corresponding sections below. Setting possible
errors aside, these functions can succeed in two ways.
* They return `Stored` if the given entry is new and it has been successfully
stored in the cache.
* They return `Already_present` if the given entry has already been present in
the cache and can therefore be discarded. This is a rare scenario where
multiple systems race to add the same entry, and only one of them will receive
the glory of the `Stored` response.
### Atomic writing to the cache
As mentioned above, the cache can be modified concurrently by multiple systems,
so to prevent collisions on individual files, we need to create new files
atomically. To do that, we first create a temporary file in the `temp`
directory, then create a hard link to it from the cache (this operation will
fail if another process managed to create the cache entry earlier), and then
unlink the temporary file.
From now on, whenever we say "create a file", we mean create a file atomically.
If two systems attempt to create a file with the same name simultaneously, one
of them will win the competition and the contents it writes will remain in the
cache until it is deleted during cache trimming.
Note that it is possible for a metadata file with a given name to have multiple
possible contents due to _non-determinism_, and the cache implementation should
not assume otherwise.
### Storing artifacts
To store artifacts produced by a build rule, we perform the following sequence
of steps.
* Create a metadata file in the `meta` directory, listing all the artifacts.
If the file already exists (which should be a rare case), verify that it
contains the expected list of artifacts (both file names and content hashes).
If it doesn't, we have found a non-deterministic build rule and report an
error.
* For each artifact, we store it in the `files` directory using the artifact's
content hash as the name. In each case, there are two scenarios:
- If the artifact is already in the cache, we perform
deduplication by replacing the artifact in the build directory
with a hard link to the file stored in the cache. We assume that
the build system will wait for `store_artifacts` to complete before
starting any further actions that might read these artifacts from
the build directory and thus interfere with the deduplication.
- Otherwise, we create a hard link to the artifact from the `files`
directory.
The function returns `Already_present` if the metadata file and all of the
artifacts were already in the cache; otherwise, it returns `Stored`.
### Storing values
Storing a value is simpler than storing artifacts because there is no need
for deduplication. The steps are:
* Create a metadata file in the `meta` directory, recording the value's hash.
If the file already exists (which should be a rare case), verify that it
contains the same hash. If it doesn't, we have found a non-deterministic build
action and report an error.
* Store the value as a file in the `values` directory using the value's hash as
the file name. If the file is already in the cache, we don't need to do
anything.
The function returns `Already_present` if the metadata file and the value were
already in the cache; otherwise, it returns `Stored`.
## Restoring entries from the cache
To restore entries from the cache, we use the functions `restore_artifacts` and
`restore_value` described in the corresponding sections below. Setting possible
errors aside, these functions either fail to find the entry in the cache and
return `Not_found_in_cache`, or succeed and return `Restored` along with some
information about the restored entry.
### Restoring artifacts
Given a rule hash, the function `restore_artifacts` performs the following
steps.
* Look up the corresponding metadata file in the `meta` directory. If it doesn't
exist, return `Not_found_in_cache`. Otherwise, read the list of artifacts,
i.e. the list of file names and their content hashes from the metadata file.
* For each artifact, lookup the content hash in the `files` directory. If it
doesn't exist, return `Not_found_in_cache`. Otherwise: (i) delete the
corresponding (most likely stale) file in the build directory, and then (ii)
create a hard link with the same name, pointing to the file in the cache.
If the above succeeds for every artifact in the list, the function returns
`Restored` along with the obtained list of file name and content hash pairs.
### Restoring values
Given an action hash, the function `restore_value` performs the following steps.
* Look up the corresponding metadata file in the `meta` directory. If it doesn't
exist, return `Not_found_in_cache`. Otherwise, read the hash of the value.
* Look up the hash in the `values` directory. If it doesn't exist, return
`Not_found_in_cache`. Otherwise, return `Restored` along with the value read
from the stored file.
## Trimming the cache
Storing all historically produced artifacts and values is infeasible, so the
cache needs to be regularly trimmed. The current trimming algorithm performs the
following steps.
* Scan the `files` directory to find all currently unused artifact entries. An
artifact is _unused_ if its hard link count is equal to 1. There is no point in
trimming other entries, since they appear in at least one build directory. In
fact, trimming them is potentially harmful because if the same entries were to
be added to the cache again from a new directory, we would have been unable to
perform the deduplication, thus losing some sharing opportunities.
* Scan the `values` directory to find all value entries. We have no information
about their current usage, so we conservatively allow all of them to be
trimmed and recomputed in the next build if needed.
* Sort the entries according to the following criteria:
- Type: artifacts precede values in the trimming list since artifacts are
generally larger and we know for sure that they are unused;
- The time of last change: entries that became unused more recently go later
in the list.
* Traverse the list and delete the corresponding entries until the trimming goal
has been met. Right before deleting an artifact entry, double check that its
hard link count is still equal to 1. A build system running concurrently might
have created a hard link to it after we collected the information, so deleting
this file from the cache could lead to a loss of sharing between different
build directories.
* Finally, traverse the `meta` directory and remove all _broken_ metadata files,
i.e. the files that refer to content hashes with no corresponding entries in
the `files` and `values` directories. This step does not need to be done on
every trimming. It is expensive but metadata files are generally small and
there is no harm in keeping broken metadata files in the cache. In fact, the
information contained in broken metadata files can be utilised by the build
system for so-called _shallow builds_ where intermediate build artifacts are
not materialised on the disk and it is sufficient to only know their hashes,
which are listed in the metadata files.
To enable more sophisticated trimming strategies, we could augment the metadata
stored in the cache with information about the _cost_ of producing cache
entries, i.e. the time it would take to execute the corresponding rule or action
to restore the entry if needed. For deterministic build rules, we can do _local
cost reasoning_, i.e. we do not need to take the cost of rebuilding their
dependents into account, since such rebuilding would be unnecessary due to the
early cut-off optimisation.
Another promising idea is to add support for incremental cache trimming where
the build system informs the cache that a previously added entry has become
obsolete, letting the cache trim it early if it meets the trimming criteria.
### Interaction with the previous cache versions
Note also that as the new cache format evolves further and we, for example, move
from `files/v3` to `files/v4`, the cache trimmer will need to evolve too, to be
able to cope with entries of all currently supported versions.

View file

@ -0,0 +1,64 @@
# Directory targets
A **directory target** corresponds to a file tree rooted at a specified root
directory, for example, `docs/*`. Typical examples of rules producing directory
targets are: unpacking an archive, running `make` in a vendored package, and
building files with non-deterministic names (e.g. including the current date).
## Declaring a directory target
To declare a directory target `docs/*`, use the syntax `(target (dir docs))` in
a rule stanza. The corresponding rule should create the directory `docs` and is
free to populate it with an arbitrary number of files and/or subdirectories.
Like file targets, directory targets can be promoted to the source tree by
adding `(mode promote)` to the rule stanza.
## Depending on a directory target
There are two ways to depend on a directory target:
* An **opaque dependency** on the whole file tree `docs/*`. Opaque dependencies
are invalidated if the contents of the tree is changed in any way. To declare
an opaque dependency on `docs/*`, use the syntax `(dep (dir docs))` in a rule
stanza.
* A **projection dependency** on a specific file in the tree, e.g.
`docs/html/index.html`. A projection dependency is declared using the standard
syntax `(dep docs/html/index.html)` and works like a dependency on a normal
file target. For example, if the `docs/*` directory is rebuilt and only
`docs/html/logo.png` is modified, then the dependency on `docs/html/index.html`
is considered to be up-to-date. Note that it is easy to make a mistake with
such projection dependencies, for example, by forgetting that `index.html`
actually does include the image `docs/html/logo.png`. In such cases,
sandboxing will help since only the requested projection dependencies will be
available in the sandbox (i.e., not the whole directory target).
## Building a directory target
Users can request building whole directory targets or individual files via
`dune build docs` and `dune build docs/html/index.html` commands.
## Current limitations
* It is not allowed to have two rules with the same directory target. That is,
like file targets, directory targets are **exclusive** (but see _shared
directory targets_ below).
* Directory targets cannot have nested file or directory targets, i.e. other
rules are not allowed to declare targets within the file tree of a directory
target.
## Possible future extensions
Here are some possible extensions to consider:
* **Opaque directory targets**: a rule may declare that its directory target is
opaque, in which case projection dependencies on its content will be
disallowed. One can also consider only partially opaque directory targets,
where the contents of the directory is only partially visible.
* **Shared directory targets**: we can allow multiple rules to write to the same
directory target, as long as they do not write to the same files. In this
case, depending on a directory target would mean depending on all of the rules
that declare it as a target.

View file

@ -0,0 +1,106 @@
The Revision Store
==================
The revision store is the place where Git data that is relevant to the Dune
package management is cached.
The Concepts
------------
The revision store uses Git in the way of its original slogan, as a
content-addressable file system. A lot of data (code) and meta-data (opam files)
is stored in Git repositories that are often forked from each other, hence to
save space Dune has a Git object cache.
Git is implemented as a way to store revisions and being able to address them.
However, these revisions do not have to have a common ancestor and given Git
uses SHA1 hashes for addressing it is possible to join multiple repositories in
one single Git repository without clashing. A fairly common usecase outside of
Dune are `gh-pages` branches to serve documentation on Github, which do not
share a common parent with the main branch of the repository.
The revision store exploits this feature by putting all revisions of all
repositories into one single large repository to take advantage of caching
effects.
The Advantages
--------------
This way of organizing means that all revisions shared between multiple forks
of a Git repository can reuse the same Git objects that are in common and don't
need to download them nor store them again. Updating repositories can be done
incrementally, as Git knows which revisions are available locally and which
ones need to be fetched.
It is also possible to refer to previous states easily as the commits are part
of a Git repo and checking out an older version is as simple as checking out
the current version of the files.
Considerations and Compromises
------------------------------
An important consideration was that the management of the revision store should
be entirely transparent to the user, they should not need to do any steps to
create nor maintain it. It should get created automatically if needed and all
the steps that are necessary to keep it updated should happen in the
background. The store should always work like a cache that can be discarded
safely without causing data loss.
The revision store should always give out the most recent version of data,
unless explicitly instructed otherwise. This means that :
* If only a Git source is specified, then the revision store will
automatically get the newest revision
* If the source specifies a tag or branch, then the revision store will
automatically update to the newest revision
* It a revision is specified, the revision store will only update if the
revision is not yet cached, otherwise the cached version can be used
The final consideration means that an offline usage is possible if all
repositories specified are specified with their hash.
Due to the fact that the revision store is a Git repository it means that the
data sources that can be added to it also have to be available via Git. This
means that adding repositories that use different version control systems
aren't supported at the moment nor are plain HTTP sources supported.
Support for other kinds of VCSes is a possible extension by replicating similar
concepts with other version control systems, provided they allow for similar
flexibility as the Git way of storing revisions. However at the moment most
users have settled on using Git, hence this version should be able to
accommodate the needs for most users.
Another compromise is that old repositories with long histories and large sizes
have to be cloned before use, thus increasing the size of the initial download
compared to the same metadata downloaded as a compressed tarball. Despite Git
compressing objects, the history of the repositories to be added does increase
the overhead.
A solution to this could be shallow clones which only contain the latest
revisions, however these have [shown to be
problematic](https://blog.cocoapods.org/Master-Spec-Repo-Rate-Limiting-Post-Mortem/)
thus for time being we are fetching the complete histories.
Implementation
--------------
This section describes the current way the revision store is implemented.
As the revision store is not project specific, it is stored in the user's cache
directory (using the [freedesktop.org](https://www.freedesktop.org/wiki/)
specifications, the directory specified by `XDG_CACHE_HOME`), with all dune
instances sharing one single revision store.
The revision store itself is a `bare` Git repository without a worktree. This
is because all repositories in the revision store are equal and checking out
one particular revision would be a waste of disk space as the Git tooling can
be used to construct any revisions out of the bare repository anyway.
Thus every source that is added to the revision store as a remote that tracks
the default branch (or, if a branch is specified explicitly, then that
branch) and fetched, thus storing the required revisions in the revision store.
The implementation of these features is a mix of calling the `git` binary and
implementing parts in OCaml. This means that the `git` binary is required on
the system. Possible future improvements could be using
[ocaml-git](https://github.com/mirage/ocaml-git) to avoid the dependency.

View file

@ -0,0 +1,180 @@
# Runtime RPC versioning implementation notes
This document describes the versioning protocol used to ensure two-way
compatibility between different versions of the API.
The approach is loosely inspired by the `Both_converts` model used by
[Versioned_rpc](https://ocaml.janestreet.com/ocaml-core/latest/doc/async_rpc_kernel/Async_rpc_kernel/index.html#module-Versioned_rpc)
in `Async`, in which both parties maintain a "menu" of supported RPC
versions, which is used to negotiate a common protocol for each
method.
This is a working document and will be updated as the design evolves.
## Terms
- A **procedure** is a common term encompassing *notifications*
(one-way messages) and *requests* (a communication to which
a response is expected).
- A **model** is the logical payload type for one direction of
a procedure. Note that this is a per-actor entity; the ultimate
goal of runtime RPC versioning is to allow clients and servers to
disagree on a model type without preventing them from interacting.
- A **wire type** for a procedure is the logical type sent "over the
wire" for one direction of a procedure. The end result of
negotiating a version for a procedure is to select a wire type
known to both the client and the server. Typically, the older of
the two model types will be chosen as the wire type,
- A **generation** of a procedure is the set of wire types
corresponding to each direction of a procedure, along with the
de/serialisation logic and upgrade and downgrade functions
transforming the wire types to the model types and vice versa.
Each generation is associated with a *version number*, which
should be unique within a procedure.
- The **menu** is a mapping from method names to the particular
generations that will be used for each procedure for a particular
session. In the source code, this term is overloaded to also refer
to a mapping from method names to *all known* generations of
a procedure.
- The **declaration** of a procedure lists its model types and all
known generations, along with its method name. Multiple
declarations of the same procedure is allowed, so long as they do
not overlap version numbers.
- The **implementation** of a declaration is the actual behavior of
a procedure, which acts on the model types. Typically, this will
be on the server, but in the future there may also be a use for
server-to-client requests. This document is not concerned with the
internals of any given implementation, only whether such an
implementation exists at all.
## Background
Previously, there was no distinction between model and wire types.
This meant that any change to a model type required both build servers
and clients to upgrade in lockstep, as otherwise the receiver would be
unable to deserialize the payload of a procedure.
Unfortunately, most lighter-weight solutions (such as modifying the
de/serialization logic to be resilient to, e.g., extra/missing fields
or variants in types) are insufficient. Early designs of the
diagnostic API, for example, reported targets as strings, but was
changed to give structured information instead.
Similarly, requirements like "the client must always be older than the
server" (or the reverse) don't work in environments like Jane Street,
where the same editor plugin must be able to interact seamlessly with
multiple iterations of Dune (which may be older or newer than the
editor plugin itself).
The main goal of the system, then, is to ensure that both the server
and client applications can be programmed against the current model
types for each procedure, with all backwards- or forwards- conversions
happening under the hood.
## Protocol
At session initialization time, the client will first send an
initialization request to the server containing a single version
number corresponding to the overall RPC version the client will use.
If this number is determined to be versioning-compatible (see
[Session versioning](#session-versioning)), the server will respond
with a token instructing the client to initiate version negotiation.
Otherwise, the server will respond with an error.
Upon receiving this token, the client will initiate version
negotiation by sending a list of `(method-name, generations)`
pairs, where `method-name` is the name of each declared procedure, and
`generations` is the list of version numbers for that procedure's
generations.
Upon receiving a list of supported versions from the client, the
server will compare it to its list of *implemented* versions,
selecting the greatest common generation for each procedure. If the
client and server do not share any generations for a procedure, it is
omitted entirely. If there is at least one method for which a common
version exists, then the server responds with a list of `(method-name,
selected-version)` pairs, where `selected-version` is the version
number of the greatest common generation. This list is then used by
both parties to construct the version menu. Otherwise, if there are no
common versions for any methods, an error is returned to the client
and the session is invalidated.
Note that we do not currently require declared/implemented versions to
span a contiguous range of version numbers. This can have a few uses,
such as preventing clients from using a known-bugged generation of
a procedure.
When executing a procedure, the sender first looks up the correct
generation in the menu (see [Error handling](#error-handling)), and
downgrades the payload from the sender-side model type to the wire
type. Upon receipt, the server performs the same lookup to deserialize,
then upgrade the payload to the receiver-side model type, then the
procedure implementation is performed, producing a response in the
case of requests. If necessary, the same transformations are then
performed in reverse, sending the value back to the sender, completing
the procedure.
Barring strange circumstances (such as a client declaring a generation
with a newer version number than the type exposed in `dune_rpc.mli`), it
is always the case that the transformation from wire to model types will
be the identity function on the side that is older.
## Miscellaneous implementation notes
### Session versioning
In addition to version numbers existing for each procedure version,
there are two further version numbers associated with the session as
a whole which are sent as part of session initialization.
The first is the version of Dune each side purports to be as
a `MAJOR.MINOR` number (serialized as an `int * int` pair). This is
not currently checked.
Next is a version of the initial handshake protocol to be used. This
takes the form of a single `int`. In the future, if the initial
negotiation protocol changes, this value can be adjusted and checked to
account for this.
### Error handling
Handling of versioning errors has become more complex, as we need to
distinguish between "no such method exists" and "the server and client
do not share any common generations for this method". Secondly, this
means that the initiation of a procedure can now fail, which
complicates one-way communications (for example, the server must
swallow errors and clients must be upgraded to handle version errors
on notifications, which were previously infallible).
Finally, the versioning protocol itself must be either versioned
separately or stabilised (see [Session versioning](#session-versioning)).
### Tweaks
- We currently send the entire version menu from client to server and
back twice, once for the client to inform the server of all
supported versions, and again for the server to inform the client
of the common versions. This can lead to large messages being
passed at session initialization, which may become a performance
bottleneck.
- The size of the version negotiation messages is proportional to
the number of all known generations for all procedures, which
can be approximated by `number-of-procedures` times
`number-of-supported-generations`. In practice, I do not
expect this number to be large (I would be surprised if this
number is ever on the order of 100).
- One alternative is to perform per-procedure negotiation, where
the initiator of a procedure first sends its known version
ranges, the recipient sends the selected version (or an
error), then the procedure proceeds as before. This approach
trades startup and lookup overhead for a constant
per-communication overhead. It also makes distinguishing "no
such method exists" and "no common versions" simpler.

View file

@ -0,0 +1,176 @@
# Rule production
This document describes how rule production works in Dune. It was originally
written by Jérémie Dimino as part of the
[streaming RFC](https://github.com/ocaml/dune/pull/5251), but moved
into the dev documentation as it provides a great overview on how this part of
Dune works at present.
## How does rule production works?
### `Dune_engine.Load_rules`
The production of rules is driven by the module `Load_rules` in the
`dune_engine` library. This library is the build system core of
Dune. It is meant as a general purpose library for writing build
systems, and the Dune software is built on top of it. In theory,
`dune_engine` shouldn't know about `dune` or `dune-project`
files. However, for historical reason this is not the case yet and
`dune_engine` still knows some things about them.
As we work on Dune, we expect that `dune_engine` will become more and
more agnostic. Even though it is not completely agnostic, we have
successfully been using it to build Jane Street code base, using the
Jane Street rules on top of this core. So it's already more general
than Dune itself.
For the purpose of this design doc, we will treat `dune_engine` as a
completely general library that doesn't know about `dune` files.
The main feature of `Load_rules` is the `Load_rules.load_dir` function:
```ocaml
val load_dir : dir:Path.t -> Loaded.t Memo.t
```
`Loaded.t` represents a "loaded" set of rules for a particular
directory. It can also be thought as a "compiled" set of rules. A
`Loaded.t` contains all the rules in existence that produce targets in
`dir`. For instance, given a `Loaded.t` we can figure out all the
files that would be produced in `dir` if we were building everything
that could be built. While `dir` is a build directory, this also
includes files present in the source tree. This is because
`Load_rules.load_dir` implicitly adds copy rules for all source files
present in the source directory that correspond to the build directory
`dir`. For instance, if `dir` is `_build/default/src`, Dune will
implicitly add rules to copy files in `src` to `_build/default/src`.
Except for rules that have the special `promote` or `fallback` modes.
This is in fact how Dune evaluates globs during the build. Indeed,
when writing `dune` files we work in an imaginary world where both the
source files and the generated files are present. So when we write
`(deps (glob_files *.txt))`, this `*.txt` denotes both `.txt` files
that are present on disk in the source tree but also as the ones that
can be generated by the build.
In practice, to evaluate `(glob_files *.txt)` in directory `d`, Dune
calls `Load_rules.load_dir ~dir:d` and filter the list of files that can
be built. Similarly, when Dune needs to build a file
`_build/default/src/x`, it first calls `Load_rules.load_dir` with
`_build/default/src` and then looks up a rule that has `x` has
target in the returned `Loaded.t`. The `Load_rules.load_dir` is
memoised, so it can be called multiple times during the build without
guilt.
While `Load_rules` is responsible for driving the production of rules,
it is part of `dune_engine` which doesn't know about `dune` files and
doesn't know about OCaml libraries or OCaml compilation in general. So
it is not responsible for actually producing the build rules that
allow to build Dune projects. Instead, `Load_rules` defers the actual
production of rules to a callback that it obtains via
`Build_config`. Inside Dune, this callback is implemented by the
`Gen_rules` module inside the `dune_rules` library. `dune_rules` is
the library that is responsible for parsing, interpreting and
compiling `dune` files down to low-level build rules.
### `Dune_rules.Gen_rules`
The entry of `Dune_rules.Gen_rules` is the `gen_rules` function. Its
API looks like:
```ocaml
val gen_rules :
Build_config.Context_or_install.t ->
dir:Path.Build.t ->
string list ->
Build_config.gen_rules_result Memo.t
```
Where `Build_config.gen_rules_result` is, in most cases —when the value
returned is `Build_config.Rules _`—, a "raw" set of rules. Raw in the sense
that there is no overlap checks or any other checks. During the rule
production phase, we merely accumulate a set of rules that is later
processed. The API of `gen_rules` is in fact a bit more complex, but
the above definition is enough for the purpose of this document.
The first thing `gen_rules` does is analyse the directory it is
given. If the directory corresponds to a source directory with a `dune`
file, `gen_rules` will dispatch the call to the part of `dune_rules`
that parses and interprets the `dune` file. This is the simplest case,
but even in this case there are some things worth mentioning.
For instance, when compiling an OCaml library dune stores the
artifacts for the library in generated dot-directories. For instance,
the cmi files for library `foo` living in source directory `src` will
end up in `_build/default/src/.foo.objs/byte`. We could produce these
rules when `gen_rules` is called with directory
`_build/default/src/.foo.objs/byte`, however that would spread out the
logic for interpreting `library` stanzas. It is much simpler to
produce all the build rules corresponding to a `library` stanza in one
go. This is what is happening at the moment: when called with
directory `_build/default/src`, `gen_rules` will not only produce
rules for this directory but will also produce rules for
`_build/default/src/.foo.objs/byte` and various other directories.
`Load_rules` doesn't know anything about this. And in particular, it
doesn't know that it is the `gen_rules` call for directory
`_build/default/src/` that will produce the rules for the dot
subdirectories. When `Load_rules` loads the rules for the
`.../.foo.objs/byte` sub-directory, it simply calls `gen_rules` with
this directory. It is `gen_rules` that "redirects" the call to the
`_build/default/src` directory by calling
`Load_rules.load_dir_and_produce_its_rules`. This function simply
calls `Load_rules.load_dir` and re-emits all the raw rules that were
returned by the corresponding `gen_rules` call.
This works because `Load_rules.load_dir` accepts the facts that
`gen_rules` produces rules for many directory at once. It simply
filters out the result. But for things to behave well, the unwritten
following invariant must hold: `gen_rules ~dir:d` is allowed to
generate rules for directory `d'` iff `gen_rules ~dir:d'` emits a call
to `Load_rules.load_dir ~dir:d`.
This scenario happens in a number of cases. All these cases share a
common pattern: the redirections are always to an ancestor
directory. At the moment, there is one exception to this pattern in
the odoc rules, however it is easy to remove.
Finally, the `copy_files` stanza creates another form of dependency
between directory. In order to calculate the targets produced by
`copy_files`, which needs to be known at rule production time, we need
to evaluate the glob given to `copy_files`. Which requires doing a
call to `Load_rules.load_dir` as previously described. Contrary to the
other form of dependency we just describe, this ones can go from any
directory to any other directory. For instance, the following stanza
in `src/dune`:
```
(copy_files foo/*.txt)
```
would create a dependency from `_build/default/src` to
`_build_default/src/foo`.
So in the end, if we were looking at the internal computation graph of
Dune and narrowing it to just the calls to `Load_rules.load_dir`, we
would see a graph with many edges going from a directory to one of its
ancestor. These would mostly be between generated dot-subdirectories
and their first ancestor that has a corresponding directory in the
source tree. Plus a few other arbitrary ones for each `copy_files`
stanza.
## Directory targets
Before directory targets, answering the question "what rules produces
file X?" was easy. Dune would just call `Load_rules.load_dir` and
lookup `X` in the result. With directory targets, things are a bit
more complicated. Indeed, `X` might also be produced by a directory
target in an ancestor directory. This means that `Load_rules.load_dir`
now need to look in parent directories as well, which introduce more
dependencies from directories to their parents and can create cycles
because of `copy_files` stanza that create dependencies in the other
direction.
At a result, some combinations of `copy_files` and directory targets
don't produce the expected result. This is documented in the test
suite.

View file

@ -0,0 +1,82 @@
# Rule streaming
This document describes a new design for the production of build rules
in Dune. The new design aims to be more natural, easier to reason
about and to make existing features work well with newer ones such as
directory targets.
It was originally written by Jérémie Dimino as part of the
[streaming RFC](https://github.com/ocaml/dune/pull/5251), and later on moved
into the dev documentation.
## Problem
The [rule production](./rule-production.md) document exposes a concrete problem
with directory targets, but there is also a general sense of messiness in the
way things work. Generating rules for multiple directories at once is
natural, but the current encoding is odd.
## Proposal
The proposal is to add the following rule: `gen_rules ~dir` is allowed
to produced rules in `dir` or any of its descendant only. It is not
allowed to produce rules anywhere else.
`Load_rules.load_dir ~dir` will then always call itself recursively on
the parent of `dir` and take the union of the rules produced by
`gen_rules` for `dir` and the ones produced by the recursive
call. `gen_rules` will no longer have to redirect a call via
`Load_rules.load_dir_and_produce_its_rules`, which we would simply
remove.
This introduces a cycle with all `copy_files` stanza that copy files
from a sub-directory. We propose the break this cycle by introducing
laziness in the rule production code.
### Generating rules with a mask
The idea is that when we produce rules, we will produce rules under
a current active "mask" that tells us where we are allowed to generate
files or directories. Trying to produce a rule with targets not
matched by this mask will be a runtime error.
When entering `gen_rules ~dir`, the initial mask will be: "any files
and directories that is a descendant of directory `dir`".
We can then narrow the mask to a sub-mask:
```ocaml
val narrow : Target_mask.t -> unit Memo.t -> unit Memo.t
```
With `narrow mask m`, `m` would only be allowed to produce rules whose
target are matched by the intersection of `mask` and the current
mask. `m` wouldn't be evaluated eagerly. Instead, `gen_rules` would
now return a set of direct rules as well as a list of
`(Target_mask.t * unit Memo.t)`. Let's call such a pair a
suspension. A suspension can be forced by evaluation its second
component. Doing so will yield a list of rules matched by the mask and
a new list of suspension.
### Staged rules loading
The next step is to stage `Load_rules.load_dir`. In addition to taking
a directory, `load_dir` will now also take a mask and will return the
set of rules for this mask. To do that, it might need to force a bunch of
suspensions recursively.
### How does that help?
We will put `copy_rules` under a `narrow <only file targets in current
dir>`. In order to determine if a directory is part of a directory
target in an ancestor directory, we wouldn't need to force this
suspension.
### Difficulties
Interpreting a `library` stanza requires knowing the set of `.ml`
files in the current directory. Knowing this requires interpreting
`copy_files` in the current directory. So the interpretation of
`library` stanzas will need to go under a `narrow` as well.

View file

@ -0,0 +1,157 @@
.. _documentation:
************************
Generating Documentation
************************
.. TODO(diataxis)
Split between:
- A "generating API documentation" how-to guide
- Some reference documentation
Prerequisites
=============
Documentation in Dune is done courtesy of the odoc_ tool. Therefore, to
generate documentation in Dune, you will need to install this tool. This
should be done with opam:
::
$ opam install odoc
Writing Documentation
=====================
Documentation comments will be automatically extracted from your OCaml source
files following the syntax described in the section ``Text formatting`` of
the `OCaml manual <http://caml.inria.fr/pub/docs/manual-ocaml/ocamldoc.html>`_.
Additional documentation pages may be attached to a package using the
:doc:`/reference/dune/documentation` stanza.
Building Documentation
======================
To generate documentation using the :doc:`/reference/aliases/doc` alias, all
that's required to is to build this alias:
.. code:: console
$ dune build @doc
An index page containing links to all the opam packages in your project can be
found in:
.. code:: console
$ open _build/default/_doc/_html/index.html
Documentation for private libraries may also be built with
:doc:`/reference/aliases/doc-private`:
.. code:: console
$ dune build @doc-private
But these libraries will not be in the main HTML listing above, since they
don't belong to any particular package, but the generated HTML will still be
found in ``_build/default/_doc/_html/<library>``.
Documentation Stanza: Examples
------------------------------
The :doc:`/reference/dune/documentation` stanza will attach all the
``.mld`` files in the current directory in a project with a single package.
.. code-block:: dune
(documentation)
This stanza will attach three ``.mld`` files to package ``foo``. The ``.mld`` files should
be named ``foo.mld``, ``bar.mld``, and ``baz.mld``
.. code-block:: dune
(documentation
(package foo)
(mld_files foo bar baz))
This stanza will attach all ``.mld`` files to the inferred package,
excluding ``wip.mld``, in the current directory:
.. code-block:: dune
(documentation
(mld_files :standard \ wip))
All ``.mld`` files attached to a package will be included in the generated
``.install`` file for that package. They'll be installed by opam.
.. code-block:: dune
(documentation
(files
(glob_files_rec
(doc/* with_prefix .))))
All files in the ``doc/`` folder will be attached to the inferred package. The
hierarchy between them will be preserved, relative to ``doc/`` considered as the
root.
.. note::
``dune`` does not yet support building the documentation with a non-flat
hierarchy, or with non-mld files. However, it supports installing those files
following a convention, so that ``odoc_driver`` can build the docs with
hierarchy and asset files.
Package Entry Page
------------------
The ``index.mld`` file (specified as ``index`` in ``mld_files``) is treated
specially by Dune. This will be the file used to generate the entry page for
the package, linked from the main package listing.
To generate pleasant documentation, we recommend writing an ``index.mld`` file
with at least short description of your package and possibly some examples.
If you do not write your own ``index.mld`` file, Dune will generate one with
the entry modules for your package. But this generated file will not be
installed.
.. _odoc-options:
Passing Options to ``odoc``
===========================
.. code-block:: dune
(env
(<profile>
(odoc <optional-fields>)))
See :doc:`/reference/dune/env` for more details on the ``(env ...)``
stanza. ``<optional-fields>`` are:
- ``(warnings <mode>)`` specifies how warnings should be handled. ``<mode>``
can be: ``fatal`` or ``nonfatal``. The default value is ``nonfatal``. This
field is available since Dune 2.4.0 and requires odoc_ 1.5.0.
.. _odoc: https://github.com/ocaml-doc/odoc
Local Documentation Search Using Sherlodoc
==========================================
If Sherlodoc is installed, generated HTML documentation will include a
search bar. It supports search by name, documentation and fuzzy type search.
In can be installed with:
.. code:: console
$ opam install sherlodoc

View file

@ -0,0 +1,36 @@
(rule
(with-stdout-to
dune.1
(run %{bin:dune} --help=groff)))
(install
(section man)
(package dune)
(files dune.1))
(rule
(with-stdout-to
dune-config.5
(run %{bin:dune} help config --man-format=groff)))
(install
(section man)
(package dune)
(files dune-config.5))
(include dune.inc)
(rule
(alias runtest)
(mode promote)
(deps
(package dune))
(action
(with-stdout-to
dune.inc
(run bash %{dep:update-jbuild.sh}))))
(documentation
(package dune))
(data_only_dirs tutorials)

View file

@ -0,0 +1,193 @@
**************
Dune Libraries
**************
.. TODO(diataxis) Move into :doc:`reference/dune-libs`
.. _configurator:
Configurator
============
Configurator is a small library designed to query features available on the
system in order to generate configuration for Dune builds. Such generated
configuration is usually in the form of command line flags, generated headers,
and stubs, but there are no limitations on this.
Configurator allows you to query for the following features:
* Variables defined in ``ocamlc -config``,
* pkg-config_ flags for packages,
* Test features by compiling C code,
* Extract compile time information such as ``#define`` variables.
Configurator is designed to be cross-compilation friendly and avoids *running*
any compiled code to extract any of the information above.
Configurator started as an `independent library
<https://github.com/janestreet/configurator>`__, but now lives in dune. It is
released as the package ``dune-configurator``.
Usage
-----
We'll describe configurator with a simple example. Everything else can be
easily learned by studying `configurator's API
<https://github.com/ocaml/dune/blob/master/otherlibs/configurator/src/v1.mli>`__.
To use Configurator, write an executable that will query the system using
Configurator's API and output a set of targets reflecting the results. For
example:
.. code-block:: ocaml
module C = Configurator.V1
let clock_gettime_code = {|
#include <time.h>
int main(void)
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return 0;
}
|}
let () =
C.main ~name:"foo" (fun c ->
let has_clock_gettime =
C.c_test c clock_gettime_code ~link_flags:["-lrt"] in
C.C_define.gen_header_file c ~fname:"config.h"
[ "HAS_CLOCK_GETTIME", Switch has_clock_gettime ]);
Usually, the module above would be named ``discover.ml``. The next step is to
invoke it as an executable and tell Dune about the targets that it produces:
.. code-block:: dune
(executable
(name discover)
(libraries dune-configurator))
(rule
(targets config.h)
(action (run ./discover.exe)))
Another common pattern is to produce a flags file with Configurator and then
use this flag file using ``:include``:
.. code-block:: dune
(library
(name mylib)
(foreign_stubs (language c) (names foo))
(c_library_flags (:include (flags.sexp))))
For this, generate the list of flags for your library (for example, using
``Configurator.V1.Pkg_config``), and then write them to a file: in the above
example, ``flags.sexp`` with ``Configurator.V1.write_flags "flags.sexp"
flags``.
Upgrading From the Old Configurator
-----------------------------------
The old Configurator is the independent `Configurator
<https://github.com/janestreet/configurator>`__ opam package. It's now
deprecated, and users are encouraged to migrate to Dune's own Configurator. The
advantage of the transition include:
* No extra dependencies,
* No need to manually pass ``-ocamlc`` flag,
* New Configurator is cross-compilation compatible.
The following steps must be taken to transition from the old Configurator:
* Mentions of the ``configurator`` opam package should be replaced
with ``dune-configurator``.
* The library name ``configurator`` should be changed ``dune-configurator``.
* The ``-ocamlc`` flag in rules that runs Configurator scripts should be removed.
This information is now passed automatically by Dune.
* The new Configurator API is versioned explicitly. The version that's
compatible with old Configurator is under the ``V1`` module. Hence, to
transition one's code, it's enough to add this module alias:
.. code-block:: ocaml
module Configurator = Configurator.V1
.. _pkg-config: https://www.freedesktop.org/wiki/Software/pkg-config/
.. _build-info:
`dune-build-info` Library
=========================
Dune can embed build information such as versions in executables
via the special ``dune-build-info`` library. This library exposes
some information about how the executable was built, such as the
version of the project containing the executable or the list of
statically linked libraries with their versions. Printing the version
at which the current executable was built is as simple as:
.. code:: ocaml
Printf.printf "version: %s\n"
(match Build_info.V1.version () with
| None -> "n/a"
| Some v -> Build_info.V1.Version.to_string v)
You can specify the project version using the ``version`` field in the
``dune-project`` file. For example:
.. code:: dune
(version 1.2.3)
For more details, refer to :doc:`/reference/dune-project/version`.
For libraries and executables from development repositories that don't
have version information written directly in the ``dune-project``
file, the version is obtained by querying the version control
system. For instance, the following Git command is used in Git
repositories:
.. code:: console
$ git describe --always --dirty --abbrev=7
which produces a human readable version string of the form
``<version>-<commits-since-version>-<hash>[-dirty]``.
Note that in the case where the version string is obtained from the version
control system, the version string will only be written in the binary once it's
installed or promoted to the source tree. In particular, if you evaluate this
expression as part of your package build, it will return ``None``. This ensures
that committing doesn't hurt your development experience. Indeed, if Dune
stored the version directly inside the freshly built binaries, then every time
you commit your code, the version would change and Dune would need to rebuild
all the binaries and everything that depends on them, such as tests. Instead,
Dune leaves a placeholder inside the binary and fills it during installation or
promotion.
.. _dune-action-plugin:
(Experimental) Dune Action Plugin
=================================
*This library is experimental and no backwards compatibility is implied. Use at
your own risk.*
``Dune-action-plugin`` provides a monadic interface to express program
dependencies directly inside the source code. Programs using this feature
should be declared using :doc:`/reference/actions/dynamic-run` instead of usual
:doc:`/reference/actions/run`.

View file

@ -0,0 +1,316 @@
(rule
(with-stdout-to dune-printenv.1
(run dune printenv --help=groff)))
(install
(section man)
(package dune)
(files dune-printenv.1))
(rule
(with-stdout-to dune-promote.1
(run dune promote --help=groff)))
(install
(section man)
(package dune)
(files dune-promote.1))
(rule
(with-stdout-to dune-test.1
(run dune test --help=groff)))
(install
(section man)
(package dune)
(files dune-test.1))
(rule
(with-stdout-to dune-build.1
(run dune build --help=groff)))
(install
(section man)
(package dune)
(files dune-build.1))
(rule
(with-stdout-to dune-cache.1
(run dune cache --help=groff)))
(install
(section man)
(package dune)
(files dune-cache.1))
(rule
(with-stdout-to dune-clean.1
(run dune clean --help=groff)))
(install
(section man)
(package dune)
(files dune-clean.1))
(rule
(with-stdout-to dune-coq.1
(run dune coq --help=groff)))
(install
(section man)
(package dune)
(files dune-coq.1))
(rule
(with-stdout-to dune-describe.1
(run dune describe --help=groff)))
(install
(section man)
(package dune)
(files dune-describe.1))
(rule
(with-stdout-to dune-diagnostics.1
(run dune diagnostics --help=groff)))
(install
(section man)
(package dune)
(files dune-diagnostics.1))
(rule
(with-stdout-to dune-exec.1
(run dune exec --help=groff)))
(install
(section man)
(package dune)
(files dune-exec.1))
(rule
(with-stdout-to dune-external-lib-deps.1
(run dune external-lib-deps --help=groff)))
(install
(section man)
(package dune)
(files dune-external-lib-deps.1))
(rule
(with-stdout-to dune-fmt.1
(run dune fmt --help=groff)))
(install
(section man)
(package dune)
(files dune-fmt.1))
(rule
(with-stdout-to dune-format-dune-file.1
(run dune format-dune-file --help=groff)))
(install
(section man)
(package dune)
(files dune-format-dune-file.1))
(rule
(with-stdout-to dune-help.1
(run dune help --help=groff)))
(install
(section man)
(package dune)
(files dune-help.1))
(rule
(with-stdout-to dune-init.1
(run dune init --help=groff)))
(install
(section man)
(package dune)
(files dune-init.1))
(rule
(with-stdout-to dune-install.1
(run dune install --help=groff)))
(install
(section man)
(package dune)
(files dune-install.1))
(rule
(with-stdout-to dune-installed-libraries.1
(run dune installed-libraries --help=groff)))
(install
(section man)
(package dune)
(files dune-installed-libraries.1))
(rule
(with-stdout-to dune-internal.1
(run dune internal --help=groff)))
(install
(section man)
(package dune)
(files dune-internal.1))
(rule
(with-stdout-to dune-monitor.1
(run dune monitor --help=groff)))
(install
(section man)
(package dune)
(files dune-monitor.1))
(rule
(with-stdout-to dune-ocaml.1
(run dune ocaml --help=groff)))
(install
(section man)
(package dune)
(files dune-ocaml.1))
(rule
(with-stdout-to dune-ocaml-merlin.1
(run dune ocaml-merlin --help=groff)))
(install
(section man)
(package dune)
(files dune-ocaml-merlin.1))
(rule
(with-stdout-to dune-package.1
(run dune package --help=groff)))
(install
(section man)
(package dune)
(files dune-package.1))
(rule
(with-stdout-to dune-pkg.1
(run dune pkg --help=groff)))
(install
(section man)
(package dune)
(files dune-pkg.1))
(rule
(with-stdout-to dune-promotion.1
(run dune promotion --help=groff)))
(install
(section man)
(package dune)
(files dune-promotion.1))
(rule
(with-stdout-to dune-rpc.1
(run dune rpc --help=groff)))
(install
(section man)
(package dune)
(files dune-rpc.1))
(rule
(with-stdout-to dune-rules.1
(run dune rules --help=groff)))
(install
(section man)
(package dune)
(files dune-rules.1))
(rule
(with-stdout-to dune-runtest.1
(run dune runtest --help=groff)))
(install
(section man)
(package dune)
(files dune-runtest.1))
(rule
(with-stdout-to dune-show.1
(run dune show --help=groff)))
(install
(section man)
(package dune)
(files dune-show.1))
(rule
(with-stdout-to dune-shutdown.1
(run dune shutdown --help=groff)))
(install
(section man)
(package dune)
(files dune-shutdown.1))
(rule
(with-stdout-to dune-subst.1
(run dune subst --help=groff)))
(install
(section man)
(package dune)
(files dune-subst.1))
(rule
(with-stdout-to dune-tools.1
(run dune tools --help=groff)))
(install
(section man)
(package dune)
(files dune-tools.1))
(rule
(with-stdout-to dune-top.1
(run dune top --help=groff)))
(install
(section man)
(package dune)
(files dune-top.1))
(rule
(with-stdout-to dune-uninstall.1
(run dune uninstall --help=groff)))
(install
(section man)
(package dune)
(files dune-uninstall.1))
(rule
(with-stdout-to dune-upgrade.1
(run dune upgrade --help=groff)))
(install
(section man)
(package dune)
(files dune-upgrade.1))
(rule
(with-stdout-to dune-utop.1
(run dune utop --help=groff)))
(install
(section man)
(package dune)
(files dune-utop.1))

View file

@ -0,0 +1,53 @@
How Dune Uses Dune to Build Dune
================================
Dune's build system is itself Dune. This works thanks to a bootstrap process.
This document explains how this works.
``boot/bootstrap.ml``
---------------------
``boot/bootstrap.ml`` is an OCaml script (it is interpreted, not compiled) that
is a mini-build system tailored to Dune itself. It computes dependencies
between the various modules by calling ``ocamldep``, and it will generate build
and link commands. It knows how to execute these commands in parallel. It does
not read any ``dune`` file. However, the project structure and its system
dependencies are encoded in ``boot/libs.ml``.
This step produces ``_boot/dune.exe``.
Completing the Opam Installation
--------------------------------
``_boot/dune.exe`` is the bootstrap Dune. Since it has been built from
the Dune sources, it will act like Dune: it can read ``dune`` files, etc.
This is actually the ``dune`` executable that will get installed. This is the
same executable as the one obtained by running ``opam install dune``. At this
stage of the process, Opam does not know about this: it expects a
``dune.install`` file that explains what files to install.
The next command run by the Opam instruction is the following:
.. code:: console
$ ./_boot/dune.exe build dune.install --release --profile dune-bootstrap
By using the ``dune-bootstrap`` :term:`build profile`, it does not run a full
build, but only copy ``_boot/dune.exe`` to its install location (as the `dune`
binary), and generate ``dune.install``.
``make dev``: Everything Else for Local Development
---------------------------------------------------
The above describes how Dune itself is built through Opam, but that's not all
there is it to it: the Dune repository contains other libraries that need to be
built. In fact, executing the ``boot/bootstrap.ml`` script did not generate
files useful for editor integration.
So the main ``Makefile`` has a ``make dev`` target that will run
``_boot/dune.exe build @install``: this will rebuild the project using Dune
itself.
As a special rule, this build will regenerate ``boot/libs.ml`` using the
locations of the internal libraries used to build Dune.

View file

@ -0,0 +1,17 @@
Explanation
===========
These documents explain how certain feature works, or how Dune integrates with
the rest of the OCaml ecosystem.
.. toctree::
:maxdepth: 1
scopes
preprocessing
ocaml-ecosystem
package-management
opam-integration
bootstrap
mental-model
tour/index

View file

@ -0,0 +1,202 @@
The Dune Mental Model
=====================
It is not strictly necessary to understand Dune's underlying model to use it;
but knowing how it works under the hood will help writing build rules, and also
help understand some errors and what's possible with Dune.
.. note::
This document is a simplification of the reality: the actual rules might be
different, it does not touch rule loading and glosses over how caching
works, but should be a useful tool to build an understanding of Dune.
How Dune Works
--------------
The building block of Dune is the *rule*:
A *rule* reads *dependencies* and writes *targets* using an *action* (and
it can be attached to *aliases*).
When ``dune build`` is executed, it will first read the project's ``dune``
files to determine the rules that apply to the project. Once it has done this,
it will determine what actions it needs to execute to build the required
targets.
An Example
----------
Let's take the following example.
- there's a CLI tool written in OCaml.
- it has some build-time configuration stored in ``config.json``.
- it has an integration test, in which the tool is executed with
``testdata.txt`` as input.
Configuration Generation
^^^^^^^^^^^^^^^^^^^^^^^^
To express the generation of the configuration module we could write:
.. code:: dune
(rule
(deps convert/json2ml.exe config.json)
(target config.ml)
(action
(run convert/json2ml.exe config.json -o config.ml)))
This rule will:
- read its dependencies: ``convert/json2ml.exe`` and ``config.json``
- and write its target: ``config.ml``
- using an action: ``(run convert/json2ml.exe config.json -o config.ml)``
This rule is very explicit: we write a stanza for a single Dune rule.
Building the Executable
^^^^^^^^^^^^^^^^^^^^^^^
In contrast, to describe the compilation of the executable, we would write:
.. code:: dune
(executable
(name tool)
(modules main config))
Here, we use Dune's abstractions. Dune knows about the OCaml compilation model:
the modules need to be compiled and linked together. So it will generate the
following rules under the hood:
- one rule to compile the ``Main`` module:
- it will read its dependency: ``main.ml``
- and write its output: ``main.cmx``
- using an action: ``(run ocamlopt -c main.ml)``
- one rule to compile the ``Config`` module:
- it will read its dependency: ``config.ml``
- and write its output: ``config.cmx``
- using an action: ``(run ocamlopt -c config.ml)``
- one rule to link the ``tool.exe`` executable:
- it will read its dependencies: ``main.cmx`` and ``config.cmx``
- and write its output: ``tool.exe``
- using an action: ``(run ocamlopt -o tool.exe main.cmx config.cmx``)
Note that in this example, some files are targets of a rule and dependencies of
another (``.cmx`` files). We are unlikely to ever interact with them directly,
so it can also be useful to think of the ``(executable)`` stanza as a group of
rules with ``main.ml`` and ``config.ml`` as inputs and ``tool.exe`` as output.
Running the Tests
^^^^^^^^^^^^^^^^^
Some rules do not produce any output file, but we're still interested in
running their actions. A test is a good example: we want the build process to
exit with an error code if the action fails. In that case, the rule does not
have targets, but we "attach" it to an :term:`alias`, ``runtest`` in this case.
This gives us a way of requesting this rule to be executed. As we are about to
see, rules are executed lazily by asking for their targets to be built, so we
would not be able to execute such rules.
.. code:: dune
(rule
(deps tool.exe testdata.txt)
(alias runtest)
(action
(run tool.exe testdata.txt)))
This rule:
- reads its dependencies: ``tool.exe`` and ``testdata.txt``
- writes no targets
- using an action: ``(run tool.exe testdata.txt)``
- (and it is attached to ``runtest``)
What to Build
-------------
Dune can build *files* and *aliases*. These can be found on the command line:
- ``dune build tool.exe`` will build the ``tool.exe`` file.
- ``dune build @example`` will build the ``example`` alias.
- ``dune build tool.exe @example`` will build both the file ``tool.exe`` and
the ``example`` alias.
- ``dune runtest`` is a shortcut for ``dune build @runtest``: it will build the
``runtest`` alias. Passing a directory will build all tests in that directory.
Passing the path to a cram test will run that test individually.
- ``dune build`` is a shortcut for ``dune build @@default``: it will build the
default alias in the current directory (by default the ``all`` alias).
In other words, each ``dune build`` or ``dune runtest`` command always
corresponds to a list of files and aliases to build.
.. seealso:: :doc:`Reference information on aliases</reference/aliases>`
How Dune Interprets Rules
-------------------------
We have now seen that Dune sets up rules for a project, and that every build
command has a list of files and aliases that we are asking to build.
Now let's see how this request is processed:
- to build a file, Dune will first check if it is in the source tree. In that
case, there is nothing to do. Otherwise, it will check if it is the
target of a rule. In that case, it will execute this rule. (Dune will raise
an error in other cases: if the file is both in the source tree and the
target of a rule, or if it is neither)
- to build an alias, Dune will execute all the rules that are attached to this
alias.
- to execute a rule, Dune will first build all the dependencies (files or
aliases) of this rule. Then it will execute the action attached to the rule.
When Dune is about to execute an action, it checks (in various caches) if it
executed it before on the same set of dependencies, and, if yes, it can skip
executing it and reuse the previous result.
In the case of our example, if we call ``dune runtest``, Dune will consider all
rules attached to the ``runtest`` alias. In this case it is just the
integration test rule. It needs to build its dependencies, ``tool.exe`` and
``testdata.txt``. The latter is present in the source tree.
However, ``tool.exe`` is the target of the linking rule defined by the
``(executable)`` stanza. This rule requires ``main.cmx`` and ``config.cmx``.
``main.cmx`` is the target of the compilation rule for the ``Main`` module,
which depends on ``main.ml``. This file is in the source tree, so let's copy it
under ``_build``. This rule has all its dependencies available, so we can run
its action, which writes ``main.cmx``. Getting back to the dependencies of
``tool.exe``, ``config.cmx`` is the target of the linking rule of the
``Config`` module. This rule has ``config.ml`` has a dependency. This file is
itself the target of the configuration module rule, which lists ``config.json``
and ``convert/json2ml.exe``. The first is available in the source tree and to
simplify, let's assume that the second one has been built. This action has all
its dependencies available, so we can execute its action to produce its target,
``config.ml``. Now the module compilation rule for ``Config`` can be executed,
producing ``config.cmx``; and in turn the linking rule can be executed,
producing ``tool.exe``. Finally, ``tool.exe`` can be executed with
``testdata.txt`` as its argument.
In a nutshell: we recursively copied all the dependencies of the test rule, and
executed the rules in the correct order.
This is a "cold build", where there were no previous build artifacts. Note that
if we change only part of the project (say the ``main.ml`` file), only a small
number of rules will be evaluated, the ones that depend on ``main.ml``.
Conclusion
----------
Dune's underlying model is based on rules. Stanzas are high-level constructs
that can generate multiple rules, that are not always visible.
To build a target, Dune looks for the rule that produces that target and makes
its way back to source files.
Rules define a directed acyclic graph which models dependency relations between
files. Most of the rules in that graph may be executed for a cold build, but
just the minimum will be executed for an incremental build.

View file

@ -0,0 +1,94 @@
The OCaml Ecosystem
===================
The OCaml ecosystem is not monolithic: the compiler and tools are not
maintained by the same entities. As such, it can be difficult to understand the
history and roles of the various pieces of this ecosystem. The goal of this
page is to give a quick overview of the situation and the role that Dune
plays in it.
The OCaml Compiler Distribution: Compiling and Linking
------------------------------------------------------
The `OCaml compiler distribution <https://github.com/ocaml/ocaml>`_ contains
"core" tools including the compilers (``ocamlc`` and ``ocamlopt``). They turn
source files (with extensions ``.ml`` and ``.mli``) into executables and
libraries. Dependencies between compiled objects only exist at the module
level, so this is a low-level tool.
Findlib: Metadata for Libraries
-------------------------------
Findlib_ is a tool that defines the concept of library, so that libraries can
depend on other libraries on top of the notion of module. Definitions of
libraries, and other pieces of metadata, are stored in ``META`` files.
Findlib ships an executable named ``ocamlfind`` that can be used as a wrapper
on top of the compilers to perform tasks such as producing an executable from
compiled object files and external libraries.
.. _findlib: https://github.com/ocaml/ocamlfind
Opam: a Collection of Software Projects
---------------------------------------
Opam is a package manager. It is used to determine which packages are
necessary, and how to fetch and build them. Packages can contain libraries,
executables, and other kinds of files.
The notion of version is specific to opam. If your project uses a function
named ``Png.read_file`` but this function has been added only in version
``1.2.0`` of that package, opam needs to know about it.
Opam manages collections of installed packages, called switches. Using your
project's dependencies (names and version constraints), it is able to create a
switch that you'll be using to develop your project.
Public definitions of packages are available in a database called
``opam-repository`` which is maintained as a public Git repository. Publishing a
package on opam (to make sure that external users can use your project)
consists in adding its definition to ``opam-repository``.
Dune: Giving Structure to Your Source Tree
------------------------------------------
Dune is a build system. It is used to orchestrate the compilation of source
files into executables and libraries.
Assuming you have a development switch set up, you communicate to Dune about how your
project is organized in terms of executables, libraries, and tests. It is then able to assemble the source files of your projects, with the dependencies installed in an opam switch, to create compiled assets for your project.
How Dune Integrates With the Ecosystem
--------------------------------------
Dune is designed to integrate with the tools mentioned above:
- By knowing how the OCaml compilers operate, it knows which build commands should be
re-executed if some source files change.
- It outputs metadata like dependency information into ``META`` files that
Findlib is able to make use of. This ensures that even if a project does not use Dune, it
can use a library that has been produced by Dune. Conversely, it can read
these files to determine dependency information for dependencies that have
not been produced by Dune.
- It is able to generate opam files with filenames consistent with how opam
looks for them. The generated files use build commands that make use of the
:doc:`/reference/aliases/install` and ``@runtest`` :term:`aliases <alias>` so
that the Dune abstractions map to the opam ones.
Dune is Opinionated
-------------------
As described above, the OCaml ecosystem does not have a centralized toolchain.
Units such as modules, libraries, and packages operate at different levels, and
the relation between these can be confusing to users.
Dune tries to simplify the picture by reducing the difference between these
objects:
- By default, a library will only expose a single top-level module named after
the library (this is called a wrapped library).
- A library can only be installed in the package of the same name. This means
that the names found in ``dune-project`` and ``opam`` files (package names)
are consistent with the names found in ``dune`` files (library names). More
precisely, libraries ``foo``, ``foo.bar`` and ``foo.baz`` are part of the
``foo`` package.

View file

@ -0,0 +1,119 @@
How Dune integrates with opam
=============================
.. highlight:: opam
When instructed to do so (see :doc:`../howto/opam-file-generation`), Dune generates opam files with the following instructions::
build: [
["dune" "subst"] {dev}
[
"dune"
"build"
"-p"
name
"-j"
jobs
"@install"
"@runtest" {with-test}
"@doc" {with-doc}
]
]
Let's see what this means in detail.
Substitution
------------
The first step is to call ``dune subst``, but only if the ``{dev}`` opam
variable is set. This variable is only set when the package is pinned.
This means that :ref:`dune-subst` does not run for released versions, but it
does for development versions.
This is not a problem since released versions should have a ``(version)`` field
set in ``dune-project``, and :term:`placeholder substitution` should have been
performed. `dune-release`_ takes care of these steps.
.. _dune-release: https://github.com/tarides/dune-release
Opam Variables
--------------
In the second command line, ``name`` is a variable that evaluates to the name
of the package being built, and ``jobs`` is a variable that corresponds to the
number of commands to run in parallel.
What ``-p`` Means
-----------------
The ``-p`` flag, shorthand for ``--release-of-packages``, is Dune's public interface to set up the options for an opam build. The exact semantics may change, but as of Dune 3.8 it is equivalent to the combination of:
- ``--root .``: set the :term:`root` to prevent Dune from :ref:`looking it up <finding-root>`.
- ``--only-packages name``: ignore packages other than ``name`` defined in the project.
- ``--profile release``: set the :term:`build profile` to ``release``. In particular, this ensures that warnings are not fatal.
- ``--ignore-promoted-rules``: silently ignores all rules with ``(mode promote)``.
- ``--default-target @install``: make sure that ``dune build`` with no target argument builds ``@install``, not ``@@default`` (this is not used in the opam integration since an explicit target is passed)
- ``--no-config``: do not load the configuration file in the user's home directory.
- ``--always-show-command-line``: ensures that the programs executed by Dune end up in the opam logs.
- ``--promote-install-files``: ensures that ``*.install`` files are present in the source tree after the build.
- ``--require-dune-project-file``: fail if ``dune-project`` is not present. In some previous Dune versions, ``dune-project`` could be generated when it is not present. This is not desirable with opam since the version the package has been prepared with is not known.
The Targets We're Building
--------------------------
The targets are specified as::
"@install"
"@runtest" {with-test}
"@doc" {with-doc}
The ``{with- }`` syntax is an opam filter. It means that the string before is
present or not depending on the opam variable. These variables, in turn, are set depending on the opam configuration.
Concretely, in the next table, if the opam command on the left is executed, the Dune target on the right will be built:
.. list-table::
:header-rows: 1
* - opam command
- Dune target
* - ``opam install pkg``
- ``@install``
* - ``opam install pkg --with-test``
- ``@install @runtest``
* - ``opam install pkg --with-test --with-doc``
- ``@install @runtest @doc``
This filtering mechanism is also used to declare dependencies.
If a package is using ``lwt`` and ``alcotest``, but the latter only in its test
suite, its ``depends:`` field is::
"lwt"
"alcotest" {with-test}
This is expanded to just ``"lwt"`` in ``opam install pkg``, but to ``"lwt"
"alcotest"`` in ``opam install pkg --with-test``.
The meaning of these :term:`aliases <alias>` is the following:
- :doc:`/reference/aliases/install` depends on all the ``*.install`` files in
the project. In turn, these depend on all the installable files (libraries and
executables with a public name and files that are manually installed through
``(install)`` stanzas).
- :doc:`/reference/aliases/runtest` is the alias to which all tests are
attached, including ``(test)`` stanzas. ``dune build @runtest`` is equivalent
to ``dune runtest``.
- :doc:`/reference/aliases/doc` executes ``odoc`` to create HTML docs under
``_build``.
What Opam Expects From Dune
---------------------------
Given this ``build:`` lines and the fact that there is no ``install:`` line,
what happens is the following:
- Opam executes ``dune subst``, if the package is being pinned.
- Opam executes the build instruction, usually just ``dune build -p pkg @install``
- This Dune command builds all the installable files and creates a ``pkg.install`` file.
- This file contains the paths to built files (somewhere in the ``_build`` directory) and the opam sections they should be installed in.
- Opam interprets this file and copies the built files to their destination. The install file is also used as a manifest of which files belong to which package, which is used when uninstalling the package.

View file

@ -0,0 +1,234 @@
# How Package Management Works
This document explains how Dune's package management works under the hood. It
requires a bit of familiarity with how opam repositories work and how Dune
builds packages. Thus it is aimed at people who want to understand how the
feature works, not how it is used.
For a tour on how to apply package management to a project, refer to the
{doc}`/tutorials/dune-package-management/index` tutorial.
## Motivation
A core part of modern programming is using existing code to save time. The
OCaml package ecosystem has quite a long history with many projects building
upon each other over many years. A significant step forward was the creation of
the OCaml Package Manager, opam, along with the establishment of a public
package repository which made it a lot more feasible to share code between
people and projects.
Over time, best practices have evolved, and while opam has incorporated some
changes, it couldn't adopt all the modern workflows due to its existing user
base and constraints.
Thus the Dune Package Management has been designed with a few core goals in
mind:
* No global state visible to users, everything is local to projects
* Package management is configured through files (`dune-project` and optionally
`dune-workspace`)
* Repositories are automatically kept up to date unless explicitly configured
to use specific versions
* Builds can only access packages they have declared dependencies on
* Reproducible builds through lockfiles
Dune plays well with the existing OCaml ecosystem and does not introduce a new
type of packages. Rather, it uses the same package repository and Dune packages
stay installable with opam.
## Package Management in a Project
This section describes what happens in a Dune project using the package
management feature.
## Dependency Selection
The first step is to determine which packages need to be installed.
Traditionally this has been defined in the `depends` field of a project's opam
file(s).
Since version 1.10 Dune has supported {doc}`opam file generation
</howto/opam-file-generation>` by specifying the package dependencies in the
`dune-project`.
The package management feature uses the same metadata, as Dune will determine
the list of packages to install from the `depends` field in the `dune-project`
file. This allows projects to completely omit generation of `.opam` files, as
long as they use Dune for package management. Thus all dependencies on OCaml
packages are only declared in one single file.
To maintain compatibility with a large number of existing projects, Dune
continues to support `.opam` files. While it is recommended to declare the
dependencies directly in the `dune-project` file, it is not mandatory to do so.
Dune will fall back to reading dependencies from `.opam` files when the package
is not defined in `dune-project`.
## Locking
Given the list of the project's dependencies and their version
constraints, the next steps are:
1. Find the transitive dependencies and figure out a version for each
dependency that satisfies the constraints
2. For each dependency, download it, build it, and make it available to the
project
In opam, `opam install` does both of these.
In Dune, these are separate steps: the first one is `dune pkg lock`, and the
second one happens implicitly as part of [building](#building).
The idea of doing the first step and recording it for later is popular in other
programming language package managers like NPM and is usually called locking.
Creating a lock file ensures that the dependencies to be installed are
always the same - unless that lock file is updated of course.
:::{note}
`opam` also supports creating lock files. However, these are not as central to
the opam workflow as they are in the case of package management in Dune, which
always requires a set of locked packages.
:::
In the most general sense, a lock file is just a set of specific packages
and their versions to be installed.
Instead of a lock file, Dune writes this information to a directory (the "lock
directory") with files that describe the dependencies. It includes the
package's name and version. Unlike many other package managers, the files
include a lot of other information as well, such as the location of the source
archives to download (since there is no central location for all archives), the
build instructions (since each package can use its own way of building), and
additional metadata like the system packages it depends upon.
The information is stored in a directory (`dune.lock` by default) as separate
files, to reduce potential merge conflicts and simplify code review. Storing
additional files like patches is also simpler this way.
### Package Repository Management
To find a valid solution that allows a project to be built, it is necessary to
know what packages exist, what versions of these packages exist, and what other
packages these depend on, etc.
In opam, this information is tracked in a central repository called
[`opam-repository`](https://github.com/ocaml/opam-repository), which contains
all the metadata for published packages.
It is managed using Git; opam typically uses a snapshot to find the
dependencies when searching for a solution that satisfies the constraints.
Likewise, Dune uses the same repository; however, instead of snapshots of the
contents, it uses the Git repository directly.
:::{note}
Dune maintains a shared internal cache containing all Git repositories that
projects use. This way updates and checkouts are very fast because only new
revisions have to be retrieved. The downside is that to be included in the
cache, all the Git repos have to be cloned first which depending on the size of
the repositories can take a bit of time.
:::
On every call to `dune pkg lock`, Dune will update the metadata repository
first (hence why efficiently updating that repository matters). This means that
each `dune pkg lock` will use the newest set of packages available.
However, it is also possible to declare specific revisions of the repositories,
to get a reproducible solution. Due to using Git, any previous revision of the
repository can be used by specifying a commit hash.
Dune uses two repositories by default:
* `upstream` refers to the default branch of `opam-repository`, which contains
all the publicly released packages.
* `overlay` refers to
[opam-overlay](https://github.com/ocaml-dune/opam-overlays), which defines
packages patched to work with package management. The long-term goal is to
have as few packages as possible in this repository as more and more packages
work within Dune Package Management upstream. Check the
[compatibility](#compatibility) section for details.
### Solving
After Dune has read the constraints and loaded set of candidate packages, it is
necessary to determine which packages and versions should be selected for the
package lock.
To do so, Dune uses
[`opam-0install-solver`](https://github.com/ocaml-opam/opam-0install-solver),
which is a variant of the [`0install`](https://github.com/0install/0install)
solver to find solutions for opam packages.
Contrary to opam, the Dune solver always starts from a blank slate; it assumes
nothing is installed and everything needs to be installed. This has the
advantage that solving is now simpler, and previous solver solutions don't
interfere with the current one. Thus, given the same inputs, it should always
come up with the same result; no state is held between the solver runs.
This can lead to more packages being installed (as opam won't install new
package versions by default if the existing versions satisfy the constraints),
but it avoids interference from already installed packages that lead to
potentially different solutions.
After solving is done, the solution gets written into the lock directory with
all the metadata necessary to build and install the packages. From this point
on, there is no need to access the package metadata repositories.
:::{note}
Solving and locking does not download the package sources. These are downloaded
in the build step.
:::
(building)=
## Building
When building, Dune will read the information from the lock directory and set
up rules for the packages. Check {doc}`/explanation/mental-model` for details
about rules.
The rules that the package management sets up include:
* Fetch rules to download and unpack the source archives, and also download any
additional sources such as patches
* Build rules to execute the build instructions stored in the lock directory
* Install rules to put the artifacts that were built into the appropriate
Dune-managed folders
Creating these processes as rules mean that they will only be executed on
demand, so if the project has already downloaded the sources, it does not need
to download them again. Likewise, if packages are installed, they stay
installed.
The results of the rules are stored in the project's `_build` directory and
managed automatically by Dune. Thus, when cleaning the build directory, the
installed packages are cleaned as well and will be reinstalled at the next
build.
(compatibility)=
## Packaging for Dune Compatibility
Dune can build and install most packages as dependencies, even if they are not
built with Dune themselves. Dune will execute the build instructions from the
lock directory, very similar to opam.
However, packages must adhere to certain rules to be compatible with Dune.
The most important one is that the packages must not use absolute paths to
refer to files. That means they cannot read the path they are being built or
installed in and expect this path to remain the same. Dune builds packages in a
sandbox location, and after the build has finished, it moves the files to the
actual destination.
:::{note}
Unlike opam, Dune at the moment does not wrap the build in sandboxing tools
like [Bubblewrap](https://github.com/containers/bubblewrap).
:::
To comply with these restrictions the usual solution is to use relative paths,
as Dune guarantees that packages installed into different sections are
installed in a way where their relative location stays the same.
The `overlay` repository exists specifically to make currently non-compliant
packages compatible with Dune's package management. It does so by supplying
releases of packages where the current upstream releases don't support Dune
package management yet.

View file

@ -0,0 +1,55 @@
How Preprocessing Works
=======================
Preprocessing consists in transforming source code before it is compiled. The
goal of this document is to explain how this works in Dune.
Dune supports two separate ways of applying preprocessors, the "classic pipeline" (used
with ``(staged_pps)``), and the "fast pipeline" (used for all other
:doc:`preprocessing specifications <../reference/preprocessing-spec>` including
``(pps)``).
The OCaml compilers provide options for specifying a preprocessing step. The
``-pp`` option is used to invoke a textual preprocessor (something that reads
text and returns text). The ``-ppx`` option is used to invoke a `ppx rewriter`
(a function that takes an AST and outputs an AST).
This is the "classic pipeline": preprocessing is part of the compilation
itself. This is simple, but has a problem: in order to compute the dependencies
of a module, it is necessary to pass the same ``-pp`` or ``-ppx`` option to
``ocamldep``.
The classic pipeline has the following steps:
- preprocessing (as part of ``ocamldep``)
- dependency analysis
- preprocessing (as part of compilation)
- compilation
Dune supports a "fast pipeline" where the preprocessor is invoked separately
from the compiler and its output is saved. Afterwards the preprocessed code is
compiled directly.
The fast pipeline has the following steps:
- preprocessing
- dependency analysis
- compilation
It has several advantages: it only invokes the preprocessor once per file, and
the preprocessed code is reused between dependency analysis and different kinds
of compilation. Also, when several preprocessors use ``ppxlib``, they can be
combined in a preprocessing program that traverses the AST only once.
However, some specific code generators or preprocessors require direct
access to the compilation artefacts of their dependencies. Therefore they
need to be used with the classic pipeline, even if it is slower. Note that a
PPX is able to know if it was called as part of ``ocamldep -ppx`` or ``ocamlopt
-ppx``, so it can act differently in each phase.
Dune chooses which pipeline to use depending on the
provided :doc:`../reference/preprocessing-spec`. It will select the fast pipeline,
unless ``(staged_pps)`` is used. In that case, the classic pipeline is used.
In the case of the fast pipeline, a single executable is built and accepts
arguments for all preprocessors.

View file

@ -0,0 +1,43 @@
Dune Projects and Workspaces
============================
Whenever Dune builds anything, it does so at the level of a *Dune workspace*. A
Dune workspace is a set of Dune projects. A typical workspace consists of a
single Dune project.
A *Dune project* is defined by the presence of a
:doc:`/reference/dune-project/index` file. Each Dune project extends over the
file tree rooted at the directory containing the
:doc:`/reference/dune-project/index` file, excluding any nested Dune projects.
Dune determines the root of the current workspace by finding the topmost
ancestor containing a :doc:`/reference/dune-project/index` file or by the
presence of a :doc:`/reference/dune-workspace/index` file (see
:ref:`finding-root` and :ref:`forcing-root` for details).
Different Dune projects within the same Dune workspace are independent of each
other and no settings are shared between them, even if they are nested within
each other.
Settings in :doc:`/reference/dune-workspace/index`, on the other hand, are
inherited by all Dune projects in the workspace. Some settings (those that make
sense for all projects) can be specified both in
:doc:`/reference/dune-project/index` and :doc:`/reference/dune-workspace/index`
files, with the former taking precedence. Note that all
:doc:`/reference/dune-workspace/index` files other than the one specifying the
root of the workspace are ignored.
Within a Dune project, :doc:`/reference/dune/index` files are used to define all
objects of interest for Dune: libraries, executables, tests, etc. There are
typically many :doc:`/reference/dune/index` files in a Dune project: one per
directory, unless the directory does not contain anything relevant to Dune. In
each :doc:`/reference/dune/index` file, references are resolved relative to the
directory containing the file.
Note that there are specific stanzas and actions that may result in exceptions
to some of the rules stated in the previous paragraph. See, for example, the
:doc:`/reference/dune/subdir` and :doc:`/reference/dune/include_subdirs`
stanzas, as well as the :doc:`/reference/actions/chdir` action.
Finally, note that only public items (public libraries, public executables) of a
Dune project are visible to other Dune projects within the same Dune workspace.

View file

@ -0,0 +1,29 @@
Command-Line Interface
----------------------
The command-line interface is defined using `cmdliner
<https://erratique.ch/software/cmdliner>`_. One thing to note is that we use
binding operators to compose terms:
`bin/print_rules.ml <https://github.com/ocaml/dune/blob/3.15.0/bin/print_rules.ml#L174-L190>`_
.. code-block:: ocaml
:linenos:
:lineno-start: 174
let+ builder = Common.Builder.term
and+ out =
Arg.(
value
& opt (some string) None
& info [ "o" ] ~docv:"FILE" ~doc:"Output to a file instead of stdout.")
and+ recursive =
Arg.(
value
& flag
& info
[ "r"; "recursive" ]
~doc:
"Print all rules needed to build the transitive dependencies of the given \
targets.")
and+ syntax = Syntax.term
and+ targets = Arg.(value & pos_all dep [] & Arg.info [] ~docv:"TARGET") in

View file

@ -0,0 +1,69 @@
Parsing of Dune Files
---------------------
Parsing ``dune`` files is done in two steps:
- They are parsed as S-expressions using :file:`src/dune_sexp/parser.mli`;
- Then they are decoded using :file:`src/dune_sexp/decoder.mli`. The result of
this decoding step is added to an extensible variant using a mechanism in
:file:`src/dune_lang/stanza.mli`.
Instead of writing a parser or using pattern matching, we define decoders,
which are abstract values of type ``'a Decoder.t`` (returning a value of type
``'a``). These decoders are assembled using combinators. For example, we can
use simple decoders to write a decoder for a record type. This decoder
abstraction is monadic, but the applicative subset is sufficient for most
decoders.
As an example, here is how ``(copy_files)`` is parsed:
`src/dune_rules/stanzas/copy_files.ml <https://github.com/ocaml/dune/blob/3.15.0/src/dune_rules/stanzas/copy_files.ml#L31-L50>`_
.. code-block:: ocaml
:linenos:
:lineno-start: 31
let long_form =
let check = Dune_lang.Syntax.since Stanza.syntax (2, 7) in
let+ alias = field_o "alias" (check >>> Dune_lang.Alias.decode)
and+ mode = field "mode" ~default:Rule.Mode.Standard (check >>> Rule_mode_decoder.decode)
and+ enabled_if = Enabled_if.decode ~allowed_vars:Any ~since:(Some (2, 8)) ()
and+ files = field "files" (check >>> String_with_vars.decode)
and+ only_sources =
field_o
"only_sources"
(Dune_lang.Syntax.since Stanza.syntax (3, 14) >>> decode_only_sources)
and+ syntax_version = Dune_lang.Syntax.get_exn Stanza.syntax in
let only_sources = Option.value only_sources ~default:Blang.false_ in
{ add_line_directive = false
; alias
; mode
; enabled_if
; files
; only_sources
; syntax_version
}
The fields are queried individually, and a record is built using all the
intermediate results. This will automatically take care of generating "unknown
field X," "duplicate field X," and similar error messages.
Another interesting thing to note is that the fields are not decoded directly,
but use the following pattern:
.. code:: ocaml
Syntax.since Stanza.syntax (x, y) >>> decoder
Let's unpack this: ``(>>>)`` will run a ``unit Decoder.t`` on the input before
passing the input to an actual decoder. The first decoder can be used to
implement a check and trigger an error in some cases.
Here, it is used for versioning. For example the ``(copy_files)`` stanza
started supporting ``(enabled_if``) in version 2.8. Decoding this field is
protected by this ``since`` call: it means that if the language version in
:doc:`/reference/dune-project/index` file is greater than 2.8. In particular,
this ensures that the project can not be built with Dune versions older than
``2.8.0``.
Once decoding succeeds, various stanzas are turned into various types defined
in :file:`src/dune_rules/stanzas/`.

View file

@ -0,0 +1,15 @@
The Engine
==========
The engine is the core, reusable part of Dune. It contains all the composable
primitives that make it a build system.
The fact that it is split from the :doc:`rules part <rule-generation>` makes it
possible to create a different build system using this library. For example,
Jane Street internally uses a build system with this engine as a backend, but a
different frontend and CLI.
In the context of Dune, the engine keeps track of the various directories and
the rules in them and is able to build files using them. In addition, it takes
care of the various caches that Dune uses, such as the one present in the
``_build`` directory, the :doc:`shared cache </caching>`, etc.

View file

@ -0,0 +1,34 @@
A Tour of the Dune Codebase
===========================
.. note::
This document is based on Dune 3.15.0, whose source can be browsed `here
<https://github.com/ocaml/dune/tree/3.15.0>`_. The links in this tour point
to this version, but will not reflect how this works in other versions of
Dune.
Let's start with a very high level tour of how ``dune build`` operates.
As explained in :doc:`/explanation/mental-model`, ``dune build`` will interpret
the targets listed on the command line, interpret the ``dune`` files in the
workspace as rules, and execute the rules relevant to the requested targets.
These steps correspond to areas of the Dune codebase:
- the command-line interface is defined in :file:`bin/`;
- the ``dune`` files are interpreted using a library defined in
:file:`src/dune_rules/`;
- they are registered into an engine in :file:`src/dune_engine/`.
Next, we will go deeper into these areas.
.. toctree::
cli
decoding
rule-generation
engine
libraries
vendor
tests

View file

@ -0,0 +1,12 @@
Libraries
=========
Dune, as a package, is primarily an executable, but its source tree embeds a
few public libraries. These are developed in :file:`otherlibs/`.
Some of these have a special link to Dune, such as ``dune-build-info`` or
``dune-site``. Others are just helper libraries that we develop as part of
Dune, but they have a strong relation to the Dune internals, like ``dyn`` or
``xdg``.
.. seealso:: :doc:`/dune-libs`

View file

@ -0,0 +1,168 @@
Rule Generation
---------------
Using these parsed stanzas, the next step is to generate rules. This work
starts in :file:`src/dune_rules/gen_rules.ml`, which dispatches to various
modules in :file:`src/dune_rules/`.
Rules are registered on the build engine using the following function from the
``Super_context`` module:
.. code-block:: ocaml
val add_rule
: t
-> ?mode:Rule.Mode.t
-> ?loc:Loc.t
-> dir:Path.Build.t
-> Action.Full.t Action_builder.With_targets.t
-> unit Memo.t
A value of ``Super_context.t`` represents an OCaml toolchain (``Context.t``) as
well as various capabilities to expand variables and refer to :doc:`(env)
stanzas </reference/dune/env>`. The last, unlabelled argument corresponds to
the fully annotated action. We'll go through its type below.
The modules in :file:`src/dune_rules` often expose a function ``gen_rules``
taking a parsed stanza, a ``Super_context.t`` value, a directory name (and
other arguments), and returning ``unit Memo.t``.
.. note::
The ``Memo`` module is central to how Dune operates. It is a monadic
memoization framework that allows two things:
- Sharing and caching expensive internal computations, such as computing the
list of libraries Dune knows about, or computing the list of flags that
should be used to compile a given module.
- Incremental recomputation of this cached data. ``Memo`` tracks dependencies
between memoized values and will only recompute the necessary ones when an
input changes. This is a mini in-memory build system that works like a
spreadsheet. It is essential to the watch mode.
An example of rule is the :doc:`/reference/dune/mdx` stanza, implemented in
:file:`src/dune_rules/mdx.ml`. There are several steps in setting up rules for
a ``(mdx)`` stanza:
- How to run ``ocaml-mdx deps`` on the input file to produce a ``.mdx.deps``
- Run ``ocaml-mdx dune-gen`` to produce a ``mdx_gen.ml-gen`` OCaml source file
- Compile this executable
- Run this executable to produce a ``.corrected`` file
- Register a :doc:`/reference/actions/diff` action between the ``.corrected`` file
and the original file
Let's walk through these rules.
The first one is about producing a ``.mdx.deps`` file. It is a simple call to
``Super_context.add_rule``.
.. code-block:: ocaml
:linenos:
:lineno-start: 312
let* () = Super_context.add_rule sctx ~loc ~dir (Deps.rule ~dir ~mdx_prog files)
``Deps.rule`` is defined in a helper function:
.. code-block:: ocaml
:linenos:
:lineno-start: 77
let rule ~dir ~mdx_prog (files : Files.t) =
Command.run_dyn_prog
~dir:(Path.build dir)
mdx_prog
~stdout_to:files.deps
[ Command.Args.A "deps"; Lazy.force color_always; Dep (Path.build files.Files.src) ]
This is a rule made by just running a command, here ``mdx_prog`` (a resolved
path to ``ocaml-mdx``, meaning it can point to a binary in ``PATH`` or a built
version in the current workspace). Its arguments are a domain-specific language
defined in :file:`src/dune_rules/command.mli` where ``A`` refers to a plain
string, and ``Dep`` refers to a string that should be interpreted as a dependency.
Between that, and the ``~stdout_to`` parameter, it is enough for Dune to know
about the rule's dependencies (what it will read) and its target (what it will
produce).
The second rule, which generates ``mdx_gen.ml-gen``, is similar. It is also done
by calling ``Command.run_dyn_prog``.
The third rule, to build the executable, calls ``Exe.build_and_link`` that is a
helper function.
Let's observe how the fourth rule (that calls the generated executable) is set
up.
.. code-block:: ocaml
let mdx_action ~loc:_ =
let open Action_builder.With_targets.O in
let mdx_input_dependencies = (* ... *) in
let executable, command_line = (* ... *) in
let deps, sandbox = (* ... *) in
let+ action =
Action_builder.with_no_targets deps
>>> Action_builder.with_no_targets
(Action_builder.env_var "MDX_RUN_NON_DETERMINISTIC")
>>> Action_builder.with_no_targets
(Action_builder.map mdx_input_dependencies ~f:(fun d -> (), d)
|> Action_builder.dyn_deps)
>>> Command.run_dyn_prog
~dir:(Path.build dir)
~stdout_to:files.corrected
executable
command_line
and+ locks =
Expander.expand_locks expander stanza.locks |> Action_builder.with_no_targets
in
Action.Full.add_locks locks action |> Action.Full.add_sandbox sandbox
in
Super_context.add_rule sctx ~loc ~dir (mdx_action ~loc)
Here, the ``mdx_action`` that is set up is not just a single
``Command.run_dyn_prog`` call. It is assembled using combinators from
``Action_builder.With_targets``. This is another monad used in Dune. It
corresponds to what can happen at build time, like running commands or creating
files, or more complex actions such as reading a file that needs to be built by
another rule. It is also used to track dependencies and targets. The "thing"
that we register to the Dune engine using ``Super_context.add_rule`` has type
``Action.Full.t Action_builder.With_targets.t``.
.. note::
This is different from ``Memo``, which corresponds to what happens within
Dune itself. But it is also possible to use ``Memo`` from an
``Action_builder`` context. In that sense, ``Action_builder`` is more
powerful: at execution time, ``Action_builder`` will manage what happens in
the ``_build`` directory, while ``Memo`` is only concerned with what happens
in memory.
Finally, to register the correction, the technique is to attach the
:doc:`/reference/actions/diff` action to the :doc:`/reference/aliases/runtest`
alias (a collection of rules) using this call:
.. code-block:: ocaml
:linenos:
:lineno-start: 405
(* Attach the diff action to the @runtest for the src and corrected files *)
Files.diff_action files
|> Super_context.add_alias_action sctx (Alias.make Alias0.runtest ~dir) ~loc ~dir
Where ``Files.diff_action`` is defined as:
.. code-block:: ocaml
:linenos:
:lineno-start: 33
let diff_action { src; corrected; deps = _ } =
let src = Path.build src in
let open Action_builder.O in
let+ () = Action_builder.path src
and+ () = Action_builder.path (Path.build corrected) in
Action.Full.make (Action.diff ~optional:false src corrected)
;;
As explained above, ``Action_builder`` keeps tracks of dependencies, so using
``let+ () = Action_builder.path src`` is a way to declare ``src`` as a
dependency of the current action.

View file

@ -0,0 +1,33 @@
Tests
=====
The :file:`test/` directory contains all the tests for Dune itself. Additionally,
the tests for our :doc:`libraries` are stored in :file:`otherlibs/` next to the
library itself.
We have 3 kind of tests:
- Unit tests, in :file:`test/unit-tests` (we have very few of these, usually
preferring other kinds)
- Expect tests, in :file:`test/expect-tests` (using ``ppx_expect``)
- :doc:`Cram tests </reference/cram>`, in :file:`test/blackbox-tests/`. This is
our preferred way of testing.
The actual Cram tests are in :file:`test/expect-tests/test-cases`. There is a
mix of file tests and directory tests. For regression tests, the pattern
``githubNUMBER.t`` is used.
The ``dune`` file at :file:`test/expect-tests/test-cases/dune` sets up some
metadata for the tests. For example, if a test has an external dependency like
``strace``, a dependency on ``%{bin:strace}`` will prevent the test from even
trying to start. Some tests are also disabled on some configurations using
``(enabled_if)``.
Finally, some programs available in the Cram tests are defined in
:file:`test/expect-tests/blackbox-tests/utils`. For example, we have `a
dune_cmd program
<https://github.com/ocaml/dune/blob/3.15.0/test/blackbox-tests/utils/dune_cmd.ml>`_
that contains reimplementations of common utilities like ``stat``, which do not
have the same output on the different systems we use to test Dune.
.. seealso:: :doc:`/hacking`

View file

@ -0,0 +1,15 @@
Vendored Libraries
==================
As an opam package, Dune has no dependencies. But it uses some existing
libraries by copying, or "vendoring", their source code into the
:file:`vendor/` directory.
In some cases, the external dependency is extracted from the upstream
repository. In other cases, we carry patches and refer to a fork in the
`ocaml-dune GitHub organization <https://github.com/ocaml-dune>`_.
The source code in the :file:`vendor/` directory is not meant to be edited
directly. Instead, it is edited in the external repository, and the copy in the
Dune source tree is updated by running an update script, such as
`update-spawn.sh <https://github.com/ocaml/dune/blob/3.15.0/vendor/update-spawn.sh>`_.

View file

@ -0,0 +1,24 @@
from pygments.lexer import DelegatingLexer, RegexLexer, bygroups, default
from pygments.lexers.shell import BashLexer
from pygments.token import Generic, Other, Comment
class CramBaseLexer(RegexLexer):
tokens = {
"root": [
(r"( \$ )(.*\n)", bygroups(Generic.Prompt, Other.Code), "continuations"),
(r"^ .*\n", Generic.Output),
(r"^.*\n", Comment.Multiline),
],
"continuations": [
(r"( > )(.*\n)", bygroups(Generic.Prompt, Other.Code)),
default("#pop"),
],
}
class CramLexer(DelegatingLexer):
name = "cram"
def __init__(self, **options):
super().__init__(BashLexer, CramBaseLexer, Other.Code, **options)

View file

@ -0,0 +1,36 @@
from pygments.token import Comment, Punctuation, Text, Name, String, Generic
from pygments.lexer import RegexLexer
def after_paren(r):
return r"(?<=\()(%s)" % r
class DuneLexer(RegexLexer):
name = "dune"
atom = r'[^\s()"]+'
tokens = {
"root": [
(r";.*$", Comment.Single),
(r"(\(|\))", Punctuation),
# pforms (%{var} and %{fun:arg}
(r"%{[^}]+}", String.Backtick),
(r":[-\w]+", String.Symbol),
# placeholders like <arg> and ... that appear in docs
(r"<[-\w ]+>", Generic.Emph),
(r"\.\.\.", Generic.Emph),
(r'"\\\|', String.Double, "string-multiline"),
(r'"', String, "string"),
# instead of hardcoding "builtin" names,
# highlight the first atom in a list differently
(after_paren(atom), Name.Function),
(atom, Name),
(r"\s+", Text),
],
"string": [
(r'(\\\\|\\"|[^"])*"', String, "#pop"),
],
"string-multiline": [(r"[^\n]*\n", String, "#pop")],
}

View file

@ -0,0 +1,26 @@
from pygments.lexer import RegexLexer
from pygments.token import (
Keyword,
Number,
Punctuation,
String,
Text,
Whitespace,
)
ident = r"[-\w]+"
class OpamLexer(RegexLexer):
name = "opam"
tokens = {
"root": [
(r"\d\.\d", Number),
(r"^" + ident + ":", Keyword),
(ident, Text),
(r"[\[\]{}>=]", Punctuation),
(r"\s+", Whitespace),
(r'"[^\"]*"', String),
]
}

View file

@ -0,0 +1,266 @@
***
FAQ
***
.. TODO(diataxis)
This is an odd one - most of these questions are not frequently asked at all.
Some of these are mini how-to guides, or sections of existing guides.
Why do many Dune projects contain a ``Makefile``?
=================================================
Many Dune projects contain a root ``Makefile``. It's often only there for
convenience for the following reasons:
1. There are many different build systems out there, all with a different CLI.
If you have been hacking for a long time, the one true invocation you know
is ``make && make install``, possibly preceded by ``./configure``.
2. You often have a few common operations that aren't part of the build, so
``make <blah>`` is a good way to provide them.
3. ``make`` is shorter to type than ``dune build @install``
How to add a configure step to a Dune project?
==============================================
The with-configure-step_ example shows one way to add a configure step that
preserves composability; i.e., it doesn't require manually running the
``./configure`` script when working on multiple projects simultaneously.
.. _with-configure-step: https://github.com/ocaml/dune/tree/master/example/with-configure-step.t
Can I use ``topkg`` with Dune?
==============================
While it's possible to use the topkg-jbuilder_, it's not recommended.
dune-release_ subsumes ``topkg-jbuilder`` and is specifically tailored to Dune
projects.
How do I publish my packages with Dune?
=======================================
Dune is just a build system and considers publishing outside of its scope.
However, the dune-release_ project is specifically designed for releasing Dune
projects to opam. We recommend using this tool for publishing Dune packages.
Where can I find some examples of projects using Dune?
======================================================
The dune-universe_ repository contains a snapshot of the latest versions of all
opam packages that depend on Dune. Therefore, it's a useful reference to find
different approaches for constructing build rules.
What is Jenga?
==============
jenga_ is a build system developed by Jane Street, mainly for internal use. It
was never usable outside of Jane Street, so it's not recommended for general
use. It has no relationship to Dune apart from Dune being the successor to
Jenga externally. Eventually, Dune is expected to replace Jenga internally at
Jane Street as well.
.. _dune-universe: https://github.com/dune-universe/dune-universe
.. _topkg-jbuilder: https://github.com/samoht/topkg-jbuilder
.. _dune-release: https://github.com/samoht/dune-release
.. _jenga: https://github.com/janestreet/jenga
How to make warnings non-fatal?
===============================
`jbuilder` formerly displayed warnings, but most of them wouldn't stop the
build. However, Dune makes all warnings fatal by default. This can be a
challenge when porting a codebase to Dune. There are two ways to make warnings
non-fatal:
- The ``jbuilder`` compatibility executable works even with ``dune`` files. You
can use it while some warnings remain and then switch over to the ``dune``
executable. This is the recommended way to handle the situation.
- You can pass ``--profile release`` to ``dune``. It will set up different
compilation options that usually make sense for release builds, including
making warnings non-fatal. This is done by default when installing packages
from opam.
- You can change the flags used by the ``dev`` profile by adding the following
stanza to a ``dune`` file:
.. code:: dune
(env
(dev
(flags (:standard -warn-error -A))))
How to turn specific errors into warnings?
==========================================
Dune is strict about warnings by default in that all warnings are treated as
fatal errors. To change certain errors into warnings for a project, you can add
the following to ``dune-workspace``:
.. code:: dune
(env (dev (flags :standard -warn-error -27-32)))
In this example, the warnings 27 (unused-var-strict) and 32
(unused-value-declaration) are treated as warnings rather than errors.
How to display the output of commands as they run?
==================================================
When Dune runs external commands, it redirects and saves their output, then
displays it when complete. This ensures that there's no interleaving when
writing to the console.
But this might not be what the you want. For example, when you debug a hanging
build.
In that case, one can pass ``-j1 --no-buffer`` so the commands are directly
printed on the console (and the parallelism is disabled so the output stays
readable).
How can I generate an ``mli`` file from an ``ml`` file?
=======================================================
When a module starts as just an implementation (``.ml`` file), it can be
tedious to define the corresponding interface (``.mli`` file).
It is possible to use the ``ocaml-print-intf`` program (available on opam
through ``$ opam install ocaml-print-intf``) to generate the right ``mli``
file:
.. code:: console
$ dune exec -- ocaml-print-intf ocaml_print_intf.ml
val root_from_verbose_output : string list -> string
val target_from_verbose_output : string list -> string
val build_cmi : string -> string
val print_intf : string -> unit
val version : unit -> string
val usage : unit -> unit
The ``ocaml-print-intf`` program has special support for Dune, so it will
automatically understand external dependencies.
How can I build a single library?
=================================
You might want to do this when you don't have all the dependencies installed to
compile an entire project, or parts of the project don't build for whatever
reason. Maybe you want to check if your changes compile or produce build
artifacts needed by ``ocaml-lsp-server``.
Suppose you have a library defined in ``src/foo/dune``:
.. code:: dune
(library
(public_name my_library)
...)
You can build this library on its own by running the following from the project
root directory:
.. code:: console
$ dune build %{cmxa:src/foo/my_library}
Note that the path (``src/foo`` in the example above) is relative to the current
directory - not the project root. If the library defines a ``name`` distinct from
its ``public_name`` then that can be used interchangeably with the ``public_name``
in this command.
Why does ``source_tree`` ignore files and directories when they begin with a "." (period)?
==========================================================================================
Dune's default behaviour is to ignore files and directories starting with "."
when copying directories with ``source_tree``. This is to avoid accidentally
copying the ``.git`` directory into the ``_build`` directory during a build.
This is a common source of confusion when interoperating with other libraries
that use hidden directories for configuration, such as Rust. For example
consider this rule which builds a Rust library contained in a subdirectory
foo-rs:
.. code:: dune
(rule
(target foo.a)
(deps
(source_tree foo-rs))
(action
(progn
(chdir
foo-rs
(run cargo build --release))
(run mv foo-rs/target/release/%{target} %{target}))))
The build config for the Rust project will be in a directory
``foo-rs/.cargo/config.toml``, and by default the ``.cargo`` directory won't
get copied into the ``_build`` directory and so the Rust project will build
with an incorrect configuration.
To fix this, create a ``dune`` file at the top level of the Rust project (i.e.,
``foo-rs/dune``):
.. code:: dune
(dirs :standard .cargo)
If you're following the standard advice for embedding Rust projects into Dune
projects then you likely already have a ``dune`` project inside your Rust
project that looks like:
.. code:: dune
(dirs :standard \ target)
(data_only_dirs vendor)
In this case you can update it to look like this:
.. code:: dune
(dirs :standard .cargo \ target)
(data_only_dirs vendor)
Why can't I write inline tests in a package without users needing to install ``ppx_inline_test``?
=================================================================================================
If you came to OCaml from Rust and noticed that Dune has a feature for running
inline tests you might be wondering how to do the OCaml equivalent of:
.. code:: rust
// define a private function
fn foo() { ... }
// test the function right next to its definition
#[test]
fn test_of_foo() { ... }
That is, writing tests for private functions right next to the definition of
those functions. The :ref:`inline_tests` documentation describes how to do this
using the ``ppx_inline_test`` package; however, if you do this in your package,
then your package must `unconditionally` depend on the ``ppx_inline_test``
package. Opam has a notion of test-only dependencies (its ``with-test`` flag),
but you cannot use this with ``ppx_inline_test``. The consequence of this is
that anyone depending on your package is also transitively depending on
``ppx_inline_test`` as well as all of its dependencies.
The reason for this is OCaml code with preprocessor directives (such as those
used for inline tests with ``ppx_inline_test``) is technically not valid OCaml
code until it has been preprocessed. Unlike the cargo build system used for
Rust, Dune does not have a preprocessor built into it. Instead, it relies on
external tools (such as ``ppx_inline_test``) to parse the code and replace any
preprocessor directives with valid OCaml. Dune doesn't know how to parse OCaml
code at all so it can't even remove inline tests from the code in cases where
``ppx_inline_test`` is unavailable.
The blessed workaround for folks who want to use ``ppx_inline_test`` in their
packages but don't want to add it as a dependency is to create a new
(unreleased) package which contains all the tests. In the original package,
expose all the private APIs you intend to test via public modules named
something foreboding such as ``For_test`` so your users know not to rely on
their contents and then have the test package define tests that call your
"private" APIs through the ``For_test`` modules.

View file

@ -0,0 +1,392 @@
******************************
Dealing with Foreign Libraries
******************************
.. TODO(diataxis)
There are various types of content here:
- how-to guide for adding C stubs to an existing library
- tutorial for ctypes
- reference for ctypes field
The OCaml programming language can interface with libraries written in foreign
languages such as C. This section explains how to do this with Dune. Note that
it does not cover how to write the C stubs themselves, but this is covered by
the `OCaml manual <https://caml.inria.fr/pub/docs/manual-ocaml/intfc.html>`_.
More precisely, this section covers:
- How to add C/C++ stubs to an OCaml library
- How to pass specific compilation flags for compiling the stubs
- How to build a library with a foreign build system
In general, Dune has limited support for building source files written in
foreign languages. This support is suitable for most OCaml projects containing
C stubs, but it is too limited for building complex libraries written in C or
other languages. For such cases, Dune can integrate a foreign build system into
a normal Dune build.
Adding C/C++ Stubs to an OCaml Library
======================================
To add C stubs to an OCaml library, simply list the C files without the ``.c``
extension in the :doc:`(foreign_stubs) </reference/foreign-stubs>` field. For
instance:
.. code:: dune
(library
(name mylib)
(foreign_stubs (language c) (names file1 file2)))
You can also add C++ stubs to an OCaml library by specifying
``(language cxx)`` instead.
Dune is currently not flexible regarding the extension of the C/C++ source
files. They have to be ``.c`` for C files and ``.cpp``, ``.cc`` or ``.cxx`` for
C++ files. If you have source files with other extensions and you want to build
them with Dune, you need to rename them first. Alternatively, you can use the
:ref:`foreign build sandboxing <foreign-sandboxing>` method described below.
Header Files
------------
C/C++ source files may include header files in the same directory as the C/C++
source files or in the same directory group when using
:doc:`/reference/dune/include_subdirs`.
The header files must have the ``.h`` extension.
Installing Header Files
-----------------------
It is sometimes desirable to install header files with the library. For that
you have two choices: install them explicitly with an
:doc:`/reference/dune/install` stanza or use the ``install_c_headers``
field of the :doc:`/reference/dune/library` stanza. This field takes a
list of header files names without the ``.h`` extension. When a library
installs header files, they are made visible to users of the library via the
include search path.
.. _ctypes-stubgen:
Stub Generation with Dune Ctypes
================================
Beginning in Dune 3.0, it's possible to use the ctypes_ field to generate
bindings for C libraries without writing any C code.
Note that Dune support for this feature is experimental and is not subject to
backward compatibility guarantees.
To use Dune ctypes stub generation, you must provide two OCaml modules: a "type
description" module for describing the C library types and constants, and a
"function description" module for describing the C library functions.
Additionally, you must list any C headers and a method for resolving build and
link flags.
If you're binding a library distributed by your OS, you can use the pkg-config_
utility to resolve any build and link flags. Alternatively, if you're using a
locally installed library or a vendored library, you can provide the flags
manually.
The "type description" module must define a functor named ``Types`` with
signature ``Ctypes.TYPE``. The "function description" module must define a
functor named ``Functions`` with signature ``Ctypes.FOREIGN``.
A Toy Example
-------------
To begin, you must declare the ``ctypes`` extension in your ``dune-project``
file:
.. code:: dune
(lang dune 3.20)
(using ctypes 0.3)
Next, here is a ``dune`` file you can use to define an OCaml program that binds
a C system library called ``libfoo``, which offers ``foo.h`` in a standard
location.
.. code:: dune
(executable
(name foo)
(libraries core)
; ctypes backward compatibility shims warn sometimes; suppress them
(flags (:standard -w -9-27))
(ctypes
(external_library_name libfoo)
(build_flags_resolver pkg_config)
(headers (include "foo.h"))
(type_description
(instance Types)
(functor Type_description))
(function_description
(concurrency unlocked)
(instance Functions)
(functor Function_description))
(generated_types Types_generated)
(generated_entry_point C)))
This field will introduce a module named ``C`` into your project, with the
sub-modules ``Types`` and ``Functions`` that will have your fully-bound C
types, constants, and functions.
Given ``libfoo`` with the C header file ``foo.h``:
.. code:: c
#define FOO_VERSION 1
int foo_init(void);
int foo_fnubar(char *);
void foo_exit(void);
Your example ``type_description.ml`` file is:
.. code:: ocaml
open Ctypes
module Types (F : Ctypes.TYPE) = struct
open F
let foo_version = constant "FOO_VERSION" int
end
Your example ``function_description.ml`` file is:
.. code:: ocaml
open Ctypes
(* This Types_generated module is an instantiation of the Types
functor defined in the type_description.ml file. It's generated by
a C program that Dune creates and runs behind the scenes. *)
module Types = Types_generated
module Functions (F : Ctypes.FOREIGN) = struct
open F
let foo_init = foreign "foo_init" (void @-> returning int)
let foo_fnubar = foreign "foo_fnubar" (string_opt @-> returning int)
let foo_exit = foreign "foo_exit" (void @-> returning void)
end
Finally, the entry point of your executable named above, ``foo.ml``,
demonstrates how to access the bound C library functions and values:
.. code:: ocaml
let () =
if (C.Types.foo_version <> 1) then
failwith "foo only works with libfoo version 1";
match C.Functions.foo_init () with
| 0 ->
C.Functions.foo_fnubar "fnubar!";
C.Functions.foo_exit ()
| err_code ->
Printf.eprintf "foo_init failed: %d" err_code;
;;
From here, one only needs to run ``dune build ./foo.exe`` to generate the stubs
and build and link the example ``foo.exe`` program.
Complete information about the ``ctypes`` combinators used above is available
at the ctypes_ project.
Ctypes Field Reference
------------------------
The ``ctypes`` field can be used in any ``executable(s)`` or ``library``
stanza.
.. code:: dune
((executable|library)
...
(ctypes
(external_library_name <package-name>)
(type_description
(instance <module-name>)
(functor <module-name>))
(function_description
(instance <module-name>)
(functor <module-name>)
<optional-function-description-fields>)
(generated_entry_point <module-name>)
<optional-ctypes-fields>)
)
- ``type_description``: the ``functor`` module is a description of the C
library types and constants written in the ``ctypes`` domain-specific
language you wish to bind. The ``instance`` module is the name of the
instantiated functor, inserted into the top-level of the
``generated_entry_point`` module.
- ``function_description``: the ``functor`` module is a description of the C
library functions written in the ``ctypes`` domain-specific language you wish
to bind. The ``instance`` module is the name of the instantiated functor,
inserted into the top-level of the ``generated_entry_point`` module. The
``function_description`` field can be repeated. This is useful if you need
to specify sets of functions with different concurrency policies (see below).
The instantiated types described above can be accessed from the function
descriptions by referencing them as the module specified in optional
``generated_types`` field.
``<optional-ctypes-fields>`` are:
- ``(build_flags_resolver <pkg_config|vendored-field>)`` tells Dune how to
compile and link your foreign library. Specifying ``pkg_config`` will use
the pkg-config_ tool to query the compilation and link flags for
``external_library_name``. For vendored libraries, provide the build and link
flags using ``vendored`` field. If ``build_flags_resolver`` is not
specified, the default of ``pkg_config`` will be used.
- ``(generated_types <module-name>)`` is the name of an intermediate module. By
default, it's named ``Types_generated``. You can use this module to access
the types defined in ``Type_description`` from your ``Function_description``
module(s).
- ``(generated_entry_point <module-name>)`` is the name of a generated module
that your instantiated ``Types`` and ``Functions`` modules will instantiated
under. We suggest calling it ``C``.
- Headers can be added to the generated C files:
- ``(headers (include "include1" "include2" ...))`` adds ``#include
<include1>``, ``#include <include2>``. It uses the
:doc:`reference/ordered-set-language`.
- ``(headers (preamble <preamble>)`` adds directly the preamble. Variables
can be used in ``<preamble>`` such as ``%{read: }``.
- Since the Dune's ``ctypes`` feature is still experimental, it could be useful to
add additional dependencies in order to make sure that local
headers or libraries are available: ``(deps <deps-conf list>)``. See
:doc:`concepts/dependency-spec` for more details.
``<optional-function-description-fields>`` are:
- ``(concurrency <sequential|unlocked|lwt_jobs|lwt_preemptive>)`` tells ``ctypes
stubgen`` whether to call your C functions with the runtime lock held or
released. These correspond to the ``concurrency_policy`` type in the
``ctypes`` library. If ``concurrency`` is not specified, the default of
``sequential`` will be used.
- ``(errno_policy <ignore_errno|return_errno>)`` specifies the errno_policy_
passed to the code generator. With ``ignore_errno``, the errno variable is
not accessed or returned by function calls. With ``return_errno``, all
functions will return the tuple ``(retval, errno)``.
``<vendored-field>`` is:
- ``(vendored (c_flags <flags>) (c_library_flags <flags>))`` provide the build
and link flags for binding your vendored code. You must also provide
instructions in your ``dune`` file on how to build the vendored foreign
library; see the :doc:`/reference/dune/foreign_library` stanza. Usually
the ``<flags>`` should contain ``:standard`` in order to add the default
flags used by the OCaml compiler for C files
:doc:`/reference/dune-project/use_standard_c_and_cxx_flags`.
.. _foreign-sandboxing:
Foreign Build Sandboxing
========================
When the build of a C library is too complicated to express in the
Dune language, it's possible to simply *sandbox* a foreign
build. Note that this method can be used to build other things, not
just C libraries.
To do that, follow the following procedure:
- Put all the foreign code in a sub-directory
- Tell Dune not to interpret configuration files in this directory via an
:doc:`/reference/dune/data_only_dirs` stanza
- Write a custom rule that:
- depends on this directory recursively via :ref:`source_tree <source_tree>`
- invokes the external build system
- copies the generated files
- the C archive ``.a`` must be built with ``-fpic``
- the ``libfoo.so`` must be copied as ``dllfoo.so``, and no ``libfoo.so``
should appear, otherwise the dynamic linking of the C library will be
attempted. However, this usually fails because the ``libfoo.so`` isn't available at
the time of the execution.
- *Attach* the C archive files to an OCaml library via
:doc:`/reference/foreign-archives`.
For instance, let's assume that you want to build a C library
``libfoo`` using ``libfoo``'s own build system and attach it to an
OCaml library called ``foo``.
The first step is to put the sources of ``libfoo`` in your project,
for instance in ``src/libfoo``. Then tell Dune to consider
``src/libfoo`` as raw data by writing the following in ``src/dune``:
.. code:: dune
(data_only_dirs libfoo)
The next step is to setup the rule to build ``libfoo``. For this,
writing the following code ``src/dune``:
.. code:: dune
(rule
(deps (source_tree libfoo))
(targets libfoo.a dllfoo.so)
(action
(no-infer
(progn
(chdir libfoo (run make))
(copy libfoo/libfoo.a libfoo.a)
(copy libfoo/libfoo.so dllfoo.so)))))
We copy the resulting archive files to the top directory where they can be
declared as ``targets``. The build is done in a
:doc:`/reference/actions/no-infer` action because ``libfoo/libfoo.a`` and
``libfoo/libfoo.so`` are dependencies produced by an external build system.
The last step is to attach these archives to an OCaml library as follows:
.. code:: dune
(library
(name bar)
(foreign_archives foo))
Then, whenever you use the ``bar`` library, you'll also be able to
use C functions from ``libfoo``.
Limitations
-----------
When using the sandboxing method, the following limitations apply:
- The build of the foreign code will be sequential
- The build of the foreign code won't be incremental
Both these points could be improved. If you're interested in helping make this
happen, please let the Dune team know and someone will guide you.
Real Example
------------
The `re2 project <https://github.com/janestreet/re2>`_ uses this method to
build the ``re2`` C library. You can look at the file ``re2/src/re2_c/dune`` in
this project to see a full working example.
.. _ctypes: https://github.com/ocamllabs/ocaml-ctypes
.. _pkg-config: https://www.freedesktop.org/wiki/Software/pkg-config/
.. _errno_policy: https://ocaml.org/p/ctypes/0.20.1/doc/Cstubs/index.html#type-errno_policy

View file

@ -0,0 +1,12 @@
Getting Started and Core Concepts
=================================
These documents should be the first ones read by new Dune users. They explain
what Dune is, how it works, and how to use it.
.. toctree::
:maxdepth: 2
../overview
../quick-start
../usage

View file

@ -0,0 +1,149 @@
************
Goal of Dune
************
.. TODO(diataxis)
This is an important page and should go in a sort of meta section together
with the history of the project and the glossary for example.
The Dune project strives to provide the best possible build tool for the entire
OCaml community, including individual developers contributing to open source
projects in their free time, larger companies (such as Jane Street), and
communities, like MirageOS and Irmin. Additionally, we aim to provide the same
features for other neighbouring communities, such as Coq and possibly
Reason/Bucklescript, in the future.
We haven't reached this goal yet, as Dune still requires development in some
areas to be such a tool, but we're steadily working towards that goal. On a
practical level, a few boxes must be checked, and a considerable number of
details needs to be sorted out. At a high-level, we think a tool that works for
everyone in the OCaml community should at least:
1. have excellent backward compatibility properties
2. have a robust and scalable core
3. remain a no-brainer dependency
4. remain accessible
5. have very good support for the OCaml language
6. be extensible
At this point, we've done a good job at 1, 3, 4, and 5. We're currently working
towards 2 and are doing the preparatory work for 6. Once all these boxes have
been checked, we'll consider the Dune project complete.
Below, we develop each point and give some
insights into our current and future focuses.
Have Excellent Backward-Compatibility Properties
================================================
In an open source community, two types of groups exist: those with enough
resources to continuously bring their projects up-to-date and those who work on
them in their free time. The latter obviously can't provide the same level of
continuous support and updates as the former.
From the Dune point of view, we consider every released project with ``dune``
files a precious piece that will potentially never change, so we discourage
changing Dune in a way where it could no longer understand a released project.
Of course, we can't give a 100% guarantee that Dune will always behave exactly
the same. That would be unrealistic and would prevent the project from moving
forward. In order to provide good backward-compatibility properties while still
keeping the project fresh and dynamic, we need to properly delimit, document,
and version the set of behaviours on which users rely. For this to be
manageable, the surface Dune API must remain small.
A distinguishing feature of Dune allows the user to declare which version of
the ``dune`` tool they wrote the project against, and ``dune`` will morph
itself to behave the same as this version of the ``dune`` binary, even if it's
a newer version. As a result, a recent ``dune`` binary version can understand a
wide range of Dune projects written against many different versions of Dune,
and while we strictly follow `semantic versioning`_, new major versions of Dune
effectively introduce very few breaking changes. Most projects don't need upper
bounds on Dune.
This guarantee is of course limited to documented behaviours.
.. _semantic versioning: https://semver.org/
Have a Robust and Scalable Core
===============================
Tech companies tend be fond of big mono repositories, so for compatibility,
Dune must consume large repositories without blinking. It not only needs to
build fast, but more importantly, it must not impede fast feedback during
development, no matter the size of the repository.
Note that we'll only test Dune on repositories as large as people participating
in Dune's development require. Currently, the largest user is Jane Street. If
someone wanted to use Dune on much larger repositories than the ones used at
Jane Street, and this required a significant amount of effort on Dune, this
wouldn't be considered unless we get some help to do so and we can keep the
other promises.
In particular, while making Dune scalable, we must also ensure Dune doesn't
turn into a monster, because no one wants to force their users to install a
monster to build their project. This brings us to the next point of Dune being
a no-brainer dependency:
Remain a No-Brainer Dependency
==============================
Dune is a hard dependency of any Dune project. Anyone using Dune to develop
their project will have to ask their user to install Dune. For this reason, it
is very important to keep Dune as lean as possible.
We need to be careful when we start relying on an external piece of software or
when we introduce new concepts. We must not introduce duplication or useless
stuff. The overall projects has to remain lean.
It's also important to keep Dune as easy to install as possible. Currently, the
only requirement to build Dune is a working OCaml compiler. Nothing else is
required, not even a shell, and we should keep it this way.
Remain Accessible
=================
Since Dune aims to be the best possible tool for the whole OCaml community,
it's important to keep Dune accessible. Getting started and learning Dune
should be straightforward.
For that purpose, when designing the language (the command line interface or
the documentation), we must take on the new-user perspective, one who just
discovered Dune and its features, because Dune should be suitable for everyone!
It also needs to provide advanced and more complex features for expert users.
However, the documentation should always flow from the simpler concepts and
common tasks to the more complex ones, even if the simpler features can be
explained as instances of the more general ones.
Have Excellent Support for the OCaml Language
=============================================
There are many, many build systems out there. Dune stands out because it
primarily targets the OCaml community, so Dune must come with excellent support
for the OCaml language and OCaml projects in general.
If it didn't, Dune would just be yet another generic build system.
Perhaps in the future some of the general build system will take over, and Dune
might just become a plugin in this system. It could even disappear into the
language, if the compiler gains significant high-level features. But for now,
Dune is a standalone build system that primarily serves the OCaml community's
needs, and to the extent that is reasonably possible, the needs of other
functional language communities.
Be Extensible
=============
No matter the quality of the OCaml language's support, it will never be enough
to cover every single project need. For this reason, Dune must provide some
form of openness for projects with need that don't completely fit in the Dune
model.
In the long run, extensibility tends to obstruct innovation, and we should
always strive to ensure that we cover all the general needs of the main Dune
language; however, we'll always need an escape hatch for Dune to remain a
practical choice.
It's pretty clear that extensibility must be done via OCaml code, and currently
it's a bit difficult to use OCaml as a proper extension language, though some
work is being done to help on that front.

View file

@ -0,0 +1,800 @@
****************************
Working on the Dune Codebase
****************************
.. TODO(diataxis)
This can be folded either in a meta section or as an how-to guide.
This section gives guidelines for working on Dune itself. Many of these are
general guidelines specific to Dune. However, given that Dune is a large project
developed by many different people, it's important to follow these guidelines in
order to keep the project in a good state and pleasant to work on for everybody.
Dependencies
============
To create a directory-local opam switch with the dependencies necessary to build the tests, run:
.. code:: console
$ make dev-switch
This can also be used to keep the switch updated when dependencies change.
The ``Makefile`` also has a ``make dev-deps`` which will install just the
dependencies used by tests. These are marked ``{ with-dev-setup }`` in Dune's
opam file.
Bootstrapping
=============
Dune uses Dune as its build system, which requires some specific commands to
work. Running ``make dev`` bootstraps (if necessary) and runs ``./dune.exe
build @install``.
If you want to just run the bootstrapping step itself, build the ``bootstrap``
phony target with
.. code:: console
$ make bootstrap
You can always rerun this to bootstrap again.
Once you've bootstrapped Dune, you should be using it to develop Dune itself.
Here are the most common commands you'll be running:
.. code:: console
# to make sure everything compiles:
$ ./dune.exe build @check
# run all the tests
$ ./dune.exe runtest
# run a particular cram foo.t:
$ ./dune.exe build @foo
Note that tests are currently written for version 5.3.0 of the OCaml compiler.
Some tests depend on the specific wording of compilation errors which can change
between compiler versions, so to reliably run the tests make sure that
``ocaml.5.3.0`` is installed. The ``TEST_OCAMLVERSION`` in the ``Makefile`` at
the root of the Dune repo contains the current compiler version for which tests
are written.
.. seealso:: :doc:`explanation/bootstrap`
Writing Tests
=============
Most of our tests are written as expectation-style tests. While creating such
tests, the developer writes some code and then lets the system insert the output
produced during the code execution. The system puts it right next to the code in
the source file.
Once you write and commit a test, the system checks that the captured output
matches the one produced by a fresh code execution. When the two don't match,
the test fails. The system then displays a diff between what was expected and
what the code produced.
We write both our unit tests and integration tests in this way. For unit tests,
we use the ppx_expect_ framework, where we introduce tests via
``let%expect_test``, and ``[%expect ...]`` nodes capture expectations:
.. code:: ocaml
let%expect_test "<test name>" =
print_string "Hello, world!";
[%expect {|
Hello, world!
|}]
For integration tests, we use a system similar to `Cram tests
<https://bitheap.org/cram/>`_ for testing shell commands and their behavior:
.. code:: console
$ echo 'Hello, world!'
Hello, world!
$ false
[1]
$ cat <<EOF
> multi
> line
> EOF
multi
line
.. _ppx_expect: https://github.com/janestreet/ppx_expect
.. seealso::
`actions_to_sh tests <https://github.com/ocaml/dune/blob/3.12.2/test/expect-tests/dune_engine/action_to_sh_tests.ml>`_
An example of expect-tests.
`mdx-stanza/locks.t <https://github.com/ocaml/dune/blob/3.12.2/test/blackbox-tests/test-cases/mdx-stanza/locks.t>`_
An example of Cram test.
When running Dune inside tests, the ``INSIDE_DUNE`` environment variable is set.
This has the following effects:
* Change the default root detection behaviour to use the current directory
rather than the top most ``dune-project`` / ``dune-workspace`` file.
* Be less verbose when Dune outputs a user message.
* Error reporting is deterministic by default.
* Prefer not to use a diff program for displaying diffs.
This list is not exhaustive and may change in the future. In order to find the
exact behaviour, it is recommended to search for ``INSIDE_DUNE`` in the
codebase.
Guidelines
----------
As with any long running software project, code written by one person will
eventually be maintained by another. Just like normal code, it's important to
document tests, especially since test suites are most often composed of many
individual tests that must be understood on their own.
A well-written test case should be easily understood. A reader should be able to
quickly understand what property the test is checking, how it's doing it, and
how to convince oneself that the test outcome is the right one. A well-written
test makes it easier for future maintainers to understand the test and react
when the test breaks. Most often, the code will need to be adapted to preserve
the existing behavior; however, in some rare cases, the test expectation will
need to be updated.
It's crucial that each test case makes its purpose and logic crystal clear, so
future maintainers know how to deal with it.
When writing a test, we generally have a good idea of what we want to test.
Sometimes, we want to ensure a newly developed feature behaves as expected.
Other times, we want to add a reproduction case for a bug reported by a user to
ensure future changes won't reintroduce the faulty behaviour. Just like when
programming, we turn such an idea into code, which is a formal language that a
computer can understand. While another person reading this code might be able to
follow and understand what the code does step by step, it isn't clear that
they'll be able to reconstruct the original developer's idea. Even worse, they
might understand the code in a completely different way, which would lead them
to update it incorrectly.
Setting Up Your Development Environment Using Nix
=================================================
You can use Nix to setup the development environment. This can be done by
running ``nix develop`` in the root of the Dune repository.
Note that Dune only takes OCaml as a dependency and the rest of the dependencies
are used when running the test suite.
Running ``nix develop`` can take a while the first time, therefore it is
advisable to save the state in a profile.
```sh
nix develop --profile nix/profiles/dune
```
And to load the profile:
```sh
nix develop nix/profiles/dune
```
This profile might need to be updated from time to time, since the bootstrapped
version of Dune may become stale. This can be done by running the first command.
We have the following shells for specific tasks:
- ``nix develop .#slim`` for a dev environment with fewer dependencies that is
faster to build.
- ``nix develop .#slim-melange``: same as above, but additionally includes the
``melange`` and ``mel`` packages
- Building documentation requires ``nix develop .#doc``.
- For running the Coq tests, you can use ``nix develop .#coq``. NB: Coq native
is not currently installed; this will cause some of the tests to fail. It's
currently better to fallback to opam in this case.
Releasing Dune
==============
Dune's release process relies on dune-release_. Make sure you install and
understand how this software works before proceeding. Publishing a release
consists of two steps:
* Updating ``CHANGES.md`` to reflect the version being published.
* Running ``$ make opam-release`` to create the release tarball. Then publish it
to GitHub and submit it to opam.
.. _dune-release: https://github.com/tarides/dune-release
Major & Feature Releases
------------------------
Given a new version ``x.y.z``, a major release increments ``x``, and a feature
release increments ``y``. Such a release must be done from the ``main`` branch.
Once you publish the release, be sure to publish a release branch named ``x.y``.
Point Releases
--------------
Point releases increment the ``z`` in ``x.y.z``. Such releases are done from the
respective ``x.y`` branch of the respective feature release. Once released, be
sure to update ``CHANGES.md`` in the ``main`` branch.
Adding Stanzas
==============
Adding new stanzas is the most natural way to extend Dune with new features.
Therefore, we try to make this as easy as possible. The minimal amount of steps
to add a new stanza is:
- Extend ``Stanza.t`` with a new constructor to represent the new stanza
- Modify ``Dune_file`` to parse the Dune language into this constructor
- Modify the rules to interpret this stanza into rules, usually done in
``Gen_rules``
Versioning
----------
Dune is incredibly strict with versioning of new features, modifications visible
to the user, and changes to existing rules. This means that any added stanza
must be guarded behind the version of the Dune language in which it was
introduced. For example:
.. code:: ocaml
; ( "cram"
, let+ () = Dune_lang.Syntax.since Stanza.syntax (2, 7)
and+ t = Cram_stanza.decode in
[ Cram t ] )
Here, Dune 2.7 introduced the Cram stanza, so the user must enable
``(lang dune 2.7)`` in their ``dune`` project file to use it.
``since`` isn't the only primitive for making sure that versions are respected.
See ``Dune_lang.Syntax`` for other commonly used functions.
Experimental & Independent Extensions
-------------------------------------
Sometimes, Dune's versioning policy is too strict. For example, it doesn't work
in the following situations:
- When most Dune independent extensions only exist inside Dune for development
convenience, e.g., build rules for Coq. Such extensions would like to impose
their own versioning policy.
- When experimental features cannot guarantee Dune's strict backwards
compatibility. Such features may dropped or modified at any time.
To handle both of these use cases, Dune allows the definition of new languages
(with the same syntax). These languages have their own versioning scheme and
their own stanzas (or fields). In Dune itself, ``Syntax.t`` represents such
languages. Here's an example of how the Coq syntax is defined:
.. code:: ocaml
let coq_syntax =
Dune_lang.Syntax.create ~name:"coq" ~desc:"the coq extension (experimental)"
[ ((0, 1), `Since (1, 9)); ((0, 2), `Since (2, 5)) ]
The list provides which versions of the syntax are provided and which version of
Dune introduced them.
Such languages must be enabled in the ``dune`` project file separately:
.. code:: dune
(lang dune 3.20)
(using coq 0.8)
If such extensions are experimental, it's recommended that they pass
``~experimental:true``, and that their versions are below 1.0.
We also recommend that such extensions introduce stanzas or fields of the form
``ext_name.stanza_name`` or ``ext_name.field_name`` to clarify which extensions
provide a certain feature.
Dune Rules
==========
Creating Rules
--------------
A Dune rule consists of 3 components:
- *Dependencies* that the rule may read when executed (files, aliases, etc.),
described by ``'a Action_builder.t`` values.
- *Targets* that the rule produces (files and/or directories), described by
``'a Action_builder.With_targets.t'`` values.
- *Action* that Dune must execute (external programs, redirects, etc.). Actions
are represented by ``Action.t`` values.
Combined, one needs to produce an ``Action.t Action_builder.With_targets.t``
value to create a rule. The rule may then be added by ``Super_context.add_rule``
or a related function.
To make this maximally convenient, there's a ``Command`` module to make it
easier to create actions that run external commands and describe their targets
and dependencies simultaneously.
Loading Rules
-------------
Dune rules are loaded lazily to improve performance. Here's a sketch of the
algorithm that tries to load the rule that generates some target file ``t``.
- Get the directory that contains ``t``. Call it ``d``.
- Load all rules in ``d`` into a map from targets in that directory to rules
that produce it.
- Look up the rule for ``t`` in this map.
To adhere to this loading scheme, we must generate our rules as part of the
callback that creates targets in that directory. See the ``Gen_rules`` module
for how this callback is constructed.
Documentation
=============
User documentation lives in the ``./doc`` directory.
In order to build the user documentation, you must install python-sphinx_,
sphinx-design_, sphinx-copybutton_, myst-parser_, and furo_.
Build the documentation with
.. code:: console
$ make doc
For automatically updated builds, you can install sphinx-autobuild_, and run
.. code:: console
$ make livedoc
.. seealso::
``doc/requirements.txt`` for an always up-to-date list of packages to install
.. _python-sphinx: http://www.sphinx-doc.org/en/master/usage/installation.html
.. _sphinx-design: https://sphinx-design.readthedocs.io/en/latest/index.html
.. _sphinx-copybutton: https://sphinx-copybutton.readthedocs.io/en/latest/index.html
.. _sphinx-autobuild: https://pypi.org/project/sphinx-autobuild/
.. _myst-parser: https://myst-parser.readthedocs.io/en/latest/
.. _furo: https://sphinx-themes.org/sample-sites/furo/
Nix users may drop into a development shell with the necessary dependencies for
building docs ``nix develop .#doc``.
Structure
---------
For structure, we use the `Diátaxis framework`_. The core idea is that
documents should fit in one of the following categories:
.. _Diátaxis framework: https://diataxis.fr/
- Tutorials, focused on learning
- How-to guides, focused on task solving
- Reference, focused on information
- Explanations, focused on understanding
Most features do not need a document in each category, but the important part
is that a single document should not try to be in several categories at once.
ReStructured Text
-----------------
For code blocks containing Dune files, use ``.. code:: dune`` and indent with 3
spaces. Use formatting consistent with how Dune formats Dune files (most
importantly, do not leave orphan closing parentheses).
In a document that only contains Dune code blocks, it is possible to use the
``.. highlight:: dune`` directive to have ``dune`` be the default lexer, and
then it is possible to use the ``::`` shortcut to end a line with a single
``:`` and start a code block. See the source of
:doc:`reference/lexical-conventions` for an example.
For links, prefer references that use ``:doc:`` (link to a whole document) or
``:term:`` (link to a definition in the glossary) to ``:ref:``.
Use the right lexers:
- ``dune`` for ``dune`` and related files
- ``opam`` for opam files
- ``console`` for shell sessions and commands (start with ``$``)
- ``cram`` for cram tests
Style
-----
Use American spelling.
Use `Title Case`_ for titles and headings (every word except "little words"
like of, and, or, etc.).
.. _Title Case: https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case
For project names, use the following capitalization:
- **Dune** is the project, ``dune`` is the command. Files are called ``dune``
files.
- ``dune-project`` should always be written in monospace.
- **OCaml**
- **OCamlFormat**, and ``ocamlformat`` is the command.
- ``odoc``, always in monospace.
- **opam**. Can be capitalised as Opam at the beginning of sentences only, as
the official name is formatted opam. Even in titles, headers, and subheaders,
it should be all lowercase: opam. The command is ``opam``.
- **esy**. Can be capitalised as Esy.
- **Nix**. The command is ``nix``.
- **Js_of_ocaml** can be abbreviated **JSOO**.
- **MDX**, rather than mdx or Mdx
- **PPX,** rather than ppx or Ppx; ``ppxlib``
- **UTop,** rather than utop or Utop.
Vendoring
=========
Dune vendors some code that it uses internally. This is done to make installing
Dune easy as it requires nothing but an OCaml compiler as well as to prevent
circular dependencies. Before vendoring, make sure that the license of the code
allows it to be included in Dune.
The vendored code lives in the ``vendor/`` subdirectory. To vendor new code,
create a shell script ``update-<library>.sh``, that will be launched from the
``vendor/`` folder to download and unpack the source and copy the necessary
source files into the ``vendor/<library>`` folder. Try to keep the amount of
source code imported minimal, e.g., leave out ``dune-project`` files. For the
most part, it should be enough to copy ``.ml`` and ``.mli`` files. Make sure to
also include the license if there is such a file in the code to be vendored to
stay compliant.
As these sources get vendored not as subprojects but parts of Dune, you need
to deal with ``public_name``. The preferred way is to remove the
``public_name`` and only use the private name. If that is not possible, the
library can be renamed into ``dune-private-libs.<library>``.
To deal with the modified ``dune`` files in ``update-<library>.sh`` scripts,
you can commit the modified files to ``dune`` and make the
``update-<library>.sh`` script to use ``git checkout`` to restore the ``dune``
file.
For larger modifications, it is better to fork the upstream project in the
ocaml-dune_ organisation and then vendor the forked copy in Dune. This makes
the changes better visible and easier to update from upstream in the long run
while keeping our custom patches in sync. The changes to the ``dune`` files are
to be kept in the Dune repository.
It is preferable to cut out as many dependencies as possible, e.g., ones that
are only necessary on older OCaml versions or build-time dependencies.
.. _ocaml-dune: https://github.com/ocaml-dune/
General Guidelines
==================
Dune has grown to be a fairly large project that over time has acquired its own
style. Below is an attempt to enumerate some important points of this style.
These rules aren't axioms and we may break them when justified. However, we
should have a good reason in mind when breaking them. Finally, the list isn't
exhaustive by any means and is subject to change. Feel free to discuss anything
in particular with the team.
- Parameter signatures should be self descriptive. Use labels when the types
alone aren't sufficient to make the signature readable.
Bad:
.. code:: ocaml
val display_name : string -> string -> _ Pp.t
Good:
.. code:: ocaml
val display_name : first_name:string -> last_name:string -> _ Pp.t
- Avoid type aliases when possible. Yes, they might make some type signatures
more readable, but they make the code harder to grep and make Merlin's
inferred types more confusing.
- Every ``.ml`` file must have a corresponding ``.mli``. The only exception to
this rule is ``.ml`` files with only type definitions.
- Do not write ``.mli`` only modules. They offer no advantages to ``.ml``
modules with type definitions and one cannot define exceptions in ``.mli``
only modules
- Every module should have toplevel documentation that describes the module
briefly. This is a good place to discuss its purpose, invariants, etc.
- Keep interfaces short & sweet. The less functions, types, etc., there are, the
easier it is for users to understand, use, and ultimately modify the
interface correctly. Instead of creating elaborate interfaces with the hope
of future-proofing every use case, embrace change and make it easier to throw
out or replace the interface.
Ideally the interface should have one obvious way to use it. A particularly
annoying violator of this principle is the "logic-less chain of functions"
helper. For example:
.. code:: ocaml
let foo t = bar t |> baz
If ``bar`` and ``baz`` are already public, then there's no need to add yet
another helper to save the caller a line of code.
- Define bindings as close to their use site as possible. When they're far
apart, reading code requires scrolling and IDE tools to understand the code.
Bad:
.. code:: ocaml
let dir = .. in
(* 50 odd lines or so that don't use [dir] *)
f dir
Good:
.. code:: ocaml
let dir = .. in
f dir
- A corollary to the previous guideline: keep the scope of bindings as small as
possible.
Bad:
.. code:: ocaml
let x1 = f foo in let x2 = f bar in
let y1 = g foo in let y2 = g bar in
let dx = x2 -. x1 in
let dy = y2 -. y1 in
dx^2 +. dy^2
Good:
.. code:: ocaml
let dx =
let x1 = f foo in let x2 = f bar in
x2 -. x1
in
let dy =
let y1 = g foo in let y2 = g bar in
y2 -. y1
in
dx^2 +. dy^2
- Prefer ``Code_error.raise`` instead of ``assert false``. The reader often has
no idea what invariant is broken by the ``assert false``. Kindly describe it
to the reader in the error message.
- Avoid meaningless names like ``x``, ``a``, ``b``, ``f``. Try to find a more
descriptive name or just inline it altogether.
- If a module ``Foo`` has a module type ``Foo.S`` and you'd like to avoid
repeating its definition in the implementation and the signature, introduce
an ``.ml``-only module ``Foo_intf`` and write the ``S`` only once in there.
- Instead of introducing a type ``foo``, consider introducing a module ``Foo``
with a type ``t``. This is often the place to put functions related to
``foo``.
- Avoid optional arguments. They increase brevity at the expense of readability
and are annoying to grep. Furthermore, they encourage callers not to think
at all about these optional arguments even if they often should.
- Avoid qualifying modules when accessing fields of records or constructors.
Avoid it altogether if possible, or add a type annotation if
necessary.
Bad:
.. code:: ocaml
let result = A.b () in
match result.A.field with
| B.Constructor -> ...
Good:
.. code:: ocaml
let result : A.t = A.b () in
match (result.field : B.t) with
| Constructor -> ...
- When constructing records, use the qualified names in in the record. Do not
open the record. The local open syntax pulls in all kinds of names from the
opened module and might shadow the values that you're trying to put into the
record, leading to difficult debugging.
Bad; if ``A.value`` exists, it will pick that over ``value``:
.. code:: ocaml
let value = 42 in
let record = A.{ field = value; other } in
...
Good:
.. code:: ocaml
let value = 42 in
let record = { A.field = value; other } in
...
- Stage functions explicitly with the ``Staged`` module.
- Do not raise ``Invalid_argument``. Instead, raise with ``Code_error.raise``
which allows to attach more informative payloads than just strings.
- When ignoring the value of a let binding ``let _ = ...``, we add type
annotations to the ignored value ``let (_ : t) = ...``. We do this convention
because:
* We need to make sure we never ignore ``Fiber.t`` accidentally. Functions that
return ``Fiber.t`` are always free of side effects so we need to bind on the
result to force the side effect.
* Whenever a function is changed to return an error via its return value, we
want the compiler to notify all the callers that need to be updated.
- To write a ``to_dyn`` function on a record type, use the following pattern. It
ensures that the pattern matching will break when a field is added. To ignore
a field, add ``; d = _``, not ``; _``.
.. code:: ocaml
let to_dyn {a; b; c} =
Dyn.record
[ ("a", A.to_dyn a)
; ("b", B.to_dyn b)
; ("c", C.to_dyn c)
]
- To write an equality function, use the following pattern (this applies to
other kinds of binary functions). The same remarks about about pattern
matching and ignoring fields apply.
.. code:: ocaml
let equal {a; b; c} t =
A.equal a t.a &&
B.equal b t.b &&
C.equal c t.c
Subjective Style Points
-----------------------
There's some stylistic decisions we made that don't have logical justification
and are basically a matter of taste. Nevertheless, it's useful to follow them
to keep the code consistent.
- Match patterns should be sorted by the length of their RHS when possible.
Keep the shorter clauses near the top.
- If a module ``Foo`` defines a type ``t``, all functions that take ``t`` in
this module should have ``t`` as their first argument. This is the "t comes
first" rule.
- Do not mix ``|>`` and ``@@`` in the same expression.
- Introduce bindings that will allow opportunities for record or label punning.
- Do not write inverted if-else expressions.
Bad:
.. code:: ocaml
(* try reading this out loud without short circuiting your brain *)
if not x then foo else bar
Good:
.. code:: ocaml
if x then bar else foo
- We prefer snake_casing identifiers. This includes the names of modules and
module types.
- Avoid qualifying constructors and record fields. Instead, add type
annotations to the type being matched on or being constructed, e.g.,
Bad:
.. code:: ocaml
let foo = Command.Args.S []
Good:
.. code:: ocaml
let (foo : _ Command.Args.t) = S []
Benchmarking
============
Dune Bench
----------
You can benchmark Dune's performance by running ``make bench``. This will run a
subset of the Duniverse. If you are running the bench locally, make sure that
you bootstrap since that is the executable that the bench will run.
The bench will build a specially selected portion of the Duniverse once, called
a "clean build". Afterwards, the build will be run 5 more times and are termed
the "Null builds".
In each run of the CI, there will be an ``ocaml-benchmarks`` status in the
summary. Clicking ``Details`` will show a bench report.
The report contains the following information:
- The build times for Clean and Null builds
- The size of the ``dune.exe`` binary
- User CPU times for the Clean and Null builds
- System CPU times for the Clean and Null builds
- All the garbage collection stats apart from "forced collections" for Clean and
Null builds
Pull requests that add new libraries are likely to increase the size of the dune
binary.
Performance gains in Dune can be observed in the Clean and Null build times.
Memory usage can be observed in the garbage collection stats.
Inline Benchmarks
-----------------
Certain performance-critical parts of Dune are benchmarked using the
``inline_benchmarks`` library. These benchmarks are run when running the tests.
Their outputs are currently not recorded and are only used to detect performance
regressions.
Build-Time Benchmarks
---------------------
We benchmark the build time of Dune in every PR. The times can be found here:
https://bench.ci.dev/ocaml/dune?worker=autumn&image=bench.Dockerfile
Melange Bench
-------------
We also benchmark a demo Melange project's build time:
https://ocaml.github.io/dune/dev/bench/
Formatting
==========
When changing the formatting configuration, it is possible to add the
reformatting commit to the :file:`.git-blame-ignore-revs` file. The commit will
disappear from blame views. It is also possible to configure ``git`` to have
the same behavior locally.
It is recommended to edit that file in a second PR, to make sure that the
referenced commit has not changed.
.. seealso::
`GitHub - Ignore commits in the blame view
<https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view>`_

View file

@ -0,0 +1,41 @@
How to Bundle Resources
=======================
This guide will show you how to configure Dune to generate modules with string resources
from other files in your project.
Folder Structure
----------------
.. code:: console
$ tree src
src
└── lib
   └── my_lib
   ├── dune
   └── resources
      └── site.css
Dune Configuration
------------------
See :doc:`/reference/actions/progn` and
:doc:`/reference/actions/with-outputs-to`.
.. code:: dune
(rule
(with-stdout-to
css.ml
(progn
(echo "let css = {|")
(cat resources/site.css)
(echo "|}"))))
Using the Bundled Resource
--------------------------
.. code:: ocaml
let () = Printf.printf "%s" Css.css

View file

@ -0,0 +1,87 @@
How to Set up Automatic Formatting
==================================
This guide will show you how to configure Dune so that it can check the formatting
of your source code.
Formatting is defined per project. This ensures that if a project is reused
elsewhere, its formatting configuration will not interfere.
Setting Up the Environment
--------------------------
First, let's open the ``dune-project`` file. Make sure that the version
specified in ``(lang dune X.Y)`` is at least ``2.0``. Most formatting
configuration happens in that file. If you want to format OCaml sources and
``dune`` files, you don't have anything to add. Otherwise, refer to the
:doc:`/reference/dune-project/formatting` stanza.
Next we need to install some code formatting tools. For OCaml code, this means
installing OCamlFormat_ with ``opam install ocamlformat``. Formatting ``dune``
files is built into Dune and does not require any extra tools. For Reason code,
this uses the ``refmt`` tool which is already installed if you are using Reason
syntax in your project. If your project uses a :term:`dialect`, a specific tool
might be required.
.. _ocamlformat: https://github.com/ocaml-ppx/ocamlformat
Using OCamlFormat requires some configuration. Take note of the version
returned by ``ocamlformat --version`` (let's name that ``X.Y.Z``) and create an
``.ocamlformat`` file in the same directory as ``dune-project`` with the
following contents:
.. code::
version=X.Y.Z
profile=default
The ``version`` line is checked by OCamlFormat and ensures that everybody
contributing to the project uses the same version.
Note that you do not have to add ``ocamlformat`` to your opam files.
Running the Formatters
----------------------
Run the ``dune build @fmt`` command. It will format the source files in the
corresponding project and display the differences:
.. code:: console
$ dune build @fmt
--- hello.ml
+++ hello.ml.formatted
@@ -1,3 +1 @@
-let () =
- print_endline
- "hello, world"
+let () = print_endline "hello, world"
Then it's possible to accept the correction by calling ``dune promote`` to
replace the source files with the corrected versions.
.. code:: console
$ dune promote
Promoting _build/default/hello.ml.formatted to hello.ml.
As usual with promotion, it's possible to combine these two steps by running
``dune build @fmt --auto-promote``. This command can also be shortened to
``dune fmt``. See :doc:`../concepts/promotion` for more details.
Setting Up Your CI
------------------
To check formatting in CI, the precise set up depends on the CI system used,
but in general it is easier to set up a dedicated job that just installs
``dune`` and the formatting tools, rather than doing that as part of the jobs
that run tests.
If you use `ocaml-ci`_, you have nothing to do: a formatting job is set up
automatically.
If you use `setup-ocaml`_, you can use the `lint-fmt` extend listed in the
README file.
.. _ocaml-ci: https://ocaml.ci.dev/
.. _setup-ocaml: https://github.com/ocaml/setup-ocaml

View file

@ -0,0 +1,25 @@
How-to Guides
=============
These guides will help you use Dune's features in your project.
.. toctree::
:maxdepth: 1
install-dune
formatting
opam-file-generation
../cross-compilation
../foreign-code
../documentation
../sites
../instrumentation
../jsoo
../wasmoo
../melange
../virtual-libraries
../tests
bundle
toplevel
rule-generation
override-default-entrypoint

View file

@ -0,0 +1,39 @@
How to Install Dune
===================
Dune is available as an Opam package. First, make sure that Opam is installed:
.. code:: console
$ opam --version
2.1.5
Any version higher than 2.0.0 is supported, though preferably at least 2.1.0.
If Opam is not available, follow `the official instructions on the Opam website
<https://opam.ocaml.org/doc/Install.html>`_ to install it and then run its
global setup with ``opam init``.
.. note::
Opam requires a "shell hook" to work properly. Make sure to set it up
correctly during ``opam init``. Otherwise you will have to run ``eval $(opam
env)`` every time you create an Opam switch or change directory.
Then, you can install Dune in an Opam switch using the following command:
.. code:: console
$ opam install dune
After the command completes, the following should display a version number:
.. code:: console
$ dune --version
3.12.1
.. note::
In most cases, when using Opam you will not need to install Dune by hand.
Installing the project's dependencies will install it in the Opam switch.

View file

@ -0,0 +1,143 @@
How to Generate Opam Files from ``dune-project``
================================================
.. highlight:: dune
This guide will show you how to configure Dune so that it generates opam files.
Declaring Package Dependencies
------------------------------
The goal of this first step is to add ``(package)`` stanzas in your
``dune-project`` file. These stanzas declare the metadata that your package
uses in the language of opam packages. See :ref:`declaring-a-package`.
The next step depends on whether you are starting from a clean slate (new
package) or adapting an existing opam file.
For a New Package (No Existing Opam File)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If your project does not have any opam files, you will have to find your
package dependencies. In the simple case, collect all the libraries
that appear in the ``(libraries)`` fields of your project and put this list in
the ``(depends)`` field of the corresponding ``(package)``. See
:doc:`../explanation/ocaml-ecosystem` for the difference between libraries and
packages.
Example: you have a library that looks like::
(library
(public_name frobnitz)
(libraries lwt fmt))
You can declare the package as::
(package
(name frobnitz)
(depends lwt fmt))
Also add common metadata using ``(authors)``, ``(maintainers)``, ``(license)``,
``(source)``, as well as a ``(synopsis)`` and a ``(description)`` for
For an Existing Package
^^^^^^^^^^^^^^^^^^^^^^^
If you already have an opam file (or several of them), you can convert it by
following the rules in :doc:`/reference/dune-project/package`.
For example, if your opam file looks like:
.. code:: opam
opam-version: 2.0
authors: ["Anil Madhavapeddy" "Rudi Grinberg"]
maintainer: ["team@mirage.org"]
name: "cohttp-async"
synopsis: "HTTP client and server for the Async library"
description: "A _really_ long description"
license: "ISC"
bug-reports: "https://github.com/mirage/ocaml-cohttp/issues"
homepage: "https://github.com/mirage/ocaml-cohttp/"
dev-repo: "git+https://github.com/mirage/ocaml-cohttp.git"
build: [
["dune" "subst"] {dev}
[
"dune"
"build"
"-p"
name
"-j"
jobs
"@install"
"@runtest" {with-test}
"@doc" {with-doc}
]
]
depends: [
"dune" { >= "3.4" }
"odoc" { with-doc }
"cohttp" { >= "1.0.2" }
"conduit-async" { >= "1.0.3" }
"async" { >= "v0.10.0" }
]
x-maintenance-intent: [ "(latest)" ]
You can express this as::
(source (github mirage/ocaml-cohttp))
(license ISC)
(authors "Anil Madhavapeddy" "Rudi Grinberg")
(maintainers "team@mirage.org")
(maintenance_intent "(latest)")
(package
(name cohttp-async)
(synopsis "HTTP client and server for the Async library")
(description "A _really_ long description")
(depends
(cohttp (>= 1.0.2))
(conduit-async (>= 1.0.3))
(async (>= v0.10.0))))
General Notes and Tips
^^^^^^^^^^^^^^^^^^^^^^
- Do not declare a dependency on the ``dune`` and ``odoc`` packages. Dune will
generate them with the right constraints.
- For fields that are common between packages (like ``(authors)`` or
``(license)``), you can use a global one rather than replicate it between
packages.
- If you use a platform such as GitHub you can use ``(source)`` as a shorthand
instead of specifying ``(bug_reports)``, ``(homepage)``, etc.
- ``(package)`` stanzas do not support all opam fields or complete syntax for
dependency specifications. If the package you are adapting requires this,
keep the corresponding opam fields in a ``pkg.opam.template`` file. See
:doc:`../reference/packages`.
- It is not necessary to specify ``(version)``, this will be added at release
time if you use `dune-release <https://github.com/tarides/dune-release>`_.
- To generate an opam variable such as ``version``, use a colon ``:`` followed
by the name of the variable. For example, to generate ``a { = version }`` in
the opam file, use ``(a (= :version))`` in ``dune-project``.
Generating Opam Files
---------------------
If you have existing ``*.opam`` files, make a backup of them because the instructions in this section will overwrite them.
Now that you have declared package metadata in ``dune-project``, you can add
``(generate_opam_files)`` in ``(dune-project)``.
From now on, commands like ``dune build`` and ``dune runtest`` are going to regenerate the contents of opam files from the metadata in ``(package)`` stanzas.
If you only want to generate the opam file, run ``dune build <project_name>.opam``.
Run ``dune build`` once and observe that the opam files have been created or
updated. Make sure to add these changes to your version control system.
.. seealso::
:token:`~pkg-dep:dep_specification`
How ``(depends)`` and similar fields are processed.
:doc:`/explanation/opam-integration`
How ``with-test`` and related variables are used by opam.

View file

@ -0,0 +1,63 @@
How to Override the Default C Entrypoint With C Stubs
-----------------------------------------------------
In some cases, it may be necessary to override the default C entry point of an
OCaml program. For example, this is the case if you want to let your program
handle argument wildcards expansion on Windows.
Let's consider a trivial "Hello world" program contained in a ``hello.ml``
file:
.. code:: ocaml
let () = print_endline "Hello, world!"
The default C entry point is a ``main`` function, originally defined in
`runtime/main.c <https://github.com/ocaml/ocaml/blob/trunk/runtime/main.c>`_. It
can be overridden by defining a ``main`` function that will at some point call
the OCaml runtime. Let's write such a minimal example in a ``main.c`` file:
.. code:: C
#include <stdio.h>
#define CAML_INTERNALS
#include "caml/misc.h"
#include "caml/mlvalues.h"
#include "caml/sys.h"
#include "caml/callback.h"
/* This is the new entry point */
int main(int argc, char_os **argv)
{
/* Here, we just print a statement */
printf("Doing stuff before calling the OCaml runtime\n");
/* Before calling the OCaml runtime */
caml_main(argv);
caml_do_exit(0);
return 0;
}
The :doc:`foreign_stubs </reference/foreign-stubs>` stanza can be leveraged to
compile and link our OCaml program with the new C entry point defined in
``main.c``:
.. code:: dune
(executable
(name hello)
(foreign_stubs
(language c)
(names main)))
With this ``dune`` file, the whole program can be compiled by merely calling
``dune build``. When run, the output shows that it calls the custom entry point
we defined:
.. code:: shell-session
$ dune build
$ _build/default/hello.exe
Doing stuff before calling the OCaml runtime
Hello, world!

View file

@ -0,0 +1,266 @@
Using Rule Generation
=====================
Sometimes it can be useful to generate Dune rules that depend on the file system
layout or on the content of configuration files. This often happens for
integration tests.
In this document, we will see two ways to encode this behavior.
We suppose that we are testing an executable named ``tool``. There are some
input files named ``*.input``, output files named ``*.output``, and we want to
ensure that when running ``tool`` on ``x.input``, the standard output
corresponds to ``x.output``.
The Generate-Include-Commit Pattern
-----------------------------------
.. note::
This is the most common way to do this. It has a couple drawbacks listed
below, but you should start with this pattern.
What we are going to do is:
- generate a ``dune.inc`` file;
- include it in our main ``dune`` file;
- commit the generated code in the source repository.
This creates a loop: a program (the "generator") looks at the file system and
creates a ``dune.inc`` file. Changes to the file system (for example, if a test
is added) mean that a change in ``dune.inc`` will be promoted. These generated
rules are included in the main ``dune`` file, so ``dune runtest`` will run the
tests. Finally, the generated file is part of the source repository, so it is
not necessary to run several commands to run the test suite.
Let's expand a bit on how to achieve this.
Generating a ``dune.inc`` File
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Create a ``gen`` subdirectory and create a ``gen.ml`` file in it:
``gen/gen.ml``
.. code:: ocaml
let generate_rules base =
Printf.printf
{|
(rule
(with-stdout-to %s.gen
(run %%{bin:tool} %s.input)))
(rule
(alias runtest)
(action
(diff %s.output %s.gen)))
|}
base base base base
let () =
Sys.readdir "."
|> Array.to_list
|> List.sort String.compare
|> List.filter_map (Filename.chop_suffix_opt ~suffix:".input")
|> List.iter generate_rules
Create a ``dune`` file in that directory:
``gen/dune``
.. code:: dune
(executable
(name gen))
This defines an executable that lists ``*.input`` files in the current
directory and outputs rules on its standard output.
.. note::
It is important to sort the input files to ensure that the output is
independent from the order in which ``Sys.readdir`` returns the files .
For each input file, we output two rules:
- The first one creates a ``x.gen`` file that corresponds to the actual output.
- The second uses a :doc:`/reference/actions/diff` action to compare the actual output to the expected output. If it is different, ``dune runtest`` will display the difference, which can be accepted by ``dune promote``.
.. note::
It is possible to have more complicated logic here. For example, to pass
different arguments to ``tool`` depending on the presence of a ``*.args``
file. To do that, check if ``*.args`` exists in ``generate_rules`` and emit
a different ``(run ...)`` action.
Including it in the Main ``dune`` File
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Our main test ``dune`` file contains the following:
``dune``
.. code:: dune
(include dune.inc)
(rule
(deps (source_tree .))
(with-stdout-to
dune.inc.gen
(run gen/gen.exe)))
(rule
(alias runtest)
(action
(diff dune.inc dune.inc.gen)))
In addition to including the contents of ``dune.inc``, we use the same pattern
as before: ``dune.inc.gen`` is the actual output of the generator, and
``dune.inc`` is the expected output. At runtime, the generator will read the
contents of the current directory (where the ``*.input`` and ``*.output`` files
are located), so we record ``(source_tree .)`` as a dependency to make it run
again if a file is created, for example.
Commit the Generated Code In The Source Repository
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To make this work, we have a final step to do. We have to add the generated
file to our source tree. But since it is generated, we will have to first
create an empty file, run the test, and promote the result.
.. code:: console
$ touch dune.inc
$ dune runtest
+ (rule
+ (with-stdout-to a.gen
+ (run %{bin:tool} a.input)))
+
+ (rule
+ (alias runtest)
+ (action
+ (diff a.output a.gen)))
$ dune promote dune.inc
$ git add dune.inc
Now, running ``dune runtest`` will run the test suite.
Notes
^^^^^
This pattern is "correct": it will execute all tests and make sure the
list of tests is up to date. But when adding a test, it is necessary to first
run ``dune runtest``, promote the result, and then re-run ``dune runtest`` to
actually run the test (and possibly promote the result of the test itself).
There is a variant of this pattern which will promote the output automatically
instead of using a manual promotion step. This variant can be used either for
the test list or for the individual tests.
To use it in the test list, replace the ``dune`` file by this version:
``dune`` (alternative version)
.. code:: dune
(include dune.inc)
(rule
(mode promote)
(alias runtest)
(deps (source_tree .))
(with-stdout-to
dune.inc
(run gen/gen.exe)))
Using this version, ``dune runtest`` will directly replace ``dune.inc`` with an
updated version.
Another caveat of this approach is that the generator needs to emit the same
output on all systems. For example, if some tests should be skipped on Linux,
the generator can not just filter the corresponding tests depending on
``Sys.os_type``. It has to consistently emit a ``(enabled_if)`` field for the
rules.
Using ``(dynamic_include)``
---------------------------
.. versionadded:: 3.14
This technique relies on :doc:`/reference/dune/dynamic_include`, which is
more flexible than :doc:`/reference/dune/include`. The difference is that
the intermediate ``dune.inc`` file does not need to be part of the source tree.
It will only be generated by a rule and be present in the ``_build`` directory.
At first it looks like it would be possible to reuse the same pattern as above:
change ``include`` to ``dynamic_include`` and delete the ``dune.inc`` file.
However, it is not possible. The reason is that rules are loaded per directory,
and there needs to be a strict order (no cycles) between directories for this
to work.
So, instead we are going to:
- generate ``dune.inc`` in a subdirectory named ``generate``, and
- include these rules in a subdirectory named ``run``.
These subdirectories do not need to be actual directories. They can be emulated
through :doc:`/reference/dune/subdir`.
To do this, we can create the following ``dune`` file in the same directory as
the ``*.input`` and ``*.output`` files.
``dune``
.. code:: dune
(executable
(name gen))
(subdir run
(dynamic_include ../generate/dune.inc))
(subdir generate
(rule
(deps (glob_files ../*.input))
(action
(with-stdout-to dune.inc
(run ../gen.exe)))))
Then create the following ``gen.ml`` file. Note that here we can define it in
the same directory.
``gen.ml``
.. code:: ocaml
let generate_rules base =
Printf.printf
{|
(rule
(with-stdout-to %s.gen
(run %%{bin:tool} ../%s.input)))
(rule
(alias runtest)
(action
(diff ../%s.output %s.gen)))
|}
base base base base
let () =
Sys.readdir ".." |> Array.to_list |> List.sort String.compare
|> List.filter_map (Filename.chop_suffix_opt ~suffix:".input")
|> List.iter generate_rules
There are a few differences from the generator above because this one
is going to be invoked from subdirectories, so it is necessary to refer to the
``..`` directory both in the input (which files to read) and in the output (how
the rules are executed).
These two files are enough. ``dune runtest`` is going to generate the rules and
interpret them in a single command.
Notes
^^^^^
This approach is shorter, but it might be more difficult to debug because changes
to the generated rules will not be visible. Also, it works in that case, but it
is not possible to generate all kinds of stanzas with that pattern. See
:doc:`/reference/dune/dynamic_include` for more information about the
limitations.

View file

@ -0,0 +1,55 @@
How to Load a Project in a Toplevel
===================================
It is possible to use OCaml code in an interactive way, by typing an
expression, which gets evaluated and its result printed. Such a program is
called a `toplevel`, or REPL (Read-Eval-Print Loop).
The compiler distribution comes with a small REPL called simply ``ocaml``, and
the community has developed enhanced versions such as `UTop
<https://github.com/ocaml-community/utop>`_.
Building a Specialized UTop Executable
--------------------------------------
It is possible to generate a specialized version of UTop that embeds the
current project. To do so, use the following command:
.. code:: console
$ dune utop
The interactive session will start with all the modules loaded.
If some of the libraries are PPX rewriters, the phrases you type in the
toplevel will be rewritten with these PPX rewriters. Similarly, PPX derivers
defined in the project will be available.
Loading the Project in a Toplevel
---------------------------------
It is also possible to load Dune projects in any toplevel. To do that, simply
execute the following in your toplevel:
.. code:: ocaml
# #use_output "dune ocaml top";;
``dune ocaml top`` is a Dune command that builds all the libraries in the
current directory and subdirectories and outputs the relevant toplevel
directives (``#directory`` and ``#load``) to make the various modules
available in the toplevel.
Loading a Single Module in a Toplevel
-------------------------------------
It's also possible to load individual modules for interactive development. Use
the following dune command:
.. code:: ocaml
# #use_output "dune ocaml top-module foo.ml";;
This will print directives that will load ``foo.ml`` without sealing it behind
``foo.mli``. This is particularly useful for peeking and prodding at a module's
internals.

View file

@ -0,0 +1,6 @@
{1 [dune] - Fast, portable, and opinionated build system }
The documentation for [dune] is available here:
- {{: https://dune.readthedocs.io/en/stable/} latest release}
- {{: https://dune.readthedocs.io/en/latest/} development version}

View file

@ -0,0 +1,16 @@
Dune Documentation
==================
Dune is a build system for OCaml projects. Using it, you can build executables,
libraries, run tests, and much more.
.. toctree::
:maxdepth: 2
getting-started/index
tutorials/index
howto/index
reference/index
explanation/index
advanced/index
misc/index

View file

@ -0,0 +1,139 @@
***************
Instrumentation
***************
.. TODO(diataxis)
Split between:
- reference about ``(instrumentation)``
- :doc:`howto/code-coverage`
- specific reference about ``(instrumentation.backend)``
In this section, we'll explain how to define and use instrumentation backends
(such as ``bisect_ppx`` or ``landmarks``) so that you can enable and disable
coverage via ``dune-workspace`` files or by passing a command-line flag or
environment variable. In addition to providing an easy way to toggle
instrumentation of your code, this setup avoids creating a hard dependency on
the precise instrumentation backend in your project.
Specifying What to Instrument
=============================
When an instrumentation backend is activated, Dune will only instrument
libraries and executables for which the user has requested instrumentation.
To request instrumentation, one must add the following field to a library or
executable stanza:
.. code:: dune
(library
(name ...)
(instrumentation
(backend <name> <args>)
<optional-fields>))
The backend ``<name>`` can be passed into arguments using ``<args>``.
This field can be repeated multiple times in order to support various
backends. For instance:
.. code:: dune
(library
(name foo)
(modules foo)
(instrumentation (backend bisect_ppx --bisect-silent yes))
(instrumentation (backend landmarks)))
This will instruct Dune that when either the ``bisect_ppx`` or ``landmarks``
instrumentation is activated, the library should be instrumented with this
backend.
By default, these fields are simply ignored; however, when the corresponding
instrumentation backend is activated, Dune will implicitly add the relevant
``ppx`` rewriter to the list of ``ppx`` rewriters.
At the moment, it isn't possible to instrument code that's preprocessed via an
action preprocessors. As these preprocessors are quite rare nowadays, there is
no plan to add support for them in the future.
``<optional-fields>`` are:
- ``(deps <deps-conf list>)`` specifies extra instrumentation dependencies, for
instance, if it reads a generated file. The dependencies are only applied
when the instrumentation is actually enabled. The specification of
dependencies is described in :doc:`concepts/dependency-spec`.
Enabling/Disabling Instrumentation
==================================
Activating an instrumentation backend can be done via the command line or the
``dune-workspace`` file.
Via the command line, it is done as follows:
.. code:: console
$ dune build --instrument-with <names>
Here ``<names>`` is a comma-separated list of instrumentation backends. For
example:
.. code:: console
$ dune build --instrument-with bisect_ppx,landmarks
This will instruct Dune to activate the given backend globally, i.e., in all
defined build contexts.
It's also possible to enable instrumentation backends via the
``dune-workspace`` file, either globally or for specific builds contexts.
To enable an instrumentation backend globally, type the following in your
``dune-workspace`` file:
.. code:: dune
(lang dune 3.20)
(instrument_with bisect_ppx)
or for each context individually:
.. code:: dune
(lang dune 3.20)
(context default)
(context (default (name coverage) (instrument_with bisect_ppx)))
(context (default (name profiling) (instrument_with landmarks)))
If both the global and local fields are present, the precedence is the same as
the ``profile`` field: the per-context setting takes precedence over the
command-line flag, which takes precedence over the global field.
Declaring an Instrumentation Backend
====================================
Instrumentation backends are libraries with the special field
``(instrumentation.backend)``. This field instructs Dune that the library can
be used as an instrumentation backend, and it also provides the parameters
specific to this backend.
Currently, Dune will only support ``ppx`` instrumentation tools, and the
instrumentation library must specify the ``ppx`` rewriters that instruments the
code. This can be done as follows:
.. code:: dune
(library
...
(instrumentation.backend
(ppx <ppx-rewriter-name>)))
When such an instrumentation backend is activated, Dune will implicitly add the
mentioned ``ppx`` rewriter to the list of ``ppx`` rewriters for libraries and
executables that specify this instrumentation backend.
.. _bisect_ppx: https://github.com/aantron/bisect_ppx
.. _landmarks: https://github.com/LexiFi/landmarks

View file

@ -0,0 +1,86 @@
.. _jsoo:
***************************************
JavaScript Compilation With Js_of_ocaml
***************************************
.. TODO(diataxis)
This is an how-to guide.
Js_of_ocaml_ is a compiler from OCaml to JavaScript. The compiler works by
translating OCaml bytecode to JS files. The compiler can be installed with opam:
.. code:: console
$ opam install js_of_ocaml-compiler
Compiling to JS
===============
Dune has full support building js_of_ocaml libraries and executables transparently.
There's no need to customize or enable anything to compile OCaml
libraries/executables to JS.
To build a JS executable, just define an executable as you would normally.
Consider this example:
.. code:: console
$ echo 'print_endline "hello from js"' > foo.ml
With the following ``dune`` file:
.. code:: dune
(executable (name foo) (modes js))
And then request the ``.js`` target:
.. code:: console
$ dune build ./foo.bc.js
$ node _build/default/foo.bc.js
hello from js
Similar targets are created for libraries, but we recommend sticking to the
executable targets.
If you're using the js_of_ocaml syntax extension, you must remember to add the
appropriate PPX in the ``preprocess`` field:
.. code:: dune
(executable
(name foo)
(modes js)
(preprocess (pps js_of_ocaml-ppx)))
Separate Compilation
====================
Dune supports two modes of compilation:
- Direct compilation of a bytecode program to JavaScript. This mode allows
js_of_ocaml to perform whole-program deadcode elimination and whole-program
inlining.
- Separate compilation, where compilation units are compiled to JavaScript
separately and then linked together. This mode is useful during development as
it builds more quickly.
The separate compilation mode will be selected when the build profile
is ``dev``, which is the default. It can also be explicitly specified
in an ``env`` stanza (see :doc:`/reference/dune/env`) or per executable
inside ``(js_of_ocaml (compilation_mode ...))`` (see :doc:`/reference/dune/executable`)
Sourcemap
=========
Js_of_ocaml can generate sourcemap for the generated JavaScript file.
It can either embed it at the end of the ``.js`` file or write it to separate file.
By default, it is inlined when using the ``dev`` build profile and is not generated otherwise.
The behavior can explicitly be specified in an ``env`` stanza (see :doc:`/reference/dune/env`)
or per executable inside ``(js_of_ocaml (sourcemap ...))`` (see :doc:`/reference/dune/executable`)
.. _js_of_ocaml: http://ocsigen.org/js_of_ocaml/

View file

@ -0,0 +1,36 @@
Welcome to Dune!
================
For users
---------
To build dune in release mode, please run:
$ make release
You can then install dune on your system by typing:
$ make install
You can pass PREFIX=<dir> to "make install" in order to install Dune
in a specific directory.
Note that "make release" won't rebuild Dune if you modify it. Use
"make dev" if you want to modify it.
For developers
--------------
All the following commands build Dune in development mode, i.e. with
more warnings enabled. You should build Dune this way if you intend to
work on Dune.
Here are the following commands you can use:
- make bootstrap to build the bootstrapped version of dune
- make dev to build the dune binary
- make test to build and run the testsuite
- make doc to build the Dune manual (requires sphinx)
- make livedoc to build the Dune manual and serve it
You can customize the default target by writing a Makefile.dev file.

View file

@ -0,0 +1,316 @@
.. _melange_main:
***********************************
JavaScript Compilation With Melange
***********************************
Introduction
============
`Melange <https://github.com/melange-re/melange>`_ compiles OCaml to
JavaScript. It produces one JavaScript file per OCaml module. Melange can
be installed with opam:
.. code:: console
$ opam install melange
Dune can build projects using Melange, and it allows the user to produce
JavaScript files by defining a :ref:`melange-emit` stanza. Dune libraries can be
used with Melange by adding ``melange`` to ``(modes ...)`` in the
:doc:`/reference/dune/library` stanza.
Melange support is still experimental in Dune and needs to be enabled in the
:doc:`/reference/dune-project/index` file:
.. code:: dune
(using melange 0.1)
Once that's in place, you can use the Melange mode in
:doc:`/reference/dune/library` stanzas ``melange.emit`` stanzas.
Simple Project
==============
Let's start by looking at a simple project with Melange and Dune. Subsequent
sections explain the different concepts used here in further detail.
First, make sure that the :doc:`/reference/dune-project/index` file
specifies at least version 3.8 of the Dune language, and the Melange extension
is enabled:
.. code:: dune
(lang dune 3.20)
(using melange 0.1)
Next, write a :doc:`/reference/dune/index` file with a
:ref:`melange-emit` stanza:
.. code:: dune
(melange.emit
(target output))
Finally, add a source file to build:
.. code:: console
$ echo 'Js.log "hello from melange"' > hello.ml
After running ``dune build @melange`` or just ``dune build``, Dune
produces the following file structure:
.. code::
.
├── _build
│ └── default
│ └── output
│ └── hello.js
├── dune
├── dune-project
└── hello.ml
The resulting JavaScript can now be run:
.. code:: console
$ node _build/default/output/hello.js
hello from melange
Libraries
=========
Adding Melange support to Dune libraries is done as follows:
- ``(modes melange)``: adding ``melange`` to ``modes`` is required. This
field also supports the :doc:`reference/ordered-set-language`.
- ``(melange.runtime_deps <deps>)``: optionally, define any runtime dependencies
using ``melange.runtime_deps``. This field is analog to the ``runtime_deps``
field used in ``melange.emit`` stanzas.
.. _melange-emit:
melange.emit
============
.. versionadded:: 3.8
The ``melange.emit`` stanza allows the user to produce JavaScript files
from Melange libraries and entry-point modules. It's similar to the OCaml
:doc:`/reference/dune/executable` stanza, with the exception that there
is no linking step.
.. code:: dune
(melange.emit
(target <target>)
<optional-fields>)
``<target>`` is the name of the folder where resulting JavaScript artifacts will
be placed. In particular, the folder will be placed under
``_build/default/$path-to-directory-of-melange-emit-stanza``.
The result of building a ``melange.emit`` stanza will match the file structure
of the source tree. For example, given the following source tree:
.. code::
├── dune # (melange.emit (target output) (libraries lib))
├── app.ml
└── lib
├── dune # (library (name lib) (modes melange))
└── helper.ml
The resulting layout in ``_build/default/output`` will be as follows:
.. code::
output
├── app.js
└── lib
├── lib.js
└── helper.js
``<optional-fields>`` are:
- ``(alias <alias-name>)`` specifies an alias to which to attach the targets of
the ``melange.emit`` stanza.
- These targets include the ``.js`` files generated by the stanza
modules, the targets for the ``.js`` files of any library that the stanza
depends on, and any copy rules for runtime dependencies (see
``runtime_deps`` field below).
- By default, all stanzas will have their targets attached to an alias
``melange``. The behavior of this default alias is exclusive: if an alias
is explicitly defined in the stanza, the targets from this stanza will
be excluded from the ``melange`` alias.
- The targets of ``melange.emit`` are also attached to the Dune default
alias (:doc:`/reference/aliases/all`), regardless of whether the
``(alias ...)`` field is present.
- ``(module_systems <module_systems>)`` specifies the JavaScript import and
export format used. The values allowed for ``<module_systems>`` are ``es6``
and ``commonjs``.
- ``es6`` will follow `JavaScript modules <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules>`_,
and will produce ``import`` and ``export`` statements.
- ``commonjs`` will follow `CommonJS modules <https://nodejs.org/api/modules.html>`_,
and will produce `require` calls and export values with ``module.exports``.
- If no extension is specified, the resulting JavaScript files will use
``.js``. You can specify a different extension with a pair
``(<module_system> <extension>)``, e.g. ``(module_systems (es6 mjs))``.
- Multiple module systems can be used in the same field as long as their
extensions are different. For example,
``(module_systems commonjs (es6 mjs))`` will produce one set of JavaScript
files using CommonJS and the ``.js`` extension, and another using ES6 and
the ``.mjs`` extension.
- ``(modules <modules>)`` specifies what modules will be built with Melange. By
default, if this field is not defined, Dune will use all the ``.ml/.re`` files
in the same directory as the ``dune`` file. This includes module sources
present in the file system as well as modules generated by user rules. You can
restrict this list by using an explicit ``(modules <modules>)`` field.
``<modules>`` uses the :doc:`reference/ordered-set-language`, where elements
are module names and don't need to start with an uppercase letter. For
instance, to exclude module ``Foo``, use ``(modules :standard \ foo)``.
- ``(libraries <library-dependencies>)`` specifies Melange library dependencies.
Melange libraries can only use the simple form, like
``(libraries foo pkg.bar)``. Keep in mind the following limitations:
- The ``re_export`` form is not supported.
- All the libraries included in ``<library-dependencies>`` have to support
the ``melange`` mode (see the section about libraries below).
- ``(package <package>)`` allows the user to define the JavaScript package to
which the artifacts produced by the ``melange.emit`` stanza will belong.
- ``(runtime_deps <paths-to-deps>)`` specifies dependencies that should be
copied to the build folder together with the ``.js`` files generated from the
sources. These runtime dependencies can include assets like CSS files, images,
fonts, external JavaScript files, etc. ``runtime_deps`` adhere to the formats
in :doc:`concepts/dependency-spec`. For example
``(runtime_deps ./path/to/file.css (glob_files_rec ./fonts/*))``.
- ``(emit_stdlib <bool>)`` allows the user to specify whether the Melange
standard library should be included as a dependency of the stanza or not. The
default is ``true``. If this option is ``false``, the Melange standard library
and runtime JavaScript files won't be produced in the target directory.
- ``(promote <options>)`` promotes the generated ``.js`` files to the
source tree. The options are the same as for the
:ref:`rule promote mode <promote>`.
Adding ``(promote (until-clean))`` to a ``melange.emit`` stanza will cause
Dune to copy the ``.js`` files to the source tree and ``dune clean`` to
delete them.
- ``(preprocess <preprocess-spec>)`` specifies how to preprocess files when
needed. The default is ``no_preprocessing``. Additional options are described
in the :doc:`reference/preprocessing-spec` section.
- ``(preprocessor_deps (<deps-conf list>))`` specifies extra preprocessor
dependencies, e.g., if the preprocessor reads a generated file.
The dependency specification is described in the :doc:`concepts/dependency-spec`
section.
- ``(compile_flags <flags>)`` specifies compilation flags specific to
``melc``, the main Melange executable.
``<flags>`` is described in detail in the
:doc:`reference/ordered-set-language` section. It also supports
``(:include ...)`` forms. The value for this field can also be taken
from ``env`` stanzas. It's therefore recommended to add flags
with e.g. ``(compile_flags :standard <my options>)`` rather than
replace them.
- ``(root_module <module>)`` specifies a ``root_module`` that collects all
listed dependencies in ``libraries``. See the documentation for
``root_module`` in the :doc:`/reference/dune/library` stanza.
- ``(allow_overlapping_dependencies)`` is the same as the corresponding field
of :doc:`/reference/dune/library`.
- ``(enabled_if <blang expression>)`` conditionally disables a melange emit
stanza. The JavaScript files associated with the stanza won't be built. The
condition is specified using the :doc:`reference/boolean-language`.
Recommended Practices
=====================
Keep Bundles Small by Reducing the Number of ``melange.emit`` Stanzas
---------------------------------------------------------------------
It is recommended to minimize the number of ``melange.emit`` stanzas
that a project defines: using multiple ``melange.emit`` stanzas will cause
multiple copies of the JavaScript files to be generated if the same libraries
are used across them. As an example:
.. code:: dune
(melange.emit
(target app1)
(libraries foo))
(melange.emit
(target app2)
(libraries foo))
The JavaScript artifacts for library ``foo`` will be emitted twice in the
``_build`` folder. They will be present under ``_build/default/app1``
and ``_build/default/app2``.
This can have unexpected impact on bundle size when using tools like Webpack or
Esbuild, as these tools will not be able to see shared library code as such,
as it would be replicated across the paths of the different stanzas
``target`` folders.
Faster Builds With ``subdir`` and ``dirs`` Stanzas
--------------------------------------------------
Melange libraries might be installed from the ``npm`` package repository,
together with other JavaScript packages. To avoid having Dune inspect
unnecessary folders in ``node_modules``, it is recommended to explicitly
include only the folders that are relevant for Melange builds.
This can be accomplished by combining :doc:`/reference/dune/subdir` and
:doc:`/reference/dune/subdir` stanzas in a ``dune`` file next to the
``node_modules`` folder. The :doc:`/reference/dune/vendored_dirs` stanza
can be used to avoid warnings in Melange libraries during the application
build. The :doc:`/reference/dune/data_only_dirs` stanza can be useful as
well if you need to override the build rules in one of the packages.
.. code:: dune
(subdir
node_modules
(vendored_dirs reason-react)
(dirs reason-react))
Design choices
=====================
Melange support in Dune follows the following design choices:
- :ref:`melange-emit` produces a "total" directory: the artifacts in the
``target`` dir contain all the JavaScript and ``runtime_deps`` assets
necessary to run the application either through a JS framework, a bundler, or
otherwise a deployment (excluding external dependencies installed via a JS
package manager). The structure is designed such that relative paths and
dependencies work out of the box relative to their paths in the source tree,
before compilation.
- public libraries are compiled to ``%{target}/node_modules/%{lib_name}`` such
that the `resolution algorithm <https://nodejs.org/api/modules.html#all-together>`_
works to resolve Melange libraries from compiled JS code.

View file

@ -0,0 +1,12 @@
Miscellaneous
=============
These documents contain tidbits of info that do not fit anywhere else, and
information about the project itself.
.. toctree::
:maxdepth: 2
../faq
../goals
../hacking

View file

@ -0,0 +1,179 @@
********
Overview
********
.. TODO(diataxis)
Split into:
- info on the index page
- :doc:`glossary`
- a history page that could also explain the various actors
Introduction
============
Dune is a build system for OCaml (with support for Reason and Coq). It is not
intended as a completely generic build system that's able to build any project
in any language. On the contrary, it makes lots of choices in order to encourage
a consistent development style.
This scheme is inspired from the one used inside Jane Street and adapted to the
opam world. It has matured over a long time and is used daily by hundreds of
developers, which means that it is highly tested and productive.
When using Dune, you give very little, high-level information to the build
system, which in turn takes care of all the low-level details from the
compilation of your libraries, executables, and documentation to the
installation, setting up of tests, and setting up development tools such as
Merlin, etc.
In addition to the normal features expected from an OCaml build system, Dune
provides a few additional ones that separate it from the crowd:
- You never need to tell Dune the location of things such as libraries. Dune
will discover them automatically. In particular, this means that when you
want to reorganise your project, you need nothing other than to rename your
directories, Dune will do the rest.
- Things always work the same whether your dependencies are local or installed
on the system. In particular, this means that you can insert the source for a
project dependency in your working copy, and Dune will start using it
immediately. This makes Dune a great choice for multi-project development.
- Cross-platform: as long as your code is portable, Dune will be able to
cross-compile it. Read more in the :ref:`cross-compilation` section.
- Release directly from any revision: Dune needs no setup stage. To release
your project, simply point to a specific Git tag (named revision). Of course,
you can add some release steps if you'd like, but it isn't necessary. For
more information, please refer to dune-release_.
.. _dune-release: https://github.com/tarides/dune-release
The first section below defines some terms used in this manual. The second
section specifies the Dune metadata format, and the third one describes how to
use the ``dune`` command.
Terminology
===========
.. glossary::
root
The top-most directory in a GitHub repo, workspace, and project,
differentiated by variables such as ``%{workspace_root}`` and
``%{project_root}``. Dune builds things from this directory. It knows how to
build targets that are descendants of the root. Anything outside of the tree
starting from the root is considered part of the :term:`installed world`.
Refer to :ref:`finding-root` to learn how the workspace root is determined.
workspace
The subtree starting from each root. It can contain any number of projects
that will be built simultaneously by Dune, and it must contain a
``dune-workspace`` file.
project
A collection of source files that must include a ``dune-project`` file. It
may also contain one or more packages. A project consists in a hierarchy
of directories. Every directory (at the root, or a subdirectory) can
contain a ``dune`` file that contains instructions to build files in that
directory. Projects can be shared between different applications.
package
A set of libraries and executables that opam builds and installs as one.
installed world
Anything outside of the workspace. Dune doesn't know how to build things
in the installed world.
installation
The action of copying build artifacts or other files from the
``<root>/_build`` directory to the :term:`installed world`.
scope
Defined by any directory that contains at least one `<package>.opam` file.
Typically, every project defines a single scope that is a subtree starting
from this directory. Moreover, scopes are separate from your project's
dependencies. The scope also determines where private items are visible.
Private items include libraries or binaries that will not be installed.
See :doc:`/explanation/scopes` for more details.
build context
A specific configuration written in a
:doc:`/reference/dune-workspace/index` file, which has a
corresponding subdirectory in the ``<root>/_build`` directory. It contains
all the workspace's build artifacts. Without this specific configuration
from the user, there is always a ``default`` build context that
corresponds to the executed Dune environment.
build context root
The root of a build context named ``foo`` is ``<root>/_build/<foo>``.
build target
Specified on the command line, e.g., ``dune build <target_path.exe>``. All
targets that Dune knows how to build live in the ``_build`` directory.
alias
A build target that doesn't produce any file and has configurable
dependencies. Targets starting with ``@`` on the command line are
interpreted as aliases (e.g., ``dune build @src/runtest``). Aliases are
per-directory. See :doc:`reference/aliases`.
environment
Determines the default values of various parameters, such as the
compilation flags. In Dune, each directory has an environment attached to
it. Inside a scope, each directory inherits the environment from its
parent. At the root of every scope, a default environment is used. At any
point, the environment can be altered using an
:doc:`/reference/dune/env` stanza.
build profile
A global setting that influences various defaults. It can be set from the
command line using ``--profile <profile>`` or from ``dune-workspace``
files. The following profiles are standard:
- ``release`` which is the profile used for opam releases
- ``dev`` which is the default profile when none is set explicitly, it has
stricter warnings than the ``release`` one
dialect
An alternative frontend to OCaml (such as ReasonML). It is described
by a pair of file extensions, one corresponding to interfaces and one to
implementations. It can use the standard OCaml syntax, or it can specify an
action to convert from a custom syntax to a binary OCaml abstract syntax
tree. It can also specify a custom formatter.
placeholder substitution
A build step in which placeholders such as ``3.20.2`` in source files
are replaced by concrete values such as ``1.2.3``. It is performed by
:ref:`dune-subst` for development versions and dune-release_ for
releases.
stanza
A fragment of a file interpreted by Dune, that will appear as a
s-expression at the top-level of a file. For example, the
:doc:`/reference/dune/library` stanza describes a library. This can be
either a generic term ("the library stanza") or it can refer to a
particular instance in a file ("the executable stanza in ``bin/dune``").
Project Layout
==============
A typical Dune project will have a ``dune-project`` and one or more
``<package>.opam`` files at the root as well as ``dune`` files wherever
interesting things are: libraries, executables, tests, documents to install,
etc.
We recommend organising your project to have exactly one library per
directory. You can have several executables in the same directory, as long as
they share the same build configuration. If you'd like to have multiple
executables with different configurations in the same directory, you will have
to make an explicit module list for every executable using ``modules``.
History
=======
Dune started as ``jbuilder`` in late 2016. When its 1.0.0 version was released
in 2018, the name has been changed to ``dune``. It used to be configured with
``jbuild`` and ``jbuild-workspace`` files with a slightly different syntax.
After a transition period, this syntax is not supported anymore.

View file

@ -0,0 +1 @@
*.pdf

View file

@ -0,0 +1,4 @@
A paper describing the Memo library presented at the OCaml Users and
Developers Workshop 2022.
To render a PDF version, run `pandoc memo.md --citeproc -H header.tex -o memo.pdf`.

View file

@ -0,0 +1,10 @@
% Run Pandoc with -H header.tex to apply these changes
\usepackage{url}
\definecolor{inline_code_colour}{HTML}{000080}
\let\oldtexttt\texttt
\renewcommand{\texttt}[1]{\textcolor{inline_code_colour}{\oldtexttt{#1}}}
\urlstyle{sf}

View file

@ -0,0 +1,151 @@
---
geometry: "left=2cm,right=2cm,top=2.4cm,bottom=2.1cm"
bibliography: refs.bib
csl: refs.csl
---
# Memo: an incremental computation library that powers Dune
Andrey Mokhov, Arseniy Alekseyev
*Jane Street, London, United Kingdom*
### Abstract
We present Memo, an incremental computation library that supports a new, faster
and more scalable, file-watching build mode in Dune 3.0. The requirements from
the build systems domain make Memo a unique point in the design space of
incremental computation libraries. Specifically, Memo needs to cope with
concurrency, dynamic dependencies, dependency cycles, and non-determinism;
provide support for efficiently collecting and reporting user-friendly errors;
and scale to computation graphs containing tens of millions of incremental
nodes.
## Introduction
The OCaml build system Dune [@dune] supports a continuous file-watching build
mode, where rebuilds are triggered automatically as the user is editing source
files. A simple and naive way to implement this functionality is to restart Dune
on any file change but that is too slow for large projects. To avoid re-scanning
the project's source tree, re-parsing all build specification files, and
re-generating all build rules from scratch every time, Dune uses an incremental
computation library called Memo. Below we briefly introduce the Memo's API.
Memo provides a monadic API built on top of the *structured concurrency monad*
`Fiber`. Dune uses `Fiber` to speed up builds by executing external commands in
parallel, e.g., running multiple instances of `ocamlopt` to compile independent
source files. To inject a *fiber* into the `Memo` monad, and to extract it back,
one can use the following pair of functions:
```ocaml
val of_fiber : 'a Fiber.t -> 'a Memo.t
val run : 'a Memo.t -> 'a Fiber.t
```
More interestingly, functions in the `Memo` monad can be memoized and cached
between different build runs[^1]:
[^1]: The function `create` takes a few more arguments, e.g., for reporting good
error messages, which we omit for the sake of clarity.
```ocaml
val create : ('i -> 'o Memo.t) -> ('i, 'o) Memo.Table.t
val exec : ('i, 'o) Memo.Table.t -> 'i -> 'o Memo.t
```
Here `Memo.Table.t` is a table that stores the input/output mapping computed in
the current run, along with the *dependencies* that are automatically captured
when memoized functions call one another. Such explicit memoization, instead of,
e.g., memoizing every `Memo.map` and `Memo.bind` call, makes it easy to control
the degree of incrementality.
Finally, Memo provides a way to *invalidate* a specific input/output pair, or a
*cell*, via the following API:
```ocaml
val cell : ('i, 'o) Memo.Table.t -> 'i -> ('i, 'o) Cell.t
val invalidate : ('i, 'o) Cell.t -> Invalidation.t (* [Invalidation.t]s can be combined *)
val restart : Invalidation.t -> unit
```
When Dune receives new events from the file-watching backend, it cancels the
build run by interrupting the currently running external commands (if any), and
then calls `restart` to let Memo know which files changed and initiate a new
build run. When evaluating future calls to `run`, Memo will consider all outputs
that transitively depend on the invalidated cells as out of date, and will
recompute them when/if needed.
## Key features
This section discusses the most interesting features of Memo and some aspects of
the implementation.
Firstly, to *capture dependencies*, Memo maintains a call stack, where stack
frames correspond to `exec` calls. The call stack is also used for reporting
good error messages: when a user-supplied function raises an exception, we
extend it with the current Memo stack trace using human-readable annotations
provided to `create` via an optional argument.
Memo supports two ways of *error reporting*: *early* (to show errors to the user
as soon as they occur during a build), and *deterministic* (to provide a stable
error summary at the end of the build). To speed up rebuilds, Memo also supports
*error caching*: by default, it doesn't recompute a `Memo` function that
previously failed if its dependencies are up to date. This behaviour can be
overridden for *non-reproducible errors* that should not be cached, for example,
the errors that occur while Dune cancels the current build by interrupting the
execution of external commands.
One of the most interesting features of Memo compared to other incremental
computation libraries is *dependency cycle detection*.
Since Dune supports *dynamic build dependencies* [@mokhov2020build], the
dependency graph is not known before the build starts. During the build, new
computation nodes and dependency edges are discovered concurrently, and Memo
uses an incremental cycle detection algorithm [@gueneau2019cycles] to detect and
report dependency cycles as soon as they are created. This is a unique feature
of Memo: Incremental [@incremental] and Adapton [@hammer2014adapton] libraries
do not support concurrency; the Tenacious library (used by Jane Street's
internal build system Jenga) does support concurrency but it detects cycles by
"stopping the world" and traversing the "frozen" dependency graph to see if
concurrent computations might have deadlocked by waiting for each other. The
approach used by Tenacious is conceptually simple but introduces a delay between
the creation of a dependency cycle and its detection. Memo reports dependency
cycle errors without delay, and uses the human-readable annotations supplied to
`create` to make it easier to understand and debug cycles.
Compared to the incremental computation libraries mentioned above, the current
implementation of Memo makes one unusual design choice. Incremental, Adapton and
Tenacious are all *push based*, i.e., they trigger recomputation starting from
the leaves of the computation graph. Memo is *pull based*: `invalidate` calls
merely mark leaves (cells) as out of date, but recomputation is driven from the
top-level `run` calls. The main drawback of our approach is that Memo traverses
the whole graph on each rebuild. The main benefits are: (i) Memo
doesn't need to store reverse dependencies, which saves space and eliminates
various garbage collection pitfalls; (ii) the pull based approach makes it
easier to avoid "spurious" recomputations, where a node is recomputed but
subsequently becomes unreachable from the top due to a new dependency structure.
Our current experiments show that traversing the whole graph on each rebuild
isn't prohibitively expensive even for large builds where computation graphs
contain tens of millions of nodes. We may rethink this design decision in future
as Dune and Memo need to scale to larger projects. Having said that, we consider
the pull based approach to incremental computation to be under-researched, and
are keen to investigate how far we can take it in practice.
## Development status
Memo is still in active development and we welcome feedback from the OCaml
community on how to make it better. While the current implementation is tied to
Dune's lightweight concurrency library Fiber, the core functionality can be made
available as a functor over an arbitrary concurrency monad, making it usable
with Async and Lwt.
## Acknowledgements
We thank Jeremie Dimino for driving the design of Memo and for his work on
incrementalising Dune. We are also grateful to Rudi Horn, Rudi Grinberg, Emilio Jesús Gallego
Arias and other Dune developers for their many contributions, and to Armaël
Guéneau for helping us integrate the incremental cycle detection library in
Memo.
# References
<!-- References to be generated by Pandoc. -->

View file

@ -0,0 +1,43 @@
@unpublished{dune,
title = {{Dune: A composable build system}},
author = {{Jane~Street}},
year = {2018},
note = "\url{https://dune.build}"
}
@article{mokhov2020build,
title={Build systems {\`a} la carte: Theory and practice},
author={Mokhov, Andrey and Mitchell, Neil and Peyton Jones, Simon},
journal={Journal of Functional Programming},
volume={30},
year={2020},
publisher={Cambridge University Press},
note={\url{https://doi.org/10.1017/S0956796820000088}}
}
@inproceedings{gueneau2019cycles,
title={Formal proof and analysis of an incremental cycle detection algorithm},
author={Gu{\'e}neau, Arma{\"e}l and Jourdan, Jacques-Henri and Chargu{\'e}raud, Arthur and Pottier, Fran{\c{c}}ois},
booktitle={Interactive Theorem Proving},
number={141},
year={2019},
organization={Schloss Dagstuhl--Leibniz-Zentrum fuer Informatik}
}
@unpublished{incremental,
title = {{Incremental: Library for incremental computations}},
author = {{Jane~Street}},
year = {2015},
note = "\url{https://opensource.janestreet.com/incremental}"
}
@article{hammer2014adapton,
title={Adapton: Composable, demand-driven incremental computation},
author={Hammer, Matthew and Phang, Khoo Yit and Hicks, Michael and Foster, Jeffrey},
journal={ACM SIGPLAN Notices},
volume={49},
number={6},
pages={156--166},
year={2014},
publisher={ACM New York, NY, USA}
}

View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<style xmlns="http://purl.org/net/xbiblio/csl" class="in-text" version="1.0">
<info>
<title>A bibliography style mostly inspired by ACM-Reference-Format</title>
<id>andrey-mokhov-personal-csl</id>
<author>
<name>Andrey Mokhov</name>
<email>andrey.mokhov@gmail.com</email>
</author>
<category citation-format="author-date"/>
<category field="engineering"/>
<category field="science"/>
<updated>2021-05-20</updated>
<rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
</info>
<macro name="author-family-name">
<names variable="author">
<name form="short" and="text" delimiter=", "/>
</names>
</macro>
<macro name="author-full-name">
<names variable="author">
<name form="long" and="text" delimiter=", "/>
</names>
</macro>
<macro name="issued-year">
<choose>
<if variable="issued">
<date variable="issued">
<date-part name="year"/>
</date>
</if>
<else>
<text term="no date"/>
</else>
</choose>
</macro>
<citation et-al-min="3" et-al-use-first="1">
<sort>
<key macro="author-family-name"/>
<key macro="issued-year"/>
</sort>
<layout prefix="[" suffix="]" delimiter="; ">
<group delimiter=", ">
<text macro="author-family-name"/>
<text macro="issued-year"/>
</group>
</layout>
</citation>
<bibliography entry-spacing="1">
<sort>
<key macro="author-full-name"/>
<key macro="issued-year"/>
<key variable="title" />
</sort>
<layout>
<text macro="author-full-name" suffix=". " />
<text variable="title" quotes="true" font-style="italic" suffix="." />
<text variable="container-title" prefix=" " suffix="." />
<group suffix=".">
<text variable="volume" prefix=" vol. " />
<text variable="issue" prefix=" (" suffix=")"/>
<text variable="page" prefix=", pp. " />
</group>
<text variable="note" prefix=" " />
<text macro="issued-year" prefix=" (" suffix=") " />
</layout>
</bibliography>
</style>

View file

@ -0,0 +1,498 @@
**********
Quickstart
**********
.. TODO(diataxis)
Split this into:
- :doc:`tutorials/from-zero-to-opam`
- :doc:`tutorials/developing-with-dune`
- :doc:`howto/changing-flags`
- an how-to guide about ``cppo``
- an how-to guide about staged programming / generators
- an how-to guide about testing
This document gives simple usage examples of Dune. You can also look at
`examples <https://github.com/ocaml/dune/tree/master/example>`__ for complete
examples of projects using Dune with `CRAM stanzas <https://ocaml.org/p/craml/1.0.0>`__.
To try these examples, you will need to have Dune installed. See
:doc:`howto/install-dune`.
Initializing Projects
=====================
The following subsections illustrate basic usage of the ``dune init proj``
subcommand. For more documentation, see :ref:`initializing_components` and the
inline help available from ``dune init --help``.
.. _initializing-an-executable:
Initializing an Executable
--------------------------
To initialize a project that will build an executable program, run the following
(replacing ``project_name`` with the name of your project):
.. code:: console
$ dune init proj project_name
This creates a project directory that includes the following contents:
.. code::
project_name/
├── dune-project
├── test
│ ├── dune
│ └── test_project_name.ml
├── lib
│   └── dune
├── bin
│   ├── dune
│   └── main.ml
└── project_name.opam
Now, enter your project's directory:
.. code:: console
$ cd project_name
Then, you can build your project with:
.. code:: console
$ dune build
You can run your tests with:
.. code:: console
$ dune test
You can run your program with:
.. code:: console
$ dune exec project_name
This simple project will print "Hello World" in your shell.
The following itemization of the generated content isn't necessary to review at
this point. But whenever you are ready, it will provide jump-off points from
which you can dive deeper into Dune's capabilities:
* The ``dune-project`` file specifies metadata about the project, including its
name, packaging data (including dependencies), and information about the
authors and maintainers. Open this in your editor to fill in the
placeholder values. See :doc:`/reference/dune-project/index` for
details.
* The ``test`` directory contains a skeleton for your project's tests. Add to
the tests by editing ``test/test_project_name.ml``. See :ref:`writing-tests` for
details on testing.
* The ``lib`` directory will hold the library you write to provide your executable's core
functionality. Add modules to your library by creating new
``.ml`` files in this directory. See :doc:`/reference/dune/library` for
details on specifying libraries manually.
* The ``bin`` directory holds a skeleton for the executable program. Within the
modules in this directory, you can access the modules in your ``lib`` under
the namespace ``project_name.Mod``, where ``project_name`` is replaced with
the name of your project and ``Mod`` corresponds to the name of the file in
the ``lib`` directory. You can run the executable with ``dune exec
project_name``. See :ref:`hello-world-program` for an example of specifying
an executable manually and :doc:`/reference/dune/executable` for
details.
* The ``project_name.opam`` file will be freshly generated from the
``dune-project`` file whenever you build your project. You shouldn't need to
worry about this, but you can see :doc:`explanation/opam-integration` for
details.
* The ``dune`` files in each directory specify the component to be built with
the files in that directory. For details on ``dune`` files, see :doc:`/reference/dune/index`.
Initializing a Library
----------------------
To initialize a project for an OCaml library, run the following (replacing
``project_name`` with the name of your project):
.. code:: console
$ dune init proj --kind=lib project_name
This creates a project directory that includes the following contents:
.. code::
project_name/
├── dune-project
├── lib
│   └── dune
├── test
│ ├── dune
│ └── test_project_name.ml
└── project_name.opam
Now, enter your project's directory:
.. code:: console
$ cd project_name
Then, you can build your project with:
.. code:: console
$ dune build
You can run your tests with:
.. code:: console
$ dune test
All of the subcomponents generated are the same as those described in
:ref:`initializing-an-executable`, with the following exceptions:
* There is no ``bin`` directory generated.
* The ``dune`` file in the ``lib`` directory specifies that the library should
be *public*. See :doc:`/reference/dune/library` for details.
.. _hello-world-program:
Building a Hello World Program From Scratch
===========================================
Create a new directory within a Dune project (:ref:`initializing-an-executable`).
Since OCaml is a compiled language, first create a ``dune`` file in Nano, Vim,
or your preferred text editor. Declare the ``hello_world`` executable by including the following stanza
(shown below). Name this initial file ``dune`` and save it.
.. code:: dune
(executable
(name hello_world))
Create a second file containing the following code and name it ``hello_world.ml`` (including
the .ml extension). It will implement the executable stanza in the ``dune`` file when built.
.. code:: ocaml
print_endline "Hello, world!"
Next, build your new program in a shell using this command:
.. code:: console
$ dune build hello_world.exe
This will create a directory called ``_build`` and build the
program: ``_build/default/hello_world.exe``. Note that
native code executables will have the ``.exe`` extension on all platforms
(including non-Windows systems).
Finally, run it with the following command to see that it worked. In
fact, the executable can both be built and run in a single
step:
.. code:: console
$ dune exec -- ./hello_world.exe
Voila! This should print "Hello, world!" in the command line.
Building a Hello World Program Using Lwt
========================================
Lwt is a concurrent library in OCaml.
In a directory of your choice, write this ``dune`` file:
.. code:: dune
(executable
(name hello_world)
(libraries lwt.unix))
This ``hello_world.ml`` file:
.. code:: ocaml
Lwt_main.run (Lwt_io.printf "Hello, world!\n")
And build it with:
.. code:: console
$ dune build hello_world.exe
The executable will be built as ``_build/default/hello_world.exe``
Building a Hello World Program Using Core and Jane Street PPXs
==============================================================
Write this ``dune`` file:
.. code:: dune
(executable
(name hello_world)
(libraries core)
(preprocess (pps ppx_jane)))
This ``hello_world.ml`` file:
.. code:: ocaml
open Core
let () =
Sexp.to_string_hum [%sexp ([3;4;5] : int list)]
|> print_endline
And build it with:
.. code:: console
$ dune build hello_world.exe
The executable will be built as ``_build/default/hello_world.exe``
Defining a Library Using Lwt and ``ocaml-re``
=============================================
Write this ``dune`` file:
.. code:: dune
(library
(name mylib)
(public_name mylib)
(libraries re lwt))
The library will be composed of all the modules in the same directory.
Outside of the library, module ``Foo`` will be accessible as
``Mylib.Foo``, unless you write an explicit ``mylib.ml`` file.
You can then use this library in any other directory by adding ``mylib``
to the ``(libraries ...)`` field.
Building a Hello World Program in Bytecode
============================================
In a directory of your choice, write this ``dune`` file:
.. code:: dune
;; This declares the hello_world executable implemented by hello_world.ml
;; to be build as native (.exe) or bytecode (.bc) version.
(executable
(name hello_world)
(modes byte exe))
This ``hello_world.ml`` file:
.. code:: ocaml
print_endline "Hello, world!"
And build it with:
.. code:: console
$ dune build hello_world.bc
The executable will be built as ``_build/default/hello_world.bc``.
The executable can be built and run in a single
step with ``dune exec ./hello_world.bc``. This bytecode version allows the usage of
``ocamldebug``.
Setting the OCaml Compilation Flags Globally
============================================
Write this ``dune`` file at the root of your project:
.. code:: dune
(env
(dev
(flags (:standard -w +42)))
(release
(ocamlopt_flags (:standard -O3))))
`dev` and `release` correspond to build profiles. The build profile
can be selected from the command line with ``--profile foo`` or from a
`dune-workspace` file by writing:
.. code:: dune
(profile foo)
Using Cppo
==========
Add this field to your ``library`` or ``executable`` stanzas:
.. code:: dune
(preprocess (action (run %{bin:cppo} -V OCAML:%{ocaml_version} %{input-file})))
Additionally, if you want to include a ``config.h`` file, you need to
declare the dependency to this file via:
.. code:: dune
(preprocessor_deps config.h)
Using the ``.cppo.ml`` Style Like the ``ocamlbuild`` Plugin
-----------------------------------------------------------
Write this in your ``dune`` file:
.. code:: dune
(rule
(targets foo.ml)
(deps (:first-dep foo.cppo.ml) <other files that foo.ml includes>)
(action (run %{bin:cppo} %{first-dep} -o %{targets})))
Defining a Library with C Stubs
===============================
Assuming you have a file called ``mystubs.c``, that you need to pass
``-I/blah/include`` to compile it and ``-lblah`` at link time, write
this ``dune`` file:
.. code:: dune
(library
(name mylib)
(public_name mylib)
(libraries re lwt)
(foreign_stubs
(language c)
(names mystubs)
(flags -I/blah/include))
(c_library_flags (-lblah)))
Defining a Library with C Stubs using ``pkg-config``
====================================================
Same context as before, but using ``pkg-config`` to query the
compilation and link flags. Write this ``dune`` file:
.. code:: dune
(library
(name mylib)
(public_name mylib)
(libraries re lwt)
(foreign_stubs
(language c)
(names mystubs)
(flags (:include c_flags.sexp)))
(c_library_flags (:include c_library_flags.sexp)))
(rule
(targets c_flags.sexp c_library_flags.sexp)
(action (run ./config/discover.exe)))
Then create a ``config`` subdirectory and write this ``dune`` file:
.. code:: dune
(executable
(name discover)
(libraries dune-configurator))
as well as this ``discover.ml`` file:
.. code:: ocaml
module C = Configurator.V1
let () =
C.main ~name:"foo" (fun c ->
let default : C.Pkg_config.package_conf =
{ libs = ["-lgst-editing-services-1.0"]
; cflags = []
}
in
let conf =
match C.Pkg_config.get c with
| None -> default
| Some pc ->
match (C.Pkg_config.query pc ~package:"gst-editing-services-1.0") with
| None -> default
| Some deps -> deps
in
C.Flags.write_sexp "c_flags.sexp" conf.cflags;
C.Flags.write_sexp "c_library_flags.sexp" conf.libs)
Using a Custom Code Generator
=============================
To generate a file ``foo.ml`` using a program from another directory:
.. code:: dune
(rule
(targets foo.ml)
(deps (:gen ../generator/gen.exe))
(action (run %{gen} -o %{targets})))
Defining Tests
==============
Write this in your ``dune`` file:
.. code:: dune
(test (name my_test_program))
And run the tests with:
.. code:: console
$ dune runtest
It will run the test program (the main module is ``my_test_program.ml``) and
error if it exits with a nonzero code.
In addition, if a ``my_test_program.expected`` file exists, it will be compared
to the standard output of the test program and the differences will be
displayed. It is possible to replace the ``.expected`` file with the last output
using:
.. code:: console
$ dune promote
Building a Custom Toplevel
==========================
A toplevel is simply an executable calling ``Topmain.main ()`` and linked with
the compiler libraries and ``-linkall``. Moreover, currently toplevels can only
be built in bytecode.
As a result, write this in your ``dune`` file:
.. code:: dune
(executable
(name mytoplevel)
(libraries compiler-libs.toplevel mylib)
(link_flags (-linkall))
(modes byte))
And write this in ``mytoplevel.ml``:
.. code:: ocaml
let () = exit (Topmain.main ())

View file

@ -0,0 +1,12 @@
bash
----
.. highlight:: dune
.. describe:: (bash <cmd>)
Execute a command using ``/bin/bash``. This is obviously not very portable.
Example::
(bash "echo $PATH")

View file

@ -0,0 +1,12 @@
cat
---
.. highlight:: dune
.. describe:: (cat <file> ...)
Sequentially print the contents of files to stdout.
Example::
(cat data.txt)

View file

@ -0,0 +1,13 @@
chdir
-----
.. highlight:: dune
.. describe:: (chdir <dir> <DSL>)
Run an action in a different directory.
Example::
(chdir src
(run ./build.exe))

View file

@ -0,0 +1,13 @@
cmp
---
.. highlight:: dune
.. describe:: (cmp <file1> <file2>)
``(cmp <file1> <file2>)`` is similar to ``(run cmp <file1> <file2>)`` but
allows promotion. See :doc:`/concepts/promotion` for more details.
Example::
(cmp bin.expected bin.output)

View file

@ -0,0 +1,19 @@
concurrent
----------
.. highlight:: dune
.. describe:: (concurrent <DSL> ...)
Execute several commands concurrently and collect all resulting errors, if any.
.. warning:: The concurrency is limited by the ``-j`` flag passed to Dune.
In particular, if Dune is running with ``-j 1``, these commands will
actually run sequentially, which may cause a deadlock if they talk to
each other.
Example::
(concurrent
(run ./proga.exe)
(run ./progb.exe))

View file

@ -0,0 +1,27 @@
copy#
-----
.. highlight:: dune
.. describe:: (copy# <src> <dst>)
Copy a file and add a line directive at the beginning.
Example::
(copy# config.windows.ml config.ml)
More precisely, ``copy#`` inserts the following line:
.. code:: ocaml
# 1 "<source file name>"
Most languages recognize such lines and update their current location to
report errors in the original file rather than the copy. This is important
because the copy exists only under the ``_build`` directory, and in order
for editors to jump to errors when parsing the build system's output, errors
must point to files that exist in the source tree. In the beta versions of
Dune, ``copy#`` was called ``copy-and-add-line-directive``. However, most of
time, one wants this behavior rather than a bare copy, so it was renamed to
something shorter.

View file

@ -0,0 +1,14 @@
copy
----
.. highlight:: dune
.. describe:: (copy <src> <dst>)
Copy a file. If these files are OCaml sources, you should follow the
``module_name.xxx.ml`` :ref:`naming convention <merlin-filenames>` to
preserve Merlin's functionality.
Example::
(copy data.txt.template data.txt)

View file

@ -0,0 +1,14 @@
diff
----
.. highlight:: dune
.. describe:: (diff <file1> <file2>)
``(diff <file1> <file2>)`` is similar to ``(run diff <file1> <file2>)`` but
is better and allows promotion. See :doc:`/concepts/promotion` for more
details.
Example::
(diff test.expected test.output)

View file

@ -0,0 +1,16 @@
diff?
-----
.. highlight:: dune
.. describe:: (diff? <file1> <file2>)
``(diff? <file1> <file2>)`` is similar to ``(diff <file1> <file2>)`` except
that ``<file2>`` should be produced by a part of the same action rather than
be a dependency, is optional and will be consumed by ``diff?``.
Example::
(progn
(with-stdout-to test.output (run ./test.exe))
(diff? test.expected test.output))

View file

@ -0,0 +1,13 @@
dynamic-run
-----------
.. highlight:: dune
.. describe:: (dynamic-run <prog> <args>)
Execute a program that was linked against the ``dune-action-plugin`` library.
``<prog>`` is resolved in the same way as in :doc:`run`.
Example::
(dynamic-run ./plugin.exe)

View file

@ -0,0 +1,12 @@
echo
----
.. highlight:: dune
.. describe:: (echo <string>)
Output a string on ``stdout``.
Example::
(echo "Hello, world")

View file

@ -0,0 +1,14 @@
cat
---
.. highlight:: dune
.. describe:: (format-dune-file <src> <dst>)
Output the formatted contents of the file ``<src>`` to ``<dst>``. The source
file is assumed to contain S-expressions. Note that the precise formatting
can depend on the version of the Dune language used by containing project.
Example::
(format-dune-file file.sexp file.sexp.formatted)

View file

@ -0,0 +1,14 @@
ignore-<outputs>
----------------
.. highlight:: dune
.. describe:: (ignore-<outputs> <DSL>)
Ignore the output, where ``<outputs>`` is one of: ``stdout``, ``stderr``, or
``outputs``.
Example::
(ignore-stderr
(run ./get-conf.exe))

View file

@ -0,0 +1,120 @@
Actions
=======
.. highlight:: dune
``(action ...)`` fields describe user actions.
User actions are always run from the same subdirectory of the current build
context as the ``dune`` file they are defined in, so for instance, an action defined
in ``src/foo/dune`` will be run from ``$build/<context>/src/foo``.
The argument of ``(action ...)`` fields is a small DSL that's interpreted by
Dune directly and doesn't require an external shell. All atoms in the DSL
support :doc:`/concepts/variables`. Moreover, you don't need to specify
dependencies explicitly for the special ``%{<kind>:...}`` forms; these are
recognized and automatically handled by Dune.
The DSL is currently quite limited, so if you want to do something complicated,
it's recommended to write a small OCaml program and use the DSL to invoke it.
You can use `shexp <https://github.com/janestreet/shexp>`__ to write portable
scripts or :ref:`configurator` for configuration related tasks. You can also
use :ref:`dune-action-plugin` to express program dependencies directly in the
source code.
The following constructions are available:
.. grid:: 1 1 2 2
.. grid-item::
.. toctree::
:caption: Running commands
run
system
bash
dynamic-run
chdir
setenv
with-accepted-exit-codes
.. grid-item::
.. toctree::
:caption: Input and output
echo
with-outputs-to
with-stdin-from
ignore-outputs
cat
copy
copy#
write-file
pipe-outputs
format-dune-file
.. grid-item::
.. toctree::
:caption: Comparing files
diff
diffq
cmp
.. grid-item::
.. toctree::
:caption: Control structures
progn
concurrent
no-infer
Note: expansion of the special ``%{<kind>:...}`` is done relative to the current
working directory of the DSL being executed. So for instance, if you
have this action in a ``src/foo/dune``:
.. code:: dune
(action (chdir ../../.. (echo %{dep:dune})))
Then ``%{dep:dune}`` will expand to ``src/foo/dune``. When you run various
tools, they often use the filename given on the command line in error messages.
As a result, if you execute the command from the original directory, it will
only see the basename.
To understand why this is important, let's consider this ``dune`` file living in
``src/foo``::
(rule
(target blah.ml)
(deps blah.mll)
(action
(run ocamllex -o %{target} %{deps})))
Here the command that will be executed is:
.. code:: console
$ ocamllex -o blah.ml blah.mll
And it will be executed in ``_build/<context>/src/foo``. As a result, if there
is an error in the generated ``blah.ml`` file, it will be reported as:
::
File "blah.ml", line 42, characters 5-10:
Error: ...
Which can be a problem, as your editor might think that ``blah.ml`` is at the root
of your project. Instead, this is a better way to write it::
(rule
(target blah.ml)
(deps blah.mll)
(action
(chdir %{workspace_root}
(run ocamllex -o %{target} %{deps}))))

View file

@ -0,0 +1,17 @@
no-infer
--------
.. highlight:: dune
.. describe:: (no-infer <DSL>)
Perform an action without inference of dependencies and targets. This is
useful if you are generating dependencies in a way that Dune doesn't know
about, for instance by calling an external build system.
Example::
(no-infer
(progn
(run make)
(copy mylib.a lib.a)))

View file

@ -0,0 +1,18 @@
pipe-<outputs>
--------------
.. highlight:: dune
.. describe:: (pipe-<outputs> <DSL> <DSL> <DSL>...)
.. versionadded:: 2.7
Execute several actions (at least two) in sequence, filtering the
``<outputs>`` of the first command through the other command, piping the
standard output of each one into the input of the next.
Example::
(pipe-stdout
(run ./list-tests.exe)
(run ./exec-tests.exe))

View file

@ -0,0 +1,14 @@
progn
-----
.. highlight:: dune
.. describe:: (progn <DSL> ...)
Execute several commands in sequence.
Example::
(progn
(run ./proga.exe)
(run ./progb.exe))

View file

@ -0,0 +1,13 @@
run
---
.. highlight:: dune
.. describe:: (run <prog> <args>)
Execute a program. ``<prog>`` is resolved locally if it is available in the
current workspace, otherwise it is resolved using the ``PATH``.
Example::
(run capnp compile -o %{bin:capnpc-ocaml} schema.capnp)

View file

@ -0,0 +1,14 @@
setenv
------
.. highlight:: dune
.. describe:: (setenv <var> <value> <DSL>)
Run an action with an environment variable set.
Example::
(setenv
VAR value
(bash "echo $VAR"))

View file

@ -0,0 +1,12 @@
system
------
.. highlight:: dune
.. describe:: (system <cmd>)
Execute a command using the system shell: ``sh`` on Unix and ``cmd`` on Windows.
Example::
(system "command arg1 arg2")

View file

@ -0,0 +1,20 @@
with-accepted-exit-codes
------------------------
.. highlight:: dune
.. describe:: (with-accepted-exit-codes <pred> <DSL>)
.. versionadded:: 2.0
Specifies the list of expected exit codes for the programs executed in
``<DSL>``. ``<pred>`` is a predicate on integer values, and it's specified
using the :doc:`/reference/predicate-language`. ``<DSL>`` can only contain
nested occurrences of ``run``, ``bash``, ``system``, ``chdir``, ``setenv``,
``ignore-<outputs>``, ``with-stdin-from``, and ``with-<outputs>-to``.
Example::
(with-accepted-exit-codes
(or 1 2)
(run false))

View file

@ -0,0 +1,14 @@
with-<outputs>-to
-----------------
.. highlight:: dune
.. describe:: (with-<outputs>-to <file> <DSL>)
Redirect the output to a file, where ``<outputs>`` is one of: ``stdout``,
``stderr`` or ``outputs`` (for both ``stdout`` and ``stderr``).
Example::
(with-stdout-to conf.txt
(run ./get-conf.exe))

Some files were not shown because too many files have changed in this diff Show more