6 poin oleh GN⁺ 2025-06-21 | 1 komentar | Bagikan ke WhatsApp
  • Makefile adalah alat untuk menyederhanakan otomatisasi build C/C++ dan pengelolaan dependensi
  • Menggunakan deteksi file yang berubah berbasis timestamp, sehingga kompilasi hanya dijalankan saat diperlukan
  • Menjelaskan struktur inti seperti rule, command, dan prerequisite dengan contoh
  • Juga membahas secara praktis fitur lanjutan seperti automatic variables, pattern rules, dan variable expansion
  • Memperkenalkan pentingnya skalabilitas dan kemudahan pengelolaan melalui template Makefile praktis untuk proyek skala menengah

Pengantar panduan tutorial Makefile

  • Makefile adalah alat inti untuk otomatisasi build proyek dan pengelolaan dependensi
  • Karena banyak aturan dan simbol tersembunyi, Makefile bisa terasa rumit saat pertama kali dipelajari, tetapi panduan ini merangkum poin-poin utama secara ringkas dengan contoh yang bisa langsung dijalankan
  • Setiap bagian dapat dipahami melalui contoh berbasis praktik

Memulai

Tujuan adanya Makefile

  • Makefile digunakan pada program besar untuk mengompilasi ulang hanya bagian yang berubah
  • Selain C/C++, ada berbagai alat build khusus untuk bahasa lain, tetapi Make digunakan secara luas untuk skenario build umum
  • Intinya adalah logika mendeteksi file yang berubah dan hanya menjalankan pekerjaan yang diperlukan

Sistem build alternatif untuk Make

  • Keluarga C/C++: ada banyak pilihan seperti SCons, CMake, Bazel, Ninja, dan lainnya
  • Keluarga Java: Ant, Maven, Gradle, dan sebagainya
  • Go, Rust, TypeScript, dan lain-lain juga menyediakan alat build mereka sendiri
  • Bahasa interpreter seperti Python, Ruby, JavaScript tidak memerlukan kompilasi, sehingga kebutuhan pengelolaan terpisah seperti Makefile lebih rendah

Versi dan jenis Make

  • Ada berbagai implementasi Make, tetapi panduan ini dioptimalkan untuk GNU Make (umumnya digunakan di Linux dan MacOS)
  • Contohnya kompatibel dengan GNU Make versi 3 dan 4

Cara menjalankan contoh

  • Setelah memasang make di terminal, simpan tiap contoh ke file Makefile lalu jalankan perintah make
  • Baris perintah di dalam Makefile harus diindentasi menggunakan karakter tab

Sintaks dasar Makefile

Struktur Rule

  • target: prerequisite(s)

    • command
    • command
  • Target: nama file hasil build (biasanya satu)

  • Command: skrip shell yang benar-benar dijalankan (diawali tab)

  • Prerequisite: daftar file yang harus siap sebelum target dibangun


Hakikat Make

Contoh Hello World

hello:  
	echo "Hello, World"  
	echo "This line will print if the file hello does not exist."  
  • Target hello tidak memiliki dependensi dan menjalankan 2 command
  • Saat make hello dijalankan, jika file hello belum ada maka command akan dijalankan. Jika file itu sudah ada, command tidak dijalankan
  • Umumnya target ditulis agar sesuai dengan nama file

Contoh dasar kompilasi file C

  1. Buat file blah.c (berisi int main() { return 0; })
  2. Tulis Makefile berikut
blah:  
	cc blah.c -o blah  
  • Saat make dijalankan, jika target blah belum ada maka kompilasi dilakukan dan file blah dibuat
  • Walau blah.c diubah, kompilasi ulang tidak terjadi otomatis → perlu menambahkan dependensi

Cara menambahkan dependensi

blah: blah.c  
	cc blah.c -o blah  
  • Sekarang jika blah.c baru saja berubah, target blah akan dibangun ulang
  • Timestamp file digunakan sebagai dasar deteksi perubahan
  • Jika timestamp dimanipulasi secara manual, perilakunya bisa menjadi tidak sesuai harapan

Contoh tambahan

Contoh target dan dependensi berantai

blah: blah.o  
	cc blah.o -o blah   
  
blah.o: blah.c  
	cc -c blah.c -o blah.o   
  
