Bahasa: 日本語 | Español | Français | Português | 中文 | Bahasa Indonesia | English
Sebelumnya | Berikutnya | Daftar Isi | Versi asli bahasa Inggris (gnu.org)

4 Menulis aturan

Aturan (rule) muncul dalam makefile dan menentukan kapan serta bagaimana membuat ulang file tertentu, yang disebut target dari aturan tersebut (biasanya hanya satu per aturan). Aturan mencantumkan daftar file lain yang menjadi prasyarat (prerequisite, dahulu disebut: dependensi) dari target, dan resep (recipe, konten eksekusi aturan) yang digunakan untuk membuat atau memperbarui target.

Urutan penulisan aturan pada dasarnya tidak berpengaruh, kecuali dalam menentukan sasaran default—target yang akan diproses make jika Anda tidak menentukan apa pun secara khusus. Sasaran default adalah target pertama dari aturan pertama dalam makefile pertama. Ada dua pengecualian tambahan di sini: pertama, target yang diawali dengan titik tidak akan menjadi default kecuali mengandung setidaknya satu garis miring ‘/’; kedua, target yang mendefinisikan aturan pola sama sekali tidak memengaruhi sasaran default. (Lihat bagian Mendefinisikan dan mendefinisikan ulang aturan pola.)

Karena itu, biasanya kita menulis makefile sedemikian rupa sehingga aturan pertama adalah "aturan untuk mengompilasi seluruh program (atau semua program yang dideskripsikan oleh makefile)" (target ini sering diberi nama ‘all’). Lihat bagian Argumen untuk menentukan sasaran.


4.1 Contoh aturan

Mari kita lihat dulu satu contoh aturan:

foo.o : foo.c defs.h       # modul untuk mengutak-atik frob
        cc -c -g foo.c

Target dari aturan ini adalah foo.o, dan prasyaratnya adalah foo.c dan defs.h. Resepnya berisi satu perintah, yaitu ‘cc -c -g foo.c’. Baris resep diidentifikasi sebagai resep karena dimulai dengan karakter tab.

Aturan ini menyatakan dua hal:


4.2 Sintaksis aturan

Secara umum, aturan memiliki bentuk seperti berikut:

targets : prerequisites
        recipe

Atau bisa juga berbentuk seperti berikut:

targets : prerequisites ; recipe
        recipe

targets (kelompok target) adalah nama file yang dipisahkan oleh spasi. Anda dapat menggunakan karakter wildcard di sini (lihat bagian Menggunakan karakter wildcard dalam nama file). Selain itu, nama berbentuk ‘a(m)’ menyatakan anggota m dalam file arsip a (lihat bagian Anggota arsip sebagai target). Biasanya satu aturan hanya memiliki satu target, tetapi kadang ada alasan untuk memiliki lebih dari satu (lihat bagian Banyak target dalam satu aturan).

Baris recipe (resep) dimulai dengan karakter tab (atau karakter pertama dari nilai variabel .RECIPEPREFIX; lihat bagian Variabel khusus lainnya). Baris resep pertama dapat ditulis pada baris setelah prasyarat dengan karakter tab, atau pada baris yang sama dengan tanda titik koma. Kedua cara penulisan ini memberikan efek yang sama. Ada beberapa perbedaan lain dalam sintaksis resep. Lihat bagian Menulis resep dalam aturan.

Karena tanda dolar digunakan untuk memulai referensi variabel make, jika Anda benar-benar ingin menuliskan tanda dolar itu sendiri dalam target atau prasyarat, Anda harus menuliskannya dua kali, yaitu ‘$$’ (lihat bagian Cara menggunakan variabel). Selain itu, jika Anda telah mengaktifkan ekspansi sekunder (lihat bagian Ekspansi sekunder) dan ingin menuliskan tanda dolar itu sendiri dalam daftar prasyarat, Anda sebenarnya harus menuliskan empat tanda dolar (‘$$$$’).

Baris yang panjang dapat dipecah dengan menyisipkan garis miring terbalik diikuti baris baru. Namun, ini tidak wajib karena make tidak menetapkan batas panjang untuk satu baris dalam makefile.

Aturan menyampaikan dua hal kepada make: kapan target menjadi usang (perlu diperbarui), dan bagaimana memperbaruinya jika diperlukan.

Kriteria untuk menentukan apakah sesuatu sudah usang ditentukan melalui prerequisites (prasyarat), yang terdiri atas nama file yang dipisahkan oleh spasi. (Di sini pun Anda dapat menggunakan wildcard atau anggota arsip; lihat bagian Menggunakan make untuk memperbarui file arsip.) Sebuah target dianggap usang jika target tersebut tidak ada, atau jika lebih lama (berdasarkan perbandingan waktu modifikasi terakhir) daripada salah satu prasyaratnya. Idenya adalah bahwa isi file target dihitung berdasarkan informasi dalam prasyarat, sehingga jika salah satu prasyarat berubah, isi file target yang sudah ada belum tentu masih valid.

Cara memperbarui ditentukan oleh recipe (resep). Ini berupa satu baris perintah atau lebih yang dieksekusi oleh shell (biasanya ‘sh’), tetapi dengan beberapa fitur tambahan (lihat bagian Menulis resep dalam aturan).


4.3 Jenis-jenis prasyarat

Ada dua jenis prasyarat yang dipahami oleh GNU make: prasyarat normal, yang telah dijelaskan pada bagian sebelumnya, dan prasyarat khusus urutan (order-only). Prasyarat normal menyatakan dua hal. Pertama, ia menentukan urutan pemanggilan resep: semua resep dari prasyarat suatu target harus selesai sebelum resep target itu sendiri dimulai. Kedua, ia menentukan hubungan dependensi: jika salah satu prasyarat lebih baru daripada target, target dianggap usang dan harus dibangun ulang.

Biasanya, inilah perilaku yang memang Anda inginkan: jika prasyarat suatu target diperbarui, target itu pun seharusnya ikut diperbarui.

Namun, terkadang Anda ingin memastikan bahwa suatu prasyarat dibangun sebelum target, tetapi tanpa memaksa target diperbarui ketika prasyarat tersebut diperbarui. Prasyarat khusus urutan digunakan untuk membuat jenis hubungan ini. Prasyarat khusus urutan ditentukan dengan menempatkan simbol pipa (|) dalam daftar prasyarat: prasyarat di sebelah kiri simbol pipa bersifat normal, sedangkan prasyarat di sebelah kanan bersifat khusus urutan:

targets : normal-prerequisites | order-only-prerequisites

Tentu saja, bagian prasyarat normal boleh kosong. Anda juga tetap dapat mendeklarasikan prasyarat untuk target yang sama dalam beberapa baris; prasyarat-prasyarat tersebut akan digabungkan dengan tepat (prasyarat normal ditambahkan ke daftar prasyarat normal, prasyarat khusus urutan ditambahkan ke daftar prasyarat khusus urutan). Perlu diperhatikan bahwa jika Anda mendeklarasikan file yang sama sebagai prasyarat normal sekaligus prasyarat khusus urutan, prasyarat normal akan diutamakan (karena perilaku prasyarat normal mencakup sepenuhnya perilaku prasyarat khusus urutan).

Prasyarat khusus urutan tidak akan pernah diperiksa saat menentukan apakah target sudah usang. Bahkan jika prasyarat khusus urutan ditandai sebagai target semu (lihat bagian Target semu (phony)), hal itu tidak akan menyebabkan target dibangun ulang.

Sebagai contoh, mari kita pertimbangkan kasus ketika Anda ingin menempatkan target di direktori terpisah, yang mungkin belum ada sebelum make dijalankan. Dalam situasi ini, Anda ingin direktori tersebut dibuat sebelum ada target yang ditempatkan di dalamnya, tetapi karena stempel waktu direktori berubah setiap kali ada file yang ditambahkan, dihapus, atau diganti namanya, kita tentu tidak ingin membangun ulang semua target setiap kali stempel waktu direktori berubah. Salah satu cara mengatasinya adalah dengan prasyarat khusus urutan: jadikan direktori tersebut sebagai prasyarat khusus urutan untuk semua target:

OBJDIR := objdir
OBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o)

$(OBJDIR)/%.o : %.c
        $(COMPILE.c) $(OUTPUT_OPTION) $<

all: $(OBJS)

$(OBJS): | $(OBJDIR)

$(OBJDIR):
        mkdir $(OBJDIR)

