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 के संदर्भ में, इन शब्दों का क्या मतलब है. अगले सेक्शन में, आपको प्रोजेक्ट के पहले चरण को बनाने और उसकी जांच करने का तरीका बताया जाएगा.

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

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

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

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

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

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

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

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

अब आपने दो टारगेट के साथ प्रोजेक्ट बना लिया है. 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
   └── 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

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

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

अगले चरण

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

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