How to use CMake to configure your projects with deal.II

cmake is controlled by input files that by convention are called CMakeLists.txt, listing both configuration commands as well as dependencies between source files and targets. This page presents some CMakeLists.txt examples for potential use in your projects. (A detailed description of the deal.II project configuration is given in the deal.II CMake ReadMe.)

  1. Simple CMakeLists.txt
    1. Adding multiple executable targets
    2. Adding libraries and common source files
    3. Switching build types
    4. Adding a "run" target
  2. Advanced CMakeLists.txt
    1. Source directory layout
    2. Control statements
    3. File globs
    4. DEAL_II_SETUP_TARGET revisited
    5. DEAL_II_INITIALIZE_CACHED_VARIABLES revisited
    6. Customizing include directories and compile definitions
    7. External libraries
    8. The "run" target revisited
    9. Install a project
  3. Autopilot style CMakeLists.txt
  4. deal.IIConfig.cmake

Simple CMakeLists.txt

In this section, we start out with a minimal CMakeLists.txt based on the DEAL_II_SETUP_TARGET macro. This method gives full control of what's happening and is easily extensible to more complex projects, as exemplified in the subsections here and later in the section on advanced topics. Here is a full example (plain text version):

cmake_minimum_required(VERSION 3.13.4)

find_package(deal.II 9.5.0 REQUIRED
  HINTS ${DEAL_II_DIR} ../ ../../ $ENV{DEAL_II_DIR}
  )
deal_ii_initialize_cached_variables()

project(myproject)

add_executable(mycode mycode.cc)
deal_ii_setup_target(mycode)

The first line of this code makes sure that a sufficiently high version of CMake is installed. 2.8.8 is the minimal version required to set up deal.II, therefore it is safe to use this number here.

Next, we find our deal.II installation with the help of the FIND_PACKAGE command. In this case requiring at least version 9.0.0. The HINTS are a list of directories where the install directory of deal.II is likely to be found. First, the location possibly defined in the CMake variable DEAL_II_DIR is considered. After that, we check whether we are in a subdirectory (first and second level) of the deal.II installation and otherwise use the environment variable DEAL_II_DIR. If all of these hints fail the default system locations /usr/ and /usr/local/ are considered. The list after HINTS can be changed according to your preferences.

After finding the deal.II project, we fetch a set of cached variables with the deal_ii_initialize_cached_variables macro. You can inspect these for instance with ccmake.

Every CMakeLists.txt must contain a project definition, which we do next.

Finally, the last two lines define the executable that is to be produced and its source code. The deal_ii_setup_target macro will set up necessary include directories, compile flags, compile definitions, link flags and the link interface.

Adding multiple executable targets

In order to specify multiple executable targets, simply repeat the last two lines of the simple CMakeLists.txt:

add_executable(mycode2 mycode2.cc)
deal_ii_setup_target(mycode2)

add_executable(mycode3 mycode3.cc)
deal_ii_setup_target(mycode3)
If the list gets longer, consider using a loop, possibly with GLOB.

Adding libraries and common source files

Adding a library is as simple as adding an executable target. We specify the library name and then have to tell cmake that the executables depend on it. The code in the simple file below the project definition accordingly changes for instance to:

add_library(mylib libsrc1.cc libsrc2.cc libsrc3.cc)
deal_ii_setup_target(mylib)

add_executable(mycode mycode.cc)
deal_ii_setup_target(mycode)
target_link_libraries(mycode mylib)

When you have multiple targets, repeat the last line of code for each of them. Accordingly, a loop becomes even more attractive.

If you only have a single file or few files with common source code, an alternative to creating a library might be the option:

add_executable(mycode mycode.cc common.cc)
deal_ii_setup_target(mycode)

add_executable(mycode2 mycode2.cc common.cc)
deal_ii_setup_target(mycode2)

You should be aware though that in this case common.cc will be compiled for each target, not only once. If you want to avoid this and still don't want to use a shared library or static archive, another option is to create an OBJECT "library":

