Cmake Simple Example

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.

By default, cmake will generate a build file for make on Linux systems. If you want to generate build file for other tools such as ninja, you can use -G. For example, to generate build file for ninja, use the following command: mkdir buildninja cmake -Bbuildninja -G 'Ninja'.

A Simple C/CMake Example ¶ Building a C SimpleITK application is supported by using CMake to configure the build process. CMake can generate project buildsystems for Unix Makefiles, Ninja, Microsoft Visual Studio, or Mac OS Xcode. Here is a basic CMakeLists.txt configuration file for building a SimpleITK C example program. Presumably I am doing something simple and obviously wrong in each and every case, but what would work in this simple example with this extremely simple directory structure? And if CMake is just not expecting such a simple directory structure, what directory structure would make it happy? Retrieved from ''.

The source code of the sample project used below is available on GitHub.

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 File New Project and choose C++ Executable. In our example, the project name is cmake_testapp and the selected language standard in C++14.

By default, we get the project with a single source file main.cpp and the automatically created root CMakeLists.txt containing the following commands:

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.

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 Run Edit Configurations 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 Settings / Preferences Build, Execution, Deployment CMake.

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 Settings / Preferences Build, Execution, Deployment CMake and set its build type to Release:

Notice 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.

Headers and sources that you add to the project will be resolved correctly only if you include them explicitly in CMakeLists.txt or if you include them in other files already belonging to the project (see Managing CMake project files ).

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.

For details on working with Boost.Test, see Unit testing tutorial.

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 New CMakeLists.txt ):


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 Code Insert Live Template, and choose boost_with_libs:

Adjust the inserted code to the following:

set(Boost_USE_STATIC_LIBS OFF) #enable dynamic linking # search for unit_test_framework find_package(Boost REQUIRED COMPONENTS unit_test_framework) include_directories(${Boost_INCLUDE_DIR}) # create a cmake_testapp_boost target from test.cpp add_executable(cmake_testapp_boost tests.cpp) # link Boost libraries to the new target target_link_libraries(cmake_testapp_boost ${Boost_LIBRARIES}) # link Boost with code library target_link_libraries(cmake_testapp_boost test_library)

Also, we need to place the add_subdirectory(test) command in the rootCMakeLists.txt to make our test target cmake_testapp_boost available for the main build.

This command, when placed in the root CMake script, declares a subproject test that has its own CMakeLists.txt.

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

  1. Create a subdirectory inside test and call it ctest.

  2. 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:

  3. 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 and ctest_example_addvalues_negpos, with CTest.

  4. 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(); }
  5. Next, we need to enable CTest and declare the subproject in the top-level CMakeLists.txt:

    The enable_testing command creates a built-in target test which will execute CTest.

  6. 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 Edit Configuration, we will find two automatically generated configurations of the CTest Application type:

Let'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:

For more information on the CTest Application template, see CTest run/debug configuration.

8. Learn more

To dig deeper into CMake in CLion, learn how to:

  • Run Build actions and CMake install

  • Use environment variables and the CLION_IDE macro.

Last modified: 08 March 2021

I wanted to containerize a C++ application depending on some external libraries. Some of the libraries were readily available via package repositories and others that needed to be built from sources. I’m relatively new to Docker and started searching online for recipes for such cases. Much of the Docker documentation and examples across the web are concentrating on creating images for interpreted languages.

While containerizing a C++ application isn’t hard, I ended up learning a few things. To save you from the details I’ll use an example program, but the strategies presented across the versions are what I did and learned.

The program I’ll use as an example in this post is a simple echo server using ZeroMQ as the transport library. To make things a bit more interesting, I’ll use the cppzmq library that provides the basic C++ bindings on top of ZeroMQ. And while the ZeroMQ kernel is widely available to install via the package manager, cppzmq isn’t, so it will be built and installed from sources.

Crash course to CMake

The source code for the server and the client are available in GitHub. The build is configured using CMake. Following the example of the official MySQL image, both the server and the client are packaged inside the same image.

CMake is a meta build system that uses a high level-language to describe the build configuration. The high level description can then be compiled to the actual Makefiles, IDE projects etc. consumed by your usual toolchains. In this very simple case the CMakeLists.txt file consumed by CMake is very simple:

The first two lines declare the required CMake version and the project name. The loop defines two targets (client and server), along with their dependencies. Note that “linking” a target in the CMake parlance means both that the compiler is pointed to the necessary headers, and that the linker is pointed to the necessary shared libraries.

