Semua platform utama — Linux, Windows, dan macOS — kini sudah kita ketahui cara membangun dan membungkus aplikasinya. Tinggal satu bahan yang menyatukan semuanya sebelum paket dirakit: identitas rilis. Pengguna, manajer paket, dan alat pembaruan butuh satu jawaban ringkas atas pertanyaan “versi berapa yang sedang saya pakai?”, dan jawaban itu harus konsisten di changelog, di tag repository, di nama berkas paket, sampai di keluaran program sendiri. Chapter ini melatih prosesnya secara utuh: semantic versioning untuk penomoran, changelog untuk catatan perubahan, git tag untuk menandai titik rilis, release notes untuk pengguna, plus pemeriksaan API/ABI dan otomasi ringkas yang menjaga semua sumber informasi itu tetap cocok satu sama lain.

Semantic Versioning: Tiga Angka, Satu Janji #

Semantic Versioning (SemVer) menetapkan format MAJOR.MINOR.PATCH — misalnya 1.2.0 yang menempel pada kalkulator-diskon di Chapter Release Build. Setiap komponen punya aturan kapan ia wajib naik:

KomponenKapan naikContoh perubahan
MAJORPerubahan yang memutus kompatibilitasSignature fungsi publik berubah; field struct dihapus
MINORFitur baru yang kompatibel ke belakangFlag --verbose baru; overload fungsi tambahan
PATCHPerbaikan tanpa perubahan APIBug pembulatan diperbaiki; crash di jalur jarang

Janjinya mengikat dua arah: bila program Anda versi 1.2.0, pembaruan ke 1.2.1 atau 1.3.0 tidak menuntut penyesuaian apa pun di skrip dan workflow pemakainya, sedangkan 2.0.0 berarti “berhenti, tinjau dulu”. Selama MAJOR masih 0 (fase awal), banyak proyek memperlakukan semua rilis sebagai belum stabil dan tidak menjanjikan kompatibilitas. Disiplin kecil ini adalah fondasi semua alat pembaruan yang kita sentuh di chapter-chapter sebelumnya: manajer paket, Homebrew, hingga mekanisme self-update semuanya membandingkan angka-angka ini.

Program yang Melaporkan Versinya Sendiri #

Chapter Release Build menyiapkan konstanta VERSI yang dinaikkan secara sadar saat merilis. Kini kita lengkapi pola itu menjadi program utuh: konstanta sebagai satu-satunya sumber kebenaran, pengurai format MAJOR.MINOR.PATCH, konvensi flag --version, dan jaga-jaga saat kompilasi:

cpp
#include <iostream>
#include <string_view>

// Identitas rilis disematkan sebagai konstanta — dinaikkan secara sadar saat
// merilis. Pola yang sama dipakai kalkulator-diskon di Chapter Release Build.
constexpr std::string_view NAMA_APP = "info-versi";
constexpr std::string_view VERSI    = "1.0.0";

// Mengubah deretan digit menjadi angka; -1 bila kosong atau bukan digit.
constexpr int angka(std::string_view s) {
    if (s.empty()) return -1;
    int hasil = 0;
    for (char c : s) {
        if (c < '0' || c > '9') return -1;
        hasil = hasil * 10 + (c - '0');
    }
    return hasil;
}

// Memecah "MAJOR.MINOR.PATCH" menjadi tiga komponennya; false bila format salah.
constexpr bool uraiVersi(std::string_view v, int& major, int& minor, int& patch) {
    const auto titik1 = v.find('.');
    if (titik1 == std::string_view::npos) return false;
    const auto titik2 = v.find('.', titik1 + 1);
    if (titik2 == std::string_view::npos) return false;
    major = angka(v.substr(0, titik1));
    minor = angka(v.substr(titik1 + 1, titik2 - titik1 - 1));
    patch = angka(v.substr(titik2 + 1));
    return major >= 0 && minor >= 0 && patch >= 0;
}

