CMake 타겟 기반 빌드: target_* 명령과 PUBLIC·PRIVATE·INTERFACE 의존성 전파
이 글의 핵심
CMake를 타겟 중심으로 쓰는 이유, target_* 명령으로 속성을 붙이는 법, PUBLIC·PRIVATE·INTERFACE 가시성이 의존성 전파에 미치는 영향을 멀티 라이브러리 예제로 정리합니다.
타겟을 의존성 그래프의 노드로 보기
CMake 타겟은 의존성 그래프의 노드에 가깝습니다. Rust Cargo의 크레이트·타겟이나 npm 패키지 트리와 완전히 같지는 않지만, “무엇이 무엇에 링크되는가”를 명시한다는 점에서 비교하면 팀 온보딩에 도움이 됩니다. Make 기반은 C++ Makefile·빌드 시스템 비교와 연결해 보세요.
전역 명령 대신 타겟 기반으로 쓰는 이유
include_directories·link_libraries가 만드는 혼란
문제: 예전 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
타겟 기반 방식이 전역 방식보다 나은 핵심 이유는 요구사항이 라이브러리와 함께 움직인다는 점입니다. mylib가 “나를 쓰려면 이 헤더 경로와 이 정의, 그리고 pthread가 필요하다”는 정보를 자기 속성으로 들고 있으면, 소비하는 쪽은 target_link_libraries(app PRIVATE mylib) 한 줄만 쓰면 됩니다. 전역 방식에서는 이 정보가 최상위 CMakeLists.txt 어딘가에 흩어져 있어서, 라이브러리를 다른 프로젝트로 옮기거나 한 타겟만 테스트용으로 따로 빌드하려는 순간 무엇을 같이 옮겨야 하는지 아무도 모르게 됩니다. find_package로 가져온 Boost::system 같은 imported 타겟이 바로 이 원리로 동작하는 예입니다.
add_executable·add_library로 타겟 만들기
실행 파일
# 단일 소스
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})
정적 라이브러리 (STATIC)
add_library(mylib STATIC
src/lib.cpp
src/helper.cpp
)
동적 라이브러리 (SHARED)
add_library(mylib SHARED
src/lib.cpp
src/helper.cpp
)
헤더 전용 라이브러리 (INTERFACE)
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>)
add_library(mylib src/lib.cpp)처럼 STATIC/SHARED를 생략하면 BUILD_SHARED_LIBS 변수에 따라 종류가 정해집니다. 기본은 정적 라이브러리지만 누군가 -DBUILD_SHARED_LIBS=ON으로 구성하면 같은 코드가 공유 라이브러리가 되므로, 아래의 순환 의존성처럼 종류에 따라 동작이 달라지는 부분이 있다면 명시하는 편이 안전합니다. 공유 라이브러리로 바뀌면 Windows에서는 심볼을 __declspec(dllexport)로 내보내야 해서, 정적 빌드에서 멀쩡하던 코드가 .lib 파일이 생성되지 않았다는 에러로 실패하는 경우도 흔합니다. CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS나 GenerateExportHeader 모듈이 이 문제를 다룹니다.
OBJECT 라이브러리는 컴파일 결과물(.o)만 만들고 아카이브로 묶지 않으므로, 정적 라이브러리처럼 “링커가 필요한 오브젝트만 골라 쓰는” 동작이 없습니다. 모든 오브젝트가 무조건 최종 바이너리에 들어가므로 전역 생성자로 자기 등록을 하는 플러그인 코드처럼 정적 라이브러리에서는 링커가 버려 버리는 코드를 확실히 포함시킬 때 유용합니다.
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)
find_package(Threads REQUIRED)
target_link_libraries(myapp PRIVATE
mylib
Boost::filesystem
Threads::Threads # 플랫폼별 스레드 플래그를 CMake가 결정 (raw "pthread"보다 이식성 좋음)
)
target_link_libraries에 넘기는 이름이 타겟이 아니면 CMake는 그것을 링커에 넘길 라이브러리 이름(-lpthread)으로 취급합니다. 그래서 mylib를 mylibb로 오타 내도 구성 단계에서는 에러가 없다가 링크 단계에서 cannot find -lmylibb로 실패합니다. ::가 들어간 이름(MyProject::mylib)은 반드시 타겟이어야 한다는 규칙이 있어서, 오타가 나면 구성 단계에서 바로 Target "myapp" links to target "MyProject::mylibb" but the target was not found 에러가 납니다. 아래 별칭 타겟 패턴을 권하는 이유 중 하나입니다.
target_compile_options
target_compile_options(myapp PRIVATE
-Wall
-Wextra
"$<$<CONFIG:Debug>:-O0;-g>" # 여러 옵션은 따옴표 + 세미콜론
)
제너레이터 표현식 안에 공백을 넣으면 안 됩니다. $<$<CONFIG:Debug>:-O0 -g>처럼 쓰면 CMake가 인자를 공백에서 먼저 나눠 $<$<CONFIG:Debug>:-O0와 -g> 두 조각이 되고, 구성 단계에서 Error evaluating generator expression 에러가 나거나 이상한 플래그가 컴파일러에 전달됩니다. 여러 값은 위처럼 따옴표로 감싸고 세미콜론으로 구분합니다. 최적화 수준은 CMAKE_BUILD_TYPE에 따라 CMAKE_CXX_FLAGS_RELEASE(-O3 -DNDEBUG)가 이미 넣어 주므로 타겟 옵션으로 중복 지정할 필요가 거의 없습니다. -Werror는 CI에서는 유용하지만, 라이브러리의 PUBLIC 옵션으로 넣으면 소비자가 다른 컴파일러 버전을 쓰는 순간 새 경고 때문에 빌드가 깨지므로 PRIVATE로만 두거나 CMAKE_COMPILE_WARNING_AS_ERROR(3.24+)처럼 끌 수 있는 방식으로 두는 편이 좋습니다.
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)
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)
PUBLIC과 PRIVATE를 고르는 기준은 한 문장으로 정리할 수 있습니다. 이 라이브러리의 공개 헤더가 그것을 필요로 하는가? mylib의 공개 헤더(include/mylib.h)가 #include <boost/filesystem.hpp>를 한다면 mylib를 쓰는 모든 코드가 Boost 헤더를 찾아야 하므로 Boost는 PUBLIC이어야 하고, .cpp 안에서만 Boost를 쓴다면 PRIVATE가 맞습니다. 이 구분을 대충 PUBLIC으로 통일하면 당장은 잘 빌드되지만, 헤더 경로와 정의가 불필요하게 퍼져 빌드가 느려지고 매크로 충돌의 원인이 됩니다. 반대로 공개 헤더가 쓰는데 PRIVATE로 두면, 라이브러리 자체는 빌드되는데 소비자 쪽에서 fatal error: boost/filesystem.hpp: No such file or directory가 납니다.
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 정의됨
링크를 따라 전파되는 사용 요구사항
전이적 의존성
# 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는 전파 안 됨)
여기서 “전파 안 됨”은 헤더 경로와 컴파일 옵션에 대한 이야기입니다. 링크 자체는 다릅니다. libb가 정적 라이브러리라면 libb.a 안에는 liba의 코드가 들어 있지 않으므로 최종 실행 파일을 링크할 때 liba.a도 반드시 필요하고, CMake는 PRIVATE 의존성이라도 정적 라이브러리의 경우 링크 줄에 liba를 자동으로 추가해 줍니다(내부적으로 $<LINK_ONLY:liba> 형태로 기록됩니다). 그래서 “PRIVATE로 바꿨는데 왜 여전히 링크 명령에 liba가 보이지?”라는 의문은 정상 동작입니다. myapp의 코드가 liba의 헤더를 직접 include한다면, libb를 통해 우연히 얻어 쓰지 말고 myapp도 liba를 직접 링크하는 것이 올바른 표현입니다.
타겟 설정이 꼬이는 네 가지 경우
전역 명령이 모든 타겟에 새어 들어감
원인: 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는 영향 없음
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 # 구현 헤더
)
공유 라이브러리 간 순환 의존
증상: 공유 라이브러리끼리 순환하면 구성 단계에서 The inter-target dependency graph contains the following strongly connected component (cycle) 와 Cyclic dependencies are allowed only among static libraries. 에러가 납니다.
원인: A가 B를 링크하며, B가 A를 링크함.
정적 라이브러리끼리는 CMake가 링크 줄에 두 라이브러리를 반복해 넣는 방식으로 순환을 허용하므로 에러 없이 빌드됩니다. 그래서 정적 빌드에서는 멀쩡하던 프로젝트가 BUILD_SHARED_LIBS=ON으로 바꾸는 순간 이 에러를 만나는 경우가 많습니다. 빌드가 된다고 해서 설계가 괜찮은 것은 아니므로, 순환은 발견하는 즉시 끊는 편이 좋습니다.
# ❌ 잘못된 사용
add_library(liba SHARED a.cpp)
add_library(libb SHARED 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)
CMake 3.12 이전에 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>)
3.12 이후의 방식에도 주의할 점이 있습니다. OBJECT 라이브러리를 target_link_libraries로 연결하면 그 오브젝트 파일은 직접 연결한 타겟에만 들어가고, 그 타겟을 다시 링크하는 쪽으로 전이되지 않습니다. 사용 요구사항(헤더 경로, 정의)은 PUBLIC 규칙대로 전파되지만 .o 파일 자체는 한 번만 들어가므로, 중간 라이브러리를 거쳐 쓰면 undefined reference 링크 에러가 날 수 있습니다.
공통 설정 타겟·별칭·조건부 타겟
INTERFACE 라이브러리로 경고·표준 옵션 공유
# 프로젝트 전역 컴파일 옵션을 인터페이스 라이브러리로
add_library(project_options INTERFACE)
target_compile_features(project_options INTERFACE cxx_std_20)
target_compile_options(project_options INTERFACE
"$<$<CXX_COMPILER_ID:GNU,Clang>:-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)
이 패턴은 CMAKE_CXX_FLAGS에 경고 옵션을 문자열로 붙이던 방식의 대안입니다. 전역 변수에 넣은 옵션은 add_subdirectory나 FetchContent로 가져온 외부 라이브러리에도 적용되어, 남의 코드에서 쏟아지는 경고 때문에 -Werror 빌드가 깨지는 일이 생깁니다. 인터페이스 라이브러리로 묶으면 내가 연결한 타겟에만 적용됩니다. 라이브러리에서 project_options를 PRIVATE로 연결하는 것도 중요합니다. PUBLIC으로 연결하면 이 라이브러리를 쓰는 외부 프로젝트에까지 경고 수준이 강제됩니다. 참고로 CXX_COMPILER_ID는 여러 값을 쉼표로 나열할 수 있으며(3.15+), Apple 기본 컴파일러는 AppleClang이라는 별도 ID이므로 macOS를 지원한다면 목록에 함께 넣어야 합니다.
네임스페이스 별칭 타겟
add_library(mylib src/lib.cpp)
add_library(MyProject::mylib ALIAS mylib)
# 다른 곳에서 네임스페이스로 참조
target_link_libraries(myapp PRIVATE MyProject::mylib)
옵션에 따라 만드는 조건부 타겟
option(BUILD_TOOLS "Build command-line tools" ON)
if(BUILD_TOOLS)
add_executable(tool1 tools/tool1.cpp)
target_link_libraries(tool1 PRIVATE mylib)
endif()
get_target_property로 속성 조회
# 타겟 속성 가져오기
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"
)
core·utils·app으로 나눈 멀티 라이브러리 프로젝트
프로젝트 구조
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
)
이 예제에는 규모가 커질 때 문제가 되는 부분이 두 가지 있습니다. 첫째, ${CMAKE_SOURCE_DIR}은 최상위 프로젝트의 소스 디렉터리입니다. 이 프로젝트를 다른 프로젝트가 add_subdirectory나 FetchContent로 가져가면 CMAKE_SOURCE_DIR은 가져간 쪽의 루트를 가리켜 헤더 경로가 엉뚱한 곳이 됩니다. 라이브러리 안에서는 ${PROJECT_SOURCE_DIR}이나 ${CMAKE_CURRENT_SOURCE_DIR}을 쓰는 편이 안전합니다. 또 include/core를 경로로 추가하면 소비자가 #include "core.h"로 써야 해서 다른 라이브러리의 같은 이름 헤더와 충돌하기 쉬우므로, include까지만 추가하고 #include <core/core.h>로 쓰게 하는 편이 좋습니다.
둘째, 이 라이브러리를 install()과 install(EXPORT ...)로 배포하려고 하면 Target "core" INTERFACE_INCLUDE_DIRECTORIES property contains path "..." which is prefixed in the source directory. 에러가 납니다. 소스 트리의 절대 경로는 설치된 곳에서는 존재하지 않기 때문입니다. 배포할 라이브러리라면 $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>와 $<INSTALL_INTERFACE:include>로 빌드할 때와 설치 후의 경로를 나눠 지정해야 합니다. 루트에서 CMAKE_CXX_STANDARD를 전역으로 설정하면서 project_options에도 cxx_std_20을 요구하는 것은 중복이니, 둘 중 한 가지 방식으로 통일하는 것이 읽기 쉽습니다.
타겟 명령어 요약
| 명령어 | 설명 |
|---|---|
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++ 기능 요구 |
타겟 기반 CMake 핵심 정리
| 개념 | 설명 |
|---|---|
| 타겟 | 빌드 대상 (실행 파일, 라이브러리) |
| 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를 읽어보면 좋습니다.
같이 보면 좋은 글
- CMake 3.28+ 프리셋과 모듈로 크로스 플랫폼 빌드 구성하기
- CMake find_package로 외부 라이브러리 연결하기
- Conan 기초: conanfile로 의존성 선언, 프로필, Remote, CMake 연동
- C++ CMake
- CMake 에러