Build yang Anda pakai sehari-hari dan build yang Anda kirim ke pengguna seharusnya bukan biner yang sama. Build pengembangan sengaja membawa beban ekstra: informasi debug, tabel simbol lengkap, dan pemeriksaan assert — semuanya berguna di meja kerja dan semuanya menjadi kebocoran saat ikut terdistribusi. Chapter Build Types dan Optimization sudah membahas flag konfigurasinya; chapter ini melanjutkan ke tataran rilis: flag apa yang wajib hadir, bagaimana strip memangkas biner secara nyata, bagaimana menyimpan informasi debug untuk diagnosa lapangan tanpa mengirimkannya ke pengguna, cara menyematkan informasi versi yang jujur, dan konsep reproducible builds yang menjadi standar industri. Semua perintah dijalankan nyata terhadap satu program contoh.
Program Rilis Contoh #
Bahan uji kita kalkulator diskon kecil yang menerima harga dan persentase diskon dari baris perintah — pola aplikasi CLI yang wajar untuk dirilis:
#include <iomanip>
#include <iostream>
#include <string_view>
// Kalkulator diskon sederhana yang siap dikirim sebagai rilis.
// Informasi versi disematkan sebagai konstanta sendiri — bukan
// __DATE__/__TIME__ — agar build tetap reproducible.
constexpr std::string_view NAMA_APP = "kalkulator-diskon";
constexpr std::string_view VERSI = "1.2.0";
int main(int argc, char* argv[]) {
if (argc < 3) {
std::cerr << "Pemakaian: " << NAMA_APP << " <harga> <persen-diskon>\n";
return 1;
}
double harga = std::stod(argv[1]);
int persen = std::stoi(argv[2]);
double potongan = harga * persen / 100.0;
double total = harga - potongan;
std::cout << std::fixed << std::setprecision(2);
std::cout << NAMA_APP << " v" << VERSI << '\n';
std::cout << "Harga : Rp" << harga << '\n';
std::cout << "Diskon : " << persen << "% (-Rp" << potongan << ")\n";
std::cout << "Total : Rp" << total << '\n';
return 0;
}Output yang Dihasilkan:
kalkulator-diskon v1.2.0
Harga : Rp150000.00
Diskon : 20% (-Rp30000.00)
Total : Rp120000.00Yang perlu diperhatikan: Baris 9-10 menyimpan identitas aplikasi sebagai constexpr std::string_view — konstanta waktu kompilasi yang kita kendalikan penuh, bertentangan sengaja dengan pendekatan __DATE__ yang kita bahas nanti. Baris 12-16 menagih dua argumen dan kembali dengan exit code 1 plus pesan pemakaian ke std::cerr bila tidak lengkap — perilaku yang harus sudah benar sebelum rilis, bukan sesudahnya. Baris 22 memakai std::fixed dengan presisi dua digit agar uang selalu tampil konsisten. Dengan program ini, checklist rilis kita jalankan satu per satu.
Flag Wajib untuk Build Rilis #
Checklist minimal build release untuk GCC berbentuk satu baris:
g++ -std=c++23 -Wall -Wextra -O2 -DNDEBUG kalkulator-diskon.cpp -o kalkulator-diskon-O2 mengaktifkan optimasi standar yang dipilih hampir semua software rilis; -DNDEBUG menonaktifkan assert yang memang hanya alat pengembangan; dan -Wall -Wextra dipertahankan bahkan di build rilis karena peringatan yang muncul di tingkat optimasi kadang berbeda dari build debug. Semua mekanisme di balik dua flag pertama — termasuk bukti nyata bahwa assert benar-benar hilang di build release — sudah diuji tuntas di Chapter Build Types dan Optimization, jadi di sini kita fokus pada pekerjaan yang belum kita lakukan: membersihkan biner hasilnya.
strip: Membuang Simbol yang Tidak Dibutuhkan Pengguna #
Biner hasil -g membawa informasi debug yang besar. Kita bangun versi berbeban itu dulu, lalu jalankan strip dan bandingkan:
g++ -std=c++23 -O2 -g kalkulator-diskon.cpp -o kalkulator-diskon
ls -l kalkulator-diskon | awk '{print $5, $9}'
file kalkulator-diskon | sed 's/, BuildID[^,]*//'
strip kalkulator-diskon
ls -l kalkulator-diskon | awk '{print $5, $9}'
file kalkulator-diskon | sed 's/, BuildID[^,]*//'Output yang Dihasilkan:
114016 kalkulator-diskon
kalkulator-diskon: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, with debug_info, not stripped
14680 kalkulator-diskon
kalkulator-diskon: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, strippedUkuran turun dari 114.016 ke 14.680 byte — hampir 88% isinya ternyata bukan kode program, melainkan informasi pendamping: tabel simbol (nama fungsi dan variabel) dan informasi debug (peta baris source). Perhatikan dua hal dari file: label bergeser dari not stripped ke stripped, dan program tetap berjalan normal setelah dipangkas — strip tidak menyentuh bagian yang dibutuhkan saat runtime, termasuk dynamic symbol table yang dipakai dynamic linker. Justru simbol dinamis itulah yang harus Anda sayangi: tanpa nama simbol, laporan crash dari pengguna hanya berisi alamat yang mustahil ditafsirkan.
Menyimpan Informasi Debug untuk Diagnosa Lapangan #
Membuang informasi debug bukan berarti membuangnya untuk selamanya. Alur binutils yang lazim memisahkan keduanya: simpan salinan debug untuk arsip Anda, kirim biner ramping ke pengguna:
g++ -std=c++23 -O2 -g kalkulator-diskon.cpp -o kalkulator-diskon
objcopy --only-keep-debug kalkulator-diskon kalkulator-diskon.debug
strip --strip-debug kalkulator-diskon
objcopy --add-gnu-debuglink=kalkulator-diskon.debug kalkulator-diskon
ls -l kalkulator-diskon kalkulator-diskon.debug | awk '{print $5, $9}'Output yang Dihasilkan:
17688 kalkulator-diskon
106416 kalkulator-diskon.debug--only-keep-debug menyalin seluruh informasi debug ke kalkulator-diskon.debug (106 KB), strip --strip-debug memangkasnya dari biner yang dikirim, dan --add-gnu-debuglink mencatat nama berkas debug tersebut di dalam biner. Saat pengguna melaporkan crash, Anda mengambil berkas .debug yang cocok — debugger mencocokkannya lewat BuildID yang Anda lihat di output file — dan menelusuri laporan ke baris source persis seperti men-debug di mesin sendiri. Praktik ini adalah bentuk paling sederhana dari symbol server yang dipakai proyek besar; versi lengkapnya hanya menambahkan pengarsipan berkas .debug per nomor rilis di satu tempat yang bisa diakses tim.
Informasi Versi: Konstanta Sendiri, Bukan DATE #
Sangat menggoda menandai build dengan tanggal kompilasi yang disediakan preprocessor:
#include <iostream>
int main() {
std::cout << "Dibangun: " << __DATE__ << ' ' << __TIME__ << '\n';
return 0;
}Makro __DATE__ dan __TIME__ diperluas berdasarkan jam dinding komputer saat kompilasi. Akibatnya dua build dari source yang sama menghasilkan biner dengan string berbeda — dan yang lebih serius, checksum biner berubah tanpa ada perubahan kode. Untuk laporan bug, string versi yang berguna adalah versi rilis (1.2.0), bukan tanggal build; versi itu milik proses manajemen rilis yang kita latih di Chapter Versioning dan Release. Pendekatan yang terbukti di program contoh — konstanta VERSI yang dinaikkan secara sadar saat merilis — memberi string yang stabil, mudah dicari di log, dan cocok dengan tag di repository.
Reproducible Builds dalam Satu Paragraf #
Prinsipnya sederhana: source yang sama + toolchain yang sama + flag yang sama = biner yang byte-per-byte identik, di mesin siapa pun. Prasyarat praktisnya adalah menghapus semua sumber ketidakpastian — stempel waktu, jalur build absolut, urutan input yang berubah-ubah — sehingga checksum rilis yang Anda terbitkan bisa diverifikasi ulang siapa pun dengan membangun dari source yang sama. Distribusi besar (Debian, Fedora) menuntut ini untuk seluruh repositori paketnya, dan bagi proyek kita manfaatnya nyata: proses CI yang membangun ulang rilis bisa membuktikan bahwa biner yang dikirim benar-benar berasal dari commit yang diklaim. Kita akan memakai prinsip ini secara konkret — tarball dengan stempel waktu tetap dan checksum sha256 — di Chapter Packaging Aplikasi penutup section ini.
Checklist Rilis #
- Flag rilis lengkap:
-O2 -DNDEBUG, plus-Wall -Wextratanpa peringatan baru. - Informasi versi dari konstanta sendiri, bukan tanggal build.
stripdijalankan pada biner yang dikirim; berkas.debugdiarsipkan terpisah.- Biner yang diuji adalah biner yang dikirim — jalankan smoke test pada hasil
strip, bukan pada build debug. - Exit code dan pesan pemakaian sudah benar (coba
./kalkulator-diskontanpa argumen; program contoh ini kembali dengan kode 1 dan pesan kestd::cerr).
Satu kelemahan sisa dari semua latihan di section ini: biner yang kita bangun hanya jalan di arsitektur yang sama dengan mesin praktikum — x86-64. Pengguna dengan laptop ARM modern tidak akan bisa menjalankannya, dan itulah persoalan yang dijawab cross-compilation di chapter berikutnya.
Referensi: Optimize Options — GCC Online Docs , Debugging Options — GCC Online Docs .