Bazel कोडबेस

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

इस दस्तावेज़ में, कोड बेस के बारे में जानकारी दी गई है. साथ ही, यह भी बताया गया है कि Bazel का स्ट्रक्चर कैसा है. यह उन लोगों के लिए है जो Bazel के लिए योगदान देना चाहते हैं, न कि असली उपयोगकर्ताओं के लिए.

शुरुआती जानकारी

Bazel का कोडबेस बड़ा है (~350KLOC प्रोडक्शन कोड और ~260 KLOC टेस्ट कोड) और कोई भी इस पूरे लैंडस्केप को अच्छे से नहीं जानता है: सभी को अपनी खास घाटी के बारे में अच्छी तरह पता है, लेकिन बहुत कम लोग जानते हैं कि हर तरफ़ पहाड़ियों के पीछे क्या होता है.

इस दस्तावेज़ में कोड बेस की खास जानकारी देने की कोशिश की गई है, ताकि सफ़र के दौरान लोग आसानी से जंगल में अंधेरे में अपना रास्ता खो दें.

Bazel के सोर्स कोड का पब्लिक वर्शन, GitHub पर github.com/bazelbuild/bazel पर मौजूद है. यह "सच का सोर्स" नहीं है, इसे Google के इंटरनल सोर्स ट्री से लिया गया है. इसमें ऐसी अतिरिक्त सुविधाएं भी हैं जो Google के बाहर काम की नहीं हैं. लंबे समय का लक्ष्य GitHub को सच्चाई का स्रोत बनाना है.

योगदानों को सामान्य GitHub पुल अनुरोध तकनीक के ज़रिए स्वीकार किया जाता है. इन्हें Googler मैन्युअल तौर पर इंटरनल सोर्स ट्री में इंपोर्ट करता है. इसके बाद, इन्हें वापस GitHub पर फिर से एक्सपोर्ट किया जाता है.

क्लाइंट/सर्वर आर्किटेक्चर

Bazel का ज़्यादातर हिस्सा एक सर्वर प्रोसेस में होता है, जो बिल्ड के बीच रैम में रहता है. इससे Bazel, बिल्ड के बीच स्थिति बनाए रख पाता है.

इसी वजह से Bazel कमांड लाइन के दो तरह के विकल्प होते हैं: स्टार्टअप और कमांड. इस तरह की कमांड लाइन में:

    bazel --host_jvm_args=-Xmx8G build -c opt //foo:bar

कुछ विकल्प (--host_jvm_args=), चलाए जाने वाले निर्देश के नाम से पहले और -c opt के बाद होते हैं. पहले वाले विकल्प को "स्टार्टअप विकल्प" कहा जाता है और पूरी सर्वर प्रोसेस पर असर पड़ता है. वहीं बाद वाले विकल्प, "कमांड विकल्प" से सिर्फ़ एक निर्देश पर असर पड़ता है.

हर सर्वर इंस्टेंस में एक ही फ़ाइल फ़ोल्डर होता है (सोर्स ट्री का कलेक्शन, जिसे "डेटा स्टोर करने की जगह" कहा जाता है). साथ ही, हर फ़ाइल फ़ोल्डर में आम तौर पर एक ही ऐक्टिव सर्वर इंस्टेंस होता है. कस्टम आउटपुट बेस तय करके इससे बचा जा सकता है (ज़्यादा जानकारी के लिए "डायरेक्ट्री लेआउट" सेक्शन देखें).

Bazel को एक ELF एक्ज़ीक्यूटेबल के तौर पर डिस्ट्रिब्यूट किया गया है, जो एक मान्य .zip फ़ाइल भी है. bazel टाइप करने पर, C++ ("क्लाइंट") में लागू किए गए ऊपर दिए गए ELF को कंट्रोल किया जाता है. यह नीचे दिए गए चरणों का इस्तेमाल करके एक सही सर्वर प्रोसेस सेट अप करता है:

  1. पता लगाता है कि क्या यह पहले से ही अपने-आप निकाला गया है. अगर नहीं, तो यही तरीका अपनाता है. सर्वर को यहीं से लागू किया जाता है.
  2. यह जांच करता है कि क्या कोई सक्रिय सर्वर इंस्टेंस है जो काम कर रहा है: यह चल रहा है, इसमें सही स्टार्टअप विकल्प हैं और सही फ़ाइल फ़ोल्डर डायरेक्ट्री का इस्तेमाल किया गया है. यह $OUTPUT_BASE/server डायरेक्ट्री में, चल रहे सर्वर को खोजता है. जहां पोर्ट वाली लॉक फ़ाइल मौजूद होती है, जिस पर सर्वर सुन रहा होता है.
  3. ज़रूरत पड़ने पर, पुराना सर्वर प्रोसेस बंद हो जाता है
  4. अगर ज़रूरी हो, तो एक नई सर्वर प्रोसेस शुरू करें

एक सही सर्वर प्रोसेस तैयार होने के बाद, चलाए जाने वाले निर्देश को gRPC इंटरफ़ेस पर बताया जाता है. इसके बाद, Bazel के आउटपुट को टर्मिनल में वापस पाइप कर दिया जाता है. एक समय पर सिर्फ़ एक निर्देश चल सकता है. इसे C++ में हिस्सों और Java के हिस्सों वाली एक बेहतर लॉकिंग प्रक्रिया का इस्तेमाल करके लागू किया जाता है. एक साथ कई कमांड चलाने के लिए कुछ इंफ़्रास्ट्रक्चर है. इसकी वजह यह है कि किसी दूसरे कमांड के साथ bazel version को न चला पाना थोड़ा शर्मिंदा करने वाला होता है. मुख्य ब्लॉकर, BlazeModule और BlazeRuntime की कुछ स्थिति की लाइफ़ साइकल है.

किसी निर्देश के आखिर में, Bazel सर्वर, एग्ज़िट कोड को भेजता है, जिसे क्लाइंट वापस भेजता है. दिलचस्प बात यह है कि bazel run को लागू किया जाना चाहिए: इस निर्देश का काम है, हाल ही में बनाए गए Bazel को चलाना. हालांकि, यह सर्वर प्रोसेस से ऐसा नहीं कर सकता, क्योंकि इसमें टर्मिनल नहीं है. इसलिए, यह क्लाइंट को यह बताता है कि उसे कौनसी बाइनरी को ujexec() करना चाहिए और उसे कौनसे आर्ग्युमेंट के साथ इस्तेमाल करना चाहिए.

जब एक व्यक्ति Ctrl-C दबाता है, तो क्लाइंट उसे gRPC कनेक्शन पर रद्द करें कॉल में बदल देता है, जो कमांड को जल्द से जल्द खत्म करने की कोशिश करता है. तीसरे Ctrl-C के बाद, क्लाइंट सर्वर को एक SIGKILL भेजता है.

क्लाइंट का सोर्स कोड src/main/cpp के तहत होता है और सर्वर से संपर्क करने के लिए इस्तेमाल किया जाने वाला प्रोटोकॉल src/main/protobuf/command_server.proto में होता है .

सर्वर का मुख्य एंट्री पॉइंट BlazeRuntime.main() है और क्लाइंट से आने वाले gRPC कॉल को GrpcServerImpl.run() हैंडल करता है.

डायरेक्ट्री का लेआउट

बिल्ड के दौरान Bazel, डायरेक्ट्री का कुछ हद तक मुश्किल सेट बनाता है. आउटपुट डायरेक्ट्री लेआउट में पूरी जानकारी मौजूद है.

"मुख्य संग्रह" वह सोर्स ट्री है जिसमें Bazel चलाया जा रहा है. आम तौर पर, यह किसी ऐसी चीज़ से जुड़ी होती है जिसे आपने सोर्स कंट्रोल से देखा था. इस डायरेक्ट्री के रूट को "वर्कस्पेस रूट" कहा जाता है.

Bazel अपना पूरा डेटा, "आउटपुट यूज़र रूट" के तहत रखता है. आम तौर पर, यह $HOME/.cache/bazel/_bazel_${USER} होता है, लेकिन --output_user_root स्टार्टअप विकल्प का इस्तेमाल करके, इसमें बदलाव किया जा सकता है.

"इंस्टॉल बेस" वह जगह है जहां Bazel को एक्सट्रैक्ट किया जाता है. यह काम अपने-आप होता है. हर Bazel वर्शन को, इंस्टॉल बेस में अपने चेकसम के आधार पर सबडायरेक्ट्री मिल जाती है. यह डिफ़ॉल्ट रूप से $OUTPUT_USER_ROOT/install पर होता है और इसे --install_base कमांड लाइन विकल्प का इस्तेमाल करके बदला जा सकता है.

"आउटपुट बेस" वह जगह है जहां किसी खास फ़ाइल फ़ोल्डर से जुड़ा Bazel इंस्टेंस लिखा होता है. हर आउटपुट बेस में किसी भी समय काम करने वाला ज़्यादा से ज़्यादा एक Bazel सर्वर इंस्टेंस होता है. आम तौर पर, यह $OUTPUT_USER_ROOT/<checksum of the path to the workspace> बजे होता है. इसे --output_base स्टार्टअप विकल्प का इस्तेमाल करके बदला जा सकता है. यह अन्य चीज़ों के अलावा, इस सीमा को पार करने में काम आता है कि किसी भी समय किसी भी फ़ाइल फ़ोल्डर में सिर्फ़ एक Bazel इंस्टेंस चल सकता है.

आउटपुट डायरेक्ट्री में, अन्य चीज़ों के अलावा ये चीज़ें भी होती हैं:

  • $OUTPUT_BASE/external पर फ़ेच किए गए बाहरी डेटा स्टोर करने की जगहें.
  • exec रूट एक ऐसी डायरेक्ट्री है जिसमें मौजूदा बिल्ड के सभी सोर्स कोड के सिमलिंक होते हैं. यह $OUTPUT_BASE/execroot पर है. बिल्ड के दौरान, वर्किंग डायरेक्ट्री $EXECROOT/<name of main repository> है. हम इसे बदलकर $EXECROOT करने की योजना बना रहे हैं. हालांकि, यह लंबी अवधि का प्लान है, क्योंकि यह बदलाव काम नहीं करता.
  • बिल्ड के दौरान बनाई गई फ़ाइलें.

किसी निर्देश को लागू करने की प्रोसेस

जैसे ही Bazel सर्वर कंट्रोल हो जाता है और उसे किसी निर्देश को चलाने की ज़रूरत पड़ती है, तो यह क्रम चलता है:

  1. BlazeCommandDispatcher को नए अनुरोध के बारे में सूचना दे दी गई है. यह तय करता है कि निर्देश को चलाने के लिए किसी फ़ाइल फ़ोल्डर की ज़रूरत है या नहीं (करीब हर निर्देश को छोड़कर, उन सभी कमांड को छोड़कर जिनका सोर्स कोड से कोई लेना-देना नहीं है, जैसे कि वर्शन या सहायता) और कोई दूसरा निर्देश चल रहा है या नहीं.

  2. सही निर्देश मिला. हर निर्देश को इंटरफ़ेस BlazeCommand लागू करना होगा और इसमें @Command एनोटेशन होना चाहिए (यह एक एंटीपैटर्न का हिस्सा है. यह अच्छा होगा अगर निर्देश की ज़रूरत वाले सभी मेटाडेटा के बारे में BlazeCommand पर दिए गए तरीकों से बताया गया हो)

  3. कमांड लाइन के विकल्पों को पार्स कर लिया गया है. हर निर्देश में अलग-अलग कमांड लाइन विकल्प होते हैं, जिनके बारे में @Command एनोटेशन में बताया गया है.

  4. इवेंट के लिए बस बनाई गई है. इवेंट बस, बिल्ड के दौरान होने वाले इवेंट की स्ट्रीम होती है. इनमें से कुछ को बिल्ड इवेंट प्रोटोकॉल के तहत Bazel के बाहर एक्सपोर्ट किया जाता है, ताकि दुनिया को यह बताया जा सके कि बिल्ड कैसे काम करता है.

  5. निर्देश को कंट्रोल किया जाता है. सबसे दिलचस्प निर्देश वे हैं जो बिल्ड चलाते हैं: बिल्ड, टेस्ट, रन, कवरेज वगैरह: इस फ़ंक्शन को BuildTool से लागू किया जाता है.

  6. कमांड लाइन पर टारगेट पैटर्न के सेट को पार्स किया गया है. साथ ही, //pkg:all और //pkg/... जैसे वाइल्डकार्ड को रिज़ॉल्व किया गया है. इसे AnalysisPhaseRunner.evaluateTargetPatterns() में लागू किया गया है और Skyframe में TargetPatternPhaseValue के तौर पर फिर से बदलाव किया गया है.

  7. लोडिंग/विश्लेषण का चरण, ऐक्शन ग्राफ़ बनाने के लिए चलाया जाता है. यह उन निर्देशों का एक क्रमिक ग्राफ़ होता है जिन्हें बिल्ड के लिए लागू करना होता है.

  8. प्रोग्राम चलाने का फ़ेज़ चलाया जाता है. इसका मतलब है कि अनुरोध किए गए टॉप-लेवल टारगेट को बनाने के लिए, सभी ज़रूरी कार्रवाइयां की जाएंगी.

कमांड लाइन के विकल्प

Bazel को शुरू करने के लिए कमांड लाइन के विकल्पों की जानकारी, OptionsParsingResult ऑब्जेक्ट में दी गई है. इसमें, "विकल्प की क्लास" से विकल्पों की वैल्यू तक का एक मैप शामिल होता है. "विकल्प क्लास", OptionsBase की सब-क्लास है. यह कमांड लाइन के विकल्पों को एक साथ ग्रुप करती है, जो एक-दूसरे से जुड़े होते हैं. उदाहरण के लिए:

  1. प्रोग्रामिंग भाषा (CppOptions या JavaOptions) से जुड़े विकल्प. ये FragmentOptions की सब-क्लास होनी चाहिए और बाद में BuildOptions ऑब्जेक्ट में रैप कर दी जाती हैं.
  2. Bazel कार्रवाइयों को पूरा करने के तरीके से जुड़े विकल्प (ExecutionOptions)

इन विकल्पों को विश्लेषण के चरण में इस्तेमाल करने के लिए डिज़ाइन किया गया है (या तो Java में RuleContext.getFragment() के ज़रिए या Starlark में ctx.fragments के ज़रिए). उनमें से कुछ (उदाहरण के लिए, क्या C++ में स्कैन करना शामिल है या नहीं) को लागू करने के दौरान पढ़ा जाता है, लेकिन उसके लिए हमेशा साफ़ तौर पर प्लंबिंग की ज़रूरत होती है, क्योंकि तब BuildConfiguration उपलब्ध नहीं होता. ज़्यादा जानकारी के लिए, "कॉन्फ़िगरेशन" सेक्शन देखें.

चेतावनी: हम यह दिखाते हैं कि OptionsBase इंस्टेंस नहीं बदले जा सकते हैं और उनका इस्तेमाल (जैसे कि SkyKeys का हिस्सा) इस तरह किया जाता है. हालांकि, ऐसा नहीं है. इनमें बदलाव करना एक अच्छा तरीका है, ताकि Bazel को आसानी से डीबग किया जा सके. माफ़ करें, उन फ़ाइलों को एक जैसा बनाना एक बहुत बड़ी कोशिश है. (किसी भी व्यक्ति को निर्माण के तुरंत बाद, FragmentOptions में बदलाव करने से उसका रेफ़रंस रखने का मौका मिलता है. साथ ही, equals() या hashCode() को कॉल किए जाने से पहले, उसमें बदलाव किया जा सकता है.)

Bazel, विकल्प क्लास के बारे में नीचे बताए गए तरीकों से सीखता है:

  1. इनमें से कुछ थर्मोस्टैट, Bazel (CommonCommandOptions) में हार्ड वायर से जुड़े हैं
  2. हर Bazel कमांड पर मौजूद @Command एनोटेशन से
  3. ConfiguredRuleClassProvider से (ये अलग-अलग प्रोग्रामिंग भाषाओं से संबंधित कमांड लाइन विकल्प हैं)
  4. Starlark नियम अपने खुद के विकल्प भी तय कर सकते हैं (यहां देखें)

हर विकल्प (Starlark के तय किए गए विकल्पों को छोड़कर), FragmentOptions सब-क्लास का एक मेंबर वैरिएबल है. इसमें @Option एनोटेशन होता है. इससे, कमांड लाइन विकल्प का नाम और टाइप के साथ-साथ कुछ सहायता टेक्स्ट की जानकारी भी मिलती है.

कमांड लाइन विकल्प की वैल्यू का Java टाइप, आम तौर पर आसान होता है. जैसे, स्ट्रिंग, पूर्णांक, बूलियन, लेबल वगैरह. हालांकि, हम ज़्यादा मुश्किल टाइप के विकल्प भी इस्तेमाल करने की सुविधा देते हैं. इस मामले में, कमांड लाइन स्ट्रिंग को डेटा टाइप में बदलने का काम com.google.devtools.common.options.Converter को लागू करना होता है.

सोर्स ट्री, जैसा कि बेज़ेल ने देखा