// Jaga-jaga otomatis: string versi yang salah format menolak dikompilasi.
static_assert([] {
    int major = 0, minor = 0, patch = 0;
    return uraiVersi(VERSI, major, minor, patch);
}(), "VERSI wajib berformat MAJOR.MINOR.PATCH");

int main(int argc, char* argv[]) {
    // Konvensi --version: satu baris "nama versi", mudah diparsing skrip.
    if (argc >= 2 && std::string_view(argv[1]) == "--version") {
        std::cout << NAMA_APP << ' ' << VERSI << '\n';
        return 0;
    }
    if (argc >= 2) {
        std::cerr << "Flag tidak dikenal: " << argv[1] << '\n';
        std::cerr << "Pemakaian: " << NAMA_APP << " [--version]\n";
        return 1;
    }

    int major = 0, minor = 0, patch = 0;
    uraiVersi(VERSI, major, minor, patch);

    std::cout << NAMA_APP << " versi " << VERSI << '\n';
    std::cout << "MAJOR = " << major << " -> perubahan yang memutus kompatibilitas\n";
    std::cout << "MINOR = " << minor << " -> fitur baru yang kompatibel ke belakang\n";
    std::cout << "PATCH = " << patch << " -> perbaikan tanpa perubahan API\n";
    return 0;
}

Output yang Dihasilkan:

text
info-versi versi 1.0.0
MAJOR = 1 -> perubahan yang memutus kompatibilitas
MINOR = 0 -> fitur baru yang kompatibel ke belakang
PATCH = 0 -> perbaikan tanpa perubahan API

Yang layak dicermati: Baris 7-8 menetapkan NAMA_APP dan VERSI sebagai constexpr std::string_view — konstanta waktu kompilasi yang menjadi satu-satunya sumber kebenaran versi, tidak ada salinan kedua yang bisa kehilangan sinkron. Baris 11-18 (angka) mengubah deretan digit menjadi bilangan lalu menolak input kosong atau non-digit dengan nilai -1, sehingga kesalahan format terdeteksi, bukan diam-diam dibaca sebagai nol. Baris 21-29 (uraiVersi) mencari dua titik pemisah, memotong tiga bagian dengan substr, dan mengisi tiga parameter keluaran. Baris 32-35 (static_assert) memanggil pengurai yang sama saat kompilasi: string versi yang salah format membuat build menolak jalan sebelum satu test pun dieksekusi — otomasi termurah yang pernah kita pasang. Baris 37-47 menjalankan konvensi --version plus penolakan flag asing dengan exit code 1, disiplin argumen yang sama dengan program rilis di chapter-chapter sebelumnya.

Bila flag itulah yang diberikan, program berhenti di cabang pertama dan menghasilkan satu baris yang disengaja pendek:

bash
./info-versi --version

Output yang Dihasilkan:

text
info-versi 1.0.0

Konvensi “satu baris, nama spasi versi” bukan kebetulan: format inilah yang mudah diparsing skrip, dan nanti persis baris ini yang kita bandingkan dengan tag git di bagian otomasi.

Changelog: Catatan Perubahan yang Terstruktur #

Changelog adalah berkas (umumnya CHANGELOG.md di akar repository) yang menampung setiap perubahan penting per versi, diperbarui setiap kali rilis disiapkan. Konvensi yang banyak dipakai mengelompokkan entri ke kategori tetap — Added, Changed, Deprecated, Removed, Fixed, Security — sehingga pembaca bisa melompat ke yang relevan:

text
Semua perubahan penting proyek info-versi dicatat di berkas ini.
Penomoran mengikuti Semantic Versioning.

## [1.0.0] - 2026-01-10
### Added
- Flag --version dan pemeriksaan format versi saat kompilasi.

## [0.9.0] - 2025-12-20
### Added
- Utilitas pengurai MAJOR.MINOR.PATCH dengan validasi digit.

