Make vs. Ant – Build Araçları ile Konfigürasyon Yönetimi

Konfigürasyon Ne Demek?

Bir uygulamadaki kodun satır sayısı arttıkça, uygulama üzerinde geliştirme yapan kişi sayısı arttıkça, bu kodları tek dosyada tutmak çok zor hale gelir.  Bu yüzden ana kaynak kod dosyası,  küçük kaynak kod dosyalarına bölünmüştür. Bu durumda, küçük kaynak kodların [uygulamanın çalıştırılabilir kodunu oluşturmak için birbirleri arasındaki kurdukları ilişkiler] -> [konfigürasyonu] belirtilmelidir.

Küçük dosyalar, ana dosyayı oluşturdukları için aralarında mantıksal bağlılıklar olacaktır. Bir dosyada yapılan değişiklik bu bağlılık nedeniyle diğer dosyaları etkileyecektir. Değişiklikler yapıldıktan sonra programı tekrar build ederken bu bağlılıklar(dependency) bir şekilde halledilmelidir. Bu yüzden, konfigürasyon yapılırken kullanılan dilin çalıştırılabilir kod oluşturma süreci göz önüne alınmalıdır.

Kullanılan Programlama Diline Göre Konfigürasyon

Programlama diline özgü konfigürasyonu oluşturabilmek için Build araçları yapılmıştır. C için, make; Ruby için, rake; Java için; Ant, Maven, Buildr, Raven ve diğer diller için bilmediğim başkaları vardır. Bu araçların farklılaşmasının sebebi; Make ve Ant için, kullanılan dilin derleyicilerinin, o dilde yazılmış olan kaynak kodu, çalıştırılabilir koda dönüştürme süreçlerindeki farklılıklardır. Rake‘de ve Rake‘in Java uyarlaması olan Buildr ve Raven‘de asıl sebep bu değildir.

Make ve Ant arasındaki fark == C ve Java ile Yazılmış Kaynak Kodların Derlenme Süreçleri arasındaki fark

Why is no one using make for Java? : Stackoverflow’daki bu soruda:

Neredeyse her Java projesinde build aracı olarak ya Maven ya da Ant kullanıldığını görüyorum. Bunlar güzel araçlar ve her projede kullanılabilirler ama, zaten make var, neden o kullanılmıyor? Java harici birçok projede kullanılıyor ve Java için de build işlemini yapabilecek kapasitede.

Make‘in Java’da kullanılmaması için make’in temel bir eksikliği mi var?

diye soruluyor. Verilen cevaplarda şunlar üzerinde duruluyor:

Java’da projelerin kaynak kod organizasyonu klasörler ve bunlar içerisindeki dosyaların hiyerarşisinden oluşur. C de ise düz(flatten) bir kaynak kod yapısı vardır. Make, dosya hiyerarşileri ile çalışmayı doğrudan desteklemez.

Make ile Ant arasındaki fark

Make, bir defa derlendikten sonra üzerinde değişiklik yapılan dosyaları belirleyebilme konusunda iyi değildir.

Ant ile, derlemeden sonra üzerinde sadece üzerinde değişiklik yapılan dosyalar bir defada belirlenip tek bir derleme adımında sadece bu dosyalar tekrar derlenir. Ant yerine make olsaydı, belirli bir bağlılık kuralında belirtilen dosyalardan, üzerinde değişiklik yapılsın yapılmasın, her dosya için javac‘ın ayrı ayrı çağrılması gerekecekti.

Make‘in temel prensibi; bir bağlılık(dependency) belirtilir ve bu bağlılığı çözebilecek(resolve) bir kural yazılır. Bu süreç, C de genel olarak şöyledir:

main.c yi  main.o ya dönüştürebilmek için cc main.c çağrılır. Bu işlemi Java’da,

Main.java yı, Main.class a dönüştürmek için javac Main.java diyerek yapabiliriz. Ancak,

javac Main.java 
javac This.java 
javac That.java 
javac Other.java 

ile

javac Main.java This.java That.java Other.java 

arasında gece-gündüz kadar belirgin bir fark vardır. Ayrıca, yüzlerce sınıf olduğunda bu fark dayanılmaz bir çileye dönüşecektir.

Bu sebeplerden dolayı Ant ve Maven gibi make‘e alternatif build araçlarına ihtiyaç duyulmuştur.

Diğer bir cevapta, Greg Hewgill,