blah.c:  
	echo "int main() { return 0; }" > blah.c   
  • Dependensi diikuti dalam struktur pohon sehingga proses pembuatan tiap tahap diotomatisasi

Contoh target yang selalu dijalankan

some_file: other_file  
	echo "This will always run, and runs second"  
	touch some_file  
  
other_file:  
	echo "This will always run, and runs first"  
  • Karena other_file tidak benar-benar dibuat sebagai file, command untuk some_file akan dijalankan setiap kali

Make clean

  • Target clean sering digunakan untuk menghapus hasil build
  • Ini bukan kata khusus yang dipesan oleh Make, jadi perlu didefinisikan sendiri sebagai command
  • Jika ada file bernama clean, hal itu bisa membingungkan, jadi disarankan memakai .PHONY

Contoh:

some_file:   
	touch some_file  
  
clean:  
	rm -f some_file  

Penanganan variabel

  • Variabel selalu berupa string.
  • Umumnya := direkomendasikan, tetapi ada juga berbagai bentuk assignment seperti =, ?=, +=
  • Contoh penggunaan:
files := file1 file2  
some_file: $(files)  
	echo "Look at this variable: " $(files)  
	touch some_file  
  
file1:  
	touch file1  
file2:  
	touch file2  
  
clean:  
	rm -f file1 file2 some_file  
  • Cara mereferensikan variabel: $(variable) atau ${variable}
  • Tanda kutip di dalam Makefile tidak punya arti bagi Make itu sendiri (namun diperlukan dalam command shell)

Pengelolaan target

Target all

  • Untuk menjalankan beberapa target sekaligus, berikan peran itu pada target pertama (default)
all: one two three  
  
one:  
	touch one  
two:  
	touch two  
three:  
	touch three  
  
clean:  
	rm -f one two three  

Multi-target dan automatic variables

  • Untuk banyak target, command individual dapat dijalankan masing-masing. $@ berisi nama target saat ini
all: f1.o f2.o  
  
f1.o f2.o:  
	echo $@  

Automatic variables dan wildcard

Wildcard *

  • * mencari nama langsung di file system
  • Disarankan selalu menggunakannya dengan membungkusnya dalam fungsi wildcard
print: $(wildcard *.c)  
	ls -la  $?  
  • Jangan gunakan * langsung dalam definisi variabel
thing_wrong := *.o  
thing_right := $(wildcard *.o)  

Wildcard %

  • Umumnya digunakan pada pattern rules, sehingga pola tertentu bisa diekstrak dan diperluas

Fancy Rules

Aturan implisit (Implicit rules)

  • Make memiliki berbagai aturan default tersembunyi untuk build C/C++
  • Variabel yang umum: CC, CXX, CFLAGS, CPPFLAGS, LDFLAGS, dan lainnya
  • Contoh C:
CC = gcc   
CFLAGS = -g   
  
blah: blah.o  
  
blah.c:  
	echo "int main() { return 0; }" > blah.c  
  
clean:  
	rm -f blah*  

Static Pattern Rules

  • Dapat menulis banyak rule yang mengikuti pola yang sama secara lebih ringkas
objects = foo.o bar.o all.o  
all: $(objects)  
	$(CC) $^ -o all  
  
$(objects): %.o: %.c  
	$(CC) -c $^ -o $@  
  
all.c:  
	echo "int main() { return 0; }" > all.c  
  
%.c:  
	touch $@  
  
clean:  
	rm -f *.c *.o all  

Static Pattern Rules + fungsi filter

  • Dengan filter, hanya target yang cocok dengan pola ekstensi tertentu yang bisa dipilih
obj_files = foo.result bar.o lose.o  
src_files = foo.raw bar.c lose.c  
  
all: $(obj_files)  
.PHONY: all  
  
$(filter %.o,$(obj_files)): %.o: %.c  
	echo "target: $@ prereq: $

