The Ultimate Guide to C++ CMake Targets | Target-Based Build System
이 글의 핵심
CMake targets: INTERFACE libraries, PUBLIC/PRIVATE usage requirements, and modern target_link_libraries graphs.
What are CMake Targets? Why Target-Based?
Problem Scenario: The Chaos of Global Settings
The Problem: In older CMake styles, global commands like include_directories() and link_libraries() were used. As projects grew larger, it became difficult to track which target used which headers, leading to tangled dependencies.
# ❌ Old style: Global settings
include_directories(/usr/local/include)
link_libraries(boost_system)
add_executable(app1 main1.cpp)
add_executable(app2 main2.cpp)
# Both app1 and app2 are linked to boost_system (possibly unintended)
The Solution: Using target-based commands (target_*) makes the dependencies of each target clear and avoids unnecessary linking.
# ✅ Modern: Target-specific settings
add_executable(app1 main1.cpp)
target_include_directories(app1 PRIVATE /usr/local/include)
target_link_libraries(app1 PRIVATE Boost::system)
add_executable(app2 main2.cpp)
# app2 is not linked to boost
What is a Target?
A target in CMake refers to a build entity (an executable or a library). You create targets using add_executable or add_library, and configure each target’s properties (header paths, linked libraries, compile options) using target_* commands.
flowchart TD
subgraph targets["Targets"]
exe["add_executable(myapp)"]
lib["add_library(mylib)"]
end
subgraph properties["Target Properties"]
inc["target_include_directories"]
link["target_link_libraries"]
opt["target_compile_options"]
def["target_compile_definitions"]
end
exe --> inc
exe --> link
lib --> inc
lib --> opt
Table of Contents
- Creating Targets: add_executable, add_library
- Target Properties: target_* Commands
- Visibility: PUBLIC, PRIVATE, INTERFACE
- Dependency Propagation
- Common Issues and Solutions
- Production Patterns
- Complete Example: Multi-Library Project
1. Creating Targets
Executables
# Single source
add_executable(myapp main.cpp)
# Multiple sources
add_executable(myapp
src/main.cpp
src/utils.cpp
src/config.cpp
)
# Using variables
set(APP_SOURCES
src/main.cpp
src/utils.cpp
)
add_executable(myapp ${APP_SOURCES})
Static Libraries
add_library(mylib STATIC
src/lib.cpp
src/helper.cpp
)
Shared Libraries
add_library(mylib SHARED
src/lib.cpp
src/helper.cpp
)
Header-Only Libraries
add_library(mylib INTERFACE)
target_include_directories(mylib INTERFACE include)
OBJECT Libraries
# Generates object files only (not linked)
add_library(myobj OBJECT
src/common.cpp
)
# Reuse in multiple targets
add_executable(app1 main1.cpp $<TARGET_OBJECTS:myobj>)
add_executable(app2 main2.cpp $<TARGET_OBJECTS:myobj>)
2. Target Properties: target_* Commands
target_include_directories
add_library(mylib src/lib.cpp)
target_include_directories(mylib
PUBLIC include # Exposed externally
PRIVATE src/internal # Internal use only
)
target_link_libraries
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE
mylib
Boost::filesystem
pthread
)
target_compile_options
target_compile_options(myapp PRIVATE
-Wall
-Wextra
-Werror
$<$<CONFIG:Debug>:-O0 -g>
$<$<CONFIG:Release>:-O3>
)
target_compile_definitions
target_compile_definitions(myapp PRIVATE
APP_VERSION="1.0"
$<$<CONFIG:Debug>:DEBUG_MODE>
$<$<PLATFORM_ID:Windows>:WINDOWS_BUILD>
)
target_compile_features
# Require C++20 features
target_compile_features(myapp PRIVATE cxx_std_20)
3. Visibility: PUBLIC, PRIVATE, INTERFACE
Concept
flowchart LR
subgraph lib["mylib"]
priv["PRIVATE\nInternal use only"]
pub["PUBLIC\nExposed externally"]
iface["INTERFACE\nPropagation only"]
end
subgraph app["myapp"]
use["Usage"]
end
pub --> use
iface --> use
| Keyword | Target Itself | Dependent Targets |
|---|---|---|
| PRIVATE | Used | Not used |
| PUBLIC | Used | Used |
| INTERFACE | Not used | Used |
Practical Example
# mylib: Library
add_library(mylib src/lib.cpp)
target_include_directories(mylib
PUBLIC include # Targets linking to mylib can use include/
PRIVATE src/internal # Only used internally by mylib
)
target_compile_definitions(mylib
PUBLIC MYLIB_VERSION=1 # Defined for targets linking to mylib
PRIVATE MYLIB_INTERNAL # Defined only within mylib
)
# myapp: Executable
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)
# myapp can use include/ (PUBLIC)
# myapp cannot use src/internal (PRIVATE)
# MYLIB_VERSION is defined for myapp (PUBLIC)
Use Case for INTERFACE
Used for header-only libraries.
add_library(header_only INTERFACE)
target_include_directories(header_only INTERFACE include)
target_compile_definitions(header_only INTERFACE HEADER_ONLY_LIB)
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE header_only)
# myapp can use include/
# HEADER_ONLY_LIB is defined for myapp
4. Dependency Propagation
Transitive Dependencies
# liba: Lowest-level library
add_library(liba STATIC a.cpp)
target_include_directories(liba PUBLIC include/a)
# libb: Depends on liba
add_library(libb STATIC b.cpp)
target_link_libraries(libb PUBLIC liba)
target_include_directories(libb PUBLIC include/b)
# myapp: Depends on libb
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE libb)
# myapp can use include/a and include/b (PUBLIC propagation)
flowchart TD
liba["liba\nPUBLIC include/a"]
libb["libb\nPUBLIC include/b"]
myapp["myapp"]
libb -->|PUBLIC| liba
myapp -->|PRIVATE| libb
note1["myapp can use include/a and include/b"]
Blocking Propagation with PRIVATE
# libb links to liba as PRIVATE
target_link_libraries(libb PRIVATE liba)
# myapp
target_link_libraries(myapp PRIVATE libb)
# myapp can only use include/b (liba is not propagated)
5. Common Issues and Solutions
…
6. Production Patterns
…
7. Complete Example: Multi-Library Project
…
Summary
| Concept | Description |
|---|---|
| Target | Build entity (executable, library) |
| target_* | Set properties for each target |
| PUBLIC | Target + dependent targets |
| PRIVATE | Only the target itself |
| INTERFACE | Only dependent targets (header-only) |
| Transitive Dependencies | Automatically propagated via PUBLIC |
CMake’s target-based approach makes dependency management clear and isolates build configurations, making it easier to maintain large-scale projects.
FAQ
…
In one sentence: CMake’s target-based approach allows for clear dependency management. Next, check out CMake find_package for more!
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ CMake 완벽 가이드 | 크로스 플랫폼 빌드·최신 CMake 3.28+ 기능·프리셋·모듈
- C++ CMake find_package 완벽 가이드 | 외부 라이브러리 통합
- C++ Conan 완벽 가이드 | 현대적인 C++ 패키지 관리
이 글에서 다루는 키워드 (관련 검색어)
C++, cmake, targets, library, build, dependency 등으로 검색하시면 이 글이 도움이 됩니다.