computer science,

CMake Meta-Build System

Jun 25, 2020 · 8 mins read
CMake Meta-Build System
Share this

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.

CMake Pipeline Overview
CMake Pipeline Overview

Introduction

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.

Is CMake really needed?!

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 binary.
~$ 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.

Nuances of CMake

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 3.18.0)

  • 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}) can be found from something like find_package

Example C++ Application & CMakeLists.txt

  • Install CMake and check version
    sudo make install cmake
    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.

Repository Tree
Repository Tree

Finally, the project is executed using the steps:

  • We create a build folder to avoid cluttering built files with source code - mkdir -p build
  • 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 files
  • Run make or make <executable> to compile the source code
  • ./cmake_usage_example to run the compiled sample code