Makefile adalah skrip build yang dibaca oleh program make. Makefile mendefinisikan target (file yang ingin dihasilkan), prerequisite (file yang harus ada sebelum target bisa dibuat), dan recipe (perintah shell untuk membuat target). Keunggulan utama Makefile dibanding script shell adalah incremental build — make hanya membangun ulang target yang prerequisite-nya berubah sejak target terakhir dibuat. Pada chapter ini kita akan menulis Makefile untuk proyek multi-file, memahami struktur rule, variable, dan automatic variable ($@, $<, $^), serta mengamati bagaimana make memilih target yang perlu dibangun ulang.
Struktur Dasar Makefile #
Berikut struktur proyek sederhana dengan dua file C dan satu header:
greet.h -- deklarasi fungsi greet()
greet.c -- implementasi greet()
main.c -- program utama yang memanggil greet()Makefile minimal untuk proyek ini:
CC = gcc
CFLAGS = -std=c11 -Wall -Wextra
TARGET = app
OBJS = main.o greet.o
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) $^ -o $@
main.o: main.c greet.h
$(CC) $(CFLAGS) -c $< -o $@
greet.o: greet.c greet.h
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
run: $(TARGET)
./$(TARGET)
.PHONY: clean runJalankan make di direktori yang berisi file-file di atas:
makeOutput yang Dihasilkan:
gcc -std=c11 -Wall -Wextra -c main.c -o main.o
gcc -std=c11 -Wall -Wextra -c greet.c -o greet.o
gcc -std=c11 -Wall -Wextra main.o greet.o -o appMari kita telaah struktur Makefile baris per baris:
- Baris 1 (
CC = gcc): variabelCCmenyimpan nama compiler. Konvensi: pakaiCCuntuk C compiler,CXXuntuk C++ compiler.makesudah punya variabelCCbawaan bernilaicc(alias kegccdi kebanyakan distro), tetapi menetapkannya secara eksplisit memastikan konsistensi. - Baris 2 (
CFLAGS = -std=c11 -Wall -Wextra): variabelCFLAGSmenyimpan flag compiler.makeakan menambahkan$(CFLAGS)ke resep bawaan saat memakai implicit rules, tetapi kita menyebutnya eksplisit untuk kejelasan. - Baris 3-4 (
TARGET = appdanOBJS = main.o greet.o): variabel untuk menyimpan nama target dan daftar object file. Memakai variabel membuat Makefile lebih mudah diubah — bila kita menambah file baru, cukup tambahkan keOBJS. - Baris 6 (
$(TARGET): $(OBJS)): rule dengan targetappdan prerequisitemain.o greet.o.makememeriksa apakahapplebih baru dari kedua.o; bila tidak,makemenjalankan recipe di baris berikutnya. - Baris 7 (
$(CC) $(CFLAGS) $^ -o $@): recipe — perintah shell untuk membuat target. Karakter pertama harus tab, bukan spasi — ini sering menjadi sumber frustrasi pemula. Recipe memakai automatic variable$^(semua prerequisite) dan$@(nama target). - Baris 9 (
main.o: main.c greet.h): rule untuk mengompilasimain.o. Prerequisite mencakupgreet.hkarenamain.cmeng-include-nya. Bilagreet.hberubah,main.oakan dibangun ulang. - Baris 10 (
$(CC) $(CFLAGS) -c $< -o $@): recipe memakai automatic variable$<(prerequisite pertama) alih-alih$^. Untuk kompilasi, kita hanya ingin file.cpertama, bukan header. - Baris 15 (
clean:): target tanpa prerequisite dan tanpa file output. Disebut phony target karena tidak menghasilkan file bernamaclean. Dideklarasikan di.PHONY(baris 19) agarmaketidak menganggapcleansebagai file. - Baris 17 (
run: $(TARGET)): target yang menjalankan executable setelah memastikan executable terbaru. Berguna untuk workflowmake && make run. - Baris 19 (
.PHONY: clean run): deklarasi bahwacleandanrunbukan file target. Tanpa deklarasi ini, bila ada file bernamacleandi direktori (jarang terjadi, tetapi mungkin),make cleanakan menganggap target sudah up-to-date dan menolak menjalankan recipe.
Automatic Variables #
Tiga automatic variable yang paling sering dipakai:
| Variable | Arti |
|---|---|
$@ | Nama target |
$< | Prerequisite pertama |
$^ | Semua prerequisite, dipisah spasi (dengan deduplikasi) |
$? | Prerequisite yang lebih baru dari target |
$* | Bagian target yang cocok dengan % di pattern rule |
Mari kita lihat automatic variable beraksi. Makefile berikut memakai @echo untuk mencetak nilai setiap variable saat recipe dieksekusi:
CC = gcc
CFLAGS = -std=c11 -Wall -Wextra
app: main.o greet.o
@echo "=== Linking ==="
@echo 'Target ($$@):' $@
@echo 'Sources ($$^):' $^
@echo 'First ($$<):' $<
$(CC) $(CFLAGS) $^ -o $@
main.o: main.c greet.h
$(CC) $(CFLAGS) -c $< -o $@
greet.o: greet.c greet.h
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f *.o app
.PHONY: cleanmake clean && make -f Makefile_autoOutput yang Dihasilkan:
rm -f *.o app
gcc -std=c11 -Wall -Wextra -c main.c -o main.o
gcc -std=c11 -Wall -Wextra -c greet.c -o greet.o
=== Linking ===
Target ($@): app
Sources ($^): main.o greet.o
First ($<): main.o
gcc -std=c11 -Wall -Wextra main.o greet.o -o appPerhatikan beberapa hal penting dari output di atas:
- Baris
Target ($@): appmenunjukkan$@bernilaiapp— nama target yang sedang dibuat. - Baris
Sources ($^): main.o greet.omenunjukkan$^berisi kedua prerequisite dipisah spasi. - Baris
First ($<): main.omenunjukkan$<hanya berisi prerequisite pertama (main.o), bukan keduanya. Inilah sebabnya$<dipakai untuk kompilasi (kita hanya ingin file.cpertama), sementara$^dipakai untuk linking (kita ingin semua.o). - Karakter
@di awal@echomenyuruhmaketidak menampilkan perintahechoitu sendiri — hanya outputnya. Tanpa@, barisecho "Target ($@):" appakan ikut tercetak. - Konstruksi
$$@di dalam string single-quote'...'menampilkan literal$@—$$di Makefile menjadi$saat diteruskan ke shell, dan shell tidak menafsirkan$@di dalam single-quote.
Incremental Build #
Mari kita lihat bagaimana make hanya membangun ulang apa yang perlu:
# Setelah make pertama
makeOutput yang Dihasilkan:
make: 'app' is up to date.make memeriksa timestamp app terhadap timestamp main.o dan greet.o. Karena app lebih baru dari keduanya, make tidak melakukan apa-apa. Sekarang sentuh greet.c untuk mensimulasikan perubahan:
touch greet.c
makeOutput yang Dihasilkan:
gcc -std=c11 -Wall -Wextra -c greet.c -o greet.o
gcc -std=c11 -Wall -Wextra main.o greet.o -o appmake mengompilasi ulang greet.o (karena greet.c berubah), lalu me-relink app. main.o tidak dibangun ulang karena main.c dan greet.h tidak berubah. Inilah kekuatan incremental build — proyek besar bisa di-build dalam hitungan detik, bukan menit.
Target Tambahan: clean dan run #
make cleanOutput yang Dihasilkan:
rm -f main.o greet.o appmake runOutput yang Dihasilkan:
gcc -std=c11 -Wall -Wextra -c main.c -o main.o
gcc -std=c11 -Wall -Wextra -c greet.c -o greet.o
gcc -std=c11 -Wall -Wextra main.o greet.o -o app
./app
Halo Budi!
Halo Siti!Target run bergantung pada $(TARGET), jadi make run akan membangun app bila belum ada atau sudah out-of-date, lalu menjalankannya. Workflow sehari-hari sering kali: make clean && make && make run.