Dengan cara ini, aturan untuk membuat direktori objdir akan dijalankan, jika diperlukan, sebelum ‘.o’ mana pun dibangun. Namun, hanya karena stempel waktu direktori objdir berubah, tidak ada satu pun ‘.o’ yang akan dibangun.


4.4 Menggunakan karakter wildcard dalam nama file

Satu nama file dapat menentukan banyak file sekaligus dengan menggunakan karakter wildcard. Karakter wildcard dalam make adalah ‘*’, ‘?’, dan ‘[…]’, sama seperti pada Bourne shell. Misalnya, *.c menyatakan daftar semua file (dalam direktori kerja) yang namanya berakhiran ‘.c’.

Jika sebuah ekspresi cocok dengan beberapa file, hasilnya akan diurutkan.2 Namun, beberapa ekspresi tidak akan diurutkan secara keseluruhan. Misalnya, *.c *.h akan mencantumkan semua file yang namanya berakhiran ‘.c’ (terurut), diikuti oleh semua file yang namanya berakhiran ‘.h’ (terurut).

Karakter ‘~’ di awal nama file juga memiliki arti khusus. Jika berdiri sendiri, atau diikuti oleh garis miring, karakter ini menyatakan direktori beranda (home directory) Anda. Misalnya, ~/bin diekspansi menjadi /home/you/bin. Jika ‘~’ diikuti oleh sebuah kata, string tersebut menyatakan direktori beranda milik pengguna yang namanya sesuai kata itu. Misalnya, ~john/bin diekspansi menjadi /home/john/bin. Pada sistem yang tidak memiliki direktori beranda untuk setiap pengguna (seperti MS-DOS atau MS-Windows), fungsi ini dapat disimulasikan dengan mengatur variabel lingkungan HOME.

Ekspansi wildcard dilakukan secara otomatis oleh make pada target dan prasyarat. Dalam resep, shell yang bertanggung jawab atas ekspansi wildcard. Pada konteks lain, ekspansi wildcard hanya terjadi jika Anda memintanya secara eksplisit dengan fungsi wildcard.

Arti khusus dari karakter wildcard dapat dinonaktifkan dengan menambahkan garis miring terbalik di depannya. Dengan demikian, foo\*bar akan merujuk pada file tertentu yang namanya terdiri atas ‘foo’, tanda bintang, dan ‘bar’.


4.4.1 Contoh penggunaan wildcard

Wildcard juga dapat digunakan dalam resep suatu aturan, tempat ia diekspansi oleh shell. Misalnya, berikut adalah aturan untuk menghapus semua file objek:

clean:
        rm -f *.o

Wildcard juga berguna dalam prasyarat suatu aturan. Dengan aturan berikut dalam makefile, ‘make print’ akan mencetak semua file ‘.c’ yang telah berubah sejak Anda terakhir kali mencetaknya:

print: *.c
        lpr -p $?
        touch print

Aturan ini menggunakan print sebagai file target kosong; lihat bagian File target kosong untuk mencatat peristiwa. (Variabel otomatis ‘$?’ digunakan untuk mencetak hanya file yang telah berubah; lihat bagian Variabel otomatis.)

Ekspansi wildcard tidak terjadi ketika Anda mendefinisikan variabel. Karena itu, jika Anda menulis:

objects = *.o

maka nilai variabel objects adalah string literal ‘*.o’. Namun, jika Anda menggunakan nilai objects dalam target atau prasyarat, ekspansi wildcard akan terjadi di sana. Jika Anda menggunakan nilai objects dalam resep, shell mungkin akan melakukan ekspansi wildcard saat resep dijalankan. Jika Anda ingin mengatur objects ke hasil ekspansinya, gunakan sebagai gantinya:

objects := $(wildcard *.o)

Lihat bagian Fungsi wildcard.


4.4.2 Jebakan dalam menggunakan wildcard

Berikut adalah contoh penggunaan ekspansi wildcard secara naif yang tidak berjalan sesuai yang Anda inginkan. Misalkan Anda ingin menyatakan bahwa file eksekusi foo dibuat dari semua file objek dalam direktori, dan Anda menulis:

objects = *.o

foo : $(objects)
        cc -o foo $(CFLAGS) $(objects)

Nilai objects adalah string literal ‘*.o’. Ekspansi wildcard terjadi dalam aturan untuk foo, sehingga setiap file ‘.o’ yang benar-benar ada menjadi prasyarat foo dan akan dikompilasi ulang jika diperlukan.

Namun, bagaimana jika Anda menghapus semua file ‘.o’? Ketika wildcard tidak cocok dengan file mana pun, ia dibiarkan apa adanya, sehingga foo akan bergantung pada file bernama aneh *.o. Karena file semacam itu kemungkinan besar tidak ada, make akan memberikan error yang menyatakan bahwa ia tidak tahu cara membuat *.o. Ini bukan yang Anda inginkan!

Sebenarnya, dimungkinkan untuk mendapatkan hasil yang diinginkan dengan ekspansi wildcard, tetapi diperlukan teknik yang lebih canggih, termasuk fungsi wildcard dan substitusi string. Lihat bagian Fungsi wildcard.

Sistem operasi Microsoft (MS-DOS dan MS-Windows) menggunakan garis miring terbalik untuk memisahkan direktori dalam nama path, seperti berikut:

  c:\foo\bar\baz.c

Ini setara dengan bentuk gaya Unix c:/foo/bar/baz.c (bagian c: adalah yang disebut huruf drive). Ketika make dijalankan pada sistem-sistem ini, ia mendukung garis miring terbalik sekaligus garis miring gaya Unix dalam nama path. Namun, dukungan ini tidak mencakup ekspansi wildcard, karena garis miring terbalik adalah karakter kutip (quote) di sana. Oleh karena itu, Anda harus selalu menggunakan garis miring gaya Unix dalam kasus-kasus ini.


4.4.3 Fungsi wildcard

Ekspansi wildcard terjadi secara otomatis dalam aturan. Namun, ekspansi wildcard biasanya tidak terjadi saat variabel diatur, atau di dalam argumen suatu fungsi. Jika Anda ingin melakukan ekspansi wildcard di tempat-tempat semacam itu, Anda perlu menggunakan fungsi wildcard, seperti berikut:

$(wildcard pattern…)

Di mana pun string ini digunakan dalam makefile, ia akan digantikan dengan daftar nama file yang benar-benar ada dan cocok dengan salah satu pola nama file yang diberikan, dipisahkan oleh spasi. Jika tidak ada file yang benar-benar ada yang cocok dengan suatu pola, pola tersebut akan dihilangkan dari keluaran fungsi wildcard. Perhatikan bahwa ini berbeda dari perilaku wildcard yang tidak cocok dalam aturan, tempat wildcard tersebut digunakan apa adanya, bukan diabaikan (lihat bagian Jebakan dalam menggunakan wildcard).

Sama seperti ekspansi wildcard dalam aturan, hasil dari fungsi wildcard juga diurutkan. Namun sekali lagi, setiap ekspresi individual diurutkan secara terpisah, sehingga ‘$(wildcard *.c *.h)’ akan diekspansi menjadi semua file yang cocok dengan ‘.c’ (terurut), diikuti oleh semua file yang cocok dengan ‘.h’ (terurut).

Salah satu kegunaan fungsi wildcard adalah untuk mendapatkan daftar semua file sumber C dalam suatu direktori, seperti berikut:

$(wildcard *.c)

Dengan mengganti akhiran ‘.c’ dalam hasil menjadi ‘.o’ seperti berikut, kita dapat mengubah daftar file sumber C menjadi daftar file objek:

$(patsubst %.c,%.o,$(wildcard *.c))

(Di sini kita menggunakan fungsi lain, yaitu patsubst. Lihat bagian Fungsi untuk substitusi dan analisis string.)

Dengan demikian, makefile yang mengompilasi dan me-link semua file sumber C dalam direktori dapat ditulis sebagai berikut:

objects := $(patsubst %.c,%.o,$(wildcard *.c))

foo : $(objects)
        cc -o foo $(objects)

(Ini memanfaatkan aturan implisit untuk mengompilasi program C, sehingga Anda tidak perlu menulis aturan eksplisit untuk mengompilasi setiap file. ‘:=’ adalah varian dari ‘=’; untuk penjelasannya, lihat bagian Dua jenis variabel.)


4.5 Mencari prasyarat dari direktori

Pada sistem berskala besar, sering kali diinginkan untuk menempatkan sumber di direktori yang terpisah dari biner. Fitur pencarian direktori (directory search) pada make memudahkan hal ini dengan secara otomatis mencari beberapa direktori untuk menemukan prasyarat. Ketika Anda memindahkan file antar-direktori, Anda tidak perlu mengubah masing-masing aturan; cukup ubah path pencariannya saja.


