Merhaba sevgili e-bergi okuyucuları. Bu ay size nasıl yapılır köşesinden make/makefile kullanımı hakkında bilgi vereceğim. Sözü fazla uzatmadan hemen başlayalım isterseniz. Yazılım dünyasında, make komutu dosyaları bir formdan diğerini çevirme işlemini otomatikleştiren bir araçtır. Çevrilecek programların ihtiyaç duyacağı diğer programları kontrol ettikten sonra gerekli programları çalıştırarak çevirme işlemini gerçekleştirir. Çevrilen programı çalıştırmak için ihtiyaç duyulan diğer programlara bağımlılıklar (dependencies) denir. Unix/Linux tabanlı işletim sistemlerinde, kaynak kodu(source code) nesne koduna (object code) derlerken ve nesne kodlarını kütüphane(library) ve çalıştırılabilir (executable) dosyalarda bağlarken sıklıkla kullanılır. Make, bir çok yazılımda kullanılır; fakat C ve C++ dillerinde diğerlerinden daha geniş kullanım alanına sahiptir. Make komutu ile program derleyebilir, döküman derleyebilir (Latex) yada program kurabiliriz.

Make kullanımını anlatmadan önce isterseniz make komutunun tarihçesine bir göz atalım. Aslında, bu tarz işlev gören bir kaç tane daha komut var ama make bunların arasında en yaygın olanıdır. Make, Stuart Feldman tarafından 1977 yılında Bell Lab’ında geliştirilmiştir. Unix’e katılmasından sonra yaygınlaşmaya başlamıştır. Dr. Feldman 2003 yılında bu buluşu sayesinde ACM’nin "Yazılım Sistemleri" ödülünü almıştır.

Make Nasıl Kullanılır?

Şimdi gelelim make komutunun nasıl kullanılacağına. Make genel olarak projenin derlenmesi, kurulması, sistemden kaldırılması, derlenmiş olan dosyaları silmesi gibi sıklıkla yapılan işlemleri basitleştirmeye yarar. Konsolda make komutuna basit bir parametre vererek bu işlemler veya özel komutlar gerçekleştirilebilir. En basit kullanımı konsolda make yazılması ile gerçekleşir. Bundan sonra, make açıklayıcı dosya (makefile) üzerinden geçerek hedef dosyayı oluşturacaktır. Make aynı zamanda, bütün hedef dosyaların birbirine bağımlılığını da kontrol eder, bunlar arasında bir bağımlılık zinciri oluşturur. Sonuna ulaştıktan sonra, bazen farkeder ki bu zincirdeki her dosya tamamen derlenmek zorunda değildir, böyle durumlarda ilgili dosyaların ilgili yerlerini derler ve kullanır. Daha açık olmak gerekirse, make kaynak dosyalardan nesne dosyasını oluşturur, çalıştırılabilir dosya oluşturmak için nesne dosyasını bağlar. Eğer, kaynak dosya değişirse, sadece nesne dosyası derlenmeye ihtiyaç duyar ve daha sonra aynı şekilde bu dosya çalıştırılabilir dosyaya bağlanır. Böylece, bütün kaynak dosyaları tekrardan derlemeye gerek kalmaz. Basit bir örnek vermek gerekirse, Debian web sitesi, tamamen statik html sayfalardan oluşur. Bu sayede yansılanması daha kolay hale gelir ve statik sayfalar web sunucusuna çok az yük getirir. Ancak binlerce sayfadan oluşan Debian web sitesi, statik olmasına rağmen çok hızlı güncellenebilmektedir. Aynı zamanda siteyi ziyaret ettiyseniz farketmiş olacağınız gibi, tarayıcınızın dil ayarına göre sayfanın o dile çevirilmiş bir versiyonu mevcut ise karşınıza o getirilmektedir. Tüm bu dinamiklik alt tarafta kullanılan, çoğunluğu wml, binlerce dosya tarafından sağlanmaktadır. Her 3-4 saatte bir CVS'te bulunan kaynak kodu çekilerek make ile wml dosyalarından html dosyaları üretilmekte, sayfalar arası hiyerarşiler düzenlenmekte, farklı dillere çevirilen sayfalar kontrol edilmekte, bazı programlar ve betikler çalıştırılmaktadır. Kısaca özetlemek gerekirse böyle; ama gerçekte tüm sitenin yeniden oluşturulması için gerçekten oldukça kompleks işlemler yapılmaktadır. Make komutunun nasıl çalıştığını da anladıktan sonra, şimdi make komutunun yanına verilebilecek parametrelere bir göz atmak istiyorum.

  • -f: Öntanımlı kural dosyası adı dışındaki kural dosyaları için $make -f <dosya_adi>kullanılır.</dosya_adi>
  • -n: Hiçbir komut çalıştırılmaz ancak mevcut duruma göre hangi komutların çalıştırılması gerektiği gösterilir.
  • -i: Eğer bir kural sırasında hata oluşursa, make durur. Bu argüman ile hata kodları önemsenmez.
  • -k: -i’ye benzer, ancak hata oluşan kuralın diğer kuralları yürütülmez.
  • -s: Normalde yürütülecek her kural yürütülmeden hemen önce ekrana yazılır. Bu argüman bunu engeller.
  • -e: Çevre değişkenlerinin kural dosyası değişkenlerinden üstün olmasını sağlar.
  • -j[GNU]: Çok işlemcili makinalarda, eş zamanlı yürütülebilecek kurallar eş zamanlı yürütülür.

