Önceki bölümde temel olarak make kullanımı üzerinde durduk. Örnek bir Makefile hazırladık. Ancak tek bir kaynak dosyasından oluşturulan bir uygulama için make o kadar da yararlı bir şey değil. Zaten gerçekte de en küçük uygulama bile onlarca kaynak dosyadan oluşur. Şimdi böyle bir uygulama için Makefile hazırlayalım.
Kullandığımız derleyici, derleyici seçenekleri, kütüphaneler gibi değerleri değişkenlere atamakla neler kazandığımıza bir bakalım. Derleyici parametrelerini değiştirmeye karar verdiğimizde değişken kullanmıyor olsaydık 9 farklı yerde bu değişikliği el ile yapmak zorunda kalacaktır. Fakat şimdi ise sadece CFLAGS değişkeninin değerini değiştirmemiz yeterli olacaktır.
Ancak gene de yukarıdaki gibi bir Makefile yazmak uzun sürecek bir işlemdir. Eğer uygulamanız 60 cpp dosyasından oluşuyorsa ve 60 farklı obje için tek tek kuralları yazmak zorunda kalıyorsanız bu hoş olmaz. Çünkü tüm .o dosyalarını üretebilmek için vereceğimiz komut aynı: $(CC) $(CFLAGS) $(INCLUDES) -c xxx.cpp. Oysa biz 60 defa bu komutu tekrar yazmak zorundayız. İşte bu noktada soyut kurallar (abstract rules) imdadımıza yetişir.
Bir soyut kural *.u1 uzantılı bir dosyadan nasıl *.u2 uzantılı bir dosyanın üretileceğini tanımlar. Genel olarak kullanımı aşağıdaki gibidir:
.u1.u2:
komutlar
komutlar
...
Burada u1 kaynak dosyanın uzantısı iken, u2 hedef dosyanın
uzantısıdır. Bu tür kullanımda dikkat ederseniz bağımlılık tanımlamaları yer almamaktadır. Çünkü
tanımladığımız soyut genel kural için bağımlılık belirtmek çok anlamlı değildir. Bunun yerine .u1
uzantılı bir dosyadan .u2 uzantılı dosya üretmede istisnai olarak farklı bağımlılıkları olan
kurallar da ileride vereceğimiz örnekte olduğu gibi belirtilebilir.
Soyut kurallar tanımlarken aşağıdaki özel değişkenleri kullanmak gerekecektir:
$< Değiştiği zaman hedefin yeniden oluşturulması gereken bağımlılıkları gösterir.
$@ Hedefi temsil eder.
$^ Geçerli kural için tüm bağımlılıkları temsil eder.
.cpp.o:
g++ -c $<
Şimdi biraz daha açıklık getirelim. Kaynak dosyamızın adı helper.cpp ve
amacımız helper.o obje dosyasını üretmek olsun. Yukarıdaki kural kaynak dosyamız
için çalıştığında .cpp.o: satırı yüzünden helper.cpp oluşacak helper.o için bir bağımlılık durumunu
alır. Bu nedenle $< değişkenini helper.cpp'yi gösterir. Bu sayede helper.o dosyası üretilmiş olacaktır.
Şimdi aynı mantıkla obje dosyalarından çalıştırılabilir programımızı üretelim.
.o:
g++ $^ -o $@
Bu biraz daha karışık çünkü çalıştırılabilir dosyamızın uzantısı olmayacak. Eğer tek bir uzantı verilmiş
ise bunun birinci uzantı olduğu ve ikincinin boş olduğu düşünülür.
Soyut kurallar tanımladığımızda yapmamız gereken iki işlem daha var. Bunlardan birincisi kullandığımız uzantıların neler olduğunu belirtmektir. Bu işlem için .SUFFIXES özel değişkeni kullanılır:
.SUFFIXES: .cpp .oDiğer yapmamız gereken işlem ise üretilecek çalıştırılabilir dosyamızın hangi obje dosyalara, obje dosyalarımızın ise hangi kaynak dosyalara bağımlı olduğunu belirtmek olacaktır. İşin en güç tarafı budur. Her zaman doğru değerleri yazmak o kadar kolay olmayabilir. Bu noktada gcc derleyicisi -MM seçeneğiyle bize yardımcı olacaktır. Aşağıdaki ekran çıktısına bakalım:
$ g++ -MM -c server.cpp server.o: server.cpp server.h ortak.h $Görüldüğü gibi server.o için gerekli Makefile kuralını bizim için hatasız olarak verdi. Tek yapmamız gereken bu satırları kopyalayıp Makefile içerisine yapıştırmaktır. Şimdi bölümün başında verdiğimiz Makefile dosyasını bu yöntemle yeniden yazalım: