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

8 Fungsi untuk transformasi teks

Dengan fungsi (function), Anda dapat melakukan pemrosesan teks di dalam makefile. Dengan cara ini, Anda dapat menentukan file yang akan diproses, atau menyusun perintah yang digunakan dalam resep (recipe). Fungsi digunakan dalam bentuk pemanggilan fungsi. Jika Anda memberikan nama fungsi beserta teks yang akan diproses oleh fungsi tersebut (disebut argumen), fungsi akan melakukan pemrosesan dan hasilnya akan disisipkan pada posisi pemanggilan tersebut. Mekanisme ini sama persis dengan cara variabel diekspansi lalu digantikan.

8.1 Sintaksis pemanggilan fungsi

Pemanggilan fungsi memiliki bentuk yang sangat mirip dengan referensi variabel. Anda dapat menuliskannya di mana pun referensi variabel dapat dituliskan, dan aturan ekspansinya pun sama dengan referensi variabel. Pemanggilan fungsi berbentuk seperti berikut:

$(function arguments)

Atau, dapat juga ditulis dalam bentuk berikut:

${function arguments}

Di sini function adalah nama fungsi, yaitu salah satu dari sedikit nama fungsi yang sudah tersedia bawaan di make. Selain itu, dengan fungsi bawaan call, Anda pun pada dasarnya dapat membuat fungsi sendiri.

arguments adalah argumen dari fungsi tersebut. Argumen dipisahkan dari nama fungsi oleh satu spasi atau tab atau lebih, dan jika terdapat lebih dari satu argumen, argumen-argumen tersebut dipisahkan dengan koma. Spasi dan koma pemisah ini bukan bagian dari nilai argumen. Delimiter yang membungkus pemanggilan fungsi boleh berupa tanda kurung biasa maupun kurung kurawal, tetapi jika jenis kurung yang sama dituliskan di dalam argumen, kurung tersebut harus berpasangan. Sebaliknya, jenis delimiter yang lain boleh dituliskan tanpa berpasangan. Jika argumen itu sendiri berisi pemanggilan fungsi atau referensi variabel lain, sebaiknya gunakan jenis delimiter yang sama untuk semua referensi tersebut. Artinya, tulislah seperti ‘$(subst a,b,$(x))’, bukan ‘$(subst a,b,${x})’ yang mencampur keduanya. Ini karena cara tersebut lebih jelas, dan karena hanya satu jenis delimiter yang dicocokkan saat mencari akhir dari suatu referensi.

Setiap argumen diekspansi masing-masing sebelum fungsi dipanggil (kecuali ada catatan khusus di bawah ini). Ekspansi dilakukan sesuai urutan argumen tersebut dituliskan.

Karakter khusus

Ketika Anda ingin menggunakan karakter yang memiliki makna khusus bagi make sebagai argumen fungsi, terkadang Anda perlu menyembunyikannya. GNU make tidak mendukung escape karakter dengan backslash atau escape sequence lainnya. Namun, karena argumen dipisahkan sebelum diekspansi, Anda dapat menyembunyikan karakter khusus tersebut dengan menyimpannya ke dalam variabel.

Karakter yang mungkin perlu disembunyikan antara lain:

Misalnya, Anda dapat mendefinisikan variabel comma dan space yang nilainya hanya berupa satu karakter koma dan satu karakter spasi, lalu menyisipkan variabel tersebut di tempat karakter itu diperlukan. Caranya seperti berikut:

comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
# Nilai bar sekarang menjadi 'a,b,c'.

Di sini fungsi subst mengganti setiap spasi dalam nilai foo dengan koma, lalu menyisipkan hasilnya.

8.2 Fungsi untuk substitusi dan analisis string

Berikut adalah beberapa fungsi yang beroperasi pada string:

$(subst from,to,text)

Melakukan penggantian teks pada teks text: setiap kemunculan from diganti dengan to. Hasilnya disisipkan pada posisi pemanggilan fungsi. Misalnya,

$(subst ee,EE,feet on the street)

menghasilkan nilai ‘fEEt on the strEEt’.

$(patsubst pattern,replacement,text)

Mencari kata-kata yang dipisahkan whitespace dalam text yang cocok dengan pattern, lalu menggantinya dengan replacement. Di sini pattern dapat berisi ‘%’ yang berfungsi sebagai wildcard, yang cocok dengan sejumlah karakter apa pun dalam sebuah kata. Jika replacement juga berisi ‘%’, maka ‘%’ tersebut akan digantikan dengan teks yang cocok dengan ‘%’ pada pattern. Kata yang tidak cocok dengan pola akan tetap tidak berubah pada keluaran. Yang diperlakukan secara khusus seperti ini hanyalah ‘%’ pertama pada pattern dan replacement; ‘%’ kedua dan seterusnya diperlakukan apa adanya.

Karakter ‘%’ dalam pemanggilan fungsi patsubst dapat di-quote dengan meletakkan backslash (‘\’) di depannya. Backslash yang akan meng-quote karakter ‘%’ itu sendiri dapat di-quote dengan menambahkan backslash lagi. Backslash yang meng-quote karakter ‘%’ atau backslash lainnya akan dihapus dari pola sebelum pola tersebut dicocokkan dengan nama file atau sebelum stem disisipkan ke dalamnya. Sebaliknya, backslash yang tidak berpotensi meng-quote karakter ‘%’ akan dibiarkan apa adanya. Misalnya, pada pola the\%weird\\%pattern\\, terdapat ‘the%weird\’ sebelum karakter ‘%’ yang efektif, dan ‘pattern\\’ setelahnya. Dua backslash terakhir dibiarkan apa adanya karena tidak dapat memengaruhi karakter ‘%’ mana pun.

Whitespace di antara kata-kata dilipat menjadi satu karakter spasi; whitespace di awal dan akhir dibuang.

Misalnya,

$(patsubst %.c,%.o,x.c.c bar.c)

menghasilkan nilai ‘x.c.o bar.o’.

Referensi substitusi (lihat bagian Referensi substitusi) adalah cara yang lebih sederhana untuk mendapatkan efek yang sama dengan fungsi patsubst:

$(var:pattern=replacement)

setara dengan

$(patsubst pattern,replacement,$(var))

