What you'll learn
A professional C++ build describes targets and their requirements—not a pile of global flags. You will organize translation units, create modern CMake targets, consume dependencies, install packages, manage ABI boundaries, and automate builds across platforms.
By the end, you'll be able to:
- Explain translation units, linking, and library kinds
- Create target-based CMake with transitive usage requirements
- Integrate versioned dependencies without hidden global state
- Install, export, package, test, and release a reusable target
Translation units and linking
Each source file after preprocessing is a translation unit compiled independently into an object file. The linker resolves definitions across objects and libraries. Headers should contain declarations and templates that consumers need; non-inline definitions generally belong in one source file to satisfy the One Definition Rule.
| Artifact | Role |
|---|---|
| Object file | Compiled code and data from one translation unit |
| Static library | Archive whose needed objects are copied into consumers |
| Shared library | Dynamically loaded code with a runtime ABI boundary |
| Executable | Linked program entry point and dependencies |
| Header-only library | Definitions instantiated or inlined in consumer translation units |
Key idea
Modern CMake targets
A target describes an executable or library plus the requirements needed to build and use it.PRIVATE requirements affect only the target, INTERFACE requirements affect consumers, and PUBLIC requirements affect both. Avoid directory-wide flags that silently leak into unrelated targets.
cmake_minimum_required(VERSION 3.24)
project(tasker VERSION 1.0.0 LANGUAGES CXX)
add_library(tasker src/task.cpp)
add_library(tasker::tasker ALIAS tasker)
target_compile_features(tasker PUBLIC cxx_std_20)
target_include_directories(tasker
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_compile_options(tasker PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wall;-Wextra;-Wpedantic>
$<$<CXX_COMPILER_ID:MSVC>:/W4;/permissive->
)
add_executable(tasker_cli app/main.cpp)
target_link_libraries(tasker_cli PRIVATE tasker::tasker)Tip
target_compile_features. Do not manually paste compiler-specific -std= flags into every target.Dependencies and usage requirements
Prefer dependencies that export CMake package targets, such asSomeLibrary::Core. Link that target and inherit its include paths, definitions, and transitive libraries. Pin compatible versions through a package manager, vendored source, or reviewed FetchContent declaration according to the project's reproducibility policy.
find_package(fmt 10 CONFIG REQUIRED)
target_link_libraries(tasker_cli PRIVATE fmt::fmt)
# Or, when project policy permits fetching reviewed source:
include(FetchContent)
FetchContent_Declare(example
GIT_REPOSITORY https://example.invalid/example.git
GIT_TAG 0123456789abcdef0123456789abcdef01234567
)Watch out
main is not a reproducible dependency version. Record exact versions or immutable revisions and verify source integrity in the chosen dependency workflow.Build and analysis configurations
Debug, release, sanitizer, coverage, and profiling builds have different goals. Prefer CMake presets to capture generators, toolchains, cache variables, and build directories without asking developers to remember long command lines. Keep output directories separate so configurations never contaminate one another.
{
"version": 6,
"configurePresets": [
{
"name": "debug",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/debug",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
},
{
"name": "release",
"inherits": "debug",
"binaryDir": "${sourceDir}/build/release",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Release" }
}
]
}- Compile tests against the same public target consumers use
- Make sanitizer options a target or preset concern
- Generate compile_commands.json for analysis tools when supported
- Test both optimized and diagnostic configurations
Install, export, and package
Installation copies binaries, libraries, headers, and package metadata into a relocatable prefix. Exported targets let downstream CMake projects call find_package and link the same names used in-tree. Packages also need version compatibility policy, licenses, runtime dependencies, and platform-appropriate archive or installer formats.
include(GNUInstallDirs)
install(TARGETS tasker
EXPORT taskerTargets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(EXPORT taskerTargets
FILE taskerTargets.cmake
NAMESPACE tasker::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/tasker
)ABI, CI, and releases
A shared-library ABI includes symbol names, calling conventions, layouts, compiler runtime expectations, and exception boundaries. Source compatibility does not guarantee binary compatibility. Keep unstable implementation behind a Pimpl or C boundary when ABI stability matters, control symbol visibility, and version breaking releases deliberately.
- Build on every supported compiler, operating system, and architecture tier
- Run unit, integration, sanitizer, and packaging tests
- Install into a clean prefix and build a separate consumer against it
- Cache dependencies carefully without caching stale build outputs
- Create signed or checksummed release artifacts from tagged source
- Publish compiler, standard-library, runtime, and platform compatibility
Note
Recap & quick check
Key takeaways
- Translation units compile independently; the linker resolves cross-unit definitions.
- Modern CMake expresses build and usage requirements on targets with scoped visibility.
- Dependencies should be versioned, immutable, and consumed through exported targets.
- Presets make diagnostic and optimized configurations repeatable.
- A professional release validates installation, downstream consumption, ABI policy, and platform matrices.
Quick check
1. What does PUBLIC mean in target_link_libraries?
2. Which stage reports an unresolved external symbol?
3. Why pin an immutable dependency revision?
4. What validates an installed CMake package best?
Next: Module 28 — Architecture, Design Patterns & API Design, where target boundaries become maintainable software boundaries.