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: हिस्सा, वर्कस्पेस के रूट के मुकाबले 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" मैसेज प्रिंट होता है.

यहां पहले चरण का डिपेंडेंसी ग्राफ़ दिया गया है:

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

main पैकेज में मौजूद 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 के बारे में ज़्यादा जानने के लिए, यहां कुछ और संसाधन दिए गए हैं:

बिल्ड करने के लिए शुभकामनाएं!