Bentuk singkat kedua ini menyederhanakan salah satu penggunaan patsubst yang paling umum — mengganti sufiks di akhir nama file.

$(var:suffix=replacement)

setara dengan

$(patsubst %suffix,%replacement,$(var))

Misalnya, Anda memiliki daftar file objek:

objects = foo.o bar.o baz.o

Untuk mendapatkan daftar file sumber yang sesuai, alih-alih menggunakan bentuk umum,

$(patsubst %.o,%.c,$(objects))

Anda cukup menulis:

$(objects:.o=.c)
$(strip string)

Menghapus whitespace di awal dan akhir string, dan mengganti setiap rangkaian satu atau lebih karakter whitespace di bagian dalamnya masing-masing menjadi satu spasi. Dengan demikian, ‘$(strip a b c )’ menghasilkan ‘a b c’.

Fungsi strip sangat berguna jika digunakan bersama percabangan kondisional. Ketika membandingkan sesuatu dengan string kosong ‘’ menggunakan ifeq atau ifneq, biasanya Anda ingin string yang hanya berisi whitespace dianggap cocok dengan string kosong (lihat Bagian kondisional dalam makefile).

Oleh karena itu, contoh berikut mungkin tidak memberikan hasil sesuai yang diinginkan:

.PHONY: all
ifneq   "$(needs_made)" ""
all: $(needs_made)
else
all:;@echo 'Nothing to make!'
endif

Mengganti referensi variabel ‘$(needs_made)’ dalam direktif ifneq dengan pemanggilan fungsi ‘$(strip $(needs_made))’ akan membuatnya lebih andal.

$(findstring find,in)

Mencari apakah find muncul di dalam in. Jika muncul, nilainya adalah find. Jika tidak muncul, nilainya kosong. Fungsi ini dapat digunakan dalam percabangan kondisional untuk memeriksa keberadaan substring tertentu dalam suatu string. Dengan demikian, kedua contoh berikut,

$(findstring a,a b c)
$(findstring a,b c)

masing-masing menghasilkan nilai ‘a’ dan ‘’ (string kosong). Untuk penerapan praktis findstring, lihat Kondisional yang menguji flag.

$(filter pattern…,text)

Mengembalikan semua kata yang dipisahkan whitespace dalam text yang cocok dengan salah satu kata pattern yang diberikan, dan membuang kata yang tidak cocok. Pola dituliskan menggunakan ‘%’, sama seperti pola yang digunakan pada fungsi patsubst di atas.

Fungsi filter dapat digunakan untuk memilah berbagai jenis string (misalnya nama file) dalam sebuah variabel. Misalnya:

sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
        cc $(filter %.c %.s,$(sources)) -o foo

Contoh ini menyatakan bahwa foo bergantung pada foo.c, bar.c, baz.s, dan ugh.h, tetapi hanya foo.c, bar.c, dan baz.s yang perlu diberikan dalam perintah ke kompiler.

$(filter-out pattern…,text)

Mengembalikan semua kata yang dipisahkan whitespace dalam text yang tidak cocok dengan salah satu kata pattern yang diberikan, dan membuang kata yang cocok dengan satu atau lebih pola. Ini adalah kebalikan tepat dari fungsi filter.

Misalnya, jika diberikan:

objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o

maka ekspresi berikut menghasilkan daftar yang hanya berisi file objek yang tidak termasuk dalam ‘mains’:

$(filter-out $(mains),$(objects))
$(sort list)

Mengurutkan kata-kata pada list secara leksikal, sambil menghapus kata yang duplikat. Keluarannya adalah daftar kata yang dipisahkan oleh satu spasi. Dengan demikian,

$(sort foo bar lose)

mengembalikan nilai ‘bar foo lose’.

Perlu diketahui, karena sort menghapus kata duplikat, Anda dapat memanfaatkannya untuk tujuan ini saja meskipun urutan pengurutannya tidak menjadi masalah.

$(word n,text)

Mengembalikan kata ke-n dari text. Nilai n yang valid dimulai dari 1. Jika n lebih besar daripada jumlah kata dalam text, nilainya kosong. Misalnya,

$(word 2, foo bar baz)

mengembalikan ‘bar’.

$(wordlist s,e,text)

Mengembalikan daftar kata dalam text mulai dari kata ke-s hingga kata ke-e (termasuk kedua batasnya). Nilai s yang valid dimulai dari 1; e dapat dimulai dari 0. Jika s lebih besar daripada jumlah kata dalam text, nilainya kosong. Jika e lebih besar daripada jumlah kata dalam text, kata-kata hingga akhir text akan dikembalikan. Jika s lebih besar daripada e, tidak ada yang dikembalikan. Misalnya,

$(wordlist 2, 3, foo bar baz)

mengembalikan ‘bar baz’.

$(words text)

Mengembalikan jumlah kata dalam text. Dengan demikian, kata terakhir dari text dapat diperoleh dengan $(word $(words text),text).

$(firstword names…)

Argumen names dianggap sebagai rangkaian nama yang dipisahkan whitespace. Nilainya adalah nama pertama dalam rangkaian tersebut. Nama-nama sisanya diabaikan.

Misalnya,

$(firstword foo bar)

menghasilkan ‘foo’. Meskipun $(firstword text) sama dengan $(word 1,text), fungsi firstword tetap dipertahankan demi kesederhanaannya.

$(lastword names…)

Argumen names dianggap sebagai rangkaian nama yang dipisahkan whitespace. Nilainya adalah nama terakhir dalam rangkaian tersebut.

Misalnya,

$(lastword foo bar)

menghasilkan ‘bar’. Meskipun $(lastword text) sama dengan $(word $(words text),text), fungsi lastword ditambahkan demi kesederhanaan dan performa yang lebih baik.

Berikut adalah contoh nyata penggunaan subst dan patsubst. Misalkan sebuah makefile menggunakan variabel VPATH untuk menentukan daftar direktori yang akan dicari oleh make untuk menemukan file prasyarat (prerequisite) (lihat VPATH: path pencarian untuk semua prasyarat). Contoh ini menunjukkan cara memberi tahu kompiler C agar turut mencari file header pada daftar direktori yang sama.

Nilai VPATH adalah daftar direktori yang dipisahkan dengan titik dua, misalnya ‘src:../headers’. Pertama, fungsi subst digunakan untuk mengubah titik dua menjadi spasi:

