Build Systems¶
Writing C++ code is only the first step. To execute the code, it must be compiled and linked. While using the compiler directly works for single-file projects, professional C++ development relies on automated build systems to manage multiple dependencies, trigger incremental compilations, and compile binaries across different platforms.
1. Why Do We Need Build Systems?¶
A typical C++ application consists of multiple header files (.hpp) and source files (.cpp). If you modify only a single .cpp file, compiling the entire project from scratch is highly inefficient.
A Build System performs the following: 1. Dependency Tracking: Knows which source files depend on which headers. 2. Incremental Compilations: Compiles only the modified files, linking them with the cached unchanged object files. 3. Linker Orchestration: Manages linking order and integrates external third-party libraries.
2. Make and Makefiles¶
make is a classic build tool that reads instructions from a file named Makefile. It uses rules to compile code based on file timestamps.
Anatomy of a Simple Makefile¶
[!IMPORTANT] Strict Tab Constraint: Makefile commands must be indented using a literal Tab character. Using spaces instead of a tab will cause
maketo fail with a syntax error.
3. Modern Target-Based CMake¶
make is platform-dependent and becomes verbose in large projects. CMake is a meta-build system. It does not compile code directly; instead, it generates build files (like Makefiles for Linux, or Visual Studio Project files for Windows).
Legacy vs. Modern Target-Based CMake¶
- Legacy CMake (Avoid): Set global settings using
include_directories()andlink_libraries(). This pollutes all targets in a project. - Modern CMake (Preferred): Uses a Target-Based approach where properties are bound directly to specific executable or library targets using
target_*commands.
Modern CMakeLists.txt Structure¶
Out-of-Source Builds¶
To keep your source code clean, always compile C++ projects in a separate build folder:
4. Third-Party Dependency Management: FetchContent¶
Modern CMake (3.11+) provides a built-in module called FetchContent that automatically downloads, builds, and links external libraries at configure-time, solving the legacy problem of manually installing libraries to /usr/local/lib.
5. Static vs. Dynamic Libraries¶
When building libraries, you must choose between two formats:
1. Static Libraries (.a on Linux, .lib on Windows)¶
The linker copies all object code from the library directly into your final executable. - Pros: Independent. The executable can be distributed alone without any runtime library dependencies. - Cons: Large binary size. Modifying the library code requires compiling the entire application again.
2. Dynamic/Shared Libraries (.so on Linux, .dll on Windows)¶
The linker inserts a reference to the library, but the actual code is loaded dynamically in RAM when the application runs.
- Pros: Small binary size. Multiple applications can share a single library file in RAM, reducing system memory usage.
- Cons: Dependency issues ("DLL hell"). The application will crash on startup if the .so file is missing or updated with breaking changes.
Related Guides¶
- Introduction — First compiler flags and basic compilation steps.
- STL Containers — External STL code structure.