Bazel सॉफ़्टवेयर बनाने का कारोबार है. यह काम, सोर्स कोड को पढ़कर और उसे समझने पर होता है. Bazel, सोर्स कोड की जो जानकारी देता है उसे "वर्कस्पेस" कहा जाता है. इसे डेटा स्टोर करने की जगहों, पैकेज, और नियमों के हिसाब से बनाया जाता है.

डेटा स्टोर करने की जगह

"डेटा स्टोर करने की जगह" एक सोर्स ट्री होता है, जिस पर डेवलपर काम करता है. आम तौर पर, यह एक प्रोजेक्ट को दिखाता है. बैजल के पूर्वज ब्लेज़, मोनो रिपॉज़िटरी (एक ही सोर्स ट्री) पर काम करते थे. इसका मतलब है कि यह एक सोर्स ट्री है, जिसमें बिल्ड को चलाने के लिए इस्तेमाल किए गए सभी सोर्स कोड मौजूद होते हैं. वहीं दूसरी ओर, Bazel उन प्रोजेक्ट में मदद करता है जिनके सोर्स कोड का इस्तेमाल कई डेटा स्टोर करने की जगहों पर किया जाता है. जिस डेटा संग्रह से Bazel को शुरू किया जाता है उसे "मुख्य डेटा स्टोर करने की जगह" कहा जाता है, अन्य डेटा को "बाहरी डेटा स्टोर करने की जगह" कहा जाता है.

रिपॉज़िटरी को उसकी रूट डायरेक्ट्री में, रेपो बाउंड्री फ़ाइल (MODULE.bazel, REPO.bazel या लेगसी कॉन्टेक्स्ट में, WORKSPACE या WORKSPACE.bazel) से मार्क किया जाता है. मुख्य रेपो वह सोर्स ट्री है जहां से बाज़ेल का अनुरोध किया जा रहा है. डेटा स्टोर करने की बाहरी जगहें कई तरीकों से तय होती हैं. ज़्यादा जानकारी के लिए, बाहरी डिपेंडेंसी की खास जानकारी देखें.

डेटा स्टोर करने की बाहरी जगहों का कोड, $OUTPUT_BASE/external में सिमलिंक या डाउनलोड किया गया है.

बिल्ड चलाते समय, पूरे सोर्स ट्री को एक साथ जोड़ना ज़रूरी है. ऐसा SymlinkForest के ज़रिए किया जाता है, जो मुख्य रिपॉज़िटरी (डेटा स्टोर करने की जगह) में मौजूद हर पैकेज को $EXECROOT और हर बाहरी रिपॉज़िटरी के लिए सिमलिंक करता है. ऐसा, $EXECROOT/external या $EXECROOT/.. से होता है.

पैकेज

हर रिपॉज़िटरी, पैकेज से मिलकर बनती है. यह संबंधित फ़ाइलों का कलेक्शन और डिपेंडेंसी की खास जानकारी होती है. इन्हें BUILD या BUILD.bazel नाम की फ़ाइल से बताया जाता है. अगर दोनों मौजूद हैं, तो Bazel BUILD.bazel को प्राथमिकता देता है. BUILD फ़ाइलों को अब भी स्वीकार किए जाने की वजह यह है कि Bazel के पूर्वज, Blaze ने इस फ़ाइल नाम का इस्तेमाल किया था. हालांकि, यह आम तौर पर इस्तेमाल किया जाने वाला पाथ सेगमेंट बन गया है, खास तौर पर Windows में, जहां फ़ाइल के नाम केस-इनसेंसिटिव होते हैं.

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

BUILD फ़ाइल की जांच को "पैकेज लोड करना" कहते हैं. इसे PackageFactory क्लास में लागू किया गया है. यह Starlark अनुवादक को कॉल करता है. इसके लिए, उपलब्ध नियम क्लास के सेट की जानकारी होना ज़रूरी है. पैकेज लोड होने पर, Package ऑब्जेक्ट मिलता है. यह ज़्यादातर मैप की स्ट्रिंग (टारगेट का नाम) से टारगेट तक का होता है.

पैकेज लोड होने के दौरान, बहुत बड़ी मुश्किल होती है ग्लोबिंग: Bazel के लिए हर सोर्स फ़ाइल को साफ़ तौर पर लिस्ट करने की ज़रूरत नहीं होती. इसके बजाय, यह ग्लॉब (जैसे कि glob(["**/*.java"])) चला सकता है. शेल के उलट, यह बार-बार इस्तेमाल होने वाले ग्लॉब के साथ काम करता है जो सबडायरेक्ट्री में चले जाते हैं, लेकिन सब-पैकेज में नहीं. इसके लिए फ़ाइल सिस्टम की ज़रूरत होती है और यह धीमा हो सकता है. इसलिए, हम इसे साथ-साथ चलाने के लिए सभी तरह की तरकीबें लागू करते हैं और जितना हो सके उतने बेहतर तरीके से काम करते हैं.

ग्लोबिंग को इन क्लास में लागू किया गया है:

  • LegacyGlobber, एक तेज़ और खुशी से चलने वाला Skyframe से अनजान ग्लोबर
  • SkyframeHybridGlobber, यह ऐसा वर्शन है जिसमें Skyframe का इस्तेमाल किया जाता है. साथ ही, "Skyframe के रीस्टार्ट होने" से बचने के लिए, यह पुराने ग्लोबर पर वापस चला जाता है. इसके बारे में नीचे बताया गया है

Package क्लास में कुछ ऐसे सदस्य होते हैं जिनका इस्तेमाल खास तौर पर, "बाहरी" पैकेज (बाहरी डिपेंडेंसी से जुड़ा) को पार्स करने के लिए किया जाता है. इस पैकेज का असल से कोई मतलब नहीं होता. यह डिज़ाइन की गड़बड़ी है, क्योंकि रेगुलर पैकेज के बारे में बताने वाले ऑब्जेक्ट में ऐसी फ़ील्ड नहीं होनी चाहिए जो किसी और चीज़ के बारे में बताती हो. इनमें शामिल हैं:

  • रिपॉज़िटरी की मैपिंग
  • रजिस्टर किए गए टूलचेन
  • प्रोग्राम चलाने के लिए रजिस्टर किए गए प्लैटफ़ॉर्म

आम तौर पर, "बाहरी" पैकेज को पार्स करने और सामान्य पैकेज को पार्स करने के बीच ज़्यादा फ़र्क़ होगा. इससे Package को, दोनों की ज़रूरतों को पूरा करने की ज़रूरत नहीं पड़ेगी. माफ़ करें, ऐसा करना मुश्किल है, क्योंकि दोनों आपस में बहुत ज़्यादा आपस में जुड़े हुए हैं.

लेबल, टारगेट, और नियम

पैकेज में टारगेट शामिल होते हैं और इनमें ये टाइप शामिल होते हैं:

  1. फ़ाइलें: ऐसी चीज़ें जो बिल्ड का इनपुट या आउटपुट होती हैं. बज़ेल की भाषा में, हम उन्हें आर्टफ़ैक्ट कहते हैं (इसके बारे में कहीं और चर्चा की गई है). बिल्ड के दौरान बनाई गई सभी फ़ाइलें टारगेट नहीं होती हैं. आम तौर पर, Bazel के पास उनसे जुड़ा कोई लेबल नहीं होता है.
  2. नियम: यहां दिए गए इनपुट से इसके नतीजे पाने का तरीका बताया गया है. ये आम तौर पर किसी प्रोग्रामिंग भाषा (जैसे कि cc_library, java_library या py_library) से जुड़े होते हैं, लेकिन कुछ लैंग्वेज-ऐग्नोस्टिक भी होते हैं (जैसे कि genrule या filegroup)
  3. पैकेज ग्रुप: इनके बारे में किसको दिखे सेक्शन में बताया गया है.

टारगेट के नाम को लेबल कहा जाता है. लेबल का सिंटैक्स @repo//pac/kage:name है. इसमें repo, उस रिपॉज़िटरी का नाम है जिसमें लेबल मौजूद है. pac/kage वह डायरेक्ट्री है जिसमें BUILD फ़ाइल मौजूद है. साथ ही, name, पैकेज की डायरेक्ट्री से जुड़ी फ़ाइल का पाथ (अगर लेबल किसी सोर्स फ़ाइल के बारे में बताता है) है. कमांड लाइन पर टारगेट के बारे में बताते समय, लेबल के कुछ हिस्सों को हटाया जा सकता है:

  1. अगर डेटा स्टोर करने की जगह को हटाया जाता है, तो लेबल को मुख्य डेटा स्टोर करने की जगह में ले जाया जाता है.
  2. अगर पैकेज वाला हिस्सा हटा दिया जाता है (जैसे कि name या :name), तो लेबल को मौजूदा वर्किंग डायरेक्ट्री के पैकेज में ले जाया जाता है (अप-लेवल रेफ़रंस (..) वाले मिलते-जुलते पाथ की अनुमति नहीं है)

एक तरह के नियम (जैसे कि "C++ लाइब्रेरी") को "नियम क्लास" कहा जाता है. नियम की क्लास या तो Starlark (rule() फ़ंक्शन) या Java (जिसे "नेटिव रूल", टाइप RuleClass कहा जाता है) में लागू की जा सकती हैं. आने वाले समय में, हर भाषा के हिसाब से नियम Starlark में लागू होंगे, लेकिन कुछ लेगसी रूल फ़ैमिली (जैसे, Java या C++) अब भी कुछ समय से Java में हैं.

स्टारलार्क नियम की क्लास को load() स्टेटमेंट का इस्तेमाल करके, BUILD फ़ाइलों की शुरुआत में इंपोर्ट किया जाना चाहिए. वहीं, जावा नियम की क्लास को "आसानी से" के तौर पर जाना जाता है. इसे Bazel के लिए रजिस्टर किया जाता है, क्योंकि इन्हें ConfiguredRuleClassProvider के साथ रजिस्टर किया जाता है.

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

  1. इसके एट्रिब्यूट (जैसे कि srcs, deps): उनके टाइप, डिफ़ॉल्ट वैल्यू, सीमाएं वगैरह.
  2. अगर कोई हो, तो हर एट्रिब्यूट से जुड़े कॉन्फ़िगरेशन ट्रांज़िशन और पहलुओं
  3. नियम को लागू करने का तरीका
  4. सार्वजनिक तौर पर जानकारी देने वाला वह नियम जिसके तहत "आम तौर पर" इस्तेमाल किया जाता है

शब्दावली नोट: कोड बेस में, हम अक्सर नियम क्लास से बनाए गए टारगेट का मतलब करने के लिए "नियम" का इस्तेमाल करते हैं. हालांकि, Starlark और लोगों को दिखने वाले दस्तावेज़ों में, "नियम" का इस्तेमाल खास तौर पर नियम की कैटगरी को रेफ़र करने के लिए किया जाना चाहिए. टारगेट सिर्फ़ एक "टारगेट" होता है. यह भी ध्यान रखें कि RuleClass के नाम में "class" होने के बावजूद, किसी नियम क्लास और उस तरह के टारगेट के बीच, Java इनहेरिटेंस का कोई संबंध नहीं होता है.

स्काईफ़्रेम

Bazel के इवैलुएशन फ़्रेमवर्क को Skyframe कहा जाता है. का मॉडल

ग्राफ़ के नोड को SkyValue कहा जाता है और उनके नामों को SkyKey कहा जाता है. दोनों ही पूरी तरह से नहीं बदले जा सकने वाले हैं. इनमें सिर्फ़ वे ऑब्जेक्ट होने चाहिए जिन्हें बदला न जा सके. आम तौर पर, इस तरह के वैरिएंट में बदलाव नहीं होता है. अगर ऐसा नहीं होता है, तो हम इन्हें बदलने या सिर्फ़ उन तरीकों से बदलाव करने की कोशिश करते हैं जो बाहर से देखने लायक न हों. जैसे, अलग-अलग विकल्पों की क्लास BuildOptions, जो कि BuildConfigurationValue और इसके SkyKey का सदस्य है, हम कड़ी मेहनत करते हैं. इसके बाद, यह पता चलता है कि Skyframe में कंप्यूट किए गए सभी टास्क (जैसे कि कॉन्फ़िगर किए गए टारगेट) नहीं बदले जा सकने वाले होने चाहिए.

स्काईफ़्रेम ग्राफ़ को देखने का सबसे आसान तरीका bazel dump --skyframe=deps को चलाना है. इससे ग्राफ़ को एक लाइन में एक SkyValue की रफ़्तार से डंप किया जाता है. छोटे बिल्ड के लिए ऐसा करना सबसे अच्छा होता है, क्योंकि यह बहुत बड़ा हो सकता है.

Skyframe, com.google.devtools.build.skyframe पैकेज में मौजूद है. इसी नाम वाले पैकेज com.google.devtools.build.lib.skyframe में, Skyframe के ऊपर Bazel को लागू किया गया. Skyframe के बारे में ज़्यादा जानकारी यहां उपलब्ध है.

दिए गए SkyKey का SkyValue के रूप में आकलन करने के लिए, Skyframe कुंजी के प्रकार के हिसाब से SkyFunction को शुरू करेगा. फ़ंक्शन के आकलन के दौरान, यह SkyFunction.Environment.getValue() के अलग-अलग ओवरलोड को कॉल करके, Skyframe से दूसरी डिपेंडेंसी का अनुरोध कर सकता है. इसका असर इन डिपेंडेंसी को Skyframe के अंदरूनी ग्राफ़ में रजिस्टर करने पर पड़ता है. इससे पता चलता है कि जब Skyframe की कोई डिपेंडेंसी बदली जाएगी, तो वह फ़ंक्शन का फिर से आकलन करेगा. दूसरे शब्दों में कहें, तो Skyframe की कैशिंग और इंक्रीमेंटल कंप्यूटेशन सुविधा, SkyFunction और SkyValue के लेवल पर काम करती है.

जब भी कोई SkyFunction किसी ऐसी डिपेंडेंसी का अनुरोध करता है जो उपलब्ध नहीं है, तो getValue() शून्य दिखाएगा. इसके बाद फ़ंक्शन को बिना कोई वैल्यू लौटाकर, Skyframe को कंट्रोल वापस मिल जाना चाहिए. बाद में, Skyframe उपलब्ध नहीं होने की डिपेंडेंसी का आकलन करेगा. इसके बाद, फ़ंक्शन को शुरू से रीस्टार्ट करेगा — सिर्फ़ इस बार getValue() कॉल, नॉन-शून्य नतीजे के साथ काम करेगा.

इसका नतीजा यह होता है कि रीस्टार्ट से पहले SkyFunction में किए गए कैलकुलेशन को दोहराया जाना चाहिए. हालांकि, इसमें डिपेंडेंसी SkyValues का आकलन करने के लिए किया गया काम शामिल नहीं है, जिसे कैश मेमोरी में सेव किया जाता है. इसलिए, हम आम तौर पर इस समस्या को ऐसे हल करते हैं:

  1. रीस्टार्ट होने की संख्या को सीमित करने के लिए, getValuesAndExceptions() का इस्तेमाल करके बैच में डिपेंडेंसी तय करें.
  2. SkyValue को अलग-अलग हिस्सों में बांटा गया है, जिनका हिसाब अलग-अलग SkyFunction कोड ने लगाया है, ताकि उनका हिसाब लगाया जा सके और उन्हें अलग-अलग कैश मेमोरी में सेव किया जा सके. इसे सोच-समझकर किया जाना चाहिए, क्योंकि इससे मेमोरी का इस्तेमाल बढ़ सकता है.
  3. SkyFunction.Environment.getState() का इस्तेमाल करके या ऐड-हॉक स्टैटिक कैश मेमोरी को "Siframe के पीछे" रखकर, रीस्टार्ट करने के बीच में स्टोर करना. जटिल Skyफ़ंक्शन में, रीस्टार्ट होने के बीच स्टेट मैनेजमेंट मुश्किल हो सकता है. इसलिए, StateMachine को लॉजिकल कंकरेंसी के लिए, व्यवस्थित तरीके से पेश किया गया था. इसमें, SkyFunction में हैरारकी (हैरारकी) कंप्यूटेशन को निलंबित करने और फिर से शुरू करने वाले हुक शामिल हैं. उदाहरण: DependencyResolver#computeDependencies किसी कॉन्फ़िगर किए गए टारगेट की सीधे तौर पर डिपेंडेंसी के संभावित बड़े सेट का पता लगाने के लिए, getState() के साथ StateMachine का इस्तेमाल करता है. ऐसा न करने पर, रीस्टार्ट करने में ज़्यादा खर्च लग सकता है.

बुनियादी तौर पर, Bazel को इस तरह के समाधानों की ज़रूरत है, क्योंकि हज़ारों इन-फ़्लाइट Skyframe नोड आम हैं. साथ ही, जावा लाइटवेट थ्रेड के लिए, 2023 में लागू की गईStateMachine प्रोसेस की तुलना में बेहतर नहीं रहा.

स्टारलार्क

