CMake

CMake presets are 4C’s recommended way to configure and manage different configurations of 4C. This small article will go through a few of them. The experts should also read the official CMake presets documentation to get all convenient tricks.

Configuration from a terminal

Suppose you are in the build directory where you want to build 4C. You can configure a release build of 4C with:

cmake --preset=lnm_workstation ../path/to/source

Hint: The global configurations of cmake are stored in the CMakeCache.txt within the build folder and it’s sometimes helpful to remove it with rm CMakeCache.txt, before configuring the project new.

There is a number of available preset files, which can be retrieved by cmake <4C_sourcedir> --list-presets. This is the current output of this command:

Available configure presets:

  "docker"                          - Release build for Docker image
  "docker_assertions"               - Optimized build with assertions for Docker image
  "docker_clang"                    - Release build with LLVM Clang
  "docker_asan"                     - ASAN build for Docker image
  "docker_codeclimate"              - Code climate job with clang-tidy
  "docker_coverage"                 - Coverage report for Docker image
  "docker_minimal"                  - Minimal build for Docker image
  "docker_no_optional_dependencies" - Build with all optional dependencies turned off
  "lnm_workstation"                 - Release build for an LNM workstation
  "lnm_workstation_relwithdebinfo"  - Release build with debug information for an LNM workstation
  "lnm_workstation_debug"           - Debug build for an LNM workstation
  "lnm_workstation_coverage"        - Coverage report for an LNM workstation
  "lnm_bruteforce"                  - Release build for bruteforce at LNM
  "lnm_thought"                     - Release build for thought at LNM
  "imcs_workstation"                - Release build for an IMCS workstation
  "imcs_workstation_debug"          - Debug build for an IMCS workstation
  "imcs_workstation_trilinos"       - Develop build with debug information for an IMCS workstation
  "imcs_charon"                     - Release build for charon at IMCS

In general, it is highly recommended to create your own preset, see below.

Defining your own CMake presets

CMake presets allow you to also create your own configuration. You need to put a CMakeUserPresets.json-file (important: User) in the source-directory of 4C. This file will not be part of the repository as it is listed in .gitignore. In this file, you can define your own configurations. Particularly, you may define the binary directory, so you don’t need to go to your binary directory in order to configure 4C. CMake presets integrate well with recent releases of IDEs.

You can define as many configurations as you need. Note that you can inherit from other configurations by using the keyword inherits.

Such a local preset could look like this:

{
  "version": 5,
  "configurePresets": [
    {
      "name": "myworkstation",
      "displayName": "Release build for my workstation",
      "binaryDir": "<4C-execdir>/4C",
      "generator": "Ninja",
      "inherits": [
        "lnm_workstation"
      ],
      "cacheVariables": {
        "CMAKE_CXX_COMPILER": "g++",
        "CMAKE_CXX_COMPILER_LAUNCHER": "ccache",
        "FOUR_C_WITH_GOOGLETEST": "OFF",
        "FOUR_C_BUILD_DOCUMENTATION": "ON",
        "FOUR_C_BUILD_DOXYGEN": "ON",
        "FOUR_C_ENABLE_DEVELOPER_MODE": "ON",
      }
    }
  ]
}

Don’t be overwhelmed by the options you could potentially set.

  • For a basic build of 4C, you should start with CMAKE_BUILD_TYPE (either RELEASE or DEBUG) and maybe a compiler.

  • If you are developing 4C with this configuration, you usually want to turn on FOUR_C_ENABLE_DEVELOPER_MODE which optimizes the build setup for iterative development cycles.

  • We try to detect reasonable defaults for you internally.

Reference of all CMake variables

Over time you might realize that you want to turn on additional dependencies or features. To see which other options you can set, consult the console output of CMake or run ccmake . in the build folder. Alternatively, many options are also printed with their ON or OFF state whenever cmake runs.

Remark: Variables either start with the prefix FOUR_C_ indicating that this variable only affects 4C itself, or they start with CMAKE_ indicating that the variable (potentially) affects all dependent projects in a way specified directly in the CMake documentation.

This is a list of all variables that are available to configure 4C:

Variable

Type

Description

FOUR_C_ARBORX_FIND_INSTALLED

BOOL

Use installed ARBORX instead of fetching sources

FOUR_C_ARBORX_ROOT

PATH

Root directory of ArborX (default: )

FOUR_C_BACKTRACE_ROOT

PATH