1 komentar

 
GN⁺ 2025-06-21
Pendapat Hacker News
  • Pernah melihat langsung seseorang di lab Graphics Boston University pada tahun 1985 membuat renderer 3D untuk animasi dengan Makefile. Orang itu adalah programmer Lisp, sedang mengerjakan pembuatan prosedur awal dan sistem aktor 3D, lalu membuat Makefile yang sangat elegan hanya sekitar 10 baris. Strukturnya menghasilkan ratusan animasi secara otomatis hanya dengan dependensi tanggal file yang sederhana. Bentuk 3D tiap frame dibuat dengan Lisp, lalu Make menghasilkan frame-frame tersebut. Pada tahun 1985, berbeda dengan sekarang ketika 3D dan animasi dianggap biasa, semua orang sangat takjub, dan yang diingat adalah bahwa orang itu adalah Brian Gardner, yang kemudian menangani renderer 3D untuk Iron Giant dan Coraline

    • Mengungkapkan rasa penasaran apakah orang yang dimaksud adalah orang yang ada di 3d-consultant.com/bio.html

    • Memastikan apakah yang dimaksud memang film Coraline

  • Memperkenalkan beberapa flag berguna yang kurang dikenal saat memakai Make

    • --output-sync=recurse -j10: artinya stdout/stderr dikumpulkan lalu ditampilkan saat pekerjaan tiap target selesai; kalau tidak, log akan tercampur dan sulit dianalisis
    • Di sistem yang sibuk atau lingkungan multi-pengguna, bisa memakai --load-average alih-alih -j untuk mengatur beban sistem saat paralelisasi (make -j10 --load-average=10)
    • Opsi --shuffle, yang mengacak jadwal target build, berguna di lingkungan CI untuk menangkap masalah dependensi di dalam Makefile
    • Menyebut gagasan bahwa kalau berbagai opsi make dirangkum secara resmi lalu disertakan ke dalam program dalam bentuk teks atau dokumentasi, aksesibilitas pengguna akan meningkat

    • Opsi yang paling sering dipakai adalah flag -B untuk build paksa penuh

    • Karena sering melihat masalah yang disebabkan make -j pada mesin DOS, fenomena itu dianggap sebagai bug

    • Bertanya apakah masalah paralelisasi di sistem sibuk atau lingkungan multi-pengguna bukan sesuatu yang seharusnya ditangani scheduler OS

    • Flag-flag ini berguna, tetapi karena tidak portabel, disarankan untuk tidak memakainya kecuali di proyek privat untuk diri sendiri

  • Menganggap bahwa melewatkan .PHONY di tutorial hanya karena tidak dipakai adalah alasan yang lemah. Menurutnya, yang benar adalah mengajarkan cara memakai alat dengan benar

    • Di timnya pernah ada perdebatan karena memakai Make sebagai task runner sambil menambahkan dan memelihara .PHONY di semua recipe
    • Merekomendasikan panduan gaya Makefile Clark Grubb (clarkgrubb.com/makefile-style-guide)
    • Berbagi pengalaman tentang berbagai gaya, antara mendeklarasikan .PHONY per recipe versus mengumpulkannya sekaligus di bagian atas file, dan berharap itu bisa ditegakkan dengan linter
    • Setelah dibaca, dokumennya cukup bagus, tetapi ada beberapa hal yang tidak disetujui
      • Menerapkan -o pipefail secara membabi buta itu bermasalah, dan bisa rusak saat memakai grep dan semacamnya dalam pipe, jadi lebih baik diterapkan sesuai situasi
      • Menandai target non-file dengan .PHONY memang lebih ketat, tetapi hampir tidak perlu dan hanya membuat Makefile makin bertele-tele, jadi lebih baik dipakai saat perlu saja
      • Recipe yang menghasilkan beberapa file output dulu memakai file dummy, tetapi sejak GNU Make 4.3 sekarang sudah ada dukungan resmi untuk grouped targets (lihat di sini)
  • Ada klaim bahwa Make adalah alat yang dikhususkan untuk build codebase C berskala besar

    • Ada yang suka memakainya sebagai job runner per proyek, tetapi menurutnya Make tidak cocok sebagai job runner dan bahkan membuat hal seperti conditional menjadi sulit
    • Pernah juga melihat kasus gagal saat mencoba membungkus alat seperti Terraform
    • Menurut pendapat lain, Make lebih merupakan alat shell umum yang mengubah shell script linear menjadi bentuk dependensi deklaratif daripada sekadar job runner

    • Ada pandangan bahwa melihat Make sebagai alat build khusus codebase C saja sudah tidak tepat lagi. Disebutkan kenyataan bahwa selama 20 tahun terakhir sudah dikembangkan sistem build yang lebih kokoh dan lebih jelas. Ada seruan agar cara pandang itu diperbarui

    • Pertanyaan tentang apa itu job runner yang baik. (Disertai permintaan maaf karena ternyata salah memahami arti job runner)

  • Merekomendasikan just sebagai alat modern yang menggantikan bagian-bagian Makefile yang menjadi rumit

    • just bagus sebagai pengganti daftar shell script, tetapi tidak bisa menggantikan fungsi esensial Make, yaitu “jalankan hanya rule yang perlu dijalankan ulang”

    • Selain itu, alternatif lain antara lain

    • Alat-alat alternatif menyebut dirinya pengganti Make, tetapi menurutnya sebenarnya sangat berbeda sehingga sulit dibandingkan. Inti Make adalah menghasilkan artefak dan tidak membangun ulang hal yang sudah dibangun. Sementara just hanya berperan sebagai eksekutor perintah sederhana

    • Keuntungan memakai Make sebagai eksekutor perintah adalah kestabilannya sebagai alat standar yang terpasang hampir di mana-mana. Walaupun alternatif mungkin dibuat lebih baik, tetap ada beban instalasi tambahan sehingga tidak terasa perlu dipakai

    • Task dipakai dengan baik untuk proyek hobi sederhana berbasis C, tetapi masih sulit menilai apakah cocok juga untuk proyek besar (situs resmi Task)

  • Menarik bahwa baru-baru ini CMake menilai Makefile tidak cocok untuk dukungan modul C++20, sehingga memilih ninja sebagai default (panduan CMake)

    • Pada praktiknya, hampir mustahil mendefinisikan dependensi target secara statis, sehingga diadopsi pendekatan analisis dinamis dengan alat seperti clang-scan-deps (slide teknis)
    • Menurut pendapat lain, batasan ini sebenarnya keputusan dari pihak CMake atau karena generator Makefile tidak punya kontributor yang mendukungnya. ninja juga tidak bisa langsung mendukung modul C++ (isu terkait), dan justru punya lebih sedikit fitur daripada Make serta mengharuskan semua dependensi dinyatakan secara statis

    • Ada pendapat bahwa pengenalan modul itu sendiri rumit dan membingungkan

  • Bertanya apakah ada yang punya pengalaman memakai tup. (dokumentasi resmi)

    • tup adalah sistem build yang secara otomatis mengetahui dependensi berdasarkan akses file system, sehingga bisa diterapkan ke compiler/alat apa pun
  • Memperkenalkan diri sebagai pencipta dan maintainer utama alat alternatif Make bernama Task. Sudah dikembangkan lebih dari 8 tahun dan terus berkembang

    • just juga direkomendasikan sebagai alternatif Make lainnya (GitHub just)

    • Sebagai kebetulan yang lucu, ia sendiri sering memakai Task dan pagi ini juga mengajukan issue

  • Tutorial ini punya masalah yang berbahaya dan subtil

    • Saat mem-parsing opsi dari MAKEFLAGS, untuk menangani opsi panjang atau opsi pendek kosong, seharusnya ditulis seperti ini
      ifneq (,$(findstring t,$(firstword -$(MAKEFLAGS))))
    • Jika perlu kompatibilitas dengan make lama bawaan OS X, banyak fiturnya tidak ada atau berbeda secara subtil
    • Masalah lainnya sebagian besar hanya typo atau pelanggaran gaya terbaik, jadi diabaikan
    • Sebagai catatan, load lebih portabel daripada guile, dan di lingkungan cross-compiling penentuan compiler harus dilakukan dengan tepat
    • Disarankan untuk membaca Paul’s Rules of Makefiles (di sini), manual GNU make (di sini), dan manual terkait lainnya
    • Juga mengelola proyek demo Makefile sederhana (demo github)
  • Punya kebiasaan selalu menyertakan Makefile di setiap repo GitHub

    • Karena perintah mudah lupa, menyimpannya dalam Makefile memudahkan untuk menambahkan step yang rumit juga, dan cukup menjalankan make agar perilaku yang diharapkan untuk tiap proyek langsung dijalankan tanpa perlu mengingatnya lagi