Bazel ट्यूटोरियल: C++ प्रोजेक्ट बनाएं

परिचय

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

Bazel एक बिल्ड सिस्टम है, जो कई भाषाओं में बिल्ड करने की सुविधा देता है. इस ट्यूटोरियल में, C++ प्रोजेक्ट का उदाहरण दिया गया है. साथ ही, इसमें सामान्य दिशा-निर्देश और फ़्लो दिए गए हैं, जो ज़्यादातर भाषाओं पर लागू होते हैं.

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

ज़रूरी शर्तें

अगर आपने अब तक Bazel इंस्टॉल नहीं किया है, तो इसे इंस्टॉल करें. इस ट्यूटोरियल में, सोर्स कंट्रोल के लिए Git का इस्तेमाल किया गया है. इसलिए, सबसे अच्छे नतीजे पाने के लिए, Git इंस्टॉल करें.

इसके बाद, Bazel की GitHub रिपॉज़िटरी से सैंपल प्रोजेक्ट को वापस पाएं. इसके लिए, अपनी पसंद की कमांड-लाइन टूल में यह कमांड चलाएं:

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

इस ट्यूटोरियल के लिए सैंपल प्रोजेक्ट, examples/cpp-tutorial डायरेक्ट्री में है.

देखें कि यह कैसे काम करता है:

examples
└── cpp-tutorial
    ├──stage1
    │  ├── main
    │  │   ├── BUILD
    │  │   └── hello-world.cc
    │  └── MODULE.bazel
    ├──stage2
    │  ├── main
    │  │   ├── BUILD
    │  │   ├── hello-world.cc
    │  │   ├── hello-greet.cc
    │  │   └── hello-greet.h
    │  └── MODULE.bazel
    └──stage3
       ├── main
       │   ├── BUILD
       │   ├── hello-world.cc
       │   ├── hello-greet.cc
       │   └── hello-greet.h
       ├── lib
       │   ├── BUILD
       │   ├── hello-time.cc
       │   └── hello-time.h
       └── MODULE.bazel

फ़ाइलों के तीन सेट हैं. हर सेट, इस ट्यूटोरियल के एक चरण को दिखाता है. पहले चरण में, आपको एक पैकेज में मौजूद एक टारगेट बनाना होगा. दूसरे चरण में, आपको एक ही पैकेज से बाइनरी और लाइब्रेरी, दोनों बनानी होंगी. तीसरे और आखिरी चरण में, आपको कई पैकेज वाला प्रोजेक्ट बनाना होगा. साथ ही, उसे कई टारगेट के साथ बनाना होगा.

खास जानकारी: शुरुआती जानकारी

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

शुरू करना

कोई प्रोजेक्ट बनाने से पहले, आपको उसका वर्कस्पेस सेट अप करना होगा. वर्कस्पेस एक डायरेक्ट्री होती है. इसमें आपके प्रोजेक्ट की सोर्स फ़ाइलें और Bazel के बिल्ड आउटपुट होते हैं. इसमें ये अहम फ़ाइलें भी शामिल हैं:

  • MODULE.bazel फ़ाइल, डायरेक्ट्री और उसके कॉन्टेंट की पहचान Bazel वर्कस्पेस के तौर पर करती है. यह प्रोजेक्ट की डायरेक्ट्री स्ट्रक्चर के रूट में मौजूद होती है. इसी फ़ाइल में, बाहरी डिपेंडेंसी के बारे में भी बताया जाता है.
  • एक या उससे ज़्यादा BUILD फ़ाइलें. इनसे Bazel को यह पता चलता है कि प्रोजेक्ट के अलग-अलग हिस्सों को कैसे बनाया जाए. वर्कस्पेस में मौजूद ऐसी डायरेक्ट्री जिसमें BUILD फ़ाइल होती है उसे पैकेज कहा जाता है. (इस ट्यूटोरियल में पैकेज के बारे में बाद में ज़्यादा जानकारी दी गई है.)

आने वाले समय में, किसी डायरेक्ट्री को Bazel वर्कस्पेस के तौर पर सेट करने के लिए, उस डायरेक्ट्री में MODULE.bazel नाम की एक खाली फ़ाइल बनाएं. इस ट्यूटोरियल के लिए, हर स्टेज में MODULE.bazel फ़ाइल पहले से मौजूद है.

BUILD फ़ाइल के बारे में जानकारी

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

cpp-tutorial/stage1/main डायरेक्ट्री में मौजूद BUILD फ़ाइल देखें:

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

हमारे उदाहरण में, hello-world टारगेट, Bazel के बिल्ट-इन cc_binary नियम को इंस्टैंशिएट करता है. यह नियम, Bazel को hello-world.cc> सोर्स फ़ाइल से, बिना किसी डिपेंडेंसी के एक सेल्फ-कंटेन्ड एक्ज़ीक्यूटेबल बाइनरी बनाने के लिए कहता है.

