बेज़ल ट्यूटोरियल: एक गो प्रोजेक्ट बनाएं

किसी समस्या की शिकायत करें सोर्स देखें Nightly · 7.4 .

इस ट्यूटोरियल में, Go (Golang) प्रोजेक्ट बनाने का तरीका बताकर, Bazel की बुनियादी बातों के बारे में बताया गया है. इसमें आपको अपना वर्कस्पेस सेट अप करने, छोटा प्रोग्राम बनाने, लाइब्रेरी इंपोर्ट करने, और उसका टेस्ट चलाने का तरीका पता चलेगा. इस पूरी प्रोसेस में, आपको बैज और BUILD फ़ाइलों जैसे अहम बैजल कॉन्सेप्ट के बारे में भी जानकारी मिलेगी.

पूरा होने में लगने वाला अनुमानित समय: 30 मिनट

शुरू करने से पहले

Bazel इंस्टॉल करना

शुरू करने से पहले, अगर आपने अब तक bazel इंस्टॉल नहीं किया है, तो ऐसा करें.

किसी भी डायरेक्ट्री में bazel version चलाकर, यह देखा जा सकता है कि Bazel इंस्टॉल है या नहीं.

Go इंस्टॉल करना (ज़रूरी नहीं)

Bazel की मदद से Go प्रोजेक्ट बनाने के लिए, आपको Go इंस्टॉल करने की ज़रूरत नहीं है. Bazel Go नियम सेट, आपकी मशीन पर इंस्टॉल किए गए टूलचेन का इस्तेमाल करने के बजाय, Go टूलचेन को अपने-आप डाउनलोड और इस्तेमाल करता है. इससे यह पक्का होता है कि किसी प्रोजेक्ट पर काम करने वाले सभी डेवलपर, Go के एक ही वर्शन का इस्तेमाल करके प्रोजेक्ट बना रहे हैं.

हालांकि, हो सकता है कि आप अब भी go get और go mod tidy जैसे कमांड का इस्तेमाल करने के लिए, Go टूलचेन इंस्टॉल करना चाहें.

किसी भी डायरेक्ट्री में go version चलाकर यह देखा जा सकता है कि Go इंस्टॉल किया गया है या नहीं.

सैंपल प्रोजेक्ट पाना

Bazel के उदाहरण, Git रिपॉज़िटरी में सेव किए जाते हैं. इसलिए, अगर आपने Git इंस्टॉल नहीं किया है, तो आपको ऐसा करना होगा. उदाहरणों का कलेक्शन डाउनलोड करने के लिए, यह कमांड चलाएं:

git clone https://github.com/bazelbuild/examples

इस ट्यूटोरियल का सैंपल प्रोजेक्ट, examples/go-tutorial डायरेक्ट्री में मौजूद है. देखें कि इसमें क्या-क्या शामिल है:

go-tutorial/
└── stage1
└── stage2
└── stage3

यहां तीन सबडायरेक्ट्री (stage1, stage2, और stage3) हैं. हर सबडायरेक्ट्री, इस ट्यूटोरियल के अलग-अलग सेक्शन के लिए है. हर चरण, पिछले चरण पर आधारित होता है.

Bazel की मदद से बिल्ड करना

stage1 डायरेक्ट्री से शुरू करें, जहां हमें एक प्रोग्राम मिलेगा. हम इसे bazel build की मदद से बना सकते हैं, फिर इसे चला सकते हैं:

$ 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! 💚

हम प्रोग्राम को एक bazel run कमांड से भी चला सकते हैं:

$ 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! 💚

प्रोजेक्ट के स्ट्रक्चर को समझना

हमने अभी-अभी जो प्रोजेक्ट बनाया है उसे देखें.

hello.go में प्रोग्राम का Go सोर्स कोड होता है.

package main

import "fmt"

func main() {
    fmt.Println("Hello, Bazel! 💚")
}

BUILD में Basel के लिए कुछ निर्देश हैं, जो बताते हैं कि हमें क्या बनाना है. आम तौर पर, आपको हर डायरेक्ट्री में इस तरह की फ़ाइल लिखनी होगी. इस प्रोजेक्ट के लिए, हमारे पास एक go_binary टारगेट है, जो hello.go से हमारा प्रोग्राम बनाता है.