4.5.1 VPATH: path pencarian untuk semua prasyarat

Nilai variabel make bernama VPATH menentukan daftar direktori yang harus dicari oleh make. Biasanya, direktori-direktori tersebut diharapkan berisi file prasyarat yang tidak ada di direktori saat ini; namun, make menggunakan VPATH sebagai daftar pencarian untuk prasyarat maupun target suatu aturan.

Karena itu, jika file yang tercantum sebagai target atau prasyarat tidak ada di direktori saat ini, make akan mencari file dengan nama tersebut di direktori-direktori yang tercantum dalam VPATH. Jika file itu ditemukan di salah satu direktori tersebut, file itu bisa menjadi prasyarat (lihat penjelasan di bawah). Dengan begitu, aturan dapat menyebutkan nama file dalam daftar prasyarat seolah-olah semuanya ada di direktori saat ini. Lihat bagian Menulis resep dengan mempertimbangkan pencarian direktori.

Dalam variabel VPATH, nama direktori dipisahkan dengan titik dua atau spasi. Urutan direktori yang dituliskan adalah urutan pencarian yang diikuti oleh make. (Pada MS-DOS dan MS-Windows, titik koma digunakan sebagai pemisah nama direktori dalam VPATH, karena titik dua dapat digunakan dalam nama path itu sendiri setelah huruf drive.)

Misalnya,

VPATH = src:../headers

menentukan path yang berisi dua direktori, src dan ../headers, yang akan dicari oleh make dalam urutan tersebut.

Dengan nilai VPATH ini, aturan berikut,

foo.o : foo.c

ditafsirkan seolah-olah ditulis seperti ini:

foo.o : src/foo.c

Namun, ini hanya berlaku jika foo.c tidak ada di direktori saat ini dan ditemukan di direktori src.


4.5.2 Direktif vpath

Mirip dengan variabel VPATH, tetapi lebih selektif, adalah direktif vpath (perhatikan huruf kecilnya). Direktif ini memungkinkan Anda menentukan path pencarian untuk kelas nama file tertentu, yaitu yang cocok dengan pola tertentu. Dengan begitu, Anda dapat memberikan direktori pencarian tertentu untuk satu kelas nama file, dan direktori lain (atau tidak sama sekali) untuk nama file lainnya.

Ada tiga bentuk direktif vpath:

vpath pattern directories

Menentukan directories sebagai path pencarian untuk nama file yang cocok dengan pattern.

Path pencarian directories adalah daftar direktori yang harus dicari, dipisahkan dengan titik dua (titik koma pada MS-DOS dan MS-Windows) atau spasi, sama seperti path pencarian yang digunakan pada variabel VPATH.

vpath pattern

Menghapus path pencarian yang terkait dengan pattern.

vpath

Menghapus semua path pencarian yang sebelumnya ditentukan dengan direktif vpath.

Pola vpath adalah string yang mengandung karakter ‘%’. String ini harus cocok dengan nama file prasyarat yang sedang dicari; karakter ‘%’ cocok dengan sembarang urutan nol karakter atau lebih (sama seperti pada aturan pola; lihat bagian Mendefinisikan dan mendefinisikan ulang aturan pola). Misalnya, %.h cocok dengan file yang berakhiran .h. (Jika tidak ada ‘%’, pola harus cocok persis dengan prasyaratnya, yang jarang berguna.)

Karakter ‘%’ dalam pola direktif vpath dapat dikutip dengan garis miring terbalik (‘\’) di depannya. Garis miring terbalik yang jika tidak dikutip akan mengutip karakter ‘%’ dapat dikutip lagi dengan garis miring terbalik tambahan. Garis miring terbalik yang mengutip karakter ‘%’ atau garis miring terbalik lain akan dihapus dari pola sebelum pola tersebut dibandingkan dengan nama file. Garis miring terbalik yang tidak berisiko mengutip karakter ‘%’ akan dibiarkan apa adanya.

Jika prasyarat tidak ada di direktori saat ini, dan pattern pada suatu direktif vpath cocok dengan nama file prasyarat tersebut, direktori (directories) pada direktif itu akan dicari sama seperti (dan sebelum) direktori pada variabel VPATH.

Misalnya,

vpath %.h ../headers

memerintahkan make untuk mencari prasyarat apa pun yang namanya berakhiran .h di direktori ../headers jika file tersebut tidak ditemukan di direktori saat ini.

Jika beberapa pola vpath cocok dengan nama file prasyarat, make akan memproses setiap direktif vpath yang cocok satu per satu, mencari semua direktori yang disebutkan dalam masing-masing direktif. make memproses beberapa direktif vpath dalam urutan kemunculannya dalam makefile; beberapa direktif dengan pola yang sama bersifat independen satu sama lain.

Karena itu,

vpath %.c foo
vpath %   blish
vpath %.c bar

akan mencari file yang berakhiran ‘.c’ di foo, kemudian blish, kemudian bar, sedangkan

vpath %.c foo:bar
vpath %   blish

akan mencari file yang berakhiran ‘.c’ di foo, kemudian bar, kemudian blish.


4.5.3 Cara kerja pencarian direktori

Ketika prasyarat ditemukan melalui pencarian direktori (baik yang bersifat umum maupun selektif), path yang ditemukan belum tentu menjadi path yang benar-benar diberikan make kepada Anda dalam daftar prasyarat. Kadang-kadang, path yang ditemukan melalui pencarian direktori justru dibuang.

Algoritme yang digunakan make untuk memutuskan apakah akan menyimpan atau membuang path yang ditemukan melalui pencarian direktori adalah sebagai berikut:

  1. Jika file target tidak ada pada path yang ditentukan dalam makefile, pencarian direktori akan dilakukan.
  2. Jika pencarian direktori berhasil, path tersebut disimpan, dan file ini untuk sementara disimpan sebagai target.
  3. Semua prasyarat dari target ini diperiksa dengan cara yang sama.
  4. Setelah memproses prasyarat, target mungkin perlu dibangun ulang, atau mungkin tidak:
    1. Jika target tidak perlu dibangun ulang, path ke file yang ditemukan melalui pencarian direktori digunakan pada setiap daftar prasyarat yang mengandung target ini. Singkatnya, jika make tidak perlu membangun ulang target, path yang ditemukan melalui pencarian direktori akan digunakan.
    2. Jika target perlu dibangun ulang (usang), nama path yang ditemukan melalui pencarian direktori dibuang, dan target dibangun ulang menggunakan nama file yang ditentukan dalam makefile. Singkatnya, jika make harus membangun ulang, target dibangun ulang secara lokal, bukan di direktori yang ditemukan melalui pencarian direktori.

Algoritme ini mungkin terlihat rumit, tetapi pada praktiknya, ini justru sering kali menjadi hal yang memang Anda inginkan.

Versi make lain menggunakan algoritme yang lebih sederhana: jika file tidak ada dan ditemukan melalui pencarian direktori, path tersebut akan selalu digunakan, terlepas dari apakah target perlu dibangun atau tidak. Dengan demikian, jika target dibangun ulang, target itu akan dibuat pada path yang ditemukan selama pencarian direktori.

Jika, pada kenyataannya, ini adalah perilaku yang Anda inginkan untuk sebagian atau semua direktori Anda, Anda dapat menggunakan variabel GPATH untuk memberitahukannya kepada make.

GPATH memiliki sintaksis dan format yang sama dengan VPATH (yaitu, daftar nama path yang dipisahkan dengan spasi atau titik dua). Jika target usang ditemukan melalui pencarian direktori di direktori yang juga muncul dalam GPATH, nama path tersebut tidak akan dibuang. Target akan dibangun ulang menggunakan path yang telah diekspansi.


4.5.4 Menulis resep dengan mempertimbangkan pencarian direktori

Meskipun prasyarat ditemukan di direktori lain melalui pencarian direktori, hal ini tidak mengubah resep suatu aturan; resep tetap dijalankan sesuai apa yang tertulis. Oleh karena itu, Anda harus menulis resep dengan cermat agar mencari prasyarat di direktori tempat make menemukannya.

Ini dilakukan dengan menggunakan variabel otomatis seperti ‘$^’ (lihat bagian Variabel otomatis). Misalnya, nilai ‘$^’ adalah daftar semua prasyarat suatu aturan, termasuk nama direktori tempat prasyarat itu ditemukan, dan nilai ‘$@’ adalah target. Dengan demikian:

