본문으로 건너뛰기
Previous
Next
C++ CMake 완벽 가이드 | 크로스 플랫폼 빌드·최신 CMake 3.28+ 기능·프리셋·모듈

C++ CMake 완벽 가이드 | 크로스 플랫폼 빌드·최신 CMake 3.28+ 기능·프리셋·모듈

C++ CMake 완벽 가이드 | 크로스 플랫폼 빌드·최신 CMake 3.28+ 기능·프리셋·모듈

이 글의 핵심

C++ CMake : 크로스 플랫폼 빌드·최신 CMake 3.28+ 기능·프리셋·모듈. 빌드 관리의 어려움·최소 CMakeLists.txt.

들어가며: “플랫폼마다 빌드 설정이 달라서 관리가 힘들어요”

실무에서 겪는 빌드 문제들

C++ 프로젝트를 여러 플랫폼에서 개발하다 보면 이런 문제를 겪습니다:

  • 플랫폼별 빌드 파일 — Windows는 .vcxproj, Linux는 Makefile, macOS는 Xcode 프로젝트를 따로 관리
  • 팀원마다 다른 환경 — 누구는 Visual Studio, 누구는 CLion, 누구는 VS Code
  • 의존성 지옥 — Boost, OpenSSL 등 외부 라이브러리 경로가 환경마다 다름
  • CI/CD 설정 중복 — GitHub Actions, GitLab CI에서 각각 빌드 스크립트 작성
  • 빌드 시간 증가 — 프로젝트가 커질수록 빌드가 느려짐 CMake로 해결:
# ❌ 기존 방법 (플랫폼별 관리)
# Windows: msbuild MyProject.vcxproj
# Linux: make -f Makefile.linux
# macOS: xcodebuild -project MyProject.xcodeproj
# ✅ CMake 방법 (한 번 작성, 어디서나 빌드)
mkdir build && cd build
cmake ..
cmake --build .
flowchart LR
    subgraph input[입력]
        cmake[CMakeLists.txt]
        preset["CMakePresets.json (3.19+)"]
    end
    subgraph cmake_tool[CMake]
        gen[Generator]
    end
    subgraph output[출력]
        make["Makefile (Linux)"]
        vs["Visual Studio (Windows)"]
        xcode["Xcode (macOS)"]
        ninja["Ninja (모든 플랫폼)"]
    end
    cmake --> gen
    preset --> gen
    gen --> make
    gen --> vs
    gen --> xcode
    gen --> ninja

CMake의 핵심 개념

빌드 시스템 생성기 (Generator) - CMake의 핵심 역할:

CMake는 직접 빌드하지 않습니다!
→ 플랫폼별 빌드 시스템 파일을 생성하는 메타 빌드 시스템

흐름:
1. CMakeLists.txt 작성 (플랫폼 독립적)
2. cmake 명령 실행 → Generator 선택
3. Generator가 플랫폼 특화 빌드 파일 생성
4. 실제 빌드 도구 (make, msbuild, ninja 등)가 빌드

예시:
cmake -G "Unix Makefiles" ..
→ Makefile 생성 (Linux/macOS)

cmake -G "Visual Studio 17 2022" ..
→ .sln, .vcxproj 생성 (Windows)

cmake -G "Xcode" ..
→ .xcodeproj 생성 (macOS)

cmake -G "Ninja" ..
→ build.ninja 생성 (모든 플랫폼)

Generator 내부 동작:

cmake 명령 실행 시 (Configure + Generate):

1. Configure 단계:
   CMakeLists.txt 파싱

   변수 치환: ${PROJECT_NAME}, ${CMAKE_CXX_FLAGS} 등

   타겟 정의 수집:
   - add_executable(myapp main.cpp)
   - add_library(mylib mylib.cpp)

   의존성 그래프 생성:
   myapp → mylib → pthread

   컴파일러 감지:
   - Linux: g++ (GCC), clang++
   - Windows: cl.exe (MSVC)
   - macOS: clang++ (Xcode)

   플랫폼 특화 설정:
   - Windows: /MD, /O2, /W4
   - Linux: -O2, -Wall, -fPIC

   CMakeCache.txt 생성:
   - 설정 캐싱 (재실행 시 빠름)
   - CMAKE_CXX_COMPILER=/usr/bin/g++
   - CMAKE_BUILD_TYPE=Release
   - 사용자가 직접 수정 가능

2. Generate 단계:
   타겟 의존성 그래프

   Generator 실행 (예: Unix Makefiles)

   Makefile 생성:
   
   all: myapp
   
   myapp: main.o mylib.a
       g++ -o myapp main.o -L. -lmylib -lpthread
   
   main.o: main.cpp
       g++ -c main.cpp -I./include -std=c++20 -O2
   
   mylib.a: mylib.o
       ar rcs mylib.a mylib.o
   
   mylib.o: mylib.cpp
       g++ -c mylib.cpp -I./include -std=c++20 -O2 -fPIC
   
   clean:
       rm -f *.o myapp mylib.a
   
   install:
       cp myapp /usr/local/bin/

생성된 파일 구조 (build 디렉토리):
build/
├── CMakeCache.txt       ← 설정 캐시
├── CMakeFiles/          ← 내부 메타데이터
│   ├── myapp.dir/       ← myapp 타겟 빌드 정보
│   │   ├── flags.make   ← 컴파일 플래그
│   │   ├── build.make   ← 빌드 규칙
│   │   └── link.txt     ← 링크 명령
│   └── mylib.dir/
├── Makefile             ← 생성된 빌드 파일
└── cmake_install.cmake  ← 설치 스크립트

Generator 종류와 특징:

Unix Makefiles (Linux/macOS 기본):
- 순차적 빌드 (단일 CPU)
- 병렬: make -j$(nproc)
- 디버깅 용이 (Makefile 직접 확인 가능)

Ninja (권장):
- 병렬 빌드 기본
- 빌드 속도 최적화 (증분 빌드 우수)
- 모든 플랫폼 지원
- 설치: apt install ninja-build
- 사용: cmake -G Ninja ..

Visual Studio (Windows):
- .sln (솔루션) + .vcxproj (프로젝트) 생성
- IDE 통합 (IntelliSense, 디버거)
- 멀티 구성 (Debug/Release 한 번에)
- cmake -G "Visual Studio 17 2022" -A x64 ..

Xcode (macOS):
- .xcodeproj 생성
- IDE 통합
- 멀티 구성

Generator 선택 기준:
개발: IDE Generator (Visual Studio, Xcode)
CI/CD: Ninja (빠름)
디버깅: Unix Makefiles (직접 확인 가능)

타겟 기반 빌드:

Modern CMake (3.0+) 철학:
타겟 = 빌드 결과물 + 속성 (의존성, 플래그, 경로)

타겟 정의:
add_executable(myapp main.cpp)
→ myapp 타겟 생성 (실행 파일)

add_library(mylib mylib.cpp)
→ mylib 타겟 생성 (라이브러리)

타겟 속성 설정 (Modern CMake):
target_include_directories(mylib PUBLIC include/)
→ mylib 사용 시 include/ 경로 자동 추가

target_link_libraries(myapp PRIVATE mylib)
→ myapp이 mylib에 의존

target_compile_features(myapp PRIVATE cxx_std_20)
→ C++20 기능 요구

의존성 전파:
myapp → mylib (PRIVATE)
→ myapp 빌드 시에만 mylib 사용

mylib → pthread (PUBLIC)
→ mylib 사용하는 모든 타겟이 pthread도 링크

interface_lib → header_only (INTERFACE)
→ 헤더 전용 라이브러리

타겟 의존성 그래프:

예시:
add_executable(myapp main.cpp)
add_library(mylib mylib.cpp)
add_library(utils utils.cpp)

target_link_libraries(myapp PRIVATE mylib)
target_link_libraries(mylib PUBLIC utils)
target_link_libraries(utils PUBLIC pthread)

의존성 그래프:
       myapp
         ↓ (PRIVATE)
       mylib
         ↓ (PUBLIC)
       utils
         ↓ (PUBLIC)
      pthread

빌드 순서 (CMake 자동 결정):
1. pthread (시스템 라이브러리, 이미 존재)
2. utils.o → libutils.a
3. mylib.o → libmylib.a (utils 링크)
4. main.o → myapp (mylib 링크)

링크 명령:
g++ -o myapp main.o -lmylib -lutils -lpthread

PRIVATE vs PUBLIC vs INTERFACE:
PRIVATE:
  - 타겟 내부에서만 사용
  - 의존성 전파 안 됨
  - 예: 구현 세부사항

PUBLIC:
  - 타겟 + 의존하는 타겟 모두 사용
  - 의존성 전파
  - 예: 헤더에 노출된 API