load("@rules_go//go:def.bzl", "go_binary")

go_binary(
    name = "hello",
    srcs = ["hello.go"],
)

MODULE.bazel आपके प्रोजेक्ट की डिपेंडेंसी को ट्रैक करता है. यह आपके प्रोजेक्ट की रूट डायरेक्ट्री को भी मार्क करता है, ताकि हर प्रोजेक्ट के लिए सिर्फ़ एक MODULE.bazel फ़ाइल लिखी जा सके. इसका मकसद, Go की go.mod फ़ाइल जैसा ही है. असल में, आपको Basel प्रोजेक्ट में go.mod फ़ाइल की ज़रूरत नहीं है. हालांकि, आपके लिए यह फ़ाइल काम आ सकती है. इससे डिपेंडेंसी मैनेजमेंट के लिए go get और go mod tidy का इस्तेमाल जारी रखा जा सकता है. Bazel Go का नियम सेट, go.mod से डिपेंडेंसी इंपोर्ट कर सकता है. हालांकि, हम इस बारे में किसी दूसरे ट्यूटोरियल में बताएंगे.

हमारी MODULE.bazel फ़ाइल में, Go के नियमों के सेट rules_go पर एक डिपेंडेंसी है. हमें इस डिपेंडेंसी की ज़रूरत है, क्योंकि Bazel में Go के लिए पहले से कोई सुविधा नहीं है.

bazel_dep(
    name = "rules_go",
    version = "0.50.1",
)

आखिर में, MODULE.bazel.lock एक ऐसी फ़ाइल है जिसे Bazel जनरेट करता है. इसमें हमारी डिपेंडेंसी के बारे में हैश और अन्य मेटाडेटा शामिल होता है. इसमें, Bazel के ज़रिए जोड़ी गई डिपेंडेंसी शामिल होती हैं. इसलिए, यह सूची काफ़ी लंबी होती है और हम इसे यहां नहीं दिखाएंगे. go.sum की तरह ही, आपको अपनी MODULE.bazel.lock फ़ाइल को सोर्स कंट्रोल में कमिट करना चाहिए, ताकि यह पक्का किया जा सके कि आपके प्रोजेक्ट में शामिल सभी लोगों को हर डिपेंडेंसी का एक ही वर्शन मिले. आपको MODULE.bazel.lock में मैन्युअल रूप से बदलाव करने की ज़रूरत नहीं पड़ेगी.

BUILD फ़ाइल को समझना

Bazel के साथ आपका ज़्यादातर इंटरैक्शन, BUILD फ़ाइलों (या BUILD.bazel फ़ाइलों) के ज़रिए होगा. इसलिए, यह समझना ज़रूरी है कि ये फ़ाइलें क्या करती हैं.

BUILD फ़ाइलें, Starlark नाम की स्क्रिप्टिंग भाषा में लिखी जाती हैं. यह Python का सीमित सबसेट है.

BUILD फ़ाइल में, टारगेट की सूची होती है. टारगेट कुछ ऐसा होता है बेज़ल बाइनरी, लाइब्रेरी या टेस्ट जैसा बना सकता है.

टारगेट, एट्रिब्यूट की सूची के साथ नियम फ़ंक्शन को कॉल करता है, ताकि यह बताया जा सके कि क्या बनाया जाना चाहिए. हमारे उदाहरण में दो एट्रिब्यूट हैं: name, कमांड लाइन पर टारगेट की पहचान करता है और srcs, सोर्स फ़ाइल के पाथ की सूची है. यह सूची, BUILD फ़ाइल वाली डायरेक्ट्री के हिसाब से स्लैश से अलग की गई है.

नियम, Bazel को टारगेट बनाने का तरीका बताता है. अपने उदाहरण में, हमने go_binary नियम का इस्तेमाल किया है. हर नियम ऐसी कार्रवाइयों (कमांड) के बारे में बताता है जो आउटपुट फ़ाइलों का सेट जनरेट करती हैं. उदाहरण के लिए, go_binary उन कार्रवाइयों को कंपाइल और लिंक करने के बारे में बताता है जो एक्ज़ीक्यूटेबल आउटपुट फ़ाइल बनाती हैं.

