Using CMake
Every program you write for this course is more than a single .cpp file — sooner or later you will
have multiple source files, external libraries, and test binaries all living in the same project. CMake
is the tool that describes how those pieces fit together and hands off the actual compilation to
whatever compiler and build system your machine uses.
This primer covers enough CMake to build, link, and iterate on the projects in this course. You do
not need to understand every detail; you need to be able to read a CMakeLists.txt, modify it, and
know what to run when something goes wrong.
Why Not Just Use g++ Directly?
You can always compile a single file by hand:
That works for one file. It breaks down quickly when:
- You have five source files that each depend on the same header.
- You want to link against a third-party library like Google Test.
- Your teammate is on Windows and you are on macOS.
- You want your IDE to understand which files belong to the project.
CMake solves all of these. You describe your project once in a CMakeLists.txt file; CMake reads it
and generates build files for whatever toolchain is on the machine — Makefiles on Linux and macOS,
Visual Studio projects on Windows, Ninja files anywhere.
CMake does not compile your code. It generates the instructions for something else — make,
ninja, or an IDE — to do the compiling. That two-step indirection is what makes it cross-platform.
Installation
Before writing any CMakeLists.txt, make sure CMake 3.15 or later is installed.
If it is missing:
- macOS:
brew install cmake - Ubuntu/Debian:
sudo apt install cmake - Windows: download the installer from cmake.org and add it to
your
PATH.
You will also need a C++ compiler:
- macOS:
xcode-select --install(gives youclang++) - Linux:
sudo apt install build-essential(gives youg++) - Windows: install Visual Studio with the "Desktop development with C++" workload, or install
MSYS2 and then
pacman -S mingw-w64-x86_64-gcc
A Minimal Project
Every CMake project needs at least one file: CMakeLists.txt in the project root.
Line by line:
cmake_minimum_required— guards against running with an older CMake that lacks features you need.project— gives the project a name and sets some internal CMake variables.set(CMAKE_CXX_STANDARD 17)— tells the compiler to use C++17. This course uses C++17 throughout.add_executable— declares a build target namedmy_programthat is compiled frommain.cpp.
Out-of-Source Builds
Never run CMake inside your source tree. Instead, create a separate build/ directory. This keeps
generated files (Makefiles, object files, the final binary) out of your source tree so git status
stays clean.
After the second command, my_program (or my_program.exe on Windows) will be in build/.
cmake .. means "configure, using the CMakeLists.txt one directory up." cmake --build .
means "build whatever was configured in the current directory." You can also pass -j4 to
cmake --build . -- -j4 to build with four parallel jobs on Linux/macOS.
Re-running after changes
Once the build/ directory is configured, you only need cmake --build . to recompile after editing
source files. Re-run cmake .. only when you change CMakeLists.txt itself (add a new source file,
change a flag, etc.).
Multiple Source Files
Real projects split code across multiple .cpp and .h files. List all the source files in
add_executable:
Header files (.h, .hpp) do not need to be listed — the compiler finds them when a .cpp includes
them. Only list .cpp files (translation units that produce object files).
If you add a new .cpp file to the project and forget to add it to CMakeLists.txt, the linker
will fail with "undefined reference to …" errors. The fix is almost always: add the file to
add_executable, then re-run cmake ...
Libraries and Multiple Targets
As projects grow it helps to separate a reusable library from the executable that uses it. CMake
supports this with add_library:
target_link_libraries wires data_structures into my_program. The PRIVATE keyword means the
dependency is an implementation detail of my_program — consumers of my_program do not
automatically inherit it.
Include Directories
If your headers live in a subdirectory (e.g., include/), tell CMake where to find them:
target_include_directories with PUBLIC means any target that links against data_structures
also gets include/ added to its include path automatically. That is why my_program does not need
its own target_include_directories call — it inherits the path from the library.
Compiler Warnings
Enable warnings so the compiler catches obvious mistakes before they become hard bugs:
On MSVC (Windows), the equivalent flags are /W4 /WX. For a project that needs to build on both
platforms you can branch:
Debug vs. Release Builds
CMake builds in an unoptimized, no-debug-info mode by default. Specify a build type when you configure:
For coursework, always use Debug so you get useful stack traces and the address sanitizer can catch
memory errors. Use Release only when benchmarking.
After switching build type, delete the build/ directory and reconfigure from scratch. CMake
caches the build type on first configure; changing the flag mid-run can produce inconsistent
builds.
Quick Reference
| Task | Command |
|---|---|
Configure (first time or after editing CMakeLists.txt) | cmake -S . -B build |
| Build all targets | cmake --build build |
| Build in parallel | cmake --build build -j$(nproc) |
| Clean build artifacts | rm -rf build (then reconfigure) |
| Debug build | cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug |
| Release build | cmake -S . -B build -DCMAKE_BUILD_TYPE=Release |
-S . means "source directory is here"; -B build means "put build files in ./build". This
is the modern CMake syntax and does not require you to cd into the build directory first.
Exercises
Q1.
You have this layout:
Write a CMakeLists.txt that compiles both files into an executable named list_program using
C++17.
Q2.
You run cmake --build build and see:
You have linked_list.cpp in your project directory. What is the most likely cause, and how do you
fix it?
Q3.
A teammate checks out your project and runs cmake --build build without configuring first. What
happens, and what should they run instead?
Q4.
You want to add compiler warnings (-Wall -Wextra) to your my_program target. Show where in the
CMakeLists.txt you would add this, and what command to use.