# 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.