Build multi-file C++ projects with CMake
Organize a small C++ project with source files, headers, CMake targets, and repeatable builds.
Learning objectives
- Explain why a project needs a build system as it grows beyond one file.
- Understand CMakeLists.txt, targets, and source organization.
- Configure and build a small C++ project.
- Keep build output separate from source files.
Prerequisites: compiler command-line use, functions, and header/source file organization. Review Compiler toolchain and Header files first.
Example project layout
student-app/
CMakeLists.txt
include/
student.hpp
src/
main.cpp
student.cppHeaders declare the interface; source files provide definitions. A build system records how these pieces form an executable or library.
Header: include/student.hpp
#pragma once
#include <string>
struct Student {
std::string name;
int score;
};
bool passed(const Student& student);Implementation: src/student.cpp
#include "student.hpp"
bool passed(const Student& student) {
return student.score >= 40;
}Program: src/main.cpp
#include "student.hpp"
#include <iostream>
int main() {
Student s{"Asha", 82};
std::cout << passed(s) << '\n';
}CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(StudentApp LANGUAGES CXX)
add_executable(student_app
src/main.cpp
src/student.cpp
)
target_include_directories(student_app PRIVATE include)
target_compile_features(student_app PRIVATE cxx_std_17)
if(MSVC)
target_compile_options(student_app PRIVATE /W4 /permissive-)
else()
target_compile_options(student_app PRIVATE -Wall -Wextra -Wpedantic)
endif()add_executable creates a target. CMake tracks the source files and generates build instructions for a selected build tool. target_include_directories tells the compiler where to find project headers. target_compile_features requests C++17 support for this target.
Configure and build
cmake -S . -B build cmake --build build ./build/student_app
On Windows, the executable may be under a configuration-specific subdirectory when using a multi-configuration generator such as Visual Studio. Run the generated executable from its actual build location.
Compiler options and portability
The warning flags shown are common to GCC and Clang. For a cross-platform project, conditionally select compiler-specific warning options rather than assuming every compiler accepts the same flags. CMake itself is cross-platform; the generated build tool depends on the chosen generator.
Common mistakes
- Including a .cpp file instead of compiling it as part of the target.
- Forgetting to add a source file to add_executable.
- Putting generated build files into the source directory.
- Using a relative include path that works only from one working directory.
- Assuming the executable path is identical for every generator.
Practice
- Add a second function in student.cpp and declare it in the header.
- Change the target to require C++20.
- Explain the difference between configure and build.
Show solutions
- Add a declaration to the header and definition to student.cpp; call it from main.
- Use
cxx_std_20. - Configure generates build-system files; build compiles and links the target.
Summary
CMake describes targets and their dependencies, then generates build instructions. Keep headers, source files, and build output organized so projects are repeatable and portable.