Use CMake like a pro
Mihalis Tsoukalos teaches you about the CMake project, its configuration files and how to visualise project dependencies using Graphviz.
Mihalis Tsoukalos teaches you about CMake, its configuration files and how to visualise project dependencies using Graphviz.
Acornerstone of the build process is CMake, an open source project largely supported by Kitware. If you want to build and compile code like a pro then you’ve get to get in the know! Here, we’re going to show you how to use CMake for compiling, linking and installing software on Linux and UNIX machines. We’ll then use the Graphviz output from CMake to visualise the dependencies of a CMake project. Remember that if you have a properly setup CMake configuration file, cmake will do most of the boring work for you. However, it should be noted that you should use the GNU
make utility to process a CMake project. You’ll most likely install CMake using your usual package manager, which will make the whole process much simpler for you. You might also want to install some extra packages to make your life easier in the long run – on an Ubuntu Linux, you can do that by executing the following command: $ sudo apt-get install cmake cmake-data cmake-doc cmakeextras Then, you can find out which version of CMake you have by running this command: $ cmake --version cmake version 3.10.1 CMake suite maintained and supported by Kitware (kitware. com/cmake).
Should you wish to look at the numerous options supported by cmake, you can execute the cmake --help command. After this brief introduction, we’re now ready to start working and building projects with CMake.
MakeBasic
The simplest project you can have is one with a single source file. So, imagine that you have a C file called hw.c, which contains the C code of the famous HelloWorld program, and that you want to manage it using CMake.
The name of the CMake file that holds the configuration of that naive project will be CMakeLists.txt, which is the default configuration file for all CMake projects. The contents of that simplistic and naive CMakeLists.txt file are the following: cmake_minimum_required(VERSION 3.0) PROJECT( LXF ) add_executable(LXF hw.c)
The first line defines the minimum CMake version required for this project, the second line specifies the name of the project and the third line specifies the files involved ( hw.c) in the creation of the executable file as well as the filename of the executable file ( LXF).
Please note that it’s considered good practice to have a directory named build inside the root directory of your project in order to have a cleaner root directory. Additionally, because cmake doesn’t provide a command for cleaning up,
you’ll just need to delete the build directory when you want to clean up your project.
After having a syntactically correct CMake file, you’ll need to execute the cmake command with a single parameter, which will be the path to the root directory of your project. This will automatically generate some additional files including a Makefile that will be used by the make utility. So, the required steps are as follows: $ cd hw $ ls -l total 12 drwxr-xr-x 2 mtsouk mtsouk 4096 Dec 30 21:45 build -rw-r--r-- 1 mtsouk mtsouk 78 Dec 30 21:45 CMakeLists.txt -rw-r--r-- 1 mtsouk mtsouk 94 Dec 30 19:47 hw.c $ mkdir build $ cd build $ cmake ..
The last command will create lots of output that can be seen in the screenshot ( left). Executing the make command afterwards will create a executable file called LXF in the root directory of your CMake project. If you don’t want to manually create the build directory you can execute cmake -H. -Bbuild from the root directory of the project.
Finally, if cmake can’t find a CMakeLists.txt file in the specified directory, you’ll see the following error message: $ cmake /tmp CMake Error: The source directory “/tmp” appear to contain CMakeLists.txt. Specify --help for usage, or press the help button on the CMake GUI.
As the CMake file shown here is relatively straightforward, we won’t deal with it any further. However, if you’re familiar with Makefiles then it would be very interesting to have a quick look at the generated Makefile.
The next section will present the CMake configuration of a more realistic project with multiple source files written in C++.
Starting C++
This C++ project will have three source files that are needed for creating the final product, which will be an executable file. To avoid overcomplicating matters, the C++ code of this project will be fairly simplistic. A CMake project can also create a shared library or a static library – the general idea behind processing such projects with cmake is the same as in creating an executable file.
The steps and the commands for creating the structure of a new CMake project called ‘simple’ are the following: $ mkdir simple $ cd simple $ touch CMakeLists.txt $ mkdir build $ mkdir myLibrary $ mkdir myLibrary/include $ mkdir myApplication $ touch myApplication/main.cpp $ touch myLibrary/include/aClass.h $ touch myLibrary/aClass.cpp
The first thing that you see in this project is that although there are multiple directories, there’s only one
txt file. Additionally, main.cpp apart from the central C++ CMakeLists. file that’s named and contains the main() function of the project, there are two additional files: one C++ source file ( aClass.cpp) and one header file ( aClass.h). In these two files you’ll find the implementation of a C++ class that’s used in main.cpp.
The contents of the main CMakeLists.txt, which can be found in the root directory of the project, can be seen in the screenshot ( below). Note that you could have used multiple
CMakeLists.txt files instead, but for such a relatively simple project that approach wouldn’t be necessary. Many new and interesting things can be seen in this
CMakeLists.txt file. First, notice that the CMAKE_BINARY_ DIR variable in combination with the EXECUTABLE_
OUTPUT_PATH variable specify the directory where the executable file of the project is going to be placed.
Then, SOURCES specifies the two C++ source files of the project. Additionally, include_directories() tells CMake where to find aClass.h. After that, add_executable() specifies that the final product will be named simple and that it’ll be based on the value of the SOURCES variable.
The last line of CMakeLists.txt tells CMake that the generated binary file will be installed on the bin directory of the installation directory.
It’s now time to try to use CMakeLists.txt. As you already know, you’ll first need to execute the cmake command before continuing with the make command: $ ls -l $ cmake -H. -Bbuild $ cd build $ make If there’s an error in the CMake configuration file, you’ll see error messages that will look similar to the following: CMake Error at CMakeLists.txt:7 (INSTALL): INSTALL called with unknown mode DESTINATION The cause of the first error message is an unknown variable in CMakeLists.txt, which was corrected in the CMakeLists.txt version that you have: CMake Warning (dev) in CMakeLists.txt:
No cmake_minimum_required command is present. A line of code such as cmake_minimum_required(VERSION 3.10)
should be added at the top of the file. The version specified may be lower
if you wish to support older CMake versions for this project. For more
information run “cmake --help-policy CMP0000”. This warning is for project developers. Use -Wno-dev to suppress it.
The second message is a warning message telling you that you should define the minimum cmake version required for this project. As you can see, both messages are very clear.
The screenshot ( below) shows the output you’ll get when processing the CMakeLists.txt file with cmake.
CMake variables
The executable file of CMake supports a range of variables that can help you change various things on a CMake project without the need to alter CMakeLists.txt. The most useful variable is CMAKE_INSTALL_PREFIX, which helps you define the installation path of your project. The default value of CMAKE_ INSTALL_PREFIX is /usr/local on all UNIX machines. When testing a project, a good candidate for the installation directory would be the /tmp directory. The CMAKE_BUILD_TYPE option variable specifies the build type that will be built. The valid values for the CMAKE_BUILD_TYPE variable are Debug, Release, RelWithDebInfo and MinSizeRel. The Debug value turns on the debug flags on the generated files. However, when creating the final version of a project, you’ll need to use the Release value for CMAKE_BUILD_TYPE.
Finally, there’s CMAKE_<LANG>_FLAGS. You should replace <LANG> with the string that’s related to the programming language of your choice – this is for defining the compiler flags which will be used. For C, the value of <LANG> should be C whereas for C++ the value of <LANG> should be CXX. Therefore, for a C++ project, this option variable should be CMAKE_CXX_FLAGS. For the full list of the available option variables you can visit https://cmake.org/cmake/help/v3.0/manual/cmakevariables.7.html.
Compile and install
In this section you’ll learn how to compile and install the simple CMake project, using the make command.
So, in order to compile the project, after successfully running cmake, you’ll need to execute the make command from the build directory of the C++ project. Should you wish to compile your project with the debug flags turned on, you should use the CMAKE_BUILD_TYPE variable from the command line, as follows: $ rm -rf build $ make build $ cd build $ cmake .. -DCMAKE_BUILD_TYPE=Debug $ make
The use of the objdump(1) command will verify that the generated executable file contains debugging information: $ objdump --syms bin/simple | grep debug 0000000000000000 l d .debug_aranges 0000000000000000 .debug_aranges 0000000000000000 l d .debug_info 0000000000000000 .debug_info 0000000000000000 l d .debug_abbrev 0000000000000000 .debug_abbrev 0000000000000000 l d .debug_line 0000000000000000 .debug_line
0000000000000000 l d .debug_str 0000000000000000 .debug_str
To install the binary file of the simple project to the bin directory of the default location, which is /usr/local, you’ll need to execute the make install command. However, because putting files in the /usr/local directory requires root privileges, you’ll most likely receive an error message when executing make install that you can overcome by executing
sudo make install instead. Should you wish to install the project in a different directory where you have enough permissions, which in this case will be /tmp, you should use CMAKE_INSTALL_
PREFIX and execute cmake as follows: $ cd build $ cmake .. -DCMAKE_INSTALL_PREFIX=/tmp Then, you can use make install to install the binary executable on /tmp. The result of the preceding command can be seen by looking at the contents of the /tmp directory: $ make install [100%] Built target simple Install the project... -- Install configuration: “” -- Up-to-date: /tmp/bin/simple $ ls -l /tmp/bin/ total 12 -rwxr-xr-x 1 mtsouk mtsouk 11120 Jan 1 20:57 simple
If you want to test the results of any make command, you can execute make with the -n command line option. This prints the commands that will be executed without actually executing them!
CTest and CPack
CTest is a tool for testing that comes with CMake and can be invoked with the ctest binary. The CTest tool can operate in two modes. In the first mode, you should use CMake to create and run the tests for CTest. In the other mode, CTest takes charge and runs as a script in order to run the testing process.
CPack is a cross-platform software packaging tool that comes with CMake. CPack uses the CPackConfig.cmake file and is called by the cpack binary. As CMake comes with a CPack module, you can use CPack from a CMake configuration file by including the INCLUDE(CPack) line in CMakeCache.txt. Finally, you can debug CPack by calling the cpack executable cpack --debug --verbose .
CMake and Graphviz
Graphviz is a collection of tools for generating impressive and useful directed or undirected graph layouts that uses its own language called dot. CMake can generate dot code that visualises the dependencies of the targets of a CMake project with the help of the --graphviz command line option.
Given a CMake project, which in this case it would be the simple project created earlier in this tutorial, you can create a visualisation of its dependencies as follows: $ cd build $ cmake .. --graphviz=simple.dot
The screenshot ( topleft) shows the output of the previous command as well the three generated dot files, which are all pretty simple as the CMake project is also straightforward. The simple.dot file shows all the dependencies in the project whereas the simple.dot.simple file shows the dependencies for a particular target – so if you have only one target these two files will be exactly the same! Finally, the simple.dot. simple.dependers file shows the targets that depend on a particular target. Because this CMake project has only one target, simple.dot.simple.dependers is the same as simple. dot.simple. More interesting projects will generate more complex Graphviz files and therefore more interesting graphical output. You can generate a PNG file named simple. png from a dot file called simple.dot by executing the dot -Tpng -osimple.png simple.dot command.
The good thing is that by adding some extra configuration code in your CMakeCache.txt file, you can instructCMaketo automatically generate the project dependencies using Graphviz. The screenshot ( above) shows the final version of the CMakeCache.txt file.
The CMakeGraphVizOptions.cmake file is related to the Graphviz output of CMake and enables you to customise the Graphviz output. You can obtain the full list of the options supported in CMakeGraphVizOptions.cmake, which is pretty big, by executing the cmake --help-module CMakeGraphVizOptions command.
Remember, you’ll only be able to recognise the benefits that you can obtain from CMake if you start using it in your projects on a regular basis So what are you waiting for?