Starlark, डोमेन की खास भाषा है. इसका इस्तेमाल, लोग Bazel को कॉन्फ़िगर करने और उसे बढ़ाने के लिए करते हैं. इसे Python के प्रतिबंधित सबसेट के तौर पर माना गया है. इसमें कंट्रोल फ़्लो के लिए ज़्यादा पाबंदियां और कम टाइप हैं. साथ ही, एक साथ पढ़ने की सुविधा को चालू करने के लिए, मज़बूत नहीं बदले जा सकने की गारंटी दी गई है. यह ट्यूरिंग-कंप्लीट नहीं होता, जिससे कुछ (लेकिन सभी नहीं) उपयोगकर्ताओं को भाषा में सामान्य प्रोग्रामिंग टास्क पूरा करने से प्रेरणा मिलती है.

Starlark को net.starlark.java पैकेज में लागू किया गया है. इसे लागू करने के लिए, Go को यहां स्वतंत्र रूप से भी लागू किया गया है. फ़िलहाल, Bazel में इस्तेमाल किया जाने वाला Java लागू करने का तरीका, एक इंटरप्रेटर है.

Starlark का इस्तेमाल कई कॉन्टेक्स्ट में किया जाता है. इनमें ये शामिल हैं:

  1. BUILD फ़ाइलें. यहां नए बिल्ड टारगेट तय किए जाते हैं. इस संदर्भ में चलने वाले Starlark कोड के पास, सिर्फ़ BUILD फ़ाइल के कॉन्टेंट और उससे लोड की गई .bzl फ़ाइलों का ऐक्सेस है.
  2. MODULE.bazel फ़ाइल. इसी जगह पर बाहरी डिपेंडेंसी तय होती है. इस संदर्भ में चलने वाले Starlark कोड के पास, पहले से तय किए गए कुछ निर्देशों का सीमित ऐक्सेस है.
  3. .bzl फ़ाइलें. यहां नए बिल्ड नियम, रिपो नियम, मॉड्यूल एक्सटेंशन तय किए जाते हैं. यहां Starlark कोड नए फ़ंक्शन तय कर सकता है और अन्य .bzl फ़ाइलों से लोड कर सकता है.

BUILD और .bzl फ़ाइलों के लिए उपलब्ध बोलियां थोड़ी अलग होती हैं, क्योंकि वे अलग-अलग चीज़ों को दिखाती हैं. अंतर की सूची यहां दी गई है.

Starlark के बारे में ज़्यादा जानकारी यहां उपलब्ध है.

लोडिंग/विश्लेषण का चरण

लोडिंग/विश्लेषण का चरण वह जगह है जहां Bazel तय करता है कि किसी खास नियम को बनाने के लिए किन कार्रवाइयों की ज़रूरत है. इसकी बुनियादी इकाई "कॉन्फ़िगर किया गया टारगेट" है, जो काफ़ी हद तक, एक (टारगेट, कॉन्फ़िगरेशन) जोड़ी है.

इसे "लोडिंग/विश्लेषण चरण" कहा जाता है. इसे दो अलग-अलग हिस्सों में बांटा जा सकता है, जिन्हें पहले क्रम से लगाया जाता था, लेकिन अब वे समय के साथ ओवरलैप कर सकते हैं:

  1. पैकेज लोड होने का मतलब है, BUILD फ़ाइलों को उन Package ऑब्जेक्ट में बदलना जो उन्हें दिखाते हैं
  2. कॉन्फ़िगर किए गए टारगेट का विश्लेषण करना. इसमें बताया गया है कि ऐक्शन ग्राफ़ बनाने के लिए, नियमों को लागू करने की प्रोसेस कैसे की जाती है

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

  1. कॉन्फ़िगरेशन. ("कैसे" कहा जा सकता है; उदाहरण के लिए, टारगेट प्लैटफ़ॉर्म के साथ-साथ कमांड लाइन के विकल्प जैसी ऐसी चीज़ें जिन्हें उपयोगकर्ता C++ कंपाइलर को पास करना चाहता है)
  2. डायरेक्ट डिपेंडेंसी. ये कंपनियां, जिन नियमों का विश्लेषण कर रही हैं, उनके लिए डेटा उपलब्ध करवाता है. इन्हें इस तरह कहा जाता है, क्योंकि ये कॉन्फ़िगर किए गए टारगेट के ट्रांज़िटिव क्लोज़र में जानकारी का "रोल-अप" देते हैं, जैसे कि क्लासपाथ पर सभी .जार फ़ाइलें या वे सभी .o फ़ाइलें जिन्हें C++ बाइनरी से लिंक करने की ज़रूरत होती है)
  3. खुद टारगेट. यह टारगेट, उस पैकेज को लोड करने का नतीजा होता है. नियमों के लिए, इसमें इसके एट्रिब्यूट शामिल होते हैं, जो आम तौर पर मायने रखते हैं.
  4. कॉन्फ़िगर किए गए टारगेट को लागू करना. नियमों के लिए, यह या तो Starlark या Java में हो सकता है. नियम के बिना कॉन्फ़िगर किए गए सभी टारगेट, Java में लागू किए जाते हैं.

कॉन्फ़िगर किए गए टारगेट के विश्लेषण का आउटपुट यह है:

  1. जानकारी देने वाली वे कंपनियां जो इस पर निर्भर टारगेट कॉन्फ़िगर करती हैं, वे ऐक्सेस कर सकती हैं
  2. इससे बनाए जा सकने वाले आर्टफ़ैक्ट और उन्हें बनाने वाली कार्रवाइयां.

Java के नियमों को दिया जाने वाला एपीआई, RuleContext है. यह Starlark नियमों के ctx आर्ग्युमेंट की तरह है. इसका एपीआई ज़्यादा असरदार है, लेकिन साथ ही साथ, Bad ThingsTM को करना भी आसान है. उदाहरण के लिए, ऐसे कोड लिखना जिसके समय या स्पेस की जटिलता को क्वाड्रेटिक (या खराब) होना चाहिए. इससे, Java अपवाद के साथ Bazel सर्वर क्रैश हो सकता है या इन्वेरिएंट का उल्लंघन हो सकता है (जैसे, अनजाने में किसी Options इंस्टेंस में बदलाव करना या कॉन्फ़िगर किए गए टारगेट को म्यूट करना)

कॉन्फ़िगर किए गए टारगेट की सीधे तौर पर डिपेंडेंसी तय करने वाला एल्गोरिदम, DependencyResolver.dependentNodeMap() में रहता है.

कॉन्फ़िगरेशन

कॉन्फ़िगरेशन से यह पता चलता है कि लक्ष्य बनाने का "कैसे" है: किस प्लैटफ़ॉर्म के लिए, किस कमांड लाइन के विकल्प के साथ वगैरह.

एक ही टारगेट को एक ही बिल्ड में कई कॉन्फ़िगरेशन के लिए बनाया जा सकता है. यह

सैद्धान्तिक तौर पर, कॉन्फ़िगरेशन एक BuildOptions इंस्टेंस है. हालांकि, व्यावहारिक तौर पर, BuildOptions को BuildConfiguration रैप करता है, जो कि अतिरिक्त फ़ंक्शन मुहैया कराता है. यह डिपेंडेंसी ग्राफ़ के ऊपर से नीचे तक लागू होता है. अगर इसमें बदलाव होता है, तो बिल्ड का फिर से विश्लेषण करना होगा.

इसकी वजह से पूरे बिल्ड का फिर से विश्लेषण करना जैसी अनियमितताएं होती हैं, उदाहरण के लिए, अनुरोध किए गए टेस्ट की संख्या में बदलाव होता है, भले ही उससे सिर्फ़ टेस्ट टारगेट पर असर पड़ता हो. कॉन्फ़िगरेशन में "काट-छांट" करने की हमारी योजना है, ताकि ऐसा न हो, लेकिन यह अभी तैयार नहीं है.

जब किसी नियम को लागू करने के लिए कॉन्फ़िगरेशन के किसी हिस्से की ज़रूरत होती है, तो उसे RuleClass.Builder.requiresConfigurationFragments() का इस्तेमाल करके, उसकी परिभाषा के मुताबिक इसकी जानकारी देनी होती है. यह दोनों गलतियों से बचने के लिए होता है (जैसे कि Java फ़्रैगमेंट का इस्तेमाल करने वाले Python नियम) और कॉन्फ़िगरेशन में काट-छांट करना आसान बनाया जाता है, ताकि Python विकल्प बदलने पर, C++ टारगेट का फिर से विश्लेषण करने की ज़रूरत न पड़े.

यह ज़रूरी नहीं है कि किसी नियम का कॉन्फ़िगरेशन उसके "पैरंट" नियम के जैसा ही हो. किसी डिपेंडेंसी एज में कॉन्फ़िगरेशन बदलने की प्रोसेस को "कॉन्फ़िगरेशन ट्रांज़िशन" कहा जाता है. ऐसा दो जगहों पर हो सकता है:

  1. डिपेंडेंसी के किनारे पर. ये ट्रांज़िशन, Attribute.Builder.cfg() में बताए गए हैं. ये फ़ंक्शन Rule (जहां ट्रांज़िशन होता है) और BuildOptions (मूल कॉन्फ़िगरेशन) से एक या उससे ज़्यादा BuildOptions (आउटपुट कॉन्फ़िगरेशन) में बदला जाता है.
  2. कॉन्फ़िगर किए गए टारगेट के लिए, किसी भी इनकमिंग किनारे पर. इनकी जानकारी RuleClass.Builder.cfg() में दी गई है.

काम की क्लास TransitionFactory और ConfigurationTransition हैं.

कॉन्फ़िगरेशन ट्रांज़िशन का इस्तेमाल किया जाता है, उदाहरण के लिए:

  1. यह बताने के लिए कि बिल्ड के दौरान किसी खास डिपेंडेंसी का इस्तेमाल किया जाता है और इसे एक्ज़ीक्यूशन आर्किटेक्चर में बनाया जाना चाहिए
  2. यह एलान करने के लिए कि एक से ज़्यादा आर्किटेक्चर (जैसे कि Android APKs में फ़ैट वाले नेटिव कोड के लिए) के लिए कोई खास डिपेंडेंसी बनाई जानी चाहिए

अगर किसी कॉन्फ़िगरेशन ट्रांज़िशन के नतीजे में एक से ज़्यादा कॉन्फ़िगरेशन होते हैं, तो उसे स्प्लिट ट्रांज़िशन कहा जाता है.

कॉन्फ़िगरेशन ट्रांज़िशन को Starlark में भी लागू किया जा सकता है (दस्तावेज़, यहां देखें)

सार्वजनिक परिवहन के लिए जानकारी देने वाली कंपनियां

ट्रांज़िट स्थिति वाली जानकारी देने वाली कंपनियां, कॉन्फ़िगर किए गए टारगेट के लिए _only _way हैं. इससे, कॉन्फ़िगर किए गए उन अन्य टारगेट के बारे में जानकारी मिलती है जो इस पर निर्भर हैं. उनके नाम में "ट्रांज़िक" होने की वजह यह है कि आम तौर पर यह कॉन्फ़िगर किए गए टारगेट के कुछ समय के लिए ही प्रोफ़ाइल बंद करने का तरीका होता है.

आम तौर पर, Java के प्लैटफ़ॉर्म और Starlark के बीच 1:1 मेल-जोल होता है. हालांकि, DefaultInfo का अपवाद, FileProvider, FilesToRunProvider, और RunfilesProvider का मिला-जुला रूप है, क्योंकि इस एपीआई को Java के ट्रांसलिट्रेशन के मुकाबले, Starlark-ish ज़्यादा माना गया है. उनकी कुंजी नीचे दी गई चीज़ों में से एक है:

  1. Java क्लास ऑब्जेक्ट. यह सिर्फ़ उन कंपनियों के लिए उपलब्ध है जिन्हें Starlark से ऐक्सेस नहीं किया जा सकता. सेवा देने वाली ये कंपनियां, TransitiveInfoProvider की सब-क्लास हैं.
  2. स्ट्रिंग. यह पुराना तरीका है और इसकी सलाह बिलकुल नहीं दी जाती, क्योंकि इससे नाम के साथ टकराव हो सकता है. ट्रांज़िट स्थिति वाली जानकारी देने वाली ऐसी कंपनियां, build.lib.packages.Info की डायरेक्ट सब-क्लास हैं .
  3. सेवा देने वाली कंपनी का सिंबल. इसे provider() फ़ंक्शन का इस्तेमाल करके Starlark से बनाया जा सकता है. यह सेवा देने वाली नई कंपनियां बनाने के लिए, सुझाया गया तरीका है. इस चिह्न को Java में Provider.Key इंस्टेंस से दिखाया जाता है.

Java में लागू की गई नई कंपनियों को BuiltinProvider का इस्तेमाल करके लागू किया जाना चाहिए. NativeProvider के इस्तेमाल पर रोक लगा दी गई है (हमें अभी तक इसे हटाने का समय नहीं मिला है) और Starlark से TransitiveInfoProvider सब-क्लास ऐक्सेस नहीं की जा सकती.

कॉन्फ़िगर किए गए टारगेट

कॉन्फ़िगर किए गए टारगेट RuleConfiguredTargetFactory के तौर पर लागू किए जाते हैं. Java में लागू की गई हर नियम क्लास के लिए एक सब-क्लास होती है. Starlark कॉन्फ़िगर किए गए टारगेट StarlarkRuleConfiguredTargetUtil.buildRule() से बनाए गए हैं .

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

  1. "यह नियम, फ़ाइलों के जिस सेट को दिखाता है" उसका filesToBuild." ये वे फ़ाइलें होती हैं जो तब बनाई जाती हैं, जब कॉन्फ़िगर किया गया टारगेट कमांड लाइन पर या किसी जेनरूल के srcs में होता है.
  2. उनकी रनफ़ाइलें, सामान्य डेटा, और डेटा.
  3. उनके आउटपुट ग्रुप. ये "फ़ाइलों के अन्य सेट" हैं, जिन्हें नियम बना सकता है. इन्हें BUILD में filegroup नियम केout_group एट्रिब्यूट का इस्तेमाल करके और Java में, OutputGroupInfo की सेवा देने वाली कंपनी की मदद से ऐक्सेस किया जा सकता है.

रनफ़ाइलें

कुछ बाइनरी में चलाने के लिए डेटा फ़ाइलों की ज़रूरत होती है. एक खास उदाहरण ऐसी जांच है जिनके लिए इनपुट फ़ाइलों की ज़रूरत होती है. इसे Bazel में, "रनफ़ाइल" के सिद्धांत से दिखाया गया है. "रनफ़ाइल ट्री", किसी खास बाइनरी की डेटा फ़ाइलों का डायरेक्ट्री ट्री है. इसे फ़ाइल सिस्टम में एक सिमलिंक ट्री के तौर पर बनाया जाता है. इसमें अलग-अलग सिमलिंक होते हैं, जो आउटपुट ट्री के सोर्स में मौजूद फ़ाइलों की ओर इशारा करते हैं.

रनफ़ाइलों का सेट, Runfiles इंस्टेंस के तौर पर दिखाया जाता है. सैद्धांतिक तौर पर, यह रनफ़ाइल ट्री में किसी फ़ाइल के पाथ से लेकर Artifact इंस्टेंस तक का एक मैप होता है. यह दो वजहों से एक Map से थोड़ा ज़्यादा मुश्किल है:

  • ज़्यादातर मामलों में, किसी फ़ाइल का रनफ़ाइल पाथ वही होता है जो उसके एक्ज़िक्यूटिव पाथ में होता है. हम इसका इस्तेमाल कुछ रैम सेव करने के लिए करते हैं.
  • रनफ़ाइल ट्री में कई तरह की लेगसी एंट्री हैं, जिन्हें दिखाना भी ज़रूरी है.

रनफ़ाइल को RunfilesProvider का इस्तेमाल करके इकट्ठा किया जाता है: इस क्लास का इंस्टेंस, कॉन्फ़िगर किए गए टारगेट (जैसे कि लाइब्रेरी) और इसकी ट्रांज़िटिव बंद करने की ज़रूरतों को रनफ़ाइल के तौर पर दिखाता है और उन्हें नेस्ट किए गए सेट की तरह इकट्ठा किया जाता है (असल में, उन्हें कवर के तहत नेस्ट किए गए सेट का इस्तेमाल करके लागू किया जाता है): हर टारगेट यूनियन अपनी डिपेंडेंसी की रनफ़ाइल्स को जोड़ता है, फिर डिपेंडेंसी ग्राफ़ में ऊपर की ओर नतीजे भेजता है. एक RunfilesProvider इंस्टेंस में दो Runfiles इंस्टेंस होते हैं. पहला, "डेटा" एट्रिब्यूट की मदद से नियम पर निर्भर होने पर और दूसरा, आने वाली हर डिपेंडेंसी के लिए. ऐसा इसलिए है, क्योंकि कभी-कभी टारगेट, डेटा एट्रिब्यूट पर निर्भर होने पर अलग-अलग रनफ़ाइल दिखाता है. यह पुरानी लेगसी कार्रवाई है, जिसे हटाने के बारे में हम अभी तक कुछ नहीं कर पाए हैं.

बाइनरी की रनफ़ाइल को RunfilesSupport के इंस्टेंस के तौर पर दिखाया जाता है. यह Runfiles से अलग है, क्योंकि RunfilesSupport में असल में बनाने की क्षमता है (Runfiles से अलग, जो सिर्फ़ एक मैपिंग है). इसके लिए इन अतिरिक्त कॉम्पोनेंट की ज़रूरत होती है:

  • इनपुट रनफ़ाइल मेनिफ़ेस्ट. यह रनफ़ाइल ट्री की क्रम में दी गई जानकारी है. इसका इस्तेमाल रनफ़ाइल ट्री के कॉन्टेंट के लिए प्रॉक्सी के तौर पर किया जाता है. बैज़ल यह मानता है कि अगर मेनिफ़ेस्ट का कॉन्टेंट बदलता है, तो रनफ़ाइल ट्री बदलता है.
  • आउटपुट रनफ़ाइल मेनिफ़ेस्ट. इसका इस्तेमाल रनटाइम लाइब्रेरी में किया जाता है, जो रनफ़ाइल ट्री को हैंडल करती है. खास तौर पर, Windows पर. इनमें कभी-कभी सिंबॉलिक लिंक काम नहीं करते हैं.
  • फ़ंड फ़ाइल का बिचौलिए. रनफ़ाइल ट्री बनाने के लिए, किसी व्यक्ति को सिमलिंक ट्री बनाना होगा. साथ ही, वह आर्टफ़ैक्ट भी बनाना होगा जिसकी ओर सिमलिंक ले जाता है. डिपेंडेंसी के किनारों की संख्या को कम करने के लिए, रन फ़ाइल्स मिडिलमैन का इस्तेमाल इन सभी को दिखाने के लिए किया जा सकता है.
  • उस बाइनरी को चलाने के लिए कमांड लाइन तर्क, जिसकी रनफ़ाइल RunfilesSupport ऑब्जेक्ट दिखाती है.

पक्ष

आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) "डिपेंडेंसी ग्राफ़" में, कंप्यूटेशन को लागू करने का तरीका है. इसके बारे में, यहां Bazel के उपयोगकर्ताओं के लिए बताया गया है. इसका एक बेहतर उदाहरण प्रोटोकॉल बफ़र है: proto_library नियम के लिए किसी खास भाषा की जानकारी नहीं होनी चाहिए. हालांकि, किसी भी प्रोग्रामिंग भाषा में प्रोटोकॉल बफ़र मैसेज (प्रोटोकॉल बफ़र की "बुनियादी इकाई") को लागू करने के लिए, proto_library नियम को जोड़ा जाना चाहिए. इससे, एक ही भाषा में दो टारगेट एक ही प्रोटोकॉल बफ़र पर निर्भर होने पर, वह सिर्फ़ एक बार बन जाता है.

कॉन्फ़िगर किए गए टारगेट की तरह ही, उन्हें Skyframe में SkyValue के रूप में दिखाया जाता है. उन्हें बनाने का तरीका, कॉन्फ़िगर किए गए टारगेट को बनाने के तरीके से काफ़ी मिलता-जुलता होता है: इनमें ConfiguredAspectFactory नाम की फ़ैक्ट्री क्लास होती है, जिसके पास RuleContext का ऐक्सेस होता है. हालांकि, कॉन्फ़िगर किए गए टारगेट फ़ैक्ट्री के मुकाबले, यह कॉन्फ़िगर किए गए टारगेट फ़ैक्ट्री के बारे में भी जानता है.

Attribute.Builder.aspects() फ़ंक्शन का इस्तेमाल करके, हर एट्रिब्यूट के लिए डिपेंडेंसी ग्राफ़ में बताए गए पहलुओं का सेट तय किया जाता है. इस प्रोसेस में कुछ ऐसी क्लास हैं जिनका नाम गलत है:

  1. AspectClass आसपेक्ट को लागू करता है. यह Java (इस मामले में, यह एक सब-क्लास है) या Starlark (इस मामले में, StarlarkAspectClass का उदाहरण है). यह RuleConfiguredTargetFactory के जैसा है.
  2. AspectDefinition, आसपेक्ट की परिभाषा है; इसमें इसके लिए ज़रूरी प्रोवाइडर शामिल होते हैं. साथ ही, इसमें इसे लागू करने की जानकारी शामिल होती है, जैसे कि सही AspectClass इंस्टेंस. यह RuleClass के जैसा है.
  3. AspectParameters, उस पहलू को पैरामीटर में जोड़ने का तरीका है जिसे डिपेंडेंसी ग्राफ़ में दिखाया जाता है. फ़िलहाल, यह स्ट्रिंग मैप के लिए एक स्ट्रिंग है. यह उपयोगी क्यों है, इसका एक अच्छा उदाहरण प्रोटोकॉल बफ़र है: अगर किसी भाषा में एक से ज़्यादा एपीआई हैं, तो डिपेंडेंसी ग्राफ़ में यह जानकारी दिखेगी कि प्रोटोकॉल बफ़र को किस एपीआई के लिए बनाया जाना चाहिए.
  4. Aspect वह सारा डेटा दिखाता है जो डिपेंडेंसी ग्राफ़ को प्रोसेस करने वाले किसी आसपेक्ट की गणना करने के लिए ज़रूरी होता है. इसमें आसपेक्ट क्लास, उसकी परिभाषा, और उसके पैरामीटर शामिल होते हैं.
  5. RuleAspect वह फ़ंक्शन है जो तय करता है कि किसी खास नियम को किन पहलुओं को लागू करना चाहिए. यह एक Rule -> Aspect फ़ंक्शन है.

थोड़ी-बहुत अनचाही जटिलता यह है कि पहलू दूसरे पहलुओं से जुड़ सकते हैं; उदाहरण के लिए, किसी Java IDE के लिए क्लासपाथ को इकट्ठा करने वाला एक पहलू शायद क्लासपाथ पर सभी .जार फ़ाइलों के बारे में जानना चाहेगा, लेकिन उनमें से कुछ प्रोटोकॉल बफ़र हैं. ऐसी स्थिति में, IDE वाला आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) (proto_library नियम + Java प्रोटो आसपेक्ट) पेयर के साथ अटैच करना चाहेगा.

अलग-अलग पहलुओं के जटिल पहलुओं को, क्लास AspectCollection में कैप्चर किया गया है.

प्लैटफ़ॉर्म और टूलचेन

Bazel, मल्टी-प्लैटफ़ॉर्म बिल्ड के साथ काम करता है. इसका मतलब है कि उस बिल्ड में कई आर्किटेक्चर हो सकते हैं जहां बिल्ड ऐक्शन चलते हैं. साथ ही, जिस कोड को बनाया गया है उसके लिए एक से ज़्यादा आर्किटेक्चर का इस्तेमाल किया जा सकता है. Bazel Parlance में इन आर्किटेक्चर को प्लैटफ़ॉर्म के तौर पर जाना जाता है (पूरा दस्तावेज़ यहां दिया गया है)

किसी प्लैटफ़ॉर्म के बारे में, कंस्ट्रेंट सेटिंग (जैसे- "सीपीयू आर्किटेक्चर" का कॉन्सेप्ट) से कंस्ट्रेंट वैल्यू (जैसे कि कोई खास सीपीयू, जैसे कि x86_64) तक की-वैल्यू मैपिंग की मदद से बताया जाता है. हमारे पास @platforms रिपॉज़िटरी में, सबसे ज़्यादा इस्तेमाल किए जाने वाले कंस्ट्रेंट सेटिंग और वैल्यू का "डिक्शनरी" है.

टूलचेन का कॉन्सेप्ट यहां से पता चलता है कि बिल्ड किन प्लैटफ़ॉर्म पर चल रहा है और किन प्लैटफ़ॉर्म को टारगेट किया जा रहा है, इसके आधार पर अलग-अलग कंपाइलर का इस्तेमाल करना पड़ सकता है. उदाहरण के लिए, एक खास C++ टूलचेन किसी खास ओएस पर चल सकती है और कुछ अन्य ओएस को टारगेट कर सकती है. Bazel को C++ कंपाइलर का पता लगाना होगा.

ऐसा करने के लिए, टूलचेन को एक्ज़ीक्यूशन और उस टारगेट प्लैटफ़ॉर्म से जुड़ी पाबंदियों के साथ एनोटेट किया जाता है जिन पर वह काम करती है. ऐसा करने के लिए, टूलचेन की परिभाषा को दो हिस्सों में बांटा गया है:

  1. toolchain() नियम, जो टूलचेन के साथ काम करने वाले एक्ज़ीक्यूशन और टारगेट की सीमाओं के बारे में बताता है. साथ ही, यह बताता है कि वह टूलचेन किस तरह का (जैसे कि C++ या Java) है (जैसे, बाद वाला नियम toolchain_type() नियम के ज़रिए दिखाया जाता है)
  2. भाषा के हिसाब से बनाया गया नियम, जो असल टूलचेन की जानकारी देता है (जैसे कि cc_toolchain())

ऐसा इसलिए किया जाता है, क्योंकि हमें हर टूलचेन की सीमाओं को जानना होता है, ताकि टूलचेन रिज़ॉल्यूशन और किसी खास भाषा के हिसाब से बने *_toolchain() नियमों में उससे ज़्यादा जानकारी हो. इसलिए, उन्हें लोड होने में ज़्यादा समय लगता है.

एक्ज़ीक्यूशन प्लैटफ़ॉर्म को इनमें से किसी एक तरीके से बताया जाता है:

  1. MODULE.bazel फ़ाइल में, register_execution_platforms() फ़ंक्शन का इस्तेमाल करके
  2. कमांड लाइन पर --extra_execution_platforms कमांड लाइन विकल्प का इस्तेमाल करें

उपलब्ध एक्ज़ीक्यूशन प्लैटफ़ॉर्म के सेट की गणना RegisteredExecutionPlatformsFunction में की गई है .

कॉन्फ़िगर किए गए टारगेट के लिए, टारगेट प्लैटफ़ॉर्म PlatformOptions.computeTargetPlatform() से तय किया जाता है . यह प्लैटफ़ॉर्म की सूची है, क्योंकि हम कई टारगेट प्लैटफ़ॉर्म पर काम करना चाहते हैं, लेकिन अभी इसे लागू नहीं किया गया है.

कॉन्फ़िगर किए गए टारगेट के लिए इस्तेमाल किए जाने वाले टूलचेन के सेट को ToolchainResolutionFunction से तय किया जाता है. यह इसका काम करता है:

  • रजिस्टर किए गए टूलचेन का सेट (MODULE.bazel फ़ाइल और कॉन्फ़िगरेशन में)
  • मनचाहे तरीके से काम करना और टारगेट प्लैटफ़ॉर्म (कॉन्फ़िगरेशन में)
  • टूलचेन टाइप का एक सेट, जो कॉन्फ़िगर किए गए टारगेट (UnloadedToolchainContextKey) में) के लिए ज़रूरी है
  • UnloadedToolchainContextKey में, कॉन्फ़िगर किए गए टारगेट (exec_compatible_with एट्रिब्यूट) और कॉन्फ़िगरेशन (--experimental_add_exec_constraints_to_targets) के एक्ज़ीक्यूशन प्लैटफ़ॉर्म की सीमाओं का सेट

इसका नतीजा एक UnloadedToolchainContext है, जो मुख्य रूप से, टूलचेन टाइप (इसे ToolchainTypeInfo इंस्टेंस के तौर पर दिखाया जाता है) से चुने गए टूलचेन के लेबल तक ले जाने वाला मैप होता है. इसे "अनलोड किया गया" कहा जाता है, क्योंकि इसमें टूलटिप नहीं होते, सिर्फ़ उनके लेबल होते हैं.

इसके बाद, टूलचेन को ResolvedToolchainContext.load() का इस्तेमाल करके लोड किया जाता है और उसका इस्तेमाल, कॉन्फ़िगर किए गए उस टारगेट को लागू करने के लिए किया जाता है जिसने उनसे अनुरोध किया था.

हमारे पास एक लेगसी सिस्टम भी है, जो इस बात पर निर्भर करता है कि सिर्फ़ एक "होस्ट" कॉन्फ़िगरेशन और टारगेट कॉन्फ़िगरेशन हैं, जो अलग-अलग कॉन्फ़िगरेशन फ़्लैग से दिखाए जाते हैं, जैसे कि --cpu . हम धीरे-धीरे ऊपर बताए गए तरीके पर अपग्रेड कर रहे हैं. जिन मामलों में लोग लेगसी कॉन्फ़िगरेशन वैल्यू पर निर्भर रहते हैं उन्हें मैनेज करने के लिए, हमने प्लैटफ़ॉर्म मैपिंग की सुविधा लागू की है. इससे लेगसी फ़्लैग और नए प्लैटफ़ॉर्म से जुड़ी पाबंदियों के बीच अनुवाद किया जा सकेगा. उनका कोड PlatformMappingFunction में है और इसमें स्टारलार्क के अलावा, "छोटी भाषा" का इस्तेमाल किया गया है.

कंस्ट्रेंट

कभी-कभी कोई व्यक्ति किसी टारगेट को यह तय करना चाहता है कि वह सिर्फ़ कुछ प्लैटफ़ॉर्म के साथ काम करे. अफ़सोस की बात यह है कि Bazel के पास कई तरीके हैं:

  • नियम के हिसाब से पाबंदियां
  • environment_group() / environment()
  • प्लैटफ़ॉर्म कंस्ट्रेंट

नियम-आधारित पाबंदियों का इस्तेमाल, ज़्यादातर Google में Java के नियमों के लिए किया जाता है. हालांकि, वे अब काम नहीं कर रहे हैं और Bazel में उपलब्ध नहीं हैं. हालांकि, सोर्स कोड में इसके रेफ़रंस हो सकते हैं. इसे कंट्रोल करने वाले एट्रिब्यूट को constraints= कहा जाता है .

वातावरण_group() और वातावरण()

ये नियम एक लेगसी मैकेनिज़्म हैं और ज़्यादा इस्तेमाल नहीं किए जाते.

बिल्ड के सभी नियमों से यह तय किया जा सकता है कि उन्हें किस "एनवायरमेंट" के लिए बनाया जा सकता है. साथ ही, जहां "एनवायरमेंट", environment() नियम का इंस्टेंस है.

किसी नियम के लिए इस्तेमाल किए जा सकने वाले एनवायरमेंट कई तरह से तय किए जा सकते हैं:

  1. restricted_to= एट्रिब्यूट के ज़रिए. यह जानकारी देने का सबसे सीधा तरीका है. यह उन एनवायरमेंट के सटीक सेट की जानकारी देता है जिनका इस्तेमाल नियम इस ग्रुप के लिए करता है.
  2. compatible_with= एट्रिब्यूट के ज़रिए. इससे यह पता चलता है कि एनवायरमेंट में एक नियम लागू होगा और वे "स्टैंडर्ड" एनवायरमेंट के साथ भी काम करेंगे, जो डिफ़ॉल्ट रूप से काम करते हैं.
  3. पैकेज-लेवल एट्रिब्यूट के ज़रिए default_restricted_to= और default_compatible_with=.
  4. environment_group() नियमों में डिफ़ॉल्ट जानकारी के ज़रिए. हर एनवायरमेंट, थीम के हिसाब से मिलते-जुलते ऐप्लिकेशन के ग्रुप से जुड़ा होता है. जैसे, "सीपीयू आर्किटेक्चर", "JDK वर्शन" या "मोबाइल ऑपरेटिंग सिस्टम". एनवायरमेंट ग्रुप की परिभाषा में, यह भी शामिल होता है कि अगर restricted_to= / environment() एट्रिब्यूट में किसी एनवायरमेंट को नहीं बताया गया है, तो इनमें से कौनसे एनवायरमेंट को “डिफ़ॉल्ट” पर इस्तेमाल किया जा सकता है. जिस नियम में ऐसी कोई विशेषता नहीं होती है, वह सभी डिफ़ॉल्ट को इनहेरिट कर लेता है.
  5. नियम वर्ग डिफ़ॉल्ट के ज़रिए. यह दिए गए नियम क्लास के सभी इंस्टेंस के लिए, ग्लोबल डिफ़ॉल्ट को बदल देता है. उदाहरण के लिए, इसका इस्तेमाल हर इंस्टेंस के बिना सभी *_test नियमों को टेस्ट करने लायक बनाने के लिए किया जा सकता है. इसके लिए, इस सुविधा को साफ़ तौर पर बताने की ज़रूरत नहीं होती.

environment() को एक सामान्य नियम के तौर पर लागू किया गया है, जबकि environment_group() Target की सब-क्लास है, लेकिन Rule (EnvironmentGroup) की सब-क्लास नहीं है. साथ ही, ऐसा फ़ंक्शन जो Starlark (StarlarkLibrary.environmentGroup()) की ओर से डिफ़ॉल्ट रूप से उपलब्ध है, जो आखिर में एक अनाम टारगेट बनाता है. यह साइकलिक डिपेंडेंसी से बचने के लिए है, क्योंकि हर एनवायरमेंट को अपने एनवायरमेंट ग्रुप के बारे में बताना होता है. साथ ही, हर एनवायरमेंट ग्रुप को अपने डिफ़ॉल्ट एनवायरमेंट के बारे में बताना होता है.

