build2
| FAQ
What is build2
?
At the center of the project is the build2
toolchain, a
hierarchy of tools consisting of the build system, the package manager (for
package consumption), and the project manager (for project development).
Then there is cppget.org
, a
central repository/registry of open source packages. Each package in this
repository builds with the build2
build system, can be consumed
with the package manager, and can be conveniently released and published
with the project manager. Once a package is submitted to
cppget.org
, it is automatically built and tested on a substantial number of commonly
used platforms/compilers with the results available as part of the
package page in the repository (pick any version of libsqlite3
for an
example). To allow the same building and testing during development, there
is ci.cppget.org
which
provides a push-style CI service (normally accessed via the project
manager).
Besides these central elements, there is also a number of other tools and services. The following list summarizes all the notable parts:
build2
- Build system.
bpkg
- Package manager.
bdep
- Project manager.
libbuild2-*
- Collection of build system modules.
cppget.org
- Package repository/registry.
queue.cppget.org
- Package repository queue.
ci.cppget.org
- CI service.
github.com/build2-packaging/
- Packaging efforts of third-party projects.
*.stage.build2.org
- Staging infrastructure.
brep
- Package repository web interface (runs on
cppget.org
). bbot
- Build bot.
buildos
- Custom (Debian-based) operating system for running build bots.
Why are you doing this?
To make something people want? No, scratch that: we are making what we need.
We have a bunch of cross-platform C++ tools and libraries that
are becoming an ever increasing pain to develop, test, distribute, and
support. A mailing list thread of 20 messages where we painstakingly
discovered that the user accidentally built our library with
libstdc++
and their application with libc++
which
all resulted in mysterious crashes (exception handling was not working
properly) – this is a waste of life.
What is the end goal?
The end goal is a universal build toolchain that can build a project of any complexity written in any language (or a combination of languages), including all its dependencies, from the ground up and without relying on any other build systems, package managers, etc.
Since all the modern software stacks in practical use today are based on C/C++, the medium-term goal is to rebuild the foundation of the C/C++ software ecosystem with a single, modern, general-purpose build system and make it available from a single package repository that can be accessed from all the major platforms (including Windows) and used with all the major compilers in a uniform manner and without requiring another "system" (Java, Python, Cygwin, etc).
Who is behind this?
The development of the build2
core and packaging of the
foundational C/C++ libraries is funded by Code Synthesis, both in terms of the
full-time engineering effort and build infrastructure. The bulk of the
packages on cppget.org
are
available thanks to the packaging efforts of the
growing build2
community.
How are you going to make money from this?
We are not sure. Maybe we won't. Honestly, if this succeeds, and we won't have to manually test and distribute our projects on the myriad of platform/compiler/IDE combinations, then it would have already been worth it.
What does 2
in build2
stand
for?
It stands for "take two", after a previous, GNU
make
-based attempt called build
.
Why not to use build2
?
See also:
- What is the end goal?
- How does
build2
compare to other build systems (CMake, Bazel, etc)? - How does
build2
compare to other package managers (Conan, Vcpkg, etc)?
While we aim for a general-purpose build system and package manager,
build2
is not everything to everyone. As a result, if you are
looking for one or more of the following, build2
may not be a
good fit:
- May be an overkill for simple, single-platform projects.
build2
's main use cases are complex, cross-platform C/C++ (or mixed language) projects. Complex single-platform projects (for example, that rely heavily on auto-generated source code) and simple cross-platform projects could also benefit. However, it may not be worth it for a simple single-platform project if you find the platform's native build system and package management adequate.- Not an IDE project generator (Visual Studio, Xcode, etc).
build2
is not a meta-build system and will unlikely ever support IDE project generation. Though an IDE could conceivably usebuild2
buildfiles as a source of project information.- No build-by-convention.
- The build system expects an explicit description of the project. However, use of wildcard patterns can approximate build-by-convention (while supporting various layouts found in real-world C/C++ projects).
- Build description is not a general-purpose language (JSON, YAML, etc).
- We believe the importance and complexity of the problem warrants a purpose-built language.
- You may not get far without reading the documentation.
- The complexity of real-world C/C++ projects and variability in platforms
that they must support requires an elaborate and flexible conceptual model
of build (which is also substantially different compared to other build
systems, such as CMake). While you should be able to make minor tweaks in
existing buildfiles without much prior experience, to fully understand
build2
and use it effectively you will need to read a substantial amount of documentation, at least eventually. - You may not be able to replicate existing source/output layout exactly.
- Most C/C++ build systems are "mono-repo first" while
build2
is decidedly "multi-repo first". Whilebuild2
offers quite a bit of flexibility when it comes to source and output layouts, there are a few fundamental expectations (such as that the output directory tree must be the same or outside of the source) which are probably not worth fighting. - No
autoconf
-style configuration probing. build2
eschews error-proneautoconf
-style probing in favor of expectation-based configuration where a project assumes a feature to be available if certain conditions are met, for example, a feature test macro is defined. This approach is supported by theautoconf
build system module.
How does build2
compare to other
build systems (CMake, Bazel, etc)?
See also:
- What is the end goal?
- Why not to use
build2
? - How does
build2
compare to other package managers (Conan, Vcpkg, etc)?
Modern build systems vary greatly not only in the individual features
they support but in the overall approach they take. The following key points
should give you a sense of where build2
fits in this space:
- General-purpose, multi-threaded, native build system written in C++14.
- Conceptual model of build (targets, prerequisites, recipes, rules).
- Integrated configuration management that is part of the build.
- Extensible with build system modules written in C++14 (see
autoconf
,kconfig
,rust
). - Integrated (but not embedded) package/project manager.
- Support for all the major platforms and C/C++ compilers.
- Uniform interface across platforms and compilers (including Windows).
- No dependencies on other "systems" (Python, Java, etc).
And the following list highlights other major features of the
build2
build system:
- Concise, mostly declarative build description with wildcard pattern support.
- Full set of operations:
update
/clean
,test
,install
/uninstall
,dist
. - Flexible source code layout with in- and out-of-source builds.
- Project composition via importation and amalgamation (bundling).
- High-fidelity and hermetic build support with precise change detection.
- First-class cross-compilation support.
- Ability to build in multiple configuration with a single build system invocation.
- Uniform configuration of build system core, modules, and user projects.
- Support for custom configuration mechanisms (see
kconfig
). - Support for languages other than C/C++ (see
bash
,rust
). - Support for source code generation as part of the main build step.
- User-defined target groups, recipes, and pattern rules.
- Support for dynamic dependencies in recipes and rules.
- Automatic project version management.
- Domain-specific language for concise test description and parallel execution.
- Complete C++20 named modules,
import std;
support in Clang and MSVC. - Complete C++20 named modules and header units support in GCC.
- Build system as a library (for custom drivers, IDE integration, etc).
- Suitable for embedded development.
How does build2
compare to other
package managers (Conan, Vcpkg, etc)?
See also:
- What is the end goal?
- Why not to use
build2
? - Why not just use a system package manager?
- How does
build2
compare to other build systems (CMake, Bazel, etc)?
Similar to build systems, there is great variability in this area and the
following list highlights the key features of the build2
package manager:
- Fast, transactional package manager written in C++14.
- Central package repository (
cppget.org
) with integrated CI service. - Growing number of packages, all built with the same build system.
- Support for package development (release management, CI, publishing).
- Archive and VCS-based repositories.
- Repository federation and signing.
- Generalized version format (not just semver).
- Flexible version
constraints (
~
,^
, ranges, etc). - Conditional Dependencies.
- Dependency Configuration Negotiation (including non-bool values).
- Build-time dependencies and target/host configuration split.
- Build configuration linking (overlays).
- Ability to consume system-installed packages.
- Ability to produce binary distribution packages.
- Comprehensive and scalable CI infrastructure.
But the most significant difference, at least when it comes to C/C++, is
the mandatory use of the build2
build system by all packages
(using system-installed packages is the only escape hatch). This has a
number of benefits:
- More robust builds.
- One build system means fewer things to go wrong. Also, when things do go wrong, you don't need to be an expert in multiple build systems.
- Support for new platforms and compilers.
- When
build2
adds support for a new platform or compiler, you are not dependent on other build systems to provide the same support. So far this already happened twice with the addition of support for Clang targeting MSVC and Emscripten. - Faster builds.
- With
build2
, your project and all its dependencies are built as part of the same build system invocation. This has a number of build performance implications. As an example, let's consider a hypothetical project that useslibcurl
andlibboost-regex
, withlibcurl
in turn depending onlibssl
from OpenSSL andlibboost-regex
– on a few libraries from ICU.Outside of
build2
these four libraries use four different build systems, which means we would have to build them one-by-one in the dependency order (we could build OpenSSL and ICU at the same time but then we would need to split available hardware threads between the two jobs). In contrast, when usingbuild2
packages of these libraries, everything is built at the same time: ICU and OpenSSL builds will proceed in parallel and as soon as one of them is ready, its dependent (Curl or Boost) can start building (it's actually even better than this: Curl source code can start compiling before the OpenSSL library is linked and the same with Boost and ICU).But that's not the end of it: with
build2
we will only build what's ultimately needed by our project. For example, ICU actually consists of three libraries butlibboost-regex
only depends on two. The same story with Boost: we only needlibboost-regex
(and all its dependencies) while Boost provides over a hundred libraries. This also continues with library variants of our dependencies: should we build static, shared, or both variants of each library? Withbuild2
we automatically build only what's needed.To put some specific numbers on all of this, a debug version of this build takes about 25 seconds on i9-12900K.
Why not just use a system package manager?
See also:
In short, there is nothing wrong with that, they work well (unless you
are on Windows), and we use them ourselves every day. And
build2
(both the build system and package manager) work well
with system-installed packages.
To elaborate, system package managers (such as rpm
,
dpkg
, FreeBSD pkg
, etc) serve a different purpose
compared to bpkg
and bdep
: they are optimized for
the end-user who wants to easily install and use a program, not the
developer who wants flexibility and power in juggling their library/tool
dependencies. Yes, most system package managers support installing
development files for a library but you can normally only have one version
at a time. While during development we often want multiple versions (so we
can make sure our code works with all of them) that are built in multiple
configurations (different compilers, 32/64-bit, debug/release, and don't
forget cross-compilation). And this is what build2
is optimized
for.
Still, it often makes sense to use a system-installed library or tool
instead of building it from source. Take libsqlite3
as an
example: it is stable, available and works the same way pretty much
everywhere – it probably doesn't make much sense to build it from
source for every configuration.
The build2
build system will happily use a system-installed
library; there is even pkg-config
integration to extract
additional information such as the location of headers, additional libraries
to link, etc. Also, the package manager has a notion of system-installed
packages (that sys:
prefix) which allows you to instruct bpkg
(and
bdep
) to assume the package is available from the system rather
than building it from source. There is even integration with some system package
managers meaning that bpkg
will query the system package
manager for the installed version of a sys:
package and will
attempt to install it if not already installed but available from the system
package repository.
Why did you write build2
in C++ instead of
another language (Rust, Python, etc)?
We needed a cross-platform (in particular, must work on Windows), efficient (nobody likes to wait for their builds), and stable/proven language that is available on all the major platforms without installing another "system" (Python, Java, Cygwin, etc). Put another way, a C/C++ toolchain is the base of every piece of software on every operating system in use today.
There is also this perspective: developing a build system and/or package
manager is a multi-year, full-time project (build2
is going for
almost 10 years now). If you are writing it in, say, Python, then this means
you are a full-time Python developer trying to design and implement a major
piece of C/C++ infrastructure for a language you don't actually use yourself
for any serious development.
What are your plans for cppget.org?
We hope it will become The C++ Package Repository, the place where
people publish theirs and look for other's C++ libraries and tools. If/when
this happens, it will probably be a good idea to turn the control over to
someone impartial, like Standard C++
Foundation (to this effect, we are running build2.org
and
cppget.org
on completely separate infrastructures from the
start).
Do you support cross-compilation?
Yes, cross-compilation was supported from day one with native compilation
being just a special case (target is the same as host). Fun
fact: in the early days, build2
could cross-compile to Windows
before it could build natively.
Do you support binary packages?
The "binary package" term can mean many things but in this context it usually means one of two things: "binary distribution package" and "binary pre-built package".
A binary distribution package is a package for a Linux distribution such
as Debian (.deb
) or Fedora (.rpm
), or another
operating system, such as Mac OS (.pkg
). The key property here
is that the corresponding source package is built and then installed and
what's packaged is the result of the installation.
In contrast, a binary pre-built package is essentially an archive of the build directory of the source package before the installation. It is meant to be dropped into your project's build directory with the result looking as if the package was built from source.
Now to answer the question for each meaning of binary package:
There is binary distribution package generation support for Debian (or alike, such as Ubuntu), Fedora (or alike, such as RHEL), and for other operating systems as installation archives.
At this stage there is no support for binary pre-built package but maybe in the future.
The problem with supporting binary pre-built packages is you have to make sure the binary package built on someone else's machine matches your environment precisely (if you think this is a Windows-only problem, check the recent GCC dual ABI mess). If we start cutting corners, we will end up in tears (and long mailing list threads). One interesting approach would be to dump the entire build system state into, say, an SQLite database and then ship that with the binary package. On the other end we can then load this database and verify that all the prerequisites (like system headers, compiler versions, etc) match.
Do you support VCS other than git
?
Not at this stage but maybe in the future.
While the build system and the package manager are perfectly usable
without a VCS, most of the project manager's functionality assumes the
presence of a VCS (you can still publish and CI without it) and currently
the only supported option is git
. However, we are open to
supporting other options if there is interest and willingness to do the
work.
Can I run my own private/public repositories?
Yes, the entire toolchain, including the server-side web interface, is
open source, licensed under the MIT License (see brep/INSTALL
for details). We are also working on a VM with everything pre-installed to
ease the setup process. Get in touch if you would be interested in trying it
out.
Why did you call the build system driver b
?
We found typing something like make -j8
too tedious so
we've decided to be bold and call it just b
(the
-j8
part is unnecessary since the hardware concurrency is
detected automatically).
In retrospect, this was probably a bad idea since, somewhat surprisingly,
it seems to upset a few people. Yes, in the UNIX world, single letter
commands are said to be reserved for user aliases (though in my GNU/Linux
/usr/bin/
I have w
, X
, and, of
course, [
). Note, however, that we often have to run
b
on machines (like build bots) where one is unlikely to bother
with aliases. And it would be nice to have the same short name available
everywhere.
As another indication of this probably not being a big deal, the Fedora
project accepted the /usr/bin/b
executable in the
build2
package without any fuss.
In any case, it is probably too late to change now (maybe in version 2).
Also, if you really must, you can always add an executable prefix or suffix
when installing build2
(see the install script help for
details).
Why does Ninja start compiling faster than
build2
?
TL;DR: While there may be an initial delay before the first compilation command line is printed, useful work is being done and the overall build might even be slightly (because of file caching) or even significantly (because of more precise change detection) faster.
Ninja and build2
use different C/C++ compilation models when
it comes to handling header dependencies (sets of headers each translation
unit depends on that are used to decide what needs to be rebuilt).
Ninja extracts header dependencies as part of the compilation step itself. This allows it to issue the first compilation command pretty much immediately but requires all the headers to already exist and be up-to-date. Which means no auto-generated headers, at least not as part of the overall build (maybe as a pre-build step).
In contrast, build2
extracts header dependencies before
starting the compilation. While it means there is a delay before you see the
first compilation command, this approach has a number of advantages: It
supports auto-generated source code as part of the build itself which can
become essential for complex code generators like ORMs that may need to
extract their own dependencies. This setup also opens the door for
supporting C++ modules which in many ways are like auto-generated headers:
the build system has to update all the binary module interfaces before it
can start compiling a translation unit that imports them.
And this is not all: there are even more advantages but to fully
appreciate them we need to understand how build2
sets this
compilation pipeline up. At the compiler level, header dependency extraction
is essentially a preprocessor run: it has to process #include
directives recursively (and keeping track of which ones are
#ifdef
'ed out, etc) and produce the list of headers that have
been included. So instead of extracting only the header list,
build2
instructs the compiler to also save the (partially)
preprocessed output. Later, if the translation unit needs to be rebuilt, the
compiler is given this (partially) preprocessed file rather than the
original.
So build2
does do some extra work compared to Ninja but the
results of this work are not wasted. While one would still expect Ninja to
be faster overall (one compiler invocation rather than two, no extra
filesystem access to save the preprocessed file, etc), the reality is
surprising: in certain cases the build2
approach results in a
slightly faster overall build. The reason is probably the time-localized and
cached access to all the headers; with build2
all this happens
at the beginning of the build while with Ninja the access is spread over the
entire build. See Separate Preprocess and
Compile Performance for details.
Going back to other advantages, now that we have a (partially)
preprocessed translation unit we can do a few more interesting things: we
can perform more precise change detection by ignoring changes that do not
affect the resulting token stream (or its debug information) such as changes
to comments, macros that are not used, etc. In build2
this
precise change detection is combined with the module dependency (import)
extraction.
Finally, the (partially) preprocessed unit is a perfect candidate for
shipping to a remote machine for distributed compilation. Since all the
#include
directives have been expanded we don't need to worry
about the remote machine having exactly the same set of headers in exactly
the same locations.
Note also that build2
itself is multi-threaded: it not only
runs compilation jobs in parallel but also parallelizes internal tasks, such
as parsing of the dependency output, calculation of the translation unit
checksums, etc.