6 puan yazan GN⁺ 2025-06-21 | 1 yorum | WhatsApp'ta paylaş
  • Makefile, C/C++ derleme otomasyonu ve bağımlılık yönetimini basitleştiren bir araçtır
  • Zaman damgalarını kullanarak değişen dosyaları tespit etme yöntemiyle yalnızca gerektiğinde derleme çalıştırır
  • Kural (rule), komut (command), bağımlılık (prerequisite) gibi temel yapıları örneklerle açıklar
  • Otomatik değişkenler, pattern kuralları, değişken genişletme gibi ileri özellikleri de pratik şekilde ele alır
  • Orta ölçekli projeler için gerçek dünya Makefile şablonu üzerinden ölçeklenebilirlik ve yönetimin önemini tanıtır

Makefile eğitim rehberine giriş

  • Makefile, proje derleme otomasyonu ve bağımlılık yönetiminden sorumlu temel bir araçtır
  • Çeşitli gizli kurallar ve semboller nedeniyle ilk bakışta karmaşık gelebilir; ancak bu rehber, ana noktaları kısa ve doğrudan çalıştırılabilir örneklerle düzenler
  • Her bölümde uygulamaya dayalı örnekler üzerinden konuyu anlamak mümkündür

Başlarken

Makefile'ın var olma amacı

  • Makefile, büyük programlarda yalnızca değişen bölümleri yeniden derlemek için kullanılır
  • C/C++ dışında da dillere özel çeşitli build araçları vardır; ancak Make, genel build senaryolarının tamamında kullanılabilir
  • Temel mantık, değişen dosyaları algılayıp yalnızca gerekli işleri çalıştırmasıdır

Make'e alternatif build sistemleri

  • C/C++ tarafında: SCons, CMake, Bazel, Ninja gibi birçok seçenek vardır
  • Java tarafında: Ant, Maven, Gradle
  • Go, Rust, TypeScript gibi diller de kendi build araçlarını sunar
  • Python, Ruby, JavaScript gibi yorumlanan diller derleme gerektirmediğinden Makefile benzeri ayrı bir yönetim ihtiyacı daha düşüktür