--target_environment कमांड लाइन विकल्प की मदद से, बिल्ड को कुछ खास एनवायरमेंट तक सीमित किया जा सकता है.

कंस्ट्रेंट जांच को लागू करने की प्रोसेस RuleContextConstraintSemantics और TopLevelConstraintSemantics में है.

प्लैटफ़ॉर्म कंस्ट्रेंट

कोई टारगेट किन प्लैटफ़ॉर्म के साथ काम करता है, इस बारे में बताने का मौजूदा "आधिकारिक" तरीका, टूलचेन और प्लैटफ़ॉर्म के बारे में बताने के लिए इस्तेमाल की जाने वाली शर्तों का इस्तेमाल करना है. पुल अनुरोध में इसकी समीक्षा की जा रही है #10945.

किसे दिखे

अगर कई डेवलपर के साथ किसी बड़े कोडबेस पर काम किया जा रहा है (जैसे कि Google में), तो आपको इस बात का ध्यान रखना होगा कि दूसरे सभी डेवलपर को आपके कोड के आधार पर स्वेच्छा से काम करने से रोका जा सके. ऐसा न होने पर, Hyrum के कानून के मुताबिक लोग उन गतिविधियों पर भरोसा करेंगे जिन्हें आपने लागू करने की जानकारी माना है.

Bazel, किसको दिखे तरीके से इसकी सुविधा देता है: यह बताया जा सकता है कि किसी खास टारगेट पर सिर्फ़ विज़िबिलिटी एट्रिब्यूट का इस्तेमाल करके भरोसा किया जा सकता है. यह एट्रिब्यूट थोड़ा खास है, क्योंकि इसमें लेबल की सूची होती है. हालांकि, ये लेबल किसी खास टारगेट के लिए पॉइंटर के बजाय, पैकेज के नाम पर पैटर्न को कोड में बदल सकते हैं. (हां, यह डिज़ाइन की गड़बड़ी है.)

