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

7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

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

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

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

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

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

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

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

JDK इंस्टॉल करें

  1. Java JDK इंस्टॉल करें (पसंदीदा वर्शन 11 है, हालांकि 8 और 15 के बीच के वर्शन काम करते हैं).

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

    • 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. सभी डायलॉग विंडो पर "ठीक है" पर क्लिक करें.

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

Baज़ल के 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 खास फ़ाइलों के तौर पर पहचानता है:

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

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

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

जब 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

टारगेट लेबल में, // हिस्सा Workspace के रूट (इस मामले में, रूट ही) के हिसाब से 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-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 एट्रिब्यूट से बेज़ल को पता चलता है कि 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 है. - Baze, इस बात को deps एट्रिब्यूट के ज़रिए जान लेता है. डिपेंडेंसी ग्राफ़ पर एक नज़र डालें:

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

हालांकि, बिल्ड को पूरा करने के लिए, आपको visibility एट्रिब्यूट का इस्तेमाल करके, //BUILD में मौजूद टारगेट के लिए, //src/main/java/com/example/cmdline/BUILD में मौजूद टारगेट की साफ़ तौर पर जानकारी देनी होगी.runner इसकी वजह यह है कि डिफ़ॉल्ट रूप से टारगेट, सिर्फ़ एक ही 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

Baज़ल, इस तरह के आउटपुट देता है:

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 फ़ाइलों और कमांड लाइन में, Baze, टारगेट को रेफ़रंस देने के लिए टारगेट लेबल का इस्तेमाल करता है - उदाहरण के लिए, //:ProjectRunner या //src/main/java/com/example/cmdline:runner. इनका सिंटैक्स इस तरह है:

//path/to/package:target-name

अगर टारगेट, किसी नियम का टारगेट है, तो path/to/package उस डायरेक्ट्री का पाथ होता है जिसमें BUILD फ़ाइल होती है. आपने BUILD फ़ाइल में टारगेट को target-name कहा है (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 शामिल नहीं है. Basel की जनरेट की गई 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

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

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

मज़े से बनाएं!