Perhatikan pola kecilnya: entri tertua di bawah, versi terbaru di atas, dan setiap judul versi memakai format [x.y.z] - tanggal yang bisa dicocokkan dengan tag repository. Changelog yang disiplin seperti ini dibaca dua audiens sekaligus — manusia yang menilai “apakah saya perlu memperbarui?” dan alat yang menyusun release notes secara otomatis dari kategori-kategorinya.

Menandai Rilis dengan git tag #

Tag git adalah penunjuk permanen ke sebuah commit — beda dengan branch, tag tidak bergerak setelah dibuat. Untuk rilis kita pakai annotated tag (git tag -a) yang menyimpan pesan, penanda waktu, dan identitas pembuatnya. Kita latih di repositori latihan sendiri di /tmp — bukan di repository kursus ini:

bash
mkdir -p /tmp/demo-rilis && cd /tmp/demo-rilis
cp /jalur/bundle/info-versi.cpp .    # sesuaikan dengan lokasi unduhan Anda
cat > CHANGELOG.md <<'EOF'
Semua perubahan penting proyek info-versi dicatat di berkas ini.
Penomoran mengikuti Semantic Versioning.

## [1.0.0] - 2026-01-10
### Added
- Flag --version dan pemeriksaan format versi saat kompilasi.

## [0.9.0] - 2025-12-20
### Added
- Utilitas pengurai MAJOR.MINOR.PATCH dengan validasi digit.
EOF
git init -q
git config user.name  "Budi Santoso"
git config user.email "[email protected]"
git add info-versi.cpp CHANGELOG.md
git commit -qm "Rilis 1.0.0"
git tag -a v1.0.0 -m "Rilis 1.0.0: pelacak versi pertama"
git tag -n

Output yang Dihasilkan:

text
v1.0.0          Rilis 1.0.0: pelacak versi pertama

Alurnya sederhana: repository diinisialisasi, dua berkas — source dan changelog — dicatat sebagai commit, lalu git tag -a v1.0.0 -m "..." membekukan titik itu dengan pesan rilisnya. git tag -n mengonfirmasi: nama tag plus pesan anotasinya terbaca tanpa membuka log. Konvensi penamaan v + nomor versi (v1.0.0) adalah kebiasaan mayoritas proyek dan dikenali banyak alat; perintah git describe --tags pada commit yang bertag bahkan cukup menjawab “v1.0.0” — nama rilis persis yang bisa langsung dipakai skrip build.

Release Notes: Ringkasan untuk Pengguna #

Changelog dan release notes sering disangka sama, padahal keduanya melayani kebutuhan berbeda: changelog adalah arsip lengkap milik proyek, sedangkan release notes adalah dokumen per rilis yang menemani paket di halaman unduhan — ditulis dari sudut pandang pengguna, lengkap dengan tindakan yang mungkin perlu mereka lakukan:

text
info-versi 1.0.0 — 10 Januari 2026
==================================

Rilis stabil pertama.

Baru
- Flag --version untuk skrip pembaruan: program melaporkan
  "info-versi 1.0.0" dalam satu baris.
- Keluaran kini menjelaskan arti setiap komponen versi.

Catatan pembaruan
- Dari 0.9.0: tanpa tindakan. Tidak ada perubahan yang memutus
  kompatibilitas; angka MAJOR baru naik setelah rilis ini.

Struktur ringkasnya berulang antar rilis: judul dengan versi dan tanggal, ringkasan satu kalimat, daftar “baru/berubah”, lalu bagian paling berharga — catatan pembaruan yang menyatakan eksplisit apakah pengguna harus melakukan sesuatu. Kalimat terakhirnya bahkan mengutip janji SemVer secara terang: kompatibilitas dijaga selama MAJOR tidak naik.

API dan ABI: Janji di Balik Angka MAJOR #