इसे इन जगहों पर लागू किया गया है:

  • RuleVisibility इंटरफ़ेस, प्रॉडक्ट दिखने की जानकारी के बारे में बताता है. यह एक स्थायी (पूरी तरह से सार्वजनिक या पूरी तरह से निजी) या लेबल की सूची हो सकती है.
  • लेबल, पैकेज ग्रुप (पैकेज की पहले से तय की गई सूची) से सीधे पैकेज (//pkg:__pkg__) या पैकेज के सबट्री (//pkg:__subpackages__) का हवाला दे सकते हैं. यह कमांड लाइन सिंटैक्स से अलग होता है, जिसमें //pkg:* या //pkg/... का इस्तेमाल किया जाता है.
  • पैकेज समूह उनके अपने टारगेट (PackageGroup) और कॉन्फ़िगर किए गए टारगेट (PackageGroupConfiguredTarget) के तौर पर लागू किए जाते हैं. अगर हम चाहें, तो शायद इन्हें आसान नियमों से बदल सकते थे. उनका लॉजिक इन चीज़ों की मदद से लागू किया जाता है: PackageSpecification, जो किसी एक पैटर्न से जुड़ा होता है, जैसे कि //pkg/...; PackageGroupContents, जो एक package_group के packages एट्रिब्यूट से जुड़ा होता है; औरPackageSpecificationProvider, जो package_group और इसके ट्रांज़िटिव includes को एग्रीगेट करता है.
  • विज़िबिलिटी लेबल की सूचियों से डिपेंडेंसी में बदलाव, DependencyResolver.visitTargetVisibility और कुछ अन्य जगहों पर किया जाता है.
  • असली जांच CommonPrerequisiteValidator.validateDirectPrerequisiteVisibility() में की जाती है

नेस्ट किए गए सेट

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

  • बिल्ड में इस्तेमाल की जाने वाली C++ हेडर फ़ाइलें
  • वे ऑब्जेक्ट फ़ाइलें जो cc_library के ट्रांज़िशनिव क्लोज़िंग के बारे में बताती हैं
  • .जार फ़ाइलों का ऐसा सेट जिसे Java नियम के लिए क्लासपाथ पर होना चाहिए, ताकि उसे कंपाइल किया जा सके या चलाया जा सके
  • Python नियम के ट्रांज़िटिव क्लोज़िंग में Python फ़ाइलों का सेट

अगर हम, उदाहरण के लिए, List या Set का इस्तेमाल करके, यह काम आसानी से करते हैं, तो हम क्वाड्रेटिक मेमोरी का इस्तेमाल करेंगे: अगर N नियमों की एक चेन है और हर नियम एक फ़ाइल जोड़ता है, तो हमारे पास कलेक्शन के सदस्यों की संख्या 1+2+...+N होती.

इस समस्या से बचने के लिए, हमने NestedSet का सिद्धांत दिया है. यह एक डेटा स्ट्रक्चर है, जो अन्य NestedSet इंस्टेंस और अपने कुछ सदस्यों से मिलकर बना है. इससे सेट का डायरेक्ट असाइकलिक ग्राफ़ बनाया जाता है. इन्हें नहीं बदला जा सकता और इनके सदस्यों को बार-बार दोहराया जा सकता है. हम कई इटरेशन ऑर्डर (NestedSet.Order) के बारे में बताते हैं: preorder, पोस्टऑर्डर, टोपिकल (एक नोड हमेशा अपने पूर्वजों के बाद आता है) और "ध्यान न दें, लेकिन यह हर बार एक ही होना चाहिए".

Starlark में इसी डेटा स्ट्रक्चर को depset कहा जाता है.

आर्टफ़ैक्ट और कार्रवाइयां

असल बिल्ड में निर्देशों का एक ऐसा सेट होता है जिसे उपयोगकर्ता का मनचाहा आउटपुट बनाने के लिए चलाया जाता है. निर्देश, Action क्लास के इंस्टेंस के तौर पर दिखाए जाते हैं और फ़ाइलों को Artifact क्लास के इंस्टेंस के तौर पर दिखाया जाता है. ये दो-पक्षों वाले ग्राफ़ में व्यवस्थित होते हैं. ये डायरेक्ट, एक साइकल वाले ग्राफ़ होते हैं, जिसे "ऐक्शन ग्राफ़" कहते हैं.

आर्टफ़ैक्ट दो तरह के होते हैं: सोर्स आर्टफ़ैक्ट (एक ऐसा जो Bazel के लागू होने से पहले उपलब्ध होता है) और उससे मिले आर्टफ़ैक्ट (जिसका निर्माण करना ज़रूरी हो). बनाई गई आर्टफ़ैक्ट कई तरह की हो सकती हैं:

  1. **सामान्य आर्टफ़ैक्ट. **इनकी जांच, mtime की मदद से अपने चेकसम का हिसाब लगाकर, अप-टू-डेट जानकारी के लिए की जाती है. अगर फ़ाइल का समय नहीं बदला है, तो हम उसकी जांच नहीं करते.
  2. समाधान न किए गए सिमलिंक आर्टफ़ैक्ट. इन्हें Readinglink() कॉल करके अप-टू-डेट रखा जाता है. आम आर्टफ़ैक्ट के उलट, ये सिमलिंक खतरे में डाल सकते हैं. आम तौर पर तब इस्तेमाल किया जाता है जब कोई फिर कुछ फ़ाइलों को किसी तरह के संग्रह में पैक कर देता है.
  3. ट्री आर्टफ़ैक्ट. ये सिंगल फ़ाइल नहीं होती हैं, बल्कि डायरेक्ट्री ट्री होती हैं. उन्हें अप-टू-डेट रखने के लिए, फ़ाइल में मौजूद फ़ाइलों और उनके कॉन्टेंट की जांच की जाती है. इन्हें TreeArtifact के तौर पर दिखाया जाता है.
  4. कॉन्सटेंट मेटाडेटा आर्टफ़ैक्ट. इन आर्टफ़ैक्ट में किए गए बदलावों से, प्रोजेक्ट को फिर से बनाने की प्रोसेस शुरू नहीं होती. इसका इस्तेमाल खास तौर पर स्टैंप की जानकारी बनाने के लिए किया जाता है: मौजूदा समय बदलने की वजह से हम इसे फिर से नहीं बनाना चाहते.

सोर्स आर्टफ़ैक्ट, ट्री आर्टफ़ैक्ट या अनसुलझे सिमलिंक आर्टफ़ैक्ट न होने की कोई बुनियादी वजह नहीं है. हमने इसे अभी तक लागू नहीं किया है (हालांकि, हमें BUILD फ़ाइल में सोर्स डायरेक्ट्री का रेफ़रंस देना, Bazel के साथ गलत होने से जुड़ी काफ़ी पुरानी समस्याओं में से एक है. इसे लागू करने के लिए, हम BAZEL_TRACK_SOURCE_DIRECTORIES=1 JVM प्रॉपर्टी का इस्तेमाल करते हैं)

मशहूर Artifact बिचौलिए होते हैं. इन्हें Artifact इंस्टेंस से दिखाया जाता है, जो MiddlemanAction के आउटपुट होते हैं. इनका इस्तेमाल कुछ चीज़ों को खास बनाने के लिए किया जाता है:

  • एग्रीगेट करने वाले बिचौलियों का इस्तेमाल कलाकृतियों को एक साथ रखने के लिए किया जाता है. इसका मतलब यह है कि अगर कई कार्रवाइयां इनपुट के एक ही बड़े सेट का इस्तेमाल करती हैं, तो हमारे पास N*M डिपेंडेंसी एज नहीं होते हैं, सिर्फ़ N+M होते हैं (इन्हें नेस्ट किए गए सेट से बदला जा रहा है)
  • समय-निर्धारण डिपेंडेंसी मध्यस्थ यह पक्का करते हैं कि कोई कार्रवाई दूसरी कार्रवाई से पहले चलती रहे. इनका इस्तेमाल ज़्यादातर लिंटिंग के लिए किया जाता है. हालांकि, C++ कंपाइलेशन के लिए भी इनका इस्तेमाल किया जाता है. ज़्यादा जानकारी के लिए CcCompilationContext.createMiddleman() देखें
  • रनफ़ाइल बिचौलिए का इस्तेमाल यह पक्का करने के लिए किया जाता है कि रनफ़ाइल ट्री मौजूद हो. इस तरह, किसी को अलग से आउटपुट मेनिफ़ेस्ट और रनफ़ाइल ट्री से रेफ़र किए गए हर एक आर्टफ़ैक्ट पर निर्भर रहने की ज़रूरत नहीं पड़ती.

कार्रवाइयों को चलाए जाने वाले निर्देश, उसकी ज़रूरत के मुताबिक माहौल, और उससे मिलने वाले आउटपुट के सेट के तौर पर, सबसे अच्छी तरह समझा जाता है. ये चीज़ें किसी कार्रवाई के ब्यौरे के मुख्य कॉम्पोनेंट हैं:

  • जिस कमांड लाइन को चलाना है
  • जिन इनपुट आर्टफ़ैक्ट की ज़रूरत है
  • ऐसे एनवायरमेंट वैरिएबल जिन्हें सेट करना ज़रूरी है
  • वे एनोटेशन जो उस एनवायरमेंट (जैसे कि प्लैटफ़ॉर्म) के बारे में बताते हैं जिसे उसे चलाने की ज़रूरत होती है \

इसके अलावा कुछ और खास मामले भी होते हैं, जैसे कि ऐसी फ़ाइल लिखना जिसका कॉन्टेंट Bazel को जाना-पहचाना हो. वे AbstractAction की सब-क्लास हैं. ज़्यादातर कार्रवाइयों के लिए, SpawnAction या StarlarkAction इस्तेमाल किए जाते हैं (यह एक जैसा होता है, बेशक उनकी अलग-अलग क्लास नहीं होनी चाहिए). हालांकि, Java और C++ के ऐक्शन टाइप (JavaCompileAction, CppCompileAction, और CppLinkAction) हैं.

आखिर में हम सब कुछ SpawnAction में ले जाना चाहेंगे; JavaCompileAction बहुत करीब है, लेकिन .d फ़ाइल पार्स करने और स्कैन करने की सुविधा शामिल होने की वजह से C++ कुछ खास केस है.

ऐक्शन ग्राफ़ को ज़्यादातर स्काईफ़्रेम ग्राफ़ में "एम्बेड किया गया" दिखाया जाता है: सैद्धांतिक तौर पर, किसी कार्रवाई के चलने को ActionExecutionFunction के अनुरोध के रूप में दिखाया जाता है. ऐक्शन ग्राफ़ डिपेंडेंसी एज से स्काईफ़्रेम डिपेंडेंसी एज की मैपिंग के बारे में ActionExecutionFunction.getInputDeps() और Artifact.key() में बताया गया है. साथ ही, इसमें कुछ ऑप्टिमाइज़ेशन हैं, ताकि Skyframe के किनारों की संख्या कम रहे:

  • हासिल किए गए आर्टफ़ैक्ट का अपना SkyValue नहीं है. इसके बजाय, Artifact.getGeneratingActionKey() का इस्तेमाल उस कार्रवाई के लिए कुंजी का पता लगाने के लिए किया जाता है जो इसे जनरेट करती है
  • नेस्ट किए गए सेट की अपनी Skyframe कुंजी होती है.

शेयर की गई कार्रवाइयां

कुछ कार्रवाइयां, कॉन्फ़िगर किए गए कई टारगेट से जनरेट होती हैं; Starlark के नियम ज़्यादा सीमित होते हैं. ऐसा इसलिए होता है, क्योंकि उन्हें सिर्फ़ उनके कॉन्फ़िगरेशन और पैकेज के मुताबिक तय की गई डायरेक्ट्री में, हासिल की गई कार्रवाइयों को रखने की अनुमति होती है (लेकिन ऐसा भी है, तो एक ही पैकेज में मौजूद नियमों में टकराव हो सकता है). हालांकि, Java में लागू किए गए नियमों से मिले आर्टफ़ैक्ट को कहीं भी रखा जा सकता है.

इसे एक गलत सुविधा माना जाता है, लेकिन इससे छुटकारा पाना वाकई मुश्किल होता है. क्योंकि यह एक्ज़ीक्यूशन समय में काफ़ी बचत करता है, उदाहरण के लिए, जब किसी सोर्स फ़ाइल को किसी तरह प्रोसेस करने की ज़रूरत होती है और उस फ़ाइल का रेफ़रंस एक से ज़्यादा नियमों (हैंडवेव-हैंडवेव) के ज़रिए दिया जाता है. इसके लिए, कुछ रैम की लागत आती है: शेयर की गई कार्रवाई के हर इंस्टेंस को मेमोरी में अलग-अलग सेव करना ज़रूरी होता है.

अगर दो कार्रवाइयां एक ही आउटपुट फ़ाइल जनरेट करती हैं, तो वे बिलकुल एक जैसी होनी चाहिए: एक जैसे इनपुट, एक जैसे आउटपुट, और एक ही कमांड लाइन चलाएं. यह तुलना Actions.canBeShared() में लागू की गई है. साथ ही, हर कार्रवाई को देखकर, विश्लेषण और उसे लागू करने के चरणों के बीच इसकी पुष्टि की जाती है. इसे SkyframeActionExecutor.findAndStoreArtifactConflicts() में लागू किया जाता है. यह Bazel की उन कुछ जगहों में से एक है जहां बिल्ड को "सभी जगह" देखना ज़रूरी होता है.

प्रोग्राम चलाने का चरण

ऐसा तब होता है, जब Bazel असल में बिल्ड ऐक्शन चलाना शुरू करता है, जैसे कि आउटपुट जनरेट करने वाले कमांड.

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

अगला कदम, एक्ज़ीक्यूशन रूट बनाना है. Bazel के पास फ़ाइल सिस्टम (--package_path) में अलग-अलग जगहों से सोर्स पैकेज पढ़ने का विकल्प होता है. इसलिए, इसे फ़ुल सोर्स ट्री के साथ स्थानीय तौर पर लागू की गई कार्रवाइयां देनी होंगी. इसे SymlinkForest क्लास मैनेज करती है. साथ ही, यह विश्लेषण के दौरान इस्तेमाल किए गए हर टारगेट का ध्यान रखती है. साथ ही, एक ऐसी डायरेक्ट्री ट्री बनाती है जो हर पैकेज के साथ उसकी असल जगह से इस्तेमाल किए गए टारगेट के साथ सिमलिंक करती है. एक विकल्प यह है कि निर्देशों पर सही पाथ भेजा जाए (--package_path को ध्यान में रखकर). ऐसा करना सही नहीं है, क्योंकि:

  • जब किसी पैकेज को पैकेज पाथ की एंट्री से दूसरी जगह ले जाया जाता है, तो यह ऐक्शन कमांड लाइन बदल देता है. यह आम बात थी
  • अगर कोई कार्रवाई रिमोट तरीके से की जाती है, तो स्थानीय तौर पर की गई कार्रवाई से अलग
  • इसके लिए, जिस टूल का इस्तेमाल किया जा रहा है उसके लिए खास तौर पर कमांड लाइन ट्रांसफ़ॉर्मेशन की ज़रूरत होती है (Java क्लासपाथ और C++ में पाथ शामिल करने जैसे अलग-अलग विकल्पों पर विचार करें)
  • किसी कार्रवाई की कमांड लाइन बदलने से, ऐक्शन कैश एंट्री अमान्य हो जाती है
  • --package_path धीरे-धीरे काम कर रहा है और अपने-आप बंद हो रहा है

इसके बाद, बेज़ेल, ऐक्शन ग्राफ़ को एक्सप्लोर करना शुरू करता है. यह ग्राफ़, ऐक्शन ग्राफ़ और उनके इनपुट और आउटपुट आर्टफ़ैक्ट को मिलाकर बनाया गया दो-पक्षों वाला ग्राफ़ होता है. हर कार्रवाई की कार्रवाई को SkyValue क्लास ActionExecutionValue के इंस्टेंस से दिखाया जाता है.

कार्रवाई करना महंगा होता है, इसलिए हमारे पास कैश मेमोरी की कुछ लेयर होती हैं जिन्हें Skyframe के पीछे हिट किया जा सकता है:

  • ActionExecutionFunction के Skyframe को रीस्टार्ट करने पर कम डेटा खर्च करने पर, ActionExecutionFunction.stateMap में डेटा मौजूद होता है
  • लोकल ऐक्शन कैश मेमोरी में, फ़ाइल सिस्टम की स्थिति के बारे में डेटा होता है
  • रिमोट एक्ज़ीक्यूशन सिस्टम में, आम तौर पर अपनी कैश मेमोरी भी होती है

लोकल ऐक्शन कैश मेमोरी

यह कैश मेमोरी में एक और लेयर है, जो Skyframe के पीछे मौजूद रहता है. भले ही, किसी कार्रवाई को Skyframe में फिर से किया जाए, लेकिन यह लोकल ऐक्शन कैश में अब भी हिट हो सकती है. यह लोकल फ़ाइल सिस्टम की स्थिति को दिखाता है और इसे डिस्क पर क्रम से लगाया जाता है. इसका मतलब है कि जब कोई नया Bazel सर्वर स्टार्ट करता है, तो Skyframe ग्राफ़ खाली होने पर भी लोकल ऐक्शन कैश हिट मिल सकते हैं.

इस कैश मेमोरी में, ActionCacheChecker.getTokenIfNeedToExecute() तरीके का इस्तेमाल करके हिट की जांच की जाती है .

अपने नाम से अलग, यह किसी डिराइव्ड आर्टफ़ैक्ट के पाथ से उस ऐक्शन तक का मैप है जिससे उसे ट्रैक किया गया है. कार्रवाई का ब्यौरा इस तरह दिया गया है:

  1. इसके इनपुट और आउटपुट फ़ाइलों का सेट और उनके चेकसम
  2. इसकी "ऐक्शन कुंजी", आम तौर पर एक्ज़ीक्यूट होने वाली कमांड लाइन होती है. हालांकि, आम तौर पर यह हर वह चीज़ दिखाती है जो इनपुट फ़ाइलों के चेकसम से कैप्चर नहीं की जाती. जैसे, FileWriteAction के लिए, यह लिखे गए डेटा का चेकसम होता है

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

इनपुट डिस्कवरी और इनपुट की काट-छांट

कुछ कार्रवाइयां, सिर्फ़ इनपुट के सेट की मदद से मुश्किल होती हैं. किसी कार्रवाई के इनपुट के सेट में किए गए बदलाव दो तरह से होते हैं:

  • किसी कार्रवाई को लागू होने से पहले, नए इनपुट मिल सकते हैं या यह तय किया जा सकता है कि उसके कुछ इनपुट असल में ज़रूरी नहीं हैं. कैननिकल उदाहरण है C++. जहां इस बारे में सही अनुमान लगाना बेहतर है कि C++ फ़ाइल अपने ट्रांज़िटिव क्लोज़र से कौन-कौनसी हेडर फ़ाइलों का इस्तेमाल करती है, ताकि हम हर फ़ाइल को रिमोट एक्ज़ीक्यूटर को भेजने पर ध्यान न दें. इसलिए, हमारे पास हर हेडर फ़ाइल को "इनपुट" के तौर पर रजिस्टर न करने का विकल्प है. हालांकि, हमारे पास हर हेडर फ़ाइल को 'इनपुट' के तौर पर रजिस्टर न करें.#include
  • कार्रवाई से यह पता चल सकता है कि प्रोग्राम चलाने के दौरान, कुछ फ़ाइलों का इस्तेमाल नहीं किया गया था. C++ में, इसे ".d फ़ाइलें" कहा जाता है: कंपाइलर बताता है कि इस तथ्य के बाद कौनसी हेडर फ़ाइल इस्तेमाल की गई थी. साथ ही, मेक की तुलना में खराब बढ़ोतरी होने की शर्मिंदगी से बचने के लिए, Bazel इस तथ्य का इस्तेमाल करता है. इससे, शामिल किए गए स्कैनर के मुकाबले बेहतर अनुमान मिलता है, क्योंकि यह कंपाइलर पर निर्भर होता है.

इन्हें कार्रवाई पर लागू करने के तरीकों का इस्तेमाल करके लागू किया जाता है:

  1. Action.discoverInputs() को कॉल किया गया. इसमें ऐसी आर्टफ़ैक्ट का नेस्ट किया गया सेट दिखाया जाना चाहिए जो ज़रूरी है. ये सोर्स आर्टफ़ैक्ट होने चाहिए, ताकि ऐक्शन ग्राफ़ में ऐसी डिपेंडेंसी सीमाएं न हों जिनके लिए, कॉन्फ़िगर किए गए टारगेट ग्राफ़ में ऐसी कोई सीमा न हो.
  2. Action.execute() को कॉल करके यह कार्रवाई की जाती है.
  3. Action.execute() के आखिर में, इस कार्रवाई के तहत Action.updateInputs() को कॉल किया जा सकता है, ताकि बैजल को यह बताया जा सके कि इसके सभी इनपुट की ज़रूरत नहीं है. अगर इस्तेमाल किए गए इनपुट के 'इस्तेमाल नहीं किए गए' के तौर पर रिपोर्ट किया जाता है, तो इससे गलत इंक्रीमेंटल बिल्ड हो सकते हैं.

जब कार्रवाई कैश मेमोरी, कार्रवाई के नए इंस्टेंस (जैसे कि सर्वर के रीस्टार्ट होने के बाद बनाया जाता है) पर हिट दिखाती है, तो Bazel खुद updateInputs() को कॉल करता है. इससे इनपुट का सेट, इनपुट को खोजे जाने और पहले की गई काट-छांट का नतीजा दिखाता है.

Starlark की कार्रवाइयां इस सुविधा का इस्तेमाल करके, ctx.actions.run() के unused_inputs_list= आर्ग्युमेंट का इस्तेमाल करके, कुछ इनपुट इस्तेमाल नहीं किए गए के तौर पर एलान कर सकती हैं.

कार्रवाइयां करने के कई तरीके: Strategies/ActionContexts

कुछ कार्रवाइयां अलग-अलग तरीकों से की जा सकती हैं. उदाहरण के लिए, किसी कमांड लाइन को स्थानीय तौर पर, स्थानीय तौर पर, लेकिन कई तरह के सैंडबॉक्स में या रिमोट तरीके से चलाया जा सकता है. इसका प्रतीक ActionContext है, जिसे Strategy या Strategy कहते हैं, क्योंकि

किसी कार्रवाई के संदर्भ का लाइफ़ साइकल इस तरह है:

  1. प्लान लागू करने का चरण शुरू होने पर, BlazeModule इंस्टेंस से पूछा जाता है कि उनमें किस तरह की कार्रवाई है. ऐसा ExecutionTool के कंस्ट्रक्टर में होता है. कार्रवाई के कॉन्टेक्स्ट टाइप की पहचान Java Class इंस्टेंस से की जाती है, जो ActionContext के सब-इंटरफ़ेस को रेफ़र करता है. साथ ही, जो इंटरफ़ेस ऐक्शन कॉन्टेक्स्ट को लागू करना चाहिए.
  2. कार्रवाई के लिए उपलब्ध कॉन्टेक्स्ट में से, उसके कॉन्टेक्स्ट को चुना जाता है और उसे ActionExecutionContext और BlazeExecutor पर भेजा जाता है .
  3. कार्रवाइयां, ActionExecutionContext.getContext() और BlazeExecutor.getStrategy() का इस्तेमाल करके कॉन्टेक्स्ट का अनुरोध करती हैं (ऐसा करने का सिर्फ़ एक तरीका होना चाहिए...)

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

एक अहम रणनीति वह है जो स्थायी वर्कर प्रोसेस को लागू करती है (WorkerSpawnStrategy). आइडिया यह है कि कुछ टूल को शुरू होने में लंबा समय लगता है इसलिए, हर काम के लिए एक नए

अगर टूल बदलता है, तो वर्कर प्रोसेस को फिर से शुरू करना होगा. WorkerFilesHash का इस्तेमाल करके इस्तेमाल किए गए टूल के लिए चेकसम का हिसाब लगाकर, यह तय किया जाता है कि किसी वर्कर का फिर से इस्तेमाल किया जा सकता है या नहीं. यह इस बात पर निर्भर करता है कि कार्रवाई के कौनसे इनपुट, टूल के हिस्से के बारे में बताते हैं और कौनसे इनपुट के बारे में बताते हैं. इसे कार्रवाई का क्रिएटर तय करता है: Spawn.getToolFiles() और Spawn की रनफ़ाइल को टूल के हिस्से के तौर पर गिना जाता है.

रणनीतियों (या कार्रवाई के कॉन्टेक्स्ट!) के बारे में ज़्यादा जानकारी:

  • कार्रवाइयां करने से जुड़ी अलग-अलग रणनीतियों के बारे में जानकारी यहां दी गई है.
  • डाइनैमिक रणनीति के बारे में जानकारी, वह जगह है जहां हम स्थानीय और कहीं से भी कार्रवाई करते हैं, ताकि यह पता चल सके कि कौनसी कार्रवाई पहले खत्म हो चुकी है यहां.
  • स्थानीय तौर पर कार्रवाइयों को पूरा करने से जुड़ी बारीकियां यहां उपलब्ध हैं.

लोकल रिसोर्स मैनेजर

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

इसे ResourceManager क्लास में लागू किया जाता है: हर कार्रवाई की जानकारी, उन स्थानीय संसाधनों के अनुमान के साथ दी जानी चाहिए जो ResourceSet इंस्टेंस (सीपीयू और रैम) के तौर पर ज़रूरी हैं. इसके बाद, जब कार्रवाई के कॉन्टेक्स्ट में कोई ऐसा काम होता है जिसके लिए स्थानीय संसाधनों की ज़रूरत होती है, तो वह ResourceManager.acquireResources() पर कॉल करती है. यह रोक तब तक लगी रहती है, जब तक ज़रूरी संसाधन उपलब्ध नहीं हो जाते.

स्थानीय संसाधन प्रबंधन की ज़्यादा जानकारी यहां उपलब्ध है.

आउटपुट डायरेक्ट्री का स्ट्रक्चर

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

$EXECROOT/bazel-out/<configuration>/bin/<package>/<artifact name>

किसी खास कॉन्फ़िगरेशन से जुड़ी डायरेक्ट्री का नाम कैसे तय किया जाता है? दो अलग-अलग ज़रूरी प्रॉपर्टी हैं:

  1. अगर एक ही बिल्ड में दो कॉन्फ़िगरेशन हो सकते हैं, तो उनकी अलग-अलग डायरेक्ट्री होनी चाहिए, ताकि दोनों के पास एक ही ऐक्शन वाला अपना वर्शन हो. ऐसा न होने पर, अगर दो कॉन्फ़िगरेशन एक ही आउटपुट फ़ाइल बनाने वाली कार्रवाई की कमांड लाइन जैसे कि
  2. अगर दो कॉन्फ़िगरेशन "आम तौर पर" एक ही चीज़ को दिखाते हैं, तो उनका एक ही नाम होना चाहिए, ताकि कमांड लाइन मेल खाने पर किसी एक में की गई कार्रवाई का फिर से इस्तेमाल किया जा सके: उदाहरण के लिए, Java कंपाइलर के कमांड लाइन विकल्पों में बदलाव करने से, C++ कंपाइल ऐक्शन फिर से नहीं चलेगा.

अब तक, हमने इस समस्या को हल करने का कोई सिद्धांत नहीं दिया है. यह तरीका कॉन्फ़िगरेशन में काट-छांट करने की समस्या से मिलता-जुलता है. विकल्पों की एक लंबी चर्चा यहां उपलब्ध है. स्टारलार्क नियम (जिनके लेखक आम तौर पर बेज़ेल के बारे में नहीं जानते) और पहलुओं में सबसे ज़्यादा समस्या है, जो चीज़ों के स्पेस में एक और डाइमेंशन जोड़ देते हैं जो "एक ही" आउटपुट फ़ाइल बना सकता है.

मौजूदा तरीका यह है कि कॉन्फ़िगरेशन के लिए पाथ सेगमेंट <CPU>-<compilation mode> है. इसमें अलग-अलग सफ़िक्स जोड़े गए हैं, ताकि Java में लागू कॉन्फ़िगरेशन ट्रांज़िशन की वजह से कार्रवाई में टकराव न आए. इसके अलावा, Starlark कॉन्फ़िगरेशन ट्रांज़िशन के सेट का एक चेकसम जोड़ा जाता है, ताकि उपयोगकर्ता कार्रवाई करने में विरोध पैदा न करें. यह सच से बहुत दूर है. इसे OutputDirectories.buildMnemonic() में लागू किया गया है. साथ ही, यह आउटपुट डायरेक्ट्री के नाम में हर कॉन्फ़िगरेशन फ़्रैगमेंट का अपना हिस्सा जोड़ने पर निर्भर करता है.

जांच

Bazel के पास, टेस्ट करने से जुड़ी पूरी सहायता है. यह इन सुविधाओं का इस्तेमाल करता है:

  • रिमोट तरीके से टेस्ट चलाना (अगर रिमोट एक्ज़ीक्यूशन बैकएंड उपलब्ध है)
  • साथ-साथ कई बार टेस्ट चलाना (समय का डेटा इकट्ठा करने या डीफ़्लैक करने के लिए)
  • शार्डिंग टेस्ट (रफ़्तार के लिए, कई प्रोसेस पर एक ही टेस्ट में टेस्ट केस को अलग करना)
  • अस्थिर परीक्षण फिर से चलाना
  • टेस्ट को टेस्ट सुइट में ग्रुप करना

टेस्ट, सामान्य तौर पर कॉन्फ़िगर किए गए ऐसे टारगेट होते हैं जिनमें TestProvider होता है. इससे पता चलता है कि टेस्ट को कैसे चलाया जाना चाहिए:

  • वे आर्टफ़ैक्ट जिनकी बिल्डिंग के नतीजे में टेस्ट चल रहा है. यह "कैश मेमोरी की स्थिति" वाली फ़ाइल है, जिसमें क्रम से वाला TestResultData मैसेज है
  • जांच कितनी बार की जानी चाहिए
  • उन शार्ड की संख्या जिनमें टेस्ट को बांटा जाना चाहिए
  • टेस्ट को चलाने के तरीके के बारे में कुछ पैरामीटर (जैसे कि टेस्ट का टाइम आउट)

तय किया जा रहा है कि कौनसे टेस्ट चलाने हैं

यह तय करना एक जटिल प्रक्रिया है कि कौनसे टेस्ट चलाए जा सकते हैं.

पहला, टारगेट पैटर्न पार्स करने के दौरान, टेस्ट सुइट को बार-बार बड़ा किया जाता है. एक्सपैंशन TestsForTargetPatternFunction में लागू किया गया है. कुछ हद तक हैरानी की बात यह है कि अगर कोई टेस्ट सुइट यह एलान करता है कि कोई टेस्ट नहीं है, तो इसका मतलब है उसके पैकेज में मौजूद हर टेस्ट को. सुइट के नियमों की जांच करने के लिए, $implicit_tests नाम का इंप्लिसिट एट्रिब्यूट जोड़कर, Package.beforeBuild() में इसे लागू किया जाता है.

इसके बाद, टेस्ट को कमांड लाइन के विकल्पों के हिसाब से साइज़, टैग, टाइम आउट, और भाषा के लिए फ़िल्टर किया जाता है. इसे TestFilter में लागू किया जाता है और टारगेट पार्स करने के दौरान इसे TargetPatternPhaseFunction.determineTests() से कॉल किया जाता है. इसके बाद, नतीजे को TargetPatternPhaseValue.getTestsToRunLabels() में रखा जाता है. फ़िल्टर की जा सकने वाली नियम एट्रिब्यूट को कॉन्फ़िगर न करने की वजह यह है कि यह विश्लेषण के चरण से पहले होता है. इसलिए, कॉन्फ़िगरेशन उपलब्ध नहीं है.

इसके बाद, इसे BuildView.createResult() में और प्रोसेस किया जाता है: जिन टारगेट का विश्लेषण नहीं हो सका उन्हें फ़िल्टर करके बाहर कर दिया जाता है. साथ ही, उन टेस्ट को खास और सामान्य टेस्ट में बांट दिया जाता है. इसके बाद, इसे AnalysisResult में डाला जाता है. इससे ExecutionTool को पता चलता है कि कौनसे टेस्ट चलाने हैं.

इस प्रोसेस में पारदर्शिता लाने के लिए, tests() क्वेरी ऑपरेटर (TestsFunction में लागू किया गया) यह बताने के लिए उपलब्ध है कि कमांड लाइन पर किसी खास टारगेट के बारे में बताने पर कौनसे टेस्ट चलाए जाते हैं. अफ़सोस की बात यह है कि इसे फिर से लागू किया गया है, इसलिए हो सकता है कि यह ऊपर दिए गए विकल्पों से अलग हो जाए.

चल रहे परीक्षण

कैश मेमोरी की स्थिति बताने वाले आर्टफ़ैक्ट का अनुरोध करके, जांच की जाती है. इसके बाद, TestRunnerAction लागू होता है, जो आखिर में --test_strategy कमांड लाइन विकल्प से चुने गए TestActionContext को कॉल करता है, जो अनुरोध किए गए तरीके से टेस्ट करता है.

टेस्ट एक व्यापक प्रोटोकॉल के अनुसार किए जाते हैं, जो एनवायरमेंट वैरिएबल का इस्तेमाल करके यह बताता है कि टेस्ट से क्या उम्मीद की जा रही है. Bazel को टेस्ट से क्या उम्मीद है और Bazel से क्या उम्मीद हो सकती है, इस बारे में ज़्यादा जानकारी यहां उपलब्ध है. अगर सबसे आसान है, तो 0 का एग्ज़िट कोड आने का मतलब है कि कोई और चीज़, सफल नहीं हुई.

कैश की स्थिति वाली फ़ाइल के अलावा, हर जांच प्रक्रिया से कई दूसरी फ़ाइलें भी निकलती हैं. उन्हें "टेस्ट लॉग डायरेक्ट्री" में रखा जाता है. यह टारगेट कॉन्फ़िगरेशन की आउटपुट डायरेक्ट्री की testlogs सबडायरेक्ट्री है:

  • test.xml, JUnit-स्टाइल की एक्सएमएल फ़ाइल, जिसमें टेस्ट शार्ड के अलग-अलग टेस्ट केस की जानकारी होती है
  • test.log, टेस्ट का कंसोल आउटपुट stdout और stderr को अलग नहीं किया गया है.
  • test.outputs, "अघोषित आउटपुट डायरेक्ट्री"; इसका इस्तेमाल उन टेस्ट में किया जाता है जो टर्मिनल पर प्रिंट की जाने वाली फ़ाइलों के साथ-साथ फ़ाइलें भी आउटपुट करना चाहते हैं.

टेस्ट निष्पादन के दौरान नियमित टारगेट बनाने के दौरान दो चीज़ें हो सकती हैं: एक्सक्लूज़िव टेस्ट एक्ज़ीक्यूशन और आउटपुट स्ट्रीमिंग.

कुछ टेस्ट को खास मोड में चलाया जाना ज़रूरी है. उदाहरण के लिए, अन्य टेस्ट के साथ नहीं. इसे या तो जांच के नियम में tags=["exclusive"] जोड़कर या --test_strategy=exclusive के साथ टेस्ट करके पाया जा सकता है . हर खास टेस्ट को अलग से Skyframe को शुरू किया जाता है. साथ ही, "मुख्य" बिल्ड के बाद टेस्ट को लागू करने का अनुरोध किया जाता है. इसे SkyframeExecutor.runExclusiveTest() में लागू किया गया है.

सामान्य कार्रवाइयों के उलट, कार्रवाई पूरी होने पर टर्मिनल आउटपुट को छोड़ दिया जाता है. ऐसे में, उपयोगकर्ता, टेस्ट के आउटपुट को स्ट्रीम करने का अनुरोध कर सकता है, ताकि उसे लंबे समय तक चलने वाले टेस्ट की प्रोग्रेस के बारे में सूचना मिल सके. यह --test_output=streamed कमांड लाइन विकल्प से बताया जाता है. साथ ही, इसमें खास टेस्ट एक्ज़ीक्यूट होता है, ताकि अलग-अलग टेस्ट के आउटपुट बीच में न मिलें.

इसे नाम वाली StreamedTestOutput क्लास में लागू किया जाता है. यह टेस्ट की test.log फ़ाइल में बदलाव करने के लिए पोल कराया जाता है. साथ ही, नए बाइट को टर्मिनल में डंप किया जाता है, जहां Bazel का नियम होता है.

एक्ज़ीक्यूट किए गए टेस्ट के नतीजे, इवेंट बस में अलग-अलग इवेंट (जैसे कि TestAttempt, TestResult या TestingCompleteEvent) को देखते हुए उपलब्ध होते हैं. उन्हें बिल्ड इवेंट प्रोटोकॉल में डाल दिया जाता है और AggregatingTestListener से कंसोल में भेजा जाता है.

कवरेज कलेक्शन

कवरेज को फ़ाइलों में एलसीओवी फ़ॉर्मैट में टेस्ट करके रिपोर्ट किया जाता है bazel-testlogs/$PACKAGE/$TARGET/coverage.dat .

कवरेज इकट्ठा करने के लिए, हर टेस्ट एक्ज़ीक्यूशन को collect_coverage.sh नाम की स्क्रिप्ट में रैप किया जाता है .

यह स्क्रिप्ट, कवरेज कलेक्शन को चालू करने के लिए टेस्ट एनवायरमेंट सेट अप करती है. साथ ही, यह तय करती है कि कवरेज रनटाइम से कवरेज फ़ाइलें कहां लिखी जाएं. इसके बाद, वह जांच करता है. एक टेस्ट अपने-आप कई सबप्रोसेस चला सकता है और उसमें कई अलग-अलग प्रोग्रामिंग भाषाओं में लिखे गए हिस्से (अलग कवरेज कलेक्शन रनटाइम के साथ) शामिल हो सकते हैं. ज़रूरी होने पर, रैपर स्क्रिप्ट मिलने वाली फ़ाइलों को LCOV फ़ॉर्मैट में बदलने और उन्हें एक फ़ाइल में मर्ज करने के लिए ज़िम्मेदार होती है.

collect_coverage.sh का इंटरपोज़िशन, टेस्ट की रणनीतियों के हिसाब से किया जाता है. इसके लिए, टेस्ट के इनपुट में collect_coverage.sh का होना ज़रूरी है. इसके लिए, इंप्लिसिट एट्रिब्यूट :coverage_support का इस्तेमाल किया जाता है, जिसका समाधान कॉन्फ़िगरेशन फ़्लैग की वैल्यू --coverage_support से किया जाता है (TestConfiguration.TestOptions.coverageSupport देखें)

कुछ भाषाएं ऑफ़लाइन इंस्ट्रुमेंटेशन का इस्तेमाल करती हैं. इसका मतलब है कि कवरेज इंस्ट्रुमेंट कंपाइलेशन के समय जोड़ा जाता है, जैसे कि C++ और कुछ भाषाएं ऑनलाइन इंस्ट्रुमेंटेशन पर जोड़ी जाती हैं. इसका मतलब है कि कवरेज इंस्ट्रुमेंटेशन को प्रोग्राम के काम करने के समय पर जोड़ा जाता है.

दूसरा मुख्य सिद्धांत है बेसलाइन कवरेज. यह लाइब्रेरी, बाइनरी या टेस्ट का कवरेज है, अगर इसमें कोई कोड नहीं चलाया गया. इससे समस्या यह हल हो जाती है कि अगर आपको किसी बाइनरी के लिए टेस्ट कवरेज का आकलन करना है, तो सभी जांचों के कवरेज को मर्ज करना काफ़ी नहीं है. ऐसा इसलिए, क्योंकि हो सकता है कि बाइनरी में ऐसा कोड हो जो किसी टेस्ट से लिंक न हो. इसलिए, हम हर उस बाइनरी के लिए एक कवरेज फ़ाइल उत्सर्जित करते हैं जिसमें सिर्फ़ वे फ़ाइलें होती हैं जिनके लिए हम कवरेज इकट्ठा करते हैं. टारगेट की बेसलाइन कवरेज फ़ाइल bazel-testlogs/$PACKAGE/$TARGET/baseline_coverage.dat है . अगर आपने Bazel को --nobuild_tests_only फ़्लैग भेजा, तो यह टेस्ट के अलावा बाइनरी और लाइब्रेरी के लिए भी जनरेट हो जाएगा.

बेसलाइन कवरेज फ़िलहाल काम नहीं कर रहा है.

हम हर नियम के लिए कवरेज कलेक्शन के लिए फ़ाइलों के दो ग्रुप को ट्रैक करते हैं: इंस्ट्रुमेंटेशन मेटाडेटा फ़ाइलों का सेट और इंस्ट्रुमेंटेशन मेटाडेटा फ़ाइलों का सेट.

इंस्ट्रुमेंटेड फ़ाइलों का सेट बस यही होता है, यानी कि वीडियो का एक सेट. ऑनलाइन कवरेज वाले रनटाइम के लिए, इसका इस्तेमाल रनटाइम में यह तय करने के लिए किया जा सकता है कि कौनसी फ़ाइल देनी है. इसका इस्तेमाल बेसलाइन कवरेज को लागू करने में भी किया जाता है.

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

कवरेज इकट्ठा किया जा रहा है या नहीं, यह जानकारी BuildConfiguration में सेव की जाती है. यह आसान है, क्योंकि यह इस बिट के आधार पर, टेस्ट ऐक्शन और ऐक्शन ग्राफ़ को बदलने का आसान तरीका है. हालांकि, इसका मतलब यह भी है कि अगर इस बिट को फ़्लिप किया जाता है, तो सभी टारगेट का फिर से विश्लेषण करना होगा. कुछ भाषाओं, जैसे कि C++ को कवरेज इकट्ठा करने के लिए अलग-अलग कंपाइलर के विकल्पों की ज़रूरत होती है. इससे यह समस्या कम हो जाती है. हालांकि, इसके बाद फिर से विश्लेषण करने की ज़रूरत होती है.

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

हम एक "कवरेज रिपोर्ट" भी जनरेट करते हैं. इस रिपोर्ट में, Bazel के अनुरोध में, हर जांच के लिए इकट्ठा किए गए कवरेज को मर्ज किया जाता है. इसे CoverageReportActionFactory मैनेज करता है और इसे BuildView.createResult() से कॉल किया जाता है . यह एक्ज़ीक्यूट किए जाने वाले पहले टेस्ट के :coverage_report_generator एट्रिब्यूट को देखकर, ज़रूरी टूल का ऐक्सेस ले लेता है.

क्वेरी इंजन

Bazel से बहुत कम भाषा का इस्तेमाल किया जाता है. इस तरह की क्वेरी की गई हैं:

  • bazel query का इस्तेमाल, टारगेट ग्राफ़ की जांच करने के लिए किया जाता है
  • bazel cquery का इस्तेमाल, कॉन्फ़िगर किए गए टारगेट ग्राफ़ की जांच करने के लिए किया जाता है
  • bazel aquery का इस्तेमाल, ऐक्शन ग्राफ़ की जांच करने के लिए किया जाता है

ये सभी मेट्रिक, AbstractBlazeQueryEnvironment को सब-क्लास करके लागू की जाती हैं. QueryFunction को सब-क्लास करके, क्वेरी के दूसरे फ़ंक्शन किए जा सकते हैं. क्वेरी के स्ट्रीम करने के नतीजों को कुछ डेटा स्ट्रक्चर में इकट्ठा करने के बजाय, QueryFunction को एक query2.engine.Callback भेजा जाता है. यह इसे उन नतीजों के लिए कॉल करता है जिन्हें वे दिखाना चाहते हैं.

किसी क्वेरी का नतीजा कई तरीकों से भेजा जा सकता है: लेबल, लेबल, और रूल क्लास, एक्सएमएल, प्रोटोबफ़ वगैरह. इन्हें OutputFormatter की सब-क्लास के तौर पर लागू किया जाता है.

कुछ क्वेरी आउटपुट फ़ॉर्मैट (प्रोटो, पक्के तौर पर) की एक ज़रूरी ज़रूरत यह है कि Bazel को वह _all _ जानकारी देनी होगी जो पैकेज लोड होने पर मिलती है, ताकि आउटपुट को अलग-अलग किया जा सके और यह तय किया जा सके कि किसी खास टारगेट में बदलाव हुआ है या नहीं. ऐसे में, एट्रिब्यूट की वैल्यू को क्रम से लगाने की ज़रूरत होती है. इस वजह से, ऐसे बहुत कम एट्रिब्यूट हैं जिनमें कोई भी एट्रिब्यूट, Starlark वैल्यू नहीं है. सामान्य समाधान है लेबल का इस्तेमाल करना और उस लेबल के साथ नियम में जटिल जानकारी जोड़ना. यह एक संतोषजनक समाधान नहीं है और इस आवश्यकता को पूरा करना बहुत अच्छा होगा.

मॉड्यूल सिस्टम

Bazel को बढ़ाने के लिए, उसमें मॉड्यूल जोड़े जा सकते हैं. हर मॉड्यूल के लिए यह ज़रूरी है कि BlazeModule को सब-क्लास किया जाए. यह नाम, Bazel के इतिहास की निशानी है, जब इसे Blaze कहा जाता था. इस मॉड्यूल में किसी निर्देश के लागू होने के दौरान, कई तरह के इवेंट के बारे में जानकारी मिलती है.

ज़्यादातर इनका इस्तेमाल "सामान्य" फ़ंक्शन के अलग-अलग वर्शन को लागू करने के लिए किया जाता है. Bazel के सिर्फ़ कुछ वर्शन (जैसे, वह वर्शन जिसे हम Google में इस्तेमाल करते हैं) की ज़रूरत होती है:

  • रिमोट एक्ज़ीक्यूशन सिस्टम के लिए इंटरफ़ेस
  • नए निर्देश

BlazeModule के लिए दिए जाने वाले एक्सटेंशन पॉइंट का सेट थोड़ा आसान है. इसका इस्तेमाल अच्छे डिज़ाइन सिद्धांतों के उदाहरण के तौर पर न करें.

इवेंट बस

BlazeModules का बाकी लोगों से बातचीत करने का मुख्य तरीका एक इवेंट बस (EventBus) है: हर बिल्ड के लिए एक नया इंस्टेंस बनाया जाता है. Bazel के अलग-अलग हिस्से इसमें इवेंट पोस्ट कर सकते हैं. साथ ही, मॉड्यूल लिसनर को उन इवेंट के लिए रजिस्टर कर सकते हैं जिनमें उनकी दिलचस्पी है. उदाहरण के लिए, नीचे दी गई चीज़ों को इवेंट के तौर पर दिखाया गया है:

  • बनाए जाने वाले बिल्ड टारगेट की सूची तय की गई है (TargetParsingCompleteEvent)
  • टॉप-लेवल के कॉन्फ़िगरेशन तय किए गए हैं (BuildConfigurationEvent)
  • टारगेट बनाया गया, सफलतापूर्वक या नहीं (TargetCompleteEvent)
  • टेस्ट किया गया (TestAttempt, TestSummary)

इनमें से कुछ इवेंट, Bazel के बाहर, बिल्ड इवेंट प्रोटोकॉल में दिखाए गए हैं. ये इवेंट BuildEvent हैं. इससे न सिर्फ़ BlazeModule, बल्कि Bazel प्रोसेस के बाहर मौजूद चीज़ों को भी बिल्ड का पता लगाने में मदद मिलती है. इन्हें प्रोटोकॉल मैसेज वाली फ़ाइल के तौर पर ऐक्सेस किया जा सकता है. इसके अलावा, इवेंट स्ट्रीम करने के लिए Bazel, सर्वर (जिसे इवेंट सेवा बनाएं) से कनेक्ट कर सकता है.

इसे build.lib.buildeventservice और build.lib.buildeventstream Java पैकेज में लागू किया जाता है.

डेटा स्टोर करने की बाहरी जगहें

वैसे तो Bazel को मूल रूप से मोनोरेपो (एक ऐसा पेड़ जिसमें वह सभी चीज़ें हैं जिसे बनाने की ज़रूरत है) में इस्तेमाल करने के लिए डिज़ाइन किया गया था, लेकिन ज़रूरी नहीं है कि Bazel ऐसी दुनिया में रहता हो. "बाहरी डेटा स्टोर करने की जगह" एक ऐब्स्ट्रैक्टेशन को कहते हैं जिसका इस्तेमाल इन दोनों दुनिया को इकट्ठा करने के लिए किया जाता है: ये कोड को दिखाते हैं जो बिल्ड के लिए ज़रूरी होता है, लेकिन वह मुख्य सोर्स ट्री में नहीं होता.

WORKSPACE फ़ाइल

डेटा स्टोर करने की बाहरी जगहों का सेट, WORKSPACE फ़ाइल को पार्स करके तय किया जाता है. उदाहरण के लिए, इस तरह का एलान:

    local_repository(name="foo", path="/foo/bar")

डेटा स्टोर करने की जगह में @foo नाम के नतीजे उपलब्ध हैं. इसमें यह समस्या यह होती है कि Starlark फ़ाइलों में रिपॉज़िटरी के नए नियम तय किए जा सकते हैं, जिनका इस्तेमाल नए Starlark कोड को लोड करने के लिए किया जा सकता है. इस कोड का इस्तेमाल, डेटा स्टोर करने के नए नियमों को तय करने के लिए किया जा सकता है वगैरह...

इस मामले में, WorkSPACE फ़ाइल (WorkspaceFileFunction में) को पार्स करने के बाद, उसे कई हिस्सों में बांटा जाता है. इन हिस्सों को load() स्टेटमेंट के ज़रिए दिखाया जाता है. चंक इंडेक्स को WorkspaceFileKey.getIndex() से दिखाया जाता है और यह WorkspaceFileFunction का तब तक आकलन करता है, जब तक X इंडेक्स X का मतलब Xवें load() स्टेटमेंट तक न हो.

डेटा स्टोर करने की जगहें फ़ेच की जा रही हैं

डेटा स्टोर करने की जगह का कोड Bazel में उपलब्ध होने से पहले, इसे fetched करना ज़रूरी है. इससे Bazel, $OUTPUT_BASE/external/<repository name> के तहत एक डायरेक्ट्री बना देगा.

डेटा स्टोर करने की जगह को फ़ेच करने की प्रोसेस, इन चरणों में होती है:

  1. PackageLookupFunction को पता चलता है कि उसे डेटा स्टोर करने की जगह की ज़रूरत है. साथ ही, वह SkyKey के तौर पर RepositoryName बनाता है, जो RepositoryLoaderFunction को शुरू करता है
  2. RepositoryLoaderFunction अनुरोध को RepositoryDelegatorFunction को फ़ॉरवर्ड कर देता है. इसकी कई वजहें हैं. कोड में कहा गया है कि Skyframe के रीस्टार्ट होने पर चीज़ों को फिर से डाउनलोड करने से बचाया जा सकता है. हालांकि, यह ज़रूरी नहीं है
  3. जब तक अनुरोध किया गया डेटा स्टोर नहीं मिलता, तब तक RepositoryDelegatorFunction
  4. सही RepositoryFunction मिला है जो रिपॉज़िटरी को फ़ेच करता है. यह रिपॉज़िटरी को Starlark पर लागू करता है या Java में लागू किए गए डेटा स्टोर करने की जगहों के लिए, हार्ड-कोड किया गया मैप होता है.

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

  1. डाउनलोड की गई फ़ाइलों के लिए एक कैश मेमोरी मौजूद है, जिसे उनके चेकसम (RepositoryCache) के ज़रिए कुंजी के तौर पर सेव किया जाता है. इसके लिए ज़रूरी है कि वर्कस्पेस फ़ाइल में चेकसम मौजूद हो, लेकिन यह हेमेटिकिटी के लिए अच्छा है. यह जानकारी, एक ही वर्कस्टेशन पर हर Bazel सर्वर इंस्टेंस के साथ शेयर की जाती है. भले ही, वे किसी भी वर्कस्पेस या आउटपुट बेस पर चल रहे हों.
  2. $OUTPUT_BASE/external के तहत हर रिपॉज़िटरी के लिए एक "मार्कर फ़ाइल" लिखी जाती है, जिसमें नियम का एक चेकसम होता है, जिसका इस्तेमाल उसे फ़ेच करने के लिए किया गया था. अगर Bazel सर्वर रीस्टार्ट होता है, लेकिन चेकसम नहीं बदलता है, तो उसे फिर से फ़ेच नहीं किया जाता है. इसे RepositoryDelegatorFunction.DigestWriter में लागू किया गया है .
  3. --distdir कमांड लाइन विकल्प, डाउनलोड किए जाने वाले आर्टफ़ैक्ट को देखने के लिए इस्तेमाल होने वाली दूसरी कैश मेमोरी तय करता है. यह एंटरप्राइज़ सेटिंग में काम आता है, जहाँ Bazel को इंटरनेट से कोई भी चीज़ नहीं लानी चाहिए. इसे DownloadManager ने लागू किया है .

डेटा स्टोर करने की जगह को डाउनलोड करने के बाद, उसमें मौजूद आर्टफ़ैक्ट को सोर्स आर्टफ़ैक्ट माना जाता है. इससे समस्या पैदा हो सकती है, क्योंकि आम तौर पर Bazel, सोर्स आर्टफ़ैक्ट पर stat() कॉल करके, उनकी अप-टू-डेट जानकारी की जांच करता है. जब डेटा को स्टोर करने की जगह की परिभाषा में बदलाव किया जाता है, तब इन आर्टफ़ैक्ट को भी अमान्य कर दिया जाता है. इसलिए, किसी बाहरी रिपॉज़िटरी में आर्टफ़ैक्ट के लिए, FileStateValue को अपने बाहरी रिपॉज़िटरी पर निर्भर होना चाहिए. इसे ExternalFilesHelper मैनेज करता है.

डेटा स्टोर करने की जगह की मैपिंग

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

इसलिए, Bazel, बाहरी रिपॉज़िटरी लेबल को फिर से मैप करने की अनुमति देता है, ताकि स्ट्रिंग @guava// एक बाइनरी के रिपॉज़िटरी (जैसे कि @guava1//) का Guava रिपॉज़िटरी (जैसे कि @guava1//) का रेफ़रंस दे सके. साथ ही, यह दूसरे की डेटा स्टोर करने की जगह (जैसे कि @guava2//) का हवाला भी दे सकता है.

इसके अलावा, हीरे को join के लिए भी इसका इस्तेमाल किया जा सकता है. अगर कोई रिपॉज़िटरी, @guava1// पर निर्भर करता है और दूसरी, @guava2// पर निर्भर करती है, तो रिपॉज़िटरी मैपिंग से, दोनों रिपॉज़िटरी को फिर से मैप करके, कैननिकल @guava// रिपॉज़िटरी का इस्तेमाल किया जा सकता है.

मैपिंग को WORKSPACE फ़ाइल में अलग-अलग रिपॉज़िटरी की परिभाषाओं के repo_mapping एट्रिब्यूट के तौर पर बताया गया है. इसके बाद, यह Skyframe में WorkspaceFileValue के सदस्य के तौर पर दिखता है. इसे यहां जोड़ा जाता है:

  • Package.Builder.repositoryMapping का इस्तेमाल करके पैकेज के नियमों के लेबल वैल्यू वाले एट्रिब्यूट को RuleClass.populateRuleAttributeValues() के ज़रिए बदला जाता है
  • Package.repositoryMapping का इस्तेमाल, विश्लेषण के चरण में किया जाता है (लोडिंग फ़ेज़ में पार्स नहीं हुई $(location) जैसी गड़बड़ियों को ठीक करने के लिए)
  • load() स्टेटमेंट में लेबल के समाधान के लिए BzlLoadFunction

JNI बिट

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

C++ कोड src/main/नेटिव में होता है और नेटिव तरीकों वाली Java क्लास ये हैं:

  • NativePosixFiles और NativePosixFileSystem
  • ProcessUtils
  • WindowsFileOperations और WindowsFileProcesses
  • com.google.devtools.build.lib.platform

कंसोल आउटपुट

कंसोल आउटपुट को निकालना आसान लगता है, लेकिन कई प्रोसेस (कभी-कभी रिमोट तरीके से) को पूरा करना, बढ़िया कैश मेमोरी, अच्छे और रंगीन टर्मिनल आउटपुट और लंबे समय तक चलने वाले सर्वर की इच्छा होना सामान्य बात नहीं है.

क्लाइंट से RPC कॉल आने के ठीक बाद, दो RpcOutputStream इंस्टेंस बनाए जाते हैं (stdout और stderr के लिए) जो उनमें प्रिंट किए गए डेटा को क्लाइंट में फ़ॉरवर्ड करते हैं. इसके बाद, इन्हें OutErr (stdout, stderr) पेयर में रैप किया जाता है. कंसोल पर प्रिंट करने के लिए सभी आइटम इन स्ट्रीम में होते हैं. इसके बाद, ये स्ट्रीम BlazeCommandDispatcher.execExclusively() को भेज दी जाती हैं.

आउटपुट, डिफ़ॉल्ट रूप से एएनएसआई एस्केप सीक्वेंस के साथ प्रिंट होता है. जब इनकी इच्छा न हो (--color=no), तो इन्हें AnsiStrippingOutputStream हटा देता है. इसके अलावा, System.out और System.err को इन आउटपुट स्ट्रीम पर रीडायरेक्ट किया जाता है. ऐसा इसलिए है, ताकि डीबग करने की जानकारी को System.err.println() का इस्तेमाल करके प्रिंट किया जा सके और यह अब भी क्लाइंट के टर्मिनल आउटपुट (जो सर्वर के आउटपुट से अलग हो) में मिल सके. इस बात का ध्यान रखा जाता है कि अगर किसी प्रोसेस से बाइनरी आउटपुट (जैसे bazel query --output=proto) तैयार होती है, तो कोई बदलाव नहीं किया जाएगा.

छोटे मैसेज (गड़बड़ियों, चेतावनियों वगैरह) की जानकारी EventHandler इंटरफ़ेस की मदद से दी जाती है. खास तौर पर, ये EventBus की किसी पोस्ट से अलग होती हैं (इससे भ्रम की स्थिति पैदा होती है). हर Event में एक EventKind (गड़बड़ी, चेतावनी, जानकारी, और कुछ अन्य) होता है. साथ ही, हो सकता है कि उनमें Location (सोर्स कोड में मौजूद वह जगह हो जिसकी वजह से इवेंट हुआ था).

EventHandler को लागू करने के कुछ तरीके, उन्हें मिले इवेंट सेव करते हैं. इसका इस्तेमाल, यूज़र इंटरफ़ेस (यूआई) को जानकारी फिर से चलाने के लिए किया जाता है. यह जानकारी, कैश मेमोरी में सेव कई तरह की प्रोसेस की वजह से होती है. उदाहरण के लिए, कैश मेमोरी में कॉन्फ़िगर किए गए टारगेट से मिलने वाली चेतावनियां.

कुछ EventHandler ऐसे इवेंट पोस्ट करने की अनुमति भी देते हैं जो इवेंट के बस में जाने का रास्ता ढूंढ लेते हैं (सामान्य Event वहां _not _appear करते हैं). ये ExtendedEventHandler को लागू करने के तरीके हैं. इनका मुख्य इस्तेमाल, कैश मेमोरी में सेव किए गए EventBus इवेंट को फिर से चलाना है. ये सभी EventBus इवेंट Postable लागू करते हैं, लेकिन यह ज़रूरी नहीं है कि EventBus पर पोस्ट की जाने वाली हर चीज़, इस इंटरफ़ेस को लागू करे. सिर्फ़ वे इवेंट जिन्हें ExtendedEventHandler की मदद से कैश मेमोरी में सेव किया गया हो (यह अच्छा होगा और ज़्यादातर काम करेंगे; हालांकि, इसे लागू नहीं किया गया है)

टर्मिनल आउटपुट ज़्यादातर UiEventHandler के ज़रिए उत्सर्जित होता है. यह Bazel के सभी फ़ैंसी आउटपुट फ़ॉर्मैटिंग और प्रोग्रेस रिपोर्टिंग के लिए ज़िम्मेदार है. इसमें दो इनपुट होते हैं:

  • इवेंट बस
  • इवेंट स्ट्रीम ने रिपोर्टर के ज़रिए इसमें शामिल किया

Reporter.getOutErr() के ज़रिए, कमांड एक्ज़ीक्यूट करने वाली मशीनों (उदाहरण के लिए, बाकी Bazel) को RPC स्ट्रीम से सीधे तौर पर कनेक्ट किया जा सकता है. यह कनेक्शन, इन स्ट्रीम को सीधे तौर पर ऐक्सेस करने की अनुमति देता है. इसका इस्तेमाल सिर्फ़ तब किया जाता है, जब किसी निर्देश को संभावित बाइनरी डेटा (जैसे कि bazel query) को बहुत ज़्यादा मात्रा में डंप करने की ज़रूरत होती है.

प्रोफ़ाइलिंग बेज़ेल

Bazel तेज़ है. Bazel भी धीमा है, क्योंकि बिल्ड तब तक बढ़ता रहता है, जब तक कि वह जितना छोटा न हो जाए. इसी वजह से, Bazel में एक प्रोफ़ाइलर शामिल है. इसका इस्तेमाल, प्रोफ़ाइल बनाने और Bazel को प्रोफ़ाइल बनाने में किया जा सकता है. इसे एक ऐसी क्लास में लागू किया गया है जिसका नाम Profiler है. यह डिफ़ॉल्ट रूप से चालू रहती है. हालांकि, यह सिर्फ़ बहुत छोटा डेटा रिकॉर्ड करती है, ताकि इसका ऊपरी हिस्सा मैनेज किया जा सके. कमांड लाइन --record_full_profiler_data हर वह चीज़ रिकॉर्ड करती है जो वह कर सकता है.

इससे Chrome प्रोफ़ाइलर फ़ॉर्मैट में एक प्रोफ़ाइल बनती है. यह Chrome पर सबसे अच्छी तरह से दिखती है. इसका डेटा मॉडल टास्क स्टैक की तरह होता है: कोई व्यक्ति टास्क शुरू कर सकता है और टास्क खत्म कर सकता है. साथ ही, उन्हें एक-दूसरे में ठीक से नेस्ट किया जाना चाहिए. हर Java थ्रेड को अपना अपना टास्क स्टैक मिलता है. TODO: यह कार्रवाइयों और लगातार पास रहने की स्टाइल के साथ कैसे काम करता है?

प्रोफ़ाइलर को क्रम से BlazeRuntime.initProfiler() और BlazeRuntime.afterCommand() में चालू और बंद किया जाता है. यह जितना हो सके उतने लंबे समय तक लाइव रहने की कोशिश करता है, ताकि हम सभी चीज़ों की प्रोफ़ाइल बना सकें. प्रोफ़ाइल में कुछ जोड़ने के लिए, Profiler.instance().profile() पर कॉल करें. यह Closeable के बारे में जानकारी देता है, जिसका जवाब टास्क के खत्म होने के बारे में बताता है. इसका इस्तेमाल, 'संसाधनों की मदद से करें' स्टेटमेंट के साथ सबसे बढ़िया होता है.

हम MemoryProfiler में, प्राइमरी मेमोरी प्रोफ़ाइलिंग भी करते हैं. यह हमेशा चालू रहता है और यह ज़्यादातर हीप के ज़्यादा से ज़्यादा साइज़ और जीसी व्यवहार को रिकॉर्ड करता है.

Bazel को टेस्ट करना

Bazel के दो मुख्य प्रकार हैं: एक जिसमें बैजल को "ब्लैक बॉक्स" के रूप में देखा जाता है और दूसरा, जो सिर्फ़ विश्लेषण के चरण में होता है. हम पहले वाले "इंटिग्रेशन टेस्ट" और बाद वाले "यूनिट टेस्ट" को कहते हैं, हालांकि वे इंटिग्रेशन टेस्ट की तरह होते हैं, जो कि कम इंटिग्रेट होते हैं. हमारे पास कुछ ऐसे वास्तविक यूनिट परीक्षण भी हैं, जहां उनकी ज़रूरत होती है.

इंटिग्रेशन टेस्ट दो तरह के होते हैं:

  1. यहां src/test/shell के तहत, बहुत विस्तार से बैश टेस्ट फ़्रेमवर्क का इस्तेमाल करके लागू किया गया है
  2. Java में लागू किए गए. इन्हें BuildIntegrationTestCase की सब-क्लास के तौर पर लागू किया गया है

BuildIntegrationTestCase, इंटिग्रेशन टेस्टिंग का पसंदीदा फ़्रेमवर्क है, क्योंकि यह टेस्टिंग की ज़्यादातर स्थितियों में अच्छी तरह से काम करता है. यह Java फ़्रेमवर्क है, इसलिए यह डीबग करने की क्षमता और कई सामान्य डेवलपमेंट टूल के साथ आसान इंटिग्रेशन देता है. बेज़ेल रिपॉज़िटरी में BuildIntegrationTestCase क्लास के कई उदाहरण हैं.

विश्लेषण के टेस्ट, BuildViewTestCase की सब-क्लास के तौर पर लागू किए जाते हैं. BUILD फ़ाइलों को लिखने के लिए, एक स्क्रैच फ़ाइल सिस्टम का इस्तेमाल किया जा सकता है. इसके बाद, हेल्पर तरीके, कॉन्फ़िगर किए गए टारगेट के लिए अनुरोध कर सकते हैं, कॉन्फ़िगरेशन बदल सकते हैं, और विश्लेषण के नतीजे के बारे में अलग-अलग बातें बता सकते हैं.