खास जानकारी: शुरू करना

अब आपको कुछ मुख्य शब्दों के बारे में पता चल गया है. साथ ही, आपको यह भी पता चल गया है कि इस प्रोजेक्ट और Bazel के संदर्भ में उनका क्या मतलब है. अगले सेक्शन में, प्रोजेक्ट के पहले चरण को बनाया और टेस्ट किया जाएगा.

पहला चरण: एक टारगेट, एक पैकेज

अब प्रोजेक्ट का पहला हिस्सा बनाने का समय है. विज़ुअल रेफ़रंस के लिए, प्रोजेक्ट के पहले चरण के सेक्शन का स्ट्रक्चर यह है:

examples
└── cpp-tutorial
    └──stage1
       ├── main
       │   ├── BUILD
       │   └── hello-world.cc
       └── MODULE.bazel

cpp-tutorial/stage1 डायरेक्ट्री पर जाने के लिए, यह कमांड चलाएं:

cd cpp-tutorial/stage1

इसके बाद, यह कमांड चलाएं:

bazel build //main:hello-world

टारगेट लेबल में, //main: हिस्सा, Workspace के रूट के हिसाब से BUILD फ़ाइल की जगह की जानकारी देता है. वहीं, hello-world, BUILD फ़ाइल में टारगेट का नाम होता है.

Bazel, इस तरह का कुछ आउटपुट जनरेट करता है:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 2.267s, Critical Path: 0.25s

आपने अभी-अभी अपना पहला Bazel टारगेट बनाया है. Bazel, बिल्ड आउटपुट को वर्कस्पेस के रूट में मौजूद bazel-bin डायरेक्ट्री में रखता है.

अब अपने नए बाइनरी की जांच करें. यह बाइनरी:

bazel-bin/main/hello-world

इस वजह से, "Hello world" मैसेज प्रिंट हो जाता है.

यहां स्टेज 1 का डिपेंडेंसी ग्राफ़ दिया गया है:

hello-world के लिए डिपेंडेंसी ग्राफ़, एक सोर्स फ़ाइल के साथ एक टारगेट दिखाता है.

खास जानकारी: पहला चरण

अब आपने पहला बिल्ड पूरा कर लिया है. इससे आपको बिल्ड के स्ट्रक्चर के बारे में बुनियादी जानकारी मिल गई है. अगले चरण में, एक और टारगेट जोड़कर इसे और मुश्किल बनाया जाएगा.

दूसरा चरण: एक से ज़्यादा बिल्ड टारगेट

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

यह वह डायरेक्ट्री है जिसका इस्तेमाल आपको दूसरे चरण के लिए करना है:

    ├──stage2
    │  ├── main
    │  │   ├── BUILD
    │  │   ├── hello-world.cc
    │  │   ├── hello-greet.cc
    │  │   └── hello-greet.h
    │  └── MODULE.bazel

cpp-tutorial/stage2/main डायरेक्ट्री में मौजूद BUILD फ़ाइल देखें:

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
    ],
)

इस BUILD फ़ाइल की मदद से, Bazel सबसे पहले hello-greet लाइब्रेरी बनाता है. इसके लिए, Bazel के बिल्ट-इन cc_library नियम का इस्तेमाल किया जाता है. इसके बाद, hello-world बाइनरी बनाई जाती है. hello-world टारगेट में मौजूद deps एट्रिब्यूट, Bazel को बताता है कि hello-world बाइनरी बनाने के लिए hello-greet लाइब्रेरी की ज़रूरत है.

प्रोजेक्ट का नया वर्शन बनाने से पहले, आपको डायरेक्ट्री बदलनी होगी. इसके लिए, यह कमांड चलाकर cpp-tutorial/stage2 डायरेक्ट्री पर स्विच करें:

cd ../stage2

अब इस जानी-पहचानी कमांड का इस्तेमाल करके, नया बाइनरी बनाया जा सकता है:

bazel build //main:hello-world

Bazel, एक बार फिर इस तरह का कुछ आउटपुट जनरेट करता है:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 2.399s, Critical Path: 0.30s

अब आपके पास, हाल ही में बनाए गए बाइनरी को टेस्ट करने का विकल्प है. इससे एक और "Hello world" मिलता है:

bazel-bin/main/hello-world

अब अगर hello-greet.cc में बदलाव किया जाता है और प्रोजेक्ट को फिर से बनाया जाता है, तो Bazel सिर्फ़ उस फ़ाइल को फिर से कंपाइल करता है.

