Table of Contents

  1. What is CMake?
  2. Installing CMake
  3. The CMake Workflow
  4. Your First CMake Project
  5. Command-Line Interface
  6. GUI Tools
  7. Out-of-Source Builds
  8. Exercises
  9. Conclusion & Next Steps
Back to CMake Mastery Series

Part 1: Getting Started with CMake

June 4, 2026 Wasil Zafar 35 min read

Install CMake on any platform, understand the configure-generate-build workflow, and compile your first C++ project from scratch using the command line and GUI tools.

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.

Key Insight: CMake does not compile code itself. It generates Makefiles, Ninja files, Visual Studio solutions, or Xcode projects that then do the actual compilation. This is why it's called a "build system generator" rather than a "build system."

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
CMake's Role in the Build Pipeline
        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:

ToolPurposeDocumentation
cmakeConfigure and generate build systemscmake(1)
cmake --buildBuild projects (wraps native tool)Build mode
ctestRun tests and report resultsctest(1)
cpackPackage projects for distributioncpack(1)
cmake-guiGraphical configuration interfacecmake-gui(1)
ccmakeTerminal-based configuration UIccmake(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.

Windows Requirement: You also need a C++ compiler. Install either Visual Studio (includes MSVC) or MinGW-w64. Visual Studio's "Developer Command Prompt" or "Developer PowerShell" sets up the environment automatically.

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
macOS Compiler: Install Xcode Command Line Tools with 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
Linux Compiler: Most distributions include GCC. If not: 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.

The Three-Step CMake Workflow
        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:

  1. Reads CMakeLists.txt from the source directory
  2. Detects the compiler and platform
  3. Resolves dependencies (find_package, FetchContent)
  4. Evaluates all CMake commands and control flow
  5. Writes CMakeCache.txt storing 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:

PlatformDefault GeneratorBuild Tool
LinuxUnix Makefilesmake
macOSUnix Makefiles (or Xcode)make / xcodebuild
Windows (VS)Visual Studio 17 2022MSBuild
Windows (MinGW)MinGW Makefilesmingw32-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):

CommandPurposeReference
cmake_minimum_requiredSets the minimum CMake version and enables policiesDocs
projectDefines project name, version, and enabled languagesDocs
add_executableCreates a build target (executable) from source filesDocs
target_compile_featuresRequires a specific language standard for a targetDocs

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
Hands-On First Build Experience
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.

configure generate build

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:

  1. Set "Where is the source code" to your project root
  2. Set "Where to build the binaries" to a build directory
  3. Click Configure — select your generator
  4. Modify any red-highlighted (new) variables
  5. Click Configure again to resolve dependencies
  6. Click Generate to write build files
Pro Tip: Variables shown in red are new or changed since the last configure. After adjusting them, always click Configure again before Generate — some variables depend on others.

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:

KeyAction
cConfigure
gGenerate and exit
tToggle advanced variables
EnterEdit variable value
dDelete variable
qQuit 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
Never do in-source builds! Running 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 .gitignore and 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

Exercise 1 Install and Verify
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.

setup verify
Exercise 2 Hello CMake
Build Your First Project

Create the hello_cmake project from this article. Then modify it:

  1. Add a second source file (greet.cpp + greet.h) with a function that returns a greeting string
  2. Call that function from main.cpp
  3. Add greet.cpp to the add_executable command
  4. Rebuild and verify it works
multi-file add_executable
Exercise 3 Multiple Build Types
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.

Debug Release build types
Exercise 4 Try Different Generators
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.

Ninja generators performance

Conclusion & Next Steps

You now have CMake installed and understand its fundamental workflow:

  1. Configure — CMake reads CMakeLists.txt, detects tools, resolves dependencies
  2. Generate — Native build files are written to the build directory
  3. 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_executable form the minimal CMakeLists.txt
  • The cmake --build command abstracts away generator differences
  • Use cmake-gui or ccmake to explore project options interactively
Official Reference: Bookmark the CMake Documentation — it's the definitive reference for every command, variable, property, and module discussed in this series.