Proyek tiga berkas sudah membuat perintah kompilasinya panjang, dan proyek dua puluh berkas membuat pengetikan ulang tidak dapat dipertahankan. make adalah alat build yang membaca berkas Makefile, membandingkan waktu perubahan berkas sumber dengan hasil kompilasinya, lalu hanya menjalankan langkah yang benar-benar perlu. Topik ini membangun Makefile untuk proyek konversi satuan berisi tiga berkas sumber, membahas anatomi target, prasyarat, dan resep, lalu menutup dengan tata letak direktori yang lazim pada proyek C yang lebih besar. Seluruh langkah build pada topik ini dijalankan dengan GNU Make, sehingga keluarannya dapat direproduksi apa adanya.
Proyek contoh dan kompilasi manual#
Proyek contoh terdiri atas tiga berkas sumber, yaitu main.c sebagai titik masuk program, satuan.h sebagai antarmuka, dan satuan.c sebagai implementasinya. Tata letaknya masih rata karena jumlahnya sedikit, dan pembahasan tata letak berlapis menyusul di bagian akhir. Keempat berkas itu, termasuk Makefile, menjadi bahan seluruh pembahasan pada topik ini.
proyek-satuan/
├── main.c titik masuk program
├── satuan.c implementasi fungsi konversi
├── satuan.h antarmuka fungsi konversi
└── Makefile aturan buildBerkas header menyimpan dua prototipe fungsi konversi, yaitu meter ke kaki dan liter ke galon. Prototipe inilah satu-satunya bagian yang perlu diketahui pemanggil, karena isi implementasinya tersembunyi di berkas terpisah.
// tidak-dijalankan
#ifndef SATUAN_H
#define SATUAN_H
/* Antarmuka konversi satuan yang dipakai bersama. */
double meter_ke_kaki(double m);
double liter_ke_galon(double l);
#endifImplementasinya berupa dua fungsi dengan rumus konversi masing-masing. Setiap fungsi hanya mengandalkan parameternya sendiri, sehingga keduanya dapat diuji terpisah dari program utamanya.
// tidak-dijalankan
#include "satuan.h"
double meter_ke_kaki(double m)
{
return m * 3.28084;
}
double liter_ke_galon(double l)
{
return l / 3.78541;
}Berkas utama memanggil kedua fungsi itu dan mencetak hasilnya. Seluruh pengetahuan tentang rumus konversi berada di luar berkas ini, sehingga perubahan rumus tidak pernah menyentuh main.c.
// tidak-dijalankan
#include <stdio.h>
#include "satuan.h"
int main(void)
{
printf("10.00 m = %.2f ft\n", meter_ke_kaki(10.0));
printf("5.00 L = %.2f gal\n", liter_ke_galon(5.0));
return 0;
}Kompilasi manualnya hanya satu perintah, tetapi perintah itu memuat nama dua berkas sumber, opsi peringatan, dan nama keluaran yang semuanya harus diingat penulisnya. Program hasilnya diberi nama satuan-manual agar tidak tertukar dengan program keluaran make pada bagian berikutnya.
# mengompilasi seluruh proyek dalam satu perintah
gcc -Wall -Wextra main.c satuan.c -o satuan-manual
./satuan-manual10.00 m = 32.81 ft
5.00 L = 1.32 galMasalahnya baru terasa ketika satu berkas berubah. Apabila satuan.c disunting, seluruh perintah tetap harus diketik ulang padahal main.c tidak berubah sama sekali, dan kebiasaan seperti ini memboroskan waktu pada proyek besar. make menghilangkan pengulangan itu dengan mengandalkan graf ketergantungan yang dinyatakan secara eksplisit.
Anatomi Makefile#
Satu berkas Makefile memuat sejumlah aturan (rule), dan setiap aturan terdiri atas target, prasyarat, serta resep. Baris satuan: main.o satuan.o menyatakan bahwa program satuan dibangun dari dua berkas objek, sedangkan baris resep di bawahnya memerintahkan cara mengaitkan kedua berkas objek itu. Variabel CC dan CFLAGS menampung compiler serta opsi peringatan di satu tempat, sehingga penggantiannya cukup dilakukan sekali.
CC = gcc
CFLAGS = -Wall -Wextra
satuan: main.o satuan.o
$(CC) main.o satuan.o -o satuan
main.o: main.c satuan.h
$(CC) $(CFLAGS) -c main.c
satuan.o: satuan.c satuan.h
$(CC) $(CFLAGS) -c satuan.c
clean:
rm -f satuan *.o
.PHONY: cleanPerhatikan bahwa setiap baris resep diawali karakter tab, bukan spasi. Batas antara aturan dan resepnya memang diletakkan pada karakter tab, sehingga resep yang diawali spasi ditolak dengan pesan missing separator.
Graf ketergantungan Makefile di atas berbentuk tiga lapis, yaitu sumber di dasar, berkas objek di tengah, dan program utuh di puncak.
Perhatikan posisi satuan.h yang menjadi prasyarat dua berkas objek sekaligus. Karena itu, satu perubahan header memicu kompilasi ulang dua berkas objek, dan pendaftaran prasyarat inilah yang membuat make dapat menghitung pekerjaan minimumnya. make yang dipanggil tanpa argumen mengeksekusi target pertama pada berkasnya, yaitu satuan, lalu program hasilnya dijalankan sebagaimana biasa.
# membangun program dengan make lalu menjalankannya
make && ./satuangcc -Wall -Wextra -c main.c
gcc -Wall -Wextra -c satuan.c
gcc main.o satuan.o -o satuan
10.00 m = 32.81 ft
5.00 L = 1.32 galBuild bertahap dan target tiruan#
Menjalankan make sekali lagi setelah build lengkap menghasilkan keluaran yang berbeda, yaitu satu baris kabar bahwa tidak ada pekerjaan yang tersisa. Kabar itu sekaligus membuktikan bahwa make menyadari tidak ada yang berubah sejak build sebelumnya.
# menjalankan make sekali lagi tanpa perubahan apa pun
makemake: 'satuan' is up to date.Kabar itu muncul karena make membandingkan waktu perubahan setiap prasyarat dengan targetnya. Apabila satu sumber disentuh, misalnya melalui touch satuan.c, hanya langkah yang bergantung padanya yang dijalankan ulang.
# menyentuh waktu ubah satuan.c lalu membangun ulang
touch satuan.c && makegcc -Wall -Wextra -c satuan.c
gcc main.o satuan.o -o satuanmain.o tidak dikompilasi ulang karena main.c tidak berubah, dan pengaitan diulang karena salah satu berkas objek prasyaratnya baru saja diperbarui. Pada proyek dengan ratusan berkas, pemisahan pekerjaan inilah yang menjaga waktu build tetap singkat. Resep juga dapat ditulis lebih ringkas dengan variabel otomatis (automatic variables) yang diisi make.
satuan: main.o satuan.o
$(CC) $^ -o $@
Pada resep tersebut, $@ meluas menjadi nama target dan $^ meluas menjadi seluruh prasyaratnya, sedangkan $< meluas menjadi prasyarat pertama. Bentuk eksplisit seperti pada Makefile utama tetap dianjurkan untuk pemula karena isinya terbaca apa adanya.
Target clean pada Makefile menghapus program dan seluruh berkas objek. Di bagian bawah berkas, .PHONY menyatakan bahwa clean adalah target tiruan (phony target), yaitu nama tugas yang bukan berkas.
# membersihkan hasil build
make cleanrm -f satuan *.oTanpa .PHONY, make akan menganggap target clean sudah terpenuhi apabila kebetulan ada berkas bernama clean di direktori yang sama, sehingga tugasnya tidak pernah dijalankan. Target tiruan lain yang lazim adalah all dan test. all menjadi target pertama yang menyebut semua program yang harus dibangun, sedangkan test menjalankan penguji.
Tata letak proyek yang lebih besar#
Proyek yang terus tumbuh lazim memisahkan kode menjadi tiga direktori, yaitu src/ untuk kode sumber, include/ untuk header yang dipakai bersama, dan build/ untuk berkas objek serta program hasilnya.
proyek-satuan/
├── Makefile
├── include/
│ └── satuan.h
└── src/
├── main.c
└── satuan.cKonsekuensi tata letak ini terletak pada jalur berkas di dalam Makefile, bukan pada cara kerja make. Melalui opsi -Iinclude, compiler menemukan berkas header yang dimuat arahan #include "satuan.h", sedangkan berkas objek dituliskan ke build/ agar tidak bercampur dengan sumbernya. Setiap aturan cukup menyesuaikan jalurnya tanpa mengubah bentuk grafnya. Folder hasil build yang terpisah juga memudahkan pembersihan, karena satu perintah dapat menghapus seluruh isi build/ tanpa menyentuh satu berkas sumber pun. make tetap menjadi alat yang sama, dan proyek besar menambahkan target seperti all, test, dan install di atas pola yang telah dibangun pada topik ini.
Kesalahan umum#
- Mengawali resep dengan spasi alih-alih tab. make berhenti dengan pesan
missing separatoryang menunjuk nomor baris resep tersebut. Pastikan editor menulis karakter tab asli pada baris resep, bukan sekadar tampilan menjorok. - Lupa mencantumkan header sebagai prasyarat. Apabila
satuan.hdiubah tetapi tidak terdaftar padamain.odansatuan.o, keduanya dianggap segar dan program akhirnya memakai deklarasi lama. Daftarkan setiap header yang dimuat berkas sumber pada prasyarat objeknya. - Menamai target tiruan sama dengan berkas yang ada. Target
cleantidak pernah dijalankan apabila berkas bernamacleankebetulan ada, karena make menganggapnya sudah terpenuhi. Nyatakan target semacam itu pada baris.PHONY. - Menyimpan berkas build dengan nama lain. make mencari berkas bernama
Makefileataumakefilesecara otomatis, sedangkan nama lain harus disebut lewat opsi-f. Gunakan nama bawaan agar perintah build tidak menuntut opsi tambahan.
Ringkasan#
- Makefile memetakan target ke prasyarat beserta resepnya, dan make hanya menjalankan langkah yang targetnya lebih tua daripada prasyaratnya.
- Karakter tab pada resep bersifat wajib, variabel
CCdanCFLAGSmenampung opsi di satu tempat, dan.PHONYmenandai target yang bukan berkas. - Perubahan satu sumber hanya memicu kompilasi ulang objek yang bergantung padanya, sedangkan header yang dipakai dua objek memicu keduanya.
- Tata letak
src/,include/, danbuild/menjadi konvensi yang membuat proyek besar tetap tertata tanpa mengubah cara kerja make.
Referensi#
- GNU Make — GNU Project — Manual resmi make, termasuk anatomi aturan dan pemilihan target bawaan.
- Automatic Variables — GNU Make
— Daftar
$@,$^, dan$<beserta makna masing-masing. - Phony Targets — GNU Make
— Kaidah
.PHONYuntuk target yang bukan berkas.