package b0

  1. Overview
  2. Docs

B0 manual

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.

Introduction

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:

  • Build environments.
  • Software configuration, build and testing.
  • Source or built artefact deployments.
  • Development life-cycle procedures and actions.

These descriptions are made in B0 files which we describe next.

B0 files

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.

Root B0 file

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.

Root and _b0 directory

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

Relative file paths

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.

Including B0 files

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"]

Scopes

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.

Access control and isolation

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

Dependency vendoring

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

Using libraries

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.

B0 file bootstrap

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

B0 file definitions

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.

Metadata

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.

Build units

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.

Build packs

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:

  • Define convenience subsets, possibly locked, of units to build. These builds can then be easily triggered via the -p option of b0 build.
  • Define metadata and build units to create a distribution package. For example opam package descriptions can be generated from build packs.
  • Define build prerequisites for actions and deployments

The default pack

The default pack defines the build units that are built when a bare b0 build is invoked.

Build environments

The default environment

The user environment

Actions

B0 file reference

The following parts can be distinguished in a B0 file:

  1. The initial sequence of directives. Before any comment or OCaml construct.
  2. The OCaml unit implementation.

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

Syntax

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

Directives

Directive @@@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.

Directive @@@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.

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