What Is CMake and Why Should You Use It?

CMake is a cross-platform build system generator. Rather than writing compiler commands by hand, you describe your project in a CMakeLists.txt file, and CMake generates the appropriate build files for your platform — Makefiles on Linux/macOS, Visual Studio projects on Windows, Ninja files, and more.

Almost every serious C++ project uses CMake today. It's supported by major IDEs (CLion, VS Code, Visual Studio), integrates with package managers like vcpkg and Conan, and handles complex multi-target projects cleanly.

Installing CMake

  • Windows: Download the installer from cmake.org or use winget install cmake.
  • macOS: brew install cmake
  • Linux: sudo apt install cmake

Verify your installation: cmake --version

Your First CMakeLists.txt

Create a project directory with this structure:

my_project/
├── CMakeLists.txt
└── src/
    └── main.cpp

Here's a minimal CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(MyProject VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(my_app src/main.cpp)

Breaking it down:

  • cmake_minimum_required — ensures a compatible CMake version.
  • project() — names your project and sets metadata.
  • set(CMAKE_CXX_STANDARD 17) — enforces C++17 across all targets.
  • add_executable() — defines a binary target and its source files.

Building the Project

CMake uses an out-of-source build pattern — you build in a separate directory to keep source files clean:

cd my_project
mkdir build
cd build
cmake ..          # Configure (generates build files)
cmake --build .   # Compile

Your compiled binary will appear in the build/ directory.

Adding Multiple Source Files

# Option 1: list files explicitly (recommended)
add_executable(my_app
    src/main.cpp
    src/utils.cpp
    src/parser.cpp
)

# Option 2: use a glob (convenient but not recommended for large projects)
file(GLOB SOURCES "src/*.cpp")
add_executable(my_app ${SOURCES})

Tip: Prefer listing files explicitly. Globbing doesn't always detect new files without re-running CMake.

Linking Libraries

CMake makes library linking clean and explicit:

# Add a static library
add_library(mylib STATIC src/mylib.cpp)

# Link it to your executable
target_link_libraries(my_app PRIVATE mylib)

# Link a system library (e.g., pthreads on Linux)
target_link_libraries(my_app PRIVATE pthread)

Using find_package for Third-Party Libraries

find_package(OpenSSL REQUIRED)
target_link_libraries(my_app PRIVATE OpenSSL::SSL OpenSSL::Crypto)

CMake ships with find modules for many common libraries. For others, use vcpkg or Conan as a package manager alongside CMake.

Common CMake Best Practices

  • Always use out-of-source builds.
  • Use target_include_directories instead of include_directories.
  • Prefer PRIVATE, PUBLIC, or INTERFACE scoping on all target commands.
  • Set CMAKE_CXX_STANDARD_REQUIRED ON to avoid silent fallbacks.
  • Use cmake --build . --config Release for optimized builds.

Summary

CMake has a learning curve, but once you understand its core model — targets, properties, and linking — it becomes an indispensable tool. Start simple, then build up to multi-target projects, testing with CTest, and packaging with CPack as your projects grow.