INTERFACE:
  - 타겟 자체는 사용 안 함
  - 의존하는 타겟만 사용
  - 예: 헤더 전용 라이브러리
  • 빌드 시스템 생성기: CMake 자체는 빌드를 하지 않고, 플랫폼별 빌드 시스템(Makefile, .sln 등)을 생성합니다.
  • 타겟 기반: add_executable, add_library로 빌드 타겟을 정의하고, target_link_libraries, target_include_directories로 의존성을 명시합니다.
  • 프리셋 (3.19+): CMakePresets.json으로 빌드 설정을 공유하고 재사용합니다.
  • C++20 모듈 (3.28+): C++20 모듈을 네이티브로 지원합니다. CMake는 C++에서 사실상 표준에 가까운 빌드 생성기입니다. Rust 입문 글의 CargoNode.js의 npm·모듈 해석처럼 빌드와 패키지가 한 도구에 묶여 있지는 않지만, 크로스 플랫폼 빌드와 vcpkg·Conan 연동으로 비슷한 목표를 달성합니다. Go 모듈·go.sum의 의존성 고정, Python의 pip·uv·Poetry의 락·가상환경 개념과 나란히 보면 의존성 관리 차이가 선명해집니다. 언어별 빌드 철학·도구 스택 비교는 C++ 빌드 시스템 완전 비교를 참고하세요. 병렬 빌드·캐시(ccache 등)는 Makefile 가이드와도 이어집니다. 목표:
  • CMake 기초 (최소 설정, 타겟, 의존성)
  • 최신 CMake 3.28+ 기능 (프리셋, C++20 모듈, FetchContent 개선)
  • 실전 패턴 (멀티 플랫폼, CI/CD, 의존성 관리)
  • 성능 최적화 (병렬 빌드, ccache, PCH)
  • 자주 하는 실수와 해결법
  • 프로덕션 패턴 요구 환경: CMake 3.20 이상 (최신 기능은 3.28+ 권장)

1. 문제 시나리오: 빌드 관리의 어려움

시나리오 1: “팀원마다 빌드 설정이 달라요”

상황: Windows 개발자는 Visual Studio, Linux 개발자는 Makefile, macOS 개발자는 Xcode 사용
문제: 각자 다른 빌드 파일을 관리하다 보니 설정이 동기화되지 않음
결과: "내 환경에서는 빌드되는데요?" 문제 발생
→ CMake + CMakePresets.json으로 통일된 빌드 설정 공유

주의사항: “내 PC에서는 된다”는 문제는 보통 생성기·플래그·경로가 커밋되지 않아서 생기므로, 재현 가능한 한 줄짜리 프리셋이 더 중요합니다.

시나리오 2: “외부 라이브러리 경로가 환경마다 달라요”

상황: Boost가 Windows는 C:\boost, Linux는 /usr/local/boost, macOS는 /opt/homebrew/boost
문제: 하드코딩된 경로로 인해 다른 환경에서 빌드 실패
결과: 팀원마다 CMakeLists.txt를 수정해야 함
→ find_package + CMAKE_PREFIX_PATH로 자동 탐지

시나리오 3: “빌드가 너무 느려요”

상황: 프로젝트가 커지면서 풀 빌드에 30분 소요
문제: 매번 모든 파일을 재컴파일, 헤더 파싱 반복
결과: 개발 생산성 저하
→ ccache, PCH, Ninja, 병렬 빌드로 최적화

시나리오 4: “CI/CD에서 빌드 설정이 복잡해요”

상황: GitHub Actions, GitLab CI, Jenkins에서 각각 다른 빌드 스크립트
문제: 빌드 설정 변경 시 모든 CI 파일 수정 필요
결과: 유지보수 부담 증가
→ CMakePresets.json으로 CI 설정 통일
flowchart TB
    subgraph Problems[빌드 관리 문제]
        P1[플랫폼별 빌드 파일]
        P2[의존성 경로 불일치]
        P3[느린 빌드]
        P4[CI/CD 설정 중복]
    end
    subgraph Solutions[CMake 해결책]
        S1[CMakeLists.txt 통일]
        S2[find_package 자동 탐지]
        S3[ccache + Ninja + PCH]
        S4[CMakePresets.json]
    end
    P1 --> S1
    P2 --> S2
    P3 --> S3
    P4 --> S4

2. 최소 CMakeLists.txt

Hello World 빌드

# CMakeLists.txt - CMake 설정 파일
# CMake 최소 버전 지정 (3.20 이상 필요)
cmake_minimum_required(VERSION 3.20)
# 프로젝트 정보 설정 (이름, 버전, 언어)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
# C++ 표준 설정
set(CMAKE_CXX_STANDARD 20)              # C++20 사용
set(CMAKE_CXX_STANDARD_REQUIRED ON)     # C++20 미지원 시 에러
set(CMAKE_CXX_EXTENSIONS OFF)           # GNU 확장 등 비활성화 (이식성 향상)
# 실행 파일 타겟 생성
# myapp: 타겟 이름 (빌드 결과물 이름)
# main.cpp: 소스 파일
add_executable(myapp main.cpp)
// main.cpp
#include <iostream>
int main() {
    std::cout << "Hello, CMake!" << std::endl;
    return 0;
}
# 빌드 디렉토리 생성 (out-of-source build)
mkdir build
cd build

# CMake 실행 (빌드 시스템 생성)
cmake ..
# 내부 동작:
# 1. CMakeLists.txt 파싱
# 2. Generator 선택 (플랫폼별 기본값)
#    - Linux: Unix Makefiles
#    - Windows: Visual Studio 2022
#    - macOS: Unix Makefiles
# 3. 빌드 파일 생성
#    - Makefile (Linux)
#    - .sln + .vcxproj (Windows)
#    - .xcodeproj (macOS with Xcode)
# 4. CMakeCache.txt 생성 (설정 캐시)

# 빌드 (생성된 빌드 시스템으로 컴파일)
cmake --build .
# 내부 동작:
# - Linux: make를 호출
# - Windows: msbuild를 호출
# - Ninja: ninja를 호출

# 실행
./myapp  # Linux/macOS
# myapp.exe  # Windows

CMake 빌드 프로세스 상세:

단계별 흐름:

1. Configuration Phase (cmake ..)
   ┌─────────────────────────────────────┐
   │ CMakeLists.txt 파싱                  │
   │ - project() 실행 → 프로젝트 메타데이터  │
   │ - add_executable() → 타겟 등록       │
   │ - target_link_libraries() → 의존성   │
   ├─────────────────────────────────────┤
   │ Generator 선택                       │
   │ - 명시: cmake -G "Ninja" ..         │
   │ - 자동: 플랫폼 기본값                 │
   ├─────────────────────────────────────┤
   │ Compiler Detection                  │
   │ - CMAKE_CXX_COMPILER 탐지            │
   │ - 컴파일러 feature 테스트             │
   │ - ABI 확인 (32/64bit)               │
   ├─────────────────────────────────────┤
   │ Cache 생성: CMakeCache.txt          │
   │ - 모든 변수 저장                     │
   │ - 다음 실행 시 재사용                │
   └─────────────────────────────────────┘
   
2. Generation Phase
   ┌─────────────────────────────────────┐
   │ 빌드 파일 생성                        │
   │ Unix Makefiles:                     │
   │   - Makefile                        │
   │   - cmake_install.cmake             │
   │ Visual Studio:                      │
   │   - MyProject.sln                   │
   │   - myapp.vcxproj                   │
   │ Ninja:                              │
   │   - build.ninja                     │
   ├─────────────────────────────────────┤
   │ compile_commands.json (옵션)        │
   │ - CMAKE_EXPORT_COMPILE_COMMANDS=ON  │
   │ - LSP(clangd 등) 지원               │
   └─────────────────────────────────────┘

3. Build Phase (cmake --build .)
   ┌─────────────────────────────────────┐
   │ 생성된 빌드 시스템 실행               │
   │ - make (Unix Makefiles)             │
   │ - msbuild (Visual Studio)           │
   │ - ninja (Ninja)                     │
   ├─────────────────────────────────────┤
   │ 의존성 그래프 실행                   │
   │ - 헤더 변경 → 소스 재컴파일           │
   │ - 라이브러리 변경 → 실행파일 재링크    │
   │ - 병렬 빌드 (make -j, ninja 자동)    │
   └─────────────────────────────────────┘

CMakeCache.txt의 역할:
- 첫 실행 시 모든 변수를 저장
- 다음 실행 시 캐시를 읽어 빠르게 구성
- 수동 편집 가능 (ccmake, cmake-gui도 이용 가능)
- 삭제 시 완전히 새로 구성 (권장: rm -rf build/)

Generator 종류:
- Unix Makefiles: 전통적, 느림
- Ninja: 빠른 빌드, 병렬 처리 우수 (권장!)
- Visual Studio: Windows IDE 통합
- Xcode: macOS IDE 통합
- Ninja Multi-Config: 여러 빌드 타입을 한 빌드 디렉토리에

핵심 명령어 설명

명령어설명
cmake_minimum_required(VERSION 3.20)최소 CMake 버전 지정
project(MyProject VERSION 1.0.0)프로젝트 이름과 버전 설정
set(CMAKE_CXX_STANDARD 20)C++20 표준 사용
set(CMAKE_CXX_EXTENSIONS OFF)GNU 확장 등 비활성화 (이식성 향상)
add_executable(myapp main.cpp)실행 파일 타겟 생성
cmake ..부모 디렉토리의 CMakeLists.txt로 빌드 시스템 생성
cmake --build .플랫폼 독립적 빌드 명령

CMake 버전별 주요 기능

버전주요 기능
3.19CMakePresets.json 도입
3.20C++23 지원
3.23FILE_SET 도입 (헤더 관리 개선)
3.25LINUX 변수 추가
3.26SYSTEM 속성 개선
3.28C++20 모듈 네이티브 지원

3. 타겟: 실행 파일과 라이브러리

실행 파일

# 단일 소스
add_executable(myapp main.cpp)
# 여러 소스
add_executable(myapp
    src/main.cpp
    src/utils.cpp
    src/config.cpp
)
# 변수 사용
set(SOURCES
    src/main.cpp
    src/utils.cpp
)
add_executable(myapp ${SOURCES})