add_library(common OBJECT common.cc)
deal_ii_setup_target(common)

add_executable(mycode mycode.cc $<TARGET_OBJECTS:common>)
deal_ii_setup_target(mycode)

add_executable(mycode2 mycode2.cc $<TARGET_OBJECTS:common>)
deal_ii_setup_target(mycode2)
This will compile common.cc once for the object target common and link the resulting object file into the two executables.

Switching build types

The build type is controlled via the variable CMAKE_BUILD_TYPE. If it is set to Debug executables and libraries specified in your CMakeLists.txt file will be compiled in debug mode and linked against the debug version of the deal.II library. Contrary, Release will build in optimized mode and link against the optimized release version of deal.II. You can set CMAKE_BUILD_TYPE with the help of ccmake or via cmake on the command line:

$ cmake -DCMAKE_BUILD_TYPE="Debug" .

$ cmake -DCMAKE_BUILD_TYPE="Release" .
Alternatively, you can specify custom targets to switch the build type and compile automatically:
add_custom_target(debug
  COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug ${CMAKE_SOURCE_DIR}
  COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target all
  COMMENT "Switch CMAKE_BUILD_TYPE to Debug"
  )

add_custom_target(release
  COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Release ${CMAKE_SOURCE_DIR}
  COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target all
  COMMENT "Switch CMAKE_BUILD_TYPE to Release"
  )
With that, switching the build type and compiling the project can be done very conveniently via:
$ make debug

$ make release

Adding a "run" target

If you wish to have a "run" target for make, like in the deal.II tutorial, specify one this way (obviously, a single "run" target can only run a single executable):

add_custom_target(run COMMAND mycode
  COMMENT "Run with ${CMAKE_BUILD_TYPE} configuration"
  )

Advanced CMakeLists.txt

This section covers some advanced topics for a user CMakeLists.txt file.

Source directory layout

For complex projects it is desirable to organize source code and header files in subdirectories. Assume the following project structure with a library "mylib" and an executable "mycode":