$(subst :, ,$(VPATH))

Ini menghasilkan ‘src ../headers’. Selanjutnya patsubst digunakan untuk mengubah setiap nama direktori menjadi flag ‘-I’. Hasilnya dapat ditambahkan ke nilai variabel CFLAGS, yang secara otomatis diteruskan ke kompiler C, seperti berikut:

override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))

Efeknya adalah menambahkan teks ‘-Isrc -I../headers’ ke nilai CFLAGS yang sudah ada sebelumnya. Direktif override digunakan di sini agar nilai baru tetap diberikan meskipun nilai CFLAGS sebelumnya sudah ditentukan melalui argumen command line (lihat Direktif override).

8.3 Fungsi untuk nama file

Beberapa fungsi ekspansi bawaan berkaitan khusus dengan penguraian nama file atau daftar nama file.

Setiap fungsi berikut melakukan transformasi tertentu pada nama file. Argumen fungsi dianggap sebagai rangkaian nama file yang dipisahkan whitespace (whitespace di awal dan akhir diabaikan). Setiap nama file dalam rangkaian tersebut ditransformasi dengan cara yang sama, dan hasilnya digabungkan dengan satu spasi di antaranya.

$(dir names…)

Mengambil bagian direktori dari setiap nama file dalam names. Bagian direktori dari nama file adalah semua karakter hingga (dan termasuk) garis miring terakhir di dalamnya. Jika nama file tidak mengandung garis miring, bagian direktorinya adalah string ‘./’. Misalnya,

$(dir src/foo.c hacks)

menghasilkan ‘src/ ./’.

$(notdir names…)

Mengambil seluruh bagian nama file dalam names kecuali bagian direktorinya. Jika nama file tidak mengandung garis miring, nama file tersebut tidak diubah. Jika mengandung garis miring, semua karakter hingga garis miring terakhir akan dihapus.

Nama file yang diakhiri dengan garis miring akan menjadi string kosong. Ini agak disayangkan, karena berarti jumlah nama file yang dipisahkan whitespace pada hasil tidak selalu sama dengan jumlah argumen yang diberikan. Namun, tidak ada alternatif lain yang lebih baik.

Misalnya,

$(notdir src/foo.c hacks)

menghasilkan ‘foo.c hacks’.

$(suffix names…)

Mengambil sufiks dari setiap nama file dalam names. Jika nama file mengandung tanda titik, sufiksnya adalah semua karakter mulai dari titik terakhir. Jika tidak, sufiksnya adalah string kosong. Karena itu, hasilnya sering kali kosong meskipun names tidak kosong, dan jika names berisi beberapa nama file, hasilnya bisa memuat lebih sedikit nama file.

Misalnya,

$(suffix src/foo.c src-1.0/bar.c hacks)

menghasilkan ‘.c .c’.

$(basename names…)

Mengambil seluruh bagian nama file dalam names kecuali sufiksnya. Jika nama file mengandung tanda titik, basename adalah semua karakter hingga (tidak termasuk) titik terakhir. Titik pada bagian direktori diabaikan. Jika tidak ada titik sama sekali, basename adalah nama file secara keseluruhan. Misalnya,

$(basename src/foo.c src-1.0/bar hacks)

menghasilkan ‘src/foo src-1.0/bar hacks’.

$(addsuffix suffix,names…)

Argumen names dianggap sebagai rangkaian nama yang dipisahkan whitespace; suffix diperlakukan sebagai satu kesatuan. Nilai suffix ditambahkan di akhir setiap nama, lalu nama-nama yang telah diperpanjang tersebut digabungkan dengan satu spasi di antaranya. Misalnya,

$(addsuffix .c,foo bar)

menghasilkan ‘foo.c bar.c’.

$(addprefix prefix,names…)

Argumen names dianggap sebagai rangkaian nama yang dipisahkan whitespace; prefix diperlakukan sebagai satu kesatuan. Nilai prefix ditambahkan di awal setiap nama, lalu nama-nama yang telah diperpanjang tersebut digabungkan dengan satu spasi di antaranya. Misalnya,

$(addprefix src/,foo bar)

menghasilkan ‘src/foo src/bar’.

$(join list1,list2)

Menggabungkan kedua argumen kata demi kata: dua kata pertama (masing-masing dari satu argumen) digabungkan menjadi kata pertama hasil, dua kata kedua menjadi kata kedua hasil, dan seterusnya. Dengan kata lain, kata ke-n dari hasil berasal dari kata ke-n masing-masing argumen. Jika salah satu argumen memiliki lebih banyak kata daripada yang lain, kata-kata yang berlebih akan disalin tanpa perubahan ke dalam hasil.

Misalnya, ‘$(join a b,.c .o)’ menghasilkan ‘a.c b.o’.

Whitespace di antara kata-kata dalam daftar tidak dipertahankan; whitespace tersebut diganti dengan satu spasi.

Fungsi ini dapat menggabungkan hasil fungsi dir dan notdir untuk memulihkan daftar file asli yang diberikan ke kedua fungsi tersebut.

$(wildcard pattern)

Argumen pattern adalah pola nama file, biasanya berisi karakter wildcard (seperti pada pola nama file shell). Hasil dari wildcard adalah daftar nama file yang ada dan cocok dengan pola tersebut, dipisahkan dengan spasi. Lihat Menggunakan karakter wildcard dalam nama file.

$(realpath names…)

Untuk setiap nama file dalam names, mengembalikan nama absolut yang sudah dinormalisasi (canonical). Nama yang sudah dinormalisasi tidak mengandung komponen . atau .., tidak mengandung pemisah path (/) berulang, dan tidak mengandung symlink. Jika gagal, string kosong dikembalikan. Untuk daftar kemungkinan penyebab kegagalan, lihat dokumentasi realpath(3).

$(abspath names…)

Untuk setiap nama file dalam names, mengembalikan nama absolut yang tidak mengandung komponen . atau .., maupun pemisah path (/) berulang. Perlu diperhatikan bahwa, berbeda dengan fungsi realpath, abspath tidak me-resolve symlink dan tidak mengharuskan nama file merujuk ke file atau direktori yang benar-benar ada. Gunakan fungsi wildcard jika ingin memeriksa keberadaannya.

