What is CMake?
CMake is a cross-platform build system generator. Unlike Make, Ninja, or MSBuild which are build systems that directly compile code, CMake sits one layer above — it generates the native build files for whatever platform you're on. Think of it as a universal translator between your project description and your platform's build tools.
Why CMake?
CMake has become the de facto standard for C and C++ projects for compelling reasons:
- Cross-platform — One CMakeLists.txt works on Windows, macOS, Linux, and embedded targets
- Generator-agnostic — Output Makefiles, Ninja files, Visual Studio projects, or Xcode projects from the same source
- Ecosystem support — Nearly every major C/C++ library provides CMake configuration (Qt, Boost, OpenCV, LLVM, etc.)
- IDE integration — First-class support in CLion, Visual Studio, VS Code, and Qt Creator
- Scalability — Handles projects from single-file programs to massive codebases like LLVM (millions of lines)
- Modern features — Targets, generator expressions, presets, FetchContent, and C++20 module support
flowchart LR
A[CMakeLists.txt] --> B[CMake]
B --> C{Generator}
C -->|Unix| D[Makefiles]
C -->|Ninja| E[build.ninja]
C -->|VS| F[.sln / .vcxproj]
C -->|Xcode| G[.xcodeproj]
D --> H[make]
E --> I[ninja]
F --> J[MSBuild]
G --> K[xcodebuild]
H --> L[Binary]
I --> L
J --> L
K --> L
The CMake Ecosystem
CMake ships with several companion tools that together form a complete build infrastructure:
| Tool | Purpose | Documentation |
|---|---|---|
cmake | Configure and generate build systems | cmake(1) |
cmake --build | Build projects (wraps native tool) | Build mode |
ctest | Run tests and report results | ctest(1) |
cpack | Package projects for distribution | cpack(1) |
cmake-gui | Graphical configuration interface | cmake-gui(1) |
ccmake | Terminal-based configuration UI | ccmake(1) |
Installing CMake
CMake requires version 3.21+ for modern features used throughout this series. We recommend the latest stable release (3.30+ as of 2026). Always reference the official documentation for the most current information.
Windows Installation
There are several ways to install CMake on Windows:
Option 1: Official Installer (Recommended)
# Download from https://cmake.org/download/
# Run the .msi installer
# IMPORTANT: Check "Add CMake to the system PATH" during installation
# Verify installation
cmake --version
Option 2: winget (Windows Package Manager)
# Install via winget
winget install Kitware.CMake
# Verify
cmake --version
Option 3: Chocolatey
# Install via Chocolatey
choco install cmake --installargs '"ADD_CMAKE_TO_PATH=System"'
# Verify
cmake --version
Option 4: Visual Studio (Bundled)
Visual Studio 2019+ includes CMake as part of the "Desktop development with C++" workload. The bundled version is typically recent but may lag the latest release.
macOS Installation
# Option 1: Homebrew (recommended)
brew install cmake
# Option 2: MacPorts
sudo port install cmake
# Option 3: Official DMG from https://cmake.org/download/
# Drag CMake.app to /Applications
# Add to PATH:
sudo "/Applications/CMake.app/Contents/bin/cmake-gui" --install
# Verify
cmake --version
xcode-select --install. This provides AppleClang which CMake detects automatically.
Linux Installation
Ubuntu / Debian
# System package (may be outdated)
sudo apt update
sudo apt install cmake
# For latest version, use Kitware's APT repository:
sudo apt install software-properties-common
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | \
gpg --dearmor - | sudo tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/kitware.list >/dev/null
sudo apt update
sudo apt install cmake
# Verify
cmake --version
Fedora / RHEL / CentOS
# Fedora
sudo dnf install cmake
# RHEL / CentOS (EPEL required)
sudo yum install epel-release
sudo yum install cmake3
# Verify
cmake --version
Arch Linux
sudo pacman -S cmake
cmake --version
Building from Source (Any Distribution)
# Download source tarball
wget https://github.com/Kitware/CMake/releases/download/v3.30.0/cmake-3.30.0.tar.gz
tar -xzf cmake-3.30.0.tar.gz
cd cmake-3.30.0
# Bootstrap and build
./bootstrap --parallel=$(nproc)
make -j$(nproc)
sudo make install
# Verify
cmake --version
sudo apt install build-essential (Debian/Ubuntu) or sudo dnf install gcc gcc-c++ make (Fedora).
The CMake Workflow
Every CMake project follows a three-step workflow: Configure → Generate → Build. Understanding this separation is fundamental to mastering CMake.
flowchart TD
A[Source Tree
CMakeLists.txt + .cpp/.h files] -->|"cmake -S . -B build"| B[Configure Step]
B -->|Reads CMakeLists.txt
Checks compilers
Finds libraries| C[CMakeCache.txt
Internal state]
C --> D[Generate Step]
D -->|Creates native build files| E[Build Tree
Makefiles / build.ninja / .sln]
E -->|"cmake --build build"| F[Build Step]
F -->|Compiles & links| G[Binaries
Executables & Libraries]
The Configure Step
During configuration, CMake:
- Reads
CMakeLists.txtfrom the source directory - Detects the compiler and platform
- Resolves dependencies (find_package, FetchContent)
- Evaluates all CMake commands and control flow
- Writes
CMakeCache.txtstoring all resolved variables
# Configure step — specify source (-S) and build (-B) directories
cmake -S . -B build
The Generate Step
Generation happens automatically at the end of configuration. CMake writes the native build system files (Makefiles, build.ninja, etc.) into the build directory. The generator is chosen by the -G flag or defaults to the platform standard:
| Platform | Default Generator | Build Tool |
|---|---|---|
| Linux | Unix Makefiles | make |
| macOS | Unix Makefiles (or Xcode) | make / xcodebuild |
| Windows (VS) | Visual Studio 17 2022 | MSBuild |
| Windows (MinGW) | MinGW Makefiles | mingw32-make |
The Build Step
The build step invokes the native build tool. CMake provides a universal command that works regardless of which generator was used:
# Build using the native tool (make, ninja, MSBuild, etc.)
cmake --build build
# Build with parallel jobs
cmake --build build --parallel 8
# Build a specific target
cmake --build build --target my_app
# Build in Release configuration (multi-config generators)
cmake --build build --config Release
Your First CMake Project
Project Structure
Let's create the simplest possible CMake project — a "Hello, World!" application:
# Create project directory
mkdir hello_cmake
cd hello_cmake
# Create source file and CMakeLists.txt
# (We'll show the contents below)
The directory structure:
hello_cmake/
├── CMakeLists.txt ← Build configuration
└── main.cpp ← Source code
Writing CMakeLists.txt
Create main.cpp:
#include <iostream>
int main() {
std::cout << "Hello, CMake!" << std::endl;
std::cout << "CMake version: " << __cplusplus << std::endl;
return 0;
}
Create CMakeLists.txt:
# Minimum CMake version required
cmake_minimum_required(VERSION 3.21)
# Project name, version, and languages
project(HelloCMake
VERSION 1.0.0
DESCRIPTION "My first CMake project"
LANGUAGES CXX
)
# Create an executable target from main.cpp
add_executable(hello_cmake main.cpp)
# Set C++ standard to C++17
target_compile_features(hello_cmake PRIVATE cxx_std_17)
Let's break down each command (all documented in the CMake Reference):
| Command | Purpose | Reference |
|---|---|---|
cmake_minimum_required | Sets the minimum CMake version and enables policies | Docs |
project | Defines project name, version, and enabled languages | Docs |
add_executable | Creates a build target (executable) from source files | Docs |
target_compile_features | Requires a specific language standard for a target | Docs |
Building the Project
Now build and run:
# Step 1: Configure (creates build/ directory)
cmake -S . -B build
# Step 2: Build
cmake --build build
# Step 3: Run the executable
# Linux/macOS:
./build/hello_cmake
# Windows:
build\Debug\hello_cmake.exe
Expected output:
Hello, CMake!
CMake version: 201703
What Just Happened?
During configuration, CMake detected your compiler (GCC, Clang, or MSVC), verified it supports C++17, and generated the appropriate build files. The build/ directory now contains everything needed to compile — you never need to modify it manually.
Look inside build/ to see what was generated: CMakeCache.txt, CMakeFiles/, and either a Makefile, build.ninja, or .sln depending on your platform.
Command-Line Interface
The cmake command-line interface is your primary tool. Here are the essential commands (full reference: cmake(1)):
Configure Commands
# Basic configure
cmake -S <source-dir> -B <build-dir>
# Specify generator
cmake -S . -B build -G "Ninja"
cmake -S . -B build -G "Unix Makefiles"
cmake -S . -B build -G "Visual Studio 17 2022"
# Set build type (single-config generators only)
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
# Set install prefix
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr/local
# Pass custom options
cmake -S . -B build -DENABLE_TESTS=ON -DBUILD_SHARED_LIBS=ON
# List available generators
cmake --help
# View cached variables
cmake -L build # List non-advanced variables
cmake -LA build # List all variables (including advanced)
cmake -LAH build # List all with help strings
Build Commands
# Build (uses native tool)
cmake --build build
# Build with parallelism
cmake --build build -j 8
cmake --build build --parallel $(nproc)
# Build specific target
cmake --build build --target my_library
# Build in Release mode (multi-config generators)
cmake --build build --config Release
# Verbose output (see actual compiler commands)
cmake --build build --verbose
# Clean build artifacts
cmake --build build --target clean
Install Commands
# Install to configured prefix
cmake --install build
# Install with custom prefix (overrides CMAKE_INSTALL_PREFIX)
cmake --install build --prefix /opt/myapp
# Install specific component
cmake --install build --component Runtime
# Install in Release configuration
cmake --install build --config Release
GUI Tools
cmake-gui (Graphical Interface)
The cmake-gui tool provides a graphical interface for configuring projects. It's particularly useful for exploring available options and understanding what a project supports:
# Launch cmake-gui
cmake-gui
# Or point it to a source directory
cmake-gui -S /path/to/source
The GUI workflow:
- Set "Where is the source code" to your project root
- Set "Where to build the binaries" to a build directory
- Click Configure — select your generator
- Modify any red-highlighted (new) variables
- Click Configure again to resolve dependencies
- Click Generate to write build files
ccmake (Terminal UI)
For headless servers or terminal-focused workflows, ccmake provides a curses-based interface:
# Launch ccmake pointed at build directory
ccmake -S . -B build
# Or configure an existing build directory
ccmake build
Key bindings in ccmake:
| Key | Action |
|---|---|
| c | Configure |
| g | Generate and exit |
| t | Toggle advanced variables |
| Enter | Edit variable value |
| d | Delete variable |
| q | Quit without generating |
Out-of-Source Builds
CMake strongly recommends out-of-source builds — keeping generated files separate from source code. This is the -B build pattern we've been using:
# GOOD: Out-of-source build
cmake -S . -B build # Source in current dir, build files in build/
cmake --build build
# Clean everything: just delete the build directory
rm -rf build
cmake . in the source directory pollutes it with generated files that are nearly impossible to clean up completely. Always use -B <dir> to specify a separate build directory.
Benefits of out-of-source builds:
- Multiple configurations — Build Debug and Release side by side:
build-debug/,build-release/ - Clean deletion — Remove the entire build by deleting one directory
- VCS-friendly — Add
build*/to.gitignoreand never commit generated files - Multiple generators — Test with Makefiles AND Ninja:
build-make/,build-ninja/
# Multiple build directories for different configurations
cmake -S . -B build-debug -DCMAKE_BUILD_TYPE=Debug
cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release
cmake -S . -B build-ninja -G Ninja
# .gitignore entry
echo "build*/" >> .gitignore
Exercises
Setup Your Environment
Install CMake on your system using your preferred method. Verify the installation by running:
cmake --version
cmake --help
Record which generator is the default on your system. Try listing all available generators from the help output.
Build Your First Project
Create the hello_cmake project from this article. Then modify it:
- Add a second source file (
greet.cpp+greet.h) with a function that returns a greeting string - Call that function from
main.cpp - Add
greet.cppto theadd_executablecommand - Rebuild and verify it works
Compare Debug vs Release
Build the same project in both Debug and Release modes:
cmake -S . -B build-debug -DCMAKE_BUILD_TYPE=Debug
cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release
cmake --build build-debug
cmake --build build-release
Compare the binary sizes. On Linux, use file and ls -la to see debug symbols vs stripped binaries.
Generator Exploration
If you have Ninja installed (sudo apt install ninja-build or brew install ninja), try:
cmake -S . -B build-ninja -G Ninja
cmake --build build-ninja
Compare build speed between Make and Ninja for the same project. Ninja is typically faster for incremental builds.
Conclusion & Next Steps
You now have CMake installed and understand its fundamental workflow:
- Configure — CMake reads CMakeLists.txt, detects tools, resolves dependencies
- Generate — Native build files are written to the build directory
- Build — The native tool (make/ninja/MSBuild) compiles and links
Key takeaways from this part:
- CMake is a build system generator, not a build system
- Always use out-of-source builds (
-B build) cmake_minimum_required+project+add_executableform the minimal CMakeLists.txt- The
cmake --buildcommand abstracts away generator differences - Use
cmake-guiorccmaketo explore project options interactively