इस ट्यूटोरियल में, 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 लेख पढ़ें.