Makefile Dosyaları

Proje dizini içinde make komutu verilmesi durumunda,yani –f ile bir dosya ismi girilmediyse, make öncelikle bulunulan dizin içinde, sırasıyla, GNUmakefile, makefile veya Makefile dosyalarından birini arar. Bu dosya hemen hemen her projede Makefile dır. Zaten programımızı çağırdığımızda ilk önce makefile yada Makefile dosyasını arar ve genellikle bulur, eğer bulamazsa GNUmakefile’ı çalıştırır. Şimdi isterseniz bir Makefile dosyasının yapısını ve içeriğini inceleyelim.

hedef: bağımlılıklar
  <tab> komut
  <tab> komut
  <tab> ...
  Diğer kurala geçmeden bir boş satır
  ...</tab></tab></tab>

Hedef: Bir kural dosyasında birden çok kural bulunabilir. Her kural bir hedef için tanımlanır. Bu hedefler "$make hedef" komutu ile çağırılır. Make komutunun gerçek faydası, hedeflerin oluşturulacak olan dosyaların adları olduğu durumlarda ortaya çıkar. Bağımlılıklar: Bir kuralın yürütülüp yürütülmeyeceği, bir takım önşartlara bağlı olabilir. Bu önşartlar bir dosya adı olabileceği gibi, dosya olmayan hedefler de olabilir. Komut: Komutlar, komut kipinde yazılan buyruklardan oluşur. Özel Amaçlı Hedef: make tarafından önceden tanımlı başka anlamları olan hedef isimleridirler. Nokta (.) ile başlarlar.

Makefile dosyasının yazılması için uyulması gereken bir takım kurallar var. İsterseniz kısaca onlara da değinelim.

  • Sadece "$make" komutu kullanılırsa, kural dosyasındaki sahte olmayan ilk hedef yürütülür.
  • Herhangi bir satırı bölmek için tersbölü () karakterini kullanıp, aynı satırdaymışçasına bir sonraki satırdan devam edilebilinir.
  • Herhangi bir satır # karakteri ile başlarsa, make komutu bu satırı dikkate almaz.(command)
  • Hedefler ile bağımlılıklar, hedeflerden sonra gelen bir iki nokta (:), karakteri ile ayrılır.
  • Her komut satırı bir <tab>karakteri ile başlamalıdır.</tab>
  • @ ile başlayan komut satırları yürütülmeden önce ekrana yansıtılmaz.

Şimdi bir örnek inceleyelim.

CC = gcc
  CFLAGS = -O2 -Wall -pedantic
  LIBS = -lm -lnsl
  hw1: hw1.o
      $(CC) $(CFLAGS) $(LIBS) -o hw1 hw1.o
  hw1.o: hw1.c
      $(CC) $(CFLAGS) -c hw1.c
  clean:
      rm -f hw1 *.o
  install: hw1
      cp hw1 /usr/bin