라이브러리

# 정적 라이브러리 (.a, .lib)
add_library(mylib STATIC
    src/lib.cpp
    src/helper.cpp
)
# 동적 라이브러리 (.so, .dll)
add_library(mylib SHARED
    src/lib.cpp
    src/helper.cpp
)
# 헤더 전용 라이브러리
add_library(mylib INTERFACE)
target_include_directories(mylib INTERFACE include)
# 실행 파일에 링크
add_executable(myapp src/main.cpp)
target_link_libraries(myapp PRIVATE mylib)

주의사항: SHARED는 런타임에 .so/.dll 경로가 잡혀야 하므로 설치·배포 규칙(RUNTIME DESTINATION 등)을 함께 설계하세요.

PUBLIC vs PRIVATE vs INTERFACE

flowchart TB
    subgraph LibA[라이브러리 A]
        A_PRIVATE["PRIVATE 헤더br/src/internal/"]
        A_PUBLIC["PUBLIC 헤더br/include/"]
    end
    
    subgraph LibB[라이브러리 B]
        B_CODE[B의 코드]
    end
    
    subgraph App[애플리케이션]
        APP_CODE[App 코드]
    end
    
    A_PRIVATE -.->|사용 가능| LibA
    A_PUBLIC -->|사용 가능| LibA
    A_PUBLIC -->|사용 가능| LibB
    A_PUBLIC -->|사용 가능| App
    
    LibB -->|링크| LibA
    App -->|링크| LibB
    
    style A_PRIVATE fill:#FFB6C1
    style A_PUBLIC fill:#90EE90
# PRIVATE: 타겟 자신만 사용
target_include_directories(mylib PRIVATE src/internal)
# PUBLIC: 타겟 + 이 타겟을 링크하는 타겟도 사용
target_include_directories(mylib PUBLIC include)
# INTERFACE: 이 타겟을 링크하는 타겟만 사용 (헤더 전용 라이브러리)
target_include_directories(mylib INTERFACE include)

실무 예시: mylib가 내부적으로 src/internal/impl.h를 쓰면 PRIVATE로 추가하고, 외부에 노출하는 include/mylib/api.hPUBLIC으로 추가합니다. myappmylib를 링크하면 PUBLIC 헤더만 접근할 수 있습니다.

3-1. CMake 내부 심화: 구성·생성·빌드, 전파, 제너레이터, 모던·프로덕션

이 절에서는 위에서 요약한 Generator, PUBLIC/PRIVATE/INTERFACE를 넘어, CMake가 실제로 언제 무엇을 평가하는지(구성·생성·빌드), 사용 요구사항(usage requirements) 이 타겟 그래프를 따라 어떻게 전파되는지, 제너레이터 표현식으로 어떤 제약을 풀 수 있는지, 그리고 저장소·CI·배포 관점의 프로덕션 패턴을 정리합니다. CMakeLists.txt는 “빌드 스크립트”가 아니라 구성 시점에 실행되는 DSL이라는 점을 염두에 두면 이후 디버깅이 수월해집니다.

구성(Configure)·생성(Generate)·빌드(Build) 단계

CMake는 한 번의 cmake -S … -B … 호출 안에서 대략 다음 순서로 동작합니다.

  1. Configure(구성): 최상위 CMakeLists.txt부터 add_subdirectory·FetchContent 등을 따라가며 CMake 스크립트를 실행합니다. 이때 project(), add_library(), target_link_libraries() 등이 호출되고, 타겟과 속성, 의존성 그래프가 메모리 상에 구축됩니다. CMakeCache.txt에 캐시 변수가 기록되고, 컴파일러 검사(try_compile 등)가 이 단계에서 수행될 수 있습니다.
  2. Generate(생성): 선택한 Generator(Ninja, Visual Studio, Xcode, Makefiles 등)에 맞게 실제 빌드 도구가 읽을 파일(build.ninja, .vcxproj, Makefile 등)을 씁니다. 이 단계에서 대부분의 제너레이터 표현식이 풀려, 설정별(Debug/Release 등) 컴파일·링크 줄이 확정됩니다.
  3. Build(빌드): cmake --build가 호출되면, 생성된 Ninja/Make/MSBuild가 컴파일러·링커를 실행합니다. add_custom_command빌드 시점 규칙, file(GENERATE)로 만든 파일 등은 이 단계와 맞물립니다.

중요한 구분: if(UNIX) 같은 조건은 구성 시점에 평가됩니다. 반면 $<CONFIG:Debug> 같은 제너레이터 표현식은 구성 시점의 if() 안에서 그대로 해석되지 않습니다(아래 제너레이터 절 참고). “왜 CMakeLists에서는 맞는데 생성된 빌드 파일은 다르지?” 같은 혼란의 상당수가 평가 시점 착오에서 옵니다.

싱글 구성 vs 멀티 구성 Generator: Ninja/Make 계열은 보통 한 빌드 트리에 하나의 CMAKE_BUILD_TYPE 을 가정하는 경우가 많고(싱글 구성), Visual Studio·Xcode는 한 트리에 Debug/Release 등 여러 구성을 동시에 두는 멀티 구성입니다. 그래서 $<CONFIG:…>·OUTPUT_NAME·디버그 postfix 등은 멀티 구성에서 특히 중요합니다.

cmake --install: 빌드 산출물을 CMAKE_INSTALL_PREFIX 아래에 배치하는 단계로, 설치 규칙(install(TARGETS …), install(EXPORT …))이 여기서 소비됩니다. 라이브러리를 다른 프로젝트의 find_package로 제공하려면 이 경로와 exported target 설계가 핵심입니다.

타겟 속성 전파와 PRIVATE / PUBLIC / INTERFACE

Modern CMake에서 target_link_libraries(A PRIVATE|PUBLIC|INTERFACE B)는 단순히 “링크 순서”가 아니라 사용 요구사항(usage requirements) 의 전파 규칙을 정합니다.

  • PRIVATE: A자기 자신을 빌드할 때만 필요한 의존성입니다. B로부터 오는 include 경로·compile definition·대부분의 링크 정보A의 구현 단위에만 적용되고, A에 링크하는 상위 타겟으로는 넘어가지 않습니다.
  • PUBLIC: A를 사용하는 쪽( A의 헤더/API를 쓰는 클라이언트)도 동일한 요구사항이 필요할 때 씁니다. 예: A의 공개 헤더가 B의 헤더를 포함한다면, B의 include와 링크 요구사항이 전이(transitive) 되어야 합니다.
  • INTERFACE: A 자체는 빌드 산출물이 없거나(헤더 전용 인터페이스 라이브러리), 요구사항만 전달하는 역할일 때 씁니다. INTERFACE로 연결된 항목은 의존하는 타겟에 전파됩니다.

전파되는 것의 예: INTERFACE_INCLUDE_DIRECTORIES, INTERFACE_COMPILE_DEFINITIONS, INTERFACE_LINK_LIBRARIES 등은 타겟 속성으로 저장되며, 링크 그래프를 따라 누적됩니다. 직접 링크한 타겟뿐 아니라 전이 의존성에 의해 컴파일·링크 줄에 반영될 수 있으므로, “내가 target_link_libraries에 쓰지 않았는데 플래그가 붙는다”는 현상은 종종 PUBLIC 체인 때문입니다.

PRIVATE로 링크해야 하는 경우: 의존성이 구현 디테일이고 공개 헤더에 노출되지 않을 때입니다. 반대로 API에 노출되면 PUBLIC이 맞고, 그렇지 않으면 캡슐화가 깨져 불필요한 재컴파일·ABI 노출이 커집니다.

INTERFACE_LINK_LIBRARIES의 LINK_ONLY (CMake 3.24+): 어떤 라이브러리는 링크할 필요는 있지만 include 전파는 원하지 않는 경우(예: 특정 정적 라이브러리를 끝에 한 번만 링크)에 PRIVATE "$<LINK_ONLY:…>" 패턴이 쓰입니다. 서드파티 CMake가 이를 요구할 때가 있어, 오류 메시지에 LINK_ONLY가 나오면 전파 의도를 확인하면 됩니다.

제너레이터 표현식(Generator Expressions)

제너레이터 표현식은 $<…> 형태로, 구성 시점의 if()와는 다른 규칙으로 평가됩니다. 대표적으로:

  • 구성·타겟별: $<CONFIG:Debug>, $<PLATFORM_ID:Windows>, $<CXX_COMPILER_ID:GNU> 등 — 멀티 구성·크로스 컴파일에서 조건 분기를 안전하게 만듭니다.
  • 타겟 정보: $<TARGET_FILE:tgt>, $<TARGET_PROPERTY:tgt,PROP> — 설치 스크립트·add_custom_command·file(GENERATE)에서 경로를 타겟 기준으로 얻을 때 유용합니다.
  • 논리·조건: $<IF:cond,then,else>, $<AND:…>, $<OR:…> — 복잡한 플래그 조합을 한 속성에 담을 때 사용합니다.

흔한 실수: if($<CONFIG:Debug>)처럼 제너레이터 표현식을 if()에 넣으면 구성 시점에는 $<CONFIG:…>가 아직 “문자 그대로”로 다뤄져 의도와 다르게 동작할 수 있습니다. 조건 분기가 구성 시점이면 일반 if(CMAKE_BUILD_TYPE …) 또는 generator expressions가 아닌 캐시 변수를 쓰고, 생성 시점이면 target_compile_options 등에 제너레이터 표현식을 그대로 넣는 패턴이 맞습니다.

