CMake find_package와 외부 의존성 — Module·Config·FetchContent
#왜 find_package가 필요한가
프로젝트가 OpenSSL과 zlib을 사용한다고 합시다. Makefile로 손으로 작성하면 OS마다 경로가 흩어져 있어 곤란해집니다.
# LinuxCFLAGS += -I/usr/include/opensslLDFLAGS += -L/usr/lib -lssl -lcrypto -lz
# macOS (Homebrew Apple Silicon)CFLAGS += -I/opt/homebrew/opt/openssl/includeLDFLAGS += -L/opt/homebrew/opt/openssl/lib -lssl -lcrypto -lz
# macOS (Homebrew Intel) — 또 다름CFLAGS += -I/usr/local/opt/openssl/include# ...
# Windows + vcpkg — 또 다름# ...플랫폼마다 경로가 다르고, 패키지 매니저마다 또 다릅니다. 버전이 바뀌면 경로도 바뀝니다. 이 정보를 직접 추적하는 것은 정상적인 개발자의 일이 아닙니다.
CMake의 답은 단순합니다. 라이브러리 이름만 알려 줘라. CMake가 찾는다.
find_package(OpenSSL REQUIRED)find_package(ZLIB REQUIRED)
add_executable(myapp main.cpp)target_link_libraries(myapp PRIVATE OpenSSL::SSL OpenSSL::Crypto ZLIB::ZLIB)이 다섯 줄이 모든 OS에서 동작합니다. CMake는:
- OS와 패키지 매니저별 표준 경로를 순회하며 라이브러리를 찾고,
- 헤더 경로·라이브러리 경로를 임포트 타겟(
OpenSSL::SSL,ZLIB::ZLIB)에 묶고, - 그 타겟을 사용하는 모든 코드에 전이적으로 옵션을 전파합니다.
target_link_libraries에 적은 OpenSSL::SSL이 어떻게 펼쳐지는지 보면, 결과적으로 위의 -I.../include + -l ssl -l crypto 같은 컴파일러 옵션이 자동으로 명령에 들어갑니다. 하지만 우리 코드에서는 이름만 보입니다. 이게 Modern CMake의 큰 그림입니다.
이 챕터는 다음을 다룹니다.
find_package의 두 가지 동작 모드 — Module 모드와 Config 모드.- 라이브러리가 없는 시스템에서 FetchContent로 자동 다운로드하기.
find_package+ FetchContent의 하이브리드 패턴 (3.25+).
#find_package 기초
#기본 사용법
find_package(ZLIB REQUIRED)
add_executable(myapp main.cpp)target_link_libraries(myapp PRIVATE ZLIB::ZLIB)REQUIRED는 필수 의존성을 의미합니다. 찾지 못하면 CMake가 오류로 중단합니다.
#동작 방식 — Module 모드와 Config 모드
find_package가 라이브러리를 찾는 방법은 두 가지 완전히 다른 메커니즘이 있습니다. 이 둘의 차이를 이해하지 않으면 왜 어떤 라이브러리는 잘 찾는데 어떤 라이브러리는 안 찾히는지 디버깅이 어렵습니다.
Module 모드 — CMake가 가진 Find 스크립트
CMake 자체에 수십 개의 FindXxx.cmake 스크립트가 내장되어 있습니다($CMAKE/Modules/ 디렉터리). 이 스크립트는 전통적인 라이브러리를 시스템 표준 위치에서 찾아 줍니다 — zlib, OpenSSL, Threads, X11, JPEG 같이 오래된, 그리고 CMake보다 먼저 존재한 라이브러리들.
$CMAKE/Modules/├── FindZLIB.cmake├── FindOpenSSL.cmake├── FindThreads.cmake├── FindJPEG.cmake└── ... (수십 개)Module 모드 흐름:
find_package(ZLIB)호출- CMake가
FindZLIB.cmake를 실행 - 이 스크립트가 직접 코드를 써서 라이브러리 위치 탐색 (시스템 경로, 환경변수, 표준 위치)
- 찾으면
ZLIB_FOUND,ZLIB_INCLUDE_DIRS,ZLIB_LIBRARIES같은 변수에 결과를 채움 - 최근 버전들은 imported 타겟(
ZLIB::ZLIB)도 함께 만들어 줌
이 모드의 한계는 라이브러리 자체가 아니라 CMake가 검색 로직을 안다는 점입니다. 라이브러리가 새 버전을 내거나 새 옵션을 추가해도, CMake의 Find 스크립트가 따라가지 못하면 활용할 수 없습니다.
Config 모드 — 라이브러리가 제공한 정보 파일
요즘 잘 만든 라이브러리는 자기 자신에 대한 CMake 정보 파일을 직접 제공합니다. 이 파일은 라이브러리가 설치될 때 같이 깔립니다.
/usr/lib/cmake/fmt/├── fmt-config.cmake ← find_package(fmt)가 찾는 파일├── fmt-config-version.cmake ← 버전 정보└── fmt-targets.cmake ← imported 타겟 정의Config 모드 흐름:
find_package(fmt)호출- CMake가 시스템 경로에서
fmt-config.cmake(또는fmtConfig.cmake)를 검색 - 찾으면 그 파일을 그대로 실행
- 그 파일이 imported 타겟과 변수를 모두 정의
라이브러리가 자기 정보를 직접 들고 다닌다는 점에서 훨씬 정확합니다. CMake가 새 버전 라이브러리를 알 필요가 없습니다.
우선순위 — Config가 먼저
find_package(X)는 기본적으로 Config 모드를 먼저 시도합니다. 시스템에 XConfig.cmake가 있으면 그쪽을, 없으면 FindX.cmake로 폴백합니다.
특정 모드만 쓰고 싶을 때는 명시할 수 있습니다.
# Config 모드 강제 — Find 스크립트는 무시find_package(fmt REQUIRED CONFIG)
# Module 모드 강제 — Config 파일은 무시find_package(ZLIB REQUIRED MODULE)#검색 경로 — CMake가 어디를 보는가
find_package가 실제로 어디를 뒤지는지는 모드에 따라 미묘하게 다릅니다. 디버깅이 필요하면 --debug-find 옵션이 정답입니다.
cmake --debug-find -B build# 또는 특정 패키지만cmake --debug-find-pkg=fmt -B build출력에 find_package가 어떤 경로를 순서대로 시도했는지 모두 찍힙니다.
자주 만나는 검색 경로 영향 변수:
| 변수 | 의미 |
|---|---|
CMAKE_PREFIX_PATH | 가장 강력. 콜론(;) 구분 prefix 목록. /opt/myapp:/usr/local 식. |
<Package>_DIR | 특정 패키지의 Config 파일 위치를 직접 지정. -DZLIB_DIR=/opt/zlib/lib/cmake/zlib |
CMAKE_MODULE_PATH | Module 모드에서 사용자 Find 스크립트 추가 경로 |
환경변수 <Package>_ROOT | 패키지별 root prefix (3.12+) |
가장 실용적인 두 패턴:
# vcpkg / Conan 같은 패키지 매니저 통합cmake -B build -DCMAKE_PREFIX_PATH=/path/to/vcpkg/installed/x64-linux
# 직접 빌드한 라이브러리 사용cmake -B build -DZLIB_DIR=/opt/zlib-1.3/lib/cmake/zlib#선택적 의존성
REQUIRED 없이 호출하면 선택적 의존성이 됩니다.
find_package(ZLIB) # 없어도 진행
if(ZLIB_FOUND) target_link_libraries(myapp PRIVATE ZLIB::ZLIB) target_compile_definitions(myapp PRIVATE HAS_ZLIB)endif()#버전 지정
# 1.70 이상find_package(Boost 1.70 REQUIRED)
# 정확히 4.0find_package(OpenCV 4.0 EXACT REQUIRED)
# 범위find_package(fmt 10.0...<12.0 REQUIRED) # CMake 3.19+#컴포넌트 지정
큰 라이브러리는 여러 컴포넌트로 나뉩니다.
# Boost의 filesystem과 system만 필요find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)
# Qt6의 특정 모듈만find_package(Qt6 REQUIRED COMPONENTS Widgets Gui Core)#Imported 타겟 사용하기
#현대적인 방식 (권장)
현대 패키지는 imported 타겟을 제공합니다. 타겟 이름은 보통 <Package>::<Component> 형식입니다.
find_package(OpenSSL REQUIRED)target_link_libraries(myapp PRIVATE OpenSSL::SSL OpenSSL::Crypto)
find_package(Threads REQUIRED)target_link_libraries(myapp PRIVATE Threads::Threads)
find_package(CURL REQUIRED)target_link_libraries(myapp PRIVATE CURL::libcurl)Imported 타겟을 사용하면:
- 인클루드 디렉터리가 자동으로 추가됩니다.
- 링크 플래그가 자동으로 설정됩니다.
- 의존성 전파가 올바르게 동작합니다.
#레거시 변수 방식
오래된 패키지는 imported 타겟 없이 변수만 설정합니다.
find_package(JPEG REQUIRED)
# 레거시 방식target_include_directories(myapp PRIVATE ${JPEG_INCLUDE_DIRS})target_link_libraries(myapp PRIVATE ${JPEG_LIBRARIES})일반적인 변수 이름:
| 변수 | 설명 |
|---|---|
<PKG>_FOUND | 찾았는지 여부 |
<PKG>_INCLUDE_DIRS | 헤더 경로 |
<PKG>_LIBRARIES | 라이브러리 파일 |
<PKG>_VERSION | 버전 문자열 |
<PKG>_DEFINITIONS | 전처리기 정의 |
권장: 가능하면 항상 imported 타겟(Pkg::Component)을 사용하세요.
#자주 쓰는 패키지
#Threads (pthreads)
find_package(Threads REQUIRED)target_link_libraries(myapp PRIVATE Threads::Threads)#Boost
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem regex)target_link_libraries(myapp PRIVATE Boost::filesystem Boost::regex)
# 헤더 전용 Boost 라이브러리find_package(Boost REQUIRED)target_link_libraries(myapp PRIVATE Boost::headers)#OpenSSL
find_package(OpenSSL REQUIRED)target_link_libraries(myapp PRIVATE OpenSSL::SSL OpenSSL::Crypto)#CURL
find_package(CURL REQUIRED)target_link_libraries(myapp PRIVATE CURL::libcurl)#ZLIB
find_package(ZLIB REQUIRED)target_link_libraries(myapp PRIVATE ZLIB::ZLIB)#fmt
find_package(fmt REQUIRED)target_link_libraries(myapp PRIVATE fmt::fmt)#nlohmann_json
find_package(nlohmann_json REQUIRED)target_link_libraries(myapp PRIVATE nlohmann_json::nlohmann_json)#pkg-config
Unix 시스템에서 많은 라이브러리가 pkg-config를 사용합니다. CMake의 PkgConfig 모듈로 이런 라이브러리를 찾을 수 있습니다.
find_package(PkgConfig REQUIRED)pkg_check_modules(LIBUSB REQUIRED IMPORTED_TARGET libusb-1.0)
target_link_libraries(myapp PRIVATE PkgConfig::LIBUSB)#pkg_check_modules 옵션
# 버전 요구pkg_check_modules(FOO REQUIRED foo>=1.0)
# 조용히 (오류 메시지 없이)pkg_check_modules(FOO QUIET foo)
# Imported 타겟 생성 (권장)pkg_check_modules(FOO REQUIRED IMPORTED_TARGET foo)IMPORTED_TARGET을 사용하면 PkgConfig::<NAME> 타겟이 생성되어 target_link_libraries로 바로 연결할 수 있습니다.
#FetchContent — 의존성을 프로젝트 안에 가져오기
#왜 FetchContent인가
지금까지 본 find_package는 시스템에 이미 설치된 라이브러리를 찾습니다. 사용자가 직접 apt install이나 brew install을 해야 한다는 뜻입니다. 사용자 환경에 따라 있을 수도, 없을 수도, 버전이 다를 수도 있어 불안정합니다.
FetchContent는 이 의존성을 프로젝트 안으로 통째로 끌고 옵니다. CMake 구성 시점에 Git이나 URL에서 라이브러리를 내려받아, 내 빌드의 일부로 함께 컴파일합니다. 사용자는 cmake -B build 한 번이면 모든 의존성이 자동으로 갖춰집니다.
이 모델의 장단점은 명확합니다.
장점
- 사용자 환경 무관. 어디서든 같은 라이브러리·버전이 동작.
- 디버깅 가능. 의존 라이브러리도 내 빌드에 같이 들어가서 그 안까지 스텝 인이 됨.
- 버전 고정. Git tag로 정확한 상태를 묶을 수 있음.
단점
- 빌드 시간 증가. 내 코드뿐 아니라 의존성도 같이 컴파일.
- 디스크 사용. 각 프로젝트가 자기 사본을 만듬.
- 시스템 라이브러리 활용 안 함. 이미 깔린 zlib을 두고 자기 사본을 만들면 비효율.
이 트레이드오프 때문에, CMake 3.24+에서 하이브리드 패턴(find_package + FetchContent)이 도입됐습니다 — 시스템에 있으면 그걸 쓰고, 없으면 가져옵니다. 자세한 건 이 절 마지막에서.
#FetchContent vs ExternalProject — 언제 무엇을
CMake에는 외부 의존성을 다루는 두 가지 도구가 있습니다.
ExternalProject | FetchContent | |
|---|---|---|
| 등장 시기 | CMake 2.8 (오래됨) | CMake 3.11 (2018) |
| 시점 | 빌드 시점에 다운로드·빌드 | 구성 시점에 다운로드 (3.12+) |
| 타겟 노출 | 아니오 — 자체 외부 빌드로 묶임 | 예 — 메인 빌드에 함께 흡수 |
target_link_libraries | 직접 불가, ExternalProject 출력 경로 수동 지정 | 일반 타겟처럼 즉시 링크 가능 |
| 디버깅 | 별도 빌드로 스텝 인 어려움 | 메인 빌드 일부라 자유롭게 가능 |
| 적합한 자리 | 거대한 외부 도구 (CMake로 안 만든 것도 됨) | C++ 라이브러리 의존성 (대부분의 경우) |
요약: C++ 라이브러리 의존성은 거의 항상 FetchContent. ExternalProject는 CMake와 무관한 빌드 시스템을 가진 외부 도구(autotools, scons로 빌드되는 것들)에만 씁니다.
#기본 사용법
#기본 사용법
include(FetchContent)
FetchContent_Declare( fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 11.0.2)
FetchContent_MakeAvailable(fmt)
target_link_libraries(myapp PRIVATE fmt::fmt)FetchContent_Declare로 어디서 가져올지 선언하고, FetchContent_MakeAvailable로 실제로 가져옵니다.
#여러 의존성
include(FetchContent)
FetchContent_Declare( fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 11.0.2)
FetchContent_Declare( spdlog GIT_REPOSITORY https://github.com/gabime/spdlog.git GIT_TAG v1.15.0)
FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG v1.15.2)
# 한 번에 모두 가져오기FetchContent_MakeAvailable(fmt spdlog googletest)#URL로 다운로드
Git 대신 아카이브 파일을 다운로드할 수도 있습니다.
FetchContent_Declare( json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz URL_HASH SHA256=8c4b26bf4b422252e13f332bc5e388ec0ab5c3443d24f...)
FetchContent_MakeAvailable(json)URL_HASH로 무결성을 검증합니다.
#의존성 옵션 설정
FetchContent로 가져오는 프로젝트의 옵션을 미리 설정할 수 있습니다.
include(FetchContent)
# googletest 옵션 설정 (FetchContent_MakeAvailable 전에!)set(BUILD_GMOCK OFF CACHE BOOL "" FORCE)set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG v1.15.2)
FetchContent_MakeAvailable(googletest)#시스템 우선, FetchContent 폴백
시스템에 라이브러리가 있으면 사용하고, 없으면 다운로드하는 패턴입니다.
#수동 방식
find_package(fmt QUIET)
if(NOT fmt_FOUND) message(STATUS "fmt not found, fetching from GitHub...") include(FetchContent) FetchContent_Declare( fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 11.0.2 ) FetchContent_MakeAvailable(fmt)endif()
target_link_libraries(myapp PRIVATE fmt::fmt)#CMake 3.24+ 간편 문법
FIND_PACKAGE_ARGS를 사용하면 자동으로 먼저 find_package를 시도합니다.
include(FetchContent)
FetchContent_Declare( fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 11.0.2 FIND_PACKAGE_ARGS # 먼저 find_package(fmt) 시도)
FetchContent_MakeAvailable(fmt)
# 시스템에 있으면 시스템 것, 없으면 다운로드한 것 사용target_link_libraries(myapp PRIVATE fmt::fmt)#ExternalProject
ExternalProject는 빌드 시점(configure가 아닌)에 외부 프로젝트를 다운로드/빌드합니다.
include(ExternalProject)
ExternalProject_Add( external_zlib URL https://zlib.net/zlib-1.3.tar.gz URL_HASH SHA256=... PREFIX ${CMAKE_BINARY_DIR}/external CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/external/install)#FetchContent vs ExternalProject
| 특성 | FetchContent | ExternalProject |
|---|---|---|
| 실행 시점 | Configure (cmake 실행) | Build (make 실행) |
| 타겟 사용 | 즉시 가능 | 복잡함 (DEPENDS 필요) |
| 빌드 통합 | 메인 프로젝트와 함께 | 별도 빌드 |
| CMake가 아닌 프로젝트 | 어려움 | 쉬움 |
| 일반적인 용도 | CMake 프로젝트 | 레거시/비-CMake |
권장: 가능하면 FetchContent를 사용하세요. ExternalProject는 CMake 프로젝트가 아닌 레거시 라이브러리에 적합합니다.
#패키지 탐색 경로
#CMAKE_PREFIX_PATH
여러 경로를 세미콜론으로 구분합니다.
cmake -B build -DCMAKE_PREFIX_PATH="/opt/mylibs;/home/user/libs"list(APPEND CMAKE_PREFIX_PATH "/opt/mylibs")#_ROOT
특정 패키지의 설치 위치를 지정합니다.
cmake -B build -DBoost_ROOT=/opt/boost-1.80cmake -B build -DOpenSSL_ROOT=/opt/opensslCMake 3.12부터 <PackageName>_ROOT 변수가 자동으로 탐색 경로에 포함됩니다.
#환경 변수
export CMAKE_PREFIX_PATH="/opt/mylibs:$CMAKE_PREFIX_PATH"export Boost_ROOT="/opt/boost-1.80"cmake -B build#실전 예시
cmake_minimum_required(VERSION 3.20)project(MyServer VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
# === 필수 시스템 의존성 ===find_package(Threads REQUIRED)find_package(OpenSSL REQUIRED)
# === 선택적 의존성 (시스템 또는 FetchContent) ===
# fmt: 시스템에 있으면 사용, 없으면 다운로드find_package(fmt 10.0 QUIET)if(NOT fmt_FOUND) message(STATUS "fmt not found, fetching...") FetchContent_Declare(fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 11.0.2 ) FetchContent_MakeAvailable(fmt)endif()
# spdlog: 위와 같은 패턴find_package(spdlog 1.12 QUIET)if(NOT spdlog_FOUND) message(STATUS "spdlog not found, fetching...") set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "" FORCE) FetchContent_Declare(spdlog GIT_REPOSITORY https://github.com/gabime/spdlog.git GIT_TAG v1.15.0 ) FetchContent_MakeAvailable(spdlog)endif()
# === 실행 파일 ===add_executable(myserver src/main.cpp src/server.cpp)
target_link_libraries(myserver PRIVATE Threads::Threads OpenSSL::SSL OpenSSL::Crypto fmt::fmt spdlog::spdlog)#흔한 실수
#Imported 타겟 대신 변수 사용
# 회피: 레거시 방식find_package(ZLIB REQUIRED)target_include_directories(myapp PRIVATE ${ZLIB_INCLUDE_DIRS})target_link_libraries(myapp PRIVATE ${ZLIB_LIBRARIES})
# Good: Imported 타겟find_package(ZLIB REQUIRED)target_link_libraries(myapp PRIVATE ZLIB::ZLIB)#FetchContent 옵션 설정 순서
# 회피: 옵션 설정이 MakeAvailable 뒤에FetchContent_MakeAvailable(googletest)set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) # 너무 늦음!
# Good: MakeAvailable 전에 설정set(BUILD_GMOCK OFF CACHE BOOL "" FORCE)FetchContent_MakeAvailable(googletest)#REQUIRED 없이 _FOUND 체크 누락
# 회피: 찾지 못해도 사용 시도find_package(ZLIB)target_link_libraries(myapp PRIVATE ZLIB::ZLIB) # ZLIB 없으면 오류
# Good: REQUIRED 사용find_package(ZLIB REQUIRED)target_link_libraries(myapp PRIVATE ZLIB::ZLIB)
# 또는: _FOUND 체크find_package(ZLIB)if(ZLIB_FOUND) target_link_libraries(myapp PRIVATE ZLIB::ZLIB)endif()#Git 태그 대신 브랜치 사용
# 회피: 브랜치는 변할 수 있음FetchContent_Declare(fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG master # 위험! 내일 다른 코드)
# Good: 고정된 태그나 커밋 해시FetchContent_Declare(fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 11.0.2 # 버전 태그)#정리
find_package는 시스템에 설치된 라이브러리를 찾습니다.- Imported 타겟(
Pkg::Component)을 사용하는 것이 권장됩니다. pkg_check_modules로 pkg-config 라이브러리를 찾습니다.- FetchContent로 구성 시점에 소스를 다운로드합니다.
- 시스템 우선, FetchContent 폴백 패턴을 활용하세요.
CMAKE_PREFIX_PATH,<Pkg>_ROOT로 탐색 경로를 지정합니다.- Git 태그는 고정된 버전을 사용하세요.
#다음 장 예고
Ch 6에서는 테스트와 CTest를 다룹니다. enable_testing(), add_test(), 그리고 Google Test 연동을 살펴봅니다.
#참고 자료
CMake · 5 of 9
- 1CMake 소개와 첫 프로젝트 — 설치부터 빌드까지
- 2CMake 언어 분석 — 변수·조건문·함수의 동작
- 3CMake 타겟과 라이브러리 — INTERFACE·PUBLIC·PRIVATE 전파
- 4CMake 옵션과 캐시 변수 — option·set·cache type 분석
- 5CMake find_package와 외부 의존성 — Module·Config·FetchContent
- 6CMake 테스트와 CTest — add_test·테스트 fixture·리포트
- 7CMake 설치와 패키징 — install·EXPORT·CPack
- 8Modern CMake 베스트 프랙티스 — target_* 중심 설계
- 9Modern CMake 고급 — BUILD/INSTALL_INTERFACE·Presets·cmake -E