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:

bash
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
ls

Output yang Dihasilkan:

text
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.c

Bedah aturan dan eksekusinya:

  1. CC = gcc dan CFLAGS = -Wall: variabel Makefile; setiap $(CC) dan $(CFLAGS) pada resep digantikan nilainya, sehingga ganti compiler atau tambah flag cukup di satu tempat.
  2. all: halo: target pertama dalam file menjadi target default, jadi make tanpa argumen membangunnya; all hanyalah nama kebiasaan untuk “seluruh hasil”.
  3. halo: halo.c: aturan membangun file halo dari prasyarat halo.c; baris resep di bawahnya wajib diawali karakter TAB, bukan spasi.
  4. make: membangun halo — perhatikan make mencetak setiap resep sebelum menjalankannya, lalu ./halo membuktikan hasilnya.
  5. make halo: halo masih lebih baru dari halo.c, jadi make menolak bekerja dengan up to date — penghematan inilah alasan make ada.
  6. touch halo.c lalu make: touch memperbarui waktu modifikasi halo.c; karena prasyarat kini lebih baru dari targetnya, make membangun ulang. Inilah logika timestamp: make membandingkan waktu modifikasi file, bukan isinya.
  7. make clean dan ls: target pembersih menghapus hasil build, dan ls mengonfirmasi halo sudah 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:

bash
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
ls

Output yang Dihasilkan:

text
gcc -Wall -o halo halo.c
Makefile
halo.c

Perintah 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:

bash
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 clean

Output yang Dihasilkan:

text
make: 'clean' is up to date.
rm -f halo

Apa yang barusan terjadi:

  1. touch clean: membuat file kosong bernama clean — mungkin sisa arsip atau salah ketik.
  2. make clean (pertama): karena file clean ada dan tidak punya prasyarat yang lebih baru, make menganggapnya up to date dan tidak menjalankan resep rm sama sekali.
  3. printf '.PHONY: clean\n' >> Makefile: mendaftarkan clean sebagai target phony (palsu) — nama yang bukan file.
  4. make clean (kedua): make kini tahu clean bukan file, jadi resepnya selalu dijalankan — rm -f halo tereksekusi.

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:

bash
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 clean

Output yang Dihasilkan:

text
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 statistik

Bedah Makefile proyek ini:

  1. OBJS = main.o statistik.o: daftar object diringkas jadi satu variabel, sehingga baris target cukup statistik: $(OBJS) — tambah file baru berarti tambah satu entri di satu tempat.
  2. main.o: main.c statistik.h: aturan tanpa resep; make memakai aturan bawaan .c → .o dengan $(CC) $(CFLAGS) -c — itu sebabnya dua resep gcc -Wall -c muncul padahal kita tidak menuliskannya (celah ekstra di output adalah ruang untuk CPPFLAGS yang kosong).
  3. Dependensi ke statistik.h: mengubah header membuat kedua object usang dan dikompilasi ulang — melupakan dependensi header adalah penyebab klasik bug yang bandel.
  4. make clean: *.o membersihkan 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.