Cmake basics

We already know make, a powerful tool to automate all kinds of building sequences. But if you are using it to compile large C++ projects, you will quickly find that it’s tedious to maintain, and that you’re often repeating the same commands over and over again. This, of course, opens up the door to mistakes and bugs, which can even get hard to track. Is there no better way?
Enter CMake, a build system based on make which is specialized for C and C++ projects. The advantage? Whilst make has no idea what it’s actually building, CMake already knows you’re trying to compile a C/C++ project (actually, there are some more options there like FORTRAN too), so it can make a lot of decisions by itself.

A basic CMake example

Say we have a file main.cpp which needs to be compiled. This file requires our custom-made library mylib, which is composed of the files src/mylib1.cpp, src/mylib2.cpp and src/mylib3.cpp, with corresponding header files. In order to easily compile our project, we’ll create a file called CMakeLists.txt:

# Minimum version of CMake required:
cmake_minimum_required(VERSION 3.1)

# A name for our project:
project(demo_cmake)

# This allows us to enable C++11 for compilation on all targets.
# Possible values: 98, 11, 14
set(CMAKE_CXX_STANDARD 11)

# This allows us to add flags to all compiler calls
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -Wextra")

# Now tell CMake we want to compile a file "main" from "main.cpp"
add_executable(main main.cpp)

# Tell CMake to compile our library as "mylib"
add_library(mylib src/mylib1.cpp src/mylib2.cpp src/mylib3.cpp)

# Make sure the compiler can find include files for our library
#  when other libraries or executables link to mylib
target_include_directories(mylib PUBLIC ${CMAKE_SOURCE_DIR}/src/)

# Now tell CMake that our file "main" requires the library "mylib"
target_link_libraries(main mylib)

And that’s it! Now all we have to do is build our project. This is typically done by creating a separate directory build to keep all the build files in (CMake creates a lot of them). Then, we can open the terminal and cd to our build directory. Now we run cmake <path_to_project>. CMake will expect a file called CMakeLists.txt to be in the specified folder, and will use that to create a make file for our project. CMake will automatically find and select a compiler on our machine, too, which adds a nice layer of portability to the entire endeavour. Once we have run cmake, we have a valid Makefile in our build directory. So we can just make all to build, and finally ./main to test our program!

As you can see, CMake is much “smarter” than make, and can do a lot of work for us, like automatically finding the compiler, finding dependencies etc. And of course CMake is a lot more powerful than just this basic example!

Recursing into subfolders

Usually, if we use a library like for instance mylib, we don’t have to worry about which files need to be included in order for it to work. What we can do instead, is create a CMakeLists.txt file dedicated to the compilation of that library and save it in src/:

# Minimum version of CMake required:
cmake_minimum_required(VERSION 3.1)

# A name for our library project:
project(mylib)

file(GLOB mylib_SRC
    "*.h"
    "*.cpp"
)

# Tell CMake to compile our library as "mylib"
# add_library(mylib mylib1.cpp mylib2.cpp mylib3.cpp)
add_library(mylib ${mylib_SRC})

# Make sure the compiler can find include files for our library
#  when other libraries or executables link to mylib
target_include_directories(mylib PUBLIC ${CMAKE_SOURCE_DIR}/src/)

# Tell CMake we want C++11 or greater (if available) for this library
set_property(TARGET penna_bones PROPERTY CXX_STANDARD 11)
# Tell CMake that the specified C++11 standard is a requirement
set_property(TARGET penna_bones PROPERTY CXX_STANDARD_REQUIRED ON)

This CMakeLists.txt file will compile our library by itself, so now we can tweak our original CMakeLists.txt to include it instead of compiling mylib itself:

# Minimum version of CMake required:
cmake_minimum_required(VERSION 3.1)

# A name for our project:
project(demo_cmake)

# This allows us to enable C++11 for compilation on all targets.
# Possible values: 98, 11, 14
set(CMAKE_CXX_STANDARD 11)

# This allows us to add flags to all compiler calls
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -Wextra")

# Now tell CMake we want to compile a file "main" from "main.cpp"
add_executable(main main.cpp)

# Tell CMake to look for a CMakeLists.txt file in src/ and execute it:
add_subdirectory(src/)

# Now tell CMake that our file "main" requires the library "mylib"
target_link_libraries(main mylib)

And voilĂ ! We have a modular library and CMake build system.