Build using cmake

Build process

If you want to build only the C++ part and want to have more control over the build process, you can manually run the tasks that have been automatically executed by pip in the automatic build. For this, you have to first install the python build dependencies for your python environment manually.

If you want to use mkl you should also run pip install mkl mkl-devel.

You can then build the software with standard CMake commands:

mkdir build
cd build
cmake ..
cmake --build .

Make sure that on a Windows platform, you have set the correct environmental variables. If you are using a multi-configuration generator for CMake, you must also specify a configuration at the build command.

Running the different build commands manually has the advantage that you can pass additional options to the build system. For example, you can enable the code coverage by running CMake with cmake -DWITH_COVERAGE=ON .. (the general format to set an option is -D<OPTION_NAME>=<VALUE>). A full list of build options is provided in the following:

Option

Effect

Default

WITH_COVERAGE

Generate code coverage report [1]

OFF

Moreover, executing the commands manually allows for running additional targets. For example, you can use the doxygen target to build the C++ doxygen documentation by executing cmake --build . --target doxygen. In contrast, if you use pip to build the software, only the default target for building the library is executed. In the following, a list of all available targets is provided. Note that some targets require specific build options to be enabled in addition to the default options, and have varying names depending on the platform.

Target (Windows)

Target (OS X and Unix)

Task

Requirement

ALL_BUILD

all

Build the software (default target)

RUN_TESTS

test

Run the C++ tests (without any python tests, use the automatic build above for this)

DOXYGEN

doxygen

Build the Doxygen documentation in src/cpp/docs

Note

Since Visual Studio 17 2022 is a multi-configuration generator, you have to specify a configuration, otherwise the build will not work. You can choose between Release, RelWithDebInfo, Debug and MinSizeRel. They are optimizing different purposes:

Configuration

Purpose

Release

Performance optimized, no debug information

Debug

Includes debugging information, no optimization

RelWithDebInfo

Combines optimization with Debug information

MinSizeRel

Optimizes binary size of output

When testing, you should use the same configuration as in the build, for example

cmake --build . --config RelWithDebInfo
ctest -C RelWithDebInfo

In addition, a number of options are typically available for the native build tool that is called by CMake. For example, you can pass the -j num_jobs option to the native build tool to enable parallel compilation, where num_jobs specifies the maximal number of jobs that will be run. Setting num_jobs to the number of available processors can speed up the compilation process significantly.

cmake --build . -j 8

Tips and Tricks

1. Compiler Optimizations

To speed up the software, you can pass optimization flags to the compiler by setting the CXXFLAGS environment variable before running CMake. For example, the following bash command sets the environment variable under GNU/Linux, enabling several optimizations at once for the gcc compiler:

export CXXFLAGS="-march=x86-64-v3"

If you are using Windows with Visual Studio, reasonable optimization flags can be set by running the following command in the PowerShell:

$env:CXXFLAGS="/Ox /arch:AVX2"

2. Using a Faster Build System

Under GNU/Linux, you can use the ninja build system and the mold linker to reduce the build time by a factor of about 1.5. These tools are typically available in the package repositories of your distribution. For example, on Ubuntu, you can install them by running:

sudo apt install ninja-build mold

Then, you can tell CMake to build the software with these tools by running the following commands within the build directory. Note that ninja uses all available processors by default.

cmake -G"Ninja Multi-Config" -DCMAKE_CXX_FLAGS="-fuse-ld=mold" ..
cmake --build .

3. Using Compiler Caching

If you delete the build directory because you want to compile a different branch of pairinteraction or use different build options, the compilation has to start from scratch - as long as you do not use a compiler cache like ccache. Using this tool has the additional advantage that adding comments to the source code does not trigger a recompilation. It can be installed on many operating systems, e.g., on Ubuntu by running:

sudo apt install ccache

To use the tool with CMake, pass -DCMAKE_CXX_COMPILER_LAUNCHER=ccache to the cmake command.

4. Building and Testing Only Parts of the Software

If you’re developing and making changes to specific parts of the software, you can save time by using specific targets to build and test only those parts. You can read off the names of relevant targets from the CMakeLists.txt files located in the directories where you perform the changes. For example, you can build and test only the C++ backend by running the following commands within the build directory:

cmake --build . --config Release --target unit_tests
ctest -V -C Release -R unit_tests

However, before pushing your changes, you should always run the full test suite to ensure that your changes do not break other parts of the software. The --config Release and -C Release options tell the tools to build and test the release version of the software if a multi-configuration generator is used. For further explanations on the build type, see the next section.

5. Improve the Code Quality with Clang-Tidy and Include-What-You-Use

Our continues integration system uses the C++ linter tool clang-tidy to check the code quality of pull requests and find programming errors. If you have the clang compiler installed, you can run it by yourself during compilation by building the software with the following commands:

cmake -DCMAKE_CXX_COMPILER="clang++" -DCMAKE_CXX_CLANG_TIDY="clang-tidy" ..
cmake --build .

In addition, it is recommended to use the include-what-you-use tool to find unnecessary includes in your code. While the tool is not perfect, its suggestions can help to reduce the compilation time. If the tool is installed on your system, you can run it during compilation by executing the following commands:

cmake -DCMAKE_CXX_COMPILER="clang++" -DCMAKE_CXX_INCLUDE_WHAT_YOU_USE="iwyu" ..
cmake --build .

6. Changing the log level

We use the spdlog library for logging. The log level can be set by the environment variable SPDLOG_LEVEL. Possible values are info (the default), debug, warn, and error.

7. Debugging with GDB

For tracking down errors like segmentation faults, running a debug build with the GNU Debugger GDB can be very helpful.

If CMake uses a multi-configuration generator (e.g., Ninja Multi-Config, Visual Studio Generators), you can build the software with debug symbols by using the --config Debug option. Afterwards, you can execute the build with GDB. For example:

cmake -G"Ninja Multi-Config" -DCMAKE_CXX_FLAGS="-fuse-ld=mold" ..
cmake --build . --config Debug --target unit_tests
gdb -ex r --args src/cpp/tests/Debug/unit_tests

If you are using a single-configuration generator (e.g., Unix Makefiles), you must specify the build type directly:

cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake --build . --target unit_tests
gdb -ex r --args src/cpp/tests/unit_tests

If you have executed a build without GDB, a crash occurred, and a core dump was created, you can load the core dump into GDB:

gdb path/to/my/executable path/to/core

After starting the debugger, you can use GDB’s commands to analyze the crash. Some of the most important commands are listed in the tables below.

Basics

help COMMAND

Display help for the given COMMAND

q

Quit the debugger

Investigating a backtrace

bt

Display a backtrace of the call stack

frame NUMBER

Select the frame with the given NUMBER on the call stack

up / down

Select one frame up or down from the currently selected frame

list

Display code around the selected frame

p EXPR

Display the value of EXPR

Debugging with multiple threads

info threads

Display all threads running in the program, the first field is the thread number

thread NUMBER

Select the thread with the given NUMBER

Breakpoints and stepping

b FUNCTIONNAME

Set breakpoint at FUNCTIONNAME

delete FUNCTIONNAME

Delete breakpoint at FUNCTIONNAME

c

Continue executing the program until the next breakpoint

n

Execute next source-code line, stepping over function calls

s

Execute next source-code line, stepping into function calls