Menyiapkan CI untuk proyek Rust kernel-style dengan QEMU dan Cargo berbeda dari aplikasi Rust biasa. Anda tidak hanya memeriksa fmt, clippy, dan build, tetapi juga harus memastikan image boot benar-benar bisa dijalankan di emulator, berhenti dengan exit code yang bisa diverifikasi, dan menghasilkan log yang cukup saat gagal.
Untuk proyek low-level yang terinspirasi dari pendekatan seperti linux-0.11-rs, tujuan CI bukan sekadar “build hijau”, melainkan release confidence: perubahan kecil pada linker script, target custom, kode boot, atau layout image harus cepat terdeteksi. Cara paling praktis adalah memisahkan pemeriksaan cepat untuk semua commit dan pemeriksaan emulasi yang lebih lambat untuk branch utama, pull request, atau rilis.
Tujuan pipeline CI pada proyek Rust low-level
Pada proyek kernel-style, bug sering muncul di area yang tidak disentuh pipeline aplikasi biasa: inisialisasi awal, layout memori, format image boot, interaksi dengan bootloader, atau asumsi perangkat virtual. Karena itu, pipeline CI sebaiknya memverifikasi beberapa lapisan berikut:
- Kualitas kode cepat:
cargo fmt,cargo clippy, dan build dasar. - Kompilasi target khusus: termasuk
no_std, target JSON, atau arsitektur non-host bila relevan. - Pembuatan artefak bootable: ELF, binary mentah, disk image, atau image yang dibungkus skrip build.
- Smoke test boot di QEMU: memverifikasi kernel benar-benar mulai, mengeluarkan sinyal lulus, lalu keluar dengan kode yang bisa dicek CI.
- Diagnostik saat gagal: log serial, artefak image, dan informasi build agar kegagalan bisa direproduksi lokal.
Jika semua ini digabung dalam satu job besar, feedback ke developer akan lambat. Praktik yang lebih sehat adalah memisahkan job menjadi fast checks dan emulation checks.
Struktur workflow yang disarankan
1. Fast checks untuk feedback cepat
Job ini berjalan pada hampir semua push dan pull request. Tujuannya memfilter kesalahan murah lebih dulu:
cargo fmt --all -- --checkcargo clippy --workspace --all-targetscargo builduntuk crate host-side seperti tool pembuat imagecargo build --target ...untuk kernel atau komponenno_std
Manfaatnya sederhana: sebagian besar kesalahan sintaks, lint, impor, atau build target akan terdeteksi dalam hitungan menit tanpa menunggu QEMU.
2. Emulation checks untuk validasi boot
Job ini menjalankan image hasil build di QEMU. Cakupannya bisa dimulai dari smoke test paling kecil:
- Build kernel atau image bootable.
- Jalankan QEMU dalam mode non-grafis.
- Tangkap output serial ke file log.
- Tunggu sinyal sukses, atau biarkan QEMU keluar dengan kode tertentu.
- Fail jika timeout, panic, triple fault, atau tidak ada sinyal sukses.
Untuk tim kecil, ini biasanya sudah cukup baik sebagai baseline. Anda tidak perlu langsung membuat test matrix yang berat bila target utama Anda adalah memastikan “kernel masih bisa boot”.
Menangani target khusus dan crate no_std
Proyek kernel-style sering memakai target custom atau setidaknya target non-default. Di sini ada beberapa pola umum:
- Kernel crate memakai
#![no_std]dan mungkin#![no_main]. - Host tool tetap memakai target host normal untuk membangun image, menggabungkan binary, atau menjalankan langkah packaging.
- Build script tambahan dipakai untuk menyalin artefak, menjalankan
objcopy, atau menyusun image disk.
Pemisahan ini penting untuk CI karena dependensinya berbeda. Host tool bisa diuji dengan cargo test biasa, sedangkan kernel biasanya diverifikasi lewat cargo build dan QEMU.
Jika Anda memakai target JSON atau toolchain tambahan, buat setup CI yang eksplisit. Jangan mengandalkan lingkungan runner seolah sudah berisi semua komponen. Untuk proyek low-level, kegagalan “works on my machine” sering terjadi karena:
- target belum ditambahkan
- komponen
rust-srcbelum tersedia bila dibutuhkan - tool eksternal seperti
qemu-system-*,objcopy, ataunasmbelum terpasang - skrip build mengasumsikan path lokal tertentu
Contoh langkah build yang umum
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo build --workspace
cargo build -p kernel --target x86_64-unknown-none
./scripts/build-image.shNama target dan skrip tentu harus disesuaikan dengan proyek Anda. Poin utamanya adalah host build dan kernel build diperlakukan sebagai langkah terpisah.
Membangun image boot dan merancang smoke test QEMU
Prinsip smoke test yang efektif
Smoke test terbaik untuk CI bukan test yang paling canggih, melainkan test yang deterministik, cepat, dan mudah di-debug. Untuk kernel-style project, pola yang paling umum adalah:
- kernel mencetak penanda awal ke serial, misalnya
BOOT_OKatauTEST_PASS - kernel kemudian memicu jalur keluar yang bisa diterjemahkan menjadi status sukses di CI
- bila terjadi panic, kernel mencetak panic message ke serial sebelum berhenti
Output serial jauh lebih berguna dibanding hanya melihat bahwa QEMU hang. Karena itu, konfigurasikan kernel agar log boot minimal bisa dikirim ke port serial virtual.
Mengapa serial log penting
Pada lingkungan CI, Anda tidak punya monitor grafis atau debugger interaktif secara default. Output serial memberi tiga keuntungan:
- mudah ditangkap ke file
- mudah diunggah sebagai artifact
- mudah dipakai sebagai dasar keputusan pass/fail dengan
grepatau parser sederhana
Contoh command QEMU untuk smoke test
timeout 30s qemu-system-x86_64 \
-nographic \
-serial file:qemu.log \
-drive format=raw,file=build/os.img \
-no-reboot \
-no-shutdownContoh di atas sengaja generik. Opsi detail seperti mesin, CPU, memory, kernel loading method, atau device tambahan harus mengikuti kebutuhan proyek Anda. Yang penting untuk CI adalah:
-nographicagar QEMU tidak butuh display- serial ke file agar log bisa dianalisis
timeoutagar job tidak menggantung tanpa batas- kontrol reboot/shutdown agar kegagalan tidak tersembunyi oleh restart otomatis
Menentukan kondisi lulus atau gagal
Ada dua pendekatan umum:
- Berdasarkan isi log: CI memeriksa apakah
qemu.logmengandung penanda sukses sepertiTEST_PASS. - Berdasarkan exit code: kernel atau test harness memicu mekanisme keluar yang menghasilkan status proses yang bisa diverifikasi.
Pendekatan log lebih mudah diterapkan lebih awal. Pendekatan exit code lebih rapi jika proyek Anda sudah punya test harness kernel sendiri. Dalam praktiknya, banyak tim memakai keduanya: log untuk diagnosis, exit code untuk keputusan pass/fail.
grep -q "TEST_PASS" qemu.logJika Anda memilih verifikasi berbasis log, buat penanda yang unik dan stabil. Hindari string terlalu umum yang bisa muncul di panic path atau output boot biasa.
Contoh workflow GitHub Actions
Berikut contoh struktur workflow yang memisahkan job cepat dan job emulasi. Contoh ini tidak mengasumsikan detail spesifik proyek, tetapi cukup realistis untuk dijadikan fondasi.
name: ci
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
jobs:
fast-checks:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Cache cargo
uses: Swatinem/rust-cache@v2
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y qemu-system-x86 binutils
- name: Format
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --workspace --all-targets -- -D warnings
- name: Build workspace
run: cargo build --workspace
- name: Build kernel target
run: cargo build -p kernel --target x86_64-unknown-none
boot-smoke:
runs-on: ubuntu-latest
needs: fast-checks
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: Swatinem/rust-cache@v2
- name: Install runtime dependencies
run: |
sudo apt-get update
sudo apt-get install -y qemu-system-x86 binutils
- name: Build boot image
run: ./scripts/build-image.sh
- name: Run QEMU smoke test
run: |
set -euxo pipefail
timeout 30s qemu-system-x86_64 \
-nographic \
-serial file:qemu.log \
-drive format=raw,file=build/os.img \
-no-reboot \
-no-shutdown || true
test -f qemu.log
grep -q "TEST_PASS" qemu.log
- name: Upload logs
if: always()
uses: actions/upload-artifact@v4
with:
name: qemu-logs
path: |
qemu.log
build/Ada beberapa hal yang perlu diperhatikan dari contoh ini:
needs: fast-checksmencegah job QEMU berjalan bila langkah dasar sudah gagal.timeout-minutespada level job dantimeout 30spada command sama-sama berguna. Timeout job mencegah deadlock besar; timeout command membatasi proses QEMU itu sendiri.|| truedipakai agar workflow tetap sempat memeriksa log dan mengunggah artifact. Tanpa ini, proses bisa langsung berhenti sebelum file diagnostik dikumpulkan.- artifact upload dengan
if: always()sangat penting untuk investigasi kegagalan.
Jika proyek Anda memakai target JSON atau komponen toolchain tertentu, tambahkan langkah setup yang eksplisit sebelum build.
Caching dependency tanpa membuat CI rapuh
Caching pada Rust bisa menghemat waktu cukup besar, terutama bila workspace berisi host tools, builder, dan kernel crate. Namun, cache yang terlalu agresif dapat menyembunyikan masalah atau membuat hasil sulit direproduksi.
Apa yang layak di-cache
- registry crate Cargo
- git dependency Cargo
- hasil kompilasi target directory, bila strategi cache Anda mendukungnya dengan aman
Untuk GitHub Actions, pendekatan yang umum adalah memakai action cache yang memang dibuat untuk Rust agar key cache mengikuti perubahan yang relevan seperti Cargo.lock.
Trade-off caching
- Lebih cepat: build incremental lebih pendek.
- Lebih rumit: cache bisa invalid atau kurang efektif saat target banyak.
- Berpotensi bias: bug setup dependency kadang tidak terlihat karena tertolong cache.
Untuk tim kecil, pilihan yang masuk akal adalah:
- cache dependency dan build host-side
- jangan terlalu mengoptimalkan sampai pipeline sulit dipahami
- sesekali jalankan build bersih pada branch utama atau workflow terjadwal untuk memastikan setup tetap valid dari nol
Pemisahan fast checks vs emulation checks
Ini salah satu keputusan paling penting dalam CI untuk proyek Rust kernel-style dengan QEMU dan Cargo. Bila semua pemeriksaan dijalankan untuk setiap commit kecil, feedback loop akan lambat. Bila QEMU jarang dijalankan, Anda kehilangan jaminan bahwa image masih bisa boot.
Strategi yang realistis untuk tim kecil
- Setiap pull request: fmt, clippy, build host tool, build kernel target, dan satu smoke test boot.
- Branch utama: tambahkan satu atau dua variasi emulasi tambahan bila memang dibutuhkan.
- Rilis atau nightly: jalankan verifikasi lebih luas, misalnya kombinasi image berbeda, mode debug, atau test scenario tambahan.
Pendekatan ini menjaga dua hal sekaligus: developer tetap mendapat feedback cepat, dan branch utama tetap punya tingkat kepercayaan yang layak.
Kapan menambah cakupan verifikasi
Tambahkan tes emulasi lebih luas jika proyek Anda sudah sering gagal di area berikut:
- perubahan bootloader atau format image
- inisialisasi memori awal
- driver perangkat virtual dasar
- regresi panic handler atau output serial
Jangan menambah cakupan hanya karena “terlihat lebih lengkap”. Pada proyek low-level, setiap tes tambahan punya biaya waktu dan maintenance yang nyata.
Strategi debugging saat boot test gagal di CI
Kegagalan paling umum pada boot test biasanya terlihat seperti salah satu pola berikut:
- QEMU timeout tanpa output berarti
- log berhenti sangat awal
- panic terjadi sebelum penanda sukses
- image tidak ditemukan atau format image salah
- QEMU langsung exit karena opsi runtime tidak cocok
1. Pastikan log serial benar-benar ada
Jika qemu.log kosong, masalahnya bisa berada di beberapa titik:
- kernel belum menginisialisasi serial
- jalur boot gagal sebelum serial aktif
- opsi QEMU salah sehingga output tidak diarahkan ke file yang diharapkan
Mulailah dari penanda paling awal yang mungkin, misalnya satu karakter atau string pendek segera setelah entry point relevan dijalankan.
2. Unggah artefak yang cukup
Jangan hanya unggah qemu.log. Bila memungkinkan, unggah juga:
- image boot hasil build
- binary kernel atau ELF
- map file atau symbol file jika tersedia
- log build image dari skrip packaging
Ini membantu membedakan apakah masalahnya ada pada kompilasi, packaging image, atau runtime QEMU.
3. Cetak command akhir yang dijalankan CI
Kesalahan path file image atau nama target sangat sering terjadi. Gunakan shell yang ketat seperti:
set -euxo pipefailDengan ini, command yang dijalankan akan terlihat jelas di log GitHub Actions dan pipeline akan gagal lebih cepat jika ada variabel kosong atau command antara yang rusak.
4. Bedakan timeout dengan panic
Timeout biasanya berarti salah satu dari dua hal:
- kernel hang atau masuk loop tak berujung
- kondisi sukses tidak pernah tercapai walau boot sebagian berhasil
Sebaliknya, panic yang masih sempat mencetak log biasanya lebih mudah ditangani. Karena itu, panic handler yang mengirim pesan ke serial sangat membantu pada CI.
5. Reproduksi lokal dengan command yang sama
Salah satu kebiasaan terbaik adalah menjaga agar command QEMU di CI juga bisa dipakai developer secara lokal. Misalnya, definisikan satu skrip:
./scripts/run-qemu-smoke.shLalu panggil skrip yang sama dari GitHub Actions. Dengan begitu, Anda mengurangi perbedaan antara lingkungan lokal dan CI.
Common mistakes yang sering muncul
- Menganggap build sukses berarti boot sukses. Pada proyek kernel-style, ini asumsi yang salah.
- Tidak memberi timeout. Satu hang di QEMU bisa menghabiskan menit CI tanpa informasi berguna.
- Tidak menangkap serial log. Akibatnya semua kegagalan terlihat sama: job merah tanpa konteks.
- Menggabungkan semua verifikasi dalam satu job. Feedback lambat dan sulit tahu titik gagalnya.
- Terlalu banyak matrix sejak awal. Pipeline jadi mahal dan rapuh sebelum kebutuhan nyatanya jelas.
- Tidak mengunggah artifact saat gagal. Ini memperlambat investigasi secara signifikan.
Rekomendasi baseline yang praktis
Jika Anda baru mulai, baseline berikut biasanya cukup seimbang:
- Job fast-checks: fmt, clippy, build workspace, build kernel target.
- Job boot-smoke: buat image, jalankan QEMU non-grafis, tangkap serial log, fail jika penanda sukses tidak muncul dalam timeout yang wajar.
- Upload artifact untuk log dan image pada setiap kegagalan.
- Gunakan cache Cargo, tetapi tetap sediakan jalur build bersih sesekali.
- Pakailah skrip build dan run yang sama antara lokal dan CI.
Dengan baseline ini, Anda sudah menutup banyak sumber regresi yang khas pada proyek Rust low-level tanpa membebani tim kecil dengan infrastruktur test yang terlalu besar.
Penutup
Pipeline CI untuk proyek Rust kernel-style dengan QEMU dan Cargo sebaiknya dirancang untuk dua kebutuhan sekaligus: feedback cepat bagi developer dan kepercayaan bahwa image benar-benar bisa boot. Kombinasi job cepat, build target khusus, image boot yang terotomasi, smoke test QEMU dengan timeout, verifikasi log atau exit code, serta artifact diagnostik memberi hasil yang jauh lebih berguna daripada sekadar menjalankan cargo build.
Untuk tim kecil, fokuslah pada alur yang sederhana namun konsisten. Mulai dari smoke test boot yang deterministik, pastikan log mudah dibaca saat gagal, lalu tambah cakupan verifikasi hanya ketika benar-benar dibutuhkan oleh pola bug proyek Anda.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!