Skip to content

🚀 Modern CMake Fundamentals Core

CMake là industry standard để build C++ projects. Nhưng có 2 cách viết CMake: Legacy (sai) và Modern (đúng). Tài liệu này dạy cách Modern.

Old CMake vs Modern CMake

❌ LEGACY CMAKE (PROHIBITED)

cmake
# SAI - Đừng bao giờ viết như này
cmake_minimum_required(VERSION 2.8)  # Quá cũ
project(MyApp)

include_directories(${CMAKE_SOURCE_DIR}/include)
add_definitions(-DDEBUG_MODE)
link_directories(/usr/local/lib)

add_executable(app main.cpp utils.cpp)
link_libraries(pthread boost_system)

✅ MODERN CMAKE (ENFORCED)

cmake
# ĐÚNG - Target-based approach
cmake_minimum_required(VERSION 3.20)
project(MyApp LANGUAGES CXX)

add_executable(app main.cpp utils.cpp)

target_include_directories(app PRIVATE ${CMAKE_SOURCE_DIR}/include)
target_compile_definitions(app PRIVATE DEBUG_MODE)
target_link_libraries(app PRIVATE pthread Boost::system)
target_compile_features(app PRIVATE cxx_std_20)

Sự khác biệt cốt lõi

┌─────────────────────────────────────────────────────────────────────────┐
│                    LEGACY vs MODERN CMAKE                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   LEGACY (Directory-scoped)              MODERN (Target-scoped)         │
│   ─────────────────────────              ─────────────────────          │
│   include_directories(...)         →     target_include_directories()   │
│   add_definitions(...)             →     target_compile_definitions()   │
│   link_libraries(...)              →     target_link_libraries()        │
│   set(CMAKE_CXX_STANDARD 20)       →     target_compile_features()      │
│                                                                         │
│   ❌ Ảnh hưởng TẤT CẢ targets            ✅ Chỉ ảnh hưởng target cụ thể │
│   ❌ Không encapsulation                  ✅ Clean dependency graph      │
│   ❌ Order-dependent                      ✅ Declarative, không phụ thuộc│
│      (phải viết trước add_executable)       thứ tự                      │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

CMakeLists.txt Anatomy

Một CMakeLists.txt chuẩn có cấu trúc:

cmake
# ============================================
# 1. MINIMUM VERSION & PROJECT
# ============================================
cmake_minimum_required(VERSION 3.20)
project(HPN_Engine 
    VERSION 1.0.0
    DESCRIPTION "High Performance Network Engine"
    LANGUAGES CXX
)

# ============================================
# 2. GLOBAL SETTINGS (trước khi define targets)
# ============================================
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)  # Cho clangd/LSP
set(CMAKE_CXX_EXTENSIONS OFF)          # -std=c++20 thay vì -std=gnu++20

# ============================================
# 3. DEPENDENCIES (imported targets)
# ============================================
find_package(Threads REQUIRED)
find_package(fmt REQUIRED)

# ============================================
# 4. DEFINE TARGETS
# ============================================
add_library(hpn_core STATIC
    src/core/engine.cpp
    src/core/network.cpp
    src/core/utils.cpp
)

add_executable(hpn_app
    src/main.cpp
)

# ============================================
# 5. TARGET PROPERTIES
# ============================================
target_include_directories(hpn_core PUBLIC include)
target_compile_features(hpn_core PUBLIC cxx_std_20)
target_link_libraries(hpn_core 
    PUBLIC Threads::Threads
    PRIVATE fmt::fmt
)

target_link_libraries(hpn_app PRIVATE hpn_core)

add_executable vs add_library

add_executable — Tạo binary chạy được

cmake
add_executable(my_app
    src/main.cpp
    src/app.cpp
)

add_library — Tạo library

cmake
# Static library (.a / .lib)
add_library(my_lib STATIC
    src/lib.cpp
)

# Shared/Dynamic library (.so / .dll)
add_library(my_lib SHARED
    src/lib.cpp
)

# Header-only library (no compilation)
add_library(my_header_lib INTERFACE)
target_include_directories(my_header_lib INTERFACE include)

target_include_directories — The Right Way

cmake
add_library(network STATIC src/network.cpp)

# PUBLIC: Cả library VÀ consumers cần include path này
target_include_directories(network PUBLIC include/network)

# PRIVATE: Chỉ library cần (implementation detail)
target_include_directories(network PRIVATE src/internal)

# INTERFACE: Chỉ consumers cần (header-only components)
target_include_directories(network INTERFACE include/network/api)

cmake
# Library dependencies
add_library(utils STATIC src/utils.cpp)
add_library(network STATIC src/network.cpp)
add_library(engine STATIC src/engine.cpp)

# network phụ thuộc utils
target_link_libraries(network PRIVATE utils)

# engine phụ thuộc network (và transitively, utils)
target_link_libraries(engine PUBLIC network)

# app chỉ cần link engine, nhận network + utils tự động
add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE engine)
┌─────────────────────────────────────────────┐
│              DEPENDENCY GRAPH               │
├─────────────────────────────────────────────┤
│                                             │
│   app ─────────► engine                     │
│                    │                        │
│                    ▼                        │
│                 network                     │
│                    │                        │
│                    ▼                        │
│                  utils                      │
│                                             │
│   CMake tự động resolve transitive deps!    │
│                                             │
└─────────────────────────────────────────────┘

