Program yang memakai pustaka eksternal — zlib untuk kompresi, OpenSSL untuk kriptografi — menuntut dua informasi saat dibangun: di mana file header (zlib.h) berada, dan pustaka mana yang harus ditautkan linker. Menuliskan flag -I/usr/include/... dan -lz secara hafalan berhasil di satu mesin lalu gagal di mesin lain, karena tiap distribusi menaruh pustaka di lokasi berbeda. Alat pkg-config menghapus tebak-tebakan itu: satu kueri, semua flag siap tempel. Alat ini terpasang di sistem latihan, jadi seluruh contoh berikut dijalankan sungguhan dengan pustaka zlib yang benar-benar ada.

Masalah Klasik Linking #

Kita mulai dari program kecil yang mengompresi 256 byte data memakai zlib, lalu sengaja membangunnya tanpa memberi tahu compiler soal pustakanya:

bash
cat > salin.c << 'AKHIR'
#include <stdio.h>
#include <string.h>
#include <zlib.h>

int main(void)
{
    char asal[256];
    memset(asal, 'a', sizeof asal);
    unsigned char hasil[256];
    uLongf panjang = sizeof hasil;

    if (compress(hasil, &panjang, (Bytef *)asal, sizeof asal) != Z_OK)
        return 1;
    printf("asli: %zu byte, terkompresi: %lu byte\n",
           sizeof asal, (unsigned long)panjang);
    return 0;
}
AKHIR
gcc -c salin.c -o salin.o && echo "kompilasi object: OK"
gcc -o salin salin.o 2>&1

Output yang Dihasilkan:

text
kompilasi object: OK
/usr/bin/ld: salin.o: in function `main':
salin.c:(.text+0x4d): undefined reference to `compress'
collect2: error: ld returned 1 exit status

Kegagalan ini memisahkan dua tahap yang berbeda. Baris gcc -c sukses karena stdio.h maupun zlib.h ditemukan di path default /usr/include — tahap kompilasi hanya butuh deklarasi. Yang gagal adalah tahap linking: object file salin.o memanggil fungsi compress yang dideklarasikan zlib, tetapi tidak ada implementasinya di antara file yang diserahkan ke linker. Pesan undefined reference to 'compress' adalah cara linker berkata: “kamu memanggil fungsi ini, tapi aku tidak tahu di mana tubuhnya”. Implementasi itu ada di /usr/lib/x86_64-linux-gnu/libz.so — kita hanya belum memberi flag -lz agar linker mencarinya.

pkg-config: Penyedia Flag yang Pasti #

Daripada menghafal lokasi tiap pustaka, kita tanyakan langsung ke pkg-config:

bash
pkg-config --modversion zlib
pkg-config --cflags --libs zlib
pkg-config --exists zlib && echo "zlib dikenal pkg-config"

Output yang Dihasilkan:

text
1.3.1
-lz 
zlib dikenal pkg-config

Tiga kueri itu adalah seluruh API yang kita butuhkan sehari-hari:

  1. --modversion zlib: versi pustaka yang ditemukan — berguna saat program menuntut zlib minimal versi tertentu.
  2. --cflags --libs zlib: flag siap tempel untuk gcc; hasilnya hanya -lz karena header zlib memang berada di path default, sehingga bagian -I kosong.
  3. --exists zlib: uji keberadaan tanpa mencetak apa pun — inilah yang dipakai script configure untuk memeriksa dependensi sebelum menulis Makefile.

Kompilasi dengan Flag dari pkg-config #

Output pkg-config dirancang untuk dimakan substitusi perintah — pola $(pkg-config ...) menempelkan flag tepat di tempatnya:

bash
cat > salin.c << 'AKHIR'
#include <stdio.h>
#include <string.h>
#include <zlib.h>

int main(void)
{
    char asal[256];
    memset(asal, 'a', sizeof asal);
    unsigned char hasil[256];
    uLongf panjang = sizeof hasil;

    if (compress(hasil, &panjang, (Bytef *)asal, sizeof asal) != Z_OK)
        return 1;
    printf("asli: %zu byte, terkompresi: %lu byte\n",
           sizeof asal, (unsigned long)panjang);
    return 0;
}
AKHIR
gcc -c salin.c -o salin.o
gcc -o salin salin.o $(pkg-config --libs zlib)
./salin
objdump -p salin | grep NEEDED

Output yang Dihasilkan:

text
asli: 256 byte, terkompresi: 12 byte
  NEEDED               libz.so.1
  NEEDED               libc.so.6

