Giao diện
⚙️ CMake Advanced Production
Kỹ thuật nâng cao để biến CMake project thành production-grade: dependency propagation, sanitizers, và static analysis.
PUBLIC vs PRIVATE vs INTERFACE — The Heart of Modern CMake
Đây là concept quan trọng nhất của Modern CMake. Hiểu sai = dependency hell.
Visualization
┌─────────────────────────────────────────────────────────────────────────┐
│ TARGET DEPENDENCY PROPAGATION │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────┐ links to ┌───────────┐ │
│ │ app │ ───────────────► │ mylib │ │
│ └───────────┘ └───────────┘ │
│ │ │
│ │ PUBLIC: spdlog │
│ │ PRIVATE: zlib │
│ │ INTERFACE: boost_headers │
│ ▼ │
│ │
│ Kết quả cho app: │
│ ├── GETS: spdlog (vì PUBLIC) │
│ ├── GETS: boost_headers (vì INTERFACE) │
│ └── NOT GET: zlib (vì PRIVATE - implementation detail) │
│ │
└─────────────────────────────────────────────────────────────────────────┘Định nghĩa chính xác
| Keyword | Ai dùng? | Use case |
|---|---|---|
| PUBLIC | Library + Consumers | Dependency xuất hiện trong public headers |
| PRIVATE | Chỉ Library | Implementation detail, không exposed |
| INTERFACE | Chỉ Consumers | Header-only, hoặc export-only |
Ví dụ thực tế
cmake
# Library definition
add_library(network STATIC
src/connection.cpp
src/socket.cpp
)
target_link_libraries(network
# PUBLIC: consumer code cần include boost/asio.hpp
PUBLIC Boost::asio
# PRIVATE: OpenSSL dùng trong implementation, consumer không biết
PRIVATE OpenSSL::SSL OpenSSL::Crypto
# INTERFACE: N/A for this example
)
target_include_directories(network
# PUBLIC: include/network/connection.hpp được consumer include
PUBLIC ${CMAKE_SOURCE_DIR}/include/network
# PRIVATE: src/internal/ chứa implementation headers
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/internal
)
# Consumer
add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE network)
# app automatically gets: Boost::asio (PUBLIC từ network)
# app KHÔNG cần link: OpenSSL (PRIVATE của network)Decision Flowchart
┌───────────────────────────────────────────────────────┐
│ Dependency X được dùng trong library L │
└───────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────┐
│ X xuất hiện trong PUBLIC │
│ headers của L? │
└───────────────────────────────┘
│ │
YES NO
│ │
▼ ▼
┌───────────┐ ┌───────────────────────┐
│ PUBLIC │ │ X được dùng trong │
└───────────┘ │ implementation (cpp)? │
└───────────────────────┘
│ │
YES NO
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│ PRIVATE │ │ INTERFACE │
└───────────┘ └───────────┘
(header-only)Static Analysis Integration
CMake Module: StaticAnalysis.cmake
Tạo file cmake/StaticAnalysis.cmake:
cmake
# cmake/StaticAnalysis.cmake
# Integration with clang-tidy and cppcheck
option(ENABLE_CLANG_TIDY "Enable clang-tidy static analysis" OFF)
option(ENABLE_CPPCHECK "Enable cppcheck static analysis" OFF)
# ============================================
# CLANG-TIDY
# ============================================
if(ENABLE_CLANG_TIDY)
find_program(CLANG_TIDY_EXE NAMES clang-tidy clang-tidy-15 clang-tidy-14)
if(CLANG_TIDY_EXE)
message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}")
set(CMAKE_CXX_CLANG_TIDY
${CLANG_TIDY_EXE}
-checks=*,-llvmlibc-*,-fuchsia-*,-google-runtime-references
-warnings-as-errors=*
--header-filter=${CMAKE_SOURCE_DIR}/include/.*
)
else()
message(WARNING "clang-tidy requested but not found!")
endif()
endif()
# ============================================
# CPPCHECK
# ============================================
if(ENABLE_CPPCHECK)
find_program(CPPCHECK_EXE NAMES cppcheck)
if(CPPCHECK_EXE)
message(STATUS "cppcheck found: ${CPPCHECK_EXE}")
set(CMAKE_CXX_CPPCHECK
${CPPCHECK_EXE}
--enable=warning,performance,portability
--suppress=missingIncludeSystem
--inline-suppr
--inconclusive
--error-exitcode=1
)
else()
message(WARNING "cppcheck requested but not found!")
endif()
endif()Sử dụng trong Root CMakeLists.txt
cmake
cmake_minimum_required(VERSION 3.20)
project(MyProject LANGUAGES CXX)
# Include static analysis module
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
include(StaticAnalysis)
# Targets sau đây sẽ được scan
add_library(mylib src/lib.cpp)Chạy Analysis
powershell
# Configure với static analysis enabled
cmake -B build -DENABLE_CLANG_TIDY=ON -DENABLE_CPPCHECK=ON
# Build (analysis chạy parallel với compile)
cmake --build buildCompiler Warnings Module
Tạo file cmake/CompilerWarnings.cmake:
cmake
# cmake/CompilerWarnings.cmake
# Strict warnings configuration
function(set_project_warnings target)
set(CLANG_WARNINGS
-Wall
-Wextra
-Wpedantic
-Wshadow
-Wunused
-Wformat=2
-Wcast-align
-Wconversion
-Wsign-conversion
-Wnull-dereference
-Wdouble-promotion
)
set(GCC_WARNINGS
${CLANG_WARNINGS}
-Wmisleading-indentation
-Wduplicated-cond
-Wduplicated-branches
-Wlogical-op
)
set(MSVC_WARNINGS
/W4
/permissive-
/w14640 # thread un-safe static member initialization
/w14242 # conversion, possible loss of data
/w14254 # conversion, possible loss of data
/w14263 # member function hides virtual function
/w14265 # class has virtual functions but destructor is not virtual
/w14287 # unsigned/negative constant mismatch
/w14296 # expression is always true/false
)
if(MSVC)
target_compile_options(${target} PRIVATE ${MSVC_WARNINGS})
elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
target_compile_options(${target} PRIVATE ${CLANG_WARNINGS})
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(${target} PRIVATE ${GCC_WARNINGS})
endif()
endfunction()
# Optional: Warnings as Errors for CI
option(WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
function(enable_warnings_as_errors target)
if(WARNINGS_AS_ERRORS)
if(MSVC)
target_compile_options(${target} PRIVATE /WX)
else()
target_compile_options(${target} PRIVATE -Werror)
endif()
endif()
endfunction()Usage
cmake
include(CompilerWarnings)
add_library(mylib src/lib.cpp)
set_project_warnings(mylib)
enable_warnings_as_errors(mylib)🔴 CI/CD MANDATE
-Werror (hoặc /WX trên MSVC) BẮT BUỘC trong CI pipeline. Warnings = bugs waiting to happen.
Sanitizer Build Types
Tạo file cmake/Sanitizers.cmake:
cmake
# cmake/Sanitizers.cmake
# AddressSanitizer, UndefinedBehaviorSanitizer, ThreadSanitizer
option(ENABLE_ASAN "Enable AddressSanitizer" OFF)
option(ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer" OFF)
option(ENABLE_TSAN "Enable ThreadSanitizer" OFF)
option(ENABLE_MSAN "Enable MemorySanitizer (Clang only)" OFF)
function(enable_sanitizers target)
if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" OR
CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(SANITIZER_FLAGS "")
if(ENABLE_ASAN)
list(APPEND SANITIZER_FLAGS -fsanitize=address)
message(STATUS "AddressSanitizer ENABLED for ${target}")
endif()
if(ENABLE_UBSAN)
list(APPEND SANITIZER_FLAGS -fsanitize=undefined)
message(STATUS "UndefinedBehaviorSanitizer ENABLED for ${target}")
endif()
if(ENABLE_TSAN)
if(ENABLE_ASAN)
message(FATAL_ERROR "ASan and TSan cannot be used together!")
endif()
list(APPEND SANITIZER_FLAGS -fsanitize=thread)
message(STATUS "ThreadSanitizer ENABLED for ${target}")
endif()
if(ENABLE_MSAN)
if(NOT CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
message(FATAL_ERROR "MemorySanitizer requires Clang!")
endif()
list(APPEND SANITIZER_FLAGS -fsanitize=memory)
message(STATUS "MemorySanitizer ENABLED for ${target}")
endif()
if(SANITIZER_FLAGS)
# Compile và Link flags
target_compile_options(${target} PRIVATE ${SANITIZER_FLAGS}
-fno-omit-frame-pointer -g)
target_link_options(${target} PRIVATE ${SANITIZER_FLAGS})
endif()
elseif(MSVC)
if(ENABLE_ASAN)
target_compile_options(${target} PRIVATE /fsanitize=address)
message(STATUS "AddressSanitizer ENABLED for ${target} (MSVC)")
endif()
# Note: MSVC has limited sanitizer support
endif()
endfunction()Sanitizer Descriptions
┌─────────────────────────────────────────────────────────────────────────┐
│ SANITIZER CHEAT SHEET │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 🔍 AddressSanitizer (ASan) │
│ ───────────────────────── │
│ Detect: Buffer overflow, use-after-free, memory leaks │
│ Overhead: ~2x slowdown, ~2x memory │
│ Essential for: Any code touching raw pointers │
│ │
│ 🔍 UndefinedBehaviorSanitizer (UBSan) │
│ ───────────────────────────────────── │
│ Detect: Integer overflow, null pointer dereference, shift errors │
│ Overhead: ~1.2x slowdown │
│ Essential for: Numeric heavy code, parsing │
│ │
│ 🔍 ThreadSanitizer (TSan) │
│ ───────────────────────── │
│ Detect: Data races, deadlocks │
│ Overhead: ~5-15x slowdown, ~5-10x memory │
│ Essential for: Multithreaded code │
│ ⚠️ Cannot combine with ASan │
│ │
│ 🔍 MemorySanitizer (MSan) - Clang only │
│ ───────────────────────────────────── │
│ Detect: Uninitialized memory reads │
│ Overhead: ~3x slowdown │
│ Essential for: Security-critical code │
│ │
└─────────────────────────────────────────────────────────────────────────┘Usage
cmake
include(Sanitizers)
add_executable(app src/main.cpp)
enable_sanitizers(app)powershell
# Configure với ASan + UBSan
cmake -B build -DENABLE_ASAN=ON -DENABLE_UBSAN=ON
# Build và run - sanitizers sẽ báo lỗi runtime
cmake --build build
./build/appExample: ASan catches use-after-free
cpp
// bug.cpp - ASan sẽ catch lỗi này
#include <iostream>
int main() {
int* ptr = new int(42);
delete ptr;
// Use-after-free - ASan báo lỗi:
// ==12345==ERROR: AddressSanitizer: heap-use-after-free
std::cout << *ptr << std::endl;
return 0;
}Custom Build Types
cmake
# CMakeLists.txt
# Custom build type for sanitizer builds
# Define sanitizer configurations
set(CMAKE_CONFIGURATION_TYPES
"Debug;Release;RelWithDebInfo;ASan;TSan"
CACHE STRING "" FORCE
)
# ASan configuration
set(CMAKE_CXX_FLAGS_ASAN
"${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fno-omit-frame-pointer"
CACHE STRING "" FORCE
)
set(CMAKE_EXE_LINKER_FLAGS_ASAN
"${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fsanitize=address"
CACHE STRING "" FORCE
)
# TSan configuration
set(CMAKE_CXX_FLAGS_TSAN
"${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=thread -fno-omit-frame-pointer"
CACHE STRING "" FORCE
)
set(CMAKE_EXE_LINKER_FLAGS_TSAN
"${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fsanitize=thread"
CACHE STRING "" FORCE
)powershell
# Build với specific configuration
cmake -B build -DCMAKE_BUILD_TYPE=ASan
cmake --build buildCustom make lint Target
cmake
# Custom target for running clang-tidy manually
find_program(CLANG_TIDY_EXE clang-tidy)
if(CLANG_TIDY_EXE)
add_custom_target(lint
COMMAND ${CLANG_TIDY_EXE}
-p ${CMAKE_BINARY_DIR}
--config-file=${CMAKE_SOURCE_DIR}/.clang-tidy
${CMAKE_SOURCE_DIR}/src/*.cpp
${CMAKE_SOURCE_DIR}/include/**/*.hpp
COMMENT "Running clang-tidy static analysis..."
VERBATIM
)
endif()powershell
# Run lint
cmake --build build --target lintExport Compile Commands for IDE/LSP
cmake
# Always generate compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)File compile_commands.json được tạo trong build/, cần cho:
- clangd (VSCode C++ LSP)
- clang-tidy standalone
- IDE navigation (CLion, etc.)
powershell
# Symlink cho clangd
ln -s build/compile_commands.json compile_commands.json