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