Make sürümleri ve türleri

  • Farklı Make uygulamaları olsa da, bu rehber GNU Make için optimize edilmiştir (çoğunlukla Linux ve MacOS'ta kullanılır)
  • Örnekler GNU Make 3 ve 4 sürümleriyle uyumludur

Örnekleri çalıştırma yöntemi

  • Terminalde make kurulduktan sonra her örneği Makefile dosyası olarak kaydedip make komutunu çalıştırın
  • Makefile içindeki komut satırları mutlaka sekme karakteri ile girintilenmelidir

Makefile temel sözdizimi

Kuralın (Rule) yapısı

  • hedef: bağımlılık(lar)

    • komut
    • komut
  • Hedef: derleme sonucunda oluşan dosya adı (genellikle bir tane)

  • Komut: fiilen çalışan shell script'i (sekmeyle başlar)

  • Bağımlılık: hedef derlenmeden önce hazır olması gereken dosyaların listesi


Make'in özü

Hello World örneği

hello:  
	echo "Hello, World"  
	echo "This line will print if the file hello does not exist."  
  • hello hedefinin bağımlılığı yoktur ve 2 komut çalıştırır
  • make hello çalıştırıldığında hello dosyası yoksa komutlar yürütülür. Dosya zaten varsa çalışmaz
  • Genellikle hedef, dosya adıyla aynı olacak şekilde yazılır

C dosyası derleme için temel örnek

  1. blah.c dosyasını oluşturun (int main() { return 0; } içeriğiyle)
  2. Aşağıdaki Makefile'ı yazın
blah:  
	cc blah.c -o blah  
  • make çalıştırıldığında blah hedefi yoksa derleme yapılır ve blah dosyası oluşturulur
  • blah.c değişse bile otomatik yeniden derleme olmaz → bağımlılık eklemek gerekir

Bağımlılık ekleme yöntemi

blah: blah.c  
	cc blah.c -o blah  
  • Artık blah.c yeni değiştirilmişse blah hedefi yeniden derlenir
  • Değişiklik tespitinde dosya zaman damgaları temel alınır
  • Zaman damgaları elle değiştirilirse beklenmedik davranışlar oluşabilir

Daha fazla örnek

Bağlantılı hedef ve bağımlılık örneği

blah: blah.o  
	cc blah.o -o blah   
  
blah.o: blah.c  
	cc -c blah.c -o blah.o   
  
blah.c:  
	echo "int main() { return 0; }" > blah.c   
  • Ağaç yapısında bağımlılıkları izleyerek her aşamadaki üretim süreci otomatikleştirilir

Her zaman çalışan hedef örneği

some_file: other_file  
	echo "This will always run, and runs second"  
	touch some_file  
  
other_file:  
	echo "This will always run, and runs first"  
  • other_file gerçek bir dosya olarak oluşturulmadığı için some_file komutu her seferinde çalışır

Make clean

  • clean hedefi, build çıktılarının silinmesi için sık kullanılır
  • Make içinde özel bir anahtar sözcük değildir; komut olarak sizin tanımlamanız gerekir
  • Dosya adı clean ise karışıklık yaşanabileceğinden .PHONY kullanılması önerilir

Örnek:

some_file:   
	touch some_file  
  
clean:  
	rm -f some_file  

Değişken kullanımı

  • Değişkenler her zaman string'dir.
  • Genellikle := önerilir; ayrıca =, ?=, += gibi farklı atama biçimleri de vardır
  • Kullanım örneği:
files := file1 file2  
some_file: $(files)  
	echo "Look at this variable: " $(files)  
	touch some_file  
  
file1:  
	touch file1  
file2:  
	touch file2  
  
clean:  
	rm -f file1 file2 some_file  
  • Değişken başvuru biçimi: $(variable) veya ${variable}
  • Makefile içindeki tırnak işaretlerinin Make açısından özel bir anlamı yoktur (ancak shell komutlarında gerekebilir)

Hedef yönetimi

all hedefi

  • Birden çok hedefi tek seferde çalıştırmak için ilk (varsayılan) hedefe bu rol verilir
all: one two three  
  
one:  
	touch one  
two:  
	touch two  
three:  
	touch three  
  
clean:  
	rm -f one two three  

Çoklu hedefler ve otomatik değişkenler

  • Birden fazla hedef için her biri adına ayrı komut çalıştırılabilir. $@, mevcut hedef adını taşır
all: f1.o f2.o  
  
f1.o f2.o:  
	echo $@  

Otomatik değişkenler ve wildcard'lar

* wildcard'ı

  • *, dosya sistemindeki adları doğrudan tarar
  • Kullanırken wildcard fonksiyonuyla sarmalanması önerilir
print: $(wildcard *.c)  
	ls -la  $?  
  • Değişken tanımında doğrudan * kullanmayın
thing_wrong := *.o  
thing_right := $(wildcard *.o)  

% wildcard'ı

  • Çoğunlukla pattern kurallarında kullanılır; belirtilen kalıbı çıkarıp genişletebilir

Fancy Rules

Örtük (Implicit) kurallar

  • Make, C/C++ build süreçleriyle ilgili çeşitli gizli varsayılan kuralları gömülü olarak içerir
  • Temsilî değişkenler: CC, CXX, CFLAGS, CPPFLAGS, LDFLAGS vb.
  • C örneği:
CC = gcc   
CFLAGS = -g   
  
blah: blah.o  
  
blah.c:  
	echo "int main() { return 0; }" > blah.c  
  
clean:  
	rm -f blah*  

Static Pattern Rules

  • Aynı kalıbı izleyen çok sayıdaki kural kısa biçimde yazılabilir
objects = foo.o bar.o all.o  
all: $(objects)  
	$(CC) $^ -o all  
  
$(objects): %.o: %.c  
	$(CC) -c $^ -o $@  
  
all.c:  
	echo "int main() { return 0; }" > all.c  
  
%.c:  
	touch $@  
  
clean:  
	rm -f *.c *.o all  

Static Pattern Rules + filter fonksiyonu

  • filter kullanıldığında yalnızca belirli uzantı kalıbına uyan hedefler seçilebilir
obj_files = foo.result bar.o lose.o  
src_files = foo.raw bar.c lose.c  
  
all: $(obj_files)  
.PHONY: all  
  
$(filter %.o,$(obj_files)): %.o: %.c  
	echo "target: $@ prereq: $

1 yorum

 
GN⁺ 2025-06-21
Hacker News görüşleri
  • 1985’te Boston University Graphics laboratuvarında birinin Makefile kullanarak animasyon için 3D bir renderer yaptığını bizzat görmüş. O kişi bir Lisp programcısıymış; erken prosedürel üretim ve 3D aktör sistemi üzerinde çalışıyormuş ve yaklaşık 10 satırlık gerçekten zarif bir Makefile yazmış. Basit dosya tarih bağımlılıklarıyla yüzlerce animasyonu otomatik üreten bir yapıymış. Her karenin 3D biçimini Lisp üretiyor, Make de kareleri oluşturuyormuş. 1985’te, bugün 3D ve animasyonu sıradan görmemizin aksine herkesin buna hayran kaldığını; o kişinin daha sonra Iron Giant ve Coraline’ın 3D renderer’larında çalışan Brian Gardner olduğunu hatırlıyor

    • Acaba bahsedilen kişi 3d-consultant.com/bio.html’deki kişi mi diye merak ediyor

    • Coraline adlı filmi kastettiğini doğruluyor

  • Make kullanırken pek bilinmeyen bazı faydalı bayrakları tanıtıyor

    • --output-sync=recurse -j10: her hedef işi bitene kadar stdout/stderr’ı tamponlayıp sonra yazdırıyor; aksi halde loglar birbirine giriyor ve analiz zorlaşıyor
    • Yoğun sistemlerde veya çok kullanıcılı ortamlarda -j yerine --load-average kullanarak paralel çalışmada sistem yükü kontrol edilebilir (make -j10 --load-average=10)
    • Derleme hedeflerinin sırasını rastgele karıştıran --shuffle seçeneği, CI ortamında Makefile içindeki bağımlılık sorunlarını yakalamakta yararlı
    • make’in çeşitli seçeneklerini resmî olarak derleyip programın içine metin ya da belge olarak eklemenin kullanımı daha erişilebilir kılacağı fikrini dile getiriyor

    • Kendisinin sık kullandığı seçeneğin, her şeyi zorla yeniden derlemek için kullanılan -B bayrağı olduğunu açıklıyor

    • make -j yüzünden DOS makinelerinde oluşan sorunları sık gördüğü için bunu bir bug olarak gördüğünü söylüyor

    • Yoğun sistemlerde veya çok kullanıcılı ortamlarda paralelleştirme sorunlarını işletim sistemi zamanlayıcısının çözmesi gerekmiyor mu diye soruyor

    • Faydalı bayraklar olduklarını ama taşınabilir olmadıkları için, sadece kişisel özel projeler dışında kullanılmamasını öneriyor

  • .PHONY kullanılmadığı için bunu öğreticide atlamanın zayıf bir bahane olduğunu düşünüyor. Asıl yapılması gerekenin aracı doğru kullanmayı öğretmek olduğu görüşünde

    • Ekipte Make’i bir görev çalıştırıcısı olarak kullanırken her recipe’ye .PHONY ekleyip bunu sürdürmek yüzünden tartışmalar yaşamışlar
    • Clark Grubb’ın Makefile stil rehberini öneriyor: clarkgrubb.com/makefile-style-guide
    • .PHONY bildirimlerini her recipe yanında yapmakla dosyanın başında topluca yapmak arasında farklı stiller gördüğünü, bunun bir linter ile zorunlu kılınmasını isteyeceğini söylüyor
    • Okuduğunda iyi bir belge bulduğunu ama bazı noktalarda katılmadığını söylüyor
      • -o pipefail seçeneğini körü körüne uygulamak sorunlu; pipe içinde grep gibi araçlar kullanıldığında kırılabileceği için duruma göre kullanılmasını öneriyor
      • Dosya olmayan hedefleri .PHONY ile işaretlemek teknik olarak daha doğru olsa da çoğu zaman gereksiz ve Makefile’ı uzatıyor; bu yüzden sadece gerektiğinde uygulanmasının daha iyi olduğu görüşünde
      • Birden çok çıktı dosyası üreten recipe’ler için eskiden dummy dosyalar kullanıldığını, ancak GNU Make 4.3’ten beri grup hedeflerin resmen desteklendiğini belirtiyor (burada)
  • Make’in büyük C kod tabanlarını derlemek için özelleşmiş bir araç olduğu iddiası

    • Birinin bunu proje bazlı iş çalıştırıcısı olarak severek kullandığını, ama Make’in iş çalıştırıcısı olarak uygun olmadığını ve koşul ifadeleri gibi şeyleri bile zorlaştıran bir yapısı bulunduğunu söylüyor
    • Terraform gibi araçları sarmalamaya çalışırken başarısız olunan örnekler de gördüğünü ekliyor
    • Make’in bir iş çalıştırıcısından çok, doğrusal shell script’lerini bildirime dayalı bağımlılıklar biçimine dönüştüren genel amaçlı bir shell aracı olduğu görüşü paylaşılıyor

    • Make’i yalnızca C kod tabanları için bir derleme aracı olarak görmenin artık doğru olmadığını savunuyor. Son 20 yılda daha sağlam ve daha açık derleme sistemleri geliştirildiğini hatırlatıp bu bakışın güncellenmesi gerektiğini söylüyor

    • İyi bir iş çalıştırıcısının ne olduğunu soruyor. (Kendisinin “job runner” anlamını karıştırdığını söyleyerek özür de ekliyor)

  • Makefile’ın karmaşıklaştığı kısımları modern biçimde ikame eden bir araç olarak just öneriliyor

    • just, shell script listelerinin yerine geçmek için iyi ama Make’in özündeki “yalnızca yeniden çalışması gereken kuralları çalıştırma” yeteneğinin yerini tutmuyor

    • Diğer alternatifler olarak

    • Alternatif araçların kendilerini Make’in yerine koyduğunu ama kendisine göre tamamen farklı olduklarını ve doğrudan karşılaştırmanın zor olduğunu söylüyor. Make’in özünün çıktı üretmek ve zaten derlenmiş olanı yeniden derlememek olduğunu; buna karşılık just’ın basit bir komut çalıştırıcısı olduğunu belirtiyor

    • Make’i komut çalıştırıcısı olarak kullanmanın avantajının, hemen her yerde kurulu bulunan standart bir araç olmasının verdiği güven olduğunu söylüyor. Alternatifler daha iyi tasarlanmış olsa bile ayrıca kurulum gerektirdikleri için buna ihtiyaç duymadığını düşünüyor

    • Task’ı C ile yaptığı küçük hobi projelerinde memnuniyetle kullandığını, ama büyük projeler için de uygun olup olmadığı konusunda henüz emin olmadığını ekliyor (Task resmî sitesi)

  • Son dönemde CMake’in, Makefile’ın C++20 modül desteği için uygun olmadığını düşünüp varsayılan olarak ninja’yı seçmesini ilginç buluyor (CMake kılavuzu)

    • Gerçekte hedef bağımlılıklarını statik olarak tanımlamak neredeyse imkânsız olduğu için clang-scan-deps gibi araçlarla dinamik analiz yaklaşımının benimsendiğini söylüyor (teknik slaytlar)
    • Aslında bu kısıtın CMake tarafında alınmış bir karar olduğunu ya da Makefile generator’ına destek verecek gönüllü eksikliğiyle ilgili olduğunu düşünüyor. ninja’nın da C++ modüllerini doğrudan desteklemediğini (ilgili issue), hatta ninja’nın Make’ten daha az özellik sunduğunu ve tüm bağımlılıkların statik olarak açıkça belirtilmesini gerektirdiğini vurguluyor

    • Modül kavramının kendisinin de karmaşık ve kafa karıştırıcı olduğunu düşünüyor

  • tup kullanan olup olmadığını soruyor. (resmî belge)

    • tup, dosya sistemi erişimlerini temel alarak bağımlılıkları otomatik çıkaran ve bu sayede herhangi bir derleyici/araçla çalışabilen bir derleme sistemi
  • Kendisinin, Make’e alternatif olan Task aracının yaratıcısı ve ana geliştiricisi olduğunu söylüyor. 8 yıldan uzun süredir geliştirildiğini ve gelişmeye devam ettiğini belirtiyor

    • Farklı bir deneyim isteyenlere denemelerini öneriyor; sorusu olanların her zaman sorabileceğini söylüyor
    • Task resmî sitesi, GitHub deposu bağlantılarını paylaşıyor
    • just da başka bir Make alternatifi olarak öneriliyor (just GitHub)

    • Komik bir tesadüf olarak, kendisinin de Task’ı sık kullandığını ve bu sabah issue açtığını söylüyor

  • Bu öğreticide tehlikeli ve ince bazı sorunlar var

    • MAKEFLAGS içindeki seçenekleri ayrıştırırken, uzun seçenekleri ya da boş kısa seçenekleri ele almak için şöyle yapılması gerektiğini söylüyor
      ifneq (,$(findstring t,$(firstword -$(MAKEFLAGS))))
    • OS X ile gelen eski make sürümüyle uyumluluk gerekiyorsa epey çok özelliğin eksik ya da ince farklarla davranıyor olduğunu belirtiyor
    • Diğer sorunların çoğunun yazım hatası ya da en iyi stil kurallarına aykırılık olduğunu, bu yüzden atladığını söylüyor
    • Ayrıca load’un guile’dan daha taşınabilir olduğunu; çapraz derleme ortamlarında derleyicinin doğru biçimde belirtilmesi gerektiğini ekliyor
    • Paul’s Rules of Makefiles(burada) ile GNU make kılavuzunun(burada) ve ilgili belgelerin mutlaka okunmasını tavsiye ediyor
    • Basit bir demo Makefile projesi de sürdürdüğünü ekliyor (demo github)
  • Her GitHub deposunda daima bir Makefile bulundurma alışkanlığı olduğunu söylüyor

    • Komutlar kolay unutulduğu için bunları Makefile’a yazmanın faydalı olduğunu; böylece karmaşık adımların da eklenebildiğini ve yalnızca make çalıştırarak proje için beklenen işlemlerin ne olduğunu hatırlamadan hemen yapabildiğini anlatıyor