Comprehensive Guide Managing Building Cplus Projects
Comprehensive Guide Managing Building Cplus Projects
February 2025
Contents
Contents 2
Author’s Introduction 14
1 Introduction to CMake 16
1.1 Why Do You Need CMake? . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.1.2 The Challenges of Traditional Build Systems . . . . . . . . . . . . . . 17
1.1.3 How CMake Solves These Challenges . . . . . . . . . . . . . . . . . . 19
1.1.4 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.2 CMake vs. Traditional Build Systems (Make, Autotools, Ninja, etc.) . . . . . . 22
1.2.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.2.2 Traditional Build Systems: Overview and Limitations . . . . . . . . . . 22
1.2.3 How CMake Compares to Traditional Build Systems . . . . . . . . . . 26
1.2.4 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.3 Installing CMake on Different Operating Systems (Windows, Linux, macOS) . 29
1.3.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1.3.2 Installing CMake on Windows . . . . . . . . . . . . . . . . . . . . . . 29
1.3.3 Installing CMake on Linux . . . . . . . . . . . . . . . . . . . . . . . . 33
1.3.4 Installing CMake on macOS . . . . . . . . . . . . . . . . . . . . . . . 35
2
3
1.3.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
1.4 Verifying CMake Installation and Running It . . . . . . . . . . . . . . . . . . . 39
1.4.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
1.4.2 Verifying CMake Installation . . . . . . . . . . . . . . . . . . . . . . . 39
1.4.3 Running CMake for the First Time . . . . . . . . . . . . . . . . . . . . 42
1.4.4 Troubleshooting Common Issues . . . . . . . . . . . . . . . . . . . . . 46
1.4.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
1.5 Your First CMake Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
1.5.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
1.5.2 Setting Up a Simple C++ Project . . . . . . . . . . . . . . . . . . . . . 48
1.5.3 Creating the CMakeLists.txt File . . . . . . . . . . . . . . . . . . 50
1.5.4 Configuring the Build System with CMake . . . . . . . . . . . . . . . 51
1.5.5 Building the Project . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
1.5.6 Running the Executable . . . . . . . . . . . . . . . . . . . . . . . . . 54
1.5.7 Understanding the Build Process . . . . . . . . . . . . . . . . . . . . . 54
1.5.8 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
2 Fundamentals of CMakeLists.txt 56
2.1 Understanding the Structure of CMakeLists.txt . . . . . . . . . . . . . . . . . 56
2.1.1 Introduction to CMakeLists.txt . . . . . . . . . . . . . . . . . . . . . . 56
2.1.2 Key Sections of a CMakeLists.txt File . . . . . . . . . . . . . . . . . . 57
2.1.3 Best Practices for Organizing CMakeLists.txt . . . . . . . . . . . . . . 63
2.1.4 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
2.2 Defining the Minimal CMake Project . . . . . . . . . . . . . . . . . . . . . . . 65
2.2.1 What Makes a CMake Project ”Minimal”? . . . . . . . . . . . . . . . 65
2.2.2 CMake File Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
2.2.3 CMakeLists.txt File for the Minimal Project . . . . . . . . . . . . . . . 66
2.2.4 Understanding the Minimal CMake Project . . . . . . . . . . . . . . . 70
4
Appendices 278
Appendix A: CMake Command Reference . . . . . . . . . . . . . . . . . . . . . . . 278
Appendix B: CMake Best Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . 281
Appendix C: CMake Troubleshooting Guide . . . . . . . . . . . . . . . . . . . . . . 284
Appendix D: CMake Project Examples . . . . . . . . . . . . . . . . . . . . . . . . . 286
Appendix E: CMake Tools and Integrations . . . . . . . . . . . . . . . . . . . . . . 288
13
References 290
Author’s Introduction
One of the most challenging aspects of C++ programming is the compilation process. Unlike
many modern languages that come with built-in package managers and streamlined build
systems, C++ requires developers to have a deep understanding of compilation, linking,
dependency management, and platform-specific configurations. This complexity often
discourages students and even experienced developers, leading them to abandon C++ in favor of
languages with simpler build processes. However, while C++ may have a steep learning curve in
this regard, mastering the right tools can dramatically improve the development experience and
unlock the full potential of the language.
This is where CMake comes in. CMake is not just another build system; it is a powerful
meta-build tool that simplifies and standardizes the configuration, compilation, and linking
processes across multiple platforms and compilers. In large-scale projects, particularly those
targeting multiple operating systems (Windows, macOS, Linux) or architectures (x86, ARM,
embedded systems), managing build files manually can be overwhelming. CMake provides an
elegant solution by allowing developers to define their build processes in a
platform-independent manner, generating appropriate build scripts for a variety of compilers
and environments.
Many C++ programmers hesitate to learn CMake, thinking of it as an additional layer of
complexity rather than a solution. However, once you understand its workflow, CMake becomes
an indispensable tool that simplifies project setup, dependency management, and integration with
external libraries. Whether you are working on small projects or large-scale software systems,
14
15
using CMake can save you countless hours of manual configuration and troubleshooting.
In this book, I will guide you through the fundamentals of CMake, from basic setup to
advanced configurations. You will learn how to efficiently manage source files, handle
third-party dependencies, optimize compilation settings, and create robust cross-platform
builds. By the end of this journey, you will have a solid grasp of CMake, allowing you to focus
more on writing great C++ code rather than struggling with build issues.
I strongly encourage every C++ developer to invest time in mastering CMake. It is not just a
tool—it is an essential skill that will make your C++ development process more efficient,
scalable, and enjoyable.
Stay Connected
For more discussions and valuable content about Modern C++ Pointers, I invite you to follow
me on LinkedIn:
https://linkedin.com/in/aymanalheraki
You can also visit my personal website:
https://simplifycpp.org
Ayman Alheraki
Chapter 1
Introduction to CMake
1.1.1 Introduction
Software development, particularly in languages like C++, involves multiple stages, including
writing source code, compiling it into object files, linking those files to create an executable, and
finally deploying the application. As projects grow in complexity, managing these tasks
efficiently becomes increasingly difficult. While simple programs with a few source files can be
compiled manually using compiler commands, larger projects require automated build systems
to handle dependencies, multiple files, libraries, and different build configurations.
Historically, developers have relied on manual Makefiles, Autotools, and other
platform-specific build scripts to manage the compilation process. However, these traditional
methods come with significant limitations, particularly when it comes to cross-platform
compatibility, maintainability, and scalability.
CMake is a modern, flexible, and cross-platform build system generator that simplifies the
process of compiling, linking, and managing C++ projects. Instead of manually writing complex
16
17
and platform-dependent Makefiles or build scripts, developers define their project’s structure
in a simple and declarative manner using CMakeLists.txt, and CMake generates the appropriate
build system for the target platform.
This section explores the need for CMake by identifying the challenges associated with
traditional build systems and demonstrating how CMake provides a powerful solution.
One of the biggest challenges in software development is ensuring that a project can be
compiled and executed on multiple operating systems. Many projects need to support
Windows, Linux, macOS, and even embedded platforms.
When using traditional build methods, developers often have to write separate build scripts
for each platform:
Each of these build scripts is tailored to the specific operating system and compiler used.
A Makefile that works on Linux with GCC might not work on Windows without
modifications, and a Visual Studio project file cannot be easily ported to Linux. This
fragmentation leads to increased maintenance overhead and makes cross-platform
development cumbersome.
Modern C++ projects often rely on multiple external libraries, such as Boost, OpenCV,
Qt, Eigen, GLFW, and SQLite. Managing these dependencies manually presents several
challenges:
• Locating the library: Developers must specify the correct paths for headers and
compiled binaries.
• Handling different versions: Different systems may have different versions of a
library installed, leading to potential compatibility issues.
• Static vs. dynamic linking: Some projects require static linking, while others need
shared libraries, leading to different linking options.
• Managing transitive dependencies: A library may depend on other libraries,
complicating the linking process.
app: $(OBJS)
$(CC) $(CFLAGS) -o app $(OBJS)
19
Every time a new source file is added, it must be explicitly listed in OBJS, making
maintenance error-prone. Large projects with hundreds of source files require a more
dynamic and automated approach to handling build configurations.
A project developed on Linux using Makefiles and GCC may not compile on Windows
without modification. Differences in compilers, library locations, and system APIs
require additional effort to ensure cross-platform compatibility.
1. Cross-Platform Compatibility
This allows developers to write once and build anywhere, eliminating the need for
maintaining multiple build scripts for different platforms.
CMake provides built-in functionality for locating and integrating third-party libraries.
Instead of manually specifying library paths, developers can use:
This streamlines dependency management and reduces the risk of version mismatches or
missing dependencies.
CMake detects the compiler, available system libraries, and hardware capabilities
automatically. This allows developers to write portable build configurations without
worrying about platform-specific details.
For example, CMake can check for the presence of certain libraries and enable features
accordingly:
find_package(OpenGL REQUIRED)
find_package(Boost 1.71 REQUIRED)
21
If the required libraries are not found, CMake can provide meaningful error messages,
guiding users to install the necessary dependencies.
1.1.4 Conclusion
CMake addresses the limitations of traditional build systems by providing a cross-platform,
maintainable, and scalable approach to building C++ projects. It simplifies dependency
management, multi-platform support, and automatic configuration detection, making it the
preferred choice for modern C++ development.
With CMake, developers can focus on writing code instead of dealing with the intricacies of
manually managing builds, dependencies, and platform-specific configurations.
22
1.2.1 Introduction
The process of building software from source code involves compiling, linking, and organizing
dependencies to create an executable or library. As software projects grow in complexity,
managing the build process manually becomes inefficient and error-prone.
Traditionally, developers relied on build systems such as Make, Autotools, and Ninja to
automate the compilation process. However, these systems come with limitations in
cross-platform support, maintainability, and flexibility.
CMake was developed to overcome these limitations by providing a higher-level build
system generator that abstracts platform-specific complexities. Unlike traditional build systems
that require developers to write platform-specific scripts, CMake allows them to define their
projects once and generate the appropriate build files for multiple platforms and compilers.
This section provides an in-depth comparison between CMake and traditional build systems,
highlighting their strengths, weaknesses, and use cases.
Make is one of the earliest and most widely used build systems. It is primarily used in
Unix-like operating systems and relies on Makefiles to define build rules and
dependencies.
23
CC = g++
CFLAGS = -Wall -O2
OBJ = main.o module.o
app: $(OBJ)
$(CC) $(CFLAGS) -o app $(OBJ)
%.o: %.cpp
$(CC) $(CFLAGS) -c $< -o $@
make
This compiles the source files and links them into an executable.
Advantages of Make
Disadvantages of Make
• Manual dependency tracking: Developers must explicitly list source files and
dependencies.
3. Libtool – Handles shared and static library creation across different platforms.
./configure
make
make install
Advantages of Autotools
Disadvantages of Autotools
3. Ninja
Ninja is a build system optimized for speed and efficiency. Unlike Make and Autotools,
which handle dependency resolution and build configuration, Ninja is designed purely for
executing build tasks as quickly as possible.
How Ninja Works
Ninja relies on a build.ninja file, which describes how source files should be
compiled and linked. However, developers do not write these files manually—they are
typically generated by higher-level tools like CMake or Meson.
26
rule compile
command = g++ -c $in -o $out
build main.o: compile main.cpp
Advantages of Ninja
Disadvantages of Ninja
cmake_minimum_required(VERSION 3.16)
project(MyApp)
add_executable(MyApp main.cpp)
cmake -S . -B build
cmake --build build
CMake automatically detects the compiler, generates the appropriate build system
(Makefiles, Ninja, Visual Studio), and compiles the project efficiently.
1.2.4 Conclusion
While traditional build systems like Make, Autotools, and Ninja have been widely used, they
come with limitations in portability, dependency management, and maintainability.
CMake provides a modern, flexible, and cross-platform solution that simplifies the build
process by generating native build files for different systems. With its ability to handle
dependencies, detect system configurations, and support multiple build backends, CMake
has become the industry standard for managing C++ projects.
29
1.3.1 Introduction
CMake is a powerful and flexible build system generator that plays a crucial role in simplifying
the process of building C++ projects across different platforms. The installation process is
straightforward, but due to the variety of operating systems and user preferences, there are
multiple methods to install it. This section will provide a detailed guide on how to install CMake
on the most widely used operating systems: Windows, Linux, and macOS.
The installation process for CMake can involve precompiled binary installers, package
managers, or even manual compilation from source. The method chosen depends on the
user's specific needs, such as ensuring the latest version, ease of use, or whether the user prefers
a command-line or graphical interface for installation.
Regardless of the installation method, once CMake is installed, it will allow you to generate
build files for various platforms, manage dependencies, and enable a seamless integration with a
variety of development tools. This section will walk you through each installation method for
different operating systems, and provide verification steps to ensure CMake is installed and
functioning correctly.
One of the easiest and most common methods for installing CMake on Windows is by
30
using the official CMake installer provided by Kitware, the creators of CMake. This
method ensures that you have the latest stable version of CMake, and it allows for an easy
installation process with a graphical user interface (GUI).
1. After the download is complete, double-click the .msi file to launch the
CMake installation wizard.
2. During the installation, you will be presented with different options. The key
option is to add CMake to the system PATH. This is a critical step because it
allows you to run CMake from the command line (Command Prompt or
PowerShell) without needing to specify its full path. You can select the option
”Add CMake to the system PATH for all users”.
3. Proceed with the default installation settings and click Next through the
installation wizard until the installation is complete.
cmake --version
If CMake has been installed correctly, you should see the version of CMake
displayed in the terminal, similar to:
If you see this output, CMake has been installed and is ready to use.
This will automatically install CMake and add it to your system’s PATH so that you
can use it from the command line.
cmake --version
cmake --version
• Ubuntu/Debian-based Distributions
If you're using a Debian-based distribution such as Ubuntu, CMake can easily be
installed using the APT package manager:
cmake --version
• Fedora-based Distributions
For Fedora or similar distributions, the DNF package manager is used:
cmake --version
Verify it using:
cmake --version
wget
,→ https://github.com/Kitware/CMake/releases/latest/download/cmake-3
./bootstrap
make -j$(nproc)
cmake --version
Homebrew is the most popular package manager for macOS, and it simplifies software
installation.
cmake --version
cmake --version
1. Open the .dmg file and drag the CMake.app into the /Applications folder.
2. To enable command-line usage, open CMake.app, go to Tools > How to
Install For Command Line Use, and follow the steps provided.
cmake --version
1.3.5 Conclusion
The installation of CMake varies depending on the operating system being used, but regardless
of the method, CMake provides the necessary tools to streamline and automate the build process
for C++ projects. Whether you use an installer, a package manager, or build from source, the
goal remains the same: to ensure that CMake is set up properly so that you can manage and
configure your project builds across various platforms.
38
Once CMake is installed successfully, you can begin using it to generate platform-specific build
files, configure your project, and manage complex builds in a consistent and efficient manner. In
the next section, we will explore CMake’s basic commands and structure, which will lay the
groundwork for mastering CMake in the context of real-world projects.
39
1.4.1 Introduction
After you have installed CMake on your system, it is essential to ensure that the installation was
successful and that CMake is functioning properly. This process is vital because any issues with
the installation or configuration of CMake could lead to problems when building C++ projects.
Verifying CMake’s installation helps to confirm that the required binaries, environment variables,
and necessary configuration files have been set up correctly.
CMake is a powerful build system generator, and if installed and configured properly, it should
work seamlessly across various platforms like Windows, Linux, and macOS. This section walks
you through how to verify your CMake installation and offers guidance on how to run it for the
first time.
1. On Windows:
cmake --version
• If CMake has been installed successfully, you will see the version number of
CMake. For example:
This indicates that CMake is installed and ready to use. If you encounter an error
message such as ”command not found” or ”CMake is not recognized as an internal
or external command,” this suggests that either the installation has failed or the
system’s PATH environment variable is not set correctly.
2. On Linux/macOS:
• Open a Terminal window.
• Run the following command:
cmake --version
• If CMake is correctly installed, you should see an output similar to the one on
Windows:
If you see an error indicating that cmake is not found, you may need to recheck the
installation process and ensure that the cmake binary is properly linked to your
system’s PATH.
If you receive an error message indicating that the cmake command cannot be found,
here are some troubleshooting steps:
• Ensure CMake is Added to PATH: When you install CMake, it is important to add
the CMake executable to your system’s PATH environment variable. If this step was
missed during installation, you can manually add CMake to your PATH:
– On Windows: You can add CMake to the PATH through the Environment
Variables settings. To do this, go to System Properties > Advanced >
Environment Variables, then edit the System PATH and add the directory
where CMake is installed (e.g., C:\Program Files\CMake\bin).
– On Linux/macOS: Open the shell configuration file (.bashrc or .zshrc,
depending on your shell) and add the following line to include CMake in your
PATH:
export PATH="/path/to/cmake/bin:$PATH"
• Verify Installation Location: Ensure that CMake was installed in the correct
directory. If you installed it via a package manager, it may have been installed in a
non-standard directory, especially on Linux or macOS. Verify that the installation
path is valid and contains the cmake binary.
• Reinstall CMake: If none of the above solutions work, you may need to reinstall
CMake. Be sure to follow the installation instructions carefully to avoid errors, and
make sure to include the option to add CMake to the system PATH during the
installation.
42
Before you can run CMake, it is best to set up a basic C++ project. This allows you to test
CMake’s functionality by creating a minimal project and using CMake to generate the
necessary build files. The following example demonstrates a very simple C++ program
and how to use CMake with it.
1. Create a Project Directory: First, create a directory to house the project files. Open
a terminal or command prompt and create a new directory:
mkdir MyTestProject
cd MyTestProject
2. Create a Simple C++ Source File: Inside the MyTestProject directory, create
a simple C++ source file named main.cpp:
// main.cpp
#include <iostream>
int main() {
std::cout << "Hello, CMake!" << std::endl;
return 0;
}
43
# CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(MyTestProject)
add_executable(MyTestProject main.cpp)
In this example:
• cmake minimum required(VERSION 3.0) specifies that the project
requires at least version 3.0 of CMake.
• project(MyTestProject) defines the project name as
MyTestProject.
• add executable(MyTestProject main.cpp) tells CMake to
generate an executable named MyTestProject from the main.cpp source
file.
This configuration file is very basic, but it covers the essential elements of a typical
CMake project.
1. Create a Build Directory: To keep the build files separate from the source code, it
is common practice to create a separate build directory. Create a new directory inside
your project folder called build:
44
mkdir build
cd build
2. Run CMake to Configure the Project: Inside the build directory, run the
following CMake command to configure your project:
cmake ..
This command tells CMake to look for the CMakeLists.txt file in the parent
directory (..) and generate the build system files based on the configuration in that
file. If everything is set up correctly, CMake will display output showing the
configuration process, where it detects the system’s compilers, checks for required
tools, and prepares the necessary build files.
The output will look something like:
If there are any errors during this process, CMake will output detailed messages that
can help you diagnose the issue. Common issues include missing dependencies,
incorrect file paths, or unsupported compilers.
Once the configuration step is complete, you can now build the project. CMake has
generated the necessary build files, so you can use the appropriate build tool to compile
the code.
make
This command will invoke the build system (Make) to compile the main.cpp file
and generate the executable MyTestProject.
2. On Windows (Using Visual Studio Project Files): If you are using Windows and
CMake generated Visual Studio project files, you can open the .sln file generated
by CMake and build the project directly in Visual Studio. Alternatively, you can use
the MSBuild command to build from the command line:
MSBuild MyTestProject.sln
Once the build completes successfully, you can run the executable generated by CMake.
./MyTestProject
Hello, CMake!
• On Windows: If you are using Visual Studio or the command line, you can simply
run the MyTestProject.exe executable.
While running CMake for the first time, there are a few common issues that you might
encounter:
• Missing or Incorrect Compiler: CMake requires a working C++ compiler to build your
project. If CMake cannot detect a valid compiler, it will show an error. Make sure that a
C++ compiler (like GCC, Clang, or MSVC) is properly installed and accessible. On
Linux/macOS, you can check the installed compiler version using gcc --version or
clang --version. On Windows, make sure the Visual Studio build tools are
installed correctly.
• Permissions Issues: On Linux and macOS, you may encounter permission-related errors
if you do not have write access to certain directories. Ensure that you are running CMake
with the appropriate permissions or try running with sudo if necessary.
• CMake Cache Conflicts: CMake caches configuration data to avoid reprocessing the
same information multiple times. However, if you make changes to the project structure or
the CMakeLists.txt file, the cached configuration may cause issues. You can delete
the CMakeCache.txt file in your build directory and rerun the CMake command to
clear the cache.
47
1.4.5 Conclusion
Verifying CMake installation and running it for the first time is a crucial step in setting up your
development environment. By checking the CMake version and running it on a simple C++
project, you can ensure that everything is working as expected. If you encounter issues,
troubleshooting steps such as checking the system PATH, verifying the compiler installation, or
clearing the CMake cache can help resolve common problems. Once CMake is verified and
working, you can proceed to more advanced topics, such as configuring larger projects and
utilizing CMake’s advanced features to improve your build system and project management
workflow.
48
1.5.1 Introduction
The previous sections provided an understanding of the importance of CMake, how it differs
from traditional build systems, and how to install it on various operating systems. Now, it’s time
to take the next step and create your very first CMake project. This hands-on guide will walk
you through the entire process, from setting up a simple C++ program to configuring the
necessary build files, and ultimately compiling and running the project. By the end of this
section, you will be comfortable with the fundamental aspects of working with CMake, which
will form the foundation for tackling more advanced CMake features in subsequent sections.
Creating a project with CMake is straightforward and involves defining how your source code is
compiled and linked, specifying compiler options, and ensuring that CMake generates the
correct build system files for your chosen platform. With CMake, the complexity of build
system generation is abstracted away, which saves time and reduces the possibility of errors in
managing project configurations.
1. Project Structure
The basic structure for your project will include the source code and a CMake
configuration file. Start by creating the project directory on your local machine. The
directory should look like this:
49
MyFirstCMakeProject/
CMakeLists.txt
main.cpp
• CMakeLists.txt: This file is the heart of CMake. It contains instructions that CMake
uses to configure the build process. It defines things like which source files to
compile, which compiler options to use, and how to organize the output.
• main.cpp: This is your source code, containing the C++ code that will be compiled
into an executable.
Let’s start by writing the code for the C++ program. Create the main.cpp file inside the
MyFirstCMakeProject directory. Here is a simple “Hello World” program to begin
with:
// main.cpp
#include <iostream>
int main() {
std::cout << "Hello, CMake!" << std::endl;
return 0;
}
This is a basic C++ program that prints ”Hello, CMake!” to the console. It contains the
minimal code needed to ensure the program compiles successfully and serves as a simple
test case for understanding how CMake is used to build a C++ project.
50
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
This is the simplest form of a CMakeLists.txt file, but as your projects become more
complex, this file will grow to include other configurations, such as libraries, dependencies,
compiler flags, etc.
build
mkdir build
cd build
This will be the directory where all the build-related files will be placed, such as Makefiles
or Visual Studio project files.
cmake ..
This command tells CMake to look for the CMakeLists.txt file in the parent
directory (..) and configure the project according to the specifications in that file.
Upon running the command, CMake will inspect the environment and attempt to detect
which compiler and tools to use for building the project. After processing the
CMakeLists.txt file, it will generate the necessary files for the build system. On a
typical system, this could include Makefiles, Visual Studio project files, or Ninja files,
depending on your platform.
At this point, CMake has successfully configured the project and written the necessary
build files in the build directory. These build files describe the process CMake will use
to compile your project.
If the build system is configured to use Make, which is common on Linux and macOS
systems, you can compile the project by running the following command:
make
This will invoke the make utility, which reads the generated Makefile and begins the
process of compiling your project. Make will compile the main.cpp source file into an
object file and link it to create the final executable.
The output will indicate the progress of the build process, and once the build is complete,
you should see something like:
This means the build was successful, and the MyFirstCMakeProject executable has
been created.
If you’re on a Windows system and CMake generated Visual Studio project files, you have
the option to either open the generated .sln solution file in Visual Studio and build the
project through the IDE or use the command line.
To build the project from the command line, use MSBuild as follows:
MSBuild MyFirstCMakeProject.sln
This command tells MSBuild to use the Visual Studio build system to compile and link
the project. After the build completes, you will have an executable ready to run.
54
1. Running on Linux/macOS
On Linux and macOS, the executable is typically located in the build directory. To run
the executable, use the following command:
./MyFirstCMakeProject
This will execute the program, and you should see the output:
Hello, CMake!
This confirms that the build was successful, and your program has run as expected.
2. Running on Windows
If you are using Visual Studio, you can run the executable directly from the IDE by
pressing the ”Start” button. Alternatively, after building the project, you can navigate to
the Debug or Release folder (depending on your build configuration) and double-click
the executable MyFirstCMakeProject.exe to run it.
1. CMake Configuration: When you run cmake .., CMake reads the
CMakeLists.txt file in the parent directory. It checks for the system’s environment,
55
such as the available compiler and toolchain, and configures the project according to the
options specified in the CMakeLists.txt file.
2. Build Generation: CMake generates build files that describe how to compile and link the
project. This could be Makefiles, Visual Studio project files, or Ninja files, depending on
your platform and configuration.
3. Compilation: When you run make or MSBuild, the build system compiles your source
code files into object files and links them to form the final executable.
4. Execution: Once the executable is built, you can run it and see the output. If all the steps
are followed correctly, you should see your program’s output displayed on the terminal or
IDE.
1.5.8 Conclusion
In this section, you learned how to create a basic CMake project, write the necessary C++ code,
configure the project using the CMakeLists.txt file, and then build and run the project. You
should now understand the basic workflow involved in working with CMake and be able to
create simple projects.
This foundational knowledge will serve as a springboard for diving into more advanced CMake
features, such as managing external libraries, building multi-target projects, handling
dependencies, and customizing build options. Understanding the basics of how CMake
configures and generates build files is crucial to unlocking the full potential of CMake in larger,
more complex projects.
Chapter 2
Fundamentals of CMakeLists.txt
56
57
beauty of CMake lies in its flexibility and portability; once a project is set up correctly, the same
CMakeLists.txt file can generate build files for different platforms without any
modification.
A simple project might only have one CMakeLists.txt file located at the root of the project
directory. However, for larger projects with multiple modules or libraries, each directory might
have its own CMakeLists.txt file that CMake will read recursively.
Here is an example of the simplest CMakeLists.txt file, which declares a minimum version
of CMake, defines a project, and specifies an executable target:
cmake_minimum_required(VERSION 3.10)
project(MyProject)
add_executable(MyExecutable main.cpp)
This minimal setup creates a project called MyProject and an executable named
MyExecutable, built from the source file main.cpp.
Every CMakeLists.txt file begins with the declaration of the minimum version of
CMake required to process it. This is essential because different versions of CMake may
support different sets of features, syntax, or functionality. By specifying the minimum
58
version, you ensure that the build system will only be configured using a version of
CMake that is compatible with your project's requirements.
For example:
cmake_minimum_required(VERSION 3.10)
This line tells CMake that the project requires at least version 3.10 of CMake to work
correctly. If the user tries to configure the project with an older version of CMake, an error
will be generated.
2. Project Declaration
For instance, the following line declares a project named MyProject that uses the C++
language:
In this case:
• VERSION 1.0 declares the project version (although version numbers are often
omitted for smaller projects).
• LANGUAGES CXX specifies that the project uses the C++ language. This is optional
in CMake 3.0 and later, as CMake automatically assumes C and C++.
59
3. Build Configuration
This section defines various settings and configuration variables that affect the overall
build process. The settings in this section can include the programming language
standards, compiler flags, and whether to use debug or release builds.
For example:
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_BUILD_TYPE Debug)
• set(CMAKE CXX STANDARD 17) ensures that the C++17 standard is used for
compiling C++ code. This is equivalent to passing the -std=c++17 flag to the
compiler.
• set(CMAKE BUILD TYPE Debug) specifies that the project should be built in
the Debug configuration, which will include debugging information and disable
optimizations.
For example:
add_executable(MyExecutable src/main.cpp)
In this case, MyExecutable is the name of the executable, and src/main.cpp is the
source file that will be compiled into it.
For example:
Here, MyLibrary is a shared library built from the src/my library.cpp file.
Once the targets (executables or libraries) are defined, you can modify their properties,
link them with other libraries, and define include directories. CMake provides commands
like target include directories(), target link libraries(), and
target compile options() to achieve this.
Example:
61
target_include_directories(MyExecutable PRIVATE
,→ ${PROJECT_SOURCE_DIR}/include)
This command tells CMake to include the include directory, which is located at the
root of the project (${PROJECT SOURCE DIR} is a CMake variable that holds the root
project directory).
• Linking Libraries: The target link libraries() command links the target
with other libraries. In this case, we are linking MyExecutable with
MyLibrary.
Example:
This makes sure that MyExecutable will be linked with MyLibrary when it is built.
• Compiler Options: You can also set compiler-specific options for individual targets
using target compile options().
Example:
For example, to find and link the Boost library, you would use the find package()
command as follows:
find_package(Boost REQUIRED)
target_link_libraries(MyExecutable Boost::Boost)
In this example, find package(Boost REQUIRED) searches for the Boost library
and ensures it is found. If Boost is not found, CMake will stop with an error. The
target link libraries() command then links Boost::Boost to
MyExecutable.
For large projects, you may want to break the project into smaller, manageable
submodules. CMake allows you to include other CMakeLists.txt files from
subdirectories by using the add subdirectory() command.
Example:
add_subdirectory(lib)
add_subdirectory(app)
In this example, CMake will process the CMakeLists.txt files in the lib and app
subdirectories. Each of these directories can have its own targets and build configuration,
making it easier to modularize the build process. The main CMakeLists.txt file
remains clean and high-level, delegating the detailed configuration to these subdirectories.
While not a functional part of the build process, comments are extremely important for
documenting the CMakeLists.txt file. CMake allows single-line comments using the
# symbol, and multiline comments can be handled by using an if() block with
endif().
For example:
if(FALSE)
# This block is not executed, but useful for documentation
endif()
Comments can clarify the purpose of certain sections or describe why specific options are
used, helping future developers (or yourself) understand the rationale behind the
configuration.
• Avoid Hardcoding Paths: Instead of hardcoding paths, use variables and CMake’s
built-in path handling functions to make your project portable. This ensures your build
configuration works across different environments and operating systems.
64
• Use Variables Wisely: Define and use variables to store file paths, flags, and other
project-specific settings. This makes the configuration more flexible and easier to
maintain.
• Modularize Large Projects: For large projects, break them into smaller subprojects and
use add subdirectory() to include these submodules in the build process. This
keeps your CMakeLists.txt files clean and modular.
• Write Clear and Descriptive Comments: It’s important to explain complex sections of
the CMakeLists.txt file. A well-commented file makes it easier to understand the
build process, especially when dealing with large or complex projects.
2.1.4 Conclusion
The CMakeLists.txt file is an essential part of every CMake-based project. Understanding
its structure and commands gives you the power to manage and customize the build process for
your C++ projects. By organizing the file into well-defined sections—such as setting up the
minimum CMake version, declaring the project, defining targets, handling dependencies, and
configuring the build—you ensure that your project is flexible, maintainable, and portable.
With the knowledge from this section, you should be able to write and understand the basic
structure of a CMakeLists.txt file, setting you on the path toward becoming proficient in
CMake and mastering your C++ build system.
65
This structure is sufficient to compile and link a simple C++ program. Once these basic elements
are understood, you can gradually extend the configuration to handle more complex tasks like
linking external libraries, defining multiple targets, or creating shared/static libraries.
Let’s begin by examining the key components and how to define them in a minimal CMake
project.
66
/MyMinimalProject
CMakeLists.txt
main.cpp
• CMakeLists.txt: The configuration file used by CMake to define the build instructions.
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.10)
This command sets the minimum version of CMake required to process the project. It
ensures that the CMake version being used is at least 3.10, which is necessary for certain
features that might be used in the configuration. This line is essential because it
guarantees that your CMake file will not break or behave unexpectedly on older versions
of CMake that do not support newer commands or features.
The minimum required version should be chosen carefully based on the features your
project needs. It is a good practice to specify a version that is compatible with the features
you plan to use, but also widely available across different environments.
2. project()
The project() command defines the name, version, and language of the project. In
this case, MyMinimalProject is the name of the project, and 1.0 is the version
number. The LANGUAGES CXX argument specifies that the project is written in C++
(CMake defaults to C and C++ if no languages are specified, but explicitly stating it can
prevent potential confusion).
This command also sets some project-wide variables, such as PROJECT NAME (which
holds the name of the project) and PROJECT VERSION (which holds the version
number). These variables can be used later in the build process or in the project
documentation.
set(CMAKE_CXX_STANDARD 17)
The set() command is used here to define the C++ standard version for the project. In
this case, we are specifying that the project should be compiled using the C++17 standard.
This is equivalent to passing the -std=c++17 flag to the C++ compiler. By setting this
in the CMake configuration, we ensure that the C++17 features are enabled across the
project.
You can change this value to 11, 14, 20, etc., depending on the version of C++ you wish
to use in your project. This is an important setting because CMake will automatically
propagate the standard across all targets in the project, reducing the need for repetitive
compiler flags.
4. add executable()
add_executable(MyMinimalExecutable main.cpp)
The add executable() command defines an executable target that will be built from
the provided source files. In this case, we are creating an executable named
MyMinimalExecutable from the main.cpp source file.
This is the key command that ties together your source files and defines the primary output
of the build process—an executable program.
Once you have the CMakeLists.txt file set up and the source files in place, you can
now build your project using CMake. Here are the steps to build a minimal CMake project
from the command line:
mkdir build
cd build
2. Run CMake: Run CMake from the build directory, specifying the path to the root
directory of the project (where the CMakeLists.txt file is located):
cmake ..
This command will generate the necessary build system files (such as Makefiles or
Visual Studio project files) based on the configuration in the CMakeLists.txt
file.
3. Build the Project: Once the build files have been generated, you can build the
project using the appropriate build tool. If you're using Makefiles, for example, you
can use the make command:
make
4. Run the Executable: After the build process completes, you can run the executable:
70
./MyMinimalExecutable
This should output the result of your main.cpp program, which, in this case, might
simply be a ”Hello, World!” message or any other code you include in the main.cpp
file.
• Simplicity: The project is small and simple, containing only one executable target and
one source file. This minimal structure is useful for learning and testing the most basic
functionality of CMake.
• Portability: Once you have a minimal CMake project set up, it is portable. By adjusting
only the CMakeLists.txt file, you can generate build files for different platforms,
such as Linux, Windows, and macOS, without having to modify your source code or
project structure.
• Extensibility: Although this project is minimal, it serves as a foundation for extending the
project as you add more features. For instance, you can later add more source files, link
external libraries, define multiple targets, or introduce more advanced CMake features like
custom build commands or conditional logic.
71
1. Adding More Source Files: If your project grows and you need to organize your code
into multiple files, you can simply add more source files to the add executable()
command.
Example:
3. Building Libraries: If your project requires shared or static libraries, you can use the
add library() command to define libraries in addition to executables.
4. Organizing Source Files: For larger projects, consider organizing source files into
directories. You can then use add subdirectory() to manage different parts of the
project.
2.2.6 Conclusion
In this section, we defined the minimal CMake project, which includes the essential components
necessary to build a simple C++ program. By creating a CMakeLists.txt file with the
minimum required CMake version, project declaration, executable definition, and compiler
settings, you have established a basic build system that is portable, extensible, and easy to
maintain.
72
Understanding this minimal structure serves as a foundation for more complex projects. Once
you have mastered this, you can start adding features like multiple targets, external
dependencies, custom build commands, and other advanced CMake functionality. This is just
the first step toward building more sophisticated and scalable CMake projects.
73
2. Syntax
74
cmake_minimum_required(VERSION <version>)
3. Example
cmake_minimum_required(VERSION 3.10)
In this example, the project will require at least CMake version 3.10. If CMake is run with
an older version, an error will occur, and the build process will not proceed. This is
important to ensure that your CMakeLists.txt file uses only features and commands that
are supported by the specified version or later.
• Compatibility: It guarantees that your CMakeLists.txt file will run on systems with
a version of CMake that supports all the commands used in the script. If the
minimum version is not specified, CMake will assume that any version of CMake is
valid, which could lead to compatibility issues.
• Error Prevention: By specifying the minimum required version, you prevent
unexpected errors related to incompatible features and behaviors that may be
introduced in future versions of CMake.
• Clarity: It provides clear documentation about the version of CMake needed to
build the project, which helps anyone working with the project (especially in a team
or open-source context) understand which version of CMake is compatible with the
project.
75
2.3.2 project
1. Purpose
The project command is used to define the project's name, version, and the
programming languages that the project uses. This command essentially declares the
project's identity and tells CMake how to configure the build process accordingly. It is
typically one of the first commands in a CMakeLists.txt file after the
cmake minimum required command.
2. Syntax
• <name>: The name of the project. This is the primary identifier for the project and
is often used to define the output executable or library names.
3. Example
In this example, we define a project named MyProject, with a version of 1.0, and we
specify that the project uses the C++ programming language (CXX).
• Project Name: The name specified in the project command is stored in the
PROJECT NAME variable, and this name is used throughout the project
configuration process. For example, the output executables and libraries will often
take the project name as part of their default names.
• Project Version: The version is stored in the PROJECT VERSION variable and can
be used for version-specific logic in the CMakeLists.txt file, such as selecting
different compiler flags, dependencies, or features based on the version of the
project.
The add executable command is used to define an executable target for your project.
This is the primary command for specifying the compilation of a source file or set of
source files into an executable program. It ties together the source code and tells CMake to
generate the corresponding binary after compilation.
This command can be thought of as the key step in creating an application or a runnable
program. It is typically followed by additional configuration to link libraries or specify
77
2. Syntax
• <name>: The name of the executable that will be generated. This name will be the
resulting file’s name (on Linux or macOS, the executable will not have an extension,
but on Windows, it will have a .exe extension).
• [source1] [source2] ...: A list of source files that will be compiled into
the executable. These can be C++ source files (.cpp), header files (.h), or other
files needed for the build process.
3. Example
In this example, CMake will compile the source files main.cpp and utils.cpp into
an executable called MyApp. After running cmake and make (or an equivalent build
tool), an executable file named MyApp will be generated.
• Target Name: The <name> parameter in add executable defines the name of
the output executable. It is best to use a name that clearly represents the program’s
purpose.
• Source Files: The source files listed in add executable are compiled together
to produce the final binary. It is important to ensure that the correct set of files is
included for the project to build successfully.
78
• Multiple Source Files: You can list multiple source files within the
add executable command, and CMake will handle their compilation. For larger
projects, it is also common to organize source files into directories and use CMake
variables or file(GLOB ...) to automatically collect source files.
• Dependencies: After defining an executable, you will often link it to other libraries
or dependencies using the target link libraries() command. This ensures
that the executable has access to the necessary functionality provided by external
libraries.
cmake_minimum_required(VERSION 3.10)
set(CMAKE_CXX_STANDARD 17)
Explanation:
3. set(CMAKE CXX STANDARD 17): Specifies that C++17 should be used for
compiling the project.
This project can be built by creating a separate build directory, running CMake, and then
building the executable.
2.3.5 Conclusion
In this section, we examined three essential CMake commands that form the backbone of a basic
CMake project: cmake minimum required, project, and add executable.
• project defines the project’s name, version, and programming language(s), establishing
the identity of the project.
Mastering these commands is critical for any developer working with CMake. They help
structure your project and ensure that the build process is compatible with different CMake
versions and setups, making your project easier to manage and maintain.
These commands form the foundation on which more advanced CMake features—such as library
management, external dependencies, and complex configurations—are built. By understanding
these fundamental commands, you can begin creating simple CMake projects, and as your needs
grow, you can expand the configuration to accommodate more complex requirements.
80
2. Syntax
• <type>: The type of the variable (e.g., STRING, PATH, BOOL, FILEPATH).
• [FORCE]: Optional. Forces the variable to be set even if it has already been defined
in the cache.
3. Example
• User-defined configurations: If you want to allow the user to set certain variables
during configuration (e.g., through the CMake GUI or via the command line), you
can use CACHE variables. These are ideal for settings that are configurable, such as
the installation directory, path to external dependencies, or build options.
• Controlling build options: You can use CACHE variables to allow users to toggle
features (e.g., whether to enable a particular module or build type) during the CMake
configuration phase.
cmake -DMY_PROJECT_PATH="/new/path/to/project" ..
3. If you want to force a value to be set for a cache variable, even if it has already been
set, you can use the FORCE option:
2. Syntax
set(<variable> $ENV{<env_variable>})
3. Example
set(MY_LIBRARY_PATH $ENV{LIBRARY_PATH})
In this example, the CMake variable MY LIBRARY PATH will be set to the value of the
environment variable LIBRARY PATH. The value of LIBRARY PATH is typically set by
the system and contains directories where libraries are located.
• Portable builds: ENV variables help make your build system more portable across
different machines by automatically picking up paths and settings defined in the
environment.
• PATH: Contains directories for executable binaries. You can use this to find tools
like compilers.
• HOME: Represents the user's home directory and can be used for paths to
configuration files, data directories, etc.
LOCAL variables are used to define variables that exist only within the scope of the
current directory or block (such as a function() or macro()). These variables are
not visible outside of the scope in which they are defined, ensuring that they do not
interfere with other parts of the build configuration.
LOCAL variables are the default type of variable in CMake. When you use the set()
command without explicitly specifying a variable type, it creates a LOCAL variable. These
variables are temporary and are discarded once the scope in which they are defined ends.
2. Syntax
85
set(<variable> <value>)
3. Example
In this example, MY LOCAL VAR is a LOCAL variable. It is available only within the
current scope (e.g., within the CMakeLists.txt file or a specific function or block)
and cannot be accessed outside of it.
• Temporary values: Use LOCAL variables when you need to store temporary values
that will only be used within a specific part of the CMakeLists.txt file, such as
within a loop or function.
• Avoiding conflicts: Since LOCAL variables are confined to the scope in which they
are created, they help avoid conflicts with other variables defined in different parts of
the project. This is especially useful when writing functions or macros that need to
operate without altering the global state.
• Scope control: LOCAL variables provide better control over where a variable is
accessible. They don’t ”leak” into other parts of the project, which can help keep the
build configuration clean and organized.
86
• CACHE variables are used for persistent values that need to be available across multiple
CMake runs and can be modified by the user.
• ENV variables are used to access system or environment variables from the host operating
system.
• LOCAL variables are temporary and only exist within the scope in which they are defined,
making them ideal for internal values that don’t need to persist beyond the current
configuration step.
2.4.5 Conclusion
Understanding the different variable types in CMake—CACHE, ENV, and LOCAL—is essential
for effective project configuration and build management. By selecting the right variable type for
the job, you can control the scope, persistence, and visibility of configuration values in a way
that helps keep your build system clean and maintainable.
• Use CACHE for user-configurable settings that should persist across multiple CMake runs.
87
• Use ENV for environment-specific values that need to be accessed during the build
process.
• Use LOCAL for temporary values that are needed only in a specific scope.
By mastering these variable types, you can ensure that your CMake configuration is both flexible
and efficient.
88
• Debugging: Printing variable values, checking paths, or verifying conditions during the
configuration phase.
• Status Updates: Informing users or developers about the progress or state of the build
configuration.
• Warnings and Errors: Alerting users to issues that need attention or stopping the
configuration process if critical errors occur.
The message() command can output messages at different levels of severity, allowing you to
categorize the output based on its importance.
89
message([<mode>] <message>)
• <mode>
: Optional. Defines the severity level of the message. It can be one of the following:
– AUTHOR WARNING: A warning message intended only for the author (developer),
not the user. It is similar to WARNING but can be filtered out by users in a
non-interactive setup.
– SEND ERROR: Prints an error message and halts the configuration process. This is
used to indicate a critical issue that prevents further configuration.
– DEPRECATION: Used to warn the user about the use of deprecated features or
practices in the CMake configuration.
• <message>: The text or string that you want to print. This can include variables, paths,
or any other information you want to display.
90
This will print the message "This is a simple message" in the standard output
during the configuration phase.
By default, message() uses the STATUS mode, which is suitable for informational
messages that are not critical to the build process.
This will display the message "Configuring project..." in the output, typically
with a green color to denote that it's informational.
You can use WARNING to display a warning message. This is helpful when you want to
inform users of a potential issue that does not block the build process but might require
attention.
This will print a yellow-colored warning message that informs users about a possible
issue, but it won't stop the build configuration.
91
If you encounter a situation that must be addressed before proceeding with the
configuration, you can use SEND ERROR to display an error message. This will not stop
the configuration immediately but will mark the build as having an error, preventing the
generation of makefiles or build files.
This will print the error message and continue with the configuration process, but the error
will be recorded, and no build files will be generated.
If the configuration cannot proceed due to a critical error, you can use FATAL ERROR.
This will immediately stop the configuration process and prevent any further steps.
When this message is encountered, CMake will stop immediately, and no build files will
be generated. This is useful for situations where proceeding without resolving the error
would lead to a broken or incomplete build.
This will display a message indicating that a certain feature is deprecated, helping guide
users or developers toward better practices.
Using ${} allows you to reference the value of a variable and incorporate it into the
message.
You can use message() to debug variable values during the configuration process. This
is particularly useful when you want to track the values of important variables at different
points in the CMakeLists.txt file.
One way to control verbosity is by setting the CMAKE VERBOSE MAKEFILE variable.
When set to TRUE, it enables more detailed output during the build process, which can be
helpful for debugging the build steps themselves. However, this does not directly control
message() output, but it can help control the level of detail you get from the build
process.
set(CMAKE_VERBOSE_MAKEFILE TRUE)
Another way to control output visibility is by setting the CMAKE MESSAGE LOG LEVEL
variable. This determines the threshold of message severity that is displayed. You can
choose to show only errors, warnings, or detailed status messages.
set(CMAKE_MESSAGE_LOG_LEVEL "WARNING")
This would display only warnings and errors, suppressing informational messages.
Chapter 3
CMake is a powerful tool that automates the process of building and managing complex
projects. However, before the actual build process takes place, CMake goes through a few
preliminary steps: configure, generate, and build. These three distinct phases are
essential to the CMake workflow and understanding their roles is crucial for effectively
using CMake to manage your C++ projects. In this section, we will delve into each of
these steps and explore what they involve, how they relate to each other, and why they are
important.
95
96
2. Generate: After the configuration step, CMake generates the build system files.
These files are specific to the generator you selected (such as Makefiles, Visual
Studio project files, or Xcode project files). The generated files are used by the build
tools to carry out the actual compilation and linking of the project.
3. Build: This step is where the actual compilation and linking of your project take
place. It involves invoking a build tool (like make, ninja, or the native build
system for IDEs such as Visual Studio or Xcode) to perform the build based on the
files generated in the previous step.
The configure step is the initial phase of working with CMake, and it is where CMake sets
up everything needed to generate the build files. During configuration, CMake performs
the following tasks:
(e.g., gcc, clang, or MSVC), system libraries, required tools, and other software
dependencies. If a required dependency is missing, CMake will either notify you or
attempt to download or build it.
cmake <path-to-source>
For example:
cmake ../my_project
This will trigger CMake to process the CMakeLists.txt files in the specified
directory and configure the project for the current system. Once complete, CMake
will have generated the necessary build system files for the next step.
Once the configuration step is complete, the next phase is the generate step. In this phase,
CMake generates the files required by the build system. The generation process is
determined by the generator you select, which could be a build tool or IDE-specific file
format.
CMake supports several types of generators, such as:
• Makefiles: This is the most common generator for Linux and macOS environments.
It produces a Makefile that can be used with the make tool to compile and link
the project.
• Ninja: A small, fast build system that is an alternative to make. If you specify -G
Ninja, CMake will generate build.ninja files for use with the ninja build
tool.
• IDE-Specific Generators: These are used to generate project files for various IDEs
like Visual Studio, Xcode, or CodeBlocks. For example, on Windows, running
cmake -G "Visual Studio 16 2019" .. will generate Visual Studio
project files that you can open directly in the IDE.
• Unix Makefiles: These are the default generator on many Unix-like systems,
producing a set of Makefile scripts that can be used to invoke make.
The generation step is invoked automatically as part of the configuration phase when you
run the CMake command. For example:
This command will configure and then generate Makefile build files in the build
directory.
After the configuration and generation phases are complete, you move to the build step.
This is the phase in which the actual compilation, linking, and final build of your project
occur.
During this step, the build tool (such as make, ninja, or Visual Studio) uses the files
generated in the previous phase to build the project. The build tool will execute the
instructions specified in the generated files to compile the source code, link the object files
into executables, and create libraries as defined in the CMakeLists.txt file.
make
• With Ninja: If you used the Ninja generator, you would use the ninja tool
to build the project:
100
ninja
• With IDEs (e.g., Visual Studio): If you generated project files for an IDE like
Visual Studio, you can build the project directly from within the IDE interface
or use the command line:
msbuild MyProject.sln
make install
This will install the project if you have set up the installation rules in your
CMakeLists.txt file using commands like install().
While the configure, generate, and build steps are distinct, they are interdependent and
occur in sequence:
1. Configure: Set up the project, inspect the system, define variables, and check
dependencies.
2. Generate: Create the necessary build files (such as Makefile, ninja, or
IDE-specific files) based on the configuration.
3. Build: Use the generated build files to compile and link the project into executables
or libraries.
101
It’s important to note that the configure step often only needs to be run once unless you
make changes to the configuration (such as adding new source files, changing build
options, or modifying dependencies). The generate step is run after configuration to
generate the appropriate build system files, and the build step can be run multiple times
during the development cycle, especially when making incremental changes to the project.
If you need to modify your build configuration (for example, to change compiler flags or
enable/disable features), you can re-run the configure and generate steps. When this
happens, CMake will read the configuration files again, update the cache, and regenerate
the build system files.
Sometimes, changes to the CMakeLists.txt files or other source files will require
cleaning the build directory before re-running the configuration. CMake supports
incremental builds, but certain changes might require a fresh configuration.
cmake ../my_project
This will reconfigure the project. If the configuration or generator has changed, CMake
will regenerate the necessary files.
3.1.8 Conclusion
Understanding the three key steps of the CMake workflow—configure, generate, and
build—is critical for efficiently managing and building C++ projects with CMake. These
steps are interdependent and serve distinct purposes in the overall process:
By following this workflow, you can efficiently manage complex builds, handle
dependencies, and customize your project setup according to your system and
development environment.
103
CMake supports a variety of generators that allow you to configure and generate build
files for different platforms, build systems, and Integrated Development Environments
(IDEs). These generators define the type of build system that CMake will create for your
project. Understanding how to run cmake with different generators—such as Ninja,
Makefile, and Visual Studio—is essential for customizing your build process to suit your
development environment.
In this section, we will explore how to use CMake with different generators and how they
affect the project setup and build process. We'll walk through the specifics of working
with each of these popular build systems, explaining their strengths and providing
practical examples.
When you run CMake, one of the key options you specify is the generator. The generator
determines what kind of build files CMake will produce. Each generator corresponds to a
specific build system or IDE, and selecting the right one ensures that CMake can interact
seamlessly with your development environment.
Some of the most common generators include:
These generators offer flexibility in terms of performance, platform compatibility, and user
preference. Let’s dive into how to configure CMake to use each of these generators and
the scenarios in which they are most useful.
Ninja is a small, fast build system with a focus on performance. It is often used in
environments where speed is important and works especially well for large projects. Ninja
operates by processing small build files that contain just enough information to trigger the
necessary build steps, making it significantly faster than traditional build systems in many
cases.
• Fast: Ninja is known for its speed in incremental builds. It minimizes the work
done by only rebuilding parts of the project that have changed.
• Minimalistic: Unlike other build systems that generate large build files, Ninja
generates concise and efficient build files, resulting in reduced I/O and faster
execution.
• Cross-Platform: Ninja can be used on multiple platforms (Linux, macOS, and
Windows), making it a great choice for cross-platform projects.
This command tells CMake to generate Ninja build files in the build directory, based
on the source code in the specified directory. After configuration, you can then use
Ninja to build the project:
ninja
mkdir build
cd build
cmake -G Ninja ../my_app
ninja
This sequence of commands will configure the project, generate the Ninja build files,
and then execute the build process.
After CMake generates the necessary Makefiles, you can build the project using the
make command:
make
mkdir build
cd build
cmake -G "Unix Makefiles" ../my_app
make
This will configure the project, generate the Makefile, and compile the project using
the make tool.
107
1. Visual Studio is one of the most widely used IDEs for C++ development,
particularly on Windows. CMake provides generators for multiple versions of Visual
Studio, which allow you to create Visual Studio project files (e.g., .sln,
.vcxproj) for your project. This is particularly useful for developers who prefer
the Visual Studio environment for building and debugging C++ projects.
2. Why Choose Visual Studio?
• IDE Support: Visual Studio is a powerful IDE that provides an extensive set of
features such as debugging, profiling, and an intuitive graphical interface for
project management.
• Native Windows Development: Visual Studio is the standard development
environment for C++ on Windows, making it ideal for targeting
Windows-specific APIs and libraries.
• Advanced Features: Visual Studio provides features like IntelliSense, a visual
debugger, and integrated testing tools that improve productivity.
This will create .sln files that can be opened directly in Visual Studio. After the
project is generated, you can open the .sln file in Visual Studio and build the
project from the IDE.
4. Example: Using Visual Studio with a Project
108
Let’s say you have a project located in C:\projects\my app. To configure and
generate Visual Studio project files, use:
mkdir build
cd build
cmake -G "Visual Studio 16 2019" C:\projects\my_app
This will generate a Visual Studio solution file (e.g., my app.sln) in the build
directory. You can then open this .sln file in Visual Studio and build the project.
Choosing the correct generator depends on your specific requirements and development
environment. Here are some guidelines to help you decide which generator to use:
• Ninja: Ideal for fast, efficient builds, especially in large projects. It’s a good choice
if you prioritize build speed and want a cross-platform solution.
• Makefile: Best for traditional Unix-based systems (Linux, macOS). It’s the default
for many open-source projects and is widely supported.
• Visual Studio: Perfect for Windows-based development using the Visual Studio IDE.
If you need to work in a Microsoft-centric development environment, generating
Visual Studio project files is the way to go.
Additionally, consider the complexity of your project. For simple, small projects, any
generator will work fine. For large projects with complex dependencies or custom build
steps, you might prefer Ninja or Makefile due to their simplicity and speed. Visual Studio
is best suited for projects that benefit from deep IDE integration, such as debugging or
visual design tools.
109
3.2.6 Conclusion
• Ninja offers fast and efficient builds, making it ideal for large projects.
• Makefile is the traditional choice for Unix-based systems and offers flexibility and
control over the build process.
• Visual Studio is a powerful IDE for Windows development and integrates
seamlessly with CMake for generating project files.
By selecting the appropriate generator, you can streamline the build process and integrate
CMake more effectively into your existing workflow. Whether you are developing on a
Linux, macOS, or Windows platform, CMake provides the tools you need to manage and
build your C++ projects efficiently.
110
When working with CMake, understanding how to effectively manage source files and
define output executables is a critical part of setting up your build process. CMake
simplifies this task by providing clear, intuitive mechanisms to specify the organization of
source files and control where the final executables and libraries are placed. This section
will explain how to manage source files and control output executables in a CMake
project, covering basic commands like add executable(), add library(), and
the use of source groups.
Source files are the building blocks of your project—they contain the code that will be
compiled into the final executable or library. CMake offers a variety of ways to manage
these files, from defining them explicitly to leveraging wildcard patterns and automatic file
discovery. Whether your project has a simple structure or a more complex, multi-directory
setup, CMake provides tools to help organize and include source files efficiently.
CMake supports different types of source files for various build targets:
The key here is to specify the source files correctly to ensure that they are compiled into
the appropriate output.
111
The most fundamental operation in CMake is defining the source files that will be
compiled into an executable. This is achieved using the add executable() command.
The general syntax is:
• <source1>, <source2>, ... <sourceN>: The list of source files (e.g., .cpp
files) that will be compiled to create the executable.
main.cpp
foo.cpp
foo.h
This tells CMake to create an executable named my app from the source files
main.cpp and foo.cpp. After running the build process, the output will be an
executable file named my app (or my app.exe on Windows).
112
For larger projects with multiple directories, you might want to automate the process of
collecting source files. CMake provides the file() and aux source directory()
commands to facilitate this.
aux_source_directory(src SOURCES)
add_executable(my_app ${SOURCES})
This command will search the src directory for all .cpp files and add them to the
SOURCES variable. The executable my app will then be built from all the source
files found in the src directory.
2. Using file(GLOB ...)
Alternatively, you can use file(GLOB ...) to collect all files matching a
specific pattern, such as all .cpp files in a given directory:
113
The file(GLOB ...) command is useful when you have files in a directory that
follow a specific naming pattern. However, it’s generally recommended to avoid
overusing this command, as it can make the build system harder to maintain when
files are added or removed.
Once you have specified the source files for your project, you need to define where the
resulting executable should be placed. CMake provides various commands and options to
control this.
This sets the output directory for the executable my app to a subdirectory called
bin inside the build directory.
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
This will result in my app and my app2 being placed in the bin directory inside
your build folder, making it easier to manage the outputs of your project.
Many projects may require the creation of multiple executables from different sets of
source files. In CMake, each executable or library you define is treated as a target, and
you can manage them separately.
Each target is built independently, and CMake will ensure that all source files are
compiled correctly and linked into the appropriate executables.
my_project/
CMakeLists.txt
bin/
CMakeLists.txt
my_app.cpp
my_app2.cpp
lib/
libfoo.cpp
add_subdirectory(bin)
add_executable(my_app my_app.cpp)
add_executable(my_app2 my_app2.cpp)
This modular structure helps keep the build process organized, especially when
dealing with large projects.
In addition to executables, CMake also makes it easy to define libraries. Libraries are
reusable collections of code that can be linked with other projects or executables.
target_link_libraries(my_app my_lib)
This links the static or shared library my lib to the executable my app.
3.3.7 Conclusion
Managing source files and output executables in CMake is an essential part of setting up a
successful build system. By using commands like add executable(),
add library(), and target link libraries(), you can define which source
files to compile, where to place your output executables, and how to manage libraries
within your project. Additionally, CMake offers powerful tools like file() and
117
Once you’ve configured your CMake project and generated the appropriate build files, the
next step is to actually build and install your project. These tasks are essential to the
process of turning your source code into a usable application or library. CMake simplifies
the build and install process with the cmake --build and cmake --install
commands, respectively.
In this section, we’ll delve into how to run these commands, what they do, and how they
fit into the broader CMake workflow.
The cmake --build command is used to compile and link the project, essentially
performing the build step. This command simplifies the process by abstracting away the
complexities of interacting directly with the build system (such as make, ninja, or
MSBuild), and it ensures consistency across different platforms and environments.
• [options]: These are optional flags that you can pass to modify the build
process (such as building a specific target or specifying the number of parallel
jobs).
2. Running cmake --build Without Arguments
In its simplest form, you can run cmake --build without any additional
arguments to build the default target (typically the project’s primary executable or
library):
This command will invoke the correct build system for your platform (e.g., make,
ninja, or MSBuild) and will build the default target. If you are in the build
directory, you can simply run:
cmake --build .
CMake will handle determining the correct tool and invoking it with the necessary
arguments to perform the build.
3. Specifying Build Targets
You can specify which target you want to build by using the --target option.
This is useful if you want to build a specific target, such as a particular executable or
library, rather than the default target.
For example, to build a specific executable (e.g., my app), you would run:
This command will only build the my app target, skipping the other targets in the
project. This is particularly useful in larger projects with multiple components, as it
allows you to build only the necessary parts of the project.
120
This tells the build system to use 4 processors to compile the project in parallel.
This removes the object files and other intermediate files, but leaves the
CMake-generated files (such as Makefiles or Visual Studio project files) intact.
After building the project, the next step is typically to install it. The cmake
--install command allows you to copy the built files (executables, libraries, headers,
etc.) to their final locations on the system, making them ready for use or distribution. This
step is crucial when you want to deploy your project or make it available for other
software to link to.
121
cmake -DCMAKE_INSTALL_PREFIX=/path/to/install/directory
,→ <path-to-source>
Alternatively, you can specify the installation directory during the cmake
--install command itself:
122
This will copy the necessary files (such as executables, libraries, and headers) from
the build directory to the installation directory defined by
CMAKE INSTALL PREFIX.
This installs only the my app executable, not any other components.
CMake allows you to define custom installation rules for specific files or directories using
the install() command within your CMakeLists.txt. This command provides
flexibility in deciding what gets installed and where.
For example, if you want to install an executable and a library, you can define the
following in your CMakeLists.txt:
3.4.4 Conclusion
The cmake --build and cmake --install commands are key components of the
CMake build process.
124
• cmake --build compiles and links the project, invoking the underlying build
system (such as make, ninja, or MSBuild) to produce the final executables,
libraries, or other targets.
• cmake --install copies the built project files to a specified installation
directory, making them ready for use or distribution.
By understanding how these commands work and how to configure them for your needs,
you can effectively manage the build and installation of your CMake-based projects.
Whether you're working on a small application or a large library, CMake provides a
flexible and consistent way to build and install your software across multiple platforms.
125
CMake provides a powerful mechanism for controlling the behavior of the build process
through various configuration options, one of the most crucial being
CMAKE BUILD TYPE. This variable determines the type of build configuration you want
to generate, such as a debug build, release build, or a custom build type. This section will
explore how to effectively control build options using CMAKE BUILD TYPE, the impact
of different build types, and how to customize and fine-tune the configuration of your
builds.
The CMAKE BUILD TYPE variable is one of the primary ways to control how your
project is built. It specifies the type of build configuration that CMake should use when
generating the build system. Typically, this is a setting you configure before you generate
your build files, and it can affect several important aspects of the build, such as
optimization levels, debugging information, and compiler flags.
CMake supports several common build types, each of which comes with different
optimizations and debugging settings. The most commonly used build types are:
126
• Debug: This build type generates debugging information and disables optimizations
to help with debugging. It's suitable when you need to inspect your code in a
debugger or need detailed information about your program’s state during execution.
• MinSizeRel: This build type is focused on minimizing the size of the compiled
binary while still providing optimizations. It’s often used for embedded systems or
other scenarios where small binaries are essential.
To set the build type in CMake, you can define CMAKE BUILD TYPE during the
configuration phase. This is typically done from the command line when you run the
cmake command to generate the build files.
127
1. Basic Example
For example, to configure the build for a Debug build type, you can run:
This command tells CMake to configure the project for debugging, meaning it will
generate the appropriate build system with debugging flags and without
optimizations.
Similarly, to generate a Release build type, you would use:
This will configure the project for release, ensuring that compiler optimizations are
enabled and debugging symbols are removed.
Each of these will configure CMake to use the corresponding set of compiler flags
and options.
Setting the CMAKE BUILD TYPE variable not only determines the compiler flags for
optimization and debugging but also influences several other aspects of the build:
128
• Compiler Options: The compiler flags vary between build types, such as
optimizations (-O3), debugging symbols (-g), and additional runtime checks (e.g.,
-fsanitize=address for sanitizers).
• Linker Flags: Depending on the build type, CMake may add different linker options.
For example, a release build might include additional flags to strip debugging
information or reduce the size of the final executable.
• Debugging Symbols: In the Debug build type, CMake ensures that debugging
symbols are included, which are necessary for debugging tools like gdb or lldb.
In contrast, in Release builds, debugging symbols are typically omitted to
improve performance and reduce the size of the binary.
• Optimization: Release builds enable higher levels of optimization, leading to faster
execution times, while debug builds avoid optimization to make stepping through
code easier in a debugger. RelWithDebInfo provides a middle ground by
optimizing the code while still including some debug information.
• Conditional Code: Some code in the project might only be included in specific
build types. For example, CMake allows conditional inclusion of certain code
depending on the build type, using constructs like if(CMAKE BUILD TYPE
MATCHES "Debug").
specify the build type during the configuration step with Visual Studio because the IDE
handles it dynamically.
When generating build files for Visual Studio, you do not need to specify the build type at
the configuration step:
After running this command, you can open the generated .sln file in Visual Studio and
select the build configuration (Debug, Release, etc.) from the IDE’s build settings.
Similarly, with Xcode, you can specify the configuration directly within Xcode once the
project has been generated.
CMake also allows you to customize build types by defining your own custom
configurations. For example, you may want to create a special configuration that combines
optimizations and additional warnings or debugging checks for a particular use case.
To do this, you can define custom build types by adding to the CMAKE CXX FLAGS
variable in your CMakeLists.txt. For example:
Then, when running the configuration, you can specify this custom type:
130
This approach provides flexibility, especially in complex projects or when dealing with
specialized requirements like performance profiling or testing.
• CMAKE CXX FLAGS DEBUG: This variable allows you to add or modify flags
specifically for the Debug build type.
• CMAKE CXX FLAGS RELEASE: This variable lets you adjust flags for the Release
build type.
• CMAKE CXX FLAGS RELWITHDEBINFO: Adjusts flags for the RelWithDebInfo
build type.
• CMAKE CXX FLAGS MINSIZEREL: Used to modify flags for the MinSizeRel
build type.
For example, you might want to modify the compiler flags to add more strict debugging or
security checks for the Debug build:
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}
,→ -fsanitize=address")
This will enable AddressSanitizer in the debug configuration, helping to catch memory
errors.
131
3.5.7 Conclusion
CMAKE BUILD TYPE is a powerful tool in CMake for controlling the type of build you
generate, whether you're working on a development/debugging phase or preparing for a
production release. By setting CMAKE BUILD TYPE, you can adjust compiler and linker
flags, optimization levels, and debugging information to match the needs of your project.
For single-configuration generators, setting the CMAKE BUILD TYPE is crucial for
customizing the build behavior. For multi-configuration generators like Visual Studio and
Xcode, this step is less critical, as these IDEs let you select the build configuration during
the actual build process. By combining CMAKE BUILD TYPE with other CMake features
like custom flags and multi-stage builds, you can fine-tune your project’s build process
and make it more efficient and easier to maintain.
Chapter 4
In CMake, when building C++ projects, libraries play an essential role in modularizing the
code, making it reusable, and simplifying the build process. These libraries can be either
static libraries or shared libraries, and understanding the differences between them is
key to managing dependencies and ensuring the correct build setup for your project. This
section dives into the key differences between static and shared libraries, their use cases,
and how they affect the build process.
4.1.1 Definition
132
133
copies the object code from the static library directly into the executable. As a result,
the executable does not need the static library at runtime because all the necessary
code is included within the executable itself.
• Shared Libraries: Also known as dynamic libraries or DLLs (Dynamic Link
Libraries) on Windows, shared libraries contain code that is linked at runtime
rather than during the compile time. The executable or other libraries that use the
shared library will dynamically load it during execution. This means the executable
depends on the shared library being present in the system at runtime to function
correctly.
• Static Libraries:
– The static library is compiled from the source code into object files. These
object files are then bundled together into a single library file, typically with a
.a extension on Unix-like systems and .lib on Windows.
– During linking, the entire library is copied into the executable. This process
results in larger executable files but does not require the library to be available
during runtime.
– Static libraries are commonly used for applications that require all dependencies
to be packaged into a single executable.
• Shared Libraries:
– A shared library is compiled and linked in a way that only the symbol
information (function names, data structures, etc.) is placed in the executable.
The actual code for these functions is placed in the shared library itself.
– The executable is not directly linked to the code but to the dynamic library file.
The linking to the library happens at runtime when the program is executed.
134
1. Static Libraries
Advantages:
Disadvantages:
• Larger Executables: The final executable file size is larger because it includes
all the code from the static libraries.
• No Shared Memory: Each running instance of the application gets its own
copy of the static library code, leading to higher memory usage when the
program is running.
• Updates Require Rebuilding: If the library code is updated, all executables
that use it must be recompiled and redistributed.
2. Shared Libraries
Advantages:
• Shared Memory: Multiple running instances of a program can share the same
loaded version of a shared library, which reduces overall memory usage.
• Easier Updates: When a shared library is updated, all executables that depend
on it will benefit from the update immediately without needing recompilation.
Disadvantages:
• Static Libraries:
– Ideal for standalone applications that do not need to rely on external libraries
at runtime.
– Useful in embedded systems, where minimizing external dependencies is
crucial.
– Used when you want to distribute a single file containing all necessary
components, such as in a proprietary application or for performance-sensitive
applications.
• Shared Libraries:
137
In CMake, you can specify whether to create a static or shared library using the
add library() command. Here’s how you would do that:
• Static Library:
• Shared Library:
You can also specify different build types (Release, Debug) and link libraries conditionally
based on the type of build being performed.
4.1.7 Conclusion
Choosing between static and shared libraries depends on the specific requirements of your
project. Static libraries are useful for reducing external dependencies, making the build
138
simpler, and ensuring that the application is completely self-contained. Shared libraries,
on the other hand, are more efficient in terms of memory and disk space, especially when
the same code is used across multiple applications or processes.
In CMake, it is straightforward to configure either type of library, but it is important to
understand the implications of your choice to make informed decisions about the
architecture of your project.
139
In this section, we will explore how to create a static library in CMake. A static library is
a collection of object files bundled together into a single file. This file can be linked into
executables at compile-time, resulting in larger executables but ensuring that no external
dependencies are required at runtime.
Creating a static library in CMake is simple, but understanding the process and
configuration is crucial to ensure it integrates seamlessly into your build system. This
section will cover the add library() command in CMake, the steps to create a static
library, and the typical workflow involved in using static libraries within a C++ project.
In CMake, the add library() command is used to create libraries, and the STATIC
keyword specifies that the library will be a static library.
Where:
• <library name>: The name you want to give to your library (e.g., MyLib).
• STATIC: Specifies that the library is a static library.
• <source files>: The list of source files to include in the library, such as .cpp
files.
For example, to create a static library MyLib from the source files my lib.cpp and
my lib utils.cpp, you would write:
Let’s walk through an example to create a static library MyLib in a CMake project:
MyProject/
CMakeLists.txt
src/
CMakeLists.txt
my_lib.cpp
my_lib_utils.cpp
main.cpp
141
cmake_minimum_required(VERSION 3.10)
project(MyProject)
4. src/my lib.cpp: This is the main source file for the static library.
// my_lib.cpp
#include "my_lib.h"
void MyLib::doSomething() {
// Implement some functionality here
}
5. src/my lib utils.cpp: Another source file that adds utility functions to the
library.
142
// my_lib_utils.cpp
#include "my_lib_utils.h"
6. main.cpp: The main executable that links to the static library MyLib.
#include <iostream>
#include "my_lib.h"
#include "my_lib_utils.h"
int main() {
MyLib lib;
lib.doSomething();
MyLibUtils utils;
std::cout << "Sum: " << utils.add(3, 4) << std::endl;
return 0;
}
7. Building the Project: After configuring CMake, you can build the project:
mkdir build
cd build
cmake ..
make
This process will compile the static library and link it to the main executable.
143
Once the static library is created, you need to link it to the executable that depends on it.
In CMake, this is done using the target link libraries() command.
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE MyLib) # Link the static library
This will ensure that MyApp links against the static library MyLib during the linking
phase, incorporating the object files from MyLib into the final executable.
When creating a static library, you often want to make sure that the header files used by
the library are accessible to other parts of the project. The
target include directories() command is used to specify the include
directories for the library.
• PUBLIC: This keyword makes the include directory available both to the static
library and to any target that links against the library (like MyApp).
• PRIVATE: If the include directory is only needed internally by the static library, you
can use PRIVATE instead.
144
If your static library has dependencies on other libraries (e.g., third-party libraries), you
need to link those libraries within your CMake configuration.
For example, if MyLib depends on another library OtherLib, you would modify the
CMake configuration as follows:
This ensures that when MyLib is linked to an executable or another library, the
OtherLib will also be included as a dependency.
Instead of hardcoding the list of source files, you can use CMake variables to collect them,
especially if you have a large number of files or want to avoid manual updates.
For example:
set(SOURCES
my_lib.cpp
my_lib_utils.cpp
)
This approach is useful when organizing larger projects with multiple source files.
If you want to install the static library for use outside the current project, you can use the
install() command. This is often done to create a CMake package for others to use.
For example:
This will install the MyLib static library to the lib directory and the headers to the
include directory.
• Larger Executables: The executable becomes larger since it includes all the
necessary object files from the static library.
146
• No Shared Memory: Each running instance of the program gets its own copy of the
library code, leading to higher memory consumption.
• Rebuild Required: If the static library is updated, you need to rebuild and
redistribute all executables that depend on it.
4.2.11 Conclusion
Creating a static library in CMake is a straightforward process that involves using the
add library() command with the STATIC keyword. Static libraries are a great
choice when you want self-contained executables and don’t mind the larger file sizes.
They are particularly useful for smaller applications or when you want to avoid runtime
dependencies. By following the steps in this section, you’ll be able to create, link, and
manage static libraries effectively in your C++ projects with CMake.
147
In this section, we will explore the process of creating a shared library in CMake, which
is also referred to as a dynamic library. Shared libraries differ from static libraries in that
they are linked at runtime, rather than being statically included within the executable.
This allows for smaller executables, easier updates, and shared code across different
applications, but it also introduces some challenges, such as dependency management at
runtime.
This section will guide you through the creation of shared libraries using CMake, explain
the key differences between static and shared libraries, and provide best practices for
managing and linking shared libraries in CMake projects.
A shared library (also called a dynamic library) is a collection of object files that are
linked at runtime. When an executable or another shared library is linked to a shared
library, the actual code is not included in the executable. Instead, a reference is created,
and the code from the shared library is loaded when the application is run.
• On Unix-like systems (Linux, macOS), shared libraries typically have .so (Shared
Object) extensions.
The key advantage of using shared libraries is that they can be shared between multiple
programs or processes, which helps reduce memory usage and the overall size of
executables. Additionally, shared libraries can be updated independently without requiring
148
the applications that use them to be recompiled, as long as the interface remains
compatible.
In CMake, shared libraries are created using the add library() command with the
SHARED keyword.
Where:
• <library name>: The name of the shared library you want to create (e.g.,
MyLib).
For example, to create a shared library MyLib from the source files my lib.cpp and
my lib utils.cpp, you would use:
MyProject/
CMakeLists.txt
src/
CMakeLists.txt
my_lib.cpp
my_lib_utils.cpp
main.cpp
cmake_minimum_required(VERSION 3.10)
project(MyProject)
4. src/my lib.cpp: This is the main source file for the shared library.
// my_lib.cpp
#include "my_lib.h"
void MyLib::doSomething() {
// Implement some functionality here
}
5. src/my lib utils.cpp: Another source file that adds utility functions to the
library.
// my_lib_utils.cpp
#include "my_lib_utils.h"
6. main.cpp: The main executable that will link to the shared library MyLib.
#include <iostream>
#include "my_lib.h"
#include "my_lib_utils.h"
151
int main() {
MyLib lib;
lib.doSomething();
MyLibUtils utils;
std::cout << "Sum: " << utils.add(3, 4) << std::endl;
return 0;
}
7. Building the Project: After configuring the CMake project, you can build it using
the following commands:
mkdir build
cd build
cmake ..
make
This process will compile the shared library MyLib, create the corresponding shared
object (.so or .dll), and link it to the executable MyApp.
Once the shared library is created, you need to link it to the executable that depends on it.
This is done using the target link libraries() command in CMake.
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE MyLib) # Link the shared library
This command tells CMake to link the MyApp executable with the MyLib shared library
during the linking phase.
Since shared libraries are loaded at runtime, you must ensure that the shared library is
available to the executable when it runs. This is typically managed using RPATH
(runtime library search path), which tells the system where to look for shared libraries.
You can configure RPATH in CMake with the following commands:
• $ORIGIN means that the library will be searched for in the directory where the
executable is located.
• Alternatively, you can specify an absolute path or relative path to the shared library.
When installing the shared library, CMake can also set the proper install paths for the
shared library and the executable:
This ensures that the shared library is placed in the correct lib directory, and the
executable is placed in the bin directory.
153
As with static libraries, you can use target include directories() to specify
the include directories for the shared library:
The PUBLIC keyword indicates that this include directory is necessary not only for the
library itself but also for any executable or library that links to it.
Shared libraries often require versioning to ensure that applications can link to a specific
version of the library. To manage versioned shared libraries in CMake, you can use the
following syntax:
set_target_properties(MyLib PROPERTIES
VERSION 1.0.0
SOVERSION 1
)
• SOVERSION: Specifies the API version of the shared library. This version is used
to ensure compatibility between the library and the applications that use it.
154
When building the shared library, CMake will automatically append the version and
SOVERSION to the library filename (e.g., libMyLib.so.1.0.0 or
libMyLib.so.1), which can help avoid version conflicts.
• Smaller Executables: The executable remains small because the shared library code
is not embedded within the executable.
• Memory Efficiency: Multiple running applications can share the same instance of
the shared library in memory, reducing memory usage.
• Easier Updates: You can update the shared library independently of the applications
that use it, as long as the interface remains backward compatible.
• Slightly Slower Startup: Shared libraries are loaded at runtime, which can result in
a slight delay in application startup.
155
4.3.10 Conclusion
Creating a shared library in CMake is a powerful way to modularize your C++ projects,
reduce the size of executables, and promote code reuse across multiple applications. By
using the add library(MyLib SHARED) command, you can easily create shared
libraries, link them to executables, and manage dependencies efficiently.
However, as with any dynamic linking, shared libraries introduce challenges such as
dependency management and versioning, which need to be carefully managed to ensure
the stability of your application. With proper setup and configuration, shared libraries can
significantly enhance the maintainability and scalability of your C++ projects.
156
Linking libraries is a fundamental concept when building C++ projects using CMake. The
target link libraries() command in CMake is used to specify which libraries
(static or shared) an executable or another library should be linked with. Proper linking
ensures that all necessary code from external libraries (whether static or shared) is
incorporated into the final executable or library.
In this section, we will explore the target link libraries() command in detail,
how it works with static and shared libraries, and provide practical examples to
demonstrate how to manage and link libraries effectively in your CMake project.
When you create a library or executable in CMake, it typically relies on external libraries
to provide functionality that isn't part of the C++ standard library. These external libraries
could be static libraries (which are compiled into the executable at compile time) or
shared libraries (which are linked at runtime). The target link libraries()
command is used to link an executable or library to one or more of these external libraries.
target_link_libraries(<target> <libraries>)
Where:
• <target>: The name of the target (either an executable or another library) that
you want to link the libraries to.
157
• <libraries>: The libraries that the target should be linked with. This can be a
single library or a list of libraries.
For example:
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE MyLib)
This command tells CMake to link the MyApp executable with the library MyLib.
When linking static libraries, CMake will ensure that the object files from the static library
are copied into the final executable during the linking phase. Static libraries are included
in the executable at compile-time, making the executable self-contained.
Here’s how to link a static library to an executable using
target link libraries():
# Create an executable
add_executable(MyApp main.cpp)
In this case, MyApp depends on MyLib, and the MyLib library will be included directly
into the final executable. The PRIVATE keyword indicates that MyLib is needed only for
MyApp and does not need to be propagated to other targets that depend on MyApp.
158
When linking shared libraries, the executable or library doesn’t include the shared library's
object files at compile time. Instead, a reference to the shared library is included, and the
actual code is loaded into memory when the program runs (at runtime). This means the
shared library must be available when the program starts.
# Create an executable
add_executable(MyApp main.cpp)
In this case, MyApp will not contain the code from MyLib directly, but it will rely on the
shared library at runtime. The shared library (MyLib.so, .dll, or .dylib) must be
found in the system’s library search paths when the executable is run.
The target link libraries() command can also be used to specify a target’s
dependencies on other libraries. These dependencies can either be private, public, or
interface:
• PRIVATE: The library is only required for the target itself. Other targets that link to
this target do not need to know about this library.
159
• PUBLIC: The library is required for the target, and any other target that links to this
target will also need to link to this library.
• INTERFACE: The library is not required for the target itself, but any target that
links to this target will need to link to the library.
# Create an executable
add_executable(MyApp main.cpp)
• MyLib is linked only for MyApp and won’t propagate to any target that depends on
MyApp (via the PRIVATE keyword).
• OtherLib is required both for MyApp and for any target that links against MyApp
(via the PUBLIC keyword).
160
You can link multiple libraries to a target at once. In this case, simply list the libraries
separated by spaces:
# Create an executable
add_executable(MyApp main.cpp)
In this example, MyApp is linked with three libraries: MyLib, OtherLib, and
AnotherLib.
In addition to linking libraries that are part of your project, you may need to link to system
libraries or third-party libraries installed on your system (such as pthread, zlib, or
boost). These libraries can be linked in the same way:
You can also use find package() to find installed libraries, such as Boost, and then
link them to your target:
find_package(Boost REQUIRED)
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE Boost::Boost)
161
In this case, CMake will search for the Boost library on your system and link it to MyApp.
When a target is linked to another target that itself has dependencies, CMake will
automatically propagate those dependencies, provided the correct visibility is specified.
For instance, if a shared library OtherLib is linked to MyLib, and MyLib is linked to
MyApp, the dependencies of MyLib will be propagated to MyApp if the PUBLIC or
INTERFACE keyword is used:
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE MyLib) # MyApp will also depend
,→ on OtherLib
Here, since MyLib is linked to OtherLib using PUBLIC, MyApp will also implicitly
depend on OtherLib when it links to MyLib.
In some cases, your libraries may not be located in standard directories (such as
/usr/lib or /lib). You can specify custom library paths using the
link directories() command:
162
link_directories(/path/to/custom/libs)
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE MyLib)
This command tells CMake to look for libraries in the specified directory when linking.
However, it’s usually better practice to use find package() for third-party libraries or
to specify the full path in target link libraries() to avoid issues with finding
libraries across different systems.
Here are some best practices to keep in mind when linking libraries with
target link libraries():
• Use find package() for system libraries: For commonly used external
libraries (such as Boost, OpenSSL, or zlib), use CMake’s find package()
functionality to automatically locate and link these libraries.
163
4.4.10 Conclusion
When working with libraries in CMake, controlling symbol visibility is crucial for
managing dependencies, improving build efficiency, and ensuring modularity. The
PUBLIC, PRIVATE, and INTERFACE keywords help define how include directories,
compile options, and linking dependencies are propagated between different targets.
Understanding these keywords allows developers to:
This section explains the role of these keywords in CMake, how they affect compilation
and linking, and provides real-world examples.
The PUBLIC, PRIVATE, and INTERFACE keywords in CMake define how compile
options, include directories, and library dependencies are applied to a target and its
consumers.
These keywords are primarily used with:
• PRIVATE
– The setting applies only to the target itself.
– It does not propagate to other targets that depend on this target.
• PUBLIC
– The setting applies to the target itself and also to any targets that link to it.
– Useful for dependencies that must be available both during compilation of the
library and when used by consumers.
• INTERFACE
– The setting applies only to targets that link to this library.
– The target itself does not use the setting.
– Useful for header-only libraries or when exposing dependencies to consumers
without affecting the library itself.
The following table summarizes how these keywords behave:
how include directories are applied to the library and its consumers.
MyProject/
CMakeLists.txt
src/
CMakeLists.txt
include/
MyLib.h
my_lib.cpp
my_lib_utils.cpp
my_lib_utils.h
main.cpp
– MyLib uses headers from include/, but these headers are not exposed to
consumers.
– If another target links against MyLib, it won’t see this include directory.
target_include_directories(MyLib PUBLIC
,→ ${CMAKE_CURRENT_SOURCE_DIR}/include)
– MyLib and any target linking to MyLib will use the include/ directory.
– If MyLib.h is part of the public API, it must be accessible to consumers.
• Using INTERFACE Include Directories
target_include_directories(MyLib INTERFACE
,→ ${CMAKE_CURRENT_SOURCE_DIR}/include)
Visibility Effect
PRIVATE MyLib needs OtherLib, but consumers of MyLib don’t need it.
PUBLIC MyLib and all targets linking to MyLib also need OtherLib.
INTERFACE MyLib itself doesn’t use OtherLib, but consumers must link to it.
The target compile options() command applies compiler flags to a target and
can use visibility keywords.
# MyLib and any target linking to MyLib will be compiled with -DDEBUG
target_compile_options(MyLib PUBLIC -DDEBUG)
Visibility Effect
PRIVATE -Wall applies only to MyLib.
PUBLIC -DDEBUG applies to MyLib and its consumers.
INTERFACE -O2 applies only to consumers of MyLib.
• Use PUBLIC when the dependency is required by both the target and its
consumers (e.g., a core utility library).
4.5.7 Conclusion
As C++ projects grow in complexity, they often transition from a single-file structure to a
multi-file structure. While small projects may have a single main.cpp file containing
all logic, larger projects require modular organization to improve maintainability,
reusability, and scalability.
CMake provides robust mechanisms to structure large-scale projects effectively. A
well-organized project:
171
172
This section explores how to design and implement a multi-file project structure,
including best practices and an example project setup using CMake.
single_file_project/
CMakeLists.txt
main.cpp
main.cpp:
#include <iostream>
int main() {
std::cout << "Hello, CMake!" << std::endl;
return 0;
}
This structure is suitable for small prototypes or simple scripts, but as the codebase
grows, this monolithic file becomes difficult to manage.
173
multi_file_project/
CMakeLists.txt # Root CMake file
src/ # Source code directory
CMakeLists.txt # CMake file for source files
main.cpp # Main application entry
MyLibrary.cpp # Implementation of MyLibrary
MyLibrary.h # Header for MyLibrary
Utilities.cpp # Additional source file
include/ # Header files
MyLibrary.h
Utilities.h
lib/ # External or custom libraries
ThirdPartyLib/
CMakeLists.txt
third_party.cpp
third_party.h
174
1. Root CMakeLists.txt
At the root of the project, CMakeLists.txt sets up the project name, minimum
required CMake version, and subdirectories for modular components.
cmake_minimum_required(VERSION 3.10)
project(MyProject)
# Add subdirectories
add_subdirectory(src)
add_subdirectory(lib/ThirdPartyLib)
Explanation:
• add executable(MyApp ...) – Creates an executable called MyApp
from main.cpp, MyLibrary.cpp, and Utilities.cpp.
• target include directories() – Ensures that headers from
include/ are available.
• target link libraries() – Links MyApp with an external library
(ThirdPartyLib).
3. lib/ThirdPartyLib/CMakeLists.txt (External Libraries)
The lib/ThirdPartyLib/ directory may contain third-party libraries or
custom-built libraries. To build it separately:
target_include_directories(ThirdPartyLib PUBLIC
,→ ${CMAKE_CURRENT_SOURCE_DIR})
2. Faster Compilation
3. Code Reusability
5. Easier Collaboration
include/MyLibrary.h # Declarations
src/MyLibrary.cpp # Implementations
• Example:
5.1.6 Conclusion
As C++ projects grow, breaking them into subprojects (or modules) improves
modularity, maintainability, and scalability. Instead of managing a large monolithic
codebase, CMake allows projects to be structured into smaller, independent subprojects,
each with its own CMake configuration.
CMake achieves this using the add subdirectory() command, which enables:
This section explores how to structure and build multi-module C++ projects using
add subdirectory() in CMake.
Syntax:
179
Example Usage
add_subdirectory(lib/MyLibrary)
MyProject/
CMakeLists.txt # Root CMake file
src/ # Main application source
CMakeLists.txt
180
main.cpp
App.cpp
App.h
lib/ # Libraries (subprojects)
MyLibrary/
CMakeLists.txt
MyLibrary.cpp
MyLibrary.h
Utils/
CMakeLists.txt
Utils.cpp
Utils.h
build/ # Build directory (created by CMake)
1. Root CMakeLists.txt
The main CMakeLists.txt should include all subprojects using
add subdirectory().
cmake_minimum_required(VERSION 3.10)
project(MyProject)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# Add subprojects
add_subdirectory(lib/MyLibrary)
add_subdirectory(lib/Utils)
add_subdirectory(src)
Key Points:
• add subdirectory(lib/MyLibrary) includes MyLibrary as a
subproject.
• add subdirectory(lib/Utils) includes Utils as a subproject.
• target link libraries(MyApp PRIVATE MyLibrary Utils)
links these subprojects to the main executable.
2. lib/MyLibrary/CMakeLists.txt (Defining a Subproject)
Each subproject should define its own library and specify include directories.
# Include headers
target_include_directories(MyApp PRIVATE
,→ ${PROJECT_SOURCE_DIR}/lib/MyLibrary)
target_include_directories(MyApp PRIVATE
,→ ${PROJECT_SOURCE_DIR}/lib/Utils)
# Link libraries
target_link_libraries(MyApp PRIVATE MyLibrary Utils)
2. Modular Compilation
183
5. Reusability
If a subproject is optional, you can exclude it from the default build using
EXCLUDE FROM ALL.
add_subdirectory(lib/ExperimentalFeature EXCLUDE_FROM_ALL)
cmake -DBUILD_EXPERIMENTAL=ON ..
184
5.2.8 Conclusion
As C++ projects grow in size and complexity, they often rely on external libraries for
additional functionality. Instead of manually managing and compiling dependencies,
CMake provides the find package() command, which enables projects to
automatically locate and use prebuilt libraries.
This section explores how to use find package() to locate external libraries,
configure dependencies, and integrate them into CMake projects effectively.
Basic Syntax
Example Usage
find_package(OpenSSL REQUIRED)
cmake_minimum_required(VERSION 3.10)
project(MyProject)
# Find OpenSSL
find_package(OpenSSL REQUIRED)
187
# Create an executable
add_executable(MyApp main.cpp)
# Link OpenSSL
target_link_libraries(MyApp PRIVATE OpenSSL::SSL OpenSSL::Crypto)
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE Boost::filesystem Boost::system)
find_package(OpenSSL QUIET)
if(NOT OpenSSL_FOUND)
message(FATAL_ERROR "OpenSSL not found! Please install it.")
endif()
find_package(OpenSSL REQUIRED)
find_package(OpenSSL QUIET)
if(NOT OpenSSL_FOUND)
message(FATAL_ERROR "OpenSSL not found! Please install it.")
endif()
cmake -DCMAKE_PREFIX_PATH=/path/to/custom/install ..
5.3.8 Conclusion
The find package() command is an essential tool in CMake for managing external
dependencies efficiently. By using it, projects can automatically detect installed
libraries, link dependencies correctly, and ensure compatibility across platforms.
By following best practices, CMake projects can integrate external libraries seamlessly,
improve modularity, and reduce manual configuration overhead.
191
Advantages of FetchContent
• Simplifies build setup – Avoids the need for users to manually install libraries.
• Direct integration with CMake targets – Enables easy linking of fetched libraries.
This section explores how to use FetchContent to fetch, configure, and integrate
dependencies in a CMake project.
192
include(FetchContent)
Basic Syntax
FetchContent_Declare(
<DependencyName>
GIT_REPOSITORY <URL>
GIT_TAG <TagOrBranch>
)
FetchContent_MakeAvailable(<DependencyName>)
fmt is a popular C++ formatting library. To include fmt in a CMake project dynamically,
use FetchContent:
cmake_minimum_required(VERSION 3.14)
project(MyProject)
include(FetchContent)
# Define an executable
add_executable(MyApp main.cpp)
Explanation
from GitHub.
2. FetchContent MakeAvailable(fmt) downloads and integrates fmt into
the build.
3. target link libraries(MyApp PRIVATE fmt) links the fmt library to
the executable.
This approach ensures that fmt is automatically downloaded and built if not already
available, eliminating the need for manual installation.
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.13.0
)
FetchContent_MakeAvailable(googletest)
add_executable(MyTests test.cpp)
Key Points
This ensures that GoogleTest is always available for unit testing, without requiring users
to install it manually.
find_package(fmt QUIET)
if(NOT fmt_FOUND)
include(FetchContent)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.0.0
)
FetchContent_MakeAvailable(fmt)
endif()
This approach allows the project to use a system-installed version of the library if
available, reducing unnecessary downloads.
FetchContent_Declare(
mylib
URL https://example.com/mylib.tar.gz
197
URL_HASH SHA256=abcdef1234567890
)
FetchContent_MakeAvailable(mylib)
This downloads a tarball, verifies its integrity with SHA256, and extracts it.
FetchContent_Declare(
mylib
SOURCE_DIR ${CMAKE_SOURCE_DIR}/third_party/mylib
)
FetchContent_MakeAvailable(mylib)
This allows bundling dependencies within the project instead of downloading them
from external sources.
5.4.8 Conclusion
• Ensuring that external dependencies are built before the main project.
This section explores how to use ExternalProject Add to integrate and manage
external dependencies effectively.
include(ExternalProject)
Basic Syntax
ExternalProject_Add(
<ProjectName>
GIT_REPOSITORY <URL>
GIT_TAG <TagOrBranch>
PREFIX <Directory>
CMAKE_ARGS <Arguments>
BUILD_COMMAND <BuildCommand>
INSTALL_COMMAND <InstallCommand>
)
• GIT REPOSITORY – URL of the Git repository (or URL for tarballs).
• PREFIX – The directory where the project will be downloaded and built.
cmake_minimum_required(VERSION 3.14)
project(MyProject)
include(ExternalProject)
# Define an executable
add_executable(MyApp main.cpp)
Explanation
202
ExternalProject Add provides full control over the build and installation process.
If a library needs specific configuration options, pass them using CMAKE ARGS.
ExternalProject_Add(
mylib
GIT_REPOSITORY https://github.com/example/mylib.git
GIT_TAG master
PREFIX ${CMAKE_BINARY_DIR}/mylib
CONFIGURE_COMMAND ./configure
,→ --prefix=${CMAKE_BINARY_DIR}/mylib/install
BUILD_COMMAND make -j4
INSTALL_COMMAND make install
)
Key Differences
This flexibility makes ExternalProject Add ideal for integrating projects that do
not use CMake.
If multiple external projects depend on each other, use DEPENDS to ensure correct build
order.
ExternalProject_Add(
mylib
GIT_REPOSITORY https://github.com/example/mylib.git
PREFIX ${CMAKE_BINARY_DIR}/mylib
)
ExternalProject_Add(
myapp
GIT_REPOSITORY https://github.com/example/myapp.git
PREFIX ${CMAKE_BINARY_DIR}/myapp
DEPENDS mylib
)
• Example:
ExternalProject_Add(myproj CMAKE_ARGS
,→ -DCMAKE_BUILD_TYPE=Release)
5.5.9 Conclusion
6.1.1 Introduction
When developing a C++ project with CMake, it's common to rely on external libraries to
extend functionality and avoid reinventing the wheel. Managing these dependencies
efficiently is crucial for maintainability and portability. CMake provides the
find package() command as a robust mechanism to locate and integrate installed
libraries on a system. This section delves into how find package() works, its usage
patterns, and best practices.
207
208
paths, and compile definitions required to use the library in a C++ project.
Basic Syntax
Arguments Explanation
• REQUIRED – If specified, CMake will terminate with an error if the package is not
found.
Suppose we want to use Eigen3, a popular C++ linear algebra library, in our CMake
project.
209
find_package(Eigen3 REQUIRED)
• This command searches for Eigen3 and ensures it is found before proceeding.
• If Eigen3 is not installed, CMake generates an error and stops the configuration
process.
If our project needs Eigen3 version 3.3 or later, we specify it like this:
Config-Mode (CONFIG)
• Used when the library provides its own CMake configuration files
(<PackageName>Config.cmake or
<PackageName>-config.cmake).
• Typically found in installation directories like /usr/lib/cmake/ or custom
locations.
• More reliable than Module-Mode because it contains package-specific settings.
Example:
Module-Mode (MODULE)
Example:
find_package(OpenGL REQUIRED)
• CMake will look for FindOpenGL.cmake in its module path (usually inside
CMake’s installation directory).
While this method works for many libraries, it is less preferred than Config-Mode
because it may not always align with the latest versions of the library.
find_package(Eigen3 QUIET)
if (NOT Eigen3_FOUND)
message(FATAL_ERROR "Eigen3 was not found! Please install it.")
endif()
find_package(SDL2 QUIET)
if (SDL2_FOUND)
212
Alternatively, users can provide the path at the CMake command line:
cmake -DCMAKE_PREFIX_PATH=/custom/install/path ..
Project Structure
/my_project
CMakeLists.txt
main.cpp
213
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyProject)
# Locate Eigen3
find_package(Eigen3 REQUIRED)
add_executable(my_project main.cpp)
main.cpp
#include <Eigen/Dense>
#include <iostream>
int main() {
Eigen::Matrix2d mat;
mat << 1, 2,
3, 4;
std::cout << "Matrix:\n" << mat << std::endl;
return 0;
}
Build Instructions
cmake -B build
cmake --build build
214
6.1.9 Conclusion
6.2.1 Introduction
When working with CMake, managing custom libraries or dependencies within your own
projects becomes more seamless if you create and use Config.cmake files. These files
provide a standardized way to define how your library should be located, linked, and
configured within other CMake-based projects. In this section, we’ll discuss how to create
Config.cmake files for custom libraries, which simplifies the integration of your
library into other projects.
3. Define Targets
Create imported targets to represent the library, so users can link to it easily.
4. Set Variables
Define variables that CMake users can refer to in their own CMakeLists.txt.
5. Package-Specific Settings
Define specific options that may be needed to configure the package (e.g., build
settings, compile options).
Let’s consider a simple C++ library called MyLib. Below is an example of what the
MyLibConfig.cmake file might look like.
# MyLibConfig.cmake
IMPORTED_LOCATION "${MYLIB_LIBRARIES}"
INTERFACE_INCLUDE_DIRECTORIES "${MYLIB_INCLUDE_DIR}"
)
• Set Variables: MYLIB INCLUDE DIR and MYLIB LIBRARIES define the paths
to the library’s headers and compiled libraries, respectively.
• Define Targets: The add library() command creates an imported target
MyLib::MyLib, which can be used by other projects to link with your library.
• Set Properties: set target properties() assigns properties like the
location of the library and the include directories to the target.
• Version and Metadata: The version and MYLIB FOUND variable provide metadata
that can be checked by users.
For other CMake projects to find your library, the Config.cmake file should be
installed into a known CMake search directory. Common locations include:
The CMAKE PREFIX PATH variable is often used to specify the path where CMake
should search for the Config.cmake files.
218
cmake -DCMAKE_PREFIX_PATH=/path/to/install ..
If you are distributing the library, you might want to install the Config.cmake file into
the following location in your library’s installation process:
install(
FILES MyLibConfig.cmake
DESTINATION lib/cmake/MyLib
)
This installation ensures that when another project uses find package(MyLib
REQUIRED), CMake can locate and use the MyLibConfig.cmake file.
Once the Config.cmake file is created and installed, other projects can use the
find package() command to locate and link your library. For example, in another
project, you can include the following in your CMakeLists.txt:
find_package(MyLib REQUIRED)
add_executable(my_project main.cpp)
When find package() is called, CMake will search for the MyLibConfig.cmake
file. If the library is found, it will configure the project to use MyLib. This includes
219
setting include paths, linking to the correct library, and creating an imported target for the
library.
If your library is evolving and you want to support multiple versions, you can specify the
version in the Config.cmake file. Additionally, you can enforce version checks by
specifying a version requirement in the find package() call.
Example:
# In MyLibConfig.cmake
set(MYLIB_VERSION "1.0.0")
This approach ensures that only the correct version of the library is linked with the
consuming project.
Libraries often have optional components or features that can be enabled or disabled. You
can add configuration options in the Config.cmake file to manage these features.
220
For example, suppose MyLib has an optional feature for SSL support. You could add an
option in the Config.cmake file like this:
# In MyLibConfig.cmake
option(MYLIB_USE_SSL "Enable SSL support" ON)
if(MYLIB_USE_SSL)
target_compile_definitions(MyLib::MyLib INTERFACE USE_SSL)
endif()
When using the library, consumers can enable or disable SSL support by setting the
option:
find_package(MyLib REQUIRED)
This way, the Config.cmake file provides flexibility in how the library is configured.
Here are some best practices for creating and maintaining Config.cmake files for
custom libraries:
6.2.9 Conclusion
Creating Config.cmake files is an essential skill for developers managing custom C++
libraries. These files provide a robust and flexible interface for users of your library,
helping them integrate it seamlessly into their own CMake-based projects. By following
the outlined best practices, you can ensure that your library is easy to use, versioned
correctly, and well-suited for complex dependency management.
222
6.3.1 Introduction
Managing external dependencies is a crucial part of any C++ project, especially when
building modular and scalable applications. While methods like find package() or
manually linking libraries have been widely used, CMake offers an elegant solution for
dynamically fetching and managing dependencies during the build process:
FetchContent. This feature allows you to download, configure, and use external
dependencies directly from the source at the time of the build, without the need for
pre-installed packages or system-wide configurations. In this section, we will explore how
to use FetchContent effectively for fetching dependencies dynamically.
FetchContent_Declare(
<content_name>
GIT_REPOSITORY <repository_url>
GIT_TAG <commit_or_branch_or_tag> # Optional: specify a version
DOWNLOAD_NO_EXTRACT <ON|OFF> # Optional: whether to extract
,→ content
)
• DOWNLOAD NO EXTRACT: If set to ON, CMake will only download the content but
will not extract it. Useful when dealing with archives that don't need to be extracted.
Let’s take an example where we want to fetch a library called fmt, a popular C++
formatting library. Here is how we can declare and fetch it dynamically using
FetchContent.
CMakeLists.txt Example:
cmake_minimum_required(VERSION 3.14)
project(MyProject)
Explanation:
225
While Git repositories are commonly used with FetchContent, CMake also supports
fetching from other sources like tarballs or zip files. The general idea remains the same,
but the parameters change slightly.
FetchContent_Declare(
my_library
URL https://example.com/my_library.tar.gz
)
FetchContent_MakeAvailable(my_library)
• URL: Specifies the location of the tarball or zip file to be downloaded and extracted.
If your project has a local subdirectory containing source files for an external dependency,
you can use FetchContent to manage it similarly.
FetchContent_Declare(
my_local_lib
URL_FILEPATH ${CMAKE_CURRENT_SOURCE_DIR}/libs/my_local_lib.tar.gz
)
FetchContent_MakeAvailable(my_local_lib)
Example:
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 8.0.1 # Fetch a specific release version
)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 94e57d7a098ae3289e6a658801c5b5487ef0981a # Specific
,→ commit hash
)
This ensures that the content will always be retrieved at that exact state, guaranteeing
reproducibility.
You can fetch multiple dependencies in the same CMakeLists.txt file by calling
FetchContent Declare() multiple times and using
FetchContent MakeAvailable() for each one.
cmake_minimum_required(VERSION 3.14)
project(MyProject)
# Fetch fmt
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 8.0.1
)
FetchContent_MakeAvailable(fmt)
# Fetch spdlog
228
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.9.2
)
FetchContent_MakeAvailable(spdlog)
In this example, both fmt and spdlog are fetched dynamically, and we link them to our
project.
Benefits:
Limitations:
229
• Build Time: Fetching and building dependencies during the configuration process
can increase the overall build time, especially if the dependencies are large or
numerous.
• Use Version Control: Always specify a version (tag or commit hash) to ensure
reproducibility. Avoid using branches like master unless necessary.
• Check CMake Version: Ensure that you’re using a version of CMake that supports
FetchContent (CMake 3.14 or newer).
6.3.10 Conclusion
6.4.1 Introduction
In this section, we’ll explore how to integrate pkg-config with CMake and use
find library() to locate libraries dynamically, enabling you to link external
dependencies seamlessly into your project. This integration is especially useful when
working with libraries that provide pkg-config files, such as GTK, OpenSSL, or
others commonly used in the open-source ecosystem.
• Library locations
• Version information
232
pkg-config works by reading .pc files, which are installed by the package manager
when a library is installed. These files contain the necessary information about the library
and are typically located in directories like /usr/lib/pkgconfig/ or
/usr/local/lib/pkgconfig/.
You can pass these flags to the compiler and linker manually, or automate the process with
CMake.
1. Enable pkg-config: First, ensure that CMake knows to look for pkg-config
by enabling the PkgConfig module.
2. Use find package(): You can then call find package() to find the
required library, relying on pkg-config to handle the search.
Example:
Let’s say we want to use the libpng library in our project. CMake has support for
pkg-config through its FindPkgConfig module, and we can use it like this:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyProject)
# Enable pkg-config
find_package(PkgConfig REQUIRED)
Explanation:
234
While pkg-config is helpful for retrieving metadata, CMake also has the built-in
find library() function for locating libraries in specified directories. This command
searches for a library by name and returns the full path to the library file. It's particularly
useful when the library is installed but doesn't have a .pc file for pkg-config to find.
Where:
• <VAR>: The variable that will store the path to the found library.
• name: The name of the library (without any prefixes or extensions, e.g., m for
libm.so).
• [path1 path2 ...]: Optional search directories to look for the library.
235
Here’s how you can use find library() to locate the mathematics library libm:
find_library(MATH_LIB m)
if(MATH_LIB)
message(STATUS "Found libm at ${MATH_LIB}")
else()
message(STATUS "libm not found")
endif()
In this example:
• find library(MATH LIB m): Searches for libm (the math library).
In some cases, you might want to use both pkg-config and find library()
together. This is common when a library is available through pkg-config, but its
dependencies are not declared or need to be manually located.
Here’s an example of using both pkg-config and find library() for a project
that depends on the libpng library (via pkg-config) and the libm math library
(using find library()):
236
# Enable pkg-config
find_package(PkgConfig REQUIRED)
# Create executable
add_executable(my_project main.cpp)
# Link libraries
target_link_libraries(my_project PRIVATE ${LIBPNG_LIBRARIES}
,→ ${MATH_LIB})
Explanation:
• find library(MATH LIB m): Locates libm using the find library()
function.
• target link libraries(): Links both libpng and libm to the project.
This approach is useful for combining the strengths of both methods: pkg-config for
easily finding well-supported libraries and find library() for libraries that don’t
237
• Use find package() with PkgConfig when available: CMake has built-in
support for pkg-config via the PkgConfig module, so use
find package(PkgConfig REQUIRED) whenever possible to handle
external dependencies more elegantly.
• Set CMAKE PREFIX PATH for Custom Locations: If your libraries are installed in
non-standard locations, you can set the CMAKE PREFIX PATH to help
pkg-config or find library() locate the libraries.
cmake -DCMAKE_PREFIX_PATH=/path/to/custom/libs ..
• Check for Dependency Availability: Always verify that the required dependencies
are found before proceeding with the build. You can use find package() or
check the results of find library() to ensure everything is in place.
if(NOT LIBPNG_FOUND)
message(FATAL_ERROR "libpng not found!")
endif()
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBPNG REQUIRED libpng>=1.6)
238
6.4.7 Conclusion
6.5.1 Introduction
In this section, we’ll dive deep into how to integrate vcpkg and Conan with CMake,
providing you with powerful tools for dependency management, versioning, and
cross-platform builds. We will cover each tool’s installation, configuration, and usage
within CMake.
vcpkg
Conan
Conan is another popular C++ package manager that focuses on providing a
decentralized, user-centric way of managing dependencies. Unlike vcpkg, which uses a
central repository, Conan supports multiple repositories and gives developers the
flexibility to host and manage their own packages.
Key features of Conan include:
vcpkg simplifies the process of installing libraries and managing them for CMake-based
projects. Below is a detailed guide on how to use vcpkg in your CMake workflow.
Installing vcpkg
241
1. Clone the vcpkg repository: First, clone the vcpkg repository from GitHub:
2. Bootstrap the vcpkg build system: Inside the vcpkg directory, run the bootstrap
script to build the package manager:
On Windows:
.\bootstrap-vcpkg.bat
On Linux/macOS:
./bootstrap-vcpkg.sh
3. Install packages with vcpkg: Use vcpkg to install C++ libraries. For example, to
install the fmt library:
To make vcpkg work with your CMake project, you need to set the appropriate
environment variable and tell CMake where vcpkg is located.
1. Set the CMake Toolchain File: When invoking CMake, specify the vcpkg
toolchain file to let CMake know that you want it to use vcpkg for package
management:
242
cmake
,→ -DCMAKE_TOOLCHAIN_FILE=<path_to_vcpkg>/scripts/buildsystems/vcpkg.cma
,→ ..
Replace <path to vcpkg> with the actual path to your vcpkg installation.
2. Linking Installed Libraries: After installing a package using vcpkg, you can link
it to your project using find package() or target link libraries() as
you would with any other CMake-managed library.
For example, after installing fmt with vcpkg, you can use:
find_package(fmt REQUIRED)
target_link_libraries(my_project PRIVATE fmt::fmt)
Conan is another excellent option for C++ package management, providing an alternative
approach to managing dependencies with a focus on flexibility, versioning, and
decentralized package repositories. Let’s go over how to integrate Conan with your
CMake project.
Installing Conan
1. Install Conan: You can install Conan via pip (Python's package manager):
243
[requires]
fmt/8.0.1
[generators]
cmake
The --build=missing flag ensures that any missing packages are built from
source.
2. Link with CMake: After installing dependencies, you need to tell CMake to use the
Conan-generated configuration files. Conan generates CMake files that set up
environment variables and paths for the dependencies.
244
include_directories(${CMAKE_BINARY_DIR}/conan_includes)
Alternatively, if you are using the cmake generator, include the Conan CMake
module:
include(${CMAKE_BINARY_DIR}/conan.cmake)
3. Link the Libraries: With the dependencies installed and configured by Conan, you
can link the libraries to your project in the usual way:
Both vcpkg and Conan are popular and powerful tools for package management in C++.
However, they have different strengths and use cases:
• If you are working on a cross-platform C++ project and want a simple, streamlined
way to manage dependencies.
• If you need decentralized package management and control over your package
repository.
• If you need detailed version control and fine-grained control over the dependencies
of your project.
6.5.6 Conclusion
Both vcpkg and Conan provide excellent solutions for managing external dependencies
in CMake-based C++ projects. While vcpkg offers a simpler, centralized approach with
easy integration into CMake, Conan offers more flexibility, version control, and
decentralized package management. Depending on your project’s needs, either tool can
significantly simplify dependency management and ensure a more consistent, reproducible
build process.
By leveraging the power of these package managers, you can focus more on developing
your application and less on handling dependency configurations.
Chapter 7
CMake is a cross-platform tool that provides users with the ability to manage the build
process of software projects in a compiler-independent manner. One of the most
convenient ways to interact with CMake is through its graphical user interface (GUI),
which offers a user-friendly way to configure projects, set up build paths, and generate
platform-specific build files. This section focuses on how to use the CMake GUI on both
Windows and Linux platforms, giving you insights into its interface and functionality.
247
248
• Configure Projects: Set CMake variables, choose generators, and set specific paths
for building your project.
• Generate Build Files: Create platform-specific build files (e.g., Makefiles, Visual
Studio project files).
• Troubleshoot Configuration Errors: View error messages and warnings related to
the project configuration.
Before you can use the CMake GUI, you'll need to install it. Here are the installation steps
for both Windows and Linux.
For Fedora:
2. Verify Installation:
• After installation, you can open the CMake GUI by running the following
command in a terminal:
cmake-gui
This should launch the CMake GUI application, ready to configure and generate
build files.
Upon launching the CMake GUI, the interface will look slightly different on Windows and
Linux but functions similarly across both platforms. Below is a breakdown of the interface
elements:
1. Initial Configuration
250
When you first open the CMake GUI, you will be prompted to provide paths for both
your source directory (where the CMakeLists.txt file is located) and the binary
directory (where the build files will be generated).
• Source Directory: This is the location of the source code of your project,
typically the folder containing your CMakeLists.txt.
• Binary Directory: This is the folder where the generated build files will be
stored. It's common to create a separate build folder within your project’s
root directory for this purpose.
2. Setting CMake Variables
In the CMake GUI, the main window consists of several buttons and areas where you
can input information:
• CMake Cache Entries
: On the left side of the GUI, there is a list of variables that CMake uses for the
configuration. These variables can be set manually, or CMake can automatically
populate them based on the system configuration. For instance:
– CMAKE BUILD TYPE: Specifies the build type (e.g., Debug, Release).
– CMAKE INSTALL PREFIX: Defines the installation directory after
building the project.
– CMAKE CXX COMPILER: Specifies the C++ compiler to use.
• Edit Variables: You can double-click on any variable in the list to modify its
value. If you're unsure about a variable, you can click the Help button for a brief
description.
3. Generating Build Files
Once you have configured your project in the GUI, you can generate the appropriate
build files for your platform. The “Configure” and “Generate” buttons in the CMake
GUI allow you to:
251
1. Configure: CMake will analyze the source and binary directories, attempt to
detect your system’s properties, and populate the GUI with relevant values (e.g.,
compilers, libraries). If there are issues or warnings, they will be displayed in
the output window.
2. Generate: After configuration, you can press the Generate button to create the
build files. This step will generate files such as:
• Makefiles (on Linux/macOS)
• Visual Studio project files (on Windows)
• Ninja build files (if Ninja is selected as the generator)
4. Advanced Options
The CMake GUI also allows you to specify advanced settings:
• Choose a Generator
: CMake supports different generators for different platforms, such as:
– On Windows, you may choose Visual Studio or MinGW.
– On Linux, you may select Unix Makefiles or Ninja.
• CMake Log: The log section provides output messages that help debug or
verify configurations.
5. Building the Project
While the CMake GUI does not directly build the project, it helps set up the build
environment. After generating the build files, you will typically use your chosen
build system (e.g., make, Visual Studio) to actually compile and link your project.
For instance:
• On Linux, you would navigate to the build directory and run make or ninja
(depending on the generator you chose).
• On Windows, you would open the generated Visual Studio solution and build
from there.
252
Though the CMake GUI is very similar on both platforms, some Linux-specific behavior
should be noted:
• On Linux, the CMake GUI relies on the Qt framework for its interface, so having the
appropriate Qt libraries installed is essential.
• The Generate button will allow you to choose between different build systems, such
as Unix Makefiles or Ninja.
On Windows, the process is mostly the same as on Linux, with a few key differences:
• The most notable difference is the choice of Visual Studio generators, which allow
you to create Visual Studio project files directly from the GUI. Once you generate
these files, you can open and build them inside the Visual Studio IDE.
• If you're using MinGW or another alternative to Visual Studio, the CMake GUI will
configure for those environments as well.
7.1.6 Troubleshooting
The CMake GUI offers helpful error messages if something goes wrong during
configuration or generation:
• Missing Dependencies: If CMake can’t find required libraries or tools, it will show
warnings or errors. You can often resolve these by specifying the correct paths in the
GUI or ensuring that necessary software is installed.
253
7.1.7 Conclusion
Using the CMake GUI is an effective way to simplify the process of configuring and
building C++ projects. Whether you're working on Windows or Linux, the GUI provides
an intuitive interface that guides you through configuring your project, setting CMake
variables, and generating platform-specific build files. With its built-in error messages and
advanced options, it is an excellent tool for both beginners and advanced developers alike.
254
While the CMake GUI is a powerful tool for configuring and generating build files with a
graphical interface, many advanced users prefer the Command-Line Interface (CLI)
because of its flexibility, automation capabilities, and faster workflows. The CMake CLI
allows you to execute the same tasks that you would with the GUI, but through terminal
commands, making it easier to integrate CMake into scripts, continuous integration (CI)
pipelines, and large-scale automated builds.
In this section, we will walk through how to use CMake’s Command-Line Interface to
configure, generate, and build CMake projects. We’ll also cover important commands and
options that you can use to streamline your build process.
The CMake CLI is a text-based tool that uses commands to control how CMake configures
and generates build files. The basic syntax of the cmake command is as follows:
Where:
You can use the CMake CLI on both Windows and Linux (and other Unix-based systems),
and its commands are consistent across platforms, though the underlying build system
may differ.
255
Let’s go through a typical CMake CLI workflow, from configuring the project to
generating build files and building the project.
mkdir build
cd build
This step ensures that all generated files (such as Makefiles or Visual Studio project
files) are placed in the build directory rather than in the source directory.
cmake <path-to-source>
For example, if your project’s source code is in the parent directory, you would run:
cmake ..
– CMAKE BUILD TYPE: Specifies the build type, which typically defines
whether you want a Debug or Release build.
For example:
cmake -DCMAKE_BUILD_TYPE=Release ..
This sets the build type to Release, optimizing the code for performance.
– CMAKE GENERATOR: Defines the build system generator, such as Makefiles,
Ninja, or Visual Studio.
Example for Unix Makefiles:
cmake -G "Ninja" ..
Once you run the cmake command with the necessary options, CMake will generate
the build files in the build directory. The generated files are specific to the platform
and generator you’ve selected (e.g., Makefiles, Visual Studio solution files, etc.).
CMake provides a range of commands and options for advanced configurations. Here are
some key ones that you will likely encounter when using the CLI:
1. cmake --build
After configuring the project with CMake, you can invoke the build process directly
from the CLI using the cmake --build command. This command allows you to
build your project using the same configuration you generated earlier, without
needing to switch to a separate build tool or IDE.
For example, to build the project:
cmake --build .
This will use the default generator (e.g., Makefiles or Ninja) to build the project in
the current directory.
You can also specify the number of parallel jobs to run during the build process
(useful for speeding up builds, especially in large projects):
cmake -DCMAKE_INSTALL_PREFIX=/custom/install/path ..
cmake -DCMAKE_CXX_COMPILER=g++-9 ..
3. cmake --install
Once your project is built, you can use the cmake --install command to
install the project into the specified location. This is especially useful for projects
that require installation steps, such as libraries or applications that need to be copied
to a system-wide location.
For example:
cmake --install .
This will install the project using the installation paths defined during the
configuration process.
4. cmake --version
To check the installed version of CMake, you can use the following command:
cmake --version
This is useful when you want to confirm that you are using the correct version of
CMake, particularly in environments with multiple versions.
5. cmake --help
To view a list of available options and commands for CMake, you can use the help
command:
259
cmake --help
For more advanced workflows, the CMake CLI provides several additional features to
support complex build processes.
This reduces the need to manually set multiple flags and ensures consistency in how
your project is configured.
#!/bin/bash
Such automation is especially useful in CI/CD pipelines and for teams working on
large projects with many dependencies.
While the CMake CLI is powerful and flexible, there are a few common issues you might
encounter:
• Build Type Issues: If you’re not specifying the CMAKE BUILD TYPE, CMake
might default to an empty configuration, leading to unexpected results. Always
explicitly set the build type if needed.
261
• Generator Errors: If you specify an invalid generator (e.g., Visual Studio version
mismatch), CMake will return an error. Ensure that the chosen generator matches
your system’s available build tools.
7.2.6 Conclusion
The Command-Line Interface (CLI) provides CMake users with the flexibility to
configure, generate, and build projects directly from the terminal. This is especially useful
for automating builds, integrating with continuous integration systems, and streamlining
workflows. Mastery of the CMake CLI is a valuable skill for C++ developers, as it gives
them complete control over the build process while maintaining portability across
platforms. Through the combination of simple commands and powerful flags, the CLI
allows for highly customizable and efficient project management.
262
One of the most powerful features of CMake is its ability to generate build files for
different platforms, compilers, and build systems. This flexibility is enabled by CMake’s
use of generators, which control how the build process is handled once CMake has
configured the project. A generator specifies the type of build system (e.g., Makefiles,
Visual Studio project files, Ninja build files) that CMake will use to create the final output.
In this section, we will explore how to configure different generators in CMake, such as
-G "Ninja", -G "Unix Makefiles", and others. We’ll discuss when and why
you might choose one generator over another, and how to properly configure them using
the command-line interface (CLI).
A generator in CMake is a template that defines how build files will be created based on
the current platform and build tools. When you run the cmake command with a specific
generator, CMake uses that generator to produce the build system files that your chosen
platform requires.
For instance:
• On Linux, CMake may generate Makefile files that can be processed by the
make tool.
The -G option in the cmake command is used to specify which generator to use. You can
also choose from a wide variety of generators depending on your project’s needs.
Here are some of the most common CMake generators, how they work, and when you
might want to use them.
1. -G "Unix Makefiles"
The Unix Makefiles generator creates traditional Makefiles that can be
processed by the make build tool. This is one of the most widely used generators on
Linux and other Unix-like systems, including macOS.
Use Case:
• When working on Linux, BSD, or macOS systems with make as the build tool.
• For projects that do not require a specific IDE and prefer the command-line
interface.
Example:
This command will generate a Makefile that can later be used to build the project
with make:
make
Advantages:
Disadvantages:
• Slower compared to some other build tools like Ninja, especially for large
projects.
• Lacks advanced parallelization features found in other build systems.
2. -G "Ninja"
The Ninja generator is designed for fast builds and provides better parallelization
than traditional make-based builds. Ninja is often preferred for large projects where
speed is a priority. It also has a simpler, more efficient design than Make, which
leads to faster incremental builds.
Use Case:
Example:
cmake -G "Ninja" ..
Once the project is configured with Ninja, you can build it using:
ninja
Advantages:
Advantages:
• Provides seamless integration with Visual Studio.
• Supports advanced IDE features like debugging, code navigation, and profiling.
• Can be used for both C++ and C# projects in Visual Studio.
266
Disadvantages:
4. -G "Xcode"
The Xcode generator creates an Xcode project on macOS, enabling developers to
build their projects using the Xcode IDE. This is an ideal choice for macOS
developers who want to integrate with Xcode’s graphical interface, testing tools, and
simulator support for iOS/macOS applications.
Use Case:
• When working on macOS and targeting the Xcode IDE for development and
testing.
• Ideal for iOS and macOS applications, especially if you need to use the Xcode
interface for design, profiling, or app store submissions.
Example:
cmake -G "Xcode" ..
Advantages:
Disadvantages:
267
Advantages:
• Allows using make on Windows with the MinGW toolchain.
• Suitable for cross-platform C++ development targeting both Windows and
Unix-like systems.
Disadvantages:
• Requires MinGW to be installed and properly configured.
• Not as widely used as Visual Studio on Windows, so you may encounter
compatibility issues in certain environments.
• Toolchain: If you are using a specific build tool (e.g., Ninja, Make, or Visual
Studio), select the corresponding generator.
• Project Size: For larger projects, generators like Ninja are preferred due to their
faster incremental builds and parallelization features.
• IDE Preferences: If you prefer working in a specific IDE, such as Visual Studio or
Xcode, select the generator that integrates with that IDE.
• Linux/Unix Project: If you are on a Linux or Unix system and working with a
command-line environment, you would typically use -G "Unix Makefiles"
or -G "Ninja".
• Windows Developer: On Windows, if you are using Visual Studio, you would use
-G "Visual Studio 16 2019". If you are using a different build system like
MinGW, you would use -G "MinGW Makefiles".
In some cases, you may need to test or build your project with different generators. You
can simply run CMake with different -G flags, specifying a different generator for each
configuration. Keep in mind that some build systems (such as Visual Studio) may create
269
multiple configuration files (e.g., solution files, project files) for different build types
(Debug, Release).
Sometimes, you may run into issues while configuring CMake with a specific generator.
Some common issues include:
• Incorrect Generator Syntax: Make sure the generator string is entered correctly.
CMake is very specific about the exact names of the generators.
• Missing Tools: Some generators, like Ninja or MinGW, require the corresponding
tools to be installed. If CMake cannot find the necessary toolchain, it will generate
an error message.
• Generator Compatibility: Some generators might not work well on certain
platforms or with specific versions of CMake. Always refer to the CMake
documentation to verify compatibility.
7.3.6 Conclusion
CMake’s ability to support multiple generators makes it a highly flexible tool for
managing cross-platform and cross-toolchain projects. Whether you are targeting
traditional Makefiles, Ninja for speed, Visual Studio for IDE integration, or Xcode for
macOS/iOS development, CMake makes it easy to configure and generate the right build
files for your platform and toolchain. By understanding the various generator options and
when to use them, you can streamline your build process and improve productivity in your
C++ projects.
270
In this section, we’ll explore ccmake, how it works, how to manage project settings and
options through it, and how it compares to both the full CMake GUI and the
command-line interface.
ccmake stands for CMake curses interface, and it’s a command-line tool that provides
an interactive interface to configure CMake options for a project. The main purpose of
ccmake is to provide a text-based configuration interface that can be run inside a
terminal. This tool allows users to:
• Modify project settings such as build types, installation paths, and optional features.
• Save and apply changes interactively without needing to edit configuration files
manually.
Unlike the full graphical CMake GUI, ccmake works entirely in the terminal, making it
useful for remote development or environments where a GUI is impractical. It’s also often
preferred by advanced users who are comfortable with terminal-based workflows but still
want to take advantage of the interactive nature of CMake's configuration process.
271
ccmake --version
If the tool is not installed, you may need to install CMake via your system’s package
manager. For example:
Using ccmake is straightforward and involves running it in the build directory where
CMake has been previously configured. Here's the basic workflow:
1. Navigate to the Build Directory: It's a best practice to use an out-of-source build
directory (to keep source files clean). If you haven’t already created a build directory,
do so:
272
mkdir build
cd build
2. Run ccmake: Once you’re inside the build directory, run the ccmake command,
pointing to the project’s source directory (typically the parent directory containing
the CMakeLists.txt file).
ccmake ..
This command will start the interactive configuration interface, displaying the
current configuration options and settings for your project.
3. Navigate the ccmake Interface: The ccmake interface uses keyboard navigation.
Once the interface appears, you'll see a list of cache variables with their current
values. You can use the following keys to interact with ccmake:
A key feature of ccmake is the ability to view and modify the CMake cache variables.
These variables control the configuration of the project, such as the paths to dependencies,
compiler flags, or build types. The cache variables are saved in the CMakeCache.txt
file in your build directory.
In ccmake, the cache variables are displayed in a table-like structure, with each row
representing a variable, its current value, and its description. Some common types of
cache variables include:
You can change the values of these variables using the arrow keys to navigate to the
desired row, pressing Enter to edit the value, and typing the new value.
Example: Let’s say your project has an option to enable or disable a specific feature, and
it is defined as a Boolean cache variable, ENABLE FEATURE X. If the default value is
OFF, you can change it to ON using ccmake:
• Clear cached variables: If you want to reset the configuration to its initial state,
you can delete or clear the cache variables.
• Use advanced mode: By pressing the t key, you can switch to advanced mode,
which reveals additional configuration options that are typically hidden. This is
useful for more complex projects or when you need to fine-tune the build
configuration.
While both ccmake and the graphical CMake GUI serve similar purposes, they have
distinct use cases and benefits:
• ccmake is a text-based tool that works within the terminal, ideal for environments
without graphical interfaces. It’s best suited for users who prefer terminal-based
workflows but still want interactive configuration.
• CMake GUI provides a more visually intuitive interface, ideal for those who prefer
using a mouse and require a more user-friendly experience.
• CMake CLI is fully command-line based, offering the highest level of automation
and flexibility, particularly for integrating CMake into scripts or CI/CD systems.
mkdir build
cd build
ccmake ..
• Use the arrow keys to select CMAKE BUILD TYPE and change it from Debug
to Release.
• Enable or disable certain features by modifying Boolean cache variables (e.g.,
ENABLE TESTING).
6. Build the project: After generating the files, you can use the make or ninja
command (depending on your generator) to build the project.
7.4.7 Conclusion
This appendix is a comprehensive list of the most commonly used CMake commands,
providing a quick reference for when you're working on CMake-based projects.
1. cmake
The main command to configure, generate, and build a project.
cmake <path-to-source>
Example:
cmake /path/to/project
2. add executable
Defines an executable target in a CMake project.
278
279
add_executable(MyApp main.cpp)
3. add library
Defines a library target (static or shared).
target_link_libraries(MyApp MyLibrary)
5. find package
Searches for and configures external dependencies, such as libraries or tools.
find_package(OpenCV REQUIRED)
6. include directories
Adds directories to the compiler's search path for include files.
include_directories(${CMAKE_SOURCE_DIR}/include)
7. set
Sets a variable or cache entry.
8. message
Displays messages during the configuration process.
9. install
Defines installation rules for files and targets.
enable_testing()
add_test(NAME MyTest COMMAND MyTestExecutable)
This appendix allows you to quickly locate command syntax and descriptions, which can
be especially helpful when debugging or experimenting with advanced CMake
configurations.
281
This appendix provides a collection of best practices for writing clean, efficient, and
maintainable CMakeLists.txt files. These best practices are essential for scaling
projects and ensuring that your build system is easy to manage in the long term.
add_subdirectory(src)
add_subdirectory(tests)
set(MY_LIBRARY_PATH ${CMAKE_SOURCE_DIR}/libs)
target_include_directories(MyApp PRIVATE
,→ ${CMAKE_SOURCE_DIR}/include)
target_link_libraries(MyApp PRIVATE MyLibrary)
282
find_package(OpenCV REQUIRED)
set(CMAKE_BUILD_TYPE "Release")
284
The troubleshooting guide in this appendix offers solutions to common issues and tips for
diagnosing problems during the CMake configuration or build process.
cmake_minimum_required(VERSION 3.10)
project(SimpleProject)
add_executable(MyApp main.cpp)
cmake_minimum_required(VERSION 3.10)
project(MultiTargetProject)
cmake_minimum_required(VERSION 3.10)
project(OpenCVExample)
find_package(OpenCV REQUIRED)
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE ${OpenCV_LIBS})
These examples provide a foundation for building larger, more complex CMake
projects. You can adapt these templates as needed for your own projects.
288
This appendix lists various tools and IDE integrations that work seamlessly with CMake
to streamline your development process.
1. CMake GUI
A graphical interface that simplifies the configuration process for projects. It allows
you to set variables and configure your project without using the command line.
3. CLion
A powerful IDE for C++ development, CLion has native CMake support, which
makes it a great choice for managing large C++ projects with CMake.
4. Ninja
A small build system with a focus on speed, Ninja can be used with CMake to speed
up builds and optimize the build process.
Final Thoughts
The appendices in this book offer essential reference material and troubleshooting advice
to support your ongoing CMake journey. By utilizing these resources, you can navigate
289
common pitfalls, write more efficient CMakeLists.txt files, and manage complex
builds with ease.
References
Books
290
291
• Key Topics: CMake internals, writing custom CMake modules, advanced topics
in toolchain file configurations, and understanding the build process at a low
level.
• Why It’s Useful: This is an excellent resource for developers who need to write
highly customized CMake files or troubleshoot complex build systems.
• Key Topics: Modern CMake syntax, integration with external tools like CI/CD
pipelines, writing clean and reusable CMake code, and utilizing new CMake
features.
• Why It’s Useful: It offers a practical, example-driven approach to modern
CMake, which is great for developers looking to move away from legacy
CMake practices.
• Why It’s Useful: For any question or issue related to CMake, the official
documentation is the go-to resource. It will help you understand the tool's core
functionality and how to use it in various scenarios.
2. CMake GitLab Repository
This repository contains the source code of CMake itself. It’s a valuable resource if
you need to see how CMake is implemented or want to report a bug, contribute to the
development of CMake, or explore the change history.
• Key Features: CMake source code, bug tracking, feature requests, and
contributions.
• Why It’s Useful: Developers who want to go beyond using CMake and
contribute to its development or learn about its internal mechanics will find this
a great resource.
3. CMake Wiki
The CMake Wiki is a community-driven resource that supplements the official
documentation with additional guides, how-tos, and solutions to common problems.
• Key Features: Community-contributed tutorials, tips and tricks, case studies,
and frequently asked questions.
• Why It’s Useful: The Wiki often contains solutions to real-world CMake
problems, contributed by experienced users and experts from the community.
It’s a great place to find practical solutions.
4. CMake Discourse
CMake’s official forum is an excellent place to discuss issues, ask for help, and find
discussions around CMake features. It’s a valuable resource for troubleshooting and
learning from others' experiences.
• Key Features: Discussion threads on CMake topics, feature requests, bug
reports, and user-provided examples.
293
• Why It’s Useful: The CMake Discourse forum allows you to connect with the
community, ask specific questions, and read about other users' experiences and
solutions to problems.
1. Modern CMake
This website is dedicated to modern CMake best practices and provides an excellent
summary of the most current CMake features, syntax, and patterns.
• Key Features: Quick reference, guides on how to use CMake in modern C++
development, and examples of best practices.
• Why It’s Useful: This website is a great starting point for developers who want
to learn modern CMake practices in a structured and concise way.
2. Kitware Blog
Kitware, the company behind CMake, regularly publishes blog posts on new
features, best practices, case studies, and tutorials. The blog provides insights into
how CMake is used in various industries, including scientific computing, game
development, and more.
• Key Features: Updates on CMake features, case studies, tutorials, and advice
from CMake experts.
• Why It’s Useful: For developers looking to stay up-to-date with new features in
CMake or explore case studies of CMake used in complex environments, the
Kitware blog is a valuable resource.
tips, and tricks from the community. It’s an excellent place to find clean,
maintainable examples of how to structure and organize CMake files.
• Key Features: Best practices for writing reusable CMake code, guidelines for
organizing large projects, and solutions to common problems.
• Why It’s Useful: This repository is a useful guide for writing clean and efficient
CMake code. It contains advice on how to structure your build system to make
your projects more scalable and maintainable.
4. CMake Examples
This GitHub repository provides a collection of CMake examples covering various
use cases, from simple projects to more complex configurations.
• Key Features: Practical examples, explanations of CMake techniques, and
diverse use cases.
• Why It’s Useful: If you are learning CMake through hands-on examples, this
repository is a goldmine for realistic configurations. You can find examples on
advanced topics like cross-compiling, multi-platform support, and complex
dependency management.
1. LLVM/Clang
LLVM is a highly modular project that uses CMake as its build system. It
demonstrates complex, cross-platform CMake configurations in a large-scale
codebase.
• Why It’s Useful: By studying LLVM’s CMake setup, you can learn how to
handle large codebases, manage external dependencies, and optimize build
processes using CMake.
295
2. Boost Libraries
Boost is one of the most widely-used C++ libraries, and its CMake setup provides
examples of managing complex dependencies, creating libraries, and structuring
large C++ projects.
• Why It’s Useful: Boost’s CMake files provide a clear example of handling
external libraries, setting up multi-platform builds, and supporting various build
configurations.
3. OpenCV
OpenCV is a popular open-source computer vision library that uses CMake. The
OpenCV project is a great example of how to manage cross-platform builds, link to
external libraries, and organize large projects with CMake.
• Why It’s Useful: Studying OpenCV’s CMake configuration will give you
practical examples of using CMake with external dependencies and handling
complex C++ codebases.