This document is meant to provide useful hints for Boost library maintainers to integrate CMake into their library.
TOC:
- Quickstart
- How to add your tests to the CMake build
- The special tests that Boost.CI additionally uses
- Quick CMake intro of the CMake commands/functions you might need
- Additional info for header-only libraries and CMake scopes
- How the Boost super-project works
As the minimum you need a CMakeLists.txt
file (or "CML") for short) in the root directory of the library.
The CMakeLists from Boost.CI can be used as a starting point with only small modifications required.
You may also want a CML in the "test" folder for your tests.
To avoid duplicating tests in B2 and CMake you can use include(BoostTestJamfile)
to get access to boost_test_jamfile(FILE Jamfile.v2)
which will "translate" (only) run <name>.cpp ;
into CMake tests.
You can also use boost_test(NAME <name> SOURCES <name>.cpp)
to add tests semi-automated.
Both read a CMake variable which you can set with set(BOOST_TEST_LINK_LIBRARIES <Boost::lib-name>)
to have your Boost library linked to each test after adding it (see below).
See BoostTest.cmake (included by BoostTestJamfile.cmake) for full details of the helper macros.
Of course you can use add_executable
and add_test
to create your tests manually.
You should then use a unique name for the created binaries.
The ones created by the boost_test
macro use ${PROJECT_NAME}-
as the prefix for each test.
Additionally the Boost.CI CI scripts reference subfolders of the test
folder to test 2 common cases:
- Searching for the installed library and using it.
- Including the library to be built alongside the library/application that uses it.
For this the folders cmake_install_test
and cmake_subdir_test
are used for each case respectively.
They should contain a simple CML and a minimal program to check that the library can be linked to, its headers are found and the program runs.
There is no need for more than a very small "Hello World"-example here as the real tests should be in the (parent) "test" folder.
As there is practically no difference (or: "should be") between both use cases both can instead be combined into a single cmake_test
folder with a single CML and CPP-file.
The CI config detects the presence of a cmake_test
folder and configures each case with BOOST_CI_INSTALL_TEST
set to either ON
or OFF
.
See the Boost.CI cmake_test CML for an example.
Key elements usually required by Boost libraries are (in order of use, with <name>
as placeholders):
cmake_minimum_required
(VERSION <version>)
:
Will error if the CMake being used is older than specified. Possibly changes behavior according to CMake policies, i.e. potentially incompatible changes introduced in some CMake version. The<version>
can be<min>...<max>
which basically means: Require at least CMake<min>
and enable all policies up to the current CMake version or<max>
.project
(<boost-name> VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX)
:
Must come first (aftercmake_minimum_required
), the name should be specific & unique enough (e.g.boost_locale
) but doesn't matter much. The version usesBOOST_SUPERPROJECT_VERSION
set by the top-level Boost CML if that is used, i.e. not when the library is built on its own or as part of another project viaadd_subdirectory
. FinallyLANGUAGES
overrides the default and avoids needlessly initializing the C compiler.add_library
(boost_<name> <sources>)
:
Register the library and its source files as a target.<sources>
is a list of relative paths (space separated so double-quote if the path contains spaces) and can (and should) contain headers too, so they show up in IDEs. For getting all headers without explicitly listing them usefile(GLOB_RECURSE headers include/*.hpp)
prior to this command and pass${headers}
to this command. This is not recommended for source files because sources should be explicitly listed in order to regenerate the build system when the list of sources changes.
Special case: For header-only libraries useINTERFACE
instead of<sources>
.add_library
(Boost::<name> ALIAS boost_<name>)
:
Register the ("Boost")-namespaced target which users will use instead of theboost_<name>
target. This makes usage more conventional and allows to detect missing libraries earlier but the "real" target (the one with the sources) cannot have special characters in its name. Hence the need to have an actual library namespaced/prefixed with"boost_"
(which we can manipulate) and an alias prefixed with"Boost::"
.
IMPORTANT: The<name>
here and above must be the library/repository name with non-alphanumeric characters replaced by underscores in order to be compatible with expectations by the Boost super-project. Examples:add_library(Boost::locale ALIAS boost_locale)
,add_library(Boost::type_traits ALIAS boost_type_traits)
.
It makes sense to use the name of the real target (e.g.boost_locale
) as the project name.target_include_directories
(boost_<name> PUBLIC include)
:
Add the"include"
folder (i.e. a relative path) to the list of header search directories for this target/library and those using it (hence the"PUBLIC"
). You can add more paths (space separated) and/or restrict the "scope" of those paths. To e.g. additionally add the "src/helpers
" directory only when compiling this library usetarget_include_directories(boost_<name> PUBLIC include PRIVATE src/helpers)
instead ortarget_include_directories(boost_<name> PRIVATE src/helpers)
in addition to the above.target_link_libraries
(boost_<name> PUBLIC <list of libs> PRIVATE <list of libs>)
:
Add the libraries as dependencies. This means theirPUBLIC
includes are visible when compilingboost_<name>
and if the dependency is not header-only that library is linked to this library. If your "PUBLIC
" headers include headers of a dependency then it needs to be here asPUBLIC
. If you only include those headers in your compiled sources then the dependency can bePRIVATE
.
Boost dependencies should be their namespaced name, e.g.Boost::type_traits
orBoost::core
.
IMPORTANT: Each Boost dependency must be listed on its own line because the Boost super-project uses a very simple dependency scanner.
Optionally you may want to use:
target_sources
(boost_<name> PRIVATE <sources>)
:
If you don't want to or cannot pass (all) sources (and/or headers) to theadd_library
command you can add them with this command. Note that you (almost) always need to usePRIVATE
here.target_compile_definitions
(boost_<name> PRIVATE <name>=<value>)
:
Add definitions to be used when compiling the library, i.e.-D<name>=value
. The=<value>
part is optional to define a valueless preprocessor symbol.target_compile_features
(boost_locale PUBLIC cxx_std_11)
:
Require (at least) C++11. Can also be used for any other (supported) compiler feature.if
(<condition>)\n<commands>\nendif()
:
CMake supports also conditionals. Often you wantif(<variable>)
(check if variable is not "false", i.e. unset, empty,"OFF"
,"FALSE"
etc.) orif(<variable> STREQUAL "<string>")
orif(NOT <variable> STREQUAL <other_variable>)
.
Examples:if(BOOST_SUPERPROJECT_SOURCE_DIR)
If the super-project is being builtif(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
If CMake was invoked on the library folder, i.e. not the root Boost folder, using predefined variables.if(MSVC)
If the Visual Studio compiler is used.if(MSVC)
If the Visual Studio compiler is used.- `if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
If either GCC or Clang (or "AppleClang") is used.
All of the above applies (almost) unchanged to header-only libraries:
They are added as 2 targets and with their dependencies just as compiled libraries but with add_library(boost_<name> INTERFACE)
, i.e. no sources and use of INTERFACE
.
The "sources" (i.e. headers) can then be added via target_sources
in CMake >= 3.19.
The only other difference is the use of INTERFACE
instead of PUBLIC
/PRIVATE
in the target_*
commands.
The single exception of this rule is target_sources
which needs PRIVATE
as you want to associate the headers with this target (only) and not consumers of it.
PUBLIC
, PRIVATE
& INTERFACE
are "scopes" in CMake.
PRIVATE
: Compile requirements, i.e. what is required for building the library.INTERFACE
: Usage requirements, i.e. what is required for using/linking to the library.PUBLIC
: CombinesPRIVATE
andINTERFACE
, i.e. what is required for building and using the library.
To decide between PUBLIC
and PRIVATE
check what is visible in headers included by users, including transitive includes (e.g. detail/*.hpp
).
For example:
You most likely have a <lib>/config.hpp
which is included by every header a user might include and that config.hpp
includes <boost/config.hpp>
.
Hence even though a user never includes your config header directly it will transitively be included and hence also <boost/config.hpp>
from Boost.Config.
Therefore Boost::config
is a PUBLIC
dependency.
For header-only libraries everything is "user-visible" so PUBLIC
but there is no compiled part so INTERFACE
is enough (and PUBLIC
would actually be an error reported by CMake).
CMake can be invoked on the Boost root folder due to its CML which is its own project including each library as subprojects.
Hence the top-level Boost project is called the "super-project".
This is achieved with the Boost.CMake submodule.
The most important CMake variables (passed on the command line as cmake -D<name>=<value> <...>
) are:
BOOST_INCLUDE_LIBRARIES
: Similar to the--with<library>
B2 optionBOOST_EXCLUDE_LIBRARIES
: Similar to the--without-<library>
B2 optionCMAKE_BUILD_TYPE
: Can be e.g.Debug
orRelease
BUILD_SHARED_LIBS
: Set toON
for shared orOFF
for static libraries.BUILD_TESTING
: Set toON
orOFF
(default) to build tests. This needs to be checked by each libraries CML.
In short what the super-project does is check each library folder for a CMakeLists.txt
and include that if found.
It will then search for the Boost::<name>
and boost_<name>
targets with <name>
derived from the repository/folder name of the library.
Those targets will be added to-be-installed and the name and location of the library file are changed to match that of the B2 build.
The super-project build will also try to automatically find and include dependencies of the library by checking for lines containing only Boost::<library>
.
For this to work, libraries must follow the convention to have 2 targets named Boost::<name>
and boost_<name>
with <name>
being the repository/folder name.
Any special character in the name needs to be replaced by underscores.
Also any Boost dependency (i.e. Boost::<lib>
targets) must be listed on its own line (whitespace is allowed).
Note: Building Boost via CMake is experimental!