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
      └── WORKSPACE
    ├──stage2
      ├── main
         ├── BUILD
         ├── hello-world.cc
         ├── hello-greet.cc
         └── hello-greet.h
      └── WORKSPACE
    └──stage3
       ├── main
          ├── BUILD
          ├── hello-world.cc
          ├── hello-greet.cc
          └── hello-greet.h
       ├── lib
          ├── BUILD
          ├── hello-time.cc
          └── hello-time.h
       └── WORKSPACE

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

खास जानकारी: परिचय

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

शुरू करना

वर्कस्पेस सेट अप करना

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

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

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

NOTE: जब 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 rule को इंस्टैंशिएट करता है. यह नियम, Bazel को बताता है कि hello-world.cc सोर्स फ़ाइल से, बिना किसी डिपेंडेंसी के एक सेल्फ-कंटेन्ड एक्ज़ीक्यूटेबल बाइनरी को बिल्ड करना है.

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

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

स्टेज 1: एक टारगेट, एक पैकेज

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

examples
└── cpp-tutorial
    └──stage1
       ├── main
          ├── BUILD
          └── hello-world.cc
       └── WORKSPACE

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 के लिए डिपेंडेंसी ग्राफ़ में, एक सोर्स फ़ाइल के साथ एक टारगेट दिखाया गया है.

खास जानकारी: स्टेज 1

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

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

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

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

    ├──stage2
      ├── main
         ├── BUILD
         ├── hello-world.cc
         ├── hello-greet.cc
         └── hello-greet.h
      └── WORKSPACE

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 rule का इस्तेमाल करके) को बिल्ड करता है. इसके बाद, 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` के लिए डिपेंडेंसी ग्राफ़, फ़ाइल में बदलाव करने के बाद डिपेंडेंसी में हुए बदलावों को दिखाता है.

खास जानकारी: स्टेज 2

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

स्टेज 3: एक से ज़्यादा पैकेज

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

└──stage3
   ├── main
      ├── BUILD
      ├── hello-world.cc
      ├── hello-greet.cc
      └── hello-greet.h
   ├── lib
      ├── BUILD
      ├── hello-time.cc
      └── hello-time.h
   └── WORKSPACE

यहां आपको दो सब-डायरेक्ट्री दिखेंगी. इनमें से हर डायरेक्ट्री में एक 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

खास जानकारी: स्टेज 3

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

अगले चरण

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

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