foo.o : foo.c
        cc -c $(CFLAGS) $^ -o $@

(Variabel CFLAGS ada agar Anda dapat menentukan flag untuk kompilasi C melalui aturan implisit; di sini kita menggunakannya demi konsistensi agar berlaku seragam untuk semua kompilasi C; lihat bagian Variabel yang digunakan oleh aturan implisit.)

Prasyarat sering kali juga mencakup file header, yang biasanya tidak ingin Anda sebutkan dalam resep. Variabel otomatis ‘$<’ hanya menyatakan prasyarat pertama:

VPATH = src:../headers
foo.o : foo.c defs.h hack.h
        cc -c $(CFLAGS) $< -o $@

4.5.5 Pencarian direktori dan aturan implisit

Pencarian direktori yang ditentukan dengan VPATH atau vpath juga dilakukan ketika aturan implisit dipertimbangkan (lihat bagian Menggunakan aturan implisit).

Misalnya, ketika file foo.o tidak memiliki aturan eksplisit, make akan mempertimbangkan aturan implisit, seperti aturan bawaan untuk mengompilasi foo.c jika file tersebut ada. Jika file ini tidak ditemukan di direktori saat ini, direktori yang sesuai akan dicari. Jika foo.c ada di salah satu direktori tersebut (atau disebutkan dalam makefile), aturan implisit untuk kompilasi C akan diterapkan.

Resep aturan implisit biasanya menggunakan variabel otomatis karena memang diperlukan; akibatnya, nama file yang ditemukan melalui pencarian direktori akan digunakan tanpa perlu usaha tambahan.


4.5.6 Pencarian direktori untuk pustaka tautan

Pencarian direktori diterapkan dengan cara khusus untuk pustaka (library) yang digunakan oleh linker. Fitur khusus ini berlaku ketika Anda menulis prasyarat dengan nama berbentuk ‘-lname’. (Anda bisa tahu ada sesuatu yang tidak biasa di sini karena prasyarat biasanya adalah nama file, sedangkan nama file pustaka umumnya berbentuk libname.a, bukan ‘-lname’.)

Ketika nama prasyarat berbentuk ‘-lname’, make menanganinya secara khusus dengan terlebih dahulu mencari file libname.so, dan jika tidak ditemukan, mencari file libname.a, di direktori saat ini, di direktori yang ditentukan oleh path pencarian vpath yang cocok dan path pencarian VPATH, lalu di direktori /lib, /usr/lib, dan prefix/lib (biasanya /usr/local/lib, tetapi versi MS-DOS/MS-Windows dari make berperilaku seolah-olah prefix didefinisikan sebagai root dari pohon instalasi DJGPP), secara berurutan.

Misalnya, jika sistem Anda memiliki pustaka /usr/lib/libcurses.a (dan tidak ada file /usr/lib/libcurses.so), maka

foo : foo.c -lcurses
        cc $^ -o $@

akan menyebabkan perintah ‘cc foo.c /usr/lib/libcurses.a -o foo’ dijalankan ketika foo lebih usang daripada foo.c atau /usr/lib/libcurses.a.

Kumpulan default file yang menjadi target pencarian adalah libname.so dan libname.a, tetapi ini dapat disesuaikan melalui variabel .LIBPATTERNS. Setiap kata dalam nilai variabel ini adalah string pola. Ketika prasyarat seperti ‘-lname’ muncul, make akan mengganti tanda persen pada setiap pola dalam daftar dengan name, dan melakukan pencarian direktori di atas menggunakan setiap nama file pustaka yang dihasilkan.

Nilai default untuk .LIBPATTERNS adalah ‘lib%.so lib%.a’, yang memberikan perilaku default seperti dijelaskan di atas.

Anda dapat menonaktifkan ekspansi pustaka tautan sepenuhnya dengan mengatur variabel ini ke nilai kosong.


4.6 Target semu (phony)

Target semu (phony target) adalah target yang sebenarnya bukan nama file. Sebaliknya, itu hanyalah sebuah nama untuk resep yang dijalankan ketika Anda memintanya secara eksplisit. Ada dua alasan untuk menggunakan target semu: untuk menghindari konflik dengan file yang memiliki nama sama, dan untuk meningkatkan kinerja.

Jika Anda menulis aturan yang resepnya tidak membuat file target, resep tersebut akan dijalankan setiap kali target itu dipertimbangkan untuk dibangun ulang. Berikut contohnya:

clean:
        rm *.o temp

Karena perintah rm tidak membuat file bernama clean, kemungkinan besar file semacam itu tidak akan pernah ada. Oleh karena itu, perintah rm akan dijalankan setiap kali Anda mengetik ‘make clean’.

Dalam contoh ini, jika ada file bernama clean yang dibuat di direktori ini, target clean tidak akan berfungsi dengan benar. Karena tidak memiliki prasyarat, clean akan selalu dianggap mutakhir, dan resepnya tidak akan dijalankan. Untuk menghindari masalah ini, Anda dapat mendeklarasikan target tersebut secara eksplisit sebagai target semu dengan menjadikannya prasyarat dari target khusus .PHONY (lihat bagian Nama target bawaan khusus), seperti berikut:

.PHONY: clean
clean:
        rm *.o temp

Dengan cara ini, ‘make clean’ akan menjalankan resep terlepas dari apakah ada file bernama clean atau tidak.

Prasyarat .PHONY selalu ditafsirkan sebagai nama target literal (bahkan jika mengandung karakter ‘%’) dan tidak pernah diperlakukan sebagai pola. Jika Anda ingin selalu membangun ulang suatu aturan pola, pertimbangkan untuk menggunakan “target paksa (force target)” (lihat bagian Aturan tanpa resep atau prasyarat).

Target semu juga berguna jika dikombinasikan dengan pemanggilan make secara rekursif (lihat bagian Penggunaan rekursif make). Dalam situasi ini, makefile sering kali berisi variabel yang mencantumkan sejumlah subdirektori yang harus dibangun. Cara paling sederhana untuk menanganinya adalah dengan mendefinisikan satu aturan yang resepnya melakukan loop pada subdirektori-subdirektori tersebut, seperti berikut:

SUBDIRS = foo bar baz

subdirs:
        for dir in $(SUBDIRS); do \
          $(MAKE) -C $$dir; \
        done

Namun, cara ini memiliki beberapa masalah. Pertama, error yang terdeteksi dalam sub-make diabaikan oleh aturan ini, sehingga jika satu subdirektori gagal, pembangunan subdirektori lainnya tetap dilanjutkan. Ini bisa diatasi dengan menambahkan perintah shell yang mencatat error tersebut lalu keluar, tetapi dengan begitu, proses akan berhenti bahkan ketika make dijalankan dengan opsi -k, yang tidak diinginkan. Kedua, dan mungkin lebih penting, karena hanya ada satu aturan, Anda tidak dapat sepenuhnya memanfaatkan kemampuan make untuk membangun target secara paralel (lihat bagian Eksekusi paralel). Target dalam masing-masing makefile akan dibangun secara paralel, tetapi hanya satu subdirektori yang dibangun pada satu waktu.

Dengan mendeklarasikan subdirektori sebagai target .PHONY (Anda harus melakukan ini karena subdirektori jelas selalu ada; jika tidak, subdirektori tidak akan dibangun), Anda dapat menghilangkan masalah-masalah ini:

SUBDIRS = foo bar baz

.PHONY: subdirs $(SUBDIRS)

subdirs: $(SUBDIRS)

$(SUBDIRS):
        $(MAKE) -C $@

foo: baz

Di sini kita juga mendeklarasikan bahwa subdirektori foo tidak dapat dibangun sampai subdirektori baz selesai; deklarasi hubungan semacam ini sangat penting ketika mencoba melakukan build paralel.

Pencarian aturan implisit (lihat bagian Menggunakan aturan implisit) dilewati untuk target .PHONY. Inilah sebabnya mendeklarasikan target sebagai .PHONY baik untuk kinerja, bahkan jika Anda tidak mengkhawatirkan keberadaan file yang sebenarnya.

Target semu tidak boleh menjadi prasyarat dari file target yang sungguhan. Jika hal itu terjadi, resep target semu akan dijalankan setiap kali make mempertimbangkan file tersebut. Selama target semu tidak pernah menjadi prasyarat dari target yang sungguhan, resep target semu hanya akan dijalankan ketika target semu itu sendiri menjadi sasaran yang ditentukan (lihat bagian Argumen untuk menentukan sasaran).

