CMake Targets in C++ | PUBLIC/PRIVATE, Propagation & Modern CMake
이 글의 핵심
Target-based CMake: replace global include_directories with target_link_libraries and clear PUBLIC/PRIVATE/INTERFACE semantics.
What are CMake Targets? Why Target Based?
Problem Scenario: Confusion in global settings
Problem: Older CMake styles used global commands like include_directories() and link_libraries(). As a project grows, it becomes difficult to keep track of which target uses which header, and dependencies become complicated.
# ❌ Old school: global settings
include_directories(/usr/local/include)
link_libraries(boost_system)
add_executable(app1 main1.cpp)
add_executable(app2 main2.cpp)
# app1, app2 are both linked to boost_system (maybe unintentional)
Solution: Using target-based commands (target_*) makes the dependencies of each target clear and eliminates unnecessary links.
# ✅ 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?
Target refers to the target (executable file, library) that CMake will build. Create targets with add_executable and add_library, and set the properties (header path, link library, compilation options) of each target with the target_* command.
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
index
- Create target: add_executable, add_library
- Target properties: target_* commands
- Visibility: PUBLIC, PRIVATE, INTERFACE
- Dependency propagation
- Frequently occurring problems and solutions
- Production Patterns
- Complete example: multi-library project
1. Create target
executable
# single source
add_executable(myapp main.cpp)
# Multiple sources
add_executable(myapp
src/main.cpp
src/utils.cpp
src/config.cpp
)
# Use variables
set(APP_SOURCES
src/main.cpp
src/utils.cpp
)
add_executable(myapp ${APP_SOURCES})
Static library
add_library(mylib STATIC
src/lib.cpp
src/helper.cpp
)
Dynamic library
add_library(mylib SHARED
src/lib.cpp
src/helper.cpp
)
Header-only library
add_library(mylib INTERFACE)
target_include_directories(mylib INTERFACE include)
OBJECT library
# Create only object files (no linking)
add_library(myobj OBJECT
src/common.cpp
)
# Reuse on 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 to the outside world
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
# C++20 feature requirements
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\nExternal exposure"]
iface["INTERFACE\nPropagation only"]
end
subgraph app["myapp"]
use["use"]
end
pub --> use
iface --> use
| Keyword | target yourself | Dependent Target |
|---|---|---|
| PRIVATE | use | Disabled |
| PUBLIC | use | use |
| INTERFACE | Disabled | use |
Practical example
# mylib: library
add_library(mylib src/lib.cpp)
target_include_directories(mylib
PUBLIC include # Targets linking mylib also use include/
PRIVATE src/internal # Only used inside mylib
)
target_compile_definitions(mylib
PUBLIC MYLIB_VERSION=1 # Target linking mylib is also defined
PRIVATE MYLIB_INTERNAL # defined only inside mylib
)
# myapp: executable file
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)
# myapp can include/ be used (PUBLIC)
# myapp cannot use src/internal (PRIVATE)
# MYLIB_VERSION defined in myapp (PUBLIC)
INTERFACE USE CASE
Used in 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 defined in 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 both 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 both include/a and include/b"]
Block transmission with PRIVATE
# libb links 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. Frequently occurring problems and solutions
Issue 1: Using global commands
Cause: Global commands such as include_directories() and link_libraries() affect all future targets.
# ❌ Incorrect use
include_directories(/usr/local/include)
add_executable(app1 main1.cpp)
add_executable(app2 main2.cpp)
# Both app1 and app2 use /usr/local/include
# ✅ Correct use: Target-specific settings
add_executable(app1 main1.cpp)
target_include_directories(app1 PRIVATE /usr/local/include)
add_executable(app2 main2.cpp)
# app2 is not affected
Issue 2: PUBLIC/PRIVATE confusion
Symptom: Header not found, or unnecessary header exposed.
# ❌ Misuse: Internal header as PUBLIC
add_library(mylib src/lib.cpp)
target_include_directories(mylib PUBLIC src/internal)
# src/internal is exposed to the outside world
# ✅ Correct use
target_include_directories(mylib
PUBLIC include #API header
PRIVATE src/internal # implementation header
)
Problem 3: Circular dependencies
Symptom: CMake Error: Circular dependency.
Cause: A links B, and B links A.
# ❌ Incorrect use
add_library(liba a.cpp)
add_library(libb b.cpp)
target_link_libraries(liba PRIVATE libb)
target_link_libraries(libb PRIVATE liba) # Circulation!
# ✅ Correct use: Dependency redesign
# Common code that both liba and libb depend on is separated into libcommon
add_library(libcommon common.cpp)
add_library(liba a.cpp)
add_library(libb b.cpp)
target_link_libraries(liba PRIVATE libcommon)
target_link_libraries(libb PRIVATE libcommon)
Issue 4: OBJECT library linking
Cause: OBJECT libraries cannot be linked directly with target_link_libraries (prior to CMake 3.12).
# CMake 3.12+: OBJECT library linking possible
add_library(myobj OBJECT common.cpp)
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE myobj)
# CMake 3.11 and below: Use $<TARGET_OBJECTS:>
add_executable(myapp main.cpp $<TARGET_OBJECTS:myobj>)
6. production pattern
Pattern 1: Common settings with interface libraries
# Project global compilation options to interface library
add_library(project_options INTERFACE)
target_compile_features(project_options INTERFACE cxx_std_20)
target_compile_options(project_options INTERFACE
$<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra -Wpedantic>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
)
# Apply to all targets
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE project_options)
add_library(mylib lib.cpp)
target_link_libraries(mylib PRIVATE project_options)
Pattern 2: Alias Target
add_library(mylib src/lib.cpp)
add_library(MyProject::mylib ALIAS mylib)
# Reference to the namespace elsewhere
target_link_libraries(myapp PRIVATE MyProject::mylib)
Pattern 3: Conditional Target
option(BUILD_TOOLS "Build command-line tools" ON)
if(BUILD_TOOLS)
add_executable(tool1 tools/tool1.cpp)
target_link_libraries(tool1 PRIVATE mylib)
endif()
Pattern 4: Target attribute lookup
# Get target properties
get_target_property(MYLIB_INCLUDES mylib INCLUDE_DIRECTORIES)
message(STATUS "mylib includes: ${MYLIB_INCLUDES}")
# Set target properties
set_target_properties(mylib PROPERTIES
VERSION 1.0.0
SOVERSION 1
OUTPUT_NAME "my_library"
)
7. Complete example: multi-library project
Project structure
project/
├── CMakeLists.txt
├── core/
│ ├── CMakeLists.txt
│ ├── core.cpp
│ └── core.h
├── utils/
│ ├── CMakeLists.txt
│ ├── utils.cpp
│ └── utils.h
├── app/
│ ├── CMakeLists.txt
│ └── main.cpp
└── include/
├── core/
│ └── core.h
└── utils/
└── utils.h
root CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(MultiLib VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Common settings
add_library(project_options INTERFACE)
target_compile_features(project_options INTERFACE cxx_std_20)
add_subdirectory(core)
add_subdirectory(utils)
add_subdirectory(app)
core/CMakeLists.txt
add_library(core STATIC
core.cpp
core.h
)
target_include_directories(core
PUBLIC ${CMAKE_SOURCE_DIR}/include/core
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(core PRIVATE project_options)
add_library(MultiLib::core ALIAS core)
utils/CMakeLists.txt
add_library(utils STATIC
utils.cpp
utils.h
)
target_include_directories(utils
PUBLIC ${CMAKE_SOURCE_DIR}/include/utils
)
# Utils depend on core
target_link_libraries(utils
PUBLIC MultiLib::core
PRIVATE project_options
)
add_library(MultiLib::utils ALIAS utils)
app/CMakeLists.txt
add_executable(myapp main.cpp)
# If you link utils, core will also be linked automatically (PUBLIC propagation)
target_link_libraries(myapp PRIVATE
MultiLib::utils
project_options
)
Target command summary
| command | Description |
|---|---|
add_executable(name sources...) | Create executable target |
add_library(name STATIC sources...) | Create static library |
add_library(name SHARED sources...) | Create dynamic library |
add_library(name INTERFACE) | Header-only library |
add_library(name OBJECT sources...) | Create only object files |
target_include_directories(target vis dirs...) | Add header path |
target_link_libraries(target vis libs...) | Library Link |
target_compile_options(target vis opts...) | Add compilation options |
target_compile_definitions(target vis defs...) | Add preprocessing definition |
target_compile_features(target vis features...) | C++ Feature Requirements |
organize
| concept | Description |
|---|---|
| Target | Build target (executable, library) |
| target_* | Setting properties for each target |
| PUBLIC | target + dependent target |
| PRIVATE | Target only |
| INTERFACE | Dependent targets only (header only) |
| transitive dependency | Automatic propagation to PUBLIC |
CMake’s target-based approach clarifies dependencies and isolates build settings by target, making it easy to maintain even large projects.
FAQ
Q1: Global commands vs target commands?
A: Use target command (target_*). Global commands (include_directories, link_libraries) affect all targets, thereby tying up dependencies.
Q2: When should I use PUBLIC vs PRIVATE?
A: The header to be exposed externally is PUBLIC, and the internal implementation header is PRIVATE. When creating a library, set the API header to PUBLIC and the implementation header to PRIVATE.
Q3: When do I use INTERFACE?
A: Used by header-only library. If there are no sources to compile and only headers are provided, create them with add_library(name INTERFACE) and specify the header path with target_include_directories(name INTERFACE ...).
Q4: When do I use the OBJECT library?
A: Used when the same source is reused in multiple targets. You can avoid duplicate compilation by creating only the object file and including it as $<TARGET_OBJECTS:myobj> in multiple executable files/libraries.
Q5: Why use alias targets?
A: By adding a namespace with add_library(MyProject::mylib ALIAS mylib), you can reference external packages and internal targets in a consistent way. Like target_link_libraries(myapp PRIVATE MyProject::mylib Boost::filesystem), they are all unified in :: format.
Q6: What are the CMake Targets learning resources?
A:
- CMake official documentation - cmake-buildsystem
- “Professional CMake: A Practical Guide”
- Effective Modern CMake
One-line summary: CMake target-based approach allows for clear dependency management. Next, it would be a good idea to read CMake find_package.
Good article to read together (internal link)
Here’s another article related to this topic.
- C++ CMake Complete Guide | Cross-platform build·latest CMake 3.28+ features·presets·modules
- C++ CMake find_package complete guide | External library integration
- C++ Conan Complete Guide | Modern C++ package management
Practical tips
These are tips that can be applied right away in practice.
Debugging tips
- If you run into a problem, check the compiler warnings first.
- Reproduce the problem with a simple test case
Performance Tips
- Don’t optimize without profiling
- Set measurable indicators first
Code review tips
- Check in advance for areas that are frequently pointed out in code reviews.
- Follow your team’s coding conventions
Practical checklist
This is what you need to check when applying this concept in practice.
Before writing code
- Is this technique the best way to solve the current problem?
- Can team members understand and maintain this code?
- Does it meet the performance requirements?
Writing code
- Have you resolved all compiler warnings?
- Have you considered edge cases?
- Is error handling appropriate?
When reviewing code
- Is the intent of the code clear?
- Are there enough test cases?
- Is it documented?
Use this checklist to reduce mistakes and improve code quality.
Keywords covered in this article (related search terms)
This article will be helpful if you search for C++, cmake, targets, library, build, dependency, etc.
Related articles
- C++ CMake find_package complete guide | External library integration
- C++ CMake |
- C++ CMake Complete Guide | Cross-platform build·latest CMake 3.28+ features·presets·modules
- C++ Conan Complete Guide | Modern C++ package management
- CMake error |