8.4 Fungsi untuk percabangan kondisional

Terdapat empat fungsi yang menyediakan ekspansi kondisional. Ciri penting dari fungsi-fungsi ini adalah tidak semua argumennya diekspansi sejak awal. Hanya argumen yang perlu diekspansi yang akan diekspansi.

$(if condition,then-part[,else-part])

Fungsi if mendukung ekspansi kondisional dalam bentuk fungsional (berbeda dengan percabangan kondisional makefile GNU make seperti ifeq; lihat Sintaksis kondisional).

Argumen pertama, condition, mula-mula dihapus whitespace di awal dan akhirnya, lalu diekspansi. Jika hasil ekspansinya berupa string yang tidak kosong, kondisi dianggap benar (true). Jika hasil ekspansinya berupa string kosong, kondisi dianggap salah (false).

Jika kondisi benar, argumen kedua, then-part, akan dievaluasi dan digunakan sebagai hasil evaluasi keseluruhan fungsi if.

Jika kondisi salah, argumen ketiga, else-part, akan dievaluasi dan menjadi hasil dari fungsi if. Jika tidak ada argumen ketiga, fungsi if tidak menghasilkan apa-apa (string kosong).

Perlu diperhatikan bahwa hanya salah satu dari then-part atau else-part yang akan dievaluasi, tidak pernah keduanya sekaligus. Oleh karena itu, keduanya boleh mengandung efek samping (seperti pemanggilan fungsi shell, dan lain-lain).

$(or condition1[,condition2[,condition3…]])

Fungsi or menyediakan operasi OR yang “short-circuiting”. Setiap argumen diekspansi secara berurutan. Begitu suatu argumen diekspansi menjadi string yang tidak kosong, pemrosesan berhenti dan hasil ekspansi tersebut menjadi nilai kembalian. Jika setelah semua argumen diekspansi ternyata semuanya salah (kosong), hasil ekspansinya adalah string kosong.

$(and condition1[,condition2[,condition3…]])

Fungsi and menyediakan operasi AND yang “short-circuiting”. Setiap argumen diekspansi secara berurutan. Begitu suatu argumen diekspansi menjadi string kosong, pemrosesan berhenti dan hasil ekspansinya adalah string kosong. Jika semua argumen diekspansi menjadi string yang tidak kosong, hasil ekspansinya adalah hasil ekspansi argumen terakhir.

$(intcmp lhs,rhs[,lt-part[,eq-part[,gt-part]]])

Fungsi intcmp mendukung perbandingan numerik antar bilangan bulat. Fungsi ini tidak memiliki padanan pada percabangan kondisional makefile GNU make.

Sisi kiri lhs dan sisi kanan rhs diekspansi dan diuraikan sebagai bilangan bulat berbasis 10. Ekspansi argumen selanjutnya dikendalikan oleh bagaimana nilai numerik sisi kiri dibandingkan dengan nilai numerik sisi kanan.

Jika tidak ada argumen lebih lanjut, fungsi ini akan diekspansi menjadi kosong jika sisi kiri dan sisi kanan tidak sama, atau menjadi nilai numeriknya jika keduanya sama.

Jika tidak, jika sisi kiri secara ketat lebih kecil daripada sisi kanan, fungsi intcmp dievaluasi menjadi hasil ekspansi argumen ketiga, lt-part. Jika kedua sisi sama, fungsi intcmp dievaluasi menjadi hasil ekspansi argumen keempat, eq-part. Jika sisi kiri secara ketat lebih besar daripada sisi kanan, fungsi intcmp dievaluasi menjadi hasil ekspansi argumen kelima, gt-part.

Jika gt-part tidak diberikan, nilai defaultnya adalah eq-part. Jika eq-part tidak diberikan, nilai defaultnya adalah string kosong. Dengan demikian, ‘$(intcmp 9,7,hello)’ maupun ‘$(intcmp 9,7,hello,world,)’ sama-sama dievaluasi menjadi string kosong, sedangkan ‘$(intcmp 9,7,hello,world)’ (perhatikan tidak adanya koma setelah world) dievaluasi menjadi ‘world’.

8.5 Fungsi let

Fungsi let menyediakan cara untuk membatasi scope suatu variabel. Penetapan (assignment) variabel bernama di dalam ekspresi let hanya berlaku dalam teks yang disediakan oleh ekspresi let tersebut, dan penetapan ini tidak memengaruhi variabel bernama sama di scope luar mana pun.

Selain itu, fungsi let memungkinkan “unpacking” (pengambilan/pembongkaran) daftar, dengan menetapkan semua nilai yang belum ditetapkan ke variabel bernama terakhir.

Sintaksis fungsi let adalah sebagai berikut:

$(let var [var ...],[list],text)

Kedua argumen pertama, var dan list, diekspansi lebih dulu sebelum hal lain apa pun dilakukan; perlu dicatat bahwa argumen terakhir, text, tidak diekspansi pada saat yang bersamaan. Selanjutnya, setiap kata dari nilai list yang telah diekspansi diikat (bound) ke masing-masing nama variabel var secara berurutan, dengan nama variabel terakhir diikat ke sisa dari list yang telah diekspansi. Dengan kata lain, kata pertama dari list diikat ke variabel var pertama, kata kedua ke variabel var kedua, dan seterusnya.

Jika jumlah nama variabel var lebih banyak daripada jumlah kata dalam list, sisa nama variabel var akan diberi nilai string kosong. Jika jumlah var lebih sedikit daripada jumlah kata dalam list, maka var terakhir akan diberi semua kata sisa dalam list.

Setiap variabel dalam var ditetapkan sebagai variabel ekspansi sederhana selama eksekusi let. Lihat Dua jenis variabel.

Setelah semua variabel diikat dengan cara ini, text diekspansi untuk menghasilkan hasil dari fungsi let.

Misalnya, makro berikut membalik urutan kata dalam daftar yang diberikan sebagai argumen pertamanya:

reverse = $(let first rest,$1,\
            $(if $(rest),$(call reverse,$(rest)) )$(first))

all: ; @echo $(call reverse,d c b a)