Also note that although cppzmq is just a header-only wrapper on top of the ZeroMQ kernel, the cppzmq target carries a dependency to the ZeroMQ kernel. The CMakeLists.txt doesn’t need to specify that kind of transitive dependencies explicitly.

First version: One big image containing the build and runtime environments

The simplest idea is just basing an image on a “batteries included” base image containing the compiler, installing the extra libraries, and finally building the example application. Here’s the example Dockerfile:

The gcc:9 image is based on Debian, and since cppzmq isn’t included in the Debian package repository, it’s downloaded and installed directly from Github. cppzmq itself is a header-only library, so just dropping the header file to the include path would suffice if I would author find module for cppzmq myself. But luckily cppzmq also provides an upstream CMake config file that is installed alongside the headers when doing a full installation.

A very careful approach to installing cppzmq would also include verifying the checksum of the archive in case Github or the certificate trust chain was compromised. But if I was to undergo that kind of scrutiny, I might as well package cppzmq and serve it (with signatures) from a private apt repository.

The COPY instruction will copy the CMakeLists.txt and the source code of the application from the build context. What’s left is compiling and installing the application, and defining the default entry point to the container.

Let’s see what happens when it’s compiled:

Understandably having the build-time environment in the image will bring its size up. Still, the amount of stuff that comes with the C++ compilers and development libraries is astounding.

Second version: Breaking the build to stages

Docker supports multi-stage builds designed to solve the problem described in the previous chapter. The idea is to build two images: The first one containing the whole build-time environment with compilers and development libraries, and the second one just containing the necessary runtime libraries and compiled artifacts.

Cmake Simple Example Free

Let’s take a look at the new Dockerfile:

There are two FROM directives in the Dockerfile, corresponding to two separate images named builder and runtime. The builder image doesn’t have entrypoint because its purpose is to produce the executables and install them to /usr/local/bin. The second image is built by getting just the necessary libraries and copying the compiled binaries from the builder. Notice the syntax of the COPY directive with the --from option.

The naming of the ZeroMQ library is confusing, but libzmq3-dev indeed is the develoment version of libzmq5.

The final image size is just a bit over one-tenth of the first example:

Much better!

Interlude: Pitfalls of distributing C++ code in binary format

You may have noticed that I changed the base image from gcc:9 to debian:buster when going from the first to the second example. I initially tried to use gcc:9 as the base image for the builder but ran into issues. Let me explain with an example:

What happened is that the client program wouldn’t run:

Turns out debian:buster uses GCC 8 instead of GCC 9, and the standard library ABI is not backward compatible between versions. Compiling and linking an application with GCC 9 and then running against libstdc++ compiled for GCC 8 doesn’t work. Thankfully libstdc++ checks for this instead of letting the ABI incompatibility silently corrupt the program.

The complexity of the C++ ABI is why portable C++ libraries are typically either header only or have a C API. ZeroMQ itself is a good example of this: The kernel is written in C++, but its public API has C linkage, which is again converted to a C++ API via cppzmq.

I could have worked around the problem by using gcc:8 as the “batteries included” base image for the builder container. But I realized its probably the best just to use a common base image for both stages, and install the missing pieces of the build-time environment by hand. After all, containerizing an application is all about controlling the dependencies for maximum compatibility. More on that in the next section.

Third version: Sharing the runtime libraries between the stages

Cmake Build Example

In the second example, both the builder and the runtime images are based directly on debian:buster and install the environment separately. In the spirit of the DRY principle, I wanted to refactor the Dockerfile to eliminate this redundancy. I ended up having an intermediate layer containing the runtime libraries that both the builder and runtime used:

Cmake Simple Examples

Let’s build and see what happens:

Cmakelists Example

What the third version produced was, in fact, the same image (compare the hashes). This comes as no surprise since the same directives are used to build the runtime image. However, I see the following benefits of structuring the Dockerfile like this:

  • Instead of invoking apt-get update twice like in the second version, the third version requires it only once. This speeds up the build.
  • The builder and the runtime container manifestly share the same runtime libraries. This means that the builder links the executables against the same shared library as the runtime container. While I would normally expect to get the same library version when downloading it from the same package repository within a short timespan, the approach makes it very explicit.

Cmake Simple Example Pdf

Please enable JavaScript to view the comments powered by Disqus.