Make adalah alat build otomatis yang mengelola dependensi antar file—ketika header util.h berubah, hanya file .c yang meng-include header tersebut yang perlu di-compile ulang. Make membaca file Makefile (atau makefile) berisi rule yang mendeskripsikan cara menghasilkan target dari prerequisite. Bab ini menyusun sintaks Make yang paling sering kita pakai, lengkap dengan automatic variables, built-in functions, dan contoh Makefile yang berfungsi.

Anatomi Rule #

Rule dasar Make terdiri dari target, prerequisite, dan recipe:

makefile
target: prerequisite1 prerequisite2
<TAB>recipe command

Penting: Baris recipe wajib diawali dengan karakter TAB, bukan spasi. Banyak editor secara default mengonversi TAB menjadi spasi—ini adalah penyebab error missing separator paling umum.

Variabel #

Variabel di Make didefinisikan dengan =, :=, ?=, atau +=. Perbedaannya terletak pada waktu ekspansi: = dievaluasi saat dipakai (recursive), := dievaluasi saat didefinisikan (simple), ?= hanya set jika belum terdefinisi, += menambah ke nilai yang ada.

OperatorNamaWaktu evaluasi
=RecursiveSaat variabel dipakai
:= / ::=SimpleSaat didefinisikan
?=ConditionalSaat didefinisikan, jika belum ada
+=AppendSesuai operator asal
makefile
CC      = gcc                # recursive
CFLAGS  := -std=c11 -Wall   # simple
LDFLAGS ?= -lm              # hanya jika belum diset
SOURCES += main.c           # append

Automatic Variables #

Saat recipe dieksekusi, Make menyediakan variabel otomatis yang merujuk ke target dan prerequisite rule. Tabel berikut mencantumkan yang paling sering dipakai.

VariabelArti
$@Nama target rule
$<Prerequisite pertama
$^Semua prerequisite (dengan duplikat dihilangkan)
$+Semua prerequisite (termasuk duplikat)
$?Prerequisite yang lebih baru dari target
$*Suffix matching stem (untuk pattern rule)
$(@D)Direktori dari $@
$(@F)Nama file dari $@

Built-in Functions #

Make menyediakan banyak function built-in untuk manipulasi teks dan daftar. Sintaks pemanggilan: $(function arg1,arg2,...).

FunctionEfek
$(wildcard pattern)Daftar file yang cocok glob pattern
$(patsubst pattern,replacement,text)Substitusi pattern
$(subst from,to,text)Substitusi teks sederhana
$(filter pattern...,text)Filter elemen yang cocok
$(filter-out pattern...,text)Kebalikan filter
$(strip text)Hapus whitespace leading/trailing
$(shell command)Eksekusi command shell, kembalikan output
$(foreach var,list,text)Iterasi list
$(if cond,then,else)Kondisional
$(notdir name)Nama file tanpa path
$(dir name)Direktori saja
$(basename name)Nama tanpa extension
$(addsuffix suffix,names)Tambah suffix
$(addprefix prefix,names)Tambah prefix
$(sort list)Urutkan dan hapus duplikat
$(words text)Jumlah kata
$(word n,text)Kata ke-n
$(words text)Hitung jumlah kata

Program Demo #

Kita akan membuat program tiga file—main.c, util.c, dan util.h—lalu menyusun dua versi Makefile: versi eksplisit dan versi dengan pattern rule. Kode programnya adalah sebagai berikut.

c
/* File: main.c - Demo Makefile */
#include <stdio.h>
#include "util.h"

int main(void) {
    cetak_salam("Budi");
    printf("2 + 3 = %d\n", tambah(2, 3));
    return 0;
}
c
/* File: util.h */
#ifndef UTIL_H
#define UTIL_H
void cetak_salam(const char *nama);
int tambah(int a, int b);
#endif
c
/* File: util.c */
#include <stdio.h>
#include "util.h"
void cetak_salam(const char *nama) {
    printf("Halo, %s!\n", nama);
}
int tambah(int a, int b) { return a + b; }