Alur bangun-run-periksa itu berjalan mulus: kompresi memangkas 256 byte berisi huruf a berulang menjadi 12 byte, dan objdump -p membedah isi binary untuk menampilkan entri NEEDED — daftar soname pustaka yang dicatat linker. Dari sini terlihat bahwa dependensi zlib ikut tertanam di dalam binary; menyertakan $(shell pkg-config --cflags --libs zlib) pada variabel CFLAGS/LDLIBS di Makefile — pola yang kita bangun di chapter Make dan Build Automation — membuat proyek multi-file memakai flag yang sama otomatis.

Anatomi File .pc dan PKG_CONFIG_PATH #

Dari mana pkg-config tahu semua itu? Dari file metadata berekstensi .pc yang dipasang tiap pustaka ke /usr/lib/<arsitektur>/pkgconfig/. File itu teks biasa — kita bisa membuatnya sendiri untuk SDK lokal, lalu mengarahkan PKG_CONFIG_PATH ke foldernya:

bash
mkdir -p sdk
cat > sdk/teman.pc << 'AKHIR'
prefix=/opt/sdk-teman
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include

Name: teman
Description: SDK fiktif untuk latihan pkg-config
Version: 1.2.3
Cflags: -I${includedir}
Libs: -L${libdir} -lteman
AKHIR
PKG_CONFIG_PATH=sdk pkg-config --modversion teman
PKG_CONFIG_PATH=sdk pkg-config --cflags --libs teman

Output yang Dihasilkan:

text
1.2.3
-I/opt/sdk-teman/include -L/opt/sdk-teman/lib -lteman 

Bedah isi file teman.pc:

  1. prefix sampai includedir: variabel internal yang menunjuk lokasi pemasangan SDK; ${prefix} di baris lain merujuk nilainya, persis variabel Makefile.
  2. Name, Description, Version: metadata untuk manusia dan untuk --modversion.
  3. Cflags: flag untuk tahap kompilasi — -I menambah lokasi header.
  4. Libs: flag untuk tahap linking — -L menambah lokasi pustaka, -l menautkan libteman.
  5. PKG_CONFIG_PATH=sdk: variabel lingkungan perintah-perintah yang memperluas lokasi pencarian .pc — tanpa itu, teman tidak akan ditemukan karena pkg-config hanya melihat direktori sistem.

Pola ini menjelaskan kenapa proyek dengan dependensi lokal cukup menyimpan SDK di folder proyek: .pc-nya ditemukan lewat PKG_CONFIG_PATH, dan seluruh flag konsisten di mana pun proyek dibangun.

Sisi Runtime: ld.so, ldconfig, dan rpath #

pkg-config menyelesaikan separuh cerita — sisa pekerjaan terjadi setiap kali program dijalankan. Loader dinamis ld.so membaca entri NEEDED binary lalu mencari libz.so.1 di beberapa tempat berurutan: path tertanam rpath di binary, variabel LD_LIBRARY_PATH, lalu cache /etc/ld.so.cache. Alat ldd memperlihatkan hasil resolusinya; sebagai contoh tampilan, inilah wujud tipikal ldd salin di mesin 64-bit:

text
        libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f8e2a3d9000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8e2a200000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f8e2a400000)

Angka heksadesimal di ujung adalah alamat pemuatan yang berubah tiap eksekusi — yang stabil adalah pemetaan nama ke path. Cache yang dicari ld.so dibangun oleh ldconfig dari /lib, /usr/lib, dan direktori tambahan di /etc/ld.so.conf.d/; setelah memasang pustaka baru ke lokasi sistem, sudo ldconfig menyegarkannya. Urutan pencarian dan opsi-opsinya dirinci di ld.so(8) dan ldconfig(8) .

Untuk aplikasi yang membawa pustakanya sendiri — misalnya dipasang di /opt/aplikasi/lib — urutan pertama tadi menjadi solusi: flag -Wl,-rpath,/opt/aplikasi/lib saat linking menanam path itu ke binary, sehingga program berjalan tanpa LD_LIBRARY_PATH dan tanpa menyentuh direktori sistem. Paksaan terakhir seperti ini jarang diperlukan; pola bersihnya tetap pustaka dari package manager, pkg-config sebagai juru bicara flag, dan cache ldconfig sebagai peta runtime.

Dengan flag yang benar, program akhirnya terbangun dan berjalan — namun berjalan bukan berarti benar. Ketika hasilnya salah atau program tiba-tiba mati, kita pindah ke ranah yang berbeda: mengintip isi proses yang sedang berjalan. Chapter berikutnya tentang gdb, strace, dan ltrace dimulai dari situ.