Anda tidak boleh mendeklarasikan makefile yang di-include sebagai target semu. Target semu tidak dimaksudkan untuk mewakili file sungguhan, dan karena target dianggap selalu usang, make akan selalu membangunnya ulang lalu menjalankan dirinya sendiri kembali (lihat bagian Cara makefile dibuat ulang). Untuk menghindari hal ini, make tidak akan menjalankan dirinya sendiri kembali jika file include yang ditandai semu dibangun ulang.

Target semu dapat memiliki prasyarat. Jika satu direktori berisi beberapa program, cara paling praktis adalah mendeskripsikan semua program tersebut dalam satu makefile, ./Makefile. Karena target yang dibangun ulang secara default adalah target pertama dalam makefile, umumnya target ini dijadikan target semu bernama ‘all’ dan diberi semua program individual sebagai prasyaratnya. Misalnya:

all : prog1 prog2 prog3
.PHONY : all

prog1 : prog1.o utils.o
        cc -o prog1 prog1.o utils.o

prog2 : prog2.o
        cc -o prog2 prog2.o

prog3 : prog3.o sort.o utils.o
        cc -o prog3 prog3.o sort.o utils.o

Dengan begini, Anda cukup mengetik ‘make’ untuk membangun ulang ketiga program tersebut, atau menentukan sebagai argumen program mana saja yang ingin dibangun ulang (seperti pada ‘make prog1 prog3’). Sifat semu tidak diwariskan: prasyarat dari target semu tidak dengan sendirinya menjadi semu, kecuali dinyatakan secara eksplisit.

Jika suatu target semu menjadi prasyarat dari target semu lainnya, target itu berperan seperti subrutin bagi target semu yang lebih besar. Misalnya, pada contoh berikut, ‘make cleanall’ akan menghapus file objek, file diff, dan file program:

.PHONY: cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff
        rm program

cleanobj :
        rm *.o

cleandiff :
        rm *.diff

4.7 Aturan tanpa resep atau prasyarat

Jika suatu aturan tidak memiliki prasyarat maupun resep, dan target dari aturan tersebut adalah file yang tidak ada, make akan menganggap target ini diperbarui setiap kali aturan tersebut dijalankan. Ini berarti bahwa setiap target yang bergantung pada target ini akan selalu menjalankan resepnya.

Contoh berikut akan memperjelas hal ini:

clean: FORCE
        rm $(objects)
FORCE:

Di sini target ‘FORCE’ memenuhi kondisi khusus di atas, sehingga target clean yang bergantung padanya dipaksa untuk menjalankan resepnya. Nama ‘FORCE’ itu sendiri tidak memiliki arti khusus, tetapi merupakan salah satu nama yang sering digunakan untuk tujuan ini.

Seperti yang Anda lihat, penggunaan ‘FORCE’ dengan cara ini menghasilkan hasil yang sama dengan penggunaan ‘.PHONY: clean’.

Menggunakan ‘.PHONY’ lebih eksplisit dan efisien. Namun, versi make lain tidak mendukung ‘.PHONY’. Karena itu, ‘FORCE’ muncul di banyak makefile. Lihat bagian Target semu (phony).


4.8 File target kosong untuk mencatat peristiwa

Target kosong (empty target) adalah salah satu jenis target semu, yang digunakan untuk menyimpan resep bagi proses yang sesekali diminta secara eksplisit. Berbeda dengan target semu, file target ini boleh benar-benar ada. Namun, isi filenya tidak menjadi masalah, dan biasanya kosong.

Tujuan file target kosong adalah untuk mencatat kapan terakhir kali resep suatu aturan dijalankan, melalui waktu modifikasi terakhirnya. Hal ini dimungkinkan karena salah satu perintah dalam resep adalah perintah touch yang memperbarui file target.

File target kosong harus memiliki prasyarat (jika tidak, tidak ada gunanya). Ketika Anda meminta agar target kosong dibangun ulang, resep akan dijalankan jika salah satu prasyaratnya lebih baru daripada target—dengan kata lain, jika salah satu prasyarat telah berubah sejak terakhir kali target itu dibangun ulang. Berikut contohnya:

print: foo.c bar.c
        lpr -p $?
        touch print

Dengan aturan ini, ‘make print’ akan menjalankan perintah lpr jika salah satu file sumber telah berubah sejak ‘make print’ terakhir kali dijalankan. Variabel otomatis ‘$?’ digunakan agar hanya file yang telah berubah yang dicetak (lihat bagian Variabel otomatis).


4.9 Nama target bawaan khusus

Nama-nama tertentu memiliki arti khusus jika muncul sebagai target.

.PHONY

Prasyarat dari target khusus .PHONY dianggap sebagai target semu. Ketika target semacam itu dipertimbangkan, make akan menjalankan resepnya tanpa syarat, terlepas dari apakah file dengan nama tersebut ada atau waktu modifikasi terakhirnya. Lihat bagian Target semu (phony).

.SUFFIXES

Prasyarat dari target khusus .SUFFIXES adalah daftar akhiran (suffix) yang digunakan saat memeriksa aturan sufiks. Lihat bagian Aturan sufiks gaya lama.

.DEFAULT

Resep yang ditentukan untuk .DEFAULT digunakan untuk semua target yang tidak memiliki aturan (baik eksplisit maupun implisit). Lihat bagian Mendefinisikan aturan default sebagai upaya terakhir. Jika resep .DEFAULT ditentukan, resep tersebut akan dijalankan sebagai gantinya untuk semua file yang dicantumkan sebagai prasyarat, tetapi tidak pernah dicantumkan sebagai target, dalam aturan mana pun. Lihat bagian Algoritma pencarian aturan implisit.

.PRECIOUS

Target yang dicantumkan sebagai prasyarat .PRECIOUS mendapat perlakuan khusus berikut: jika make dihentikan paksa atau diinterupsi selama eksekusi resep, target tersebut tidak akan dihapus. Lihat bagian Menginterupsi atau menghentikan paksa make. Selain itu, jika target adalah file antara (intermediate file) yang biasanya dihapus setelah tidak lagi diperlukan, penghapusan itu pun tidak akan dilakukan. Lihat bagian Rantai aturan implisit. Pada poin terakhir ini, fungsinya tumpang tindih dengan target khusus .SECONDARY.

Pola target aturan implisit (seperti ‘%.o’) juga dapat dicantumkan sebagai file prasyarat dari target khusus .PRECIOUS. Dengan begitu, file antara yang dibuat oleh aturan dengan target yang cocok dengan pola tersebut akan disimpan.

.INTERMEDIATE

Target yang menjadi prasyarat .INTERMEDIATE diperlakukan sebagai file antara. Lihat bagian Rantai aturan implisit. .INTERMEDIATE tanpa prasyarat tidak memiliki efek apa pun.

.NOTINTERMEDIATE

Prasyarat dari target khusus .NOTINTERMEDIATE tidak akan pernah dianggap sebagai file antara. Lihat bagian Rantai aturan implisit. .NOTINTERMEDIATE tanpa prasyarat membuat semua target diperlakukan sebagai bukan file antara.

Jika prasyaratnya berupa pola target, target yang dibangun menggunakan aturan pola tersebut tidak lagi dianggap sebagai file antara.

.SECONDARY

Target yang menjadi prasyarat .SECONDARY diperlakukan sebagai file antara, tetapi tidak akan pernah dihapus secara otomatis. Lihat bagian Rantai aturan implisit.

.SECONDARY dapat digunakan untuk menghindari pembangunan ulang yang berlebihan dalam beberapa situasi khusus. Misalnya:

hello.bin: hello.o bye.o
        $(CC) -o $@ $^

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

.SECONDARY: hello.o bye.o

Pertimbangkan situasi ketika hello.bin sudah mutakhir terhadap file sumbernya, tetapi file objek hello.o tidak ditemukan. Tanpa .SECONDARY, make akan membangun ulang hello.o meskipun file sumbernya tidak berubah, lalu membangun ulang hello.bin juga. Dengan mendeklarasikan hello.o sebagai .SECONDARY, make tidak perlu membangunnya ulang, sehingga hello.bin juga tidak perlu dibangun ulang. Tentu saja, jika salah satu file sumber diperbarui, semua file objek akan dibangun ulang agar pembuatan hello.bin berhasil.

.SECONDARY tanpa prasyarat membuat semua target diperlakukan sebagai secondary (yaitu, tidak ada target yang dihapus hanya karena dianggap sebagai file antara).

.SECONDEXPANSION