Bazel में Java और C++ जैसी कुछ भाषाओं के लिए, पहले से नियम मौजूद हैं. इन नियमों के बारे में Build Encyclopedia में दस्तावेज़ देखें. Bazel Central Registry (BCR) पर, कई अन्य भाषाओं और टूल के लिए नियम सेट देखे जा सकते हैं.

कोई लाइब्रेरी जोड़ना

stage2 डायरेक्ट्री पर जाएं. यहां हम आपका भाग्य प्रिंट करने वाला नया प्रोग्राम बनाएंगे. यह प्रोग्राम, लाइब्रेरी के तौर पर एक अलग Go पैकेज का इस्तेमाल करता है. यह पैकेज, मैसेज की पहले से तय सूची से कोई एक मैसेज चुनता है.

go-tutorial/stage2
├── BUILD
├── MODULE.bazel
├── MODULE.bazel.lock
├── fortune
│   ├── BUILD
│   └── fortune.go
└── print_fortune.go

fortune.go, लाइब्रेरी की सोर्स फ़ाइल है. fortune लाइब्रेरी एक अलग Go पैकेज है. इसलिए, इसकी सोर्स फ़ाइलें एक अलग डायरेक्ट्री में होती हैं. Bazel के लिए, आपको Go पैकेज को अलग-अलग डायरेक्ट्री में रखने की ज़रूरत नहीं होती. हालांकि, Go नेटवर्क में यह एक आम बात है. इसे अपनाने से, आपको Go के अन्य टूल के साथ काम करने में मदद मिलेगी.

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 डायरेक्ट्री में एक BUILD फ़ाइल होती है, जो Bazel को यह बताती है कि इस पैकेज को कैसे बनाएं. हम यहां go_binary के बजाय go_library का इस्तेमाल करते हैं.

हमें importpath एट्रिब्यूट को एक स्ट्रिंग पर भी सेट करना होगा, ताकि लाइब्रेरी को अन्य Go सोर्स फ़ाइलों में इंपोर्ट किया जा सके. यह नाम, डेटा स्टोर करने की जगह के अंदर की डायरेक्ट्री के साथ जुड़ा हुआ, डेटा स्टोर करने की जगह का पाथ (या मॉड्यूल पाथ) होना चाहिए.

आखिर में, हमें visibility एट्रिब्यूट को ["//visibility:public"] पर सेट करना होगा. visibility को किसी भी टारगेट पर सेट किया जा सकता है. इससे यह तय होता है कि कौनसे Bazel पैकेज इस टारगेट पर निर्भर हो सकते हैं. हमारे मामले में, हम चाहते हैं कि कोई भी टारगेट इस लाइब्रेरी पर निर्भर हो. इसलिए, हम खास वैल्यू //visibility:public का इस्तेमाल करते हैं.

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"],
)

इस लाइब्रेरी को इनका इस्तेमाल करके बनाया जा सकता है:

$ bazel build //fortune

इसके बाद, देखें कि print_fortune.go इस पैकेज का इस्तेमाल कैसे करता है.

package main

import (
    "fmt"

    "github.com/bazelbuild/examples/go-tutorial/stage2/fortune"
)

func main() {
    fmt.Println(fortune.Get())
}

print_fortune.go, fortune लाइब्रेरी के importpath एट्रिब्यूट में बताई गई उसी स्ट्रिंग का इस्तेमाल करके पैकेज इंपोर्ट करता है.

हमें Bazel को भी इस डिपेंडेंसी के बारे में बताना होगा. stage2 डायरेक्ट्री में मौजूद BUILD फ़ाइल यहां दी गई है.

load("@rules_go//go:def.bzl", "go_binary")

go_binary(
    name = "print_fortune",
    srcs = ["print_fortune.go"],
    deps = ["//fortune"],
)

इसे नीचे दिए गए निर्देश की मदद से चलाया जा सकता है.

bazel run //:print_fortune

print_fortune टारगेट में एक deps एट्रिब्यूट है. यह उन दूसरे टारगेट की सूची है जिन पर यह निर्भर करता है. इसमें "//fortune" शामिल है, जो fortune नाम की डायरेक्ट्री में टारगेट के बारे में बताने वाली लेबल स्ट्रिंग है.fortune

Bazel के लिए ज़रूरी है कि सभी टारगेट, deps जैसे एट्रिब्यूट की मदद से अपनी डिपेंडेंसी के बारे में साफ़ तौर पर बताएं. ऐसा करना मुश्किल लग सकता है, क्योंकि सोर्स फ़ाइलों में भी डिपेंडेंसी की जानकारी दी गई होती है. हालांकि, Bazel में साफ़ तौर पर जानकारी दी जाती है, जिससे इसे फ़ायदा मिलता है. Bazel, कोई भी निर्देश चलाने से पहले, सभी निर्देशों, इनपुट, और आउटपुट वाला एक ऐक्शन ग्राफ़ बनाता है. इसके लिए, वह किसी भी सोर्स फ़ाइल को नहीं पढ़ता. इसके बाद, Bazel कार्रवाई के नतीजों को कैश मेमोरी में सेव कर सकता है या रिमोट तरीके से प्रोग्राम चलाने के लिए कार्रवाइयां भेज सकता है. इसके लिए, उसे भाषा के हिसाब से बने लॉजिक की ज़रूरत नहीं होती.

लेबल को समझना

लेबल एक स्ट्रिंग होती है. इसका इस्तेमाल, Bazel किसी टारगेट या फ़ाइल की पहचान करने के लिए करता है. लेबल का इस्तेमाल कमांड लाइन आर्ग्युमेंट और BUILD फ़ाइल एट्रिब्यूट में किया जाता है, जैसे कि deps. हमने पहले ही कुछ सूचनाएं देख ली हैं, जैसे कि //fortune, //:print-fortune, और @rules_go//go:def.bzl.

लेबल के तीन हिस्से होते हैं: रिपॉज़िटरी का नाम, पैकेज का नाम, और टारगेट (या फ़ाइल) का नाम.

रिपॉज़िटरी का नाम @ और // के बीच लिखा जाता है. इसका इस्तेमाल, किसी दूसरे Bazel मॉड्यूल के टारगेट को रेफ़र करने के लिए किया जाता है. पुरानी वजहों से, मॉड्यूल और रिपॉज़िटरी को कभी-कभी एक जैसे इस्तेमाल किया जाता है. लेबल, @rules_go//go:def.bzl में, रिपॉज़िटरी का नाम rules_go है. एक ही डेटा स्टोर करने की जगह में मौजूद टारगेट का रेफ़रंस देते समय, डेटा स्टोर करने की जगह का नाम छोड़ा जा सकता है.

पैकेज का नाम // और : के बीच लिखा जाता है. इसका इस्तेमाल, किसी दूसरे Bazel पैकेज के टारगेट को रेफ़र करने के लिए किया जाता है. लेबल @rules_go//go:def.bzl में, पैकेज का नाम go है. Bazel पैकेज, फ़ाइलों और टारगेट का एक सेट होता है. इसे टॉप-लेवल डायरेक्ट्री में मौजूद BUILD या BUILD.bazel फ़ाइल से तय किया जाता है. इसका पैकेज नाम, मॉड्यूल की रूट डायरेक्ट्री (MODULE.bazel वाली) से लेकर BUILD फ़ाइल वाली डायरेक्ट्री तक का स्लैश से अलग किया गया पाथ होता है. किसी पैकेज में सबडायरेक्ट्री शामिल हो सकती हैं. हालांकि, ऐसा सिर्फ़ तब किया जा सकता है, जब उनमें अपने पैकेज की जानकारी देने वाली BUILDफ़ाइलें शामिल न हों.

ज़्यादातर Go प्रोजेक्ट में हर डायरेक्ट्री में एक BUILD फ़ाइल और हर BUILD फ़ाइल में एक Go पैकेज होता है. एक ही डायरेक्ट्री में मौजूद टारगेट का रेफ़रंस देते समय, लेबल में पैकेज का नाम छोड़ा जा सकता है.

टारगेट का नाम : के बाद लिखा जाता है. यह किसी पैकेज में मौजूद टारगेट को दिखाता है. अगर टारगेट का नाम पैकेज के नाम के आखिरी कॉम्पोनेंट से मेल खाता है, तो इसे हटाया जा सकता है. इसलिए, //a/b/c:c और //a/b/c, और //fortune:fortune और //fortune एक जैसे हैं.

