CMake 옵션과 캐시 변수 — option·set·cache type 분석
#왜 캐시 변수가 필요한가
cmake -B build를 부를 때마다 모든 설정을 다시 입력해야 한다면 빌드 사이클이 견디기 힘들어집니다.
# 매번 이걸 칠 수는 없다cmake -B build -DCMAKE_BUILD_TYPE=Debug -DENABLE_TESTS=ON -DUSE_SYSTEM_ZLIB=OFFCMake의 답은 캐시입니다. cmake -B build가 끝나는 순간, 그때 사용된 모든 설정이 build/CMakeCache.txt라는 파일에 저장됩니다. 다음 호출에서 CMake는 이 캐시를 기본값으로 가져옵니다. -D로 새 값을 명시한 변수만 갱신됩니다.
# 처음 — 모든 설정 명시cmake -B build -DCMAKE_BUILD_TYPE=Debug -DENABLE_TESTS=ON
# 이후 — 캐시 자동 사용cmake -B build # 이전 설정 그대로cmake -B build -DENABLE_TESTS=OFF # 변경할 것만이게 캐시 변수의 첫 용도입니다. 그 외에도 캐시는 두 가지 역할을 더 합니다.
- 사용자 옵션: 프로젝트가 사용자에게 노출하는 설정(
ENABLE_TESTS,BUILD_SHARED_LIBS등).cmake-gui나ccmake에 보이는 모든 항목이 캐시 변수입니다. - 탐지 결과 저장:
find_package가 찾은 라이브러리 경로, 컴파일러 위치, 시스템 라이브러리 버전 — 한 번 탐지한 결과를 재탐지 없이 재사용합니다. 큰 프로젝트에서find_package가 수십 개 있으면, 캐시가 없을 때 매번 수십 초의 탐지 시간이 추가됩니다.
캐시 파일은 그냥 텍스트입니다. 직접 열어 보면 CMake가 무엇을 알고 있는지 한눈에 보입니다.
$ head build/CMakeCache.txt# This is the CMakeCache file.# It was generated by CMake: /usr/bin/cmake#CMAKE_BUILD_TYPE:STRING=DebugCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/g++ENABLE_TESTS:BOOL=ON...이름:타입=값 형식입니다. 디버깅 시 유용한 첫 단서가 됩니다.
#캐시 변수
#설정 방법
set(MY_VAR "default" CACHE STRING "변수 설명")| 타입 | 설명 | GUI 표시 |
|---|---|---|
STRING | 문자열 | 텍스트 입력 |
BOOL | 불리언 (ON/OFF) | 체크박스 |
PATH | 디렉터리 경로 | 디렉터리 선택기 |
FILEPATH | 파일 경로 | 파일 선택기 |
INTERNAL | 내부 사용 | 표시 안 됨 |
# 예시set(MY_INSTALL_PREFIX "/usr/local" CACHE PATH "Installation prefix")set(ENABLE_TESTS ON CACHE BOOL "Enable unit tests")set(VERSION_STRING "1.0.0" CACHE STRING "Version string")set(DETECTED_COMPILER_PATH "/usr/bin/gcc" CACHE INTERNAL "")#명령줄에서 설정
-D변수=값 형식으로 캐시 변수를 설정합니다.
cmake -B build -DENABLE_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/opt/myapp#이미 존재하는 캐시 변수
캐시 변수는 이미 존재하면 덮어쓰지 않습니다. 이 동작 덕분에 사용자 설정이 보존됩니다.
# 첫 번째 cmake 실행: MY_VAR = "default"가 캐시에 저장됨set(MY_VAR "default" CACHE STRING "")
# 사용자가 cmake -DMY_VAR=custom 으로 실행# MY_VAR = "custom"
# 두 번째 cmake 실행: 캐시에 이미 있으므로 "default"로 덮어쓰지 않음set(MY_VAR "default" CACHE STRING "")# MY_VAR = "custom" 유지#FORCE 옵션
강제로 덮어쓰려면 FORCE를 사용합니다.
set(MY_VAR "new_value" CACHE STRING "설명" FORCE)주의: FORCE는 사용자가 설정한 값도 덮어쓰므로 신중히 사용하세요. 주로 CMakeLists.txt에서 계산된 값을 저장할 때만 사용합니다.
#option() 명령 — 불리언 캐시의 단축 문법
option(ENABLE_TESTS "Enable unit tests" ON)
# 위 한 줄은 다음과 동치set(ENABLE_TESTS ON CACHE BOOL "Enable unit tests")겉모습은 비슷하지만 둘 사이엔 세 가지 미묘한 차이가 있습니다.
- 타입 강제.
option()은 반드시 BOOL입니다.STRING같은 다른 타입을 못 만듭니다. STRING이 필요하면set(... CACHE STRING ...)이 답. ON/OFF외 값 거부.option(X "" yes)는 동작하지 않습니다. 정해진 키워드만 받습니다.- CMake 3.13+ 정책 차이. 3.13부터
option은 일반 변수가 이미 같은 이름으로 정의되어 있으면 그걸 그대로 사용합니다 (CMP0077). 그 이전엔 캐시 변수가 일반 변수를 덮어썼습니다.
세 번째 점이 종종 사고를 부릅니다. CMake 호출 전에 일반 변수로 미리 값을 설정해 두면, option()이 그걸 보고 캐시 작성을 건너뛸 수 있습니다. 의도된 동작이지만 모르고 만나면 헷갈립니다.
#option() vs set(... CACHE STRING ...) — 언제 무엇을
| 상황 | 권장 |
|---|---|
| 켜기/끄기 토글 (true/false) | option(NAME "doc" ON) |
| 여러 값 중 하나 (예: “Debug” / “Release”) | set(NAME "Debug" CACHE STRING "doc") + set_property(CACHE NAME PROPERTY STRINGS Debug Release ...) |
| 경로 | set(NAME "/path" CACHE PATH "doc") |
| 파일 경로 | set(NAME "/path/file" CACHE FILEPATH "doc") |
| 사용자가 보면 안 되는 내부 | set(NAME val CACHE INTERNAL "doc") |
set_property(CACHE ... PROPERTY STRINGS ...)이 흥미롭습니다. STRING 캐시 변수에 허용 값 목록을 붙이면 cmake-gui나 ccmake에 드롭다운으로 표시됩니다. 사용자가 임의 문자열을 입력하는 사고를 막아 줍니다.
set(MYAPP_BACKEND "OpenGL" CACHE STRING "Rendering backend")set_property(CACHE MYAPP_BACKEND PROPERTY STRINGS OpenGL Vulkan DirectX)#일반적인 옵션 패턴
# 기능 토글option(ENABLE_TESTING "Enable unit tests" ON)option(ENABLE_COVERAGE "Enable code coverage" OFF)option(ENABLE_SANITIZERS "Enable sanitizers in Debug" ON)
# 빌드 방식option(BUILD_SHARED_LIBS "Build shared libraries" OFF)option(BUILD_DOCS "Build documentation" OFF)
# 의존성 선택option(USE_SYSTEM_ZLIB "Use system zlib instead of bundled" OFF)option(USE_SYSTEM_JSON "Use system nlohmann_json" OFF)#옵션 사용
option(ENABLE_TESTING "Enable testing" ON)option(USE_SYSTEM_ZLIB "Use system zlib" OFF)
if(ENABLE_TESTING) enable_testing() add_subdirectory(tests)endif()
if(USE_SYSTEM_ZLIB) find_package(ZLIB REQUIRED) target_link_libraries(myapp PRIVATE ZLIB::ZLIB)else() add_subdirectory(third_party/zlib) target_link_libraries(myapp PRIVATE zlib)endif()#캐시 변수와 일반 변수
같은 이름의 캐시 변수와 일반 변수가 있으면, 일반 변수가 우선합니다.
set(MY_VAR "cache_value" CACHE STRING "")set(MY_VAR "normal_value")
message("${MY_VAR}") # normal_value캐시 값을 직접 참조하려면 $CACHE{...}를 사용합니다.
set(MY_VAR "cache_value" CACHE STRING "")set(MY_VAR "normal_value")
message("Cache: $CACHE{MY_VAR}") # cache_valuemessage("Current: ${MY_VAR}") # normal_value이 동작은 함수 안에서 캐시 변수를 일시적으로 오버라이드할 때 유용합니다.
#조건부 옵션
#CMakeDependentOption
다른 옵션에 의존하는 옵션을 만듭니다.
include(CMakeDependentOption)
option(BUILD_TOOLS "Build tools" ON)
# BUILD_TOOLS가 ON일 때만 BUILD_TOOL_A를 선택할 수 있음# BUILD_TOOLS가 OFF이면 BUILD_TOOL_A는 강제로 OFFcmake_dependent_option( BUILD_TOOL_A "Build tool A" ON "BUILD_TOOLS" OFF)
cmake_dependent_option( BUILD_TOOL_B "Build tool B" ON "BUILD_TOOLS" OFF)복잡한 조건도 가능합니다.
# ENABLE_TESTING이 ON이고 BUILD_SHARED_LIBS가 OFF일 때만 사용 가능cmake_dependent_option( ENABLE_STATIC_TESTS "Static test builds" ON "ENABLE_TESTING;NOT BUILD_SHARED_LIBS" OFF)#FeatureSummary
설정된 기능을 요약 출력합니다.
include(FeatureSummary)
option(ENABLE_FOO "Enable foo feature" ON)option(ENABLE_BAR "Enable bar feature" OFF)option(ENABLE_BAZ "Enable baz feature" ON)
add_feature_info(Foo ENABLE_FOO "The foo feature for X")add_feature_info(Bar ENABLE_BAR "The bar feature for Y")add_feature_info(Baz ENABLE_BAZ "The baz feature for Z")
feature_summary(WHAT ALL)출력은 활성화된 기능(Foo, Baz)과 비활성화된 기능(Bar)을 그룹으로 나누어 보여줍니다.
사용자가 빌드 구성을 한눈에 확인할 수 있어 편리합니다.
#빌드 타입 처리
#기본 빌드 타입 설정
빌드 타입을 지정하지 않으면 빈 문자열이 됩니다. 기본값을 설정하는 패턴입니다.
# 단일 설정 생성기(Make, Ninja)에서만 유효if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to 'Release' as none was specified.") set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
# GUI에서 선택 가능한 값 설정 set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo" )endif()CMAKE_CONFIGURATION_TYPES는 multi-config 생성기(Visual Studio, Xcode)에서 설정됩니다. 이 변수가 있으면 CMAKE_BUILD_TYPE을 건드리지 않습니다.
#빌드 타입별 설정
if문 방식 (단일 설정 생성기에서만 동작):
if(CMAKE_BUILD_TYPE STREQUAL "Debug") target_compile_options(myapp PRIVATE -fsanitize=address) target_link_options(myapp PRIVATE -fsanitize=address)endif()제너레이터 표현식 방식 (모든 생성기에서 동작):
target_compile_options(myapp PRIVATE $<$<CONFIG:Debug>:-fsanitize=address,undefined>)target_link_options(myapp PRIVATE $<$<CONFIG:Debug>:-fsanitize=address,undefined>)제너레이터 표현식 방식을 권장합니다. Visual Studio에서도 Debug 빌드 시에만 sanitizer가 활성화됩니다.
#설정 파일 생성
#configure_file
CMake 변수를 C/C++ 헤더로 내보냅니다.
템플릿 파일 (config.h.in):
#ifndef CONFIG_H#define CONFIG_H
#define PROJECT_NAME "@PROJECT_NAME@"#define VERSION_MAJOR @PROJECT_VERSION_MAJOR@#define VERSION_MINOR @PROJECT_VERSION_MINOR@#define VERSION_PATCH @PROJECT_VERSION_PATCH@
#cmakedefine ENABLE_FEATURE_X#cmakedefine01 ENABLE_FEATURE_Y
#endifCMakeLists.txt:
set(ENABLE_FEATURE_X ON)set(ENABLE_FEATURE_Y OFF)
configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
target_include_directories(myapp PRIVATE ${CMAKE_CURRENT_BINARY_DIR})생성된 파일 (build/config.h):
#ifndef CONFIG_H#define CONFIG_H
#define PROJECT_NAME "MyApp"#define VERSION_MAJOR 1#define VERSION_MINOR 0#define VERSION_PATCH 0
#define ENABLE_FEATURE_X/* #undef ENABLE_FEATURE_Y */
#endif#치환 규칙 — 네 가지 패턴
| 템플릿 | 결과 (값이 참) | 결과 (값이 거짓/미정의) |
|---|---|---|
@VAR@ | 변수 값 그대로 | 빈 문자열 |
${VAR} | 변수 값 그대로 | 빈 문자열 (기본 모드) |
#cmakedefine VAR | #define VAR <값> | /* #undef VAR */ |
#cmakedefine01 VAR | #define VAR 1 | #define VAR 0 |
@VAR@과 ${VAR} 두 형태가 같은 효과를 갖는데, 굳이 둘이 있는 이유는 셸 스크립트 템플릿과의 호환성입니다. 셸 스크립트는 $을 자기 변수로 쓰기 때문에 ${VAR}이 의도와 다르게 풀릴 수 있습니다. 그런 경우 configure_file(... @ONLY)을 써서 @...@만 치환되도록 강제합니다.
configure_file( script.sh.in script.sh @ONLY # ${} 형태는 그대로 두고, @...@만 치환)#configure_file() vs file(GENERATE ...)
비슷해 보이는 두 명령이 언제 평가되느냐에서 갈립니다.
configure_file() | file(GENERATE ...) | |
|---|---|---|
| 평가 시점 | CMake 구성 시점 | 빌드 시스템 생성 시점 |
제너레이터 식 ($<...>) | 지원 안 함 (리터럴로 나옴) | 지원 — 빌드 시점에 풀림 |
| 입력 | .in 파일 또는 INPUT 옵션 | CONTENT 인라인 또는 INPUT |
| 구성당 출력 | 1개 | 구성마다 다른 파일도 가능 |
configure_file()은 컴파일 시점에 고정될 정보(버전 번호, 빌드 타입과 무관한 매크로)에 씁니다. 빌드 구성에 따라 내용이 달라야 할 파일은 file(GENERATE ...)을 씁니다.
# configure_file() — 한 번만 결정configure_file(version.h.in version.h)
# file(GENERATE) — 각 구성마다 결정 (예: pkg-config 파일)file(GENERATE OUTPUT mylib-$<CONFIG>.pc INPUT mylib.pc.in)#CMakePresets.json
CMake 3.19부터 프리셋으로 빌드 구성을 미리 정의할 수 있습니다.
#기본 구조
{ "version": 6, "configurePresets": [ { "name": "debug", "displayName": "Debug Build", "generator": "Ninja", "binaryDir": "${sourceDir}/build/debug", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "ENABLE_TESTING": "ON", "ENABLE_SANITIZERS": "ON" } }, { "name": "release", "displayName": "Release Build", "generator": "Ninja", "binaryDir": "${sourceDir}/build/release", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", "ENABLE_TESTING": "OFF" } } ], "buildPresets": [ { "name": "debug", "configurePreset": "debug" }, { "name": "release", "configurePreset": "release" } ]}#사용
# 프리셋 목록 확인cmake --list-presets
# 프리셋으로 구성cmake --preset debug
# 프리셋으로 빌드cmake --build --preset debug#상속
프리셋을 상속하여 공통 설정을 재사용합니다.
{ "version": 6, "configurePresets": [ { "name": "base", "hidden": true, "generator": "Ninja", "cacheVariables": { "CMAKE_CXX_STANDARD": "17" } }, { "name": "debug", "inherits": "base", "binaryDir": "${sourceDir}/build/debug", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } }, { "name": "release", "inherits": "base", "binaryDir": "${sourceDir}/build/release", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } } ]}hidden: true인 프리셋은 --list-presets에 나타나지 않지만 상속용으로 사용됩니다.
#캐시 관리
#캐시 확인
# 텍스트로 확인cat build/CMakeCache.txt
# 변수 검색grep ENABLE build/CMakeCache.txt
# TUI (Terminal UI)ccmake build
# GUIcmake-gui build#캐시 초기화
문제가 생기면 캐시를 초기화합니다.
# 캐시 파일만 삭제rm build/CMakeCache.txtcmake -B build
# 빌드 디렉터리 전체 삭제 (확실한 방법)rm -rf buildcmake -B build#실전 예시
cmake_minimum_required(VERSION 3.15)project(MyApp VERSION 1.0.0 LANGUAGES CXX)
# === 옵션 ===option(BUILD_SHARED_LIBS "Build shared libraries" OFF)option(ENABLE_TESTING "Enable unit tests" ON)option(ENABLE_COVERAGE "Enable code coverage" OFF)option(ENABLE_SANITIZERS "Enable sanitizers in Debug" ON)option(USE_SYSTEM_JSON "Use system nlohmann_json" OFF)
include(CMakeDependentOption)cmake_dependent_option( ENABLE_COVERAGE_HTML "Generate HTML coverage report" ON "ENABLE_COVERAGE" OFF)
# === 빌드 타입 기본값 ===if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to 'Release'") set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release MinSizeRel RelWithDebInfo )endif()
# === 설정 헤더 생성 ===configure_file(config.h.in config.h)
# === 컴파일러 설정 ===set(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_CXX_EXTENSIONS OFF)
# === 라이브러리 ===add_library(mylib src/mylib.cpp)target_include_directories(mylib PUBLIC include PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
# === 실행 파일 ===add_executable(myapp src/main.cpp)target_link_libraries(myapp PRIVATE mylib)
# === Sanitizers ===if(ENABLE_SANITIZERS) target_compile_options(mylib PRIVATE $<$<CONFIG:Debug>:-fsanitize=address,undefined> ) target_link_options(mylib PRIVATE $<$<CONFIG:Debug>:-fsanitize=address,undefined> )endif()
# === 테스트 ===if(ENABLE_TESTING) enable_testing() add_subdirectory(tests)endif()
# === 요약 출력 ===include(FeatureSummary)add_feature_info(Testing ENABLE_TESTING "Unit tests")add_feature_info(Coverage ENABLE_COVERAGE "Code coverage")add_feature_info(Sanitizers ENABLE_SANITIZERS "Address/UB sanitizers")add_feature_info(SystemJSON USE_SYSTEM_JSON "System nlohmann_json")feature_summary(WHAT ALL)#흔한 실수
#option 기본값이 적용 안 됨
# 회피: 캐시에 이미 있으면 기본값 무시됨cmake -B build -DENABLE_TESTS=OFF # 처음 실행# ... 나중에 CMakeLists.txt 수정 ...option(ENABLE_TESTS "..." ON) # 기본값 ON으로 변경cmake -B build # 여전히 OFF (캐시에 남아 있음)
# 해결: 명시적으로 변경하거나 캐시 삭제cmake -B build -DENABLE_TESTS=ON # 명시적 설정# 또는rm build/CMakeCache.txt && cmake -B build#if(CMAKE_BUILD_TYPE)가 multi-config에서 동작 안 함
# 회피: Visual Studio에서 CMAKE_BUILD_TYPE은 빈 문자열if(CMAKE_BUILD_TYPE STREQUAL "Debug") target_compile_definitions(app PRIVATE DEBUG_MODE)endif()# Visual Studio에서는 항상 거짓
# Good: 제너레이터 표현식 사용target_compile_definitions(app PRIVATE $<$<CONFIG:Debug>:DEBUG_MODE>)#FORCE 남용
# 회피: 사용자 설정을 무시함option(ENABLE_TESTS "..." ON)# 사용자가 cmake -DENABLE_TESTS=OFF 로 실행해도...set(ENABLE_TESTS ON CACHE BOOL "" FORCE) # 강제로 ON# 사용자가 혼란스러움
# Good: FORCE는 계산된 값에만 사용set(DETECTED_INCLUDE_PATH "${FOUND_PATH}/include" CACHE PATH "" FORCE)#일반 변수와 캐시 변수 혼동
# 회피: 일반 변수 설정이 캐시에 반영 안 됨set(ENABLE_TESTS OFF) # 일반 변수# 캐시의 ENABLE_TESTS는 여전히 이전 값
# Good: 캐시 변수 사용set(ENABLE_TESTS OFF CACHE BOOL "" FORCE)# 또는 명령줄에서# cmake -B build -DENABLE_TESTS=OFF#정리
- 캐시 변수는
CMakeCache.txt에 저장되어 재설정 시 유지됩니다. option()은 불리언 캐시 변수의 단축 문법입니다.- 명령줄에서
-DVAR=value로 캐시 변수를 설정합니다. - 캐시 변수는 이미 존재하면 덮어쓰지 않습니다.
- 일반 변수가 같은 이름의 캐시 변수보다 우선합니다.
configure_file()로 CMake 변수를 C/C++ 헤더로 내보냅니다.- CMakePresets.json으로 빌드 설정을 미리 정의합니다.
- 빌드 타입별 설정은 제너레이터 표현식을 사용하세요.
#다음 장 예고
Ch 5에서는 find_package와 외부 의존성을 다룹니다. 시스템 라이브러리 탐색, FetchContent, 그리고 패키지 설정 파일을 살펴봅니다.
#참고 자료
CMake · 4 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