Yıllanmış make aracı, C ve C++ gibi ayrı ayrı derlenen(seperately compiled) diller için iyidir.

C’de, bir modülü derlerken, derlenecek dosya için tek bir object file oluşturulur.

Yani, derleme işlemi bir defada bir .c dosyasını derleyip bir .o dosyası oluşturmak şeklindedir.

Linker ile, ayrı bir, derlenmiş .o dosyalarını çalıştırılabilir binary koda entegre etme aşaması vardır.

NOT: Bu aşamalar:

Derleme ve bunun sonucunda .o dosyası oluşturma,

gcc -c file1.c    // file1.o oluşur.

gcc -c file2.c   // file2.o oluşur.

gcc -o exe_dosya file1.o file2.o   //exe_dosya oluşur.

Yukarıdaki aşamalar tek bir komutta birleştirilip aşağıdaki gibi yazılabilir,

gcc -o exe_dosya file1.c file2.c

şeklindedir.

Java‘da ise import ile bir sınıfın kaynak koduna dahil edilen bütün sınıfların hepsi bu yeni sınıfla birlikte tekrar derlenir.

(C deki gibi önceden derlenmiş bir kod yoktur.)

Buna rağmen, derlenenecek olan Java kodundaki bağlılıklar belirtilip bu bağlılıkları çözen kurallar makefile içinde yazılabilir. Bu durumda, make, sınıfların doğru sırada derlenmesini sağlayarak build işlemini gerçekleştirebilir. Ancak, makeCircular Dependency‘leri(karşılıklı bağlılıklar) çözemez.

