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.