The Ultimate Guide to C++ CMake Targets | Target-Based Build System

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

  1. Creating Targets: add_executable, add_library
  2. Target Properties: target_* Commands
  3. Visibility: PUBLIC, PRIVATE, INTERFACE
  4. Dependency Propagation
  5. Common Issues and Solutions
  6. Production Patterns
  7. 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
)
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
KeywordTarget ItselfDependent Targets
PRIVATEUsedNot used
PUBLICUsedUsed
INTERFACENot usedUsed

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

ConceptDescription
TargetBuild entity (executable, library)
target_*Set properties for each target
PUBLICTarget + dependent targets
PRIVATEOnly the target itself
INTERFACEOnly dependent targets (header-only)
Transitive DependenciesAutomatically 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 등으로 검색하시면 이 글이 도움이 됩니다.

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3