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

इस ट्यूटोरियल में, Bazel की मदद से Java ऐप्लिकेशन बनाने की बुनियादी जानकारी दी गई है. इसमें आपको अपना वर्कस्पेस सेट अप करने और एक आसान Java प्रोजेक्ट बनाने के बारे में बताया जाएगा. इससे आपको Bazel के मुख्य कॉन्सेप्ट समझने में मदद मिलेगी. जैसे, टारगेट और BUILD फ़ाइलें.

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

आपको क्या सीखने को मिलेगा

इस ट्यूटोरियल में, आपको इनके बारे में जानकारी मिलेगी:

  • कोई टारगेट बनाना
  • प्रोजेक्ट की डिपेंडेंसी को विज़ुअलाइज़ करना
  • प्रोजेक्ट को कई टारगेट और पैकेज में बांटना
  • पैकेज में टारगेट की विज़िबिलिटी को कंट्रोल करना
  • लेबल की मदद से टारगेट का रेफ़रंस देना
  • कोई टारगेट डिप्लॉय करना

शुरू करने से पहले

Bazel इंस्टॉल करना

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

JDK इंस्टॉल करना

  1. Java JDK इंस्टॉल करें. हमारा सुझाव है कि वर्शन 11 इंस्टॉल करें. हालांकि, वर्शन 8 से 15 भी काम करते हैं.

  2. JAVA_HOME एनवायरमेंट वैरिएबल को JDK पर पॉइंट करने के लिए सेट करें.

    • Linux/macOS पर:

      export JAVA_HOME="$(dirname $(dirname $(realpath $(which javac))))"
      
    • Windows पर:

      1. कंट्रोल पैनल खोलें.
      2. "सिस्टम और सुरक्षा" > "सिस्टम" > "ऐडवांस सिस्टम सेटिंग" > "ऐडवांस" टैब > "एनवायरमेंट वैरिएबल..." पर जाएं. .
      3. "उपयोगकर्ता वैरिएबल" सूची (सबसे ऊपर वाली) में, "नया..." पर क्लिक करें.
      4. "वैरिएबल का नाम" फ़ील्ड में, JAVA_HOME डालें.
      5. "डायरेक्ट्री ब्राउज़ करें..." पर क्लिक करें.
      6. JDK डायरेक्ट्री पर जाएं. उदाहरण के लिए, C:\Program Files\Java\jdk1.8.0_152.
      7. सभी डायलॉग विंडो में "ठीक है" पर क्लिक करें.

सैंपल प्रोजेक्ट पाना

Bazel के GitHub डेटाबेस से सैंपल प्रोजेक्ट पाएं:

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

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

java-tutorial
├── BUILD
├── src
   └── main
       └── java
           └── com
               └── example
                   ├── cmdline
                      ├── BUILD
                      └── Runner.java
                   ├── Greeting.java
                   └── ProjectRunner.java
└── MODULE.bazel

Bazel की मदद से बिल्ड करना

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

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

  • MODULE.bazel फ़ाइल. यह डायरेक्ट्री और उसके कॉन्टेंट को Bazel वर्कस्पेस के तौर पर पहचानती है. यह प्रोजेक्ट की डायरेक्ट्री स्ट्रक्चर के रूट में मौजूद होती है,

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

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

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

BUILD फ़ाइल को समझना

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

java-tutorial/BUILD फ़ाइल देखें:

java_binary(
    name = "ProjectRunner",
    srcs = glob(["src/main/java/com/example/*.java"]),
)

हमारे उदाहरण में, ProjectRunner टारगेट, Bazel के बिल्ट-इन java_binary नियम को इंस्टैंशिएट करता है. इस नियम से Bazel को .jar फ़ाइल और रैपर शेल स्क्रिप्ट (दोनों का नाम टारगेट के नाम पर रखा जाता है) बिल्ड करने का निर्देश मिलता है.

टारगेट में मौजूद एट्रिब्यूट, उसकी डिपेंडेंसी और विकल्पों के बारे में साफ़ तौर पर बताते हैं. name एट्रिब्यूट ज़रूरी है. हालांकि, कई एट्रिब्यूट ज़रूरी नहीं होते. उदाहरण के लिए, ProjectRunner नियम टारगेट में, name टारगेट का नाम है. srcs उन सोर्स फ़ाइलों के बारे में बताता है जिनका इस्तेमाल Bazel, टारगेट को बिल्ड करने के लिए करता है. वहीं, main_class उस क्लास के बारे में बताता है जिसमें मुख्य तरीका शामिल होता है. (आपने देखा होगा कि हमारे उदाहरण में, सोर्स फ़ाइलों के सेट को Bazel पर पास करने के लिए, glob का इस्तेमाल किया गया है. ऐसा एक-एक करके सोर्स फ़ाइलों की सूची बनाने के बजाय किया गया है.)

