Bu eğitimde, bir Go (Golang) projesinin nasıl oluşturulacağını göstererek Bazel'in temelleri tanıtılmaktadır. Çalışma alanınızı nasıl ayarlayacağınızı, küçük bir program oluşturmayı, kitaplık içe aktarmayı ve testini çalıştırmayı öğreneceksiniz. Bu süreçte, hedefler ve BUILD
dosyaları gibi temel Bazel kavramlarını öğreneceksiniz.
Tahmini tamamlama süresi: 30 dakika
Başlamadan önce
Bazel'i yükleme
Başlamadan önce henüz yapmadıysanız bazel'i yükleyin.
Herhangi bir dizinde bazel version
komutunu çalıştırarak Bazel'in yüklü olup olmadığını kontrol edebilirsiniz.
Go'yu yükleyin (isteğe bağlı)
Bazel ile Go projeleri oluşturmak için Go'yu yüklemeniz gerekmez. Bazel Go kural grubu, makinenize yüklenen araç zincirini kullanmak yerine otomatik olarak bir Go araç zinciri indirip kullanır. Bu, bir projede çalışan tüm geliştiricilerin aynı Go sürümünü kullanarak derleme yapmasını sağlar.
Ancak go
get
ve go mod tidy
gibi komutları çalıştırmak için yine de bir Go araç zinciri yüklemek isteyebilirsiniz.
Herhangi bir dizinde go version
komutunu çalıştırarak Go'nun yüklü olup olmadığını kontrol edebilirsiniz.
Örnek projeyi alma
Bazel örnekleri bir Git deposunda saklanır. Bu nedenle, henüz yapmadıysanız Git'i yüklemeniz gerekir. Örnekler deposunu indirmek için şu komutu çalıştırın:
git clone https://github.com/bazelbuild/examples
Bu eğitimdeki örnek proje examples/go-tutorial
dizinindedir.
İçeriğini görün:
go-tutorial/
└── stage1
└── stage2
└── stage3
Bu eğitimin farklı bölümleri için üç alt dizin (stage1
, stage2
ve stage3
) vardır. Her aşama, bir önceki aşamanın üzerine inşa edilir.
Bazel ile derleme
stage1
dizininde bir program bulalım. bazel build
ile oluşturup çalıştırabiliriz:
$ 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! 💚
Programı tek bir bazel run
komutuyla da oluşturup çalıştırabiliriz:
$ 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! 💚
Proje yapısını anlama
Yeni oluşturduğumuz projeye göz atın.
hello.go
, programın Go kaynak kodunu içerir.
package main
import "fmt"
func main() {
fmt.Println("Hello, Bazel! 💚")
}
BUILD
, Bazel'e ne oluşturmak istediğimizi söyleyen bazı talimatlar içerir.
Genellikle her dizinde bu şekilde bir dosya yazarsınız. Bu proje için, programımızı hello.go
'den oluşturan tek bir go_binary
hedefimiz var.
load("@rules_go//go:def.bzl", "go_binary")
go_binary(
name = "hello",
srcs = ["hello.go"],
)
MODULE.bazel
projenizin bağımlılıklarını izler. Ayrıca projenizin kök dizinini işaretler. Böylece proje başına yalnızca bir MODULE.bazel
dosyası yazarsınız. Bu dosya, Go'nun go.mod
dosyasıyla benzer bir amaca hizmet eder. Bazel projesinde go.mod
dosyasına ihtiyacınız olmasa da bağımlılık yönetimi için go get
ve go mod tidy
kullanmaya devam edebilmek adına bu dosyayı bulundurmanız faydalı olabilir. Bazel Go kural grubu, go.mod
'dan bağımlılıkları içe aktarabilir ancak bu konuyu başka bir eğitimde ele alacağız.
MODULE.bazel
dosyamız, Go kural grubu olan rules_go'ya tek bir bağımlılık içerir. Bazel, Go için yerleşik desteğe sahip olmadığından bu bağımlılığa ihtiyacımız var.
bazel_dep(
name = "rules_go",
version = "0.50.1",
)
Son olarak, MODULE.bazel.lock
, Bazel tarafından oluşturulan ve bağımlılıklarımızla ilgili karma değerleri ve diğer meta verileri içeren bir dosyadır. Bu liste, Bazel'in kendisi tarafından eklenen örtülü bağımlılıkları içerdiğinden oldukça uzundur ve burada gösterilmez. go.sum
gibi, projenizdeki herkesin her bağımlılığın aynı sürümünü almasını sağlamak için MODULE.bazel.lock
dosyanızı kaynak kontrolüne işlemeniz gerekir. MODULE.bazel.lock
öğesini manuel olarak düzenlemeniz gerekmez.
BUILD dosyasını anlama
Bazel ile etkileşiminizin çoğu BUILD
dosyaları (veya eşdeğeri olan BUILD.bazel
dosyaları) üzerinden gerçekleşir. Bu nedenle, bu dosyaların ne işe yaradığını anlamanız önemlidir.
BUILD
dosyaları, Python'ın sınırlı bir alt kümesi olan Starlark adlı bir komut dosyası dilinde yazılır.
BUILD
dosyası, hedeflerin listesini içerir. Hedef, Bazel'in oluşturabileceği bir şeydir (ör. ikili, kitaplık veya test).
Bir hedef, neyin oluşturulması gerektiğini açıklamak için bir özellik listesiyle bir kural işlevini çağırır. Örneğimizde iki özellik var: name
, komut satırındaki hedefi tanımlar ve srcs
, kaynak dosya yollarının listesidir (eğik çizgiyle ayrılmış, BUILD
dosyasını içeren dizine göre).
Kural, Bazel'a bir hedefi nasıl oluşturacağını söyler. Örneğimizde, go_binary
kuralını kullandık. Her kural, bir dizi çıkış dosyası oluşturan işlemleri (komutlar) tanımlar. Örneğin, go_binary
, yürütülebilir bir çıkış dosyası üreten derleme ve bağlantı işlemlerini tanımlar.
Bazel, Java ve C++ gibi birkaç dil için yerleşik kurallara sahiptir. Bu dillerin dokümanlarını Build Encyclopedia'da bulabilirsiniz. Diğer birçok dil ve araç için kural kümelerini Bazel Central Registry (BCR)'de bulabilirsiniz.
Kitaplık ekleme
stage2
dizinine gidin. Burada, falınızı yazdıracak yeni bir program oluşturacağız. Bu program, önceden tanımlanmış mesaj listesinden bir mesaj seçen ayrı bir Go paketini kitaplık olarak kullanır.
go-tutorial/stage2
├── BUILD
├── MODULE.bazel
├── MODULE.bazel.lock
├── fortune
│ ├── BUILD
│ └── fortune.go
└── print_fortune.go
fortune.go
, kitaplığın kaynak dosyasıdır. fortune
kitaplığı ayrı bir Go paketi olduğundan kaynak dosyaları ayrı bir dizindedir. Bazel, Go paketlerini ayrı dizinlerde tutmanızı gerektirmez ancak bu, Go ekosisteminde yaygın bir uygulamadır ve bu uygulamaya uymak diğer Go araçlarıyla uyumlu kalmanıza yardımcı olur.
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))]
}
fortune
dizininin, Bazel'e bu paketin nasıl oluşturulacağını bildiren kendi BUILD
dosyası vardır. Burada go_binary
yerine go_library
kullanıyoruz.
Ayrıca importpath
özelliğini, kitaplığın diğer Go kaynak dosyalarına aktarılabileceği bir dize olarak ayarlamamız gerekir. Bu ad, depodaki dizinle birleştirilmiş depo yolu (veya modül yolu) olmalıdır.
Son olarak, visibility
özelliğini ["//visibility:public"]
olarak ayarlamamız gerekir.
visibility
herhangi bir hedefte ayarlanabilir. Bu hedefle hangi Bazel paketlerinin ilişkili olabileceğini belirler. Bizim durumumuzda, herhangi bir hedefin bu kitaplığa bağlı olabilmesini istiyoruz. Bu nedenle, özel değer //visibility:public
kullanıyoruz.
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"],
)
Bu kitaplığı şu öğelerle oluşturabilirsiniz:
$ bazel build //fortune
Ardından, print_fortune.go
bu paketi nasıl kullandığını görün.
package main
import (
"fmt"
"github.com/bazelbuild/examples/go-tutorial/stage2/fortune"
)
func main() {
fmt.Println(fortune.Get())
}
print_fortune.go
, paketi fortune
kitaplığının importpath
özelliğinde belirtilen dizeyi kullanarak içe aktarıyor.
Ayrıca bu bağımlılığı Bazel'e bildirmemiz gerekir. stage2
dizinindeki BUILD
dosyası aşağıdadır.
load("@rules_go//go:def.bzl", "go_binary")
go_binary(
name = "print_fortune",
srcs = ["print_fortune.go"],
deps = ["//fortune"],
)
Bu işlemi aşağıdaki komutla çalıştırabilirsiniz.
bazel run //:print_fortune
print_fortune
hedefi, bağlı olduğu diğer hedeflerin listesi olan deps
özelliğine sahiptir. fortune
adlı dizindeki fortune
hedefiyle ilgili bir etiket dizesi olan "//fortune"
içerir.
Bazel, tüm hedeflerin bağımlılıklarını deps
gibi özelliklerle açıkça bildirmesini gerektirir. Bağımlılıklar kaynak dosyalarda da belirtildiği için bu durum zahmetli görünebilir ancak Bazel'in açık olması avantaj sağlar. Bazel, herhangi bir komut çalıştırmadan önce tüm komutları, girişleri ve çıkışları içeren bir işlem grafiği oluşturur ve kaynak dosyaları okumaz. Bazel daha sonra işlem sonuçlarını önbelleğe alabilir veya yerleşik dile özgü mantık olmadan uzaktan yürütme için işlemler gönderebilir.
Etiketleri anlama
Etiket, Bazel'in bir hedefi veya dosyayı tanımlamak için kullandığı bir dizedir. Etiketler, komut satırı bağımsız değişkenlerinde ve BUILD
gibi deps
dosya özelliklerinde kullanılır. //fortune
, //:print-fortune
ve @rules_go//go:def.bzl
gibi birkaç örnek gördük.
Etiketler üç bölümden oluşur: depo adı, paket adı ve hedef (veya dosya) adı.
Depo adı, @
ve //
arasında yazılır ve farklı bir Bazel modülündeki bir hedefi ifade etmek için kullanılır (geçmişe dayanan nedenlerden dolayı module ve repository bazen eş anlamlı olarak kullanılır). @rules_go//go:def.bzl
etiketinde depo adı rules_go
'dır. Aynı depodaki hedeflere başvurulurken depo adı atlanabilir.
Paket adı, //
ve :
arasında yazılır ve farklı bir Bazel paketindeki bir hedefi ifade etmek için kullanılır. Etikette @rules_go//go:def.bzl
,
paket adı go
. Bazel paketi, en üst düzey dizinindeki bir BUILD
veya BUILD.bazel
dosyası tarafından tanımlanan bir dosya ve hedef grubudur.
Paket adı, modül kök dizininden (MODULE.bazel
içeren) BUILD
dosyasını içeren dizine kadar eğik çizgiyle ayrılmış bir yoldur. Bir paket, alt dizinler içerebilir ancak yalnızca kendi paketlerini tanımlayan BUILD
dosyaları içermemeleri koşuluyla.
Çoğu Go projesinde dizin başına bir BUILD
dosyası ve BUILD
dosyası başına bir Go paketi bulunur. Aynı dizindeki hedeflere başvurulurken etiketteki paket adı atlanabilir.
Hedef adı, :
simgesinden sonra yazılır ve bir paket içindeki hedefi ifade eder.
Hedef adı, paket adının son bileşeniyle aynıysa atlanabilir (ör. //a/b/c:c
, //a/b/c
ile aynıdır; //fortune:fortune
, //fortune
ile aynıdır).
Komut satırında, bir paketteki tüm hedefleri belirtmek için ...
joker karakterini kullanabilirsiniz. Bu, bir depodaki tüm hedefleri oluşturmak veya test etmek için kullanışlıdır.
# Build everything
$ bazel build //...
Projenizi test etme
Ardından, test ekleyeceğimiz stage3
dizinine gidin.
go-tutorial/stage3
├── BUILD
├── MODULE.bazel
├── MODULE.bazel.lock
├── fortune
│ ├── BUILD
│ ├── fortune.go
│ └── fortune_test.go
└── print-fortune.go
fortune/fortune_test.go
yeni test kaynak dosyamızdır.
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)
}
}
Bu dosya, dışa aktarılmamış fortunes
değişkenini kullandığı için fortune.go
ile aynı Go paketinde derlenmesi gerekir. Bu özelliğin nasıl çalıştığını görmek için BUILD
dosyasına bakın:
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"],
)
Test edilebilir bir dosya derlemek ve bağlamak için go_test
kuralını kullanan yeni bir fortune_test
hedefimiz var. go_test
, fortune.go
ve fortune_test.go
öğelerini aynı komutla derlemelidir. Bu nedenle, fortune
hedefinin özelliklerini fortune_test
öğesine dahil etmek için embed
özelliğini kullanırız. embed
en sık go_test
ve go_binary
ile birlikte kullanılır ancak bazen oluşturulan kod için yararlı olan go_library
ile de çalışır.
embed
özelliğinin, yürütülebilir bir dosyaya kopyalanan veri dosyalarına erişmek için kullanılan Go'nun embed
paketiyle ilişkili olup olmadığını merak ediyor olabilirsiniz. Bu, talihsiz bir ad çakışmasıdır: rules_go'nun embed
özelliği, Go'nun embed
paketinden önce kullanıma sunulmuştur. Bunun yerine, embedsrcs
paketiyle yüklenebilecek dosyaları listelemek için embed
kullanılır.
Testimizi bazel test
ile çalıştırmayı deneyin:
$ 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.
Tüm testleri çalıştırmak için ...
joker karakterini kullanabilirsiniz. Bazel, test olmayan hedefleri de oluşturur. Bu nedenle, test içermeyen paketlerde bile derleme hataları yakalanabilir.
$ bazel test //...
Sonuç ve daha fazla bilgi
Bu eğitimde, Bazel ile küçük bir Go projesi oluşturup test ettik ve bu süreçte bazı temel Bazel kavramlarını öğrendik.
- Bazel ile başka uygulamalar oluşturmaya başlamak için C++, Java, Android ve iOS ile ilgili eğitimlere göz atın.
- Diğer diller için önerilen kurallar listesine de göz atabilirsiniz.
- Go hakkında daha fazla bilgi için rules_go modülüne, özellikle de Core Go rules dokümanlarına göz atın.
- Projenizin dışındaki Bazel modülleriyle çalışma hakkında daha fazla bilgi edinmek için harici bağımlılıklar başlıklı makaleyi inceleyin. Özellikle, Bazel'in modül sistemi aracılığıyla Go modüllerine ve araç zincirlerine nasıl bağımlı olunacağıyla ilgili bilgiler için Go with bzlmod başlıklı makaleyi inceleyin.