hedef: bağımlılıklar
<TAB> komut
<TAB> komut
<TAB> ...
Burada <TAB> kullanmak zorunludur. Bir satır boşluk bırakmak GNU
make için zorunlu
olmamakla birlikte bazı Unix sürümleriyle uyumluluk için boşluk bırakılması
gereklidir.
İlk satırda
hedefin oluşturulmasında etkili(bağımlı) dosyalar birbirinden boşluk ile ayrılarak
sırasıyla yazılır. Etkili(bağımlı) dosyalar hedefin oluşturulmasında kullanılacak
olan dosyalardır. Eğer bağımlı dosyalarda herhangi birinin değiştirilme tarihi
hedefin tarihinden daha yeni ise hedef yeniden oluşturulur. Bu sınama ile
hedefin tekrar tekrar oluşturulması engellenmiş olunur. hedef ve bağımlı
dosyalar belirtildikten sonra bir alt starırda <TAB> ile başlanarak
komutlar yazılır.
keypad: keypad.c
gcc keypad.c -o output_file_name
Örnekte hedefe verilen isim keypad, bağımlı dosya keypad.c.
Komut kısmında da bu hedefe yönelik kod yazılmıştır.(kodun
zorunlu olan bir <TAB> içerinde yazıldığına dikkat ediniz.)
Hedeflerin diğer bir özelliği de tek başlarına
işletilebilir olmasıdır. Örneğin elimizde aşağıdaki gibi yazılmış bir makefile
dosyası bulunsun:
dest1: file.c
#commands
dest2: file.c
#commands
dest3: file.o
#dommands
Konsoldan
Makefile bulunduğu dizine geçtikten sonra “make dest1”, ya da “make dest2” gibi
sadece o hedefi çalıştıracak komut
verebiliriz. Eğer hiç bir
hedef belirtmeden sadece make komutunu çalıştırırsak işlem ilk hedefi çalıştırır. Genelde Makefile dosyalarında ilk hedefe diğer hedefleri bağlayabiliriz ve bu sayede tüm hedefler de çağrılmış olur. Bu yüzden ilk hedefe çoğunlukla “all” ismi verilir ve
konsoldan bu hedef “make all” yerine sadece “make” yazılarak kısa
bir yazımla çağrılmış olur. Makefile kurallarına ufak bir
uygulama üzerinden
anlatalım.
CC = gcc
CFLAGS = -O2
-Wall -pedantic
LIBS = -lm
-lnsl
test: test.o
$(CC)
$(CFLAGS) $(LIBS) -o test test.o
test.o: test.c
$(CC)
$(CFLAGS) -c test.c
clean:
rm -f test
*.o
install: test
cp test
/usr/bin
Dosyanın başında yaptığımız ilk işlemler atama işlemleridir.
Makefile içinde bu şekilde atama işlemi yapabiliriz. Kullanımı ise koddan da görüleceği gibi $(DEGISKEN). Makefile yazılırken kullanılacak derleyici,
derleyiciye ait komutlar ve kütüphane komutları bu şekilde tanımlanır.
Bu sayede tek bir noktadan kontrol oluşturulur.
Makefile dosyasında çoğunlukla clean ve install hedefleri bulunur. Neredeyse her makefile içinde bu amaçlı çalışan hedefler
bulunmaktadır. Hedef ismi yaygın olarak “clean” olarak kullanılsa da makefile
yazan kişi istediği ismi verebileceği gibi bu hedef altında başka yardımcı
kodları da koşturabilir. Örneğin obje dosyalarının silinmesi yanında
derleme sonrası oluşan çalıştırılabilir çıktıyı ya da .so uzantılı
dosyaları da sildirebilir. Burada yazanın proje içindeki gereksinimine göre işlem yapması
gerekir. Ayrıca versiyon kontrol programları ile proje commit edilmeden önce bu komut kullanılarak repostory
bulunmaması gereken dosyaları da bu şekilde temizlediğimizi göz önüne almak gerekir. Tüm bu
isteklere cevap verecek bir “clean” hedefi yazmak gerekir.
Örnek makefile dosyasında clean komutu
için bağımlılık listesi
olmadığına dikkat çekmek gerekir. Çünkü bağımlı listedeki belirtilen dosyada herhangi
bir değişiklik olduğunda çağrılan hedef çalışır. Fakat biz clean
hedefine her seferinde koşulsuz olarak çalıştırmak istediğimizden
bağımlılık listesi clean komutu için kullanılmaz.
Clean komutunu kullanırken şu hata
durum da oluşabilir: proje dosyaları içince “clean” adında bir dosya
var olması durumunda siz clean komutunu çalıştırmak
isterseniz bunu yapamazsınız. make clean komutuna karşılık “make: `clean' is
up to date.” cevabını alırsınız. İşte bu hataya karşı “.PHONY: clean”
komutunu makefile dosyanıza eklemeniz gerekir. Aslında bu komutu tüm bağımlılık listesi
olmayan hedefler için kullanmalıyız.
“install” hedefi ise projenin
derlenmesi sonucunda oluşan sonuç dosyanın nereye
kopyalanması gerektiğini gösterir. Bu adım da bir
makefile dosyasında bulunması gerekir. Çünkü kullanıcı sonuç dosyasının nerede
oluşacağını bilemez. Ayrıca bu hedefin hangi dizine kopyalama yapacağı konsola çıktı olarak da verilmelidir.
2-Orta Düzeyde
Makefile
Basit Düzeyde Makefile başlığı altında
verilen makefile dosyası uygulamada kullanılmayacak kadar basit düzeyde. Zaten uygulamalarda
projelerde yüzlerce hatta binlerce dosya bulunabilir. Peki bu büyüklükteki projeler
için nasıl bir makefile yazmak gerekir.
CC = g++
CFLAGS = -O2 -Wall -pedantic
LIBS = -lnsl -lm
INCLUDES = -I/usr/local/include/custom
all: server client
server: ortak.o server.o list.o que.o \
data.o hash.o
$(CC) $(CFLAGS) $(LIBS) -o
server ortak.o server.o \
list.o que.o data.o
hash.o
client: ortak.o client.o
$(CC) $(CFLAGS) $(LIBS) -o
client ortak.o client.o
ortak.o: ortak.cpp ortak.h
$(CC) $(CFLAGS) $(INCLUDES) -c
ortak.cpp
server.o: server.cpp server.h ortak.h
$(CC) $(CFLAGS) $(INCLUDES) -c
server.cpp
client.o: client.cpp client.h ortak.h
$(CC) $(CFLAGS) $(INCLUDES) -c
client.cpp
list.o: list.cpp list.h
$(CC) $(CFLAGS) $(INCLUDES) -c
list.cpp
que.o: que.cpp que.h
$(CC) $(CFLAGS) $(INCLUDES) -c que.cpp
data.o: data.cpp data.h
$(CC) $(CFLAGS) $(INCLUDES) -c
data.cpp
hash.o: hash.cpp hash.h
$(CC) $(CFLAGS) $(INCLUDES) -c
hash.cpp
install: client server
mkdir -p /usr/local/bin/test
cp client /usr/local/bin/test
cp server /usr/local/bin/test
uninstall:
rm -rf /usr/local/bin/test
clean:
rm -f *.o server client
.PHONY:
clean
Yukarıdaki makefile soyut kurallar kullanılmadan
yazılmıştır. Proje sayıda dosyadan oluşmasına rağmen makefile yazımı yorucu ve
zaman alıcı bir görünüm sunmaktadır ki bu basit projenini 50 dosyadan oluştuğunu
düşünürsek 50 adet dosyanın derlenmesini
tek tek bildirmek gerekecektir. Bu durum hem zaman kaybı hemde hatalara açıktır. Orta düzeyeki projelerde bile yukarıdaki örnek biçiminde makefile yazmaktan
kaçınmalıyız.
Bu fikirle hareket edilirse çözüm olarak soyut
kurallar (abstract rules) imdadımıza yetişir. Bu
soyut kurallar bize örneğin tüm .bet uzantılı dosyalardan
pratik ve hatasız bir biçimde nasıl .son uzantılı dosyalar oluşturulacak
sorununa yardımcı olur.
.bet.son:
komutlar
komutlar
...
Yukarıdaki özet kullanım .bet kaynak
dosya .son hedef dosyayı gösterir ve dikkat edilirse bir bağımlılık listesi kullanılmadığı dikkati çeker. Daha kapsamlı bir örneğe geçmeden önce burada bilmemiz gereken
bazı değişkenler var.
·
$< 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.
Bir örnek ile bu değişlenlerin nasıl
kullanıldığına bakalım.
.c.o:
$(CC)
-o $@ -c $< $(LIBS)
@echo
">> $<: compiled $@ created"
all: $(OBJECT_FILE)
gcc
-o $(OUTPUT_FILE) $^ $(LIBS)
@echo
"Link done"
.c.o hedef yazımı yukarıdaki .bet
ile .son karşılık gelir yani kaynak dosyaların uzantısı .c hedef dosyanın uzantısı
.o olmalı bildrimini yapmış olduk.
$(CC) -o $@ -c $< $(LIBS) bu satırda kaynak
dosyadan hedef dosyatı oluşturacak komutu işleriz. $@ ve $< anlarmları
kapalı da olsa aslında basit bir ifadesi vardır. $@ burada oluşturulacak hedef
dosyanın ismini içerirken $< ise kaynak dosyanın ismini
içerir. Zaten komut bir .c(source) dosyasından .o(obje)
dosyasının oluşmasını sağlıyor. Bunun açık yazılmış hali
şu şekilde olurdu:
$(CC) -o mayfile.o -c mayfile.c $(LIBS). Peki neden
bu şekilde açık değilde yukarıdaki
gibi kapalı yazım yaptık. Çok sayıda dosyadan oluşan projelerde kapalı yazım
ile dosyaların tek tek derlenme komutunu biz değil bu kapalı yazım yapar. Yani
işin güzel tarafı bu kapalı yazım
ile projede ne kadar .c(source) dosyası var ise o kadar .o(obje) dosyasını tek
bir satır komut ile oluşturmuş oluruz.
İşin tam olarak nasıl bu kadar basitçe yapıldığını anlamak için
all: hedefini incelememiz gerekir. Çünkü .c.o ile all hedefleri
birbirleriyle yardımlaşarak çalışırlar. All
hedefine ve altında işlenen komutlara bir bakalım.
all: $(OBJECT_FILE)
$(CC)
-o $(OUTPUT_FILE) $^ $(LIBS)
@echo
"Link done"
Kodu inceleyenin ilk dikkatini çekmesi gereken nokta
all hedefinin bir bağımlığının olması. All hedefi $(OBJECT_FILE) bağımlıdır. Yani all
hedefinin altında yazılmış olan $(CC) -o $(OUTPUT_FILE) $^ $(LIBS) komutunun işlenebilmesi için $(OBJECT_FILE) ile belirtilen
dosyalardan en az birinin değişmiş olması
gerekir. OBJECT_FILE bir değişiklik ya da eksikli olduğunda ilk olarak bu
ihtiyaçlar
giderilir.
OBJECT_FILE tanımlaması makefile dosyasının başında yapılmalıdır.
Örneğin şu şekilde
olabilir:
OBJECT_FILE = test.o test2.o test3.o test4.o test5.o. Bu
durumda all hedefi aslında şu şekilde yazılmış olur: all: test.o test2.o
test3.o test4.o test5.o.
Artık all hedefi ile .c.o hedeflerinin yardımlaşarak
çalışmasını daha
basit bir şekilde açıklayabiliriz. All hedefi bağımlılık listesindeki her bir elemanı
kontrol eder, eğer bir bağımlılığı bulamaz ya da güncel değil ise o bağımlılığın
oluşması için .c.o
hedefine çağrı yapar. .c.o hedefide üstüne düşen görevi yaparak all hedefinin ihtiyacı olan dosyayı üretir. İşin pratikliğini
şu örnek ile daha
da iyi anllayabiliriz: all hedefine bağlı dosyaların hepsinin değiştiğini ya da
silindiğini var sayarsak tüm dosyalar sırasıyla otomatik bir şekilde oluşturulacaktır.
İşte bu durum makefile yazana büyük bir kolaylık sağlar.
Farkettiyseniz projemizde kullandığımız dosya isimleri sadece tek bir noktada
geçiyor;
OBJECT_FILE = test.o test2.o test3.o test4.o test5.o. Artık projeye
eklenen her bir dosyanın ismini bu listeye eklemek yeter.
Aşağıdaki örnek kod ile projenizdeki tüm
dosyları derleyip obje dosyası oluşturabilir ve daha sonrasında link ederek çıktı dosyanızı
almayı sağlayabilirsiniz.
CC = gcc
CFLAGS = -O2 -Wall
LIBS = -lm -lnsl
OBJECT_FILE = test.o test2.o test3.o test4.o test5.o
OUTPUT_FILE_NAME = pinkFloyd
.PHONY: clean
.SUFFIXES: .c .o
.c.o:
@$(CC) -o $@ -c $<
$(LIBS)
@echo " -[CC] $<
compiled"
all: $(OBJECT_FILE)
@$(CC) -o
$(OUTPUT_FILE_NAME) $^ $(LIBS)
@echo "
---------------------------------------------------------------------"
@echo " |-- Linking is successful. Output file name:
$(OUTPUT_FILE) was created --|"
@echo "
---------------------------------------------------------------------"
@##cp $(OUTPUT_FILE_NAME)
/home/zafer/Desktop/
@#@echo "output file
$(OUTPUT_FILE) copyed to Desktop"
clean:
@rm -f $(OUTPUT_FILE_NAME)
*.o
@echo "
---------------------------------------------------------"
@echo " |-- All object file and outputfile:
$(OUTPUT_FILE) deleted --|"
@echo "
---------------------------------------------------------\n"
Not: .SUFFIXES:
.c .o ile
kullanılacak
uzantılar tanımlanır.
Çalıştırılan kodun terminalde görünmemesini istiyorsanız ilgili kodun başına @ koymanız yeterli. Bunu
yapmaz iseniz çalıştrılacak komut
ilk olarak komut satırına yazılır daha sonra çalışır. Bu da komut
satırının kirlenmesine sebep olur ve ekranı takip etmeyi zorlaştırır.
3-İleri Düzeyde Makefile
İleri düzeyde makefile yazabilmek için yukarıda kullandığımız komutlardan fazlasını
bilmek gerekir. Örneğin bir önceki örnekte clean hedefinde kullandığımız
rm -f $(OUTPUT_FILE_NAME) *.o komutu
gerçek uygulamalarda yetersiz kalacaktır. Sizinde görebileceğiniz gibi
silme işlemi sabit bir dizinde -içinde
olduğumuz dizin- sadece
obje dosyalarını silmeye yöneliktir.
Gerçek
uygulamalarda projeler birden fazla hatta çok sayıda klasörlerden oluşur. Doğal olarak da obje dosyalarının
tek bir klasörde bulunmasını beklemek anlamsız olur. Yapılması
gereken bu düzene uyum sağlayarak tüm
klasörleri dolaşıp derleyicinin
oluşturduğu obje dosyalarını silmektir.
Tüm
klasörleri dolaşıp içlerindeki obje dosyasını silmek göze çok zahmetli görünsede bu işi find komutu ile yapabiliriz. Bu
komut istenilen öğeyi komutun çağrıldığı dizinden başlayarak aramaya başlar. İstenilen ögeyi bulduktan sonra tek yapmamız gereken bunu rm komutuna
parametre olarak geçmektir. Hatta istenirse hangi dizinde
silme yapıldığını komut terminaline de yazdırarak kullanıcıyı
bilgilendirmiş oluruz. Tüm bu anlatılanlara uyan örnek kullanım aşağıdaki gibi olur.
@find -name '*.o' -exec rm -f {} \; -exec
echo " >> "{} " Deleted" \;
@rm -f $(OUTPUT_FILE) *.o
@echo "\n
---------------------------------------------------------"
@echo " |-- All object file and outputfile:
$(OUTPUT_FILE) deleted --|"
@echo " ---------------------------------------------------------\n"
Ana konumuzu kaçırmamak için Makefile konusunu şimdilik burada sonlandırıyorum. İleri düzeyde makefile konusuna daha sonra devam edeceğim.
Faydalanacağını düşündüğünüz kişilere iletin, eksik ya da yanlış olduğunu düşündüğünüz yerleri belirtin.
Şimdi Yazmaya Devam ....