प्रोजेक्ट बिल्ड करना

अपने सैंपल प्रोजेक्ट को बिल्ड करने के लिए, java-tutorial डायरेक्ट्री पर जाएं और यह निर्देश चलाएं:

bazel build //:ProjectRunner

टारगेट लेबल में, // हिस्सा, वर्कस्पेस के रूट के मुकाबले BUILD फ़ाइल की जगह है. इस मामले में, यह रूट ही है. वहीं, ProjectRunner, BUILD फ़ाइल में टारगेट का नाम है. (आपको इस ट्यूटोरियल के आखिर में, टारगेट लेबल के बारे में ज़्यादा जानकारी मिलेगी.)

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

   INFO: Found 1 target...
   Target //:ProjectRunner up-to-date:
      bazel-bin/ProjectRunner.jar
      bazel-bin/ProjectRunner
   INFO: Elapsed time: 1.021s, Critical Path: 0.83s

बधाई हो, आपने Bazel का पहला टारगेट बिल्ड कर लिया है! Bazel, बिल्ड आउटपुट को वर्कस्पेस के रूट में मौजूद bazel-bin डायरेक्ट्री में रखता है. Bazel के आउटपुट स्ट्रक्चर के बारे में जानने के लिए, इसके कॉन्टेंट को ब्राउज़ करें.

अब, नई बिल्ड की गई बाइनरी की जांच करें:

bazel-bin/ProjectRunner

डिपेंडेंसी ग्राफ़ की समीक्षा करना

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

सैंपल प्रोजेक्ट की डिपेंडेंसी को विज़ुअलाइज़ करने के लिए, डिपेंडेंसी ग्राफ़ का टेक्स्ट वर्शन जनरेट किया जा सकता है. इसके लिए, वर्कस्पेस के रूट पर यह निर्देश चलाएं:

bazel query  --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graph

ऊपर दिए गए निर्देश से Bazel को, टारगेट //:ProjectRunner के लिए सभी डिपेंडेंसी (होस्ट और इंप्लिसिट डिपेंडेंसी को छोड़कर) खोजने और आउटपुट को ग्राफ़ के तौर पर फ़ॉर्मैट करने का निर्देश मिलता है.

इसके बाद, टेक्स्ट को GraphViz में चिपकाएं.

जैसा कि देखा जा सकता है, प्रोजेक्ट में एक ही टारगेट है, जो बिना किसी अतिरिक्त डिपेंडेंसी के दो सोर्स फ़ाइलें बिल्ड करता है:

टारगेट 'ProjectRunner' का डिपेंडेंसी ग्राफ़

वर्कस्पेस सेट अप करने, प्रोजेक्ट बिल्ड करने, और उसकी डिपेंडेंसी की जांच करने के बाद, इसमें कुछ जटिलता जोड़ी जा सकती है.

Bazel बिल्ड को बेहतर बनाना

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

कई बिल्ड टारगेट तय करना

सैंपल प्रोजेक्ट के बिल्ड को दो टारगेट में बांटा जा सकता है. java-tutorial/BUILD फ़ाइल के कॉन्टेंट को इससे बदलें:

java_binary(
    name = "ProjectRunner",
    srcs = ["src/main/java/com/example/ProjectRunner.java"],
    main_class = "com.example.ProjectRunner",
    deps = [":greeter"],
)

java_library(
    name = "greeter",
    srcs = ["src/main/java/com/example/Greeting.java"],
)

इस कॉन्फ़िगरेशन के साथ, Bazel पहले greeter लाइब्रेरी को बिल्ड करता है. इसके बाद, ProjectRunner बाइनरी को बिल्ड करता है. java_binary में मौजूद deps एट्रिब्यूट, Bazel को बताता है कि ProjectRunner बाइनरी को बिल्ड करने के लिए, greeter लाइब्रेरी की ज़रूरत होती है.

प्रोजेक्ट के इस नए वर्शन को बिल्ड करने के लिए, यह निर्देश चलाएं:

