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

View file

@ -0,0 +1,354 @@
.. _sites:
***************************************
How to Load Additional Files at Runtime
***************************************
.. TODO(diataxis)
Split between:
- an how-to guide
- some reference material
There are many ways for applications to load files at runtime and Dune provides
a well-tested, key-in-hand portable system for doing so. The Dune model works by
defining ``sites`` where files will be installed and looked up at runtime. At
runtime, each site is associated to a list of directories which contain the
files added in the site.
*WARNING*: This feature remains experimental and is subject to breaking changes
without warning. It must be explicitly enabled in the ``dune-project`` file with
``(using dune_site 0.1)``
Sites
=====
Defining a Site
---------------
A site is defined in a package :doc:`/reference/dune-project/package` in the
``dune-project`` file. It consists of a name and a :doc:`section
</reference/dune/install>` (e.g ``lib``, ``share``, ``etc``) where the site
will be installed as a sub-directory.
.. code:: dune
(lang dune 3.20)
(using dune_site 0.1)
(name mygui)
(package
(name mygui)
(sites (share themes)))
Adding Files to a Site
----------------------
Here the package ``mygui`` defines a site named ``themes`` that will be located
in the section ``share``. This package can add files to this ``site`` using the
:doc:`install stanza </reference/dune/install>`:
.. code:: dune
(install
(section (site (mygui themes)))
(files
(layout.css as default/layout.css)
(ok.png as default/ok.png)
(ko.png as default/ko.png)))
Another package ``mygui_material_theme`` can install files inside ``mygui``
directory for adding a new theme. Inside the scope of ``mygui_material_theme``
the ``dune`` file contains:
.. code:: dune
(install
(section (site mygui themes))
(files
(layout.css as material/layout.css)
(ok.png as material/ok.png)
(ko.png as material/ko.png)))
The package ``mygui`` must be present in the workspace or installed.
.. warning::
Two files should not be installed by different packages at the same destination.
Getting the Locations of a Site at Runtime
------------------------------------------
The executable ``mygui`` will be able to get the locations of the ``themes``
site using the :doc:`generate_sites_module stanza
</reference/dune/generate_sites_module>`.
.. code:: dune
(executable
(name mygui)
(modules mygui mysites)
(libraries dune-site))
(generate_sites_module
(module mysites)
(sites mygui))
The generated module ``mysites`` depends on the library ``dune-site`` provided
by Dune. As such, the the dependency on ``dune-site`` must be specified
explicitly.
.. note::
The dependency on ``dune-site`` also needs to be added to the ``depends``
field of your project. If left out it might be accidentally captured via
transitive dependencies, however this will not work if
:doc:`implicit_transitive_deps </reference/dune-project/implicit_transitive_deps>`
is set to ``false`` in the ``dune-project`` file. It is always recommended
to specify ``dune-site`` as a dependency explicitly.
Then inside ``mygui.ml`` module the locations can be recovered and used:
.. code:: ocaml
(** Locations of the site for the themes *)
let themes_locations : string list = Mysites.Sites.themes
(** Merge the contents of the directories in [dirs] *)
let lookup_dirs dirs =
List.filter Sys.file_exists dirs
|> List.map (fun dir -> Array.to_list (Sys.readdir dir))
|> List.concat
(** Get the available themes *)
let find_available_themes () = lookup_dirs themes_locations
(** [lookup_file name dirs] finds the first file called [name] in [dirs] *)
let lookup_file filename dirs =
List.find_map
(fun dir ->
let filename' = Filename.concat dir filename in
if Sys.file_exists filename' then Some filename' else None)
dirs
(** [lookup_theme_file theme file] get the [file] of the [theme] *)
let lookup_theme_file file theme =
lookup_file (Filename.concat theme file) themes_locations
let get_layout_css = lookup_theme_file "layout.css"
let get_ok_ico = lookup_theme_file "ok.png"
let get_ko_ico = lookup_theme_file "ko.png"
Tests
-----
During tests, the files are copied into the sites through the dependency
``(package mygui)`` and ``(package mygui_material_theme)`` as for other files in
install stanza.
Installation
------------
Installation is done simply with ``dune install``; however, if one wants to
install this tool to make it relocatable, one can use ``dune
install --relocatable --prefix $dir``. The files will be copied to the directory
``$dir`` but the binary ``$dir/bin/mygui`` will find the site location relative
to its location. So even if the directory ``$dir`` is moved,
``themes_locations`` will be correct.
For installation through opam, ``dune install`` must be invoked with the option
``--create-install-files`` which creates an install file ``<pkg>.install`` and
copy the file that needs substitution to an intermediary directory. The
``<pkg>.opam`` file generated by Dune
:doc:`/reference/dune-project/generate_opam_files` does the right
invocation.
Implementation Details
----------------------
The main difficulty for sites is that their directories are found at different
locations at different times:
- When the package is available locally, the location is inside ``_build``
- When the package is installed, the location is inside the install prefix
- If a local package wants to install files to the site of another installed
package the location is at the same time in ``_build`` and in the install prefix
of the second package.
With the last example, we see that the location of a site is not always a single
directory, but rather it can consist of a sequence of directories: ``["dir1" ; "dir2"]``.
So a lookup must first look into `dir1`, then into `dir2`.
.. _plugins:
Plugins and Dynamic Loading of Packages
========================================
Dune allows you to define and load plugins without having to deal with specific
compilation, installation directories, dependencies, or the ``Dynlink_`` module.
To define a plugin:
- The package defining the plugin interface must define a `site` where the
plugins must live. Traditionally, this is in ``(lib plugins)``, but it's just
a convention.
- Define a library that each plugin must use to register itself (or otherwise
provide its functionality).
- Define the plugin in another package using the `plugin` stanza.
- Generate a module that may load all available plugins using the
`generated_module` stanza.
Example
-------
We demonstrate an example of the scheme above. The example consists of the
following components:
Inside package `app`:
- An executable `app`, that we intend to extend with plugins
- A library `app.registration` which defines the plugin registration interface
- A generated module `Sites` which can load available plugins at runtime
- An executable `app` that will use the module `Sites` to load all the plugins
Inside package `Plugin1`, we declare a plugin using the `app.registration` api and the
`plugin` stanza.
Directory structure
^^^^^^^^^^^^^^^^^^^
.. code::
.
├── app.ml
├── dune
├── dune-project
├── plugin
│   ├── dune
│   ├── dune-project
│   └── plugin1_impl.ml
└── registration.ml
Main Executable (C)
^^^^^^^^^^^^^^^^^^^^^
- The ``dune-project`` file:
.. code:: dune
(lang dune 3.20)
(using dune_site 0.1)
(name app)
(package
(name app)
(sites (lib plugins)))
- The ``dune`` file:
.. code:: dune
(executable
(public_name app)
(modules sites app)
(libraries app.register dune-site dune-site.plugins))
(library
(public_name app.register)
(name registration)
(modules registration))
(generate_sites_module
(module sites)
(plugins (app plugins)))
The generated module `sites` depends here also on the library
`dune-site.plugins` because the `plugins` optional field is requested.
If the executable being created is an OCaml toplevel, then the
``libraries`` stanza needs to also include the ``dune-site.toplevel``
library. This causes the loading to use the toplevel's normal loading
mechanism rather than ``Dynload.loadfile`` (which is not allowed in
toplevels).
- The module ``registration.ml`` of the library ``app.registration``:
.. code:: ocaml
let todo : (unit -> unit) Queue.t = Queue.create ()
- The code of the executable ``app.ml``:
.. code:: ocaml
(* load all the available plugins *)
let () = Sites.Plugins.Plugins.load_all ()
let () = print_endline "Main app starts..."
(* Execute the code registered by the plugins *)
let () = Queue.iter (fun f -> f ()) Registration.todo
The Plugin "plugin1"
^^^^^^^^^^^^^^^^^^^^
- The ``plugin/dune-project`` file:
.. code:: dune
(lang dune 3.20)
(using dune_site 0.1)
(generate_opam_files true)
(package
(name plugin1))
- The ``plugin/dune`` file:
.. code:: dune
(library
(public_name plugin1.plugin1_impl)
(name plugin1_impl)
(modules plugin1_impl)
(libraries app.register))
(plugin
(name plugin1)
(libraries plugin1.plugin1_impl)
(site (app plugins)))
- The code of the plugin ``plugin/plugin1_impl.ml``:
.. code:: ocaml
let () =
print_endline "Registration of Plugin1";
Queue.add (fun () -> print_endline "Plugin1 is doing something...") Registration.todo
Running the Example
^^^^^^^^^^^^^^^^^^^
.. code:: console
$ dune build @install && dune exec ./app.exe
Registration of Plugin1
Main app starts...
Plugin1 is doing something...
.. _Dynlink: https://caml.inria.fr/pub/docs/manual-ocaml/libref/Dynlink.html