Dua chapter lalu memperkenalkan alatnya — vcpkg dan Conan — tetapi alat hanyalah pelaksana. Keputusan yang menentukan kesehatan proyek dalam jangka panjang terjadi satu tingkat di atasnya: strategi apa yang kita pilih untuk menyertakan setiap library, versi mana yang kita kunci, dan seberapa sering kita berani naik versi. Strategi ini dipilih per proyek (kadang per dependensi), dan salah pilih tidak langsung terasa — ia muncul berbulan-bulan kemudian, saat build rekan satu tim gagal atau pembaruan kecil merusak API. Chapter ini merapikan keempat strategi yang sudah Anda temui secara terpisah menjadi satu peta keputusan.
Empat Strategi, Tiga Pertanyaan #
Setiap cara menyertakan dependensi menjawab tiga pertanyaan: siapa menyimpan source-nya, siapa membangunnya, dan siapa memilih versinya. Empat strategi utama di ekosistem C++ membagi jawabannya begini:
Diagram itu bukan aturan baku, melainkan titik mulai — mari bedah satu per satu.
Vendor: Salin Source ke Proyek #
Strategi tertua: salin seluruh source library ke folder proyek (misalnya external/fmt/) dan build bersama kode kita — lewat add_subdirectory seperti pada Chapter CMake Dependencies. Kelebihannya maksimal: build self-contained, tidak ada jaringan, tidak ada tool tambahan, dan versi terkunci sempurna karena source-nya benar-benar ikut tersimpan di repositori kita. Kelemahannya juga maksimal: setiap pembaruan berarti salin-tempel ulang, dan repositori membengkak sebanding ukuran library. Vendor wajar untuk library kecil berisi satu-dua header; untuk library besar, tali penghubung ke upstream perlu lebih rapi.
Git Submodule: Vendor dengan Tali #
git submodule adalah jawaban Git untuk masalah itu: repositori kita tidak menyalin file library, melainkan menyimpan referensi — alamat repositori library dan commit tepat yang dipakai:
git submodule add https://github.com/fmtlib/fmt.git external/fmt
git submodule update --init --recursiveBaris pertama mendaftarkan submodule dan menarik source-nya; baris kedua adalah perintah yang dijalankan siapa pun yang baru meng-clone proyek kita. Versi tetap terkunci — commit yang disimpan di gitlink — dan pembaruan tinggal masuk ke folder submodule lalu git checkout versi baru. Harga yang dibayar: submodule terkenal canggung (state dua repositori dalam satu folder kerja, sering lupa --init), dan build tetap membutuhkan jaringan saat clone pertama.
Package Manager dan FetchContent #
Dua strategi terakhir sudah kita dalami:
- Package manager (vcpkg, Conan) — manifest proyek mencatat dependensi; alat yang mengunduh dan membangun. Terbaik untuk banyak dependensi lintas banyak proyek, karena satu cache lokal dilayani bersama.
- FetchContent (CMake) —
CMakeLists.txtmengunduh source saat configure. Tanpa alat tambahan, tapi butuh jaringan di setiap configure pertama.
Trade-offs dalam Satu Tabel #
| Strategi | Source ikut di-repo? | Versi terkunci | Butuh alat ekstra | Pembeda utama |
|---|---|---|---|---|
| Vendor | Ya — salinan penuh | Ya, otomatis | Tidak | Pembaruan manual, repo membengkak |
| Submodule | Referensi + commit | Ya, per commit | Git saja | Satu clone lagi, state ganda |
| Package manager | Tidak — manifest saja | Ya, di manifest | vcpkg / Conan | Cache biner bersama, banyak library |
| FetchContent | Tidak — URL + tag | Ya, via GIT_TAG | CMake saja | Jaringan saat configure pertama |
Perhatikan bahwa tidak ada baris yang menang di semua kolom — itulah mengapa strategi ini keputusan, bukan resep. Proyek nyata sering mencampur: library inti di-vendor, sisanya lewat manajer paket.
Semantic Versioning: Bahasa Nomor Versi #
Semua strategi di atas akhirnya menyentuh satu hal yang sama: angka versi. Konvensi yang paling banyak diikuti ekosistem adalah Semantic Versioning (SemVer) — nomor berformat MAJOR.MINOR.PATCH:
- MAJOR naik bila ada perubahan yang merusak pemakai lama — fungsi dihapus, parameter berubah.
- MINOR naik bila ada fitur baru yang tidak merusak — fungsi tambahan, overload baru.
- PATCH naik untuk perbaikan bug tanpa perubahan antarmuka.
Dari konvensi ini lahir version range yang kita tulis di manifest: fmt 9.x berarti “major 9, minor apa pun — aman karena tidak merusak”, sedangkan lompatan 9 → 10 layak dicurigai dan dibaca release notes-nya dulu. Satu jebakan klasik: membandingkan versi sebagai string. Program kecil berikut membuktikannya — kelima rilis diurutkan dari yang terbaru, dan 1.10.0 harus berada di atas 1.9.4 padahal secara string "10" < "9":
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
struct Versi {
int mayor;
int minor;
int patch;
};
// Mengurai "1.10.0" menjadi {1, 10, 0}.
Versi urai(const std::string& teks) {
std::size_t titik1 = teks.find('.');
std::size_t titik2 = teks.find('.', titik1 + 1);
return {
std::stoi(teks.substr(0, titik1)),
std::stoi(teks.substr(titik1 + 1, titik2 - titik1 - 1)),
std::stoi(teks.substr(titik2 + 1))
};
}
// Perbandingan numerik: 1.10.0 lepas dari "1.9.4" padahal "10" < "9" secara string.
bool lebihBaru(const Versi& a, const Versi& b) {
if (a.mayor != b.mayor) return a.mayor > b.mayor;
if (a.minor != b.minor) return a.minor > b.minor;
return a.patch > b.patch;
}
int main() {
std::vector<Versi> rilis {
urai("2.1.0"), urai("1.9.4"), urai("2.0.0"),
urai("1.10.0"), urai("2.1.7")
};
std::sort(rilis.begin(), rilis.end(), lebihBaru);
std::cout << "Rilis dari terbaru:\n";
for (const Versi& v : rilis) {
std::cout << " " << v.mayor << '.' << v.minor << '.' << v.patch << "\n";
}
return 0;
}Output yang Dihasilkan:
Rilis dari terbaru:
2.1.7
2.1.0
2.0.0
1.10.0
1.9.4Fungsi urai memecah teks versi pada dua titik menjadi tiga angka — std::stoi yang mengubah potongan string menjadi int — lalu lebihBaru membandingkan ketiganya secara berjenjang: major dulu, bila sama baru minor, bila sama baru patch. Urutan hasilnya membuktikan 1.10.0 (minor 10) lebih baru daripada 1.9.4 (minor 9) — hal yang mustahil diperoleh dari perbandingan string.
Prinsip Minimal Dependencies #
Terakhir, pertanyaan yang paling sering diabaikan: apakah library ini perlu menjadi dependensi? Setiap dependensi menambah waktu build, permukaan keamanan yang harus dipantau, dan risiko konflik versi saat proyek membesar. Sebelum menambah satu baris di manifest, layak mempertimbangkan: apakah fungsinya cukup ditulis sendiri dalam lima puluh baris — seperti semver.cpp di atas, yang menyelesaikan tugasnya tanpa library apa pun; apakah kita hanya butuh sepuluh persen kemampuannya; dan apakah library itu aktif dirawat. Proyek yang matang tidak punya nol dependensi — tetapi setiap dependensinya punya alasan yang bisa diucapkan satu kalimat.
Peta strateginya sudah lengkap; yang tersisa adalah memastikan semua bagian itu benar. Library yang kita tarik, target yang kita link, dan aplikasi yang kita build pada akhirnya harus lulus uji — dan uji adalah pekerjaan yang layak dikerjakan alat, bukan dilakukan manual. Chapter berikutnya memperkenalkan CTest.
Referensi: FetchContent , Git Tools - Submodules .