Tüm eğitimler
TEKNİK REHBER ARAÇLAR CMAKE MAKEFILE

CMake & Makefile —
Build Sistemleri.

Makefile'ın target-rule mantığından CMakeLists.txt'e, cross-compilation toolchain'e ve ccache optimizasyonuna. Embedded Linux mühendisleri için kapsamlı bir build sistemi rehberi.

00 Build system landscape

Build sistemi, derleyici komutlarını otomatize eder, dependency'leri takip eder ve sadece değişen dosyaları yeniden derler.

Bir C/C++ projesini derlemek için en basit yol gcc main.c -o myapp komutunu doğrudan çalıştırmaktır. Ama proje büyüdüğünde — onlarca kaynak dosyası, harici kütüphaneler, farklı platformlar, debug/release konfigürasyonları — bu yaklaşım sürdürülemez. Build sistemi bu problemi çözer:

  Kaynak dosyaları  →  Build sistemi  →  Binary / kütüphane
  (*.c, *.cpp)           (bağımlılık grafiği)     (*.o → *.a / *.so / ELF)
    

Neden build system?

OtomasyonTek komutla tüm projeyi derle. El ile gcc çağrısı yazmak zorunda değilsin.
Dependency trackingSadece değişen dosyaları yeniden derle. 100 dosyalı bir projede bir .c değiştirirsen yalnızca o derlenir.
Incremental buildBüyük projelerde derleme süresi dakikalardan saniyelere düşer. CI/CD pipeline'larında kritik.
Platform bağımsızlığıAynı build tanımı Linux, macOS ve Windows'ta çalışır. Cross-compilation toolchain entegrasyonu kolaylaşır.
TekrarlanabilirlikTüm derleme seçenekleri (flag'ler, define'lar, path'ler) dosyada tanımlı. "Bende çalışıyor" sorunu ortadan kalkar.

Tarihsel timeline

  1976  make          Stuart Feldman — Bell Labs. "Yeterli" ama taşınabilir değil.
  1991  autotools     GNU: autoconf + automake + libtool. Taşınabilir ama karmaşık.
  1999  SCons         Python tabanlı. Güçlü ama yavaş — büyük projelerde ölümcül.
  2000  CMake         Kitware. Generator modeli: platform-native build files üretir.
  2009  Ninja         Chromium'dan. CMake backend olarak — hız odaklı.
  2012  Meson         Python tabanlı sözdizimi, Ninja backend. Modern alternatif.
  2015  Bazel         Google. Hermetic build — monorepo ve büyük ölçek için.
    

Karşılaştırma tablosu

Araç Öğrenme eğrisi Platform Ecosystem Hız
make Düşük–Orta Unix-centric Legacy, geniş Orta
autotools Yüksek Unix/Linux GNU projeler Yavaş
CMake Orta Cross-platform C/C++ dominant Hızlı (Ninja ile)
Meson Düşük–Orta Cross-platform Büyüyor Hızlı
Bazel Çok yüksek Cross-platform Google/monorepo Çok hızlı (cache)

Embedded Linux'ta gerçek hayat

Embedded Linux dünyasında hangi araçların kullanıldığını bilmek, hangi beceriyi önce öğreneceğini belirler:

Yocto / OpenEmbeddedAltta make tabanlı BitBake. Recipe'ler CMake veya autotools projelerini çağırır. Her ikisini de anlamak şart.
Zephyr RTOSCMake zorunlu. Her Zephyr uygulaması CMakeLists.txt ile başlar. west build → cmake --build.
Buildrootmake tabanlı. Upstream paketlerin kendi build sistemini (CMake dahil) wrap eder.
Arduino / PlatformIOmake veya CMake. PlatformIO CMake tabanlıdır; Arduino IDE altında make.
Bare-metal / custom BSPGenellikle Makefile + arm-none-eabi-gcc. CMake ile arm-none-eabi toolchain file yazılır.
ÖNERİ

Hem Makefile hem CMake öğren. Makefile'ı anlamadan CMake'in ne ürettiğini kavrayamazsın. CMake olmadan modern cross-platform veya Zephyr projelerinde çalışamazsın.

Bu bölümde

  • Build sisteminin temel amacı: otomasyon, dependency tracking, incremental build
  • make → autotools → CMake → Meson → Bazel tarihsel sırası
  • Embedded Linux ekosisteminde CMake dominant; Yocto + Zephyr + PlatformIO

01 Makefile temelleri

Makefile, target-prerequisite-recipe üçlüsü üzerine kurulu bir bağımlılık yönetim dilidir.

Temel sözdizimi

Her Makefile kuralı üç parçadan oluşur: hedef (target), ön koşullar (prerequisites) ve tarif (recipe). Recipe satırı mutlaka bir tab ile başlamalıdır — space kullanılamaz.

Makefile — temel kural yapısı
# target: prerequisites
# [TAB]recipe

target: prerequisite1 prerequisite2
	recipe_komutu arg1 arg2

# Örnek: main.c ve utils.c varsa → myapp oluştur
myapp: main.o utils.o
	gcc -o myapp main.o utils.o

main.o: main.c main.h
	gcc -c main.c

utils.o: utils.c utils.h
	gcc -c utils.c
DİKKAT

Recipe satırları tab karakteri ile başlamalıdır. Space ile başlarsa Makefile:N: *** missing separator. Stop. hatası alırsın. Bu, 1976'dan beri gelen tarihsel bir tasarım kararıdır ve hâlâ geçerlidir.

Phony targets

Phony target, karşılığında gerçek bir dosya olmayan sanal hedeftir. .PHONY bildirimi yapılmazsa make, aynı isimde bir dosya olup olmadığını kontrol eder.

Makefile — phony targets
.PHONY: all clean help install

all: myapp
	@echo "Build tamamlandı."

clean:
	rm -f myapp *.o *.d

help:
	@echo "Hedefler: all  clean  install  help"

install: myapp
	install -m 755 myapp /usr/local/bin/

Basit C projesi — tam Makefile

İki kaynak dosyalı (main.c + utils.c) bir C projesi için gerçek bir Makefile:

Makefile
CC      := gcc
CFLAGS  := -Wall -Wextra -std=c11 -O2
TARGET  := myapp
SRCS    := main.c utils.c
OBJS    := $(SRCS:.c=.o)

.PHONY: all clean

all: $(TARGET)

# Link: .o dosyalarından binary oluştur
$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^

# Compile: her .c → .o
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f $(TARGET) $(OBJS)

Otomatik header dependency tracking

Bir header dosyası değiştiğinde, onu include eden tüm .c dosyalarının yeniden derlenmesi gerekir. Bunu otomatik yapmak için gcc'nin -MMD flag'ini kullan:

Makefile — .d dosyaları ile dependency tracking
CC      := gcc
CFLAGS  := -Wall -Wextra -std=c11 -O2
TARGET  := myapp
SRCS    := main.c utils.c
OBJS    := $(SRCS:.c=.o)
DEPS    := $(SRCS:.c=.d)   # her .c için bir .d dependency dosyası

.PHONY: all clean

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) -o $@ $^