कमांड-लाइन पर, ... का इस्तेमाल वाइल्डकार्ड के तौर पर किया जा सकता है, ताकि किसी पैकेज में मौजूद सभी टारगेट को रेफ़र किया जा सके. इससे, डेटा स्टोर करने की जगह में सभी टारगेट बनाने या उनकी जांच करने में मदद मिलती है.

# Build everything
$ bazel build //...

अपने प्रोजेक्ट की जांच करना

इसके बाद, stage3 डायरेक्ट्री पर जाएं, जहां हम एक टेस्ट जोड़ेंगे.

go-tutorial/stage3
├── BUILD
├── MODULE.bazel
├── MODULE.bazel.lock
├── fortune
│   ├── BUILD
│   ├── fortune.go
│   └── fortune_test.go
└── print-fortune.go

fortune/fortune_test.go हमारी नई टेस्ट सोर्स फ़ाइल है.

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)
    }
}

यह फ़ाइल एक्सपोर्ट नहीं किए गए fortunes वैरिएबल का इस्तेमाल करती है. इसलिए, इसे fortune.go वाले उसी Go पैकेज में कंपाइल किया जाना चाहिए. BUILD फ़ाइल देखकर जानें कि यह कैसे काम करती है:

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"],
)

हमारे पास एक नया fortune_test टारगेट है, जो go_test नियम का इस्तेमाल करके, एक्ज़ीक्यूटेबल टेस्ट को कंपाइल और लिंक करता है. go_test को एक ही निर्देश के साथ fortune.go और fortune_test.go को कंपाइल करना होगा. इसलिए, हम यहां embed एट्रिब्यूट का इस्तेमाल करते हैं, ताकि fortune टारगेट के एट्रिब्यूट को fortune_test में शामिल किया जा सके. embed का इस्तेमाल आम तौर पर go_test और go_binary के साथ किया जाता है. हालांकि, यह go_library के साथ भी काम करता है. यह जनरेट किए गए कोड के लिए कभी-कभी मददगार होता है.

आपको शायद यह जानना हो कि embed एट्रिब्यूट, Go के embed पैकेज से जुड़ा है या नहीं. इसका इस्तेमाल, किसी एक्सीक्यूटेबल में कॉपी की गई डेटा फ़ाइलों को ऐक्सेस करने के लिए किया जाता है. यह नाम का मेल खाने की समस्या है: rules_go के embed एट्रिब्यूट को Go के embed पैकेज से पहले लॉन्च किया गया था. इसके बजाय, rules_go, embedsrcs का इस्तेमाल करके उन फ़ाइलों की सूची बनाता है जिन्हें embed पैकेज के साथ लोड किया जा सकता है.

bazel test के साथ हमारा टेस्ट करने का प्रयास करें:

$ 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.

सभी टेस्ट चलाने के लिए, ... वाइल्डकार्ड का इस्तेमाल किया जा सकता है. Bazel, ऐसे टारगेट भी बनाएगा जो टेस्ट नहीं हैं. इसलिए, यह उन पैकेज में भी कंपाइल करने से जुड़ी गड़बड़ियां पकड़ सकता है जिनमें टेस्ट नहीं हैं.

$ bazel test //...

निष्कर्ष और आगे का पढ़ने के लिए

इस ट्यूटोरियल में, हमने Bazel की मदद से एक छोटा Go प्रोजेक्ट बनाया और उसका टेस्ट किया. साथ ही, हमने Bazel के कुछ बुनियादी कॉन्सेप्ट के बारे में भी जाना.

  • Bazel की मदद से अन्य ऐप्लिकेशन बनाने के लिए, C++, Java, Android, और iOS के लिए ट्यूटोरियल देखें.
  • अन्य भाषाओं के लिए, सुझाए गए नियमों की सूची भी देखी जा सकती है.
  • Go के बारे में ज़्यादा जानकारी के लिए, rules_go मॉड्यूल देखें. खास तौर पर, Go के मुख्य नियमों का दस्तावेज़ देखें.
  • अपने प्रोजेक्ट के बाहर Bazel मॉड्यूल के साथ काम करने के बारे में ज़्यादा जानने के लिए, बाहरी डिपेंडेंसी देखें. खास तौर पर, Bazel के मॉड्यूल सिस्टम की मदद से, Go मॉड्यूल और टूलचेन पर निर्भर रहने के तरीके के बारे में जानने के लिए, Go के साथ bzlmod देखें.