디버깅 팁: cmake --build … --verbose로 실제 컴파일/링크 명령을 확인하고, 생성된 build.ninjacompile_commands.json에서 어떤 플래그가 어느 타겟에 붙었는지를 역추적합니다.

예시: 멀티 구성·컴파일러별로 플래그를 나누되, if() 대신 타겟 속성에 제너레이터 표현식을 둡니다.

add_library(core STATIC src/core.cpp)
target_compile_options(core PRIVATE
  $<$<CXX_COMPILER_ID:MSVC>:/W4>
  $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra>
)
target_compile_definitions(core PRIVATE
  $<$<CONFIG:Debug>:CORE_DEBUG=1>
)

모던 CMake 패턴: “변수가 아니라 타겟”

과거 스타일의 include_directories(${FOO_INCLUDE_DIRS}), link_libraries(…), 전역 add_definitions(-DUSE_X)전 타겟에 영향을 주고 캐시·서브프로젝트와 충돌하기 쉽습니다. 모던 CMake의 기본기는 다음과 같습니다.

  • 타겟 단위로 한정: target_include_directories, target_compile_definitions, target_compile_options, target_link_libraries영향 범위를 타겟에 묶습니다.
  • 인터페이스 라이브러리로 요구사항 묶기: 헤더 전용·플래그 번들·외부 패키지 래핑을 add_library(x INTERFACE) + target_include_directories(x INTERFACE …)로 표현하면, 소비자는 target_link_libraries(app PRIVATE x) 한 줄로 일관된 usage requirements를 가져갑니다.
  • 표준·경고도 타겟별: 전역 CMAKE_CXX_FLAGS에 손대기보다 target_compile_featurestarget_compile_options필요한 타겟만 C++ 표준·경고 정책을 올립니다.
  • find_package 결과를 Imported Target으로: Foo::foo 같은 타겟이 있으면 변수 나열 대신 타겟 링크가 우선입니다. 없는 레거시 Find 모듈만 변수 경로로 연결합니다.

이렇게 해 두면 서브디렉터리 추가 순서에 덜 민감하고, 정적·동적 라이브러리 전환, 테스트 타겟만 sanitizer 적용 같은 시나리오가 단순해집니다.

프로덕션 환경에서의 CMake 패턴(보강)

로컬에서만 돌아가는 설정과, 팀·CI·배포까지 고려한 설정은 다릅니다. 자주 쓰는 보강 패턴은 다음과 같습니다.

  • 재현 가능한 구성: CMakePresets.json으로 컴파일러·캐시·generator를 고정하고, CI에서는 동일 프리셋을 사용합니다. 개인 실험은 CMakeUserPresets.json(저장소에서 제외)로 분리합니다.
  • 설치·내보내기: install(TARGETS … EXPORT), install(EXPORT …), export(EXPORT …)네임스페이스 타겟(MyProj::core) 을 제공하면, 소비자는 find_package(MyProj)target_link_libraries(app PRIVATE MyProj::core)만으로 끝낼 수 있습니다. 버전 호환은 write_basic_package_version_file과 함께 쓰는 경우가 많습니다.
  • 의존성 공급 방식 명시: 동일 라이브러리에 대해 시스템 패키지 / vcpkg / Conan / FetchContent를 혼용하면 중복 심볼·헤더 버전 충돌이 납니다. 팀 규약으로 “CI와 릴리스는 vcpkg toolchain”처럼 한 경로를 기본으로 정하는 것이 안전합니다.
  • FetchContent: 빌드 편의는 좋지만 네트워크·버전 고정·오프라인 빌드 이슈가 있습니다. 태그·GIT_SHALLOW·OVERRIDE_FIND_PACKAGE(프로젝트 정책에 맞을 때) 등으로 재현성을 확보합니다.
  • 테스트: enable_testing() + add_test() + CTest, 프리셋의 testPresets동일한 빌드 산출물에 대해 CI에서 반복 실행합니다.
  • 캐시 무효화: “이상하면 빌드 디렉터리 삭제”가 여전히 유효합니다. CMake 3.24+의 cmake --fresh는 캐시를 비우고 재구성할 때 유용합니다.

위 패턴들은 프로덕션 패턴 절의 예제와 겹치지 않게 동작 원리·평가 시점 관점에서 보완한 것입니다. 실무에서는 cmake -B build -S . --trace-expand로 의심 구간만 추적하거나, Ninja를 쓰면 build.ninja에서 최종 플래그를 직접 확인하는 것이 가장 빠른 검증입니다.


4. CMake 프리셋 (3.19+)

프리셋이란?

문제: 팀원마다 다른 CMake 명령어를 사용합니다.

# 개발자 A
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=g++ ..
# 개발자 B
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang++ -DENABLE_TESTS=ON ..
# CI/CD
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=......

주의사항:cmake 한 줄을 문서에 복붙하는 방식은 깨지기 쉬우니, 아래 프리셋으로 옮기는 것이 목적입니다. 해결: CMakePresets.json으로 빌드 설정을 공유합니다.

CMakePresets.json 기본 구조

{
  "version": 6,
  "cmakeMinimumRequired": {
    "major": 3,
    "minor": 25,
    "patch": 0
  },
  "configurePresets": [
    {
      "name": "default",
      "hidden": true,
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build/${presetName}",
      "cacheVariables": {
        "CMAKE_CXX_STANDARD": "20",
        "CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
      }
    },
    {
      "name": "debug",
      "displayName": "Debug Build",
      "description": "Debug build with sanitizers",
      "inherits": "default",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Debug",
        "CMAKE_CXX_FLAGS": "-fsanitize=address,undefined"
      }
    },
    {
      "name": "release",
      "displayName": "Release Build",
      "description": "Optimized release build",
      "inherits": "default",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Release"
      }
    },
    {
      "name": "windows-msvc",
      "displayName": "Windows MSVC",
      "inherits": "default",
      "generator": "Visual Studio 17 2022",
      "condition": {
        "type": "equals",
        "lhs": "${hostSystemName}",
        "rhs": "Windows"
      }
    }
  ],
  "buildPresets": [
    {
      "name": "debug",
      "configurePreset": "debug",
      "configuration": "Debug"
    },
    {
      "name": "release",
      "configurePreset": "release",
      "configuration": "Release",
      "jobs": 8
    }
  ],
  "testPresets": [
    {
      "name": "default",
      "configurePreset": "debug",
      "output": {
        "outputOnFailure": true
      }
    }
  ]
}

프리셋 사용법

# 사용 가능한 프리셋 확인
cmake --list-presets
# 프리셋으로 구성
cmake --preset=debug
# 프리셋으로 빌드
cmake --build --preset=debug
# 프리셋으로 테스트
ctest --preset=default

실전 프리셋 예제: 멀티 플랫폼

{
  "version": 6,
  "configurePresets": [
    {
      "name": "linux-gcc",
      "displayName": "Linux GCC",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build/linux-gcc",
      "cacheVariables": {
        "CMAKE_CXX_COMPILER": "g++",
        "CMAKE_BUILD_TYPE": "Release"
      },
      "condition": {
        "type": "equals",
        "lhs": "${hostSystemName}",
        "rhs": "Linux"
      }
    },
    {
      "name": "linux-clang",
      "displayName": "Linux Clang",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build/linux-clang",
      "cacheVariables": {
        "CMAKE_CXX_COMPILER": "clang++",
        "CMAKE_BUILD_TYPE": "Release"
      },
      "condition": {
        "type": "equals",
        "lhs": "${hostSystemName}",
        "rhs": "Linux"
      }
    },
    {
      "name": "macos",
      "displayName": "macOS",
      "generator": "Xcode",
      "binaryDir": "${sourceDir}/build/macos",
      "condition": {
        "type": "equals",
        "lhs": "${hostSystemName}",
        "rhs": "Darwin"
      }
    },
    {
      "name": "windows",
      "displayName": "Windows MSVC",
      "generator": "Visual Studio 17 2022",
      "binaryDir": "${sourceDir}/build/windows",
      "architecture": "x64",
      "condition": {
        "type": "equals",
        "lhs": "${hostSystemName}",
        "rhs": "Windows"
      }
    }
  ]
}

주의사항: 조건 프리셋이 많아지면 이름 규칙을 팀 문서로 고정하지 않으면 “어떤 프리셋이 공식인지” 혼란이 생깁니다.

CI/CD에서 프리셋 활용

# .github/workflows/build.yml
name: Build
on: [push, pull_request]
jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        preset: [debug, release]
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Install CMake
        uses: lukka/get-cmake@latest
      
      - name: Configure
        run: cmake --preset=${{ matrix.preset }}
      
      - name: Build
        run: cmake --build --preset=${{ matrix.preset }}
      
      - name: Test
        run: ctest --preset=default

사용자별 프리셋 (CMakeUserPresets.json)

팀 공유 설정은 CMakePresets.json, 개인 설정은 CMakeUserPresets.json에 작성합니다.

// CMakeUserPresets.json (git ignore에 추가)
{
  "version": 6,
  "configurePresets": [
    {
      "name": "my-dev",
      "inherits": "debug",
      "cacheVariables": {
        "CMAKE_PREFIX_PATH": "/home/myuser/local",
        "ENABLE_EXPERIMENTAL_FEATURES": "ON"
      }
    }
  ]
}

주의사항: CMakeUserPresets.json.gitignore에 넣되, 팀원 온보딩용 예시 파일(CMakeUserPresets.json.example)은 두는 편이 좋습니다.

