CMake 에러 해결: 컴파일러·find_package·링커·캐시 문제를 단계별로 좁히는 법
이 글의 핵심
CMake 에러는 어느 단계에서 났는지만 구분해도 절반은 풀립니다. 이 글은 configure(컴파일러·find_package·정책), generate, build(컴파일·링크), 실행(공유 라이브러리 로딩) 단계별로 자주 보는 에러 메시지와 원인을 정리하고, CMake 4.0에서 오래된 서드파티 프로젝트가 한꺼번에 깨지는 문제, CMake 3.30에서 FindBoost가 빠진 영향, Conan 2의 CMakeDeps 연동, --trace-expand와 --debug-find로 원인을 찾는 방법을 다룹니다.
들어가며: 에러가 난 “단계”부터 확인하기
CMake 에러 로그는 길고, 처음 보는 사람에게는 모두 비슷해 보입니다. 제가 CMake 문제를 볼 때 가장 먼저 하는 일은 메시지를 읽기 전에 어느 단계에서 멈췄는지 확인하는 것입니다. cmake -S . -B build(configure)에서 멈췄는지, cmake --build build(build)에서 멈췄는지, 빌드는 됐는데 실행할 때 죽는지에 따라 봐야 할 곳이 완전히 다르기 때문입니다.
- configure 단계:
CMake Error at CMakeLists.txt:12 (find_package):처럼 CMake 파일의 줄 번호와 명령 이름이 나옵니다. 컴파일러 탐지,find_package, 정책·버전 문제가 여기서 납니다. - generate 단계: configure 뒤 “Generating done” 전에 나는 에러로, 생성기 표현식 오류나 존재하지 않는 타겟 참조 같은 것들입니다.
- build 단계: 컴파일러나 링커의 메시지(
error:,undefined reference,LNK2019)가 나옵니다. 이건 CMake 에러가 아니라 컴파일러 에러지만, 원인은 CMake 설정(누락된 include 경로, 링크 안 된 라이브러리)인 경우가 많습니다. - 실행 단계:
error while loading shared libraries, Windows의 “DLL을 찾을 수 없습니다”처럼 빌드는 됐는데 실행이 안 되는 경우입니다.
flowchart TD
A["cmake -S . -B build"] --> B{Configure}
B -->|실패| C["컴파일러 탐지 / find_package / 정책·버전"]
B -->|성공| D{Generate}
D -->|실패| E["생성기 표현식 / 타겟 참조"]
D -->|성공| F["cmake --build build"]
F -->|실패| G["컴파일 에러 / 링커 에러"]
F -->|성공| H["실행"]
H -->|실패| I["공유 라이브러리 로딩 / DLL"]
configure 단계 에러
CMake 4.0: “Compatibility with CMake < 3.5 has been removed”
CMake Error at third_party/somelib/CMakeLists.txt:1 (cmake_minimum_required):
Compatibility with CMake < 3.5 has been removed from CMake.
Update the VERSION argument <min> value. Or, use the <min>...<max> syntax
to tell CMake that the project requires at least <min> but has been updated
to work with policies introduced by <max> or earlier.
Or, add -DCMAKE_POLICY_VERSION_MINIMUM=3.5 to try configuring anyway.
2025년 3월 CMake 4.0이 나온 뒤 가장 많이 보게 된 에러입니다. cmake_minimum_required(VERSION 2.8)이나 3.1처럼 오래된 버전을 선언한 프로젝트와의 호환 코드가 제거됐습니다. 에러 위치를 보면 대부분 내 CMakeLists.txt가 아니라 add_subdirectory나 FetchContent로 가져온 서드파티 라이브러리입니다. CI 이미지나 Homebrew가 CMake를 4.x로 자동으로 올리면서, 코드를 한 줄도 안 바꿨는데 어느 날 갑자기 빌드가 깨지는 형태로 나타납니다.
해결 순서는 이렇습니다.
- 문제의 라이브러리를 최신 버전으로 올립니다. 활발히 관리되는 라이브러리는 대부분 이미 선언을 고쳤습니다.
- 당장 올릴 수 없다면
-DCMAKE_POLICY_VERSION_MINIMUM=3.5를 configure에 넘깁니다. 프리셋을 쓴다면cacheVariables에 넣어 두면 됩니다. 다만 이건 “3.5 수준의 정책으로 일단 시도해 본다”는 뜻이라 그 라이브러리가 실제로 잘 빌드된다는 보장은 없습니다. - 내 프로젝트라면
cmake_minimum_required(VERSION 3.15...3.31)처럼 범위 문법으로 바꿉니다. 앞 숫자는 최소 버전, 뒤 숫자는 “이 버전까지의 새 정책을 확인했다”는 뜻입니다.
CI에서는 CMake 버전을 고정해 두는 것(예: jwlawson/actions-setup-cmake로 특정 버전 설치)이 이런 갑작스러운 깨짐을 막는 가장 확실한 방법입니다.
컴파일러를 찾을 수 없음
CMake Error at CMakeLists.txt:2 (project):
No CMAKE_CXX_COMPILER could be found.
컴파일러가 설치되지 않았거나, cmake를 실행한 셸의 PATH에 없는 경우입니다.
# Linux
which gcc g++ c++
sudo apt-get install build-essential # Ubuntu/Debian
sudo dnf install gcc-c++ # Fedora/RHEL
# macOS
xcode-select --install
Windows에서는 생성기에 따라 해결책이 다릅니다. Visual Studio 17 2022 생성기는 설치된 Visual Studio를 스스로 찾으므로 일반 PowerShell에서도 됩니다. 반면 Ninja나 NMake Makefiles 생성기로 MSVC를 쓰면 CMake는 PATH에서 cl.exe를 찾기 때문에, x64 Native Tools Command Prompt(vcvars가 적용된 셸)에서 실행해야 합니다. VS Code에서 “No CMAKE_CXX_COMPILER”가 나는 경우도 대부분 CMake Tools에서 kit(컴파일러 환경)을 선택하지 않은 것입니다.
where cl.exe # 아무것도 안 나오면 vcvars가 적용되지 않은 셸
cmake -G "Visual Studio 17 2022" -A x64 -S . -B build # VS 생성기는 셸과 무관
컴파일러를 명시하고 싶다면 -DCMAKE_CXX_COMPILER=/usr/bin/g++-13처럼 전체 경로를 주는데, 이 값은 빌드 디렉터리를 처음 만들 때만 적용됩니다. 이미 configure한 디렉터리에 나중에 넘기면 CMake가 경고와 함께 캐시를 지우고 다시 구성하거나, 기대와 다르게 동작합니다. 컴파일러를 바꿀 때는 새 빌드 디렉터리를 만드세요.
C++ 표준을 지원하지 않는 컴파일러
CMake Error in CMakeLists.txt:
Target "myapp" requires the language dialect "CXX20" (with compiler
extensions), but CMake does not know the compile flags to use to enable it.
컴파일러가 너무 오래됐거나, CMake가 그 컴파일러의 해당 표준 옵션을 모르는 경우입니다(오래된 CMake + 최신 컴파일러 조합에서도 납니다). 표준은 전역 변수보다 타겟에 요구하는 방식이 권장됩니다.
target_compile_features(myapp PRIVATE cxx_std_20)
set_target_properties(myapp PROPERTIES CXX_EXTENSIONS OFF)
특정 컴파일러 버전을 강제하고 싶다면 configure에서 명확한 메시지를 내는 것이 좋습니다.
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 11)
message(FATAL_ERROR "GCC 11 이상이 필요합니다 (현재 ${CMAKE_CXX_COMPILER_VERSION})")
endif()
find_package 실패
CMake Error at CMakeLists.txt:10 (find_package):
Could not find a package configuration file provided by "fmt" with any of
the following names:
fmtConfig.cmake
fmt-config.cmake
find_package는 두 가지 방식으로 패키지를 찾습니다.
- Config 모드: 라이브러리가 설치하면서 함께 넣어 둔
<이름>Config.cmake를 찾습니다. 현대 라이브러리 대부분이 이 방식이고, 위 에러 메시지가 이 모드의 실패입니다. - Module 모드: CMake(또는 프로젝트)가 가진
Find<이름>.cmake스크립트가 헤더와 라이브러리 파일을 직접 찾습니다.FindOpenSSL,FindThreads같은 것이 여기에 해당합니다.
가장 빠른 진단은 CMake에게 어디를 찾아봤는지 물어보는 것입니다.
cmake -S . -B build --debug-find-pkg=fmt # CMake 3.23+: 특정 패키지의 탐색 경로만 출력
cmake -S . -B build --debug-find # 모든 find_* 호출의 탐색 과정
출력에서 실제 설치 위치가 탐색 목록에 없다면, 설치 루트를 알려 주면 됩니다.
cmake -S . -B build -DCMAKE_PREFIX_PATH="/opt/fmt;/opt/spdlog"
# 또는 Config 파일이 있는 폴더를 직접
cmake -S . -B build -Dfmt_DIR=/opt/fmt/lib/cmake/fmt
리눅스 배포판 패키지라면 개발 패키지가 설치됐는지도 확인합니다. libfmt9처럼 런타임 라이브러리만 있고 libfmt-dev가 없으면 Config 파일과 헤더가 없습니다.
버전 불일치, 컴포넌트 누락, 패키지 이름 대소문자, vcpkg·Conan 툴체인 연동처럼 find_package 실패의 원인별 점검 순서는 CMake find_package로 외부 라이브러리 연결하기: Config vs Module 모드와 커스텀 Find 모듈에 따로 정리했습니다. CMakeLists.txt 작성 실수(타겟 이름 오타, 괄호, 중복 타겟) 쪽은 CMake 자주 나는 에러 10가지를 참고하세요.
Boost: CMake 3.30부터 FindBoost가 빠졌다
CMake Warning (dev) at CMakeLists.txt:8 (find_package):
Policy CMP0167 is not set: The FindBoost module is removed.
CMake 3.30에서 오래된 FindBoost 모듈이 정책 CMP0167로 제거 대상이 됐습니다. Boost 1.70부터 Boost 자체가 BoostConfig.cmake를 제공하므로, 이제는 Config 모드로 찾는 것이 기본입니다.
cmake_minimum_required(VERSION 3.15...3.31)
find_package(Boost 1.80 CONFIG REQUIRED COMPONENTS filesystem program_options)
target_link_libraries(myapp PRIVATE Boost::filesystem Boost::program_options)
인터넷의 오래된 예제에 나오는 Boost_INCLUDE_DIR, Boost_LIBRARY_DIR, BOOST_ROOT 같은 힌트 변수는 FindBoost용이라 Config 모드에서는 대부분 효과가 없습니다. 대신 Boost 설치 루트를 CMAKE_PREFIX_PATH에 넣거나 Boost_DIR에 lib/cmake/Boost-<버전> 폴더를 지정합니다. 헤더 전용 부분만 쓴다면 Boost::headers 타겟을 링크하면 됩니다.
버전 불일치
Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the
system variable OPENSSL_ROOT_DIR (found suitable version "1.1.1w", minimum required is "3.0")
요구 버전보다 낮은 패키지만 찾은 경우입니다. 시스템에 여러 버전이 깔려 있다면 원하는 쪽을 가리키게 합니다. FindOpenSSL은 OPENSSL_ROOT_DIR 힌트를 읽습니다.
cmake -S . -B build -DOPENSSL_ROOT_DIR=/opt/openssl-3
# macOS Homebrew
cmake -S . -B build -DOPENSSL_ROOT_DIR="$(brew --prefix openssl@3)"
캐시에 남은 옛 값
CMake Error: Error: generator : Ninja
Does not match the generator used previously: Unix Makefiles
Either remove the CMakeCache.txt file and CMakeFiles directory or choose a different binary directory.
생성기, 컴파일러, 툴체인 파일은 빌드 디렉터리가 처음 만들어질 때 고정됩니다. 옵션을 바꿨는데 반영이 안 되는 문제 대부분이 이것입니다.
cmake --fresh -S . -B build # CMake 3.24+: 캐시를 버리고 새로 구성
# 또는
rm -rf build && cmake -S . -B build -G Ninja
반대로 프로젝트 코드에서 캐시 변수를 쓸 때는, 사용자가 명령줄로 준 값을 덮어쓰지 않도록 FORCE 없이 기본값만 지정하는 것이 원칙입니다.
set(MYAPP_BACKEND "opengl" CACHE STRING "Rendering backend") # 사용자가 -D로 주면 그 값 유지
패키지 매니저 툴체인이 적용되지 않음
vcpkg나 Conan을 쓰는데 find_package가 실패한다면, 툴체인 파일이 첫 configure 때 들어갔는지부터 확인합니다. 이미 만든 빌드 디렉터리에 나중에 -DCMAKE_TOOLCHAIN_FILE을 추가하면 적용되지 않습니다.
# vcpkg (manifest 모드: 프로젝트 루트에 vcpkg.json)
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
Conan은 버전 2부터 연동 방식이 바뀌었습니다. Conan 1 시절 예제의 [generators] cmake나 cmake_find_package는 Conan 2에서 제거됐고, 지금은 CMakeDeps(각 의존성의 Config 파일 생성)와 CMakeToolchain(툴체인 파일 생성)을 씁니다.
# conanfile.txt (Conan 2)
[requires]
fmt/10.2.1
[generators]
CMakeDeps
CMakeToolchain
[layout]
cmake_layout
conan install . --build=missing -s build_type=Release
cmake --preset conan-release # CMakeToolchain이 CMakeUserPresets.json을 만들어 줌
cmake --build --preset conan-release
Conan 1용 예제를 그대로 따라 하면 “generator ‘cmake_find_package’ not found” 같은 에러가 납니다. 인터넷 예제의 연도를 꼭 확인하세요. 두 매니저의 사용법은 vcpkg 기초와 Conan 기초에 따로 정리했습니다.
크로스 컴파일에서 try_run
CMake Error: TRY_RUN() invoked in cross-compiling mode, please set the following cache variables appropriately:
HAVE_POSIX_REGEX_EXITCODE (advanced)
크로스 컴파일할 때는 대상 바이너리를 빌드 머신에서 실행할 수 없으므로, try_run을 쓰는 검사가 결과를 알 수 없어 멈춥니다. 대상 기기에서 확인한 결과를 캐시 변수로 미리 주거나, 툴체인 파일에 CMAKE_CROSSCOMPILING_EMULATOR(예: qemu-aarch64)를 지정해 에뮬레이터로 실행하게 합니다.
# toolchain-aarch64.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # 빌드 도구는 호스트에서
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) # 라이브러리·헤더는 대상 루트에서만
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_CROSSCOMPILING_EMULATOR qemu-aarch64 -L /usr/aarch64-linux-gnu)
CMAKE_FIND_ROOT_PATH_MODE_* 설정이 빠지면 호스트(x86)용 라이브러리를 찾아 링크하다가 “file in wrong format”으로 실패하는 경우가 흔합니다.
build 단계: 컴파일 에러
헤더를 찾지 못함
fatal error: fmt/core.h: No such file or directory
CMake 에러가 아니라 컴파일러 에러지만, 원인은 대개 타겟에 include 경로가 전달되지 않은 것입니다. 현대 CMake에서는 include_directories()로 경로를 직접 더하기보다 라이브러리 타겟을 링크하면 include 경로와 정의가 함께 전파됩니다.
find_package(fmt CONFIG REQUIRED)
target_link_libraries(myapp PRIVATE fmt::fmt) # include 경로까지 자동 전파
fmt::fmt 같은 ::가 들어간 이름은 IMPORTED 타겟이라, 이름을 틀리면 CMake가 generate 단계에서 “Target links to target fmt::fmt but the target was not found”로 알려 줍니다. :: 없는 이름(fmt)을 쓰면 CMake는 그걸 그냥 라이브러리 파일 이름으로 해석해 링커에 -lfmt를 넘기므로, 에러가 한참 뒤 링크 단계에서야 납니다. 그래서 가능하면 네임스페이스가 있는 타겟 이름을 씁니다.
컴파일러별 옵션 충돌
cl : Command line warning D9002 : ignoring unknown option '-Wall'
g++: error: /W4: No such file or directory
컴파일러 전용 옵션을 조건 없이 넣은 경우입니다.
target_compile_options(myapp PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/W4 /permissive->
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall -Wextra -Wpedantic>
)
clang-cl은 컴파일러 ID가 Clang이지만 MSVC 스타일 옵션을 받으므로, 이 조합을 지원해야 한다면 CMAKE_CXX_COMPILER_FRONTEND_VARIANT(값이 MSVC 또는 GNU)로 분기합니다.
build 단계: 링커 에러
undefined reference / LNK2019
/usr/bin/ld: main.cpp.o: undefined reference to `fmt::v10::vformat(...)'
error LNK2019: unresolved external symbol "..." referenced in function main
링커가 심볼 정의를 찾지 못한 것입니다. 흔한 원인 순서대로 확인합니다.
- 라이브러리를 링크하지 않음:
target_link_libraries에 해당 타겟이 있는지 확인합니다. 헤더 전용이라고 생각한 라이브러리가 실제로는 컴파일된 부분이 있는 경우가 많습니다(fmt, spdlog의 컴파일 모드 등). - 스레드:
pthread_create가 없다고 나오면-lpthread를 직접 쓰지 말고find_package(Threads REQUIRED)+Threads::Threads를 링크합니다. - 정적 라이브러리 순서: GNU ld는 정적 라이브러리를 명령줄 순서대로 한 번만 훑기 때문에, A가 B를 쓰는데
-lB -lA순서면 실패합니다. CMake에서는 각 라이브러리 타겟이 자기 의존성을target_link_libraries로 선언하면 CMake가 올바른 순서를 만들어 주므로, 실행 파일에서 순서를 맞추려 애쓰기보다 라이브러리 쪽 선언을 고치는 게 근본 해결입니다. - C와 C++ 섞기: C 라이브러리 헤더를 C++에서 include할 때
extern "C"가 없으면 이름 맹글링 때문에 심볼 이름이 달라져 못 찾습니다. - ABI 불일치: GCC의
_GLIBCXX_USE_CXX11_ABI나 MSVC의 런타임(/MDvs/MT, Debug vs Release)이 다른 라이브러리를 섞으면std::string이 들어간 심볼이 안 맞습니다. MSVC에서LNK2038: mismatch detected for 'RuntimeLibrary'가 이 경우입니다.CMAKE_MSVC_RUNTIME_LIBRARY로 프로젝트 전체와 의존성의 런타임을 맞춥니다.
링커에 실제로 어떤 명령이 넘어갔는지 보면 원인이 금방 드러납니다.
cmake --build build --verbose # 실제 컴파일·링크 명령 출력 (Ninja/Make 공통)
정적 라이브러리의 모든 오브젝트를 강제로 포함해야 할 때(자기 등록 패턴 등)는 CMake 3.24부터 링크 기능 문법을 씁니다.
target_link_libraries(myapp PRIVATE "$<LINK_LIBRARY:WHOLE_ARCHIVE,plugins>")
플랫폼별로 -Wl,--whole-archive, -Wl,-force_load, /WHOLEARCHIVE를 직접 분기하던 코드를 이 한 줄로 대신할 수 있습니다. 직접 분기할 때 if(UNIX) ... elseif(APPLE) 순서로 쓰면 macOS도 UNIX가 참이라 Apple 분기에 절대 도달하지 않는다는 점도 흔한 실수입니다.
중복 정의 (multiple definition / LNK2005)
multiple definition of `helper()'; a.cpp.o: first defined here
error LNK2005: "void __cdecl helper(void)" already defined in a.obj
헤더에 inline 없이 함수를 정의해 여러 번역 단위에 들어간 경우가 가장 흔합니다. 헤더에 정의를 두려면 inline을 붙이고, 아니면 선언만 두고 정의는 .cpp 하나에 둡니다. 같은 .cpp를 두 타겟의 소스 목록에 넣고 두 타겟을 함께 링크해도 같은 에러가 납니다. 공통 코드는 라이브러리 타겟 하나로 만들어 링크하세요.
실행 단계: 공유 라이브러리를 못 찾음
./myapp: error while loading shared libraries: libfoo.so.1: cannot open shared object file: No such file or directory
빌드 디렉터리에서는 CMake가 빌드용 RPATH를 넣어 주므로 잘 실행되다가, cmake --install로 설치한 뒤나 다른 머신에 복사한 뒤에 나는 경우가 많습니다. 설치 시 CMake가 빌드 RPATH를 지우기 때문입니다.
include(GNUInstallDirs)
set_target_properties(myapp PROPERTIES
INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}" # 실행 파일 기준 상대 경로
)
macOS는 $ORIGIN 대신 @loader_path/../lib를 씁니다. 시스템 전체에 설치하는 라이브러리라면 /etc/ld.so.conf.d/에 경로를 추가하고 ldconfig를 실행하는 방법도 있는데, 이때 sudo echo "..." > /etc/...는 리다이렉트가 root 권한으로 실행되지 않아 실패하므로 echo "/opt/foo/lib" | sudo tee /etc/ld.so.conf.d/foo.conf처럼 씁니다. ldd ./myapp로 어떤 라이브러리를 못 찾는지 먼저 확인하세요.
Windows는 RPATH 개념이 없고, DLL을 실행 파일 옆에 두는 것이 기본입니다. CMake 3.21부터는 의존 DLL을 한 번에 복사할 수 있습니다.
add_custom_command(TARGET myapp POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_RUNTIME_DLLS:myapp> $<TARGET_FILE_DIR:myapp>
COMMAND_EXPAND_LISTS
)
FetchContent·ExternalProject 에러
CMake Error at .../FetchContent.cmake: Failed to clone repository: 'https://github.com/...'
네트워크·프록시·태그 이름 오류가 대부분입니다. 사내망이라면 git 프록시 설정을 확인하고, 태그 대신 커밋 해시를 쓰면 재현성도 좋아집니다.
include(FetchContent)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG e69e5f977d458f2650bb346dadf2ad30c5320281 # 10.2.1
GIT_SHALLOW FALSE # 커밋 해시를 쓸 때는 shallow clone이 실패할 수 있음
FIND_PACKAGE_ARGS CONFIG # CMake 3.24+: 시스템에 있으면 find_package로 먼저 시도
)
FetchContent_MakeAvailable(fmt)
FIND_PACKAGE_ARGS를 주면 설치된 패키지가 있을 때는 그걸 쓰고, 없을 때만 내려받습니다. 예전 예제에 많은 FetchContent_GetProperties + FetchContent_Populate + add_subdirectory 조합은 CMake 3.30부터 정책 CMP0169로 deprecated되었으니 FetchContent_MakeAvailable로 바꾸세요. 그리고 가져온 라이브러리가 오래된 cmake_minimum_required를 선언하고 있으면 1.1절의 CMake 4.0 에러가 여기서 납니다.
ExternalProject_Add는 빌드 단계에서 실행되므로, 같은 configure 안에서 그 결과물을 find_package로 찾을 수 없습니다. “ExternalProject로 받은 라이브러리를 못 찾는다”는 문제는 대부분 이 순서 문제이고, 같은 빌드 안에서 쓰려면 FetchContent를 쓰거나 슈퍼빌드 구조로 나눠야 합니다.
크로스 플랫폼에서만 나는 에러
- 대소문자: Windows와 macOS 기본 파일 시스템은 대소문자를 구분하지 않아서
#include "Config.h"가config.h를 찾아 주지만, 리눅스에서는 실패합니다. CI에서 리눅스를 한 번이라도 돌리면 바로 드러납니다. - 경로 하드코딩:
C:/Users/...나/home/...을CMakeLists.txt에 쓰지 말고${CMAKE_CURRENT_SOURCE_DIR},${PROJECT_SOURCE_DIR}기준으로 씁니다. 사용자별 경로는CMakeUserPresets.json이나 명령줄로 넘깁니다. - find_library에 확장자:
find_library(FOO_LIB libfoo.so)처럼 확장자를 쓰지 말고find_library(FOO_LIB foo)로 이름만 줍니다. Windows에서find_library가 찾는 것은 DLL이 아니라 링크용.lib(import library)라는 점도 헷갈리기 쉽습니다. WIN32와MSVC의 차이:WIN32는 대상 플랫폼,MSVC는 컴파일러입니다. MinGW는WIN32이지만MSVC는 아니므로, 컴파일러 옵션 분기는 컴파일러 기준, API·정의 분기는 플랫폼 기준으로 나눕니다.
디버깅 도구
cmake -S . -B build --log-level=DEBUG # message(DEBUG ...)까지 출력
cmake -S . -B build --debug-find-pkg=Boost # find_package 탐색 경로 (3.23+)
cmake -S . -B build --trace-expand --trace-source=CMakeLists.txt # 변수가 전개된 채로 한 줄씩 추적
cmake -S . -B build --graphviz=deps.dot # 타겟 의존성 그래프
cmake --build build --verbose # 실제 컴파일·링크 명령
--trace-expand는 출력이 매우 많으므로 --trace-source로 파일을 좁히거나, --trace-redirect=trace.log로 파일에 쓴 뒤 검색하는 편이 낫습니다. 제가 “이 옵션이 왜 안 먹지” 류의 문제를 볼 때는 거의 항상 이 출력에서 해당 변수가 어디서 덮어써지는지를 찾습니다. 상위 CMakeLists.txt에서 set(CMAKE_CXX_FLAGS ...)로 통째로 덮어쓰는 코드가 원인인 경우가 의외로 많습니다.
타겟 속성을 확인할 때는 get_target_property로 찍어 볼 수 있지만, INTERFACE_* 속성으로 전파되는 값은 최종 명령에 합쳐진 뒤에야 보이므로 --verbose 빌드 명령을 보는 것이 가장 정확합니다.
재발을 막는 습관
cmake_minimum_required(VERSION 3.15...3.31)처럼 범위로 선언하고, CI의 CMake 버전을 고정합니다.- 전역 변수(
CMAKE_CXX_FLAGS,include_directories,link_directories) 대신 타겟 단위 명령(target_compile_options,target_include_directories,target_link_libraries)을 씁니다. 옵션이 어디서 왔는지 추적하기 쉬워집니다. - 의존성은 네임스페이스 타겟(
fmt::fmt,Boost::filesystem,Threads::Threads)으로 링크합니다. - 팀과 CI가 같은 configure 옵션을 쓰도록 CMake Presets를 둡니다.
- 빌드 디렉터리는 소스 밖(또는
build/<preset>)에 두고, 이상하면 주저하지 말고 새로 만듭니다.
참고 자료
- CMake 공식 문서
- cmake-policies(7) — CMP0167(FindBoost 제거), CMP0169(FetchContent_Populate) 등
- CMake Discourse
- Professional CMake