Skip to content

Expert · Exceptions, concurrency and building projects with CMake · topic 37 of 42 · 35 min

Building real projects: CMake, warnings, debuggers and sanitizers

Explanation

Real C++ projects have many files and libraries, so teams describe the build in CMake (a CMakeLists.txt file) instead of typing long g++ commands. CMake generates build files for Make, Ninja or Visual Studio. Combine it with warnings, a debugger (gdb, lldb or Visual Studio), sanitizers and a formatter to work like a professional.

Why a build system?

With two files you can type g++ main.cpp gst.cpp -o app. With 200 files, external libraries, debug and release builds, Windows and Linux, typing commands is impossible, and recompiling everything after a one-line change wastes minutes. A build system knows which files depend on which, rebuilds only what changed, and works the same on every machine. CMake is the most widely used build system generator for C++.

A typical project layout

output
billing/
├── CMakeLists.txt
├── include/billing/gst.h
├── src/gst.cpp
├── src/main.cpp
└── tests/gst_test.cpp
Headers in include/, sources in src/, tests in tests/. Many teams follow this pattern.

A minimal CMakeLists.txt

output
cmake_minimum_required(VERSION 3.20)
project(Billing LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_library(billing src/gst.cpp)
target_include_directories(billing PUBLIC include)

add_executable(billing_app src/main.cpp)
target_link_libraries(billing_app PRIVATE billing)

if (MSVC)
  target_compile_options(billing PRIVATE /W4)
else()
  target_compile_options(billing PRIVATE -Wall -Wextra -Wpedantic)
endif()
add_library makes a library from gst.cpp; add_executable makes the program; target_link_libraries connects them.

Modern CMake is "target-based": you describe targets (libraries and executables) and attach settings to them with target_* commands, instead of setting global flags. PUBLIC settings pass on to whoever links the target; PRIVATE ones do not.

Configure, build, run

bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
./build/billing_app

# Release build in a separate folder
cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release
cmake --build build-release
-S is the source folder, -B the build folder. Build files never mix with your source. (With Visual Studio generators, choose the type at build time: cmake --build build --config Release.)
Think of it like this: CMakeLists.txt is like a recipe card that any kitchen can follow. CMake reads the card and writes out instructions for the specific kitchen it is in: a gas stove (Make or Ninja on Linux) or an induction cooktop (Visual Studio on Windows). You keep one recipe card.

Using libraries

output
find_package(fmt CONFIG REQUIRED)               # a library installed on the system or via vcpkg/Conan
target_link_libraries(billing_app PRIVATE fmt::fmt)

include(FetchContent)                           # or download one at configure time
FetchContent_Declare(googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG v1.15.2)
FetchContent_MakeAvailable(googletest)
Package managers like vcpkg and Conan install libraries; FetchContent downloads a pinned version during configure.

Debug versus Release

Build typeFlags (GCC/Clang)Use for
Debug-g, no optimisationstepping through code in a debugger
Release-O3 -DNDEBUGshipping; asserts are removed
RelWithDebInfo-O2 -g -DNDEBUGprofiling and debugging optimised code

Debuggers and sanitizers

  • A debugger (gdb on Linux, lldb on macOS, the Visual Studio or VS Code debugger) lets you pause at a breakpoint, step line by line and inspect variables. Build with -g (Debug) first.
  • AddressSanitizer (-fsanitize=address) finds out-of-bounds access, use-after-free and leaks.
  • UndefinedBehaviorSanitizer (-fsanitize=undefined) finds signed overflow, bad shifts and similar undefined behaviour.
  • ThreadSanitizer (-fsanitize=thread) finds data races (do not combine it with AddressSanitizer in the same build).
  • Valgrind (Linux) finds memory errors without recompiling, but runs programs much slower.
output
target_compile_options(billing_app PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer)
target_link_options(billing_app PRIVATE -fsanitize=address,undefined)
Add sanitizers to a test build in CMake (GCC/Clang on Linux and macOS).

Formatting and static analysis

clang-format formats code automatically from a .clang-format file, so reviews focus on logic, not spaces. clang-tidy and compiler warnings catch bug patterns and suggest modern replacements. Many teams run all of these in CI (continuous integration: automatic checks on every pull request).

Careful: Do not commit your build/ folder to Git. Add it to .gitignore. Commit CMakeLists.txt and the sources; anyone can then regenerate the build files on their own machine.
Tip: Generating compile_commands.json (set(CMAKE_EXPORT_COMPILE_COMMANDS ON)) lets editors like VS Code with clangd understand your project: jump to definition, errors as you type and accurate autocomplete.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Building real projects: CMake, warnings, debuggers and sanitizers · 1/6

CMake

Real projects have many files and libraries. A build system like CMake builds them reliably on every machine.

Prefer a video by a teacher? Find free videos on “cmake tutorial for beginners modern cmake targets” on YouTube ↗

In real life: A small team project for a college hackathon

Three students build a library-management tool in C++. With one CMakeLists.txt, one student on Windows with Visual Studio and two on Ubuntu with g++ all build the same project with the same two commands. A GitHub Actions workflow builds it with -Wall -Wextra and runs the tests with AddressSanitizer on every push, catching a use-after-free the night before the demo.

output
cmake_minimum_required(VERSION 3.20)
project(LibraryManager LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

add_library(library_core src/book.cpp src/member.cpp src/loans.cpp)
target_include_directories(library_core PUBLIC include)

add_executable(library_app src/main.cpp)
target_link_libraries(library_app PRIVATE library_core)

enable_testing()
add_executable(library_tests tests/loans_test.cpp)
target_link_libraries(library_tests PRIVATE library_core)
add_test(NAME loans COMMAND library_tests)
ctest --test-dir build runs every test registered with add_test.

Practice (0/4 done)

Task 1 · Question

Which command makes CMake generate build files into a folder named build?

Task 2 · Question

What does target_link_libraries(app PRIVATE billing) do?

Task 3 · Question

Which build type is best for stepping through code in a debugger?

Task 4 · Practice

Turn your earlier GST functions into a CMake project: a library target with gst.cpp and include/gst.h, an executable target using it, C++20 required, warnings on, and a Debug build with AddressSanitizer. Build it with cmake -S . -B build and cmake --build build, and run it.

Quick quiz

Question 1

1.What does CMake produce?

Question 2

2.What does PUBLIC mean in target_include_directories(lib PUBLIC include)?

Question 3

3.Which sanitizer finds use-after-free bugs?

Question 4

4.Why keep the build folder out of Git?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does CMake produce?

Answer: Build files for a native tool such as Make, Ninja or Visual Studio

CMake is a build system generator; the native tool then compiles.

What does PUBLIC mean in target_include_directories(lib PUBLIC include)?

Answer: lib uses the folder AND so does anything that links lib

PUBLIC requirements propagate to consumers; PRIVATE ones do not.

Which sanitizer finds use-after-free bugs?

Answer: AddressSanitizer

AddressSanitizer checks memory accesses.

Why keep the build folder out of Git?

Answer: It is generated, machine-specific and large; anyone can regenerate it

Commit sources and CMakeLists.txt, not build output.

Should sanitizer builds be shipped to customers?Related

Answer: No; use them in development and testing

Ship normal hardened builds.

From Sanitizers and warnings

Which tool generates Makefiles for cross-platform projects?Related

Answer: CMake

CMake generates build files.

From Building with make

What does "Step Into" do in a debugger?Related

Answer: Goes inside the method called on the current line

Step Into enters the called method.

From Reading errors and debugging

Which flag adds debug information for gdb?Related

Answer: -g

Line numbers and variable names.

From Debugging with gdb

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Exceptions, concurrency and building projects with CMake”. The module exam comes next.
  2. Module 9 exam: Exceptions, concurrency and CMake · 12 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Competitive programming, interviews and careers. 42 topics left in the course before the final exam.

← Previous: Concurrency 2: atomics, async, futures and condition variables