June 6, 2020

​Welcome to ​a tutorial about the basic of CMake! I want to introduce to you some great functionalities and a very simple example of CMakeLists, code organization and public includes!

Introduction

​For this project, We will follow a "Little Kennel" project which contains three types of animals (Bunnies, Cats and Dogs)!

Those animals are provided by an external package "AnimalPackage". This package provides the animals API. "MyLittleKennel" is the ​executable, this one is linked to the "AnimalPackage" library. To ​​complete this, I add an external binary, from one of my previous review, the "Multithread-glog" test. Then we have:

  • One shared Library - AnimalPackage
  • ​Two executable -
    • MyLittleKennel which depends ​on AnimalPackage.
    • Multithread-glog which is independent.


​Shared library ​creation

The creation of a shared library is pretty simple with CMake. I created the "AnimalPackage" module with the following file architecture:

​Let's focus on the CMakeLists.txt in RED - The one which defines the build of the shared library.

cmake_minimum_required(VERSION 3.8)

set(SRCS
AnimalInterface.cpp
Bunny.cpp
Cat.cpp
Dog.cpp)

add_library(AnimalPackage SHARED ${SRCS} )

target_include_directories(AnimalPackage
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../include
)
  • set([VariableName] [Content]) -
    It is a CMake variable. I created the SRCS variable to reference all the cpp files to compile.
    - set documentation

  • add_library([TARGET] [​SHARED/STATIC/MODULE] [​SOURCES LIST]) -
    It declares a new TARGET defined as a Library. ​The [TYPE] "SHARED" gives to CMake the ​data that I want ​to create this "SHARED" library.
    - ​add_library documentation

  • target_include_directories([TARGET] [PUBLIC|PRIVATE|INTERFACE]
    [HEADER ​PATHS]) -
    ​In my example, I ​give the path "Animal/include" as a header directory. The PUBLIC mark guarantees that each project which will use this TARGET will inherit of those include directories. We will see this ​a next chapter !
    ​Concerning "CMAKE_CURRENT_SOURCE_DIR", this macro gives an absolute link to ​my current CMakeList.txt repository (the one in red !).
    ​- target_include_directories documentation

So, this CMakeLists is defined to create a new shared library "AnimalPackages" based on the "SRCS" cpp files. Let's print the GCC instruction with the following commands to build:

cmake ..         # Generate my CMake project
make VERBOSE=1   #Build with VERBOSE = 1 to print my GCC commands generated by CMake



​Now my CMake project to create my shared library is created. Now, let's link this to my binary MyLittleKennel.

​Executable/Binary creation

Now let's create the executable "MyLittleKennel". ​I organized the CMake lists as the followings:

​The ​"Animal" folder contains the "AnimalPackage" target created in the previous chapter.

Concerning the two ​​CMakeLists.txt:

  • The green one is what I like to name a "connector", we'll see this in the next chapter!
  • The red one is used to describe how the executable "MyLittleKennel" is created. Nothing complicated here:
cmake_minimum_required(VERSION 3.8)

set(SRCS
MyLittleKennel.cpp)

add_executable(MyLittleKennel ${SRCS} )

target_link_libraries(MyLittleKennel
PRIVATE
AnimalPackage
)
  • ​add_executable(​[TARGET] ​[​SOURCES LIST]) -
    ​It declares a new TARGET defined as an executable.
    - add_executable documentation

  • ​target_link_libraries([TARGET] [​SHARED/STATIC/MODULE] [​LIB LIST]) -
    It declares all the libraries link to my TARGET. This CMake command is powerful: Thanks to the "AnimalPackage" link, CMake​ can generate a build order. Then, for a "MyLittleKennel" generation, our build will start to check if the "AnimalPackage" library is available. If not, "AnimalPackage" is built before the "MyLittleKennel" linkage.
    - ​target_link_libraries documentation

​It is great! Now we have an executable defined and linked with another target (the library "AnimalPackage")...

However, something is missing:

How CMake can link the "AnimalPackage" and "MyLittleKennel" target? In this state, CMake is unable to know what is the "AnimalPackage" in the "MyLittleKennel" ​​CMakeLists.txt. It is the point for the ​next chapter of this tutorial:

​Link the directories. Generation of the build order

In the previous chapters, you ​saw some ​​CMakeLists.txt in green rectangles. Those ​​CMakeLists.txt are used to link the modules ​sub-directories to browse them successively. Now, I'll show the complete project with the last independent ​executable "MultithreadLog_Glog".

