C++ CMake Complete Guide | Mastering Cross-Platform Build Systems

C++ CMake Complete Guide | Mastering Cross-Platform Build Systems

이 글의 핵심

CMake fundamentals: targets, generator expressions, install trees, and cross-platform project layout for C++ teams.

What is CMake and Why Do You Need It?

The Pain of Cross-Platform Builds

Scenario: You’ve developed a C++ project using Visual Studio on Windows and now need to deploy it to a Linux server. On Windows, you build using .vcxproj files, but on Linux, you need to write a Makefile. If you also want to test on macOS, you’ll need to create an Xcode project. Managing build configurations for each platform separately can quickly turn into a maintenance nightmare.

Solution: CMake allows you to write a platform-independent build configuration (CMakeLists.txt) that automatically generates platform-specific build systems (Makefile, Visual Studio projects, Ninja, etc.). Write it once, build it anywhere.

flowchart LR
    subgraph input["Input"]
        cmake["CMakeLists.txt"]
    end
    subgraph cmake_tool["CMake"]
        gen["Generator"]
    end
    subgraph output["Output"]
        make["Makefile (Linux)"]
        vs["Visual Studio (Windows)"]
        xcode["Xcode (macOS)"]
        ninja["Ninja (All Platforms)"]
    end
    cmake --> gen
    gen --> make
    gen --> vs
    gen --> xcode
    gen --> ninja

Core Concepts of CMake

  • Build System Generator: CMake doesn’t build your project directly. Instead, it generates platform-specific build systems (e.g., Makefile, .sln).
  • Target-Based: Use add_executable and add_library to define build targets, and target_link_libraries and target_include_directories to specify dependencies.
  • Variables and Cache: Use set() to define variables and option() to provide user-configurable options.

Table of Contents

  1. Minimal CMakeLists.txt
  2. Targets: Executables and Libraries
  3. Finding External Libraries: find_package
  4. Build Types and Compiler Options
  5. Project Structure and Subdirectories
  6. Common Issues and Solutions
  7. Production Patterns
  8. Complete Example: Multi-Target Project
  9. Performance Optimization
  10. CMake Adoption Checklist

1. Minimal CMakeLists.txt

Building a Hello World

# CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(MyProject)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(myapp main.cpp)
# Create a build directory (out-of-source build)
mkdir build
cd build

# Run CMake (generate the build system)
cmake ..

# Build (compile using the generated build system)
cmake --build .

# Or directly use make (Linux/macOS)
make

# Run the executable
./myapp

Key Commands Explained

CommandDescription
cmake_minimum_required(VERSION 3.20)Specifies the minimum required version of CMake
project(MyProject)Sets the project name
set(CMAKE_CXX_STANDARD 20)Specifies the use of C++20 standard
add_executable(myapp main.cpp)Creates an executable target
cmake ..Generates the build system using the parent directory’s CMakeLists.txt
cmake --build .Platform-independent build command

2. Targets: Executables and Libraries

Executables

# Single source file
add_executable(myapp main.cpp)

# Multiple source files
add_executable(myapp
    src/main.cpp
    src/utils.cpp
    src/config.cpp
)

# Using variables
set(SOURCES
    src/main.cpp
    src/utils.cpp
)
add_executable(myapp ${SOURCES})

Libraries

# Static library (.a, .lib)
add_library(mylib STATIC
    src/lib.cpp
    src/helper.cpp
)

# Shared library (.so, .dll)
add_library(mylib SHARED
    src/lib.cpp
    src/helper.cpp
)

# Header-only library
add_library(mylib INTERFACE)
target_include_directories(mylib INTERFACE include)

# Link library to executable
add_executable(myapp src/main.cpp)
target_link_libraries(myapp PRIVATE mylib)

PUBLIC vs PRIVATE vs INTERFACE

# PRIVATE: Used only within the target
target_include_directories(mylib PRIVATE src/internal)

# PUBLIC: Used by the target and any target linking to it
target_include_directories(mylib PUBLIC include)

# INTERFACE: Used only by targets linking to this target (for header-only libraries)
target_include_directories(mylib INTERFACE include)

Practical Example: If mylib internally uses src/internal/impl.h, it should be added as PRIVATE. Headers exposed to external projects, like include/mylib/api.h, should be added as PUBLIC. When myapp links to mylib, it will only have access to the PUBLIC headers.


3. Finding External Libraries: find_package

Boost Example

find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)

add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE Boost::filesystem Boost::system)

OpenSSL Example

find_package(OpenSSL REQUIRED)

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

Handling Missing Packages

find_package(SomeLib)

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

Using 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})

4. Build Types and Compiler Options

Build Types

# Set default build type
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
endif()

# Flags for each build type
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")
# Debug build
cmake -DCMAKE_BUILD_TYPE=Debug ..

# Release build
cmake -DCMAKE_BUILD_TYPE=Release ..

# Optimized build with debug info
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..

Target-Specific Compiler Options

add_executable(myapp main.cpp)

# Enable warnings
target_compile_options(myapp PRIVATE
    -Wall -Wextra -Wpedantic
    $<$<CONFIG:Debug>:-Werror>  # Treat warnings as errors in Debug mode
)

# Preprocessor definitions
target_compile_definitions(myapp PRIVATE
    APP_VERSION="1.0"
    $<$<CONFIG:Debug>:DEBUG_MODE>
)

# Include directories
target_include_directories(myapp PRIVATE
    ${CMAKE_SOURCE_DIR}/include
    ${CMAKE_BINARY_DIR}/generated
)

Generator Expressions

# Conditional flags based on build type
target_compile_options(myapp PRIVATE
    $<$<CONFIG:Debug>:-O0 -g>
    $<$<CONFIG:Release>:-O3>
)

# Conditional flags based on compiler
target_compile_options(myapp PRIVATE
    $<$<CXX_COMPILER_ID:GNU>:-fno-exceptions>
    $<$<CXX_COMPILER_ID:MSVC>:/EHsc>
)

(Translation continues with the same structure for the remaining sections.)

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

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

  • C++ CMake Targets 완벽 가이드 | 타겟 기반 빌드 시스템
  • C++ CMake find_package 완벽 가이드 | 외부 라이브러리 통합
  • C++ Conan 완벽 가이드 | 현대적인 C++ 패키지 관리
  • C++ vcpkg 완벽 가이드 | Microsoft C++ 패키지 관리자


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

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