डिपेंडेंसी ग्राफ़ में, यह देखा जा सकता है कि hello-world, hello-greet नाम के एक अतिरिक्त इनपुट पर निर्भर करता है:

`hello-world` के लिए डिपेंडेंसी ग्राफ़, फ़ाइल में बदलाव करने के बाद डिपेंडेंसी में हुए बदलावों को दिखाता है.

खास जानकारी: दूसरा चरण

अब आपने दो टारगेट वाला प्रोजेक्ट बना लिया है. hello-world टारगेट, एक सोर्स फ़ाइल बनाता है और किसी दूसरे टारगेट (//main:hello-greet) पर निर्भर करता है. यह दो अतिरिक्त सोर्स फ़ाइलें बनाता है. अगले सेक्शन में, एक और पैकेज जोड़ें.

तीसरा चरण: एक से ज़्यादा पैकेज

इस अगले चरण में, एक और लेयर जोड़ी जाती है. साथ ही, कई पैकेज वाला प्रोजेक्ट बनाया जाता है. cpp-tutorial/stage3 डायरेक्ट्री का स्ट्रक्चर और कॉन्टेंट देखें:

└──stage3
   ├── main
   │   ├── BUILD
   │   ├── hello-world.cc
   │   ├── hello-greet.cc
   │   └── hello-greet.h
   ├── lib
   │   ├── BUILD
   │   ├── hello-time.cc
   │   └── hello-time.h
   └── MODULE.bazel

अब आपको दिखेगा कि दो सब-डायरेक्ट्री हैं और हर एक में BUILD फ़ाइल है. इसलिए, Bazel के लिए अब वर्कस्पेस में दो पैकेज हैं: lib और main.

lib/BUILD फ़ाइल देखें:

cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["//main:__pkg__"],
)

साथ ही, main/BUILD फ़ाइल में:

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
        "//lib:hello-time",
    ],
)

मुख्य पैकेज में मौजूद hello-world टारगेट, lib पैकेज में मौजूद hello-time टारगेट पर निर्भर करता है. इसलिए, टारगेट का लेबल //lib:hello-time है. Bazel को इस बारे में deps एट्रिब्यूट से पता चलता है. इसे डिपेंडेंसी ग्राफ़ में देखा जा सकता है:

`hello-world` के लिए डिपेंडेंसी ग्राफ़ दिखाता है कि मुख्य पैकेज में मौजूद टारगेट, `lib` पैकेज में मौजूद टारगेट पर कैसे निर्भर करता है.

बिल्ड को पूरा करने के लिए, विज़िबिलिटी एट्रिब्यूट का इस्तेमाल करके, lib/BUILD में मौजूद //lib:hello-time टारगेट को main/BUILD में मौजूद टारगेट के लिए साफ़ तौर पर दिखाया जाता है. ऐसा इसलिए है, क्योंकि डिफ़ॉल्ट रूप से टारगेट सिर्फ़ उसी BUILD फ़ाइल में मौजूद अन्य टारगेट को दिखते हैं. Bazel, टारगेट विज़िबिलिटी का इस्तेमाल करता है. इससे, सार्वजनिक एपीआई में लागू करने से जुड़ी जानकारी वाली लाइब्रेरी लीक होने जैसी समस्याओं को रोकने में मदद मिलती है.

अब प्रोजेक्ट का यह फ़ाइनल वर्शन बनाएं. cpp-tutorial/stage3 डायरेक्ट्री पर स्विच करने के लिए, यह कमांड चलाएं:

cd  ../stage3

यहां दिया गया कमांड फिर से चलाएं:

bazel build //main:hello-world

Bazel, इस तरह का कुछ आउटपुट जनरेट करता है:

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 0.167s, Critical Path: 0.00s

अब इस ट्यूटोरियल के आखिरी बाइनरी को फ़ाइनल Hello world मैसेज के लिए टेस्ट करें:

bazel-bin/main/hello-world

खास जानकारी: तीसरा चरण

अब आपने प्रोजेक्ट को दो पैकेज के तौर पर बनाया है. इनमें तीन टारगेट हैं. साथ ही, आपने इनके बीच की डिपेंडेंसी को समझ लिया है. इससे आपको Bazel का इस्तेमाल करके, आने वाले समय में प्रोजेक्ट बनाने में मदद मिलेगी. अगले सेक्शन में, Bazel का इस्तेमाल जारी रखने का तरीका जानें.

अगले चरण

आपने Bazel की मदद से पहला बुनियादी बिल्ड बना लिया है. हालांकि, यह सिर्फ़ शुरुआत है. Bazel के बारे में ज़्यादा जानने के लिए, यहाँ कुछ और संसाधन दिए गए हैं:

ऐप बनाने का आनंद लें!