B0 is a set of OCaml libraries and command line tools to
configure, build and deploy generic software projects using
modular and extensible descriptions written in OCaml. It provides
a fully integrated and customizable software construction
experience from development to deployment.
Open the module to use it, it defines only types, modules and result combinators in your scope.
ba9fe0b — homepage
('a, R.msg) Pervasives.result
B0results of type
val (>>=) :
('a, 'b) Pervasives.result ->
('a -> ('c, 'b) Pervasives.result) -> ('c, 'b) Pervasives.result
r >>= fis
r = Ok vand
val (>>|) :
('a, 'b) Pervasives.result -> ('a -> 'c) -> ('c, 'b) Pervasives.result
r >>| fis
f >>= fun v -> Ok (f v).
val strf :
('a, Format.formatter, unit, string) Pervasives.format4 -> 'a
These concepts are not core to the build system they allow to
organize multiple, independent builds and their deployment.
FIXME. Move that out of the core build API
to driver libraries or
d0 or B0_care.
TODO show a few examples, using B0_ocaml care and the naked
system. For now have a look at the
examples directory in the
B0 build system is an OCaml program that executes
arbitrary external commands in parallel and whose effects on the
file system are memoized across program runs with an on-disk
There is no notion of build rule in
B0: you simply generate and
execute program commands using arbitrary OCaml functions. This
allows to define modular and rich data structures for describing
builds that are "compiled" down, on each build, to parallel
invocations of memoized commands.
Next to this simple build model
B0 adds a configuration mecanism
under the form of a typed, persisted, key-value store which builds
can consult and depend on.
Since outputs from previous builds are kept in the cache, build configurations can be switched over and back almost instantaneously without loosing the earlier CPU cycles.
The configuration layer is also cross-compilation ready: any configuration key value can differ for the build and host operating system and the build system of B0 keeps tracks of build tools that are build and used by the build system to make sure they are built with the build OS toolchain. For programmers of the build system, cross compilation is oblivious in B0, it happens without the user having to perform anything special.
More on configuration.
Build units are statically known and named entities with metadata that gather sets of related build operations. Typical build units are sequences of commands that build a library, an executable, etc. Dependencies can be defined statically among units on dynamically on builds.
Build units structure builds in well identified fragments, allowing to run them independently and perform coarse grained actions on their build outcomes.
Packages are statically known named entities with metadata that represent a set of build units. They are units of deployment, they allow to build a set of units in isolation from the other and define deployment over them. They have no special functionality expect to structure the build units.
The basic build library and model allows build operations to act anywhere and can be used as such. However to structure the builds, the notion of build variant is added on top of that. Build variant allow builds with different configurations to live next to each other or be performed in containers or on remote machines. They define a basic directory layout in which the build occurs and setup the build environment in which the configuration occurs and build tools are looked up.
More on variants.
Deployments abstract the general process of extracting part of the sources and/or build artefacts of your software to a new location.
Examples of deployments are: installing build artefacts in a system (FIXME unclear), pushing build artefacts to a remote server or device, making source or binary distribution tarballs and pushing them to a location, interacting with package manager repositories.
More on deployments.
d0 tool allow to build projects that are described
by writing one or more
B0.ml OCaml files in a source tree or a
More on description files.
Generally the layout of the build directory is as follows:
_b0/cache, holds the build cache.
_b0/defaults, holds description defaults.
_b0/i, holds the root description and compiled driver instances.
_b0/v, path to build variants.
_b0/d, path to deployments.
The structure of a build variant
n is as follows:
_b0/v/n/scheme, holds the variant's scheme name.
_b0/v/n/outcome, holds the variant's build outcome (if any).
_b0/v/n/conf, holds the variant's configuration (if any).
_b0/v/n/trash, holds the build variant unit trash (if any).
_b0/v/n/index, holds the variant's cache index (if any).
_b0/v/n/proxy, path to data related to a proxy build (if any).
_b0/v/n/b, path to the variant's build directory
_b0/v/n/b/u1, holds the build of unit
u1of the build variant.
_b0/v/n/b/u2, holds the build of unit
u2of the build variant.
The structure of a deployment
n is as follows:
_b0/d/n/scheme, holds the deployment scheme name.
_b0/d/n/conf, holds the deployment configuration (if any).
_b0/d/n/s, holds the deployment's staging directory.
A configuration is a set of typed key-value bindings consulted by descriptions and build procedures to adjust their outcomes to the build environment and desires of the end user.
A configuration key the user did not explicitely set has a default value, specified in the description at key creation time. This value is either constant or discovered via a function.
A key can belong to at most one group which is
simply a named set of related keys. Groups are used to easily
select a subset of keys for user interaction. For example on
key get command invocations, using the
-g ocaml option will
report the value of all configuration keys that declared
themselves to be part of the
Configuration presets are named sets of key-value bindings defined in descriptions. They are a convenience to set key subsets in bulk in configurations.
The configuration used by the last build is persisted in the build
outcome and called the last configuration. It is immutable and
contains only the key-value pairs of the configuration that were
accessed by the last build. It can be accessed via the
b0 key get
The mutable stored configuration is the configuration to be used
by the next build. It can be acted upon via the
b0 key get and
b0 key set commands.
A configuration key has different values depending where and in which context it is looked up:
B0_C_$KEYenvironment variable where
keyuppercased and with
'_'. In a build this value overrides both the stored and default value of a key. It ends up defining the last value of a key but it doesn't get saved in the stored configuration.
During a build the effective value of keys is looked up using the stored configuration. As a side effect new key-value pairs may be added to the stored configuration for keys whose default value is used and discovered during the build. This modified stored configuration is persisted at the end of the build.
A build variant is a build performed in a particular environment with a particular configuration.
The build environment is defined by a variant scheme which is responsible for setting up the environment for the variant. For example this can be: configuring and setting up the environment for an opam switch, spin a container or ssh to a remote machine.
Build variants are identified by a name
n which is used to
operate on the variant. The build directory of a variant
isolated from the others in
_b0/v/n. Variants are created via:
or implicitely on the first
b0 variant create [SCHEME] [-n NAME]
b0 buildif there's no existing variant (see The initial variant). If you don't specify a variant name on creation a unique one will be automatically derived from the scheme name. If you don't specify a scheme, the default scheme (likely the nop scheme) will be used.
b0 allows variants to exist and be acted upon side by side, use
b0 variant list to list them. Most
b0 commands act on the
variant specified explicitely via the
or on the default variant as reported by
b0 variant get. If
there is no default variant or if it doesn't exist commands might
The variant scheme
B0.Variant.Scheme.nop available under the name
nop is the simplest variant scheme. It does nothing, it runs builds in
the environment where the build tool
b0 itself is run.
The default variant can be consulted, set or cleared via:
b0 variant get [--effective | --stored] b0 variant set [--clear | VARIANT]
B0_VARIANTenvironment variable is defined, it's value will define the default. The default variant is automatically set to a newly created variant this can be prevented with the
b0 variant create -c SCHEME # Do not set the new variant as the default
If no variant exists and there is no default variant when
build (or equivalently
b0) is run, a variant is created using
the default variant scheme. So on a fresh checkout of your project:
automatically creates a variant, set it as the default one and builds your project.
B0 descriptions are made of a grab bag of OCaml values,
configuration keys, build units, packages, variants, variant
schemes, deployments, etc. In order to operate on these values
from end-user interfaces (e.g. the
d0 tools), the
following must be guaranteed:
B0may not be aware of (FIXME implement
As far as 1. is concerned,
B0 relies on the discpline of
file writers. They should define all their description values
let definitions and never conditionalize
their existence or the definition of their components. For
examples this should NOT be done:
As far as 2. is concerned.
let myprogram = (* NEVER DO THIS *) let default = Conf.const (if Sys.win32 then "bla.exe" else "blu") in Conf.(key "myprogram" ~default)
B0handles this automatically. In two manners:
B0logs a warning and automatically rename the new definition.
B0.mlare composed toghether. The names defined by subdescriptions get automatically namespaced by their position in the file hierarchy. TODO Give example.
Deployements are handled via the
d0 tool. They do not
necessarily need a build to exist but can request for builds of
specific packages to exist. They occur through a sequence of
steps, all of which are configurable and made available through deployment schemes.
A description file is either:
B0.b0file that describes how to compile a description.
B0.mlOCaml file in a directory without a
If your description is simple or uses only the default
library then a simple
B0.ml description will do. If not, a
B0.b0 file is an s-expression based configuration file
that describes how to compile a self-contained and isolated
description. It can specify additional (and conditional) sources
and libraries to use, compilation flags and control how
subdescriptions (see below) are looked up.
b0 supports file hierarchies that contain more than one
description file. In general, to ease build setup understanding,
it is better to keep a single description per project.
However multiple descriptions allow to merge the description of multiple parallel and interdependent projects into a root description that is built in a root directory.
We first explain formally how an invocation of
b0 finds the root
directory, examples follow. Given the root directory we can
proceed to describe which descriptions belong to the root
When started in a directory
b0, unless invoked with
--root option, finds a root directory for the build as
dir(included) and moving up in the hierarchy, find the first
startdirectory with a description file (a
B0.mlfile). If there is no such directory there is no root directory and no build description.
startmove to the parent directory
uphas a description file and does not exclude
subskey of an
upand go to 2.
upor if it excludes
startis the root directory.
Here's an example of a file hierarchy with multiple descriptions:
d └── root ├── B0.b0 ├── B0.ml ├── p1 │ ├── B0.b0 │ └── B0.ml ├── p2 │ ├── B0.ml │ ├── hop │ │ └── B0.ml │ └── sub │ ├── a │ │ └── B0.ml │ └── b └── src ├── bin └── lib
In the example above starting a driver in
d/root/p2/sub/b will all find the
d/root. However starting a driver in
d/root/p2/sub/a will find the root directory
as there is no description in
root/p2/sub. Adding an empty file
d/root/p2/sub/B0.b0 would allow to find
Given a root directory with (a possibly empty) description,
gathers and merge the descriptions files of all direct subdirectories and recursively into the root
subs key of
can be used to control which direct subdirectories are looked
up. The OCaml sources of different sub descriptions cannot refer
to each other directly; they are properly isolated and linked in
any (but deterministic) order.
B0.b0 file makes use of the
subs key in the
above example, the root description in
root takes into account
all descriptions files except
d/root/p2/sub/a/B0.ml. Here again
adding an empty file
d/root/p2/sub/B0.b0 would allow to take it
B0.b0 description file is a possibly empty sequence of
s-expressions of the form
(key value). Here's an annoted example:
(b0-version 0) ; Mandatory, except if the file is empty (libs (b0_cmdliner)) ; Always compile with the external b0_cmdliner library ; Describe the sources that make up the description in dependency order. ; As a convention if you split your build in many build files put them ; in a B0.d/ directory. If the [srcs] key is absent and a B0.ml file ; exists next to the B0.b0 file it is always automatically added as if ; ("B0.ml" () "B0.ml file") was appended at the end of srcs. (srcs ; If the source path has no suffix looks up both for an .ml and mli file ((B0.d/util () "Utility module") ; The following source needs the b0_jsoo library and is only added to ; the description if the library is found to be installed. (B0.d/with_jsoo.ml (b0_jsoo) "Description with jsoo support"))) (compile (-w -23)) ; Disable warning 23 for compiling the description
B0.b0 file without keys and without a
B0.ml file sitting
next to it is an empty and valid description.
If a key is defined more than once, the last one takes over;
other than that the key order is irrelevant. Except for keys
that start with
x-, unknown keys trigger parse warnings.
Relative file paths. Relative file paths are relative to the description file directory.
Library lookup. FIXME. Library lookup is currently quite restricted and done according to the following name mapping:
$LIBDIRbeing defined (first match) by:
cmifiles have to be in the corresponding
(b0-version 0). File format version, mandatory, except if the description is empty.
(libs (<libname>...)). Libraries unconditionally used to compile and link.
(drop-libs (<libname>...)). Libraries dropped from compile and link (e.g. to drop
(srcs ((<path> (<libname>...) <docstring>)...))OCaml source files to use for the description. Each source is described by a path to an ml file, libraries whose existence condition their usage and a documentation string describing their purpose. If
<path>doesn't end with
<path>.mliexist and are used. A
B0.mlfile sitting next to the
B0.b0is always automatically added at the end of the list.
(subs (<op> (<dirname>...))). Subdescription lookup.
B0.b0resides that do not start with
_are looked up for descriptions.
includeonly the specified list of directory names are looked up.
excludeexcludes the given list of directory names from the default lookup.
auto. The kind of binary to compile to. Allows to force the use of
ocamlopt. Defaults to
autowhich selects native code if available and bytecode otherwise. Subdescriptions propagate their constraint to the root. Inconsistent compilation kind in subdescripitions lead to failure. Can be overriden with the `--d-compile-kind` option or by the
(b0-dir <path>). The b0 directoy to use. Can be overriden on the command line with the `--b0-dir` option or by the
B0_DIRenvironment variable. Defaults to
(driver-dir <path>). The driver directory to use. Can be overriden on the command line or by the
(compile (<arg>...)). Arguments added to byte and native compilation. More can be added on the command line via the `--d-compile` option.
(compile-byte (<arg>...)). Arguments added to byte compilation.
(compile-native (<arg>...)). Arguments added to native compilation.
(link (<arg>...)). Arguments added to byte and native linking. More can be added on the command line via theh `--d-link` option.
(link-byte (<arg>...)). Arguments added to byte linking.
(link-native (<arg>...)). Arguments added to native linking.
ocamlfindbinary to use. Ignored in subdescriptions.
ocamlcbinary to use. Ignored in subdescriptions. Can be overriden with the `--d-ocamlc` option or by the
ocamloptbinary to use. Ignored in subdescriptions. Can be overriden with the `--d-ocamlopt` option or by the
(x-<key> value). Arbitrary user defined key.
B0.b0 file are used, their specification is merged
with the root description. During this process the key values of
subdescriptions are either:
(compile-kind native)and another one
Error only if really needed. Otherwise log with warning and
default to a reasonable deafult value. Build units can still abort
if they can't use the value.