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

समस्या की शिकायत करें स्रोत देखें

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

पूरा होने का अनुमानित समय: 30 मिनट.

आप इन चीज़ों के बारे में जानेंगे

इस ट्यूटोरियल में, ये काम करने के तरीके बताए गए हैं:

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

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

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

ट्यूटोरियल के लिए तैयार होने के लिए, अगर आपने पहले से {1/} class="ph-1-1"> इंस्टॉल नहीं किया है, तो पहले इंस्टॉल करें.

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

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

किसी डायरेक्ट्री को Bazel फ़ाइल फ़ोल्डर के तौर पर तय करने के लिए, उस डायरेक्ट्री में 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 उन सोर्स फ़ाइलों के बारे में बताता है जिनका इस्तेमाल बैजल, टारगेट बनाने के लिए करता है, और main_class उस क्लास के बारे में बताता है जिसमें मुख्य तरीका शामिल है. (आपने देखा होगा कि हमारा उदाहरण, सोर्स फ़ाइलों को एक-एक करके दिखाने के बजाय, सोर्स फ़ाइलों के एक सेट को पास करने के लिए ग्लोब का इस्तेमाल करता है.)

प्रोजेक्ट बनाएं

अपना सैंपल प्रोजेक्ट बनाने के लिए, 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 एट्रिब्यूट, बैजल को बताता है कि 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 एट्रिब्यूट का इस्तेमाल करके, यह साफ़ तौर पर बताना होगा कि //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

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

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

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

इमारत बनाने के लिए शुभकामनाएं!