Root directory of Backtrace (default: )

FOUR_C_BOOST_ROOT

PATH

Root directory of Boost (default: )

FOUR_C_BUILD_DOCUMENTATION

BOOL

Build the documentation (default: OFF)

FOUR_C_BUILD_DOXYGEN

BOOL

Build doxygen documentation (default: OFF)

FOUR_C_BUILD_SHARED_LIBS

BOOL

Build shared libraries instead of static ones (default: ON)

FOUR_C_CLN_ROOT

PATH

Root directory of CLN (default: )

FOUR_C_CXX_FLAGS

UNINITIALIZED

No help, variable specified on the command line.

FOUR_C_DEAL_II_ROOT

PATH

Root directory of deal.II (default: )

FOUR_C_DETECT_LINKER

BOOL

Detect a fast linker

FOUR_C_DOXYGEN_LOCAL_MATHJAX_BASEPATH

PATH

Path to local MathJax installation (default: )

FOUR_C_DOXYGEN_USE_LOCAL_MATHJAX

BOOL

Use local MathJax installation (default: OFF)

FOUR_C_ENABLE_ADDRESS_SANITIZER

BOOL

Compile with address sanitizer (default: OFF)

FOUR_C_ENABLE_ASSERTIONS

BOOL

Turn on assertions and debug sections in code. Automatically turned on for DEBUG as CMAKE_BUILD_TYPE. (default: OFF)

FOUR_C_ENABLE_CORE_DUMP

BOOL

Uncaught exceptions create a core file (default: OFF)

FOUR_C_ENABLE_COVERAGE

BOOL

Set up a build to gather coverage information with LLVM source based coverage (default: OFF)

FOUR_C_ENABLE_DEVELOPER_MODE

BOOL

Enable developer mode (tries to optimize setup for iterative development cycles) (default: OFF)

FOUR_C_ENABLE_FE_TRAPPING

BOOL

Crash the program if a nan or inf would occur (default: ON)

FOUR_C_ENABLE_IWYU

BOOL

Enable include-what-you-use (default: OFF)

FOUR_C_ENABLE_METADATA_GENERATION

BOOL

Generate metadata after building 4C. Requires python and invokes 4C. (default: ON)

FOUR_C_ENABLE_NATIVE_OPTIMIZATIONS

BOOL

Optimize for current hardware (default: OFF)

FOUR_C_ENABLE_WARNINGS_AS_ERRORS

BOOL

Treat warnings as errors when compiling (default: OFF)

FOUR_C_FFTW_ROOT

PATH

Root directory of FFTW (default: )

FOUR_C_HAVE_LINKER_PROGRAM_lld

FILEPATH

Path to a program.

FOUR_C_HAVE_LINKER_PROGRAM_mold

FILEPATH

Path to a program.

FOUR_C_HDF5_ROOT

PATH

Root directory of HDF5 (default: )

FOUR_C_MAGIC_ENUM_ROOT

PATH

Root directory of magic_enum (default: )

FOUR_C_MIRCO_FIND_INSTALLED

BOOL

Use installed MIRCO instead of fetching sources

FOUR_C_MIRCO_ROOT

PATH

Root directory of MIRCO (default: )

FOUR_C_MPIEXEC_ARGS_FOR_TESTING

STRING

Arguments to pass to mpiexec for testing. (default: –bind-to none –use-hwthread-cpus –mca orte_tmpdir_base /__w/4C/4C/build/tmp)

FOUR_C_MPI_ROOT

PATH

Root directory of MPI (default: )

FOUR_C_PVPYTHON

FILEPATH

Path to the pvpython executable used for post-processing tests (default: pvpython-not-set)

FOUR_C_QHULL_ROOT

PATH

Root directory of Qhull (default: )

FOUR_C_RYML_ROOT

PATH

Root directory of ryml (default: )

FOUR_C_TEST_TIMEOUT_SCALE

STRING

Scale timeout of tests by this factor. (default: 1)

FOUR_C_TRILINOS_ROOT

PATH

Root directory of Trilinos (default: )

FOUR_C_WITH_ARBORX

BOOL

Build 4C with ArborX (default: OFF)

FOUR_C_WITH_BACKTRACE

BOOL

Build 4C with Backtrace (default: OFF)

FOUR_C_WITH_BOOST

BOOL

Build 4C with Boost (default: ON)

FOUR_C_WITH_CLN

BOOL

Build 4C with CLN (default: ON)

