Make
Uygulama geliştirirken sıklıkla obje dosyalarımızı yeniden ve yeniden oluşturmak zorunda kalırız. Yerine göre gcc
, ld
, ar
vb. uygulamaları tekrar tekrar aynı parametrelere çağırırız. İşte make
ugulaması, programların yeniden derlenme sürecini otomatik hale getirmek, sadece değişen kısımların yeniden derlenmesini sağlamak suretiyle zamandan kazanmak ve işlemleri her zaman otomatik olarak doğru sırada yapmak için tasarlanmıştır.
Temel Kurallar
make
uygulaması çalıştırıldığında, bulunulan dizinde sırasıyla GNUmakefile
, makefile
ve Makefile
dosyalarını arar. Alternatif olarak -f
seçeneği ile Makefile olarak kullanacağınız dosyayı da belirlemeniz mümkün olsa da standartların dışına çıkmamakta fayda var. make
neyi nasıl yapacağını bu dosyalardan öğrenecektir. Eğer bulunduğunuz dizinde bir Makefile
dosyası yoksa aşağıdaki gibi bir çıktı alacaksınız demektir:
$ make
make: *** No targets specified and no makefile found. Stop.
Genel kabul görmüşlüğü ve göz alışkanlığı açısından dosya adı olarak alternatiflerin yerine
Makefile
isminin kullanılması önerilir
Bir Makefile
aslında işlemlerin nasıl yapılacağını gösteren kural tanımlamalarından oluşmaktadır. Genel olarak dosyanın biçimi aşağıdaki gibidir:
hedef1: bağımlılıklar
<TAB> komut
<TAB> komut
<TAB> ...
hedef2: bağımlılıklar
...
Burada en sık yapacağımız hata TAB
tuşuna basmayı unutmak olacaktır. Makefile
dosyasını hazırladığınız editörden kaynaklanan bir problem de olabilir. TAB
işlemine dikkat edilmediğinde aşağıdaki gibi bir uyarı alabilirsiniz:
$ make
Makefile:6: *** missing separator (did you mean TAB instead of 8 spaces?)
Kurallar arasında bir satır boş bırakılması GNU make için zorunlu olmamakla birlikte bazı Unix versiyonlarıyla uyumluluk için boşluk bırakılması gereklidir.
İlk satırda hedef1
'in oluşturulmasında etkili olan, dolayısıyla bağımlılık üreten dosyalar birbirinden boşluk ile ayrılmış olarak tek satırda listelenir. Eğer bağımlılık kısmında yer alan dosyalardan en az birinin son değiştirilme tarihi, hedef1
'den daha yeni ise, hedef1
yeniden oluşturulur. Diğer durumda hedef1
'in yeniden oluşturulmasına gerek olmadığı anlaşılır, çünkü hedef1
'in bağımlı olduğu dosyalarda hedef1
üretildikten sonra bir değişiklik olmamıştır.
NOT: Görüldüğü üzere dosyaların son değiştirilme tarihleri üzerinden işleyen bir mekanizma bulunmaktadır. Sistem saatiniz ileri veya geri zıplarsa kurallar doğru çalışamayacaktır. Bu durumda
make clean
ile önce tam bir temizlik yapılıp yeniden süreci başlatabilirsiniz.Eğer sistem zamanındaki oynamalar nedeniyle, dosyaların son güncellenme zamanları, o anki sistem saatinden daha ileride ise, make: warning: Clock skew detected. Your build may be incomplete şeklinde bir uyarı alınacaktır.
Sonraki satırlarda bağımlılık yaratan bu dosyalardan hedef1
'in oluşturulabilmesi için gerekli komutlar yer alır. Şimdi basit bir örnek için önce yeni bir dizin oluşturup içerisinde aşağıdaki Makefile
dosyasını oluşturalım:
bolgeler: marmara karadeniz ege
cat marmara karadeniz ege | sort -u > bolgeler
marmara: istanbul bursa
cat istanbul bursa | sort -u > marmara
karadeniz: samsun sinop
cat samsun sinop | sort -u > karadeniz
ege: izmir aydin
cat izmir aydin | sort -u > ege
clean:
rm -f ege karadeniz marmara bolgeler
Dosyamızı hazırladıktan sonra make
komutunu çalıştıralım:
$ make
make: *** No rule to make target `istanbul', needed by `marmara'. Stop.
Yukarıdaki örnekte neler olduğunu anlamaya çalışalım:
make
uygulamasınaMakefile
içerisindeki bir hedef kural ismini parametre olarak vermediğimizde öntanımlı olarak dosyada bulduğu ilk hedefi gerçekleme çalışır- Dosyamızdaki ilk hedefin
bolgeler
olduğunu gördük bolgeler
hedefinin bağımlılıklarımarmara
,karadeniz
veege
dosyaları şeklindeymiş- Eğer bulunduğumuz dizinde
bolgeler
dosyası zaten mevcut ve son değiştirilme tarihi, bağımlılıkları olanmarmara
,karadeniz
veege
dosyalarının her üçünden de daha güncel olsa idi,make
yeni bir işlem yapmaya gerek olmadığını düşünecekti. Ancak bizim dizinimizde henüz bu dosyaların hiç biri yok - Bu nedenle
bolgeler
hedefini gerçeklemek için öncelikle dizinde mevcut olmayanmarmara
hedefini gerçeklemek için işlemlere başlandı marmara
hedefi de benzer şekildeistanbul
vebursa
dosyalarına bağımlı ve her iki dosya da sistemde yok- Bir önceki durumdan farklı olarak,
istanbul
dosyası dizinde mevcut olmadığı gibi bu dosyayı üretecek herhangi bir hedef de tanımlı değil. - Bu yüzden marmara hedefini üretebilmek için gereken (dizinde mevcut olmayan) istanbul hedefini üretecek kural da yok şeklinde bir hata mesajı ile
make
süreci sonlanmıştır
Eğer bulunduğumuz dizinde, istanbul
ve bursa
dosyalarını oluşturacak olursak, make
sonrası marmara
hedefinin üretildiğini görebileceğiz. Aynı şekilde diğer bölgeler için de Makefile
dosyasında tanımladığımız kurallar doğrultusunda gereken dosyaları ürettiğimizde, onlar da make
tarafından oluşturulacaktır.
Sonrasında herhangi bir il dosyasını güncellediğimizde, ona bağlı bölge tüm bölgeleri içeren dosya otomatik olarak güncellenecektir.
Şimdi de C
dilinde yazılmış basit bir uygulamanın derlenmesi sürecine yönelik örnek Makefile
dosyamıza bakalım:
CC = gcc
CFLAGS = -O2 -Wall -pedantic
LIBS = -lm -lnsl
all: install
ornek: ornek.o
$(CC) $(CFLAGS) $(LIBS) -o ornek ornek.o
ornek.o: ornek.c
$(CC) $(CFLAGS) -c ornek.c
clean:
rm -f ornek *.o
install: ornek
cp ornek /usr/local/bin
Bu örnekte hedef olarak ornek
uygulaması derlenecektir. Uygulamanın bağımlı olduğu dosya ornek.o
şeklinde olup bu dosya da ornek.c
kaynak kod dosyasına bağımlıdır.
İlk satırda yer alan CC
değişkeniyle kullanacağımız derleyiciyi belirliyoruz. Makefile dosyaları içerisinde bu şekilde değişken tanımlaması yapıp, değişkeni dosya içerisinde $(değişken)
şeklinde kullanabiliriz. İkinci satırda ise derleyiciye vereceğimiz bazı seçenekleri CFLAGS
değişkenine atıyoruz. Üçüncü satırda uygulamamızın linklenmesi gereken kütüphaleri -l
parametresiyle listeledik. Ardından ilk kuralımız geliyor: ornek
dosyası ornek.o
dosyasına bağımlı olarak belirtilmiş ve ornek.o
'dan ornek
'in oluşturulabilmesi için gerekli komut hemen altında listelenmiştir. Değişkenlerin değerlerini yerine koyduğumuzda komutumuz gcc -O2 -Wall -pedantic -lm -lnsl -o ornek ornek.o
şeklinde olacaktır.
İkinci kuralımız ornek.o
'nun nasıl oluşturulacağını belirtmektedir. ornek.c
dosyasında bir değişiklik olduğunda ornek.o
dosyası hemen altında listelenen komutla yeniden oluşturulur:
$ gcc -O2 -Wall -pedantic -c ornek.c
Üçüncü kuralımızda çalıştığımız dizinde nasıl temizlik yapacağımızı belirtiyoruz. make clean
komutunu çalıştırdığımızda ornek
dosyası ve .o
ile biten obje dosyaları silinecektir.
Dördüncü kuralımız ise install
şeklinde. Bu kuralda da ornek dosyasında bir değişme olduğunda cp ornek /usr/local/bin
komutu ile dosyayı /usr/local/bin dizini altına kopyalıyoruz.
Makefile içerisindeki her bir kural make
uygulamasına seçenek olarak verilebilir ve ayrıca işletilebilir. Yukarıdaki gibi bir Makefile dosyasına sahipsek make ornek.o
komutuyla sadece ornek.o için verilen kuralın çalıştırılmasını sağlayabiliriz. Veya make install
komutuyla sadece install kuralının çalışmasını sağlayabiliriz. Ancak install
hedefi aynı zamanda ornek
'e bağımlı olduğundan ornek
için girilen kurallar da çalışacaktır. Aynı şekilde ornek
de ornek.o
'ya bağımlı olduğundan ornek.o
kuralı da çalışacaktır.
Şimdi bu Makefile
dosyasının bulunduğu yerde ornek.c
kaynak dosyasını da hazırladığımızı varsayarak aşağıdaki çıktıları inceleyelim:
$ make ornek.o
gcc -O2 -Wall -pedantic -c ornek.c
$ make clean
rm -f ornek *.o
$ make
gcc -O2 -Wall -pedantic -c ornek.c
gcc -O2 -Wall -pedantic -lm -lnsl -o ornek ornek.o
cp ornek /usr/local/bin
Yukarıdaki Makefile örneğimize tekrar dönelim. make clean
komutunu çalıştırdığımızda derleme sonrasında oluşan dosyalar silinmektedir. Peki, bulunduğumuz dizinde ismi clean olan bir dosya mevcut ise ne olur?
$ touch clean
$ make clean
make: `clean' is up to date.
Gördüğünüz gibi clean adında bir dosya var olduğu ve clean için bağımlılık listesi olmadığından dolayı, kuralın güncelliğini koruduğunu ve alttaki komutların çalıştırılmaması gerektiğini düşündü. İşte bu gibi durumlar için özel bir kural mevcuttur: .PHONY
Yukarıda anlatılan problemi giderebilmek için Makefile dosyamızın içeriğine aşağıdaki kuralı da eklemeliyiz:
.PHONY: clean
Böylelikle make clean
komutunun, bulunulan dizinde clean adında bir dosya olsa bile düzgün olarak çalışmasını sağlamış olduk, bir nevi clean
hedefini korumaya almış olduk.
Soyut Makefile Kuralları Tanımlamak
Ö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
sistemi o kadar da yararlı bir şey değil. Zaten gerçekte de en küçük uygulama bile onlarca kaynak dosyasından oluşur. Şimdi böyle bir uygulama için Makefile hazırlayalım.
Örnek: Soyut kurallar kullanılmamış Makefile
Aşağıdaki bir kısmı ortak kullanılan az sayıda kaynak dosyadan oluşan 2 adet uygulamanın derleme sürecini yöneten Makefile örneğini inceleyiniz:
LIBS = -lm \
-lrt \
-lpthread \
$(shell pkg-config --libs openssl)
INCLUDES = -I/usr/local/include/custom
all: server client
server: common.o server.o list.o
$(CC) $(CFLAGS) $(LIBS) -o server common.o server.o list.o
client: common.o client.o
$(CC) $(CFLAGS) $(LIBS) -o client common.o client.o
common.o: common.c common.h
$(CC) $(CFLAGS) $(INCLUDES) -c common.c
server.o: server.c server.h common.h list.h
$(CC) $(CFLAGS) $(INCLUDES) -c server.c
client.o: client.c client.h ortak.h
$(CC) $(CFLAGS) $(INCLUDES) -c client.c
list.o: list.c list.h
$(CC) $(CFLAGS) $(INCLUDES) -c list.c
install: client server
mkdir -p /usr/local/bin
cp client /usr/local/bin/
cp server /usr/local/bin/
uninstall:
rm -f /usr/local/bin/client
rm -f /usr/local/bin/server
clean:
rm -f *.o server client
.PHONY: clean
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 6 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 adet .c 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.c
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 genel olarak *.u1
uzantılı bir dosyadan *.u2
uzantılı bir dosyanın nasıl üretileceğini tanımlar. 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:
Özel Değişken | İşlevi |
---|---|
$< | 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 |
Bu bilgiler ışığında hemen bir örnek verelim. Uzantısı .cpp olan bir kaynak kodundan obje kodunu üretebilmek için aşağıdaki gibi bir kural tanımlayabiliriz:
.cpp.o:
g++ -c $<
Şimdi konuya 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şkeni 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 bulunur. 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 .o
Diğer yapmamız gereken işlem ise üretilecek çalıştırılabilir dosyamızın hangi obje dosyalarına, obje dosyalarımızın ise hangi kaynak dosyalarına bağımlı olduğunu belirtmek olacaktır. İşin en güç tarafı da budur. Her zaman doğru değerleri yazmak o kadar kolay olmayabilir. Bu noktada gcc derleyicisinin -M
, g++ derleyicisinin -MM
seçenekleriyle bağımlılıkları Makefile dosya biçimine uygun şekilde hesaplatabiliriz. Aşağıdaki ekran çıktısına bakalım:
$ gcc -M server.c
server.o: server.c /usr/include/stdio.h /usr/include/features.h \
/usr/include/x86_64-linux-gnu/bits/wordsize.h \
/usr/lib/gcc/x86_64-linux-gnu/4.7/include/stddef.h \
/usr/include/x86_64-linux-gnu/bits/types.h \
/usr/lib/gcc/x86_64-linux-gnu/4.7/include/stdarg.h \
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
/usr/include/x86_64-linux-gnu/bits/sys_errlist.h common.h list.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. Ancak bağımlılıklar hesaplandığında, esasen pek sık değişmeyen sistem kütüphaneleri içindeki referans gösterdiğimiz başlık dosyalarının da eklenmiş olduğunu görüyoruz. Makefile dosyasına her bir .o
bağımlılık listesi için bunlara yazarsak dosya bizim için iyice okunmaz hale gelecek. O yüzden genel sistem başlık dosyalarını atlayarak, Makefile dosyamızı bu yöntemler eşliğinde soyut kurallarla yeniden yazmayı deneyelim.
Örnek: Soyut kuralların kullanıldığı Makefile
LIBS = -lm \
-lrt \
-lpthread \
$(shell pkg-config --libs openssl)
INCLUDES = -I/usr/local/include/custom
.SUFFIXES: .c .o
.c.o:
$(CC) $(CFLAGS) $(INCLUDES) -c $<
.o:
$(CC) $(CFLAGS) $(LIBS) $^ -o $@
all: server client
server: common.o server.o list.o
client: common.o client.o
common.o: common.c common.h
server.o: server.c server.h common.h list.h
client.o: client.c client.h ortak.h
list.o: list.c list.h
install: client server
mkdir -p /usr/local/bin
cp client /usr/local/bin/
cp server /usr/local/bin/
uninstall:
rm -f /usr/local/bin/client
rm -f /usr/local/bin/server
clean:
rm -f *.o server client
.PHONY: clean
Makro Kütüphaneleri Kullanımı
Önceki örneğimizde tüm bağımlılık kurallarını gcc
'ye hesaplattığımızda çıkan uzun listeden pek memnun olmadık. El yordamıyla içlerinden standart kütüphane başlık dosyalarını çıkarıyor olmanın pek uygulanabilir bir çözüm olmadığı ortada. Üstelik dosya sayısı arttıkça Makefile dosyamız içerisindeki karmaşa da artacaktır. Bu sorunları nasıl çözebiliriz?
Çözüm yolu, işleri bizim için kolaylaştıran Makefile makroları yazmaktan veya hazır yazılmış olanları kullanmaktan geçmektedir.
İnternet üzerinde çeşitli Makefile makro kütüphaneleri bulmanız mümkün. Bunlardan bizce fonksiyon seti / kullanım kolaylığı / maksimum fayda dengesinde en iyilerinden biri, Alper Akcan tarafından hazırlanmış olan Makefile.lib
makro kütüphanesidir. Kütüphaneyi https://github.com/alperakcan/libmakefile adresinden indirebilirsiniz.
Makefile.lib
, çapraz derleme işlemlerini CROSS_COMPILE_PREFIX
değişkeninin ayarlanması suretiyle yönetebilmektedir. Ayrıca detaylı çıktı vermesi için make sistemlerinde alışageldiğimiz haliyle verbose opsiyonu V
değişkeni üzerinden V=1
şeklinde bir atama yapmak suretiyle aktifleştirilebilmektedir.
Bu şekilde yazılmış örnek bir Makefile dosyasına bakalım:
target-y = target1
target-y += target2
target1_files-y = \
target_file_shared.c \
target1_file_2.c \
target1_file_3.c
target1_includes-y = \
./ \
/opt/include
target1_libraries-y = \
./ \
/opt/lib
target1_cflags-y = \
-DUSER_DEFINED \
-O2
target1_ldflags-y = \
-luserdefined
target2_files-y = \
target_file_shared.c \
target2_file_2.c \
target2_file_3.c
target2_includes-y = \
./ \
/opt/include
target2_libraries-y = \
./ \
/opt/lib
target2_cflags-y = \
-DUSER_DEFINED \
-O2
target2_ldflags-y = \
-luserdefined
include Makefile.lib
Projenin github sayfasından ve Makefile.lib
dosya içeriğinden kullanımıyla ilgili yardım alabilirsiniz.
Makefile.lib kullandığınızda, tüm bağımlılıklar sistem kütüphaneleri de dahil olarak detaylı biçimde hesaplanır. Bağımlılıkların neler olduğu ve hesaplanmasında hangi komutun kullanıldığı gibi ek bilgiler, bulunulan dizinde nokta ile başlayan (dolayısıyla ön tanımlı ls
komutunda listelenmeyen ve göz kalabalığı oluşturmayan) bir dizin yapısı altında saklanır. Derleme süreci daha sağlıklı ve temiz bir şekilde ilerler. Aşağıdaki Makefile.lib kullanılan bir projedeki derleme zamanı çıktıları görünmektedir:
$ make
DEP /home/demirten/embedded/gateway/.gateway/base64.dep
DEP /home/demirten/embedded/gateway/.gateway/backend.dep
DEP /home/demirten/embedded/gateway/.gateway/database.dep
DEP /home/demirten/embedded/gateway/.gateway/common.dep
DEP /home/demirten/embedded/gateway/.gateway/ini.dep
DEP /home/demirten/embedded/gateway/.gateway/gateway.dep
CC /home/demirten/embedded/gateway/.gateway/gateway.o
CC /home/demirten/embedded/gateway/.gateway/ini.o
CC /home/demirten/embedded/gateway/.gateway/common.o
CC /home/demirten/embedded/gateway/.gateway/database.o
CC /home/demirten/embedded/gateway/.gateway/backend.o
CC /home/demirten/embedded/gateway/.gateway/base64.o
LINK /home/demirten/embedded/gateway/.gateway/gateway
CP /home/demirten/embedded/gateway/gateway
$ ls .gateway/
backend.dep backend.o.cmd base64.o common.dep.cmd
database.dep database.o.cmd gateway.dep gateway.o.cmd
ini.o backend.dep.cmd base64.dep base64.o.cmd
common.o database.dep.cmd gateway.dep.cmd ini.dep
ini.o.cmd backend.o base64.dep.cmd common.dep
common.o.cmd database.o gateway.cmd gateway.o
ini.dep.cmd
Görüldüğü üzere make uygulaması çalıştırıldığında önce bağımlılıklar hesaplanmış, hesaplama sonuçları ve kullanılan komutlar hedef uygulama ismi olan gateway
'den yola çıkılarak .gateway
adıyla oluşturulan dizin altında toplanmış, tüm derleme işlemleri sonucu oluşan obje dosyaları da .gateway
altında biriktirilmeye devam edilmiş ve işlem sonucunda ana dizinde gateway
hedef uygulaması oluşturulmuştur.
Eğer derleme sürecinde daha detaylı ekran çıktısı almak istersek, make V=1
şeklinde komutu çalışamız yeterli olacaktır.
Daha karmaşık bir Makefile.lib örneğine bakalım (gerçek zamanlı harita render kütüphanemizin demo uygulaması kısmından alınmıştır):
libmakefile Örnek 2
-include ${PLATFORM}.config
include Makefile.config
MOC ?= moc
target_o-y = \
libnavigator.o
target_a-y = \
libnavigator.a
target-$(ENABLE_NAVIGATOR_DEMO) = \
navigator-demo
libnavigator.o_files-y = \
tag.h \
navigator.c \
../amalgamation/libamalgamation.o
libnavigator.a_files-y = \
libnavigator.o \
ifeq (${COMMON_POINT_TYPE}, int)
libnavigator.o_cflags-y += \
-DNAVIGATOR_COMMON_POINT_TYPE_INT=1
endif
ifeq (${COMMON_POINT_TYPE}, double)
libnavigator.o_cflags-y += \
-DNAVIGATOR_COMMON_POINT_TYPE_DOUBLE=1
endif
ifeq (${COMMON_BOUND_TYPE}, int)
libnavigator.o_cflags-y += \
-DNAVIGATOR_COMMON_BOUND_TYPE_INT=1
endif
ifeq (${COMMON_BOUND_TYPE}, double)
libnavigator.o_cflags-y += \
-DNAVIGATOR_COMMON_BOUND_TYPE_DOUBLE=1
endif
navigator-demo_depends-y = \
libnavigator.o
navigator-demo_files-y = \
navigator-qt.cpp \
navigator-qt-moc.cpp \
libnavigator.o
navigator-qt-moc.cpp: navigator-qt.h
${Q}$(MOC) ${navigator-demo_cflags-y} navigator-qt.h -o navigator-qt-moc.cpp
navigator-demo_cflags-y = \
$(shell pkg-config QtGui --cflags)
navigator-demo_cflags-${ENABLE_RENDER_CAIRO} += \
-DNAVIGATOR_ENABLE_RENDER_CAIRO=1
navigator-demo_cflags-${ENABLE_RENDER_FLOATING_SCALE} += \
-DNAVIGATOR_ENABLE_RENDER_FLOATING_SCALE=1
navigator-demo_ldflags-y = \
$(shell echo $(CC) | awk '{ if (/mingw/) { print "-Wl,-Bstatic -static-libgcc -L/mingw/Qt/lib -lmman"; } }') \
$(shell pkg-config freetype2 --libs) \
-lexpat \
-lm \
-lz \
$(shell pkg-config QtGui --libs) \
-lpthread
navigator-demo_ldflags-${ENABLE_RENDER_CAIRO} += \
$(shell pkg-config cairo --libs)
navigator-demo_ldflags-${ENABLE_COMPRESS_SNAPPY} += \
-lsnappy
navigator-demo_ldflags-${ENABLE_INPUT_TIF} += \
-lgeotiff \
-ltiff
navigator-demo_ldflags-${ENABLE_RENDER_PNG} += \
$(shell pkg-config libpng --libs)
navigator-demo_ldflags-${ENABLE_RENDER_JPEG} += \
-ljpeg
include Makefile.lib
İlk 2 satıra dikkatlice tekrar bakalım:
-include ${PLATFORM}.config
include Makefile.config
Satır başında yer alan - karakteri, ilgili dosya include edilmek için arandığında dizinde yer almıyorsa make sürecinin hata vermeyip yoluna devam etmesini sağlamak için konulmuştur. Eğer make
çalıştırılırken PLATFORM
değişkenine atama yapılırsa, öncelikle ilgili dosya include edilecektir.
Ardından her koşulda Makefile.config
dosyasının include edildiğini görmekteyiz.
Şimdi PLATFORM değişkenin Debian olarak atandığı örnek bir make
kullanımını ve Debian.config
ile Makefile.config
dosyalarının içeriklerini görelim:
$ make ENABLE_INPUT_OSM=n PLATFORM=Debian -j 8
Yukarıdaki kullanımda öncelikle ENABLE_INPUT_OSM
değişkeninin değeri n
olarak, PLATFORM
değişkenin değeri Debian
olarak atanmakta ve 8 paralel derleme sürecine imkan verecek şekilde uygulama başlatılmaktadır.
# Debian.config
ENABLE_NAVIGATOR_DEMO ?= y
ENABLE_RENDER_COPYRIGHT ?= y
RENDER_COPYRIGHT_COLOR ?= 0x20ddbbcc
ENABLE_COMMON_CLIPPER ?= y
COMMON_FILE_CACHE_SIZE ?= 0x1400000
ENABLE_INPUT_TIF ?= y
ENABLE_INPUT_OSM ?= y
ENABLE_INPUT_JPEG_TURBO ?= n
ENABLE_INPUT_TILE ?= y
ENABLE_RENDER_CAIRO ?= n
ENABLE_RENDER_PNG ?= y
ENABLE_RENDER_JPEG ?= y
ENABLE_RENDER_JPEG_TURBO ?= n
ENABLE_RENDER_TEXT_ON_PATH ?= y
ENABLE_COMPRESS_SNAPPY ?= n
ENABLE_RENDER_FLOATING_SCALE ?= y
ENABLE_EXTERNAL_LOGGER ?= n
ENABLE_DEBUGF ?= n
ENABLE_ERRORF ?= y
ENABLE_INFOF ?= y
ENABLE_TODOF ?= n
ENABLE_ASSERTF ?= n
COMMON_POINT_TYPE ?= int
COMMON_BOUND_TYPE ?= int
# Makefile.config
ENABLE_NAVIGATOR_DEMO ?= y
ENABLE_RENDER_COPYRIGHT ?= y
RENDER_COPYRIGHT_COLOR ?= 0x202a77a3
ENABLE_COMMON_CLIPPER ?= y
COMMON_FILE_CACHE_SIZE ?= 0x2000000
ENABLE_INPUT_TIF ?= y
ENABLE_INPUT_OSM ?= y
ENABLE_INPUT_JPEG_TURBO ?= y
ENABLE_INPUT_TILE ?= y
ENABLE_RENDER_CAIRO ?= n
ENABLE_RENDER_PNG ?= n
ENABLE_RENDER_JPEG ?= y
ENABLE_RENDER_JPEG_TURBO ?= y
ENABLE_RENDER_TEXT_ON_PATH ?= y
ENABLE_COMPRESS_SNAPPY ?= y
ENABLE_RENDER_FLOATING_SCALE ?= y
ENABLE_EXTERNAL_LOGGER ?= n
ENABLE_DEBUGF ?= n
ENABLE_ERRORF ?= y
ENABLE_INFOF ?= y
ENABLE_TODOF ?= n
ENABLE_ASSERTF ?= y
COMMON_POINT_TYPE ?= double
COMMON_BOUND_TYPE ?= double
Bu şekildeki konfigürasyon dosyaları yardımıyla, çeşitli değişkenlerin hem öntanımlı değerlerini atayabilmekte, hem de kullanıcı tarafından özellikle belirtilmiş ise, ilgili değeri kullanabilmekteyiz. Bunun için ?=
tanımından faydalanıyoruz. Bu tanım Makefile içerisinde, eğer değişkene atama yapılmamış ise → ata şeklinde işlev görmektedir
Bu yapı ile farklı konfigürasyon dosyaları kullanılabildiği gibi, konfigürasyon dosyası içerisinde ?=
şeklinde tanımlanmış değişkenleri aşağıdaki şekilde ezmek de mümkün olmaktadır:
$ make ENABLE_DEBUGF=y PLATFORM=Debian
Yukarıdaki komut, bir önceki Makefile örneğiyle birlikte değerlendirildiğinde, Debian.config dosyası içerisinde yer alan ENABLE_DEBUGF ?= n
şeklindeki satırın geçersiz olmasını sağlayacaktır
Eğer Debian.config içerisinde bu tanım ENABLE_DEBUGF = n
şeklinde doğrudan eşittir karakteri ile yapılmış olsaydı, öncesinde atanan değerden bağımsız olarak bu konfigürasyon dosyası işlendiğinde her zaman dosyanın içindeki atama geçerli olacaktı. Buradaki küçük detaylar dikkatle kullanıldığında, aynı kod üzerinden farklı konfigürasyonlarda derleme işleminiz kolaylaşacaktır.
Make Alternatifleri
CMake
CMake, birden çok platformu destekleyen (Linux, Apple, Windows) güçlü bir inşa aracıdır.
Geliştirilmesi büyük ölçüde Kitware firması tarafından yapılmaktadır.
CMake kendi kural dosyalarını işleyerek, hangi platformda çalışıyorsa o platform için doğal inşa sistemine ait kural dosyaları oluşturur (*NIX sistemler için Makefile).
Aşağıdaki örnek CMake dosyasını inceleyiniz:
if (${UNIX})
set (DESKTOP $ENV{HOME})
else()
set (DESKTOP $ENV{USERPROFILE}/Desktop)
endif()
set (PRJ ${DESKTOP}/common/svn )
set (FILELIST ${PRJ}/src/source.txt )
message(STATUS "CMAKE_GENERATOR : ${CMAKE_GENERATOR}")
message(STATUS "DESKTOP : ${DESKTOP}")
message(STATUS "PRJ : ${PRJ}")
message(STATUS "FILELIST : ${FILELIST}")
message(STATUS "SYSTEM_NAME : ${CMAKE_SYSTEM_NAME}")
project(project_name)
include_directories(
${PRJ}/src
${PRJ}/includes
)
# Load SRC Variable from file
file(READ ${FILELIST} SRC)
string(REGEX REPLACE "#.*$" "" SRC ${SRC})
string(REPLACE "\n" ";" SRC ${SRC})
add_executable(${PROJECT_NAME} ${SRC} )
foreach (f ${SRC})
set_source_files_properties(${f} PROPERTIES LANGUAGE CXX)
endforeach(f)
if (${WIN32})
link_directories(
)
add_definitions(
-DDEFINE1
)
target_link_libraries(
${PROJECT_NAME}
wsock32.lib
)
endif()
SCons
SCons, Python dili ile geliştirilmiş, birden çok platformu destekleyen diğer bir inşa aracıdır.
Konfigürasyon betikleri Python dosyalarından oluşur
C, C++ ve Fortran için doğrudan kod bağımlılık analizi desteği sunar.
Versiyon kontrol sistemlerine doğrudan destek verir (SCCS, RCS, CVS, Subversion, BitKeeper, Perforce).
Dosyaların değişimindeki kontroller son değiştirilme tarihi yerine MD5SUM değerleri üzerinden yapılır. Parametre vererek dosyanın değiştirilme tarihine bakacak hale de getirmek mümkündür.
Aşağıdaki örnek SCons dosyasını inceleyebilirsiniz:
env = Environment()
env.Append(CPPFLAGS=['-Wall','-g'])
env.Program('hello', ['hello.c', 'main.c'])
Rake
Ruby ile geliştirilmiş ve daha çok Ruby projelerinde kullanılan bir inşa aracıdır.
Ruby’nin DSL tanımlama noktasındaki güçlü özelliklerini kullanır.
Kurallar Rakefile
dosyalarında tutulur.
Rakefile içerisinde Ruby dilinde görevler ve kurallar tanımlanabildiği gibi, dilinden kendisinden gelen ekstra özelliklerle örneğin çalışma zamanında yeni sınıflar dahi üretilebilir.
Aşağıdaki örnek Rakefile dosyasını inceleyebilirsiniz:
namespace :cake do
desc 'make pancakes'
task :pancake => [:flour,:milk,:egg,:baking_powder] do
puts "sizzle"
end
task :butter do
puts "cut 3 tablespoons of butter into tiny squares"
end
task :flour => :butter do
puts "use hands to knead butter squares into 1{{frac|1|2}} cup flour"
end
task :milk do
puts "add 1{{frac|1|4}} cup milk"
end
task :egg do
puts "add 1 egg"
end
task :baking_powder do
puts "add 3{{frac|1|2}} teaspoons baking powder"
end
end