Quick CMake tutorial
This tutorial will guide you through the process of creating and developing a simple CMake project. Step by step, we will learn the basics of CMake as a build system, along with the CLion settings and actions for CMake projects.
1. Basic CMake project
CMake is a meta build system that uses scripts called CMakeLists to generate build files for a specific environment (for example, makefiles on Unix machines). When you create a new CMake project in CLion, a CMakeLists.txt file is automatically generated under the project root.
Let’s start with creating a new CMake project. For this, go to C++ Executable. In our example, the project name is cmake_testapp and the selected language standard in C++14.
and chooseBy default, we get the project with a single source file main.cpp and the automatically created root CMakeLists.txt containing the following commands:
Command | Description |
---|---|
cmake_minimum_required(VERSION 3.13) | Specifies the minimum required version of CMake. It is set to the version of CMake bundled in CLion (always one of the newest versions available). |
project(cmake_testapp) | Defines the project name according to what we provided during project creation. |
set(CMAKE_CXX_STANDARD 14) | Sets the CMAKE_CXX_STANDARD variable to the value of 14, as we selected when creating the project. |
add_executable(cmake_testapp main.cpp) | Adds the cmake_testapp executable target which will be built from main.cpp. |
2. Build targets and Run/Debug configurations
Target is an executable or a library to be built using a CMake script. You can define multiple build targets in a single script.
For now, our test project has only one build target, cmake_testapp. Upon the first project loading, CLion automatically adds a Run/Debug configuration associated with this target:
Click Edit Configurations in the switcher or select from the main menu to view the details. The target name and the executable name were taken directly from the CMakeLists.txt:
Notice the Before launch area of this dialog: Build is set as a before launch step by default. So we can use this configuration not only to debug or run our target but also to perform the build. To learn more about various build actions available in CLion, see Build actions.
3. Adding targets and reloading the project
Now let’s add another source file calc.cpp and create a new executable target from it.
Right-click the root folder in the Project tree and select New | C/C++ Source File. CLion prompts to add the file to an existing target:
Since our goal is to create a new target, we clear the Add to targets checkbox. Accordingly, CLion notifies us that the new file currently does not belong to any target:
Now let's declare a new target manually in the CMakeLists.txt. Note that CLion treats CMake scripts as regular code files, so we can use code assistance features like syntax highlighting, auto-completion, and navigation: When we make changes in CMakeLists.txt, CLion needs to reload it in order to update the project structure:
We can either reload the project once (Reload changes) or enable automatic reload to let CLion silently apply all the changes in CMakeLists.txt. The option for enabling/disabling auto-reload is also available in .
After reloading the project, CLion adds a Run/Debug configuration for the new target:
Library targets
Up to this point, the targets we added were executables, and we used add_executable
to declare them. For library targets, we need another command - add_library. As an example, let's create a static library from the calc.cpp source file:
As well as for executables, CLion adds a Run/Debug configuration for the library target after reloading the project:
However, this is a non-executable configuration, so if we attempt to run or debug it, we will get the Executable not specified error message.
To obtain the library file, we need to build the test_library target. For this, we can switch to the corresponding configuration and press , or call Build | Build "test_library". The libtest_library.a file will appear in the cmake-build-debug folder.
4. Build types and CMake profiles
All the Run/Debug configurations created by far were Debug configurations, which is the default build type of the CMake profile that was automatically configured for our project. CMake profile is a set of options for the project build. It specifies the toolchain, build type, CMake flags, path for storing build artifacts, make build options, and environment variables.
For example, to separate the Debug and Release builds, we can add a new CMake profile in Release:
and set its build type toNotice the Build directory field that specifies the location of build results. The default folders are cmake-build-debug for Debug profiles and cmake-build-release for Release profiles. You can always set other locations of your choice.
Now the Run/Debug configuration switcher shows two available profiles:
Switching configurations or CMake profiles may affect preprocessor definitions used while resolving the code. For example, when there are separate flags for Debug and Release builds, or when there are some variables that take different values depending on the build type. This is called resolve context.
Resolve context defines how CLion performs syntax highlighting and other code insights like Find Usages, refactorings, and code completion. When you switch between configurations, the resolve context for the current file is changed automatically. Also, you can select it manually in the context switcher (<Select automatically> restores the automatic selection):
5. Adding include directories
In order to use additional headers located in separate directories, we need to add them either to all the targets or to some specific ones.
For example, let’s create three directories under the project root, includes, includes/general, includes/math, and write the following commands in CMakeLists.txt:
These two commands make the headers located in general and math available for including from the sources of all targets. For example, we can write #include "header_math.h"
in calc.cpp.
6. Linking libraries
Static libraries
On step 3, we created a static library called test_library (the default filename is libtest_library.a).
Let's create a lib directory under the project root and copy libtest_library.a from its default location (cmake-build-debug) to this folder.
We will use two commands to link our static library to the cmake_testapp target: find_library provides the full path, which we then pass directly into the target_link_libraries command via the ${TEST_LIBRARY}
variable:
Note: make sure to place target_link_libraries
after the add_executable
command, so that CMake actually builds the target before linking the library.
Dynamic libraries (Boost example)
To illustrate linking dynamic libraries, we will take an example of using Boost.Test framework.
Let's write a simple function int add_values (int a, int b) { return a+b;}
in calc.cpp and create the associated header calc.h with the function declaration. We will test this function with the help of Boost.Test framework.
As our project gets more complicated, the root CMakeLists.txt file can become difficult to maintain. To avoid this, and to build a transparent project structure, we will extract the tests into a subproject.
Let's add a directory called test and create a source file tests.cpp within it. Also, we need to provide this directory with its own CMakeLists.txt file (right-click test in the Project tree and select ):
The subdirectory test/CMakeLists.txt script is initially empty. We can start filling it up by inserting a live template for Boost with libs. Press Ctrl+J or click , and choose boost_with_libs
:
Adjust the inserted code to the following:
After reloading the changes in both CMakeLists.txt files, CLion creates a Run/Debug configuration for the cmake_testapp_boost target. This is a regular CMake Application configuration which we could run/debug right away. However, to be able to use the built-in test runner, let's create another configuration out of the Boost.Test template:
Now we can run this configuration and get test results. Test runner shows the tree of tests in the suite, their output, status, and duration:
7. Working with CTest
This chapter gives a simple example of how to use CTest, a framework for compiling and running tests as part of the CMake build process. Find general description of the framework in CTest support.
Add CTest to the sample project
Create a subdirectory inside test and call it ctest.
Add two source files, addvalues_zero.cpp and addvalues_negpos.cpp, a header file (where we will place the assertion macro) assert_macro.h, and a CMakeLists.txt script:
Add the following lines to ctest/CMakeLists.txt:
cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR) add_executable(ctest_exe_addvalues_zero addvalues_zero.cpp) add_executable(ctest_example_addvalues_negpos addvalues_negpos.cpp) add_test(ctest_addvalues_zero ctest_exe_addvalues_zero) add_test(ctest_addvalues_negpos ctest_example_addvalues_negpos)The first line states the minimum supported version of CTest, which corresponds to the version of CMake, 3.14.
We are using the add_test command here to register our executables,
ctest_exe_addvalues_zero
andctest_example_addvalues_negpos
, with CTest.- Now we can place the actual code inside the test sources:
assert_macro.h:
#include <iostream> #include <sstream> #define assertEqual( ... ) \ do { \ if( !( __VA_ARGS__ ) ) { \ std::cerr << "Unit test assert [ " \ << ( #__VA_ARGS__ ) \ << " ] failed in line [ " \ << __LINE__ \ << " ] file [ " \ << __FILE__ << " ]" \ << std::endl; \ err_code = 1; \ } \ } while( false )addvalues_zero.cpp:
#include "assert_macro.h" int test_addvalues_zero() { int err_code = 0; assertEqual (0+0 == 0); assertEqual ((-5)+5 == 0); return err_code; } int main() { return test_addvalues_zero(); }addvalues_negpos.cpp:
#include "assert_macro.h" int test_addvalues_negpos() { int err_code = 0; assertEqual ((-5)+10 == 5); assertEqual ((-10)+5 == -5); assertEqual (5+(10) == 5); //test to fail return err_code; } int main() { return test_addvalues_negpos(); }
Next, we need to enable CTest and declare the subproject in the top-level CMakeLists.txt:
enable_testing() add_subdirectory(test/ctest)The enable_testing command creates a built-in target
test
which will execute CTest.Reload the project.
After the reload, CLion detects the tests and creates a ready-to-go All CTest configuration:
If we run this configuration, the results will be shown in the Test Runner window, similarly to other supported testing frameworks:
We can also use the gutter menu next to the add_test
commands in ctest/CMakeLists.txt to run or debug our tests:
In case of running/debugging a test via the gutter menu, CLion creates a temporary configuration, so if we go to CTest Application type:
, we will find two automatically generated configurations of theLet's check the All CTest configuration. Click the pen icon next to the Test list to run field. This opens the List of Available Tests dialog, where we can change the set of tests:
8. Learn more
To dig deeper into CMake in CLion, learn how to: