← Kursa Dön
📄 Text · 15 min

Build Sistemleri Karşılaştırması

Bir evin inşaatını düşün. Tek bir duvar örerken tuğlaları elle taşıyabilirsin. Ama 10 katlı bir bina yaparken her şeyin koordineli olması gerekir — önce temel atılır, sonra kolon, sonra kiriş, sonra döşeme. Malzeme siparişleri zamanında gelmeli, bir katın betonunu kurutmadan üstüne çıkamamalısın. İşte build sistemi senin yazılım projendeki bu şantiye koordinatörüdür. Hangi dosyanın önce derleneceğini, neyin neye bağlı olduğunu, hangi parçaların yeniden derlenmesi gerektiğini otomatik yönetir.

Küçük projelerde g++ main.cpp -o main yeterli. Ama dosya sayısı 5'i geçtiğinde, kütüphaneler eklendiğinde, farklı platformları desteklemen gerektiğinde elle derleme sürdürülemez hale gelir. Bu derste Makefile'dan başlayarak CMake, Meson, Bazel ve Ninja'yı tanıyacak, hangisini ne zaman seçmen gerektiğini öğreneceksin.


Neden Build Sistemi Gerekli?

Elle Derlemenin Sınırları

Basit bir projede bile sorunlar hızla büyür:

# 3 dosyalık bir proje — elle derleme
g++ -c -std=c++17 src/main.cpp -o build/main.o
g++ -c -std=c++17 src/utils.cpp -o build/utils.o
g++ -c -std=c++17 src/database.cpp -o build/database.o
g++ build/main.o build/utils.o build/database.o -o build/app -lsqlite3

Her değişiklikte bu dört komutu çalıştırman gerekiyor. Sadece utils.cpp değiştiysen diğer ikisini yeniden derlemeye gerek yok — ama bunu elle takip etmek imkansız. Dosya sayısı 50'ye çıkınca derleme 5 dakika sürüyorsa ve her seferinde hepsini derliyorsan, verimlilik sıfıra düşer.

Build sistemi şu sorunları çözer:

ProblemBuild Sistemi Çözümü
Hangi dosyalar değişti?Dependency tracking — sadece değişeni derle
Derleme sırası ne?Bağımlılık grafiği — doğru sırayı otomatik belirle
Farklı platformlar?Platform abstraction — aynı config, farklı komutlar
Kütüphaneler nerede?Package finding — sistem kütüphanelerini otomatik bul
Paralel derleme?-j flag'i — birden fazla çekirdeği kullan

Makefile: Klasik Unix Build Aracı

Temel Kavramlar

Makefile, 1976'dan beri kullanılan en eski build aracıdır. Basit ama güçlü bir yapıya sahiptir:

hedef: bağımlılıklar
	tarif (recipe)

Üç temel kavram:

  • Target (hedef): Oluşturulacak dosya veya yapılacak iş

  • Prerequisite (bağımlılık): Hedefin oluşması için gereken dosyalar

  • Recipe (tarif): Hedefi oluşturmak için çalıştırılacak komutlar

⚠️ Dikkat: Recipe satırları TAB karakteriyle başlamalıdır — boşluk (space) kullanırsan Makefile:X: *** missing separator. Stop. hatası alırsın. Bu, Makefile'ın en bilinen tuzağıdır.

Basit Makefile Örneği

# Değişkenler
CXX      = g++
CXXFLAGS = -std=c++17 -Wall -Wextra -Werror
LDFLAGS  = -lsqlite3
SRCDIR   = src
BUILDDIR = build

# Kaynak dosyalar ve object dosyaları
SOURCES = $(wildcard $(SRCDIR)/*.cpp)
OBJECTS = $(patsubst $(SRCDIR)/%.cpp, $(BUILDDIR)/%.o, $(SOURCES))
TARGET  = $(BUILDDIR)/app

# Varsayılan hedef
all: $(TARGET)

# Linkleme
$(TARGET): $(OBJECTS)
	$(CXX) $(OBJECTS) -o $(TARGET) $(LDFLAGS)

# Derleme kuralı (pattern rule)
$(BUILDDIR)/%.o: $(SRCDIR)/%.cpp | $(BUILDDIR)
	$(CXX) $(CXXFLAGS) -c $< -o $@

# Build dizinini oluştur
$(BUILDDIR):
	mkdir -p $(BUILDDIR)

# Temizlik
clean:
	rm -rf $(BUILDDIR)

# Phony hedefler (dosya değil, komut)
.PHONY: all clean

Kullanım:

make          # Varsayılan hedefi (all) derle
make -j4      # 4 çekirdek ile paralel derle
make clean    # Build dosyalarını temizle
make TARGET=build/debug  # Değişkeni override et

Makefile'ın Gücü ve Sınırları

make'in güçlü yönü sadeliğidir. Küçük projelerde, script'lerde, C projelerinde hâlâ çok kullanılır. Ama ciddi sınırları var:

  • Platform bağımlılığı: Windows'ta farklı, Linux'ta farklı komutlar gerekir

  • Kütüphane bulma: Kütüphane yollarını elle belirtmen gerekir

  • Cross-compilation: Çok zor

  • IDE entegrasyonu: Yetersiz

  • Ölçeklenme: 100+ dosyalık projelerde yönetimi zorlaşır


CMake: Endüstri Standardı

Kısa Özet

CMake'i zaten biliyorsun. Burada kısa bir hatırlatma ve diğer araçlarla karşılaştırma için özet:

cmake_minimum_required(VERSION 3.14)
project(MyApp LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Kütüphane
add_library(utils src/utils.cpp)
target_include_directories(utils PUBLIC include/)

# Ana program
add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE utils)

# Harici kütüphane bul
find_package(Threads REQUIRED)
target_link_libraries(app PRIVATE Threads::Threads)

# FetchContent ile dependency indir
include(FetchContent)
FetchContent_Declare(
    fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG        10.2.1
)
FetchContent_MakeAvailable(fmt)
target_link_libraries(app PRIVATE fmt::fmt)

Build:

cmake -B build -G Ninja         # Ninja generator ile configure
cmake --build build -j$(nproc)  # Derle
ctest --test-dir build          # Testleri çalıştır

CMake'in avantajları:

  • Endüstri standardı — neredeyse her C++ projesi CMake kullanır

  • Platform bağımsız — Windows, Linux, macOS

  • IDE desteği — CLion, VS Code, Visual Studio

  • Geniş ekosistem — vcpkg, Conan, FetchContent

  • Generator tabanlı — Makefile, Ninja, VS Solution oluşturabilir

CMake'in dezavantajları:

  • Syntax — kendine özgü dil, öğrenme eğrisi yüksek

  • Dokümantasyon — resmi dokümantasyon karmaşık

  • Hata mesajları — bazen anlaşılmaz

  • Eski kod — eski CMake tarzı kodlar (CMAKE_CXX_FLAGS vs.) kafa karıştırır


Meson: Modern ve Basit

Tanıtım

Meson, 2013'te başlayan ve hızla popülerleşen modern bir build sistemidir. GNOME, GStreamer, systemd gibi büyük projeler Meson'a geçti. Felsefesi: basitlik ve hız.

Kurulum:

# Python ile
pip3 install meson

# Ubuntu
sudo apt install meson

# macOS
brew install meson

meson.build Dosyası

Meson'un konfigürasyon dosyası meson.build adını taşır ve Python benzeri bir syntax kullanır:

# meson.build
project('my-app', 'cpp',
  version : '1.0.0',
  default_options : ['cpp_std=c++17', 'warning_level=3'])

# Kütüphane
utils_lib = library('utils',
  sources : ['src/utils.cpp'],
  include_directories : include_directories('include'))

# Ana program
executable('app',
  sources : ['src/main.cpp'],
  link_with : utils_lib,
  include_directories : include_directories('include'))

# Harici dependency
thread_dep = dependency('threads')
sqlite_dep = dependency('sqlite3', required : true)

executable('db_app',
  sources : ['src/db_main.cpp'],
  dependencies : [thread_dep, sqlite_dep],
  include_directories : include_directories('include'))

Build:

meson setup builddir          # Configure
meson compile -C builddir     # Derle (Ninja kullanır)
meson test -C builddir        # Testleri çalıştır

Neden Meson?

Meson'un CMake'e göre avantajları:

  • Okunabilir syntax: Python benzeri, anlaşılır

  • Hızlı: Arka planda Ninja kullanır, configure süresi çok kısa

  • Cross-compilation: Cross file ile basit yapılandırma

  • Subprojects: Dependency yönetimi (wrap dosyaları)

  • Test desteği: Yerleşik test runner

Dezavantajları:

  • Ekosistem: CMake kadar yaygın değil

  • IDE desteği: CMake'den daha az

  • Python bağımlılığı: Meson'un kendisi Python ile yazılmış

Alt Proje (Subproject) Yönetimi

Meson'da dış bağımlılıkları wrap dosyasıyla yönetirsin:

# subprojects/fmt.wrap
[wrap-git]
url = https://github.com/fmtlib/fmt.git
revision = 10.2.1
depth = 1

[provide]
fmt = fmt_dep
# meson.build
fmt_dep = dependency('fmt', fallback : ['fmt', 'fmt_dep'])

executable('app',
  sources : ['src/main.cpp'],
  dependencies : fmt_dep)

Sistem kütüphanesi varsa onu kullanır, yoksa subproject'ten derler.


Bazel: Google'ın Build Sistemi

Tanıtım

Bazel, Google'ın iç build sistemi (Blaze) üzerine kuruludur. Binlerce geliştiricinin milyonlarca satır kod üzerinde çalıştığı monorepo ortamları için tasarlanmıştır.

Kurulum:

# Bazelisk (önerilen — otomatik versiyon yönetimi)
npm install -g @bazel/bazelisk

# veya doğrudan
# https://bazel.build/install adresinden

WORKSPACE ve BUILD Dosyaları

Bazel'de her proje bir WORKSPACE dosyası ile tanımlanır. Her dizinde BUILD dosyası hangi hedeflerin nasıl derleneceğini belirtir.

my-project/
├── WORKSPACE
├── BUILD
├── src/
│   ├── BUILD
│   ├── main.cpp
│   └── utils.cpp
├── include/
│   └── utils.h
└── tests/
    ├── BUILD
    └── utils_test.cpp
# WORKSPACE
workspace(name = "my_project")

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

# Google Test
http_archive(
    name = "com_google_googletest",
    urls = ["https://github.com/google/googletest/archive/v1.14.0.tar.gz"],
    strip_prefix = "googletest-1.14.0",
)
# src/BUILD
cc_library(
    name = "utils",
    srcs = ["utils.cpp"],
    hdrs = ["//include:utils.h"],
    visibility = ["//visibility:public"],
)

cc_binary(
    name = "app",
    srcs = ["main.cpp"],
    deps = [":utils"],
)
# tests/BUILD
cc_test(
    name = "utils_test",
    srcs = ["utils_test.cpp"],
    deps = [
        "//src:utils",
        "@com_google_googletest//:gtest_main",
    ],
)

Build ve test:

bazel build //src:app       # Tek hedef derle
bazel build //...           # Her şeyi derle
bazel test //tests:...      # Tüm testleri çalıştır
bazel run //src:app         # Derle ve çalıştır

Hermetic Build Nedir?

Bazel'in en önemli özelliği hermetic (kapalı/izole) build'dir. Bu şu anlama gelir:

  • Aynı girdiler → aynı çıktı: Hangi makinede, ne zaman derlersen derle, sonuç aynı

  • Sandboxing: Her build adımı izole bir ortamda çalışır

  • Remote caching: Build sonuçları uzak sunucuda cache'lenir — 1000 kişilik ekipte biri derlerse diğerleri cache'den alır

  • Remote execution: Build adımları uzak sunucularda paralel çalıştırılabilir

Bu özellikler küçük projelerde gereksiz, ama Google, Meta, Uber ölçeğinde hayat kurtarır.

Bazel Ne Zaman Mantıklı?

  • Monorepo: Tüm kod tek repository'de ve birbirine bağımlıysa

  • Çoklu dil: C++, Java, Python, Go aynı projede

  • Büyük ekip: 50+ geliştirici, build süresi kritik

  • Reproducibility: Bit-perfect tekrarlanabilir build gerekiyorsa

Küçük-orta projelerde Bazel'in karmaşıklığı faydadan çok ağır basar. 5-10 kişilik bir ekipte CMake veya Meson çok daha pratik.


Ninja: Düşük Seviye Build Executor

CMake + Ninja

Ninja bir meta-build sistemi değildir — kendi başına konfigürasyon yapmaz. CMake veya Meson gibi araçlar Ninja dosyası üretir, Ninja sadece çok hızlı bir şekilde çalıştırır.

Ninja, Make'in alternatifidir ama tek bir amacı var: mümkün olan en hızlı build. Minimal overhead, paralel derleme, akıllı dependency tracking.

# CMake ile Ninja kullan
cmake -B build -G Ninja
cmake --build build

# Veya doğrudan
cd build && ninja

# Paralel iş sayısını kontrol et
ninja -j8

# Hangi adımlar çalışacak (dry run)
ninja -n

# Dependency grafiğini göster
ninja -t graph | dot -Tpng > graph.png

Make vs Ninja Performans Karşılaştırması

ÖzellikMakeNinja
Startup süresiYavaş (Makefile parse)✅ Çok hızlı
Dependency analiziDosya bazlı✅ Hash bazlı
Paralel derleme-j ile✅ Varsayılan paralel
İnsan okunabilir✅ Evet❌ Makine formatı
Elle yazılır mıEvetHayır (generator gerekir)
Build logBasit✅ Kompakt ve bilgilendirici

Ninja dosyaları (build.ninja) elle yazılmak üzere tasarlanmamıştır. CMake veya Meson tarafından üretilirler.

💡 İpucu: CMake projelerinde -G Ninja kullanmak, özellikle büyük projelerde build süresini belirgin şekilde azaltır. Tek değişiklik bir flag — dene ve farkı gör.

Ninja Dosyası Neye Benzer?

Merak edenler için küçük bir örnek:

# build.ninja (genellikle elle yazmassın)
cxx = g++
cxxflags = -std=c++17 -Wall -Wextra

rule compile
  command = $cxx $cxxflags -c $in -o $out
  description = Compiling $in

rule link
  command = $cxx $in -o $out
  description = Linking $out

build build/main.o: compile src/main.cpp
build build/utils.o: compile src/utils.cpp

build build/app: link build/main.o build/utils.o

Syntax minimalist ve makineler için optimize edilmiş. Hız farkının kaynağı bu sadelik — Ninja parse ederken zaman kaybetmez.


Büyük Karşılaştırma Tablosu

ÖzellikMakefileCMakeMesonBazel
İlk çıkış1976200020132015
DilMake syntaxCMake diliPython benzeriStarlark
Öğrenme eğrisiDüşükOrta-YüksekDüşükYüksek
Platform desteğiUnix✅ Tümü✅ Tümü✅ Tümü
IDE entegrasyonuMinimal✅ MükemmelİyiOrta
Dependency yönetimiManuelFetchContent, vcpkgWrap, pkg-configWORKSPACE
Cross-compilationZorToolchain dosyasıCross filePlatform kuralları
Build hızıOrtaGenerator'a bağlı✅ Hızlı (Ninja)✅ Hızlı (cache)
ÖlçeklenebilirlikDüşükİyiİyi✅ Mükemmel
Hermetic build✅ Evet
Remote cache✅ Evet
Çoklu dilSınırlıC/C++ odaklıC/C++ odaklı✅ Tümü
ToplulukÇok geniş✅ En genişBüyüyenGoogle ekosistemi
DokümantasyonİyiKarmaşık✅ Temizİyi

Hangisini Seçmeli?

Proje Büyüklüğüne Göre Karar Ağacı

Tek dosya veya script düzeyinde proje: → Build sistemi gereksiz. g++ main.cpp -o main yeterli.

2-10 dosya, kişisel/küçük proje:Makefile veya CMake. Makefile hızlı başlamak için, CMake büyüme potansiyeli için.

10-100 dosya, küçük-orta ekip (2-10 kişi):CMake. Endüstri standardı, IDE desteği mükemmel, ekosistem geniş.

Yeni proje, modern araç istiyorsan:Meson. Syntax temiz, konfigürasyon hızlı, Ninja ile build hızlı. Ekosistem CMake kadar geniş değil ama yeterli.

100+ dosya, büyük ekip (10+ kişi):CMake (yerleşik ekosistem) veya Bazel (build süresi kritikse).

Monorepo, çoklu dil, 50+ geliştirici:Bazel. Remote cache ve execution ile build sürelerini dramatik azaltır.

Gerçek Dünya Tercihleri

ProjeBuild Sistemi
Linux KernelMakefile (Kbuild)
LLVM/ClangCMake
QtCMake (eskiden qmake)
ChromiumGN + Ninja
systemdMeson
GStreamerMeson
TensorFlowBazel
Abseil (Google)CMake + Bazel
Unreal EngineÖzel (UnrealBuildTool)
BoostB2 (eskiden bjam)

Çoğu C++ projesi CMake kullanır. Bu bir standart haline gelmiş durumda. Eğer özel bir nedenin yoksa CMake güvenli tercihtir.


Pratik: Aynı Projeyi Farklı Araçlarla

Basit bir projenin her araçla nasıl yapılandırıldığını görelim:

Proje Yapısı

calculator/
├── include/
│   └── calculator.h
├── src/
│   ├── calculator.cpp
│   └── main.cpp
└── tests/
    └── calculator_test.cpp
// include/calculator.h
#pragma once

class Calculator {
public:
    double add(double a, double b);
    double divide(double a, double b);
};
// src/calculator.cpp
#include "calculator.h"
#include <stdexcept>

double Calculator::add(double a, double b) {
    return a + b;
}

double Calculator::divide(double a, double b) {
    if (b == 0.0) throw std::invalid_argument("Division by zero");
    return a / b;
}

Makefile Versiyonu

CXX      = g++
CXXFLAGS = -std=c++17 -Wall -Wextra -Iinclude
BUILDDIR = build

SOURCES = src/calculator.cpp src/main.cpp
OBJECTS = $(patsubst src/%.cpp, $(BUILDDIR)/%.o, $(SOURCES))

all: $(BUILDDIR)/app

$(BUILDDIR)/app: $(OBJECTS)
	$(CXX) $^ -o $@

$(BUILDDIR)/%.o: src/%.cpp | $(BUILDDIR)
	$(CXX) $(CXXFLAGS) -c $< -o $@

$(BUILDDIR):
	mkdir -p $(BUILDDIR)

clean:
	rm -rf $(BUILDDIR)

.PHONY: all clean

CMake Versiyonu

cmake_minimum_required(VERSION 3.14)
project(Calculator LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)

add_library(calculator src/calculator.cpp)
target_include_directories(calculator PUBLIC include/)

add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE calculator)

Meson Versiyonu

project('calculator', 'cpp',
  default_options : ['cpp_std=c++17', 'warning_level=3'])

calc_lib = library('calculator',
  sources : ['src/calculator.cpp'],
  include_directories : include_directories('include'))

executable('app',
  sources : ['src/main.cpp'],
  link_with : calc_lib,
  include_directories : include_directories('include'))

Bazel Versiyonu

# src/BUILD
cc_library(
    name = "calculator",
    srcs = ["calculator.cpp"],
    hdrs = ["//include:calculator.h"],
    includes = ["../include"],
    visibility = ["//visibility:public"],
)

cc_binary(
    name = "app",
    srcs = ["main.cpp"],
    deps = [":calculator"],
)

Aynı proje — dört farklı syntax. CMake ve Meson en okunabilir olanlar. Makefile en kısa ama en az esnek. Bazel en yapılandırılmış ama küçük proje için overkill.


İleri Seviye: Build Sistemi Özellikleri

Precompiled Headers (PCH)

Büyük projelerde sık kullanılan header'ları önderlemek build süresini azaltır:

# CMake
target_precompile_headers(app PRIVATE
    <vector>
    <string>
    <iostream>
    <memory>
)
# Meson
executable('app',
  sources : ['src/main.cpp'],
  cpp_pch : 'src/pch/pch.h')

Unity Build

Tüm .cpp dosyalarını tek bir derleme birimine birleştirerek header parsing'i azaltır:

# CMake 3.16+
set(CMAKE_UNITY_BUILD ON)
set(CMAKE_UNITY_BUILD_BATCH_SIZE 8)

ccache ile Derleme Cache'leme

ccache, daha önce derlenmiş dosyaları cache'leyerek tekrar derlemeyi önler:

# Kurulum
sudo apt install ccache

# CMake ile kullanım
cmake -B build -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
# Meson (otomatik algılar)
# Yalnızca ccache kurulu olmalı

Bu teknikler build sisteminden bağımsızdır ama entegrasyonu build sistemi sağlar.

⚠️ Dikkat: Build sistemi seçimini projenin başında yap ve ekiple hemfikir ol. Sonradan değiştirmek (özellikle büyük projelerde) çok maliyetlidir. Yanlış tercihi düzeltmektense baştan doğru tercihi yapmak iyidir.


Özet

  • Build sistemi, çok dosyalı projelerde derleme otomasyonu, dependency tracking ve paralel build sağlayan zorunlu bir araçtır

  • Makefile basit ve hızlı başlamak için iyidir ama platform bağımlılığı ve ölçeklenme sorunları vardır

  • CMake endüstri standardıdır — IDE desteği, ekosistem ve topluluk açısından en güçlü seçenek

  • Meson modern, okunabilir syntax ve Ninja tabanlı hızlı build ile CMake'e güçlü bir alternatiftir

  • Bazel monorepo, hermetic build ve remote cache ile büyük ölçekli projelerde parlar — küçük projelerde gereksizdir

  • Çoğu C++ projesi için CMake + Ninja kombinasyonu en pratik ve performanslı çözümdür