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şul2opt… komut1opt 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. |