# -MMD: .d dosyası üret; -MP: sahte hedef ekle (eksik header için hata verme)
%.o: %.c
	$(CC) $(CFLAGS) -MMD -MP -c $< -o $@

# Üretilen .d dosyalarını dahil et
-include $(DEPS)

clean:
	rm -f $(TARGET) $(OBJS) $(DEPS)
NOT

-include $(DEPS) satırındaki tire (-) öneki, .d dosyaları henüz yoksa (ilk derleme) make'in hata vermesini engeller. İlk derlemeden sonra her derleme aşamasında .d dosyaları güncellenir.

Bu bölümde

  • target: prerequisites → [TAB]recipe sözdizimi
  • Tab zorunluluğu — space çalışmaz, tarihsel bir kural
  • .PHONY: all, clean, help, install tanımları
  • -MMD -MP ile otomatik header dependency tracking

02 Makefile değişkenler ve fonksiyonlar

Makefile değişkenleri ve built-in fonksiyonlar, büyük projeleri bakımı kolay hale getirir.

Atama operatörleri

VAR = valRecursive (gecikmeli) atama. Her kullanımda yeniden değerlendirilir. Döngüsel referanslara dikkat.
VAR := valSimple (anlık) atama. Değer hemen değerlendirilir. Genellikle önerilen form — tahmin edilebilir davranış.
VAR += valAppend. Mevcut değere ekler. CFLAGS += -DDEBUG yaygın kullanım.
VAR ?= valConditional. Değişken henüz tanımlı değilse atar. Komut satırından override edilebilir varsayılan değerler için ideal.
Makefile — atama farkları
# Recursive: CFLAGS her kullanımda $(EXTRA) ile genişler
CFLAGS  = -Wall $(EXTRA)

# Simple: OBJS anında hesaplanır, sonraki SRCS değişikliği etkilemez
OBJS    := $(SRCS:.c=.o)

# Append: mevcut değere ekle
CFLAGS  += -O2

# Conditional: komut satırından make CC=clang ile override edilebilir
CC      ?= gcc

Önemli built-in değişkenler

CCC derleyicisi. Default: cc. Embedded: arm-linux-gnueabihf-gcc
CXXC++ derleyicisi. Default: g++.
CFLAGSC derleyici flag'leri. Tüm .c dosyaları için geçerli.
CXXFLAGSC++ derleyici flag'leri.
LDFLAGSLinker flag'leri. Örn: -L/opt/lib -Wl,-rpath,/opt/lib
LDLIBSLinker kütüphane adları. Örn: -lpthread -lm -lssl
MAKERecursive make çağrısı için. Alt dizinde $(MAKE) -C subdir kullan.

Automatic variables

Pattern rule içinde kullanılan özel değişkenler — her kural için otomatik doldurulur:

$@Hedefin (target) adı. gcc -o $@ ... → binary adını yazar.
$<İlk prerequisite. Pattern rule'da genellikle kaynak .c dosyası.
$^Tüm prerequisite'lar (tekrarsız). Link adımında tüm .o dosyalarını geçirmek için.
$?Hedeften daha yeni olan prerequisite'lar. Incremental operasyonlar için.
$*Pattern rule'daki stem (% ile eşleşen kısım). %.o: %.c → main.c için $* = main.
$(@D) / $(@F)Target'ın dizin / dosya adı kısmı.

Built-in fonksiyonlar

Makefile — fonksiyon örnekleri
# wildcard: glob ile dosya listesi
SRCS    := $(wildcard src/*.c)

# patsubst: pattern substitution — .c → .o
OBJS    := $(patsubst %.c,%.o,$(SRCS))

# notdir: path'den dosya adını çıkar
FILENAMES := $(notdir $(SRCS))   # src/main.c → main.c

# addprefix: her öğeye prefix ekle
OBJS    := $(addprefix build/,$(notdir $(SRCS:.c=.o)))

# dir: path kısmı
DIRS    := $(dir $(SRCS))   # src/main.c → src/

# shell: shell komutu çalıştır, çıktısını al
GIT_HASH := $(shell git rev-parse --short HEAD)

# subst: literal string ikamesi
REL_SRCS := $(subst src/,,$(SRCS))   # src/ prefix'ini kaldır

# filter / filter-out: listeyi filtrele
C_SRCS  := $(filter %.c,$(ALL_SRCS))
NO_TEST := $(filter-out %_test.c,$(SRCS))

Multi-directory proje Makefile

Makefile — çok dizinli proje
CC      := gcc
CFLAGS  := -Wall -Wextra -std=c11 -Iinclude
LDFLAGS :=
LDLIBS  := -lm
TARGET  := build/myapp

# src/ ve src/platform/ altındaki tüm .c dosyaları
SRCS    := $(wildcard src/*.c) $(wildcard src/platform/*.c)

# src/foo.c → build/foo.o
OBJS    := $(patsubst src/%.c,build/%.o,$(SRCS))
DEPS    := $(OBJS:.o=.d)

.PHONY: all clean dirs

all: dirs $(TARGET)

dirs:
	@mkdir -p build/platform

$(TARGET): $(OBJS)
	$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)

build/%.o: src/%.c
	$(CC) $(CFLAGS) -MMD -MP -c $< -o $@

-include $(DEPS)

clean:
	rm -rf build/

Bu bölümde

  • = (recursive) vs := (simple) vs += (append) vs ?= (conditional) farkları
  • CC, CXX, CFLAGS, CXXFLAGS, LDFLAGS, LDLIBS built-in değişkenleri
  • $@, $<, $^, $* automatic variables
  • wildcard, patsubst, notdir, addprefix, shell fonksiyonları

03 CMake temelleri

CMake, platform-native build dosyaları üreten bir meta-build sistemidir — doğrudan derlemez, Makefile veya Ninja dosyaları üretir.

CMake generator modeli

  CMakeLists.txt  →  cmake (configure)  →  Makefile / Ninja / .sln / Xcode
                                              ↓
                                         make / ninja / VS (build)
                                              ↓
                                         binary / kütüphane
    

Bu iki aşamalı model sayesinde aynı CMakeLists.txt ile Linux'ta Makefile, Windows'ta Visual Studio solution, macOS'ta Xcode projesi üretebilirsin.

Minimum CMakeLists.txt

CMakeLists.txt
# CMake minimum versiyonu — kullanılan özelliklere göre belirle
cmake_minimum_required(VERSION 3.21)

# Proje adı, versiyonu ve kullanılan diller
project(MyProject
  VERSION 1.0.0
  LANGUAGES C CXX
)

# Executable tanımla
add_executable(myapp
  src/main.c
  src/utils.c
)

Out-of-source build

CMake projeleri kaynak ağacı dışında derlenir. Build dosyaları kaynak kodla karışmaz:

bash — out-of-source build
# build/ dizini oluştur ve içine gir
mkdir build && cd build

# CMake ile configure: üst dizindeki CMakeLists.txt'i işle
cmake ..

# Derleme — platform-native aracı çağırır (make veya ninja)
cmake --build .

# Temizle
cmake --build . --target clean

# Paralel build (tüm CPU çekirdeklerini kullan)
cmake --build . --parallel $(nproc)

# Alternatif: build/ dizininden çıkmadan configure + build
cmake -S . -B build
cmake --build build

Ninja generator — daha hızlı

bash — Ninja generator
# Ninja'yı yükle (Ubuntu/Debian)
sudo apt install ninja-build

# Ninja generator ile configure
cmake -G "Ninja" -S . -B build-ninja

# Build — cmake --build ninja'yı otomatik çağırır
cmake --build build-ninja

# Mevcut generator'ları listele
cmake --help | grep "Generators" -A 30

CMake cache ve değişkenler

CMake ilk configure sırasında CMakeCache.txt oluşturur. Sonraki çalıştırmalarda cache'den okur:

bash — CMake cache değişkenleri
# -D ile cache değişkeni set et
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake -DCMAKE_INSTALL_PREFIX=/opt/myapp ..
cmake -DMYAPP_ENABLE_TESTS=ON ..

# Birden fazla -D bir arada
cmake \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_INSTALL_PREFIX=/opt/myapp \
  -DBUILD_SHARED_LIBS=OFF \
  ..

# Cache'i sil ve yeniden configure (clean configure)
rm -rf build/ && cmake -S . -B build

TUI ve GUI araçlar

ccmake ..Terminal tabanlı CMake cache editörü. Tüm değişkenleri listeler, değiştirebilirsin. apt install cmake-curses-gui
cmake-guiQt tabanlı grafik arayüz. Windows'ta yaygın. Cross-platform. apt install cmake-qt-gui
cmake --presetCMakePresets.json ile önceden tanımlanmış konfigürasyonlar. CMake 3.19+ ile geldi.
NOT

CMakeCache.txt'i elle düzenleme. Bunun yerine cmake -D veya ccmake kullan. Cache bozulursa build/ dizinini tamamen sil ve yeniden configure et.

Bu bölümde

  • CMake iki aşamalıdır: configure (cmake) → build (make/ninja)
  • cmake -S . -B build ile out-of-source build
  • cmake -G "Ninja" ile Ninja generator — paralel build hızı
  • cmake -DVAR=val ile CMakeCache.txt üzerinden değişken set etme

04 Targets ve library türleri

CMake'de her şey target etrafında döner — executable, library veya custom target.

Executable ve library tanımları

CMakeLists.txt — target türleri
# ── Executable ──────────────────────────────────────────────
add_executable(myapp
  src/main.c
  src/app.c
)

# ── Static library (.a) ─────────────────────────────────────
# Derleme zamanında binary'e gömülür. Runtime'da bağımlılık yok.
add_library(mylib_static STATIC
  src/utils.c
  src/parser.c
)

# ── Shared library (.so / .dll) ─────────────────────────────
# Runtime'da dinamik yüklenir. ABI uyumluluğu önemli.
add_library(mylib_shared SHARED
  src/utils.c
  src/parser.c
)

# ── Object library (.o dosya koleksiyonu) ───────────────────
# Static ve shared arasında derlenmeden paylaşılır.
add_library(mylib_obj OBJECT
  src/utils.c
  src/parser.c
)

# ── Interface library (header-only) ─────────────────────────
# Compile edilecek kaynak yok; sadece usage requirements taşır.
add_library(mylib_iface INTERFACE)
target_include_directories(mylib_iface INTERFACE include/)

target_link_libraries ve visibility

CMake 3.x'in en güçlü özelliği: her target kendi bağımlılıklarını bilir ve bunları downstream'e aktarıp aktarmayacağını belirler.

PRIVATEBağımlılık yalnızca bu target için geçerli. Downstream'e aktarılmaz. Implementation detail.
PUBLICHem bu target hem de bu target'a bağlanan diğer target'lar için geçerli. API bağımlılıkları için.
INTERFACEBu target'ın kendisi kullanmaz; yalnızca downstream için geçerli. Header-only kütüphaneler için.
CMakeLists.txt — target_link_libraries visibility
# mylib: libssl'i kullanır ama dışarı aktarmaz (PRIVATE)
add_library(mylib STATIC src/crypto.c)
target_link_libraries(mylib
  PRIVATE  OpenSSL::SSL      # sadece mylib'in implementation'ı kullanır
  PUBLIC   mylib_headers     # mylib de kullanır, mylib'e bağlananlar da
)

# myapp: mylib'e bağlanır → OpenSSL transitif DEĞİL gelir (PRIVATE)
add_executable(myapp src/main.c)
target_link_libraries(myapp
  PRIVATE  mylib
)

Library seçim rehberi — embedded context

TürÇıktıNe zaman
STATIC.aEmbedded target, runtime bağımlılık istemiyorsun
SHARED.soPlugin, büyük kütüphane, ABI kararlılığı gerekli
OBJECT.o koleksiyonuOrtak kodu hem static hem shared'e gömeceksin
INTERFACEHeader-only template kütüphanesi, pure CMake hedef
NOT

Embedded sistemlerde genellikle STATIC tercih edilir — deployment sırasında ayrı .so dosyası taşıma gerekliliği ortadan kalkar. Rootfs boyutu kritikse shared da kullanılabilir.

Bu bölümde

  • add_executable, add_library (STATIC, SHARED, OBJECT, INTERFACE)
  • PRIVATE / PUBLIC / INTERFACE visibility modeli
  • Embedded'de STATIC tercih sebebi

05 find_package ve bağımlılıklar

Dış kütüphaneleri bulmanın ve projeye eklemenin birden fazla yolu vardır — find_package, FetchContent ve ExternalProject.

find_package — sistem kütüphanesi bul

CMakeLists.txt — find_package
# REQUIRED: bulunamazsa hata ver ve dur
find_package(OpenSSL REQUIRED)
find_package(Threads REQUIRED)
find_package(ZLIB REQUIRED)

# OPTIONAL: bulunamazsa devam et, sonraki if ile kontrol et
find_package(OpenMP)
if(OpenMP_FOUND)
  target_link_libraries(myapp PRIVATE OpenMP::OpenMP_C)
endif()

# Versiyon gereksinimi
find_package(Boost 1.75 REQUIRED COMPONENTS filesystem system)

# Imported target ile kullan
target_link_libraries(myapp
  PRIVATE
    OpenSSL::SSL
    OpenSSL::Crypto
    Threads::Threads
    ZLIB::ZLIB
)

Config-mode vs Module-mode

Module-modeCMake'in kendi Find<Package>.cmake modülleri. cmake --help-module-list ile listele. Eski kütüphaneler bu yolla bulunur.
Config-modeKütüphanenin kendi <Package>Config.cmake dosyası. Modern kütüphaneler (OpenSSL 3.x, Boost 1.70+) bu yolu sağlar. Imported target'lar buradan gelir.

pkg-config entegrasyonu

CMakeLists.txt — pkg-config
find_package(PkgConfig REQUIRED)

# pkg_check_modules: pkg-config ile kütüphane bul
pkg_check_modules(LIBCURL REQUIRED libcurl)
pkg_check_modules(GLIB2 REQUIRED glib-2.0>=2.50)

target_include_directories(myapp PRIVATE ${LIBCURL_INCLUDE_DIRS})
target_link_libraries(myapp PRIVATE ${LIBCURL_LIBRARIES})
target_compile_options(myapp PRIVATE ${LIBCURL_CFLAGS_OTHER})

FetchContent — bağımlılığı kaynak koddan indir

Sistem kütüphanesi olmayan veya belirli versiyon gerektiren bağımlılıklar için FetchContent kullan. Build zamanında indirir ve configure eder:

CMakeLists.txt — FetchContent
include(FetchContent)

# nlohmann/json — header-only JSON kütüphanesi
FetchContent_Declare(
  json
  URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
  URL_HASH SHA256=d6c65aca6b1ed68e7a182f4757257b107ae403032760ed6ef121c9d55e81757d
)

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

# İndir + configure + kaynak ağacına ekle
FetchContent_MakeAvailable(json googletest)

# Artık imported target olarak kullanılabilir
target_link_libraries(myapp PRIVATE nlohmann_json::nlohmann_json)
target_link_libraries(mytest PRIVATE GTest::gtest_main)

ExternalProject_Add — kısa özet

NOT

ExternalProject_Add, FetchContent'ten farklı olarak bağımlılığı build aşamasında (configure değil) indirir ve ayrı bir build dizininde derler. Cross-compilation ve non-CMake projeleri (autotools vb.) için tercih edilir. FetchContent ise kaynak ağacına dahil edildiği için CMake target'ları doğrudan kullanılabilir.

Bu bölümde

  • find_package: sistem kütüphanesi, REQUIRED / OPTIONAL, versiyon
  • Config-mode (Package-Config.cmake) vs Module-mode (FindPackage.cmake)
  • pkg_check_modules ile pkg-config entegrasyonu
  • FetchContent_Declare + FetchContent_MakeAvailable — git veya URL'den indirme

06 target_* direktifleri ve properties

Modern CMake'de her şey target property'leri üzerinden yönetilir — global değişkenler yerine target-scoped direktifler kullan.

target_include_directories

CMakeLists.txt — include directories
# include/ klasörünü bu target için arama yoluna ekle
target_include_directories(myapp
  PRIVATE   include/                  # sadece myapp kullanır
  PUBLIC    ${CMAKE_CURRENT_SOURCE_DIR}/include  # myapp + bağlananlar
  INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/api      # sadece bağlananlar
)

# Generator expression ile kaynak vs install ayrımı
target_include_directories(mylib
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

target_compile_options

CMakeLists.txt — compile options
target_compile_options(myapp
  PRIVATE
    -Wall
    -Wextra
    -Wpedantic
    -Werror             # uyarıları hata yap
    -fstack-protector-strong
    $<$<CONFIG:Debug>:-g -O0>
    $<$<CONFIG:Release>:-O3 -DNDEBUG>
)

# Derleyici bazlı koşullu flag (Clang vs GCC)
target_compile_options(myapp PRIVATE
  $<$<C_COMPILER_ID:Clang>:-Weverything>
  $<$<C_COMPILER_ID:GNU>:-Wformat=2>
)

target_compile_definitions

CMakeLists.txt — compile definitions
target_compile_definitions(myapp
  PRIVATE
    DEBUG=1
    VERSION="${PROJECT_VERSION}"
    PLATFORM_LINUX
    $<$<CONFIG:Debug>:ENABLE_LOGGING>    # sadece Debug'da
)

# Makefile'daki -DFOO=1 eşdeğeri
# target_compile_definitions, -D prefix'ini kendisi ekler

target_sources — kaynak ekleme

CMakeLists.txt — target_sources
add_executable(myapp src/main.c)

# Sonradan kaynak ekle (koşullu platform dosyaları için ideal)
target_sources(myapp PRIVATE
  src/utils.c
  src/parser.c
)

if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
  target_sources(myapp PRIVATE src/platform/linux.c)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
  target_sources(myapp PRIVATE src/platform/macos.c)
endif()

set_target_properties ve get_target_property

CMakeLists.txt — target properties
# C/C++ standart versiyonunu zorla
set_target_properties(myapp PROPERTIES
  C_STANDARD          11
  C_STANDARD_REQUIRED ON
  C_EXTENSIONS        OFF
  CXX_STANDARD        17
  CXX_STANDARD_REQUIRED ON
  OUTPUT_NAME         "myapp-v${PROJECT_VERSION}"  # binary adını değiştir
  RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)

# Property oku
get_target_property(MY_SOURCES myapp SOURCES)
message(STATUS "Sources: ${MY_SOURCES}")

# Shared library versiyonlama
set_target_properties(mylib PROPERTIES
  VERSION   ${PROJECT_VERSION}
  SOVERSION ${PROJECT_VERSION_MAJOR}
)
DİKKAT

Eski CMake kılavuzlarında include_directories(), add_compile_options() ve add_definitions() gibi global direktifler görürsün. Bunlar tüm sonraki target'ları etkiler ve büyük projelerde istenmeyen yan etkiler yaratır. Modern CMake'de her zaman target_* versiyonlarını kullan.

Bu bölümde

  • target_include_directories — PRIVATE / PUBLIC / INTERFACE visibility
  • target_compile_options — -Wall, -O2, debug/release generator expression
  • target_compile_definitions — -DFOO=1 eşdeğeri
  • target_sources — koşullu platform kaynak ekleme
  • set_target_properties — C_STANDARD, OUTPUT_NAME, SOVERSION

07 Cross compilation

Cross compilation, hedef platforma (ARM, MIPS, RISC-V) farklı bir host'ta (x86_64 Linux) binary üretmektir — CMake toolchain file ile sistematik şekilde yönetilir.

Toolchain file nedir?

Toolchain file, CMake'e hangi derleyiciyi, hangi sysroot'u ve hangi arama yollarını kullanacağını söyleyen özel bir CMake script'idir. CMAKE_TOOLCHAIN_FILE değişkeniyle belirtilir ve configure aşamasının en başında okunur.

cmake/arm-linux-gnueabihf.cmake
# Hedef sistem bilgisi
set(CMAKE_SYSTEM_NAME    Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

# Cross compiler — PATH'te olmalı
set(CMAKE_C_COMPILER   arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
set(CMAKE_ASM_COMPILER arm-linux-gnueabihf-as)

# Sysroot: hedef sistemin rootfs kopyası
set(CMAKE_SYSROOT /opt/sysroot/arm-linux-gnueabihf)

# find_* komutlarının arama davranışı
set(CMAKE_FIND_ROOT_PATH  /opt/sysroot/arm-linux-gnueabihf)

# HOST araçları (cmake, python) host'ta ara
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)

# Kütüphaneleri ve header'ları sadece sysroot'ta ara
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

# ARM Cortex-A için optimize flag'ler (opsiyonel)
set(CMAKE_C_FLAGS_INIT   "-march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard")
set(CMAKE_CXX_FLAGS_INIT "-march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard")

Toolchain file ile configure ve build

bash — cross compile
# Toolchain yükle (Debian/Ubuntu)
sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf

# Ayrı bir build dizini kullan — host build'dan ayrı tut
cmake \
  -S . \
  -B build-arm \
  -DCMAKE_TOOLCHAIN_FILE=cmake/arm-linux-gnueabihf.cmake \
  -DCMAKE_BUILD_TYPE=Release

cmake --build build-arm --parallel $(nproc)

# Çıktıyı doğrula
file build-arm/myapp
# → build-arm/myapp: ELF 32-bit LSB executable, ARM, EABI5 version 1

# QEMU ile test (opsiyonel)
qemu-arm -L /opt/sysroot/arm-linux-gnueabihf build-arm/myapp

CMAKE_FIND_ROOT_PATH_MODE değerleri

NEVERfind_program için kullanılır. Host araçlarını (cmake, python, node) host'ta ara — sysroot'ta arama.
ONLYfind_library / find_include için. Yalnızca sysroot ve CMAKE_FIND_ROOT_PATH içinde ara. Cross-compilation'da doğru kütüphane bulunmasını garanti eder.
BOTHHem sysroot hem host'ta ara. Genellikle istenmez — cross ve host kütüphaneleri karışabilir.

Raspberry Pi için toolchain

cmake/rpi4-aarch64.cmake
set(CMAKE_SYSTEM_NAME    Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)

set(CMAKE_C_COMPILER   aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)

# Raspberry Pi OS sysroot (rsync ile RPi'den alınmış)
set(CMAKE_SYSROOT /opt/rpi4-sysroot)
set(CMAKE_FIND_ROOT_PATH /opt/rpi4-sysroot)

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

# Cortex-A72 (RPi4) optimizasyonu
set(CMAKE_C_FLAGS_INIT "-mcpu=cortex-a72 -mtune=cortex-a72")

Yocto SDK entegrasyonu

Yocto, cross-compilation ortamını otomatik kuran bir SDK (Software Development Kit) üretir. Bu SDK bir CMake toolchain file içerir:

bash — Yocto SDK ile build
# SDK'yı yükle (Yocto'dan üretilen installer)
./poky-glibc-x86_64-core-image-minimal-aarch64-qemuarm64-toolchain-4.3.sh

# SDK ortamını aktif et
source /opt/poky/4.3/environment-setup-aarch64-poky-linux

# SDK'nın sağladığı toolchain file ile CMake çalıştır
cmake \
  -DCMAKE_TOOLCHAIN_FILE=$OECORE_NATIVE_SYSROOT/usr/share/cmake/OEToolchainConfig.cmake \
  -S . -B build-yocto

cmake --build build-yocto

Bu bölümde

  • CMAKE_TOOLCHAIN_FILE ile cross compiler, sysroot ve arama modları
  • CMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER, LIBRARY/INCLUDE=ONLY
  • Raspberry Pi aarch64 ve Yocto SDK entegrasyon örnekleri
  • file build-arm/myapp ile ELF doğrulama

08 Generator expressions

Generator expression'lar, CMake'in configure aşamasında değil build/install aşamasında değerlendirilen koşullu ifadelerdir.

Normal CMake değişkenleri configure zamanında değerlendirilir. Ama bazı şeyler ancak o anda bilinir: build tipi (Debug/Release), derleyici ID'si, hedef platform. Generator expression'lar bu bilgilere erişim sağlar.

Temel sözdizimi

CMakeLists.txt — generator expression sözdizimi
# $<condition>  →  1 veya 0
$<BOOL:val>           # val boş veya 0 değilse 1
$<CONFIG:Debug>       # build tipi Debug ise 1
$<PLATFORM_ID:Linux>  # platform Linux ise 1
$<C_COMPILER_ID:GNU>  # C derleyicisi GCC ise 1

# $<IF:condition,then,else>
$<IF:$<CONFIG:Debug>,-g,-O3>

# $<condition:value>  →  condition=1 ise value, 0 ise boş string
$<$<CONFIG:Debug>:-fsanitize=address>

Sık kullanılan generator expression'lar

$<CONFIG:Debug>Build tipi Debug ise 1. cmake -DCMAKE_BUILD_TYPE=Debug ile kontrol edilir.
$<TARGET_FILE:tgt>target'ın tam binary path'i. install() ve test komutlarında faydalı.
$<TARGET_FILE_DIR:tgt>target binary'sinin dizini.
$<COMPILE_LANGUAGE:C>Derlenen dosya C ise 1. Karışık C/C++ projelerinde dile özgü flag eklemek için.
$<BUILD_INTERFACE:path>Kaynak ağacından build ederken geçerli include path. Install sonrasında kullanılmaz.
$<INSTALL_INTERFACE:path>Install edilen kütüphane için geçerli include path.
$<LINK_ONLY:lib>Yalnızca link aşamasında bağla, usage requirement olarak aktarma.

Debug / Release koşullu flag'ler

CMakeLists.txt — Debug'da ASan, Release'de -O3
target_compile_options(myapp PRIVATE
  # Debug: adres sanitizer + debug semboller, optimizasyon yok
  $<$<CONFIG:Debug>:
    -g
    -O0
    -fsanitize=address
    -fsanitize=undefined
    -fno-omit-frame-pointer
  >

  # Release: maksimum optimizasyon
  $<$<CONFIG:Release>:
    -O3
    -DNDEBUG
    -ffunction-sections
    -fdata-sections
  >

  # RelWithDebInfo: release + debug semboller
  $<$<CONFIG:RelWithDebInfo>:
    -O2
    -g
    -DNDEBUG
  >
)

# Linker flag: ASan için link etmek gerekiyor
target_link_options(myapp PRIVATE
  $<$<CONFIG:Debug>:-fsanitize=address -fsanitize=undefined>
)

# Kullanım: cmake -DCMAKE_BUILD_TYPE=Debug -S . -B build-debug

Dile göre flag (C vs C++)

CMakeLists.txt — dile özgü flag
# Karma C/C++ projesinde dile özgü standart flag'ler
target_compile_options(myapp PRIVATE
  $<$<COMPILE_LANGUAGE:C>:   -std=c11   -Wstrict-prototypes>
  $<$<COMPILE_LANGUAGE:CXX>: -std=c++17 -Wno-deprecated>
)

# TARGET_FILE: test komutunda binary path'i dinamik al
add_test(NAME mytest
  COMMAND $<TARGET_FILE:myapp> --test
)

Bu bölümde

  • Generator expression'lar configure zamanı değil, build zamanı değerlendirilir
  • $<CONFIG:Debug>, $<IF:cond,then,else>, $<COMPILE_LANGUAGE:C>
  • Debug'da -fsanitize=address, Release'de -O3 — koşullu flag örneği
  • $<BUILD_INTERFACE:path> vs $<INSTALL_INTERFACE:path>

09 ccache ve build optimizasyonu

ccache, derleme çıktısını önbelleğe alır — aynı kaynak kodu yeniden derlediğinde önbellekten döner ve derleme süresini dramatik düşürür.

ccache nedir?

ccache (Compiler Cache) şeffaf bir derleme önbelleğidir. Girdi (kaynak dosyası + flag'ler + header'lar) değişmediyse önceki derlemenin .o çıktısını doğrudan döner. Büyük projelerde full rebuild süresini %80-95 düşürebilir.

bash — ccache kurulum ve temel kullanım
# Yükle
sudo apt install ccache

# Versiyon ve yol
ccache --version
which ccache   # → /usr/bin/ccache

# İstatistikler
ccache -s

# Cache'i temizle
ccache -C

# Cache boyutunu ayarla (default 5GB)
ccache -M 10G

CMake entegrasyonu — CMake 3.4+

CMakeLists.txt — ccache entegrasyonu
# Yöntem 1: CMAKE_*_COMPILER_LAUNCHER (önerilen, CMake 3.4+)
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
  set(CMAKE_C_COMPILER_LAUNCHER   ${CCACHE_PROGRAM})
  set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
  message(STATUS "ccache found: ${CCACHE_PROGRAM}")
endif()

# Yöntem 2: global set (CMakeLists.txt'e sabit yaz)
set(CMAKE_C_COMPILER_LAUNCHER   ccache)
set(CMAKE_CXX_COMPILER_LAUNCHER ccache)

# Yöntem 3: komut satırından
# cmake -DCMAKE_C_COMPILER_LAUNCHER=ccache ...

ccache -s — istatistik okuma

bash — ccache istatistik çıktısı
ccache -s
# Çıktı örneği:
# Summary:
#   Hits:            892 / 1024 (87.1%)
#     Direct:        712
#     Preprocessed:  180
#   Misses:          132
#     Direct:        105
#     Preprocessed:   27
# Primary storage:
#   Cache size (GiB):  2.14 / 10.00
#   Files:           4816

Ninja vs Make — paralel build hızı

Ninja, make'e kıyasla daha az overhead'e sahip bir build sistemidir. Özellikle incremental build ve büyük projelerde fark belirginleşir:

make -j$(nproc)GNU Make: paralel build için -j flag'i gerekir. Dependency analizi biraz yavaş.
ninjaParalel build varsayılan. Startup overhead daha düşük. Büyük projelerde %20-40 daha hızlı.
cmake --build . --parallelHem make hem ninja'yı otomatik paralele taşır. Taşınabilir çözüm.

Precompiled headers (PCH)

CMakeLists.txt — precompiled headers
# CMake 3.16+: target_precompile_headers
target_precompile_headers(myapp PRIVATE
  include/pch.h           # sık değişmeyen, büyük header'lar buraya
  <vector>
  <string>
  <unordered_map>
)

# Birden fazla target arasında PCH paylaş
target_precompile_headers(mylib2 REUSE_FROM mylib)

Unity build

CMakeLists.txt — Unity build
# CMake 3.16+: UNITY_BUILD — tüm .c dosyalarını tek bir compilation unit'e toplar
set_target_properties(myapp PROPERTIES
  UNITY_BUILD ON
)

# Batch boyutu: kaç dosyayı tek compilation unit'e koy
set_target_properties(myapp PROPERTIES
  UNITY_BUILD_BATCH_SIZE 8
)
DİKKAT

Unity build, static global değişkenlerin ve anonim namespace'lerin davranışını değiştirebilir. Önce projenin unity build ile düzgün derlenip derlenmediğini doğrula.

Bu bölümde

  • ccache: derleme çıktısını önbellekle, full rebuild süresini %80+ düşür
  • CMAKE_C_COMPILER_LAUNCHER=ccache ile CMake entegrasyonu
  • ccache -s ile hit/miss istatistikleri
  • target_precompile_headers (CMake 3.16+) ve UNITY_BUILD property

10 Pratik: eksiksiz C projesi

Library, executable, testler, install ve paketleme içeren tam bir CMake projesi — gerçek dünya yapısı.

Proje yapısı

bash — proje dizin yapısı
myproject/
├── CMakeLists.txt          # Ana CMake dosyası
├── cmake/
│   ├── arm-linux-gnueabihf.cmake
│   └── myproject-config.cmake.in
├── include/
│   └── myproject/
│       └── core.h          # Public API header'ları
├── src/
│   ├── core.c              # Library kaynak kodu
│   └── main.c              # Executable entry point
└── tests/
    ├── CMakeLists.txt
    └── test_core.c

Ana CMakeLists.txt

CMakeLists.txt — ana dosya
cmake_minimum_required(VERSION 3.21)

project(myproject
  VERSION     1.2.0
  DESCRIPTION "Embedded Linux utility library"
  LANGUAGES   C
)

# ── ccache ──────────────────────────────────────────────────
find_program(CCACHE ccache)
if(CCACHE)
  set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
endif()

# ── Build type varsayılanı ───────────────────────────────────
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()

# ── Options ─────────────────────────────────────────────────
option(MYPROJECT_BUILD_TESTS   "Build unit tests"   ON)
option(MYPROJECT_BUILD_SHARED  "Build shared lib"  OFF)

# ── Library ─────────────────────────────────────────────────
set(LIB_TYPE STATIC)
if(MYPROJECT_BUILD_SHARED)
  set(LIB_TYPE SHARED)
endif()

add_library(myproject_core ${LIB_TYPE}
  src/core.c
)

target_include_directories(myproject_core
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

target_compile_options(myproject_core PRIVATE
  -Wall -Wextra -Wpedantic
  $<$<CONFIG:Debug>:-g -O0 -fsanitize=address>
  $<$<CONFIG:Release>:-O2>
)

set_target_properties(myproject_core PROPERTIES
  C_STANDARD          11
  C_STANDARD_REQUIRED ON
  VERSION             ${PROJECT_VERSION}
  SOVERSION           ${PROJECT_VERSION_MAJOR}
)

# ── Executable ──────────────────────────────────────────────
add_executable(myapp src/main.c)
target_link_libraries(myapp PRIVATE myproject_core)

# ── Tests ───────────────────────────────────────────────────
if(MYPROJECT_BUILD_TESTS)
  enable_testing()
  add_subdirectory(tests)
endif()

# ── Install ─────────────────────────────────────────────────
include(GNUInstallDirs)

install(TARGETS myproject_core myapp
  EXPORT  myproject-targets
  ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
  LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

install(DIRECTORY include/myproject
  DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

install(EXPORT myproject-targets
  FILE        myproject-targets.cmake
  NAMESPACE   myproject::
  DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/myproject
)

tests/CMakeLists.txt — CTest entegrasyonu

tests/CMakeLists.txt
add_executable(test_core test_core.c)
target_link_libraries(test_core PRIVATE myproject_core)

# CTest'e test ekle
add_test(
  NAME    core_unit_test
  COMMAND $<TARGET_FILE:test_core>
)

# Test ortam değişkenleri
set_tests_properties(core_unit_test PROPERTIES
  ENVIRONMENT "MYAPP_LOG_LEVEL=debug"
  TIMEOUT     30
)

Build, test ve install

bash — full workflow
# Configure
cmake \
  -S . -B build \
  -DCMAKE_BUILD_TYPE=Debug \
  -DMYPROJECT_BUILD_TESTS=ON

# Build
cmake --build build --parallel $(nproc)

# Test — tüm testleri çalıştır
ctest --test-dir build --output-on-failure

# Install (custom prefix)
cmake --install build --prefix /opt/myproject

# Sonuç dizin yapısı:
# /opt/myproject/
# ├── bin/myapp
# ├── include/myproject/core.h
# └── lib/libmyproject_core.a

CPack ile paketleme

CMakeLists.txt — CPack eklentisi
# Ana CMakeLists.txt'e ekle (install direktiflerinden sonra)
set(CPACK_PACKAGE_NAME           "myproject")
set(CPACK_PACKAGE_VERSION        ${PROJECT_VERSION})
set(CPACK_PACKAGE_CONTACT        "dev@example.com")
set(CPACK_GENERATOR              "DEB;TGZ")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.17)")
include(CPack)

# Paket oluştur
# cmake --build build
# cpack --config build/CPackConfig.cmake
# → myproject-1.2.0-Linux.tar.gz
# → myproject_1.2.0_amd64.deb

GitHub Actions CI snippet

.github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-22.04
    strategy:
      matrix:
        build_type: [Debug, Release]

    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: sudo apt-get install -y ccache ninja-build

      - name: Configure
        run: |
          cmake \
            -S . -B build \
            -G Ninja \
            -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
            -DCMAKE_C_COMPILER_LAUNCHER=ccache \
            -DMYPROJECT_BUILD_TESTS=ON

      - name: Build
        run: cmake --build build --parallel

      - name: Test
        run: ctest --test-dir build --output-on-failure

Bu bölümde

  • Gerçek proje yapısı: src/, include/, tests/, cmake/ dizinleri
  • Library + executable + tests — tek CMakeLists.txt'te
  • enable_testing() + add_test() + ctest ile CTest entegrasyonu
  • GNUInstallDirs + install(TARGETS) + cmake --install ile kurulum
  • CPack ile .deb ve .tar.gz paket oluşturma
  • GitHub Actions CI: ccache + Ninja + Debug/Release matrix