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:
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:
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:
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:
Dosyamızı hazırladıktan sonra make
komutunu çalıştıralım:
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:
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:
Üçü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:
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?
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:
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:
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:
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:
Ş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.
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:
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:
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
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:
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:
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):
İlk 2 satıra dikkatlice tekrar bakalım:
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:
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.
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:
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:
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:
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:
Last updated
Was this helpful?