Modern CMake Yapısına Geçiş Deneyimim ve Proje Mimarisi
C++ dünyasında derleme sistemleri uzun süre geliştiricilerin en çok zorlandığı ve kaçındığı konuların başında geldi. Yıllar önce yazılmış CMake projelerine baktığınızda includedirectories(), linkdirectories() ve set(CMAKECXXFLAGS ...) gibi küresel değişkenlerle doldurulmuş, projenin bir yerindeki değişikliğin başka bir hedefi beklenmedik şekilde bozduğu kırılgan yapılara rastlarsınız.
Bu yazıda; projelerimde eski CMake alışkanlıklarını bırakıp modern hedef (Target) odaklı CMake mimarisine geçerken edindiğim tecrübeleri ve temiz bir proje yapısını nasıl kurduğumu özetliyorum.
1. Temel Felsefe: Küresel Değişkenlerden Hedeflere (Targets)
Modern CMake’in (CMake 3.12+) en temel kuralı şudur: Her kütüphane veya çalıştırılabilir dosya bağımsız bir hedeftir (Target) ve kendi gereksinimlerini kendisi taşır.
| Eski CMake (Kaçınılması Gereken) | Modern CMake (Önerilen) |
|---|---|
include_directories(...) | targetincludedirectories(my_target ...) |
link_libraries(...) | targetlinklibraries(my_target ...) |
add_definitions(-DFOO) | targetcompiledefinitions(my_target ...) |
set(CMAKECXXFLAGS "-std=c++17") | targetcompilefeatures(mytarget PRIVATE cxxstd_17) |
Küresel komutlar projede tanımlı tüm hedefleri etkilerken, hedef odaklı komutlar yalnızca ilgili kütüphaneyi ve ona bağımlı olan modülleri etkiler.
2. PUBLIC, PRIVATE ve INTERFACE Ayrımı
Modern CMake’te en çok kafa karıştıran ancak kavrandığında sistemi son derece esnek kılan konu erişim belirteçleridir:
target_include_directories(my_lib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
- PRIVATE: Yalnızca bu kütüphanenin kendi kaynak kodları tarafından kullanılır. Dışarıdan bu kütüphaneyi bağlayan hedeflere aktarılmaz.
- INTERFACE: Bu kütüphanenin kendisi derlenirken gerekmez (örneğin header-only bir kütüphane), ancak bu kütüphaneyi kullanan tüketicilere aktarılır.
- PUBLIC: Hem kütüphanenin kendisi derlenirken kullanılır hem de bu kütüphaneyi bağlayan tüm diğer hedeflere otomatik olarak miras bırakılır.
3. Örnek Modüler Proje Şablonu
Benim standart olarak benimsediğim dizin ve CMake hiyerarşisi:
my_project/
├── CMakeLists.txt # Ana orkestrasyon dosyası
├── include/ # Dışarıya açık başlık dosyaları
│ └── core/
│ └── engine.hpp
├── src/ # İç kaynak kodlar
│ ├── CMakeLists.txt
│ ├── engine.cpp
│ └── internal_helper.cpp
└── tests/ # Birim testler
├── CMakeLists.txt
└── test_engine.cpp
Ana CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)
project(MyEngine VERSION 1.0.0 LANGUAGES CXX)
# Derleme kurallarını standartlaştır
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Alt modülleri ekle
add_subdirectory(src)
# Testler isteğe bağlı derlensin
option(BUILD_TESTS "Testleri derle" OFF)
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
src/CMakeLists.txt:
add_library(core_lib
engine.cpp
internal_helper.cpp
)
target_include_directories(core_lib
PUBLIC
${CMAKE_SOURCE_DIR}/include
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
# Gerekli bağımlılıkları bağla
find_package(Threads REQUIRED)
target_link_libraries(core_lib PRIVATE Threads::Threads)
4. Dış Bağımlılıkları Yönetmek: FetchContent
Projeye harici kütüphaneleri (örneğin nlohmann_json veya googletest) dahil ederken sistem paket yöneticisine bağımlı kalmak yerine CMake’in yerleşik FetchContent modülünü kullanmak derleme sürecini platformdan bağımsız hale getiriyor:
include(FetchContent)
FetchContent_Declare(
json
URL https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xz
)
FetchContent_MakeAvailable(json)
# Doğrudan hedefe bağla
target_link_libraries(core_lib PRIVATE nlohmann_json::nlohmann_json)
5. Çıkarımlar
Modern CMake’e geçmek ilk etapta alışkanlıkları değiştirmeyi gerektirse de, projenin büyümesiyle birlikte gelen derleme karmaşasını ortadan kaldırıyor. Bir kütüphaneyi başka bir projede alt modül olarak kullanmak istediğinizde tek satırlık targetlinklibraries çağrısı tüm include dizinlerini ve derleme bayraklarını otomatik olarak taşıyor.