NOT: Çalıştırılabilir dosyanın(executablebyte sayısı, önceden derlenmiş dosyalardaki(#include ile belirtilen .o dosyaları) toplam byte sayısı + yeni derlenecek olan dosyanın üreteceği toplam byte sayısı demektir. Yani .o dosyalarının byte sayıları toplamıdır. Bunu görmek için, Hello from a libc-free world! yazısında -nostdlib bayrağı ile derleme işleminin sonucuna bakınız.

jesstess@kid-charlemagne:~/c$ cat hello.c
#include <stdio.h> int main() { printf("Hello World\n"); return 0; }
jesstess@kid-charlemagne:~/c$ gcc -o hello hello.c
jesstess@kid-charlemagne:~/c$ wc -c hello
10931 hello
jesstess@kid-charlemagne:~/c$ cat hello.c
int main() { char *str = "Hello World"; return 0; }
jesstess@kid-charlemagne:~/c$ gcc -o hello hello.c
jesstess@kid-charlemagne:~/c$ wc -c hello
10892 hello
jesstess@kid-charlemagne:~/c$ gcc -nostdlib -o hello hello.c,
jesstess@kid-charlemagne:~/c$ wc -c hello
1329 hello

NOT: Burdan sonraki kısım, bu yazıya bağlı Make yazısını yazarken aklıma geldi ve buraya 15.05.2011 Tarihinde eklendi.

Bu yazıda bahsettiğim Separate Compilation(ayrı ayrı derleme) mevzusunu açalım:

Ana dosyayı küçük dosyalara bölerken, amacımızın Separete Compilation olduğunu söylemiştim. Bunun aşağıdaki avantajları vardır:

  1. CVS gibi versiyon kontrol sistemleri, bir dosyana aynı anda birden fazla kişi tarafından yazılmasına izin vermez. Bu nedenle ana dosyayı küçük parçalara bölersek herkes bir küçük parça üzerine yazabilir hale gelir.
  2. Tekrar Derleme(recompilation) hızı artar. Kodda sık sık yeni eklemeler yapıldığında tüm programı derlemek zorunda kalmak yerine, program küçük parçalara bölündüğü için, sadece değişiklik yapılan küçük parçaları derlemek yeterli olacaktır.
  3. Kodu yönetmek(değişiklik ve debug) daha kolay hale gelir. Sadece değişikliği yapacağımız küçük dosyayı bulur orada değişiklik yaparız.
  4. Program modüler hale gelir. Belli veriler ve bunlar üzerinde işlem yapan fonksiyonlar bir arada tutulur. Bu şekilde diğer kısımlardan ayrılır. Sonraki projelerde bu kısımları kullanmak kolay hale gelir.(NOT: .lib, .ar, .so, .dll gibi kütüphaneler ile modüller oluşturulur.)
Şimdi ayrı ayrı derlemenin nasıl yapıldığını görelim.
Öğrencilerin aldıkları bir dersin final notlarını hesaplayan bir program yapalım. Bu program şu parçalardan oluşacak:
  1. student class, bu sınıfın örnekleri her öğrenci için o öğrencinin bilgilerini tutacak.(isim, öğrenci notu, vize notları vs..)
  2. Notları belli bir eğriye(normal dağılım vs.) uyduracak olan formülleri uygulayan fonksiyonlar
  3. main program
Kodun iskeleti aşağıdaki gibi olacak:
class student
{
    ...
}

string student::name() { ... }
int student::studentNo() { ... }
...

float linearScale(...) { ... }
float quadraticScale(...) { ... }
float bellCurve(...) { ... }

int main()
{
    ...
}
Bu programı parçalara bölerken her parçayı iki ayrı parça olarak ifade edeceğiz. Oluşan parçalarda ayrı ayrı derleme yapacağız. Bunun için,
  1. Öncelikle, programı parçalara böleriz. Her bir parça ya bir sınıftan ya da birbiriyle ilişkili fonksiyonların oluşturduğu bir gruptan oluşacak. Dolayısıyla, yukarıdaki program için; bir tane student sınıfını tutan parça olacak, bir tane eğri uydurma fonksiyonları için olacak, bir tane de main fonksiyonu içerecek olan parça olacak.
  2. Sonra, oluşan her parça bir header bir de source olmak üzere iki parçayla ifade edilir. Header, source‘da gerçeklemeleri yazılacak olan fonksiyonları belirten bildirimleri(declaration) ya da sınıfın verilerini – durumunu(data – state) tutacak.
Dolayısıyla, elimizde aşağıdaki beş dosya olmuş oldu:
    1. student.h, student sınıfı state‘ini tutar.
    2. student.c, student sınıfı için, student’in state’i üzerinde uygulanacak behavior‘u tutar.
    3. scaling.h, ölçekleme fonksiyonlarının declaration‘larını(prototiplerini) tutar.
    4. scaling.c, ölçekleme fonksiyonlarının gerçeklemelerini tutar.
    5. main.c, ana programı tutar.
NOT: Header Dosyaları
Derleyici, gerçeklemelerin bulunduğu kaynak kodu derlemeden önce header dosyasını okur. Burada, derleyeceği dosyayı derlerken bilmesi gereken bilgiler-bildirimler yer alır. Ayrıca, Make, bağlılıkları header ile kontrol eder.
Programızın kodu en son şöyle oldu:
// student.h
class student
{
            ...
}

// student.C
#include "student.h"

string student::name() { ... }
int    student::studentNo() { ... }
...

// scaling.h
float linearScale(...);
float quadraticScale(...);
float bellCurve(...);

// scaling.C
#include "scaling.h"

float linearScale(...){ ... }
float quadraticScale(...) { ... }
float bellCurve(...) { ... }

// main.C
#include "student.h"
#include "scaling.h"

int main()
{
    ...
}
Çalıştırılabilir dosyayı oluşturabilmek için .c dosyalarını ayrı ayrı derleyip oluşan .o dosyalarını link etmek gerekir.
% g++ -c student.C
% g++ -c scaling.C
% g++ -c main.C

% g++ -o grade student.o scaling.o main.o

C ve Java derlenme süreçleri konusunda daha detaylı bilgi için Intro to Reverse Engineering Software – Chapter 2 – The Compilation Process‘e bakılabilir.

Güzel bir söz
Everything we hear is an opinion, not a fact. Everything we see is a perspective, not the truth.
[Duyduğumuz herşey bir fikirdir, bir gerçek değil. Gördüğümüz herşey bir bakış açısıdır, bir doğru, değildir.]
– Marcus Aurelius

Advertisements

4 Comments

Filed under build

4 responses to “Make vs. Ant – Build Araçları ile Konfigürasyon Yönetimi

  1. Pingback: C ile Standart Kütüphane olan LIBC’yi Kullanmadan Program Yazmak | Taha Yavuz Bodur weblogging..

  2. Pingback: C – Linker ve Nesne Dosyaları | Taha Yavuz Bodur weblogging..

  3. Çok başarılı bir makale, tebrikler.

    • Teşekkürler 🙂 Aslında yaklaşık bir yıldır bu siteye pek bakmıyordum. Bu yazıyı tekrar elden geçirsem güzel olabilir.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s