Tutorial Bazel: Mem-build Project Go

Laporkan masalah Lihat sumber Nightly · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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.