Belajar Makefile dengan Contoh Terbaik
(makefiletutorial.com)- Makefile terutama digunakan untuk mengotomatiskan kompilasi C/C++ pada proyek besar dengan menjalankan ulang hanya langkah build yang diperlukan berdasarkan perubahan file
- Make membandingkan timestamp filesystem dari target dan dependensi, lalu menjalankan perintah hanya saat target tidak ada atau dependensinya lebih baru
- Aturan dasar terdiri dari
targets: prerequisitesdan perintah yang diindentasi dengan tab; indentasi spasi dapat menyebabkanmakegagal - Alur build dapat diperluas dengan variabel, wildcard, variabel otomatis, aturan pola, kondisional, fungsi, recursive make,
include,.PHONY, dan.DELETE_ON_ERROR - Contoh Cookbook membuat sumber C/C++ di
src/menjadi artefak dibuild/, serta menyusun manajemen dependensi otomatis dengan-MMD -MPdan-include
Situasi saat Makefile dibutuhkan
- Makefile digunakan dalam program besar untuk menentukan file mana yang perlu dikompilasi ulang
- Contoh utama dalam tutorial ini adalah kompilasi file C/C++
- Make juga dapat digunakan di luar kompilasi untuk tugas yang perlu menjalankan serangkaian perintah berdasarkan apakah file berubah atau tidak
- Untuk C/C++, alat build lain yang diperkenalkan adalah SCons, CMake, Bazel, dan Ninja
- Java memiliki Ant, Maven, dan Gradle, sementara bahasa seperti Go, Rust, dan TypeScript memiliki alat build sendiri
- Bahasa interpreted seperti Python, Ruby, dan JavaScript murni tidak perlu dikompilasi ulang saat file berubah, sehingga alat seperti Makefile tidak bersifat wajib
Model eksekusi dasar Make
- Contoh berjalan dengan membuat file
Makefiledi terminal yang sudah terpasangmake, lalu menjalankanmakedi direktori tersebut - Aturan dasar mengikuti bentuk berikut
targets: prerequisitescommandyang diawali dengan tab
- Target biasanya berupa nama file, dan perintah adalah langkah untuk membuat target tersebut
- Prerequisites adalah file yang harus ada sebelum perintah dijalankan, dan juga disebut dependencies
- Baris perintah dalam Makefile harus diindentasi dengan karakter tab; jika memakai spasi,
makeakan gagal
Perbandingan waktu antara target dan dependensi
make hellomenjalankan perintah yang terhubung ke targethello, tetapi jika filehellosudah ada, perintah tidak dijalankan- Target dan nama file terhubung langsung; umumnya perintah pada target membuat file dengan nama yang sama dengan target
- Dalam aturan
blah: blah.c, perintah kompilasi hanya dijalankan ketikablahtidak ada ataublah.clebih baru daripadablah - Make tidak melacak riwayat perubahan secara langsung, melainkan menggunakan timestamp filesystem sebagai heuristik
- Jika Anda mengubah file lalu mengubah waktu modifikasinya ke masa lalu, Make dapat keliru menganggap file tersebut tidak berubah
Target default dan clean
- Jika target tidak ditentukan pada
make, target pertama dalam Makefile dijalankan sebagai target default - Target
alldigunakan untuk membuat beberapa target sekaligus; jika diletakkan sebagai aturan pertama, seluruh target akan dibuild hanya dengan menjalankanmake cleanbiasanya digunakan sebagai target untuk menghapus artefak build, tetapi bukan kata khusus yang direservasi oleh Make- Jika
cleanbukan target pertama dan bukan dependensi dari target lain, ia harus dipanggil secara eksplisit sepertimake cleanagar dijalankan - Jika file bernama
cleanbenar-benar ada, targetcleanmungkin tidak dijalankan; hal ini diselesaikan nanti dengan.PHONY
Variabel dan pemrosesan string
- Variabel Make hanya dapat berisi string, dan tutorial ini umumnya merekomendasikan penggunaan
:= - Referensi variabel menggunakan format
$(name)atau${name} - Tanda kutip tunggal dan ganda tidak memiliki makna khusus di Make itu sendiri; keduanya hanya karakter yang masuk ke variabel
- Cara mendefinisikan variabel secara garis besar ada dua
=: variabel rekursif, yang mencari dan mengekspansi variabel saat digunakan:=: variabel dengan ekspansi sederhana, yang hanya mengekspansi nilai yang sudah didefinisikan pada saat definisi
?=hanya menetapkan nilai pada variabel yang belum disetel+=menambahkan nilai ke variabel- Variabel yang tidak didefinisikan diperlakukan sebagai string kosong
- Spasi di akhir baris tidak dihapus, dan untuk membuat variabel spasi satu karakter dapat digunakan teknik seperti
$(nullstring)
Wildcard dan variabel otomatis
- Di Make,
*dan%sama-sama disebut wildcard, tetapi maknanya berbeda *mencari nama file yang cocok di filesystem; daripada dipakai langsung dalam definisi variabel, lebih aman membungkusnya dengan fungsi wildcard seperti$(wildcard *.o)- Jika
*tidak cocok dengan file apa pun, di luar fungsiwildcardia dapat tetap tersisa apa adanya %digunakan dalam aturan dan fungsi tertentu, dan bagian yang cocok disebut stem- Variabel otomatis yang sering digunakan adalah sebagai berikut
$@: nama target$?: dependensi yang lebih baru daripada target$^: semua dependensi$<: dependensi pertama
Aturan implisit dan aturan pola
- Make memiliki aturan implisit untuk kompilasi C, sehingga build dapat berjalan tanpa menulis perintah kompilasi secara eksplisit
- Aturan implisit yang umum berbentuk sebagai berikut
n.odibuat darin.cdalam bentuk$(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@n.odibuat darin.ccataun.cppdalam bentuk$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@ndibuat darin.odengan perintah linker
- Variabel penting dalam aturan implisit adalah
CC,CXX,CFLAGS,CXXFLAGS,CPPFLAGS, danLDFLAGS - Aturan pola statis berbentuk
targets...: target-pattern: prereq-patterns ..., dan memasukkan stem yang cocok dengan%pada target ke pola dependensi - Aturan pola menyertakan
%pada target seperti%.o: %.c, dan dapat digunakan seperti aturan implisit buatan sendiri - Aturan double-colon
::dapat mendefinisikan beberapa aturan untuk target yang sama, tetapi jarang digunakan
Eksekusi perintah dan penanganan error
- Jika
@ditambahkan di depan perintah, perintah itu sendiri tidak ditampilkan - Menjalankan
make -sberperilaku seolah setiap baris diawali dengan@ - Setiap baris perintah efektif dijalankan di shell baru, sehingga
cd ..pada satu baris tidak memengaruhi baris berikutnya - Untuk mempertahankan status shell yang sama, tulis dalam satu baris seperti
cd ..; echo \pwd`` atau sambungkan dengan backslash - Shell default adalah
/bin/sh, dan dapat diganti sepertiSHELL=/bin/bash - Untuk memakai
$milik variabel shell di dalam Makefile, gunakan$$ - Penanganan error berbeda tergantung cara menjalankannya
make -k: tetap menjalankan tugas lain yang memungkinkan meskipun ada error-di depan perintah: mengabaikan error pada perintah tersebutmake -i: menerapkan pengabaian error pada semua perintah
- Jika Make dihentikan dengan
ctrl+c, Make menghapus target terbaru yang baru saja dibuat
Recursive make dan variabel lingkungan
- Saat memanggil make lagi di dalam Makefile, gunakan variabel khusus
$(MAKE)alih-alihmake $(MAKE)meneruskan flag make dan menangani pengaruh flag dengan tepat- Saat Make dimulai, variabel lingkungan yang disetel pada saat eksekusi otomatis dibuat sebagai variabel Make
- Direktif
exportmenyetel variabel ke lingkungan perintah shell untuk semua recipe - Agar variabel juga dipakai di make turunan, variabel tersebut harus di-
export .EXPORT_ALL_VARIABLESdapat digunakan untuk mengekspor semua variabel- Jika beberapa target ditentukan sekaligus seperti
make clean run test, target dijalankan dalam urutanclean,run,test
Kondisional dan fungsi
- Kondisional terdiri dari
ifeq,ifneq,ifdef,ifndef,else, danendif ifdeftidak mengekspansi referensi variabel, hanya memeriksa apakah variabel sudah didefinisikan$(MAKEFLAGS)danfindstringdapat digunakan untuk memeriksa apakah flag sepertimake -iditeruskan- Fungsi Make terutama digunakan untuk pemrosesan teks, dan dipanggil dalam bentuk
$(fn, arguments)atau${fn, arguments} - Fungsi utama adalah sebagai berikut
subst: substitusi stringpatsubst: substitusi kata yang cocok dengan polaforeach: transformasi daftar kata yang dipisahkan spasiif: menggunakan argumen kedua jika argumen pertama tidak kosong, jika tidak menggunakan argumen ketigacall: memanggil variabel seperti fungsi dasar dan menggunakan parameter seperti$(1),$(2)shell: memanggil shell, tetapi mengubah newline menjadi spasifilter: memilih elemen dalam daftar yang cocok dengan pola tertentufilter-out: memilih elemen yang tidak cocok dengan pola tertentu
include, vpath, .PHONY, .DELETE_ON_ERROR
include filenames...membuat satu atau beberapa Makefile lain dibacaincludeberguna saat membaca Makefile yang dibuat dengan opsi seperti-Mmilikgccvpath <pattern> <directories>menentukan direktori untuk mencari file dependensi tertentu- Variabel
VPATHdapat menentukan perilaku jalur pencarian serupa secara global - Backslash
\digunakan untuk membagi perintah panjang menjadi beberapa baris - Jika
.PHONYditambahkan ke target, Make tidak akan keliru menganggap target tersebut sebagai file dengan nama yang sama .DELETE_ON_ERRORmenghapus target dari aturan tersebut jika perintah mengembalikan status keluar bukan 0 saat aturan dijalankan.DELETE_ON_ERRORtidak hanya berlaku pada target tepat sebelumnya sepertiPHONY, melainkan pada semua target
Contoh Makefile Cookbook
- Contoh Cookbook menyediakan template Makefile yang sesuai untuk proyek skala menengah
- Jika file C/C++ dimasukkan ke folder
src/, Makefile otomatis menentukan target kompilasi dan dependensinya - Konfigurasi variabel utama adalah sebagai berikut
TARGET_EXEC := final_programBUILD_DIR := ./buildSRC_DIRS := ./src
SRCSmenggunakanfinddan$(shell ...)untuk mencari file*.cpp,*.c, dan*.sOBJSmenambahkanBUILD_DIRdi depan setiap file sumber dan menambahkan.ountuk membuat jalur artefak buildDEPSmengubah.omenjadi.duntuk membuat daftar Makefile dependensiINC_DIRSmencari semua direktori di bawahsrc/, danINC_FLAGSmenambahkan prefiks-Ike setiap direktoriCPPFLAGS := $(INC_FLAGS) -MMD -MPmengonfigurasi GCC agar membuat file dependensi.d- Sumber C dan C++ masing-masing membuat file
.odi bawahbuild/dengan aturan pola terpisah, dan direktori yang diperlukan dibuat denganmkdir -p $(dir $@) cleandideklarasikan sebagai.PHONYdan menghapus direktori build denganrm -r $(BUILD_DIR)-include $(DEPS)menyertakan file.d, tetapi menekan error yang muncul saat file belum ada seperti pada build pertama
1 komentar
Komentar Hacker News
Ada beberapa flag
makeyang kurang dikenal tetapi berguna. Sinkronisasi output membuat stdout/stderr baru ditampilkan setelah target selesai, sehingga mengurangi masalah log yang tercampur dan sulit dibaca saat build paralelmake --output-sync=recurse -j10Pada sistem multi-pengguna yang sibuk, jumlah job
-jsaja mungkin tidak cukup, jadi paralelisme juga bisa dibatasi berdasarkan load average:make -j10 --load-average=10--shuffle, yang mengacak urutan penjadwalan target, juga berguna di CI untuk menemukan dependensi yang hilang di Makefile:make --shuffle # or --shuffle=seed/reverse-B, opsi yang memaksa semuanya di-build ulang tanpa syaratmake -jbeberapa kali membuat mesin berada dalam kondisi DoS, jadi saya menganggapnya hampir seperti bugSekitar tahun 1985 di Boston University Graphics lab, saya melihat seseorang memakai Makefile untuk menggerakkan pembuatan renderer 3D untuk animasi. Ia orang Lisp dan sedang menangani generasi prosedural awal serta sistem aktor 3D, dan Makefile-nya sangat elegan, total sekitar 10 baris
Hanya dengan dependensi tanggal file sederhana, ia membuat ratusan animasi; Lisp membuat bentuk 3D untuk tiap frame, lalu Make menghasilkan frame-nya
Karena itu tahun 1985, masa ketika hampir tidak ada hal-hal yang sekarang kita anggap biasa dalam 3D dan animasi, semua orang dibuat takjub. Setelah itu, seingat saya, ia menulis renderer 3D untuk Iron Giant dan juga berperan penting di Coraline. Namanya Brian Gardner
Make adalah salah satu alat yang saya sangat syukuri telah saya pelajari di awal karier. Sekarang saya tidak banyak memakainya, tetapi ia menunjukkan kekuatan sistem deklaratif dibanding sistem imperatif
Saya menyadari bahwa ide ini dapat diperluas secara alami ke pekerjaan lain. Melihat gambar di bagian atas situs ini, penulisnya tampaknya merasakan hal serupa: resep masakan bisa dipahami lebih baik jika dilihat secara deklaratif seperti Makefile, daripada sebagai urutan perintah bergaya skrip seperti resep tradisional
Tulisan terkait: https://blog.gpkb.org/posts/cooking-with-make/
Saya selalu menulis resep agar bisa dibaca seperti Makefile dan membawanya ke dapur; saya penasaran apakah ada yang pernah menata huruf atau menampilkannya seperti ini. Saat membaca resep baru, sepertinya akan sangat menghemat waktu kalau tidak perlu mengubah skrip menjadi Makefile di kepala
Tentu saja biayanya adalah harus menyelesaikan sendiri graf dependensi. Kalau hanya ingin mengikuti langkah berurutan yang sudah diserialisasi sebelumnya, pekerjaan yang ditanggung jadi lebih banyak
Artikel itu mengatakan kebanyakan resep tidak ditandai sebagai
.PHONY, dan memakai itu seolah sebagai alasan untuk tidak membahasnya dalam tutorial, tetapi itu alasan yang lemah. Alat harus mengajarkan cara penggunaan yang benarDi tim, karena kami memakai make sebagai runner tugas, saya pernah dikritik karena menambahkan dan memelihara
.PHONYuntuk semua resepPanduan gaya Makefile dari Clark Grubb bagus: https://clarkgrubb.com/makefile-style-guide
Saya penasaran apakah ada yang memakai panduan gaya ini. Saya juga penasaran apakah resep phony biasanya ditandai di titik deklarasinya, atau diletakkan sebagai daftar besar di bagian atas file. Akan bagus kalau ada linter yang memaksakan ini
Menggunakan
-o pipefailsecara kebiasaan bukan ide bagus. Pipefail berguna, tetapi ia merusak pemfilteran output dengangrep, hal yang paling sering dilakukan orang dalam pipeline. Lebih baik ditambahkan hanya pada resep yang membutuhkannyaMenandai target yang bukan file sebagai
.PHONYmemang secara ketat benar, tetapi biasanya tidak diperlukan. Jika targetnya banyak, itu menambah verbosity yang tidak perlu pada Makefile, jadi menurut saya lebih baik ditambahkan hanya saat diperlukanPada resep yang menghasilkan beberapa file output, dulu file dummy/flag merupakan standar ketika aturan pola tidak cocok, tetapi mulai GNU Make 4.3, target grup didukung secara native. Ini juga sudah tersedia di Ubuntu 22.04 LTS: https://www.gnu.org/software/make/manual/html_node/Multiple-...
Hal yang menarik belakangan ini adalah CMake menilai bahwa Makefile tidak cocok untuk proyek yang memakai modul C++20, dan Ninja-lah yang tepat. https://cmake.org/cmake/help/latest/manual/cmake-cxxmodules....
Pada dasarnya, tampaknya mereka menganggap mendefinisikan dependensi target secara statis terlalu sulit atau mustahil. Sekarang ini ditangani secara dinamis dengan alat seperti
clang-scan-deps: https://llvm.org/devmtg/2019-04/slides/TechTalk-Lorenz-clang...Bahkan, karena Ninja mengharuskan semua dependensi didefinisikan secara statis, dalam hal ini fiturnya lebih sedikit daripada Make biasa
Make memang punya tempat sebagai alat build untuk codebase C berukuran besar. Namun sering kali ia diperlakukan seperti “task runner per proyek” serbaguna, dan untuk itu kurang cocok. Bahkan kondisi sederhana pun sulit
Misalnya, saya beberapa kali melihat upaya beritikad baik untuk membungkus Terraform dengan Make, tetapi akhirnya tidak berjalan baik
Ada alat modern yang bagus bernama just untuk menggantikan bagian Makefile yang cenderung berantakan: https://github.com/casey/just
Sebagai konsep yang sepenuhnya berbeda, ada juga Makedown yang dibahas di HN 8 bulan lalu: https://news.ycombinator.com/item?id=41825344
Makefile itu bagus, tetapi sebaiknya jangan terlalu tenggelam di dalamnya. Beberapa tahun lalu saya mencoba membuat framework GNU Make murni, lalu menyadari bahwa pada dasarnya saya sedang menciptakan ulang autoconf; saat itulah saya memahami mengapa GNU autotools dibuat
Makefile secara aneh merupakan Turing tarpit yang mirip Lisp. GNU Make bahkan punya fitur metaprogramming, sehingga sulit menahan dorongan untuk memetaprogramkan sistem yang tidak suci di dalam Makefile. Fakta bahwa GNU Make terpasang sangat luas juga makin menggoda
Saya adalah pembuat sekaligus salah satu maintainer Task, alternatif untuk Make. Proyek ini sudah ada lebih dari 8 tahun dan terus berkembang, jadi kalau Anda mencari sesuatu yang baru, silakan coba. Jangan ragu bertanya
https://taskfile.dev/
https://github.com/go-task/task
Saya penasaran apakah ada yang pernah memakai tup: https://gittup.org/tup/ex_dependencies.html
Ini adalah sistem build yang secara otomatis menentukan dependensi berdasarkan akses file system, sehingga bisa dipakai bersama compiler atau alat apa pun