Bu dosyada ilk satırda CC ile nitelendirdiğimiz şey kullanacağımız derleyicinin adıdır. Bu örnekten de anlaşılacağı gibi burada C derleyicisi kullanılmıştır. Makefile dosyasının içinde bu şekilde tanımlamalar yapıp, daha sonra bunu $ (değişken) şeklinde kullanabiliriz. İkinci satırda CFLAGS olarak tanımladığımız şey de derleyiciye vereceğimiz seçeneklerdir. Adından da anlaşılacağı gibi üçüncü satırdaki LIBS ile nitelendirdiğimiz şey de, derlerken kullanacağımız kütüphanelerin listesidir. Şimdi bundan sonra, yazdığımız kurallarında nasıl algılandığını anlatacağım size :) Kuralları anlamak ise çok basit. Başta yazdığımız değişkenleri tek tek yerine koyduğumuzda,

gcc –O2 –Wall –pedantic –lm –lnsl –o hw1 hw1.o

haline geliyor. Yani, ilk kuralı çalıştırmak için normalde komut satırından yukarıda elde ettiğimiz komutu girmemiz gerekiyor. Bu ilk kural bize hw1 dosyasının hw1.o dosyasına bağlı hale getirildiğini ve hw1.o dosyasını oluşturabilmemiz için yukarıda elde ettiğimiz komutu çalıştırmamız gerektiğini anlatıyor. Aynı mantıkla, ikinci kural da hw1.o dosyasını hw1.c dosyasına bağlı hale getirilmiş ve hw1.o dosyasını yaratmak için komut satırına

gcc –O2 –Wall –pedantic –c hw1.c

komutunu yazarak çalıştırmamız gerekiyor. Üçüncü ve dördüncü kurallar make komutunun yanına verilen en yaygın kurallardan ikisi. Aslında, makefile dosyasının içindeki bütün kurallar make komutunun yanına yazılabiliyor. Yani, “make hw1.o” yazdığımızda direk hw1.o için yazılan kuralı çalıştırabiliriz. Tekrar örnek dosyamıza dönersek üçüncü kural olan clean ile “make clean” diyerek derleme sonrasında oluşan tüm dosyalar silinirken, dördüncü kural olan install ile “make install” diyerek sadece install kuralını çalıştırabiliriz. Eğer, make komutunu öylece çağırırsak, makefile dosyasını alır ve ilk bulduğu kuralı işler. Böylece herşeyi tek tek yapmak yerine bir makefile dosyasıyla halledebiliyoruz.

Neden Make Komutunu Kullanalım?

Şimdi ise gelelim make kullanımının avantajlarına ve dezavantajlarına. Yukarıda da bahsettiğim gibi make komutu dosyaları bir şekilde birbirine bağlıyor. Eğer bu işlemi yaparken bir tane bile olsa dosya unutursak programımız derlenmeyebilir yada hatalı çalışabilir. Ama bunu engellemek de mümkün. GNU projesinin “Automake” adlı programı bu dosya bağlama işlemini bizim için yapıyor. Bir başka problem ise, bir derleyicinin kabul ettiği seçenekler diğer bir derleyici tarafından kabul edilmeyebilir. Bu durumuda engellemek için "Autoconf" yada "Cmake" gibi uygulamalar geliştirilmiştir. Bir diğer problem ise, makefile dosyasını oluştururken yapabilceğimiz söz dizimi hatalarıdır. Yukarıda nasıl yazılması gerektiğini anlatmıştım. Mesela, kuralları yazarken her biri için tab karakterini unutmamamız gerekiyor. Bu hata en sık karşılaşılan makefile söz dizim hatalarından biridir. Bunu önlemek için de bir takım programlar geliştirilmiştir fakat; pek yaygın değildirler. Ama yinede bu hataları önleyebilmek için metin editörleri kullanılmaktadır. Bu saydıklarımdan da anlaşılacağı gibi her problem için çözüm bulunmuştur. Bu da bizim make komutunun avantajlı bir şey olduğu yargısına varmamızı sağlar :)

Ve bir yazının da sonuna geldik. Bu ay sizlere make nedir, nasıl kullanılır, makefile nasıl yazılır gibi soruların cevabını vermeye çalıştım. Umarım keyifli bir yazı olmuştur. Gelecek ay görüşmek üzere sayın okuyucular :)

Kaynaklar: