CMake Build System Tutorial
CMake is the de facto standard build system generator for C++. It does NOT build your code directly — it generates build files for other tools: - On Windows: Visual Studio solutions (.sln) or Ninja files - On Linux: Makefiles or Ninja files - On macOS: Xcode projects, Makefiles, or Ninja files
WHY CMAKE: 1. Cross-platform: one CMakeLists.txt works on Windows, Linux, macOS 2. IDE integration: CLion, VS Code, Visual Studio all understand CMake 3. Dependency management: find_package() locates installed libraries 4. Out-of-source builds: build artifacts go in a separate directory 5. Industry standard: almost every open-source C++ project uses it
BASIC WORKFLOW: 1. Write CMakeLists.txt (this file) in your project root 2. Create a build directory: mkdir build && cd build 3. Generate build files: cmake .. 4. Build the project: cmake --build . 5. Run the executable: ./my_program
Or, with modern CMake presets (CMake 3.21+): cmake --preset default cmake --build --preset default
MINIMUM YOU NEED TO KNOW: cmake_minimum_required() — what CMake version you need project() — name your project add_executable() — define a program to build target_link_libraries() — link libraries to your program
Reference: https://cmake.org/cmake/help/latest/
Frequently Asked Questions
What is the difference between CMake and Make?
What is "out-of-source" building?
What is a "target"?
What's the difference between PUBLIC, PRIVATE, and INTERFACE?
What does "modern CMake" mean?
What is a "generator"?
How do I add a third-party library?
cmake_minimum_required(VERSION 3.20)
# --- 2. Required: project declaration ---
# Names the project and optionally specifies languages and version.
# VERSION sets variables like PROJECT_VERSION, PROJECT_VERSION_MAJOR, etc.
project(EduCPlusPlus
VERSION 1.0.0
DESCRIPTION "Educational Modern C++ Examples"
LANGUAGES CXX # CXX = C++. Could also add C, CUDA, etc.
)
# --- 3. Set the C++ standard ---
# Do this globally OR per-target. Per-target is "more modern CMake"
# but global is fine for a single-standard project.
set(CMAKE_CXX_STANDARD 20) # Use C++20
set(CMAKE_CXX_STANDARD_REQUIRED ON) # Error if compiler doesn't support it
set(CMAKE_CXX_EXTENSIONS OFF) # Don't use GNU extensions (-std=c++20, not -std=gnu++20)
# WHY CMAKE_CXX_EXTENSIONS OFF?
# With extensions ON (default), GCC uses -std=gnu++20 which enables
# compiler-specific features. With OFF, it uses -std=c++20 for
# strict standard compliance. Always use OFF for portable code.EXECUTABLE TARGETS
--- 4. Define executable targets --- add_executable(target_name source1.cpp source2.cpp ...) Each add_executable creates a separate program. Example: a simple single-file executable
add_executable(hello_modern
${CMAKE_SOURCE_DIR}/01_fundamentals/basics/hello_modern.cpp
)
# Example: the type casting module
add_executable(casting_operators
${CMAKE_SOURCE_DIR}/11_type_casting/casting_operators/casting_operators.cpp
)
# Example: variadic templates
add_executable(variadic_templates
${CMAKE_SOURCE_DIR}/06_templates/variadic/variadic_templates.cpp
)
# Example: streams I/O
add_executable(streams
${CMAKE_SOURCE_DIR}/12_io_and_filesystem/streams/streams.cpp
)
# Example: filesystem operations
add_executable(filesystem_ops
${CMAKE_SOURCE_DIR}/12_io_and_filesystem/filesystem/filesystem_ops.cpp
)
# Example: variant and type traits
add_executable(variant_and_type_traits
${CMAKE_SOURCE_DIR}/14_variant_and_type_traits/variant/variant_and_visitors.cpp
)
add_executable(type_traits
${CMAKE_SOURCE_DIR}/14_variant_and_type_traits/type_traits/type_traits_guide.cpp
)COMPILER WARNINGS (best practice)
--- 5. Enable warnings --- Warnings catch bugs at compile time. Always enable them. We define a function to apply warnings to any target.
function(enable_warnings target)
target_compile_options(${target} PRIVATE
# MSVC warnings
$<$<CXX_COMPILER_ID:MSVC>:/W4 /permissive->
# GCC / Clang warnings
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Wpedantic>
)
endfunction()
# The $<...> syntax is a "generator expression" — it's evaluated at
# build time, not configure time. $<CXX_COMPILER_ID:MSVC> is true
# only when compiling with MSVC. This makes the CMakeLists.txt
# work on all compilers without #ifdef-style branching.
# Apply warnings to all our targets
enable_warnings(hello_modern)
enable_warnings(casting_operators)
enable_warnings(variadic_templates)
enable_warnings(streams)
enable_warnings(filesystem_ops)
enable_warnings(variant_and_type_traits)
enable_warnings(type_traits)THREADING SUPPORT
--- 6. Find and link the threading library --- On Linux, threading requires -pthread. On Windows, it's built-in. find_package(Threads) handles this portably.
find_package(Threads REQUIRED)
# For the multithreading examples:
# add_executable(thread_basics
# ${CMAKE_SOURCE_DIR}/07_multithreading/threads/thread_basics.cpp
# )
# target_link_libraries(thread_basics PRIVATE Threads::Threads)
# enable_warnings(thread_basics)LIBRARY TARGETS (how to create and use libraries)
--- 7. Static library example ---
A static library (.a on Linux, .lib on Windows) is linked INTO the
executable at build time. The library's code becomes part of the exe.
add_library(my_math STATIC
src/math/vector.cpp
src/math/matrix.cpp
)
target_include_directories(my_math PUBLIC include/)
# PUBLIC means: any target that links my_math automatically gets
# include/ in its include path.
add_executable(my_app src/main.cpp)
target_link_libraries(my_app PRIVATE my_math)
# PRIVATE means: my_app uses my_math, but targets linking my_app
# don't automatically get my_math.
--- 8. Shared (dynamic) library example ---
A shared library (.so on Linux, .dll on Windows) is loaded at runtime.
Smaller executables, but requires the .so/.dll to be present at runtime.
add_library(my_plugin SHARED
src/plugin/plugin.cpp
) --- 9. FetchContent — download dependencies at configure time ---
This is the modern way to add third-party libraries without
requiring the user to install them separately.
include(FetchContent)
FetchContent_Declare(
fmt # Name we'll use locally
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1 # Always pin a specific version!
)
FetchContent_MakeAvailable(fmt)
# Now link against it:
target_link_libraries(my_app PRIVATE fmt::fmt)
Watch out: FetchContent downloads and builds at CONFIGURE time
(when you run "cmake .."). This can be slow for large dependencies.
For production projects, consider vcpkg or Conan for package management. --- 10. Install rules ---
install() tells CMake where to put files when the user runs:
cmake --install build/
install(TARGETS hello_modern
RUNTIME DESTINATION bin # Executables go to <prefix>/bin
)
install(FILES README.md
DESTINATION share/doc/EduCPlusPlus
) --- 11. Enable testing ---
enable_testing() activates CTest. After building, run tests with:
cd build && ctest
enable_testing()
add_test(NAME test_hello COMMAND hello_modern)
# The test passes if the program exits with code 0.
For a real test framework, use FetchContent to pull in Google Test:
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
FetchContent_MakeAvailable(googletest) cmake_minimum_required(VERSION x.y) — set minimum CMake version project(Name VERSION x.y.z) — declare the project add_executable(name src.cpp ...) — build an executable add_library(name STATIC|SHARED src..) — build a library target_link_libraries(target SCOPE lib) — link a library to a target target_include_directories(target SCOPE dir) — add include paths target_compile_options(target SCOPE flags) — add compiler flags target_compile_definitions(target SCOPE DEF) — add #define macros find_package(Lib REQUIRED) — find an installed library FetchContent_Declare / MakeAvailable — download & build a dependency install(TARGETS ...) — install rules enable_testing() / add_test() — testing integration SCOPE = PUBLIC | PRIVATE | INTERFACE BUILDING (command line): mkdir build && cd build cmake .. -G Ninja # Generate (Ninja recommended) cmake --build . # Build all targets cmake --build . --target name # Build one target ctest # Run tests cmake --install . # Install