target_compile_features — C++ Standard

cmake
# Cách ĐÚNG: Sử dụng compile features
target_compile_features(my_lib PUBLIC cxx_std_20)

# Cách SAI: Set global variable
# set(CMAKE_CXX_STANDARD 20)  # Ảnh hưởng MỌI targets

💡 PRO TIP

cxx_std_20 đảm bảo compiler hỗ trợ C++20. Nếu không, CMake báo lỗi rõ ràng thay vì để compiler fail với message khó hiểu.


target_compile_definitions — Preprocessor Macros

cmake
# Định nghĩa macros
target_compile_definitions(my_lib 
    PUBLIC  
        HPN_VERSION="1.0.0"
    PRIVATE 
        DEBUG_INTERNAL
        $<$<CONFIG:Debug>:DEBUG_MODE>      # Chỉ Debug build
        $<$<CONFIG:Release>:NDEBUG>        # Chỉ Release build
)

Project Structure chuẩn

project/
├── CMakeLists.txt              # Root CMake
├── cmake/                      # Custom CMake modules
│   ├── CompilerWarnings.cmake
│   └── Sanitizers.cmake
├── include/                    # PUBLIC headers
│   └── project/
│       ├── engine.hpp
│       └── network.hpp
├── src/                        # Source files
│   ├── CMakeLists.txt          # Subdirectory CMake
│   ├── engine.cpp
│   ├── network.cpp
│   └── internal/               # PRIVATE headers
│       └── impl.hpp
├── tests/                      # Unit tests
│   ├── CMakeLists.txt
│   └── test_engine.cpp
├── apps/                       # Executables
│   ├── CMakeLists.txt
│   └── main.cpp
└── build/                      # OUT-OF-SOURCE (gitignored)

Root CMakeLists.txt

cmake
cmake_minimum_required(VERSION 3.20)
project(HPN_Project VERSION 1.0.0 LANGUAGES CXX)

# Options
option(BUILD_TESTS "Build unit tests" ON)
option(ENABLE_SANITIZERS "Enable ASan/UBSan" OFF)

# Include custom modules
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
include(CompilerWarnings)
include(Sanitizers)

# Add subdirectories
add_subdirectory(src)
add_subdirectory(apps)

if(BUILD_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()

src/CMakeLists.txt

cmake
add_library(hpn_core STATIC
    engine.cpp
    network.cpp
)

target_include_directories(hpn_core
    PUBLIC 
        ${CMAKE_SOURCE_DIR}/include
    PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/internal
)

target_compile_features(hpn_core PUBLIC cxx_std_20)

Out-of-Source Build — The Only Way

powershell
# Từ project root
cmake -B build              # Configure vào thư mục build/
cmake --build build         # Compile
cmake --build build --target test  # Run tests (nếu có)

❌ NEVER DO THIS

powershell
cd project
cmake .       # In-source build = pollution
make
# Giờ project/ chứa đầy CMakeCache, CMakeFiles, Makefile...

✅ ALWAYS DO THIS

powershell
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel 8

Generator Expression — Conditional Logic

cmake
target_compile_definitions(my_lib PRIVATE
    # Debug-only
    $<$<CONFIG:Debug>:DEBUG_ENABLED>
    
    # Platform-specific
    $<$<PLATFORM_ID:Windows>:WIN32_LEAN_AND_MEAN>
    $<$<PLATFORM_ID:Linux>:_GNU_SOURCE>
    
    # Compiler-specific
    $<$<CXX_COMPILER_ID:GNU>:GCC_SPECIFIC>
    $<$<CXX_COMPILER_ID:Clang>:CLANG_SPECIFIC>
)

target_compile_options(my_lib PRIVATE
    # Warnings per compiler
    $<$<CXX_COMPILER_ID:GNU,Clang>:-Wall -Wextra -Wpedantic>
    $<$<CXX_COMPILER_ID:MSVC>:/W4>
)

CMake 3.x: find_package

cmake
# Tìm package đã install trong system
find_package(fmt REQUIRED)
find_package(Boost 1.74 REQUIRED COMPONENTS system filesystem)
find_package(OpenSSL REQUIRED)

# Sử dụng imported targets
target_link_libraries(my_app PRIVATE
    fmt::fmt
    Boost::system
    Boost::filesystem
    OpenSSL::SSL
    OpenSSL::Crypto
)

📦 DEPENDENCY NOTE

find_package yêu cầu library đã được install. Để quản lý installation, sử dụng Conan hoặc Vcpkg (xem bài Package Management).


Quick Reference

TaskCommand
Configurecmake -B build
Configure Releasecmake -B build -DCMAKE_BUILD_TYPE=Release
Buildcmake --build build
Build parallelcmake --build build -j8
Run testsctest --test-dir build
Installcmake --install build --prefix /usr/local
Cleancmake --build build --target clean
Full rebuildrm -rf build && cmake -B build && cmake --build build

Bước tiếp theo

Đã hiểu fundamentals, hãy đi sâu vào kỹ thuật nâng cao:

⚙️ CMake Advanced → — PUBLIC/PRIVATE/INTERFACE, Sanitizers, Static Analysis