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

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

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

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

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

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

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

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

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

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

जेडीके इंस्टॉल करें

  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
└── MODULE.bazel

Bazel के साथ बनाएं

वर्कस्पेस सेट अप करना

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

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

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

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

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

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

सैंपल प्रोजेक्ट बनाने के लिए, 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 एट्रिब्यूट, 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 और //. ऐसा इसलिए, क्योंकि Workspace के रूट में एक 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 फ़ाइल (name एट्रिब्यूट) में आपने टारगेट को target-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

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

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

बिल्डिंग मुबारक!