All those ​​CMakeLists.txt in green boxes are "connectors" between all the project. So, if I launch a build from the root, the "Main ​​CMakeLists.txt" is able to retrieve my complete project. Let's see the "Main ​​CMakeLists.txt", which is similar to the other "connectors":

cmake_minimum_required(VERSION 3.8)

project(MyLittleKennelAndGlog)

add_subdirectory(CMakeExample)
add_subdirectory(Multithread-logger)

​add_subdirectory will simply request to our ​​CMakeLists.txt to find other CMakeFile.txt in this folder. ​add_subdirectory documentation

Now, ​the project is linked, let's build it from the project root !

Thanks to this work, CMake was able to construct a simple (but complete!) build order. At first, it built the "MyLittleKennel" dependency ​plus the independent executable "MultithreadLog_Glog". Then, when the "AnimalPackage" is completed, "MyLittleKennel" build can be launched! For the last chapter, we'll review our MyLittleKennel build instruction.

​Link the ​targets

Do you remember the ​CMakeLists.txt from "MyLittleKennel"?

cmake_minimum_required(VERSION 3.8)

set(SRCS
MyLittleKennel.cpp)

add_executable(MyLittleKennel ${SRCS} )

target_link_libraries(MyLittleKennel
PRIVATE
AnimalPackage
)

Here we'll see why the "target_link_libraries" instruction is powerful and will help you to generate simple CMakeLists.txt​. "MyLittleKennel" module contains a simple cpp file with the following code:

#include <AnimalInterface.hpp>
#include <iostream>

int main()
{
    AnimalInterface Bunny(AnimalInterface::BUNNY);
    AnimalInterface Cat(AnimalInterface::CAT);
    AnimalInterface Dog(AnimalInterface::DOG);

    std::cout << "The Bunny says " << Bunny.letHearThisAnimal() << std::endl;
    std::cout << "The Cat says " << Dog.letHearThisAnimal() << std::endl;
    std::cout << "The Dog says " << Cat.letHearThisAnimal() << std::endl;
}

​Now, let's see the instruction generated by CMake with a "make VERBOSE=1":

cd /home/jafie/grapeProgrammer/GrapeProgrammer_ExampleCode/build/CMakeExample/MyLittleKennel && /usr/bin/c++   -I/home/jafie/grapeProgrammer/GrapeProgrammer_ExampleCode/CMakeExample/Animal/src/../include   -o CMakeFiles/MyLittleKennel.dir/MyLittleKennel.cpp.o -c /home/jafie/grapeProgrammer/GrapeProgrammer_ExampleCode/CMakeExample/MyLittleKennel/MyLittleKennel.cpp

​You see in bold here an extra instruction which was not present in our CMakeList.txt... ​An include instruction from AnimalPackage had been retrieved! It was a consequence of the "PUBLIC" instruction from "AnimalPackage", remember:

cmake_minimum_required(VERSION 3.8)

set(SRCS
AnimalInterface.cpp
Bunny.cpp
Cat.cpp
Dog.cpp)

add_library(AnimalPackage SHARED ${SRCS} )

target_include_directories(AnimalPackage
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../include
)

​This is one of the great advantage of CMake:

​Thanks to the PUBLIC mark and the "target_link_libraries", "MyLittleKennel" inherited ​the"AnimalPackage" includes - ​And only the PUBLIC part! ​​​​This kind of configuration can be done with libraries too!

​"MyLittleKennel" can only retrieve the "AnimalInterface" header, the others like "Bunny.hpp" and "Cat.hpp" are not accessible from "MyLittleKennel". It avoids that my executable include another header from "AnimalPackage".  ​In concequence, all the internal header from "AnimalInterface" are ​PRIVATE. Let's run our binary generated by CMake!

./MyLittleKennel 
The Bunny says Honk-honk
The Cat says Woof woof
The Dog says Meeeew !

​Conclusion

​With this kind of simple architecture, you'll be able to construct a complete project with CMake easily. More of that, CMake will generate for you a  powerful build order!

​I hope you liked this first tutorial! I'll complete this one with my next posts concerning CMake (and specially the usage of find_package)!

About the author 

Axel Fortun

​Developer specialized in Linux environment and embedded systems.
​Knowledge in multiple languages as C/C++, Java, Python​ and AngularJs.
​Working as Software developer since 2014 with a beginning in the car industry​ and then in ​medical systems.