Bazel Eğitimi: Go Projesi Oluşturma

Sorun bildir Kaynağı görüntüle Nightly · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

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.