Topik ini membahas build system yang dipakai proyek C++ profesional beserta cara mengelola pustaka luarnya. Pembahasan mencakup struktur CMakeLists.txt pada CMake, Makefile tulisan tangan yang dijalankan langsung oleh make, dan dua manajer package populer yaitu vcpkg serta Conan. Setelah membaca topik ini, pembaca dapat membangun program multi-berkas dengan satu perintah make dan mengetahui peran masing-masing alat dalam rantai build yang utuh.
Deskripsi proyek dalam CMakeLists.txt#
CMake adalah alat yang membaca deskripsi proyek bernama CMakeLists.txt lalu menghasilkan berkas build untuk alat pelaksana yang tersedia di sistem, misalnya Makefile untuk make. Deskripsi ini bukan perintah kompilasi, melainkan pernyataan tentang apa yang hendak dibangun, sehingga proyek yang sama dapat dibangun di Linux, macOS, maupun Windows tanpa mengubah isinya. Tiga perintah paling dasarnya adalah cmake_minimum_required yang menetapkan versi CMake terendah, project yang memberi nama proyek beserta bahasanya, dan add_executable yang mendeklarasikan target program beserta berkas sumbernya.
# tidak-dijalankan (potongan untuk dibaca; CMake dijalankan pada lingkungan yang memasangnya)
cmake_minimum_required(VERSION 3.16)
project(rekap-nilai CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(app main.cpp)Perintah set pada contoh di atas mengunci standar bahasa ke C++17 agar build tidak bergantung pada standar bawaan compiler. Build dijalankan dengan dua perintah, yaitu cmake -B build untuk menghasilkan berkas build di direktori build dan cmake --build build untuk mengeksekusinya. Menempatkan hasil build di direktori terpisah disebut build out-of-source dan menjadi kebiasaan baku karena berkas yang dihasilkan CMake tidak bercampur dengan kode sumber.
Makefile: aturan build yang dijalankan make#
Make menjalankan build berdasarkan aturan yang tertulis di berkas Makefile. Setiap aturan terdiri atas tiga bagian, yaitu target berupa berkas yang hendak dihasilkan, prasyarat berupa berkas yang dibutuhkan target, dan resep berupa perintah pembentuknya. Make membandingkan waktu perubahan prasyarat dengan target, lalu hanya menjalankan resep yang targetnya kedaluwarsa. Sifat ini yang membuat build berikutnya menjadi cepat karena berkas yang tidak berubah tidak dikompilasi ulang.
Program statistik berikut dipakai sepanjang topik ini dan terdiri atas satu berkas sumber.
// tidak-dijalankan (dibangun bersama Makefile melalui blok bash di bawah)
#include <iostream>
#include <vector>
double rataRata(const std::vector<int>& nilai) {
if (nilai.empty()) {
return 0.0;
}
double jumlah = 0.0;
for (int n : nilai) {
jumlah += n;
}
return jumlah / nilai.size();
}
int main() {
std::vector<int> nilaiUjian{80, 75, 90, 85, 70};
std::cout << "rata-rata: " << rataRata(nilaiUjian) << '\n';
return 0;
}Makefile yang mendeskripsikan pembangunannya berbentuk berikut. Variabel CXX menampung compiler yang dipakai dan CXXFLAGS menampung flag kompilasi, sehingga mengganti compiler atau menambah flag cukup dilakukan di satu tempat.
CXX = g++
CXXFLAGS = -Wall -Wextra
app: main.o
$(CXX) main.o -o app
main.o: main.cpp
$(CXX) $(CXXFLAGS) -c main.cpp -o main.o
clean:
rm -f main.o app
Aturan app membutuhkan main.o, sedangkan aturan main.o mengompilasi main.cpp dengan opsi -c menjadi berkas objek. Target clean bersifat konvensi dan berperan menghapus semua hasil build, misalnya sebelum pindah compiler atau sebelum mengirim proyek ke repositori. Perintah build dijalankan dari direktori yang memuat Makefile.
# bangun program dengan make
make
# jalankan program hasil build
./appg++ -Wall -Wextra -c main.cpp -o main.o
g++ main.o -o app
rata-rata: 80Make mencetak setiap resep tepat sebelum menjalankannya, sehingga dua baris pertama keluaran di atas adalah perintah kompilasi dan penautan, sedangkan baris ketiga adalah keluaran programnya. Apabila make dijalankan lagi tanpa perubahan apa pun, keluarannya hanya make: 'app' is up to date karena tidak ada target yang kedaluwarsa. Flag -Wall -Wextra pada resep juga membuat peringatan compiler muncul sejak build pertama, kebiasaan yang diuraikan lebih jauh pada topik debugging.
Manajer package: vcpkg dan Conan#
Pustaka luar menuntut pekerjaan yang berulang bila dikelola manual, yaitu mencari versinya, mengunduh sumbernya, membangunnya, lalu menautkannya ke proyek. Manajer package menyatukan pekerjaan itu, dan dua pilihan yang paling banyak dipakai adalah vcpkg dari Microsoft dan Conan. Keduanya menyimpan daftar pustaka beserta versinya dalam berkas yang disebut manifest, sehingga mesin lain dapat mengulang lingkungan build yang sama persis.
Pada vcpkg, daftar dependensi ditulis dalam berkas vcpkg.json di akar proyek, kemudian vcpkg dipasangkan dengan CMake melalui berkas toolchain sehingga pustaka yang sudah terpasang ditemukan lewat find_package. Manifest untuk pustaka pemformatan fmt berbentuk berikut.
{
"name": "rekap-nilai",
"version-string": "0.1.0",
"dependencies": [
"fmt"
]
}Conan bekerja dengan pola serupa melalui berkas conanfile.txt, sedangkan generatornya menghasilkan berkas integrasi yang dibaca CMake pada tahap konfigurasi. Perbedaan praktisnya tipis untuk proyek kecil, sehingga pilihan sering ditentukan oleh ekosistem yang sudah dipakai tim. Yang terpenting adalah menempatkan daftar dependensi dalam berkas manifest, karena build yang bergantung pada pustaka yang terpasang secara manual di satu mesin tidak dapat direproduksi di mesin lain.
Kesalahan umum#
- Resep Makefile tidak diawali tab. Make menolak Makefile dengan pesan
missing separatorapabila baris resep diawali spasi, bukan karakter tab. Penyebabnya sering berupa editor yang mengubah tab menjadi spasi. Perbaikannya berupa pemeriksaan pengaturan editor, lalu penulisan ulang baris resep dengan tab. - Flag baru tidak berpengaruh pada build lama. Menambahkan flag di
CXXFLAGStidak membuat Makefile membangun ulang target yang sudah ada, karena make hanya membandingkan waktu berkas. Hasilnya program lama yang berjalan tanpa flag baru. Perbaikannya berupamake cleanterlebih dahulu, lalu build dari awal. - Pustaka luar dipasang tanpa manifest. Build berhasil di satu mesin, tetapi gagal di mesin lain karena versi pustaka yang terpasang berbeda. Penyebabnya adalah dependensi yang diunduh secara manual tanpa dicatat. Perbaikannya berupa pemindahan daftar pustaka ke
vcpkg.jsonatauconanfile.txtsejak pustaka pertama dipakai.
Ringkasan#
- CMake membaca CMakeLists.txt yang memuat
cmake_minimum_required,project, danadd_executable, lalu menghasilkan berkas build untuk pelaksana seperti make. - Makefile memasangkan target dengan prasyarat dan resep, sehingga make hanya membangun ulang berkas yang berubah, dan
make cleanmenghapus seluruh hasil build. - vcpkg dan Conan mencatat dependensi dalam manifest, lalu menyatukannya dengan CMake agar pustaka luar dapat diunduh, dibangun, dan ditautkan secara otomatis.
Referensi#
- CMake Tutorial — CMake — Panduan resmi menulis CMakeLists.txt dan menjalankan build bertahap.
- cmake-packages — CMake — Cara CMake menemukan paket pustaka dan menyediakannya bagi proyek.
- Invoking G++ — GCC
— Dokumentasi opsi g++ yang dipakai pada resep Makefile, termasuk
-cdan-o.