Bazel Eğitimi: Go Projesi Oluşturma

Sorun bildir Kaynağı görüntüleyin Nightly · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.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ı 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.