Hello! In our last lesson, we saw how auto can simplify our code, especially when dealing with complex types. So far, we've been compiling our single-file programs directly with g++, like g++ main.cpp -o my_program. While this works for simple cases, it quickly becomes unmanageable as soon as a project grows to include multiple source files and dependencies.
This lesson directly addresses this challenge and fulfills the learning outcome: Write a basic CMakeLists.txt file to build a C++ executable.
We will explore CMake, the de facto standard build system generator for C++. It's a fundamental tool for any serious C++ development, from desktop applications to embedded systems—two of your key interest areas.
Today, you will learn:
- What a build system is and why CMake is essential for C++ projects.
- The core commands needed to create a minimal
CMakeLists.txtfile. - How to configure and build a C++ project from the command line using an "out-of-source" build.
- How to expand your build script to handle multiple source files.
1. Why Do We Need a Build System?
As a project grows, you'll split your code into multiple .cpp and .h files. You might also need to link against external libraries. Manually running g++ with a long list of files and flags becomes tedious and error-prone. A build system automates this process.
CMake is not a build system itself, but a meta-build system or a build system generator. It doesn't compile your code. Instead, it reads a configuration file you write (CMakeLists.txt) and generates the actual build files for a native build tool like Make on Linux/macOS or Visual Studio on Windows.
This approach gives you a single, cross-platform way to define your project.
CMake Tutorial: Building Your First C++ Project & Beyond
This article provides a fantastic mental model for understanding CMake. The key takeaway is that with CMake, you don't tell it how to compile; you describe what you want to build.
Please read the first two sections, 'What is CMake?' and 'Why CMake is Confusing? (And How to Fix That)'. Focus on the core idea that CMake generates native build files and that you should think in terms of describing your project, not scripting the compilation.
This two-phase process is a cornerstone of CMake:
- Configure Phase: You run
cmaketo parse yourCMakeLists.txtand generate the native build files (e.g., aMakefile). - Build Phase: You run the native build tool (e.g.,
make) or use a genericcmakecommand to execute the build, which compiles and links your code.
This flowchart clearly visualizes these two distinct phases.