mylib/source/*.cc
mylib/include/*.h

mycode/source/*.cc
mycode/include/*.h
In this case the top level CMakeLists.txt file may be:
# top level CMakeLists.txt
cmake_minimum_required(VERSION 3.13.4)
find_package(deal.II 9.5.0 REQUIRED)

deal_ii_initialize_cached_variables()
project(myproject)

add_subdirectory(mylib)
add_subdirectory(mycode)
The add_subdirectory statement will include the CMakeLists.txt file in the specified subdirectory. In our case:
# mylib/CMakeLists.txt

include_directories(include)

add_library(mylib
  source/mylib1.cc
  source/mylib2.cc
  )

deal_ii_setup_target(mylib)
We have to add the directory include for the header files to the current include directories with the INCLUDE_DIRECTORIES statement (see this section for details). The corresponding configuration file for the executable looks like:
# mycode/CMakeLists.txt

include_directories(
  include
  ${CMAKE_SOURCE_DIR}/mylib/include
  )

add_executable(mycode source/mycode.cc)
deal_ii_setup_target(mycode)

target_link_libraries(mycode mylib)

Please note that CMakeLists.txt files have directory scope. Any manipulation of properties and variables have only effect in the current directory (and subdirectories, included with add_subdirectory. The level above will not be affected. Therefore, we have to specify the include directories for "mylib" again in the subdirectory mycode - this time with full path ${CMAKE_SOURCE_DIR}/mylib/include.

CMake defines the following variables for access to important directories:

CMAKE_SOURCE_DIR
  - the source directory (i.e. the directory of the top level
    CMakeLists.txt file)

CMAKE_BINARY_DIR
  - the (top level) build directory

CMAKE_CURRENT_SOURCE_DIR
  - the current source directory, i.e. location of the currently processed
    CMakeLists.txt file (top level or included via ADD_SUBDIRECTORY)

CMAKE_CURRENT_BINARY_DIR
  - the build (sub)directory corresponding to CMAKE_CURRENT_SOURCE_DIR

Control statements

Control statements in CMake take the following form:

if(<expression>)
  ...
endif()
or in long form:
if(<expression1>)
  ...
elseif(<expression2>)
  ...
else()
  ...
endif()
Please note the (somehow uncommon) empty, opening and closing brackets behind else() and endif(). <expression> can take a multitude of different forms, have a look at the CMake documentation for a complete list. Important examples are:
if(${variable})
  - the body will be evaluated if the variable "variable" is defined and
    synonymous to true, e.g. 1, TRUE, ON, YES (modulo case insensitivity)

if(variable MATCHES <regular expression>)
  - the body will be evaluated if the variable "variable" is defined and
    matches the specified regular expression

if("${variable}" STREQUAL "foobar")
  - the body will be evaluated if both strings are equal. Note that
    "${variable}" will be replaced by the content of the (string)
    variable "variable"

An expression can be negated by prefixing NOT:

IF(NOT <expression>)
  ...
ENDIF()

Loops are implemented with the help of WHILE and FOR statements. The former takes the same <expression> as the IF statement:

while(<expression>)
  ...
endwhile()
Given a variable list containing a list, the individual elements element can be accessed with a FOREACH statement:
foreach(element ${list})
  ...
endforeach()
Note: It is also possible to specify the list directly:
foreach(element foo bar baz)
  # The variable element will iterate through foo, bar and baz.
endforeach

File globs

A very common task is to pick up a list of source files from a directory. You can either manage a list of source files in CMakeLists.txt by hand, e.g. by manually updating all source files for a given target, or you can use a glob to automate this process. The following example will pick up every source file under SOURCE_DIR/sources/ and add it to an executable:

file(GLOB sources ${CMAKE_SOURCE_DIR}/source/*.cc)
add_executable(mycode ${sources})

Please be aware of one caveat of this approach: Due to the fact that CMake is a build system generator the resulting build configuration (for make) has no way to detect whether a new source file was added (or removed) and that it has to call back to cmake. So, after adding a new source file you have to touch a CMakeLists.txt file or to run cmake . again by hand.

deal_ii_setup_target revisited

The deal_ii_setup_target macro is responsible for setting up a target to compile and link against deal.II. It will append the include_directories property with the location of the deal.II include directories, and append the properties COMPILE_FLAGS, COMPILE_DEFINITIONS and LINK_FLAGS by their respective values from the deal.II configuration (depending on build type and available debug and/or optimized flavor of the library).

Except in the case of an object library, the specified target will also be set up to link against deal.II (and its transitive link interface) as well.

Optionally, the deal_ii_setup_target macro takes an additional argument DEBUG, or RELEASE, after the target name to explicitly state the library flavor the target should be set up for. If the parameter is omitted, the correct choice is deduced from the current build type. (This is supported for the build types Debug, and Release. In case of DebugRelease the additional argument is always required.)

Note: The flags that are added with DEAL_II_SETUP_TARGET to the target come last in the final link compiler invocation, or linker invocation. This means they take precedence over all flags defined via globally via CMAKE_CXX_FLAGS, etc., or as a directory property. If you wish to modify flags or preprocessor definitions set up with DEAL_II_SETUP_TARGET modify one of the following variables (see the section about deal.IIConfig.cmake for details):

DEAL_II_CXX_FLAGS
DEAL_II_CXX_FLAGS_DEBUG
DEAL_II_CXX_FLAGS_RELEASE
DEAL_II_LINKER_FLAGS
DEAL_II_LINKER_FLAGS_DEBUG
DEAL_II_LINKER_FLAGS_RELEASE
DEAL_II_DEFINITIONS
DEAL_II_DEFINITIONS_DEBUG
DEAL_II_DEFINITIONS_RELEASE

DEAL_II_INITIALIZE_CACHED_VARIABLES revisited

The DEAL_II_INITIALIZE_CACHED_VARIABLES macro is responsible for setting up cached variables and has to invoked before the PROJECT call:

find_package(deal.II 9.5.0 REQUIRED)

deal_ii_initialize_cached_variables()

project(myproject)
The macro will set an uninitialized CMAKE_BUILD_TYPE variable to the build type of deal.II, i.e. DEAL_II_BUILD_TYPE. If CMAKE_BUILD_TYPE is specified it will automatically be reset if the given value is unsupported by the deal.II installation (i.e., if it is not equal to Debug, Release, or DebugRelease).

Furthermore, this macro sets the C++ compiler to the one used for compiling the deal.II library. The variables CMAKE_CXX_FLAGS, CMAKE_CXX_FLAGS_DEBUG, and CMAKE_CXX_FLAGS_RELEASE will be initialized with the empty string.

Note: If you wish to override the flags and definitions set by the deal_ii_setup_target macro you have to override the corresponding DEAL_II_* variable instead. See the documentation of deal_ii_setup_target for further details.

Customizing include directories and compile definitions

You can specify custom include directories and compile definitions prior to a target definition on a per directory basis (have a look at the CMake documentation for further details):

include_directories(include1 include2)

add_definitions(-DFOO -DBAR="BAZ")

add_executable(...) # or add_library(...)

External libraries

For external libraries that provide a CMake project configuration or where a CMake find module is available, including this external library in your project is more or less straightforward. E.g. to require an external project "foo" at least of version 8.0 write:

find_package(foo 8.0 REQUIRED)
Alternatively, the version number and REQUIRED keyword can be omitted. Depending on the external library, the project configuration or find macro will usually define variables like FOO_INCLUDE_DIRS and FOO_LIBRARIES that can be directly used in your CMakeLists.txt file:
include_directories(${FOO_INCLUDE_DIRS})

add_executable(mycode mycode.cc)
deal_ii_setup_target(mycode)

target_link_libraries(mycode ${FOO_LIBRARIES})
The first statement will set up the include directories for the following targets as explained above. The last statement with target_link_libraries will add the libraries in the FOO_LIBRARIES variable to the link interface of the target mycode.

The "run" target revisited

The simple run statement as explained above will run the generated executable in the build directory. Sometimes it is more desirable to run the executable in a dedicated run directory within in the build directory which is a copy of a skeleton run folder from the source directory:

# Copy folder run from the source to the build directory:
file(COPY ${CMAKE_SOURCE_DIR}/run DESTINATION ${CMAKE_BINARY_DIR})

add_executable(mycode mycode.cc)
set_property(TARGET mycode
  PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/run
  )
add_custom_target(run
  COMMAND mycode
  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/run
  )
Here, we modify the RUNTIME_OUTPUT_DIRECTORY property of our target so that the executable is linked inside our designated run folder, so that it is conveniently available as an executable inside the run folder. Furthermore, we specify a WORKING_DIRECTORY for the run target, so that make run invokes the executable inside the intended run directory.

Install a project

If you want the make install to install your project to CMAKE_INSTALL_PREFIX (that may be set on command line or in the cache during the configuration stage), add appropriate INSTALL statements. To install e.g. a project consisting of a library and an executable as well as a run folder:

# [...] all the target definitions

INSTALL(TARGETS mylib DESTINATION lib)
INSTALL(TARGETS mycode DESTINATION bin)

INSTALL(DIRECTORY run DESTINATION share/mycode/run)

Autopilot style CMakeLists.txt

If you want a make interface similar to the deal.II library and its tutorial, namely maker targets for debug and release versions, running the code and cleaning, the easiest way to write a CMakeLists.txt file may be to use an "autopilot" style macro. Here is a minimalistic example for the step-1 tutorial program (plain text version) that can be used for simple projects:

find_package(deal.II 9.5.0 REQUIRED
  HINTS
    ${DEAL_II_DIR} ../ ../../ $ENV{DEAL_II_DIR}
    # You can specify additional hints for search paths here, e.g.
    # $ENV{HOME}/workspace/deal.II
)

# Set the name of the project and target:
set(TARGET "step-1")

# Declare all source files the target consists of:
set(TARGET_SRC
  step-1.cc
  # You can specify additional files here!
)

cmake_minimum_required(VERSION 3.13.4)
deal_ii_initialize_cached_variables()
project(${TARGET} CXX)
deal_ii_invoke_autopilot()
This CMakeLists.txt is intended for use with a small project and in-source build (i.e., one does not create a separate build directory as we recommend for the deal.II build in the readme file). Using this input file, you can run cmake in the source directory as follows:
$ cd step-1
$ cmake .

[...]
###
#
#  Project  step-1  set up with  deal.II-8.3  found at
#      /usr
#
#  CMAKE_BUILD_TYPE:          Debug
#
#  You can now run
#       $ make                - to compile and link the program
#       $ make run            - to (compile, link and) run the program
#
#       $ make debug          - to switch the build type to 'Debug'
#       $ make release        - to switch the build type to 'Release'
#
#       $ make edit_cache     - to change (cached) configuration variables
#                               and rerun the configure and generate phases of CMake
#
#       $ make strip_comments - to strip the source files in this
#                               directory off the documentation comments
#       $ make clean          - to remove the generated executable as well as
#                               all intermediate compilation files
#       $ make runclean       - to remove all output generated by the program
#       $ make distclean      - to clean the directory from _all_ generated
#                               files (includes clean, runclean and the removal
#                               of the generated build system)
#       $ make info           - to view this message again
#
#  Have a nice day!
#
###
There are two additional configuration options (in addition to TARGET and TARGET_SRC) that can be set via variables before DEAL_II_INVOKE_AUTOPILOT() is called (plain text version):
# (Optional)
# Specify a list of files (file globs) that will be removed
# with the "make runclean" and "make distclean" targets.
# (If empty, sensible default values will be used.)
set(CLEAN_UP_FILES
  # a custom list of globs, e.g. *.log *.vtk
)

# (Optional)
# A custom command line that should be invoked by "make run".
# (If empty, ./${TARGET} will be invoked.)
set(TARGET_RUN
  # a custom command line, e.g. mpirun -np 2 ${TARGET}
)

deal.IIConfig.cmake

Importing the deal.IIConfig.cmake file via FIND_PACKAGE will set the following variables and macros; all of the form DEAL_II_*:

#
# General package information:
#

DEAL_II_PACKAGE_NAME
DEAL_II_PACKAGE_VERSION     - the full package version string, e.g. "8.1.pre"
DEAL_II_PACKAGE_VENDOR
DEAL_II_PACKAGE_DESCRIPTION

DEAL_II_VERSION             - numerical version number (with "pre" and "rc?"
                              replaced by "0"), e.g. "8.2.0"
DEAL_II_VERSION_MAJOR       - the major number, e.g. "8"
DEAL_II_VERSION_MINOR       - the minor version number, e.g. "2"
DEAL_II_VERSION_SUBMINOR    - the minor version number, e.g. "0"

DEAL_II_GIT_BRANCH          - name of the local git branch of the source directory
DEAL_II_GIT_REVISION        - full sha1 revision of the current git HEAD
DEAL_II_GIT_SHORTREV        - short sha1 revision of the current git HEAD

DEAL_II_BUILD_TYPE          - the configured build type, e.g. "DebugRelease"
DEAL_II_BUILD_TYPES         - an all caps list of available configurations,
                              e.g. "DEBUG;RELEASE"

#
# Information about component locations:
#

DEAL_II_PATH
DEAL_II_SHARE_RELDIR
DEAL_II_DOCREADME_RELDIR
DEAL_II_DOCHTML_RELDIR
DEAL_II_EXAMPLES_RELDIR
DEAL_II_EXECUTABLE_RELDIR
DEAL_II_INCLUDE_RELDIR
DEAL_II_LIBRARY_RELDIR
DEAL_II_PROJECT_CONFIG_RELDIR

DEAL_II_BUILD_DIR             - true if deal.II was picked up from a build
                                dir, false if the configuration is from an installation

#
# Compiler and linker configuration
#

DEAL_II_CXX_COMPILER         - the compiler used to compile deal.II

DEAL_II_CXX_FLAGS            - compile flags for all configurations
DEAL_II_CXX_FLAGS_DEBUG      - _additional_ compile flags for the debug configuration
DEAL_II_CXX_FLAGS_RELEASE    - _additional_ compile flags for the release configuration

DEAL_II_LINKER_FLAGS         - link flags for all configurations
DEAL_II_LINKER_FLAGS_DEBUG   - _additional_ link flags for debug configuration
DEAL_II_LINKER_FLAGS_RELEASE - _additional_ link flags for release configuration

DEAL_II_DEFINITIONS          - compile definitions for all configurations
DEAL_II_DEFINITIONS_DEBUG    - _additional_ compile definitions for debug configuration
DEAL_II_DEFINITIONS_RELEASE  - _additional_ compile definitions for release configuration

#
# Information about MPI runtime for the mpi implementation used in the
# deal.II library
#

DEAL_II_MPIEXEC
DEAL_II_MPIEXEC_NUMPROC_FLAG
DEAL_II_MPIEXEC_PREFLAGS
DEAL_II_MPIEXEC_POSTFLAGS

#
# Information about include directories and libraries
#

DEAL_II_INCLUDE_DIRS

DEAL_II_LIBRARIES_DEBUG   - a list of the full link interface for the debug configuration
DEAL_II_LIBRARIES_RELEASE - a list of the full link interface for the release configuration
DEAL_II_LIBRARIES         - full list of libraries with "debug" and "optimized" keywords

#
# Information about library targets
#

DEAL_II_TARGET_CONFIG  - the target config file

DEAL_II_TARGET_DEBUG   - the name of the debug target that is available after inclusion
                         of the target config file
DEAL_II_TARGET_RELEASE - the name of the release target
DEAL_II_TARGET         - full list of targets with "debug" and "optimized" keywords

#
# Feature configuration: The following booleans are set to "ON" or "OFF" depending
# on the current feature configuration (note that other variables are defined
# only if one of the following variable is "ON", for example DEAL_II_MPI_VERSION
# is only defined if DEAL_II_WITH_MPI is "ON"):
#

DEAL_II_WITH_64BIT_INDICES
DEAL_II_WITH_ADOLC
DEAL_II_WITH_ARPACK
DEAL_II_WITH_ASSIMP
DEAL_II_WITH_COMPLEX_VALUES
DEAL_II_WITH_CUDA
DEAL_II_WITH_GINKGO
DEAL_II_WITH_GMSH
DEAL_II_WITH_GSL
DEAL_II_WITH_HDF5
DEAL_II_WITH_LAPACK
LAPACK_WITH_64BIT_BLAS_INDICES
DEAL_II_LAPACK_WITH_MKL
DEAL_II_WITH_METIS
DEAL_II_WITH_MPI
DEAL_II_WITH_MUPARSER
DEAL_II_WITH_OPENCASCADE
DEAL_II_WITH_P4EST
DEAL_II_WITH_PETSC
DEAL_II_WITH_SCALAPACK
DEAL_II_WITH_SLEPC
DEAL_II_WITH_SUNDIALS
DEAL_II_WITH_SYMENGINE
DEAL_II_WITH_TASKFLOW
DEAL_II_WITH_TBB
DEAL_II_WITH_TRILINOS
DEAL_II_WITH_UMFPACK
DEAL_II_WITH_ZLIB

Valid HTML 4.01! Valid CSS!