bazel build //:ProjectRunner

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

INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
  bazel-bin/ProjectRunner.jar
  bazel-bin/ProjectRunner
INFO: Elapsed time: 2.454s, Critical Path: 1.58s

अब, नई बिल्ड की गई बाइनरी की जांच करें:

bazel-bin/ProjectRunner

अगर अब ProjectRunner.java में बदलाव किया जाता है और प्रोजेक्ट को फिर से बिल्ड किया जाता है, तो Bazel सिर्फ़ उस फ़ाइल को फिर से कंपाइल करता है.

डिपेंडेंसी ग्राफ़ को देखने पर पता चलता है कि ProjectRunner पहले की तरह ही इनपुट पर निर्भर करता है. हालांकि, बिल्ड का स्ट्रक्चर अलग है:

डिपेंडेंसी जोड़ने के बाद, टारगेट 'ProjectRunner' का डिपेंडेंसी ग्राफ़

अब आपने प्रोजेक्ट को दो टारगेट के साथ बिल्ड कर लिया है. ProjectRunner टारगेट, एक सोर्स फ़ाइल बिल्ड करता है और एक अन्य टारगेट (:greeter) पर निर्भर करता है. यह एक अतिरिक्त सोर्स फ़ाइल बिल्ड करता है.

एक से ज़्यादा पैकेज इस्तेमाल करना

अब प्रोजेक्ट को कई पैकेज में बांटते हैं. src/main/java/com/example/cmdline डायरेक्ट्री को देखने पर पता चलता है कि इसमें BUILD फ़ाइल के साथ-साथ कुछ सोर्स फ़ाइलें भी मौजूद हैं. इसलिए, Bazel के लिए, वर्कस्पेस में अब दो पैकेज हैं: //src/main/java/com/example/cmdline और //. ऐसा इसलिए है, क्योंकि वर्कस्पेस के रूट में BUILD फ़ाइल मौजूद है.

src/main/java/com/example/cmdline/BUILD फ़ाइल देखें:

java_binary(
    name = "runner",
    srcs = ["Runner.java"],
    main_class = "com.example.cmdline.Runner",
    deps = ["//:greeter"],
)

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

टारगेट 'रनर' का डिपेंडेंसी ग्राफ़

हालांकि, बिल्ड को सफल बनाने के लिए, आपको visibility एट्रिब्यूट का इस्तेमाल करके, //src/main/java/com/example/cmdline/BUILD में मौजूद runner टारगेट को, //BUILD में मौजूद टारगेट के लिए साफ़ तौर पर विज़िबिलिटी देनी होगी. ऐसा इसलिए है, क्योंकि डिफ़ॉल्ट रूप से टारगेट सिर्फ़ उसी BUILD फ़ाइल में मौजूद अन्य टारगेट को दिखते हैं. (Bazel, टारगेट की विज़िबिलिटी का इस्तेमाल करके, समस्याओं को रोकने के लिए करता है. जैसे, लाइब्रेरी में मौजूद इंप्लीमेंटेशन की जानकारी, सार्वजनिक एपीआई में लीक न हो.)

इसके लिए, java-tutorial/BUILD में मौजूद greeter टारगेट में visibility एट्रिब्यूट जोड़ें. जैसे, यहां दिखाया गया है:

java_library(
    name = "greeter",
    srcs = ["src/main/java/com/example/Greeting.java"],
    visibility = ["//src/main/java/com/example/cmdline:__pkg__"],
)

अब वर्कस्पेस के रूट पर यह निर्देश चलाकर, नया पैकेज बिल्ड किया जा सकता है:

bazel build //src/main/java/com/example/cmdline:runner

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

INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner up-to-date:
  bazel-bin/src/main/java/com/example/cmdline/runner.jar
  bazel-bin/src/main/java/com/example/cmdline/runner
  INFO: Elapsed time: 1.576s, Critical Path: 0.81s

अब, नई बिल्ड की गई बाइनरी की जांच करें:

./bazel-bin/src/main/java/com/example/cmdline/runner

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

टारगेट का रेफ़रंस देने के लिए लेबल का इस्तेमाल करना

BUILD फ़ाइलों और कमांड लाइन पर, Bazel, टारगेट का रेफ़रंस देने के लिए टारगेट लेबल का इस्तेमाल करता है. उदाहरण के लिए, //:ProjectRunner या //src/main/java/com/example/cmdline:runner. इनका सिंटैक्स इस तरह है:

