We will begin with the root CMakeLists.txt:
- We declare a C++11 project as usual:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-02 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
- We set the EP_BASE directory property:
set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/subprojects)
- We set the STAGED_INSTALL_PREFIX variable. This directory will be used to install the dependencies within our build tree:
set(STAGED_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/stage)
message(STATUS "${PROJECT_NAME} staged install: ${STAGED_INSTALL_PREFIX}")
- Our project needs the filesystem and system components of the Boost libraries. We declare a list variable to hold this information and also set the minimum required version of Boost:
list(APPEND BOOST_COMPONENTS_REQUIRED filesystem system)
set(Boost_MINIMUM_REQUIRED 1.61)
- We add the external/upstream subdirectory, which will in turn add the external/upstream/boost subdirectory:
add_subdirectory(external/upstream)
- Then, we include the ExternalProject.cmake standard CMake module. This defines, among others, the ExternalProject_Add command, which is the key to orchestrating superbuilds:
include(ExternalProject)
- Our project resides under the src subdirectory and we add it as an external project. We pass CMake options using CMAKE_ARGS and CMAKE_CACHE_ARGS:
ExternalProject_Add(${PROJECT_NAME}_core
DEPENDS
boost_external
SOURCE_DIR
${CMAKE_CURRENT_LIST_DIR}/src
CMAKE_ARGS
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
-DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}
-DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}
CMAKE_CACHE_ARGS
-DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS}
-DCMAKE_INCLUDE_PATH:PATH=${BOOST_INCLUDEDIR}
-DCMAKE_LIBRARY_PATH:PATH=${BOOST_LIBRARYDIR}
BUILD_ALWAYS
1
INSTALL_COMMAND
""
)
Let us now look at the CMakeLists.txt in external/upstream. This file simply adds the boost folder as an additional directory:
add_subdirectory(boost)
The CMakeLists.txt in external/upstream/boost describes the operations needed to satisfy the dependency on Boost. Our goal is simple, if the desired version is not installed, download the source archive and build it:
- First of all, we attempt to find the Boost components needed for the minimum required version:
find_package(Boost ${Boost_MINIMUM_REQUIRED} QUIET COMPONENTS "${BOOST_COMPONENTS_REQUIRED}")
- If these are found, we add an interface library, boost_external. This is a dummy target, needed to properly handle build order in our superbuild:
if(Boost_FOUND)
message(STATUS "Found Boost version ${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}")
add_library(boost_external INTERFACE)
else()
# ... discussed below
endif()
- If find_package was not successful or we are forcing the superbuild, we need to set up a local build of Boost and for this, we enter the else-section of the previous conditional:
else()
message(STATUS "Boost ${Boost_MINIMUM_REQUIRED} could not be located, Building Boost 1.61.0 instead.")
- Since these libraries do not use CMake, we need to prepare the arguments for their native build toolchain. First, we set the compiler to be used for Boost:
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
if(APPLE)
set(_toolset "darwin")
else()
set(_toolset "gcc")
endif()
elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
set(_toolset "clang")
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Intel")
if(APPLE)
set(_toolset "intel-darwin")
else()
set(_toolset "intel-linux")
endif()
endif()
- We prepare the list of libraries to be built based on the required components. We define some list variables: _build_byproducts, to contain the absolute path to the libraries that will be built; _b2_select_libraries, to contain the list of libraries we want to build; and _bootstrap_select_libraries, which is a string with the same contents in a different format:
if(NOT "${BOOST_COMPONENTS_REQUIRED}" STREQUAL "")
# Replace unit_test_framework (used by CMake's find_package) with test (understood by Boost build toolchain)
string(REPLACE "unit_test_framework" "test" _b2_needed_components "${BOOST_COMPONENTS_REQUIRED}")
# Generate argument for BUILD_BYPRODUCTS
set(_build_byproducts)
set(_b2_select_libraries)
foreach(_lib IN LISTS _b2_needed_components)
list(APPEND _build_byproducts ${STAGED_INSTALL_PREFIX}/boost/lib/libboost_${_lib}${CMAKE_SHARED_LIBRARY_SUFFIX})
list(APPEND _b2_select_libraries --with-${_lib})
endforeach()
# Transform the ;-separated list to a ,-separated list (digested by the Boost build toolchain!)
string(REPLACE ";" "," _b2_needed_components "${_b2_needed_components}")
set(_bootstrap_select_libraries "--with-libraries=${_b2_needed_components}")
string(REPLACE ";" ", " printout "${BOOST_COMPONENTS_REQUIRED}")
message(STATUS " Libraries to be built: ${printout}")
endif()
- We can now add the Boost project as an external project. First of all, we specify the download URL and the checksum in the Download options class. DOWNLOAD_NO_PROGRESS is set to 1 to suppress printing download progress information:
include(ExternalProject)
ExternalProject_Add(boost_external
URL
https://sourceforge.net/projects/boost/files/boost/1.61.0/boost_1_61_0.zip
URL_HASH
SHA256=02d420e6908016d4ac74dfc712eec7d9616a7fc0da78b0a1b5b937536b2e01e8
DOWNLOAD_NO_PROGRESS
1
- Next, we set Update/Patch and Configure options:
UPDATE_COMMAND
""
CONFIGURE_COMMAND
<SOURCE_DIR>/bootstrap.sh
--with-toolset=${_toolset}
--prefix=${STAGED_INSTALL_PREFIX}/boost
${_bootstrap_select_libraries}
- The Build options are set using the BUILD_COMMAND directive. BUILD_IN_SOURCE is set to 1 to signal that building will happen within the source directory. Moreover, we set LOG_BUILD to 1 to log ouput from the build script to a file:
BUILD_COMMAND
<SOURCE_DIR>/b2 -q
link=shared
threading=multi
variant=release
toolset=${_toolset}
${_b2_select_libraries}
LOG_BUILD
1
BUILD_IN_SOURCE
1
- The Install options are set using the INSTALL_COMMAND directive. Note the use of the LOG_INSTALL option to also log the install step to file:
INSTALL_COMMAND
<SOURCE_DIR>/b2 -q install
link=shared
threading=multi
variant=release
toolset=${_toolset}
${_b2_select_libraries}
LOG_INSTALL
1
- Finally, we list our libraries as BUILD_BYPRODUCTS and close the ExternalProject_Add command:
BUILD_BYPRODUCTS
"${_build_byproducts}"
)
- We set some variables useful for directing the detection of the newly installed Boost:
set(
BOOST_ROOT ${STAGED_INSTALL_PREFIX}/boost
CACHE PATH "Path to internally built Boost installation root"
FORCE
)
set(
BOOST_INCLUDEDIR ${BOOST_ROOT}/include
CACHE PATH "Path to internally built Boost include directories"
FORCE
)
set(
BOOST_LIBRARYDIR ${BOOST_ROOT}/lib
CACHE PATH "Path to internally built Boost library directories"
FORCE
)
- The final action performed in the else-branch of the conditional is to unset all internal variables:
unset(_toolset)
unset(_b2_needed_components)
unset(_build_byproducts)
unset(_b2_select_libraries)
unset(_boostrap_select_libraries)
Finally, let us look at src/CMakeLists.txt. This file describes a standalone project:
- We declare a C++ project:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-02_core LANGUAGES CXX)
- The project depends on Boost and we invoke find_package. The configuration of the project from the root CMakeLists.txt guarantees that the dependency is always satisfied, either by using Boost pre-installed on the system or the ones we built as a subproject:
find_package(Boost 1.61 REQUIRED COMPONENTS filesystem)
- We add our example executable target, describing its link libraries:
add_executable(path-info path-info.cpp)
target_link_libraries(path-info
PUBLIC
Boost::filesystem
)
The use of imported targets, while neat, is not guaranteed to work for arbitrary Boost and CMake version combinations. This is because the CMake FindBoost.cmake module creates the imported targets by hand, so if the Boost version was unknown at the time of the CMake release, there will be Boost_LIBRARIES and Boost_INCLUDE_DIRS, but no imported targets (see also https://stackoverflow.com/questions/42123509/cmake-finds-boost-but-the-imported-targets-not-available-for-boost-version).