Jika .SECONDEXPANSION disebutkan sebagai target di mana pun dalam makefile, semua daftar prasyarat yang didefinisikan setelah kemunculannya akan diekspansi sekali lagi setelah semua makefile selesai dibaca. Lihat bagian Ekspansi sekunder.

.DELETE_ON_ERROR

Jika .DELETE_ON_ERROR disebutkan sebagai target di mana pun dalam makefile, make akan menghapus target suatu aturan jika target itu telah dimodifikasi dan resepnya berakhir dengan status keluar bukan nol, sama seperti ketika menerima sinyal. Lihat bagian Error dalam resep.

.IGNORE

Jika Anda menentukan prasyarat untuk .IGNORE, make akan mengabaikan error yang terjadi saat menjalankan resep untuk file-file tersebut. Resep untuk .IGNORE (jika ada) diabaikan.

Jika disebutkan sebagai target tanpa prasyarat, .IGNORE memerintahkan agar error saat menjalankan resep diabaikan untuk semua file. Penggunaan ‘.IGNORE’ seperti ini hanya didukung demi kompatibilitas historis. Karena memengaruhi semua resep dalam makefile, cara ini kurang praktis. Sebaiknya gunakan cara yang lebih selektif untuk mengabaikan error pada resep tertentu. Lihat bagian Error dalam resep.

.LOW_RESOLUTION_TIME

Jika Anda menentukan prasyarat untuk .LOW_RESOLUTION_TIME, make akan menganggap file-file tersebut dibuat oleh perintah yang menghasilkan stempel waktu beresolusi rendah. Resep pada target .LOW_RESOLUTION_TIME diabaikan.

Stempel waktu file beresolusi tinggi yang dimiliki banyak sistem file modern mengurangi kemungkinan make keliru menganggap suatu file sudah mutakhir. Namun sayangnya, karena tidak semua host menyediakan cara untuk mengatur stempel waktu file beresolusi tinggi, perintah yang secara eksplisit mengatur stempel waktu file, seperti ‘cp -p’, terpaksa membuang bagian sub-detiknya. File yang dibuat oleh perintah semacam itu sebaiknya dicantumkan sebagai prasyarat .LOW_RESOLUTION_TIME, agar make tidak keliru menganggap file tersebut usang. Misalnya:

.LOW_RESOLUTION_TIME: dst
dst: src
        cp -p src dst

Karena ‘cp -p’ membuang bagian sub-detik dari stempel waktu src, dst biasanya sedikit lebih usang daripada src meskipun sebenarnya sudah mutakhir. Dengan adanya baris .LOW_RESOLUTION_TIME, make akan menganggap dst mutakhir jika stempel waktunya berada pada awal detik yang sama dengan stempel waktu src.

Karena keterbatasan format arsip, stempel waktu anggota arsip selalu beresolusi rendah. Anda tidak perlu mencantumkan anggota arsip sebagai prasyarat .LOW_RESOLUTION_TIME karena make melakukannya secara otomatis.

.SILENT

Jika Anda menentukan prasyarat untuk .SILENT, make tidak akan menampilkan resep yang digunakan untuk membangun ulang file-file tersebut sebelum menjalankannya. Resep untuk .SILENT diabaikan.

Jika disebutkan sebagai target tanpa prasyarat, .SILENT memerintahkan agar tidak ada apa pun yang ditampilkan sebelum menjalankan resep. Anda juga dapat menggunakan cara yang lebih selektif untuk membungkam hanya baris perintah dari resep tertentu. Lihat bagian Menampilkan echo resep. Jika Anda ingin membungkam semua resep untuk satu kali eksekusi make tertentu, gunakan opsi ‘-s’ atau ‘--silent’ (lihat bagian Ringkasan opsi).

.EXPORT_ALL_VARIABLES

Cukup dengan disebutkan sebagai target, ini memerintahkan make untuk secara default mengekspor semua variabel ke proses anak. Ini adalah alternatif dari penggunaan export tanpa argumen. Lihat bagian Menyampaikan variabel ke sub-make.

.NOTPARALLEL

Jika .NOTPARALLEL disebutkan sebagai target tanpa prasyarat, semua target dalam eksekusi make ini akan dijalankan secara serial meskipun opsi ‘-j’ diberikan. Perintah make yang dipanggil secara rekursif tetap akan menjalankan resepnya secara paralel (kecuali makefile-nya juga memuat target ini).

Jika .NOTPARALLEL memiliki target sebagai prasyarat, semua prasyarat dari target-target tersebut akan dijalankan secara serial. Ini secara implisit menambahkan .WAIT di antara setiap prasyarat target yang dicantumkan. Lihat bagian Menonaktifkan eksekusi paralel.

.ONESHELL

Jika .ONESHELL disebutkan sebagai target, saat target dibangun, semua baris resep akan diteruskan ke satu kali pemanggilan shell, bukan dipanggil satu per satu secara terpisah. Lihat bagian Eksekusi resep.

.POSIX

Jika .POSIX disebutkan sebagai target, makefile akan diurai dan dijalankan dalam mode kepatuhan POSIX. Ini bukan berarti bahwa hanya makefile yang sesuai POSIX yang akan diterima; semua fitur canggih GNU make tetap dapat digunakan. Target ini membuat make berperilaku sesuai yang disyaratkan POSIX pada bagian-bagian yang perilaku default make-nya berbeda dari POSIX.

Secara khusus, jika target ini disebutkan, resep akan dipanggil seolah-olah flag -e diberikan kepada shell, yaitu resep akan langsung gagal begitu perintah pertama di dalamnya gagal.

Akhiran (suffix) aturan implisit yang telah didefinisikan sebelumnya juga dianggap sebagai target khusus jika muncul sebagai target, begitu pula gabungan dua akhiran seperti ‘.c.o’. Target-target ini adalah aturan sufiks, cara lama untuk mendefinisikan aturan implisit (meskipun masih banyak digunakan hingga sekarang). Pada prinsipnya, nama target apa pun dapat diberi makna khusus semacam ini dengan cara membaginya menjadi dua bagian dan menambahkan kedua potongan tersebut ke daftar akhiran. Namun pada praktiknya, akhiran biasanya diawali dengan ‘.’, sehingga nama-nama target khusus ini pun diawali dengan ‘.’. Lihat bagian Aturan sufiks gaya lama.


4.10 Banyak target dalam satu aturan

Jika suatu aturan eksplisit memiliki beberapa target, target-target itu akan diperlakukan dengan salah satu dari dua cara: sebagai target independen, atau sebagai target yang dikelompokkan (grouped targets). Perlakuan mana yang berlaku ditentukan oleh tanda pemisah yang muncul setelah daftar target.

Aturan dengan target independen

Aturan yang menggunakan tanda pemisah target standar ‘:’ mendefinisikan target independen. Ini setara dengan menuliskan prasyarat dan resep yang sama secara terpisah untuk setiap target. Biasanya, resep menggunakan variabel otomatis seperti ‘$@’ untuk menunjukkan target mana yang sedang dibangun.

Aturan dengan target independen berguna dalam dua situasi berikut:

Namun, sama seperti variabel ‘$@’ dapat mengubah resep, Anda mungkin ingin mengubah prasyarat sesuai targetnya. Hal ini tidak dapat dilakukan dengan aturan biasa yang memiliki banyak target, tetapi dapat dilakukan dengan aturan pola statis. Lihat bagian Aturan pola statis.

Aturan dengan target yang dikelompokkan

Jika, alih-alih target independen, Anda memiliki resep yang menghasilkan beberapa file dalam satu kali pemanggilan, hubungan ini dapat dinyatakan dengan mendeklarasikan bahwa aturan tersebut menggunakan target yang dikelompokkan (grouped targets). Aturan dengan target yang dikelompokkan menggunakan tanda pemisah ‘&:’ (di sini ‘&’ digunakan untuk menyiratkan “semua”).

Ketika make membangun salah satu dari target yang dikelompokkan, make memahami bahwa semua target lain dalam grup itu juga diperbarui sebagai hasil dari pemanggilan resep tersebut. Selain itu, meskipun hanya sebagian dari target yang dikelompokkan yang usang atau hilang, make tetap mengenali bahwa menjalankan resep akan memperbarui semua target. Terakhir, jika salah satu dari target yang dikelompokkan usang, semua target dalam kelompok itu dianggap usang.

Sebagai contoh, aturan berikut mendefinisikan target yang dikelompokkan:

foo bar biz &: baz boz
        echo $^ > foo
        echo $^ > bar
        echo $^ > biz