Makefile Versi Eksplisit #

Versi pertama menuliskan setiap rule secara eksplisit. Mudah dipahami tetapi repetitif untuk proyek besar.

makefile
# Makefile demo untuk mkdemo
CC      = gcc
CFLAGS  = -std=c11 -Wall -Wextra -g
LDFLAGS =

OBJS    = main.o util.o
TARGET  = mkdemo

.PHONY: all clean run

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(LDFLAGS) -o $@ $^

main.o: main.c util.h
	$(CC) $(CFLAGS) -c -o $@ $<

util.o: util.c util.h
	$(CC) $(CFLAGS) -c -o $@ $<

run: $(TARGET)
	./$(TARGET)

clean:
	rm -f $(OBJS) $(TARGET)

Build dan jalankan:

bash
make
./mkdemo

Output yang Dihasilkan (perintah make mencetak setiap perintah yang dijalankan, lalu program menghasilkan outputnya sendiri):

text
gcc -std=c11 -Wall -Wextra -g -c -o main.o main.c
gcc -std=c11 -Wall -Wextra -g -c -o util.o util.c
gcc  -o mkdemo main.o util.o
Halo, Budi!
2 + 3 = 5

Mari kita telaah baris-baris kode di atas:

  1. Baris 2-4 (CC, CFLAGS, LDFLAGS): Variabel konfigurasi. CC memilih compiler, CFLAGS berisi flag saat kompilasi, LDFLAGS untuk linker. Memisahkan variabel membuat ganti compiler atau flag hanya perlu satu baris.
  2. Baris 11 ($(TARGET): $(OBJS)): Rule untuk executable. Target mkdemo bergantung pada dua object file. Recipe $(CC) $(LDFLAGS) -o $@ $^ memakai automatic variables $@ (nama target mkdemo) dan $^ (semua prerequisite main.o util.o).
  3. Baris 14-15 (main.o: main.c util.h): Make melacak dependensi pada file header. Saat util.h diubah, hanya main.o dan util.o yang di-compile ulang, bukan keseluruhan proyek.
  4. Baris 23 (clean): Rule phony (bukan file) yang membersihkan artefak build. Ditandai .PHONY agar Make tidak bingung jika ada file bernama clean di direktori.

Makefile Versi Pattern Rule #

Untuk proyek dengan banyak file .c, menulis rule satu per satu menjadi melelahkan. Pattern rule memakai % sebagai wildcard dan function wildcard untuk mendeteksi source file otomatis.

makefile
# Makefile dengan automatic variables dan functions
CC       = gcc
CFLAGS   = -std=c11 -Wall -Wextra -g
SRCS     = $(wildcard *.c)
OBJS     = $(SRCS:.c=.o)
TARGET   = mkdemo

.PHONY: all clean run

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(LDFLAGS) -o $@ $^

%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

run: $(TARGET)
	./$(TARGET)

clean:
	$(RM) $(OBJS) $(TARGET)

Output yang Dihasilkan (make setelah make clean):

text
gcc -std=c11 -Wall -Wextra -g -c -o main.o main.c
gcc -std=c11 -Wall -Wextra -g -c -o util.o util.c
gcc  -o mkdemo main.o util.o

Tracking Dependensi Header #

Pattern rule %.o: %.c hanya melacak dependensi .c → .o, bukan header. Untuk header, gunakan compiler option -MMD -MP yang menghasilkan file .d berisi dependensi:

makefile
CFLAGS   = -std=c11 -Wall -Wextra -g -MMD -MP
SRCS     = $(wildcard *.c)
OBJS     = $(SRCS:.c=.o)
DEPS     = $(SRCS:.c=.d)

-include $(DEPS)

clean:
	$(RM) $(OBJS) $(TARGET) $(DEPS)