//path/to/package:target-name

अगर टारगेट, नियम टारगेट है, तो path/to/package उस डायरेक्ट्री का पाथ है जिसमें BUILD फ़ाइल है. वहीं, target-name उस टारगेट का नाम है जिसे आपने BUILD फ़ाइल में नाम दिया है. यह name एट्रिब्यूट है. अगर टारगेट, फ़ाइल टारगेट है, तो path/to/package, पैकेज के रूट का पाथ है. वहीं, target-name, टारगेट फ़ाइल का नाम है. इसमें उसका पूरा पाथ शामिल होता है.

डेटाबेस के रूट पर टारगेट का रेफ़रंस देते समय, पैकेज का पाथ खाली होता है. सिर्फ़ //:target-name का इस्तेमाल करें. एक ही BUILD फ़ाइल में मौजूद टारगेट का रेफ़रंस देते समय, // वर्कस्पेस रूट आइडेंटिफ़ायर को भी छोड़ा जा सकता है. सिर्फ़ :target-name का इस्तेमाल करें.

उदाहरण के लिए, java-tutorial/BUILD फ़ाइल में मौजूद टारगेट के लिए, आपको पैकेज का पाथ तय नहीं करना पड़ा. ऐसा इसलिए है, क्योंकि वर्कस्पेस का रूट ही एक पैकेज (//) है. आपके दो टारगेट लेबल, //:ProjectRunner और //:greeter थे.

हालांकि, //src/main/java/com/example/cmdline/BUILD फ़ाइल में मौजूद टारगेट के लिए, आपको //src/main/java/com/example/cmdline का पूरा पैकेज पाथ तय करना पड़ा. आपका टारगेट लेबल //src/main/java/com/example/cmdline:runner था.

डिप्लॉयमेंट के लिए Java टारगेट को पैकेज करना

अब, रनटाइम की सभी डिपेंडेंसी के साथ बाइनरी को बिल्ड करके, डिप्लॉयमेंट के लिए Java टारगेट को पैकेज करते हैं. इससे, डेवलपमेंट एनवायरमेंट के बाहर बाइनरी को चलाया जा सकता है.

आपको याद होगा कि java_binary बिल्ड नियम .jar और रैपर शेल स्क्रिप्ट जनरेट करता है. इस निर्देश का इस्तेमाल करके, runner.jar के कॉन्टेंट देखें:

jar tf bazel-bin/src/main/java/com/example/cmdline/runner.jar

कॉन्टेंट ये हैं:

META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class

जैसा कि देखा जा सकता है, runner.jar में Runner.class है, लेकिन इसकी डिपेंडेंसी Greeting.class नहीं है. Bazel से जनरेट की गई runner स्क्रिप्ट, क्लासपाथ में greeter.jar जोड़ती है. इसलिए, अगर इसे ऐसे ही छोड़ दिया जाता है, तो यह स्थानीय तौर पर चलेगी. हालांकि, इसे किसी अन्य मशीन पर स्टैंडअलोन नहीं चलाया जा सकेगा. अच्छी बात यह है कि java_binary नियम की मदद से, डिप्लॉय की जा सकने वाली, सेल्फ-कंटेन्ड बाइनरी बिल्ड की जा सकती है. इसे बिल्ड करने के लिए, टारगेट के नाम में _deploy.jar जोड़ें:

bazel build //src/main/java/com/example/cmdline:runner_deploy.jar

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

INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner_deploy.jar up-to-date:
  bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
INFO: Elapsed time: 1.700s, Critical Path: 0.23s

आपने runner_deploy.jar बिल्ड कर लिया है. इसे डेवलपमेंट एनवायरमेंट के बाहर स्टैंडअलोन चलाया जा सकता है, क्योंकि इसमें रनटाइम की ज़रूरी डिपेंडेंसी शामिल हैं. पहले वाले निर्देश का इस्तेमाल करके, इस स्टैंडअलोन JAR के कॉन्टेंट देखें:

jar tf bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar

कॉन्टेंट में, चलाने के लिए ज़रूरी सभी क्लास शामिल हैं:

META-INF/
META-INF/MANIFEST.MF
build-data.properties
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
com/example/Greeting.class

इस बारे में और पढ़ें

ज़्यादा जानकारी के लिए, ये लेख देखें:

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