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 पर:

    • कंट्रोल पैनल खोलें.

    • "सिस्टम और सुरक्षा" > "सिस्टम" > "ऐडवांस सिस्टम सेटिंग" > "ऐडवांस" टैब > "एनवायरमेंट वैरिएबल..." पर जाएं. .

    • "उपयोगकर्ता के वैरिएबल" सूची (सबसे ऊपर वाली) में, "नया..." पर क्लिक करें.

    • "वैरिएबल का नाम" फ़ील्ड में, JAVA_HOME डालें.

    • "डायरेक्ट्री ब्राउज़ करें..." पर क्लिक करें.

    • JDK डायरेक्ट्री पर जाएं. उदाहरण के लिए, C:\Program Files\Java\jdk1.8.0_152.

    • सभी डायलॉग विंडो में "ठीक है" पर क्लिक करें.

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

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
└── WORKSPACE

Bazel की मदद से बनाना

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

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

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

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

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

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

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

BUILD फ़ाइल में, Bazel के लिए कई तरह के निर्देश होते हैं. सबसे अहम निर्देश बिल्ड नियम होता है. इससे Bazel को यह पता चलता है कि मनचाहे आउट101पुट कैसे बनाए जाएं. जैसे, एक्ज़ीक्यूटेबल बाइनरी या लाइब्रेरी. 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
``` In the target label, the `//`
part is the location of the `BUILD` file relative to the root of the workspace
(in this case, the root itself), and `ProjectRunner` is the target name in the
`BUILD` file. (You will learn about target labels in more detail at the end of
this tutorial.)
Bazel produces output similar to the following:
```bash
   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 एट्रिब्यूट से मिलती है. डिपेंडेंसी ग्राफ़ देखें:

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

हालांकि, बिल्ड को पूरा करने के लिए, आपको 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

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

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

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