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

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

पूरा होने का अनुमानित समय: 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. सभी डायलॉग विंडो में "ठीक है" पर क्लिक करें.

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

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 फ़ाइल, डायरेक्ट्री और उसके कॉन्टेंट की पहचान बेज़ल वर्कस्पेस के तौर पर करती है. साथ ही, यह प्रोजेक्ट की डायरेक्ट्री स्ट्रक्चर के रूट में रहती है,

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

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

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

BUILD फ़ाइल को समझें

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

java-tutorial/BUILD फ़ाइल पर एक नज़र डालें:

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

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

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

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

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

bazel build //:ProjectRunner

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

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

अब अपने नए बाइनरी को टेस्ट करें:

bazel-bin/ProjectRunner

डिपेंडेंसी ग्राफ़ देखें

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

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

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"],
)

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

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

bazel build //:ProjectRunner

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

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 में बदलाव किया जाता है और प्रोजेक्ट को फिर से बनाया जाता है, तो Baze, सिर्फ़ उस फ़ाइल को फिर से कंपाइल करता है.

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

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

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

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

अब प्रोजेक्ट को कई पैकेज में बांट लें. src/main/java/com/example/cmdline डायरेक्ट्री में एक BUILD फ़ाइल के साथ-साथ कुछ सोर्स फ़ाइलें भी मौजूद हैं. इसलिए, Basel के लिए, फ़ाइल फ़ोल्डर में अब दो पैकेज शामिल हैं, //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 फ़ाइल में मौजूद दूसरे टारगेट को दिखते हैं. (Bazz, टारगेट की मदद से विज़िबिलिटी का इस्तेमाल करता है. इससे पब्लिक एपीआई में ले जाने वाली लाइब्रेरी जैसी समस्याओं से बचा जा सकता है. जैसे, लागू करने की जानकारी वाली लाइब्रेरी.)

ऐसा करने के लिए, 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 फ़ाइल होती है. आपने 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

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

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

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