1. Presets란 무엇이며 어디에 놓이는가
한번이라도 "내 컴퓨터에서는 돌아가는데"라는 말을 들어본 적이 있다면, 당신은 이미 직관적으로 CMake Presets가 왜 필요한지 이해하고 있습니다. 문제는 누군가가 나쁜 개발자라서가 아니라 빌드 설정이 IDE, 환경 변수, CMake 캐시, 여러 빌드 폴더와 팀원들의 머릿속(가장 불안정한 저장소)에 흩어져 있다는 점입니다.
상황을 가정해봅시다. 당신은 CLion에서 Debug를 선택했고, 친구는 VS Code에서 왠지 Release를 선택했으며, 세 번째 사람은 콘솔에서 빌드하는데 어느 날 C++23 표준을 지정하는 것을 깜빡했습니다. 소스는 동일한데 동작은 들쭉날쭉합니다: 어딘가에서는 assert가 작동하고 어느 곳에서는 작동하지 않으며; 어느 곳에서는 최적화 때문에 디버거에 "변수가 보이지 않음"으로 나오고; 또 어느 곳에서는 링커가 다른 버전의 라이브러리를 가져오는 경우가 발생합니다.
CMake Presets는 이렇게 말하는 방법입니다: "이것이 공식적인 프로젝트 빌드 시나리오입니다. 파일에 기술되어 있고, 저장소에 존재하며, 우리 모두가 동일하게 사용합니다." 말로만 같은 게 아니라 실제로 같은 것: 핵심 구성 변수, 빌드 디렉터리, 명령이 동일합니다.
preset이란 무엇인가: 작은 JSON이 큰 스트레스를 줄입니다
Preset은 CMake용의 명명된 시나리오입니다. 보통 시나리오는 여러 단계로 나뉩니다: 먼저 프로젝트를 설정(configure)합니다(CMake가 빌드 파일을 빌드 폴더에 생성), 그다음 빌드(build)하고 때로는 테스트 실행까지 합니다. CMake Presets는 이런 단계를 기술해서, 당신(또는 강사, CI)이 "preset debug를 빌드해"라고 말하면 어떤 플래그, 경로, 파라미터를 써야 하는지 기억할 필요가 없게 합니다.
간단한 모델을 기억해두면 유용합니다:
flowchart LR
A["소스 (sourceDir)"] --> B["Configure preset
cmake --preset ..."]
B --> C["빌드 디렉터리 (binaryDir)
build-debug/ 또는 build-release/"]
C --> D["Build preset
cmake --build --preset ..."]
C --> E["Test preset
ctest --preset ... (필요한 경우)"]
여기서 중요한 점: configure preset은 빌드 폴더가 어떻게 생기고 그 안의 CMakeCache.txt에 무엇이 들어갈지를 결정합니다. build preset은 이미 설정된 디렉터리를 어떻게 빌드할지 담당합니다. test preset은 테스트 실행을 담당합니다(테스트가 있는 경우).
네, 이건 보통 IDE가 버튼 하나로 하던 일을 그대로 옮긴 것입니다. 차이점은 이제 설정이 "어딘가에 UI로 설정되어 있다"가 아니라 저장소에 기술되어 있어 어떤 머신에서도 반복 가능하다는 점입니다.
CMakePresets.json: 파일 구조는 어렵지 않습니다
CMake Presets는 프로젝트 루트에 있는 JSON 파일 CMakePresets.json에 기술됩니다(CMakeLists.txt 옆에 위치). 이것은 정식 JSON이므로 엄격합니다: 따옴표는 필수이고 쉼표도 중요하며, // 스타일의 주석은 허용되지 않습니다(주석이 있으면 편리하겠지만 JSON은 엄격합니다).
파일의 최소 구조는 다음과 같습니다:
{
"version": 6,
"configurePresets": [],
"buildPresets": [],
"testPresets": []
}
version 필드는 preset 포맷의 버전입니다(프로젝트나 CMake 자체의 버전이 아님). 실제로는 4/5/6 같은 버전을 보게 될 것이고, 중요한 것은 사용 중인 CMake가 해당 버전을 읽을 수 있어야 한다는 점입니다. 만약 CMake가 오래되어 포맷을 "이해하지 못하면", 빌드 이전 단계에서 불쾌한 오류가 발생할 것입니다.
이 파일에는 보통 프로젝트의 "공식" presets을 기술합니다: debug, release, 때로는 asan(산타이저는 따로 다룰 예정), 가끔은 ci. 아이디어는 모든 경우의 수를 위한 20가지 변형을 만들지 말고, 팀이 사용하는 2–4개의 실무 시나리오를 고정하는 것입니다.
3. Configure preset: 빌드 폴더와 핵심 변수
Configure preset은 본질적으로 "우리는 어디에, 어떤 파라미터로 프로젝트를 설정하는가?"에 대한 답입니다. 이 preset은 binaryDir을 지정하고, generator를 지정할 수 있으며, 가장 실용적인 것은 cacheVariables를 지정하는 것입니다. 이 변수들은 CMakeCache.txt에 들어가서 구성에 영향을 줍니다.
먼저 기본 preset을 만들고 Debug와 Release가 그것을 상속하도록 해봅시다. 이것은 단지 보기 좋으라고 하는 것이 아니라 복사-붙여넣기를 줄이고, 한 preset에서는 C++23 표준을 설정하는 것을 잊고 다른 preset에서는 설정하는 실수를 줄여줍니다.
예시:
{
"version": 6,
"configurePresets": [
{
"name": "base",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_STANDARD": "23"
}
},
{
"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"
}
}
]
}
여기서 중요한 일이 몇 가지 일어납니다.
binaryDir는 빌드가 어디에 위치할지를 엄격하게 고정합니다. 이것은 규율입니다: Debug와 Release가 서로 섞이지 않도록 하고, "빌드 타입을 바꿨는데 적용이 안 됐다" 같은 상황을 피합니다. 실제로는 적용되었지만 캐시에 이전 설정이 남아 있어서 과거 상태로 동작하는 것입니다. Presets는 과거에 머무르는 일을 줄여줍니다.
${sourceDir}는 CMake Presets의 변수로 "소스 디렉터리"를 의미합니다. 절대 경로(예: C:\Users\Alice\Projects\...)를 쓰는 것보다 낫습니다. 절대 경로는 preset을 이동 불가능하게 만듭니다: 다른 머신에서는 작동하지 않게 됩니다.
cacheVariables는 실제로 캐시에 들어가는 변수들입니다. 가장 흔한 예는 CMAKE_BUILD_TYPE로, 단일 구성 생성기에서 Debug/Release를 결정합니다. 또 합리적인 예는 CMAKE_CXX_STANDARD로, 프로젝트가 "팀 합의"로 C++23로 빌드되도록 보장합니다.
Debug/Release 주제를 코드로 연결하기 위해 간단한 체크를 추가해 실제로 어떤 구성이 빌드되는지 확인할 수 있습니다:
#include <iostream>
int main() {
#ifdef NDEBUG
std::cout << "Release-like build (NDEBUG is defined)\n";
#else
std::cout << "Debug-like build (NDEBUG is NOT defined)\n";
#endif
}
목적은 실제 제품 코드에서 이렇게 쓰라는 것이 아니라, preset이 구성(configuration)을 바꾸고 구성은 매크로(예: NDEBUG)에 영향을 주며 매크로는 컴파일에 영향을 준다는 점을 체감하게 하는 것입니다.
4. Build 및 Test presets: 빌드와 테스트를 재현 가능한 단계로 만들기
Build preset: 빌드는 별도의 단계입니다
Build preset은 "이미 구성된 것을 어떻게 빌드할 것인가"를 담당합니다. 지루하게 들리겠지만, 당신이 IDE에서, 터미널에서, 또는 다른 빌드 폴더에서 15번씩 빌드를 실행하는 상황을 겪어보면 그 필요성을 깨닫게 됩니다. 터미널 탭이 다른 폴더를 가리키고 있었던 적이 있을 겁니다.
Build preset은 보통 configure preset을 이름으로 참조합니다. 예시:
{
"version": 6,
"configurePresets": [
{
"name": "debug",
"binaryDir": "${sourceDir}/build-debug",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
}
],
"buildPresets": [
{
"name": "debug",
"configurePreset": "debug"
}
]
}
의미는 이렇습니다: 빌드 명령은 어떤 빌드 디렉터리를 사용할지 "알고" 있으므로, 여러분이 그 위치를 기억할 필요가 없습니다. 여러 빌드 폴더를 사용하는 경우 특히 유용합니다.
실제로 build preset에는 추가 파라미터를 넣을 수 있습니다: multi-config 생성기에서의 구성 지정, 병렬 빌드(동시에 빌드할 작업 수), 상세 출력 모드 등. 그러나 이 강의 수준에서는 핵심만 잡으면 됩니다: configure preset은 빌드 폴더를 생성하고 설정하며, build preset은 그 폴더를 빌드한다.
Test preset: 테스트 실행도 "합의"로 만들 수 있습니다
Test preset은 테스트 실행을 고정하는 방법입니다. 주의할 점은 test preset이 "테스트 프레임워크"나 "테스트를 작성하는 방법"을 의미하지 않는다는 것입니다. 단순히 ctest 실행 시나리오를 고정하는 것이며, 프로젝트가 CMake에서 테스트를 추가(enable_testing(), add_test(...))한 경우에 작동합니다.
완전한 단위 테스트는 나중에 다루겠지만, 지금은 아이디어를 이해하는 것이 중요합니다: 프로젝트가 테스트 가능하다면, 테스트 실행도 재현 가능해야 합니다.
간단한 test preset 예시는 다음과 같습니다:
{
"version": 6,
"configurePresets": [
{
"name": "debug",
"binaryDir": "${sourceDir}/build-debug",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
}
],
"testPresets": [
{
"name": "debug",
"configurePreset": "debug"
}
]
}
이 preset은 "debug에 해당하는 빌드 디렉터리에서 테스트를 실행하라"고 말합니다. 실제로는 누군가가 build-release에서 ctest를 실행하고 결과를 마치 build-debug의 것으로 보려는 혼동을 많이 줄여줍니다. 프로그래밍에는 이미 충분한 수수께끼가 있으니 불필요한 혼란은 만들지 맙시다.
5. 실무에서의 사용: 명령과 공통/개인 파일 구분
"내가 뭘 클릭했더라" 대신 세 가지 짧은 명령
Presets는 모두가 동일하게 사용하면 특히 빛을 발합니다: 개인 머신, 강사의 환경, 팀 동료, CI 등 모든 곳에서 동일하게요. 아이디어는 단순합니다: 더 이상 "정상 명령"을 머릿속에 두지 않고, 프리셋 이름만 기억하면 됩니다.
일반적인 작업 흐름은 다음과 같습니다:
cmake --preset debug
cmake --build --preset debug
ctest --preset debug
여기서 초보자가 자주 묻는 질문: "한 줄로 못 합니까?" 할 수는 있지만 그럴 필요는 없습니다. configure/build/test로 분리하는 것은 관료주의가 아니라 현실 반영입니다: 설정은 빌드 시스템을 생성하고 매개변수를 캐시하며, 빌드는 바이너리를 만들고, 테스트는 검사를 실행합니다.
만약 CMakePresets.json을 변경했다면, 해당 빌드 디렉터리가 이미 이전 값으로 설정되어 있을 수 있음을 기억하세요. 이 경우 변경 사항을 캐시와 생성에 반영하려면 configure 단계를 다시 실행해야 합니다. Presets가 CMakeCache.txt의 존재를 무시하진 않습니다; 다만 그 생성을 관리 가능하게 만듭니다.
CMakePresets.json와 CMakeUserPresets.json: 어떤 것을 커밋하고 개인 설정은 어디에
흔한 경우는 presets를 공유하고 싶지만 개발자마다 로컬 특성이 있을 수 있다는 점입니다. 예를 들어 누군가는 아주 상세한 빌드 로그를 원하고, 누군가는 비표준 컴파일러 경로를 지정하며, 또 누군가는 시스템 디스크 공간이 부족해 다른 드라이브에 빌드 폴더를 둡니다.
이를 위해 CMake는 두 파일 아이디어를 제공합니다:
- CMakePresets.json — 공유용, 보통 저장소에 커밋합니다. 프로젝트의 "공식" presets입니다.
- CMakeUserPresets.json — 사용자 개인용, 보통 커밋하지 않습니다. 개인적인 편의 설정으로, 다른 사람들의 재현성을 해치지 않아야 합니다.
혼자 작업하더라도 이 규율에 익숙해지는 것이 좋습니다: "공유" 시나리오는 공유 파일에, "개인" 편의는 사용자 파일에 둡니다. 그렇지 않으면 한 달 후에 프로젝트가 "내 환경에서만 빌드된다"는 사실을 발견하게 될 것입니다 — 그 이유는 당신이 과거에 IDE에 수동으로 설정을 추가했기 때문입니다.
6. Presets 사용 시 흔한 실수
이 주제에서 실수들은 복잡한 것이 아니라 일상에서 슬며시 터지는 것입니다: 거의 올바르게 하지만 작은 한 가지 때문에 빌드가 복불복이 됩니다. 좋은 소식은 이런 실수들이 예측 가능하고, 한 번에 그 소리를 알아차리면 다시는 같은 일을 반복하지 않는다는 점입니다.
실수 1: 모든 경우에 하나의 binaryDir 사용.
두 개의 preset(예: Debug와 Release)이 동일한 빌드 폴더를 사용하면 다시 "설정이 적용되지 않는 이유"의 세계로 돌아갑니다. Presets의 목적 중 하나는 별도의 빌드 디렉터리를 강제해 캐시가 서로 덮어쓰지 않도록 하는 것입니다. 최소한 build-debug/와 build-release/는 분리하세요.
실수 2: build preset이 새로운 cacheVariables를 자동으로 적용할 것이라 기대.
Build preset은 어떤 것도 "설정"하지 않습니다. 단지 이미 존재하는 빌드 디렉터리에서 빌드를 실행할 뿐입니다. 만약 cacheVariables를 변경했지만 configure 단계를 다시 실행하지 않았다면, 빌드는 이전의 CMakeCache.txt로 계속 동작합니다. 마법은 없습니다 — 상태일 뿐입니다.
실수 3: 프리셋에 하드코딩된 절대 경로 사용.
절대 경로는 프리셋을 특정 컴퓨터에 묶어 버립니다. 오늘은 빨리 지정했다고 좋아할지 몰라도, 내일은 다른 머신에서, CI에서, 심지어 폴더를 옮긴 뒤 자신에게서도 프로젝트를 빌드하지 못하게 됩니다. ${sourceDir}와 같은 변수를 사용하여 프리셋을 이식 가능하게 만드세요.
실수 4: preset으로 런타임 입력을 대체하려는 시도.
Preset은 빌드 관련입니다. "사용자 이름"이나 "입력 파일 경로" 같은 런타임 파라미터를 preset을 통해 전달하려고 하면 곤란해집니다. 기술적으로는 매크로를 정의해 코드를 다시 빌드하도록 만들 수 있지만, 그러면 값을 하나 바꾸기 위해 재빌드하는 프로젝트가 되어버립니다. Presets는 어떻게 컴파일할지에 관한 것이지, 프로그램을 어떻게 사용할지에 관한 것이 아닙니다.
실수 5: CMAKE_BUILD_TYPE가 항상 기대한 대로 동작한다고 무작정 믿는 것.
일부 환경에서는 CMake가 생성기로 인해 Debug/Release를 CMAKE_BUILD_TYPE로 선택하지 않습니다(이건 생성기마다 다릅니다). 실무 규칙으로 기억해야 할 것은: 만약 "Debug" preset이 이상하게 Release와 같은 동작을 보인다면, 사용하는 생성기가 무엇인지, 그리고 CMAKE_BUILD_TYPE가 실제로 반영되는지 확인하라는 것입니다.
GO TO FULL VERSION