Bazel Eğitimi: Go Projesi Oluşturma

Sorun bildirme Kaynağı görüntüleme Nightly · 8.2 · 8.1 · 8.0 · 7.6 · 7.5

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ı nasıl oluşturacağınızı, küçük bir program oluşturacağınızı, kitaplık içe aktaracağınızı ve testini nasıl çalıştıracağınızı öğreneceksiniz. Bu süreçte 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, makinenize yüklü olan araç setini kullanmak yerine otomatik olarak bir Go araç seti indirip 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 her bir bölüm için üç alt dizin (stage1, stage2 ve stage3) vardır. Her aşama, önceki aşamanın ü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 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 belirten bazı talimatlar içerir. Genellikle her dizinde böyle bir dosya yazarsınız. Bu proje için programımızı hello.go'dan derleyen 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 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 ihtiyacınız yoktur ancak bağımlılık yönetimi için go get ve go mod tidy kullanmaya devam edebilmeniz amacıyla bir go.mod dosyasına sahip olmanız 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ı öğelerimizle ilgili karma oluşturma işlemlerini ve diğer meta verileri içeren bir dosyadır. Bazel tarafından eklenen gizli bağımlılıkları içerir. Bu nedenle oldukça uzundur ve burada gösterilmez. 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ı) aracılığıyla gerçekleşeceğinden, bu dosyaların işlevlerini anlamanız önemlidir.

BUILD dosyaları, Python'un 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 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ğimizde iki özellik vardır: name, komut satırındaki hedefi tanımlar ve srcs, kaynak dosya yollarının bir listesidir (BUILD dosyasını içeren dizine göre, eğik çizgiyle ayrılmış).

Kural, Bazel'e hedefin nasıl oluşturulacağı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, Java ve C++ gibi birkaç dil için yerleşik kurallara sahiptir. Bu kuralların dokümanlarını Derleme Ansiklopedisi'nde 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 içe 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ı öğrenin.

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 tanımlanan dizeyi kullanarak 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 adlı dizinde fortune adlı hedefe atıfta bulunan bir etiket dizesi olan "//fortune" içerir.

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ığı bir 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 hedefe referans vermek 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 hedeflerden bahsederken 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'dı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çeren) BUILD dosyasını içeren dizine 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. Aynı dizindeki hedeflerden bahsederken etiketteki paket adı atlanabilir.

Hedef adı, :'ten sonra yazılır ve bir paket içindeki hedefi ifade eder. Hedef ad, paket adının son bileşeniyle aynıysa atlanabilir (yani //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 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, 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ığından fortune.go ile aynı Go paketine 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 ilgili 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.

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ı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 için rules_go modülüne, özellikle de Core Go kuralları belgelerine bakı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.