Selama ini “kompatibilitas” kita perlakukan sebagai satu gagasan; sebenarnya ada dua lapisan. API (Application Programming Interface) adalah kontrak di tingkat source: signature fungsi, nama class, isi header — yang berubah berarti kode pemakai tak lagi terkompilasi. ABI (Application Binary Interface) adalah kontrak di tingkat biner: ukuran dan urutan member struct, tata letak tabel virtual, skema penamaan simbol, konvensi pemanggilan — yang berubah berarti kode lama yang sudah dikompilasi berhenti bekerja terhadap library baru, tanpa satu pun error kompilasi. Pemetaan kasarnya ke SemVer:

PerubahanMAJOR naik?
Menambah fungsi atau flag baru yang berdiri sendiriTidak — MINOR
Memperbaiki perilaku tanpa menyentuh antarmukaTidak — PATCH
Mengubah signature fungsi publik atau menghapusnyaYa
Menambah member di struct publik yang dibagikan sebagai .soYa — ABI ikut pecah

Baris terakhir tabel adalah kejutan klasik: menambah member di akhir struct mengubah sizeof dan offset semua member setelahnya, dan menambah virtual function menggeser tabel virtual — API tampak sama, kode pemakai tetap terkompilasi, tetapi biner lama yang ditautkan ke library baru bisa menulis di luar area memorinya. Konsekuensinya praktis: bila library Anda dibagikan terpisah dari aplikasinya, perubahan semacam ini wajib menaikkan MAJOR (dan pada dunia .so, memicu perubahan nomor soname). Kabar baiknya, pola bundle yang kita rakit di Chapter Dynamic Library Deployment — library milik aplikasi bergerak bersama aplikasi lewat rpath $ORIGIN — mengunci pasangan aplikasi-library ke versi yang sama, sehingga perubahan ABI tidak pernah menyentuh library sistem maupun aplikasi lain.

Otomasi Ringkas yang Menjaga Kejujuran #

Semua mekanisme di atas membentuk rantai yang bisa putus di banyak titik: konstanta VERSI lupa dinaikkan, tag dibuat di commit yang salah, changelog tertinggal. Otomasinya bertingkat dari yang termurah: pemeriksaan format versi saat kompilasi sudah terpasang di program (static_assert); tingkat berikutnya adalah gerbang di pipeline build yang mencocokkan keluaran program dengan tag repository — keduanya nyata kita jalankan di repositori latihan tadi:

bash
g++ -std=c++23 -O2 info-versi.cpp -o info-versi
./info-versi --version | grep -q "$(git describe --tags | sed 's/^v//')" \
    && echo "VERSI cocok dengan tag rilis"

Output yang Dihasilkan:

text
VERSI cocok dengan tag rilis

Bedah gerbangnya: git describe --tags memberi v1.0.0, sed 's/^v//' melepas prefiks v agar cocok dengan format keluaran program, grep -q mencocokkannya dalam mode senyap, dan && hanya meloloskan langkah berikutnya bila pencocokan berhasil. Bila konstanta VERSI lupa dinaikkan, grep gagal, exit code berubah, dan pipeline berhenti — paket dengan versi bohong tidak pernah sempat terbit. Pola yang sama (bandingkan dua sumber kebenaran, gagalkan rilis bila beda) dipakai ulang untuk mencocokkan changelog dengan tag, atau nama berkas paket dengan versi di dalamnya, saat kita merakit paket lengkap di chapter berikutnya.

Dengan penomoran yang disiplin, changelog yang hidup, tag yang bisa dijelaskan, release notes yang berpihak ke pengguna, dan gerbang otomasi yang siap dipasang di pipeline, seluruh bahan baku rilis sudah lengkap. Chapter penutup section ini — Packaging Aplikasi — menyatukan semuanya: struktur paket bin/lib/share, metainformasi, checksum dan tanda tangan, mekanisme pembaruan, hingga checklist rilis dari hulu ke hilir.

Referensi: static_assert — C++ Reference , std::string_view — C++ Reference .