CMake is a tool that generates build systems from a unified configuration for our project.
When we start programming in C++, we usually have a single main.cpp file. Compiling it is easy: you press the IDE’s run button or type g++ main.cpp in the console.
The situation changes as the project grows:
- You have 20
.cppand 20.hfiles. - You use external libraries.
- You want your code to compile both on Windows (with Visual Studio) and Linux (with GCC).
Doing this manually is a nightmare. That’s why we use CMake.
CMake has a reputation for being difficult, and its syntax is sometimes quirky, but it is the tool that ties the entire C++ ecosystem together.
What is CMake really?
CMake is an open-source, cross-platform software build tool that simplifies the creation and management of C++ projects.
It uses configuration files called CMakeLists.txt to define how a project should be built.
There is a very common misconception: CMake is not a compiler. It does not compile your code; rather, it is a build system generator.
Think of it this way:
- You write a “recipe” in a file named
CMakeLists.txt. - CMake reads that recipe.
- CMake generates the necessary files for your preferred build tool.
- On Windows, it can generate a Visual Studio solution (
.sln). - On Linux, it can generate files for Make or Ninja.
- On macOS, it can generate an Xcode project.
- On Windows, it can generate a Visual Studio solution (
Thanks to this, you only maintain a single configuration file, and CMake handles adapting the project to any operating system or compiler.
Your first CMakeLists.txt
Every CMake project starts with a text file, mandatorily named CMakeLists.txt (case-sensitive).
Let’s create the most basic one possible for a “Hello World”.
# 1. Minimum version of CMake required (Good practice)
cmake_minimum_required(VERSION 3.10)
# 2. Project name and language
project(MiProyectoHolaMundo CXX)
# 3. Define the C++ standard (e.g., C++17)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# 4. Create the executable
# Syntax: add_executable(TargetName SourceFiles...)
add_executable(MiApp main.cpp)
Commands in CMake (project, add_executable) are case-insensitive, but the modern convention is to write them in lowercase. Variables (CMAKE_CXX_STANDARD) are Case Sensitive.
Structure of a project with CMake
A typical CMake project structure might look like this:
mi_proyecto/ ├── CMakeLists.txt # Main project configuration. ├── src/ # Source file directory. │ ├── CMakeLists.txt │ ├── main.cpp │ └── utils.cpp └── include/ # Header file directory. ├── CMakeLists.txt └── utils.h
The Workflow
One of the best practices in CMake is to do Out-of-Source Builds.
This means we don’t want to clutter our source code folder with temporary build files (.o, .obj, .exe). We want all the “junk” to go into a separate folder, usually called build.
The standard terminal process is as follows:
Configuration
We tell CMake to read the CMakeLists.txt and generate the project files inside the build folder.
# -S . -> Source (where the CMakeLists.txt is located, i.e., the current directory)
# -B build -> Build (where to generate the temporary files)
cmake -S . -B build
Compilation
Now we ask CMake to invoke the actual compiler (GCC, MSVC, Clang) to compile what is in build.
# --build build -> Compiles whatever you have configured in that folder
cmake --build build
If all goes well, your executable will be inside the build folder (or build/Debug on Windows).
Working with multiple files
A real project never has a single file. Let’s imagine we have main.cpp, calculadora.cpp, and calculadora.h.
We could list them all in the executable:
add_executable(MiApp main.cpp calculadora.cpp)
However, in modern C++, we prefer to organize code into Libraries.
add_library and target_link_libraries
We can group calculadora.cpp into an internal library. This makes the project more modular.
cmake_minimum_required(VERSION 3.10)
project(ProyectoMatematico CXX)
# 1. Create a static library called "Calculos"
add_library(Calculos calculadora.cpp)
# 2. Create the main executable with only main.cpp
add_executable(MiApp main.cpp)
# 3. Link the library to the executable
# This tells CMake: "MiApp needs to use the code from Calculos"
target_link_libraries(MiApp PUBLIC Calculos)
The concept of Target is fundamental in modern CMake. MiApp is a target. Calculos is a target. Instead of using global variables, we define relationships between targets: “This target depends on that other one”.
Managing includes
If your .h files are in a separate folder (e.g., include/), you need to tell the compiler where to find them.
Do not use compiler flags (-I include). Use the semantic CMake command:
# Tell the target 'Calculos' that its headers are in the 'include' folder
target_include_directories(Calculos PUBLIC include)
By using PUBLIC, any other executable (like MiApp) that links against Calculos will automatically inherit that include path. CMake handles propagating this configuration.