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:

  1. make uygulamasına Makefile içerisindeki bir hedef kural ismini parametre olarak vermediğimizde öntanımlı olarak dosyada bulduğu ilk hedefi gerçekleme çalışır
  2. Dosyamızdaki ilk hedefin bolgeler olduğunu gördük
  3. bolgeler hedefinin bağımlılıkları marmara, karadeniz ve ege dosyaları şeklindeymiş
  4. Eğer bulunduğumuz dizinde bolgeler dosyası zaten mevcut ve son değiştirilme tarihi, bağımlılıkları olan marmara, karadeniz ve ege 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
  5. Bu nedenle bolgeler hedefini gerçeklemek için öncelikle dizinde mevcut olmayan marmara hedefini gerçeklemek için işlemlere başlandı
  6. marmara hedefi de benzer şekilde istanbul ve bursa dosyalarına bağımlı ve her iki dosya da sistemde yok
  7. Bir önceki durumdan farklı olarak, istanbul dosyası dizinde mevcut olmadığı gibi bu dosyayı üretecek herhangi bir hedef de tanımlı değil.
  8. 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

results matching ""

    No results matching ""