Ini akan mencetak a b c d. Saat pertama kali dipanggil, let akan mengekspansi $1 menjadi d c b a. Kemudian ia menetapkan first ke d dan menetapkan rest ke c b a. Selanjutnya ia mengekspansi pernyataan if, di mana $(rest) tidak kosong sehingga kita memanggil ulang fungsi reverse secara rekursif dengan nilai rest yang kini menjadi c b a. Pemanggilan rekursif let menetapkan first ke c dan rest ke b a. Rekursi ini berlanjut hingga let dipanggil hanya dengan satu nilai, yaitu a. Di sini first adalah a dan rest kosong, sehingga kita tidak melakukan rekursi lagi, melainkan hanya mengekspansi $(first) menjadi a lalu kembali, yang kemudian menambahkan b, dan seterusnya.

Setelah pemanggilan reverse selesai, variabel first dan rest tidak lagi ditetapkan. Jika sebelumnya sudah ada variabel dengan nama tersebut, variabel itu tidak akan terpengaruh oleh ekspansi makro reverse.

8.6 Fungsi foreach

Fungsi foreach mirip dengan fungsi let, tetapi sangat berbeda dari fungsi lainnya. Fungsi ini menggunakan sepotong teks secara berulang, dengan substitusi yang berbeda setiap kali digunakan. Fungsi foreach mirip dengan perintah for pada shell sh, atau perintah foreach pada C shell csh.

Sintaksis fungsi foreach adalah sebagai berikut:

$(foreach var,list,text)

Kedua argumen pertama, var dan list, diekspansi lebih dulu sebelum hal lain apa pun dilakukan; perlu dicatat bahwa argumen terakhir, text, tidak diekspansi pada saat yang bersamaan. Kemudian, untuk setiap kata dari nilai list yang telah diekspansi, variabel bernama sesuai nilai var yang telah diekspansi diberi nilai kata tersebut, lalu text diekspansi. Kemungkinan besar text berisi referensi ke variabel itu, sehingga hasil ekspansinya akan berbeda setiap kali.

Hasilnya adalah text diekspansi sebanyak jumlah kata yang dipisahkan whitespace dalam list. Beberapa hasil ekspansi text tersebut digabungkan, dengan spasi di antaranya, untuk menghasilkan hasil dari foreach.

Contoh sederhana berikut menetapkan variabel ‘files’ dengan daftar semua file di setiap direktori dalam daftar ‘dirs’:

dirs := a b c d
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))

