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

किसी समस्या की शिकायत करें सोर्स देखें Nightly · 7.4 . 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. सभी डायलॉग विंडो पर "ठीक है" पर क्लिक करें.

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

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 को, अपनी पसंद के आउटपुट बनाने का तरीका पता चलता है. जैसे, एक्ज़ीक्यूटेबल बाइनरी या लाइब्रेरी. 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 के आउटपुट स्ट्रक्चर के बारे में जानने के लिए, इसके कॉन्टेंट को ब्राउज़ करें.

अब हाल ही में बनाई गई बाइनरी की जांच करें:

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 एट्रिब्यूट का इस्तेमाल करके, //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

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

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

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