CMake Introduction

Hanz
5 min readApr 24, 2020

CMake (cross-platform Make), is an open-source software tool for managing the build process of software using a compiler-independent method. It supports directory hierarchies and applications that depend on multiple libraries. It is used in conjunction with native build environments such as Make, Qt Creator, Ninja, Apple’s Xcode, and Microsoft Visual Studio.

1. CMake vs Make

Make (or rather a Makefile) is a build system, it drives the compiler and other build tools to build your code.
CMake is a generator of build systems, it can produce Makefiles. So if you have a platform-independent project, CMake is a way to make it build system-independent as well.

Here are advantages of CMake over Make:

  • The language used to write CMakeLists.txt files is readable and easier to understand.
  • Cross-platform discovery of system libraries/functions/headers/flags.
  • Easier to compile your files into a shared library in a platform agnostic way, and in general easier to use than make.
  • CMake doesn’t only rely on “Make” to build the project, It supports multiple generators like Xcode, Eclipse, Visual Studio, etc.

To make it simple, you can accomplish most of the features available in CMake by using Make but with an EXTRA EFFORT.

2. Compilation Process Steps

The compilation process in CMake performs two steps :

  1. Configuration
  2. Generation

Both Configuration and Generation (build) commands are specified in one location and are handled entirely by CMake.

Configuration

In this, the script CMakeLists.txt is executed. This script is responsible for defining targets. Here target can be executable, library, or some other output of the build pipeline.

If the configure step succeeds — meaning CMakeLists.txt completed without errors — CMake will generate a build pipeline using the targets defined by the scripts. The type of build pipeline generated depends on the type of generator used.

CmakeCache.txt is created so that it can be helpful by reducing the time to build the pipeline in the future.

Generation

This phase is meant to generate executable depending upon the targets created in the Configuration phase.

The above-mentioned details can be visualized from the picture below.

3. Commands to Configure and Build

Creates CMake configuration files inside folder build

$ cmake -S . -B "build" -G "Visual Studio 16 2019 Win64" -DCMAKE_INSTALL_PREFIX:PATH="install"Usage
cmake [options] <path-to-source>
cmake [options] <path-to-existing-build>
cmake [options] -S <path-to-source> -B <path-to-build>
Options
-S <path-to-source> = Explicitly specify a source directory.
-B <path-to-build> = Explicitly specify a build directory.
-D <var>[:<type>]=<value> = Create or update a cmake cache entry.
-G <generator-name> = Specify a build system generator.

Generate the output files in the build folder

$ cmake --build "build" --target "audience-encode"Usage: cmake --build <dir> [options] [ — [native-options]]
Options:
<dir> = Project binary directory to be built.
— target <tgt>…, -t <tgt>…
= Build <tgt> instead of default targets.

Execute CTest if test file CTestTestfile.cmake is exist

$ ctest "build"Usage: ctest [options]

Perform installation for all of targets

$ cmake --install --prefix "install"Usage: cmake --install <dir> [options]
Options:
<dir> = Project binary directory to install.
— prefix <prefix> = The installation prefix CMAKE_INSTALL_PREFIX.

4. Basic Usages

Minimum version

Here’s the first line of every CMakeLists.txt, which is the required name of the file CMake looks for:

cmake_minimum_required(VERSION 3.1)

This command makes sure the minimum CMake version required for the build process.
An exception is raised if the command fails.

Setting a project

Now, every top-level CMake file will have the next line:

project(MyProject VERSION 1.0
DESCRIPTION "Very nice project"
LANGUAGES CXX)

This command is used to give a name to the project you are working on.

Making an executable

Although libraries are much more interesting, and we’ll spend most of our time with them, let’s start with a simple executable.

add_executable(one two.cpp three.h)

There are several things to unpack here.

Making a library

Making a library is done with add_library, and is just about as simple:

add_library(one STATIC two.cpp three.h)

You get to pick a type of library, STATIC, SHARED, or MODULE. If you leave this choice off, the value of BUILD_SHARED_LIBS will be used to pick between STATIC and SHARED.

Targets are your friend

Now we’ve specified a target, how do we add information about it? For example, maybe it needs an include directory:

target_include_directories(one PUBLIC include)

target_include_directories adds an include directory to a target. PUBLIC doesn't mean much for an executable; for a library it lets CMake know that any targets that link to this target must also need that include directory. Other options are PRIVATE (only affect the current target, not dependencies), and INTERFACE (only needed for dependencies).

We can then chain targets:

add_library(another STATIC another.cpp another.h)
target_link_libraries(another PUBLIC one)

target_link_libraries is probably the most useful and confusing command in CMake. It takes a target (another) and adds a dependency if a target is given. If no target of that name (one) exists, then it adds a link to a library called one on your path (hence the name of the command).

5. Link an external library

Finding Existing Library

Headers can be installed along with the config files so library can be found by

find_package(spdlog CONFIG REQUIRED):[spdlog]> cmake -H. -B_builds -DCMAKE_INSTALL_PREFIX=/path/to/install -DCMAKE_BUILD_TYPE=Release
[spdlog]> cmake --build _builds --target install

Usage can be tested by building examples as a stand-alone project:

[spdlog/examples]> cmake -H. -B_builds -DCMAKE_PREFIX_PATH=/path/to/install -DCMAKE_BUILD_TYPE=Release
[spdlog/examples]> cmake --build _builds

CMakeList.txt

cmake_minimum_required(VERSION 3.2)
project(spdlog_examples CXX)
if(NOT TARGET spdlog)
# Stand-alone build
find_package(spdlog REQUIRED)
endif()
#---------------------------------------------------------------------------------------
# Example of using pre-compiled library
#---------------------------------------------------------------------------------------
add_executable(example example.cpp)
target_link_libraries(example PRIVATE spdlog::spdlog)
#---------------------------------------------------------------------------------------
# Example of using header-only library
#---------------------------------------------------------------------------------------
if(SPDLOG_BUILD_EXAMPLE_HO)
add_executable(example_header_only example.cpp)
target_link_libraries(example_header_only PRIVATE spdlog::spdlog_header_only)
endif()

reference:
https://github.com/gabime/spdlog/wiki/9.-CMake#install
https://github.com/gabime/spdlog/blob/v1.x/example/CMakeLists.txt

Including Small Project

Often, you would like to do your download of data or packages as part of the configure instead of the build. This was invented several times in third party modules, but was finally added to CMake itself as part of CMake 3.11 as the FetchContent module.

include(FetchContent)if(${CMAKE_VERSION} VERSION_LESS 3.14)
include(add_FetchContent_MakeAvailable.cmake)
endif()
set(SPDLOG_GIT_TAG v1.4.1)
set(SPDLOG_GIT_URL https://github.com/gabime/spdlog.git)
FetchContent_Declare(
spdlog
GIT_REPOSITORY ${SPDLOG_GIT_URL}
GIT_TAG ${SPDLOG_GIT_TAG}
)
FetchContent_MakeAvailable(spdlog)project(ImportExternalProject)
cmake_minimum_required(VERSION 3.11)
add_definitions(-std=c++11)
add_executable(test_spdlog testspdlog.cc)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}")
include(spdlog2)
target_link_libraries(test_spdlog PRIVATE spdlog)

reference:
https://zhuanlan.zhihu.com/p/102050750

--

--