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

समस्या की शिकायत करें सोर्स देखें Nightly · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

इस ट्यूटोरियल में, Bazel की बुनियादी बातों के बारे में बताया गया है. इसमें, 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 में Bazel के लिए कुछ निर्देश होते हैं. इनसे Bazel को पता चलता है कि हमें क्या बनाना है. आम तौर पर, हर डायरेक्ट्री में इस तरह की फ़ाइल लिखी जाती है. इस प्रोजेक्ट के लिए, हमारे पास एक 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 फ़ाइल की तरह ही है. Bazel प्रोजेक्ट में 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 फ़ाइल में टारगेट की सूची होती है. टारगेट ऐसी चीज़ होती है जिसे Bazel बना सकता है. जैसे, बाइनरी, लाइब्रेरी या टेस्ट.

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

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

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 को इस डिपेंडेंसी के बारे में भी बताना होगा. यहां BUILD फ़ाइल, stage2 डायरेक्ट्री में मौजूद है.

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 को भाषा के हिसाब से लॉजिक बनाने की ज़रूरत नहीं होती.

लेबल को समझना

लेबल एक ऐसी स्ट्रिंग होती है जिसका इस्तेमाल 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 मॉड्यूल देखें. खास तौर पर, Core Go rules का दस्तावेज़ देखें.
  • अपने प्रोजेक्ट से बाहर के Bazel मॉड्यूल के साथ काम करने के बारे में ज़्यादा जानने के लिए, बाहरी डिपेंडेंसी देखें. खास तौर पर, Bazel की मॉड्यूल सिस्टम के ज़रिए Go मॉड्यूल और टूलचेन पर निर्भर रहने के तरीके के बारे में जानने के लिए, bzlmod के साथ Go लेख पढ़ें.