5. 외부 라이브러리 찾기: find_package

find_package의 내부 동작 원리:

find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)의 동작:

1. Module 모드 검색:
   CMAKE_MODULE_PATH에서 FindBoost.cmake 검색
   → /usr/share/cmake/Modules/FindBoost.cmake 발견
   → FindBoost.cmake 실행
   
2. Config 모드 검색 (Module 모드 실패 시):
   CMAKE_PREFIX_PATH, CMAKE_SYSTEM_PREFIX_PATH에서 검색:
   - <prefix>/lib/cmake/Boost/BoostConfig.cmake
   - <prefix>/lib/cmake/boost-1.70.0/BoostConfig.cmake
   - <prefix>/share/cmake/Boost/BoostConfig.cmake
   - <prefix>/Boost*/BoostConfig.cmake
   
   일반적 경로:
   - Linux: /usr, /usr/local
   - Windows: C:/Program Files/Boost
   - macOS: /usr/local, /opt/homebrew
   
3. BoostConfig.cmake 파싱:
   - 설치된 Boost 버전 확인 (1.70 이상?)
   - COMPONENTS 검색 (filesystem, system)
   - IMPORTED 타겟 생성:
     * Boost::filesystem
     * Boost::system
   - 각 타겟에 속성 설정:
     * INTERFACE_INCLUDE_DIRECTORIES (헤더 경로)
     * INTERFACE_LINK_LIBRARIES (링크 라이브러리)
     * INTERFACE_COMPILE_DEFINITIONS (전처리 매크로)
   
4. REQUIRED 옵션:
   - 실패 시 즉시 에러, 구성 중단
   - 옵션이 없으면 Boost_FOUND 변수만 설정 (조건 분기 가능)

5. target_link_libraries(myapp PRIVATE Boost::filesystem):
   - Boost::filesystem의 모든 속성을 myapp에 전파
   - 헤더 경로 자동 추가 (target_include_directories 불필요)
   - 링크 라이브러리 자동 추가 (libboost_filesystem.a)
   - 의존성 체인 처리 (Boost::filesystem → Boost::system)

find_package 두 가지 모드:

Module 모드:
- FindXxx.cmake 파일 실행 (CMake 제공 또는 프로젝트 제공)
- 변수 기반: Xxx_FOUND, Xxx_INCLUDE_DIRS, Xxx_LIBRARIES
- 오래된 방식, 일부 패키지에만 사용

Config 모드 (권장):
- XxxConfig.cmake 파일 실행 (패키지 제공)
- IMPORTED 타겟 기반: Xxx::Xxx
- 최신 방식, 속성 전파 자동
- 패키지 설치 시 함께 설치됨

Boost 예제 (Config 모드)

find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)

# 내부 동작:
# 1. BoostConfig.cmake 검색 및 실행
# 2. IMPORTED 타겟 생성:
#    - Boost::filesystem (헤더 경로, 링크 라이브러리 포함)
#    - Boost::system
# 3. Boost_FOUND = TRUE 설정

add_executable(myapp main.cpp)

# PRIVATE Boost::filesystem 링크:
# - myapp만 Boost 사용 (외부에 노출 안 함)
# - 헤더 경로 자동 추가: -I/usr/include/boost
# - 링크 라이브러리 자동 추가: -lboost_filesystem -lboost_system
target_link_libraries(myapp PRIVATE Boost::filesystem Boost::system)

OpenSSL 예제 (Module 모드)

find_package(OpenSSL REQUIRED)

# 내부 동작:
# 1. FindOpenSSL.cmake 실행 (CMake 제공)
# 2. 시스템에서 OpenSSL 검색:
#    - Linux: /usr/lib/libssl.so, /usr/include/openssl
#    - Windows: C:/Program Files/OpenSSL-Win64
#    - macOS: /usr/local/opt/openssl@3
# 3. IMPORTED 타겟 생성:
#    - OpenSSL::SSL (libssl)
#    - OpenSSL::Crypto (libcrypto)

add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE OpenSSL::SSL OpenSSL::Crypto)

주의사항: 시스템 OpenSSL과 번들 OpenSSL이 섞이면 런타임 크래시가 날 수 있으므로, 배포 시 동일 빌드를 쓰는지 확인하세요.

찾기 실패 시 처리

find_package(SomeLib)
if(SomeLib_FOUND)
    target_link_libraries(myapp PRIVATE SomeLib::SomeLib)
else()
    message(WARNING "SomeLib not found, using fallback")
endif()

pkg-config 사용

find_package(PkgConfig REQUIRED)
pkg_check_modules(CURL REQUIRED libcurl)
target_link_libraries(myapp PRIVATE ${CURL_LIBRARIES})
target_include_directories(myapp PRIVATE ${CURL_INCLUDE_DIRS})

주의사항: 변수 기반(CURL_LIBRARIES)은 현대적인 IMPORTED 타겟보다 전파·도구 지원이 약하므로, 가능하면 공식 CMake 패키지나 PkgConfig::libcurl 래퍼를 쓰는 편이 낫습니다.

FetchContent로 의존성 자동 다운로드 (3.11+)

include(FetchContent)
# GoogleTest 자동 다운로드
FetchContent_Declare(
    googletest
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG v1.14.0
)
# 사용 가능하게 만들기
FetchContent_MakeAvailable(googletest)
# 링크
add_executable(tests test.cpp)
target_link_libraries(tests PRIVATE gtest_main)

관련 글: vcpkgConan을 사용하면 더 체계적인 패키지 관리가 가능합니다. 3.24+ 개선사항: DOWNLOAD_EXTRACT_TIMESTAMP 옵션으로 타임스탬프 경고 제거

FetchContent_Declare(
    fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG 10.0.0
    DOWNLOAD_EXTRACT_TIMESTAMP ON  # 3.24+
)

주의사항: FetchContent는 네트워크 의존이므로 SBOM·보안 스캔 프로세스와 함께 쓸지 결정해야 합니다.

6. C++20 모듈 지원 (3.28+)

C++20 모듈이란?

기존 헤더 방식의 문제:

  • 매번 헤더 파싱 (느린 컴파일)
  • 매크로 오염
  • 순환 의존성 문제 C++20 모듈 장점:
  • 한 번만 컴파일 (빠른 빌드)
  • 명시적 export (깨끗한 인터페이스)
  • 순환 의존성 방지

CMake 3.28+에서 모듈 사용

cmake_minimum_required(VERSION 3.28)
project(ModuleExample CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 모듈 지원 활성화
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
# 라이브러리 (모듈 포함)
add_library(mylib)
target_sources(mylib
    PUBLIC
        FILE_SET CXX_MODULES FILES
            src/mymodule.cppm  # 모듈 인터페이스 파일
    PRIVATE
        src/impl.cpp
)
# 실행 파일
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)

모듈 파일 예제

// src/mymodule.cppm (모듈 인터페이스)
export module mymodule;
export namespace mylib {
    int add(int a, int b);
    int multiply(int a, int b);
}
// src/impl.cpp (모듈 구현)
module mymodule;
namespace mylib {
    int add(int a, int b) {
        return a + b;
    }
    
    int multiply(int a, int b) {
        return a * b;
    }
}

주의사항: 구현 파일에서 잘못된 module 선언은 링크·가시성 오류로 이어지므로, 인터페이스와 구현 쌍을 한 세트로 관리하세요.

// main.cpp (모듈 사용)
import mymodule;
#include <iostream>
int main() {
    std::cout << mylib::add(2, 3) << std::endl;       // 5
    std::cout << mylib::multiply(4, 5) << std::endl;  // 20
    return 0;
}

모듈 + 헤더 혼용

add_library(mylib)
target_sources(mylib
    PUBLIC
        FILE_SET HEADERS FILES
            include/legacy.h  # 기존 헤더
        FILE_SET CXX_MODULES FILES
            src/newmodule.cppm  # 새 모듈
    PRIVATE
        src/legacy.cpp
        src/newmodule.cpp
)
target_include_directories(mylib PUBLIC include)

컴파일러별 모듈 지원 상황 (2026년 기준)

컴파일러버전모듈 지원
GCC14+완전 지원
Clang17+완전 지원
MSVC19.36+ (VS 2022)완전 지원

7. 빌드 타입과 컴파일 옵션

빌드 타입

# 기본값 설정
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
endif()
# 빌드 타입별 플래그
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -DDEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g")
set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG")

주의사항: 멀티 설정 생성기(Visual Studio, Xcode)는 CMAKE_BUILD_TYPE 대신 --config로 선택하므로, 이 패턴과 혼동하지 마세요.

# Debug 빌드
cmake -DCMAKE_BUILD_TYPE=Debug ..
# Release 빌드
cmake -DCMAKE_BUILD_TYPE=Release ..
# 디버그 정보 포함 최적화
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..

타겟별 컴파일 옵션

add_executable(myapp main.cpp)
# 경고 활성화
target_compile_options(myapp PRIVATE
    -Wall -Wextra -Wpedantic
    $<$<CONFIG:Debug>:-Werror>  # Debug에서만 경고를 에러로
)
# 전처리 정의
target_compile_definitions(myapp PRIVATE
    APP_VERSION="1.0"
    $<$<CONFIG:Debug>:DEBUG_MODE>
)
# 헤더 경로
target_include_directories(myapp PRIVATE
    ${CMAKE_SOURCE_DIR}/include
    ${CMAKE_BINARY_DIR}/generated
)

