Jika tim Anda merilis aplikasi CLI Rust secara manual, masalah yang sering muncul biasanya bukan pada proses build, tetapi pada konsistensi release: versi di Cargo.toml terlupa diperbarui, tag Git tidak sinkron, changelog tidak jelas, atau artefak biner tidak terunggah lengkap. Kombinasi cargo-release dan GitHub Actions membantu menutup celah ini dengan alur yang dapat diulang, diaudit, dan lebih aman untuk kolaborasi tim.
Artikel ini fokus pada implementasi praktis otomasi release Rust dengan cargo-release dan GitHub Actions: mulai dari semver bump, update metadata proyek, pembuatan tag, validasi lint/test, publish ke crates.io bila relevan, sampai membangun artefak biner lintas platform. Saya juga akan membahas risiko umum seperti tag ganda, versi tidak sinkron, pipeline yang tidak idempoten, dan strategi rollback ketika publish gagal di tengah jalan.
Kapan cargo-release cocok dipakai
cargo-release cocok ketika Anda ingin menjadikan release sebagai proses standar, bukan serangkaian langkah manual yang berbeda-beda antar anggota tim. Tool ini membantu mengorkestrasi beberapa langkah yang biasanya dilakukan terpisah:
- menaikkan versi berdasarkan semantic versioning,
- memperbarui
Cargo.tomldan file terkait, - membuat commit release,
- membuat Git tag,
- menjalankan hook sebelum/sesudah release,
- opsional melakukan publish ke crates.io.
Keuntungan utamanya bukan sekadar otomatisasi, tetapi determinisme proses. Tim memiliki satu sumber kebenaran untuk release, sehingga lebih mudah ditinjau dan dipulihkan bila terjadi kegagalan.
Untuk proyek CLI internal yang tidak dipublikasikan ke crates.io, Anda tetap bisa memakai
cargo-releasehanya untuk mengelola versi, tag, dan commit release.
Alur release yang direkomendasikan
Untuk DX tim yang baik, pisahkan proses release menjadi dua tahap logis:
- Tahap persiapan release: validasi branch, jalankan test/lint, hitung versi, perbarui
Cargo.toml, buat commit dan tag. - Tahap distribusi: publish crate ke crates.io bila diperlukan, buat GitHub Release, lalu build dan unggah artefak biner lintas platform.
Pemisahan ini penting karena tidak semua langkah punya sifat yang sama. Misalnya, publish ke crates.io sulit dibatalkan setelah berhasil, sedangkan upload artefak ke GitHub relatif mudah diulang. Dengan memisahkan tahap, Anda bisa mengurangi efek samping saat pipeline gagal di tengah jalan.
Urutan yang aman
Urutan umum yang cukup aman untuk banyak tim adalah:
- jalankan
cargo fmt --check,cargo clippy, dancargo test, - jalankan
cargo release --dry-run, - jalankan release sebenarnya untuk membuat commit dan tag,
- push commit dan tag ke remote,
- workflow berbasis tag membangun artefak dan membuat GitHub Release,
- publish ke crates.io dilakukan hanya jika proyek memang library/crate publik.
Kenapa tag sebaiknya menjadi pemicu tahap distribusi? Karena tag adalah penanda versi yang stabil. Ini mengurangi risiko artefak dibangun dari commit yang belum benar-benar menjadi release resmi.
Menyiapkan proyek Rust untuk cargo-release
Instalasi lokal untuk maintainer release biasanya sederhana:
cargo install cargo-releaseKonfigurasi dapat diletakkan di Cargo.toml. Contoh berikut realistis untuk CLI atau library sederhana:
[package]
name = "mycli"
version = "0.4.2"
edition = "2021"
description = "CLI contoh"
license = "MIT"
repository = "https://github.com/acme/mycli"
[workspace.metadata.release]
shared-version = true
tag-name = "v{{version}}"
commit-message = "release: v{{version}}"
tag-message = "Release v{{version}}"
push = false
publish = false
pre-release-commit-message = "chore: prepare release v{{version}}"
pre-release-replacements = [
{ file = "CHANGELOG.md", search = "Unreleased", replace = "{{version}} - {{date}}", min = 1 }
]Beberapa poin penting dari konfigurasi di atas:
tag-name = "v{{version}}"menjaga format tag konsisten, misalnyav0.4.3.push = falsesengaja dipilih agar pipeline atau maintainer dapat mengontrol kapan commit/tag didorong ke remote. Ini mengurangi efek samping bila langkah berikutnya gagal.publish = falseberguna bila Anda ingin memisahkan pembuatan tag dari publish ke registry. Untuk banyak tim, ini lebih aman daripada semua dilakukan dalam satu perintah.pre-release-replacementsdapat dipakai untuk mengganti bagian tertentu di changelog secara otomatis.
Jika proyek Anda berbentuk workspace, pastikan strategi versi jelas sejak awal: apakah semua crate berbagi versi yang sama, atau masing-masing punya versi sendiri. Masalah versi tidak sinkron sering muncul ketika workspace berkembang, tetapi kebijakan release belum ditegaskan.
Semver bump yang umum dipakai
cargo-release mendukung pola bump versi berdasarkan semver. Contoh perintah yang umum:
cargo release patch --dry-run
cargo release minor --dry-run
cargo release major --dry-runDry-run penting karena Anda bisa melihat perubahan yang akan dilakukan tanpa benar-benar membuat commit, tag, atau publish. Dalam tim, ini berguna untuk memverifikasi bahwa versi berikutnya memang sesuai dengan perubahan API dan kompatibilitasnya.
Menghasilkan changelog ringkas tanpa membuat pipeline rapuh
cargo-release bukan generator changelog penuh. Ia lebih cocok untuk mengorkestrasi update placeholder atau metadata release. Untuk changelog ringkas, pendekatan yang aman adalah:
- menjaga file
CHANGELOG.mddengan bagianUnreleased, - mengganti label tersebut menjadi versi dan tanggal saat release,
- membiarkan isi changelog tetap ditulis manusia agar ringkas dan relevan.
Pendekatan ini lebih mudah dikendalikan daripada mencoba menghasilkan changelog otomatis sepenuhnya dari commit yang belum tentu konsisten formatnya. Untuk tim kecil hingga menengah, ini biasanya memberi hasil lebih rapi dengan kompleksitas lebih rendah.
Contoh struktur sederhana:
# Changelog
## Unreleased
- Tambah opsi --json pada CLI
- Perbaiki error handling saat file konfigurasi tidak ditemukan
- Tingkatkan validasi argumen inputSaat release dijalankan, bagian Unreleased dapat diubah menjadi versi final, misalnya 0.4.3 - 2026-06-19.
Struktur GitHub Actions yang disarankan
Struktur yang stabil biasanya memakai dua workflow:
- workflow manual release untuk bump versi, commit, dan tag,
- workflow berbasis tag untuk build artefak, publish, dan GitHub Release.
Kenapa tidak dijadikan satu workflow? Karena pipeline satu tahap cenderung tidak idempoten. Jika gagal setelah tag dibuat tetapi sebelum artefak selesai diunggah, Anda akan kesulitan mengulang tanpa memikirkan efek terhadap tag atau publish sebelumnya.
Workflow 1: release manual dengan validasi dan dry-run
Workflow berikut dipicu manual lewat workflow_dispatch. Ia menerima input jenis bump semver, menjalankan validasi, lalu membuat commit release dan tag.
name: release
on:
workflow_dispatch:
inputs:
level:
description: "Semver bump: patch, minor, major"
required: true
default: "patch"
permissions:
contents: write
jobs:
prepare-release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: Swatinem/rust-cache@v2
- name: Install cargo-release
run: cargo install cargo-release --locked
- name: Validate formatting
run: cargo fmt --all --check
- name: Validate lint
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Validate tests
run: cargo test --all-features
- name: Dry run release
run: cargo release ${{ github.event.inputs.level }} --dry-run --execute
- name: Configure git user
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Run release
run: cargo release ${{ github.event.inputs.level }} --execute
- name: Push commit and tag
run: |
git push origin HEAD
git push origin --tagsCatatan penting:
fetch-depth: 0diperlukan agar riwayat Git dan tag tersedia penuh. Banyak masalah tag ganda atau deteksi versi salah berawal dari checkout dangkal.cargo release ... --dry-run --executememang terlihat janggal, tetapi pola ini umum untuk memerintahkan tool menjalankan simulasi penuh tanpa benar-benar melakukan perubahan permanen. Jika Anda ragu pada perilaku CLI yang terpasang, verifikasi lewatcargo release --helpdi proyek Anda.- Workflow ini sebaiknya dijalankan hanya dari branch release utama, misalnya
main. Anda bisa menambahkan guard eksplisit bila perlu.
Contoh guard branch:
- name: Ensure main branch
run: |
test "${GITHUB_REF}" = "refs/heads/main" || \
(echo "Release hanya boleh dari branch main" && exit 1)Workflow 2: build artifact lintas platform dari tag
Setelah tag seperti v0.4.3 didorong, workflow kedua akan aktif. Tugasnya membangun biner untuk beberapa target populer dan mengunggahnya sebagai artefak release.
name: build-and-publish
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
archive: tar.gz
- os: macos-latest
target: x86_64-apple-darwin
archive: tar.gz
- os: macos-latest
target: aarch64-apple-darwin
archive: tar.gz
- os: windows-latest
target: x86_64-pc-windows-msvc
archive: zip
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cache cargo
uses: Swatinem/rust-cache@v2
- name: Build release binary
run: cargo build --release --target ${{ matrix.target }}
- name: Package archive (Unix)
if: runner.os != 'Windows'
run: |
mkdir -p dist
cp target/${{ matrix.target }}/release/mycli dist/
tar -C dist -czf mycli-${{ github.ref_name }}-${{ matrix.target }}.tar.gz mycli
- name: Package archive (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
New-Item -ItemType Directory -Force dist | Out-Null
Copy-Item target/${{ matrix.target }}/release/mycli.exe dist/
Compress-Archive -Path dist/mycli.exe -DestinationPath mycli-${{ github.ref_name }}-${{ matrix.target }}.zip
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: mycli-${{ matrix.target }}
path: |
*.tar.gz
*.zip
github-release:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: artifacts/**/*Workflow ini sengaja dipicu dari tag, bukan branch. Dengan demikian, artefak selalu terasosiasi langsung dengan versi release tertentu.
Publish ke crates.io bila relevan
Untuk library atau crate yang memang ingin dipublikasikan, ada dua pendekatan:
- publish di workflow release yang sama,
- publish di workflow berbasis tag.
Untuk stabilitas, pendekatan kedua biasanya lebih baik karena publish terjadi hanya setelah tag resmi dibuat. Namun, ada trade-off: jika publish gagal, tag sudah terlanjur ada. Karena itu, Anda perlu strategi rollback yang jelas.
Contoh job publish sederhana:
publish-crate:
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Publish to crates.io
run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
Secret yang umumnya dibutuhkan:
CARGO_REGISTRY_TOKENuntuk publish ke crates.io,- token GitHub bawaan workflow untuk membuat release dan mengunggah artefak,
- bila menggunakan registry privat, secret tambahan sesuai registry Anda.
Simpan token crates.io sebagai repository secret dan batasi siapa yang boleh menjalankan workflow release. Jangan menaruh token di file konfigurasi atau environment yang tercetak ke log.
Dry-run, validasi, dan proteksi sebelum release
Bagian ini sering dianggap tambahan, padahal justru menentukan apakah pipeline release aman dipakai sehari-hari.
1. Selalu jalankan lint dan test sebelum bump/tag
Jangan membuat tag release lalu baru mengetahui build rusak. Validasi minimum yang masuk akal:
cargo fmt --all --checkcargo clippy --all-targets --all-features -- -D warningscargo test --all-features
Jika proyek punya smoke test CLI, jalankan juga. Banyak bug release terjadi bukan pada unit test, tetapi pada perilaku biner hasil build.
2. Gunakan dry-run sebagai tahap wajib
Dry-run membantu menemukan:
- placeholder changelog yang tidak cocok,
- tag yang akan bentrok dengan tag lama,
- versi berikutnya yang salah,
- workspace yang belum sinkron.
Di tim, dry-run membuat review release lebih mudah karena anggota lain bisa melihat perubahan yang akan dihasilkan tanpa efek permanen.
3. Lindungi branch utama
Release sebaiknya hanya boleh dijalankan dari branch yang sudah lolos review dan CI normal. Aktifkan branch protection agar commit release tidak menimpa perubahan yang belum tervalidasi.
Masalah umum dan cara menghindarinya
Tag ganda atau bentrok
Masalah ini muncul saat:
- release dijalankan dua kali untuk versi yang sama,
- tag sudah ada di remote tetapi runner belum mengambil seluruh tag,
- pipeline diulang tanpa pengecekan idempoten.
Pencegahannya:
- gunakan
fetch-depth: 0, - cek keberadaan tag sebelum membuat release,
- jadikan workflow release hanya bisa dipicu manual oleh maintainer tertentu.
Contoh pengecekan sederhana:
- name: Fail if tag already exists remotely
run: |
VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')
git fetch --tags
git rev-parse "v${VERSION}" >/dev/null 2>&1 && \
(echo "Tag v${VERSION} sudah ada" && exit 1) || trueJika Anda tidak ingin bergantung pada jq, gunakan pendekatan lain untuk membaca versi, atau cukup mengandalkan output cargo-release saat dry-run.
Versi tidak sinkron di workspace
Pada workspace multi-crate, satu crate bisa ter-bump sementara crate lain tidak, atau dependensi internal masih menunjuk versi lama. Solusinya adalah menetapkan kebijakan sejak awal:
- satu versi bersama untuk semua crate, atau
- versi independen dengan prosedur release per crate.
Pilihan pertama lebih mudah untuk tim kecil. Pilihan kedua lebih fleksibel, tetapi konfigurasi dan review-nya lebih kompleks.
Pipeline tidak idempoten
Ini terjadi saat sebagian langkah sudah memberi efek permanen, lalu workflow gagal dan diulang. Contohnya:
- tag sudah dibuat tetapi artefak belum selesai diunggah,
- crate sudah terpublikasi tetapi GitHub Release gagal dibuat,
- commit release sudah terdorong tetapi changelog di artefak berbeda.
Cara mengurangi risiko:
- pisahkan workflow pembuatan tag dan workflow distribusi,
- hindari melakukan
git pushsebelum validasi selesai, - buat langkah yang bisa diulang tanpa efek samping,
- cek keberadaan release/artifact sebelum membuat ulang bila perlu.
Publish crates.io gagal setelah tag terbuat
Ini skenario yang harus dipikirkan dari awal. Setelah versi terpublikasi, Anda tidak bisa sekadar menimpa versi yang sama. Jika publish gagal sebelum sukses penuh, langkah penanganannya bergantung pada titik gagal:
- Tag sudah ada, publish belum terjadi: perbaiki masalah lalu rerun job publish atau distribusi berbasis tag.
- Publish berhasil, artefak GitHub gagal: jangan ubah tag atau versi. Cukup rerun workflow distribusi atau unggah artefak yang gagal.
- Commit/tag sudah dibuat, tetapi ternyata isi release salah: buat versi berikutnya dengan patch bump. Menghapus tag dan memaksa ulang versi yang sama biasanya lebih berisiko, terutama jika ada sistem eksternal yang sudah mengonsumsi tag tersebut.
Untuk crates.io, rollback paling realistis biasanya bukan "membatalkan versi", melainkan merilis versi perbaikan secepat mungkin.
Strategi rollback yang praktis
Rollback release otomatis harus dibedakan antara sebelum push dan sesudah push.
Sebelum commit/tag didorong
Ini kondisi terbaik. Jika release lokal atau di runner gagal sebelum git push, Anda cukup membatalkan perubahan Git dan mengulang setelah perbaikan.
git tag -d v0.4.3
git reset --hard HEAD~1Perintah di atas hanya contoh konsep. Pastikan commit terakhir memang commit release sebelum menjalankannya.
Sesudah tag didorong
Jika tag sudah di remote, rollback menjadi lebih sensitif. Menghapus tag remote mungkin memungkinkan secara teknis, tetapi bisa membingungkan jika workflow lain atau pengguna sudah melihat tag tersebut.
git push --delete origin v0.4.3Gunakan hanya bila Anda yakin tag belum dipakai oleh pihak lain dan publish ke crates.io belum dilakukan. Dalam banyak kasus tim, lebih aman membuat release korektif baru daripada memaksa sejarah berubah.
Praktik terbaik untuk DX tim
- Standarkan siapa yang boleh merilis. Jangan biarkan semua orang bisa memicu workflow release tanpa kontrol.
- Dokumentasikan level bump. Kapan perubahan dianggap patch, minor, atau major harus jelas agar semver konsisten.
- Jangan campur perubahan fitur dan release commit. Commit release sebaiknya fokus pada versi, changelog, dan metadata.
- Simpan changelog tetap ringkas. Tulis perubahan yang penting bagi pengguna, bukan semua commit internal.
- Uji instalasi artefak. Build sukses tidak selalu berarti biner mudah dipakai di sistem target.
- Pisahkan concern. Versioning/tagging, publish registry, dan upload artefak sebaiknya tidak saling mengunci dalam satu langkah besar.
Penutup
cargo-release dan GitHub Actions adalah kombinasi yang kuat untuk mengotomasi release CLI atau library Rust, asalkan alurnya dirancang dengan disiplin. Kunci keberhasilannya bukan pada banyaknya otomasi, melainkan pada struktur proses yang aman: validasi dulu, dry-run dulu, buat tag yang konsisten, lalu distribusikan artefak dan publish secara terpisah bila perlu.
Jika Anda baru mulai, target minimal yang sangat masuk akal adalah: lint/test wajib, cargo release --dry-run, commit dan tag otomatis, lalu build artefak lintas platform dari tag. Setelah itu, barulah tambahkan publish ke crates.io dan pembuatan changelog yang lebih rapi sesuai kebutuhan tim.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!