Library
Module
Module type
Parameter
Class
Class type
This manual provides a conceptual overview of the B0 system and explains the basic mechanics of B0 files and their definitions. See here for other topics.
B0 aims at liberating the programmer from the essential but often tedious and brittle bureaucracy that surrounds the programming activity. It provides a framework with a decent description language to devise modular and custom solutions to software construction and deployment problems.
B0 describes:
These descriptions are made in B0 files which we describe next.
A B0 file is a syntactically valid OCaml file possibly prefixed by a few directives. The OCaml code creates definitions to describe the software by using the versioned B0_kit
API and possibly other third-party OCaml libraries.
Usually B0 files are named B0.ml
and are found at the root of a project's source tree. It centralizes the definitions that describe the software.
The definitions in B0 files are consulted and processed by drivers. The b0
and d0
tools distributed with B0 are examples of drivers. An API is provided to create custom driver, see the B0 driver development manual.
The B0 file executed by a driver is called the root B0 file. When a driver like b0
is invoked in a directory cwd
, the root B0 file is the first B0.ml
file found starting at the cwd
and moving upwards in parent directories.
The root B0 file can also be specified explicitely via the --b0-file
option or the B0_FILE
environment variable. Invoke b0 file path
to find out which root B0 file is determined by b0
.
_b0
directoryThe root directory is the directory in which the root B0 file is found. Most of the time the root directory coincides with the root of the project's source tree. However in case a root B0 file simply gathers multiple project definitions by including B0 files the root directory may be empty.
The root directory is where the scratchable _b0
directory used by drivers to operate and store their results gets created by default. The location of the _b0
directory can also be specified explicitely via the --b0-dir
option or the B0_DIR
environment variable. _b0
directories should be ignored by version control systems.
Unless otherwise indicated relative file paths specified in definitions and directives of a B0 file are always relative to the directory where the B0 file lies.
The @@@B0.include
directive provides a mecanism to compose B0 files. The intent of B0 file inclusion is to be able to aggregate independent software (sub)systems or to vendor dependencies. Include directives must be specified at the start of a B0 file, before comments or OCaml code.
For example the following B0.ml
file includes the definitions of two other B0 files:
[@@@B0.include "that" "../that-lib/B0.ml"]
[@@@B0.include "projects/proj/B0.ml"]
An included B0 file defines a named scope to which its definitions are added. Scope names are either specified explicitely or automatically derived from the directory name of the B0 file. In the example above the scope names are, in order, that
and proj
.
From the B0 file that includes, the syntax to access a definition named d
in a scope s
is s.d
.
Scope names must be unique in a given B0 file and must not contain '.'
characters. The lib
scope name is reserved and can't be used (bof change that). It is used as a root scope for library definitions. If these constraints are not satisfied the B0 file errors.
At definition time a B0 file can only access definitions that are in its own scope or in the scope of the files it @B00.include
s and recursively. This means that in any B0 file:
let all_units = B0_unit.list () (* get all units defined so far *)
the value all_units
depends only on the definitions and includes of the B0 file itself, however included it may itself be.
In the example above the two included B0 files cannot refer to the names of the other directly at definition time. However at build time their definitions may interact indirectly via metadata and build logic name resolution procedures which have access to the global scope, see Dependency vendoring.
The OCaml language scope of included files is not accessible and always fully isolated. Both includer and includee cannot access each other's OCaml identifiers.
Support for build dependency vendoring depends on the name resolution procedure of the build logic you are using.
If the build logic you use is able to look in the definitions of the root B0 file for dependencies and use these instead of those found in the build environment then vendoring is simply a matter of @@@B0.includ
ing the definitions of the dependencies to your B0 file.
A typical setup is to have at the root of a project:
B0.ml vendor/dep1/ vendor/dep2/ src/
and to start your project's B0.ml
file with:
[@@@B0.include "vendor/dep1/B0.ml"]
[@@@B0.include "vendor/dep2/B0.ml"]
...
Since dependencies may have vendored dependencies themselves, coherence issues may arise. You may have to define new consistent and locked build packs in the root B0 file in order to exclude conflicting dependencies.
B0 is a modular and extensible system. It is expected for third-parties to define build and deployment logics and distribute them as libraries.
Any OCaml library available in the OCAMLPATH
can be used in a B0 file by requiring it via the #require directive. #require
directives must be specified at the start of the file, before comments or OCaml code.
For example:
#require "mylib"
instructs to lookup library mylib
in the OCAMLPATH
and use it to compile the B0 file.
A B0 file is an OCaml source and may need additional OCaml libraries. This means it needs an OCaml compiler in the user PATH
and the #require
d libraries in the OCAMLPATH
.
Making sure these libraries are available is the purpose of B0 file boostrapping. The @@@B0.boot
directive allows to specify a system to run in order to install the B0 file prerequistes.
For example the following directive:
[@@@B0.boot "opam" "my-logic>=1.0.0"]
indicates the B0 file needs the my-logic
package to be installed with version greater or equal than 1.0.0. The syntax for opam package names and constraints follows that of the opam install
command.
This declaration allows to install the B0 file prerequisites by invoking:
b0 file boot --opam
This section provides a high-level view of the definitions made in B0 files to describe the software.
B0 definitions are named and static. The latter means they need to be defined during the initialisation of the B0 file. This allows end-user to query and act upon them from user interfaces like the command line.
All definitions in B0 have a metadata dictionary of type B0_meta.t
attached. Metadata is used to drive build logics and inform actions and deployments.
The type B0_meta.t
is a simple type-safe heterogenous value map. A few standard keys are provided, consider using these before defining your own. Also consult B0_kit
and libraries of build logic which may define more.
A build unit gathers sets of related low-level build operations (e.g. tool spawns). Typical build units are sequences of commands that build a library, an executable, etc.
Build units structure builds in well identified fragments, they form the smallest unit of build that can be be requested for building by the user.
They are given a build directory in _b0
according to the build environment and their name where they should, but are not required to output their results.
A build pack gathers a set of build units. Build packs have no formal operational functionality in the system. It is just a way to list build units under a name and perform coarse grained actions on them. Note that a build unit can belong to more than one pack.
Here are example of pack usage in the system:
-p
option of b0 build
.default
packThe default
pack defines the build units that are built when a bare b0 build
is invoked.
default
environmentuser
environmentThe following parts can be distinguished in a B0 file:
The order is important. Directives that are mentioned after the OCaml unit implementation starts are either silently ignored (@@@B0.*
directives) or produce compilation errors (#*
directives).
A B0 file is white space (no comments) separated directives followed by an OCaml unit implementation.
Using an RFC 5234 grammar this reads as:
b0_file = *(ws directive) ws unit-implementation directive = dir-boot / dir-inc / dir-req dir-boot = "[@@@B0.boot" *(ws dstring) ws "]" dir-inc = "[@@@B0.include" *(ws dstring) ws "]" dir-req = "#require" ws dstring dstring = %x22 dchar *dchar %x22 dchar = escape / cont / ws / %x21 / %x23-%x5B / %x5D-%x7E / %x80-xFF escape = %x5C (%x20 / %x22 / %x5C) cont = %x5C nl ws ws = *(%x20 / %x09 / %x0A / %x0B / %x0C / %x0D) nl = %x0A / %x0D / %x0D %x0A unit-implementation = ... ; See the syntax in the OCaml manual
@@@B0.boot
The syntax of the @@@B0.boot
directive is:
[@@@B0.boot "SYSTEM" "ARG"... ]
At the moment the only known value for "SYSTEM"
is "opam"
and its argument names are packages constraints using the same syntax as the opam install
command:
[@@@B0.boot "opam" "PKG"... ]
This indicates the opam
package PKG
need to be installed in order to compile the B0 file.
@@@B0.include
The syntax of the @@@B0.include
directive is one of:
[@@@B0.include "PATH"]
[@@@B0.include "NAME" "PATH"]
The semantics is to include in the B0 file the definitions of the B0 file at "PATH"
in a scope named "NAME"
. If "NAME"
is is not specified the directory name of the included B0 file is used.
"NAME"
must be unique among the B0 files included in the file and must not contain '.'
characters.
#require
The syntax of the #require
directive is:
#require "LIB"
The semantics is to compile and link the B0 file against library LIB
, looked up in the OCAMLPATH
. At the moment library LIB
's dependencies must be manually #required
aswell.