This commit is contained in:
parent
aa2ff7b2f0
commit
2f3113f55d
11742 changed files with 1223940 additions and 0 deletions
30
unikernel/duniverse/dune_/doc/advanced/custom-cmxs.rst
Normal file
30
unikernel/duniverse/dune_/doc/advanced/custom-cmxs.rst
Normal 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})))
|
||||
58
unikernel/duniverse/dune_/doc/advanced/findlib-dynamic.rst
Normal file
58
unikernel/duniverse/dune_/doc/advanced/findlib-dynamic.rst
Normal 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
|
||||
14
unikernel/duniverse/dune_/doc/advanced/index.rst
Normal file
14
unikernel/duniverse/dune_/doc/advanced/index.rst
Normal 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
|
||||
21
unikernel/duniverse/dune_/doc/advanced/ocaml-syntax.rst
Normal file
21
unikernel/duniverse/dune_/doc/advanced/ocaml-syntax.rst
Normal 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.
|
||||
16
unikernel/duniverse/dune_/doc/advanced/package-version.rst
Normal file
16
unikernel/duniverse/dune_/doc/advanced/package-version.rst
Normal 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.
|
||||
15
unikernel/duniverse/dune_/doc/advanced/profiling-dune.rst
Normal file
15
unikernel/duniverse/dune_/doc/advanced/profiling-dune.rst
Normal 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
|
||||
|
|
@ -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>``).
|
||||
BIN
unikernel/duniverse/dune_/doc/assets/imgs/dune_logo_459x116.png
Normal file
BIN
unikernel/duniverse/dune_/doc/assets/imgs/dune_logo_459x116.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
111
unikernel/duniverse/dune_/doc/caching.rst
Normal file
111
unikernel/duniverse/dune_/doc/caching.rst
Normal 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`.
|
||||
0
unikernel/duniverse/dune_/doc/changes/.gitkeep
Normal file
0
unikernel/duniverse/dune_/doc/changes/.gitkeep
Normal file
152
unikernel/duniverse/dune_/doc/concepts/dependency-spec.rst
Normal file
152
unikernel/duniverse/dune_/doc/concepts/dependency-spec.rst
Normal 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``
|
||||
53
unikernel/duniverse/dune_/doc/concepts/locks.rst
Normal file
53
unikernel/duniverse/dune_/doc/concepts/locks.rst
Normal 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})))
|
||||
|
||||
22
unikernel/duniverse/dune_/doc/concepts/ocaml-flags.rst
Normal file
22
unikernel/duniverse/dune_/doc/concepts/ocaml-flags.rst
Normal 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>))
|
||||
147
unikernel/duniverse/dune_/doc/concepts/package-spec.rst
Normal file
147
unikernel/duniverse/dune_/doc/concepts/package-spec.rst
Normal 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.
|
||||
109
unikernel/duniverse/dune_/doc/concepts/promotion.rst
Normal file
109
unikernel/duniverse/dune_/doc/concepts/promotion.rst
Normal 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`.
|
||||
76
unikernel/duniverse/dune_/doc/concepts/sandboxing.rst
Normal file
76
unikernel/duniverse/dune_/doc/concepts/sandboxing.rst
Normal 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``)
|
||||
241
unikernel/duniverse/dune_/doc/concepts/variables.rst
Normal file
241
unikernel/duniverse/dune_/doc/concepts/variables.rst
Normal 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)))
|
||||
174
unikernel/duniverse/dune_/doc/conf.py
Normal file
174
unikernel/duniverse/dune_/doc/conf.py
Normal 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/',
|
||||
}
|
||||
851
unikernel/duniverse/dune_/doc/coq.rst
Normal file
851
unikernel/duniverse/dune_/doc/coq.rst
Normal 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.
|
||||
104
unikernel/duniverse/dune_/doc/cross-compilation.rst
Normal file
104
unikernel/duniverse/dune_/doc/cross-compilation.rst
Normal 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})))
|
||||
5
unikernel/duniverse/dune_/doc/dev/README.md
Normal file
5
unikernel/duniverse/dune_/doc/dev/README.md
Normal 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.
|
||||
279
unikernel/duniverse/dune_/doc/dev/cache.md
Normal file
279
unikernel/duniverse/dune_/doc/dev/cache.md
Normal 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 – artifacts and values – 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 – 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.
|
||||
64
unikernel/duniverse/dune_/doc/dev/directory-targets.md
Normal file
64
unikernel/duniverse/dune_/doc/dev/directory-targets.md
Normal 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.
|
||||
106
unikernel/duniverse/dune_/doc/dev/rev-store.md
Normal file
106
unikernel/duniverse/dune_/doc/dev/rev-store.md
Normal 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.
|
||||
180
unikernel/duniverse/dune_/doc/dev/rpc-versioning.md
Normal file
180
unikernel/duniverse/dune_/doc/dev/rpc-versioning.md
Normal 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.
|
||||
176
unikernel/duniverse/dune_/doc/dev/rule-production.md
Normal file
176
unikernel/duniverse/dune_/doc/dev/rule-production.md
Normal 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.
|
||||
82
unikernel/duniverse/dune_/doc/dev/rule-streaming.md
Normal file
82
unikernel/duniverse/dune_/doc/dev/rule-streaming.md
Normal 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.
|
||||
157
unikernel/duniverse/dune_/doc/documentation.rst
Normal file
157
unikernel/duniverse/dune_/doc/documentation.rst
Normal 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
|
||||
36
unikernel/duniverse/dune_/doc/dune
Normal file
36
unikernel/duniverse/dune_/doc/dune
Normal 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)
|
||||
193
unikernel/duniverse/dune_/doc/dune-libs.rst
Normal file
193
unikernel/duniverse/dune_/doc/dune-libs.rst
Normal 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`.
|
||||
316
unikernel/duniverse/dune_/doc/dune.inc
Normal file
316
unikernel/duniverse/dune_/doc/dune.inc
Normal 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))
|
||||
|
||||
53
unikernel/duniverse/dune_/doc/explanation/bootstrap.rst
Normal file
53
unikernel/duniverse/dune_/doc/explanation/bootstrap.rst
Normal 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.
|
||||
17
unikernel/duniverse/dune_/doc/explanation/index.rst
Normal file
17
unikernel/duniverse/dune_/doc/explanation/index.rst
Normal 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
|
||||
202
unikernel/duniverse/dune_/doc/explanation/mental-model.rst
Normal file
202
unikernel/duniverse/dune_/doc/explanation/mental-model.rst
Normal 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.
|
||||
|
|
@ -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.
|
||||
119
unikernel/duniverse/dune_/doc/explanation/opam-integration.rst
Normal file
119
unikernel/duniverse/dune_/doc/explanation/opam-integration.rst
Normal 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.
|
||||
234
unikernel/duniverse/dune_/doc/explanation/package-management.md
Normal file
234
unikernel/duniverse/dune_/doc/explanation/package-management.md
Normal 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.
|
||||
55
unikernel/duniverse/dune_/doc/explanation/preprocessing.rst
Normal file
55
unikernel/duniverse/dune_/doc/explanation/preprocessing.rst
Normal 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.
|
||||
43
unikernel/duniverse/dune_/doc/explanation/scopes.rst
Normal file
43
unikernel/duniverse/dune_/doc/explanation/scopes.rst
Normal 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.
|
||||
29
unikernel/duniverse/dune_/doc/explanation/tour/cli.rst
Normal file
29
unikernel/duniverse/dune_/doc/explanation/tour/cli.rst
Normal 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
|
||||
69
unikernel/duniverse/dune_/doc/explanation/tour/decoding.rst
Normal file
69
unikernel/duniverse/dune_/doc/explanation/tour/decoding.rst
Normal 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/`.
|
||||
15
unikernel/duniverse/dune_/doc/explanation/tour/engine.rst
Normal file
15
unikernel/duniverse/dune_/doc/explanation/tour/engine.rst
Normal 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.
|
||||
34
unikernel/duniverse/dune_/doc/explanation/tour/index.rst
Normal file
34
unikernel/duniverse/dune_/doc/explanation/tour/index.rst
Normal 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
|
||||
12
unikernel/duniverse/dune_/doc/explanation/tour/libraries.rst
Normal file
12
unikernel/duniverse/dune_/doc/explanation/tour/libraries.rst
Normal 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`
|
||||
|
|
@ -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.
|
||||
33
unikernel/duniverse/dune_/doc/explanation/tour/tests.rst
Normal file
33
unikernel/duniverse/dune_/doc/explanation/tour/tests.rst
Normal 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`
|
||||
15
unikernel/duniverse/dune_/doc/explanation/tour/vendor.rst
Normal file
15
unikernel/duniverse/dune_/doc/explanation/tour/vendor.rst
Normal 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>`_.
|
||||
24
unikernel/duniverse/dune_/doc/exts/cram_lexer.py
Normal file
24
unikernel/duniverse/dune_/doc/exts/cram_lexer.py
Normal 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)
|
||||
36
unikernel/duniverse/dune_/doc/exts/dune_lexer.py
Normal file
36
unikernel/duniverse/dune_/doc/exts/dune_lexer.py
Normal 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")],
|
||||
}
|
||||
26
unikernel/duniverse/dune_/doc/exts/opam_lexer.py
Normal file
26
unikernel/duniverse/dune_/doc/exts/opam_lexer.py
Normal 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),
|
||||
]
|
||||
}
|
||||
266
unikernel/duniverse/dune_/doc/faq.rst
Normal file
266
unikernel/duniverse/dune_/doc/faq.rst
Normal 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.
|
||||
392
unikernel/duniverse/dune_/doc/foreign-code.rst
Normal file
392
unikernel/duniverse/dune_/doc/foreign-code.rst
Normal 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
|
||||
12
unikernel/duniverse/dune_/doc/getting-started/index.rst
Normal file
12
unikernel/duniverse/dune_/doc/getting-started/index.rst
Normal 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
|
||||
149
unikernel/duniverse/dune_/doc/goals.rst
Normal file
149
unikernel/duniverse/dune_/doc/goals.rst
Normal 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.
|
||||
800
unikernel/duniverse/dune_/doc/hacking.rst
Normal file
800
unikernel/duniverse/dune_/doc/hacking.rst
Normal 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>`_
|
||||
41
unikernel/duniverse/dune_/doc/howto/bundle.rst
Normal file
41
unikernel/duniverse/dune_/doc/howto/bundle.rst
Normal 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
|
||||
87
unikernel/duniverse/dune_/doc/howto/formatting.rst
Normal file
87
unikernel/duniverse/dune_/doc/howto/formatting.rst
Normal 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
|
||||
25
unikernel/duniverse/dune_/doc/howto/index.rst
Normal file
25
unikernel/duniverse/dune_/doc/howto/index.rst
Normal 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
|
||||
39
unikernel/duniverse/dune_/doc/howto/install-dune.rst
Normal file
39
unikernel/duniverse/dune_/doc/howto/install-dune.rst
Normal 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.
|
||||
143
unikernel/duniverse/dune_/doc/howto/opam-file-generation.rst
Normal file
143
unikernel/duniverse/dune_/doc/howto/opam-file-generation.rst
Normal 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.
|
||||
|
|
@ -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!
|
||||
266
unikernel/duniverse/dune_/doc/howto/rule-generation.rst
Normal file
266
unikernel/duniverse/dune_/doc/howto/rule-generation.rst
Normal 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.
|
||||
55
unikernel/duniverse/dune_/doc/howto/toplevel.rst
Normal file
55
unikernel/duniverse/dune_/doc/howto/toplevel.rst
Normal 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.
|
||||
6
unikernel/duniverse/dune_/doc/index.mld
Normal file
6
unikernel/duniverse/dune_/doc/index.mld
Normal 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}
|
||||
16
unikernel/duniverse/dune_/doc/index.rst
Normal file
16
unikernel/duniverse/dune_/doc/index.rst
Normal 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
|
||||
139
unikernel/duniverse/dune_/doc/instrumentation.rst
Normal file
139
unikernel/duniverse/dune_/doc/instrumentation.rst
Normal 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
|
||||
86
unikernel/duniverse/dune_/doc/jsoo.rst
Normal file
86
unikernel/duniverse/dune_/doc/jsoo.rst
Normal 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/
|
||||
36
unikernel/duniverse/dune_/doc/make-help.txt
Normal file
36
unikernel/duniverse/dune_/doc/make-help.txt
Normal 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.
|
||||
316
unikernel/duniverse/dune_/doc/melange.rst
Normal file
316
unikernel/duniverse/dune_/doc/melange.rst
Normal 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.
|
||||
12
unikernel/duniverse/dune_/doc/misc/index.rst
Normal file
12
unikernel/duniverse/dune_/doc/misc/index.rst
Normal 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
|
||||
179
unikernel/duniverse/dune_/doc/overview.rst
Normal file
179
unikernel/duniverse/dune_/doc/overview.rst
Normal 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.
|
||||
1
unikernel/duniverse/dune_/doc/papers/.gitignore
vendored
Normal file
1
unikernel/duniverse/dune_/doc/papers/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
*.pdf
|
||||
|
|
@ -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`.
|
||||
10
unikernel/duniverse/dune_/doc/papers/ocaml-2022/header.tex
Normal file
10
unikernel/duniverse/dune_/doc/papers/ocaml-2022/header.tex
Normal 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}
|
||||
151
unikernel/duniverse/dune_/doc/papers/ocaml-2022/memo.md
Normal file
151
unikernel/duniverse/dune_/doc/papers/ocaml-2022/memo.md
Normal 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. -->
|
||||
43
unikernel/duniverse/dune_/doc/papers/ocaml-2022/refs.bib
Normal file
43
unikernel/duniverse/dune_/doc/papers/ocaml-2022/refs.bib
Normal 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}
|
||||
}
|
||||
69
unikernel/duniverse/dune_/doc/papers/ocaml-2022/refs.csl
Normal file
69
unikernel/duniverse/dune_/doc/papers/ocaml-2022/refs.csl
Normal 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>
|
||||
498
unikernel/duniverse/dune_/doc/quick-start.rst
Normal file
498
unikernel/duniverse/dune_/doc/quick-start.rst
Normal 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 ())
|
||||
12
unikernel/duniverse/dune_/doc/reference/actions/bash.rst
Normal file
12
unikernel/duniverse/dune_/doc/reference/actions/bash.rst
Normal 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")
|
||||
12
unikernel/duniverse/dune_/doc/reference/actions/cat.rst
Normal file
12
unikernel/duniverse/dune_/doc/reference/actions/cat.rst
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
cat
|
||||
---
|
||||
|
||||
.. highlight:: dune
|
||||
|
||||
.. describe:: (cat <file> ...)
|
||||
|
||||
Sequentially print the contents of files to stdout.
|
||||
|
||||
Example::
|
||||
|
||||
(cat data.txt)
|
||||
13
unikernel/duniverse/dune_/doc/reference/actions/chdir.rst
Normal file
13
unikernel/duniverse/dune_/doc/reference/actions/chdir.rst
Normal 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))
|
||||
13
unikernel/duniverse/dune_/doc/reference/actions/cmp.rst
Normal file
13
unikernel/duniverse/dune_/doc/reference/actions/cmp.rst
Normal 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)
|
||||
|
|
@ -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))
|
||||
27
unikernel/duniverse/dune_/doc/reference/actions/copy#.rst
Normal file
27
unikernel/duniverse/dune_/doc/reference/actions/copy#.rst
Normal 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.
|
||||
14
unikernel/duniverse/dune_/doc/reference/actions/copy.rst
Normal file
14
unikernel/duniverse/dune_/doc/reference/actions/copy.rst
Normal 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)
|
||||
14
unikernel/duniverse/dune_/doc/reference/actions/diff.rst
Normal file
14
unikernel/duniverse/dune_/doc/reference/actions/diff.rst
Normal 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)
|
||||
16
unikernel/duniverse/dune_/doc/reference/actions/diffq.rst
Normal file
16
unikernel/duniverse/dune_/doc/reference/actions/diffq.rst
Normal 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))
|
||||
|
|
@ -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)
|
||||
12
unikernel/duniverse/dune_/doc/reference/actions/echo.rst
Normal file
12
unikernel/duniverse/dune_/doc/reference/actions/echo.rst
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
echo
|
||||
----
|
||||
|
||||
.. highlight:: dune
|
||||
|
||||
.. describe:: (echo <string>)
|
||||
|
||||
Output a string on ``stdout``.
|
||||
|
||||
Example::
|
||||
|
||||
(echo "Hello, world")
|
||||
|
|
@ -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)
|
||||
|
|
@ -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))
|
||||
120
unikernel/duniverse/dune_/doc/reference/actions/index.rst
Normal file
120
unikernel/duniverse/dune_/doc/reference/actions/index.rst
Normal 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}))))
|
||||
17
unikernel/duniverse/dune_/doc/reference/actions/no-infer.rst
Normal file
17
unikernel/duniverse/dune_/doc/reference/actions/no-infer.rst
Normal 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)))
|
||||
|
|
@ -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))
|
||||
14
unikernel/duniverse/dune_/doc/reference/actions/progn.rst
Normal file
14
unikernel/duniverse/dune_/doc/reference/actions/progn.rst
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
progn
|
||||
-----
|
||||
|
||||
.. highlight:: dune
|
||||
|
||||
.. describe:: (progn <DSL> ...)
|
||||
|
||||
Execute several commands in sequence.
|
||||
|
||||
Example::
|
||||
|
||||
(progn
|
||||
(run ./proga.exe)
|
||||
(run ./progb.exe))
|
||||
13
unikernel/duniverse/dune_/doc/reference/actions/run.rst
Normal file
13
unikernel/duniverse/dune_/doc/reference/actions/run.rst
Normal 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)
|
||||
14
unikernel/duniverse/dune_/doc/reference/actions/setenv.rst
Normal file
14
unikernel/duniverse/dune_/doc/reference/actions/setenv.rst
Normal 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"))
|
||||
12
unikernel/duniverse/dune_/doc/reference/actions/system.rst
Normal file
12
unikernel/duniverse/dune_/doc/reference/actions/system.rst
Normal 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")
|
||||
|
|
@ -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))
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue