source file -> genrule to rename src -> genrule to execute ppx transform -> ocaml_module
User Guide: rules_ocaml Last updated June 1, 2022
The OBazl Ruleset(s)
Core collection of primitives (build rules) that can be composed to accomplish any build task.
The most basic build tasks are compilation of interface and
implementation files, linking of archive files, and linking of
executables. OBazl includes one rule for each of these tasks, named
accordingly: ocaml_signature
, ocaml_module
, ocaml_archive
, and
ocaml_executable
.
Most real-world projects involve some additional tasks:
-
preprocessing of source files, including but not limited to PPX transformations;
-
renaming of source files to add a namespace prefix;
-
generation of a
resolver
module (sometimes called a "map" file) containing the module aliasing equations required to make namespacing ("wrapped" libraries in Dune) work.
None of these tasks involve the OCaml toolchain, so (following the
Principle of Parsimony), OBazl does not include rules for them. They
can all accomplished using the standard genrule
("general rule")
provided by Bazel.
Here is an example of a simple build pipeline using only OBazl primitives, to compile a namespaced module with a PPX transformation:
In this example, the first step would use a shell command (cp
or
ln
), and the second step, running a PPX transform, would depend on
the output of another pipeline ending in an ocaml_executable
that
produces the PPX executable. Both genrule
steps would involve a
shell command that must be written by the programmer.
In this case Bazel would function more or less as a glorified Make; it
would analyze dependencies and invoke the build actions required by a
change in sources, but would delegate actual build responsibility to
the shell scripts written in the genrules (except for the final
ocaml_*
rule). So this is not something one would do in practice.
Nonetheless, in principle such composable pipelines could be used to
build any OCaml project. But build files written at such a low level
of detail would be tedious to write, error-prone (since they involve
shell scripting), verbose, and hard to maintain. So in order to meet
its design goals (Ease of Use, etc.), OBazl extends some of its rules
to automate the most common build patterns in a more convenient and
expressive manner, and to take advantage of the Bazel’s specialized
build API. For example, instead of passing the PPX executable to a
genrule (which runs a shell command that the developer must write)
that runs it as a separate task, we can pass it directly to the
ocaml_module
rule via its ppx
attribute, which has the effect of
directing the rule to run the transform and compiles output. The
transform will still be executed as a separate build action, but it
will be managed by the ocaml_module
rule, so the developer doesn’t
have to bother directly with the details of a shell command. The OBazl
rules have also been extended to automate namespacing, so that the
developer is responsible only for annotating the rules with attributes
indicating namespace membership, and OBazl takes care of the rest.
NB: compositionality: rules v. build actions
OBazl includes two rulesets:
-
the standard ruleset (rule names prefixed by
ocaml_
orppx_
) -
tools_ocaml
- rules for third-party tools likecppo
,menhir
, etc.
rules_ocaml
The standard rules_ocaml
ruleset can be thought of as a layer
that sits on top of and extends the bootstrap ruleset. All the added
functionality could be implemented using generic Bazel facilities
(genrules, macros, custom rules), but OBazl provides built-in support
for the most commonly needed to ensure ease-of-use etc.
The point being that Bazel (Starlark) is the build language, OBazl just uses it to define rules that make life easier, and the programmer always has access to the full power Bazel. I.e. you’re not limited in to the functionality OBazl supports out-of-the-box. Contrast Dune, which does not build on a lower-level build DSL in this way. |
It adds support for:
-
PPX processing, including automated management of so-called "runtime dependencies"
-
Generalized namespacing (automatic generation of "ns resolver" modules) to compliment the automatic module renaming supported by the bootstrap rules.
-
Contingent dependencies - selection of dependencies based on configuration state
-
corresponds to Dune’s "alternative dependencies" using
(select … from …)
-
no special syntax or functionality is involved; dependencies may be selected using Bazel’s standard, generic
select
function -
NB: this is just a matter of using Bazel’s
select
function for deps, so it is available in the bootstrap ruleset.
-
-
Full control over module bindings
-
A module rule can select any implementation file for binding to any particular signature (.cmi) file, based on configuration settings; for example, binding
clock.cmi
to a platform-specific implementation e.g.clock_linux.ml
is expressible using a simpleselect
statement on a singleocaml_module
target. -
Eliminates need for "virtual libraries". Module bindings like this need not be delayed to link-time.
-
Additional Topics
-
Containerization (Docker, OCI, etc.)
-
Continuous Integration
-
Using Bazel in a continuous integration system - Bazel blog article (2016)
-
Continuous Integration on a Huge Scale Using Bazel - WiX Engineering
-
-
Platforms - cross-platform development