Bu eğitimde, Go (Golang) projesinin nasıl oluşturulacağı gösterilerek Bazel'in temel özellikleri tanıtılmaktadır. Çalışma alanınızı kurmayı, küçük program oluşturmayı, kitaplığı içe aktarmayı ve test etmeyi öğreneceksiniz. Kurs boyunca hedefler ve BUILD
dosyaları gibi
temel kavramları öğreneceksiniz.
Tahmini tamamlama süresi: 30 dakika
Başlamadan önce
Bazel'i yükleme
Başlamadan önce, henüz yapmadıysanız önce bazel'i yükleyin.
Bazel'in yüklü olup olmadığını kontrol etmek için herhangi bir dizinde bazel version
komutunu çalıştırabilirsiniz.
Go'yu yükleme (isteğe bağlı)
Bazel ile Go projeleri oluşturmak için Go'yu yüklemeniz gerekmez. Bazel Go kural grubu, makinenizde yüklü araç zincirini kullanmak yerine otomatik olarak bir Go araç zinciri indirir ve kullanır. Bu sayede, projedeki tüm geliştiriciler Go'nun aynı sürümünü kullanır.
Ancak go
get
ve go mod tidy
gibi komutları çalıştırmak için Go araç setini yüklemeniz gerekebilir.
Go'nun yüklü olup olmadığını kontrol etmek için herhangi bir dizinde go version
komutunu çalıştırabilirsiniz.
Örnek projeyi alma
Bazel örnekleri bir Git deposunda depolandığından, henüz yapmadıysanız Git'i yüklemeniz gerekir. Örnek deposunu indirmek için şu komutu çalıştırın:
git clone https://github.com/bazelbuild/examples
Bu eğitim için örnek proje examples/go-tutorial
dizinindedir.
İçerdiği öğeleri inceleyin:
go-tutorial/
└── stage1
└── stage2
└── stage3
Bu eğitimdeki farklı bölümler için üç alt dizin (stage1
, stage2
ve stage3
) vardır. Her aşama bir öncekinin üzerine inşa edilir.
Bazel ile derleme
Bir program bulacağımız stage1
dizininden başlayın. bazel build
ile derleyip ç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 derleyip ç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
Az önce oluşturduğumuz projeye bir 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 için ne oluşturmak istediğimizi anlatan bazı talimatlar içeriyor.
Genellikle her dizinde böyle bir dosya yazarsınız. Bu projede, programımızı hello.go
üzerinden oluşturan tek bir go_binary
hedefimiz vardır.
load("@rules_go//go:def.bzl", "go_binary")
go_binary(
name = "hello",
srcs = ["hello.go"],
)
MODULE.bazel
, projenizin bağımlılıklarını takip eder. Ayrıca projenizin kök dizini de işaretlenir. Böylece proje başına yalnızca bir MODULE.bazel
dosyası yazarsınız. Go'nun go.mod
dosyasına benzer bir amaca hizmet eder. Bazel projesinde go.mod
dosyasına gerçekten ihtiyacınız yoktur. Ancak yine de bağımlılık yönetimi için go get
ve go mod tidy
kullanmaya devam edebilmek amacıyla bir dosya sahibi olmak yararlı olabilir. Bazel Go kural grubu, go.mod
'ten bağımlılıkları içe aktarabilir ancak bu konuyu başka bir eğitimde ele alacağız.
MODULE.bazel
dosyamız, Go kural kümesi olan rules_go için tek bir bağımlılık içeriyor. Bazel'de Go için yerleşik destek 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 oluşturma işlemlerini ve diğer meta verileri içeren bir dosyadır. Bazel'in eklediği örtülü bağımlılıkları da içerdiğinden,
çok uzun olduğu için burada gösterilmeyeceğiz. go.sum
ile aynı şekilde, projenizdeki herkesin her bağımlılığın aynı sürümünü kullanmasını sağlamak için MODULE.bazel.lock
dosyanızı kaynak denetimine eklemeniz gerekir. MODULE.bazel.lock
dosyasını manuel olarak düzenlemeniz gerekmez.
BUILD dosyasını anlama
Bazel ile etkileşiminizin çoğu BUILD
dosyaları (veya eşdeğer olarak BUILD.bazel
dosyaları) üzerinden gerçekleşeceğinden, bu dosyaların ne işe yaradığını anlamanız önemlidir.
BUILD
dosyaları, Python'un sınırlı bir alt kümesi olan Starlark adlı bir kodlama dilinde yazılır.
BUILD
dosyası bir hedef listesi içerir. Hedef, Bazel'in derleyebileceği bir ikili, kitaplık veya test gibi bir şeydir.
Hedef, neyin oluşturulacağını açıklamak için özellik listesi içeren bir kural işlevi çağırır. Örneğimizin iki özelliği vardır: name
, komut satırında hedefi tanımlar, srcs
ise kaynak dosya yollarının bir listesidir (BUILD
dosyasını içeren dizine göre eğik çizgiyle ayrılmış).
Kural, Bazel'a nasıl hedef 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ı oluşturan Go derleme ve bağlama işlemlerini tanımlar.
Bazel'ın Java ve C++ gibi birkaç dil için yerleşik kuralları vardır. Bu kuralları Build Encyclopedia'da bulabilirsiniz. Diğer birçok dil ve araç için kural kümelerini Bazel Merkezi Sicil Dairesi'nde (BCR) bulabilirsiniz.
Kitaplık ekleme
stage2
dizinine gidin. Burada, falınızı basan yeni bir program oluşturacağız. Bu program, önceden tanımlanmış bir mesaj listesinden bir mesaj seçen bir kitaplık olarak ayrı bir Go paketi 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 Go ekosisteminde bu güçlü bir gelenektir ve bu geleneği uygulamak, 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 paketi nasıl derleyeceğini söyleyen kendi BUILD
dosyası vardır. Burada go_binary
yerine go_library
kullanılır.
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. Hangi Bazel paketlerinin bu hedefe bağlı olabileceğini belirler. Bizim durumumuzda, tüm hedeflerin bu kitaplığa bağlı olmasını istediğimizden özel değer //visibility:public
'ü kullanırız.
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ığı aşağıdakilerle oluşturabilirsiniz:
$ bazel build //fortune
Ardından, print_fortune.go
'ün 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
, fortune
kitaplığının importpath
özelliğinde belirtilen dizeyi kullanarak paketi içe aktarır.
Bu bağımlılığı Bazel'e de bildirmemiz gerekir. stage2
dizinindeki BUILD
dosyası aşağıda verilmiştir.
load("@rules_go//go:def.bzl", "go_binary")
go_binary(
name = "print_fortune",
srcs = ["print_fortune.go"],
deps = ["//fortune"],
)
Bunu aşağıdaki komutla çalıştırabilirsiniz.
bazel run //:print_fortune
print_fortune
hedefinin, bağlı olduğu diğer hedeflerin listesi olan bir deps
özelliği vardır. fortune
dizinindeki hedefe işaret eden "//fortune"
adlı bir etiket dizesini içerir.fortune
Bazel, tüm hedeflerin bağımlılıklarını deps
gibi özelliklerle açıkça belirtmesini zorunlu kılar. Bağımlılıklar kaynak dosyalarda ayrıca belirtildiği için bu durum hantal görünebilir ancak Bazel'in açıklığı bu açıdan avantaj sağlar. Bazel, herhangi bir kaynak dosyayı okumadan, komutları çalıştırmadan önce tüm komutları, girişleri ve çıkışları içeren bir işlem grafiği oluşturur. Bazel daha sonra, yerleşik dile özgü mantık olmadan işlem sonuçlarını önbelleğe alabilir veya işlemleri uzaktan yürütme için gönderebilir.
Etiketleri anlama
Etiket, Bazel'in bir hedefi veya dosyayı tanımlamak için kullandığı dizedir. Etiketler, komut satırı bağımsız değişkenlerinde ve BUILD
dosya özelliklerinde (ör. deps
) kullanılır. //fortune
, //:print-fortune
ve @rules_go//go:def.bzl
gibi birkaçını daha önce gördük.
Etiketler üç bölümden oluşur: depo adı, paket adı ve hedef (veya dosya) adı.
Depo adı @
ve //
arasına yazılır ve farklı bir Bazel modülündeki bir hedefi belirtmek için kullanılır (Geçmişe dayalı nedenlerden dolayı modül ve depo bazen eş anlamlı olarak kullanılır). @rules_go//go:def.bzl
etiketinde kod deposu adı rules_go
'dur. Aynı depodaki hedeflere atıfta bulunurken depo adı atlanabilir.
Paket adı, //
ve :
arasına yazılır ve farklı bir Bazel paketinden gelen bir hedefi belirtmek için kullanılır. @rules_go//go:def.bzl
etiketinde paket adı go
'tır. 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çerir) BUILD
dosyasını içeren dizine giden eğik çizgiyle ayrılmış bir yoldur. Bir paket, alt dizin içerebilir ancak yalnızca kendi paketlerini tanımlayan BUILD
dosyaları içermiyorsa.
Çoğu Go projesinde dizin başına bir BUILD
dosyası ve BUILD
dosyası başına bir Go paketi bulunur. Bir etiketteki paket adı, aynı dizindeki hedeflere atıfta bulunurken atlanabilir.
Hedef ad, :
ifadesinden sonra yazılır ve paket içindeki bir hedefi ifade eder.
Hedef ad, paket adının son bileşeniyle aynıysa atlanabilir (yani //a/b/c:c
, //a/b/c
ile, //fortune:fortune
ise //fortune
ile aynıdır).
Komut satırında, bir paketteki tüm hedefleri belirtmek için joker karakter olarak ...
kullanabilirsiniz. Bu, bir depoda bulunan tüm hedefleri oluşturmak veya test etmek için kullanışlıdır.
# Build everything
$ bazel build //...
Projenizi test etme
Ardından, bir test ekleyeceğimiz stage3
dizine 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ığından fortune.go
ile aynı Go paketinde derlenmesi gerekir. Bunun 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"],
)
Bir test yürütülebilir dosyasını derlemek ve bağlamak için go_test
kuralını kullanan yeni bir fortune_test
hedefimiz var. go_test
'ün fortune.go
ve fortune_test.go
öğelerini aynı komutla derlemesi gerekir. Bu nedenle, fortune
hedefinin özelliklerini fortune_test
'e dahil etmek için burada embed
özelliğini kullanırız. embed
genellikle 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 rules_go, embed
paketiyle yüklenebilecek dosyaları listelemek için embedsrcs
parametresini kullanır.
bazel test
ile testimizi ç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ını yakalayabilir.
$ bazel test //...
Sonuç ve daha fazla okuma
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ğitici içeriklere göz atın.
- Diğer diller için önerilen kurallar listesini de inceleyebilirsiniz.
- Go hakkında daha fazla bilgi edinmek için rules_go modülüne, özellikle de Core Go kuralları dokümanlarına göz atın.
- Projenizin dışında Bazel modülleriyle çalışma hakkında daha fazla bilgi edinmek için harici bağımlılıklar bölümüne bakın. Özellikle, Bazel'in modül sistemi aracılığıyla Go modüllerine ve araç zincirlerine bağımlı olma hakkında bilgi edinmek için bzlmod ile Go başlıklı makaleyi inceleyin.