GNU make menyediakan banyak kemampuan tingkat lanjut, termasuk berbagai fungsi yang berguna. Namun, make tidak menyertakan bahasa pemrograman yang lengkap sehingga memiliki keterbatasan. Keterbatasan ini terkadang dapat diatasi dengan menggunakan fungsi shell untuk memanggil program terpisah, meskipun cara ini bisa kurang efisien.
Jika kemampuan bawaan GNU make tidak mencukupi kebutuhan Anda, ada dua cara untuk memperluas make. Cara pertama, pada sistem yang menyediakannya, Anda dapat memanfaatkan GNU Guile sebagai bahasa skrip yang tertanam (lihat Integrasi dengan GNU Guile). Cara kedua, pada sistem yang mendukung objek yang dapat dimuat secara dinamis, Anda dapat menulis ekstensi Anda sendiri dalam bahasa apa pun (asalkan bahasa tersebut dapat dikompilasi menjadi objek semacam itu), lalu memuatnya untuk menyediakan kemampuan tambahan (lihat Memuat objek dinamis).
GNU make dapat dibangun dengan dukungan GNU Guile sebagai bahasa ekstensi tertanam. Guile mengimplementasikan bahasa Scheme. Penjelasan tentang GNU Guile, bahasa Scheme, dan fitur-fiturnya berada di luar cakupan manual ini: lihat dokumentasi GNU Guile dan Scheme.
Anda dapat memeriksa apakah make yang Anda gunakan mendukung Guile dengan memeriksa variabel .FEATURES; variabel ini akan berisi kata guile jika dukungan Guile tersedia.
Integrasi dengan Guile menambahkan satu fungsi make baru, yaitu fungsi guile. Fungsi guile menerima satu argumen, yang pertama-tama diekspansi oleh make dengan cara biasa, lalu diteruskan ke evaluator GNU Guile. Hasil evaluator ini dikonversi menjadi string dan digunakan sebagai hasil ekspansi fungsi guile di dalam makefile.
Selain itu, GNU make juga menyediakan beberapa prosedur Guile untuk digunakan dalam skrip Guile Anda.
Di dalam make hanya ada satu “tipe data”, yaitu string. Sebaliknya, GNU Guile menyediakan beragam tipe data yang kaya. Aspek penting dalam antarmuka antara make dan GNU Guile adalah konversi tipe data Guile menjadi string make.
Konversi ini relevan dalam dua situasi. Pertama, ketika makefile memanggil fungsi guile untuk mengevaluasi ekspresi Guile, hasil evaluasi tersebut harus dikonversi menjadi string make agar dapat dievaluasi lebih lanjut oleh make. Kedua, ketika skrip Guile memanggil salah satu prosedur yang diekspor oleh make, argumen yang diberikan ke prosedur tersebut harus dikonversi menjadi string.
Konversi tipe Guile menjadi string make adalah sebagai berikut:
#fSalah (False) dikonversi menjadi string kosong: dalam kondisional make, string kosong dianggap salah.
#tBenar (True) dikonversi menjadi string ‘#t’: dalam kondisional make, string apa pun yang tidak kosong dianggap benar.
symbolnumberSimbol atau angka dikonversi menjadi representasi string dari simbol atau angka tersebut.
characterKarakter yang dapat dicetak dikonversi menjadi karakter yang sama.
stringString yang hanya berisi karakter yang dapat dicetak dikonversi menjadi string yang sama.
listList dikonversi secara rekursif mengikuti aturan di atas. Ini berarti list yang berstruktur akan diratakan (flatten); dengan kata lain, hasil ‘'(a b (c d) e)’ akan dikonversi menjadi string make ‘a b c d e’.
otherTipe Guile lainnya akan menghasilkan error. Pada versi make mendatang, tipe Guile lain mungkin akan dikonversi juga.
Konversi ‘#f’ menjadi string kosong, dan ‘#t’ menjadi string tidak kosong ‘#t’, dirancang agar Anda dapat langsung menggunakan hasil boolean Guile sebagai kondisi boolean make. Sebagai contoh:
$(if $(guile (access? "myfile" R_OK)),$(info myfile exists))
Sebagai akibat dari aturan konversi ini, Anda perlu memperhatikan hasil dari skrip Guile Anda, karena hasil tersebut akan dikonversi menjadi string dan diuraikan (parse) oleh make. Jika skrip Anda tidak memiliki hasil alami (artinya, skrip tersebut hanya ada untuk efek sampingnya), sebaiknya tambahkan ‘#f’ sebagai ekspresi terakhir untuk menghindari error sintaksis dalam makefile.
Selain fungsi guile yang tersedia dalam makefile, make juga menyediakan beberapa prosedur untuk digunakan dalam skrip Guile Anda. Saat memulai, make membuat modul Guile baru, gnu make, dan mengekspor prosedur berikut sebagai antarmuka publik dari modul tersebut:
gmk-expandProsedur ini menerima satu argumen dan mengonversinya menjadi string. String tersebut diekspansi oleh make mengikuti aturan ekspansi make yang biasa. Hasil ekspansi dikonversi menjadi string Guile dan dikembalikan sebagai hasil prosedur ini.
gmk-evalProsedur ini menerima satu argumen dan mengonversinya menjadi string. String tersebut dievaluasi oleh make seolah-olah merupakan makefile. Ini adalah kemampuan yang sama dengan yang tersedia melalui fungsi eval (lihat Fungsi eval). Hasil prosedur gmk-eval selalu berupa string kosong.
Perhatikan bahwa gmk-eval tidak sepenuhnya sama dengan menggunakan gmk-expand bersama fungsi eval: pada kasus yang terakhir, string yang dievaluasi akan diekspansi dua kali; pertama oleh gmk-expand, kemudian oleh fungsi eval.
Berikut ini adalah contoh yang sangat sederhana tentang penggunaan GNU Guile untuk mengelola penulisan ke file. Prosedur-prosedur Guile ini hanya membuka file, memungkinkan penulisan ke file tersebut (satu string per baris), lalu menutup file itu. Perhatikan bahwa karena kita tidak dapat menyimpan nilai kompleks seperti port Guile dalam variabel make, kita akan menyimpan port tersebut sebagai variabel global di dalam interpreter Guile.
Anda dapat membuat fungsi Guile dengan mudah menggunakan define/endef untuk membuat skrip Guile, lalu menggunakan fungsi guile untuk menginternalisasinya:
define GUILEIO ;; Pustaka I/O Guile sederhana untuk GNU Make (define MKPORT #f) (define (mkopen name mode) (set! MKPORT (open-file name mode)) #f) (define (mkwrite s) (display s MKPORT) (newline MKPORT) #f) (define (mkclose) (close-port MKPORT) #f) #f endef # Internalisasi fungsi I/O Guile $(guile $(GUILEIO))
Jika Anda memiliki cukup banyak kode pendukung Guile, Anda dapat mempertimbangkan untuk menyimpannya dalam file terpisah (misalnya guileio.scm) lalu memuatnya di makefile Anda menggunakan fungsi guile:
$(guile (load "guileio.scm"))
Keuntungan dari cara ini adalah, ketika Anda mengedit guileio.scm, editor Anda akan mengenali file ini sebagai sintaksis Scheme, bukan sintaksis makefile.
Sekarang Anda dapat menggunakan fungsi-fungsi Guile ini untuk membuat file. Misalkan Anda perlu mengolah list yang sangat besar, yang tidak muat pada baris perintah, tetapi utilitas yang Anda gunakan juga menerima list tersebut sebagai input:
prog: $(PREREQS)
@$(guile (mkopen "tmp.out" "w")) \
$(foreach X,$^,$(guile (mkwrite "$(X)"))) \
$(guile (mkclose))
$(LINK) < tmp.out
Tentu saja, Anda juga dapat membuat kumpulan prosedur manipulasi file yang lebih lengkap. Misalnya, Anda dapat mengelola beberapa file output sekaligus dengan memilih satu simbol untuk masing-masing file, lalu menggunakannya sebagai kunci pada tabel hash (dengan nilainya berupa port), kemudian mengembalikan simbol tersebut untuk disimpan dalam variabel make.
|
Banyak sistem operasi menyediakan fasilitas untuk memuat objek terkompilasi secara dinamis. Jika sistem Anda menyediakan fasilitas ini, GNU make dapat memanfaatkannya untuk memuat objek dinamis saat runtime, sehingga menyediakan kemampuan baru yang kemudian dapat dipanggil dari makefile Anda.
Direktif load digunakan untuk memuat objek dinamis. Setelah objek dimuat, fungsi “setup” akan dipanggil untuk memungkinkan objek tersebut menginisialisasi dirinya sendiri dan mendaftarkan fasilitas baru ke GNU make. Sebagai contoh, objek dinamis mungkin menyertakan fungsi make baru, dan fungsi “setup” tersebut akan mendaftarkannya ke sistem penanganan fungsi GNU make.
Objek dimuat ke dalam GNU make dengan menuliskan direktif load di dalam makefile Anda. Sintaksis direktif load adalah sebagai berikut:
load object-file …
atau:
load object-file(symbol-name) …
File object-file akan dimuat secara dinamis oleh GNU make. Jika object-file tidak menyertakan path direktori, file tersebut akan dicari terlebih dahulu di direktori saat ini. Jika tidak ditemukan di sana, atau jika path direktori disertakan, path khusus sistem akan dicari. Jika pemuatan gagal karena alasan apa pun, make akan menampilkan pesan lalu keluar.
Jika pemuatan berhasil, make akan memanggil fungsi inisialisasi.
Jika symbol-name ditentukan, nama tersebut akan digunakan sebagai nama fungsi inisialisasi.
Jika symbol-name tidak ditentukan, nama fungsi inisialisasi dibentuk dengan mengambil nama file dasar dari object-file, hingga karakter pertama yang bukan karakter nama simbol yang valid (karakter alfanumerik dan garis bawah adalah karakter nama simbol yang valid). Sufiks _gmk_setup kemudian ditambahkan ke prefiks ini untuk menjadi nama fungsi.
Lebih dari satu file objek dapat dimuat dengan satu direktif load, dan kedua bentuk argumen load dapat digunakan bersama dalam direktif yang sama.
Fungsi inisialisasi akan diberi nama file dan nomor baris tempat operasi load tersebut dipanggil. Fungsi ini harus mengembalikan nilai bertipe int, yaitu 0 jika gagal dan bukan 0 jika berhasil. Jika nilai kembaliannya adalah -1, GNU Make tidak akan mencoba membangun ulang file objek tersebut (lihat Bagaimana objek yang dimuat dibuat ulang).
Sebagai contoh:
load ../mk_funcs.so
Ini akan memuat objek dinamis ../mk_funcs.so. Setelah objek dimuat, make akan memanggil fungsi mk_funcs_gmk_setup (yang diasumsikan didefinisikan oleh objek bersama (shared object) tersebut).
Di sisi lain:
load ../mk_funcs.so(init_mk_func)
Ini akan memuat objek dinamis ../mk_funcs.so. Setelah objek dimuat, make akan memanggil fungsi init_mk_func.
Berapa kali pun file objek muncul dalam direktif load, file tersebut hanya akan dimuat (dan fungsi setup-nya hanya akan dipanggil) satu kali.
Setelah objek berhasil dimuat, nama file-nya akan ditambahkan ke variabel .LOADED.
Jika Anda lebih suka agar kegagalan memuat objek dinamis tidak dilaporkan sebagai error, Anda dapat menggunakan direktif -load sebagai pengganti load. GNU make tidak akan gagal dan tidak akan menghasilkan pesan apa pun jika objek gagal dimuat. Objek yang gagal dimuat tidak ditambahkan ke variabel .LOADED, sehingga variabel ini dapat diperiksa untuk menentukan apakah pemuatan berhasil.
Objek yang dimuat menjalani prosedur pembuatan ulang yang sama seperti makefile (lihat Cara makefile dibuat ulang). Jika salah satu objek yang dimuat dibuat ulang, make akan mulai dari awal, membaca ulang semua makefile, dan memuat ulang file objek tersebut. Objek yang dimuat tidak perlu melakukan apa pun yang khusus untuk mendukung hal ini.
Menyediakan aturan yang diperlukan untuk membangun ulang objek yang dimuat adalah tanggung jawab penulis makefile.
|
Agar berguna, objek yang dimuat harus dapat berinteraksi dengan GNU make. Interaksi ini mencakup baik antarmuka yang disediakan objek yang dimuat kepada makefile, maupun antarmuka yang disediakan make kepada objek yang dimuat (untuk memanipulasi operasi make).
Antarmuka antara objek yang dimuat dan make didefinisikan oleh file header C gnumake.h. Semua objek yang dimuat yang ditulis dalam C sebaiknya menyertakan (include) file header ini. Objek yang dimuat yang tidak ditulis dalam C perlu mengimplementasikan antarmuka yang didefinisikan dalam file header ini.
Biasanya, objek yang dimuat akan mendaftarkan satu atau lebih fungsi GNU make baru menggunakan rutin gmk_add_function dari dalam fungsi setup-nya. Implementasi fungsi make ini dapat memanfaatkan rutin gmk_expand dan gmk_eval untuk melakukan tugasnya, lalu secara opsional mengembalikan string sebagai hasil ekspansi fungsi.
Setiap ekstensi dinamis harus mendefinisikan simbol global plugin_is_GPL_compatible untuk menyatakan bahwa ekstensi tersebut dilisensikan di bawah lisensi yang kompatibel dengan GPL. Jika simbol ini tidak ada, make akan mengeluarkan error fatal dan keluar saat mencoba memuat ekstensi Anda.
Tipe deklarasi simbol ini sebaiknya int. Namun, simbol ini tidak perlu berada di section yang dialokasikan secara khusus. Kode ini hanya menyatakan bahwa simbol tersebut ada dalam ruang lingkup global. Kode berikut sudah cukup:
int plugin_is_GPL_compatible;
gmk_flocStruktur ini merepresentasikan pasangan nama file dan lokasi. Struktur ini disediakan saat mendefinisikan berbagai item, sehingga GNU make dapat memberi tahu pengguna nantinya, jika diperlukan, mengenai di mana definisi tersebut dilakukan.
Saat ini, hanya ada satu cara bagi makefile untuk memanggil operasi yang disediakan oleh objek yang dimuat, yaitu melalui antarmuka pemanggilan fungsi make. Objek yang dimuat dapat mendaftarkan satu atau lebih fungsi baru, yang kemudian dapat dipanggil dari dalam makefile seperti fungsi lainnya.
Gunakan gmk_add_function untuk membuat fungsi make baru. Argumennya adalah sebagai berikut:
nameNama fungsi. Inilah yang harus digunakan makefile untuk memanggil fungsi tersebut. Nama harus terdiri dari 1 hingga 255 karakter, dan hanya boleh berisi karakter alfanumerik, titik (‘.’), tanda hubung (‘-’), dan garis bawah (‘_’). Nama tidak boleh diawali dengan titik.
func_ptrPenunjuk (pointer) ke fungsi yang akan dipanggil make saat mengekspansi fungsi ini di dalam makefile. Fungsi ini harus didefinisikan oleh objek yang dimuat.
min_argsJumlah argumen minimum yang diterima fungsi ini. Harus di antara 0 dan 255. GNU make akan memeriksa hal ini dan gagal sebelum memanggil func_ptr jika fungsi dipanggil dengan argumen yang terlalu sedikit.
max_argsJumlah argumen maksimum yang diterima fungsi ini. Harus di antara 0 dan 255. GNU make akan memeriksa hal ini dan gagal sebelum memanggil func_ptr jika fungsi dipanggil dengan argumen yang terlalu banyak. Jika nilainya 0, jumlah argumen tidak dibatasi. Jika nilainya lebih besar dari 0, nilai tersebut harus lebih besar atau sama dengan min_args.
flagsFlag yang menentukan bagaimana fungsi ini akan beroperasi; flag yang diinginkan harus digabungkan dengan OR. Jika flag GMK_FUNC_NOEXPAND diberikan, argumen fungsi tidak akan diekspansi sebelum fungsi dipanggil; jika tidak diberikan, argumen akan diekspansi terlebih dahulu.
Fungsi yang didaftarkan ke make harus sesuai dengan tipe gmk_func_ptr. Fungsi ini akan dipanggil dengan tiga parameter: name (nama fungsi), argc (jumlah argumen fungsi), dan argv (array penunjuk ke argumen fungsi). Penunjuk terakhir (yaitu argv[argc]) akan bernilai nol (0).
Nilai kembalian fungsi adalah hasil ekspansi fungsi tersebut. Jika fungsi tidak menghasilkan apa pun, nilai kembaliannya boleh nol (null). Jika tidak, nilai kembaliannya harus berupa penunjuk ke string yang dibuat dengan gmk_alloc. Setelah fungsi mengembalikan nilai, string tersebut menjadi milik make dan akan dibebaskan (di-free) oleh make pada waktu yang tepat; objek yang dimuat tidak dapat mengaksesnya lagi.
GNU make menyediakan beberapa fasilitas untuk digunakan oleh objek yang dimuat. Fasilitas ini biasanya dijalankan dari dalam fungsi setup dan/atau fungsi yang didaftarkan melalui gmk_add_function, untuk mengambil atau mengubah data yang dikerjakan make.
gmk_expandFungsi ini menerima string dan mengekspansinya menggunakan aturan ekspansi make. Hasil ekspansi dikembalikan dalam buffer string yang diakhiri nol (nil-terminated). Pemanggil bertanggung jawab untuk memanggil gmk_free dengan penunjuk ke buffer yang dikembalikan setelah selesai digunakan.
gmk_evalFungsi ini menerima buffer dan mengevaluasinya sebagai satu segmen sintaksis makefile. Fungsi ini dapat digunakan untuk mendefinisikan variabel baru, aturan baru, dan sebagainya. Fungsi ini setara dengan menggunakan fungsi eval milik make.
Perhatikan bahwa ada perbedaan antara gmk_eval dan memanggil gmk_expand pada string dengan menggunakan fungsi eval: pada kasus yang terakhir, string tersebut akan diekspansi dua kali; pertama oleh gmk_expand, lalu sekali lagi oleh fungsi eval. Dengan gmk_eval, buffer hanya diekspansi paling banyak satu kali (saat dibaca oleh parser make).
Beberapa sistem memungkinkan skema manajemen memori yang berbeda-beda. Karena itu, Anda tidak boleh sama sekali meneruskan memori yang Anda alokasikan sendiri secara langsung ke fungsi make mana pun, dan Anda juga tidak boleh mencoba membebaskan secara langsung memori apa pun yang dikembalikan kepada Anda oleh fungsi make. Sebagai gantinya, gunakan fungsi gmk_alloc dan gmk_free.
Secara khusus, string yang dikembalikan ke make oleh fungsi yang didaftarkan menggunakan gmk_add_function harus dialokasikan menggunakan gmk_alloc, dan string yang dikembalikan oleh fungsi gmk_expand milik make harus dibebaskan (ketika sudah tidak diperlukan lagi) menggunakan gmk_free.
gmk_allocMengembalikan penunjuk ke buffer yang baru dialokasikan. Fungsi ini selalu mengembalikan penunjuk yang valid; jika memori yang tersedia tidak cukup, make akan keluar. gmk_alloc tidak menginisialisasi memori yang dialokasikan.
gmk_freeMembebaskan buffer yang dikembalikan kepada Anda oleh make. Setelah fungsi gmk_free kembali, string tersebut tidak lagi valid. Jika NULL diberikan ke gmk_free, tidak ada operasi yang dilakukan.
Misalkan kita ingin menulis fungsi GNU make baru yang membuat file sementara dan mengembalikan namanya. Kita ingin fungsi ini menerima prefiks sebagai argumen. Pertama, kita dapat menulis fungsi ini dalam file mk_temp.c:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <gnumake.h>
int plugin_is_GPL_compatible;
char *
gen_tmpfile(const char *nm, int argc, char **argv)
{
int fd;
/* Hitung panjang nama file dan alokasikan ruang untuknya. */
int len = strlen (argv[0]) + 6 + 1;
char *buf = gmk_alloc (len);
strcpy (buf, argv[0]);
strcat (buf, "XXXXXX");
fd = mkstemp(buf);
if (fd >= 0)
{
/* Jangan biarkan file descriptor bocor. */
close (fd);
return buf;
}
/* Jika gagal. */
fprintf (stderr, "mkstemp(%s) failed: %s\n", buf, strerror (errno));
gmk_free (buf);
return NULL;
}
int
mk_temp_gmk_setup (const gmk_floc *floc)
{
printf ("mk_temp plugin loaded from %s:%lu\n", floc->filenm, floc->lineno);
/* Daftarkan fungsi ini dengan nama make "mk-temp". */
gmk_add_function ("mk-temp", gen_tmpfile, 1, 1, 1);
return 1;
}
Selanjutnya, kita akan menulis Makefile yang dapat membangun, memuat, dan menggunakan objek bersama ini:
all:
@echo Temporary file: $(mk-temp tmpfile.)
load mk_temp.so
mk_temp.so: mk_temp.c
$(CC) -shared -fPIC -o $@ $<
Pada MS-Windows, karena keunikan cara pembuatan objek bersama, kompiler perlu memindai pustaka impor (import library) yang dihasilkan saat membangun make (biasanya bernama libgnumake-version.dll.a, dengan version adalah versi API objek yang dimuat). Oleh karena itu, resep (recipe) untuk menghasilkan objek bersama di Windows akan terlihat seperti ini (dengan asumsi versi API-nya adalah 1):
mk_temp.dll: mk_temp.c
$(CC) -shared -o $@ $< -lgnumake-1
Sekarang, ketika Anda menjalankan make, Anda akan melihat output seperti berikut:
$ make mk_temp plugin loaded from Makefile:4 cc -shared -fPIC -o mk_temp.so mk_temp.c Temporary filename: tmpfile.A7JEwd