Advanced CMake for C++: Multi-Target Projects, External
Introduction: builds get complex as projects grow
“Every time I add a library, the build breaks”
Projects with several libraries and executables often suffer tangled build settings when new code is added.
Requirements: CMake 3.10+, g++/Clang or MSVC. If you know the basics from #4 CMake intro, this post extends the same toolchain with multi-target workflows, options, and package integration.
Use add_library for shared code and target_link_libraries for dependencies. Splitting by target clarifies build order, include paths, and platform options—and later integrates cleanly with vcpkg or Conan.
Pain scenarios from real CMake usage
Scenario 1: “Cannot find header”
fatal error: mylib.h: No such file or directory
Cause: target_include_directories only PRIVATE, or wrong link order—library A uses B but B’s include path does not propagate to A.
Scenario 2: “undefined reference”
Cause: Missing target_link_libraries, STATIC/SHARED confusion, or add_subdirectory order so a target is used before it exists.
Scenario 3: Windows-only failures
Cause: Missing platform libs (ws2_32, …), path separators, or DLL export macros.
Scenario 4: External library version skew
Cause: System find_package picking mixed Boost versions—pin versions with FetchContent.
Scenario 5: Slow builds
Cause: Duplicate compilation, no PCH, no parallel -j.
Scenario 6: Generated code not built
Cause: .proto or codegen not wired with add_custom_command—missing .pb.cc at link time.
Scenario 7: find_package after install
Cause: Missing Config.cmake / Version.cmake or CMAKE_PREFIX_PATH.
Scenario 8: CI fetch timeouts
Cause: FetchContent cloning full history—use GIT_SHALLOW TRUE.
Problem CMakeLists (anti-pattern)
Listing the same util.cpp, db.cpp in every executable duplicates work and rebuilds everything on any change.
# Bad: everything in one place
// 실행 예제
add_executable(app1 main1.cpp util.cpp db.cpp)
add_executable(app2 main2.cpp util.cpp db.cpp)
add_executable(app3 main3.cpp util.cpp db.cpp)
Better: add_library(util STATIC util.cpp), add_library(db STATIC db.cpp), then each app links util and db once.
add_library(util STATIC util.cpp)
add_library(db STATIC db.cpp)
add_executable(app1 main1.cpp)
target_link_libraries(app1 PRIVATE util db)
Project dependency architecture
flowchart TB
subgraph apps[Executables]
app1[app1]
app2[app2]
app3[app3]
end
subgraph libs[Libraries]
util[util]
db[db]
end
subgraph external[External]
boost[Boost]
json[nlohmann_json]
end
app1 --> util
app1 --> db
app2 --> util
app2 --> db
app3 --> util
app3 --> db
db --> boost
app1 --> json
Table of contents
- Project structure
- Targets and libraries
- Target properties
- External libraries
- Build options
- Common errors
- Practical patterns
- Best practices
Advanced topics: FetchContent, ExternalProject,
add_custom_command, generator expressions,install+ Config.cmake, protobuf integration.
1. Project structure
Recommended layout
Use a root CMakeLists.txt for global settings (C++ standard, build type) and add_subdirectory for src, lib, tests, external.
myproject/
├── CMakeLists.txt
├── src/
│ ├── CMakeLists.txt
│ └── ...
├── lib/
│ └── mylib/
├── tests/
└── external/
Root CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
add_subdirectory(lib)
add_subdirectory(src)
add_subdirectory(tests)
2. Targets and libraries
- STATIC:
.a/.liblinked into the executable at link time. - SHARED:
.so/.dllloaded at runtime. - INTERFACE: header-only—expose includes with
target_include_directories(mylib INTERFACE include/).
PUBLIC vs PRIVATE vs INTERFACE
PUBLIC on target_link_libraries(B PUBLIC A) means consumers of B also need A. PRIVATE keeps A internal to B.
target_link_libraries(B PUBLIC A)
target_link_libraries(C PRIVATE B)
# C gets A automatically through B’s PUBLIC edge.
3. Target properties
Use target_include_directories, target_compile_options, target_link_libraries with PUBLIC/PRIVATE/INTERFACE as appropriate.
Generator expressions
$<CONFIG:Debug>, $<BUILD_INTERFACE:...>, $<INSTALL_INTERFACE:...> apply values at generate time, not configure time.
target_compile_definitions(mylib PRIVATE
$<$<CONFIG:Debug>:DEBUG_MODE>
)
target_include_directories(mylib PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
4. External libraries
find_package
find_package(Boost REQUIRED COMPONENTS system filesystem)
target_link_libraries(myapp PRIVATE Boost::system Boost::filesystem)
FetchContent
include(FetchContent)
FetchContent_Declare(
json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.2
)
FetchContent_MakeAvailable(json)
target_link_libraries(myapp PRIVATE nlohmann_json::nlohmann_json)
find_package first, else FetchContent
Try system/vcpkg package; fall back to git tag for reproducible builds.
ExternalProject
Builds in the build phase (not configure)—use for non-CMake deps or install-then-find workflows.
add_custom_command / protobuf
Wire generated sources as outputs and add them to an add_library so CMake knows when to run the generator.
5. Build options
option(BUILD_TESTS "Build tests" ON)
if(BUILD_TESTS)
add_subdirectory(tests)
endif()
CMAKE_BUILD_TYPE: Debug (-g -O0), Release (-O3 -DNDEBUG), RelWithDebInfo.
Platform blocks: if(WIN32), elseif(APPLE), elseif(UNIX) for macros and extra libraries (ws2_32, pthread).
6. Common errors (summary)
| Issue | Fix |
|---|---|
| Missing headers | PUBLIC target_include_directories + correct usage |
| Undefined reference | List all libs in target_link_libraries; fix subdirectory order |
| find_package fails | CMAKE_PREFIX_PATH, vcpkg toolchain, Conan |
| Duplicate symbols | inline / single definition in .cpp |
| DLL not found | Align RUNTIME_OUTPUT_DIRECTORY with DLL location |
| Generator expr typo | Use $<OR:$<...>,$<...>> not commas inside one $<CXX_COMPILER_ID:...> incorrectly |
| ExternalProject rebuilds | Set BUILD_BYPRODUCTS |
| Custom command not run | Attach OUTPUT to a target’s sources or a custom target dependency |
7. Practical patterns
- BUILD_INTERFACE / INSTALL_INTERFACE for reusable libraries.
- CTest + FetchContent googletest for tests.
- install(TARGETS … EXPORT) + install(EXPORT) for
find_packageconsumers. - write_basic_package_version_file + configure_package_config_file for Config.cmake.
- target_precompile_headers (CMake 3.16+) to speed builds.
8. Best practices
- Modularize with libraries; minimize duplicate sources.
- Mark PUBLIC vs PRIVATE deliberately.
- Pin dependency versions (FetchContent
GIT_TAG). - Platform-specific code behind
if(WIN32)etc. - Options for tests, examples, sanitizers.
- Define install rules for redistribution.
Build flow (sequence)
sequenceDiagram
participant Dev as Developer
participant CMake as CMake configure
participant Fetch as FetchContent
participant Build as Build system
Dev->>CMake: cmake -B build
CMake->>Fetch: Download external deps
Fetch->>CMake: Targets created
CMake->>CMake: Dependency graph
CMake->>Build: Generate Ninja/Makefile
Dev->>Build: cmake --build build
Build->>Build: lib (mylib)
Build->>Build: src (myapp)
Build->>Build: tests (optional)
Related posts
Summary
| Task | Command |
|---|---|
| Library | add_library |
| Executable | add_executable |
| Link | target_link_libraries |
| Includes | target_include_directories |
| External | find_package, FetchContent |
| Options | option |
| Tests | enable_testing, add_test |
| Next: Package managers (#17-2) | |
| Previous: Logging and assertions (#16-3) |
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Advanced CMake: multi-target layouts, target_link_libraries, FetchContent, find_package, generator expressions.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- VS Code C++ 설정 | IntelliSense·빌드·디버깅
- CMake 입문 | 수십 개 파일 컴파일할 때 필요한 빌드 자동화 (CMakeLists.txt 기초)
- C++ 패키지 매니저 | vcpkg·Conan으로 ‘라이브러리 설치 지옥’ 탈출하기
Keywords Covered in This Article (Related Search Terms)
This article covers C++, CMake, Build system, Project layout, Cross-platform, FetchContent.