Selama eksekusi resep untuk target yang dikelompokkan, variabel otomatis ‘$@’ diatur ke nama target tertentu dalam grup yang memicu aturan tersebut. Berhati-hatilah jika resep pada aturan target yang dikelompokkan bergantung pada variabel ini.

Berbeda dengan target independen, aturan target yang dikelompokkan harus menyertakan resep. Namun, target yang menjadi anggota kelompok target yang dikelompokkan tetap boleh muncul dalam definisi aturan target independen yang tidak memiliki resep.

Setiap target hanya boleh terkait dengan satu resep. Jika target yang dikelompokkan muncul dalam aturan target independen, atau dalam aturan target yang dikelompokkan lain yang memiliki resep, sebuah peringatan akan muncul, dan resep yang belakangan akan menggantikan resep yang sebelumnya. Selain itu, target tersebut akan dikeluarkan dari kelompok sebelumnya dan hanya muncul dalam kelompok yang baru.

Jika Anda ingin suatu target muncul di beberapa kelompok, Anda harus menggunakan tanda pemisah target berkelompok titik dua ganda ‘&::’ saat mendeklarasikan semua kelompok yang memuat target tersebut. Target berkelompok titik dua ganda diperlakukan secara independen satu sama lain, dan resep dari setiap aturan berkelompok titik dua ganda dijalankan paling banyak satu kali jika setidaknya satu dari beberapa targetnya memerlukan pembaruan.


4.11 Banyak aturan untuk satu target

Satu file dapat menjadi target dari beberapa aturan. Semua prasyarat yang disebutkan di semua aturan tersebut digabungkan menjadi satu daftar prasyarat untuk target itu. Jika target lebih usang daripada salah satu prasyarat dari aturan mana pun, resep akan dijalankan.

Hanya satu resep yang dapat dijalankan untuk satu file. Jika dua aturan atau lebih memberikan resep untuk file yang sama, make akan menggunakan resep yang diberikan terakhir dan menampilkan pesan error. (Sebagai kasus khusus, jika nama file diawali dengan titik, pesan error tidak akan ditampilkan. Perilaku aneh ini hanya untuk kompatibilitas dengan implementasi make lain, jadi sebaiknya hindari penggunaannya.) Terkadang Anda ingin memicu beberapa resep untuk target yang sama, yang didefinisikan di bagian-bagian berbeda dalam makefile. Untuk ini, Anda dapat menggunakan aturan titik dua ganda (lihat bagian Aturan titik dua ganda).

Dengan menggunakan aturan tambahan yang hanya berisi prasyarat, Anda dapat memberikan beberapa prasyarat tambahan sekaligus kepada banyak file. Misalnya, makefile sering kali memiliki variabel seperti objects yang berisi daftar semua file keluaran kompiler dalam sistem yang dibangun. Untuk menyatakan secara praktis bahwa semuanya harus dikompilasi ulang jika config.h berubah, Anda dapat menulis:

objects = foo.o bar.o
foo.o : defs.h
bar.o : defs.h test.h
$(objects) : config.h

Baris ini dapat disisipkan atau dihapus tanpa mengubah aturan yang benar-benar menentukan cara membuat file objek. Bentuk ini berguna ketika Anda ingin menambahkan prasyarat tambahan secara berkala.

Trik lainnya, Anda juga dapat menentukan prasyarat tambahan melalui variabel yang diatur lewat argumen baris perintah ke make (lihat bagian Menimpa variabel). Misalnya,

extradeps=
$(objects) : $(extradeps)

maka perintah ‘make extradeps=foo.h’ akan menganggap foo.h sebagai prasyarat dari setiap file objek, tetapi perintah ‘make’ biasa tidak akan melakukannya.

Jika tidak ada satu pun aturan eksplisit untuk suatu target yang memiliki resep, make akan mencari aturan implisit yang dapat diterapkan (lihat bagian Menggunakan aturan implisit).


4.12 Aturan pola statis

Aturan pola statis (static pattern rules) adalah aturan yang menentukan beberapa target sekaligus membentuk nama prasyarat masing-masing target berdasarkan nama targetnya. Ini lebih serbaguna dibandingkan aturan biasa yang memiliki banyak target, karena target-target tersebut tidak harus memiliki prasyarat yang identik. Prasyaratnya cukup serupa, tidak harus identik.


4.12.1 Sintaksis aturan pola statis

Sintaksis aturan pola statis adalah sebagai berikut:

targets …: target-pattern: prereq-patternsrecipe

Daftar targets menentukan target-target yang diterapkan oleh aturan ini. Target dapat mengandung karakter wildcard, sama seperti target aturan biasa (lihat bagian Menggunakan karakter wildcard dalam nama file).

target-pattern dan prereq-patterns menyatakan cara menghitung prasyarat setiap target. Setiap target dicocokkan dengan target-pattern, dan bagian dari nama target—disebut stem—diekstraksi. Stem ini kemudian disubstitusikan ke setiap prereq-pattern untuk membentuk nama prasyarat (satu dari masing-masing prereq-pattern).

Setiap pola biasanya mengandung tepat satu karakter ‘%’. Ketika target-pattern cocok dengan suatu target, ‘%’ dapat cocok dengan bagian mana pun dari nama target, dan bagian ini disebut stem. Bagian pola yang lain harus cocok persis. Misalnya, target foo.o cocok dengan pola ‘%.o’, dengan ‘foo’ sebagai stem-nya. Target foo.c atau foo.out tidak cocok dengan pola ini.

Nama prasyarat untuk setiap target dibentuk dengan mengganti ‘%’ pada setiap pola prasyarat dengan stem. Misalnya, jika pola prasyaratnya adalah %.c, substitusi stem ‘foo’ akan menghasilkan nama prasyarat foo.c. Anda juga boleh menulis pola prasyarat tanpa ‘%’; dalam kasus itu, prasyarat tersebut akan sama untuk semua target.

Karakter ‘%’ pada aturan pola dapat dikutip dengan garis miring terbalik (‘\’) di depannya. Garis miring terbalik yang jika tidak dikutip akan mengutip karakter ‘%’ dapat dikutip lagi dengan garis miring terbalik tambahan. Garis miring terbalik yang mengutip karakter ‘%’ atau garis miring terbalik lain akan dihapus dari pola sebelum pola tersebut dibandingkan dengan nama file atau sebelum stem disubstitusikan. Garis miring terbalik yang tidak berisiko mengutip karakter ‘%’ akan dibiarkan apa adanya. Misalnya, pada pola the\%weird\\%pattern\\, karakter ‘%’ yang berlaku didahului oleh ‘the%weird\’ dan diikuti oleh ‘pattern\\’. Dua garis miring terbalik di akhir tidak memengaruhi karakter ‘%’ mana pun sehingga dibiarkan apa adanya.

Berikut adalah contoh yang mengompilasi masing-masing foo.o dan bar.o dari file .c yang sesuai:

objects = foo.o bar.o

all: $(objects)

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

Di sini, ‘$<’ adalah variabel otomatis yang menyimpan nama prasyarat, dan ‘$@’ adalah variabel otomatis yang menyimpan nama target. Lihat bagian Variabel otomatis.

Setiap target yang ditentukan harus cocok dengan pola target. Peringatan akan diberikan untuk setiap target yang tidak cocok. Jika Anda memiliki daftar file yang hanya sebagian cocok dengan pola, Anda dapat menggunakan fungsi filter untuk menghilangkan nama file yang tidak cocok (lihat bagian Fungsi untuk substitusi dan analisis string):

files = foo.elc bar.o lose.o

