Banner: ZumGuy Publications and Network

ZumGuy Publications and Network

CMake basics

Posted by Sean on Monday, 30th October 2017 17:00
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)

# Tell CMake to compile our library as "mylib"
add_library(mylib mylib1.cpp mylib2.cpp 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/)

# 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.
Posted by Andrew on Monday, 30th October 2017 18:44

Thank you! Very useful.

You must be logged in to post messages.

Quote of the day...


ZumGuy Internet Promotions