Mesin baru terasa menakutkan bukan karena sulit dikonfigurasi, melainkan karena tidak ada yang mencatat apa saja yang dulu dipasang. Kita ingat compiler ada, lupa apakah versinya cukup; yakin git terpasang, tidak yakin alias lamanya ke mana. Pada bagian ini kita mengganti ingatan dengan skrip: satu daftar tool esensial, satu fungsi pemeriksa yang melaporkan ada-tidaknya dan versinya, dan satu skrip setup.sh yang tahu mode --check — pratinjau lengkap tanpa efek samping. Seluruhnya berjalan sebagai user biasa, dan karena pemeriksaan tool bersifat laporan — bukan instalasi paksa — skrip aman dijalankan di mesin apa pun.
Tool Esensial dan Cara Menanyakannya #
Lingkungan development di course ini berdiri di atas lima tool inti. Tabel berikut memetakan masing-masing beserta nama paket Debian dan perintah yang dipakai untuk menanyakan versinya:
| Tool | Paket Debian | Cek versi | Peran |
|---|---|---|---|
gcc | gcc / build-essential | gcc -dumpfullversion | compiler C dan C++ |
make | make / build-essential | make --version | build automation lewat Makefile |
git | git | git --version | version control |
python3 | python3 | python3 --version | skrip, alat bantu, pengujian |
curl | curl | curl --version | uji HTTP dan unduh berkas |
Di Debian, paket meta build-essential memasang gcc, make, dan header pustaka sekaligus — pilihan praktis bila mesin benar-benar kosong. Perbedaan tiap anggota toolchain beserta cara shell menemukan mereka lewat PATH sudah kita bedah di Chapter Compilers dan Build Tools
; di sini kita cukup mengambil satu mekanisme dari chapter itu: command -v nama mengembalikan jalur bila tool ada, dan gagal bila tidak — persis yang kita butuhkan untuk pemeriksaan terprogram — perilakunya terdokumentasi di bash(1)
.
Tool di luar daftar — Node.js, Go, Rust, Java — bersifat per jenis proyek. Kita tidak menjadikannya prasyarat; skrip kita dirancang agar daftar tool mudah diperluas, dan Bagian 3 nanti menyediakan template proyek yang siap bagi mesin yang memilikinya.
Tiga Fungsi Kecil: versi_cukup, versi_tool, cek_tool #
Pemeriksaan satu tool menjawab tiga pertanyaan berurutan: apakah terpasang, versi berapa, dan apakah versinya cukup. Kita tulis sebagai tiga fungsi kecil — blok berikut mendefinisikannya lalu memanggilnya untuk kelima tool inti ditambah satu contoh tool yang memang tidak ada di mesin ini:
# versi_cukup <versi> <minimum>: keluar 0 bila versi >= minimum.
# Hanya major.minor yang dibandingkan — patch tidak menentukan layak/tidak.
versi_cukup() {
awk -v v="$1" -v m="$2" 'BEGIN {
split(v, a, "."); split(m, b, ".");
if (a[1] + 0 < b[1] + 0) exit 1;
if (a[1] + 0 == b[1] + 0 && a[2] + 0 < b[2] + 0) exit 1;
}'
}
# versi_tool <nama>: cetak nomor versi untuk tool yang dikenal.
versi_tool() {
case $1 in
gcc) gcc -dumpfullversion ;;
make) make --version | sed -n '1s/^GNU Make //p' ;;
git) git --version | sed -n 's/^git version //p' ;;
python3) python3 --version | sed -n 's/^Python //p' ;;
curl) curl --version | awk 'NR==1 {print $2}' ;;
*) printf '0' ;;
esac
}
# cek_tool <nama> <minimum>: laporan satu baris + usul instalasi bila hilang.
# exit 0 siap, 1 hilang, 2 versi terlalu tua.
cek_tool() {
nama=$1
min=$2
if ! jalur=$(command -v "$nama" 2>/dev/null); then
printf '[HILANG] %-8s tidak ditemukan\n' "$nama"
printf ' usul: sudo apt-get install %s\n' "$nama"
return 1
fi
versi=$(versi_tool "$nama")
if versi_cukup "$versi" "$min"; then
printf '[OK] %-8s versi %s (minimum %s) — %s\n' \
"$nama" "$versi" "$min" "$jalur"
return 0
fi
printf '[TUA] %-8s versi %s di bawah minimum %s\n' \
"$nama" "$versi" "$min"
return 2
}
cek_tool gcc 10.0
cek_tool make 4.0
cek_tool git 2.30
cek_tool python3 3.8
cek_tool curl 7.70
cek_tool rustc 1.70; echo "exit=$?"Output yang Dihasilkan:
[OK] gcc versi 14.2.0 (minimum 10.0) — /usr/bin/gcc
[OK] make versi 4.4.1 (minimum 4.0) — /usr/bin/make
[OK] git versi 2.47.3 (minimum 2.30) — /usr/bin/git
[OK] python3 versi 3.13.5 (minimum 3.8) — /usr/bin/python3
[OK] curl versi 8.14.1 (minimum 7.70) — /usr/bin/curl
[HILANG] rustc tidak ditemukan
usul: sudo apt-get install rustc
exit=1Mari kita bedah ketiganya satu per satu:
versi_cukup: perbandingan versi dilakukan diawkdengan memecah string pada titik lalu membandingkanmajordanminorsebagai angka (+ 0memaksa konversi numerik, sehingga4dan4.0dianggap sama). Patch sengaja tidak dibandingkan — keputusan “layak dipakai” jarang bergantung pada nomor patch, dan logika dua tingkat lebih mudah dibuktikan benar.versi_tool: setiap tool mencetak versi dengan format berbeda —makemenyisipkan kata “GNU Make”,gitmenyisipkan “git version” — jadi tiap tool dapat satu barissed/awkyang memotong ke nomornya saja. Fungsi*)di akhircasemengembalikan0untuk nama yang tidak dikenal agar pemanggil tetap mendapat nilai, bukan string kosong.cek_tool: polaif ! jalur=$(command -v ...)menangkap hasil sekaligus keadaan gagal —command -vmengembalikan exit code bukan nol saat tool tidak ada, dan stderr yang tidak relevan dibuang ke/dev/null. Nomor keluar fungsinya dibuat berbeda per kondisi (0/1/2) agar pemanggil bisa membedakan “hilang” dari “tua” tanpa mem-parsing teks laporan.- Pemanggilan
cek_tool rustc 1.70; echo "exit=$?":rustcmemang tidak terpasang di mesin ini, sehingga baris itu memperlihatkan jalur laporan[HILANG]lengkap dengan usulapt-get install— diikutiecho "exit=$?"yang membuktikan kode keluarnya 1. Perhatikan bahwa laporan usul berupa teks; skrip tidak menjalankansudoapa pun.
setup.sh Tahap 1 dengan Mode –check #
Ketiga fungsi itu kini kita bungkus menjadi setup.sh versi tahap 1: membaca daftar tool beserta versi minimum, melaporkan semuanya, dan menghitung ringkasan. Mode --check mengubah seluruh perilaku menjadi dry-run — laporan saja, tidak mengubah apa pun. Blok berikut menulis skripnya lalu menjalankannya dua kali di mesin ini, lengkap dengan bukti bahwa laporan identik dan home tidak tersentuh:
mkdir -p dev-env/scripts
cat > dev-env/scripts/setup.sh <<'BASH'
#!/usr/bin/env bash
# setup.sh — tahap 1: periksa tool development.
#
# Pemakaian:
# setup.sh periksa tool (tahap 2: dotfiles, menyusul)
# setup.sh --check dry-run: laporan saja, tidak mengubah apa pun
#
# Kode keluar:
# 0 sukses 1 opsi tidak dikenal
# 3 tool inti hilang/terlalu tua
set -u
MODE=pasang
for arg in "$@"; do
case $arg in
--check) MODE=cek ;;
*)
printf 'setup.sh: opsi tidak dikenal: %s\n' "$arg" >&2
printf 'pemakaian: setup.sh [--check]\n' >&2
exit 1
;;
esac
done
if [ "$MODE" = cek ]; then
printf 'mode --check: laporan saja, tidak ada yang diubah\n'
fi
# --- tool inti: nama:versi-minimum ------------------------------------
DAFTAR_TOOL="gcc:10.0 make:4.0 git:2.30 python3:3.8 curl:7.70"
versi_cukup() {
awk -v v="$1" -v m="$2" 'BEGIN {
split(v, a, "."); split(m, b, ".");
if (a[1] + 0 < b[1] + 0) exit 1;
if (a[1] + 0 == b[1] + 0 && a[2] + 0 < b[2] + 0) exit 1;
}'
}
versi_tool() {
case $1 in
gcc) gcc -dumpfullversion ;;
make) make --version | sed -n '1s/^GNU Make //p' ;;
git) git --version | sed -n 's/^git version //p' ;;
python3) python3 --version | sed -n 's/^Python //p' ;;
curl) curl --version | awk 'NR==1 {print $2}' ;;
*) printf '0' ;;
esac
}
cek_tool() {
nama=$1
min=$2
if ! jalur=$(command -v "$nama" 2>/dev/null); then
printf '[HILANG] %-8s tidak ditemukan\n' "$nama"
printf ' usul: sudo apt-get install %s\n' "$nama"
return 1
fi
versi=$(versi_tool "$nama")
if versi_cukup "$versi" "$min"; then
printf '[OK] %-8s versi %s (minimum %s) — %s\n' \
"$nama" "$versi" "$min" "$jalur"
return 0
fi
printf '[TUA] %-8s versi %s di bawah minimum %s\n' \
"$nama" "$versi" "$min"
return 2
}
printf '== tahap 1: tool development ==\n'
hilang=0
# shellcheck disable=SC2086 # daftar tool memang dipisah spasi
for item in $DAFTAR_TOOL; do
nama=${item%%:*}
min=${item#*:}
cek_tool "$nama" "$min" || hilang=$((hilang + 1))
done
# shellcheck disable=SC2086
printf 'ringkasan: %d tool diperiksa, %d bermasalah\n' \
"$(printf '%s\n' $DAFTAR_TOOL | wc -w)" "$hilang"
if [ "$hilang" -gt 0 ]; then
printf 'setup.sh: selesai dengan catatan: %d tool bermasalah\n' "$hilang"
exit 3
fi
printf 'setup.sh: selesai — lingkungan siap\n'
exit 0
BASH
echo "== eksekusi pertama: --check =="
bash dev-env/scripts/setup.sh --check; echo "exit=$?"
echo "== eksekusi kedua: laporan identik, home tidak tersentuh =="
bash dev-env/scripts/setup.sh --check > laporan-1.txt
bash dev-env/scripts/setup.sh --check > laporan-2.txt
diff laporan-1.txt laporan-2.txt && echo "laporan identik antar eksekusi"
n=$(find "$HOME" -maxdepth 1 -name '.[a-z]*' | wc -l)
echo "dotfiles di home: $n"Output yang Dihasilkan:
== eksekusi pertama: --check ==
mode --check: laporan saja, tidak ada yang diubah
== tahap 1: tool development ==
[OK] gcc versi 14.2.0 (minimum 10.0) — /usr/bin/gcc
[OK] make versi 4.4.1 (minimum 4.0) — /usr/bin/make
[OK] git versi 2.47.3 (minimum 2.30) — /usr/bin/git
[OK] python3 versi 3.13.5 (minimum 3.8) — /usr/bin/python3
[OK] curl versi 8.14.1 (minimum 7.70) — /usr/bin/curl
ringkasan: 5 tool diperiksa, 0 bermasalah
setup.sh: selesai — lingkungan siap
exit=0
== eksekusi kedua: laporan identik, home tidak tersentuh ==
laporan identik antar eksekusi
dotfiles di home: 0Bagian-bagian kunci skripnya:
MODE=pasangdan loopfor arg in "$@": satu-satunya opsi saat ini--check; opsi lain ditolak ke stderr dengan pesan pemakaian dan kode keluar 1 — salah ketik--Checkharus terdengar saat itu juga, bukan diam-diam diabaikan.DAFTAR_TOOL="gcc:10.0 make:4.0 ...": seluruh kebijakan tool hidup dalam satu stringnama:minimum. Menambah tool baru berarti menambah satu kata — loopfor item in $DAFTAR_TOOLmemecahnya dengan${item%%:*}(nama) dan${item#*:}(minimum), dua pola parameter expansion yang sudah kita kenal.cek_tool "$nama" "$min" || hilang=$((hilang + 1)): laporan dicetak ke stdout, sedangkan kegagalan dihitung lewat operator||— pemisahan ini membuat ringkasan akhir bisa dihitung tanpa mem-parsing teks laporan sendiri.exit 3saat ada tool bermasalah: skrip tetap menyelesaikan seluruh laporan sebelum keluar, lalu memakai kode yang berbeda dari 0/1/2 agar pemanggilnya — manusia, cron, atau test suite — tahu bahwa ada tool yang perlu ditindaklanjuti.find "$HOME" -maxdepth 1 -name '.[a-z]*' | wc -ldi akhir blok: bukti numerik bahwa--checktidak meninggalkan satu dotfile pun di home. Dikombinasikan dengandiffantara dua laporan, keduanya adalah janji dry-run yang diperiksa, bukan sekadar dijanjikan.
Perhatikan pula bahwa skrip ini idempoten sejak lahir: seluruh pekerjaannya membaca keadaan sistem (command -v, --version) tanpa menulis apa pun. Sifat itu akan tertantang di Bagian 2, saat setup.sh mulai memasang dotfile — di sanalah idempotensi harus dirancang, bukan diasumsikan.
Instalasi via apt: Bagian yang Butuh Root #
Saat cek_tool melaporkan [HILANG], usulan perbaikannya adalah perintah apt-get seperti berikut:
sudo apt-get update
sudo apt-get install -y build-essential git python3 curlSkrip kita sengaja tidak menjalankan keduanya sendiri. apt-get install membutuhkan root, sedangkan lingkungan latihan dan sebagian besar kebiasaan kerja kita berjalan sebagai user biasa; menggabungkan keduanya dalam satu skrip berarti skrip gagal total di mesin tanpa kunci sudo. Skrip memilih jalur yang lebih tahan banting: laporkan yang hilang, usulkan perintahnya, lalu lanjut — pemilik mesin yang memutuskan kapan menjalankan bagian root. Setelah instalasi selesai, menjalankan setup.sh lagi akan memastikan kelima baris berubah menjadi [OK].
Dengan laporan tool yang bekerja dan terbukti tidak menyentuh apa pun, fondasi proyek ini selesai. Kekurangannya tinggal satu: lingkungan development bukan hanya tool, melainkan juga kebiasaan — alias, prompt, identitas git. Bagian 2 melengkapinya lewat dotfiles yang terkelola dan pemasangan yang aman diulang.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
scripts/proyek-baru.sh
Unduh berkas#!/usr/bin/env bash
# proyek-baru.sh — scaffold proyek baru siap kerja dalam satu perintah.
#
# Pemakaian:
# proyek-baru.sh <nama> [jenis]
# nama : nama folder proyek, kebab-case disarankan
# jenis : c | python | node (default: c)
#
# Membuat folder berisi repo git (branch main, satu commit awal),
# README.md, .gitignore, Makefile, dan src/ dengan berkas awal sesuai
# jenis. Tidak pernah menimpa: nama yang sudah dipakai ditolak.
#
# Variabel lingkungan:
# DEV_SRC folder config berisi template (default: <skrip>/../config)
# GIT_AUTHOR_NAME / GIT_AUTHOR_EMAIL — identitas commit bila
# konfigurasi global git belum diisi (default: Pengembang)
#
# Kode keluar:
# 0 sukses 1 pemakaian salah
# 2 nama sudah dipakai / template tidak ada
set -u
BASE=$(cd "$(dirname "$0")" && pwd)
DEV_SRC="${DEV_SRC:-$BASE/../config}"
gagal() {
printf 'proyek-baru.sh: %s\n' "$1" >&2
exit "$2"
}
if [ $# -lt 1 ] || [ $# -gt 2 ]; then
gagal 'pemakaian: proyek-baru.sh <nama> [c|python|node]' 1
fi
NAMA=$1
JENIS=${2:-c}
case $JENIS in
c|python|node) ;;
*) gagal "jenis tidak dikenal: $JENIS (pilih c, python, atau node)" 1 ;;
esac
case $NAMA in
''|-*) gagal "nama tidak valid: '$NAMA' (jangan kosong atau diawali -)" 1 ;;
*/*) gagal "nama tidak boleh memuat '/'" 1 ;;
esac
[ -e "$NAMA" ] && gagal "sudah ada: $NAMA — pilih nama lain" 2
template="$DEV_SRC/gitignore-$JENIS"
[ -f "$template" ] || gagal "template tidak ditemukan: $template" 2
mkdir -p "$NAMA/src"
cd "$NAMA" || exit 1
git init -q -b main
# --- berkas dasar ----------------------------------------------------------
cp "$template" .gitignore
if [ "$JENIS" = c ]; then
# Nama binary hasil build mengikuti nama proyek — abaikan lewat pola
# yang dijangkar ke akar repo (/<nama>) agar hanya binary di root yang
# tidak dilacak git.
printf '# binary hasil build make\n/%s\n' "$NAMA" >> .gitignore
fi
printf '# %s\n\nProyek %s (jenis: %s) dibuat oleh proyek-baru.sh.\n\n## Menjalankan\n\n make run\n' \
"$NAMA" "$NAMA" "$JENIS" > README.md
# --- Makefile dan berkas awal sesuai jenis -----------------------------------
if [ "$JENIS" = c ]; then
cat > Makefile <<MAKE
# Makefile — tugas build untuk proyek C.
BIN = $NAMA
CC = gcc
CFLAGS = -Wall -Wextra -std=c17
\$(BIN): src/main.c
\$(CC) \$(CFLAGS) -o \$(BIN) src/main.c
run: \$(BIN)
./\$(BIN)
clean:
rm -f \$(BIN)
.PHONY: run clean
MAKE
cat > src/main.c <<'C'
/* main.c — titik masuk proyek. */
#include <stdio.h>
int main(void)
{
printf("halo, dunia\n");
return 0;
}
C
elif [ "$JENIS" = python ]; then
cat > Makefile <<'MAKE'
# Makefile — tugas umum proyek Python.
PY = python3
run:
$(PY) src/main.py
.PHONY: run
MAKE
cat > src/main.py <<'PY'
"""main.py — titik masuk proyek."""
def main() -> None:
print("halo, dunia")
if __name__ == "__main__":
main()
PY
else
cat > Makefile <<'MAKE'
# Makefile — tugas umum proyek Node.js.
NODE = node
run:
$(NODE) src/index.js
.PHONY: run
MAKE
cat > src/index.js <<'JS'
// index.js — titik masuk proyek.
console.log("halo, dunia");
JS
fi
# --- identitas commit --------------------------------------------------------
# Pakai konfigurasi git bila sudah ada; jika belum, nilai bawaan yang
# bisa di-override lewat environment (berguna untuk pengujian).
if [ -z "$(git config --get user.name 2>/dev/null)" ]; then
: "${GIT_AUTHOR_NAME:=Pengembang}"
: "${GIT_AUTHOR_EMAIL:[email protected]}"
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
fi
git add -A
git commit -q -m "commit awal: kerangka $JENIS"
printf 'proyek siap: %s (jenis %s)\n' "$NAMA" "$JENIS"
printf ' cd %s && make run\n' "$NAMA"
exit 0
scripts/setup.sh
Unduh berkas#!/usr/bin/env bash
# setup.sh — bangun lingkungan development dalam satu perintah.
#
# Memeriksa toolchain inti (gcc, make, git, python3, curl), memasang
# dotfiles dari config/ dengan backup .bak untuk yang lama, lalu
# melaporkan hasilnya. Idempoten: dijalankan berapa kali pun dengan isi
# config/ yang sama, keadaan akhirnya selalu sama.
#
# Pemakaian:
# setup.sh periksa tool lalu pasang dotfiles
# setup.sh --check dry-run: laporan saja, tidak mengubah apa pun
#
# Variabel lingkungan (berguna untuk pengujian):
# DEV_HOME folder home tujuan pemasangan (default: $HOME)
# DEV_SRC folder config berisi dotfile (default: <skrip>/../config)
#
# Kode keluar:
# 0 sukses 1 opsi tidak dikenal
# 2 config/ tidak ditemukan 3 tool inti hilang/terlalu tua
set -u
MODE=pasang
for arg in "$@"; do
case $arg in
--check) MODE=cek ;;
*)
printf 'setup.sh: opsi tidak dikenal: %s\n' "$arg" >&2
printf 'pemakaian: setup.sh [--check]\n' >&2
exit 1
;;
esac
done
BASE=$(cd "$(dirname "$0")" && pwd)
DEV_SRC="${DEV_SRC:-$BASE/../config}"
DEV_HOME="${DEV_HOME:-$HOME}"
if [ ! -d "$DEV_SRC" ]; then
printf 'setup.sh: folder config tidak ditemukan: %s\n' "$DEV_SRC" >&2
exit 2
fi
if [ "$MODE" = cek ]; then
printf 'mode --check: laporan saja, tidak ada yang diubah\n'
fi
# --- tool inti: nama:versi-minimum ---------------------------------------
DAFTAR_TOOL="gcc:10.0 make:4.0 git:2.30 python3:3.8 curl:7.70"
# versi_cukup <versi> <minimum>: keluar 0 bila versi >= minimum.
# Hanya major.minor yang dibandingkan — patch tidak menentukan layak/tidak.
versi_cukup() {
awk -v v="$1" -v m="$2" 'BEGIN {
split(v, a, "."); split(m, b, ".");
if (a[1] + 0 < b[1] + 0) exit 1;
if (a[1] + 0 == b[1] + 0 && a[2] + 0 < b[2] + 0) exit 1;
}'
}
# versi_tool <nama>: cetak nomor versi untuk tool yang dikenal.
versi_tool() {
case $1 in
gcc) gcc -dumpfullversion ;;
make) make --version | sed -n '1s/^GNU Make //p' ;;
git) git --version | sed -n 's/^git version //p' ;;
python3) python3 --version | sed -n 's/^Python //p' ;;
curl) curl --version | awk 'NR==1 {print $2}' ;;
*) printf '0' ;;
esac
}
# cek_tool <nama> <minimum>: laporan satu baris + usul instalasi bila hilang.
# exit 0 siap, 1 hilang, 2 versi terlalu tua.
cek_tool() {
nama=$1
min=$2
if ! jalur=$(command -v "$nama" 2>/dev/null); then
printf '[HILANG] %-8s tidak ditemukan\n' "$nama"
printf ' usul: sudo apt-get install %s\n' "$nama"
return 1
fi
versi=$(versi_tool "$nama")
if versi_cukup "$versi" "$min"; then
printf '[OK] %-8s versi %s (minimum %s) — %s\n' \
"$nama" "$versi" "$min" "$jalur"
return 0
fi
printf '[TUA] %-8s versi %s di bawah minimum %s\n' \
"$nama" "$versi" "$min"
return 2
}
# --- tahap 1: periksa tool ------------------------------------------------
printf '== tahap 1: tool development ==\n'
hilang=0
# shellcheck disable=SC2086 # daftar tool memang dipisah spasi
for item in $DAFTAR_TOOL; do
nama=${item%%:*}
min=${item#*:}
cek_tool "$nama" "$min" || hilang=$((hilang + 1))
done
# shellcheck disable=SC2086
printf 'ringkasan: %d tool diperiksa, %d bermasalah\n' \
"$(printf '%s\n' $DAFTAR_TOOL | wc -w)" "$hilang"
# --- tahap 2: pasang dotfiles ----------------------------------------------
# pasang_dotfile <sumber> <target>: salin dengan backup; idempoten.
pasang_dotfile() {
sumber=$1
target=$2
if [ -f "$target" ] && cmp -s "$sumber" "$target"; then
printf '[OK] %s sudah terpasang\n' "$target"
return 0
fi
if [ "$MODE" = cek ]; then
printf '[BARU] %s akan dipasang\n' "$target"
return 0
fi
if [ -f "$target" ]; then
cp "$target" "$target.bak"
printf '[BAK] %s -> %s.bak\n' "$target" "$target"
fi
cp "$sumber" "$target"
printf '[PASANG] %s\n' "$target"
}
# pasang_hook_bashrc: pastikan ~/.bashrc memuat ~/.bashrc-dev (sekali saja).
pasang_hook_bashrc() {
bashrc="$DEV_HOME/.bashrc"
if [ -f "$bashrc" ] && grep -qF '.bashrc-dev' "$bashrc"; then
printf '[OK] %s sudah memuat .bashrc-dev\n' "$bashrc"
return 0
fi
if [ "$MODE" = cek ]; then
printf '[BARU] hook .bashrc akan memuat .bashrc-dev\n'
return 0
fi
if [ -f "$bashrc" ]; then
cp "$bashrc" "$bashrc.bak"
printf '[BAK] %s -> %s.bak\n' "$bashrc" "$bashrc"
fi
{
printf '\n'
printf '# >>> lingkungan-dev: muat kustomisasi shell >>>\n'
printf '[ -f "$HOME/.bashrc-dev" ] && . "$HOME/.bashrc-dev"\n'
printf '# <<< lingkungan-dev <<<\n'
} >> "$bashrc"
printf '[PASANG] hook .bashrc memuat .bashrc-dev\n'
}
printf '== tahap 2: dotfiles ==\n'
pasang_dotfile "$DEV_SRC/.bashrc-dev" "$DEV_HOME/.bashrc-dev"
pasang_dotfile "$DEV_SRC/.gitconfig" "$DEV_HOME/.gitconfig"
pasang_dotfile "$DEV_SRC/.editorconfig" "$DEV_HOME/.editorconfig"
pasang_hook_bashrc
if [ "$hilang" -gt 0 ]; then
printf 'setup.sh: selesai dengan catatan: %d tool bermasalah\n' "$hilang"
exit 3
fi
printf 'setup.sh: selesai — lingkungan siap\n'
exit 0
tests/run-tests.sh
Unduh berkas#!/usr/bin/env bash
# run-tests.sh — test suite untuk paket lingkungan-development-linux.
#
# Suite menyiapkan folder kerja terisolasi (mktemp), menyalin skrip
# dari scripts/ dan dotfile dari config/, lalu menjalankan rangkaian
# pengujian dengan assertion sederhana:
# - setup.sh --check melaporkan tanpa mengubah apa pun,
# - setup.sh idempoten: dua eksekusi, keadaan akhir identik,
# - dotfiles terpasang dengan isi identik + hook .bashrc sekali saja,
# - backup .bak menyimpan versi lama sebelum ditimpa,
# - tool inti hilang dilaporkan dengan keluar 3 (PATH tiruan),
# - proyek-baru.sh membuat struktur + repo git dengan commit awal,
# - proyek hasil scaffold bisa dibangun dan dijalankan (make run),
# - penolakan: nama terpakai, tanpa nama, jenis tak dikenal.
#
# Jalankan dari mana saja: bash tests/run-tests.sh
set -u
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJ_DIR=$(dirname "$SCRIPT_DIR")
PASS=0
FAIL=0
# assert_eq <harapan> <nyata> <pesan>: bandingkan dua nilai.
assert_eq() {
if [ "$1" = "$2" ]; then
PASS=$((PASS + 1))
printf 'OK %s\n' "$3"
else
FAIL=$((FAIL + 1))
printf 'GAGAL %s: harapan %s, nyata %s\n' "$3" "$1" "$2"
fi
}
# --- folder kerja terisolasi + salin paket ----------------------------------
WORK=$(mktemp -d)
mkdir -p "$WORK/paket/scripts" "$WORK/paket/config"
cp "$PROJ_DIR/scripts/setup.sh" "$PROJ_DIR/scripts/proyek-baru.sh" \
"$WORK/paket/scripts/"
cp -R "$PROJ_DIR/config/." "$WORK/paket/config/"
SETUP="$WORK/paket/scripts/setup.sh"
BARU="$WORK/paket/scripts/proyek-baru.sh"
RUMAH="$WORK/rumah"
mkdir -p "$RUMAH"
# potret: checksum seluruh berkas di $RUMAH — bahan uji idempotensi.
potret() {
(cd "$RUMAH" && find . -type f -print | sort | xargs sha256sum)
}
# --- uji 1: --check melaporkan tanpa mengubah ------------------------------
DEV_HOME="$RUMAH" bash "$SETUP" --check > "$WORK/cek1.txt"
assert_eq 0 "$?" "setup --check keluar 0"
grep -q '^mode --check' "$WORK/cek1.txt"
assert_eq 0 "$?" "mode --check diumumkan di laporan"
grep -q '^\[OK\] gcc' "$WORK/cek1.txt"
assert_eq 0 "$?" "gcc dilaporkan OK beserta versinya"
n_baru=$(grep -c '^\[BARU\]' "$WORK/cek1.txt")
assert_eq 4 "$n_baru" "--check melaporkan 4 item akan dipasang"
n_berkas=$(find "$RUMAH" -type f | wc -l)
assert_eq 0 "$n_berkas" "--check tidak membuat berkas apa pun"
# --- uji 2: pemasangan pertama ----------------------------------------------
DEV_HOME="$RUMAH" bash "$SETUP" > "$WORK/pasang1.txt"
assert_eq 0 "$?" "setup pertama keluar 0"
[ -f "$RUMAH/.gitconfig" ]
assert_eq 0 "$?" "gitconfig terpasang"
cmp -s "$WORK/paket/config/.gitconfig" "$RUMAH/.gitconfig"
assert_eq 0 "$?" "isi gitconfig identik dengan template"
[ -f "$RUMAH/.editorconfig" ]
assert_eq 0 "$?" "editorconfig terpasang"
[ -f "$RUMAH/.bashrc-dev" ]
assert_eq 0 "$?" "bashrc-dev terpasang"
grep -qF '. "$HOME/.bashrc-dev"' "$RUMAH/.bashrc"
assert_eq 0 "$?" "hook .bashrc memuat bashrc-dev"
n=$(grep -c '^\[PASANG\]' "$WORK/pasang1.txt")
assert_eq 4 "$n" "empat item terpasang pada eksekusi pertama"
# --- uji 3: idempoten — eksekusi ulang tidak mengubah keadaan -----------------
potret > "$WORK/state1.txt"
DEV_HOME="$RUMAH" bash "$SETUP" > "$WORK/pasang2.txt"
assert_eq 0 "$?" "setup kedua keluar 0"
potret > "$WORK/state2.txt"
diff "$WORK/state1.txt" "$WORK/state2.txt" > /dev/null 2>&1
assert_eq 0 "$?" "keadaan identik setelah eksekusi kedua"
n=$(grep -c 'sudah terpasang' "$WORK/pasang2.txt")
assert_eq 3 "$n" "eksekusi kedua melaporkan 3 dotfile sudah terpasang"
grep -q 'sudah memuat .bashrc-dev' "$WORK/pasang2.txt"
assert_eq 0 "$?" "hook .bashrc tidak ditulis dua kali"
# --- uji 4: --check pada keadaan terpasang tetap tidak mengubah ----------------
potret > "$WORK/state3.txt"
DEV_HOME="$RUMAH" bash "$SETUP" --check > "$WORK/cek2.txt"
assert_eq 0 "$?" "setup --check (sudah terpasang) keluar 0"
potret > "$WORK/state4.txt"
diff "$WORK/state3.txt" "$WORK/state4.txt" > /dev/null 2>&1
assert_eq 0 "$?" "--check tidak mengubah keadaan terpasang"
n=$(grep -c 'sudah terpasang' "$WORK/cek2.txt")
assert_eq 3 "$n" "--check melaporkan 3 dotfile sudah terpasang"
# --- uji 5: backup .bak menyimpan versi lama -----------------------------------
printf '[user]\n\tname = Budi Lama\n' > "$RUMAH/.gitconfig"
DEV_HOME="$RUMAH" bash "$SETUP" > "$WORK/pasang3.txt"
assert_eq 0 "$?" "setup ulang setelah perubahan keluar 0"
grep -q '^\[BAK\]' "$WORK/pasang3.txt"
assert_eq 0 "$?" "backup .bak dibuat sebelum menimpa"
grep -q 'name = Budi Lama' "$RUMAH/.gitconfig.bak"
assert_eq 0 "$?" "isi lama tersimpan di .gitconfig.bak"
cmp -s "$WORK/paket/config/.gitconfig" "$RUMAH/.gitconfig"
assert_eq 0 "$?" "gitconfig kembali identik dengan template"
# --- uji 6: tool inti hilang dilaporkan keluar 3 --------------------------------
# PATH tiruan tanpa gcc: skrip harus melaporkan [HILANG] lalu keluar 3.
FAKEBIN="$WORK/fakebin"
mkdir -p "$FAKEBIN"
for t in bash awk sed grep wc cmp cp dirname make git python3 curl; do
ln -s "$(command -v "$t")" "$FAKEBIN/$t"
done
PATH="$FAKEBIN" DEV_HOME="$WORK/rumah-fake" bash "$SETUP" --check \
> "$WORK/cek3.txt"
assert_eq 3 "$?" "tool inti hilang dilaporkan keluar 3"
grep -q '^\[HILANG\] gcc' "$WORK/cek3.txt"
assert_eq 0 "$?" "gcc hilang terlihat di laporan"
# --- uji 7: opsi salah ditolak ---------------------------------------------------
bash "$SETUP" --salah > /dev/null 2>&1
assert_eq 1 "$?" "opsi tidak dikenal keluar 1"
# --- uji 8: proyek-baru membuat struktur + repo git -------------------------------
cd "$WORK" || exit 1
GIT_AUTHOR_NAME="Budi Santoso" GIT_AUTHOR_EMAIL="[email protected]" \
GIT_COMMITTER_NAME="Budi Santoso" GIT_COMMITTER_EMAIL="[email protected]" \
GIT_AUTHOR_DATE="2026-01-15 09:00:00 +0000" \
GIT_COMMITTER_DATE="2026-01-15 09:00:00 +0000" \
bash "$BARU" aplikasi-tes c > "$WORK/baru1.txt"
assert_eq 0 "$?" "proyek-baru (c) keluar 0"
for f in README.md .gitignore Makefile src/main.c; do
[ -f "aplikasi-tes/$f" ]
assert_eq 0 "$?" "$f dibuat"
done
[ -d aplikasi-tes/.git ]
assert_eq 0 "$?" "repo git dibuat"
cabang=$(git -C aplikasi-tes symbolic-ref --short HEAD)
assert_eq main "$cabang" "branch utama bernama main"
n_commit=$(git -C aplikasi-tes log --oneline | wc -l)
assert_eq 1 "$n_commit" "satu commit awal dibuat"
n_kotor=$(git -C aplikasi-tes status --porcelain | wc -l)
assert_eq 0 "$n_kotor" "git status bersih setelah commit awal"
# --- uji 9: proyek hasil scaffold benar-benar bisa dijalankan ----------------------
make -s -C aplikasi-tes run > "$WORK/run-c.txt"
assert_eq 0 "$?" "make run (c) keluar 0"
grep -q 'halo, dunia' "$WORK/run-c.txt"
assert_eq 0 "$?" "program c menyapa halo, dunia"
n_kotor=$(git -C aplikasi-tes status --porcelain | wc -l)
assert_eq 0 "$n_kotor" "binary hasil build diabaikan .gitignore"
GIT_AUTHOR_NAME="Budi Santoso" GIT_AUTHOR_EMAIL="[email protected]" \
GIT_COMMITTER_NAME="Budi Santoso" GIT_COMMITTER_EMAIL="[email protected]" \
GIT_AUTHOR_DATE="2026-01-15 09:00:00 +0000" \
GIT_COMMITTER_DATE="2026-01-15 09:00:00 +0000" \
bash "$BARU" web-tes python > "$WORK/baru2.txt"
assert_eq 0 "$?" "proyek-baru (python) keluar 0"
[ -f web-tes/src/main.py ]
assert_eq 0 "$?" "main.py dibuat"
make -s -C web-tes run > "$WORK/run-py.txt"
assert_eq 0 "$?" "make run (python) keluar 0"
grep -q 'halo, dunia' "$WORK/run-py.txt"
assert_eq 0 "$?" "program python menyapa halo, dunia"
# --- uji 10: penolakan --------------------------------------------------------------
bash "$BARU" aplikasi-tes c > /dev/null 2>&1
assert_eq 2 "$?" "nama yang sudah dipakai ditolak keluar 2"
bash "$BARU" > /dev/null 2>&1
assert_eq 1 "$?" "tanpa nama ditolak keluar 1"
bash "$BARU" tes-zig zig > /dev/null 2>&1
assert_eq 1 "$?" "jenis tidak dikenal ditolak keluar 1"
# --- ringkasan ------------------------------------------------------------------------
if [ "$FAIL" -eq 0 ]; then
printf 'PASSED: %d/%d test lulus\n' "$PASS" "$PASS"
else
printf 'FAILED: %d/%d test lulus\n' "$PASS" $((PASS + FAIL))
fi
cd / && rm -rf "$WORK"
[ "$FAIL" -eq 0 ]