$(filter %.o,$(files)): %.o: %.c
        $(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
        emacs -f batch-byte-compile $<

Pada contoh ini, hasil dari ‘$(filter %.o,$(files))’ adalah bar.o lose.o, sehingga melalui aturan pola statis pertama, kedua file objek ini masing-masing diperbarui dari kompilasi file sumber C yang bersesuaian. Hasil dari ‘$(filter %.elc,$(files))’ adalah foo.elc, sehingga file itu dibuat dari foo.el.

Contoh lain menunjukkan cara menggunakan $* dalam aturan pola statis:

bigoutput littleoutput : %output : text.g
        generate text.g -$* > $@

Ketika perintah generate dijalankan, $* diekspansi menjadi stem-nya—yaitu ‘big’ atau ‘little’.


4.12.2 Aturan pola statis dan aturan implisit

Aturan pola statis memiliki banyak kesamaan dengan aturan implisit yang didefinisikan sebagai aturan pola (lihat bagian Mendefinisikan dan mendefinisikan ulang aturan pola). Keduanya memiliki pola target dan pola yang membentuk nama prasyarat. Perbedaannya terletak pada bagaimana make menentukan kapan aturan diterapkan.

Aturan implisit dapat diterapkan pada target mana pun yang cocok dengan polanya, tetapi hanya diterapkan jika target itu tidak memiliki resep yang ditentukan di tempat lain, dan prasyaratnya dapat ditemukan. Jika beberapa aturan implisit tampak dapat diterapkan, hanya satu yang akan diterapkan. Aturan mana yang dipilih bergantung pada urutan aturan.

Sebaliknya, aturan pola statis diterapkan pada daftar target persis yang ditentukan dalam aturan tersebut. Aturan ini tidak dapat diterapkan pada target lain, dan pasti diterapkan pada setiap target yang ditentukan. Jika dua aturan yang saling bertentangan dan keduanya memiliki resep berlaku sekaligus, itu adalah error.

Aturan pola statis dapat dikatakan lebih unggul daripada aturan implisit karena alasan berikut:


4.13 Aturan titik dua ganda

Aturan titik dua ganda (double-colon) adalah aturan eksplisit yang ditulis dengan ‘::’ alih-alih ‘:’ setelah nama target. Aturan ini diperlakukan berbeda dari aturan biasa ketika target yang sama muncul di beberapa aturan. Aturan pola dengan titik dua ganda memiliki arti yang sama sekali berbeda (lihat bagian Aturan pola yang cocok dengan apa saja).

Jika suatu target muncul di beberapa aturan, semua aturan tersebut harus berjenis sama—semuanya aturan biasa, atau semuanya titik dua ganda. Untuk titik dua ganda, setiap aturan bersifat independen satu sama lain. Resep dari setiap aturan titik dua ganda dijalankan jika target lebih usang daripada salah satu prasyarat aturan tersebut. Jika aturan itu tidak memiliki prasyarat, resepnya akan selalu dijalankan (meskipun targetnya sudah ada). Akibatnya, di antara aturan-aturan titik dua ganda, mungkin tidak ada satu pun yang dijalankan, mungkin hanya sebagian, atau mungkin semuanya dijalankan.

Aturan titik dua ganda dengan target yang sama sebenarnya sepenuhnya terpisah satu sama lain. Setiap aturan titik dua ganda diproses secara individual, persis seperti aturan yang memiliki target berbeda diproses.

Aturan titik dua ganda untuk suatu target dijalankan dalam urutan kemunculannya dalam makefile. Meskipun demikian, aturan titik dua ganda benar-benar bermanfaat hanya ketika urutan eksekusi resep tidak menjadi masalah.

Aturan titik dua ganda agak membingungkan dan jarang benar-benar berguna. Aturan ini menyediakan mekanisme untuk kasus ketika cara memperbarui target berbeda-beda tergantung file prasyarat mana yang menjadi penyebab pembaruan, tetapi kasus semacam itu jarang terjadi.

Setiap aturan titik dua ganda sebaiknya menentukan resep. Jika tidak, aturan implisit yang dapat diterapkan (jika ada) akan digunakan. Lihat bagian Menggunakan aturan implisit.


4.14 Membuat prasyarat secara otomatis

Dalam makefile suatu program, banyak aturan yang perlu ditulis sering kali hanya menyatakan bahwa “suatu file objek bergantung pada suatu file header”. Misalnya, jika main.c menggunakan defs.h melalui #include, Anda akan menulis:

main.o: defs.h

Aturan ini diperlukan untuk memberi tahu make bahwa main.o harus dibuat ulang setiap kali defs.h berubah. Untuk program besar, Anda akan menyadari bahwa Anda perlu menulis puluhan aturan semacam ini dalam makefile. Terlebih lagi, Anda harus selalu berhati-hati agar tidak lupa memperbarui makefile setiap kali menambah atau menghapus #include.

Untuk menghindari kerepotan ini, sebagian besar kompiler C modern dapat membaca baris #include dalam file sumber dan menulis aturan-aturan ini untuk Anda. Ini biasanya dilakukan dengan opsi ‘-M’ pada kompiler. Misalnya, perintah berikut:

cc -M main.c

menghasilkan keluaran berikut:

main.o : main.c defs.h

Dengan begitu, Anda tidak perlu lagi menulis semua aturan itu sendiri. Kompilerlah yang melakukannya untuk Anda.

Perlu diperhatikan bahwa karena aturan semacam ini menyebutkan main.o dalam makefile, aturan tersebut tidak akan pernah dianggap sebagai file antara oleh pencarian aturan implisit. Ini berarti make tidak akan pernah menghapus file tersebut setelah menggunakannya. Lihat bagian Rantai aturan implisit.

Pada program make versi lama, cara tradisional untuk memanfaatkan fitur kompiler ini adalah dengan membuat prasyarat sesuai permintaan menggunakan perintah seperti ‘make depend’. Perintah tersebut membuat file bernama depend yang berisi semua prasyarat yang dihasilkan secara otomatis, dan makefile cukup membacanya dengan include (lihat bagian Menyertakan makefile lain).

Pada GNU make, karena tersedia fitur untuk membangun ulang makefile, cara ini menjadi ketinggalan zaman—make selalu meregenerasi makefile yang usang, sehingga Anda tidak perlu lagi memberi tahu make secara eksplisit untuk meregenerasi prasyarat. Lihat bagian Cara makefile dibuat ulang.

Cara yang kami rekomendasikan untuk pembuatan prasyarat otomatis adalah dengan memiliki satu makefile untuk setiap file sumber. Untuk setiap file sumber name.c, sediakan makefile name.d yang mencantumkan file-file yang menjadi dependensi file objek name.o. Dengan cara ini, hanya file sumber yang berubah yang perlu dipindai ulang untuk membangun prasyarat baru.

Berikut adalah aturan pola untuk menghasilkan file prasyarat (yaitu makefile) bernama name.d dari file sumber C name.c:

%.d: %.c
        @set -e; rm -f $@; \
         $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
         sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
         rm -f $@.$$$$

Untuk definisi aturan pola, lihat bagian Mendefinisikan dan mendefinisikan ulang aturan pola. Flag ‘-e’ pada shell membuat shell langsung keluar jika perintah $(CC) (atau perintah lain mana pun) gagal (berakhir dengan status bukan nol).

Pada kompiler GNU C, Anda mungkin ingin menggunakan flag ‘-MM’ alih-alih ‘-M’. Flag ini menghilangkan prasyarat yang berkaitan dengan file header sistem. Untuk detail lebih lanjut, lihat Opsi untuk Mengontrol Preprocessor pada Using GNU CC.

Tujuan perintah sed adalah untuk mengubah (misalnya) baris berikut:

main.o : main.c defs.h

menjadi seperti berikut:

main.o main.d : main.c defs.h

Dengan cara ini, setiap file ‘.d’ menjadi bergantung pada semua file sumber dan header yang menjadi dependensi file ‘.o’ yang sesuai. Dengan begitu, make tahu bahwa prasyarat perlu diregenerasi kapan pun ada file sumber atau header yang berubah.

Setelah aturan untuk membuat ulang file ‘.d’ didefinisikan, langkah berikutnya adalah membaca semuanya menggunakan direktif include. Lihat bagian Menyertakan makefile lain. Misalnya:

sources = foo.c bar.c

include $(sources:.c=.d)

(Contoh ini menggunakan referensi substitusi untuk mengubah daftar file sumber ‘foo.c bar.c’ menjadi daftar makefile prasyarat ‘foo.d bar.d’. Untuk informasi lebih lanjut tentang referensi substitusi, lihat bagian Referensi substitusi.) Karena file ‘.d’ adalah makefile seperti file lainnya, make akan membangunnya ulang sesuai kebutuhan tanpa perlu tindakan apa pun dari Anda. Lihat bagian Cara makefile dibuat ulang.

Perlu diperhatikan bahwa karena file ‘.d’ berisi definisi target, direktif include harus selalu ditempatkan setelah sasaran default pertama dalam makefile. Jika tidak, ada risiko salah satu file objek justru menjadi sasaran default. Lihat bagian Bagaimana make memproses makefile.


[Catatan Penerjemah] Mengenai catatan kaki (2) dalam teks. Catatan pada naskah asli menyebutkan bahwa "beberapa versi lama GNU make tidak mengurutkan hasil ekspansi wildcard". Pada GNU make versi saat ini, hasilnya diurutkan untuk setiap ekspresi wildcard.


Sebelumnya | Berikutnya | Daftar Isi | Versi asli bahasa Inggris (gnu.org)