C++ CMake Targets 완벽 가이드 | 타겟 기반 빌드 시스템
이 글의 핵심
C++ CMake Targets 완벽 가이드에 대한 실전 가이드입니다. 타겟 기반 빌드 시스템 등을 예제와 함께 상세히 설명합니다.
다른 빌드 모델과 비교
CMake 타겟은 의존성 그래프의 노드에 가깝습니다. Rust Cargo의 크레이트·타겟이나 npm 패키지 트리와 완전히 같지는 않지만, “무엇이 무엇에 링크되는가”를 명시한다는 점에서 비교하면 팀 온보딩에 도움이 됩니다. Make 기반은 C++ Makefile·빌드 시스템 비교와 연결해 보세요.
CMake Targets란? 왜 타겟 기반인가
문제 시나리오: 전역 설정의 혼란
문제: 예전 CMake 스타일에서는 include_directories(), link_libraries() 같은 전역 명령을 썼습니다. 프로젝트가 커지면 어떤 타겟이 어떤 헤더를 쓰는지 추적하기 어렵고, 의존성이 꼬입니다.
# ❌ 구식: 전역 설정
include_directories(/usr/local/include)
link_libraries(boost_system)
add_executable(app1 main1.cpp)
add_executable(app2 main2.cpp)
# app1, app2 모두 boost_system에 링크됨 (의도하지 않았을 수도)
해결: 타겟 기반 명령(target_*)을 쓰면 각 타겟의 의존성이 명확해지고, 불필요한 링크가 없어집니다.
# ✅ 모던: 타겟별 설정
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는 boost에 링크되지 않음
타겟이란
타겟은 CMake에서 빌드할 대상(실행 파일, 라이브러리)을 의미합니다. add_executable, add_library로 타겟을 만들고, target_* 명령으로 각 타겟의 속성(헤더 경로, 링크 라이브러리, 컴파일 옵션)을 설정합니다.
flowchart TD
subgraph targets["타겟"]
exe["add_executable(myapp)"]
lib["add_library(mylib)"]
end
subgraph 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
목차
- 타겟 생성: add_executable, add_library
- 타겟 속성: target_* 명령어
- 가시성: PUBLIC, PRIVATE, INTERFACE
- 의존성 전파
- 자주 발생하는 문제와 해결법
- 프로덕션 패턴
- 완전한 예제: 멀티 라이브러리 프로젝트
1. 타겟 생성
실행 파일
# 단일 소스
add_executable(myapp main.cpp)
# 여러 소스
add_executable(myapp
src/main.cpp
src/utils.cpp
src/config.cpp
)
# 변수 사용
set(APP_SOURCES
src/main.cpp
src/utils.cpp
)
add_executable(myapp ${APP_SOURCES})
정적 라이브러리
add_library(mylib STATIC
src/lib.cpp
src/helper.cpp
)
동적 라이브러리
add_library(mylib SHARED
src/lib.cpp
src/helper.cpp
)
헤더 전용 라이브러리
add_library(mylib INTERFACE)
target_include_directories(mylib INTERFACE include)
OBJECT 라이브러리
# 오브젝트 파일만 생성 (링크 안 함)
add_library(myobj OBJECT
src/common.cpp
)
# 여러 타겟에서 재사용
add_executable(app1 main1.cpp $<TARGET_OBJECTS:myobj>)
add_executable(app2 main2.cpp $<TARGET_OBJECTS:myobj>)
2. 타겟 속성: target_* 명령어
target_include_directories
add_library(mylib src/lib.cpp)
target_include_directories(mylib
PUBLIC include # 외부에 노출
PRIVATE src/internal # 내부 전용
)
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 기능 요구
target_compile_features(myapp PRIVATE cxx_std_20)
3. 가시성: PUBLIC, PRIVATE, INTERFACE
개념
flowchart LR
subgraph lib["mylib"]
priv["PRIVATE\n내부 전용"]
pub["PUBLIC\n외부 노출"]
iface["INTERFACE\n전파만"]
end
subgraph app["myapp"]
use["사용"]
end
pub --> use
iface --> use
| 키워드 | 타겟 자신 | 의존 타겟 |
|---|---|---|
| PRIVATE | 사용 | 사용 안 함 |
| PUBLIC | 사용 | 사용 |
| INTERFACE | 사용 안 함 | 사용 |
실전 예시
# mylib: 라이브러리
add_library(mylib src/lib.cpp)
target_include_directories(mylib
PUBLIC include # mylib를 링크하는 타겟도 include/ 사용
PRIVATE src/internal # mylib 내부에서만 사용
)
target_compile_definitions(mylib
PUBLIC MYLIB_VERSION=1 # mylib를 링크하는 타겟도 정의됨
PRIVATE MYLIB_INTERNAL # mylib 내부에서만 정의됨
)
# myapp: 실행 파일
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)
# myapp은 include/ 사용 가능 (PUBLIC)
# myapp은 src/internal 사용 불가 (PRIVATE)
# myapp에 MYLIB_VERSION 정의됨 (PUBLIC)
INTERFACE 사용 사례
헤더 전용 라이브러리에서 사용합니다.
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은 include/ 사용 가능
# myapp에 HEADER_ONLY_LIB 정의됨
4. 의존성 전파
전이적 의존성
# liba: 최하위 라이브러리
add_library(liba STATIC a.cpp)
target_include_directories(liba PUBLIC include/a)
# libb: liba에 의존
add_library(libb STATIC b.cpp)
target_link_libraries(libb PUBLIC liba)
target_include_directories(libb PUBLIC include/b)
# myapp: libb에 의존
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE libb)
# myapp은 include/a, include/b 모두 사용 가능 (PUBLIC 전파)
flowchart TD
liba["liba\nPUBLIC include/a"]
libb["libb\nPUBLIC include/b"]
myapp["myapp"]
libb -->|PUBLIC| liba
myapp -->|PRIVATE| libb
note1["myapp은 include/a, include/b 모두 사용 가능"]
PRIVATE로 전파 차단
# libb가 liba를 PRIVATE로 링크
target_link_libraries(libb PRIVATE liba)
# myapp
target_link_libraries(myapp PRIVATE libb)
# myapp은 include/b만 사용 가능 (liba는 전파 안 됨)
5. 자주 발생하는 문제와 해결법
문제 1: 전역 명령 사용
원인: include_directories(), link_libraries() 같은 전역 명령은 이후 모든 타겟에 영향을 줍니다.
# ❌ 잘못된 사용
include_directories(/usr/local/include)
add_executable(app1 main1.cpp)
add_executable(app2 main2.cpp)
# app1, app2 모두 /usr/local/include 사용
# ✅ 올바른 사용: 타겟별 설정
add_executable(app1 main1.cpp)
target_include_directories(app1 PRIVATE /usr/local/include)
add_executable(app2 main2.cpp)
# app2는 영향 없음
문제 2: PUBLIC/PRIVATE 혼동
증상: 헤더를 찾지 못하거나, 불필요한 헤더가 노출됨.
# ❌ 잘못된 사용: 내부 헤더를 PUBLIC으로
add_library(mylib src/lib.cpp)
target_include_directories(mylib PUBLIC src/internal)
# src/internal이 외부에 노출됨
# ✅ 올바른 사용
target_include_directories(mylib
PUBLIC include # API 헤더
PRIVATE src/internal # 구현 헤더
)
문제 3: 순환 의존성
증상: CMake Error: Circular dependency.
원인: A가 B를 링크하고, B가 A를 링크함.
# ❌ 잘못된 사용
add_library(liba a.cpp)
add_library(libb b.cpp)
target_link_libraries(liba PRIVATE libb)
target_link_libraries(libb PRIVATE liba) # 순환!
# ✅ 올바른 사용: 의존성 재설계
# liba, libb가 모두 의존하는 공통 코드를 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)
문제 4: OBJECT 라이브러리 링크
원인: OBJECT 라이브러리는 target_link_libraries로 직접 링크할 수 없습니다 (CMake 3.12 이전).
# CMake 3.12+: OBJECT 라이브러리 링크 가능
add_library(myobj OBJECT common.cpp)
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE myobj)
# CMake 3.11 이하: $<TARGET_OBJECTS:> 사용
add_executable(myapp main.cpp $<TARGET_OBJECTS:myobj>)
6. 프로덕션 패턴
패턴 1: 인터페이스 라이브러리로 공통 설정
# 프로젝트 전역 컴파일 옵션을 인터페이스 라이브러리로
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>
)
# 모든 타겟에 적용
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE project_options)
add_library(mylib lib.cpp)
target_link_libraries(mylib PRIVATE project_options)
패턴 2: 별칭 타겟
add_library(mylib src/lib.cpp)
add_library(MyProject::mylib ALIAS mylib)
# 다른 곳에서 네임스페이스로 참조
target_link_libraries(myapp PRIVATE MyProject::mylib)
패턴 3: 조건부 타겟
option(BUILD_TOOLS "Build command-line tools" ON)
if(BUILD_TOOLS)
add_executable(tool1 tools/tool1.cpp)
target_link_libraries(tool1 PRIVATE mylib)
endif()
패턴 4: 타겟 속성 조회
# 타겟 속성 가져오기
get_target_property(MYLIB_INCLUDES mylib INCLUDE_DIRECTORIES)
message(STATUS "mylib includes: ${MYLIB_INCLUDES}")
# 타겟 속성 설정
set_target_properties(mylib PROPERTIES
VERSION 1.0.0
SOVERSION 1
OUTPUT_NAME "my_library"
)
7. 완전한 예제: 멀티 라이브러리 프로젝트
프로젝트 구조
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
루트 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)
# 공통 설정
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가 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)
# utils를 링크하면 core도 자동으로 링크됨 (PUBLIC 전파)
target_link_libraries(myapp PRIVATE
MultiLib::utils
project_options
)
타겟 명령어 요약
| 명령어 | 설명 |
|---|---|
add_executable(name sources...) | 실행 파일 타겟 생성 |
add_library(name STATIC sources...) | 정적 라이브러리 생성 |
add_library(name SHARED sources...) | 동적 라이브러리 생성 |
add_library(name INTERFACE) | 헤더 전용 라이브러리 |
add_library(name OBJECT sources...) | 오브젝트 파일만 생성 |
target_include_directories(target vis dirs...) | 헤더 경로 추가 |
target_link_libraries(target vis libs...) | 라이브러리 링크 |
target_compile_options(target vis opts...) | 컴파일 옵션 추가 |
target_compile_definitions(target vis defs...) | 전처리 정의 추가 |
target_compile_features(target vis features...) | C++ 기능 요구 |
정리
| 개념 | 설명 |
|---|---|
| 타겟 | 빌드 대상 (실행 파일, 라이브러리) |
| target_* | 타겟별 속성 설정 |
| PUBLIC | 타겟 + 의존 타겟 |
| PRIVATE | 타겟만 |
| INTERFACE | 의존 타겟만 (헤더 전용) |
| 전이적 의존성 | PUBLIC으로 자동 전파 |
CMake의 타겟 기반 접근은 의존성을 명확히 하고, 빌드 설정을 타겟별로 격리해 대규모 프로젝트에서도 유지보수가 쉽습니다.
FAQ
Q1: 전역 명령 vs 타겟 명령?
A: 타겟 명령(target_*)을 쓰세요. 전역 명령(include_directories, link_libraries)은 모든 타겟에 영향을 주어 의존성이 꼬입니다.
Q2: PUBLIC vs PRIVATE 언제 쓰나요?
A: 외부에 노출할 헤더는 PUBLIC, 내부 구현 헤더는 PRIVATE입니다. 라이브러리를 만들 때 API 헤더는 PUBLIC, 구현 헤더는 PRIVATE로 두세요.
Q3: INTERFACE는 언제 쓰나요?
A: 헤더 전용 라이브러리에서 사용합니다. 컴파일할 소스가 없고, 헤더만 제공하는 경우 add_library(name INTERFACE)로 만들고, target_include_directories(name INTERFACE ...)로 헤더 경로를 지정합니다.
Q4: OBJECT 라이브러리는 언제 쓰나요?
A: 여러 타겟에서 같은 소스를 재사용할 때 사용합니다. 오브젝트 파일만 생성해 두고, 여러 실행 파일/라이브러리에서 $<TARGET_OBJECTS:myobj>로 포함하면 중복 컴파일을 피할 수 있습니다.
Q5: 별칭 타겟은 왜 쓰나요?
A: add_library(MyProject::mylib ALIAS mylib)로 네임스페이스를 붙이면, 외부 패키지와 내부 타겟을 일관된 방식으로 참조할 수 있습니다. target_link_libraries(myapp PRIVATE MyProject::mylib Boost::filesystem) 처럼 모두 :: 형식으로 통일됩니다.
Q6: CMake Targets 학습 리소스는?
A:
- CMake 공식 문서 - cmake-buildsystem
- “Professional CMake: A Practical Guide”
- Effective Modern CMake
한 줄 요약: CMake 타겟 기반 접근으로 의존성을 명확히 관리할 수 있습니다. 다음으로 CMake find_package를 읽어보면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ CMake 완벽 가이드 | 크로스 플랫폼 빌드·최신 CMake 3.28+ 기능·프리셋·모듈
- C++ CMake find_package 완벽 가이드 | 외부 라이브러리 통합
- C++ Conan 완벽 가이드 | 현대적인 C++ 패키지 관리
관련 글
- C++ CMake find_package 완벽 가이드 | 외부 라이브러리 통합
- C++ CMake |
- C++ CMake 완벽 가이드 | 크로스 플랫폼 빌드·최신 CMake 3.28+ 기능·프리셋·모듈
- C++ Conan 완벽 가이드 | 현대적인 C++ 패키지 관리
- CMake 에러 |