FOUR_C_WITH_DEAL_II

BOOL

Build 4C with deal.II (default: OFF)

FOUR_C_WITH_FFTW

BOOL

Build 4C with FFTW (default: ON)

FOUR_C_WITH_GOOGLETEST

BOOL

Use GoogleTest for unit testing (default: ON)

FOUR_C_WITH_GOOGLE_BENCHMARK

BOOL

Use Google Benchmark for micro benchmark tests (default: OFF)

FOUR_C_WITH_HDF5

BOOL

Build 4C with HDF5 (default: ON)

FOUR_C_WITH_MAGIC_ENUM

BOOL

Build 4C with magic_enum (default: ON)

FOUR_C_WITH_MIRCO

BOOL

Build 4C with MIRCO (default: OFF)

FOUR_C_WITH_MPI

BOOL

Build 4C with MPI (default: ON)

FOUR_C_WITH_QHULL

BOOL

Build 4C with Qhull (default: ON)

FOUR_C_WITH_RYML

BOOL

Build 4C with ryml (default: ON)

FOUR_C_WITH_TRILINOS

BOOL

Build 4C with Trilinos (default: ON)

FOUR_C_WITH_ZLIB

BOOL

Build 4C with ZLIB (default: ON)

FOUR_C_ZLIB_ROOT

PATH

Root directory of ZLIB (default: )

Configuration from the IDE

Our recommended IDEs (VS Code and CLion) already support cmake presets natively.

Here is a screenshot taken from VS Code:

CMake preset selection within VS Code

Hints for VS Code: You need to install the extensions “CMake Tools” from Microsoft.

For CMake maintainers

This section documents how we write the CMake code. For users and developers who do not work on the build system, this section is not necessary.

Conventions

  • In general, do not modify CMAKE_ variables inside CMake files! They affect downstream packages (e.g. from fetch_content) as well and we cannot easily know whether these packages can handle our settings.

  • Prefer the modern CMake way and use targets over variables when handling requirements and dependencies.

  • Every variable that is supposed to be set from outside has to start with FOUR_C_. Variables that toggle a dependency are named FOUR_C_WITH_<PACKAGE>. Further options for a package are specified by FOUR_C_<PACKAGE>_<OPTION>.

  • The top-most install directory of a dependency is supplied by the FOUR_C_<PACKAGE>_ROOT variable. When possible we try to use CMake files exported by dependencies. For older libraries we write our own Find<package>.cmake modules.

  • Every function that is supplied by this project starts with FOUR_C_.

  • CMake files are automatically formatted according to the style defined in ./utilities/code_checks/.cmake-format.yaml.

General

Prefer to write small CMake helpers that do the necessary steps as automatically as possible. CMake is a language that can be used in many ways. Not many developers know CMake details, so we try to make their life easier by providing a few simple functions that do what is necessary while hiding the details.

Configuring dependencies

We use a two-step process to configure dependencies. The dependencies are listed in the top-level CMakeLists.txt. A helper function takes care of basic checks that user input for a dependency is sensible and then calls the respective configuration script. From this script the package search is started via find_package which will either look for a packaged config file or use our own finders defined in cmake/modules.

Internal dependency management

The code base is split into multiple modules, that consist of several files. A module is built by compiling the source files (*.cpp) into object files (*.o). For building, information from other modules may be necessary. CMake calls this information “usage requirements” and it mainly consists of header files (*.hpp) and define flags from other modules. Notably, the compiled object files of a module are not needed to build another module. They only become necessary when an executable is built. This leads us to the following strategy:

When defining a module module_name:

  • Define a CMake INTERFACE target conventionally called module_name_deps which contains all the headers and usage requirements of the module.

  • If another module module_other is required for building module_name, this module is added as four_c_add_dependency(module_name module_other). This step encodes internal dependencies.

  • Define a CMake OBJECT target conventionally called module_name_objs which contains the source files. If a module is header-only, this target is not defined.

  • Add the OBJECT target to a central library which contains all compiled sources.

Any executable links against the central library. Note that the central library may have a certain modules turned on or off, once that module is refactored enough to separate it from the rest of the library.

This strategy comes with a number of useful properties:

  • We can build the central library as a shared or a static library.

  • All object targets can be built independently and simultaneously which offers a high degree of parallelism.

  • It allows cyclic dependencies between modules. Although we work towards removing cyclic dependencies, the code is not there yet and we need to be able to build what we have.