makefile dersi

makefile formatı yazılan bir kaynak kodu derlemek ve yüklemek için kullanılan ne yaygın derleme talimatı formatlarından biridir.

Bu yazıda sizlere makefile dosyası nasıl yazılır anlatacağım.

Genel bakış

Örneğin aşağıdaki gibi bir C kodumuz olsun

#include <stdio.h>
int main(){
    puts("Hello world!\n");
    return 0;
}

Bunu aşağıdaki komutu kulanarak derleriz ve kurarız.

$ gcc -o hello hello.c
$ install hello /usr/bin/hello

Makefile dosyalarının bölüm tanımlamalarında girintileme amaçlı Tab kullanılır.

Şimdi aşağıdaki makefile dosyasını inceleyelim.

PREFIX=/usr
build:
        $(CC) -o hello hello.c

install:
        install hello $(DESTDIR)/$(PREFIX)/bin/hello

Burada PREFIX, CC, DESTDIR gibi parametreler değişkendir. Bu değişkenler derleme esnasında değiştirilebilir.

Bu makefile dosyasını kullanarak derlemeyi ve yüklemeyi aşağıdaki gibi yaparız.

$ make
$ make install

Görüldüğü gibi derleme ve yükleme işlemi daha kolay ve nasıl derleneceğini basitçe belirtmiş olduk.

Burada kullandığımız değişkenler şu şekilde açılnabilir.

Make komutuna eğer hiç parametre verimezsek ilk baştaki bölümü çalıştırır. Biz ilk başta build tanımladığımız için make komutu build çalıştırır. make komutuna parametre olarak bölüm verirsek o bölüm çalıştırılır.

Değişken işlemleri

Değişken tanımlamak için variable=value şeklinde tanımlanabilir. değişkeni kullanırken de $() işareti arasına alınır. Örneğin:

yazi=hello world
hello:
        echo $(yazi)

Bu değişkeni make yazi=deneme123 şeklinde komut vererek değiştirebiliriz.

Var olan bir değişkene ekleme yapmak için += ifadesi kullanılır. := ifadesi eğer tanımlama varsa ekleme yapar. ?= sadece daha önceden tanımlanmışsa ekleme yapar.

yazi=hello
yazi+=world
sayi:=$(shell ls)
hello:
        echo $(yazi)

Eğer $ işareti kullanmanız gereken bir durum oluşursa $$ ifadesi kullanabilirsiniz. Örneğin:

hello:
        bash -c "echo $$HOME"

Bölümler

Makefile yazarken bölümler tanımlanır ve eğer bölümün adı belirtilmemişse ilk bölüm çalıştırılır. Bölümler arası bağımlılık vermek için aşağıdaki gibi bir kullanım yapılmalıdır.

yazi: sayi test
        echo "Hello world"
sayi:
        echo 12
test:
        echo test123

Yukarıdaki dosyayı çalıştırdığımızda sırasıyla sayi -> test -> yazi bölümleri çalıştırılır.

Aynı işi yapan birden çok bölüm şu şekilde tanımlanabilir.

bol1 bol2:
        echo Merhaba
# Şuna eşittir.
bol1:
        echo Merhaba
bol2:
        echo Merhaba

Bölümün adını $@ kullanarak öğrenebiliriz.

bolum:
        echo $@

Bölümün tüm bağımlılıklarını almak için için $^ kullanabiliriz.

bolum: bol1 bol2
        echo $^
bol1 bol2:
        true

$? ifadesi $^ ile benzerdir fakat sadece geçerli bölümden sonra tanımlanan bölümleri döndürür.

bol1:
        true
bolum: bol1 bol2
        echo $?
bol2:
        true

$< ifadesi sadece ilk bağımlılığı almak için kullanılır.

bol1 bol2:
        true
bolum: bol1 bol2
        echo $<

Eğer xxxx.o şeklinde bir kural tanımlarsanız bu kural çalıştırıldıktan sonra gcc ile kural adındaki dosya derlenir.

main: main.o
main.o: main.c test.c

main.c:
        echo "int main(){}" > main.c
%.c:
        touch $@

Burada main.c dosyası var olmayan bir dosyadır ve derleme esnasında oluşturulur. test.c dosyası ise daha önceden var olan bir dosyadır ve o dosyaya bir şey yapılmaz. main.c kuralı sadece main.c için çalıştırılırken %.c şeklinde belirtilen kular hem main.c hem test.c için çalıştırılır. main ile belirttiğimiz kuralda main.o bağımlılığı olduğu için bi derlemenin sonucu olarak main adında bir derlenmiş dosya üretilmektedir.

wildcard ve shell

Wildcard ifadesi eşleşen dosyaları döndürür.

files := $(wildcard *.c)
main:
        gcc -o main $(files)

Shell ifadesi ise komut çalıştırarak sonucunu döndürür.

files := $(shell find -type f -iname "*.c")
main:
        gcc -o main $(files)

Birden çok dosya ile çalışma

make -C xxx şeklinde alt dizindeki bir makefile dosyasını çalıştırabilirsiniz.

build:
        make -C src

Ayrıca include kullanarak başka bir dosyada bulunan kuralları kullanabilirsiniz.

# Makefile dosyası
include build.mk
build: main
        gcc main.c -o main
# build.mk dosyası
main:
        echo "int main(){return 0;}" > main.c

Koşullar

ifeq ifadesi ile koşul tanımlanabilir. aşağıdaki ifadeşi make CC=clang şeklinde çalıştırırkanız clang yazdırır, parametresiz bir şekilde çalıştırırsanız gcc yazdırır. Burada dikkat edilmesi gereken konu ifeq, else, endif girintilenmeden yazılır.

build:
ifeq ($(CC),clang)
        echo "clang"
else
        echo "gcc"
endif

Komut özellik ifadeleri

Eğer komutun başına @ işareti koyarsanız komut ekrana yazılmadan çalıştırılır. - yazarsanız komut hata alsa bile geri kalan kısımlar çalışmaya devam eder.

build:
        @echo "Merhaba dünya"
        -gcc main.c -o main

while ve for kullanımı

Bash betiklerinde kullandığımız for ve while yapısı makefile yazarken aşağıdaki gibi kullanılabilir. done dışındaki satırların sonuna \ işareti eklenir, do dışındaki satırların sonuna da ; işareti koyulur.

build:
        @for sayi in 1 2 3 $(dizi) ; do \
            echo $$sayi ; \
            echo "diger satir" ; \
        done

SHELL değişkeni

SHELL değişkeni makefile altındaki komutların hangi shell kullanılarak çalıştırılacağını belirtir. Varsayılan değeri /bin/sh olarak belirlenmiştir. Örneğin debian tabanlı dağıtımlarda /bin/sh konumu /bin/dash bağlıyken archlinuxta /bin/bash bağlıdır. dash [[ kullanımını desteklemezken bash destekler. Bu sebeple uyumluluğu arttırmak için SHELL değişkenini zorla /bin/bash olarak değiştirebiliriz. Aşağıdaki örnekle konuyu daha iyi anlamak için SHELL değişkenini python3 ayarladık ve python kodu yazdık.

SHELL=/usr/bin/python3
build:
        import os ;\
        liste = os.listdir("/") ;\
        print(liste[0])