Tutorial ini memperkenalkan dasar-dasar Bazel dengan menunjukkan cara mem-build
project Go (Golang). Anda akan mempelajari cara menyiapkan ruang kerja, membangun program
kecil, mengimpor library, dan menjalankan pengujian. Sepanjang prosesnya, Anda akan mempelajari konsep
utama Bazel, seperti target dan file BUILD
.
Estimasi waktu penyelesaian: 30 menit
Sebelum memulai
Menginstal Bazel
Sebelum memulai, instal bazel terlebih dahulu jika Anda belum melakukannya.
Anda dapat memeriksa apakah Bazel telah diinstal dengan menjalankan bazel version
di direktori mana pun.
Menginstal Go (opsional)
Anda tidak perlu menginstal Go untuk membuat project Go dengan Bazel. Kumpulan aturan Bazel Go akan otomatis mendownload dan menggunakan Go toolchain, bukan menggunakan toolchain yang diinstal di komputer Anda. Hal ini memastikan semua developer di build project menggunakan versi Go yang sama.
Namun, Anda mungkin masih ingin menginstal toolchain Go untuk menjalankan perintah seperti go
get
dan go mod tidy
.
Anda dapat memeriksa apakah Go telah diinstal dengan menjalankan go version
di direktori mana pun.
Mendapatkan project contoh
Contoh Bazel disimpan di repositori Git, jadi Anda harus menginstal Git jika belum melakukannya. Untuk mendownload repositori contoh, jalankan perintah ini:
git clone https://github.com/bazelbuild/examples
Project contoh untuk tutorial ini ada di direktori examples/go-tutorial
.
Lihat isinya:
go-tutorial/
└── stage1
└── stage2
└── stage3
Ada tiga subdirektori (stage1
, stage2
, dan stage3
), masing-masing untuk
bagian yang berbeda dalam tutorial ini. Setiap tahap dibuat berdasarkan tahap sebelumnya.
Mem-build dengan Bazel
Mulai di direktori stage1
, tempat kita akan menemukan program. Kita dapat membangunnya dengan bazel build
, lalu menjalankannya:
$ cd go-tutorial/stage1/
$ bazel build //:hello
INFO: Analyzed target //:hello (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:hello up-to-date:
bazel-bin/hello_/hello
INFO: Elapsed time: 0.473s, Critical Path: 0.25s
INFO: 3 processes: 1 internal, 2 darwin-sandbox.
INFO: Build completed successfully, 3 total actions
$ bazel-bin/hello_/hello
Hello, Bazel! 💚
Kita juga dapat mem-build dan menjalankan program dengan satu perintah bazel run
:
$ bazel run //:hello
bazel run //:hello
INFO: Analyzed target //:hello (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:hello up-to-date:
bazel-bin/hello_/hello
INFO: Elapsed time: 0.128s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Running command line: bazel-bin/hello_/hello
Hello, Bazel! 💚
Memahami struktur project
Lihatlah project yang baru saja kita buat.
hello.go
berisi kode sumber Go untuk program.
package main
import "fmt"
func main() {
fmt.Println("Hello, Bazel! 💚")
}
BUILD
berisi beberapa petunjuk untuk Bazel, yang memberi tahu Bazel apa yang ingin kita build.
Anda biasanya akan menulis file seperti ini di setiap direktori. Untuk project ini, kita
memiliki satu target go_binary
yang mem-build program dari hello.go
.
load("@rules_go//go:def.bzl", "go_binary")
go_binary(
name = "hello",
srcs = ["hello.go"],
)
MODULE.bazel
melacak dependensi project Anda. File ini juga menandai direktori root
project Anda, sehingga Anda hanya akan menulis satu file MODULE.bazel
per project. File ini
memiliki fungsi yang serupa dengan file go.mod
Go. Anda sebenarnya tidak memerlukan
file go.mod
dalam project Bazel, tetapi mungkin masih berguna untuk memilikinya sehingga
Anda dapat terus menggunakan go get
dan go mod tidy
untuk pengelolaan dependensi. Kumpulan aturan Bazel Go dapat mengimpor dependensi dari go.mod
, tetapi kita akan membahasnya di tutorial lainnya.
File MODULE.bazel
kita berisi satu dependensi pada
rules_go, kumpulan aturan Go. Kita memerlukan
dependensi ini karena Bazel tidak memiliki dukungan bawaan untuk Go.
bazel_dep(
name = "rules_go",
version = "0.50.1",
)
Terakhir, MODULE.bazel.lock
adalah file yang dihasilkan oleh Bazel yang berisi hash
dan metadata lainnya tentang dependensi kita. File ini menyertakan dependensi implisit
yang ditambahkan oleh Bazel itu sendiri, sehingga cukup panjang, dan kita tidak akan menampilkannya di sini. Sama seperti
go.sum
, Anda harus meng-commit file MODULE.bazel.lock
ke kontrol sumber untuk
memastikan semua orang di project Anda mendapatkan versi yang sama dari setiap dependensi. Anda
tidak perlu mengedit MODULE.bazel.lock
secara manual.
Memahami file BUILD
Sebagian besar interaksi Anda dengan Bazel akan melalui file BUILD
(atau
setara, file BUILD.bazel
), sehingga penting untuk memahami tindakan
yang dilakukan Bazel.
File BUILD
ditulis dalam bahasa skrip yang disebut
Starlark, subset terbatas dari Python.
File BUILD
berisi daftar
target. Target adalah sesuatu
yang dapat di-build oleh Bazel, seperti biner, library, atau pengujian.
Target memanggil fungsi aturan dengan daftar atribut untuk mendeskripsikan elemen yang harus dibuat. Contoh kami memiliki dua atribut: name
mengidentifikasi target di
command line, dan srcs
adalah daftar jalur file sumber (dipisahkan garis miring,
relatif terhadap direktori yang berisi file BUILD
).
Aturan memberi tahu Bazel cara mem-build
target. Dalam contoh ini, kami menggunakan
aturan
go_binary
. Setiap aturan menentukan tindakan
(perintah) yang menghasilkan sekumpulan file output. Misalnya, go_binary
menentukan
tindakan kompilasi dan penautan Go yang menghasilkan file output yang dapat dieksekusi.
Bazel memiliki aturan bawaan untuk beberapa bahasa seperti Java dan C++. Anda dapat menemukan dokumentasinya di Build Encyclopedia. Anda dapat menemukan kumpulan aturan untuk banyak bahasa dan alat lainnya di Bazel Central Registry (BCR).
Menambahkan library
Pindah ke direktori stage2
, tempat kita akan membuat program baru yang
mencetak keberuntungan Anda. Program ini menggunakan paket Go terpisah sebagai library yang
memilih ramalan dari daftar pesan yang telah ditentukan.
go-tutorial/stage2
├── BUILD
├── MODULE.bazel
├── MODULE.bazel.lock
├── fortune
│ ├── BUILD
│ └── fortune.go
└── print_fortune.go
fortune.go
adalah file sumber untuk library. Library fortune
adalah
paket Go terpisah, sehingga file sumbernya berada di direktori terpisah. Bazel
tidak mengharuskan Anda menyimpan paket Go dalam direktori terpisah, tetapi ini adalah
konvensi yang kuat dalam ekosistem Go, dan mengikutinya akan membantu Anda tetap
kompatibel dengan alat Go lainnya.
package fortune
import "math/rand"
var fortunes = []string{
"Your build will complete quickly.",
"Your dependencies will be free of bugs.",
"Your tests will pass.",
}
func Get() string {
return fortunes[rand.Intn(len(fortunes))]
}
Direktori fortune
memiliki file BUILD
-nya sendiri yang memberi tahu Bazel cara mem-build
paket ini. Kita menggunakan go_library
di sini, bukan go_binary
.
Kita juga perlu menetapkan atribut importpath
ke string yang dapat digunakan untuk
mengimpor library ke file sumber Go lainnya. Nama ini harus berupa
jalur repositori (atau jalur modul) yang digabungkan dengan direktori dalam
repositori.
Terakhir, kita perlu menetapkan atribut visibility
ke ["//visibility:public"]
.
visibility
dapat ditetapkan pada target apa pun. Ini menentukan paket Bazel yang dapat bergantung pada target ini. Dalam kasus ini, kita ingin semua target dapat bergantung pada library ini. Jadi, kita menggunakan nilai khusus //visibility:public
.
load("@rules_go//go:def.bzl", "go_library")
go_library(
name = "fortune",
srcs = ["fortune.go"],
importpath = "github.com/bazelbuild/examples/go-tutorial/stage2/fortune",
visibility = ["//visibility:public"],
)
Anda dapat mem-build library ini dengan:
$ bazel build //fortune
Selanjutnya, lihat cara print_fortune.go
menggunakan paket ini.
package main
import (
"fmt"
"github.com/bazelbuild/examples/go-tutorial/stage2/fortune"
)
func main() {
fmt.Println(fortune.Get())
}
print_fortune.go
mengimpor paket menggunakan string yang sama yang dideklarasikan dalam
atribut importpath
library fortune
.
Kita juga perlu mendeklarasikan dependensi ini ke Bazel. Berikut adalah file BUILD
di
direktori stage2
.
load("@rules_go//go:def.bzl", "go_binary")
go_binary(
name = "print_fortune",
srcs = ["print_fortune.go"],
deps = ["//fortune"],
)
Anda dapat menjalankannya dengan perintah di bawah.
bazel run //:print_fortune
Target print_fortune
memiliki atribut deps
, yaitu daftar target lain
yang menjadi dependensinya. File ini berisi "//fortune"
, string label yang merujuk ke target
di direktori fortune
bernama fortune
.
Bazel mewajibkan semua target mendeklarasikan dependensinya secara eksplisit dengan
atribut seperti deps
. Hal ini mungkin terlihat rumit karena dependensi juga
ditentukan dalam file sumber, tetapi penjelasan Bazel memberikan keuntungan. Bazel
mem-build grafik tindakan
yang berisi semua perintah, input, dan output sebelum menjalankan perintah apa pun,
tanpa membaca file sumber apa pun. Kemudian, Bazel dapat meng-cache hasil tindakan atau mengirim
tindakan untuk eksekusi jarak jauh tanpa logika
khusus bahasa bawaan.
Memahami label
Label adalah string yang digunakan Bazel
untuk mengidentifikasi target atau file. Label digunakan dalam argumen command line dan dalam
atribut file BUILD
seperti deps
. Kita telah melihat beberapa, seperti //fortune
,
//:print-fortune
, dan @rules_go//go:def.bzl
.
Label memiliki tiga bagian: nama repositori, nama paket, dan nama target (atau file).
Nama repositori ditulis antara @
dan //
serta digunakan untuk merujuk ke target dari modul Bazel yang berbeda (karena alasan historis, modul dan repositori terkadang digunakan secara identik). Dalam label,
@rules_go//go:def.bzl
, nama repositorinya adalah rules_go
. Nama repositori
dapat dihilangkan saat merujuk ke target dalam repositori yang sama.
Nama paket ditulis antara //
dan :
dan digunakan untuk merujuk ke
target dari paket Bazel yang berbeda. Pada label @rules_go//go:def.bzl
,
nama paketnya adalah go
. Paket
Bazel adalah kumpulan file dan
target yang ditentukan oleh file BUILD
atau BUILD.bazel
di direktori level teratas.
Nama paketnya adalah jalur yang dipisahkan garis miring dari direktori root modul
(berisi MODULE.bazel
) ke direktori yang berisi file BUILD
. Sebuah
paket dapat menyertakan subdirektori, tetapi hanya jika paket tersebut tidak berisi file BUILD
yang menentukan paketnya sendiri.
Sebagian besar project Go memiliki satu file BUILD
per direktori dan satu paket Go per
file BUILD
. Nama paket dalam label dapat dihilangkan saat merujuk ke
target dalam direktori yang sama.
Nama target ditulis setelah :
dan mengacu pada target dalam paket.
Nama target dapat dihilangkan jika sama dengan komponen terakhir
nama paket (sehingga //a/b/c:c
sama dengan //a/b/c
; //fortune:fortune
sama dengan //fortune
).
Pada command line, Anda dapat menggunakan ...
sebagai karakter pengganti untuk merujuk ke semua target dalam suatu paket. Hal ini berguna untuk mem-build atau menguji semua target dalam
repositori.
# Build everything
$ bazel build //...
Menguji project Anda
Selanjutnya, pindahkan ke direktori stage3
, tempat kita akan menambahkan pengujian.
go-tutorial/stage3
├── BUILD
├── MODULE.bazel
├── MODULE.bazel.lock
├── fortune
│ ├── BUILD
│ ├── fortune.go
│ └── fortune_test.go
└── print-fortune.go
fortune/fortune_test.go
adalah file sumber pengujian baru kita.
package fortune
import (
"slices"
"testing"
)
// TestGet checks that Get returns one of the strings from fortunes.
func TestGet(t *testing.T) {
msg := Get()
if i := slices.Index(fortunes, msg); i < 0 {
t.Errorf("Get returned %q, not one the expected messages", msg)
}
}
File ini menggunakan variabel fortunes
yang tidak diekspor sehingga harus dikompilasi ke dalam paket Go yang sama dengan fortune.go
. Lihat file BUILD
untuk mengetahui cara kerjanya:
load("@rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "fortune",
srcs = ["fortune.go"],
importpath = "github.com/bazelbuild/examples/go-tutorial/stage3/fortune",
visibility = ["//visibility:public"],
)
go_test(
name = "fortune_test",
srcs = ["fortune_test.go"],
embed = [":fortune"],
)
Kita memiliki target fortune_test
baru yang menggunakan aturan go_test
untuk mengompilasi dan
menautkan file yang dapat dieksekusi pengujian. go_test
perlu mengompilasi fortune.go
dan
fortune_test.go
bersama dengan perintah yang sama, jadi kita menggunakan atribut embed
di sini untuk menggabungkan atribut target fortune
ke dalam
fortune_test
. embed
paling sering digunakan dengan go_test
dan go_binary
,
tetapi juga berfungsi dengan go_library
, yang terkadang berguna untuk kode
yang dihasilkan.
Anda mungkin ingin tahu apakah atribut embed
terkait dengan paket
embed
Go, yang digunakan untuk mengakses file data
yang disalin ke file yang dapat dieksekusi. Ini adalah konflik nama yang tidak menguntungkan: atribut
embed
rules_go diperkenalkan sebelum paket embed
Go. Sebagai gantinya, rules_go
menggunakan embedsrcs
untuk mencantumkan file yang dapat dimuat dengan paket embed
.
Coba jalankan pengujian dengan bazel test
:
$ bazel test //fortune:fortune_test
INFO: Analyzed target //fortune:fortune_test (0 packages loaded, 0 targets configured).
INFO: Found 1 test target...
Target //fortune:fortune_test up-to-date:
bazel-bin/fortune/fortune_test_/fortune_test
INFO: Elapsed time: 0.168s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
//fortune:fortune_test PASSED in 0.3s
Executed 0 out of 1 test: 1 test passes.
There were tests whose specified size is too big. Use the --test_verbose_timeout_warnings command line option to see which ones these are.
Anda dapat menggunakan karakter pengganti ...
untuk menjalankan semua pengujian. Bazel juga akan mem-build target
yang bukan pengujian, sehingga dapat mendeteksi error kompilasi bahkan dalam paket yang tidak
memiliki pengujian.
$ bazel test //...
Kesimpulan dan bacaan lebih lanjut
Dalam tutorial ini, kita telah mem-build dan menguji project Go kecil dengan Bazel, dan kita telah mempelajari beberapa konsep inti Bazel di sepanjang prosesnya.
- Untuk mulai membangun aplikasi lain dengan Bazel, lihat tutorial untuk C++, Java, Android, dan iOS.
- Anda juga dapat memeriksa daftar aturan yang direkomendasikan untuk bahasa lainnya.
- Untuk informasi selengkapnya tentang Go, lihat modul rules_go, terutama dokumentasi Aturan Go Utama.
- Untuk mempelajari lebih lanjut cara menggunakan modul Bazel di luar project Anda, lihat dependensi eksternal. Secara khusus, untuk informasi tentang cara bergantung pada modul Go dan toolchain melalui sistem modul Bazel, lihat Menggunakan bzlmod.