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

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

इस ट्यूटोरियल में, 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 के बिल्ड आउटपुट मौजूद होते हैं. इसमें ऐसी फ़ाइलें भी शामिल होती हैं जिन्हें Basel ने खास के तौर पर पहचाना है:

  • 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 नियम को लागू करता है. यह नियम, बेज़ल को .jar फ़ाइल और एक रैपर शेल स्क्रिप्ट बनाने के लिए कहता है (दोनों का नाम टारगेट पर रखा गया है).

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

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

bazel-bin/ProjectRunner

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

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

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

bazel query  --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graph

ऊपर दिया गया निर्देश, Bazel को टारगेट //:ProjectRunner के लिए सभी डिपेंडेंसी ढूंढने के लिए कहता है. इसमें होस्ट और इनपुट डिपेंडेंसी शामिल नहीं हैं. साथ ही, यह निर्देश आउटपुट को ग्राफ़ के तौर पर फ़ॉर्मैट करने के लिए भी कहता है.

इसके बाद, टेक्स्ट को GraphViz में चिपकाएं.

जैसा कि आप देख सकते हैं, प्रोजेक्ट में एक टारगेट है, जो बिना किसी अतिरिक्त डिपेंडेंसी के दो सोर्स फ़ाइलें बनाता है:

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

अपना वर्कस्पेस सेट अप करने, प्रोजेक्ट बनाने, और उसकी डिपेंडेंसी की जांच करने के बाद, कुछ जटिलताएं जोड़ी जा सकती हैं.

अपने Basel के बिल्ड को बेहतर बनाएं

छोटे प्रोजेक्ट के लिए एक टारगेट काफ़ी होता है. हालांकि, बड़े प्रोजेक्ट को कई टारगेट और पैकेज में बांटने से, तेज़ी से इंक्रीमेंटल बिल्ड (यानी सिर्फ़ बदलाव किए गए हिस्से को फिर से बनाना) करने में मदद मिलती है. साथ ही, एक प्रोजेक्ट के कई हिस्सों को एक साथ बनाकर, बिल्ड करने की प्रोसेस को तेज़ किया जा सकता है.

एक से ज़्यादा बिल्ड टारगेट तय करना

सैंपल प्रोजेक्ट के बिल्ड को दो टारगेट में बांटा जा सकता है. 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 फ़ाइल होती है. आपने 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

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

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

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