CMake is a cross-platform generator system designed to create projects. It is not a direct build system, but a meta build system that uses a script with defined rules, called CMakeLists.txt to generate input files for build systems called project files (could be Unix Makefiles or MinGW Makefiles or others, discussed later) that support different kinds of compilation, configuration, testing and packaging options.
This article explains the reasoning behind using C++, related terminology used in C++ code compilation and general commands, options and tags to be used to write CMakeLists.txt as per desired requirements.
C/C++ is an extremely useful language used for innumerable number of applications. However, with several compilers, operating systems and hardware platforms, it becomes essential to package an optimized C++ project comptabile accross various such platforms and for different desired uses.
CMake makes it possible for developers to generate the project makefiles to be used by the build system to build the software. CMake does not compile or link any source (C/C++ code files here) itself but only creates configuration files for a build system. The build system thereafter uses the configurations to compile and link the source files as desired.
Build systems are a collection of tools that compile the source code, execute all linking and other secondary tasks. They could be configured to not only compile source codes but also synchronise builds in multi-stage build systems. CMake uses the specified generators like Unix Makefiles, Ninja, MinGW Makefiles, Visual Studio and Codeblocks among others and writes the input files for the native build system. Exactly one build system is used at once.
Note:
gcc and g++ are the compiler-drivers of the GNU Compiler Collection. While gcc can compile .c and .cpp files as C and C++ language files respectively, g++ considers both of them to be C++ files. Also, unlike gcc, g++ compilation automatically links all STL C++ libraries.
For ease of understanding, here we can consider using them interchangeably.
C++ is a widely used programming language owing to its powerful capabilities and fast operation. Among several other things, being a compiled language makes C++ really really fast.
While compilation is a topic for later, it can be understood naively as breaking down a code to its almost machine-level version and preprocessing parts as much as possible such that when executed, considerable sections of the code already have prepared answers or execute immediately on the processor and no time is spent in conversion of human-readable code into machine-compatible code at runtime.
The most common and rudimentary Hello World C++ program has one main.cpp file and it can be compiled using the following terminal command to obtain the
~$ g++ main.cpp -o main // Compile
~$ ./main // Execute
If there are 3 files, namely si_int.cpp, com_int.cpp and the main.cpp where the two former files have functions to compute simple interest and compound interest, used(by importing) in the last main.cpp file to calculate results; they could be compiled using terminal command (for C++11)
~$ g++ si_int.cpp com_int.cpp main.cpp -o main -std=c++11 // Compile
~$ ./main // Execute
The two examples are indicative of the cumbersome process that could shape if there are more files, more dependencies, more libraries to link and other such unique requirements while code compilation; command-line compilation is certainly not the way to go.
For reassurance, here are some tags needed for g++ to achieve more features.
-I<include path> - Specify a directory (to include files from)
-L<library path> - Specify a library directory
-0 - Turn ON optimizations by the compiler
-l<library> - Link with library lib<library>.a
-Wall - Turn on Warnings during compilations
-g - Generate flags for debugging during compilation (Used by GDB)
Considering the challenges in compilation, CMake provides options to the user to configure the complete project and package it as per need be. The conditions, compilation tags, machine dependencies, language versions and many such parameters.
CMake is a high-level build-system customized for C++. CMake tracks and stores all flags and conditions that should be followed when the code is finally compiled and run. The liberty of allowing extensibility in code compilation conditions and external libraries/resources used makes CMake a very useful tool.
The CMakeLists.txt has instructions that help developers design different build scenarios and conditional usage of generators, versions and more. Certain general CMake commands are used for some common operations.
The common parameters, description and command to configure the same in a CMakeLists.txt are summarized here.
NOTE:
Most of the command signatures are only short-hand signatures and do not show all the options and configurable parameters that CMake actually suppports.
More detailed explanation(generally needed only if a very complex setup is employed) can be found on the official website of CMake.
⇒ Set string variables using actual string values
set(<variable_identifier> <string_value>)
⇒ Set string variables using another variable value
set(<variable_identifier> <${another_variable_identifier}>)
⇒ Append string variables using another variable value
string(APPEND <variable_identifier> <string_value>)
⇒ Set list variables using actual string values
set(<list_identifier> <string_value1> <string_value2>)
⇒ Set list variables using another list value
set(<list_identifier> <${another_list_identifier}>)
⇒ Append list variables using another list value
list(APPEND <list_identifier> <string_value>)
The <string_value>
variable can be a file name as well. The identifiers can either be user defined for later use the script or from the common list of CMake supported variables like CMAKE_BUILD_TYPE
and PROJECT_LINK_LIBS
among others.
⇒ CMake Package version to be used. Versions > 3.0.0 are generally used though.
cmake_minimum_required(VERSION 2.8.9)
⇒ Operations dependent on OS type. CMake can generate different configurations depending on Linux/Apple/Windows OS.
if(APPLE)
// do something for APPLE OS
endif()
if(UNIX AND LINUX)
// do something for LINUX
endif())
if(WIN32)
// do something for WINDOWS OS
endif()
⇒ Software build type
set(CMAKE_BUILD_TYPE Release/Debug)
⇒ C++ Version. For instance, C++11 in this case
set(CMAKE_CXX_STANDARD 11)
⇒ Give a name to the project
project(<string_value>)
⇒ An executable is built from the source file and assign the string_value
as the name of the executable
add_executable(<string_value> <code_file_name>)
⇒ Add the executable library to the compilation sequence
add_library(<string_value> <library_type> <file_location>)
For example,
add_library_(<libName> SHARED/STATIC src/lib_executable.cpp)
If STATIC, then the name of the library is libName.a and it is libName.so if SHARED
⇒ Include the header files from the source code into the build environment
include_directories(<include_header_locations>)
⇒ While to set source code files to a string value, all files need to be mentioned manually, GLOB or GLOB_RECURSE allows for searching and selecting multiple source code files
file(GLOB SOURCES "src/*.cpp")
⇒ Several properties need to define for the executable binary
An example would be setting the language as CXX(for C++).
set_target_properties(cmake_usage_example PROPERTIES LINKER_LANGUAGE CXX)
⇒ Defining the location in the system for installation of the library.
install(TARGET <libray_name> DESTINATION <destination_location>)
⇒ Find the location of a library and store its reference in a temporary VARIABLE cache.
find_package(<VARIABLE> <library_name> <path_to_library>)
⇒ Link an already extant library (either native library or third-party installed library or custom-built one ) to the current executable or library being created
target_link_libraries(<to_be_linked> options <to_link_ref>)
For example
target_link_libraries(libApp LINK_PUBLIC ${VARIABLE})
<lib_to_link_ref>
can be found from something like find_package
Install CMake and check version
:~/$sudo apt-get install cmake // Install CMake
:~/$cmake --version // Check CMake version
The following example is a simple C++ application. It has a class with functions for a few mathematical operations. The main script to be executed uses this class as need be.
Finally, the Unix Makefiles CMake generator will be used to create the build system Makefile.
cpp-cmake-example
├── CMakeLists.txt
├── include
│ └── computations.h
└── src
└── computations.cpp
└── main,cpp
Example C++ Application - Source Code computations.cpp file
Example C++ Application - Header File computations.h file
Example C++ Application - Main file main.h file
Example C++ Application - CMake file CMakeLists.txt file
Finally, the project is executed using the steps shown in the image below.
cd
into the folder and then run cmake ..
where the two dots represent the presence of CMakeLists.txt in one folder prior; to generate the project filesmake
or make <executable>
to compile the source code./cmake_usage_example
to run the compiled sample codeIF YOU LIKED THE ARTICLE, DON'T FORGET TO LEAVE A REACTION OR A COMMENT!