Generator Expression

# 빌드 타입별 분기
target_compile_options(myapp PRIVATE
    $<$<CONFIG:Debug>:-O0 -g>
    $<$<CONFIG:Release>:-O3>
)
# 컴파일러별 분기
target_compile_options(myapp PRIVATE
    $<$<CXX_COMPILER_ID:GNU>:-fno-exceptions>
    $<$<CXX_COMPILER_ID:MSVC>:/EHsc>
)

주의사항: 제너레이터 표현식은 문자열이라 디버깅이 어렵고, 복잡해지면 target_compile_options를 여러 줄로 나누거나 커스텀 속성으로 감싸 가독성을 지키세요.

8. 프로젝트 구조와 서브디렉토리

권장 디렉토리 구조

project/
├── CMakeLists.txt          # 루트 CMake 설정
├── src/
│   ├── CMakeLists.txt      # 소스 빌드 설정
│   ├── main.cpp
│   └── lib/
│       ├── CMakeLists.txt
│       ├── lib.cpp
│       └── lib.h
├── include/                # 공개 헤더
│   └── mylib/
│       └── api.h
├── tests/
│   ├── CMakeLists.txt
│   └── test_main.cpp
├── external/               # 서브모듈, 외부 라이브러리
└── build/                  # 빌드 출력 (git ignore)

루트 CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# 옵션
option(BUILD_TESTS "Build tests" ON)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
# 서브디렉토리
add_subdirectory(src)
if(BUILD_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()

src/CMakeLists.txt

# 라이브러리
add_library(mylib
    lib/lib.cpp
    lib/lib.h
)
target_include_directories(mylib
    PUBLIC ${CMAKE_SOURCE_DIR}/include
    PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
)
# 실행 파일
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)

tests/CMakeLists.txt

find_package(GTest REQUIRED)
add_executable(tests
    test_main.cpp
    test_lib.cpp
)
target_link_libraries(tests PRIVATE
    mylib
    GTest::gtest
    GTest::gtest_main
)
add_test(NAME MyTests COMMAND tests)

9. 완전한 예제: 멀티 타겟 프로젝트

문제 1: 절대 경로 사용

원인: 절대 경로를 하드코딩하면 다른 환경에서 빌드가 깨집니다.

# ❌ 잘못된 사용
target_include_directories(myapp PRIVATE /usr/local/include)
# ✅ 올바른 사용: CMake 변수 사용
target_include_directories(myapp PRIVATE ${CMAKE_SOURCE_DIR}/include)

주의사항: 설치된 패키지 경로가 아니라 소스 트리 변수를 쓰는지 항상 확인하세요.

문제 2: 소스 디렉토리에서 빌드

원인: 소스 디렉토리에서 cmake .를 실행하면 빌드 파일이 소스와 섞입니다.

# ❌ 잘못된 사용
cd /project
cmake .
# ✅ 올바른 사용: out-of-source build
mkdir build
cd build
cmake ..

문제 3: 캐시 문제

증상: CMake 설정을 바꿔도 반영되지 않음. 원인: CMakeCache.txt에 이전 설정이 캐시됨.

# 해결법 1: 캐시 삭제
rm CMakeCache.txt
cmake ..
# 해결법 2: 빌드 디렉토리 재생성
cd ..
rm -rf build
mkdir build && cd build
cmake ..

주의사항: 삭제 전에 중요한 -D 플래그를 메모하거나 프리셋으로 옮기세요.

문제 4: 링크 순서

원인: 라이브러리 링크 순서가 잘못되면 undefined reference 에러가 발생합니다.

# ❌ 잘못된 순서: lib1이 lib2에 의존하는데 lib2가 뒤에
target_link_libraries(myapp PRIVATE lib2 lib1)
# ✅ 올바른 순서: 의존하는 라이브러리가 먼저
target_link_libraries(myapp PRIVATE lib1 lib2)

문제 5: PUBLIC/PRIVATE 혼동

증상: 헤더를 찾지 못하거나, 불필요한 헤더가 노출됨.

# ❌ 잘못된 사용: 내부 헤더를 PUBLIC으로
target_include_directories(mylib PUBLIC src/internal)
# ✅ 올바른 사용
target_include_directories(mylib
    PUBLIC include          # 외부에 노출
    PRIVATE src/internal    # 내부 전용
)

문제 6: find_package 실패

증상: Could not find package SomeLib. 원인: 라이브러리가 설치되지 않았거나, CMake가 찾을 수 없는 위치에 있음.

# 해결법 1: 패키지 설치
sudo apt install libboost-dev  # Linux
brew install boost             # macOS
# 해결법 2: CMAKE_PREFIX_PATH 지정
cmake -DCMAKE_PREFIX_PATH=/custom/install/path ..
# 해결법 3: 수동으로 경로 지정
cmake -DBoost_ROOT=/usr/local/boost ..

10. 자주 발생하는 문제와 해결법

패턴 1: 컴파일 경고를 에러로

function(enable_strict_warnings target)
    if(MSVC)
        target_compile_options(${target} PRIVATE /W4 /WX)
    else()
        target_compile_options(${target} PRIVATE
            -Wall -Wextra -Wpedantic -Werror
        )
    endif()
endfunction()
add_executable(myapp main.cpp)
enable_strict_warnings(myapp)

패턴 2: 빌드 타입별 후처리

# Release 빌드 시 strip으로 심볼 제거
if(CMAKE_BUILD_TYPE STREQUAL "Release")
    add_custom_command(TARGET myapp POST_BUILD
        COMMAND ${CMAKE_STRIP} $<TARGET_FILE:myapp>
    )
endif()

패턴 3: 설치 규칙

# 실행 파일 설치
install(TARGETS myapp
    RUNTIME DESTINATION bin
)
# 라이브러리 설치
install(TARGETS mylib
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
)
# 헤더 설치
install(DIRECTORY include/
    DESTINATION include
)
# 설정 파일 설치
install(FILES config.json
    DESTINATION etc
)
# 설치
cmake --build . --target install
# 또는
make install

패턴 4: 버전 정보 생성

# version.h.in
#define APP_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define APP_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define APP_VERSION_PATCH @PROJECT_VERSION_PATCH@
# CMakeLists.txt
project(MyProject VERSION 1.2.3)
configure_file(version.h.in ${CMAKE_BINARY_DIR}/version.h)
target_include_directories(myapp PRIVATE ${CMAKE_BINARY_DIR})

패턴 5: 조건부 컴파일

option(ENABLE_LOGGING "Enable logging" ON)
if(ENABLE_LOGGING)
    target_compile_definitions(myapp PRIVATE ENABLE_LOGGING)
    target_sources(myapp PRIVATE src/logger.cpp)
endif()

패턴 6: 플랫폼별 소스

if(WIN32)
    target_sources(myapp PRIVATE src/platform/windows.cpp)
elseif(APPLE)
    target_sources(myapp PRIVATE src/platform/macos.cpp)
elseif(UNIX)
    target_sources(myapp PRIVATE src/platform/linux.cpp)
endif()

문제 6: find_package 실패

증상: Could not find package SomeLib. 원인: 라이브러리가 설치되지 않았거나, CMake가 찾을 수 없는 위치에 있음.

# 해결법 1: 패키지 설치
sudo apt install libboost-dev  # Linux
brew install boost             # macOS
# 해결법 2: CMAKE_PREFIX_PATH 지정
cmake -DCMAKE_PREFIX_PATH=/custom/install/path ..
# 해결법 3: 수동으로 경로 지정
cmake -DBoost_ROOT=/usr/local/boost ..

문제 7: 프리셋이 작동하지 않음

증상: cmake --preset=debug 실행 시 “No such preset” 에러. 원인: CMake 버전이 3.19 미만이거나, JSON 문법 오류.

# 해결법 1: CMake 버전 확인
cmake --version  # 3.19 이상 필요
# 해결법 2: JSON 문법 검증
# VSCode나 온라인 JSON validator 사용
# 해결법 3: 프리셋 목록 확인
cmake --list-presets

11. 성능 최적화

병렬 빌드

# 모든 CPU 코어 사용
cmake --build . -j$(nproc)
# 또는 make
make -j$(nproc)

Ninja 사용

Ninja는 Make보다 빠른 빌드 시스템입니다.

# Ninja로 빌드 시스템 생성
cmake -G Ninja ..
# 빌드
ninja

ccache로 컴파일 캐싱

find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
    set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
endif()

프리컴파일 헤더 (PCH)

target_precompile_headers(myapp PRIVATE
    <iostream>
    <vector>
    <string>
)

Unity 빌드 (3.16+)

여러 소스 파일을 하나로 합쳐 컴파일 시간 단축.

set(CMAKE_UNITY_BUILD ON)
set(CMAKE_UNITY_BUILD_BATCH_SIZE 16)
add_executable(myapp ${SOURCES})

12. 모범 사례·베스트 프랙티스

1. 타겟 기반 접근 (Modern CMake)

# ❌ 구식 방법 (전역 변수)
include_directories(include)
link_libraries(mylib)
add_definitions(-DDEBUG)
# ✅ 현대적 방법 (타겟 기반)
target_include_directories(myapp PRIVATE include)
target_link_libraries(myapp PRIVATE mylib)
target_compile_definitions(myapp PRIVATE DEBUG)

2. Generator Expression 활용

