رفتن به محتوا

سیستم‌های ساخت: CMake و مدیریت بسته

اصول CMake، CMake مدرن مبتنی بر هدف، FetchContent برای وابستگی‌ها، مدیران بسته vcpkg و Conan، و فلگ‌های کامپایلر برای بیلدهای ایمن و بهینه را فرا بگیرید.

چرا سیستم‌های ساخت اهمیت دارند

وقتی پروژه‌های ‎C++‎ فراتر از یک فایل واحد رشد می‌کنند، به یک سیستم ساخت برای مدیریت کامپایل، لینک، وابستگی‌ها و تفاوت‌های پلتفرم نیاز دارید. اجرای دستی g++ *.cpp مقیاس‌پذیر نیست: به بیلدهای افزایشی (فقط فایل‌های تغییریافته را بازکامپایل کنید)، تشخیص کتابخانه، پشتیبانی چندسکویی و بیلدهای تکرارپذیر نیاز دارید. CMake به استاندارد واقعی تولیدکننده سیستم ساخت برای ‎C++‎ تبدیل شده است. خودش کد را کامپایل نمی‌کند — فایل‌های ساخت بومی (Makefiles، فایل‌های Ninja، پروژه‌های Visual Studio) را برای پلتفرم شما تولید می‌کند.

اصول CMake

یک فایل CMakeLists.txt ساختار پروژه شما را توصیف می‌کند. CMake مدرن (3.12+) از رویکرد مبتنی بر هدف استفاده می‌کند که همه چیز به اهداف (فایل‌های اجرایی یا کتابخانه‌ها) متصل است:

CMakeLists.txt
# CMakeLists.txt — minimum viable project
cmake_minimum_required(VERSION 3.20)
project(MyApp VERSION 1.0.0 LANGUAGES CXX)

# Set C++ standard for the entire project
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)  # use -std=c++20, not -std=gnu++20

# Create an executable target
add_executable(myapp
    src/main.cpp
    src/engine.cpp
    src/utils.cpp
)

# Add include directories for this target
target_include_directories(myapp PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

# Create a library target
add_library(mathlib STATIC
    src/math/vector.cpp
    src/math/matrix.cpp
)

target_include_directories(mathlib PUBLIC
    ${CMAKE_CURRENT_SOURCE_DIR}/include/math
)

# Link the library to the executable
target_link_libraries(myapp PRIVATE mathlib)

# Compiler warnings — essential for quality code
target_compile_options(myapp PRIVATE
    $<$<CXX_COMPILER_ID:GNU,Clang>:-Wall -Wextra -Wpedantic -Werror>
    $<$<CXX_COMPILER_ID:MSVC>:/W4 /WX>
)

ساخت با CMake

CMake از یک فرآیند دومرحله‌ای استفاده می‌کند: پیکربندی (تولید فایل‌های ساخت) و ساخت (کامپایل). همیشه از بیلدهای خارج از منبع استفاده کنید تا درخت منبع تمیز بماند:

terminal
# Configure — generate build files in a 'build' directory
cmake -B build -DCMAKE_BUILD_TYPE=Release

# Build — compile the project
cmake --build build --parallel $(nproc)

# Install (optional)
cmake --install build --prefix /usr/local

# Common build types:
# Debug          — -O0 -g (full debug symbols, no optimization)
# Release        — -O3 -DNDEBUG (max optimization, no asserts)
# RelWithDebInfo — -O2 -g -DNDEBUG (optimized with debug info)
# MinSizeRel     — -Os -DNDEBUG (optimize for size)

# Run with sanitizers (add to CMakeLists.txt or command line)
cmake -B build-asan -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer"

FetchContent: مدیریت وابستگی

ماژول FetchContent در CMake وابستگی‌ها را در زمان پیکربندی دانلود و ساخت می‌کند و افزودن کتابخانه‌ها بدون تنظیم دستی را آسان می‌سازد:

CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(MyApp LANGUAGES CXX)

include(FetchContent)

# Fetch GoogleTest
FetchContent_Declare(
    googletest
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG        v1.14.0
)

# Fetch fmt (formatting library)
FetchContent_Declare(
    fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG        10.2.1
)

# Download and make available
FetchContent_MakeAvailable(googletest fmt)

add_executable(myapp src/main.cpp)
target_link_libraries(myapp PRIVATE fmt::fmt)

# Test executable
enable_testing()
add_executable(tests tests/test_main.cpp)
target_link_libraries(tests PRIVATE GTest::gtest_main)
add_test(NAME unit_tests COMMAND tests)

vcpkg و Conan

برای پروژه‌های بزرگ‌تر با وابستگی‌های زیاد، مدیران بسته اختصاصی ‎C++‎ حل وابستگی و کش‌سازی بهتری فراهم می‌کنند:

vcpkg (مایکروسافت) — مستقیماً از طریق فایل toolchain با CMake ادغام می‌شود. بسته‌ها را به‌صورت سراسری یا در «حالت مانیفست» با فایل vcpkg.json نصب کنید:

{
    "dependencies": ["fmt", "spdlog", "nlohmann-json", "catch2"]
}

استفاده با: cmake -B build -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake

Conan — از conanfile.txt یا conanfile.py برای اعلان وابستگی‌ها استفاده می‌کند. از چندین پیکربندی ساخت و کش‌سازی باینری پشتیبانی می‌کند:

[requires]
fmt/10.2.1
spdlog/1.13.0

[generators]
CMakeDeps
CMakeToolchain

هر دو مدیر بسته مخازنی از هزاران کتابخانه ‎C++‎ با باینری‌های از پیش ساخته و تست‌شده نگهداری می‌کنند.

بهترین شیوه

این قوانین را برای CMake قابل نگهداری دنبال کنید:

1. همیشه از دستورات مبتنی بر هدف استفاده کنید: target_include_directories، target_compile_options، target_link_libraries — هرگز include_directories یا add_definitions (که دامنه سراسری را آلوده می‌کنند).
2. PRIVATE/PUBLIC/INTERFACE را درک کنید: PRIVATE = فقط برای این هدف، PUBLIC = برای این هدف و مصرف‌کنندگانش، INTERFACE = فقط برای مصرف‌کنندگان.
3. استاندارد ‎C++‎ را به ازای هدف یا کل پروژه تنظیم کنید، هرگز با فلگ‌های خام مانند -std=c++20.
4. حداکثر هشدارها را فعال کنید: -Wall -Wextra -Wpedantic (GCC/Clang) یا /W4 (MSVC). -Werror / /WX را در CI در نظر بگیرید.
5. از cmake --preset برای بیلدهای تکرارپذیر (CMakePresets.json) استفاده کنید.
6. هرگز مسیرها را به‌صورت ثابت ننویسید — از find_package، FetchContent یا مدیران بسته استفاده کنید.

دام

استفاده از file(GLOB ...) برای جمع‌آوری منابع: file(GLOB SOURCES src/*.cpp) وقتی فایل‌ها اضافه یا حذف می‌شوند دوباره اجرا نمی‌شود. فایل‌های جدید تا زمان پیکربندی مجدد دستی کامپایل نخواهند شد. همیشه فایل‌های منبع را به‌صورت صریح لیست کنید.

فراموش کردن CMAKE_CXX_EXTENSIONS OFF: بدون این، GCC از -std=gnu++20 به‌جای -std=c++20 استفاده می‌کند و افزونه‌های غیراستاندارد را فعال می‌کند که قابلیت حمل را خراب می‌کنند.

مخلوط کردن add_subdirectory و FetchContent: هر دو پروژه‌های خارجی را وارد می‌کنند، اما به‌شکل متفاوتی با کش ساخت تعامل دارند. برای هر وابستگی یک رویکرد را انتخاب کنید.

تنظیم نکردن CMAKE_EXPORT_COMPILE_COMMANDS ON: این فایل compile_commands.json را تولید می‌کند که برای clang-tidy، clangd (LSP) و بسیاری از ادغام‌های IDE مورد نیاز است. همیشه آن را فعال کنید.

نکات کلیدی
  • CMake تولیدکننده استاندارد سیستم ساخت ‎C++‎ است — API مبتنی بر هدف آن را بیاموزید
  • از دستورات target_* (نه دامنه سراسری) با قابلیت مشاهده PRIVATE/PUBLIC/INTERFACE استفاده کنید
  • FetchContent وابستگی‌ها را در زمان پیکربندی دانلود و ساخت می‌کند — ایده‌آل برای مجموعه‌های کوچک وابستگی
  • vcpkg (حالت مانیفست) و Conan گراف‌های وابستگی بزرگ‌تر را با کش‌سازی باینری مدیریت می‌کنند
  • همیشه هشدارها (-Wall -Wextra -Wpedantic) را فعال کنید و از بیلدهای خارج از منبع استفاده کنید
  • فایل compile_commands.json را برای ابزار IDE و تحلیل ایستا تولید کنید

آزمون — دانش خود را بسنجید

(15 امتیاز)

1. `target_link_libraries(myapp PRIVATE mathlib)` چه کاری انجام می‌دهد؟

2. چرا `file(GLOB SOURCES src/*.cpp)` در CMake شیوه بدی محسوب می‌شود؟

3. هدف `CMAKE_CXX_EXTENSIONS OFF` چیست؟