Standart C Programlama Dili


B.4. GNU Make Kullanımı

Kısım B.1’de kısmen anlatılan GCC seçenekleri ve derleme aşamaları dikkate alındığında, C programlarını derlemek ve bağlamak için kullanılan komutların çeşit ve karmaşıklık açısından büyük projelerde sıkıntı yaratabileceği düşünülebilir. Bu kısımda kısaca anlatılacak olan make yardımcı programı, her boyutta ve karmaşıklıkta programların derlenme sürecini otomatikleştirme ve yönetimi konusunda bize yardımcı olur. Tek bir make komutu ile yüzlerce derleyici ve bağlayıcı komutunu ihtiyaca göre çalıştırabiliriz. make yardımcı programı, daha önce yapılan işlemleri tekrarlamamak için, ilgili dosyaların zaman damgalarını karşılaştırır, çeşitli hedeflerin nasıl oluşturulacağını tanımlayan kuralları izleyerek ilgili tüm dosyalar arasındaki bağımlılık ilişkilerini otomatik olarak analiz eder.

Bu kısımda make ile ilgili giriş düzeyinde bilgi verilmiştir. Ayrı bir kitap konusu olduğu için, daha detaylı bilgiye ihtiyacınız olursa, ilgili kaynakları (örneğin, sadece İngilizce olarak sunulan Çevrim içi GNU Make Dokümantasyonunu araştırmanız önerilir.

Kurulum

Sisteminizde make komutunun bulunup bulunmadığını kontrol etmek için

$ make --version

komutunu girdiğinizde, gelen çıktının ilk satırının aşağıdakine benzer olması beklenir:

GNU Make 4.3

Burada GNU Make’ten sonra gelen uyarlama bilgisinin sizin sistemde farklı olabileceğine dikkat ediniz. Eğer çok farklı bir çıktı alırsanız, make yerine gmake komutunu deneyebilirsiniz. Eğer sisteminizde GNU make komutu kurulu değilse, yönetici (root) kullanıcısı ile sisteminize uygun olarak

# apt install make

veya

# yum install make

komutu ile kurmaya çalışabilirsiniz.

Make Dosyası

make komutunun kullanacağı kurallar bir make dosyasına yazılır. Eğer kullanılacak olan make dosyasının adı make komutuna parametre olarak verilmezse, varsayılan olarak içinde bulunulan dizinde makefile veya Makefile ismindeki düz metin dosyası kullanılır. Bu dosyanın içine yazılan kuralların genel biçimi şöyledir:

hedef1 hedef2opt… : önkoşul1opt önkoşul2optkomut1opt
	komut2opt

Hedef1 satırın en başında ve önünde herhangi bir boşluk karakteri olmadan yazılmalıdır. Komutlar ise takip eden satırlarda ve satırın başına mutlaka bir Tab karakteri konarak yazılmalıdır. Bu şekilde yazılmış her bir kural şu anlama gelir: Eğer hedef (dosyası) önkoşullardan (dosyalardan) daha eski ise, yenilenmesi gerekir; bunu yapmak için ise altta yazılmış olan komutlar sıra ile yerine getirilmelidir. Eğer önkoşullar, başka kurallarda hedef durumunda iseler, o zaman ilk önce onların güncellenmesi gerekip gerekmediği kontrol edilir. Bir make dosyasında yazılan kurallar silsilesi bu şekilde ihtiyaca göre yerine getirilir.

Bir make dosyasında birden fazla kural varsa, ilk yazılan kural varsayılan kuraldır ve make komutuna parametre olarak hangi kuralın kontrol edilmesi istendiği belirtilmezse, make komutu bu ilk yazılan kuralı kontrol eder. Yukarıda anlatıldığı gibi, önkoşulların bağımlılıklarına göre, diğer kuralların da kontrol edilmesi gerekebilir.

Komut içermeyen bir kural sadece bağımlılıkları tanımlamaya yarar; bu şekilde bağımlılıklar zincirleri tanımlanabilir. Önkoşul içermeyen bir kural ise hedefin (koşulsuz olarak) nasıl oluşturulacağı bilgisini verir.

Örnek bir make dosyasının içeriği aşağıda verilmiştir:

# "prog" için basit bir make dosyası
CC = gcc
DERSEC = -std=c17 -pedantic -Wall -s
BAGSEC = -lm
SIL = rm -f
AMACDL = prog1.o prog2.o

prog : $(AMACDL)
	$(CC) -o $@ $^ $(BAGSEC)
$(AMACDL) : %.o : %.c
	$(CC) $(DERSEC) -o $@ -c $<
temiz :
	$(SIL) $(AMACDL)

Yukarıdaki içerikte # ile başlayan satır bir açıklamadır ve make komutu tarafından dikkate alınmaz. Arkadan gelen beş satırda, dosya içinde kullanılacak, değişkenler tanımlanmıştır. Dosyanın geri kalanında ise üç adet kural verilmiştir:

(1) Birinci kurala göre, eğer prog dosyasının son değiştirilme tarihi prog1.o veya prog2.o dosyalarının son değiştirilme tarihinden eski ise, prog dosyasının yenilenmesi gerekmektedir. Bunun için alttaki tek komut çalıştırılacaktır. Bu komut satırında kullanılan değişkenler açıldığında aşağıdaki işletim sistemi komutu oluşur:

gcc -o prog prog1.o prog2.o -lm

Bir make dosyası içinde tanımlanmış değişkenleri kullanmak için $(değişken_adı) biçimi kullanılır. Bu bağlamda, kuralın önkoşullarının ve sistem komutunun ilk ve son bölümünün nasıl oluşturulduğunu anlamış olmalısınız. Make dosyasında kullanılan diğer üç değişkenin (hedef dosyanın adı: $@; önkoşul dosyalarının isim listesi: $^ ve ilk önkoşul dosyasının adı: $<) değeri otomatik olarak make tarafından verilmektedir. Bu değişkenlerin isimleri tek karakterden oluştuğu için parantez içine alınmazlar ve örnekte verildiği gibi kullanılırlar.

(2) İkinci kuralda biraz daha karmaşık bir yapı kullanılmıştır. Buradaki hedeflerin, yani prog1.o ve prog2.o’nun ayrı ayrı derlenmesi için temelde aynı komut kullanılacaktır; sadece derlenecek kaynak kod dosyasının adı duruma göre prog1.c veya prog2.c olacaktır. Oluşacak amaç kod dosyasının adı da benzer şekilde prog1.o veya prog2.o olacaktır. Burada hedef ve önkoşul dosya isimleri için örüntü karakteri (%) kullanılarak uygun dosya isimleri içeren komut üretimi sağlanmaktadır.

(3) Üçüncü kuralda temiz isminde bir hedef verilmiştir. Eğer bulunduğumuz dizinde temiz isminde bir dosya yoksa (bu isimde bir dosyanın olmaması beklenir), önkoşulsuz olarak, kuralda verilen komutlar çalıştırılır. Bizim örnekte, temiz kuralı işletildiğinde, amaç dosyalar bulunduğumuz dizinden silinir. Bu kuralı

$ make temiz

komutunu girerek çalıştırabiliriz.

Yukarıdaki örnekte kısaca anlatılanların dışında, make dosyalarında kullanılabilecek makrolar, fonksiyonlar ve emirler gibi daha ileri özellikler de bulunmaktadır.

Komut Satırı Seçenekleri

Genel olarak bir make komut satırı şu şekildedir:

make seçenekleropt değişken_atamalarıopt hedefopt

Eğer komut satırında hedef belirtilmezse, varsayılan hedef, yani make dosyasında ilk yazılan kuralın hedefi kontrol edilir. Birden fazla hedef verildiğinde sırasıyla bu hedefler kontrol edilir. Yukarıdaki örnek make dosyası için

$ make temiz prog

komutu verildiğinde aşağıdaki komutlar işletilir:

rm -f prog1.o prog2.o
gcc -std=c17 -pedantic -Wall -s -o prog1.o -c prog1.c
gcc -std=c17 -pedantic -Wall -s -o prog2.o -c prog2.c
gcc -o prog prog1.o prog2.o -lm

Çalıştırılan komutların sırasına dikkat edin: Önce temiz hedefini gerçekleştirmek için amaç kod dosyaları silinir. Sonra, prog hedefi için bağlama işlemi yapılmaya çalışılır. Ancak, bağlama işlemi için gerekli olan amaç kod dosyaları silinmiş olduğu için, öncelikle amaç kod dosyalarının hedefleri çalıştırılır. Yani önce bahsi geçen iki dosya için derleme yapılır; sonra da bağlama komutu çalıştırılır.

Aşağıda verilen liste make komut satırında en yaygın olarak kullanılan bazı seçenekleri içermektedir:

-B
--always-make
Güncel olup olmadığına bakmaksızın hedef(ler)i gerçekleştir.
-C dizin
--directory=dizin
Herhangi bir işlem yapmadan önce belirtilen dizine geçiş yap.
-d Yapılan kontroller hakkında detaylı bilgi ver.
-f dosya
--file=dosya
--makefile=dosya
Varsayılan make dosyası yerine verilen dosyayı kullan.
-i İşletim sistemi komutları çalıştırırken alınan hataları dikkate alma.
-I dizin
--include-dir=dizin
Bu kısımda bahsedilmemiş olan include emirlerindeki dosyaların aranacağı dizin.
-j sayıopt
--jobs=sayıopt
Aynı zamanda çalıştırılabilecek işletim sistemi komutları varsa, bunları paralel çalıştır. Eğer sayı verildiyse, aynı anda çalıştırılabilecek en fazla iş sayısı bu sayıyı geçmesin.
-k
--keep-going
Belirli bir hedef için işlem yapılırken hata alındığında make sonlanmasın; bu hedefe bağımlı olmayan başka hedefler varsa, o hedefler ile ilgili işlemlere devam et.
-n
--just-print
--dry-run
--recon
Çalıştırılacak komutları çıktı olarak ver, ancak hiçbirini çalıştırma.
-o dosya
--old-file=dosya
--assume-old=dosya
Bahsi geçen dosyanın bağımlılıklarından eski olduğunu varsay. Buna rağmen güncellemeye çalışma.
-p
--print-data-base
Kuralları çalıştırmadan önce veri tabanındaki bilgileri (kurallar, değişkenler vs.) çıktı olarak ver.
-q
--question
Herhangi bir komut çalıştırmadan ve çıktı oluşturmadan bir dönüş değeri ver:
0: tüm hedefler güncel;
1: en az bir hedef güncel değil;
2: hata alındı.
-r
--no-builtin-rules
Yerleşik örtük kuralları ve öntanımlı dosya tiplerini unut, sadece make dosyasındaki tanımları kullan.
-R
--no-builtin-variables
Yukarıdaki -r seçeneği gibi, fakat önceden tanımlanmış değişkenleri de unut.
-s
--silent
--quiet
Sessiz mod. Çalıştırılacak olan işletim sistemi komutlarını çıktı olarak verme.
-t
--touch
Güncellemek için komut çalıştırma, onun yerine güncellenecek dosyaların sadece son değişiklik tarihleri güncel hale getir.
-v
--version
Çıktı olarak uyarlama ve lisans bilgilerini ver. Başka bir işlem yapma.
-w
--print-directory
Yapılan işlemlerle ilgili verilen çıktının başında ve sonunda hangi dizinde işlem yapıldığına dair bir satırlık bir ileri ver.
-W dosya
--what-if=dosya
--new-file=dosya
--assume-new=dosya
Bahsi geçen dosyanın anlık olarak güncel olduğunu varsay. Diğer bir değişle, make komutu çalıştırıldığı anda sanki bu dosya güncellenmiş gibi davran ve buna bağlı olan hedefler için kontroller yap.
--warn-undefined-variables Normalde tanımsız değişkenlerin değeri boşluk olarak varsayılır. Bu seçenek kullanıldığıda, tanımsız değişkenler için ayrıca bir uyarı mesajı verilir.