# 빌드 타입별 플래그
target_compile_options(myapp PRIVATE
    $<$<CONFIG:Debug>:-O0 -g>
    $<$<CONFIG:Release>:-O3 -DNDEBUG>
)
# 컴파일러별 플래그
target_compile_options(myapp PRIVATE
    $<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra>
    $<$<CXX_COMPILER_ID:MSVC>:/W4>
    $<$<CXX_COMPILER_ID:Clang>:-Weverything>
)
# 플랫폼별 링크
target_link_libraries(myapp PRIVATE
    $<$<PLATFORM_ID:Linux>:pthread>
    $<$<PLATFORM_ID:Windows>:ws2_32>
)

3. 인터페이스 라이브러리 활용

# 공통 설정을 인터페이스 라이브러리로
add_library(common_settings INTERFACE)
target_compile_features(common_settings INTERFACE cxx_std_20)
target_compile_options(common_settings INTERFACE
    $<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra -Wpedantic>
    $<$<CXX_COMPILER_ID:MSVC>:/W4>
)
# 모든 타겟에 적용
target_link_libraries(myapp PRIVATE common_settings)
target_link_libraries(mylib PRIVATE common_settings)

4. 버전 정보 자동 생성

# version.h.in
#pragma once
#define PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define PROJECT_VERSION "@PROJECT_VERSION@"
# CMakeLists.txt
project(MyProject VERSION 1.2.3)
configure_file(
    ${CMAKE_SOURCE_DIR}/version.h.in
    ${CMAKE_BINARY_DIR}/generated/version.h
    @ONLY
)
target_include_directories(myapp PRIVATE ${CMAKE_BINARY_DIR}/generated)

5. 설치 규칙 명확히

# 실행 파일 설치
install(TARGETS myapp
    RUNTIME DESTINATION bin
)
# 라이브러리 설치
install(TARGETS mylib
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
    RUNTIME DESTINATION bin
)
# 헤더 설치 (FILE_SET 사용, 3.23+)
install(TARGETS mylib
    FILE_SET HEADERS DESTINATION include
)
# Config 파일 생성 (다른 프로젝트에서 find_package 가능)
install(EXPORT MyLibTargets
    FILE MyLibTargets.cmake
    NAMESPACE MyLib::
    DESTINATION lib/cmake/MyLib
)

13. 프로덕션 패턴

패턴 1: 조건부 기능 활성화

option(ENABLE_LOGGING "Enable logging" ON)
option(ENABLE_PROFILING "Enable profiling" OFF)
option(BUILD_EXAMPLES "Build examples" OFF)
if(ENABLE_LOGGING)
    target_compile_definitions(myapp PRIVATE ENABLE_LOGGING)
    target_sources(myapp PRIVATE src/logger.cpp)
endif()
if(ENABLE_PROFILING)
    target_link_libraries(myapp PRIVATE profiler)
endif()
if(BUILD_EXAMPLES)
    add_subdirectory(examples)
endif()

패턴 2: 플랫폼별 소스 관리

# 공통 소스
set(COMMON_SOURCES
    src/main.cpp
    src/core.cpp
)
# 플랫폼별 소스
if(WIN32)
    list(APPEND PLATFORM_SOURCES src/platform/windows.cpp)
elseif(APPLE)
    list(APPEND PLATFORM_SOURCES src/platform/macos.cpp)
elseif(UNIX)
    list(APPEND PLATFORM_SOURCES src/platform/linux.cpp)
endif()
add_executable(myapp ${COMMON_SOURCES} ${PLATFORM_SOURCES})

패턴 3: 컴파일 경고를 에러로 (CI/CD)

function(enable_warnings_as_errors target)
    if(MSVC)
        target_compile_options(${target} PRIVATE /W4 /WX)
    else()
        target_compile_options(${target} PRIVATE
            -Wall -Wextra -Wpedantic -Werror
        )
    endif()
endfunction()
# CI 환경에서만 활성화
if(DEFINED ENV{CI})
    enable_warnings_as_errors(myapp)
endif()

패턴 4: 빌드 타입별 후처리

# Release 빌드 시 스트립
if(CMAKE_BUILD_TYPE STREQUAL "Release" AND UNIX)
    add_custom_command(TARGET myapp POST_BUILD
        COMMAND ${CMAKE_STRIP} --strip-all $<TARGET_FILE:myapp>
        COMMENT "Stripping binary"
    )
endif()
# 빌드 정보 출력
add_custom_command(TARGET myapp POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E echo "Build complete: $<TARGET_FILE:myapp>"
    COMMAND ${CMAKE_COMMAND} -E echo "Build type: ${CMAKE_BUILD_TYPE}"
)

패턴 5: 의존성 버전 고정

include(FetchContent)
# 버전 변수로 관리
set(GOOGLETEST_VERSION "1.14.0")
set(FMT_VERSION "10.0.0")
set(SPDLOG_VERSION "1.12.0")
FetchContent_Declare(
    googletest
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG v${GOOGLETEST_VERSION}
    GIT_SHALLOW ON  # 최신 커밋만 다운로드
)
FetchContent_Declare(
    fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG ${FMT_VERSION}
    GIT_SHALLOW ON
)
FetchContent_MakeAvailable(googletest fmt)

패턴 6: 개발자 모드

option(DEVELOPER_MODE "Enable developer mode" OFF)
if(DEVELOPER_MODE)
    # 모든 경고 활성화
    add_compile_options(-Wall -Wextra -Wpedantic)
    
    # Sanitizer 활성화
    add_compile_options(-fsanitize=address,undefined)
    add_link_options(-fsanitize=address,undefined)
    
    # 컴파일 명령 내보내기 (clangd, ccls 등에서 사용)
    set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
    
    # 테스트 빌드
    set(BUILD_TESTS ON)
endif()

14. 정리 및 체크리스트

CMake 핵심 개념 요약

개념설명예제
타겟빌드 산출물 (실행 파일, 라이브러리)add_executable, add_library
속성타겟별 설정target_include_directories
의존성타겟 간 링크 관계target_link_libraries
프리셋재사용 가능한 빌드 설정CMakePresets.json
Generator플랫폼별 빌드 시스템 생성Ninja, Make, Visual Studio

CMake 도입 체크리스트

# ✅ 기본 설정
- [ ] CMake 3.20 이상 설치 (3.28+ 권장)
- [ ] out-of-source build 사용 (build/ 디렉토리)
- [ ] C++ 표준 설정 (CMAKE_CXX_STANDARD)
- [ ] CMAKE_CXX_EXTENSIONS OFF 설정
# ✅ 타겟 관리
- [ ] 타겟 기반 접근 (add_executable, add_library)
- [ ] PUBLIC/PRIVATE/INTERFACE 명확히 구분
- [ ] target_* 명령어 사용 (전역 명령어 피하기)
# ✅ 의존성 관리
- [ ] find_package로 외부 라이브러리 찾기
- [ ] FetchContent로 자동 다운로드 (선택)
- [ ] 버전 고정 (재현 가능한 빌드)
# ✅ 빌드 설정
- [ ] CMakePresets.json 작성 (팀 공유)
- [ ] 빌드 타입 설정 (Debug/Release)
- [ ] 경고 활성화 (-Wall -Wextra 또는 /W4)
- [ ] CI/CD에서 경고를 에러로 (-Werror)
# ✅ 테스트
- [ ] enable_testing() + add_test()
- [ ] CTest 통합
# ✅ 설치 및 배포
- [ ] install() 규칙 작성
- [ ] Config 파일 생성 (find_package 지원)
# ✅ 성능
- [ ] 병렬 빌드 활성화 (-j)
- [ ] Ninja 사용 고려
- [ ] ccache 설정
- [ ] PCH 활용 (큰 프로젝트)
# ✅ 최신 기능 (선택)
- [ ] C++20 모듈 지원 (3.28+)
- [ ] FILE_SET 사용 (3.23+)
- [ ] Unity 빌드 (3.16+)

버전별 권장 기능

CMake 버전권장 사항
3.20+기본 기능 사용 가능
3.23+FILE_SET으로 헤더 관리
3.25+LINUX 변수 사용
3.28+C++20 모듈 지원

실전 팁: CMake 효율적으로 사용하기

  1. 빌드 디렉토리 여러 개 유지
   # 디버그, 릴리스, 프로파일링 동시 유지
   cmake --preset=debug
   cmake --preset=release
   cmake --preset=profile
   
   # 필요할 때 각각 빌드
   cmake --build build/debug
   cmake --build build/release
  1. 컴파일 명령 내보내기 (LSP 서버용)
   # compile_commands.json 생성 (clangd, ccls 등에서 사용)
   set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
   
   # 루트에 심볼릭 링크 생성
   # ln -s build/compile_commands.json .
  1. 빌드 시간 측정
   # 빌드 시간 측정
   time cmake --build build
   
   # 또는 CMake 내장 기능
   cmake --build build --verbose
  1. 타겟별 선택 빌드
   # 특정 타겟만 빌드 (전체 빌드 불필요)
   cmake --build build --target mylib
   cmake --build build --target tests
   
   # 여러 타겟 동시 빌드
   cmake --build build --target mylib --target myapp
  1. 캐시 변수 확인
   # 현재 캐시 변수 확인
   cmake -L build
   
   # 고급 변수까지 확인
   cmake -LA build
   
   # 특정 변수 확인
   cmake -L build | grep CMAKE_CXX_COMPILER

빠른 참조: CMake 명령어 치트시트

# 🏗️ 프로젝트 설정
cmake -S . -B build              # 소스: 현재, 빌드: build/
cmake --preset=debug             # 프리셋 사용 (3.19+)
cmake -DCMAKE_BUILD_TYPE=Release # 빌드 타입 지정
# 🔨 빌드
cmake --build build              # 빌드 실행
cmake --build build -j8          # 병렬 빌드 (8 코어)
cmake --build build --target myapp  # 특정 타겟만
# 🧪 테스트
ctest --test-dir build           # 테스트 실행
ctest --preset=default           # 프리셋으로 테스트
# 📦 설치
cmake --install build            # 설치 실행
cmake --install build --prefix /usr/local  # 경로 지정
# 🧹 정리
cmake --build build --target clean  # 빌드 결과물 삭제
rm -rf build                     # 완전 초기화

트러블슈팅: 빠른 문제 해결

증상원인해결법
Could not find package라이브러리 미설치apt install, brew install 또는 CMAKE_PREFIX_PATH 지정
Undefined reference링크 순서 오류target_link_libraries 순서 확인
Header not foundinclude 경로 누락target_include_directories 추가
캐시 문제CMakeCache.txt 오래됨rm CMakeCache.txt 또는 cmake --fresh
프리셋 없음CMake 버전 낮음CMake 3.19+ 업그레이드
빌드 느림Make 사용Ninja로 전환 (-G Ninja)

빌드 시스템 성능 비교

빌드 시스템속도병렬화크로스 플랫폼학습 곡선
Make보통제한적Linux/macOS쉬움
Ninja빠름우수모든 플랫폼쉬움
Visual Studio보통우수Windows쉬움
Xcode보통우수macOS중간
권장: Ninja (가장 빠르고 모든 플랫폼 지원)

다음 단계


FAQ

Q1: CMake vs Make 차이는?

A: CMake는 빌드 시스템을 생성하는 도구(메타 빌드 시스템)이고, Make는 실제로 빌드를 수행하는 도구입니다. CMake가 Makefile을 생성하면, Make가 그 Makefile을 읽어 컴파일합니다.

CMakeLists.txt → [CMake] → Makefile → [Make] → 실행 파일

Q2: out-of-source build가 뭔가요?

A: 소스 디렉토리와 빌드 디렉토리를 분리하는 것입니다. build/ 디렉토리를 만들어 그 안에서 cmake ..를 실행하면, 빌드 파일이 build/에만 생성되어 소스가 깨끗하게 유지됩니다.

# ✅ 올바른 방법
mkdir build && cd build
cmake ..
# ❌ 잘못된 방법 (소스와 빌드 파일 섞임)
cmake .

Q3: PUBLIC vs PRIVATE vs INTERFACE 언제 쓰나요?

A:

  • PRIVATE: 이 타겟 내부에서만 사용 (구현 세부사항)
  • PUBLIC: 이 타겟 + 이 타겟을 링크하는 타겟도 사용 (공개 API)
  • INTERFACE: 이 타겟을 링크하는 타겟만 사용 (헤더 전용 라이브러리)
# 내부 헤더
target_include_directories(mylib PRIVATE src/internal)
# 공개 헤더
target_include_directories(mylib PUBLIC include)
# 헤더 전용 라이브러리
target_include_directories(header_only INTERFACE include)

Q4: CMakePresets.json은 언제 사용하나요?

A: 팀 프로젝트나 CI/CD에서 빌드 설정을 공유할 때 사용합니다. 개인 설정은 CMakeUserPresets.json에 작성하고 git ignore에 추가합니다.

# 프리셋 없이 (복잡)
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=g++ -DENABLE_TESTS=ON ..
# 프리셋 사용 (간단)
cmake --preset=debug

Q5: find_package가 실패하면?

A:

  1. 라이브러리 설치 확인
  2. CMAKE_PREFIX_PATH 지정
  3. 패키지 매니저 사용 (vcpkg, conan)
# 1. 설치
sudo apt install libboost-dev  # Linux
brew install boost             # macOS
# 2. 경로 지정
cmake -DCMAKE_PREFIX_PATH=/usr/local ..
# 3. vcpkg 사용
cmake -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg.cmake ..

Q6: C++20 모듈을 사용해야 하나요?

A: 2026년 현재, 주요 컴파일러(GCC 14+, Clang 17+, MSVC 19.36+)가 모두 지원하므로 새 프로젝트에서는 사용을 권장합니다. 기존 프로젝트는 점진적으로 마이그레이션하세요. 장점: 빠른 컴파일, 깨끗한 인터페이스, 순환 의존성 방지 단점: 빌드 시스템 복잡도 증가, 레거시 코드와 혼용 시 주의 필요

Q7: 빌드가 느린데 어떻게 최적화하나요?

A:

  1. Ninja 사용 (cmake -G Ninja)
  2. 병렬 빌드 (cmake --build . -j$(nproc))
  3. ccache 활성화
  4. PCH (프리컴파일 헤더) 사용
  5. Unity 빌드 고려 (큰 프로젝트)
# ccache 활성화
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
    set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
endif()
# Unity 빌드
set(CMAKE_UNITY_BUILD ON)

Q8: CMake 학습 리소스는?

A:

Q9: 기존 Makefile 프로젝트를 CMake로 마이그레이션하려면?

A: 단계별로 점진적으로 마이그레이션하세요.

# 1단계: 최소 CMakeLists.txt 작성
cmake_minimum_required(VERSION 3.20)
project(MyProject)
file(GLOB_RECURSE SOURCES "src/*.cpp")  # 임시로 GLOB 사용
add_executable(myapp ${SOURCES})
# 2단계: 소스 파일 명시적으로 나열
set(SOURCES
    src/main.cpp
    src/module1.cpp
    # ...
)
# 3단계: 타겟별로 분리
add_library(mylib src/lib.cpp)
add_executable(myapp src/main.cpp)
target_link_libraries(myapp PRIVATE mylib)
# 4단계: 서브디렉토리 구조화
add_subdirectory(src)
add_subdirectory(tests)

Q10: CMake 캐시를 완전히 초기화하려면?

A:

# 방법 1: 빌드 디렉토리 삭제 (가장 확실)
rm -rf build
mkdir build && cd build
cmake ..
# 방법 2: CMakeCache.txt만 삭제
rm CMakeCache.txt
cmake ..
# 방법 3: CMake 명령으로 초기화
cmake --fresh ... # CMake 3.24+

Q11: 여러 빌드 타입을 동시에 유지하려면?

A: 빌드 디렉토리를 분리하거나 프리셋을 사용하세요.

# 방법 1: 디렉토리 분리
mkdir build-debug && cd build-debug
cmake -DCMAKE_BUILD_TYPE=Debug ..
mkdir build-release && cd build-release
cmake -DCMAKE_BUILD_TYPE=Release ..
# 방법 2: 프리셋 사용 (권장)
cmake --preset=debug
cmake --preset=release
# build/debug, build/release에 각각 생성됨

Q12: CMake에서 Git 커밋 해시를 버전에 포함하려면?

A:

# Git 커밋 해시 가져오기
execute_process(
    COMMAND git rev-parse --short HEAD
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    OUTPUT_VARIABLE GIT_COMMIT_HASH
    OUTPUT_STRIP_TRAILING_WHITESPACE
)
# 버전 헤더 생성
configure_file(
    ${CMAKE_SOURCE_DIR}/version.h.in
    ${CMAKE_BINARY_DIR}/version.h
)
# version.h.in
#define VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define VERSION_MINOR @PROJECT_VERSION_MINOR@
#define GIT_COMMIT "@GIT_COMMIT_HASH@"

한 줄 요약: CMake 3.28+로 C++20 모듈을 포함한 크로스 플랫폼 프로젝트를 효율적으로 빌드할 수 있습니다. CMakePresets.json으로 팀 설정을 공유하고, 최신 기능을 활용하여 생산성을 높이세요.

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

실전 체크리스트

실무에서 이 개념을 적용할 때 확인해야 할 사항입니다.

코드 작성 전

  • 이 기법이 현재 문제를 해결하는 최선의 방법인가?
  • 팀원들이 이 코드를 이해하고 유지보수할 수 있는가?
  • 성능 요구사항을 만족하는가?

코드 작성 중

  • 컴파일러 경고를 모두 해결했는가?
  • 엣지 케이스를 고려했는가?
  • 에러 처리가 적절한가?

코드 리뷰 시

  • 코드의 의도가 명확한가?
  • 테스트 케이스가 충분한가?
  • 문서화가 되어 있는가? 이 체크리스트를 활용하여 실수를 줄이고 코드 품질을 높이세요.

관련 글

심화 부록: 구현·운영 관점

이 부록은 앞선 본문에서 다룬 주제(「C++ CMake 완벽 가이드 | 크로스 플랫폼 빌드·최신 CMake 3.28+ 기능·프리셋·모듈」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.

내부 동작과 핵심 메커니즘

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]
sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(런타임·게이트웨이·프로세스)
  participant D as 의존성(API·DB·큐·파일)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)
  • 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.

프로덕션 운영 패턴

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가
용량피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.

확장 예시: 엔드투엔드 미니 시나리오

앞선 본문 주제(「C++ CMake 완벽 가이드 | 크로스 플랫폼 빌드·최신 CMake 3.28+ 기능·프리셋·모듈」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)
  authorize(validated, ctx)
  result = domainCore(validated)
  persistOrEmit(result, idempotentKey)
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, cmake, build, makefile, tools, cross-platform, cmake-presets, modern-cmake, generator-expressions 등으로 검색하시면 이 글이 도움이 됩니다.