Di sini text adalah ‘$(wildcard $(dir)/*)’. Pengulangan pertama menemukan nilai ‘a’ untuk dir, sehingga menghasilkan hasil yang sama dengan ‘$(wildcard a/*)’; pengulangan kedua menghasilkan hasil dari ‘$(wildcard b/*)’; dan pengulangan ketiga menghasilkan hasil dari ‘$(wildcard c/*)’.

Contoh ini memberikan hasil yang sama (kecuali dalam hal penetapan ‘dirs’) dengan contoh berikut:

files := $(wildcard a/* b/* c/* d/*)

Ketika text cukup rumit, Anda dapat meningkatkan keterbacaan dengan memberinya nama, melalui variabel tambahan:

find_files = $(wildcard $(dir)/*)
dirs := a b c d
files := $(foreach dir,$(dirs),$(find_files))

Di sini kita menggunakan variabel find_files dengan cara ini. Kita menggunakan tanda ‘=’ biasa untuk mendefinisikan variabel ekspansi rekursif, agar nilainya berisi pemanggilan fungsi sesungguhnya yang akan diekspansi ulang di bawah kendali foreach; variabel ekspansi sederhana tidak akan berfungsi, karena wildcard hanya akan dipanggil satu kali saat find_files didefinisikan.

Seperti fungsi let, fungsi foreach tidak memberi efek permanen pada variabel var; nilai dan jenisnya setelah pemanggilan fungsi foreach sama seperti sebelumnya. Nilai-nilai lain yang diambil dari list hanya berlaku sementara, selama eksekusi foreach. Variabel var adalah variabel ekspansi sederhana selama eksekusi foreach. Jika var belum didefinisikan sebelum pemanggilan fungsi foreach, variabel tersebut tetap tidak terdefinisi setelah pemanggilan. Lihat Dua jenis variabel.

Anda perlu berhati-hati saat menggunakan ekspresi variabel kompleks yang menghasilkan nama variabel, karena banyak string aneh yang merupakan nama variabel yang valid, padahal mungkin bukan itu yang Anda maksudkan. Misalnya,

files := $(foreach Esta-escrito-en-espanol!,b c ch,$(find_files))

mungkin berguna jika nilai find_files mereferensikan variabel bernama ‘Esta-escrito-en-espanol!’ (es un nombre bastante largo, no? — nama yang cukup panjang, bukan?), tetapi kemungkinan besar ini justru kesalahan.

8.7 Fungsi file

Dengan fungsi file, Anda dapat menulis ke file atau membaca dari file di dalam makefile. Dua mode penulisan didukung: “overwrite” (timpa), yaitu teks ditulis mulai dari awal file dan semua isi yang ada sebelumnya hilang; dan “append” (tambahkan), yaitu teks ditulis di akhir file, dengan isi yang ada tetap dipertahankan. Pada kedua mode tersebut, file akan dibuat jika belum ada. Jika file tidak dapat dibuka untuk ditulis, atau jika operasi penulisan gagal, ini merupakan error fatal. Fungsi file diekspansi menjadi string kosong saat menulis ke file.

Saat membaca dari file, fungsi file diekspansi menjadi isi file apa adanya (kecuali baris baru terakhir, jika ada, akan dihapus). Jika mencoba membaca dari file yang tidak ada, hasilnya diekspansi menjadi string kosong.

Sintaksis fungsi file adalah sebagai berikut:

$(file op filename[,text])

Ketika fungsi file dievaluasi, semua argumennya diekspansi terlebih dahulu, kemudian file yang ditunjuk oleh filename dibuka dengan mode yang dijelaskan oleh op.

Operator op dapat berupa > untuk menandakan file akan ditimpa dengan isi baru, >> untuk menandakan isi file saat ini akan ditambahkan, atau < untuk menandakan isi file akan dibaca. filename menentukan file yang akan ditulis atau dibaca. Boleh terdapat whitespace di antara operator dan nama file.

Saat membaca file, memberikan nilai text adalah suatu error.

Saat menulis file, text akan ditulis ke file. Jika text belum diakhiri dengan baris baru, satu baris baru terakhir akan ditulis (bahkan jika text adalah string kosong). Jika argumen text sama sekali tidak diberikan, tidak ada yang akan ditulis.

Misalnya, fungsi file dapat berguna jika sistem build Anda memiliki batas panjang command line, dan perintah yang dijalankan dalam resep dapat menerima argumen dari sebuah file. Banyak perintah menggunakan konvensi bahwa argumen yang diawali dengan @ menunjuk ke file yang berisi argumen lebih lanjut. Dalam kasus ini, Anda dapat menulis resep sebagai berikut:

program: $(OBJECTS)
        $(file >$@.in,$^)
        $(CMD) $(CMDFLAGS) @$@.in
        @rm $@.in

Jika perintah tersebut mengharuskan setiap argumen berada pada baris terpisah dalam file input, Anda dapat menulis resep seperti berikut:

program: $(OBJECTS)
        $(file >$@.in) $(foreach O,$^,$(file >>$@.in,$O))
        $(CMD) $(CMDFLAGS) @$@.in
        @rm $@.in

8.8 Fungsi call

Fungsi call unik karena dapat digunakan untuk membuat fungsi baru berparameter. Anda dapat menuliskan ekspresi kompleks sebagai nilai suatu variabel, lalu menggunakan call untuk mengekspansinya dengan nilai yang berbeda-beda.

Sintaksis fungsi call adalah sebagai berikut:

$(call variable,param,param,…)

Ketika make mengekspansi fungsi ini, setiap param ditetapkan ke variabel sementara $(1), $(2), dan seterusnya. Variabel $(0) akan berisi variable. Tidak ada batas maksimum jumlah argumen parameter. Tidak ada batas minimum juga, tetapi tidak masuk akal menggunakan call tanpa parameter.

Kemudian variable diekspansi sebagai variabel make dalam konteks penetapan sementara tersebut. Dengan demikian, setiap referensi ke $(1) dalam nilai variable akan diselesaikan menjadi param pertama pada pemanggilan call tersebut.

Perlu diperhatikan bahwa variable adalah nama variabel, bukan referensi ke variabel tersebut. Oleh karena itu, biasanya Anda tidak menggunakan ‘$’ atau tanda kurung saat menuliskannya (namun, Anda dapat menggunakan referensi variabel di dalam nama tersebut jika ingin nama itu tidak konstan).

Jika variable adalah nama fungsi bawaan, fungsi bawaan tersebut akan selalu dipanggil (meskipun ada variabel make dengan nama yang sama).

Fungsi call mengekspansi argumen param sebelum menugaskannya ke variabel sementara. Ini berarti nilai variable yang berisi referensi ke fungsi bawaan dengan aturan ekspansi khusus, seperti foreach atau if, mungkin tidak akan bekerja sebagaimana yang Anda harapkan.

Beberapa contoh mungkin akan memperjelas hal ini.

Makro berikut ini hanya membalik urutan argumennya:

reverse = $(2) $(1)

foo = $(call reverse,a,b)

Di sini foo akan berisi ‘b a’.

Contoh berikut sedikit lebih menarik: contoh ini mendefinisikan makro untuk mencari kemunculan pertama sebuah program di PATH:

pathsearch = $(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(PATH)))))

LS := $(call pathsearch,ls)

Sekarang variabel LS akan berisi /bin/ls atau sejenisnya.

Fungsi call dapat disarangkan (nested). Setiap pemanggilan rekursif memiliki nilai lokalnya sendiri untuk $(1) dan seterusnya, yang menutupi nilai dari call tingkat lebih tinggi. Misalnya, berikut adalah implementasi dari fungsi map:

map = $(foreach a,$(2),$(call $(1),$(a)))

Sekarang Anda dapat mem-map fungsi yang biasanya hanya menerima satu argumen (misalnya origin) ke beberapa nilai sekaligus dalam satu langkah:

o = $(call map,origin,o map MAKE)

Hasilnya, o akan berisi sesuatu seperti ‘file file default’.

Terakhir, satu peringatan. Berhati-hatilah saat menambahkan whitespace pada argumen untuk call. Seperti fungsi lainnya, whitespace apa pun yang terdapat pada argumen kedua dan seterusnya akan dipertahankan; hal ini dapat menyebabkan hasil yang aneh. Umumnya paling aman untuk menghapus semua whitespace yang tidak perlu saat memberikan parameter ke call.

8.9 Fungsi value

Fungsi value menyediakan cara untuk menggunakan nilai suatu variabel tanpa mengekspansinya. Namun, perlu diperhatikan bahwa ini tidak membatalkan ekspansi yang sudah terjadi. Misalnya, jika Anda membuat variabel ekspansi sederhana, nilainya sudah diekspansi pada saat didefinisikan, sehingga dalam kasus tersebut fungsi value akan mengembalikan hasil yang sama dengan menggunakan variabel tersebut secara langsung.

Sintaksis fungsi value adalah sebagai berikut:

$(value variable)

Perlu diperhatikan bahwa variable adalah nama variabel, bukan referensi ke variabel tersebut. Oleh karena itu, biasanya Anda tidak menggunakan ‘$’ atau tanda kurung saat menuliskannya (namun, Anda dapat menggunakan referensi variabel di dalam nama tersebut jika ingin nama itu tidak konstan).

Hasil dari fungsi ini adalah string yang berisi nilai variable apa adanya, tanpa ekspansi apa pun. Misalnya, dalam makefile berikut:

FOO = $PATH

all:
        @echo $(FOO)
        @echo $(value FOO)

Baris keluaran pertama adalah ATH. Ini karena ‘$P’ akan diekspansi sebagai variabel make. Sementara itu, baris keluaran kedua adalah nilai variabel lingkungan $PATH Anda saat ini. Ini karena fungsi value menghindari ekspansi tersebut.

Fungsi value paling sering digunakan bersama fungsi eval (lihat Fungsi eval).

8.10 Fungsi eval

Fungsi eval sangat istimewa: dengan fungsi ini Anda dapat mendefinisikan elemen sintaksis makefile baru yang tidak konstan — yaitu elemen yang merupakan hasil evaluasi variabel atau fungsi lain. Argumen fungsi eval pertama-tama diekspansi, kemudian hasil ekspansi tersebut diuraikan sebagai sintaksis makefile. Hasil ekspansi tersebut dapat mendefinisikan variabel make baru, target, aturan implisit atau eksplisit, dan sebagainya.

Hasil dari fungsi eval selalu berupa string kosong. Oleh karena itu, fungsi ini dapat diletakkan hampir di mana saja dalam makefile tanpa menyebabkan error sintaksis.

Yang penting untuk dipahami di sini adalah bahwa argumen eval diekspansi dua kali: pertama oleh fungsi eval itu sendiri, kemudian hasil ekspansi tersebut diekspansi lagi saat diuraikan sebagai sintaksis makefile. Artinya, saat menggunakan eval, Anda mungkin perlu menambahkan tingkat escape tambahan untuk karakter ‘$’. Fungsi value (lihat Fungsi value) terkadang berguna dalam situasi seperti ini, untuk menghindari ekspansi yang tidak diinginkan.

Berikut adalah contoh bagaimana eval dapat digunakan; contoh ini menggabungkan sejumlah konsep dan fungsi lainnya. Meskipun penggunaan eval dalam contoh ini mungkin terlihat berlebihan dibandingkan hanya menuliskan aturan-aturannya secara langsung, pertimbangkan dua hal berikut. Pertama, definisi template (dalam PROGRAM_template) mungkin perlu jauh lebih kompleks daripada yang ditunjukkan di sini. Kedua, Anda dapat menempatkan bagian yang kompleks dan “generik” dari contoh ini ke dalam makefile lain, lalu menyertakannya di semua makefile masing-masing. Dengan begitu, setiap makefile individual akan menjadi sangat ringkas.

PROGRAMS    = server client

server_OBJS = server.o server_priv.o server_access.o
server_LIBS = priv protocol

client_OBJS = client.o client_api.o client_mem.o
client_LIBS = protocol

# Semua yang setelah ini adalah bagian generik

.PHONY: all
all: $(PROGRAMS)

define PROGRAM_template =
 $(1): $$($(1)_OBJS) $$($(1)_LIBS:%=-l%)
 ALL_OBJS   += $$($(1)_OBJS)
endef

$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog))))

$(PROGRAMS):
        $(LINK.o) $^ $(LDLIBS) -o $@

clean:
        rm -f $(ALL_OBJS) $(PROGRAMS)

8.11 Fungsi origin

Fungsi origin, berbeda dengan sebagian besar fungsi lainnya, tidak mengoperasikan nilai variabel. Sebaliknya, fungsi ini memberi tahu Anda sesuatu tentang suatu variabel. Secara spesifik, fungsi ini memberi tahu dari mana variabel tersebut berasal.

Sintaksis fungsi origin adalah sebagai berikut:

$(origin variable)

Perlu diperhatikan bahwa variable adalah nama variabel yang ingin diketahui asalnya, bukan referensi ke variabel tersebut. Oleh karena itu, biasanya Anda tidak menggunakan ‘$’ atau tanda kurung saat menuliskannya (namun, Anda dapat menggunakan referensi variabel di dalam nama tersebut jika ingin nama itu tidak konstan).

Hasil dari fungsi ini adalah string yang memberi tahu bagaimana variabel variable didefinisikan:

undefined

Jika variable belum pernah didefinisikan.

default

Jika variable memiliki definisi default, seperti biasanya terjadi pada CC dan sejenisnya. Lihat Variabel yang digunakan oleh aturan implisit. Perlu diketahui, jika Anda mendefinisikan ulang variabel default, fungsi origin akan mengembalikan asal dari definisi yang lebih baru.

environment

Jika variable diwariskan dari lingkungan yang diberikan ke make.

environment override

Jika variable diwariskan dari lingkungan yang diberikan ke make, dan menimpa (override) pengaturan untuk variable di dalam makefile sebagai akibat dari opsi ‘-e’ (lihat Ringkasan opsi).

file

Jika variable didefinisikan di dalam makefile.

command line

Jika variable didefinisikan pada command line.

override

Jika variable didefinisikan dengan direktif override di dalam makefile (lihat Direktif override).

automatic

Jika variable adalah variabel otomatis yang didefinisikan untuk eksekusi resep dari setiap aturan (lihat Variabel otomatis).

Informasi ini (selain sekadar untuk rasa ingin tahu Anda) terutama berguna untuk menentukan apakah Anda dapat memercayai nilai suatu variabel. Misalnya, andaikan Anda memiliki makefile foo yang menyertakan makefile lain, bar. Anda ingin variabel bletch didefinisikan di dalam bar ketika Anda menjalankan perintah ‘make -f bar’, meskipun lingkungan sudah berisi definisi bletch. Namun, jika foo sudah mendefinisikan bletch sebelum menyertakan bar, Anda tidak ingin definisi tersebut ditimpa. Hal ini dapat dilakukan dengan menggunakan direktif override di dalam foo, sehingga definisi tersebut lebih diutamakan daripada definisi berikutnya di bar. Namun sayangnya, direktif override juga akan menimpa definisi dari command line. Oleh karena itu, sebaiknya bar menyertakan isi berikut:

ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf, gag, etc.
endif
endif

Jika bletch telah didefinisikan dari lingkungan, ini akan mendefinisikannya ulang.

Jika Anda ingin menimpa definisi sebelumnya dari bletch apabila definisi itu berasal dari lingkungan, bahkan di bawah ‘-e’, Anda dapat menuliskannya seperti berikut:

ifneq "$(findstring environment,$(origin bletch))" ""
bletch = barf, gag, etc.
endif

Di sini pendefinisian ulang dilakukan jika ‘$(origin bletch)’ mengembalikan ‘environment’ atau ‘environment override’. Lihat Fungsi untuk substitusi dan analisis string.

8.12 Fungsi flavor

Fungsi flavor, seperti fungsi origin, tidak mengoperasikan nilai variabel, melainkan memberi tahu Anda sesuatu tentang suatu variabel. Secara spesifik, fungsi ini memberi tahu jenis (flavor) dari suatu variabel (lihat Dua jenis variabel).

Sintaksis fungsi flavor adalah sebagai berikut:

$(flavor variable)

Perlu diperhatikan bahwa variable adalah nama variabel yang ingin diketahui jenisnya, bukan referensi ke variabel tersebut. Oleh karena itu, biasanya Anda tidak menggunakan ‘$’ atau tanda kurung saat menuliskannya (namun, Anda dapat menggunakan referensi variabel di dalam nama tersebut jika ingin nama itu tidak konstan).

Hasil dari fungsi ini adalah string yang menunjukkan jenis (flavor) dari variabel variable:

undefined

Jika variable belum pernah didefinisikan.

recursive

Jika variable adalah variabel ekspansi rekursif.

simple

Jika variable adalah variabel ekspansi sederhana.

8.13 Fungsi untuk mengendalikan make

Fungsi-fungsi ini mengendalikan cara kerja make. Umumnya, fungsi-fungsi ini digunakan untuk memberikan informasi kepada pengguna makefile, atau untuk menghentikan make ketika terdeteksi semacam error lingkungan.

$(error text…)

Menghasilkan error fatal dengan pesan berupa text. Perlu diperhatikan bahwa error selalu dihasilkan saat fungsi ini dievaluasi. Jadi, jika Anda meletakkannya di dalam resep atau di sisi kanan penetapan variabel ekspansi rekursif, fungsi ini baru akan dievaluasi belakangan. text akan diekspansi sebelum error dihasilkan.

Misalnya,

ifdef ERROR1
$(error error is $(ERROR1))
endif

akan menghasilkan error fatal saat makefile dibaca, jika variabel make ERROR1 didefinisikan. Atau,

ERR = $(error found an error!)

.PHONY: err
err: ; $(ERR)

akan menghasilkan error fatal saat make berjalan, jika target err dipanggil.

$(warning text…)

Fungsi ini bekerja mirip dengan fungsi error di atas, hanya saja make tidak keluar (exit). Sebaliknya, text diekspansi dan pesan hasilnya ditampilkan, tetapi pemrosesan makefile tetap berlanjut.

Hasil ekspansi fungsi ini adalah string kosong.

$(info text…)

Fungsi ini tidak melakukan apa pun selain menampilkan argumennya (yang sudah diekspansi) ke standard output. Nama makefile atau nomor baris tidak disertakan. Hasil ekspansi fungsi ini adalah string kosong.

8.14 Fungsi shell

Fungsi shell berbeda dari fungsi lainnya, kecuali fungsi wildcard (lihat Fungsi wildcard), karena fungsi ini berkomunikasi dengan dunia di luar make.

Fungsi shell menyediakan bagi make fasilitas yang sama dengan backquote (‘`’) pada sebagian besar shell, yaitu ekspansi perintah (command expansion). Artinya, fungsi ini menerima perintah shell sebagai argumen dan diekspansi menjadi keluaran dari perintah tersebut. Satu-satunya pemrosesan yang dilakukan make terhadap hasilnya adalah mengubah setiap baris baru (atau pasangan carriage-return/baris baru) menjadi satu spasi. Jika terdapat (carriage-return dan) baris baru di akhir, itu akan dihapus begitu saja.

Perintah yang dijalankan oleh pemanggilan fungsi shell dijalankan pada saat pemanggilan fungsi tersebut diekspansi (lihat Cara make membaca makefile). Karena fungsi ini melibatkan penciptaan shell baru, Anda perlu mempertimbangkan dengan saksama dampak performa dari penggunaan fungsi shell di dalam variabel ekspansi rekursif dibandingkan dengan variabel ekspansi sederhana (lihat Dua jenis variabel).

Alternatif dari fungsi shell adalah operator penetapan ‘!=’. Operator ini memiliki perilaku serupa, tetapi dengan sedikit perbedaan (lihat Menetapkan variabel). Operator penetapan ‘!=’ tercakup dalam standar POSIX yang lebih baru.

Setelah fungsi shell atau operator penetapan ‘!=’ digunakan, status keluarnya disimpan dalam variabel .SHELLSTATUS.

Berikut adalah beberapa contoh penggunaan fungsi shell:

contents := $(shell cat foo)

Ini menetapkan contents dengan isi file foo, dengan spasi (bukan baris baru) yang memisahkan setiap baris.

files := $(shell echo *.c)

Ini menetapkan files dengan hasil ekspansi ‘*.c’. Kecuali make menggunakan shell yang sangat aneh, hasilnya akan sama dengan ‘$(wildcard *.c)’ (selama setidaknya ada satu file ‘.c’).

Semua variabel yang ditandai dengan export juga akan diteruskan ke shell yang dijalankan oleh fungsi shell. Hal ini memungkinkan terjadinya loop ekspansi variabel: perhatikan makefile berikut:

export HI = $(shell echo hi)
all: ; @echo $$HI

Ketika make hendak menjalankan resep, ia harus menambahkan variabel HI ke lingkungan. Untuk itu, HI perlu diekspansi. Nilai variabel ini memerlukan pemanggilan fungsi shell, dan untuk memanggilnya kita harus membuat lingkungannya terlebih dahulu. Karena HI diekspor, kita perlu mengekspansinya untuk membuat lingkungan tersebut. Begitu seterusnya. Dalam kasus yang membingungkan ini, make tidak akan terjebak dalam loop atau mengeluarkan error, melainkan menggunakan nilai variabel tersebut dari lingkungan yang diberikan ke make, atau string kosong jika tidak ada. Ini biasanya sesuai dengan yang Anda inginkan. Misalnya:

export PATH = $(shell echo /usr/local/bin:$$PATH)

Meskipun demikian, dalam kasus ini akan lebih sederhana dan efisien untuk menggunakan variabel ekspansi sederhana (‘:=’) sejak awal.

8.15 Fungsi guile

Jika GNU make dibangun dengan dukungan GNU Guile sebagai bahasa ekstensi bawaan, fungsi guile akan tersedia. Fungsi guile menerima satu argumen yang terlebih dahulu diekspansi oleh make dengan cara biasa, kemudian diteruskan ke evaluator GNU Guile. Hasil dari evaluator tersebut dikonversi menjadi string dan digunakan sebagai hasil ekspansi fungsi guile dalam makefile. Untuk detail tentang cara menulis ekstensi make dalam Guile, lihat Integrasi dengan GNU Guile.

Anda dapat menentukan apakah dukungan GNU Guile tersedia dengan memeriksa variabel .FEATURES untuk mencari kata ‘guile’.


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