This is a work-in-progress version of the Boost C++ Libraries that build as modular components. What does it mean to be modular? It means that..
-
Each library is separable from the rest.
-
Each library does not assume a particular directory arrangement in a parent Boost super-project.
-
The Boost super-project doesn’t need to exist at all, only requirement is that library dependencies be pre-declared.
-
No need for creating the headers tree.
-
Makes it possible for package managers, and hence users, to use the individual libraries they need.
Generally the main goal is to move the main Boost git repos to a modular structure over time without breaking any functionality. That is not an easy goal to achieve. As there are 153 library repos, 12 tool repos, and the super project repo to adjust along the way.
Happy to say though, that the bulk of the technical work is done. What’s left is to start merging pull requests for the transitional period. Then working on followup changes to fully become the modular structure.
To manage this effort safely there are a couple resources that I’ve created:
-
A GitHub Project to keep track of the work.
-
The "modular" super-project git repo.
-
The "transitional" super-project forked git repo.
-
Dozens of library and tool forked git repos with the modular PR changes.
There is a project to track the past, present, and future state of this effort at https://github.com/users/grafikrobot/projects/1 with the following views:
-
Overall Tech Tasks: This is mainly infrastructure type tasks that need to happen.
-
Pull Requests: The PR’s and their status against the mainline Boost repos.
-
Merge Process: How to process the PRs for each Boost git repo. It’s the result of asking all Boost library maintainers if they would like to approve and merge PRs, or have the Boost project owners do whatever is needed, or something else.
The modular super-project at https://github.com/grafikrobot/boost-b2-modular follows a similar structure as the main Boost super-project repo. It’s composed of many git submodules for all the libraries, tools, and support code with some differences:
-
All submodules track the "modular" branch on each which holds the changes.
-
There is only one tool submodule repo. The
tools/boost_build
project that holds the build support installing and staging things like the monolithic headers tree. Which is a dependency for thelibs/headers
submodule. -
There is no B2 project root. The only B2 file is a convenience (for testing)
project-config.jam
file. It declares the B2/boost
project search locations.
Hence this git repo models the fully modular structure for libraries. As might be seen from package manager, or from selective use of libraries in some other project.
This git repo tests the ability to build each of the 153 libraries on Ubuntu and Windows as a GitHub action (https://github.com/grafikrobot/boost-b2-modular/actions/workflows/check-libs.yml)
The transitional super-project at https://github.com/grafikrobot/boostorg.boost is a fork of the Boost super-project develop branch (branched into the modular branch). It contains the changes needed to create a transition from the monolithic to the modular structure. And is the source of the super-project PRs (indirectly and selectively). It has all the same Boost super-project repo plus a few more utility tools to aid in the modular development.
It also has a series of CI checks as GitHub Actions:
-
Check Libraries: Identical to the modular checks except in this repo structure. Except it only checks that B2 parses and declares the targets without errors. It doesn’t try to compile or run tests.
-
CI: This is the same as the Boost super-project runs. Done here to verify it’s as green as the Boost super-project while making pre-PR changes.
-
Modular Release: Runs an equivalent process as the Boost super-project to create a full release snapshot. I.e. the same as what runs on Circle CI to create the release archives the users download. Except it does it in the transitional structure.
In order to avoid confusion, and to make portable cross-lib references possible, we define consistent top-level project names for libraries. For all libraries that name is the GitHub repo name for the library. Everywhere else we will refer to that as the lib-name.
For B2 projects we accomplish using the unique
The biggest change for modular build support is that all libraries now have a
lib-root/build.jam
file. The file defines a project, some specific targets,
and Boost specific install targets for all libraries. Consider this the
public face of the library.
project /boost/[lib-name]
-
Defines the canonical externally usable project (and target).
alias boost_[lib-name]
-
Defines an explicit target that others can refer to to use the library in its default configuration. Some libraries will have additional, similar, targets for the cases where they have alternate, or additional, use cases. This target exists even for header-only libraries. As it’s a way to get usage requirements applied. Specifically for getting include paths added.
alias all
-
Defines a target that will build everything that makes sense to build. This minimally includes the library to build, i.e. the
boost_[lib-name]
target. But preferably also include examples and tests. Having this target makes it easier to check as much of the project as possible in an automated way. boost-library [lib-name]
-
Conditionally calls Boost specific declaration functions. The conditional aspect is that the
boost-library
rule only exists in the context of the super-project. Other times it will be ignored. Which allows to make use of Boost specific definitions while still allowing sans-super-project use cases. boost-library [lib-name] : install boost_[lib-name] ..
-
As part of the
boost-library
declaration, theinstall
defines the targets that will get built and installed as part of the super-project.
This is an example of what a complete build.jam
looks like. In this case for
the Boost.Atomic library:
require-b2 5.1 ; (1) project /boost/atomic (2) : common-requirements (3) <include>include (4) <library>/boost/align//boost_align (5) <library>/boost/assert//boost_assert <library>/boost/config//boost_config <library>/boost/predef//boost_predef <library>/boost/preprocessor//boost_preprocessor <library>/boost/type_traits//boost_type_traits <library>/boost/winapi//boost_winapi ; explicit [ alias boost_atomic : build//boost_atomic ] (6) [ alias all : boost_atomic test ] (7) ; call-if : boost-library atomic (8) : install boost_atomic (9) ;
-
Specifies the minimum B2 version needed for this project.
-
The canonical project alias. Scoped to the
/boost
project. -
Requires that are also
usage-requirements
. -
All Boost libraries need to specify their
include
directory. -
Any intra-boost library dependencies need to be listed. These are only the "public" dependencies. These are what users will see as dependencies.
-
The library, or libraries, to build. This would be an empty alias for header only libraries.
-
The
all
target. In this example builds the library and tests. -
Defines the Boost library, and calls the
boost-*
specific rules given. -
Declares the targets to install.
Important
|
The boost-library declaration should be the only Boost specific
part of the build file. Everything else needs to be generic B2 declarations.
Following this is what makes it possible to build the libraries without the
super-project. And hence be able to be consumed in a modular way.
|
In order to make modular building work, that is building without the super-project, we need to follow some basic rules. Which also means making changes in existing libraries to follow those rules.
Important
|
No super-project relative references anywhere. |
Important
|
No Boost specific build declarations except for the boost-library .
|
Important
|
No inter-library dependencies to internal targets. |
Following those rules here are some examples of the modular changes needed for current libraries..
References of any kind to the headers in boost-root/boost
need to be changed
to references to the library-root/include/boost
equivalent.
doxygen tagfile : ../../../boost/accumulators/framework/depends_on.hpp # (1) ../../../boost/accumulators/framework/extractor.hpp
doxygen tagfile : ../include/boost/accumulators/framework/depends_on.hpp # (1) ../include/boost/accumulators/framework/extractor.hpp
Addition of the boost-root
as an include location needs to be removed. As it
may not exist. And includes for library dependencies will refer to the
include directory per library.
exe fibonacci : fibonacci.cpp : <include>$(BOOST_ROOT) # (1)
exe fibonacci : fibonacci.cpp # (1) ;
exe interval_container : interval_container_/interval_container.cpp : <include>../../.. # (1) <include>$(BOOST_ROOT) ;
exe interval_container : interval_container_/interval_container.cpp : # (1) ;
All library dependency references need to be of the
/boost/[lib-name]//[target]
canonical form. As those are the references
that will work in both the super-project layout and portable modular layout.
<library>/boost//serialization/<warnings>off ; # (1)
<library>/boost/serialization//boost_serialization/<warnings>off ; # (1)
project random_multi_points : requirements <include>. <library>../../../../program_options/build//boost_program_options # (1) <link>static ;
project random_multi_points : requirements <include>. <library>/boost/program_options//boost_program_options # (1) <link>static ;
Dependencies of targets (for build, examples, tests, docs, etc) need to be included even if they are to header-only libraries. As the monolithic boost-root headers directory may not exist.
[ run thread_safety_checking.cpp : : : <debug-symbols>on <library>.//test_impl_lib_backtrace $(LINKSHARED_BT) # (1) : backtrace_lib_threaded ]
[ run thread_safety_checking.cpp : : : <debug-symbols>on <library>.//test_impl_lib_backtrace $(LINKSHARED_BT) <library>/boost/optional//boost_optional # (1) : backtrace_lib_threaded ]
Sometimes it’s more convenient to add the inter-library dependencies on the particular project instead of main targets. For example, if your tests all depend on additional libraries than the user public ones.
project boost-geometry-test : requirements <include>. <toolset>msvc:<asynch-exceptions>on <toolset>msvc:<cxxflags>/bigobj <toolset>clang:<cxxflags>-Wno-unneeded-internal-declaration <toolset>intel:<define>BOOST_GEOMETRY_TEST_ONLY_ONE_TYPE <host-os>windows,<toolset>intel:<cxxflags>/bigobj # (1) ;
project boost-geometry-test : requirements <include>. <toolset>msvc:<asynch-exceptions>on <toolset>msvc:<cxxflags>/bigobj <toolset>clang:<cxxflags>-Wno-unneeded-internal-declaration <toolset>intel:<define>BOOST_GEOMETRY_TEST_ONLY_ONE_TYPE <host-os>windows,<toolset>intel:<cxxflags>/bigobj <source>/boost/test//boost_test # (1) <source>/boost/foreach//boost_foreach <source>/boost/assign//boost_assign ;
Boost also makes use of library provided B2 extensions (modules in B2 parlance).
For example Boost.Config provides a config.jam
module. And Boost.Predef
provides a predef.jam
module. Previously those would be imported through a
relative path. But that will no longer work int he modular layout. As there’s
no guarantee those relative paths will exist. For this B2 has a new feature
to provide search paths for those modules: rule import-search ( reference )
.
The key aspect in the example below is that the /boost/config/checks
reference
is expanded out to find where the /boost/config
project is located and the
project subdirectory checks
in that is added to the paths B2 will search for
modules with the import
.
[1]
import ../../config/checks/config : requires ; # (1)
import-search /boost/config/checks ; # (1) import config : requires ;
Build files are not the only place where references to the Boost root happen.
Because the Boost root is in the include search path there are C++ source
files that #include
other sources relative to the root. In such cases it may
also be needed to pair the C++ change with a B2 change to add new #include
directories.
#include "libs/math/test/log1p_expm1_test.hpp" // (1)
#include "log1p_expm1_test.hpp" // (1)
#define BOOST_USER_CONFIG <libs/stacktrace/example/user_config.hpp> // (1)
#define BOOST_USER_CONFIG <example/user_config.hpp> // (1)