Proyek yang tumbuh membawa rutinitas: kompilasi beberapa file, menggabungkan hasilnya, menjalankan uji, lalu membersihkan file sementara. Menghafal urutan itu adalah undangan untuk lupa satu langkah. Alat make — yang sudah kita kenal sekilas di chapter Compiling from Source
— mengubah urutan tersebut menjadi file resep bernama Makefile: satu perintah menjalankan semuanya, dan yang lebih penting, hanya mengulang pekerjaan yang benar-benar perlu. Karena make terpasang di sistem latihan ini, seluruh contoh berikut dijalankan sungguhan.
Anatomi Makefile: Target, Prasyarat, dan Resep #
Satu aturan Makefile terdiri dari tiga bagian: target (file yang dihasilkan), prasyarat (file yang dibutuhkan), dan resep (perintah shell yang dijalankan). Kita rangkai menjadi proyek mini lengkap dengan variabel CC dan CFLAGS:
cat > halo.c << 'AKHIR'
#include <stdio.h>
int main(void)
{
printf("Halo dari build otomatis!\n");
return 0;
}
AKHIR
cat > Makefile << 'AKHIR'
CC = gcc
CFLAGS = -Wall
all: halo
halo: halo.c
$(CC) $(CFLAGS) -o halo halo.c
clean:
rm -f halo
AKHIR
rm -f halo
make
./halo
make halo
touch halo.c
make
make clean
lsOutput yang Dihasilkan:
gcc -Wall -o halo halo.c
Halo dari build otomatis!
make: 'halo' is up to date.
gcc -Wall -o halo halo.c
rm -f halo
Makefile
halo.cBedah aturan dan eksekusinya:
CC = gccdanCFLAGS = -Wall: variabel Makefile; setiap$(CC)dan$(CFLAGS)pada resep digantikan nilainya, sehingga ganti compiler atau tambah flag cukup di satu tempat.all: halo: target pertama dalam file menjadi target default, jadimaketanpa argumen membangunnya;allhanyalah nama kebiasaan untuk “seluruh hasil”.halo: halo.c: aturan membangun filehalodari prasyarathalo.c; baris resep di bawahnya wajib diawali karakter TAB, bukan spasi.make: membangunhalo— perhatikanmakemencetak setiap resep sebelum menjalankannya, lalu./halomembuktikan hasilnya.make halo:halomasih lebih baru darihalo.c, jadimakemenolak bekerja dengan up to date — penghematan inilah alasanmakeada.touch halo.clalumake:touchmemperbarui waktu modifikasihalo.c; karena prasyarat kini lebih baru dari targetnya,makemembangun ulang. Inilah logika timestamp:makemembandingkan waktu modifikasi file, bukan isinya.make cleandanls: target pembersih menghapus hasil build, danlsmengonfirmasihalosudah lenyap.
Mekanisme perbandingan waktunya sederhana namun andal: kernel mencatat mtime setiap file, dan make cukup membandingkan dua angka itu sebelum memutuskan bekerja atau diam. Pada proyek puluhan file, keputusan kecil ini menghemat kompilasi berulang.
Uji Tanpa Menjalankan: make -n #
Saat mengutak-atik Makefile proyek orang lain, menjalankan make langsung bisa jadi nekat — resepnya mungkin menghapus atau menimpa file. Opsi -n (dry run) mencetak resep yang akan dijalankan tanpa menjalankannya:
cat > halo.c << 'AKHIR'
#include <stdio.h>
int main(void)
{
printf("Halo dari build otomatis!\n");
return 0;
}
AKHIR
cat > Makefile << 'AKHIR'
CC = gcc
CFLAGS = -Wall
all: halo
halo: halo.c
$(CC) $(CFLAGS) -o halo halo.c
clean:
rm -f halo
AKHIR
make -n
lsOutput yang Dihasilkan:
gcc -Wall -o halo halo.c
Makefile
halo.cPerintah gcc tampil di output, tetapi ls membuktikan tidak ada binary halo yang tercipta — make -n hanya berlatih, tidak mengeksekusi. Jadikan make -n kebiasaan sebelum memercayai Makefile dari internet.
Jebakan Target Phony #
Perhatikan kembali target clean: nama itu tidak menunjuk file apa pun, tetapi sistem make tetap memeriksa apakah file bernama clean ada. Bila file semacam itu kebetulan ada, make menolak menjalankan resepnya:
printf '#include <stdio.h>\nint main(void) { return 0; }\n' > halo.c
cat > Makefile << 'AKHIR'
halo: halo.c
gcc -Wall -o halo halo.c
clean:
rm -f halo
AKHIR
touch clean
make clean
printf '.PHONY: clean\n' >> Makefile
make cleanOutput yang Dihasilkan:
make: 'clean' is up to date.
rm -f haloApa yang barusan terjadi:
touch clean: membuat file kosong bernamaclean— mungkin sisa arsip atau salah ketik.make clean(pertama): karena filecleanada dan tidak punya prasyarat yang lebih baru,makemenganggapnya up to date dan tidak menjalankan reseprmsama sekali.printf '.PHONY: clean\n' >> Makefile: mendaftarkancleansebagai target phony (palsu) — nama yang bukan file.make clean(kedua):makekini tahucleanbukan file, jadi resepnya selalu dijalankan —rm -f halotereksekusi.
Aturan praktisnya: target yang tidak menghasilkan file (clean, install, test, run) selalu didaftarkan ke .PHONY — selain aman dari tabrakan nama, resepnya juga pasti dijalankan.
Proyek Multi-File: Dependensi Antar Object #
Proyek sungguhan jarang satu file. Pola standarnya: tiap .c dikompilasi menjadi object file .o sendiri, lalu linker menggabungkannya — sehingga mengubah satu file hanya mengompilasi ulang satu object. Makefile menggambarkan graf dependensinya:
cat > statistik.h << 'AKHIR'
int maksimum(const int *nilai, int jumlah);
double rata_rata(const int *nilai, int jumlah);
AKHIR
cat > statistik.c << 'AKHIR'
#include "statistik.h"
int maksimum(const int *nilai, int jumlah)
{
int tertinggi = nilai[0];
for (int i = 1; i < jumlah; i++)
if (nilai[i] > tertinggi)
tertinggi = nilai[i];
return tertinggi;
}
double rata_rata(const int *nilai, int jumlah)
{
int total = 0;
for (int i = 0; i < jumlah; i++)
total += nilai[i];
return (double)total / jumlah;
}
AKHIR
cat > main.c << 'AKHIR'
#include <stdio.h>
#include "statistik.h"
int main(void)
{
int nilai[5] = {78, 85, 92, 67, 88};
printf("maks: %d, rata-rata: %.1f\n",
maksimum(nilai, 5), rata_rata(nilai, 5));
return 0;
}
AKHIR
cat > Makefile << 'AKHIR'
CC = gcc
CFLAGS = -Wall
OBJS = main.o statistik.o
statistik: $(OBJS)
$(CC) $(CFLAGS) -o statistik $(OBJS)
main.o: main.c statistik.h
statistik.o: statistik.c statistik.h
clean:
rm -f *.o statistik
AKHIR
make
./statistik
make cleanOutput yang Dihasilkan:
gcc -Wall -c -o main.o main.c
gcc -Wall -c -o statistik.o statistik.c
gcc -Wall -o statistik main.o statistik.o
maks: 92, rata-rata: 82.0
rm -f *.o statistikBedah Makefile proyek ini:
OBJS = main.o statistik.o: daftar object diringkas jadi satu variabel, sehingga baris target cukupstatistik: $(OBJS)— tambah file baru berarti tambah satu entri di satu tempat.main.o: main.c statistik.h: aturan tanpa resep;makememakai aturan bawaan.c→.odengan$(CC) $(CFLAGS) -c— itu sebabnya dua resepgcc -Wall -cmuncul padahal kita tidak menuliskannya (celah ekstra di output adalah ruang untukCPPFLAGSyang kosong).- Dependensi ke
statistik.h: mengubah header membuat kedua object usang dan dikompilasi ulang — melupakan dependensi header adalah penyebab klasik bug yang bandel. make clean:*.omembersihkan semua object sekaligus.
Sebagai task runner, make juga lazim dipakai di luar C: target test menjalankan test suite, target run mengeksekusi aplikasi, target deploy menyalin hasil — sintaksnya tetap yang kita pakai di sini, terdokumentasi di make(1)
dan standar POSIX di pubs.opengroup.org
.
Proyek kita kini bisa membangun dirinya sendiri dengan satu perintah. Tapi begitu program mulai memakai pustaka eksternal — zlib, OpenSSL, SQLite — muncul pertanyaan baru: flag -I dan -l yang benar itu apa, dan di mana letaknya? Chapter berikutnya tentang pkg-config menjawab persis itu.