mirror of
https://github.com/boostorg/cmake.git
synced 2026-01-19 04:02:15 +00:00
879 lines
33 KiB
Markdown
879 lines
33 KiB
Markdown
# CMake for Boost Developers
|
|
|
|
## Header-only Libraries
|
|
|
|
### Automatic Generation with Boostdep
|
|
|
|
The easiest way to add CMake support to a header-only Boost library is
|
|
to generate a `CMakeLists.txt` file with
|
|
[Boostdep](https://www.boost.org/doc/libs/release/tools/boostdep/doc/html/index.html)
|
|
using the command `boostdep --cmake <libname>`, where `<libname>` is the
|
|
name of the repository (or the directory name).
|
|
|
|
For example, a `CMakeLists.txt` file for Boost.Core can be generated with
|
|
`boostdep --cmake core`, and the result will be, as of this writing,
|
|
|
|
```cmake
|
|
# Generated by `boostdep --cmake core`
|
|
# Copyright 2020 Peter Dimov
|
|
# Distributed under the Boost Software License, Version 1.0.
|
|
# https://www.boost.org/LICENSE_1_0.txt
|
|
|
|
cmake_minimum_required(VERSION 3.5...3.16)
|
|
|
|
project(boost_core VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX)
|
|
|
|
add_library(boost_core INTERFACE)
|
|
add_library(Boost::core ALIAS boost_core)
|
|
|
|
target_include_directories(boost_core INTERFACE include)
|
|
|
|
target_link_libraries(boost_core
|
|
INTERFACE
|
|
Boost::assert
|
|
Boost::config
|
|
Boost::static_assert
|
|
)
|
|
|
|
if(BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt")
|
|
|
|
add_subdirectory(test)
|
|
|
|
endif()
|
|
```
|
|
|
|
Most header-only libraries require no modification to this `boostdep` output.
|
|
|
|
You are not required to use this exact file, but if you can, there are benefits
|
|
for doing so:
|
|
|
|
* You can regenerate the file at any time, to pick up style changes as the
|
|
Boost CMake infrastructure evolves and Boostdep is updated to match;
|
|
* Boostdep computes the library dependencies automatically (as this is its
|
|
primary purpose as a tool), and if you make changes to the library that
|
|
cause its dependencies to change, a simple regeneration can keep the list
|
|
up to date;
|
|
* You can add a CI job that compares the output of Boostdep to your current
|
|
CMakeLists.txt file, which will inform you if the file needs to be
|
|
regenerated.
|
|
|
|
Even if you decide to make changes to your `CMakeLists.txt` file, the
|
|
generated output provides a useful starting point. Its contents are explained
|
|
below.
|
|
|
|
### Version Requirement
|
|
```cmake
|
|
cmake_minimum_required(VERSION 3.5...3.16)
|
|
```
|
|
|
|
This directive sets the minimum required version of CMake and must be the
|
|
first thing in it. If CMake is older than 3.5, the result will be a fatal
|
|
error at configure time, and inability to proceed with building.
|
|
|
|
In addition, this number changes the behavior of newer CMake versions to
|
|
attempt to be compatible with the stated version. If this only said
|
|
```cmake
|
|
cmake_minimum_required(VERSION 3.5)
|
|
```
|
|
a newer version of CMake would have emulated version 3.5. The additional
|
|
`...3.16` suffix, however, requests newer versions to emulate 3.16 instead.
|
|
This is typically the latest version of CMake with which the `CMakeLists.txt`
|
|
file has been tested. If you make changes to the file for other reasons, you
|
|
may want to update the directive to, say,
|
|
```cmake
|
|
cmake_minimum_required(VERSION 3.5...3.20)
|
|
```
|
|
You should avoid increasing the minimal CMake requirement above the Boost
|
|
minimum, which is at present tentatively and conservatively set to 3.5, but
|
|
will likely be increased in the near future. If you use a higher minimum,
|
|
configuring Boost will fail with earlier CMake versions, even if the user
|
|
is not interested in your library. He will then be forced to manually exclude
|
|
your library from the build with `-DBOOST_EXCLUDE_LIBRARIES`, which is not
|
|
an ideal user experience.
|
|
|
|
### Project Declaration
|
|
```cmake
|
|
project(boost_core VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX)
|
|
```
|
|
|
|
The project declaration must generally be preceded only by the above
|
|
version requirement directive, and sets the project name, the project
|
|
version, and the languages (C, C++) that the source files will use.
|
|
|
|
Boost projects by convention are named `boost_libname`, in lowercase,
|
|
as in the above. (Libraries in `numeric` such as `numeric/conversion`
|
|
use an underscore in place of the slash: `boost_numeric_conversion`.)
|
|
|
|
The version is set to match the variable `BOOST_SUPERPROJECT_VERSION`,
|
|
which the Boost superproject `CMakeLists.txt` file sets to the current
|
|
Boost version (such as `1.77.0`.)
|
|
|
|
If your library is included directly in a user project with
|
|
`add_subdirectory`, `BOOST_SUPERPROJECT_VERSION` will not be set and
|
|
the project version will be empty, as if it weren't given:
|
|
```cmake
|
|
project(boost_core LANGUAGES CXX)
|
|
```
|
|
This is usually what one wants. Since manually maintaining a version
|
|
is time consuming and doesn't bring much, most libraries that do
|
|
include one fail to maintain it properly. It's better to leave it empty;
|
|
the version is of no significance in an `add_subdirectory` workflow.
|
|
|
|
The `LANGUAGES` portion should be left at the default `CXX`, which
|
|
enables the C++ language. If removed, CMake will configure both C and
|
|
C++. C is only needed if the library has C source files, which a
|
|
header-only library does not have.
|
|
|
|
### Library Target Declaration
|
|
```cmake
|
|
add_library(boost_core INTERFACE)
|
|
```
|
|
|
|
The first `add_library` declares the library target, which by convention
|
|
is `boost_libname`, same as the project name. `INTERFACE` means that
|
|
this library is header-only and requires no building.
|
|
|
|
```cmake
|
|
add_library(Boost::core ALIAS boost_core)
|
|
```
|
|
|
|
The second `add_library` declares an alternative name for the library,
|
|
which by convention is `Boost::libname`. It's good CMake practice to
|
|
only link to targets of this form (more specifically, to targets containing
|
|
`::`), because they are unambiguously CMake target names, whereas the
|
|
alphanumeric `boost_core` may refer to either a target or to a library
|
|
on disk named f.ex. `libboost_core.so`.
|
|
|
|
### Include Directory Declaration
|
|
```cmake
|
|
target_include_directories(boost_core INTERFACE include)
|
|
```
|
|
|
|
This directive declares the directory containing the library headers, which
|
|
for Boost libraries is the `include` subdirectory. (A relative path is
|
|
interpreted as relative to `CMAKE_CURRENT_SOURCE_DIR`, that is, to the
|
|
location of the current `CMakeLists.txt` file.)
|
|
|
|
If you are familiar with CMake, your first impulse would be to declare this
|
|
line wrong, and replace it with
|
|
```cmake
|
|
target_include_directories(boost_core INTERFACE
|
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include)
|
|
```
|
|
or
|
|
```cmake
|
|
target_include_directories(boost_core INTERFACE
|
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include
|
|
$<INSTALL_INTERFACE:include>)
|
|
```
|
|
or perhaps
|
|
```cmake
|
|
target_include_directories(boost_core INTERFACE
|
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include
|
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
|
|
```
|
|
|
|
You shouldn't; the line is, in fact, correct. The Boost superproject will
|
|
automatically invoke `boost_install` for your target, which will patch
|
|
the value of the include path to something like that last alternative
|
|
(but it will take into account the Boost-specific variables
|
|
`BOOST_INSTALL_LAYOUT` and `BOOST_INSTALL_INCLUDE_SUBDIR`.)
|
|
|
|
### Dependencies
|
|
```cmake
|
|
target_link_libraries(boost_core
|
|
INTERFACE
|
|
Boost::assert
|
|
Boost::config
|
|
Boost::static_assert
|
|
)
|
|
```
|
|
|
|
Traditionally, Boost has had all the headers copied (in a release) or
|
|
linked (in a modular layout) into a single `boost/` directory. This made
|
|
it possible to include headers from any library (A) into any other (B),
|
|
without the need to declare that B depends on A.
|
|
|
|
With CMake, we will no longer maintain a single `boost/` directory where
|
|
all the headers are copied. Headers of A will remain in `libs/A/include`,
|
|
and if this directory isn't in the include path of B, B will not be able
|
|
to include a header from A.
|
|
|
|
In order for the include path of B to contain `libs/A/include`, B must
|
|
explicitly declare a dependency on A. In CMake, this is accomplished by
|
|
"linking" to A, even when A is header-only.
|
|
|
|
This is the purpose of the `target_link_libraries` directive above. In
|
|
this specific case, it declares that `boost_core` depends on `Boost::assert`,
|
|
`Boost::config`, and `Boost::static_assert`, and will result into
|
|
`libs/assert/include`, `libs/config/include`, and `libs/static_assert/include`
|
|
being added to the include path of Core. (More precisely, they will be added
|
|
to the include paths of the users of `Boost::core`. Core itself needs no
|
|
include path because it doesn't require any compilation. This is what the
|
|
`INTERFACE` keyword means - it sets the "usage requirements" of the target,
|
|
which are propagated upwards to its users.)
|
|
|
|
Note that the exact form of the directive, with each `Boost::libname` target on
|
|
its own line, is no longer necessary after Boost 1.89.
|
|
You can as well put them on a singe line:
|
|
`target_link_libraries(boost_core INTERFACE Boost::assert Boost::config Boost::static_assert)`
|
|
This dependency specification influences the behavior of the user-settable
|
|
`BOOST_INCLUDE_LIBRARIES` option of the superproject, which requests only the
|
|
listed libraries and their dependencies to be configured, built, and/or
|
|
installed.
|
|
To determine the dependencies, a simple parser scans the `CMakeLists.txt`
|
|
files, looking for strings matching `Boost::libname`, excluding the name of the
|
|
current library and those appearing in comments. Additionally, you can use
|
|
pragmas to influence the scanner:
|
|
|
|
```cmake
|
|
# Ignored by parser if in library folder "core"
|
|
add_library(Boost::core ALIAS boost_core)
|
|
# Parser adds "Boost::assert" "Boost::dummy"
|
|
target_link_libraries(boost_core INTERFACE Boost::assert Boost::dummy
|
|
# Only "Boost::config added for those 2 lines
|
|
Boost::config # Boost::static_assert
|
|
# Boost::ignored
|
|
)
|
|
|
|
# Current dependencies: Boost::assert, Boost::dummy Boost::config
|
|
...
|
|
# Pragmas using Boost-Include, Boost-Exclude with or without colons and/or whitespace
|
|
# Boost-Include: Boost::filesystem
|
|
# Boost-Exclude Boost::dummy
|
|
|
|
# Final dependencies: Boost::assert, Boost::config, Boost::filesystem
|
|
```
|
|
|
|
This is useful if the parser misdetects a dependency (please open an issue)
|
|
or e.g. for optional dependencies.
|
|
|
|
Dependencies in `test/CMakeLists.txt` (and subfolders) are handled in the same
|
|
way, except their tests won't be build and those libraries won't be installed
|
|
unless they are dependencies in the root CMakeLists.txt of the current library
|
|
or any (transitive) dependency of those.
|
|
|
|
### Testing Support
|
|
```cmake
|
|
if(BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt")
|
|
|
|
add_subdirectory(test)
|
|
|
|
endif()
|
|
```
|
|
|
|
The final portion of the generated `CMakeLists.txt` file adds support for
|
|
invoking the library tests from the Boost superproject. Since not all
|
|
libraries have one, this is only enabled when
|
|
`libs/libname/test/CMakeLists.txt` exists.
|
|
|
|
In principle, since you know whether this file exists for your library or
|
|
not, you can either remove this condition or remove this entire section; but
|
|
doing so will make your `CMakeLists.txt` file not match the generated output,
|
|
which has its downsides.
|
|
|
|
`BUILD_TESTING` is the standard CMake option (typically defined by the `CTest`
|
|
CMake module) that allows the user to enable or disable tests for a project.
|
|
It's used here to skip the inclusion of the `test` subproject in order to
|
|
speed up the configure and build phases of Boost when testing is not required
|
|
or desired.
|
|
|
|
If your library has a `test/CMakeLists.txt` file that is not intended to be
|
|
used from the Boost superproject, and is incompatible with it, replace this
|
|
block with either
|
|
```cmake
|
|
if(BUILD_TESTING AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
|
|
|
add_subdirectory(test)
|
|
|
|
endif()
|
|
```
|
|
when your test suite is only intended to be used when your library is the
|
|
root project (that's usually the case, so this option is the recommended one),
|
|
or
|
|
```cmake
|
|
if(BUILD_TESTING AND NOT BOOST_SUPERPROJECT_VERSION)
|
|
|
|
add_subdirectory(test)
|
|
|
|
endif()
|
|
```
|
|
when your test suite is also intended to be invoked when your library is
|
|
a subproject of a user project. (This case is rare and user projects are
|
|
typically not interested in running their subprojects' tests, so you
|
|
probably don't want this.)
|
|
|
|
### Installation Support
|
|
|
|
You may have noticed by now that no installation support is declared in the
|
|
`CMakeLists.txt` file. Nevertheless, the library can in fact be installed.
|
|
The Boost superproject automatically adds the necessary support to libraries
|
|
which declare a target `boost_libname` that matches the directory of the
|
|
`CMakeLists.txt` file (`libs/libname`) and whose `target_include_directories`
|
|
directive matches the one above.
|
|
|
|
It is recommended that you don't attempt to add your own installation support.
|
|
Let the superproject handle it.
|
|
|
|
### Required C++ Standard
|
|
|
|
If your library needs C++11 or above, you can declare this requirement by
|
|
adding the following directive:
|
|
```cmake
|
|
target_compile_features(boost_libname INTERFACE cxx_std_11)
|
|
```
|
|
(use `cxx_std_14` for C++14, `cxx_std_17` for C++17, and so on.)
|
|
|
|
This will increase your CMake requirement to 3.8, so you should also update
|
|
the preamble to reflect this.
|
|
|
|
If your `meta/libraries.json` already declares the C++ requirement by means
|
|
of `"cxxstd": "xx"`, Boostdep 1.77+ will automatically take this into
|
|
account and add the above `target_compile_features`.
|
|
|
|
### Additional Functionality
|
|
|
|
This is all you need to have a header-only library that integrates into the
|
|
Boost CMake infrastructure. It is also a well-behaved suproject that can be
|
|
included into user CMake projects via `add_subdirectory`. Avoid the urge to
|
|
add more functionality unless it's really necessary, as it will compromise
|
|
the usability of your library as a subproject.
|
|
|
|
Many library authors who use CMake, however, add development-centric
|
|
functionality to their `CMakeLists.txt` file; you might already have. In this
|
|
case, try to keep the `CMakeLists.txt` portions described so far as close to
|
|
unchanged as possible, and at the end, add a section guarded with
|
|
```cmake
|
|
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
|
|
|
# Functionality enabled only when we're the root project
|
|
|
|
endif()
|
|
```
|
|
and put all your current developer-centric functionality there. This way,
|
|
subproject use will be unaffected, and you can still use CMake from your
|
|
library directory for development-related activities such as generating
|
|
Visual Studio workspaces, or testing outside the Boost tree.
|
|
|
|
## Compiled Libraries
|
|
|
|
### A Starting Point
|
|
|
|
Even if your library requires compilation, you can still use
|
|
`boostdep --cmake libname` at least as a starting point. We'll take
|
|
Timer as an example, with the output of `boostdep --cmake timer` given
|
|
below:
|
|
```cmake
|
|
# Generated by `boostdep --cmake timer`
|
|
# Copyright 2020 Peter Dimov
|
|
# Distributed under the Boost Software License, Version 1.0.
|
|
# https://www.boost.org/LICENSE_1_0.txt
|
|
|
|
cmake_minimum_required(VERSION 3.5...3.16)
|
|
|
|
project(boost_timer VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX)
|
|
|
|
add_library(boost_timer
|
|
src/auto_timers_construction.cpp
|
|
src/cpu_timer.cpp
|
|
)
|
|
|
|
add_library(Boost::timer ALIAS boost_timer)
|
|
|
|
target_include_directories(boost_timer PUBLIC include)
|
|
|
|
target_link_libraries(boost_timer
|
|
PUBLIC
|
|
Boost::config
|
|
Boost::core
|
|
Boost::system
|
|
PRIVATE
|
|
Boost::chrono
|
|
Boost::io
|
|
Boost::predef
|
|
Boost::throw_exception
|
|
)
|
|
|
|
target_compile_definitions(boost_timer
|
|
PUBLIC BOOST_TIMER_NO_LIB
|
|
PRIVATE BOOST_TIMER_SOURCE
|
|
)
|
|
|
|
if(BUILD_SHARED_LIBS)
|
|
target_compile_definitions(boost_timer PUBLIC BOOST_TIMER_DYN_LINK)
|
|
else()
|
|
target_compile_definitions(boost_timer PUBLIC BOOST_TIMER_STATIC_LINK)
|
|
endif()
|
|
|
|
if(BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt")
|
|
|
|
add_subdirectory(test)
|
|
|
|
endif()
|
|
```
|
|
|
|
We won't be repeating the explanations of the sections that match the
|
|
header-only case, and will only focus on the differences.
|
|
|
|
### Source Files
|
|
```cmake
|
|
add_library(boost_timer
|
|
src/auto_timers_construction.cpp
|
|
src/cpu_timer.cpp
|
|
)
|
|
```
|
|
|
|
For a compiled library, you need to declare your source files. This is
|
|
accomplished by listing them in the `add_library` directive. `boostdep` uses
|
|
the contents of your `src` subdirectory (but ignores any subdirectories.)
|
|
|
|
Since Timer is a simple library, this works as-is. Many compiled libraries
|
|
however might require adjusting the source file list, or choosing it based
|
|
on the platform. For example, Thread needs something like
|
|
```cmake
|
|
if(BOOST_THREAD_THREADAPI STREQUAL win32)
|
|
|
|
set(THREAD_SOURCES
|
|
src/win32/thread.cpp
|
|
src/win32/tss_dll.cpp
|
|
src/win32/tss_pe.cpp
|
|
src/win32/thread_primitives.cpp
|
|
src/future.cpp
|
|
)
|
|
|
|
else()
|
|
|
|
set(THREAD_SOURCES
|
|
src/pthread/thread.cpp
|
|
src/pthread/once.cpp
|
|
src/future.cpp
|
|
)
|
|
|
|
endif()
|
|
|
|
add_library(boost_thread ${THREAD_SOURCES})
|
|
```
|
|
The logic for choosing the source files is already spelled out in your
|
|
`Jamfile`, so you will need to port it to CMake.
|
|
|
|
If your library has C source files, you'll need to also enable C as a
|
|
language in your project declaration:
|
|
```cmake
|
|
project(boost_container VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES C CXX)
|
|
```
|
|
although `boostdep` might already have done so for you.
|
|
|
|
The `add_library(libname sources...)` declaration generates either a static
|
|
or a shared library depending on whether `BUILD_SHARED_LIBS` is set to `ON`
|
|
or `OFF`. This is idiomatic CMake behavior and is what we want.
|
|
|
|
### Directive Scope
|
|
```cmake
|
|
target_include_directories(boost_timer PUBLIC include)
|
|
```
|
|
|
|
The only difference with the header-only case is the use of `PUBLIC` instead
|
|
of `INTERFACE`. `PUBLIC` applies to both the library and its dependents; in
|
|
`b2` terms it declares both a requirement and a usage-requirement.
|
|
|
|
```cmake
|
|
target_link_libraries(boost_timer
|
|
PUBLIC
|
|
Boost::config
|
|
Boost::core
|
|
Boost::system
|
|
PRIVATE
|
|
Boost::chrono
|
|
Boost::io
|
|
Boost::predef
|
|
Boost::throw_exception
|
|
)
|
|
```
|
|
|
|
Again, the difference here is in the use of the scope keywords `PUBLIC` and
|
|
`PRIVATE` (applies only to the library, not to dependents) instead of
|
|
`INTERFACE`. `boostdep` puts the dependencies referred to from the `include`
|
|
subdirectory in the `PUBLIC` section, and those referred to from the `src`
|
|
subdirectory in the `PRIVATE` section.
|
|
|
|
### Compile Definitions
|
|
```cmake
|
|
target_compile_definitions(boost_timer
|
|
PUBLIC BOOST_TIMER_NO_LIB
|
|
PRIVATE BOOST_TIMER_SOURCE
|
|
)
|
|
```
|
|
|
|
The compile definitions are passed to the compiler with a `-D` option and
|
|
define macros. In this case by Boost convention we define `BOOST_TIMER_NO_LIB`
|
|
to disable autolink and `BOOST_TIMER_SOURCE` when compiling the library to
|
|
properly declare exported functions as exported (as opposed to imported,
|
|
which will be the case when using the library.)
|
|
|
|
```cmake
|
|
if(BUILD_SHARED_LIBS)
|
|
target_compile_definitions(boost_timer PUBLIC BOOST_TIMER_DYN_LINK)
|
|
else()
|
|
target_compile_definitions(boost_timer PUBLIC BOOST_TIMER_STATIC_LINK)
|
|
endif()
|
|
```
|
|
|
|
When building shared libraries, we define `BOOST_TIMER_DYN_LINK`, and when
|
|
building static libraries, we define `BOOST_TIMER_STATIC_LINK`. Again, this
|
|
is needed to properly export and import functions from dynamic libraries, in
|
|
particular on the Windows platform.
|
|
|
|
These defines are described in the
|
|
[Boost document about separate compilation](https://www.boost.org/development/separate_compilation.html)
|
|
and you can look at how
|
|
[the Timer library uses them](https://github.com/boostorg/timer/blob/e9387e4d9956074dffcc15bf15bd6d2625e91ebf/include/boost/timer/config.hpp)
|
|
as an example.
|
|
|
|
### Building More Than One Library Target
|
|
|
|
If your build results in more than one library being built, or if the name
|
|
of your library target does not match your directory name, you need to invoke
|
|
the installation support manually. As an example, Serialization builds two
|
|
library targets, `boost_serialization` and `boost_wserialization`, and the
|
|
procedure to install them entails adding
|
|
[the following section](https://github.com/boostorg/serialization/blob/337b3fbc7c4648d6f95f863546b9482500c8dec5/CMakeLists.txt#L116-L118)
|
|
to `CMakeLists.txt`:
|
|
```cmake
|
|
if(BOOST_SUPERPROJECT_VERSION AND NOT CMAKE_VERSION VERSION_LESS 3.13)
|
|
boost_install(TARGETS boost_serialization boost_wserialization
|
|
VERSION ${BOOST_SUPERPROJECT_VERSION} HEADER_DIRECTORY include)
|
|
endif()
|
|
```
|
|
The check for `BOOST_SUPERPROJECT_VERSION` is necessary because without the
|
|
superproject, `boost_install` is not available. The check for the CMake
|
|
version is needed because the automatic Boost installation support requires
|
|
CMake 3.13. Even though `boost_install` will work on earlier CMake versions,
|
|
you will likely get errors at generate time because the dependencies of your
|
|
library will lack install support.
|
|
|
|
For another example of a `CMakeLists.txt` file building and installing more
|
|
than one library, see [Boost.Test](https://github.com/boostorg/test/blob/bce2d24c8b32f47f0403766fe4fee3e2e93af0a0/CMakeLists.txt#L102-L114).
|
|
|
|
### Using Threads
|
|
|
|
If your library uses multiple threads or threading primitives, you need to
|
|
add the following snippet to your `CMakeLists.txt` file:
|
|
```cmake
|
|
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
|
find_package(Threads REQUIRED)
|
|
```
|
|
and then link to the target `Threads::Threads` in your
|
|
`target_link_libraries` directive. Typically, this would go in the `PUBLIC`
|
|
section (or `INTERFACE` if your library is header-only.)
|
|
|
|
(`PRIVATE` would imply that your library needs threading, but the clients
|
|
of your library do not, which is rarely the case.)
|
|
|
|
Note that this will abort the CMake configure phase with an error if
|
|
threading support can't be enabled. This is usually acceptable, but it's also
|
|
possible to omit the `REQUIRED` in `find_package(Threads REQUIRED)` and then
|
|
check `Threads_FOUND` and take some appropriate action when it's FALSE, such
|
|
as setting a preprocessor definition via `target_compile_definitions`.
|
|
|
|
### Build Options
|
|
|
|
Some libraries allow different functionality or backends. For example,
|
|
Iostreams has optional support for compressed streams and can use one or more
|
|
of the compression libraries ZLib, BZip2, LibLZMA, or Zstd, if these are
|
|
present on the system when the library is built. Locale, for another example,
|
|
can use Iconv, ICU, POSIX `newlocale`, or the Windows API, again depending on
|
|
availability at build time.
|
|
|
|
The recommended way to provide such optional functionality is to allow user
|
|
configuration with sensible defaults, as shown in the following example that
|
|
allows optional use of ZLib:
|
|
|
|
```cmake
|
|
find_package(ZLIB QUIET) # Look for ZLib
|
|
|
|
option(BOOST_MYLIB_ENABLE_ZLIB "Boost.MyLib: enable ZLib support" ${ZLIB_FOUND})
|
|
|
|
if(BOOST_MYLIB_ENABLE_ZLIB)
|
|
|
|
find_package(ZLIB REQUIRED) # For real this time
|
|
|
|
target_compile_definitions(boost_mylib PRIVATE BOOST_MYLIB_ENABLE_ZLIB=1)
|
|
target_add_sources(boost_mylib PRIVATE src/zlib.cpp)
|
|
target_link_libraries(boost_mylib PRIVATE ZLIB::ZLIB)
|
|
|
|
endif()
|
|
```
|
|
|
|
The general pattern is
|
|
|
|
* determine a sensible default
|
|
* add a CMake option to allow user control and override
|
|
* if the option is `ON`, enable functionality
|
|
|
|
Avoid silently enabling functionality on the basis of autodetection; it's
|
|
better to allow user control, in both directions. That is, the user should
|
|
be allowed to disable the functionality even if it's possible to incorporate
|
|
it, and the user should also be allowed to enable the functionality even if
|
|
autodetection says it won't work.
|
|
|
|
`find_package` in quiet mode is not the only possible way to determine the
|
|
default. You can also use platform detection (`if(WIN32)`), the result of a
|
|
configure check (`cxx_check_source_compiles`), and other measures.
|
|
|
|
After all the build options have been declared and taken into account, the
|
|
library should emit a single line of status output that shows the selected
|
|
configuration. For Iostreams, this output is of the form
|
|
```
|
|
-- Boost.Iostreams: ZLIB OFF, BZip2 OFF, LZMA OFF, Zstd OFF
|
|
```
|
|
|
|
Other Boost libraries that allow configuration are Context, Fiber, Locale,
|
|
Python, Stacktrace, Thread. For reference, their corresponding output is
|
|
```
|
|
-- Boost.Context: architecture x86_64, binary format pe, ABI ms, assembler masm, suffix .asm, implementation fcontext
|
|
-- Boost.Fiber: NUMA target OS is windows
|
|
-- Boost.Locale: iconv OFF, ICU OFF, POSIX OFF, std ON, winapi ON
|
|
-- Boost.Python: using Python 3.9.5 with NumPy at C:/Python39/Lib/site-packages/numpy/core/include
|
|
-- Boost.Stacktrace: noop ON, backtrace OFF, addr2line OFF, basic ON, windbg ON, windbg_cached ON
|
|
-- Boost.Thread: threading API is win32
|
|
```
|
|
|
|
## Guidelines and Best Practices
|
|
|
|
### Avoid Unnecessary Options
|
|
|
|
When your library is built as part of Boost, it should only add CMake options
|
|
and cache variables when they materially affect the way it's built or it will
|
|
operate.
|
|
|
|
Remember that Boost contains more than 140 libraries. If every such library
|
|
adds four "nice to have" options, this will result in 560 options in total in
|
|
`cmake-gui` for the user to wade through, most of which of no relevance for
|
|
the use at hand.
|
|
|
|
Either add the options only when `BOOST_SUPERPROJECT_VERSION` is not defined,
|
|
or only add them when your project is the root project (recommended).
|
|
|
|
(The difference is whether you insist on your options appearing when someone
|
|
uses the library with `add_subdirectory`. Typically, the options people add
|
|
to their libraries are only relevant when the library is the root project.)
|
|
|
|
Definitely don't do this:
|
|
```cmake
|
|
option(BOOST_MYLIB_MYOPTION "" ON)
|
|
|
|
if(BOOST_MYLIB_MYOPTION AND NOT BOOST_SUPERPROJECT_VERSION)
|
|
|
|
# Do highly valuable optional things
|
|
|
|
endif()
|
|
```
|
|
|
|
This displays the option, but makes it do nothing. Instead, either put the
|
|
option declaration inside an `if`, or use
|
|
[`CMakeDependentOption`](https://cmake.org/cmake/help/latest/module/CMakeDependentOption.html):
|
|
```cmake
|
|
include(CMakeDependentOption)
|
|
cmake_dependent_option(BOOST_MYLIB_MYOPTION "" ON "NOT BOOST_SUPERPROJECT_VERSION" OFF)
|
|
|
|
if(BOOST_MYLIB_MYOPTION)
|
|
|
|
# Do highly valuable optional things
|
|
|
|
endif()
|
|
```
|
|
|
|
### Avoid Unnecessary Status Output
|
|
|
|
When your library is built as part of Boost, avoid the urge to emit status
|
|
output unless it's relevant.
|
|
|
|
Remember that Boost contains more than 140 libraries. If every such library
|
|
emits two lines of status output, this will result in 280 lines in total, most
|
|
of them of no interest to the user.
|
|
|
|
Status output should be reserved for information that is of importance to
|
|
the user building and installing Boost, which usually means that it should
|
|
only be emitted by libraries that materially alter their operation on the
|
|
basis of user configuration or properties of the build environment.
|
|
|
|
Starting with CMake 3.15,
|
|
[`message`](https://cmake.org/cmake/help/latest/command/message.html)
|
|
now supports `VERBOSE` and `DEBUG` message types, which would be ideal for
|
|
the purpose of developer-centric output, if we could require CMake 3.15.
|
|
We don't (yet), so the current convention is to only emit "debug" output when
|
|
`Boost_DEBUG` is `ON`, and only emit "verbose" output when `Boost_DEBUG` is
|
|
`ON` or `Boost_VERBOSE` is `ON`.
|
|
|
|
(The rule of thumb separating "verbose" from "debug" is that the target
|
|
audience of the "debug" output is the person authoring the `CMakeLists.txt`
|
|
file, whereas the target audience of the "verbose" output is the user who
|
|
prefers verbosity over conciseness.)
|
|
|
|
### Prefix Target Names
|
|
|
|
Target names are global. Always prefix your target names with the name of your
|
|
project/library, such as `boost_mylib-mytarget`.
|
|
|
|
(This is typically only of relevance if you write your own tests by hand using
|
|
`add_executable` and `add_test`.)
|
|
|
|
### Do Not Add Tests Unless BUILD_TESTING Is ON
|
|
|
|
`BUILD_TESTING` is the standard CMake variable that controls whether
|
|
`add_test` does anything. Unless `BUILD_TESTING` is `ON`, to save time, you
|
|
should avoid creating any tests or targets on which they depend. Usually, this
|
|
translates to
|
|
```cmake
|
|
if(BUILD_TESTING)
|
|
add_subdirectory(test)
|
|
endif()
|
|
```
|
|
|
|
### Do Not Overuse Generator Expressions
|
|
|
|
Since CMake doesn't support any inline function calls or expressions,
|
|
programmers are tempted to use generator expressions. In a situation where
|
|
one would write in C++ `foo? "bar": "baz"`, one could write in CMake
|
|
`$<IF:$<BOOL:${FOO}>,BAR,BAZ>`.
|
|
|
|
Don't do this. It's not the same. Generator expressions are evaluated in the
|
|
generate phase, which happens after the configure phase. If you do
|
|
```cmake
|
|
target_compile_definitions(boost_mylib PUBLIC $<IF:$<BOOL:${BUILD_SHARED_LIBS}>,BOOST_MYLIB_DYN_LINK,BOOST_MYLINK_STATIC_LINK>)
|
|
```
|
|
(and assuming `BUILD_SHARED_LIBS` is `ON`), you're not setting the
|
|
`COMPILE_DEFINITIONS` property of `boost_mylib` to `BOOST_MYLIB_DYN_LINK`, but
|
|
to `$<IF:$<BOOL:ON>,BOOST_MYLIB_DYN_LINK,BOOST_MYLINK_STATIC_LINK>`.
|
|
|
|
Yes, it will still be evaluated to the right thing during generation, but it's
|
|
better to perform evaluations that only depend on configuration-time values at
|
|
configuration time and write the less "clever"
|
|
```cmake
|
|
if(BUILD_SHARED_LIBS)
|
|
target_compile_definitions(boost_mylib PUBLIC BOOST_MYLIB_DYN_LINK)
|
|
else()
|
|
target_compile_definitions(boost_mylib PUBLIC BOOST_MYLINK_STATIC_LINK)
|
|
endif()
|
|
```
|
|
|
|
## Usage Scenarios
|
|
|
|
### Building and Installing Boost
|
|
|
|
The primary scenario we will support is, obviously, building and installing
|
|
Boost with CMake (and optionally, running the tests, if one has a few days
|
|
to spare).
|
|
|
|
The building procedure would generally involve issuing (from the Boost root)
|
|
```bash
|
|
mkdir __build
|
|
cd __build
|
|
cmake <configuration options> ..
|
|
cmake --build . -j <threads>
|
|
```
|
|
|
|
which should result in Boost libraries being built with the specified
|
|
configuration options in subdirectories of the "stage" directory, by default
|
|
`stage/lib` and `stage/bin`.
|
|
|
|
Subsequent installation would be performed with
|
|
```bash
|
|
cmake --build . --target install
|
|
```
|
|
assuming that `CMAKE_INSTALL_PREFIX` was set beforehand to the desired
|
|
destination directory.
|
|
|
|
Under Windows, when using the default Visual Studio generator, the building
|
|
and installation procedure would need to be performed twice, once with
|
|
`--config Debug`, and once with `--config Release` (or perhaps with
|
|
`--config RelWithDebInfo`, as desired.)
|
|
|
|
Testing the entire Boost would be performed with
|
|
```bash
|
|
cmake -DBUILD_TESTING=ON ..
|
|
cmake --build . --target tests -j <threads>
|
|
ctest --output-on-failure -j <threads>
|
|
```
|
|
|
|
Again, when using the Visual Studio generator, this would be
|
|
```bash
|
|
cmake --build . --target tests -j <threads> --config Debug
|
|
ctest --output-on-failure -j <threads> -C Debug
|
|
```
|
|
resp.
|
|
```bash
|
|
cmake --build . --target tests -j <threads> --config Release
|
|
ctest --output-on-failure -j <threads> -C Release
|
|
```
|
|
|
|
### Using Boost libraries as Subprojects
|
|
|
|
The secondary scenario we would like to support would be user projects
|
|
"consuming" Boost libraries piecemeal without the superproject, by having
|
|
them in subdirectories in their project (as Git submodules, or acquired
|
|
with `FetchContent`), and then using `add_subdirectory` to incorporate them
|
|
in the master CMake project.
|
|
|
|
A sample project that demonstrates how users would consume individual Boost
|
|
libraries in this manner is available at
|
|
[github.com/pdimov/boost_cmake_demo](https://github.com/pdimov/boost_cmake_demo).
|
|
|
|
Note that `BOOST_SUPERPROJECT_VERSION` is not set in this scenario, but all of
|
|
the recommendations in the preceding sections still apply. Be sure to not
|
|
degrade the experience of the users choosing to embed Boost libraries in this
|
|
manner because your logic relies on checking `BOOST_SUPERPROJECT_VERSION`.
|
|
|
|
### Sole Boost Library as Subproject
|
|
|
|
Some Boost developers wish to support a scenario in which their library is
|
|
included via `add_subdirectory` into the user project, but other Boost
|
|
libraries are not. To obtain access to their Boost dependencies, they rely
|
|
on preexisting Boost installations, found using `find_package(Boost)`.
|
|
|
|
This rarely makes sense. Since the library is a Boost library, if
|
|
`find_package(Boost)` works for it, it will also work for the user, which
|
|
will make that library available (it being part of Boost.) There is no
|
|
need to incorporate it individually.
|
|
|
|
The cases where this does make sense generally concern a new library that
|
|
is not yet accepted into Boost, has not yet appeared in a Boost release, or
|
|
is sufficiently new that the typical `find_package(Boost)` finds a Boost
|
|
release that does not contain it.
|
|
|
|
These conditions only apply in the short term, and supporting this use case
|
|
is not recommended, because in the long term it's both a maintenance burden
|
|
and a source of problems. (When `find_package(Boost)` does find a Boost
|
|
release containing the library, it will rarely be the same version, which can
|
|
easily lead to the user project containing two versions of the library, with
|
|
the associated ODR violations which would at best manifest as link errors.)
|
|
|
|
If you insist on supporting this scenario, please make sure to not compromise
|
|
the user experience in the previous two cases.
|
|
|
|
### "Standalone" Installation
|
|
|
|
Installing an individual Boost library, without the rest of Boost, is an even
|
|
worse idea. It can easily lead to a broken Boost, and there's not much to be
|
|
gained even if it "works". Don't do it. If you do, please don't use the same
|
|
package name (`boost_libname`) or target names (`boost_libname`,
|
|
`Boost::libname`) as the legitimate Boost installation; if possible, also do
|
|
not use the `boost` namespace, to avoid link errors or ODR violations when
|
|
the "standalone" library and the legitimate Boost library end up in the same
|
|
binary (this happens more often than you might think.)
|
|
|
|
### "Standalone" Development and Testing
|
|
- Creating IDE Projects
|
|
|
|
## CI Quick Testing
|
|
|
|
### Building the Library
|
|
### Testing add_subdirectory Use
|
|
### Testing Use after Installation
|
|
|
|
## Testing
|
|
|
|
### Using boost_test
|
|
### Using boost_test_jamfile
|
|
### Using "Plain" CMake Tests
|