2. Your First CMakeLists.txt
Every CMake project is defined by a plain text file named CMakeLists.txt. For a simple executable, you only need three commands.
Let's start by looking at the official CMake tutorial to understand these essential commands.
Step 1: Getting Started with CMake
The official CMake documentation is the ultimate source of truth. This first step of their tutorial introduces the most fundamental commands required to build an executable.
Read the sections 'Background' and 'Exercise 1 - Building an Executable'. Don't worry about the TODOs or file structure in the tutorial itself. Focus on understanding the purpose of these three commands: cmake_minimum_required() project() add_executable()
To summarize, these are the three pillars of a basic CMakeLists.txt:
cmake_minimum_required(VERSION 3.10): Specifies the minimum version of CMake required to build your project. This ensures that the CMake features you're using are available. Using a relatively recent version like 3.10 or higher is a good practice.project(MyAwesomeApp): Declares the name of your project. This also sets up default configurations and checks for a working C++ compiler.add_executable(MyAwesomeApp main.cpp): This is the command that does the work. It defines an executable target. Here, it says: "Create an executable namedMyAwesomeAppfrom the source filemain.cpp."
3. Building a Single-File Project
Let's put this into practice. Imagine you have a folder with two files:
main.cpp
#include <iostream>
int main() {
std::cout << "Hello, CMake!" << std::endl;
return 0;
}
CMakeLists.txt
# 1. Set the minimum required version of CMake
cmake_minimum_required(VERSION 3.10)
# 2. Set the project name
project(HelloCMake)
# 3. Add an executable target
add_executable(HelloCMake main.cpp)
Now, how do we build this? The best practice is to perform an out-of-source build. This means all the files generated by CMake and the compiler (Makefiles, object files, the final executable) are placed in a separate directory, typically named build, keeping your source directory clean.
Here is the command-line workflow:
-
Create and navigate to a build directory:
mkdir build cd build -
Run CMake to configure the project:
# This tells cmake to look for CMakeLists.txt in the parent directory (..) cmake ..This command reads your
CMakeLists.txt, checks your compiler, and generates aMakefileinside thebuilddirectory. -
Run the build tool:
# This invokes the underlying build tool (e.g., 'make') makeThis compiles
main.cppand links it into an executable namedHelloCMake. -
Run your program:
./HelloCMake
The following video demonstrates this exact process. Although it uses a C program, the workflow is identical for C++.
CMake Tutorial for Absolute Beginners - From GCC to CMake including Make and Ninja
This video from 'Gary Explains' gives a great practical demonstration of the out-of-source build process from the command line.
Watch from 12:22 to 14:22. Observe how he creates a build directory, runs cmake .. from within it to generate the Makefile, and then runs make to compile the program. This visualizes the workflow we just discussed.
A note on modern syntax: The official CMake tutorial (LINK, section 2) now recommends a slightly different set of commands that achieve the same result without needing to cd into the build directory:
# Configure from the project's root directory
cmake -S . -B build
# Build from the project's root directory
cmake --build build
Here, -S . specifies the source directory (current directory) and -B build specifies the build directory. Both workflows are common and valid.
4. Handling Multiple Source Files
Real projects are rarely a single file. Let's expand our project.
Suppose we have this structure:
main.cppfunctions.hfunctions.cppCMakeLists.txt
functions.h
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
void print_message();
#endif //FUNCTIONS_H
functions.cpp
#include "functions.h"
#include <iostream>
void print_message() {
std::cout << "This is a message from another file!" << std::endl;
}
main.cpp
#include "functions.h"
int main() {
print_message();
return 0;
}
How do we tell CMake about functions.cpp? We just add it to the add_executable command. For projects with many files, it's cleaner to group them in a variable first using the set() command.
CMake Tutorial: Building Your First C++ Project & Beyond
This section of the tutorial shows a clean and scalable way to handle multiple source files by creating a variable to hold the list of source files.
Read the section 'Compiling Multiple Source Files Into One Program'. Focus on how set(SOURCE_FILES ...) is used to create a variable and how ${SOURCE_FILES} is used in the add_executable command.
Our updated CMakeLists.txt would look like this:
CMakeLists.txt (for multiple files)
cmake_minimum_required(VERSION 3.10)
project(MultiFileProject)
# Set the C++ standard to C++17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Group all your .cpp source files into a variable
set(SOURCE_FILES
main.cpp
functions.cpp
)
# Add the executable using the variable
add_executable(MultiFileProject ${SOURCE_FILES})
Notice two new lines: set(CMAKE_CXX_STANDARD 17) and set(CMAKE_CXX_STANDARD_REQUIRED ON). These are important for ensuring your project is compiled using the correct C++ standard (in this case, C++17).
The build process remains exactly the same. CMake is smart enough to see the updated CMakeLists.txt and rebuild things correctly.
Conclusion
You now have the fundamental knowledge to manage a C++ project build with CMake. This is a huge step up from single-file compilation and is the standard practice in the industry.
Key Takeaways:
- CMake is a meta-build system that generates native build files (like Makefiles) from a central
CMakeLists.txtscript. - The core commands for a simple project are
cmake_minimum_required(),project(), andadd_executable(). - Out-of-source builds are the standard workflow:
cmake -B buildto configure, andcmake --build buildto compile. This keeps your source directory clean. - You can easily manage multiple source files by listing them in the
add_executablecommand, often by grouping them withset(). - You can enforce a specific C++ standard using variables like
CMAKE_CXX_STANDARD.
Next Up
Our project is still entirely self-contained. Most real-world applications, however, depend on third-party libraries for things like graphics, networking, or advanced math. In our next lesson, we will learn how to configure a CMake project to find and link against an external library. This will unlock the ability to build much more powerful and feature-rich applications.