CMake: Superbuild Mode Explained

CMake: Superbuild Mode Explained
CMake: Superbuild Mode Explained

Introduction:

Every project needs to handle dependencies, and using CMake makes it easy to check whether these dependencies exist in the configured project. In previous notes, we demonstrated how to find dependencies installed on the system, and so far we have been using this pattern. However, when dependencies are not met, we can only fail the configuration and warn the user about the failure reason. Nevertheless, using CMake allows us to organize our project so that if dependencies cannot be found on the system, they can be automatically fetched and built. Subsequent notes will introduce and analyze the standard modules ExternalProject.cmake and FetchContent.cmake, as well as their use in superbuild mode. The former allows retrieving project dependencies at build time, while the latter allows retrieving dependencies at configuration time (added in CMake version 3.11). By using superbuild mode, we can leverage CMake as a package manager: dependencies will be handled in the same way across the same project, whether they are already available on the system or need to be rebuilt.

First, we will introduce superbuild mode through a simple example. We will demonstrate how to use the ExternalProject_Add command to build a hello_world program.

CMake: Superbuild Mode Explained

Project Structure

.
├── CMakeLists.txt
└── src
      ├── CMakeLists.txt
      └── hello-world.cpp

Project Structure:

https://gitee.com/jiangli01/tutorials/tree/master/cmake-tutorial/chapter8/01

Related Source Code

src/CMakeLists.txt

cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

project(example_core LANGUAGES CXX)

add_executable(hello-world hello_world.cpp)

src/hello_world.cpp

#include <cstdlib>
#include <iostream>
#include <string>
std::string say_hello() { 
  return std::string("Hello, CMake superbuild world!"); 
}

int main() {
  std::cout << say_hello() << std::endl;
  return EXIT_SUCCESS;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

project(example LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/subprojects)

include(ExternalProject)

ExternalProject_Add(${PROJECT_NAME}_core
  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}
  BUILD_ALWAYS
    1
  INSTALL_COMMAND
    ""
  )
set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/subprojects)

Set the EP_BASE directory property for the current directory and underlying directories.

include(ExternalProject)

Include the ExternalProject.cmake standard module. This module provides the ExternalProject_Add function.

ExternalProject_Add(${PROJECT_NAME}_core
  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}
  BUILD_ALWAYS
    1
  INSTALL_COMMAND
    ""
  )

The Hello, World source code is added as an external project by calling the ExternalProject_Add function. The name of the external project is example_core.

The ExternalProject_Add command can be used to add third-party sources. This article manages its own project as a collection of different CMake projects. In this example, both the main CMakeLists.txt and the sub CMakeLists.txt declare a CMake project, both using the project command.

The ExternalProject_Add has many options for configuring and compiling external projects. These options can be categorized into the following types:

  • Directory: Used to tune the structure of the source code and the build directory for external projects. In this article, we use the SOURCE_DIR option to let CMake know that the source files are in the ${CMAKE_CURRENT_LIST_DIR}/src folder. The directory for building the project and storing temporary files can also be specified in such options or directory properties. By setting the EP_BASE directory property, CMake will set all directories for each subproject according to the following layout:

    TMP_DIR = <EP_BASE>/tmp/<name>
    STAMP_DIR = <EP_BASE>/Stamp/<name>
    DOWNLOAD_DIR = <EP_BASE>/Download/<name>
    SOURCE_DIR = <EP_BASE>/Source/<name>
    BINARY_DIR = <EP_BASE>/Build/<name>
    INSTALL_DIR = <EP_BASE>/Install/<name>
    
  • Download: The code for external projects may need to be downloaded from online repositories or resources.

  • Update and Patch: Can be used to define how to update the source code of external projects or how to apply patches.

  • Configure: By default, CMake assumes that the external project is configured using CMake. As shown below, we are not limited to this case. If the external project is a CMake project, ExternalProject_Add will call the CMake executable and pass options. For this example, we pass configuration parameters through the CMAKE_ARGS and CMAKE_CACHE_ARGS options. The former is passed as command line arguments directly, while the latter is passed through CMake script files. In practice, the script file is located at build/subprojects/tmp/example_core/example_core-cache-.cmake. The configuration is as follows:

    loading initial cache file /home/jiangli/repo/tutorials/cmake-tutorial/chapter8/01/build/subprojects/tmp/example_core/example_core-cache-.cmake
    -- The CXX compiler identification is GNU 9.4.0
    -- Check for working CXX compiler: /usr/bin/c++
    -- Check for working CXX compiler: /usr/bin/c++ -- works
    -- Detecting CXX compiler ABI info
    -- Detecting CXX compiler ABI info - done
    -- Detecting CXX compile features
    -- Detecting CXX compile features - done
    -- Configuring done
    -- Generating done
    -- Build files have been written to: /home/jiangli/repo/tutorials/cmake-tutorial/chapter8/01/build/subprojects/Build/example_core
    
  • Build: Can be used to adjust the actual compilation of external projects. We use the BUILD_ALWAYS option to ensure that the external project is always rebuilt.

  • Install: These options are used to configure how the external project should be installed. We leave INSTALL_COMMAND empty.

  • Test: Run tests for software built from source code. Options like these for ExternalProject_Add can be used for this purpose. We did not use these options because the Hello, World example does not have any tests.

Results Display

$ mkdir -p build
$ cmake ..
$ cmake --build .

The structure of the build directory is a bit more complex, and the contents of the subprojects folder are as follows:

build/subprojects/
├── Build
│    └── example_core
│        ├── CMakeCache.txt
│        ├── CMakeFiles
│        ├── cmake_install.cmake
│        ├── hello-world
│        └── Makefile
├── Download
│    └── example_core
├── Install
│    └── example_core
├── Stamp
│    └── exampleq_core
│        ├── example_core-configure
│        ├── example_core-done
│        ├── example_core-download
│        ├── example_core-install
│        ├── example_core-mkdir
│        ├── example_core-patch
│        └── example_core-update
└── tmp
    └── example_core
        ├── example_core-cache-.cmake
        ├── example_core-cfgcmd.txt
        └── example_core-cfgcmd.txt.in

Additional Content

ExternalProject.cmake defines the ExternalProject_Get_Property command, which is very useful for retrieving properties of external projects. The properties of external projects are set when the ExternalProject_Add command is first called. For example, when configuring example_core, retrieving the parameters to be passed to CMake can be achieved as follows:

ExternalProject_Get_Property(${PROJECT_NAME}_core CMAKE_ARGS)
message(STATUS "CMAKE_ARGS of ${PROJECT_NAME}_core ${CMAKE_ARGS}")

The ExternalProject.cmake module defines the following additional commands:

  • ExternalProject_Add_Step: When adding an external project, this command allows locking additional commands as custom steps on it.

  • ExternalProject_Add_StepTargets: Allows defining steps (such as build and test steps) in the external project as separate targets. This means these steps can be triggered separately from the complete external project, allowing fine-grained control over complex dependencies in the project.

  • ExternalProject_Add_StepDependencies: Sometimes, the steps of an external project may depend on external targets, and this command is designed to handle those situations.

Finally, I wish everyone to become stronger!!! If this note has helped you, please like and share!!!🌹🌹🌹

CMake: Superbuild Mode Explained
CMake: Superbuild Mode Explained

Leave a Comment